1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 1996 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * tabs [tabspec] [+mn] [-Ttype] 32 * set tabs (and margin, if +mn), for terminal type 33 */ 34 35 36 #include <stdio.h> 37 #include <signal.h> 38 #include <sys/types.h> 39 #include <stdlib.h> 40 #include <fcntl.h> 41 #include <sys/stat.h> 42 #include <curses.h> 43 #include <term.h> 44 #include <locale.h> 45 #include <unistd.h> 46 #include <string.h> 47 #include <ctype.h> 48 #include <limits.h> 49 #include <signal.h> 50 51 #define EQ(a, b) (strcmp(a, b) == 0) 52 /* max # columns used (needed for GSI) */ 53 #define NCOLS 256 54 #define NTABS 65 /* max # tabs +1 (to be set) */ 55 #define NTABSCL 21 /* max # tabs + 1 that will be cleared */ 56 #define ESC 033 57 #define CLEAR '2' 58 #define SET '1' 59 #define TAB '\t' 60 #define CR '\r' 61 #define NMG 0 /* no margin setting */ 62 #define GMG 1 /* DTC300s margin */ 63 #define TMG 2 /* TERMINET margin */ 64 #define DMG 3 /* DASI450 margin */ 65 #define FMG 4 /* TTY 43 margin */ 66 #define TRMG 5 /* Trendata 4000a */ 67 68 #define TCLRLN 0 /* long, repetitive, general tab clear */ 69 70 static char tsethp[] = {ESC, '1', 0}; /* (default) */ 71 static char tsetibm[] = {ESC, '0', 0}; /* ibm */ 72 static char tclrhp[] = {ESC, '3', CR, 0}; /* hp terminals */ 73 /* short sequence for many terminals */ 74 static char tclrsh[] = {ESC, CLEAR, CR, 0}; 75 static char tclrgs[] = {ESC, TAB, CR, 0}; /* short, for 300s */ 76 static char tclr40[] = {ESC, 'R', CR, 0}; /* TTY 40/2, 4424 */ 77 static char tclribm[] = {ESC, '1', CR, 0}; /* ibm */ 78 79 static struct ttab { 80 char *ttype; /* -Tttype */ 81 char *tclr; /* char sequence to clear tabs and return carriage */ 82 int tmaxtab; /* maximum allowed position */ 83 } *tt; 84 85 static struct ttab termtab[] = { 86 "", tclrsh, 132, 87 "1620-12", tclrsh, 158, 88 "1620-12-8", tclrsh, 158, 89 "1700-12", tclrsh, 132, 90 "1700-12-8", tclrsh, 158, 91 "300-12", TCLRLN, 158, 92 "300s-12", tclrgs, 158, 93 "4424", tclr40, 80, 94 "4000a", tclrsh, 132, 95 "4000a-12", tclrsh, 158, 96 "450-12", tclrsh, 158, 97 "450-12-8", tclrsh, 158, 98 "2631", tclrhp, 240, 99 "2631-c", tclrhp, 240, 100 "ibm", tclribm, 80, 101 0 102 }; 103 104 static int err; 105 static int tmarg; 106 static char settab[32], clear_tabs[32]; 107 108 static int maxtab; /* max tab for repetitive spec */ 109 static int margin; 110 static int margflg; /* >0 ==> +m option used, 0 ==> not */ 111 static char *terminal = ""; 112 static char *tabspec = "-8"; /* default tab specification */ 113 114 static struct termio ttyold; /* tty table */ 115 static int ttyisave; /* save for input modes */ 116 static int ttyosave; /* save for output modes */ 117 static int istty; /* 1 ==> is actual tty */ 118 119 static struct stat statbuf; 120 static char *devtty; 121 122 static void scantab(char *scan, int tabvect[NTABS], int level); 123 static void repetab(char *scan, int tabvect[NTABS]); 124 static void arbitab(char *scan, int tabvect[NTABS]); 125 static void filetab(char *scan, int tabvect[NTABS], int level); 126 static int getmarg(char *term); 127 static struct ttab *termadj(); 128 static void settabs(int tabvect[NTABS]); 129 static char *cleartabs(register char *p, char *qq); 130 static int getnum(char **scan1); 131 static void endup(); 132 static int stdtab(char option[], int tabvect[]); 133 static void usage(); 134 static int chk_codes(char *codes); 135 136 int 137 main(int argc, char **argv) 138 { 139 int tabvect[NTABS]; /* build tab list here */ 140 char *scan; /* scan pointer to next char */ 141 char operand[LINE_MAX]; 142 int option_end = 0; 143 144 (void) setlocale(LC_ALL, ""); 145 146 #if !defined(TEXT_DOMAIN) 147 #define TEXT_DOMAIN "SYS_TEST" 148 #endif 149 (void) textdomain(TEXT_DOMAIN); 150 151 (void) signal(SIGINT, endup); 152 if (ioctl(1, TCGETA, &ttyold) == 0) { 153 ttyisave = ttyold.c_iflag; 154 ttyosave = ttyold.c_oflag; 155 (void) fstat(1, &statbuf); 156 devtty = ttyname(1); 157 (void) chmod(devtty, 0000); /* nobody, not even us */ 158 istty++; 159 } 160 tabvect[0] = 0; /* mark as not yet filled in */ 161 while (--argc > 0) { 162 scan = *++argv; 163 if (*scan == '+') { 164 if (!option_end) { 165 if (*++scan == 'm') { 166 margflg++; 167 if (*++scan) 168 margin = getnum(&scan); 169 else 170 margin = 10; 171 } else { 172 (void) fprintf(stderr, gettext( 173 "tabs: %s: invalid tab spec\n"), scan-1); 174 usage(); 175 } 176 } else { 177 /* 178 * only n1[,n2,...] operand can follow 179 * end of options delimiter "--" 180 */ 181 (void) fprintf(stderr, gettext( 182 "tabs: %s: invalid tab stop operand\n"), scan); 183 usage(); 184 } 185 } else if (*scan == '-') { 186 if (!option_end) { 187 if (*(scan+1) == 'T') { 188 /* allow space or no space after -T */ 189 if (*(scan+2) == '\0') { 190 if (--argc > 0) 191 terminal = *++argv; 192 else 193 usage(); 194 } else 195 terminal = scan+2; 196 } else if (*(scan+1) == '-') 197 if (*(scan+2) == '\0') 198 option_end = 1; 199 else 200 tabspec = scan; /* --file */ 201 else if (strcmp(scan+1, "code") == 0) { 202 /* EMPTY */ 203 /* skip to next argument */ 204 } else if (chk_codes(scan+1) || 205 (isdigit(*(scan+1)) && *(scan+2) == '\0')) { 206 /* 207 * valid code or single digit decimal 208 * number 209 */ 210 tabspec = scan; 211 } else { 212 (void) fprintf(stderr, gettext( 213 "tabs: %s: invalid tab spec\n"), scan); 214 usage(); 215 } 216 } else { 217 /* 218 * only n1[,n2,...] operand can follow 219 * end of options delimiter "--" 220 */ 221 (void) fprintf(stderr, gettext( 222 "tabs: %s: invalid tab stop operand\n"), scan); 223 usage(); 224 } 225 } else { 226 /* 227 * Tab-stop values separated using either commas 228 * or blanks. If any number (except the first one) 229 * is preceded by a plus sign, it is taken as an 230 * increment to be added to the previous value. 231 */ 232 operand[0] = '\0'; 233 while (argc > 0) { 234 if (strrchr(*argv, '-') == (char *)NULL) { 235 (void) strcat(operand, *argv); 236 if (argc > 1) 237 (void) strcat(operand, ","); 238 --argc; 239 ++argv; 240 } else { 241 (void) fprintf(stderr, gettext( 242 "tabs: %s: tab stop values must be positive integers\n"), 243 *argv); 244 usage(); 245 } 246 } 247 tabspec = operand; /* save tab specification */ 248 } 249 } 250 if (*terminal == '\0') { 251 if ((terminal = getenv("TERM")) == (char *)NULL || 252 *terminal == '\0') { 253 /* 254 * Use tab setting and clearing sequences specified 255 * by the ANSI standard. 256 */ 257 terminal = "ansi+tabs"; 258 } 259 } 260 if (setupterm(terminal, 1, &err) == ERR) { 261 (void) fprintf(stderr, gettext( 262 "tabs: %s: terminfo file not found\n"), terminal); 263 usage(); 264 } else if (!tigetstr("hts")) { 265 (void) fprintf(stderr, gettext( 266 "tabs: cannot set tabs on terminal type %s\n"), terminal); 267 usage(); 268 } 269 if (err <= 0 || columns <= 0 || set_tab == 0) { 270 tt = termadj(); 271 if (strcmp(terminal, "ibm") == 0) 272 (void) strcpy(settab, tsetibm); 273 else 274 (void) strcpy(settab, tsethp); 275 (void) strcpy(clear_tabs, tt->tclr); 276 maxtab = tt->tmaxtab; 277 } else { 278 maxtab = columns; 279 (void) strcpy(settab, set_tab); 280 (void) strcpy(clear_tabs, clear_all_tabs); 281 } 282 scantab(tabspec, tabvect, 0); 283 if (!tabvect[0]) 284 repetab("8", tabvect); 285 settabs(tabvect); 286 endup(); 287 return (0); 288 } 289 290 /* 291 * return 1 if code option is valid, otherwise return 0 292 */ 293 int 294 chk_codes(char *code) 295 { 296 if (*(code+1) == '\0' && (*code == 'a' || *code == 'c' || 297 *code == 'f' || *code == 'p' || *code == 's' || *code == 'u')) 298 return (1); 299 else if (*(code+1) == '2' && *(code+2) == '\0' && 300 (*code == 'a' || *code == 'c')) 301 return (1); 302 else if (*code == 'c' && *(code+1) == '3' && *(code+2) == '\0') 303 return (1); 304 return (0); 305 } 306 307 /* scantab: scan 1 tabspec & return tab list for it */ 308 void 309 scantab(char *scan, int tabvect[NTABS], int level) 310 { 311 char c; 312 if (*scan == '-') { 313 if ((c = *++scan) == '-') 314 filetab(++scan, tabvect, level); 315 else if (c >= '0' && c <= '9') 316 repetab(scan, tabvect); 317 else if (stdtab(scan, tabvect)) { 318 endup(); 319 (void) fprintf(stderr, gettext( 320 "tabs: %s: unknown tab code\n"), scan); 321 usage(); 322 } 323 } else { 324 arbitab(scan, tabvect); 325 } 326 } 327 328 /* repetab: scan and set repetitve tabs, 1+n, 1+2*n, etc */ 329 330 void 331 repetab(char *scan, int tabvect[NTABS]) 332 { 333 int incr, i, tabn; 334 int limit; 335 incr = getnum(&scan); 336 tabn = 1; 337 limit = (maxtab-1)/(incr?incr:1)-1; /* # last actual tab */ 338 if (limit > NTABS-2) 339 limit = NTABS-2; 340 for (i = 0; i <= limit; i++) 341 tabvect[i] = tabn += incr; 342 tabvect[i] = 0; 343 } 344 345 /* arbitab: handle list of arbitrary tabs */ 346 347 void 348 arbitab(char *scan, int tabvect[NTABS]) 349 { 350 char *scan_save; 351 int i, t, last; 352 353 scan_save = scan; 354 last = 0; 355 for (i = 0; i < NTABS-1; ) { 356 if (*scan == '+') { 357 scan++; /* +n ==> increment, not absolute */ 358 if (t = getnum(&scan)) 359 tabvect[i++] = last += t; 360 else { 361 endup(); 362 (void) fprintf(stderr, gettext( 363 "tabs: %s: invalid increment\n"), scan_save); 364 usage(); 365 } 366 } else { 367 if ((t = getnum(&scan)) > last) 368 tabvect[i++] = last = t; 369 else { 370 endup(); 371 (void) fprintf(stderr, gettext( 372 "tabs: %s: invalid tab stop\n"), scan_save); 373 usage(); 374 } 375 } 376 if (*scan++ != ',') break; 377 } 378 if (last > NCOLS) { 379 endup(); 380 (void) fprintf(stderr, gettext( 381 "tabs: %s: last tab stop would be set at a column greater than %d\n"), 382 scan_save, NCOLS); 383 usage(); 384 } 385 tabvect[i] = 0; 386 } 387 388 /* filetab: copy tabspec from existing file */ 389 #define CARDSIZ 132 390 391 void 392 filetab(char *scan, int tabvect[NTABS], int level) 393 { 394 int length, i; 395 char c; 396 int fildes; 397 char card[CARDSIZ]; /* buffer area for 1st card in file */ 398 char state, found; 399 char *temp; 400 if (level) { 401 endup(); 402 (void) fprintf(stderr, gettext( 403 "tabs: %s points to another file: invalid file indirection\n"), 404 scan); 405 exit(1); 406 } 407 if ((fildes = open(scan, O_RDONLY)) < 0) { 408 endup(); 409 (void) fprintf(stderr, gettext("tabs: %s: "), scan); 410 perror(""); 411 exit(1); 412 } 413 length = read(fildes, card, CARDSIZ); 414 (void) close(fildes); 415 found = state = 0; 416 scan = 0; 417 for (i = 0; i < length && (c = card[i]) != '\n'; i++) { 418 switch (state) { 419 case 0: 420 state = (c == '<'); break; 421 case 1: 422 state = (c == ':')?2:0; break; 423 case 2: 424 if (c == 't') 425 state = 3; 426 else if (c == ':') 427 state = 6; 428 else if (c != ' ') 429 state = 5; 430 break; 431 case 3: 432 if (c == ' ') 433 state = 2; 434 else { 435 scan = &card[i]; 436 state = 4; 437 } 438 break; 439 case 4: 440 if (c == ' ') { 441 card[i] = '\0'; 442 state = 5; 443 } else if (c == ':') { 444 card[i] = '\0'; 445 state = 6; 446 } 447 break; 448 case 5: 449 if (c == ' ') 450 state = 2; 451 else if (c == ':') 452 state = 6; 453 break; 454 case 6: 455 if (c == '>') { 456 found = 1; 457 goto done; 458 } else state = 5; 459 break; 460 } 461 } 462 done: 463 if (found && scan != 0) { 464 scantab(scan, tabvect, 1); 465 temp = scan; 466 while (*++temp) 467 ; 468 *temp = '\n'; 469 } 470 else 471 scantab("-8", tabvect, 1); 472 } 473 474 int 475 getmarg(char *term) 476 { 477 if (strncmp(term, "1620", 4) == 0 || 478 strncmp(term, "1700", 4) == 0 || strncmp(term, "450", 3) == 0) 479 return (DMG); 480 else if (strncmp(term, "300s", 4) == 0) 481 return (GMG); 482 else if (strncmp(term, "4000a", 5) == 0) 483 return (TRMG); 484 else if (strcmp(term, "43") == 0) 485 return (FMG); 486 else if (strcmp(term, "tn300") == 0 || strcmp(term, "tn1200") == 0) 487 return (TMG); 488 else 489 return (NMG); 490 } 491 492 493 494 struct ttab * 495 termadj(void) 496 { 497 struct ttab *t; 498 499 if (strncmp(terminal, "40-2", 4) == 0 || strncmp(terminal, 500 "40/2", 4) == 0 || strncmp(terminal, "4420", 4) == 0) 501 (void) strcpy(terminal, "4424"); 502 else if (strncmp(terminal, "ibm", 3) == 0 || strcmp(terminal, 503 "3101") == 0 || strcmp(terminal, "system1") == 0) 504 (void) strcpy(terminal, "ibm"); 505 506 for (t = termtab; t->ttype; t++) { 507 if (EQ(terminal, t->ttype)) 508 return (t); 509 } 510 /* should have message */ 511 return (termtab); 512 } 513 514 char *cleartabs(); 515 /* 516 * settabs: set actual tabs at terminal 517 * note: this code caters to necessities of handling GSI and 518 * other terminals in a consistent way. 519 */ 520 521 void 522 settabs(int tabvect[NTABS]) 523 { 524 char setbuf[512]; /* 2+3*NTABS+2+NCOLS+NTABS (+ some extra) */ 525 char *p; /* ptr for assembly in setbuf */ 526 int *curtab; /* ptr to tabvect item */ 527 int i, previous, nblanks; 528 if (istty) { 529 ttyold.c_iflag &= ~ICRNL; 530 ttyold.c_oflag &= ~(ONLCR|OCRNL|ONOCR|ONLRET); 531 (void) ioctl(1, TCSETAW, &ttyold); /* turn off cr-lf map */ 532 } 533 p = setbuf; 534 *p++ = CR; 535 p = cleartabs(p, clear_tabs); 536 537 if (margflg) { 538 tmarg = getmarg(terminal); 539 switch (tmarg) { 540 case GMG: /* GSI300S */ 541 /* 542 * NOTE: the 300S appears somewhat odd, in that there is 543 * a column 0, but there is no way to do a direct tab to it. 544 * The sequence ESC 'T' '\0' jumps to column 27 and prints 545 * a '0', without changing the margin. 546 */ 547 *p++ = ESC; 548 *p++ = 'T'; /* setup for direct tab */ 549 if (margin &= 0177) /* normal case */ 550 *p++ = margin; 551 else { /* +m0 case */ 552 *p++ = 1; /* column 1 */ 553 *p++ = '\b'; /* column 0 */ 554 } 555 *p++ = margin; /* direct horizontal tab */ 556 *p++ = ESC; 557 *p++ = '0'; /* actual margin set */ 558 break; 559 case TMG: /* TERMINET 300 & 1200 */ 560 while (margin--) 561 *p++ = ' '; 562 break; 563 case DMG: /* DASI450/DIABLO 1620 */ 564 *p++ = ESC; /* direct tab ignores margin */ 565 *p++ = '\t'; 566 if (margin == 3) { 567 *p++ = (margin & 0177); 568 *p++ = ' '; 569 } 570 else 571 *p++ = (margin & 0177) + 1; 572 *p++ = ESC; 573 *p++ = '9'; 574 break; 575 case FMG: /* TTY 43 */ 576 p--; 577 *p++ = ESC; 578 *p++ = 'x'; 579 *p++ = CR; 580 while (margin--) 581 *p++ = ' '; 582 *p++ = ESC; 583 *p++ = 'l'; 584 *p++ = CR; 585 (void) write(1, setbuf, p - setbuf); 586 return; 587 case TRMG: 588 p--; 589 *p++ = ESC; 590 *p++ = 'N'; 591 while (margin--) 592 *p++ = ' '; 593 *p++ = ESC; 594 *p++ = 'F'; 595 break; 596 } 597 } 598 599 /* 600 * actual setting: at least terminals do this consistently! 601 */ 602 previous = 1; curtab = tabvect; 603 while ((nblanks = *curtab-previous) >= 0 && 604 previous + nblanks <= maxtab) { 605 for (i = 1; i <= nblanks; i++) *p++ = ' '; 606 previous = *curtab++; 607 (void) strcpy(p, settab); 608 p += strlen(settab); 609 } 610 *p++ = CR; 611 if (EQ(terminal, "4424")) 612 *p++ = '\n'; /* TTY40/2 needs LF, not just CR */ 613 (void) write(1, setbuf, p - setbuf); 614 } 615 616 617 /* 618 * Set software tabs. This only works on UNIX/370 using a series/1 619 * front-end processor. 620 */ 621 622 623 /* cleartabs(pointer to buffer, pointer to clear sequence) */ 624 char * 625 cleartabs(register char *p, char *qq) 626 { 627 int i; 628 char *q; 629 q = qq; 630 if (clear_tabs == 0) { /* if repetitive sequence */ 631 *p++ = CR; 632 for (i = 0; i < NTABSCL - 1; i++) { 633 *p++ = TAB; 634 *p++ = ESC; 635 *p++ = CLEAR; 636 } 637 *p++ = CR; 638 } else { 639 while (*p++ = *q++) /* copy table sequence */ 640 ; 641 p--; /* adjust for null */ 642 if (EQ(terminal, "4424")) { /* TTY40 extra delays needed */ 643 *p++ = '\0'; 644 *p++ = '\0'; 645 *p++ = '\0'; 646 *p++ = '\0'; 647 } 648 } 649 return (p); 650 } 651 /* getnum: scan and convert number, return zero if none found */ 652 /* set scan ptr to addr of ending delimeter */ 653 int 654 getnum(char **scan1) 655 { 656 int n; 657 char c, *scan; 658 n = 0; 659 scan = *scan1; 660 while ((c = *scan++) >= '0' && c <= '9') n = n * 10 + c -'0'; 661 *scan1 = --scan; 662 return (n); 663 } 664 665 /* usage: terminate processing with usage message */ 666 void 667 usage(void) 668 { 669 (void) fprintf(stderr, gettext( 670 "usage: tabs [ -n| --file| [[-code] -a| -a2| -c| -c2| -c3| -f| -p| -s| -u]] \ 671 [+m[n]] [-T type]\n")); 672 673 (void) fprintf(stderr, gettext( 674 " tabs [-T type][+m[n]] n1[,n2,...]\n")); 675 676 endup(); 677 exit(1); 678 } 679 680 /* endup: make sure tty mode reset & exit */ 681 void 682 endup(void) 683 { 684 685 if (istty) { 686 ttyold.c_iflag = ttyisave; 687 ttyold.c_oflag = ttyosave; 688 /* reset cr-lf to previous */ 689 (void) ioctl(1, TCSETAW, &ttyold); 690 (void) chmod(devtty, statbuf.st_mode); 691 } 692 if (err > 0) { 693 (void) resetterm(); 694 } 695 } 696 697 /* 698 * stdtabs: standard tabs table 699 * format: option code letter(s), null, tabs, null 700 */ 701 static char stdtabs[] = { 702 'a', 0, 1, 10, 16, 36, 72, 0, /* IBM 370 Assembler */ 703 'a', '2', 0, 1, 10, 16, 40, 72, 0, /* IBM Assembler alternative */ 704 'c', 0, 1, 8, 12, 16, 20, 55, 0, /* COBOL, normal */ 705 'c', '2', 0, 1, 6, 10, 14, 49, 0, /* COBOL, crunched */ 706 'c', '3', 0, 1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 67, 707 0, /* crunched COBOL, many tabs */ 708 'f', 0, 1, 7, 11, 15, 19, 23, 0, /* FORTRAN */ 709 'p', 0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 0, 710 /* PL/I */ 711 's', 0, 1, 10, 55, 0, /* SNOBOL */ 712 'u', 0, 1, 12, 20, 44, 0, /* UNIVAC ASM */ 713 0}; 714 715 /* 716 * stdtab: return tab list for any "canned" tab option. 717 * entry: option points to null-terminated option string 718 * tabvect points to vector to be filled in 719 * exit: return (0) if legal, tabvect filled, ending with zero 720 * return (-1) if unknown option 721 */ 722 int 723 stdtab(char option[], int tabvect[]) 724 { 725 char *sp; 726 tabvect[0] = 0; 727 sp = stdtabs; 728 while (*sp) { 729 if (EQ(option, sp)) { 730 while (*sp++) /* skip to 1st tab value */ 731 ; 732 while (*tabvect++ = *sp++) /* copy, make int */ 733 ; 734 return (0); 735 } 736 while (*sp++) /* skip to 1st tab value */ 737 ; 738 while (*sp++) /* skip over tab list */ 739 ; 740 } 741 return (-1); 742 } 743