/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1996 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* * tabs [tabspec] [+mn] [-Ttype] * set tabs (and margin, if +mn), for terminal type */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define EQ(a, b) (strcmp(a, b) == 0) /* max # columns used (needed for GSI) */ #define NCOLS 256 #define NTABS 65 /* max # tabs +1 (to be set) */ #define NTABSCL 21 /* max # tabs + 1 that will be cleared */ #define ESC 033 #define CLEAR '2' #define SET '1' #define TAB '\t' #define CR '\r' #define NMG 0 /* no margin setting */ #define GMG 1 /* DTC300s margin */ #define TMG 2 /* TERMINET margin */ #define DMG 3 /* DASI450 margin */ #define FMG 4 /* TTY 43 margin */ #define TRMG 5 /* Trendata 4000a */ #define TCLRLN 0 /* long, repetitive, general tab clear */ static char tsethp[] = {ESC, '1', 0}; /* (default) */ static char tsetibm[] = {ESC, '0', 0}; /* ibm */ static char tclrhp[] = {ESC, '3', CR, 0}; /* hp terminals */ /* short sequence for many terminals */ static char tclrsh[] = {ESC, CLEAR, CR, 0}; static char tclrgs[] = {ESC, TAB, CR, 0}; /* short, for 300s */ static char tclr40[] = {ESC, 'R', CR, 0}; /* TTY 40/2, 4424 */ static char tclribm[] = {ESC, '1', CR, 0}; /* ibm */ static struct ttab { char *ttype; /* -Tttype */ char *tclr; /* char sequence to clear tabs and return carriage */ int tmaxtab; /* maximum allowed position */ } *tt; static struct ttab termtab[] = { "", tclrsh, 132, "1620-12", tclrsh, 158, "1620-12-8", tclrsh, 158, "1700-12", tclrsh, 132, "1700-12-8", tclrsh, 158, "300-12", TCLRLN, 158, "300s-12", tclrgs, 158, "4424", tclr40, 80, "4000a", tclrsh, 132, "4000a-12", tclrsh, 158, "450-12", tclrsh, 158, "450-12-8", tclrsh, 158, "2631", tclrhp, 240, "2631-c", tclrhp, 240, "ibm", tclribm, 80, 0 }; static int err; static int tmarg; static char settab[32], clear_tabs[32]; static int maxtab; /* max tab for repetitive spec */ static int margin; static int margflg; /* >0 ==> +m option used, 0 ==> not */ static char *terminal = ""; static char *tabspec = "-8"; /* default tab specification */ static struct termio ttyold; /* tty table */ static int ttyisave; /* save for input modes */ static int ttyosave; /* save for output modes */ static int istty; /* 1 ==> is actual tty */ static struct stat statbuf; static char *devtty; static void scantab(char *scan, int tabvect[NTABS], int level); static void repetab(char *scan, int tabvect[NTABS]); static void arbitab(char *scan, int tabvect[NTABS]); static void filetab(char *scan, int tabvect[NTABS], int level); static int getmarg(char *term); static struct ttab *termadj(); static void settabs(int tabvect[NTABS]); static char *cleartabs(register char *p, char *qq); static int getnum(char **scan1); static void endup(); static int stdtab(char option[], int tabvect[]); static void usage(); static int chk_codes(char *codes); void main(int argc, char **argv) { int tabvect[NTABS]; /* build tab list here */ char *scan; /* scan pointer to next char */ char operand[LINE_MAX]; int option_end = 0; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); signal(SIGINT, endup); if (ioctl(1, TCGETA, &ttyold) == 0) { ttyisave = ttyold.c_iflag; ttyosave = ttyold.c_oflag; (void) fstat(1, &statbuf); devtty = ttyname(1); (void) chmod(devtty, 0000); /* nobody, not even us */ istty++; } tabvect[0] = 0; /* mark as not yet filled in */ while (--argc > 0) { scan = *++argv; if (*scan == '+') { if (!option_end) { if (*++scan == 'm') { margflg++; if (*++scan) margin = getnum(&scan); else margin = 10; } else { (void) fprintf(stderr, gettext( "tabs: %s: invalid tab spec\n"), scan-1); usage(); } } else { /* * only n1[,n2,...] operand can follow * end of options delimiter "--" */ (void) fprintf(stderr, gettext( "tabs: %s: invalid tab stop operand\n"), scan); usage(); } } else if (*scan == '-') { if (!option_end) { if (*(scan+1) == 'T') { /* allow space or no space after -T */ if (*(scan+2) == '\0') { if (--argc > 0) terminal = *++argv; else usage(); } else terminal = scan+2; } else if (*(scan+1) == '-') if (*(scan+2) == '\0') option_end = 1; else tabspec = scan; /* --file */ else if (strcmp(scan+1, "code") == 0); /* skip to next argument */ else if (chk_codes(scan+1) || (isdigit(*(scan+1)) && *(scan+2) == '\0')) { /* * valid code or single digit decimal * number */ tabspec = scan; } else { (void) fprintf(stderr, gettext( "tabs: %s: invalid tab spec\n"), scan); usage(); } } else { /* * only n1[,n2,...] operand can follow * end of options delimiter "--" */ (void) fprintf(stderr, gettext( "tabs: %s: invalid tab stop operand\n"), scan); usage(); } } else { /* * Tab-stop values separated using either commas * or blanks. If any number (except the first one) * is preceded by a plus sign, it is taken as an * increment to be added to the previous value. */ operand[0] = '\0'; while (argc > 0) { if (strrchr(*argv, '-') == (char *)NULL) { (void) strcat(operand, *argv); if (argc > 1) (void) strcat(operand, ","); --argc; ++argv; } else { (void) fprintf(stderr, gettext( "tabs: %s: tab stop values must be positive integers\n"), *argv); usage(); } } tabspec = operand; /* save tab specification */ } } if (*terminal == '\0') { if ((terminal = getenv("TERM")) == (char *)NULL || *terminal == '\0') { /* * Use tab setting and clearing sequences specified * by the ANSI standard. */ terminal = "ansi+tabs"; } } if (setupterm(terminal, 1, &err) == ERR) { (void) fprintf(stderr, gettext( "tabs: %s: terminfo file not found\n"), terminal); usage(); } else if (!tigetstr("hts")) { (void) fprintf(stderr, gettext( "tabs: cannot set tabs on terminal type %s\n"), terminal); usage(); } if (err <= 0 || columns <= 0 || set_tab == 0) { tt = termadj(); if (strcmp(terminal, "ibm") == 0) (void) strcpy(settab, tsetibm); else (void) strcpy(settab, tsethp); (void) strcpy(clear_tabs, tt->tclr); maxtab = tt->tmaxtab; } else { maxtab = columns; (void) strcpy(settab, set_tab); (void) strcpy(clear_tabs, clear_all_tabs); } scantab(tabspec, tabvect, 0); if (!tabvect[0]) repetab("8", tabvect); settabs(tabvect); endup(); exit(0); } /* * return 1 if code option is valid, otherwise return 0 */ int chk_codes(char *code) { if (*(code+1) == '\0' && (*code == 'a' || *code == 'c' || *code == 'f' || *code == 'p' || *code == 's' || *code == 'u')) return (1); else if (*(code+1) == '2' && *(code+2) == '\0' && (*code == 'a' || *code == 'c')) return (1); else if (*code == 'c' && *(code+1) == '3' && *(code+2) == '\0') return (1); return (0); } /* scantab: scan 1 tabspec & return tab list for it */ void scantab(char *scan, int tabvect[NTABS], int level) { register char c; if (*scan == '-') if ((c = *++scan) == '-') filetab(++scan, tabvect, level); else if (c >= '0' && c <= '9') repetab(scan, tabvect); else if (stdtab(scan, tabvect)) { endup(); (void) fprintf(stderr, gettext( "tabs: %s: unknown tab code\n"), scan); usage(); } else; else arbitab(scan, tabvect); } /* repetab: scan and set repetitve tabs, 1+n, 1+2*n, etc */ void repetab(char *scan, int tabvect[NTABS]) { register incr, i, tabn; int limit; incr = getnum(&scan); tabn = 1; limit = (maxtab-1)/(incr?incr:1)-1; /* # last actual tab */ if (limit > NTABS-2) limit = NTABS-2; for (i = 0; i <= limit; i++) tabvect[i] = tabn += incr; tabvect[i] = 0; } /* arbitab: handle list of arbitrary tabs */ void arbitab(char *scan, int tabvect[NTABS]) { char *scan_save; register i, t, last; scan_save = scan; last = 0; for (i = 0; i < NTABS-1; ) { if (*scan == '+') { scan++; /* +n ==> increment, not absolute */ if (t = getnum(&scan)) tabvect[i++] = last += t; else { endup(); (void) fprintf(stderr, gettext( "tabs: %s: invalid increment\n"), scan_save); usage(); } } else { if ((t = getnum(&scan)) > last) tabvect[i++] = last = t; else { endup(); (void) fprintf(stderr, gettext( "tabs: %s: invalid tab stop\n"), scan_save); usage(); } } if (*scan++ != ',') break; } if (last > NCOLS) { endup(); (void) fprintf(stderr, gettext( "tabs: %s: last tab stop would be set at a column greater than %d\n"), scan_save, NCOLS); usage(); } tabvect[i] = 0; } /* filetab: copy tabspec from existing file */ #define CARDSIZ 132 void filetab(char *scan, int tabvect[NTABS], int level) { register length, i; register char c; int fildes; char card[CARDSIZ]; /* buffer area for 1st card in file */ char state, found; char *temp; if (level) { endup(); (void) fprintf(stderr, gettext( "tabs: %s points to another file: invalid file indirection\n"), scan); exit(1); } if ((fildes = open(scan, O_RDONLY)) < 0) { endup(); (void) fprintf(stderr, gettext("tabs: %s: "), scan); perror(""); exit(1); } length = read(fildes, card, CARDSIZ); (void) close(fildes); found = state = 0; scan = 0; for (i = 0; i < length && (c = card[i]) != '\n'; i++) { switch (state) { case 0: state = (c == '<'); break; case 1: state = (c == ':')?2:0; break; case 2: if (c == 't') state = 3; else if (c == ':') state = 6; else if (c != ' ') state = 5; break; case 3: if (c == ' ') state = 2; else { scan = &card[i]; state = 4; } break; case 4: if (c == ' ') { card[i] = '\0'; state = 5; } else if (c == ':') { card[i] = '\0'; state = 6; } break; case 5: if (c == ' ') state = 2; else if (c == ':') state = 6; break; case 6: if (c == '>') { found = 1; goto done; } else state = 5; break; } } done: if (found && scan != 0) { scantab(scan, tabvect, 1); temp = scan; while (*++temp); *temp = '\n'; } else scantab("-8", tabvect, 1); } int getmarg(char *term) { if (strncmp(term, "1620", 4) == 0 || strncmp(term, "1700", 4) == 0 || strncmp(term, "450", 3) == 0) return (DMG); else if (strncmp(term, "300s", 4) == 0) return (GMG); else if (strncmp(term, "4000a", 5) == 0) return (TRMG); else if (strcmp(term, "43") == 0) return (FMG); else if (strcmp(term, "tn300") == 0 || strcmp(term, "tn1200") == 0) return (TMG); else return (NMG); } struct ttab * termadj() { register struct ttab *t; if (strncmp(terminal, "40-2", 4) == 0 || strncmp(terminal, "40/2", 4) == 0 || strncmp(terminal, "4420", 4) == 0) (void) strcpy(terminal, "4424"); else if (strncmp(terminal, "ibm", 3) == 0 || strcmp(terminal, "3101") == 0 || strcmp(terminal, "system1") == 0) (void) strcpy(terminal, "ibm"); for (t = termtab; t->ttype; t++) { if (EQ(terminal, t->ttype)) return (t); } /* should have message */ return (termtab); } char *cleartabs(); /* * settabs: set actual tabs at terminal * note: this code caters to necessities of handling GSI and * other terminals in a consistent way. */ void settabs(int tabvect[NTABS]) { char setbuf[512]; /* 2+3*NTABS+2+NCOLS+NTABS (+ some extra) */ register char *p; /* ptr for assembly in setbuf */ register *curtab; /* ptr to tabvect item */ int i, previous, nblanks; if (istty) { ttyold.c_iflag &= ~ICRNL; ttyold.c_oflag &= ~(ONLCR|OCRNL|ONOCR|ONLRET); (void) ioctl(1, TCSETAW, &ttyold); /* turn off cr-lf map */ } p = setbuf; *p++ = CR; p = cleartabs(p, clear_tabs); if (margflg) { tmarg = getmarg(terminal); switch (tmarg) { case GMG: /* GSI300S */ /* * NOTE: the 300S appears somewhat odd, in that there is * a column 0, but there is no way to do a direct tab to it. * The sequence ESC 'T' '\0' jumps to column 27 and prints * a '0', without changing the margin. */ *p++ = ESC; *p++ = 'T'; /* setup for direct tab */ if (margin &= 0177) /* normal case */ *p++ = margin; else { /* +m0 case */ *p++ = 1; /* column 1 */ *p++ = '\b'; /* column 0 */ } *p++ = margin; /* direct horizontal tab */ *p++ = ESC; *p++ = '0'; /* actual margin set */ break; case TMG: /* TERMINET 300 & 1200 */ while (margin--) *p++ = ' '; break; case DMG: /* DASI450/DIABLO 1620 */ *p++ = ESC; /* direct tab ignores margin */ *p++ = '\t'; if (margin == 3) { *p++ = (margin & 0177); *p++ = ' '; } else *p++ = (margin & 0177) + 1; *p++ = ESC; *p++ = '9'; break; case FMG: /* TTY 43 */ p--; *p++ = ESC; *p++ = 'x'; *p++ = CR; while (margin--) *p++ = ' '; *p++ = ESC; *p++ = 'l'; *p++ = CR; (void) write(1, setbuf, p - setbuf); return; case TRMG: p--; *p++ = ESC; *p++ = 'N'; while (margin--) *p++ = ' '; *p++ = ESC; *p++ = 'F'; break; } } /* * actual setting: at least terminals do this consistently! */ previous = 1; curtab = tabvect; while ((nblanks = *curtab-previous) >= 0 && previous + nblanks <= maxtab) { for (i = 1; i <= nblanks; i++) *p++ = ' '; previous = *curtab++; (void) strcpy(p, settab); p += strlen(settab); } *p++ = CR; if (EQ(terminal, "4424")) *p++ = '\n'; /* TTY40/2 needs LF, not just CR */ (void) write(1, setbuf, p - setbuf); } /* * Set software tabs. This only works on UNIX/370 using a series/1 * front-end processor. */ /* cleartabs(pointer to buffer, pointer to clear sequence) */ char * cleartabs(register char *p, char *qq) { register i; register char *q; q = qq; if (clear_tabs == 0) { /* if repetitive sequence */ *p++ = CR; for (i = 0; i < NTABSCL - 1; i++) { *p++ = TAB; *p++ = ESC; *p++ = CLEAR; } *p++ = CR; } else { while (*p++ = *q++); /* copy table sequence */ p--; /* adjust for null */ if (EQ(terminal, "4424")) { /* TTY40 extra delays needed */ *p++ = '\0'; *p++ = '\0'; *p++ = '\0'; *p++ = '\0'; } } return (p); } /* getnum: scan and convert number, return zero if none found */ /* set scan ptr to addr of ending delimeter */ int getnum(char **scan1) { register n; register char c, *scan; n = 0; scan = *scan1; while ((c = *scan++) >= '0' && c <= '9') n = n * 10 + c -'0'; *scan1 = --scan; return (n); } /* usage: terminate processing with usage message */ void usage() { (void) fprintf(stderr, gettext( "usage: tabs [ -n| --file| [[-code] -a| -a2| -c| -c2| -c3| -f| -p| -s| -u]] \ [+m[n]] [-T type]\n")); (void) fprintf(stderr, gettext( " tabs [-T type][+m[n]] n1[,n2,...]\n")); endup(); exit(1); } /* endup: make sure tty mode reset & exit */ void endup() { if (istty) { ttyold.c_iflag = ttyisave; ttyold.c_oflag = ttyosave; /* reset cr-lf to previous */ (void) ioctl(1, TCSETAW, &ttyold); (void) chmod(devtty, statbuf.st_mode); } if (err > 0) { (void) resetterm(); } } /* * stdtabs: standard tabs table * format: option code letter(s), null, tabs, null */ static char stdtabs[] = { 'a', 0, 1, 10, 16, 36, 72, 0, /* IBM 370 Assembler */ 'a', '2', 0, 1, 10, 16, 40, 72, 0, /* IBM Assembler alternative */ 'c', 0, 1, 8, 12, 16, 20, 55, 0, /* COBOL, normal */ 'c', '2', 0, 1, 6, 10, 14, 49, 0, /* COBOL, crunched */ 'c', '3', 0, 1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 67, 0, /* crunched COBOL, many tabs */ 'f', 0, 1, 7, 11, 15, 19, 23, 0, /* FORTRAN */ 'p', 0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 0, /* PL/I */ 's', 0, 1, 10, 55, 0, /* SNOBOL */ 'u', 0, 1, 12, 20, 44, 0, /* UNIVAC ASM */ 0}; /* * stdtab: return tab list for any "canned" tab option. * entry: option points to null-terminated option string * tabvect points to vector to be filled in * exit: return (0) if legal, tabvect filled, ending with zero * return (-1) if unknown option */ int stdtab(char option[], int tabvect[]) { register char *sp; tabvect[0] = 0; sp = stdtabs; while (*sp) { if (EQ(option, sp)) { while (*sp++); /* skip to 1st tab value */ while (*tabvect++ = *sp++); /* copy, make int */ return (0); } while (*sp++); /* skip to 1st tab value */ while (*sp++); /* skip over tab list */ } return (-1); }