root/usr/src/cmd/ed/ed.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
 */

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

/*
 * Copyright 2025 OmniOS Community Edition (OmniOSce) Association.
 */

/*
 * Editor
 */

#include        <crypt.h>
#include        <libgen.h>
#include        <wait.h>
#include        <string.h>
#include        <sys/types.h>
#include        <locale.h>
#include        <regexpr.h>
#include        <regex.h>
#include        <errno.h>
#include        <paths.h>

static const    char    *msgtab[] =
{
        "write or open on pipe failed",                 /*  0 */
        "warning: expecting `w'",                       /*  1 */
        "mark not lower case ascii",                    /*  2 */
        "Cannot open input file",                       /*  3 */
        "PWB spec problem",                             /*  4 */
        "nothing to undo",                              /*  5 */
        "restricted shell",                             /*  6 */
        "cannot create output file",                    /*  7 */
        "filesystem out of space!",                     /*  8 */
        "cannot open file",                             /*  9 */
        "cannot link",                                  /* 10 */
        "Range endpoint too large",                     /* 11 */
        "unknown command",                              /* 12 */
        "search string not found",                      /* 13 */
        "-",                                            /* 14 */
        "line out of range",                            /* 15 */
        "bad number",                                   /* 16 */
        "bad range",                                    /* 17 */
        "Illegal address count",                        /* 18 */
        "incomplete global expression",                 /* 19 */
        "illegal suffix",                               /* 20 */
        "illegal or missing filename",                  /* 21 */
        "no space after command",                       /* 22 */
        "fork failed - try again",                      /* 23 */
        "maximum of 64 characters in file names",       /* 24 */
        "`\\digit' out of range",                       /* 25 */
        "interrupt",                                    /* 26 */
        "line too long",                                /* 27 */
        "illegal character in input file",              /* 28 */
        "write error",                                  /* 29 */
        "out of memory for append",                     /* 30 */
        "temp file too big",                            /* 31 */
        "I/O error on temp file",                       /* 32 */
        "multiple globals not allowed",                 /* 33 */
        "global too long",                              /* 34 */
        "no match",                                     /* 35 */
        "illegal or missing delimiter",                 /* 36 */
        "-",                                            /* 37 */
        "replacement string too long",                  /* 38 */
        "illegal move destination",                     /* 39 */
        "-",                                            /* 40 */
        "no remembered search string",                  /* 41 */
        "'\\( \\)' imbalance",                          /* 42 */
        "Too many `\\(' s",                             /* 43 */
        "more than 2 numbers given",                    /* 44 */
        "'\\}' expected",                               /* 45 */
        "first number exceeds second",                  /* 46 */
        "incomplete substitute",                        /* 47 */
        "newline unexpected",                           /* 48 */
        "'[ ]' imbalance",                              /* 49 */
        "regular expression overflow",                  /* 50 */
        "regular expression error",                     /* 51 */
        "command expected",                             /* 52 */
        "a, i, or c not allowed in G",                  /* 53 */
        "end of line expected",                         /* 54 */
        "no remembered replacement string",             /* 55 */
        "no remembered command",                        /* 56 */
        "illegal redirection",                          /* 57 */
        "possible concurrent update",                   /* 58 */
        "-",                                            /* 59 */
        "the x command has become X (upper case)",      /* 60 */
        "Warning: 'w' may destroy input file "
        "(due to `illegal char' read earlier)",
                                                        /* 61 */
        "Caution: 'q' may lose data in buffer;"
        " 'w' may destroy input file",
                                                        /* 62 */
        "Encryption of string failed",                  /* 63 */
        "Encryption facility not available",            /* 64 */
        "Cannot encrypt temporary file",                /* 65 */
        "Enter key:",                                   /* 66 */
        "Illegal byte sequence",                        /* 67 */
        "File does not exist",                          /* 68 */
        "tempnam failed",                               /* 69 */
        "Cannot open temporary file",                   /* 70 */
        0
};

#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <termio.h>
#include <ctype.h>
#include <setjmp.h>
#include <fcntl.h>
#include <wchar.h>      /* I18N */
#include <wctype.h>     /* I18N */
#include <widec.h>      /* I18N */

#define FTYPE(A)        (A.st_mode)
#define FMODE(A)        (A.st_mode)
#define IDENTICAL(A, B) (A.st_dev == B.st_dev && A.st_ino == B.st_ino)
#define ISBLK(A)        ((A.st_mode & S_IFMT) == S_IFBLK)
#define ISCHR(A)        ((A.st_mode & S_IFMT) == S_IFCHR)
#define ISDIR(A)        ((A.st_mode & S_IFMT) == S_IFDIR)
#define ISFIFO(A)       ((A.st_mode & S_IFMT) == S_IFIFO)
#define ISREG(A)        ((A.st_mode & S_IFMT) == S_IFREG)

#define PUTM()  if (xcode >= 0) puts(gettext(msgtab[xcode]))
#define UNGETC(c)       (peekc = c)
#define FNSIZE  PATH_MAX
#define LBSIZE  LINE_MAX

/* size of substitution replacement pattern buffer */
#define RHSIZE  (LINE_MAX*2)

#define KSIZE   8

#define READ    0
#define WRITE   1

extern  char    *optarg;        /* Value of argument */
extern  int     optind;         /* Indicator of argument */

struct  Fspec   {
        char    Ftabs[22];
        char    Fdel;
        unsigned char   Flim;
        char    Fmov;
        char    Ffill;
};
static struct  Fspec   fss;

static char     *fsp;
static int      fsprtn;
static char     line[70];
static char     *linp = line;
static int      sig;
static int      Xqt = 0;
static int      lastc;
static char     savedfile[FNSIZE];
static char     file[FNSIZE];
static char     funny[FNSIZE];
static int      funlink = 0;
static char     linebuf[LBSIZE];
static char     *tstring = linebuf;

static char *expbuf;

static char     rhsbuf[RHSIZE];
struct  lin     {
        long cur;
        long sav;
};
typedef struct lin *LINE;
static LINE     zero;
static LINE     dot;
static LINE     dol;
static LINE     endcore;
static LINE     fendcore;
static LINE     addr1;
static LINE     addr2;
static LINE     savdol, savdot;
static int      globflg;
static int      initflg;
static char     genbuf[LBSIZE];
static long     count;
static int      numpass;        /* Number of passes thru dosub(). */
static int      gsubf;          /* Occurrence value. LBSIZE-1=all. */
static int      ocerr1; /* Allows lines NOT changed by dosub() to NOT be put */
                        /* out. Retains last line changed as current line. */
static int      ocerr2; /* Flags if ANY line changed by substitute(). 0=nc. */
static char     *nextip;
static char     *linebp;
static int      ninbuf;
static int      peekc;
static int      io;
static void     (*oldhup)(), (*oldintr)();
static void     (*oldquit)(), (*oldpipe)();
static void     quit(int) __NORETURN;
static int      vflag = 1;
static int      xflag;
static int      xtflag;
static int      kflag;
static int      crflag;
                /* Flag for determining if file being read is encrypted */
static int      hflag;
static int      xcode = -1;
static char     crbuf[LBSIZE];
static  int     perm[2];
static int      tperm[2];
static int      permflag;
static int      tpermflag;
static int      col;
static char     *globp;
static int      tfile = -1;
static int      tline;
static char     *tfname;
extern char     *locs;
static char     ibuff[LBSIZE];
static int      iblock = -1;
static char     obuff[LBSIZE];
static int      oblock = -1;
static int      ichanged;
static int      nleft;
static long     savnames[26], names[26];
static int      anymarks;
static long     subnewa;
static int      fchange;
static int      nline;
static int      fflg, shflg;
static char     prompt[16] = "*";
static int      rflg;
static int      readflg;
static int      eflg;
static int      qflg = 0;
static int      ncflg;
static int      listn;
static int      listf;
static int      pflag;
static int      flag28 = 0; /* Prevents write after a partial read */
static int      save28 = 0; /* Flag whether buffer empty at start of read */
static long     savtime;
static char     *name = "SHELL";
static char     *rshell = "/usr/lib/rsh";
static char     *val;
static char     *home;
static int      nodelim;

int     makekey(int *);
int     _mbftowc(char *, wchar_t *, int (*)(), int *);
static int      error(int code);
static void     tlist(struct Fspec *);
static void     tstd(struct Fspec *);
static void     gdelete(void);
static void     delete(void);
static void     exfile(void);
static void     filename(int comm);
static void     newline(void);
static int      gettty(void);
static void     commands(void);
static void     undo(void);
static void     save(void);
static void     strcopy(char *source, char *dest);
static int      strequal(char **scan1, char *str);
static int      stdtab(char *, char *);
static int      lenchk(char *, struct Fspec *);
static void     clear(struct Fspec *);
static int      expnd(char *, char *, int *, struct Fspec *);
static void     tincr(int, struct Fspec *);
static void     targ(struct Fspec *);
static int      numb(void);
static int      fspec(char *, struct Fspec *, int);
static void     red(char *);
static void     newtime(void);
static void     chktime(void);
static void     getime(void);
static void     mkfunny(void);
static int      eopen(char *, int);
static void     eclose(int f);
static void     globaln(int);
static char     *getkey(const char *);
static int      execute(int, LINE);
static void     error1(int);
static int      getcopy(void);
static void     move(int);
static void     dosub(void);
static int      getsub(void);
static int      compsub(void);
static void     substitute(int);
static void     join(void);
static void     global(int);
static void     init(void);
static void     rdelete(LINE, LINE);
static void     append(int (*)(void), LINE);
static int      getfile(void);
static void     putfile(void);
static void     onpipe(int);
static void     onhup(int);
static void     onintr(int);
static void     setdot(void);
static void     setall(void);
static void     setnoaddr(void);
static void     nonzero(void);
static void     setzeroasone(void);
static long     putline(void);
static LINE     address(void);
static char     *getaline(long);
static char     *getblock(long, long);
static char     *place(char *, char *, char *);
static void     comple(wchar_t);
static void     putchr(unsigned char);
static void     putwchr(wchar_t);
static int      getchr(void);
static void     unixcom(void);
static void     blkio(int, char *, ssize_t (*)());
static void     reverse(LINE, LINE);
static void     putd();
static wchar_t  get_wchr(void);

static struct stat      Fl, Tf;
#ifndef RESEARCH
static struct statvfs   U;
static int      Short = 0;
static mode_t   oldmask; /* No umask while writing */
#endif
static jmp_buf  savej;

#ifdef  NULLS
int     nulls;  /* Null count */
#endif
static long     ccount;

static int      errcnt = 0;


static void
onpipe(int sig)
{
        (int)error(0);
}

int
main(int argc, char **argv)
{
        char *p1, *p2;
        int c;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
#endif
        (void) textdomain(TEXT_DOMAIN);

        oldquit = signal(SIGQUIT, SIG_IGN);
        oldhup = signal(SIGHUP, SIG_IGN);
        oldintr = signal(SIGINT, SIG_IGN);
        oldpipe = signal(SIGPIPE, onpipe);
        if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
                signal(SIGTERM, quit);
        p1 = *argv;
        while (*p1++)
                ;
        while (--p1 >= *argv)
                if (*p1 == '/')
                        break;
        *argv = p1 + 1;
        /* if SHELL set in environment and is /usr/lib/rsh, set rflg */
        if ((val = getenv(name)) != NULL)
                if (strcmp(val, rshell) == 0)
                        rflg++;
        if (**argv == 'r')
                rflg++;
        home = getenv("HOME");
        while (1) {
                while ((c = getopt(argc, argv, "sp:qxC")) != EOF) {
                        switch (c) {

                        case 's':
                                vflag = 0;
                                break;

                        case 'p':
                                strncpy(prompt, optarg, sizeof (prompt)-1);
                                shflg = 1;
                                break;

                        case 'q':
                                signal(SIGQUIT, SIG_DFL);
                                vflag = 1;
                                break;

                        case 'x':
                                crflag = -1;
                                xflag = 1;
                                break;

                        case 'C':
                                crflag = 1;
                                xflag = 1;
                                break;

                        case '?':
                                (void) fprintf(stderr, gettext(
                "Usage:   ed [- | -s] [-p string] [-x] [-C] [file]\n"
                "         red [- | -s] [-p string] [-x] [-C] [file]\n"));
                                exit(2);
                        }
                }
                if (argv[optind] && strcmp(argv[optind], "-") == 0 &&
                    strcmp(argv[optind-1], "--") != 0) {
                        vflag = 0;
                        optind++;
                        continue;
                }
                break;
        }
        argc = argc - optind;
        argv = &argv[optind];

        if (xflag) {
                if (permflag)
                        crypt_close(perm);
                permflag = 1;
                kflag = run_setkey(&perm[0], getkey(msgtab[66]));
                if (kflag == -1) {
                        puts(gettext(msgtab[64]));
                        xflag = 0;
                        kflag = 0;
                }
                if (kflag == 0)
                        crflag = 0;
        }

        if (argc > 0) {
                p1 = *argv;
                if (strlen(p1) >= (size_t)FNSIZE) {
                        puts(gettext("file name too long"));
                        if (kflag)
                                crypt_close(perm);
                        exit(2);
                }
                p2 = savedfile;
                while (*p2++ = *p1++)
                        ;
                globp = "e";
                fflg++;
        } else  /* editing with no file so set savtime to 0 */
                savtime = 0;
        eflg++;
        if ((tfname = tempnam("", "ea")) == NULL) {
                puts(gettext(msgtab[69]));
                exit(2);
        }

        fendcore = (LINE)sbrk(0);
        init();
        if (oldintr != SIG_IGN)
                signal(SIGINT, onintr);
        if (oldhup != SIG_IGN)
                signal(SIGHUP, onhup);
        setjmp(savej);
        commands();
        quit(sig);
        return (0);
}

static void
commands(void)
{
        LINE a1;
        int c;
        char *p1, *p2;
        int fsave, m, n;

        for (;;) {
        nodelim = 0;
        if (pflag) {
                pflag = 0;
                addr1 = addr2 = dot;
                goto print;
        }
        if (shflg && globp == 0)
                write(1, gettext(prompt), strlen(gettext(prompt)));
        addr1 = 0;
        addr2 = 0;
        if ((c = getchr()) == ',') {
                addr1 = zero + 1;
                addr2 = dol;
#ifdef XPG6
        /* XPG4 - it was an error if the second address was */
        /* input and the first address was ommitted     */
        /* Parse second address */
                if ((a1 = address()) != 0) {
                        addr2 = a1;
                }
#endif
                c = getchr();
                goto swch;
        } else if (c == ';') {
                addr1 = dot;
                addr2 = dol;
#ifdef XPG6
        /* XPG4 - it was an error if the second address was */
        /* input and the first address was ommitted     */
        /* Parse second address */
                if ((a1 = address()) != 0) {
                        addr2 = a1;
                }
#endif
                c = getchr();
                goto swch;
        } else
                peekc = c;
        do {
                addr1 = addr2;
                if ((a1 = address()) == 0) {
                        c = getchr();
                        break;
                }
                addr2 = a1;
                if ((c = getchr()) == ';') {
                        c = ',';
                        dot = a1;
                }
        } while (c == ',');
        if (addr1 == 0)
                addr1 = addr2;
swch:
        switch (c) {

        case 'a':
                setdot();
                newline();
                if (!globflg) save();
                append(gettty, addr2);
                continue;

        case 'c':
#ifdef XPG6
                setzeroasone();
#endif
                delete();
                append(gettty, addr1-1);

                /* XPG4 - If no new lines are inserted, then the current */
                /* line becomes the line after the lines deleted. */

                if (((linebuf[0] != '.') || (dot == (addr1-1))) &&
                    (addr2 <= dol))
                        dot = addr1;
                continue;

        case 'd':
                delete();
                continue;

        case 'E':
                fchange = 0;
                c = 'e';
                /* FALLTHROUGH */
        case 'e':
                fflg++;
                setnoaddr();
                if (vflag && fchange) {
                        fchange = 0;
                        (void) error(1);
                }
                filename(c);
                eflg++;
                init();
                addr2 = zero;
                goto caseread;

        case 'f':
                setnoaddr();
                filename(c);
                if (!ncflg)  /* there is a filename */
                        getime();
                else
                        ncflg--;
                puts(savedfile);
                continue;

        case 'g':
                global(1);
                continue;
        case 'G':
                globaln(1);
                continue;

        case 'h':
                newline();
                setnoaddr();
                PUTM();
                continue;

        case 'H':
                newline();
                setnoaddr();
                if (!hflag) {
                        hflag = 1;
                        PUTM();
                }
                else
                        hflag = 0;
                continue;

        case 'i':
#ifdef XPG6
                setzeroasone();
#endif
                setdot();
                nonzero();
                newline();
                if (!globflg) save();
                append(gettty, addr2-1);
                if (dot == addr2-1)
                        dot += 1;
                continue;

        case 'j':
                if (addr2 == 0) {
                        addr1 = dot;
                        addr2 = dot+1;
                }
                setdot();
                newline();
                nonzero();
                if (!globflg) save();
                join();
                continue;

        case 'k':
                if ((c = getchr()) < 'a' || c > 'z')
                        (void) error(2);
                newline();
                setdot();
                nonzero();
                names[c-'a'] = addr2->cur & ~01;
                anymarks |= 01;
                continue;

        case 'm':
                move(0);
                continue;

        case '\n':
                if (addr2 == 0)
                        addr2 = dot+1;
                addr1 = addr2;
                goto print;

        case 'n':
                listn++;
                newline();
                goto print;

        case 'l':
                listf++;
                /* FALLTHROUGH */
        case 'p':
                newline();
        print:
                setdot();
                nonzero();
                a1 = addr1;
                do {
                        if (listn) {
                                count = a1 - zero;
                                putd();
                                putchr('\t');
                        }
                        puts(getaline((a1++)->cur));
                } while (a1 <= addr2);
                dot = addr2;
                pflag = 0;
                listn = 0;
                listf = 0;
                continue;

        case 'Q':
                fchange = 0;
                /* FALLTHROUGH */
        case 'q':
                setnoaddr();
                newline();
                quit(sig);

        case 'r':
                filename(c);
        caseread:
                readflg = 1;
                save28 = (dol != fendcore);
                if (crflag == 2 || crflag == -2)
                        crflag = -1; /* restore crflag for next file */
                errno = 0;
                if ((io = eopen(file, O_RDONLY)) < 0) {
                        lastc = '\n';
                        /* if first entering editor and file does not exist */
                        /* set saved access time to 0 */
                        if (eflg) {
                                savtime = 0;
                                eflg  = 0;
                                if (c == 'e' && vflag == 0)
                                        qflg = 1;
                        }
                        if (errno == ENOENT) {
                                (void) error(68);
                        } else {
                                (void) error(3);
                        }
                }
                /* get last mod time of file */
                /* eflg - entered editor with ed or e  */
                if (eflg) {
                        eflg = 0;
                        getime();
                }
                setall();
                ninbuf = 0;
                n = zero != dol;
#ifdef NULLS
                nulls = 0;
#endif
                if (!globflg && (c == 'r')) save();
                append(getfile, addr2);
                exfile();
                readflg = 0;
                fchange = n;
                continue;

        case 's':
                setdot();
                nonzero();
                if (!globflg) save();
                substitute(globp != 0);
                continue;

        case 't':
                move(1);
                continue;

        case 'u':
                setdot();
                newline();
                if (!initflg)
                        undo();
                else
                        (void) error(5);
                fchange = 1;
                continue;

        case 'v':
                global(0);
                continue;
        case 'V':
                globaln(0);
                continue;

        case 'W':
        case 'w':
                if (flag28) {
                        flag28 = 0;
                        fchange = 0;
                        (void) error(61);
                }
                setall();

                /* on NULL-RE condition do not generate error */

                if ((linebuf[0] != '.') && (zero != dol) &&
                    (addr1 <= zero || addr2 > dol))
                        (void) error(15);
                filename(c);
                if (Xqt) {
                        io = eopen(file, O_WRONLY);
                        n = 1;  /* set n so newtime will not execute */
                } else {
                        struct stat lFl;
                        fstat(tfile, &Tf);
                        if (stat(file, &Fl) < 0) {
                                if ((io = creat(file, S_IRUSR|S_IWUSR|S_IRGRP
                                    |S_IWGRP|S_IROTH|S_IWOTH)) < 0)
                                        (void) error(7);
                                fstat(io, &Fl);
                                Fl.st_mtime = 0;
                                lFl = Fl;
                                close(io);
                        } else {
#ifndef RESEARCH
                                oldmask = umask(0);
                                /*
                                 * Must determine if file is
                                 * a symbolic link
                                 */
                                lstat(file, &lFl);
#endif
                        }
#ifndef RESEARCH
                        /*
                         * Determine if there are enough free blocks on system
                         */
                        if (!Short && statvfs(file, &U) == 0 &&
                            U.f_bfree < ((Tf.st_size/U.f_frsize) + 100)) {
                                Short = 1;
                                (void) error(8);
                        }
                        Short = 0;
#endif
                        p1 = savedfile;         /* The current filename */
                        p2 = file;
                        m = strcmp(p1, p2);
                        if (c == 'w' && Fl.st_nlink == 1 && ISREG(lFl)) {
                                if (close(open(file, O_WRONLY)) < 0)
                                        (void) error(9);
                                if (!(n = m))
                                        chktime();
                                mkfunny();
                                /*
                                 * If funlink equals one it means that
                                 * funny points to a valid file which must
                                 * be unlinked when interrupted.
                                 */

                                funlink = 1;
                                if ((io = creat(funny, FMODE(Fl))) >= 0) {
                                        chown(funny, Fl.st_uid, Fl.st_gid);
                                        chmod(funny, FMODE(Fl));
                                        putfile();
                                        exfile();

                                        if (rename(funny, file))
                                                (void) error(10);
                                        funlink = 0;
                                        /* if filenames are the same */
                                        if (!n)
                                                newtime();
                                        /* check if entire buffer was written */
                                        fsave = fchange;
                                        if (((addr1 == zero) ||
                                            (addr1 == (zero + 1))) &&
                                            (addr2 == dol))
                                                fchange = 0;
                                        else
                                                fchange = 1;
                                        if (fchange == 1 && m != 0)
                                                fchange = fsave;
                                        continue;
                                }
                        } else {
                                n = 1;  /* set n so newtime will not execute */
                        }
                        if ((io = open(file,
                            (c == 'w') ? O_WRONLY|O_CREAT|O_TRUNC
                            : O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR
                            |S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0)
                                (void) error(7);
                }
                putfile();
                exfile();
                if (!n)
                        newtime();
                fsave = fchange;
                fchange = (((addr1 == zero) || (addr1 == (zero + 1))) &&
                    (addr2 == dol)) ? 0 : 1;
        /* Leave fchange alone if partial write was to another file */
                if (fchange == 1 && m != 0) fchange = fsave;
                continue;

        case 'C':
                crflag = 1;
                        /*
                         * C is same as X, but always assume input files are
                         * ciphertext
                         */
                goto encrypt;

        case 'X':
                crflag = -1;
encrypt:
                setnoaddr();
                newline();
                xflag = 1;
                if (permflag)
                        (void) crypt_close(perm);
                permflag = 1;
                if ((kflag = run_setkey(&perm[0], getkey(msgtab[66]))) == -1) {
                        xflag = 0;
                        kflag = 0;
                        crflag = 0;
                        (void) error(64);
                }
                if (kflag == 0)
                        crflag = 0;
                continue;

        case '=':
                setall();
                newline();
                count = (addr2-zero)&077777;
                putd();
                putchr('\n');
                continue;

        case '!':
                unixcom();
                continue;

        case EOF:
                return;

        case 'P':
                setnoaddr();
                newline();
                if (shflg)
                        shflg = 0;
                else
                        shflg++;
                continue;
        }
        if (c == 'x')
                (void) error(60);
        else
                (void) error(12);
        }
}

LINE
address(void)
{
        int minus, c;
        LINE a1;
        int n, relerr, retval;

        minus = 0;
        a1 = 0;
        for (;;) {
                c = getchr();
                if ('0' <= c && c <= '9') {
                        n = 0;
                        do {
                                n *= 10;
                                n += c - '0';
                        } while ((c = getchr()) >= '0' && c <= '9');
                        peekc = c;
                        if (a1 == 0)
                                a1 = zero;
                        if (minus < 0)
                                n = -n;
                        a1 += n;
                        minus = 0;
                        continue;
                }
                relerr = 0;
                if (a1 || minus)
                        relerr++;
                switch (c) {
                case ' ':
                case '\t':
                        continue;

                case '+':
                        minus++;
                        if (a1 == 0)
                                a1 = dot;
                        continue;

                case '-':
                case '^':
                        minus--;
                        if (a1 == 0)
                                a1 = dot;
                        continue;

                case '?':
                case '/':
                        comple(c);
                        a1 = dot;
                        for (;;) {
                                if (c == '/') {
                                        a1++;
                                        if (a1 > dol)
                                                a1 = zero;
                                } else {
                                        a1--;
                                        if (a1 < zero)
                                                a1 = dol;
                                }

                                if (execute(0, a1))
                                        break;
                                if (a1 == dot)
                                        (void) error(13);
                        }
                        break;

                case '$':
                        a1 = dol;
                        break;

                case '.':
                        a1 = dot;
                        break;

                case '\'':
                        if ((c = getchr()) < 'a' || c > 'z')
                                (void) error(2);
                        for (a1 = zero; a1 <= dol; a1++)
                                if (names[c-'a'] == (a1->cur & ~01))
                                        break;
                        break;

                default:
                        peekc = c;
                        if (a1 == 0)
                                return (0);
                        a1 += minus;

                        /* on NULL-RE condition do not generate error */

                        if ((linebuf[0] != '.') && (a1 < zero || a1 > dol))
                                (void) error(15);
                        return (a1);
                }
                if (relerr)
                        (void) error(16);
        }
}

static void
setdot(void)
{
        if (addr2 == 0)
                addr1 = addr2 = dot;
        if (addr1 > addr2)
                (void) error(17);
}

static void
setall(void)
{
        if (addr2 == 0) {
                addr1 = zero+1;
                addr2 = dol;
                if (dol == zero)
                        addr1 = zero;
        }
        setdot();
}

static void
setnoaddr(void)
{
        if (addr2)
                (void) error(18);
}

static void
nonzero(void)
{
        /* on NULL-RE condition do not generate error */

        if ((linebuf[0] != '.') && (addr1 <= zero || addr2 > dol))
                (void) error(15);
}

static void
setzeroasone(void)
{
/* for the c and i commands 0 equal to 1 address */
        if (addr1 == zero) {
                addr1 = zero+1;
        }
        if (addr2 == zero) {
                addr2 = zero+1;
        }
}


static void
newline(void)
{
        int c;

        if ((c = getchr()) == '\n')
                return;
        if (c == 'p' || c == 'l' || c == 'n') {
                pflag++;
                if (c == 'l') listf++;
                if (c == 'n') listn++;
                if ((c = getchr()) == '\n')
                        return;
        }
        (void) error(20);
}

static void
filename(int comm)
{
        char *p1, *p2;
        int c;
        int i = 0;

        count = 0;
        c = getchr();
        if (c == '\n' || c == EOF) {
                p1 = savedfile;
                if (*p1 == 0 && comm != 'f')
                        (void) error(21);
                /* ncflg set means do not get mod time of file */
                /* since no filename followed f */
                if (comm == 'f')
                        ncflg++;
                p2 = file;
                while (*p2++ = *p1++)
                        ;
                red(savedfile);
                return;
        }
        if (c != ' ')
                (void) error(22);
        while ((c = getchr()) == ' ')
                ;
        if (c == '!')
                ++Xqt, c = getchr();
        if (c == '\n')
                (void) error(21);
        p1 = file;
        do {
                if (++i >= FNSIZE)
                        (void) error(24);
                *p1++ = c;
                if (c == EOF || (c == ' ' && !Xqt))
                        (void) error(21);
        } while ((c = getchr()) != '\n');
        *p1++ = 0;
        if (Xqt)
                if (comm == 'f') {
                        --Xqt;
                        (void) error(57);
                }
                else
                        return;
        if (savedfile[0] == 0 || comm == 'e' || comm == 'f') {
                p1 = savedfile;
                p2 = file;
                while (*p1++ = *p2++)
                        ;
        }
        red(file);
}


static void
exfile(void)
{
#ifdef NULLS
        int c;
#endif

#ifndef RESEARCH
        if (oldmask) {
                umask(oldmask);
                oldmask = 0;
        }
#endif
        eclose(io);
        io = -1;
        if (vflag) {
                putd();
                putchr('\n');
#ifdef NULLS
                if (nulls) {
                        c = count;
                        count = nulls;
                        nulls = 0;
                        putd();
                        puts(gettext(" nulls replaced by '\\0'"));
                        count = c;
                }
#endif
        }
}

static void
onintr(int sig)
{
        signal(SIGINT, onintr);
        putchr('\n');
        lastc = '\n';
        globflg = 0;
        if (funlink) unlink(funny); /* remove tmp file */
        /* if interrupted a read, only part of file may be in buffer */
        if (readflg) {
                sprintf(tstring, "\007read may be incomplete - beware!\007");
                puts(gettext(tstring));
                fchange = 0;
        }
        (void) error(26);
}

static void
onhup(int sig)
{
        signal(SIGINT, SIG_IGN);
        signal(SIGHUP, SIG_IGN);
        /*
         * if there are lines in file and file was not written
         * since last update, save in ed.hup, or $HOME/ed.hup
         */
        if (dol > zero && fchange == 1) {
                addr1 = zero+1;
                addr2 = dol;
                io = creat("ed.hup",
                    S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
                if (io < 0 && home) {
                        char    *fn;

                        fn = (char *)calloc(strlen(home) + 8, sizeof (char));
                        if (fn) {
                                strcpy(fn, home);
                                strcat(fn, "/ed.hup");
                                io = creat(fn, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP
                                    |S_IROTH|S_IWOTH);
                                free(fn);
                        }
                }
                if (io > 0)
                        putfile();
        }
        fchange = 0;
        ++errcnt;
        quit(sig);
}

static int
error(int code)
{
        int c;

        if (code == 28 && save28 == 0) {
                fchange = 0;
                flag28++;
        }
        readflg = 0;
        ++errcnt;
        listf = listn = 0;
        pflag = 0;
#ifndef RESEARCH
        if (oldmask) {
                umask(oldmask);
                oldmask = 0;
        }
#endif
#ifdef NULLS    /* Not really nulls, but close enough */
        /* This is a bug because of buffering */
        if (code == 28) /* illegal char. */
                putd();
#endif
        /* Cant open file or file does not exist */
        if ((code == 3) || (code == 68)) {
                if (qflg == 0) {
                        putchr('?');
                        puts(file);
                }
                else
                        qflg = 0;
        }
        else
        {
                putchr('?');
                putchr('\n');
        }
        count = 0;
        lseek(0, (long)0, 2);
        if (globp)
                lastc = '\n';
        globp = 0;
        peekc = lastc;
        if (lastc)
                while ((c = getchr()) != '\n' && c != EOF)
                        ;
        if (io) {
                eclose(io);
                io = -1;
        }
        xcode = code;
        if (hflag)
                PUTM();
        if (code == 4)
                return (0);     /* Non-fatal error. */
        longjmp(savej, 1);
        /* NOTREACHED */
}

static int
getchr(void)
{
        char c;
        if (lastc = peekc) {
                peekc = 0;
                return (lastc);
        }
        if (globp) {
                if ((lastc = (unsigned char)*globp++) != 0)
                        return (lastc);
                globp = 0;
                return (EOF);
        }
        if (read(0, &c, 1) <= 0)
                return (lastc = EOF);
        lastc = (unsigned char)c;
        return (lastc);
}

static int
gettty(void)
{
        int c;
        char *gf;
        char *p;

        p = linebuf;
        gf = globp;
        while ((c = getchr()) != '\n') {
                if (c == EOF) {
                        if (gf)
                                peekc = c;
                        return (c);
                }
                if (c == 0)
                        continue;
                *p++ = c;

                if (p > &linebuf[LBSIZE-1])
                        (void) error(27);
        }
        *p++ = 0;
        if (linebuf[0] == '.' && linebuf[1] == 0)
                return (EOF);

        /*
         * POSIX.2/XPG4 explicitly says no to this:
         *
         * in Solaris backslash followed by special character "." is
         * special character "." itself; (so terminating input mode can be
         * "\.\n").
         *
         * however, POSIX2/XPG4 says, input mode is terminated by
         * entering line consisting of only 2 characters: ".\n"
         *
         * if (linebuf[0]=='\\' && linebuf[1]=='.' && linebuf[2]==0) {
         *      linebuf[0] = '.';
         *      linebuf[1] = 0;
         * }
         */
        return (0);
}

static int
getfile(void)
{
        char c;
        char *lp, *fp;

        lp = linebuf;
        fp = nextip;
        do {
                if (--ninbuf < 0) {
                        if ((ninbuf = read(io, genbuf, LBSIZE)-1) < 0)
                                if (lp > linebuf) {
                                        puts(gettext("'\\n' appended"));
                                        *genbuf = '\n';
                                }
                                else
                                        return (EOF);
                        if (crflag == -1) {
                                if (isencrypt(genbuf, ninbuf + 1))
                                        crflag = 2;
                                else
                                        crflag = -2;
                        }
                        fp = genbuf;
                        if (crflag > 0 &&
                            run_crypt(count, genbuf, ninbuf+1, perm) == -1) {
                                (void) error(63);
                        }
                }
                if (lp >= &linebuf[LBSIZE]) {
                        lastc = '\n';
                        (void) error(27);
                }
                if ((*lp++ = c = *fp++) == 0) {
#ifdef NULLS
                        lp[-1] = '\\';
                        *lp++ = '0';
                        nulls++;
#else
                        lp--;
                        continue;
#endif
                }
                count++;
        } while (c != '\n');
        *--lp = 0;
        nextip = fp;
        if (fss.Ffill && fss.Flim && lenchk(linebuf, &fss) < 0) {
                write(1, gettext("line too long: lno = "),
                    strlen(gettext("line too long: lno = ")));
                ccount = count;
                count = (++dot-zero)&077777;
                dot--;
                putd();
                count = ccount;
                putchr('\n');
        }
        return (0);
}

static void
putfile(void)
{
        int n;
        LINE a1;
        char *fp, *lp;
        int nib;

        nib = LBSIZE;
        fp = genbuf;
        a1 = addr1;
        do {
                if (a1 > endcore)
                        break;
                lp = getaline(a1++->cur);
                if (fss.Ffill && fss.Flim && lenchk(linebuf, &fss) < 0) {
                        write(1, gettext("line too long: lno = "),
                            strlen(gettext("line too long: lno = ")));
                        ccount = count;
                        count = (a1-zero-1)&077777;
                        putd();
                        count = ccount;
                        putchr('\n');
                }
                for (;;) {
                        if (--nib < 0) {
                                n = fp-genbuf;
                                if (kflag &&
                                    run_crypt(count-n, genbuf, n, perm) == -1) {
                                        (void) error(63);
                                }
                                if (write(io, genbuf, n) != n)
                                        (void) error(29);
                                nib = LBSIZE - 1;
                                fp = genbuf;
                        }
                        if (dol->cur == 0L)break; /* Allow write of null file */
                        count++;
                        if ((*fp++ = *lp++) == 0) {
                                fp[-1] = '\n';
                                break;
                        }
                }
        } while (a1 <= addr2);
        n = fp-genbuf;
        if (kflag)
                if (run_crypt(count-n, genbuf, n, perm) == -1)
                        (void) error(63);
        if (write(io, genbuf, n) != n)
                (void) error(29);
}

static void
append(int (*f)(void), LINE a)
{
        LINE a1, a2, rdot;
        long tl;

        nline = 0;
        dot = a;
        while ((*f)() == 0) {
                if (dol >= endcore) {
                        if ((int)sbrk(512 * sizeof (struct lin)) == -1) {
                                lastc = '\n';
                                (void) error(30);
                        }
                        endcore += 512;
                }
                tl = putline();
                nline++;
                a1 = ++dol;
                a2 = a1+1;
                rdot = ++dot;
                while (a1 > rdot)
                        (--a2)->cur = (--a1)->cur;
                rdot->cur = tl;
        }
}

static void
unixcom(void)
{
        void (*savint)();
        pid_t   pid, rpid;
        int retcode;
        static char savcmd[LBSIZE];     /* last command */
        char curcmd[LBSIZE];            /* current command */
        char *psavcmd, *pcurcmd, *psavedfile;
        int endflg = 1, shflg = 0;
        wchar_t c;
        int     len;

        setnoaddr();
        if (rflg)
                (void) error(6);
        pcurcmd = curcmd;
        /* read command til end */

        /*
         * a '!' found in beginning of command is replaced with the saved
         * command.  a '%' found in command is replaced with the current
         * filename
         */

        c = getchr();
        if (c == '!') {
                if (savcmd[0] == 0)
                        (void) error(56);
                else {
                        psavcmd = savcmd;
                        while (*pcurcmd++ = *psavcmd++)
                                ;
                        --pcurcmd;
                        shflg = 1;
                }
        } else
                UNGETC(c);  /* put c back */
        while (endflg == 1) {
                while ((c = get_wchr()) != '\n' && c != '%' && c != '\\') {
                        if ((len = wctomb(pcurcmd, c)) <= 0) {
                                *pcurcmd = (unsigned char)c;
                                len = 1;
                        }
                        pcurcmd += len;
                }

                if (c == '%') {
                        if (savedfile[0] == 0)
                                (void) error(21);
                        else {
                                psavedfile = savedfile;
                                while (pcurcmd < curcmd + LBSIZE &&
                                    (*pcurcmd++ = *psavedfile++))
                                        ;
                                --pcurcmd;
                                shflg = 1;
                        }
                } else if (c == '\\') {
                        c = get_wchr();
                        if (c != '%')
                                *pcurcmd++ = '\\';
                        if ((len = wctomb(pcurcmd, c)) <= 0) {
                                *pcurcmd = (unsigned char)c;
                                len = 1;
                        }
                        pcurcmd += len;
                }
                else
                        /* end of command hit */
                        endflg = 0;
        }
        *pcurcmd++ = 0;
        if (shflg == 1)
                puts(curcmd);
        /* save command */
        strcpy(savcmd, curcmd);

        if ((pid = fork()) == 0) {
                signal(SIGHUP, oldhup);
                signal(SIGQUIT, oldquit);
                close(tfile);
                execlp(_PATH_BSHELL, "sh", "-c", curcmd, NULL);
                exit(0100);
        }
        savint = signal(SIGINT, SIG_IGN);
        while ((rpid = wait(&retcode)) != pid && rpid != (pid_t)-1)
                ;
        signal(SIGINT, savint);
        if (vflag) puts("!");
}

static void
quit(int sig)
{
        if (vflag && fchange) {
                fchange = 0;
                if (flag28) {
                        flag28 = 0;
                        (void) error(62);
                }

                /*
                 * For case where user reads in BOTH a good
                 * file & a bad file
                 */
                (void) error(1);
        }
        unlink(tfname);
        if (kflag)
                crypt_close(perm);
        if (xtflag)
                crypt_close(tperm);
        exit(errcnt? 2: 0);
}

static void
delete(void)
{
        setdot();
        newline();
        nonzero();
        if (!globflg)
                save();
        rdelete(addr1, addr2);
}

static void
rdelete(LINE ad1, LINE ad2)
{
        LINE a1, a2, a3;

        a1 = ad1;
        a2 = ad2+1;
        a3 = dol;
        dol -= a2 - a1;
        do {
                (a1++)->cur = (a2++)->cur;
        } while (a2 <= a3);
        a1 = ad1;
        if (a1 > dol)
                a1 = dol;
        dot = a1;
        fchange = 1;
}

static void
gdelete(void)
{
        LINE a1, a2, a3;

        a3 = dol;
        for (a1 = zero+1; (a1->cur&01) == 0; a1++)
                if (a1 >= a3)
                        return;
        for (a2 = a1 + 1; a2 <= a3; ) {
                if (a2->cur & 01) {
                        a2++;
                        dot = a1;
                } else
                        (a1++)->cur = (a2++)->cur;
        }
        dol = a1-1;
        if (dot > dol)
                dot = dol;
        fchange = 1;
}

static char *
getaline(long tl)
{
        char *bp, *lp;
        int nl;

        lp = linebuf;
        bp = getblock(tl, READ);
        nl = nleft;
        tl &= ~0377;
        while (*lp++ = *bp++)
                if (--nl == 0) {
                        bp = getblock(tl += 0400, READ);
                        nl = nleft;
                }
        return (linebuf);
}

static long
putline(void)
{
        char *bp, *lp;
        int nl;
        long tl;

        fchange = 1;
        lp = linebuf;
        tl = tline;
        bp = getblock(tl, WRITE);
        nl = nleft;
        tl &= ~0377;
        while (*bp = *lp++) {
                if (*bp++ == '\n') {
                        *--bp = 0;
                        linebp = lp;
                        break;
                }
                if (--nl == 0) {
                        bp = getblock(tl += 0400, WRITE);
                        nl = nleft;
                }
        }
        nl = tline;
        tline += (((lp-linebuf)+03)>>1)&077776;
        return (nl);
}

static char *
getblock(long atl, long iof)
{
        int bno, off;
        char *p1, *p2;
        int n;

        bno = atl >> 8;
        off = (atl<<1)&0774;

        /* bno is limited to 16 bits */
        if (bno >= 65535) {
                lastc = '\n';
                (void) error(31);
        }
        nleft = 512 - off;
        if (bno == iblock) {
                ichanged |= iof;
                return (ibuff+off);
        }
        if (bno == oblock)
                return (obuff+off);
        if (iof == READ) {
                if (ichanged) {
                        if (xtflag)
                                if (run_crypt(0L, ibuff, 512, tperm) == -1)
                                        (void) error(63);
                        blkio(iblock, ibuff, write);
                }
                ichanged = 0;
                iblock = bno;
                blkio(bno, ibuff, read);
                if (xtflag)
                        if (run_crypt(0L, ibuff, 512, tperm) == -1)
                                (void) error(63);
                return (ibuff+off);
        }
        if (oblock >= 0) {
                if (xtflag) {
                        p1 = obuff;
                        p2 = crbuf;
                        n = 512;
                        while (n--)
                                *p2++ = *p1++;
                        if (run_crypt(0L, crbuf, 512, tperm) == -1)
                                (void) error(63);
                        blkio(oblock, crbuf, write);
                } else
                        blkio(oblock, obuff, write);
        }
        oblock = bno;
        return (obuff+off);
}

static void
blkio(int b, char *buf, ssize_t (*iofcn)())
{
        lseek(tfile, (long)b<<9, 0);
        if ((*iofcn)(tfile, buf, 512) != 512) {
                if (dol != zero)
                        (void) error(32); /* Bypass this if writing null file */
        }
}

static void
init(void)
{
        long *markp;
        mode_t omask;

        if (tfile != -1) {
                (void) close(tfile);
                (void) unlink(tfname);
        }

        tline = 2;
        for (markp = names; markp < &names[26]; )
                *markp++ = 0L;
        subnewa = 0L;
        anymarks = 0;
        iblock = -1;
        oblock = -1;
        ichanged = 0;
        initflg = 1;
        omask = umask(0);

        if ((tfile = open(tfname, O_CREAT|O_EXCL|O_RDWR,
            S_IRUSR|S_IWUSR)) < 0) {
                puts(gettext(msgtab[70]));
                exit(2);
        }

        umask(omask);
        if (xflag) {
                xtflag = 1;
                if (tpermflag)
                        (void) crypt_close(tperm);
                tpermflag = 1;
                if (makekey(tperm)) {
                        xtflag = 0;
                        puts(gettext(msgtab[65]));
                }
        }
        brk((char *)fendcore);
        dot = zero = dol = savdot = savdol = fendcore;
        flag28 = save28 = 0;
        endcore = fendcore - sizeof (struct lin);
}

static void
global(int k)
{
        char *gp;
        wchar_t l;
        char multic[MB_LEN_MAX];
        wchar_t c;
        LINE a1;
        char globuf[LBSIZE];
        int n;
        int     len;

        if (globp)
                (void) error(33);
        setall();
        nonzero();
        if ((n = _mbftowc(multic, &l, getchr, &peekc)) <= 0)
                (void) error(67);
        if (l == '\n')
                (void) error(19);
        save();
        comple(l);
        gp = globuf;
        while ((c = get_wchr()) != '\n') {
                if (c == EOF)
                        (void) error(19);

                /* '\\' has special meaning only if preceding a '\n' */
                if (c == '\\') {
                        c = get_wchr();
                        if (c != '\n')
                                *gp++ = '\\';
                }
                if ((gp + (unsigned int)MB_CUR_MAX) >= &globuf[LBSIZE-1])
                        (void) error(34);
                if ((len = wctomb(gp, c)) <= 0) {
                        *gp = (unsigned char)c;
                        len = 1;
                }
                gp += len;
        }
        if (gp == globuf)
                *gp++ = 'p';
        *gp++ = '\n';
        *gp++ = 0;
        for (a1 = zero; a1 <= dol; a1++) {
                a1->cur &= ~01;
                if (a1 >= addr1 && a1 <= addr2 && execute(0, a1) == k)
                        a1->cur |= 01;
        }
        /*
         * Special case: g/.../d (avoid n^2 algorithm)
         */
        if (globuf[0] == 'd' && globuf[1] == '\n' && globuf[2] == '\0') {
                gdelete();
                return;
        }
        for (a1 = zero; a1 <= dol; a1++) {
                if (a1->cur & 01) {
                        a1->cur &= ~01;
                        dot = a1;
                        globp = globuf;
                        globflg = 1;
                        commands();
                        globflg = 0;
                        a1 = zero;
                }
        }
}

static void
join(void)
{
        char *gp, *lp;
        LINE a1;

        if (addr1 == addr2)
                return;
        gp = genbuf;
        for (a1 = addr1; a1 <= addr2; a1++) {
                lp = getaline(a1->cur);
                while (*gp = *lp++)
                        if (gp++ > &genbuf[LBSIZE-1])
                                (void) error(27);
        }
        lp = linebuf;
        gp = genbuf;
        while (*lp++ = *gp++)
                ;
        addr1->cur = putline();
        if (addr1 < addr2)
                rdelete(addr1+1, addr2);
        dot = addr1;
}

static void
substitute(int inglob)
{
        int nl;
        LINE a1;
        long *markp;
        int ingsav;             /* For saving arg. */

        ingsav = inglob;
        ocerr2 = 0;
        gsubf = compsub();
        for (a1 = addr1; a1 <= addr2; a1++) {
                if (execute(0, a1) == 0)
                        continue;
                numpass = 0;
                ocerr1 = 0;
                inglob |= 01;
                dosub();
                if (gsubf) {
                        while (*loc2) {
                                if (execute(1, (LINE)0) == 0)
                                        break;
                                dosub();
                        }
                }
                if (ocerr1 == 0)continue;       /* Don't put out-not changed. */
                subnewa = putline();
                a1->cur &= ~01;
                if (anymarks) {
                        for (markp = names; markp < &names[26]; markp++)
                                if (*markp == a1->cur)
                                        *markp = subnewa;
                }
                a1->cur = subnewa;
                append(getsub, a1);
                nl = nline;
                a1 += nl;
                addr2 += nl;
        }
        if (ingsav)
                return; /* Was in global-no error msg allowed. */
        if (inglob == 0)
                (void) error(35);       /* Not in global, but not found. */
        if (ocerr2 == 0)
                (void) error(35); /* RE found, but occurrence match failed. */
}

static int
compsub(void)
{
        int c;
        wchar_t seof;
        char *p;
        char multic[MB_LEN_MAX];
        int n;
        static char remem[RHSIZE];
        static int remflg = -1;
        int i;

        if ((n = _mbftowc(multic, &seof, getchr, &peekc)) <= 0)
                (void) error(67);
        if (seof == '\n' || seof == ' ')
                (void) error(36);
        comple(seof);
        p = rhsbuf;
        for (;;) {
                wchar_t cl;
                if ((n = _mbftowc(multic, &cl, getchr, &peekc)) <= 0)
                        (void) error(67);
                if (cl == '\\') {
                        *p++ = '\\';
                        if (p >= &rhsbuf[RHSIZE])
                                (void) error(38);
                        if ((n = _mbftowc(multic, &cl, getchr, &peekc)) <= 0)
                                (void) error(67);
                } else if (cl == '\n') {
                        if (nodelim == 1) {
                                nodelim = 0;
                                (void) error(36);
                        }
                        if (!(globp && globp[0])) {
                                UNGETC('\n');
                                pflag++;
                                break;
                        }
                } else if (cl == seof)
                        break;
                if (p + n > &rhsbuf[RHSIZE])
                        (void) error(38);
                (void) strncpy(p, multic, n);
                p += n;
        }
        *p++ = 0;
        if (rhsbuf[0] == '%' && rhsbuf[1] == 0)
                /*
                 * If there isn't a remembered string, it is an error;
                 * otherwise the right hand side is the previous right
                 * hand side.
                 */

                if (remflg == -1)
                        (void) error(55);
                else
                        strcpy(rhsbuf, remem);
        else {
                strcpy(remem, rhsbuf);
                remflg = 0;
        }
        c = 0;
        peekc = getchr();       /* Gets char after third delimiter. */
        if (peekc == 'g') {
                c = LBSIZE; peekc = 0;
        }
        if (peekc >= '1' && peekc <= '9') {
                c = peekc-'0';
                peekc = 0;      /* Allows getchr() to get next char. */
                while (1) {
                        i = getchr();
                        if (i < '0' || i > '9')
                                break;
                        c = c*10 + i-'0';
                        if (c > LBSIZE-1)
                                (void) error(20);       /* "Illegal suffix" */
                        }
                peekc = i;      /* Effectively an unget. */
                }
        newline();
        return (c);

        /*
         * Returns occurrence value. 0 & 1 both do first occurrence
         * only: c = 0 if ordinary substitute; c = 1
         * if use 1 in global sub(s/a/b/1). 0 in global form is illegal.
         */
}

static int
getsub(void)
{
        char *p1, *p2;

        p1 = linebuf;
        if ((p2 = linebp) == 0)
                return (EOF);
        while (*p1++ = *p2++)
                ;
        linebp = 0;
        return (0);
}

static void
dosub(void)
{
        char *lp, *sp, *rp;
        int c;

        if (gsubf > 0 && gsubf < LBSIZE) {
                numpass++;
                if (gsubf != numpass)
                        return;
        }
        ocerr1++;
        ocerr2++;
        lp = linebuf;
        sp = genbuf;
        rp = rhsbuf;
        while (lp < loc1)
                *sp++ = *lp++;
        while (c = *rp++) {
                if (c == '&') {
                        sp = place(sp, loc1, loc2);
                        continue;
                } else if (c == '\\') {
                        c = *rp++;
                        if (c >= '1' && c < nbra + '1') {
                                sp = place(sp, braslist[c-'1'],
                                    braelist[c-'1']);
                                continue;
                        }
                }
                *sp++ = c;
                if (sp >= &genbuf[LBSIZE])
                        (void) error(27);
        }
        lp = loc2;
        loc2 = sp - genbuf + linebuf;
        while (*sp++ = *lp++)
                if (sp >= &genbuf[LBSIZE])
                        (void) error(27);
        lp = linebuf;
        sp = genbuf;
        while (*lp++ = *sp++)
                ;
}

static char *
place(char *sp, char *l1, char *l2)
{

        while (l1 < l2) {
                *sp++ = *l1++;
                if (sp >= &genbuf[LBSIZE])
                        (void) error(27);
        }
        return (sp);
}

static void
comple(wchar_t seof)
{
        int cclass = 0;
        wchar_t c;
        int n;
        char *cp = genbuf;
        char multic[MB_LEN_MAX];

        while (1) {
                if ((n = _mbftowc(multic, &c, getchr, &peekc)) < 0)
                        error1(67);
                if (n == 0 || c == '\n') {
                        if (cclass)
                                error1(49);
                        else
                                break;
                }
                if (c == seof && !cclass)
                        break;
                if (cclass && c == ']') {
                        cclass = 0;
                        if (cp > &genbuf[LBSIZE-1])
                                error1(50);
                        *cp++ = ']';
                        continue;
                }
                if (c == '[' && !cclass) {
                        cclass = 1;
                        if (cp > &genbuf[LBSIZE-1])
                                error1(50);
                        *cp++ = '[';
                        if ((n = _mbftowc(multic, &c, getchr, &peekc)) < 0)
                                error1(67);
                        if (n == 0 || c == '\n')
                                error1(49);
                }
                if (c == '\\' && !cclass) {
                        if (cp > &genbuf[LBSIZE-1])
                                error1(50);
                        *cp++ = '\\';
                        if ((n = _mbftowc(multic, &c, getchr, &peekc)) < 0)
                                error1(67);
                        if (n == 0 || c == '\n')
                                error1(36);
                }
                if (cp + n > &genbuf[LBSIZE-1])
                        error1(50);
                (void) strncpy(cp, multic, n);
                cp += n;
        }
        *cp = '\0';
        if (n != 0 && c == '\n')
                UNGETC('\n');
        if (n == 0 || c == '\n')
                nodelim = 1;

        /*
         * NULL RE: do not compile a null regular expression; but process
         * input with last regular expression encountered
         */

        if (genbuf[0] != '\0') {
                if (expbuf)
                        free(expbuf);
                expbuf = compile(genbuf, (char *)0, (char *)0);
        }
        if (regerrno)
                error1(regerrno);
}

static void
move(int cflag)
{
        LINE adt, ad1, ad2;

        setdot();
        nonzero();
        if ((adt = address()) == 0)
                (void) error(39);
        newline();
        if (!globflg) save();
        if (cflag) {
                ad1 = dol;
                append(getcopy, ad1++);
                ad2 = dol;
        } else {
                ad2 = addr2;
                for (ad1 = addr1; ad1 <= ad2; )
                        (ad1++)->cur &= ~01;
                ad1 = addr1;
        }
        ad2++;
        if (adt < ad1) {
                dot = adt + (ad2-ad1);
                if ((++adt) == ad1)
                        return;
                reverse(adt, ad1);
                reverse(ad1, ad2);
                reverse(adt, ad2);
        } else if (adt >= ad2) {
                dot = adt++;
                reverse(ad1, ad2);
                reverse(ad2, adt);
                reverse(ad1, adt);
        } else
                (void) error(39);
        fchange = 1;
}

static void
reverse(LINE a1, LINE a2)
{
        long t;

        for (;;) {
                t = (--a2)->cur;
                if (a2 <= a1)
                        return;
                a2->cur = a1->cur;
                (a1++)->cur = t;
        }
}

static int
getcopy(void)
{

        if (addr1 > addr2)
                return (EOF);
        (void) getaline((addr1++)->cur);
        return (0);
}


/*
 * Handles error code returned from comple() routine: regular expression
 * compile and match routines
 */

static void
error1(int code)
{
        nbra = 0;
        (void) error(code);
}


static int
execute(int gf, LINE addr)
{
        char *p1;
        int c;

        for (c = 0; c < nbra; c++) {
                braslist[c] = 0;
                braelist[c] = 0;
        }
        if (gf)
                locs = p1 = loc2;
        else {
                if (addr == zero)
                        return (0);
                p1 = getaline(addr->cur);
                locs = 0;
        }
        return (step(p1, expbuf));
}


static void
putd()
{
        int r;

        r = (int)(count%10);
        count /= 10;
        if (count)
                putd();
        putchr(r + '0');
}


int
puts(const char *sp)
{
        int n;
        wchar_t c;
        int sz, i;
        if (fss.Ffill && (listf == 0)) {

                /* deliberate attempt to remove constness of sp because */
                /* it needs to be expanded */

                if ((i = expnd((char *)sp, funny, &sz, &fss)) == -1) {
                        write(1, funny, fss.Flim & 0377);
                        putchr('\n');
                        write(1, gettext("too long"),
                            strlen(gettext("too long")));
                }
                else
                        write(1, funny, sz);
                putchr('\n');
                if (i == -2)
                        write(1, gettext("tab count\n"),
                            strlen(gettext("tab count\n")));
                return (0);
        }
        col = 0;
        while (*sp) {
                n = mbtowc(&c, sp, MB_LEN_MAX);
                if (listf) {
                        if (n < 1)
                                (void) error(28);
                        else if (n == 1)
                                putchr((unsigned char)*sp++);
                        else {
                                sp += n;
                                putwchr(c);
                        }
                } else {
                        putchr((unsigned char)*sp++);
                }
        }
#ifndef XPG6
        if (listf)
                putchr('$');    /* end of line is marked with a $ */
#else
        if (listf) {
        /* xpg6 - ensure that the end of line $ is not preceeded with a "\" */
        /* by doing a putchr() with listf=0, thereby avoiding the $ case */
        /* statement  in putchr() */
                listf = 0;
                putchr('$');    /* end of line is marked with a $ */
                listf++;
        }
#endif
        putchr('\n');
        return (1);
}


static void
putwchr(wchar_t ac)
{
        char buf[MB_LEN_MAX], *p;
        char *lp;
        wchar_t c;
        short len;

        lp = linp;
        c = ac;
        if (listf) {
                if (!iswprint(c)) {
                        p = &buf[0];
                        if ((len = wctomb(p, c)) <= 0) {
                                *p = (unsigned char)c;
                                len = 1;
                        };
                        while (len--) {
                                if (col + 4 >= 72) {
                                        col = 0;
                                        *lp++ = '\\';
                                        *lp++ = '\n';
                                }
                                (void) sprintf(lp, "\\%03o",
                                    *(unsigned char *)p++);
                                col += 4;
                                lp += 4;
                        }
                } else {
                        if ((len = wcwidth(c)) <= 0)
                                len = 0;
                        if (col + len >= 72) {
                                col = 0;
                                *lp++ = '\\';
                                *lp++ = '\n';
                        }
                        col += len;
                        if ((len = wctomb(lp, c)) <= 0) {
                                *lp = (unsigned char)c;
                                len = 1;
                        }
                        lp += len;
                }
        } else {
                if ((len = wctomb(lp, c)) <= 0) {
                        *lp = (unsigned char)c;
                        len = 1;
                }
                lp += len;
        }
        if (c == '\n' || lp >= &line[64]) {
                linp = line;
                len = lp - line;
                write(1, line, len);
                return;
        }
        linp = lp;
}


static void
putchr(unsigned char c)
{
        char *lp;
        int len;

        lp = linp;
        if (listf && c != '\n') {
                switch (c) {
                        case '\\' :
                                *lp++ = '\\';
                                *lp++ = '\\';
                                col += 2;
                                break;
                        case '\007' :
                                *lp++ = '\\';
                                *lp++ = 'a';
                                col += 2;
                                break;
                        case '\b' :
                                *lp++ = '\\';
                                *lp++ = 'b';
                                col += 2;
                                break;
                        case '\f' :
                                *lp++ = '\\';
                                *lp++ = 'f';
                                col += 2;
                                break;
                        case '\r' :
                                *lp++ = '\\';
                                *lp++ = 'r';
                                col += 2;
                                break;
                        case '\t' :
                                *lp++ = '\\';
                                *lp++ = 't';
                                col += 2;
                                break;
                        case '\v' :
                                *lp++ = '\\';
                                *lp++ = 'v';
                                col += 2;
                                break;
#ifdef XPG6
                /* if $ characters are within the line preceed with \ */
                        case '$' :
                                *lp++ = '\\';
                                *lp++ = '$';
                                col += 2;
                                break;
#endif
                        default:
                                if (isprint(c)) {
                                        *lp++ = c;
                                        col += 1;
                                } else {
                                        (void) sprintf(lp, "\\%03o", c);
                                        col += 4;
                                        lp += 4;
                                }
                                break;
                }

        /*
         * long lines are folded w/ pt of folding indicated by writing
         * backslash/newline character
         */

                if (col + 1 >= 72) {
                        col = 0;
                        *lp++ = '\\';
                        *lp++ = '\n';
                }
        } else
                *lp++ = c;
        if (c == '\n' || lp >= &line[64]) {
                linp = line;
                len = lp - line;
                (void) write(1, line, len);
                return;
        }
        linp = lp;
}


static char *
getkey(const char *prompt)
{
        struct termio b;
        int save;
        void (*sig)();
        static char key[KSIZE+1];
        char *p;
        int c;

        sig = signal(SIGINT, SIG_IGN);
        ioctl(0, TCGETA, &b);
        save = b.c_lflag;
        b.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
        ioctl(0, TCSETAW, &b);
        write(1, gettext(prompt), strlen(gettext(prompt)));
        p = key;
        while (((c = getchr()) != EOF) && (c != '\n')) {
                if (p < &key[KSIZE])
                        *p++ = c;
        }
        *p = 0;
        write(1, "\n", 1);
        b.c_lflag = save;
        ioctl(0, TCSETAW, &b);
        signal(SIGINT, sig);
        return (key);
}


static void
globaln(int k)
{
        char *gp;
        int c;
        int n;
        wchar_t cl;
        LINE a1;
        int  nfirst;
        char globuf[LBSIZE];
        char multic[MB_LEN_MAX];
        int     len;
        int pflag_save = 0;
        int listf_save = 0;
        int listn_save = 0;

        if (globp)
                (void) error(33);
        setall();
        nonzero();
        if ((n = _mbftowc(multic, &cl, getchr, &peekc)) <= 0)
                (void) error(67);
        if (cl == '\n')
                (void) error(19);
        save();
        comple(cl);
        for (a1 = zero; a1 <= dol; a1++) {
                a1->cur &= ~01;
                if (a1 >= addr1 && a1 <= addr2 && execute(0, a1) == k)
                        a1->cur |= 01;
        }
        nfirst = 0;
        newline();
        /*
         * preserve the p, l, and n suffix commands of the G and V
         * commands during the interactive section and restore
         * on completion of the G and V command.
         */
        pflag_save = pflag;
        listf_save = listf;
        listn_save = listn;
        pflag = 0;
        listf = 0;
        listn = 0;
        for (a1 = zero; a1 <= dol; a1++) {
                if (a1->cur & 01) {
                        a1->cur &= ~01;
                        dot = a1;
                        puts(getaline(a1->cur));
                        if ((c = get_wchr()) == EOF)
                                (void) error(52);
                        if (c == 'a' || c == 'i' || c == 'c')
                                (void) error(53);
                        if (c == '\n') {
                                a1 = zero;
                                continue;
                        }
                        if (c != '&') {
                                gp = globuf;
                                if ((len = wctomb(gp, c)) <= 0) {
                                        *gp = (unsigned char)c;
                                        len = 1;
                                }
                                gp += len;
                                while ((c = get_wchr()) != '\n') {

                        /* '\\' has special meaning only if preceding a '\n' */
                                        if (c == '\\') {
                                                c = get_wchr();
                                                if (c != '\n')
                                                        *gp++ = '\\';
                                        }
                                        if ((gp + (unsigned int)MB_CUR_MAX) >=
                                            &globuf[LBSIZE-1])
                                                (void) error(34);

                                        if ((len = wctomb(gp, c)) <= 0) {
                                                *gp = (unsigned char)c;
                                                len = 1;
                                        }
                                        gp += len;
                                }
                                *gp++ = '\n';
                                *gp++ = 0;
                                nfirst = 1;
                        } else if ((c = get_wchr()) != '\n')
                                (void) error(54);
                        globp = globuf;
                        if (nfirst) {
                                globflg = 1;
                                commands();
                                globflg = 0;
                        } else
                                (void) error(56);
                        globp = 0;
                        a1 = zero;
                }
        }
        pflag = pflag_save;
        listf = listf_save;
        listn = listn_save;
}


static int
eopen(char *string, int rw)
{
#define w_or_r(a, b) (rw ? a : b)
        int pf[2];
        pid_t i;
        int io;
        int chcount;    /* # of char read. */

        if (rflg) {     /* restricted shell */
                if (Xqt) {
                        Xqt = 0;
                        (void) error(6);
                }
        }
        if (!Xqt) {
                if ((io = open(string, rw)) >= 0) {
                        if (fflg) {
                                chcount = read(io, crbuf, LBSIZE);
                                if (crflag == -1) {
                                        if (isencrypt(crbuf, chcount))
                                                crflag = 2;
                                        else
                                                crflag = -2;
                                }
                                if (crflag > 0 &&
                                    run_crypt(0L, crbuf, chcount, perm) == -1) {
                                        (void) error(63);
                                }
                                if (fspec(crbuf, &fss, 0) < 0) {
                                        fss.Ffill = 0;
                                        fflg = 0;
                                        (void) error(4);
                                }
                                lseek(io, 0L, 0);
                        }
                }
                fflg = 0;
                return (io);
        }
        if (pipe(pf) < 0)
xerr:           (void) error(0);
        if ((i = fork()) == 0) {
                signal(SIGHUP, oldhup);
                signal(SIGQUIT, oldquit);
                signal(SIGPIPE, oldpipe);
                signal(SIGINT, (void (*)()) 0);
                close(w_or_r(pf[1], pf[0]));
                close(w_or_r(0, 1));
                dup(w_or_r(pf[0], pf[1]));
                close(w_or_r(pf[0], pf[1]));
                execlp(_PATH_BSHELL, "sh", "-c", string, (char *)0);
                exit(1);
        }
        if (i == (pid_t)-1)
                goto xerr;
        close(w_or_r(pf[0], pf[1]));
        return (w_or_r(pf[1], pf[0]));
}


static void
eclose(int f)
{
        close(f);
        if (Xqt)
                Xqt = 0, wait((int *)0);
}


static void
mkfunny(void)
{
        char *p, *p1, *p2;

        p2 = p1 = funny;
        p = file;
        /*
         * Go to end of file name
         */
        while (*p)
                p++;
        while (*--p  == '/')    /* delete trailing slashes */
                *p = '\0';
        /*
         * go back to beginning of file
         */
        p = file;
        /*
         * Copy file name to funny setting p2 at
         * basename of file.
         */
        while (*p1++ = *p)
                if (*p++ == '/')
                        p2 = p1;
        /*
         * Set p1 to point to basename of tfname.
         */
        p1 = strrchr(tfname, '/');
        if (strlen(tfname) > (size_t)6)
                p1 = &tfname[strlen(tfname)-6];
        p1++;
        *p2 = '\007'; /* add unprintable char for funny  a unique name */
        /*
         * Copy tfname to file.
         */
        while (*++p2 = *p1++)
                ;
}


static void
getime(void) /* get modified time of file and save */
{
        if (stat(file, &Fl) < 0)
                savtime = 0;
        else
                savtime = Fl.st_mtime;
}


static void
chktime(void) /* check saved mod time against current mod time */
{
        if (savtime != 0 && Fl.st_mtime != 0) {
                if (savtime != Fl.st_mtime)
                        (void) error(58);
        }
}


static void
newtime(void) /* get new mod time and save */
{
        stat(file, &Fl);
        savtime = Fl.st_mtime;
}


/*
 * restricted - check for '/' in name and delete trailing '/'
 */
static void
red(char *op)
{
        char *p;

        p = op;
        while (*p)
                if (*p++ == '/'&& rflg) {
                        *op = 0;
                        (void) error(6);
                }
        /* delete trailing '/' */
        while (p > op) {
                if (*--p == '/')
                        *p = '\0';
                else break;
        }
}


/*
 * Searches thru beginning of file looking for a string of the form
 *      <: values... :>
 *
 * where "values" are
 *
 *      \b      ignored
 *      s<num>  sets the Flim to <num>
 *      t???    sets tab stop stuff
 *      d       ignored
 *      m<num>  ignored
 *      e       ignored
 */

static int
fspec(char line[], struct Fspec *f, int up)
{
        struct termio arg;
        int havespec, n;
        int     len;

        if (!up) clear(f);

        havespec = fsprtn = 0;
        for (fsp = line; *fsp && *fsp != '\n'; fsp += len) {
                if ((len = mblen(fsp, MB_CUR_MAX)) <= 0)
                        len = 1;
                switch (*fsp) {

                        case '<':       if (havespec)
                                                return (-1);
                                        if (*(fsp+1) == ':') {
                                                havespec = 1;
                                                clear(f);
                                                if (!ioctl(1, TCGETA, &arg) &&
                                                    ((arg.c_oflag & TAB3) ==
                                                    TAB3))
                                                        f->Ffill = 1;
                                                fsp++;
                                        }
                                        continue;

                        case ' ':       continue;

                        case 's':       if (havespec && (n = numb()) >= 0)
                                                f->Flim = n;
                                        continue;

                        case 't':       if (havespec) targ(f);
                                        continue;

                        case 'd':       continue;

                        case 'm':       if (havespec)  n = numb();
                                        continue;

                        case 'e':       continue;
                        case ':':       if (!havespec) continue;
                                        if (*(fsp+1) != '>') fsprtn = -1;
                                        return (fsprtn);

                        default:        if (!havespec) continue;
                                        return (-1);
                }
        }
        return (1);
}


static int
numb(void)
{
        int n;

        n = 0;
        while (*++fsp >= '0' && *fsp <= '9')
                n = 10*n + *fsp-'0';
        fsp--;
        return (n);
}


static void
targ(struct Fspec *f)
{

        if (*++fsp == '-') {
                if (*(fsp + 1) >= '0' && *(fsp+1) <= '9') tincr(numb(), f);
                else tstd(f);
                return;
        }
        if (*fsp >= '0' && *fsp <= '9') {
                tlist(f);
                return;
        }
        fsprtn = -1;
        fsp--;
}


static void
tincr(int n, struct Fspec *f)
{
        int l, i;

        l = 1;
        for (i = 0; i < 20; i++)
                f->Ftabs[i] = l += n;
        f->Ftabs[i] = 0;
}


static void
tstd(struct Fspec *f)
{
        char std[3];

        std[0] = *++fsp;
        if (*(fsp+1) >= '0' && *(fsp+1) <= '9')  {
                                                std[1] = *++fsp;
                                                std[2] = '\0';
        } else {
                std[1] = '\0';
        }
        fsprtn = stdtab(std, f->Ftabs);
}


static void
tlist(struct Fspec *f)
{
        int n, last, i;

        fsp--;
        last = i = 0;

        do {
                if ((n = numb()) <= last || i >= 20) {
                        fsprtn = -1;
                        return;
                }
                f->Ftabs[i++] = last = n;
        } while (*++fsp == ',');

        f->Ftabs[i] = 0;
        fsp--;
}


static int
expnd(char line[], char buf[], int *sz, struct Fspec *f)
{
        char *l, *t;
        int b;

        l = line - 1;
        b = 1;
        t = f->Ftabs;
        fsprtn = 0;

        while (*++l && *l != '\n' && b < 511) {
                if (*l == '\t') {
                        while (*t && b >= *t)
                                t++;
                        if (*t == 0)
                                fsprtn = -2;
                        do {
                                buf[b-1] = ' ';
                        } while (++b < *t);
                } else {
                        buf[b++ - 1] = *l;
                }
        }

        buf[b] = '\0';
        *sz = b;
        if (*l != '\0' && *l != '\n') {
                buf[b-1] = '\n';
                return (-1);
        }
        buf[b-1] = *l;
        if (f->Flim && (b-1 > (int)f->Flim))
                return (-1);
        return (fsprtn);
}


static void
clear(struct Fspec *f)
{
        f->Ftabs[0] = f->Fdel = f->Fmov = f->Ffill = 0;
        f->Flim = 0;
}


static int
lenchk(char line[], struct Fspec *f)
{
        char *l, *t;
        int b;

        l = line - 1;
        b = 1;
        t = f->Ftabs;

        while (*++l && *l != '\n' && b < 511) {
                if (*l == '\t') {
                        while (*t && b >= *t)
                                t++;
                        while (++b < *t)
                                ;
                } else {
                        b++;
                }
        }

        if ((*l != '\0' && *l != '\n') || (f->Flim && (b-1 > (int)f->Flim)))
                return (-1);
        return (0);
}
#define NTABS 21


/*
 *      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,
'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
 */


static int
stdtab(char *option, char *tabvect)
{
        char *scan;

        tabvect[0] = '\0';
        scan = stdtabs;
        while (*scan) {
                if (strequal(&scan, option)) {
                        strcopy(scan, tabvect);
                        break;
                } else
                        while (*scan++)         /* skip over tab specs */
                                ;
        }

/*      later: look up code in /etc/something */
        return (tabvect[0] ? 0 : -1);
}


/*
 *      strequal: checks strings for equality
 *              entry: scan1 points to scan pointer, str points to string
 *      exit: return (1) if equal, return (0) if not
 *              *scan1 is advanced to next nonzero byte after null
 */


static int
strequal(char **scan1, char *str)
{
        char c, *scan;
        scan = *scan1;
        while ((c = *scan++) == *str && c)
                str++;
        *scan1 = scan;
        if (c == 0 && *str == 0)
                return (1);
        if (c)
                while (*scan++)
                        ;
        *scan1 = scan;
        return (0);
}


/*      strcopy: copy source to destination */


static void
strcopy(char *source, char *dest)
{
        while (*dest++ = *source++)
                ;
}


/* This is called before a buffer modifying command so that the */
/* current array of line ptrs is saved in sav and dot and dol are saved */


static void
save(void)
{
        LINE i;
        int     j;

        savdot = dot;
        savdol = dol;
        for (j = 0; j <= 25; j++)
                savnames[j] = names[j];

        for (i = zero + 1; i <= dol; i++)
                i->sav = i->cur;
        initflg = 0;
}


/* The undo command calls this to restore the previous ptr array sav */
/* and swap with cur - dot and dol are swapped also. This allows user to */
/* undo an undo */


static void
undo(void)
{
        int j;
        long tmp;
        LINE i, tmpdot, tmpdol;

        tmpdot = dot; dot = savdot; savdot = tmpdot;
        tmpdol = dol; dol = savdol; savdol = tmpdol;
        /* swap arrays using the greater of dol or savdol as upper limit */
        for (i = zero + 1; i <= ((dol > savdol) ? dol : savdol); i++) {
                tmp = i->cur;
                i->cur = i->sav;
                i->sav = tmp;
        }

        /*
         * If the current text lines are swapped with the
         * text lines in the save buffer, then swap the current
         * marks with those in the save area.
         */

        for (j = 0; j <= 25; j++) {
                tmp = names[j];
                names[j] = savnames[j];
                savnames[j] = tmp;
        }
}

static wchar_t
get_wchr(void)
{
        wchar_t wc;
        char    multi[MB_LEN_MAX];

        if (_mbftowc(multi, &wc, getchr, &peekc) <= 0)
                wc = getchr();
        return (wc);
}