+/* 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);
+}