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