BSD 2 development
authorBill Joy <wnj@ucbvax.Berkeley.EDU>
Wed, 9 May 1979 07:19:20 +0000 (23:19 -0800)
committerBill Joy <wnj@ucbvax.Berkeley.EDU>
Wed, 9 May 1979 07:19:20 +0000 (23:19 -0800)
Work on file src/ex/ex_io.c
Work on file src/ex/ex_put.c

Synthesized-from: 2bsd

src/ex/ex_io.c [new file with mode: 0644]
src/ex/ex_put.c [new file with mode: 0644]

diff --git a/src/ex/ex_io.c b/src/ex/ex_io.c
new file mode 100644 (file)
index 0000000..0a396fe
--- /dev/null
@@ -0,0 +1,1001 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * File input/output, unix escapes, source, filtering preserve and recover
+ */
+
+/*
+ * Following remember where . was in the previous file for return
+ * on file switching.
+ */
+short  altdot;
+short  oldadot;
+bool   wasalt;
+
+long   cntch;                  /* Count of characters on unit io */
+short  cntln;                  /* Count of lines " */
+long   cntnull;                /* Count of nulls " */
+long   cntodd;                 /* Count of non-ascii characters " */
+
+/*
+ * Parse file name for command encoded by comm.
+ * If comm is E then command is doomed and we are
+ * parsing just so user won't have to retype the name.
+ */
+filename(comm)
+       int comm;
+{
+       register int c = comm, d;
+       register int i;
+
+       d = getchar();
+       if (endcmd(d)) {
+               if (savedfile[0] == 0 && comm != 'f')
+                       error("No file|No current filename");
+               CP(file, savedfile);
+               wasalt = 0;
+               oldadot = altdot;
+               if (d == EOF)
+                       ungetchar(d);
+       } else {
+               ungetchar(d);
+               getone();
+               eol();
+               if (savedfile[0] == 0 && c != 'E' && c != 'e') {
+                       c = 'e';
+                       edited = 0;
+               }
+               wasalt = strcmp(file, altfile) == 0;
+               oldadot = altdot;
+               switch (c) {
+
+               case 'f':
+                       edited = 0;
+                       /* fall into ... */
+
+               case 'e':
+                       if (savedfile[0]) {
+                               altdot = lineDOT();
+                               CP(altfile, savedfile);
+                       }
+                       CP(savedfile, file);
+                       break;
+
+               default:
+                       if (file[0]) {
+                               if (c != 'E')
+                                       altdot = lineDOT();
+                               CP(altfile, file);
+                       }
+                       break;
+               }
+       }
+       if (hush && comm != 'f' || comm == 'E')
+               return;
+       if (file[0] != 0) {
+               lprintf("\"%s\"", file);
+               if (comm == 'f') {
+                       if (!edited)
+                               printf(" [Not edited]");
+                       if (tchng)
+                               printf(" [Modified]");
+               }
+               flush();
+       } else
+               printf("No file ");
+       if (comm == 'f') {
+               if (!(i = lineDOL()))
+                       i++;
+               printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(),
+                   (long) 100 * lineDOT() / i);
+       }
+}
+
+/*
+ * Get the argument words for a command into genbuf
+ * expanding # and %.
+ */
+getargs()
+{
+       register int c;
+       register char *cp, *fp;
+
+       if (skipend())
+               return (0);
+       CP(genbuf, "echo "); cp = &genbuf[5];
+       for (;;) {
+               c = getchar();
+               if (endcmd(c)) {
+                       ungetchar(c);
+                       break;
+               }
+               switch (c) {
+
+               case '\\':
+                       if (any(peekchar(), "#%"))
+                               c = getchar();
+                       /* fall into... */
+
+               default:
+                       if (cp > &genbuf[LBSIZE - 2])
+flong:
+                               error("Argument buffer overflow");
+                       *cp++ = c;
+                       break;
+
+               case '#':
+                       fp = altfile;
+                       if (*fp == 0)
+                               error("No alternate filename@to substitute for #");
+                       goto filexp;
+
+               case '%':
+                       fp = savedfile;
+                       if (*fp == 0)
+                               error("No current filename@to substitute for %%");
+filexp:
+                       while (*fp) {
+                               if (cp > &genbuf[LBSIZE - 2])
+                                       goto flong;
+                               *cp++ = *fp++;
+                       }
+                       break;
+               }
+       }
+       *cp = 0;
+       return (1);
+}
+
+/*
+ * Glob the argument words in genbuf, or if no globbing
+ * is implied, just split them up directly.
+ */
+glob(gp)
+       struct glob *gp;
+{
+       int pvec[2];
+       register char **argv = gp->argv;
+       register char *cp = gp->argspac;
+       register int c;
+       char ch;
+       int nleft = NCARGS;
+
+       gp->argc0 = 0;
+       if (gscan() == 0) {
+               register char *v = genbuf + 5;          /* strlen("echo ") */
+
+               for (;;) {
+                       while (isspace(*v))
+                               v++;
+                       if (!*v)
+                               break;
+                       *argv++ = cp;
+                       while (*v && !isspace(*v))
+                               *cp++ = *v++;
+                       *cp++ = 0;
+                       gp->argc0++;
+               }
+               *argv = 0;
+               return;
+       }
+       if (pipe(pvec) < 0)
+               error("Can't make pipe to glob");
+       pid = fork();
+       io = pvec[0];
+       if (pid < 0) {
+               close(pvec[1]);
+               error("Can't fork to do glob");
+       }
+       if (pid == 0) {
+               int oerrno;
+
+               close(1);
+               dup(pvec[1]);
+               close(pvec[0]);
+               execl(svalue(SHELL), "sh", "-c", genbuf, 0);
+               die++;
+               oerrno = errno; close(1); dup(2); errno = oerrno;
+               filioerr(svalue(SHELL));
+       }
+       close(pvec[1]);
+       do {
+               *argv = cp;
+               for (;;) {
+                       if (read(io, &ch, 1) != 1) {
+                               close(io);
+                               c = -1;
+                       } else
+                               c = ch & TRIM;
+                       if (c <= 0 || isspace(c))
+                               break;
+                       *cp++ = c;
+                       if (--nleft <= 0)
+                               error("Arg list too long");
+               }
+               if (cp != *argv) {
+                       --nleft;
+                       *cp++ = 0;
+                       gp->argc0++;
+                       if (gp->argc0 >= NARGS)
+                               error("Arg list too long");
+                       argv++;
+               }
+       } while (c >= 0);
+       waitfor();
+       if (gp->argc0 == 0)
+               error(NOSTR);
+}
+
+/*
+ * Scan genbuf for shell metacharacters.
+ * Set is union of v7 shell and csh metas.
+ */
+gscan()
+{
+       register char *cp;
+
+       for (cp = genbuf; *cp; cp++)
+               if (any(*cp, "~{[*?$`'\"\\"))
+                       return (1);
+       return (0);
+}
+
+/*
+ * Parse one filename into file.
+ */
+getone()
+{
+       register char *str;
+       struct glob G;
+
+       if (getargs() == 0)
+               error("Missing filename");
+       glob(&G);
+       if (G.argv[0][0] == '+') {
+               firstln = getn(G.argv[0] + 1);
+               if (firstln == 0)
+                       firstln = 20000;
+               if (G.argc0 == 1) {
+                       str = savedfile;
+                       goto samef;
+               }
+       }
+       else if (G.argc0 > 1)
+               error("Ambiguous|Too many file names");
+       str = G.argv[G.argc0 - 1];
+       if (strlen(str) > FNSIZE - 4)
+               error("Filename too long");
+samef:
+       CP(file, str);
+}
+
+/*
+ * Read a file from the world.
+ * C is command, 'e' if this really an edit (or a recover).
+ */
+rop(c)
+       int c;
+{
+       register int i;
+       struct stat stbuf;
+       short magic;
+
+       if (firstln)
+               wasalt = 2, oldadot = firstln, firstln = 0;
+       io = open(file, 0);
+       if (io < 0) {
+               if (c == 'e' && errno == ENOENT)
+                       edited++;
+               syserror();
+       }
+       if (fstat(io, &stbuf))
+               syserror();
+       switch (stbuf.st_mode & S_IFMT) {
+
+       case S_IFBLK:
+               error(" Block special file");
+
+       case S_IFCHR:
+               if (isatty(io))
+                       error(" Teletype");
+               if (samei(&stbuf, "/dev/null"))
+                       break;
+               error(" Character special file");
+
+       case S_IFDIR:
+               error(" Directory");
+
+       case S_IFREG:
+               i = read(io, (char *) &magic, sizeof(magic));
+               lseek(io, 0l, 0);
+               if (i != sizeof(magic))
+                       break;
+               switch (magic) {
+
+               case 0405:
+               case 0407:
+               case 0410:
+               case 0411:
+                       error(" Executable");
+
+               case 0177545:
+               case 0177555:
+                       error(" Archive");
+
+               default:
+                       if (magic & 0100200)
+                               error(" Non-ascii file");
+                       break;
+               }
+       }
+       if (c == 'r')
+               setdot();
+       else
+               setall();
+       if (inopen && c == 'r')
+               undap1 = undap2 = dot + 1;
+       rop2();
+       rop3(c);
+}
+
+rop2()
+{
+
+       deletenone();
+       clrstats();
+       ignore(append(getfile, addr2));
+}
+
+rop3(c)
+       int c;
+{
+
+       if (iostats() == 0 && c == 'e')
+               edited++;
+       if (c == 'e') {
+               if (wasalt) {
+                       register line *addr = zero + oldadot;
+
+                       if (addr > dol)
+                               addr = dol;
+                       if (addr >= one) {
+                               if (inopen || wasalt == 2)
+                                       dot = addr;
+                               markpr(addr);
+                       } else
+                               goto other;
+               } else
+other:
+                       if (dol > zero) {
+                               if (inopen)
+                                       dot = one;
+                               markpr(one);
+                       }
+               undkind = UNDNONE;
+               if (inopen) {
+                       vcline = 0;
+                       vreplace(0, LINES, lineDOL());
+               }
+       }
+       if (laste) {
+               laste = 0;
+               sync();
+       }
+}
+
+/*
+ * Are these two really the same inode?
+ */
+samei(sp, cp)
+       struct stat *sp;
+       char *cp;
+{
+       struct stat stb;
+
+       if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
+               return (0);
+       return (sp->st_ino == stb.st_ino);
+}
+
+/* Returns from edited() */
+#define        EDF     0               /* Edited file */
+#define        NOTEDF  -1              /* Not edited file */
+#define        PARTBUF 1               /* Write of partial buffer to Edited file */
+
+/*
+ * Write a file.
+ */
+wop()
+{
+       register int c, exclam, nonexist;
+       struct stat stbuf;
+
+       c = 0;
+       exclam = 0;
+       if (peekchar() == '!')
+               exclam++, ignchar();
+       ignore(skipwh());
+       while (peekchar() == '>')
+               ignchar(), c++, ignore(skipwh());
+       if (c != 0 && c != 2)
+               error("Write forms are 'w' and 'w>>'");
+       filename('w');
+       nonexist = stat(file, &stbuf);
+       switch (c) {
+
+       case 0:
+               if (!exclam && !value(WRITEANY)) switch (edfile()) {
+               
+               case NOTEDF:
+                       if (nonexist)
+                               break;
+                       if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
+                               if (samei(&stbuf, "/dev/null"))
+                                       break;
+                               if (samei(&stbuf, "/dev/tty"))
+                                       break;
+                       }
+                       io = open(file, 1);
+                       if (io < 0)
+                               syserror();
+                       if (!isatty(io))
+                               serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
+                       close(io);
+                       break;
+
+               case PARTBUF:
+                       error(" Use \"w!\" to write partial buffer");
+               }
+cre:
+               synctmp();
+#ifdef V6
+               io = creat(file, 0644);
+#else
+               io = creat(file, 0666);
+#endif
+               if (io < 0)
+                       syserror();
+               if (hush == 0)
+                       if (nonexist)
+                               printf(" [New file]");
+                       else if (value(WRITEANY) && edfile() != EDF)
+                               printf(" [Existing file]");
+               break;
+
+       case 2:
+               io = open(file, 1);
+               if (io < 0) {
+                       if (exclam || value(WRITEANY))
+                               goto cre;
+                       syserror();
+               }
+               lseek(io, 0l, 2);
+               break;
+       }
+       putfile();
+       ignore(iostats());
+       if (c != 2 && addr1 == one && addr2 == dol) {
+               if (eq(file, savedfile))
+                       edited = 1;
+               sync();
+       }
+}
+
+/*
+ * Is file the edited file?
+ * Work here is that it is not considered edited
+ * if this is a partial buffer, and distinguish
+ * all cases.
+ */
+edfile()
+{
+
+       if (!edited || !eq(file, savedfile))
+               return (NOTEDF);
+       return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
+}
+
+/*
+ * First part of a shell escape,
+ * parse the line, expanding # and % and ! and printing if implied.
+ */
+unix0(warn)
+       bool warn;
+{
+       register char *up, *fp;
+       register short c;
+       char printub, puxb[UXBSIZE + sizeof (int)];
+
+       printub = 0;
+       CP(puxb, uxb);
+       c = getchar();
+       if (c == '\n' || c == EOF)
+               error("Incomplete shell escape command@- use 'shell' to get a shell");
+       up = uxb;
+       do {
+               switch (c) {
+
+               case '\\':
+                       if (any(peekchar(), "%#!"))
+                               c = getchar();
+               default:
+                       if (up >= &uxb[UXBSIZE]) {
+tunix:
+                               uxb[0] = 0;
+                               error("Command too long");
+                       }
+                       *up++ = c;
+                       break;
+
+               case '!':
+                       fp = puxb;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No previous command@to substitute for !");
+                       }
+                       printub++;
+                       while (*fp) {
+                               if (up >= &uxb[UXBSIZE])
+                                       goto tunix;
+                               *up++ = *fp++;
+                       }
+                       break;
+
+               case '#':
+                       fp = altfile;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No alternate filename@to substitute for #");
+                       }
+                       goto uexp;
+
+               case '%':
+                       fp = savedfile;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No filename@to substitute for %%");
+                       }
+uexp:
+                       printub++;
+                       while (*fp) {
+                               if (up >= &uxb[UXBSIZE])
+                                       goto tunix;
+                               *up++ = *fp++ | QUOTE;
+                       }
+                       break;
+               }
+               c = getchar();
+       } while (c == '|' || !endcmd(c));
+       if (c == EOF)
+               ungetchar(c);
+       *up = 0;
+       if (!inopen)
+               resetflav();
+       if (warn && hush == 0 && chng && xchng != chng && value(WARN)) {
+               xchng = chng;
+               vnfl();
+               printf(mesg("[No write]|[No write since last change]"));
+               noonl();
+               flush();
+       } else
+               warn = 0;
+       if (printub) {
+               if (uxb[0] == 0)
+                       error("No previous command@to repeat");
+               if (inopen) {
+                       splitw++;
+                       vclean();
+                       vgoto(WECHO, 0);
+               }
+               if (warn)
+                       vnfl();
+               lprintf("!%s", uxb);
+               if (inopen) {
+                       vclreol();
+                       vgoto(WECHO, 0);
+               } else
+                       putnl();
+               flush();
+       }
+}
+
+/*
+ * Do the real work for execution of a shell escape.
+ * Mode is like the number passed to open system calls
+ * and indicates filtering.  If input is implied, newstdin
+ * must have been setup already.
+ */
+unixex(opt, up, newstdin, mode)
+       char *opt, *up;
+       int newstdin, mode;
+{
+       int pvec[2], f;
+
+       signal(SIGINT, SIG_IGN);
+       if (inopen)
+               f = setty(normf);
+       if ((mode & 1) && pipe(pvec) < 0) {
+               /* Newstdin should be io so it will be closed */
+               if (inopen)
+                       setty(f);
+               error("Can't make pipe for filter");
+       }
+       pid = fork();
+       if (pid < 0) {
+               if (mode & 1) {
+                       close(pvec[0]);
+                       close(pvec[1]);
+               }
+               setrupt();
+               error("No more processes");
+       }
+       if (pid == 0) {
+               die++;
+               if (mode & 2) {
+                       close(0);
+                       dup(newstdin);
+                       close(newstdin);
+               }
+               if (mode & 1) {
+                       close(pvec[0]);
+                       close(1);
+                       dup(pvec[1]);
+                       if (inopen) {
+                               close(2);
+                               dup(1);
+                       }
+                       close(pvec[1]);
+               }
+               if (io)
+                       close(io);
+               if (tfile)
+                       close(tfile);
+               close(erfile);
+               signal(SIGHUP, oldhup);
+               signal(SIGQUIT, oldquit);
+               if (ruptible)
+                       signal(SIGINT, SIG_DFL);
+               execl(svalue(SHELL), "sh", opt, up, (char *) 0);
+               printf("No %s!\n", svalue(SHELL));
+               error(NOSTR);
+       }
+       if (mode & 1) {
+               io = pvec[0];
+               close(pvec[1]);
+       }
+       if (newstdin)
+               close(newstdin);
+       return (f);
+}
+
+/*
+ * Wait for the command to complete.
+ * F is for restoration of tty mode if from open/visual.
+ * C flags suppression of printing.
+ */
+unixwt(c, f)
+       bool c;
+       int f;
+{
+
+       waitfor();
+       if (inopen)
+               setty(f);
+       setrupt();
+       if (!inopen && c && hush == 0) {
+               printf("!\n");
+               flush();
+               termreset();
+               gettmode();
+       }
+}
+
+/*
+ * Setup a pipeline for the filtration implied by mode
+ * which is like a open number.  If input is required to
+ * the filter, then a child editor is created to write it.
+ * If output is catch it from io which is created by unixex.
+ */
+filter(mode)
+       register int mode;
+{
+       static int pvec[2];
+       register int f;
+       register int lines = lineDOL();
+
+       mode++;
+       if (mode & 2) {
+               signal(SIGINT, SIG_IGN);
+               if (pipe(pvec) < 0)
+                       error("Can't make pipe");
+               pid = fork();
+               io = pvec[0];
+               if (pid < 0) {
+                       setrupt();
+                       close(pvec[1]);
+                       error("No more processes");
+               }
+               if (pid == 0) {
+                       die++;
+                       setrupt();
+                       io = pvec[1];
+                       close(pvec[0]);
+                       putfile();
+                       exit(0);
+               }
+               close(pvec[1]);
+               io = pvec[0];
+               setrupt();
+       }
+       f = unixex("-c", uxb, (mode & 2) ? pvec[0] : 0, mode);
+       if (mode == 3) {
+               delete(0);
+               addr2 = addr1 - 1;
+       }
+       if (mode & 1)
+               ignore(append(getfile, addr2));
+       close(io);
+       io = -1;
+       unixwt(!inopen, f);
+       netchHAD(lines);
+}
+
+/*
+ * Set up to do a recover, getting io to be a pipe from
+ * the recover process.
+ */
+recover()
+{
+       static int pvec[2];
+
+       if (pipe(pvec) < 0)
+               error(" Can't make pipe for recovery");
+       pid = fork();
+       io = pvec[0];
+       if (pid < 0) {
+               close(pvec[1]);
+               error(" Can't fork to execute recovery");
+       }
+       if (pid == 0) {
+               close(2);
+               dup(1);
+               close(1);
+               dup(pvec[1]);
+               close(pvec[1]);
+               execl(EXRECOVER, "exrecover", svalue(DIRECTORY), file, (char *) 0);
+               die++;
+               close(1);
+               dup(2);
+               error(" No recovery routine");
+       }
+       close(pvec[1]);
+}
+
+/*
+ * Wait for the process (pid an external) to complete.
+ */
+waitfor()
+{
+
+       do
+               rpid = wait(&status);
+       while (rpid != pid && rpid != -1);
+       status = (status >> 8) & 0377;
+}
+
+/*
+ * The end of a recover operation.  If the process
+ * exits non-zero, force not edited; otherwise force
+ * a write.
+ */
+revocer()
+{
+
+       waitfor();
+       if (pid == rpid && status != 0)
+               edited = 0;
+       else
+               change();
+}
+
+/*
+ * Extract the next line from the io stream.
+ */
+static char *nextip;
+
+getfile()
+{
+       register short c;
+       register char *lp, *fp;
+
+       lp = linebuf;
+       fp = nextip;
+       do {
+               if (--ninbuf < 0) {
+                       ninbuf = read(io, genbuf, LBSIZE) - 1;
+                       if (ninbuf < 0) {
+                               if (lp != linebuf) {
+                                       printf(" [Incomplete last line]");
+                                       break;
+                               }
+                               return (EOF);
+                       }
+                       fp = genbuf;
+               }
+               if (lp >= &linebuf[LBSIZE]) {
+                       error(" Line too long");
+               }
+               c = *fp++;
+               if (c == 0) {
+                       cntnull++;
+                       continue;
+               }
+               if (c & QUOTE) {
+                       cntodd++;
+                       c &= TRIM;
+                       if (c == 0)
+                               continue;
+               }
+               *lp++ = c;
+       } while (c != '\n');
+       cntch += lp - linebuf;
+       *--lp = 0;
+       nextip = fp;
+       cntln++;
+       return (0);
+}
+
+/*
+ * Write a range onto the io stream.
+ */
+putfile()
+{
+       line *a1;
+       register char *fp, *lp;
+       register int nib;
+
+       a1 = addr1;
+       clrstats();
+       cntln = addr2 - a1 + 1;
+       if (cntln == 0)
+               return;
+       nib = BUFSIZ;
+       fp = genbuf;
+       do {
+               getline(*a1++);
+               lp = linebuf;
+               for (;;) {
+                       if (--nib < 0) {
+                               nib = fp - genbuf;
+                               if (write(io, genbuf, nib) != nib) {
+                                       wrerror();
+                               }
+                               cntch += nib;
+                               nib = BUFSIZ - 1;
+                               fp = genbuf;
+                       }
+                       if ((*fp++ = *lp++) == 0) {
+                               fp[-1] = '\n';
+                               break;
+                       }
+               }
+       } while (a1 <= addr2);
+       nib = fp - genbuf;
+       if (write(io, genbuf, nib) != nib) {
+               wrerror();
+       }
+       cntch += nib;
+}
+
+/*
+ * A write error has occurred;  if the file being written was
+ * the edited file then we consider it to have changed since it is
+ * now likely scrambled.
+ */
+wrerror()
+{
+
+       if (eq(file, savedfile) && edited)
+               change();
+       syserror();
+}
+
+/*
+ * Source command, handles nested sources.
+ * Traps errors since it mungs unit 0 during the source.
+ */
+static short slevel;
+
+source(fil, okfail)
+       char *fil;
+       bool okfail;
+{
+       jmp_buf osetexit;
+       register int saveinp, ointty, oerrno;
+
+       signal(SIGINT, SIG_IGN);
+       saveinp = dup(0);
+       if (saveinp < 0)
+               error("Too many nested sources");
+       close(0);
+       if (open(fil, 0) < 0) {
+               oerrno = errno;
+               setrupt();
+               dup(saveinp);
+               close(saveinp);
+               errno = oerrno;
+               if (!okfail)
+                       filioerr(fil);
+               return;
+       }
+       slevel++;
+       ointty = intty;
+       intty = isatty(0);
+       getexit(osetexit);
+       setrupt();
+       if (setexit() == 0)
+               commands(1, 1);
+       else if (slevel > 1) {
+               close(0);
+               dup(saveinp);
+               close(saveinp);
+               slevel--;
+               resexit(osetexit);
+               reset();
+       }
+       intty = ointty;
+       close(0);
+       dup(saveinp);
+       close(saveinp);
+       slevel--;
+       resexit(osetexit);
+}
+
+/*
+ * Clear io statistics before a read or write.
+ */
+clrstats()
+{
+
+       ninbuf = 0;
+       cntch = 0;
+       cntln = 0;
+       cntnull = 0;
+       cntodd = 0;
+}
+
+/*
+ * Io is finished, close the unit and print statistics.
+ */
+iostats()
+{
+
+       close(io);
+       io = -1;
+       if (hush == 0) {
+               if (value(TERSE))
+                       printf(" %d/%D", cntln, cntch);
+               else
+                       printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
+                           cntch, plural(cntch));
+               if (cntnull || cntodd) {
+                       printf(" (");
+                       if (cntnull) {
+                               printf("%D null", cntnull);
+                               if (cntodd)
+                                       printf(", ");
+                       }
+                       if (cntodd)
+                               printf("%D non-ASCII", cntodd);
+                       putchar(')');
+               }
+               noonl();
+               flush();
+       }
+       return (cntnull != 0 || cntodd != 0);
+}
diff --git a/src/ex/ex_put.c b/src/ex/ex_put.c
new file mode 100644 (file)
index 0000000..6f99e24
--- /dev/null
@@ -0,0 +1,830 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+
+/*
+ * Terminal driving and line formatting routines.
+ * Basic motion optimizations are done here as well
+ * as formatting of lines (printing of control characters,
+ * line numbering and the like).
+ */
+
+/*
+ * The routines outchar, putchar and pline are actually
+ * variables, and these variables point at the current definitions
+ * of the routines.  See the routine setflav.
+ * We sometimes make outchar be routines which catch the characters
+ * to be printed, e.g. if we want to see how long a line is.
+ * During open/visual, outchar and putchar will be set to
+ * routines in the file ex_vput.c (vputchar, vinschar, etc.).
+ */
+int    (*Outchar)() = termchar;
+int    (*Putchar)() = normchar;
+int    (*Pline)() = normline;
+
+int (*
+setlist(t))()
+       bool t;
+{
+       register int (*P)();
+
+       listf = t;
+       P = Putchar;
+       Putchar = t ? listchar : normchar;
+       return (P);
+}
+
+int (*
+setnumb(t))()
+       bool t;
+{
+       register int (*P)();
+
+       numberf = t;
+       P = Pline;
+       Pline = t ? numbline : normline;
+       return (P);
+}
+
+/*
+ * Format c for list mode; leave things in common
+ * with normal print mode to be done by normchar.
+ */
+listchar(c)
+       register short c;
+{
+
+       c &= (TRIM|QUOTE);
+       switch (c) {
+
+       case '\t':
+       case '\b':
+               outchar('^');
+               c = ctlof(c);
+               break;
+
+       case '\n':
+               break;
+
+       case '\n' | QUOTE:
+               outchar('$');
+               break;
+
+       default:
+               if (c & QUOTE)
+                       break;
+               if (c < ' ' && c != '\n' || c == DELETE)
+                       outchar('^'), c = ctlof(c);
+               break;
+       }
+       normchar(c);
+}
+
+/*
+ * Format c for printing.  Handle funnies of upper case terminals
+ * and crocky hazeltines which don't have ~.
+ */
+normchar(c)
+       register short c;
+{
+       register char *colp;
+
+       c &= (TRIM|QUOTE);
+       if (c == '~' && HZ) {
+               normchar('\\');
+               c = '^';
+       }
+       if (c & QUOTE)
+               switch (c) {
+
+               case ' ' | QUOTE:
+               case '\b' | QUOTE:
+                       break;
+
+               case QUOTE:
+                       return;
+
+               default:
+                       c &= TRIM;
+               }
+       else if (c < ' ' && (c != '\b' || !OS) && c != '\n' && c != '\t' || c == DELETE)
+               putchar('^'), c = ctlof(c);
+       else if (UPPERCASE)
+               if (isupper(c)) {
+                       outchar('\\');
+                       c = tolower(c);
+               } else {
+                       colp = "({)}!|^~'`";
+                       while (*colp++)
+                               if (c == *colp++) {
+                                       outchar('\\');
+                                       c = colp[-2];
+                                       break;
+                               }
+               }
+       outchar(c);
+}
+
+/*
+ * Print a line with a number.
+ */
+numbline(i)
+       int i;
+{
+
+       if (shudclob)
+               slobber(' ');
+       printf("%6d  ", i);
+       normline();
+}
+
+/*
+ * Normal line output, no numbering.
+ */
+normline()
+{
+       register char *cp;
+
+       if (shudclob)
+               slobber(linebuf[0]);
+       /* pdp-11 doprnt is not reentrant so can't use "printf" here
+          in case we are tracing */
+       for (cp = linebuf; *cp;)
+               putchar(*cp++);
+       if (!inopen)
+               putchar('\n' | QUOTE);
+}
+
+/*
+ * Given c at the beginning of a line, determine whether
+ * the printing of the line will erase or otherwise obliterate
+ * the prompt which was printed before.  If it won't, do it now.
+ */
+slobber(c)
+       int c;
+{
+
+       shudclob = 0;
+       switch (c) {
+
+       case '\t':
+               if (Putchar == listchar)
+                       return;
+               break;
+
+       default:
+               return;
+
+       case ' ':
+       case 0:
+               break;
+       }
+       if (OS)
+               return;
+       flush();
+       putch(' ');
+       if (BC)
+               tputs(BC, 0, putch);
+       else
+               putch('\b');
+}
+
+/*
+ * The output buffer is initialized with a useful error
+ * message so we don't have to keep it in data space.
+ */
+static char linb[66] = {
+       'E', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', ' ',
+       'f', 'i', 'l', 'e', ' ', 'n', 'o', 't', ' ',
+       'a', 'v', 'a', 'i', 'l', 'a', 'b', 'l', 'e', '\n', 0
+};
+static char *linp = linb + 33;
+
+/*
+ * Phadnl records when we have already had a complete line ending with \n.
+ * If another line starts without a flush, and the terminal suggests it,
+ * we switch into -nl mode so that we can send lineffeeds to avoid
+ * a lot of spacing.
+ */
+static bool phadnl;
+
+/*
+ * Indirect to current definition of putchar.
+ */
+putchar(c)
+       int c;
+{
+
+       (*Putchar)(c);
+}
+
+/*
+ * Termchar routine for command mode.
+ * Watch for possible switching to -nl mode.
+ * Otherwise flush into next level of buffering when
+ * small buffer fills or at a newline.
+ */
+termchar(c)
+       int c;
+{
+
+       if (pfast == 0 && phadnl)
+               pstart();
+       if (c == '\n')
+               phadnl = 1;
+       else if (linp >= &linb[63])
+               flush1();
+       *linp++ = c;
+       if (linp >= &linb[63]) {
+               fgoto();
+               flush1();
+       }
+}
+
+flush()
+{
+
+       flush1();
+       flush2();
+}
+
+/*
+ * Flush from small line buffer into output buffer.
+ * Work here is destroying motion into positions, and then
+ * letting fgoto do the optimized motion.
+ */
+flush1()
+{
+       register char *lp;
+       register short c;
+
+       *linp = 0;
+       lp = linb;
+       while (*lp)
+               switch (c = *lp++) {
+
+               case '\r':
+                       destline += destcol / COLUMNS;
+                       destcol = 0;
+                       continue;
+
+               case '\b':
+                       if (destcol)
+                               destcol--;
+                       continue;
+
+               case ' ':
+                       destcol++;
+                       continue;
+
+               case '\t':
+                       destcol += value(TABSTOP) - destcol % value(TABSTOP);
+                       continue;
+
+               case '\n':
+                       destline += destcol / COLUMNS + 1;
+                       if (destcol != 0 && destcol % COLUMNS == 0)
+                               destline--;
+                       destcol = 0;
+                       continue;
+
+               default:
+                       fgoto();
+                       for (;;) {
+                               if (AM == 0 && outcol == COLUMNS)
+                                       fgoto();
+                               c &= TRIM;
+                               putch(c);
+                               if (c == '\b') {
+                                       outcol--;
+                                       destcol--;
+                               } else if (c >= ' ' && c != DELETE) {
+                                       outcol++;
+                                       destcol++;
+                                       if (XN && outcol % COLUMNS == 0)
+                                               putch('\n');
+                               }
+                               c = *lp++;
+                               if (c <= ' ')
+                                       break;
+                       }
+                       --lp;
+                       continue;
+               }
+       linp = linb;
+}
+
+flush2()
+{
+
+       fgoto();
+       flusho();
+       pstop();
+}
+
+/*
+ * Sync the position of the output cursor.
+ * Most work here is rounding for terminal boundaries getting the
+ * column position implied by wraparound or the lack thereof and
+ * rolling up the screen to get destline on the screen.
+ */
+fgoto()
+{
+       register int l, c;
+
+       if (destcol > COLUMNS - 1) {
+               destline += destcol / COLUMNS;
+               destcol %= COLUMNS;
+       }
+       if (outcol > COLUMNS - 1) {
+               l = (outcol + 1) / COLUMNS;
+               outline += l;
+               outcol %= COLUMNS;
+               if (AM == 0) {
+                       while (l > 0) {
+                               if (pfast)
+                                       putch('\r');
+                               putch('\n');
+                               l--;
+                       }
+                       outcol = 0;
+               }
+               if (outline > LINES - 1) {
+                       destline -= outline - (LINES - 1);
+                       outline = LINES - 1;
+               }
+       }
+       if (destline > LINES - 1) {
+               l = destline;
+               destline = LINES - 1;
+               if (outline < LINES - 1) {
+                       c = destcol;
+                       if (pfast == 0 && (!CA || holdcm))
+                               destcol = 0;
+                       fgoto();
+                       destcol = c;
+               }
+               while (l > LINES - 1) {
+                       putch('\n');
+                       l--;
+                       if (pfast == 0)
+                               outcol = 0;
+               }
+       }
+       if (destline < outline && !(CA && !holdcm || UP != NOSTR))
+               destline = outline;
+       if (motion())
+               return;
+       if (CA && !holdcm) {
+               tputs(cgoto(), 0, putch);
+               outline = destline;
+               outcol = destcol;
+       } else
+               plod();
+}
+
+/*
+ * Tab to column col by flushing and then setting destcol.
+ * Used by "set all".
+ */
+tab(col)
+       int col;
+{
+
+       flush1();
+       destcol = col;
+}
+
+/*
+ * Routine to decide how to do a basic motion,
+ * choosing between local motions and cursor addressing.
+ */ 
+motion()
+{
+       register int v, h;
+       /*
+        * V is vertical distance, then cost with cr.
+        * H is horizontal distance, then direct move cost.
+        */
+
+       if (!BS)
+               return (0);
+       v = destline - outline;
+       if (v < 0)
+               if (CA && !holdcm || UP) {
+                       if (!UP)
+                               return (0);
+                       v = -v;
+               } else
+                       destline = outline;
+       h = destcol;
+       if (!v || pfast) {
+               h -= outcol;
+               if (h < 0)
+                       h = -h;
+       }
+       h += v;
+       if (pfast || !NONL) {
+               if (outcol)
+                       v++;
+               v += destcol;
+       } else
+               v = 5;
+       if (v >= 4 && h >= 4)
+               return (0);
+       plod();
+       return (1);
+}
+
+/*
+ * Move (slowly) to destination.
+ * Hard thing here is using home cursor on really deficient terminals.
+ * Otherwise just use cursor motions, hacking use of tabs and overtabbing
+ * and backspace.
+ */
+plod()
+{
+       register int i, j, k;
+
+       if (HO && (!CA || holdcm)) {
+               if (GT)
+                       i = (destcol >> 3) + (destcol & 07);
+               else
+                       i = destcol;
+               if (destcol >= outcol)
+                       if (GT && (j = ((destcol - (outcol &~ 07)) >> 3)))
+                               j += destcol & 07;
+                       else
+                               j = destcol - outcol;
+               else
+                       if (outcol - destcol <= i && (BS || BC))
+                               i = j = outcol - destcol;
+                       else
+                               j = i + 1;
+               k = outline - destline;
+               if (k < 0)
+                       k = -k;
+               j += k;
+               if (i + destline < j) {
+                       tputs(HO, 0, putch);
+                       outcol = outline = 0;
+               } else if (LL) {
+                       k = (LINES - 1) - destline;
+                       if (i + k + 2 < j) {
+                               tputs(LL, 0, putch);
+                               outcol = 0;
+                               outline = LINES - 1;
+                       }
+               }
+       }
+       if (GT && (!CA || holdcm))
+               i = (destcol >> 3) + (destcol & 07);
+       else
+               i = destcol;
+       if ((!NONL || outline >= destline) && (!NC || outline < destline) &&
+           (outcol - destcol > i + 1 || outcol > destcol && !BS && !BC)) {
+               putch('\r');
+               if (NC) {
+                       putch('\n');
+                       outline++;
+               }
+               outcol = 0;
+       }
+       while (outline < destline) {
+               outline++;
+               putch('\n');
+               if (NONL || pfast == 0)
+                       outcol = 0;
+       }
+       while (outcol > destcol) {
+               outcol--;
+               if (BC)
+                       tputs(BC, 0, putch);
+               else
+                       putch('\b');
+       }
+       while (outline > destline) {
+               outline--;
+               putS(UP);
+       }
+       if (GT && destcol - outcol > 1) {
+               while ((i = ((outcol + 8) &~ 7)) <= destcol) {
+                       if (TA)
+                               tputs(TA, 0, putch);
+                       else
+                               putch('\t');
+                       outcol = i;
+               }
+               if (destcol - outcol > 4 && (BC || BS)) {
+                       if (TA)
+                               tputs(TA, 0, putch);
+                       else
+                               putch('\t');
+                       outcol = i;
+                       while (outcol > destcol) {
+                               outcol--;
+                               if (BC)
+                                       tputs(BC, 0, putch);
+                               else
+                                       putch('\b');
+                       }
+               }
+       }
+       while (outcol < destcol) {
+               if (inopen && ND)
+                       putS(ND);
+               else
+                       putch(' ');
+               outcol++;
+       }
+}
+
+/*
+ * An input line arrived.
+ * Calculate new (approximate) screen line position.
+ * Approximate because kill character echoes newline with
+ * no feedback and also because of long input lines.
+ */
+noteinp()
+{
+
+       outline++;
+       if (outline > LINES - 1)
+               outline = LINES - 1;
+       destline = outline;
+       destcol = outcol = 0;
+}
+
+/*
+ * Something weird just happened and we
+ * lost track of whats happening out there.
+ * Since we cant, in general, read where we are
+ * we just reset to some known state.
+ * On cursor addressible terminals setting to unknown
+ * will force a cursor address soon.
+ */
+termreset()
+{
+
+       endim();
+       destcol = 0;
+       destline = LINES - 1;
+       if (CA) {
+               outcol = UKCOL;
+               outline = UKCOL;
+       } else {
+               outcol = destcol;
+               outline = destline;
+       }
+}
+
+/*
+ * Low level buffering, with the ability to drain
+ * buffered output without printing it.
+ */
+char   *obp = obuf;
+
+draino()
+{
+
+       obp = obuf;
+}
+
+flusho()
+{
+
+       if (obp != obuf) {
+               write(1, obuf, obp - obuf);
+               obp = obuf;
+       }
+}
+
+putnl()
+{
+
+       putchar('\n');
+}
+
+putS(cp)
+       char *cp;
+{
+
+       if (cp == NULL)
+               return;
+       while (*cp)
+               putch(*cp++);
+}
+
+
+putch(c)
+       int c;
+{
+
+       *obp++ = c;
+       if (obp >= &obuf[sizeof obuf])
+               flusho();
+}
+
+/*
+ * Miscellaneous routines related to output.
+ */
+
+/*
+ * Cursor motion.
+ */
+char *
+cgoto()
+{
+
+       return (tgoto(CM, destcol, destline));
+}
+
+/*
+ * Put with padding
+ */
+putpad(cp)
+       char *cp;
+{
+
+       flush();
+       tputs(cp, 0, putch);
+}
+
+/*
+ * Set output through normal command mode routine.
+ */
+setoutt()
+{
+
+       Outchar = termchar;
+}
+
+/*
+ * Printf (temporarily) in list mode.
+ */
+/*VARARGS2*/
+lprintf(cp, dp)
+       char *cp, *dp;
+{
+       register int (*P)();
+
+       P = setlist(1);
+       printf(cp, dp);
+       Putchar = P;
+}
+
+/*
+ * Newline + flush.
+ */
+putNFL()
+{
+
+       putnl();
+       flush();
+}
+
+/*
+ * Try to start -nl mode.
+ */
+pstart()
+{
+
+       if (NONL)
+               return;
+       if (!value(OPTIMIZE))
+               return;
+       if (ruptible == 0 || pfast)
+               return;
+       fgoto();
+       flusho();
+       pfast = 1;
+       normtty++;
+       tty.sg_flags = normf & ~(ECHO|CRMOD);
+       sTTY(1);
+}
+
+/*
+ * Stop -nl mode.
+ */
+pstop()
+{
+
+       if (inopen)
+               return;
+       phadnl = 0;
+       linp = linb;
+       draino();
+       normal(normf);
+       pfast &= ~1;
+}
+
+/*
+ * Prep tty for open mode.
+ */
+ostart()
+{
+       int f;
+
+       if (!intty)
+               error("Open and visual must be used interactively");
+       gTTY(1);
+       normtty++;
+       f = tty.sg_flags;
+#ifdef V6
+       tty.sg_flags = (normf &~ (ECHO|CRMOD)) | RAW;
+#else
+       tty.sg_flags = (normf &~ (ECHO|CRMOD)) | CBREAK;
+#endif
+       sTTY(1);
+       putpad(VS);
+       pfast |= 2;
+       return (f);
+}
+
+/*
+ * Stop open, restoring tty modes.
+ */
+ostop(f)
+       int f;
+{
+
+       pfast = (f & CRMOD) == 0;
+       termreset(), fgoto(), flusho();
+       normal(f);
+       putpad(VE);
+}
+
+#ifdef V6
+/*
+ * Into cooked mode for interruptibility.
+ */
+vcook()
+{
+
+       tty.sg_flags &= ~RAW;
+       sTTY(1);
+}
+
+/*
+ * Back into raw mode.
+ */
+vraw()
+{
+
+       tty.sg_flags |= RAW;
+       sTTY(1);
+}
+#endif
+
+/*
+ * Restore flags to normal state f.
+ */
+normal(f)
+       int f;
+{
+
+       if (normtty > 0) {
+               setty(f);
+               normtty--;
+       }
+}
+
+/*
+ * Straight set of flags to state f.
+ */
+setty(f)
+       int f;
+{
+       register int ot = tty.sg_flags;
+
+       tty.sg_flags = f;
+       sTTY(1);
+       return (ot);
+}
+
+gTTY(i)
+       int i;
+{
+
+       ignore(gtty(i, &tty));
+}
+
+sTTY(i)
+       int i;
+{
+
+#ifdef V6
+       stty(i, &tty);
+#else
+       ioctl(i, TIOCSETN, &tty);
+#endif
+}
+
+/*
+ * Print newline, or blank if in open/visual
+ */
+noonl()
+{
+
+       putchar(Outchar != termchar ? ' ' : '\n');
+}