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