/*
 * This code contains changes by
 *      Gunnar Ritter, Freiburg i. Br., Germany, 2002. All rights reserved.
 *
 * Conditions 1, 2, and 4 and the no-warranty notice below apply
 * to these changes.
 *
 *
 * Copyright (c) 1980, 1993
 * 	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 * Copyright(C) Caldera International Inc. 2001-2002. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   Redistributions of source code and documentation must retain the
 *    above copyright notice, this list of conditions and the following
 *    disclaimer.
 *   Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *   All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed or owned by Caldera
 *      International, Inc.
 *   Neither the name of Caldera International, Inc. nor the names of
 *    other contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
 * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE
 * LIABLE FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef	lint
#ifdef	DOSCCS
static char sccsid[] = "@(#)ex_io.c	1.25 (gritter) 9/3/03";
#endif
#endif

/* from ex_io.c	7.11.1.1 (Berkeley) 8/12/86 */

#include "ex.h"
#include "ex_argv.h"
#include "ex_temp.h"
#include "ex_tty.h"
#include "ex_vis.h"

/*
 * File input/output, source, preserve and recover
 */

/*
 * Following remember where . was in the previous file for return
 * on file switching.
 */
int	altdot;
int	oldadot;
bool	wasalt;
short	isalt;

long	cntch;			/* Count of characters on unit io */
#ifndef VMUNIX
short	cntln;			/* Count of lines " */
#else
int	cntln;
#endif
long	cntnull;		/* Count of nulls " */
#ifndef	BIT8
long	cntodd;			/* Count of non-ascii characters " */
#endif

int	checkmodeline __P((char *));

/*
 * 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(catgets(catd, 1, 72,
					"No file|No current filename"));
		CP(file, savedfile);
		wasalt = (isalt > 0) ? isalt-1 : 0;
		isalt = 0;
		oldadot = altdot;
		if (c == 'e' || c == 'E')
			altdot = lineDOT();
		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 (value(READONLY))
				printf(catgets(catd, 1, 73, " [Read only]"));
			if (!edited)
				printf(catgets(catd, 1, 74, " [Not edited]"));
			if (tchng)
				printf(catgets(catd, 1, 75, " [Modified]"));
		}
		flush();
	} else
		printf(catgets(catd, 1, 76, "No file "));
	if (comm == 'f') {
		if (!(i = lineDOL()))
			i++;
		printf(catgets(catd, 1, 77,
			" 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(catgets(catd, 1, 78, "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(catgets(catd, 1, 79,
						"Argument buffer overflow"));
			*cp++ = c;
			break;

		case '#':
			fp = altfile;
			if (*fp == 0)
				error(catgets(catd, 1, 80,
				"No alternate filename@to substitute for #"));
			goto filexp;

		case '%':
			fp = savedfile;
			if (*fp == 0)
				error(catgets(catd, 1, 81,
				"No current filename@to substitute for %%"));
filexp:
			while (*fp) {
				if (cp > &genbuf[LBSIZE - 2])
					goto flong;
				*cp++ = *fp++;
			}
			break;
		}
	}
	*cp = 0;
	return (1);
}

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

/*
 * Glob the argument words in genbuf, or if no globbing
 * is implied, just split them up directly.
 */
gglob(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(cuc(*v)))
				v++;
			if (!*v)
				break;
			*argv++ = cp;
			while (*v && !isspace(cuc(*v)))
				*cp++ = *v++;
			*cp++ = 0;
			gp->argc0++;
		}
		*argv = 0;
		return;
	}
	if (pipe(pvec) < 0)
		error(catgets(catd, 1, 82, "Can't make pipe to glob"));
	pid = fork();
	io = pvec[0];
	if (pid < 0) {
		close(pvec[1]);
		error(catgets(catd, 1, 83, "Can't fork to do glob"));
	}
	if (pid == 0) {
		int oerrno;

		close(1);
		dup(pvec[1]);
		close(pvec[0]);
		close(2);	/* so errors don't mess up the screen */
		open("/dev/null", O_WRONLY);
		execl(svalue(SHELL), "sh", "-c", genbuf, (char *)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
#ifndef	BIT8
				c = ch & TRIM;
#else
				c = ch;
#endif
			if (c <= 0 || isspace(c))
				break;
			*cp++ = c;
			if (--nleft <= 0)
				error(catgets(catd, 1, 84,
							"Arg list too long"));
		}
		if (cp != *argv) {
			--nleft;
			*cp++ = 0;
			gp->argc0++;
			if (gp->argc0 >= NARGS)
				error(catgets(catd, 1, 85,
							"Arg list too long"));
			argv++;
		}
	} while (c >= 0);
	waitfor();
	if (gp->argc0 == 0)
		error(catgets(catd, 1, 86, "No match"));
}

/*
 * Parse one filename into file.
 */
struct glob G;
getone()
{
	register char *str;

	if (getargs() == 0)
missing:
		error(catgets(catd, 1, 87, "Missing filename"));
	gglob(&G);
	if (G.argc0 > 1)
		error(catgets(catd, 1, 88, "Ambiguous|Too many file names"));
	if ((str = G.argv[G.argc0 - 1]) == NULL)
		goto missing;
	if (strlen(str) > FNSIZE - 4)
		error(catgets(catd, 1, 89, "Filename too long"));
/* samef: */
	CP(file, str);
}

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

/*
 * 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;
	char magic[4];
	static int ovro;	/* old value(READONLY) */
	static int denied;	/* 1 if READONLY was set due to file permissions */

	io = open(file, O_RDONLY);
	if (io < 0) {
		if (c == 'e' && errno == ENOENT) {
			edited++;
			/*
			 * If the user just did "ex foo" he is probably
			 * creating a new file.  Don't be an error, since
			 * this is ugly, and it screws up the + option.
			 */
			if (!seenprompt) {
				printf(catgets(catd, 1, 90, " [New file]"));
				noonl();
				return;
			}
		}
		syserror();
	}
	if (fstat(io, &stbuf))
		syserror();
#ifndef	POSIX_1
	switch (stbuf.st_mode & S_IFMT) {

	case S_IFBLK:
#else
	if (S_ISBLK(stbuf.st_mode)) {
#endif
		error(catgets(catd, 1, 91, " Block special file"));

#ifndef	POSIX_1
	case S_IFCHR:
#else
	} else if (S_ISCHR(stbuf.st_mode)) {
#endif
		if (isatty(io))
			error(catgets(catd, 1, 92, " Teletype"));
		if (samei(&stbuf, "/dev/null"))
#ifndef	POSIX_1
			break;
#else
			goto end_ft;
#endif
		error(catgets(catd, 1, 93, " Character special file"));

#ifndef	POSIX_1
	case S_IFDIR:
#else
	} else if (S_ISDIR(stbuf.st_mode)) {
#endif
		error(catgets(catd, 1, 94, " Directory"));

#ifdef	POSIX_1
#ifdef	S_ISSOCK
	} else if (S_ISSOCK(stbuf.st_mode)) {
		error(catgets(catd, 1, 95, " Socket"));
#endif
#ifdef	S_ISFIFO
	} else if (S_ISFIFO(stbuf.st_mode)) {
		error(catgets(catd, 1, 96, " Named pipe"));
#endif
#endif	/* POSIX_1 */

#ifndef	POSIX_1
	case S_IFREG:
#else
	} else if (S_ISREG(stbuf.st_mode)) {
#endif
		/*
		 * The magics are checked byte-wise now to avoid
		 * endianness problems. Some quite old types
		 * were omitted.
		 *
		 * Feel free too add more magics here, but do not
		 * make this a copy of the `file' program.
		 *
		 * GR
		 */
		i = read(io, magic, sizeof(magic));
		lseek(io, (off_t) 0, SEEK_SET);
		if (i != sizeof(magic))
#ifndef	POSIX_1
			break;
#else
			goto end_ft;
#endif
		switch (cuc(magic[0])) {

		case 01:	/* big endian a.out */
			if (magic[1] != 05 && magic[1] != 07
				&& magic[1] != 010 && magic[1] != 011
				&& magic[1] != 013 && magic[1] != 030
				&& magic[1] != 031)
				break;
			goto is_exec;
		case 0314:	/* Linux/ia32 QMAGIC */
			if (magic[1] != 0 || magic[2] != 0144)
				break;
			goto is_exec;
		case 05:	/* data overlay on exec */
		case 07:	/* unshared */
		case 010:	/* shared text */
		case 011:	/* separate I/D */
		case 013:	/* VM/Unix demand paged */
		case 030:	/* PDP-11 Overlay shared */
		case 031:	/* PDP-11 Overlay sep I/D */
			if (magic[1] == 01)
is_exec:
				error(catgets(catd, 1, 97, " Executable"));
			break;

		case 037:
			switch (cuc(magic[1])) {
			case 036:	/* pack */
			case 037:	/* compact */
			case 0235:	/* compress */
			case 0213:	/* gzip */
			/*
			 * We omit bzip2 here since it has
			 * an ASCII header.
			 */
				error(catgets(catd, 1, 98, " Compressed Data"));
			}
			break;

		case 0177:
			if (magic[1] == 'E' && magic[2] == 'L'
					&& magic[3] == 'F')
				error(catgets(catd, 1, 99, " ELF object"));
			break;

		default:
			break;
		}
#ifdef	notdef
		/*
		 * We do not forbid the editing of portable archives
		 * because it is reasonable to edit them, especially
		 * if they are archives of text files.  This is
		 * especially useful if you archive source files together
		 * and copy them to another system with ~%take, since
		 * the files sometimes show up munged and must be fixed.
		 */
		case 0177545:
		case 0177555:
			error(catgets(catd, 1, 100, " Archive"));

		default:
#ifdef mbb
			/* C/70 has a 10 bit byte */
			if (magic & 03401600)
#else
			/* Everybody else has an 8 bit byte */
			if (magic & 0100200)
#endif
				error(catgets(catd, 1, 101, " Non-ascii file"));
			break;
		}
#endif	/* notdef */
	}
#ifdef	POSIX_1
end_ft:
#endif
	if (c != 'r') {
		if (value(READONLY) && denied) {
			value(READONLY) = ovro;
			denied = 0;
		}
		if ((stbuf.st_mode & 0222) == 0 || access(file, W_OK) < 0) {
			ovro = value(READONLY);
			denied = 1;
			value(READONLY) = 1;
		}
	}
	if (value(READONLY) && !hush) {
		printf(catgets(catd, 1, 102, " [Read only]"));
		flush();
	}
	if (c == 'r')
		setdot();
	else
		setall();
	if (FIXUNDO && inopen && c == 'r')
		undap1 = undap2 = addr1 + 1;
	rop2();
	rop3(c);
}

rop2()
{
	line *first, *last, *a;
	struct stat statb;

	deletenone();
	clrstats();
	first = addr2 + 1;
	if (fstat(io, &statb) < 0 || statb.st_blksize > LBSIZE)
		bsize = LBSIZE;
	else {
		bsize = statb.st_blksize;
		if (bsize <= 0)
			bsize = LBSIZE;
	}
	ignore(append(getfile, addr2));
	last = dot;
	/*
	 *	if the modelines variable is set,
	 *	check the first and last five lines of the file
	 *	for a mode line.
	 */
	if (value(MODELINES)) {
		for (a=first; a<=last; a++) {
			if (a==first+5 && last-first > 10)
				a = last - 4;
			getline(*a);
			checkmodeline(linebuf);
		}
	}
}

/*
 * Io is finished, close the unit and print statistics.
 */
iostats()
{

	fsync(io);
	close(io);
	io = -1;
	if (hush == 0) {
		if (value(TERSE))
			printf(catgets(catd, 1, 103,
						" %d/%d"), cntln, (int)cntch);
		else
			printf(catgets(catd, 1, 104,
		" %d line%s, %d character%s"), cntln, plural((long) cntln),
			    (int)cntch, plural((int)cntch));
		if (cntnull
#ifndef	BIT8
				|| cntodd
#endif
				) {
			printf(catgets(catd, 1, 105, " ("));
			if (cntnull) {
				printf(catgets(catd, 1, 106,
						"%d null"), (int)cntnull);
#ifndef	BIT8
				if (cntodd)
					printf(catgets(catd, 1, 107, ", "));
#endif
			}
#ifndef	BIT8
			if (cntodd)
				printf(catgets(catd, 1, 108,
					"%d non-ASCII"), (int)cntodd);
#endif
			putchar(')');
		}
		noonl();
		flush();
	}
	return (cntnull != 0
#ifndef	BIT8
			|| cntodd != 0
#endif
			);
}

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);
			}
		if(FIXUNDO)
			undkind = UNDNONE;
		if (inopen) {
			vcline = 0;
			vreplace(0, TLINES, lineDOL());
		}
	}
}

/* Returns from edited() */
#define	EDF	0		/* Edited file */
#define	NOTEDF	-1		/* Not edited file */
#define	PARTBUF	1		/* Write of partial buffer to Edited file */

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

/*
 * Write a file.
 */
wop(dofname)
bool dofname;	/* if 1 call filename, else use savedfile */
{
	register int c, exclam, nonexist;
	line *saddr1 = NULL, *saddr2 = NULL;
	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(catgets(catd, 1, 109,
					"Write forms are 'w' and 'w>>'"));
		filename('w');
	} else {
		if (savedfile[0] == 0)
			error(catgets(catd, 1, 110,
					"No file|No current filename"));
		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) || value(READONLY)))
		switch (edfile()) {
		
		case NOTEDF:
			if (nonexist)
				break;
#ifndef	POSIX_1
			if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
#else
			if (S_ISCHR(stbuf.st_mode)) {
#endif
				if (samei(&stbuf, "/dev/null"))
					break;
				if (samei(&stbuf, "/dev/tty"))
					break;
			}
			io = open(file, O_WRONLY);
			if (io < 0)
				syserror();
			if (!isatty(io))
				serror(catgets(catd, 1, 111,
	" File exists| File exists - use \"w! %s\" to overwrite"), file);
			close(io);
			break;

		case EDF:
			if (value(READONLY))
				error(catgets(catd, 1, 112,
						" File is read only"));
			break;

		case PARTBUF:
			if (value(READONLY))
				error(catgets(catd, 1, 113,
						" File is read only"));
			error(catgets(catd, 1, 114,
				" Use \"w!\" to write partial buffer"));
		}
cre:
/*
		synctmp();
*/
#ifdef V6
		io = creat(file, 0644);
#else
		io = creat(file, 0666);
#endif
		if (io < 0)
			syserror();
		writing = 1;
		if (hush == 0)
			if (nonexist)
				printf(catgets(catd, 1, 115, " [New file]"));
			else if (value(WRITEANY) && edfile() != EDF)
				printf(catgets(catd, 1, 116,
							" [Existing file]"));
		break;

	case 2:
		io = open(file, O_WRONLY);
		if (io < 0) {
			if (exclam || value(WRITEANY))
				goto cre;
			syserror();
		}
		lseek(io, (off_t) 0, SEEK_END);
		break;
	}
	putfile(0);
	ignore(iostats());
	if (c != 2 && addr1 == one && addr2 == dol) {
		if (eq(file, savedfile))
			edited = 1;
		synced();
	}
	if (!dofname) {
		addr1 = saddr1;
		addr2 = saddr2;
	}
	writing = 0;
}

/*
 * Extract the next line from the io stream.
 */
char *nextip;

getfile()
{
	register short c;
	register char *lp, *fp;

	lp = linebuf;
	fp = nextip;
	do {
		if (--ninbuf < 0) {
			ninbuf = read(io, genbuf, bsize) - 1;
			if (ninbuf < 0) {
				if (lp != linebuf) {
					lp++;
					printf(catgets(catd, 1, 117,
						" [Incomplete last line]"));
					break;
				}
				return (EOF);
			}
			fp = genbuf;
			cntch += ninbuf+1;
		}
		if (lp >= &linebuf[LBSIZE]) {
			synced();
			error(catgets(catd, 1, 118, " Line too long"));
		}
		c = *fp++;
		if (c == 0) {
			cntnull++;
#ifndef	BIT8
			continue;
#else
			c = 0200;
#endif
		}
#ifndef	BIT8
		if (c & QUOTE) {
			cntodd++;
			c &= TRIM;
			if (c == 0)
				continue;
		}
#endif
		*lp++ = c;
	} while (c != '\n');
	*--lp = 0;
	nextip = fp;
	cntln++;
	return (0);
}

/*
 * Write a range onto the io stream.
 */
putfile(isfilter)
int isfilter;
{
	line *a1;
	register char *fp, *lp;
	register int nib;
	struct stat statb;

	a1 = addr1;
	clrstats();
	cntln = fixedzero ? 0 : addr2 - a1 + 1;
	if (cntln == 0 || fixedzero)
		return;
	if (fstat(io, &statb) < 0 || statb.st_blksize > LBSIZE)
		bsize = LBSIZE;
	else {
		bsize = statb.st_blksize;
		if (bsize <= 0)
			bsize = LBSIZE;
	}
	nib = bsize;
	fp = genbuf;
	do {
		getline(*a1++);
		lp = linebuf;
		for (;;) {
			if (--nib < 0) {
				nib = fp - genbuf;
				if (write(io, genbuf, nib) != nib) {
					wrerror();
				}
				cntch += nib;
				nib = bsize - 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.
 */
short slevel;
short ttyindes;

source(fil, okfail)
	char *fil;
	bool okfail;
{
	JMP_BUF osetexit;
	register int saveinp, ointty, oerrno;
	char *saveglobp;
	short savepeekc;

	signal(SIGINT, SIG_IGN);
	saveinp = dup(0);
	savepeekc = peekc;
	saveglobp = globp;
	peekc = 0; globp = 0;
	if (saveinp < 0)
		error(catgets(catd, 1, 119, "Too many nested sources"));
	if (slevel <= 0)
		ttyindes = saveinp;
	close(0);
	if (open(fil, O_RDONLY) < 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);
	globp = saveglobp;
	peekc = savepeekc;
	slevel--;
	resexit(osetexit);
}

/*
 * Clear io statistics before a read or write.
 */
clrstats()
{

	ninbuf = 0;
	cntch = 0;
	cntln = 0;
	cntnull = 0;
#ifndef	BIT8
	cntodd = 0;
#endif
}

/* It's so wonderful how we all speak the same language... */
#ifdef	__STDC__
# define index strchr
# define rindex strrchr
#else	/* !__STDC__ */
#ifdef	USG3TTY
# define index strchr
# define rindex strrchr
char	*strchr();
char	*strrchr();
#else	/* !USG3TTY */
#ifdef	USG
# define index strchr
# define rindex strrchr
char	*strchr();
char	*strrchr();
#endif	/* USG */
#endif	/* !USG3TTY */
#endif	/* !__STDC__ */

checkmodeline(lin)
char *lin;
{
	char *beg, *end;
	char cmdbuf[BUFSIZ];

	beg = index(lin, ':');
	if (beg == NULL)
		return;
	if (&beg[-2] < lin)
		return;
	if (!((beg[-2] == 'e' && beg[-1] == 'x')
	     || (beg[-2] == 'v' && beg[-1] == 'i'))) return;
	strncpy(cmdbuf, beg+1, sizeof cmdbuf);
	end = rindex(cmdbuf, ':');
	if (end == NULL)
		return;
	*end = 0;
	globp = cmdbuf;
	commands(1, 1);
}
