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