root/usr/src/ucbcmd/tset/tset.c
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved. The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 *  TSET -- set terminal modes
 *
 *      This program does sophisticated terminal initialization.
 *      I recommend that you include it in your .profile or .login
 *      file to initialize whatever terminal you are on.
 *
 *      There are several features:
 *
 *      A special file or sequence (as controlled by the termcap file)
 *      is sent to the terminal.
 *
 *      Mode bits are set on a per-terminal_type basis (much better
 *      than UNIX itself).  This allows special delays, automatic
 *      tabs, etc.
 *
 *      Erase and Kill characters can be set to whatever you want.
 *      Default is to change erase to control-H on a terminal which
 *      can overstrike, and leave it alone on anything else.  Kill
 *      is always left alone unless specifically requested.  These
 *      characters can be represented as "^X" meaning control-X;
 *      X is any character.
 *
 *      Terminals which are dialups or plugboard types can be aliased
 *      to whatever type you may have in your home or office.  Thus,
 *      if you know that when you dial up you will always be on a
 *      TI 733, you can specify that fact to tset.  You can represent
 *      a type as "?type".  This will ask you what type you want it
 *      to be -- if you reply with just a newline, it will default
 *      to the type given.
 *
 *      The current terminal type can be queried.
 *
 *      Usage:
 *              tset [-] [-EC] [-eC] [-kC] [-iC] [-s] [-h] [-u] [-r]
 *                      [-m [ident] [test baudrate] :type]
 *                      [-Q] [-I] [-S] [type]
 *
 *              In systems with environments, use:
 *                      eval `tset -s ...`
 *              Actually, this doesn't work in old csh's.
 *              Instead, use:
 *                      tset -s ... > tset.tmp
 *                      source tset.tmp
 *                      rm tset.tmp
 *              or:
 *                      set noglob
 *                      set term=(`tset -S ....`)
 *                      setenv TERM $term[1]
 *                      setenv TERMCAP "$term[2]"
 *                      unset term
 *                      unset noglob
 *
 *      Positional Parameters:
 *              type -- the terminal type to force.  If this is
 *                      specified, initialization is for this
 *                      terminal type.
 *
 *      Flags:
 *              - -- report terminal type.  Whatever type is
 *                      decided on is reported.  If no other flags
 *                      are stated, the only affect is to write
 *                      the terminal type on the standard output.
 *              -r -- report to user in addition to other flags.
 *              -EC -- set the erase character to C on all terminals
 *                      except those which cannot backspace (e.g.,
 *                      a TTY 33).  C defaults to control-H.
 *              -eC -- set the erase character to C on all terminals.
 *                      C defaults to control-H.  If not specified,
 *                      the erase character is untouched; however, if
 *                      not specified and the erase character is NULL
 *                      (zero byte), the erase character is set to CERASE.
 *              -kC -- set the kill character to C on all terminals.
 *                      Default for C is control-U.  If not specified,
 *                      the kill character is untouched; however, if
 *                      not specified and the kill character is NULL
 *                      (zero byte), the kill character is set to CKILL.
 *              -iC -- set the interrupt character to C on all terminals.
 *                      Default for C is control-C.  If not specified, the
 *                      interrupt character is untouched; however, if
 *                      not specified and the interrupt character is NULL
 *                      (zero byte), the interrupt character is set to
 *                      control-C.
 *              -qC -- reserved for setable quit character.
 *              -m -- map the system identified type to some user
 *                      specified type. The mapping can be baud rate
 *                      dependent. This replaces the old -d, -p flags.
 *                      (-d type  ->  -m dialup:type)
 *                      (-p type  ->  -m plug:type)
 *                      Syntax: -m identifier [test baudrate] :type
 *                      where: ``identifier'' is terminal type found in
 *                      /etc/ttys for this port, (abscence of an identifier
 *                      matches any identifier); ``test'' may be any combination
 *                      of  >  =  <  !  @; ``baudrate'' is as with stty(1);
 *                      ``type'' is the actual terminal type to use if the
 *                      mapping condition is met. Multiple maps are scanned
 *                      in order and the first match prevails.
 *              -n -- If the new tty driver from UCB is available, this flag
 *                      will activate the new options for erase and kill
 *                      processing. This will be different for printers
 *                      and crt's. For crts, if the baud rate is < 1200 then
 *                      erase and kill don't remove characters from the screen.
 *              -h -- don't read htmp file.  Normally the terminal type
 *                      is determined by reading the htmp file or the
 *                      environment (unless some mapping is specified).
 *                      This forces a read of the ttytype file -- useful
 *                      when htmp is somehow wrong. (V6 only)
 *              -u -- don't update htmp.  It seemed like this should
 *                      be put in.  Note that htmp is never actually
 *                      written if there are no changes, so don't bother
 *                      bother using this for efficiency reasons alone.
 *              -s -- output setenv commands for TERM.  This can be
 *                      used with
 *                              `tset -s ...`
 *                      and is to be prefered to:
 *                              setenv TERM `tset - ...`
 *                      because -s sets the TERMCAP variable also.
 *              -S -- Similar to -s but outputs 2 strings suitable for
 *                      use in csh .login files as follows:
 *                              set noglob
 *                              set term=(`tset -S .....`)
 *                              setenv TERM $term[1]
 *                              setenv TERMCAP "$term[2]"
 *                              unset term
 *                              unset noglob
 *              -Q -- be quiet.  don't output 'Erase set to' etc.
 *              -I -- don't do terminal initialization (is & if
 *                      strings).
 *              -v -- On virtual terminal systems, don't set up a
 *                      virtual terminal.  Otherwise tset will tell
 *                      the operating system what kind of terminal you
 *                      are on (if it is a known terminal) and fix up
 *                      the output of -s to use virtual terminal sequences.
 *
 *      Files:
 *              /etc/ttys
 *                      contains a terminal id -> terminal type
 *                      mapping; used when any user mapping is specified,
 *                      or the environment doesn't have TERM set.
 *              /etc/termcap
 *                      a terminal_type -> terminal_capabilities
 *                      mapping.
 *
 *      Return Codes:
 *              -1 -- couldn't open termcap.
 *              1 -- bad terminal type, or standard output not tty.
 *              0 -- ok.
 *
 *      Defined Constants:
 *              DIALUP -- the type code for a dialup port.
 *              PLUGBOARD -- the type code for a plugboard port.
 *              ARPANET -- the type code for an arpanet port.
 *              BACKSPACE -- control-H, the default for -e.
 *              CNTL('U') -- control-U, the default for -k.
 *              OLDERASE -- the ancient default erase character.
 *              FILEDES -- the file descriptor to do the operation
 *                      on, nominally 1 or 2.
 *              STDOUT -- the standard output file descriptor.
 *              UIDMASK -- the bit pattern to mask with the getuid()
 *                      call to get just the user id.
 *              GTTYN -- defines file containing generalized ttynames
 *                      and compiles code to look there.
 *
 *      Requires:
 *              Routines to handle htmp, ttys, and termcap.
 *
 *      Compilation Flags:
 *              OLDFLAGS -- must be defined to compile code for any of
 *                      the -d, -p, or -a flags.
 *              OLDDIALUP -- accept the -d flag.
 *              OLDPLUGBOARD -- accept the -p flag.
 *              OLDARPANET -- accept the -a flag.
 *              V6 -- if clear, use environments, not htmp.
 *                      also use TIOCSETN rather than stty to avoid flushing
 *              GTTYN -- if set, compiles code to look at /etc/ttys.
 *
 *      Trace Flags:
 *              none
 *
 *      Diagnostics:
 *              Bad flag
 *                      An incorrect option was specified.
 *              Too few args
 *                      more command line arguments are required.
 *              Unexpected arg
 *                      wrong type of argument was encountered.
 *              Cannot open ...
 *                      The specified file could not be openned.
 *              Type ... unknown
 *                      An unknown terminal type was specified.
 *              Cannot update htmp
 *                      Cannot update htmp file when the standard
 *                      output is not a terminal.
 *              Erase set to ...
 *                      Telling that the erase character has been
 *                      set to the specified character.
 *              Kill set to ...
 *                      Ditto for kill
 *              Erase is ...    Kill is ...
 *                      Tells that the erase/kill characters were
 *                      wierd before, but they are being left as-is.
 *              Not a terminal
 *                      Set if FILEDES is not a terminal.
 *
 *      Compilation Instructions:
 *              cc -n -O tset.c -ltermlib
 *              mv a.out tset
 *              chown bin tset
 *              chmod 4755 tset
 *
 *              where 'bin' should be whoever owns the 'htmp' file.
 *              If 'htmp' is 666, then tset need not be setuid.
 *
 *              For version 6 the compile command should be:
 *              cc -n -O -I/usr/include/retrofit tset.c -ltermlib -lretro -lS
 *
 *
 *      History:
 *              1/81 -- Added alias checking for mapping identifiers.
 *              7/80 -- '-S' added. '-m' mapping added. TERMCAP string
 *                      cleaned up.
 *              3/80 -- Changed to use tputs.  Prc & flush added.
 *              10/79 -- '-s' option extended to handle TERMCAP
 *                      variable, set noglob, quote the entry,
 *                      and know about the Bourne shell.  Terminal
 *                      initialization moved to before any information
 *                      output so screen clears would not screw you.
 *                      '-Q' option added.
 *              8/79 -- '-' option alone changed to only output
 *                      type.  '-s' option added.  'VERSION7'
 *                      changed to 'V6' for compatibility.
 *              12/78 -- modified for eventual migration to VAX/UNIX,
 *                      so the '-' option is changed to output only
 *                      the terminal type to STDOUT instead of
 *                      FILEDES.
 *              9/78 -- '-' and '-p' options added (now fully
 *                      compatible with ttytype!), and spaces are
 *                      permitted between the -d and the type.
 *              8/78 -- The sense of -h and -u were reversed, and the
 *                      -f flag is dropped -- same effect is available
 *                      by just stating the terminal type.
 *              10/77 -- Written.
 */


#define index strchr
#define rindex strrchr
#define curerase modes.c_cc[VERASE]
#define curkill modes.c_cc[VKILL]
#define curintr modes.c_cc[VINTR]
#define olderase oldmodes.c_cc[VERASE]
#define oldkill oldmodes.c_cc[VKILL]
#define oldintr oldmodes.c_cc[VINTR]

#include        <stdio.h>
#include        <stdlib.h>
#include        <termio.h>
#include        <signal.h>


#define YES             1
#define NO              0
#undef CNTL
#define CNTL(c)         ((c)&037)
#define BACKSPACE       (CNTL('H'))
#define isdigit(c)      (c >= '0' && c <= '9')
#define isalnum(c)      (c > ' ' && (index("<@=>!:|\177", c) == NULL))
#define OLDERASE        '#'

/* default special characters */
#ifndef CERASE
#define CERASE  '\177'
#endif
#ifndef CKILL
#define CKILL   CNTL('U')
#endif
#ifndef CINTR
#define CINTR   CNTL('C')
#endif
#ifndef CDSUSP
#define CQUIT   034             /* FS, ^\ */
#define CSTART  CNTL('Q')
#define CSTOP   CNTL('S')
#define CEOF    CNTL('D')
#define CEOT    CEOF
#define CBRK    0377
#define CSUSP   CNTL('Z')
#define CDSUSP  CNTL('Y')
#define CRPRNT  CNTL('R')
#define CFLUSH  CNTL('O')
#define CWERASE CNTL('W')
#define CLNEXT  CNTL('V')
#endif

#define FILEDES         2       /* do gtty/stty on this descriptor */
#define STDOUT          1       /* output of -s/-S to this descriptor */

#define UIDMASK         -1

#define USAGE   "usage: tset [-] [-rsIQS] [-eC] [-kC] " \
                "[-iC] [-m [ident][test speed]:type] [type]\n"

#define OLDFLAGS
#define DIALUP          "dialup"
#define OLDDIALUP       "sd"
#define PLUGBOARD       "plugboard"
#define OLDPLUGBOARD    "sp"

#define DEFTYPE         "unknown"

/*
 * Baud Rate Conditionals
 */
#define ANY             0
#define GT              1
#define EQ              2
#define LT              4
#define GE              (GT|EQ)
#define LE              (LT|EQ)
#define NE              (GT|LT)
#define ALL             (GT|EQ|LT)



#define NMAP            10

struct  map {
        char *Ident;
        char Test;
        char Speed;
        char *Type;
} map[NMAP];

struct map *Map = map;

struct {
        char    *string;
        int     speed;
        int     baudrate;
} speeds[] = {
        "0",            B0,             0,
        "50",           B50,            50,
        "75",           B75,            75,
        "110",          B110,           110,
        "134",          B134,           134,
        "134.5",        B134,           134,
        "150",          B150,           150,
        "200",          B200,           200,
        "300",          B300,           300,
        "600",          B600,           600,
        "1200",         B1200,          1200,
        "1800",         B1800,          1800,
        "2400",         B2400,          2400,
        "4800",         B4800,          4800,
        "9600",         B9600,          9600,
        "19200",        EXTA,           19200,
        "exta",         EXTA,           19200,
        "extb",         EXTB,           38400,
        "57600",        B57600,         57600,
        "76800",        B76800,         76800,
        "115200",       B115200,        115200,
        "153600",       B153600,        153600,
        "230400",       B230400,        230400,
        "307200",       B307200,        307200,
        "460800",       B460800,        460800,
        "921600",       B921600,        921600,
        "1000000",      B1000000,       1000000,
        "1152000",      B1152000,       1152000,
        "1500000",      B1500000,       1500000,
        "2000000",      B2000000,       2000000,
        "2500000",      B2500000,       2500000,
        "3000000",      B3000000,       3000000,
        "3500000",      B3500000,       3500000,
        "4000000",      B4000000,       4000000,
        0,
};

signed char Erase_char;         /* new erase character */
char    Kill_char;              /* new kill character */
char    Intr_char;              /* new interrupt character */
char    Specialerase;   /* set => Erase_char only on terminals with backspace */

char    *TtyType;               /* type of terminal */
char    *DefType;               /* default type if none other computed */
char    *NewType;               /* mapping identifier based on old flags */
int     Mapped;                 /* mapping has been specified */
int     Dash_u;                 /* don't update htmp */
int     Dash_h;                 /* don't read htmp */
int     DoSetenv;               /* output setenv commands */
int     BeQuiet;                /* be quiet */
int     NoInit;                 /* don't output initialization string */
int     IsReset;                /* invoked as reset */
int     Report;                 /* report current type */
int     Ureport;                /* report to user */
int     RepOnly;                /* report only */
int     CmndLine;               /* output full command lines (-s option) */
int     Ask;                    /* ask user for termtype */
int     DoVirtTerm = YES;       /* Set up a virtual terminal */
int     PadBaud;                /* Min rate of padding needed */

#define CAPBUFSIZ       1024
char    Capbuf[CAPBUFSIZ];      /* line from /etc/termcap for this TtyType */
char    *Ttycap;                /* termcap line from termcap or environ */

char    Aliasbuf[128];
char    *Alias[16];

extern char *strcpy();
extern char *index();

struct delay
{
        int     d_delay;
        int     d_bits;
};

#include        "tset.delays.h"

struct termio   mode;
struct termio   oldmode;
struct termios  modes;
struct termios  oldmodes;
int             istermios;

void reportek(char *, char, char, char);
void setdelay(char *, struct delay [], tcflag_t, tcflag_t *);
void prs(char *);
void prc(char);
void flush(void);
void cat(char *);
void bmove(char *, char *, int);
void makealias(char *);
void wrtermcap(char *);
void fatal(char *, char *);
char reset();                   /* Routine for checking&resetting chars */

int
main(int argc, char *argv[])
{
        char            buf[CAPBUFSIZ];
        char            termbuf[32];
        auto char       *bufp;
        char            *p;
        char            *command;
        int             i;
        int             Break;
        int             Not;
        char            *nextarg();
        char            *mapped();
        extern char     *rindex();
        struct winsize  win;
        extern char     *getenv();
        extern char     *tgetstr();
        char            bs_char;
        int             csh;
        int             settle = NO;
        void            setmode();
        extern char     PC;
        extern short    ospeed;

        if ((istermios = ioctl(FILEDES, TCGETS, (char *)&modes)) < 0) {
                if (ioctl(FILEDES, TCGETA, (char *)&mode) < 0) {
                        prs("Not a terminal\n");
                        exit(1);
                }
                bmove((char *)&mode, (char *)&oldmode, sizeof (mode));
                modes.c_lflag = oldmodes.c_lflag = mode.c_lflag;
                modes.c_oflag = oldmodes.c_oflag = mode.c_oflag;
                modes.c_iflag = oldmodes.c_iflag = mode.c_iflag;
                modes.c_cflag = oldmodes.c_cflag = mode.c_cflag;
                for (i = 0; i < NCC; i++)
                        modes.c_cc[i] = oldmodes.c_cc[i] = mode.c_cc[i];
        } else
                bmove((char *)&modes, (char *)&oldmodes, sizeof (modes));
        ospeed = cfgetospeed(&modes);
        (void) signal(SIGINT, setmode);
        (void) signal(SIGQUIT, setmode);
        (void) signal(SIGTERM, setmode);

        if (command = rindex(argv[0], '/'))
                command++;
        else
                command = argv[0];
        if (sequal(command, "reset")) {
                /*
                 * Reset the teletype mode bits to a sensible state.
                 * Copied from the program by Kurt Shoens & Mark Horton.
                 * Very useful after crapping out in raw.
                 */
                if ((istermios = ioctl(FILEDES, TCGETS, (char *)&modes)) < 0) {
                        (void) ioctl(FILEDES, TCGETA, (char *)&mode);
                        modes.c_lflag = mode.c_lflag;
                        modes.c_oflag = mode.c_oflag;
                        modes.c_iflag = mode.c_iflag;
                        modes.c_cflag = mode.c_cflag;
                        for (i = 0; i < NCC; i++)
                                modes.c_cc[i] = mode.c_cc[i];
                }
                curerase = reset(curerase, CERASE);
                curkill = reset(curkill, CKILL);
                curintr = reset(curintr, CINTR);
                modes.c_cc[VQUIT] = reset(modes.c_cc[VQUIT], CQUIT);
                modes.c_cc[VEOF] = reset(modes.c_cc[VEOF], CEOF);

                modes.c_iflag |= (BRKINT|ISTRIP|ICRNL|IXON);
                modes.c_iflag &= ~(IGNBRK|PARMRK|INPCK|INLCR|IGNCR|IUCLC|IXOFF);
                modes.c_oflag |= (OPOST|ONLCR);
                modes.c_oflag &= ~(OLCUC|OCRNL|ONOCR|ONLRET|OFILL|OFDEL|
                    NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
                modes.c_cflag |= (CS7|CREAD);
                modes.c_cflag &= ~(PARODD|CLOCAL);
                modes.c_lflag |= (ISIG|ICANON|ECHO|ECHOK);
                modes.c_lflag &= ~(XCASE|ECHONL|NOFLSH);
                if (istermios < 0) {
                        mode.c_lflag = modes.c_lflag;
                        mode.c_oflag = modes.c_oflag;
                        mode.c_iflag = modes.c_iflag;
                        mode.c_cflag = modes.c_cflag;
                        for (i = 0; i < NCC; i++)
                                mode.c_cc[i] = modes.c_cc[i];
                        (void) ioctl(FILEDES, TCSETAW, (char *)&mode);
                } else
                        (void) ioctl(FILEDES, TCSETSW, (char *)&modes);
                Dash_u = YES;
                BeQuiet = YES;
                IsReset = YES;
        } else if (argc == 2 && sequal(argv[1], "-")) {
                RepOnly = YES;
                Dash_u = YES;
        }
        argc--;

        /* scan argument list and collect flags */
        while (--argc >= 0) {
                p = *++argv;
                if (*p == '-') {
                        if (*++p == '\0')
                                Report = YES; /* report current terminal type */
                        else
                                while (*p)
                                        switch (*p++) {

                        case 'r':       /* report to user */
                                Ureport = YES;
                                continue;

                        case 'E':
                                /* special erase: operate on all but TTY33 */
                                Specialerase = YES;
                                /* explicit fall-through to -e case */
                                /* FALLTHROUGH */

                        case 'e':       /* erase character */
                                if (*p == '\0')
                                        Erase_char = -1;
                                else {
                                        if (*p == '^' && p[1] != '\0')
                                                if (*++p == '?')
                                                        Erase_char = '\177';
                                                else
                                                        Erase_char = CNTL(*p);
                                        else
                                                Erase_char = *p;
                                        p++;
                                }
                                continue;

                        case 'i':       /* interrupt character */
                                if (*p == '\0')
                                        Intr_char = CNTL('C');
                                else {
                                        if (*p == '^' && p[1] != '\0')
                                                if (*++p == '?')
                                                        Intr_char = '\177';
                                                else
                                                        Intr_char = CNTL(*p);
                                        else
                                                Intr_char = *p;
                                        p++;
                                }
                                continue;

                        case 'k':       /* kill character */
                                if (*p == '\0')
                                        Kill_char = CNTL('U');
                                else {
                                        if (*p == '^' && p[1] != '\0')
                                                if (*++p == '?')
                                                        Kill_char = '\177';
                                                else
                                                        Kill_char = CNTL(*p);
                                        else
                                                Kill_char = *p;
                                        p++;
                                }
                                continue;

#ifdef OLDFLAGS
#ifdef  OLDDIALUP
                        case 'd':       /* dialup type */
                                NewType = DIALUP;
                                goto mapold;
#endif

#ifdef OLDPLUGBOARD
                        case 'p':       /* plugboard type */
                                NewType = PLUGBOARD;
                                goto mapold;
#endif

#ifdef OLDARPANET
                        case 'a':       /* arpanet type */
                                Newtype = ARPANET;
                                goto mapold;
#endif

mapold:                         Map->Ident = NewType;
                                Map->Test = ALL;
                                if (*p == '\0') {
                                        p = nextarg(argc--, argv++);
                                }
                                Map->Type = p;
                                Map++;
                                Mapped = YES;
                                p = "";
                                continue;
#endif

                        case 'm':       /* map identifier to type */
                                /*
                                 * This code is very loose. Almost no
                                 * syntax checking is done!! However,
                                 * illegal syntax will only produce
                                 * weird results.
                                 */
                                if (*p == '\0') {
                                        p = nextarg(argc--, argv++);
                                }
                                if (isalnum(*p)) {
                                        Map->Ident = p; /* identifier */
                                        while (isalnum(*p)) p++;
                                }
                                else
                                        Map->Ident = "";
                                Break = NO;
                                Not = NO;
                                while (!Break)
                                        switch (*p) {
                                        case '\0':
                                                p = nextarg(argc--, argv++);
                                                continue;

                                        case ':':       /* mapped type */
                                                *p++ = '\0';
                                                Break = YES;
                                                continue;

                                        case '>':       /* conditional */
                                                Map->Test |= GT;
                                                *p++ = '\0';
                                                continue;

                                        case '<':       /* conditional */
                                                Map->Test |= LT;
                                                *p++ = '\0';
                                                continue;

                                        case '=':       /* conditional */
                                        case '@':
                                                Map->Test |= EQ;
                                                *p++ = '\0';
                                                continue;

                                        case '!':       /* invert conditions */
                                                Not = ~Not;
                                                *p++ = '\0';
                                                continue;

                                        case 'B':       /* Baud rate */
                                                p++;
                                                /* intentional fallthru */
                                        default:
                                                if (isdigit(*p) || *p == 'e') {
                                                        Map->Speed =
                                                            baudrate(p);
                                                        while (isalnum(*p) ||
                                                            *p == '.')
                                                                p++;
                                                } else
                                                        Break = YES;
                                                continue;
                                }
                                if (Not) {      /* invert sense of test */
                                        Map->Test = (~(Map->Test))&ALL;
                                }
                                if (*p == '\0') {
                                        p = nextarg(argc--, argv++);
                                }
                                Map->Type = p;
                                p = "";
                                Map++;
                                Mapped = YES;
                                continue;

                        case 'h':       /* don't get type from htmp or env */
                                Dash_h = YES;
                                continue;

                        case 'u':       /* don't update htmp */
                                Dash_u = YES;
                                continue;

                        case 's':       /* output setenv commands */
                                DoSetenv = YES;
                                CmndLine = YES;
                                continue;

                        case 'S':       /* output setenv strings */
                                DoSetenv = YES;
                                CmndLine = NO;
                                continue;

                        case 'Q':       /* be quiet */
                                BeQuiet = YES;
                                continue;

                        case 'I':       /* no initialization */
                                NoInit = YES;
                                continue;

                        case 'A':       /* Ask user */
                                Ask = YES;
                                continue;

                        case 'v':       /* no virtual terminal */
                                DoVirtTerm = NO;
                                continue;

                        default:
                                *p-- = '\0';
                                fatal("Bad flag -", p);
                        }
                } else {
                        /* terminal type */
                        DefType = p;
                }
        }

        if (DefType) {
                if (Mapped) {
                        Map->Ident = "";        /* means "map any type" */
                        Map->Test = ALL;        /* at all baud rates */
                        Map->Type = DefType;    /* to the default type */
                } else
                        TtyType = DefType;
        }

        /*
         * Get rid of $TERMCAP, if it's there, so we get a real
         * entry from /etc/termcap.  This prevents us from being
         * fooled by out of date stuff in the environment, and
         * makes tabs work right on CB/Unix.
         */
        bufp = getenv("TERMCAP");
        if (bufp && *bufp != '/')
                (void) strcpy(bufp-8, "NOTHING"); /* overwrite only "TERMCAP" */
        /* get current idea of terminal type from environment */
        if (!Dash_h && TtyType == NULL)
                TtyType = getenv("TERM");

        /* If still undefined, use DEFTYPE */
        if (TtyType == NULL) {
                TtyType = DEFTYPE;
        }

        /* check for dialup or other mapping */
        if (Mapped) {
                if (!(Alias[0] && isalias(TtyType)))
                        if (tgetent(Capbuf, TtyType) > 0)
                                makealias(Capbuf);
                TtyType = mapped(TtyType);
        }

        /* TtyType now contains a pointer to the type of the terminal */
        /* If the first character is '?', ask the user */
        if (TtyType[0] == '?') {
                Ask = YES;
                TtyType++;
                if (TtyType[0] == '\0')
                        TtyType = DEFTYPE;
        }
        if (Ask) {
ask:
                prs("TERM = (");
                prs(TtyType);
                prs(") ");
                flush();

                /* read the terminal.  If not empty, set type */
                i = read(2, termbuf, sizeof (termbuf) - 1);
                if (i > 0) {
                        if (termbuf[i - 1] == '\n')
                                i--;
                        termbuf[i] = '\0';
                        if (termbuf[0] != '\0')
                                TtyType = termbuf;
                }
        }

        /* get terminal capabilities */
        if (!(Alias[0] && isalias(TtyType))) {
                switch (tgetent(Capbuf, TtyType)) {
                case -1:
                        prs("Cannot find termcap\n");
                        flush();
                        exit(-1);

                case 0:
                        prs("Type ");
                        prs(TtyType);
                        prs(" unknown\n");
                        flush();
                        if (DoSetenv) {
                                TtyType = DEFTYPE;
                                Alias[0] = '\0';
                                goto ask;
                        } else
                                exit(1);
                }
        }
        Ttycap = Capbuf;

        if (!RepOnly) {
                /* determine erase and kill characters */
                if (Specialerase && !tgetflag("bs"))
                        Erase_char = 0;
                bufp = buf;
                p = tgetstr("kb", &bufp);
                if (p == NULL || p[1] != '\0')
                        p = tgetstr("bc", &bufp);
                if (p != NULL && p[1] == '\0')
                        bs_char = p[0];
                else if (tgetflag("bs"))
                        bs_char = BACKSPACE;
                else
                        bs_char = 0;
                /*
                 * The next statement can't be fixed, because now users
                 * depend on keeping their erase character as DEL if the
                 * system set it there.  People who want backspace have
                 * to say tset -e.
                 */
                if (Erase_char == 0 && !tgetflag("os") &&
                    curerase == OLDERASE) {
                        if (tgetflag("bs") || bs_char != 0)
                                Erase_char = -1;
                }
                if (Erase_char < 0)
                        Erase_char = (bs_char != 0) ? bs_char : BACKSPACE;

                if (curerase == 0)
                        curerase = CERASE;
                if (Erase_char != 0)
                        curerase = Erase_char;

                if (curintr == 0)
                        curintr = CINTR;
                if (Intr_char != 0)
                        curintr = Intr_char;

                if (curkill == 0)
                        curkill = CKILL;
                if (Kill_char != 0)
                        curkill = Kill_char;

                /* set modes */
                PadBaud = tgetnum("pb");        /* OK if fails */
                for (i = 0; speeds[i].string; i++)
                        if (speeds[i].baudrate == PadBaud) {
                                PadBaud = speeds[i].speed;
                                break;
                        }
                setdelay("dC", CRdelay, CRbits, &modes.c_oflag);
                setdelay("dN", NLdelay, NLbits, &modes.c_oflag);
                setdelay("dB", BSdelay, BSbits, &modes.c_oflag);
                setdelay("dF", FFdelay, FFbits, &modes.c_oflag);
                setdelay("dT", TBdelay, TBbits, &modes.c_oflag);
                setdelay("dV", VTdelay, VTbits, &modes.c_oflag);

                if (tgetflag("UC") || (command[0] & 0140) == 0100) {
                        modes.c_iflag |= IUCLC;
                        modes.c_oflag |= OLCUC;
                        modes.c_cflag |= XCASE;
                } else if (tgetflag("LC")) {
                        modes.c_iflag &= ~IUCLC;
                        modes.c_oflag &= ~OLCUC;
                        modes.c_cflag &= ~XCASE;
                }
                modes.c_iflag &= ~(PARMRK|INPCK);
                modes.c_lflag |= ICANON;
                if (tgetflag("EP")) {
                        modes.c_iflag |= INPCK;
                        modes.c_cflag |= PARENB;
                        modes.c_cflag &= ~PARODD;
                }
                if (tgetflag("OP")) {
                        modes.c_iflag |= INPCK;
                        modes.c_cflag |= PARENB;
                        modes.c_cflag |= PARODD;
                }

                modes.c_oflag |= ONLCR;
                modes.c_iflag |= ICRNL;
                modes.c_lflag |= ECHO;
                modes.c_oflag |= TAB3;
                if (tgetflag("NL")) {   /* new line, not line feed */
                        modes.c_oflag &= ~ONLCR;
                        modes.c_iflag &= ~ICRNL;
                }
                if (tgetflag("HD"))     /* half duplex */
                        modes.c_lflag &= ~ECHO;
                if (tgetflag("pt"))     /* print tabs */
                        modes.c_oflag &= ~TAB3;

                modes.c_lflag |= (ECHOE|ECHOK);
                if (tgetflag("hc")) {   /* set printer modes */
                        modes.c_lflag &= ~ECHOE;
                }

                /* get pad character */
                bufp = buf;
                if (tgetstr("pc", &bufp) != 0)
                        PC = buf[0];

                /* output startup string */
                if (!NoInit) {
                        if (oldmodes.c_oflag&(TAB3|ONLCR|OCRNL|ONLRET)) {
                                oldmodes.c_oflag &= (TAB3|ONLCR|OCRNL|ONLRET);
                                setmode(-1);
                        }
                        if (settabs()) {
                                settle = YES;
                                flush();
                        }
                        bufp = buf;
                        if (IsReset && tgetstr("rs", &bufp) != 0 ||
                            tgetstr("is", &bufp) != 0) {
                                tputs(buf, 0, prc);
                                settle = YES;
                                flush();
                        }
                        bufp = buf;
                        if (IsReset && tgetstr("rf", &bufp) != 0 ||
                            tgetstr("if", &bufp) != 0) {
                                cat(buf);
                                settle = YES;
                        }
                        if (settle) {
                                prc('\r');
                                if (IsReset)
                                        prc('\n');  /* newline too */
                                flush();
                                sleep(1);       /* let terminal settle down */
                        }
                }

                setmode(0);     /* set new modes, if they've changed */

                /* set up environment for the shell we are using */
                /* (this code is rather heuristic, checking for $SHELL */
                /* ending in the 3 characters "csh") */
                csh = NO;
                if (DoSetenv) {
                        char *sh;

                        if ((sh = getenv("SHELL")) && (i = strlen(sh)) >= 3) {
                                if ((csh = sequal(&sh[i-3], "csh")) && CmndLine)
                                        (void) write(STDOUT,
                                            "set noglob;\n", 12);
                        }
                        if (!csh) {     /* running Bourne shell */
                                (void) write(STDOUT,
                                    "export TERMCAP TERM;\n", 21);
                        }
                }
        }

        /* report type if appropriate */
        if (DoSetenv || Report || Ureport) {
                /* if type is the short name, find first alias (if any) */
                makealias(Ttycap);
                if (sequal(TtyType, Alias[0]) && Alias[1]) {
                        TtyType = Alias[1];
                }

                if (DoSetenv) {
                        if (csh) {
                                if (CmndLine)
                                        (void) write(STDOUT,
                                            "setenv TERM ", 12);
                                (void) write(STDOUT, TtyType, strlen(TtyType));
                                (void) write(STDOUT, " ", 1);
                                if (CmndLine)
                                        (void) write(STDOUT, ";\n", 2);
                        } else {
                                (void) write(STDOUT, "TERM=", 5);
                                (void) write(STDOUT, TtyType, strlen(TtyType));
                                (void) write(STDOUT, ";\n", 2);
                        }
                } else if (Report) {
                        (void) write(STDOUT, TtyType, strlen(TtyType));
                        (void) write(STDOUT, "\n", 1);
                }
                if (Ureport) {
                        prs("Terminal type is ");
                        prs(TtyType);
                        prs("\n");
                        flush();
                }

                if (DoSetenv) {
                        if (csh) {
                                if (CmndLine)
                                        (void) write(STDOUT,
                                            "setenv TERMCAP '", 16);
                        } else
                                (void) write(STDOUT, "TERMCAP='", 9);
                        wrtermcap(Ttycap);
                        if (csh) {
                                if (CmndLine) {
                                        (void) write(STDOUT, "';\n", 3);
                                        (void) write(STDOUT,
                                            "unset noglob;\n", 14);
                                }
                        } else
                                (void) write(STDOUT, "';\n", 3);
                }
        }

        if (RepOnly)
                exit(0);

        /* tell about changing erase, kill and interrupt characters */
        reportek("Erase", curerase, olderase, CERASE);
        reportek("Kill", curkill, oldkill, CKILL);
        reportek("Interrupt", curintr, oldintr, CINTR);

        return (0);
}

/*
 * Set the hardware tabs on the terminal, using the ct (clear all tabs),
 * st (set one tab) and ch (horizontal cursor addressing) capabilities.
 * This is done before if and is, so they can patch in case we blow this.
 */
int
settabs(void)
{
        char caps[100];
        char *capsp = caps;
        char *clear_tabs, *set_tab, *set_column, *set_pos;
        char *tg_out, *tgoto();
        int c;
        extern char *tgetstr();
        int lines, columns;

        clear_tabs = tgetstr("ct", &capsp);
        set_tab = tgetstr("st", &capsp);
        set_column = tgetstr("ch", &capsp);
        if (set_column == 0)
                set_pos = tgetstr("cm", &capsp);

        if (clear_tabs && set_tab) {
                prc('\r');      /* force to be at left margin */
                tputs(clear_tabs, 0, prc);
        }
        if (set_tab) {
                columns = tgetnum("co");
                lines = tgetnum("li");
                for (c = 0; c < columns; c += 8) {
                        /* get to that column. */
                        tg_out = "OOPS";        /* also returned by tgoto */
                        if (set_column)
                                tg_out = tgoto(set_column, 0, c);
                        if (*tg_out == 'O' && set_pos)
                                tg_out = tgoto(set_pos, c, lines-1);
                        if (*tg_out != 'O')
                                tputs(tg_out, 1, prc);
                        else if (c != 0) {
                                prc(' '); prc(' '); prc(' '); prc(' ');
                                prc(' '); prc(' '); prc(' '); prc(' ');
                        }
                        /* set the tab */
                        tputs(set_tab, 0, prc);
                }
                prc('\r');
                return (1);
        }
        return (0);
}

/*
 * flag serves several purposes:
 *      if called as the result of a signal, flag will be > 0.
 *      if called from terminal init, flag == -1 means reset "oldmode".
 *      called with flag == 0 at end of normal mode processing.
 */
void
setmode(int flag)
{
        struct termio *ttymode;
        struct termios *ttymodes;
        int i;

        ttymode = (struct termio *)0;
        ttymodes = (struct termios *)0;

        if (flag < 0) { /* unconditionally reset oldmode (called from init) */
                if (istermios < 0) {
                        oldmode.c_lflag = oldmodes.c_lflag;
                        oldmode.c_oflag = oldmodes.c_oflag;
                        oldmode.c_iflag = oldmodes.c_iflag;
                        oldmode.c_cflag = oldmodes.c_cflag;
                        for (i = 0; i < NCC; i++)
                                oldmode.c_cc[i] = oldmodes.c_cc[i];
                        ttymode = &oldmode;
                } else
                        ttymodes = &oldmodes;
        } else {
                if (istermios < 0) {
                        oldmode.c_lflag = oldmodes.c_lflag;
                        oldmode.c_oflag = oldmodes.c_oflag;
                        oldmode.c_iflag = oldmodes.c_iflag;
                        oldmode.c_cflag = oldmodes.c_cflag;
                        for (i = 0; i < NCC; i++)
                                oldmode.c_cc[i] = oldmodes.c_cc[i];
                        mode.c_lflag = modes.c_lflag;
                        mode.c_oflag = modes.c_oflag;
                        mode.c_iflag = modes.c_iflag;
                        mode.c_cflag = modes.c_cflag;
                        for (i = 0; i < NCC; i++)
                                mode.c_cc[i] = modes.c_cc[i];
                        if (!bequal((char *)&mode, (char *)&oldmode,
                            sizeof (mode)))
                                ttymode = &mode;
                } else if (!bequal((char *)&modes, (char *)&oldmodes,
                    sizeof (modes)))
                        ttymodes = &modes;
        }

        if (ttymode) {
                (void) ioctl(FILEDES, TCSETAW, (char *)ttymode);
        } else if (ttymodes) {
                (void) ioctl(FILEDES, TCSETSW, (char *)ttymodes);
        }
        if (flag > 0)   /* trapped signal */
                exit(1);
}

void
reportek(char *name, char new, char old, char def)
{
        char    o;
        char    n;
        char    *p;
        char            buf[32];
        char            *bufp;
        extern char *tgetstr();

        if (BeQuiet)
                return;
        o = old;
        n = new;

        if (o == n && n == def)
                return;
        prs(name);
        if (o == n)
                prs(" is ");
        else
                prs(" set to ");
        bufp = buf;
        if (tgetstr("kb", &bufp) > (char *)0 && n == buf[0] && buf[1] == '\0')
                prs("Backspace\n");
        else if (n == 0177)
                prs("Delete\n");
        else {
                if (n < 040) {
                        prs("Ctrl-");
                        n ^= 0100;
                }
                p = "x\n";
                p[0] = n;
                prs(p);
        }
        flush();
}



void
setdelay(char *cap, struct delay dtab[], tcflag_t bits, tcflag_t *flags)
{
        int             i;
        struct delay    *p;
        extern short    ospeed;

        /* see if this capability exists at all */
        i = tgetnum(cap);
        if (i < 0)
                i = 0;
        /* No padding at speeds below PadBaud */
        if (PadBaud > ospeed)
                i = 0;

        /* clear out the bits, replace with new ones */
        *flags &= ~bits;

        /* scan dtab for first entry with adequate delay */
        for (p = dtab; p->d_delay >= 0; p++) {
                if (p->d_delay >= i) {
                        p++;
                        break;
                }
        }

        /* use last entry if none will do */
        *flags |= (tcflag_t)((--p)->d_bits);
}

void
prs(char *s)
{
        while (*s != '\0')
                prc(*s++);
}


char    OutBuf[256];
int     OutPtr;

void
prc(char c)
{
        OutBuf[OutPtr++] = c;
        if (OutPtr >= sizeof (OutBuf))
                flush();
}

void
flush(void)
{
        if (OutPtr > 0)
                (void) write(2, OutBuf, OutPtr);
        OutPtr = 0;
}

void
cat(char *file)
{
        int     fd;
        int     i;
        char            buf[BUFSIZ];

        fd = open(file, 0);
        if (fd < 0) {
                prs("Cannot open ");
                prs(file);
                prs("\n");
                flush();
                return;
        }

        while ((i = read(fd, buf, BUFSIZ)) > 0)
                (void) write(FILEDES, buf, i);

        (void) close(fd);
}


void
bmove(char *from, char *to, int length)
{
        char    *p, *q;
        int     i;

        i = length;
        p = from;
        q = to;

        while (i-- > 0)
                *q++ = *p++;
}


int
bequal(char *a, char *b, int len)       /* must be same thru len chars */
{
        char    *p, *q;
        int     i;

        i = len;
        p = a;
        q = b;

        while ((*p == *q) && --i > 0) {
                p++; q++;
        }
        return ((*p == *q) && i >= 0);
}

int
sequal(char *a, char *b)        /* must be same thru NULL */
{
        char *p = a, *q = b;

        while (*p && *q && (*p == *q)) {
                p++; q++;
        }
        return (*p == *q);
}

void
makealias(char *buf)
{
        int i;
        char *a;
        char *b;

        Alias[0] = a = Aliasbuf;
        b = buf;
        i = 1;
        while (*b && *b != ':') {
                if (*b == '|') {
                        *a++ = '\0';
                        Alias[i++] = a;
                        b++;
                } else
                        *a++ = *b++;
        }
        *a = '\0';
        Alias[i] = NULL;
#ifdef  DEB
        for (i = 0; Alias[i]; printf("A:%s\n", Alias[i++]))
                ;
#endif
}

int
isalias(char *ident)    /* is ident same as one of the aliases? */
{
        char **a = Alias;

        if (*a)
                while (*a)
                        if (sequal(ident, *a))
                                return (YES);
                        else
                                a++;
        return (NO);
}


/*
 * routine to output the string for the environment TERMCAP variable
 */
#define WHITE(c)        (c == ' ' || c == '\t')
char delcap[128][2];
int ncap = 0;

void
wrtermcap(char *bp)
{
        char buf[CAPBUFSIZ];
        char *p = buf;
        char *tp;
        char *putbuf();
        int space, empty;

        /* discard names with blanks */
/* May not be desireable ? */
        while (*bp && *bp != ':') {
                if (*bp == '|') {
                        tp = bp+1;
                        space = NO;
                        while (*tp && *tp != '|' && *tp != ':') {
                                space = (space || WHITE(*tp));
                                tp++;
                        }
                        if (space) {
                                bp = tp;
                                continue;
                        }
                }
                *p++ = *bp++;
        }

        while (*bp) {
                switch (*bp) {
                case ':':       /* discard empty, cancelled  or dupl fields */
                        tp = bp + 1;
                        empty = YES;
                        while (*tp && *tp != ':') {
                                empty = (empty && WHITE(*tp));
                                tp++;
                        }
                        if (empty || cancelled(bp+1)) {
                                bp = tp;
                                continue;
                        }
                        break;

                case ' ':       /* no spaces in output */
                        p = putbuf(p, "\\040");
                        bp++;
                        continue;

                case '!':       /* the shell thinks this is history */
                        p = putbuf(p, "\\041");
                        bp++;
                        continue;

                case ',':       /* the shell thinks this is history */
                        p = putbuf(p, "\\054");
                        bp++;
                        continue;

                case '"':       /* no quotes in output */
                        p = putbuf(p, "\\042");
                        bp++;
                        continue;

                case '\'':      /* no quotes in output */
                        p = putbuf(p, "\\047");
                        bp++;
                        continue;

                case '`':       /* no back quotes in output */
                        p = putbuf(p, "\\140");
                        bp++;
                        continue;

                case '\\':
                case '^':       /* anything following is OK */
                        *p++ = *bp++;
                }
                *p++ = *bp++;
        }
        *p++ = ':';     /* we skipped the last : with the : lookahead hack */
        (void) write(STDOUT, buf, p-buf);
}

int
cancelled(char *cap)
{
        int i;

        for (i = 0; i < ncap; i++) {
                if (cap[0] == delcap[i][0] && cap[1] == delcap[i][1])
                        return (YES);
        }
        /* delete a second occurrance of the same capability */
        delcap[ncap][0] = cap[0];
        delcap[ncap][1] = cap[1];
        ncap++;
        return (cap[2] == '@');
}

char *
putbuf(ptr, str)
char    *ptr;
char    *str;
{
        char buf[20];

        while (*str) {
                switch (*str) {
                case '\033':
                        ptr = putbuf(ptr, "\\E");
                        str++;
                        break;
                default:
                        if (*str <= ' ') {
                                (void) sprintf(buf, "\\%03o", *str);
                                ptr = putbuf(ptr, buf);
                                str++;
                        } else
                                *ptr++ = *str++;
                }
        }
        return (ptr);
}

int
baudrate(char *p)
{
        char buf[8];
        int i = 0;

        while (i < 7 && (isalnum(*p) || *p == '.'))
                buf[i++] = *p++;
        buf[i] = '\0';
        for (i = 0; speeds[i].string; i++)
                if (sequal(speeds[i].string, buf))
                        return (speeds[i].speed);
        return (-1);
}

char *
mapped(type)
char    *type;
{
        extern short    ospeed;
        int     match;

#ifdef DEB
        printf("spd:%d\n", ospeed);
        prmap();
#endif
        Map = map;
        while (Map->Ident) {
                if (*(Map->Ident) == '\0' ||
                    sequal(Map->Ident, type) || isalias(Map->Ident)) {
                        match = NO;
                        switch (Map->Test) {
                        case ANY:       /* no test specified */
                        case ALL:
                                match = YES;
                                break;

                        case GT:
                                match = (ospeed > Map->Speed);
                                break;

                        case GE:
                                match = (ospeed >= Map->Speed);
                                break;

                        case EQ:
                                match = (ospeed == Map->Speed);
                                break;

                        case LE:
                                match = (ospeed <= Map->Speed);
                                break;

                        case LT:
                                match = (ospeed < Map->Speed);
                                break;

                        case NE:
                                match = (ospeed != Map->Speed);
                                break;
                        }
                        if (match)
                                return (Map->Type);
                }
                Map++;
        }
        /* no match found; return given type */
        return (type);
}

#ifdef DEB
prmap()
{
        Map = map;
        while (Map->Ident) {
                printf("%s t:%d s:%d %s\n",
                    Map->Ident, Map->Test, Map->Speed, Map->Type);
                Map++;
        }
}
#endif

char *
nextarg(argc, argv)
int     argc;
char    *argv[];
{
        if (argc <= 0)
                fatal("Too few args: ", *argv);
        if (*(*++argv) == '-')
                fatal("Unexpected arg: ", *argv);
        return (*argv);
}

void
fatal(char *mesg, char *obj)
{
        prs(mesg);
        prs(obj);
        prc('\n');
        prs(USAGE);
        flush();
        exit(1);
}


/*
 * Stolen from /usr/src/ucb/reset.c, which this mod obsoletes.
 */
char
reset(ch, def)
        char ch;
        int def;
{
        if (ch == 0 || (ch&0377) == 0377)
                return (def);
        return (ch);
}