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
main(int argc,char ** argv)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
chk_codes(char * code)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
scantab(char * scan,int tabvect[NTABS],int level)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
repetab(char * scan,int tabvect[NTABS])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
arbitab(char * scan,int tabvect[NTABS])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
filetab(char * scan,int tabvect[NTABS],int level)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
getmarg(char * term)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 *
termadj(void)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
settabs(int tabvect[NTABS])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 *
cleartabs(register char * p,char * qq)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
getnum(char ** scan1)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
usage(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
endup(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
stdtab(char option[],int tabvect[])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