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