BSD 3 release
[unix-history] / usr / src / cmd / ex / ex_io.c
/* 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.
*/
int altdot;
int oldadot;
bool wasalt;
long cntch; /* Count of characters on unit io */
#ifndef VMUNIX
short cntln; /* Count of lines " */
#else
int cntln;
#endif
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;
static char fpatbuf[32]; /* hence limit on :next +/pat */
pastwh();
if (peekchar() == '+') {
for (cp = fpatbuf;;) {
c = *cp++ = getchar();
if (cp >= &fpatbuf[sizeof(fpatbuf)])
error("Pattern too long");
if (c == '\\' && isspace(peekchar()))
c = getchar();
if (c == EOF || isspace(c)) {
ungetchar(c);
*--cp = 0;
firstpat = &fpatbuf[1];
break;
}
}
}
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);
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.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;
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 || firstpat) {
register line *addr = zero + oldadot;
if (addr > dol)
addr = dol;
if (firstpat) {
globp = (*firstpat) ? firstpat : "$";
commands(1,1);
firstpat = 0;
} else if (addr >= one) {
if (inopen)
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(dofname)
bool dofname; /* if 1 call filename, else use savedfile */
{
register int c, exclam, nonexist;
line *saddr1, *saddr2;
struct stat stbuf;
c = 0;
exclam = 0;
if (dofname) {
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');
} else {
saddr1=addr1;
saddr2=addr2;
addr1=one;
addr2=dol;
CP(file, savedfile);
if (inopen) {
vclrech(0);
splitw++;
}
lprintf("\"%s\"", file);
}
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();
}
if (!dofname) {
addr1 = saddr1;
addr2 = saddr2;
}
}
/*
* 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)
ckaw();
if (warn && hush == 0 && chng && xchng != chng && value(WARN) && dol > zero) {
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();
if (hush == 0)
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");
}
#ifndef VFORK
pid = fork();
#else
pid = vfork();
#endif
if (pid < 0) {
if (mode & 1) {
close(pvec[0]);
close(pvec[1]);
}
setrupt();
error("No more processes");
}
if (pid == 0) {
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);
#ifndef VMUNIX
close(erfile);
#endif
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) {
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) {
undap1 = undap2 = addr2+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);
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;
int oprompt;
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);
oprompt = value(PROMPT);
value(PROMPT) &= intty;
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;
value(PROMPT) = oprompt;
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);
}