root/usr/src/cmd/tabs/tabs.c
/*
 * 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   */

/*
 *      tabs [tabspec] [+mn] [-Ttype]
 *      set tabs (and margin, if +mn), for terminal type
 */


#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <curses.h>
#include <term.h>
#include <locale.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>

#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);

int
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);

        (void) 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) {
                                        /* EMPTY */
                                        /* 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();
        return (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)
{
        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 {
                arbitab(scan, tabvect);
        }
}

/*      repetab: scan and set repetitve tabs, 1+n, 1+2*n, etc */

void
repetab(char *scan, int tabvect[NTABS])
{
        int 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;
        int 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)
{
        int length, i;
        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(void)
{
        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) */
        char *p;                /* ptr for assembly in setbuf */
        int *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)
{
        int i;
        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)
{
        int n;
        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)
{
        (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(void)
{

        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[])
{
        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);
}