view ed.c @ 4:4165f1b57d18 default tip

Become SUSv3 compatible and thus remove own regexp code The Heirloom tools can be compiled to comply to several standards. This version does not need this flexibility. We can omit the regexp code and use the system's, by using the SU3 variant of ed. This is the latest of the supported standards.
author markus schnalke <meillo@marmaro.de>
date Mon, 13 Apr 2015 17:26:51 +0200
parents ac52712b2b5e
children
line wrap: on
line source

/*
 * Editor
 */

/*
 * Changes by Gunnar Ritter, Freiburg i. Br., Germany, July 2003.
 */
/*	from Unix 32V /usr/src/cmd/ed.c	*/
/*
 * 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.
 */

#if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4
#define	USED	__attribute__ ((used))
#elif defined __GNUC__
#define	USED	__attribute__ ((unused))
#else
#define	USED
#endif
static const char sccsid[] USED = "@(#)ed_su3.sl	1.99 (gritter) 7/27/06";

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include "sigset.h"
#include <setjmp.h>
#include <libgen.h>
#include <inttypes.h>
#include <locale.h>
#include <wchar.h>
#include <ctype.h>
#include <wctype.h>
#include <limits.h>
static int	FNSIZE;
static int	LBSIZE;
static int	RHSIZE;
#define	ESIZE	2048
static int	GBSIZE;
#undef	EOF
#define	EOF	-1
#define	puts(s)		xxputs(s)
#define	getline(t, n)	xxgetline(t, n)

#if (LONG_MAX > 017777777777L)
#define	MAXCNT	0777777777777777777777L		/* 2^63-1 */
#else
#define	MAXCNT	017777777777L			/* 2^31-1 */
#endif
#define	BLKMSK	(MAXCNT>>8)			/* was 0377 */

#define	READ	0
#define	WRITE	1
#define	EXIST	2

static int	peekc;
static int	lastc;
static char	*savedfile;
static char	*file;
static struct stat	fstbuf;
static char	*linebuf;
static char	*rhsbuf;
static char	expbuf[ESIZE + 4];
static long	*zero;
static long	*undzero;
static long	*dot;
static long	*unddot;
static long	*dol;
static long	*unddol;
static long	*addr1;
static long	*addr2;
static char	*genbuf;
static long	count;
static char	*linebp;
static int	ninbuf;
static int	io;
static int	ioeof;
static int	pflag;
static char	*wrtemp;
static uid_t	myuid;
static void	(*oldhup)(int);
static void	(*oldquit)(int);
static void	(*oldpipe)(int);
static int	vflag	= 1;
static int	listf;
static int	numbf;
static char	*globp;
static int	tfile	= -1;
static long	tline;
static char	tfname[64];
static char	ibuff[512];
static int	iblock	= -1;
static char	obuff[512];
static int	oblock	= -1;
static int	ichanged;
static int	nleft;
static long	*names;
static long	*undnames;
static int	anymarks;
static int	subnewa;
static int	fchange;
static int	wrapp;
static unsigned nlall = 128;
static const char	*progname;
static const char	*prompt = "*";
static int	Pflag;
static int	prhelp;
static const char	*prvmsg;
static int	lastsig;
static int	pipid = -1;
static int	readop;
static int	status;
static int	mb_cur_max;
static int	needsub;
static int	insub;
static int	maxlength;
static int	rspec;
static int	Nflag;
static int	bcount = 22;
static int	ocount = 11;

static jmp_buf	savej;

static void	usage(char);
static void	commands(void);
static long	*address(void);
static void	setdot(void);
static void	setall(void);
static void	setnoaddr(void);
static void	nonzero(void);
static void	newline(void);
static void	filename(int);
static void	exfile(void);
static void	onintr(int);
static void	onhup(int);
static void	onpipe(int);
static void	error(const char *);
static void	error2(const char *, const char *);
static void	errput(const char *, const char *);
static int	getchr(void);
static int	gettty(void);
static long	getnum(void);
static int	getfile(void);
static void	putfile(void);
static int	append(int (*)(void), long *);
static void	callunix(void);
static char	*readcmd(void);
static void	quit(int);
static void	delete(void);
static void	rdelete(long *, long *);
static void	gdelete(void);
static char	*getline(long, int);
static int	putline(void);
static char	*getblock(long, long);
static void	blkio(long, char *, int);
static void	init(void);
static void	global(int, int);
static void	globrd(char **, int);
static void	join(void);
static void	substitute(int);
static int	compsub(void);
static int	getsub(void);
static int	dosub(int);
static int	place(int, const char *, const char *);
static void	move(int);
static void	reverse(long *, long *);
static int	getcopy(void);
static int	execute(int, long *, int);
static void	cmplerr(int);
static void	doprnt(long *, long *);
static void	putd(long);
static void	puts(const char *);
static void	nlputs(const char *);
static void	list(const char *);
static int	lstchr(int);
static void	putstr(const char *);
static void	putchr(int);
static void	checkpoint(void);
static void	undo(void);
static int	maketf(int);
static int	creatf(const char *);
static int	sopen(const char *, int);
static void	sclose(int);
static void	growlb(const char *);
static void	growrhs(const char *);
static void	growfn(const char *);
static void	help(void);

#define	INIT
#define	GETC()		getchr()
#define	UNGETC(c)	(peekc = c)
#define	PEEKC()		(peekc = getchr())
#define	RETURN(c)	return c
#define	ERROR(c)	cmplerr(c)
static wint_t	GETWC(char *);

#include <regex.h>

#define	NBRA	9

static char	*braslist[NBRA];
static char	*braelist[NBRA];
static char	*loc1, *loc2, *locs;
static int	nbra;
static int	circf;
static int	nodelim;

static char	*compile(char *, char *, const char *, int);
static int	step(const char *, const char *);

int
main(int argc, char **argv)
{
	register int i;
	void (*oldintr)(int);

	progname = basename(argv[0]);
	setlocale(LC_COLLATE, "");
	setlocale(LC_CTYPE, "");
	mb_cur_max = MB_CUR_MAX;
	myuid = getuid();
	oldquit = sigset(SIGQUIT, SIG_IGN);
	oldhup = sigset(SIGHUP, SIG_IGN);
	oldintr = sigset(SIGINT, SIG_IGN);
	if (sigset(SIGTERM, SIG_IGN) != SIG_IGN)
		sigset(SIGTERM, quit);
	oldpipe = sigset(SIGPIPE, onpipe);
	argv++;
	if (argc > 1 && **argv=='-') {
		if ((*argv)[1]=='\0') {
			vflag = 0;
		} else {
			usage((*argv)[1]);
		}
		argv++;
		argc--;
	}

	growfn("no space");
	if (argc>1) {
		i = -1;
		do
			if (++i >= FNSIZE)
				growfn("maximum of characters in "
						"file names reached");
		while (savedfile[i] = (*argv)[i]);
		globp = "e";
	}
	names = malloc(26*sizeof *names);
	undnames = malloc(26*sizeof *undnames);
	zero = malloc(nlall*sizeof *zero);
	if ((undzero = malloc(nlall*sizeof *undzero)) == NULL)
		puts("no memory for undo");
	growlb("no space");
	growrhs("no space");
	init();
	if (oldintr != SIG_IGN)
		sigset(SIGINT, onintr);
	if (oldhup != SIG_IGN)
		sigset(SIGHUP, onhup);
	setjmp(savej);
	if (lastsig) {
		sigrelse(lastsig);
		lastsig = 0;
	}
	commands();
	quit(0);
	/*NOTREACHED*/
	return 0;
}

static void
usage(char c)
{
	if (c) {
		write(2, progname, strlen(progname));
		write(2, ": illegal option -- ", 20);
		write(2, &c, 1);
		write(2, "\n", 1);
	}
	write(2, "usage: ", 7);
	write(2, progname, strlen(progname));
	write(2, " [-] [file]\n", 12);
	exit(2);
}

static void
commands(void)
{
	register long *a1;
	register int c;
	int	n;

	for (;;) {
	if (pflag) {
		pflag = 0;
		addr1 = addr2 = dot;
		goto print;
	}
	if (Pflag && globp == NULL)
		write(1, prompt, strlen(prompt));
	addr1 = 0;
	addr2 = 0;
	switch (c = getchr()) {
	case ',':
	case ';':
		addr2 = c == ',' ? zero+1 : dot;
		if (((peekc = getchr()) < '0' || peekc > '9') &&
				peekc != ' ' && peekc != '\t' &&
				peekc != '+' && peekc != '-' &&
				peekc != '^' && peekc != '?' &&
				peekc != '/' && peekc != '$' &&
				peekc != '.' && peekc != '\'') {
			addr1 = addr2;
			a1 = dol;
			goto loop;
		}
		break;
	default:
		peekc = c;
	}
	do {
		addr1 = addr2;
		if ((a1 = address())==0) {
			c = getchr();
			break;
		}
	loop:	addr2 = a1;
		if ((c=getchr()) == ';') {
			c = ',';
			dot = a1;
		}
	} while (c==',');
	if (addr1==0)
		addr1 = addr2;
	switch(c) {

	case 'a':
		setdot();
		newline();
		checkpoint();
		append(gettty, addr2);
		continue;

	case 'c':
		if (addr1 == zero && addr1+1 <= dol) {
			if (addr1 == addr2)
				addr2++;
			addr1++;
		}
		delete();
		append(gettty, addr1-1);
		if (dot == addr1-1 && addr1 <= dol)
			dot = addr1;
		continue;

	case 'd':
		delete();
		continue;

	case 'E':
		fchange = 0;
		c = 'e';
	case 'e':
		setnoaddr();
		if (vflag && fchange) {
			fchange = 0;
			error("warning: expecting `w'");
		}
		filename(c);
		init();
		addr2 = zero;
		goto caseread;

	case 'f':
		setnoaddr();
		filename(c);
		puts(savedfile);
		continue;

	case 'g':
		global(1, 0);
		continue;

	case 'G':
		global(1, 1);
		continue;

	case 'H':
		prhelp = !prhelp;
		/*FALLTHRU*/

	case 'h':
		if ((peekc = getchr()) == 'e') {
			peekc = 0;
			if (getchr() != 'l' || getchr() != 'p' ||
					getchr() != '\n')
				error("illegal suffix");
			setnoaddr();
			help();
			continue;
		}
		newline();
		setnoaddr();
		if (prvmsg)
			puts(prvmsg);
		continue;

	case 'i':
		setdot();
		if (addr1 == zero) {
			if (addr1 == addr2)
				addr2++;
			addr1++;
			if (dol != zero)
				nonzero();
		} else
			nonzero();
		newline();
		checkpoint();
		append(gettty, addr2-1);
		if (dot == addr2-1)
			dot++;
		continue;


	case 'j':
		if (addr2==0) {
			addr1 = dot;
			addr2 = dot+1;
		}
		setdot();
		newline();
		nonzero();
		checkpoint();
		if (addr1 != addr2)
			join();
		continue;

	case 'k':
		if ((c = getchr()) < 'a' || c > 'z')
			error("mark not lower case");
		newline();
		setdot();
		nonzero();
		names[c-'a'] = *addr2 & ~01;
		anymarks |= 01;
		continue;

	case 'm':
		move(0);
		continue;

	case '\n':
		if (addr2==0)
			addr2 = dot+1;
		addr1 = addr2;
		goto print;

	case 'n':
		numbf = 1;
		newline();
		goto print;

	case 'N':
		newline();
		setnoaddr();
		Nflag = !Nflag;
		continue;

	case 'b':
	case 'o':
		n = getnum();
		newline();
		setdot();
		nonzero();
		if (n >= 0) {
			if (c == 'b')
				bcount = n;
			else
				ocount = n;
		}
		if (c == 'b') {
			a1 = addr2+bcount > dol ? dol : addr2 + bcount;
			doprnt(addr1, a1);
			dot = a1;
		} else {
			a1 = addr2+ocount > dol ? dol : addr2 + ocount;
			doprnt(addr2-ocount<zero+1?zero+1:addr2-ocount, a1);
			dot = addr2;
		}
		continue;

	case 'l':
		listf++;
	case 'p':
		newline();
	print:
		setdot();
		nonzero();
		doprnt(addr1, addr2);
		dot = addr2;
		continue;

	case 'P':
		setnoaddr();
		newline();
		Pflag = !Pflag;
		continue;

	case 'Q':
		fchange = 0;
	case 'q':
		setnoaddr();
		newline();
		quit(0);

	case 'r':
		filename(c);
	caseread:
		if ((io = sopen(file, READ)) < 0) {
			lastc = '\n';
			error2("cannot open input file", file);
		}
		ioeof = 0;
		setall();
		ninbuf = 0;
		if (c == 'r')
			checkpoint();
		n = zero != dol;
		rspec = (c == 'e' || !n) && file[0] != '!';
		append(getfile, addr2);
		rspec = 0;
		exfile();
		fchange = n;
		continue;

	case 's':
		setdot();
		nonzero();
		substitute(globp!=0);
		continue;

	case 't':
		move(1);
		continue;

	case 'u':
		setdot();
		newline();
		if (unddot == NULL)
			error("nothing to undo");
		undo();
		continue;

	case 'v':
		global(0, 0);
		continue;

	case 'V':
		global(0, 1);
		continue;

	case 'W':
		wrapp++;
	case 'w':
	write:
		setall();
		if (zero != dol)
			nonzero();
		filename(c);
		if(!wrapp ||
		  ((io = open(file,O_WRONLY|O_APPEND)) == -1) ||
		  ((lseek(io, 0, SEEK_END)) == -1)) {
			struct stat	st;
			if (lstat(file, &st) == 0 &&
					(st.st_mode&S_IFMT) == S_IFREG &&
					st.st_nlink == 1 &&
					(myuid==0 || myuid==st.st_uid)) {
				char	*cp, *tp;
				int	nio;
				if ((io = sopen(file, EXIST)) < 0)
					error("cannot create output file");
				if ((wrtemp = malloc(strlen(file)+8)) == NULL)
					error("out of memory");
				for (cp = file, tp = wrtemp; *cp; cp++)
					*tp++ = *cp;
				while (tp > wrtemp && tp[-1] != '/')
					tp--;
				for (cp = "\7XXXXXX"; *cp; cp++)
					*tp++ = *cp;
				*tp = '\0';
				if ((nio = mkstemp(wrtemp)) < 0) {
					free(wrtemp);
					wrtemp = NULL;
					ftruncate(io, 0);
				} else {
					close(io);
					io = nio;
				}
			} else {
				if ((io = sopen(file, WRITE)) < 0)
					error("cannot create output file");
			}
		}
		if (zero != dol) {
			ioeof = 0;
			wrapp = 0;
			putfile();
		}
		exfile();
		if (addr1==zero+1 && addr2==dol || addr1==addr2 && dol==zero)
			fchange = 0;
		if (c == 'z')
			quit(0);
		continue;

	case 'z':
		if ((peekc=getchr()) != '\n')
			error("illegal suffix");
		setnoaddr();
		goto write;

	case '=':
		setall();
		newline();
		putd((addr2-zero)&MAXCNT);
		putchr('\n');
		continue;

	case '!':
		callunix();
		continue;

	case EOF:
		return;

	}
	error("unknown command");
	}
}

static long *
address(void)
{
	register long *a1;
	register int minus, c;
	int n, relerr;

	minus = 0;
	a1 = 0;
	for (;;) {
		c = getchr();
		if ('0'<=c && c<='9') {
			n = 0;
			do {
				n *= 10;
				n += c - '0';
			} while ((c = getchr())>='0' && c<='9');
			peekc = c;
			if (a1==0)
				a1 = zero;
			if (minus<0)
				n = -n;
			a1 += n;
			minus = 0;
			continue;
		}
		relerr = 0;
		if (a1 || minus)
			relerr++;
		switch(c) {
		case ' ':
		case '\t':
			continue;
	
		case '+':
			minus++;
			if (a1==0)
				a1 = dot;
			continue;

		case '-':
		case '^':
			minus--;
			if (a1==0)
				a1 = dot;
			continue;
	
		case '?':
		case '/':
			compile(NULL, expbuf, &expbuf[ESIZE], c);
			a1 = dot;
			for (;;) {
				if (c=='/') {
					a1++;
					if (a1 > dol)
						a1 = zero;
				} else {
					a1--;
					if (a1 < zero)
						a1 = dol;
				}
				if (execute(0, a1, 0))
					break;
				if (a1==dot)
					error("search string not found");
			}
			break;
	
		case '$':
			a1 = dol;
			break;
	
		case '.':
			a1 = dot;
			break;

		case '\'':
			if ((c = getchr()) < 'a' || c > 'z')
				error("mark not lower case");
			for (a1=zero; a1<=dol; a1++)
				if (names[c-'a'] == (*a1 & ~01))
					break;
			break;
	
		default:
			peekc = c;
			if (a1==0)
				return(0);
			a1 += minus;
			if (a1<zero || a1>dol)
				error("line out of range");
			return(a1);
		}
		if (relerr)
			error("bad number");
	}
}

static void
setdot(void)
{
	if (addr2 == 0)
		addr1 = addr2 = dot;
	if (addr1 > addr2)
		error("bad range");
}

static void
setall(void)
{
	if (addr2==0) {
		addr1 = zero+1;
		addr2 = dol;
		if (dol==zero)
			addr1 = zero;
	}
	setdot();
}

static void
setnoaddr(void)
{
	if (addr2)
		error("Illegal address count");
}

static void
nonzero(void)
{
	if (addr1<=zero || addr2>dol)
		error("line out of range");
}

static void
newline(void)
{
	register int c;

	if ((c = getchr()) == '\n')
		return;
	if (c=='p' || c=='l' || c=='n') {
		pflag++;
		if (c=='l')
			listf++;
		else if (c=='n')
			numbf = 1;
		if (getchr() == '\n')
			return;
	}
	error("illegal suffix");
}

static void
filename(int comm)
{
	register char *p1, *p2;
	register int c, i;

	count = 0;
	c = getchr();
	if (c=='\n' || c==EOF) {
		p1 = savedfile;
		if (*p1==0 && comm!='f')
			error("illegal or missing filename");
		p2 = file;
		while (*p2++ = *p1++)
			;
		return;
	}
	if (c!=' ')
		error("no space after command");
	while ((c = getchr()) == ' ')
		;
	if (c=='\n')
		error("illegal or missing filename");
	i = 0;
	do {
		if (i >= FNSIZE)
			growfn("maximum of characters in file names reached");
		file[i++] = c;
		if (c==' ' && file[0] != '!' || c==EOF)
			error("illegal or missing filename");
	} while ((c = getchr()) != '\n');
	file[i++] = 0;
	if ((savedfile[0]==0 || comm=='e' || comm=='f') && file[0] != '!') {
		p1 = savedfile;
		p2 = file;
		while (*p1++ = *p2++)
			;
	}
}

static void
exfile(void)
{
	sclose(io);
	io = -1;
	if (wrtemp) {
		extern int	rename(const char *, const char *);
		if (rename(wrtemp, file) < 0)
			error("cannot create output file");
		if (myuid == 0)
			chown(file, fstbuf.st_uid, fstbuf.st_gid);
		chmod(file, fstbuf.st_mode & 07777);
		free(wrtemp);
		wrtemp = NULL;
	}
	if (vflag) {
		putd(count);
		putchr('\n');
	}
}

static void
onintr(int signo)
{
	lastsig = signo;
	putchr('\n');
	lastc = '\n';
	if (readop) {
		puts("\007read may be incomplete - beware!\007");
		fchange = 0;
	}
	error("interrupt");
}

static void
onhup(int signo)
{
	if (dol > zero && fchange) {
		addr1 = zero+1;
		addr2 = dol;
		io = creat("ed.hup", 0666);
		if (io < 0) {
			char	*home = getenv("HOME");
			if (home) {
				char	*fn = malloc(strlen(home) + 10);
				if (fn) {
					strcpy(fn, home);
					strcat(fn, "/ed.hup");
					io = creat(fn, 0666);
				}
			}
		}
		if (io >= 0)
			putfile();
	}
	fchange = 0;
	status = 0200 | signo;
	quit(0);
}

static void
onpipe(int signo)
{
	lastsig = signo;
	error("write or open on pipe failed");
}

static void
error(const char *s)
{
	error2(s, NULL);
}

static void
error2(const char *s, const char *fn)
{
	register int c;

	wrapp = 0;
	listf = 0;
	numbf = 0;
	errput(s, fn);
	count = 0;
	if (lseek(0, 0, SEEK_END) > 0)
		status = 2;
	pflag = 0;
	if (globp)
		lastc = '\n';
	globp = 0;
	peekc = lastc;
	if(lastc)
		while ((c = getchr()) != '\n' && c != EOF)
			;
	if (io > 0) {
		sclose(io);
		io = -1;
	}
	if (wrtemp) {
		unlink(wrtemp);
		free(wrtemp);
		wrtemp = NULL;
	}
	longjmp(savej, 1);
}

static void
errput(const char *s, const char *fn)
{
	prvmsg = s;
	if (fn) {
		putchr('?');
		puts(fn);
	} else
		puts("?");
	if (prhelp)
		puts(s);
}

static int
getchr(void)
{
	char c;
	if (lastc=peekc) {
		peekc = 0;
		return(lastc);
	}
	if (globp) {
		if ((lastc = *globp++) != 0)
			return(lastc);
		globp = 0;
		return(EOF);
	}
	if (read(0, &c, 1) <= 0)
		return(lastc = EOF);
	lastc = c;
	return(lastc);
}

static int
gettty(void)
{
	register int c, i;
	register char *gf;

	i = 0;
	gf = globp;
	while ((c = getchr()) != '\n') {
		if (c==EOF) {
			if (gf)
				peekc = c;
			return(c);
		}
		if (c == 0)
			continue;
		if (i >= LBSIZE)
			growlb("line too long");
		linebuf[i++] = c;
	}
	if (i >= LBSIZE-2)
		growlb("line too long");
	linebuf[i++] = 0;
	if (linebuf[0]=='.' && linebuf[1]==0)
		return(EOF);
	return(0);
}

static long
getnum(void)
{
	char	scount[20];
	int	i;

	i = 0;
	while ((peekc=getchr()) >= '0' && peekc <= '9' && i < sizeof scount) {
		scount[i++] = peekc;
		peekc = 0;
	}
	scount[i] = '\0';
	return i ? atol(scount) : -1;
}

static int
getfile(void)
{
	register int c, i, j;
	static int	nextj;

	i = 0;
	j = nextj;
	do {
		if (--ninbuf < 0) {
			if (ioeof || (ninbuf=read(io, genbuf, LBSIZE)-1) < 0) {
				if (ioeof == 0 && ninbuf < -1) {
					puts("input error");
					status = 1;
				}
				if (i > 0) {
					puts("'\\n' appended");
					c = '\n';
					ioeof = 1;
					goto wrc;
				}
				return(EOF);
			}
			j = 0;
		}
		c = genbuf[j++]&0377;
	wrc:	if (i >= LBSIZE) {
			lastc = '\n';
			growlb("line too long");
		}
		linebuf[i++] = c ? c : '\n';
		count++;
	} while (c != '\n');
	linebuf[--i] = 0;
	nextj = j;
	if (maxlength && i > maxlength) {
		putstr("line too long: lno = ");
		putd((dot - zero+1)&MAXCNT);
		putchr('\n');
	}
	return(0);
}

static void
putfile(void)
{
	long *a1;
	int n;
	register char *fp, *lp;
	register int nib;

	nib = 512;
	fp = genbuf;
	a1 = addr1;
	do {
		lp = getline(*a1++, 0);
		if (maxlength) {
			for (n = 0; lp[n]; n++);
			if (n > maxlength) {
				putstr("line too long: lno = ");
				putd((a1-1 - zero)&MAXCNT);
				putchr('\n');
			}
		}
		for (;;) {
			if (--nib < 0) {
				n = fp-genbuf;
				if(write(io, genbuf, n) != n)
					error("write error");
				nib = 511;
				fp = genbuf;
			}
			count++;
			if ((*fp++ = *lp++) == 0) {
				fp[-1] = '\n';
				break;
			} else if (fp[-1] == '\n')
				fp[-1] = '\0';
		}
	} while (a1 <= addr2);
	n = fp-genbuf;
	if(write(io, genbuf, n) != n)
		error("write error");
}

static int
append(int (*f)(void), long *a)
{
	register long *a1, *a2, *rdot;
	int nline, tl;

	nline = 0;
	dot = a;
	while ((*f)() == 0) {
		if ((dol-zero)+1 >= nlall) {
			long *ozero = zero;
			nlall += 512;
			if ((zero = realloc(zero, nlall*sizeof *zero))==NULL) {
				lastc = '\n';
				zero = ozero;
				error("out of memory for append");
			}
			dot += zero - ozero;
			dol += zero - ozero;
			addr1 += zero - ozero;
			addr2 += zero - ozero;
			if (unddot) {
				unddot += zero - ozero;
				unddol += zero - ozero;
			}
			if (undzero) {
				ozero = undzero;
				if ((undzero = realloc(undzero,
						nlall*sizeof *undzero)) == 0) {
					puts("no memory for undo");
					free(ozero);
				}
			}
		}
		tl = putline();
		nline++;
		a1 = ++dol;
		a2 = a1+1;
		rdot = ++dot;
		while (a1 > rdot)
			*--a2 = *--a1;
		*rdot = tl;
	}
	return(nline);
}

static void
callunix(void)
{
	char	*line;
	void (*savint)(int);
	pid_t pid, rpid;
	int retcode;

	setnoaddr();
	line = readcmd();
	if ((pid = fork()) == 0) {
		sigset(SIGHUP, oldhup);
		sigset(SIGQUIT, oldquit);
		sigset(SIGPIPE, oldpipe);
		execl(SHELL, "sh", "-c", line, NULL);
		_exit(0100);
	} else if (pid < 0)
		error("fork failed - try again");
	savint = sigset(SIGINT, SIG_IGN);
	while ((rpid = wait(&retcode)) != pid && rpid != -1)
		;
	sigset(SIGINT, savint);
	if (vflag)
		puts("!");
}

#define	cmadd(c)	((i>=cmsize ? \
		((line=realloc(line,cmsize+=128)) == 0 ? \
			(error("line too long"),0) : 0, 0) \
		: 0), line[i++]=(c))

static char *
readcmd(void)
{
	static char	*line, *prev;
	static int	cmsize, pvsize;
	char	*pp;
	int	c, mod = 0, i;

	i = 0;
	if ((c = getchr()) == '!') {
		for (pp = prev; *pp; pp++)
			line[i++] = *pp;
		mod = 1;
		c = getchr();
	}
	while (c != '\n' && c != EOF) {
		if (c == '\\') {
			c = getchr();
			if (c != '%')
				cmadd('\\');
			cmadd(c);
		} else if (c == '%') {
			for (pp = savedfile; *pp; pp++)
				cmadd(*pp);
			mod = 1;
		} else
			cmadd(c);
		c = getchr();
	}
	cmadd('\0');
	if (pvsize < cmsize && (prev = realloc(prev, pvsize=cmsize)) == 0)
		error("line too long");
	strcpy(prev, line);
	if (mod)
		nlputs(line);
	return line;
}

static void
quit(int signo)
{
	lastsig = signo;
	if (vflag && fchange) {
		fchange = 0;
		error("warning: expecting `w'");
	}
	if (wrtemp)
		unlink(wrtemp);
	unlink(tfname);
	exit(status);
}

static void
delete(void)
{
	setdot();
	newline();
	nonzero();
	checkpoint();
	rdelete(addr1, addr2);
}

static void
rdelete(long *ad1, long *ad2)
{
	register long *a1, *a2, *a3;

	a1 = ad1;
	a2 = ad2+1;
	a3 = dol;
	dol -= a2 - a1;
	do {
		*a1++ = *a2++;
	} while (a2 <= a3);
	a1 = ad1;
	if (a1 > dol)
		a1 = dol;
	dot = a1;
	fchange = 1;
}

static void
gdelete(void)
{
	register long *a1, *a2, *a3;

	a3 = dol;
	for (a1=zero+1; (*a1&01)==0; a1++)
		if (a1>=a3)
			return;
	for (a2=a1+1; a2<=a3;) {
		if (*a2&01) {
			a2++;
			dot = a1;
		} else
			*a1++ = *a2++;
	}
	dol = a1-1;
	if (dot>dol)
		dot = dol;
	fchange = 1;
}

static char *
getline(long tl, int nulterm)
{
	register char *bp, *lp;
	register long nl;

	lp = linebuf;
	bp = getblock(tl, READ);
	nl = nleft;
	tl &= ~0377;
	while (*lp++ = *bp++) {
		if (lp[-1] == '\n' && nulterm) {
			lp[-1] = '\0';
			break;
		}
		if (--nl == 0) {
			bp = getblock(tl+=0400, READ);
			nl = nleft;
		}
	}
	return(linebuf);
}

static int
putline(void)
{
	register char *bp, *lp;
	register long nl;
	long tl;

	fchange = 1;
	lp = linebuf;
	tl = tline;
	bp = getblock(tl, WRITE);
	nl = nleft;
	tl &= ~0377;
	while (*bp = *lp++) {
		if (*bp++ == '\n' && insub) {
			*--bp = 0;
			linebp = lp;
			break;
		}
		if (--nl == 0) {
			bp = getblock(tl+=0400, WRITE);
			nl = nleft;
		}
	}
	nl = tline;
	tline += (((lp-linebuf)+03)>>1)&(MAXCNT-1);
	return(nl);
}

static char *
getblock(long atl, long iof)
{
	register long bno, off;
	
	bno = (atl>>8)&BLKMSK;
	off = (atl<<1)&0774;
	if (bno >= BLKMSK) {
		lastc = '\n';
		error("temp file too big");
	}
	nleft = 512 - off;
	if (bno==iblock) {
		ichanged |= iof;
		return(ibuff+off);
	}
	if (bno==oblock)
		return(obuff+off);
	if (iof==READ) {
		if (ichanged)
			blkio(iblock, ibuff, 1);
		ichanged = 0;
		iblock = bno;
		blkio(bno, ibuff, 0);
		return(ibuff+off);
	}
	if (oblock>=0)
		blkio(oblock, obuff, 1);
	oblock = bno;
	return(obuff+off);
}

static void
blkio(long b, char *buf, int wr)
{
	lseek(tfile, b<<9, SEEK_SET);
	if ((wr ? write(tfile, buf, 512) : read (tfile, buf, 512)) != 512) {
		status = 1;
		error("I/O error on temp file");
	}
}

static void
init(void)
{
	register long *markp;

	tline = 2;
	for (markp = names; markp < &names[26]; markp++)
		*markp = 0;
	for (markp = undnames; markp < &undnames[26]; markp++)
		*markp = 0;
	subnewa = 0;
	anymarks = 0;
	iblock = -1;
	oblock = -1;
	ichanged = 0;
	tfile = maketf(tfile);
	dot = dol = zero;
	unddot = NULL;
}

static void
global(int k, int ia)
{
	register int c;
	register long *a1;
	static char *globuf;
	char	mb[MB_LEN_MAX+1];
	int	spflag = 0;

	if (globp)
		error("multiple globals not allowed");
	setall();
	nonzero();
	if ((c=GETWC(mb))=='\n')
		error("incomplete global expression");
	compile(NULL, expbuf, &expbuf[ESIZE], c);
	if (!ia) {
		globrd(&globuf, EOF);
		if (globuf[0] == '\n')
			globuf[0] = 'p', globuf[1] = '\n', globuf[2] = '\0';
	} else {
		newline();
		spflag = pflag;
		pflag = 0;
	}
	checkpoint();
	for (a1=zero; a1<=dol; a1++) {
		*a1 &= ~01;
		if (a1>=addr1 && a1<=addr2 && execute(0, a1, 0)==k)
			*a1 |= 01;
	}
	/*
	 * Special case: g/.../d (avoid n^2 algorithm)
	 */
	if (!ia && globuf[0]=='d' && globuf[1]=='\n' && globuf[2]=='\0') {
		gdelete();
		return;
	}
	for (a1=zero; a1<=dol; a1++) {
		if (*a1 & 01) {
			*a1 &= ~01;
			dot = a1;
			if (ia) {
				puts(getline(*a1, 0));
				if ((c = getchr()) == EOF)
					error("command expected");
				if (c == 'a' || c == 'c' || c == 'i')
					error("a, i, or c not allowed in G");
				else if (c == '&') {
					if ((c = getchr()) != '\n')
						error("end of line expected");
					if (globuf == 0 || *globuf == 0)
						error("no remembered command");
				} else if (c == '\n') {
					a1 = zero;
					continue;
				} else
					globrd(&globuf, c);
			}
			globp = globuf;
			commands();
			globp = NULL;
			a1 = zero;
		}
	}
	if (ia)
		pflag = spflag;
}

static void
globrd(char **globuf, register int c)
{
	register int i;

	if (*globuf == 0 && (*globuf = malloc(GBSIZE=256)) == 0)
		error("global too long");
	i = 0;
	if (c != EOF)
		(*globuf)[i++] = c;
	while ((c = getchr()) != '\n') {
		if (c==EOF)
			error("incomplete global expression");
		if (c=='\\') {
			c = getchr();
			if (c!='\n')
				(*globuf)[i++] = '\\';
		}
		(*globuf)[i++] = c;
		if (i>=GBSIZE-4 && (*globuf=realloc(*globuf,GBSIZE+=256)) == 0)
			error("global too long");
	}
	(*globuf)[i++] = '\n';
	(*globuf)[i++] = 0;
}

static void
join(void)
{
	register int i, j;
	register long *a1;

	j = 0;
	for (a1=addr1; a1<=addr2; a1++) {
		i = getline(*a1, 0) - linebuf;
		while (genbuf[j] = linebuf[i++])
			if (j++ >= LBSIZE-2)
				growlb("line too long");
	}
	i = 0;
	j = 0;
	while (linebuf[i++] = genbuf[j++])
		;
	*addr1 = putline();
	if (addr1<addr2)
		rdelete(addr1+1, addr2);
	dot = addr1;
}

static void
substitute(int inglob)
{
	register long *markp;
	register long *a1;
	intptr_t nl;
	int gsubf;

	checkpoint();
	gsubf = compsub();
	insub = 1;
	for (a1 = addr1; a1 <= addr2; a1++) {
		long *ozero;
		if (execute(0, a1, 1)==0)
			continue;
		inglob |= dosub(gsubf < 2);
		if (gsubf) {
			int	i = 1;

			while (*loc2) {
				if (execute(1, NULL, 1)==0)
					break;
				inglob |= dosub(gsubf == -1 || ++i == gsubf);
			}
		}
		subnewa = putline();
		*a1 &= ~01;
		if (anymarks) {
			for (markp = names; markp < &names[26]; markp++)
				if (*markp == *a1)
					*markp = subnewa;
		}
		*a1 = subnewa;
		ozero = zero;
		nl = append(getsub, a1);
		nl += zero-ozero;
		a1 += nl;
		addr2 += nl;
	}
	insub = 0;
	if (inglob==0)
		error("no match");
}

static int
compsub(void)
{
	register int seof, c, i;
	static char	*oldrhs;
	static int	orhssz;
	char	mb[MB_LEN_MAX+1];

	if ((seof = GETWC(mb)) == '\n' || seof == ' ')
		error("illegal or missing delimiter");
	nodelim = 0;
	compile(NULL, expbuf, &expbuf[ESIZE], seof);
	i = 0;
	for (;;) {
		c = GETWC(mb);
		if (c=='\\') {
			if (i >= RHSIZE-2)
				growrhs("replacement string too long");
			rhsbuf[i++] = c;
			c = GETWC(mb);
		} else if (c=='\n') {
			if (globp && *globp) {
				if (i >= RHSIZE-2)
					growrhs("replacement string too long");
				rhsbuf[i++] = '\\';
			}
			else if (nodelim)
				error("illegal or missing delimiter");
			else {
				peekc = c;
				pflag++;
				break;
			}
		} else if (c==seof)
			break;
		for (c = 0; c==0 || mb[c]; c++) {
			if (i >= RHSIZE-2)
				growrhs("replacement string too long");
			rhsbuf[i++] = mb[c];
		}
	}
	rhsbuf[i++] = 0;
	if (rhsbuf[0] == '%' && rhsbuf[1] == 0) {
		if (orhssz == 0)
			error("no remembered replacement string");
		strcpy(rhsbuf, oldrhs);
	} else {
		if (orhssz < RHSIZE &&
				(oldrhs = realloc(oldrhs, orhssz=RHSIZE)) == 0)
			error("replacement string too long");
		strcpy(oldrhs, rhsbuf);
	}
	if ((peekc = getchr()) == 'g') {
		peekc = 0;
		newline();
		return(-1);
	} else if (peekc >= '0' && peekc <= '9') {
		c = getnum();
		if (c < 1 || c > LBSIZE)
			error("invalid count");
		newline();
		return c;
	}
	newline();
	return(0);
}

static int
getsub(void)
{
	register char *p1, *p2;

	p1 = linebuf;
	if ((p2 = linebp) == 0)
		return(EOF);
	while (*p1++ = *p2++)
		;
	linebp = 0;
	return(0);
}

static int
dosub(int really)
{
	register char *lp, *sp;
	register int i, j, k;
	int c;

	if (!really)
		goto copy;
	i = 0;
	j = 0;
	k = 0;
	while (&linebuf[i] < loc1)
		genbuf[j++] = linebuf[i++];
	while (c = rhsbuf[k++]&0377) {
		if (c=='&') {
			j = place(j, loc1, loc2);
			continue;
		} else if (c == '\\') {
			c = rhsbuf[k++]&0377;
			if (c >='1' && c < nbra+'1') {
				j = place(j, braslist[c-'1'], braelist[c-'1']);
				continue;
			}
		}
		if (j >= LBSIZE)
			growlb("line too long");
		genbuf[j++] = c;
	}
	i = loc2 - linebuf;
	loc2 = j + linebuf;
	if (loc1 == &linebuf[i]) {
		int	n;
		wchar_t	wc;
		if (mb_cur_max > 1 && (n = mbtowc(&wc, loc2, mb_cur_max)) > 0)
			loc2 += n;
		else
			loc2++;
	}
	while (genbuf[j++] = linebuf[i++])
		if (j >= LBSIZE)
			growlb("line too long");
	if (really) {
		lp = linebuf;
		sp = genbuf;
	} else {
	copy:	sp = linebuf;
		lp = genbuf;
	}
	while (*lp++ = *sp++)
		;
	return really;
}

static int
place(register int j, register const char *l1, register const char *l2)
{

	while (l1 < l2) {
		genbuf[j++] = *l1++;
		if (j >= LBSIZE)
			growlb("line too long");
	}
	return(j);
}

static void
move(int cflag)
{
	register long *adt, *ad1, *ad2;

	setdot();
	nonzero();
	if ((adt = address())==0)
		error("illegal move destination");
	newline();
	checkpoint();
	if (cflag) {
		long *ozero;
		intptr_t delta;
		ad1 = dol;
		ozero = zero;
		append(getcopy, ad1++);
		ad2 = dol;
		delta = zero - ozero;
		ad1 += delta;
		adt += delta;
	} else {
		ad2 = addr2;
		for (ad1 = addr1; ad1 <= ad2;)
			*ad1++ &= ~01;
		ad1 = addr1;
	}
	ad2++;
	if (adt<ad1) {
		dot = adt + (ad2-ad1);
		if ((++adt)==ad1)
			return;
		reverse(adt, ad1);
		reverse(ad1, ad2);
		reverse(adt, ad2);
	} else if (adt >= ad2) {
		dot = adt++;
		reverse(ad1, ad2);
		reverse(ad2, adt);
		reverse(ad1, adt);
	} else
		error("illegal move destination");
	fchange = 1;
}

static void
reverse(register long *a1, register long *a2)
{
	register int t;

	for (;;) {
		t = *--a2;
		if (a2 <= a1)
			return;
		*a2 = *a1;
		*a1++ = t;
	}
}

static int
getcopy(void)
{
	if (addr1 > addr2)
		return(EOF);
	getline(*addr1++, 0);
	return(0);
}

static int
execute(int gf, long *addr, int subst)
{
	register char *p1, *p2, c;

	for (c=0; c<NBRA; c++) {
		braslist[c&0377] = 0;
		braelist[c&0377] = 0;
	}
	if (gf) {
		if (circf)
			return(0);
		p1 = linebuf;
		p2 = genbuf;
		while (*p1++ = *p2++)
			;
		locs = p1 = loc2;
	} else {
		if (addr==zero)
			return(0);
		p1 = getline(*addr, 1);
		locs = 0;
	}
	needsub = subst;
	return step(p1, expbuf);
}

static void
cmplerr(int c)
{
	const char	*msg;

	switch (c) {
	case 11:
		msg = "Range endpoint too large";
		break;
	case 16:
		msg = "bad number";
		break;
	case 25:
		msg = "`\\digit' out of range";
		break;
	case 36:
		msg = "illegal or missing delimiter";
		break;
	case 41:
		msg = "no remembered search string";
		break;
	case 42:
		msg = "'\\( \\)' imbalance";
		break;
	case 43:
		msg = "Too many `\\(' s";
		break;
	case 44:
		msg = "more than 2 numbers given";
		break;
	case 45:
		msg = "'\\}' expected";
		break;
	case 46:
		msg = "first number exceeds second";
		break;
	case 49:
		msg = "'[ ]' imbalance";
		break;
	case 50:
		msg = "regular expression overflow";
		break;
	case 67:
		msg = "illegal byte sequence";
		break;
	default:
		msg = "regular expression error";
		break;
	}
	error(msg);
}

static void
doprnt(long *bot, long *top)
{
	long	*a1;

	a1 = bot;
	do {
		if (numbf ^ Nflag) {
			putd(a1-zero);
			putchr('\t');
		}
		nlputs(getline(*a1++, 0));
	} while (a1 <= top);
	pflag = 0;
	listf = 0;
	numbf = 0;
}

static void
putd(long c)
{
	register int r;

	r = c%10;
	c /= 10;
	if (c)
		putd(c);
	putchr(r + '0');
}

static void
nlputs(register const char *sp)
{
	if (listf)
		list(sp);
	else
		puts(sp);
}

static void
puts(register const char *sp)
{
	while (*sp) {
		if (*sp != '\n')
			putchr(*sp++ & 0377);
		else
			sp++, putchr('\0');
	}
	putchr('\n');
}

static void
list(const char *lp)
{
	int col, n;
	wchar_t c;

	col = numbf ^ Nflag ? 8 : 0;
	while (*lp) {
		if (mb_cur_max > 1 && *lp&0200)
			n = mbtowc(&c, lp, mb_cur_max);
		else {
			n = 1;
			c = *lp&0377;
		}
		if (col+1 >= 72) {
			col = 0;
			putchr('\\');
			putchr('\n');
		}
		if (n<0 || c == '\\' ||
				!(mb_cur_max>1 ? iswprint(c) : isprint(c))) {
			if (n<0)
				n = 1;
			while (n--)
				col += lstchr(*lp++&0377);
		} else if (mb_cur_max>1) {
			col += wcwidth(c);
			while (n--)
				putchr(*lp++&0377);
		} else {
			putchr(*lp++&0377);
			col++;
		}
	}
	putchr('$');
	putchr('\n');
}

static int
lstchr(int c)
{
	int	cad = 1, d;

	if (c == '\n')
		c = '\0';
	if (c == '\\') {
		putchr('\\');
		putchr('\\');
		cad = 2;
	} else if (c == '\a') {
		putchr('\\');
		putchr('a');
		cad = 2;
	} else if (c == '\b') {
		putchr('\\');
		putchr('b');
		cad = 2;
	} else if (c == '\f') {
		putchr('\\');
		putchr('f');
		cad = 2;
	} else if (c == '\r') {
		putchr('\\');
		putchr('r');
		cad = 2;
	} else if (c == '\t') {
		putchr('\\');
		putchr('t');
		cad = 2;
	} else if (c == '\v') {
		putchr('\\');
		putchr('v');
		cad = 2;
	} else {
		putchr('\\');
		putchr(((c&~077)>>6)+'0');
		c &= 077;
		d = c & 07;
		putchr(c > d ? ((c-d)>>3)+'0' : '0');
		putchr(d+'0');
		cad = 4;
	}
	return cad;
}

static void
putstr(const char *s)
{
	while (*s)
		putchr(*s++);
}

static char	line[70];
static char	*linp	= line;

static void
putchr(int ac)
{
	register char *lp;
	register int c;

	lp = linp;
	c = ac;
	*lp++ = c;
	if(c == '\n' || lp >= &line[64]) {
		linp = line;
		write(1, line, lp-line);
		return;
	}
	linp = lp;
}

static void
checkpoint(void)
{
	long	*a1, *a2;

	if (undzero && globp == NULL) {
		for (a1 = zero+1, a2 = undzero+1; a1 <= dol; a1++, a2++)
			*a2 = *a1;
		unddot = &undzero[dot-zero];
		unddol = &undzero[dol-zero];
		for (a1 = names, a2 = undnames; a1 < &names[26]; a1++, a2++)
			*a2 = *a1;
	}
}

#define	swap(a, b)	(t = a, a = b, b = t)

static void
undo(void)
{
	long	*t;

	if (undzero == NULL)
		error("no undo information saved");
	swap(zero, undzero);
	swap(dot, unddot);
	swap(dol, unddol);
	swap(names, undnames);
}

static int
maketf(int fd)
{
	char	*tmpdir;

	if (fd == -1) {
		if ((tmpdir = getenv("TMPDIR")) == NULL ||
				(fd = creatf(tmpdir)) < 0)
			if ((fd = creatf("/var/tmp")) < 0 &&
					(fd = creatf("/tmp")) < 0)
				error("cannot create temporary file");
	} else
		ftruncate(fd, 0);	/* blkio() will seek to 0 anyway */
	return fd;
}

static int
creatf(const char *tmpdir)
{
	if (strlen(tmpdir) >= sizeof tfname - 9)
		return -1;
	strcpy(tfname, tmpdir);
	strcat(tfname, "/eXXXXXX");
	return mkstemp(tfname);
}

static int
sopen(const char *fn, int rdwr)
{
	int	pf[2], fd = -1;

	if (fn[0] == '!') {
		fn++;
		if (pipe(pf) < 0)
			error("write or open on pipe failed");
		switch (pipid = fork()) {
		case 0:
			if (rdwr == READ)
				dup2(pf[1], 1);
			else
				dup2(pf[0], 0);
			close(pf[0]);
			close(pf[1]);
			sigset(SIGHUP, oldhup);
			sigset(SIGQUIT, oldquit);
			sigset(SIGPIPE, oldpipe);
			execl(SHELL, "sh", "-c", fn, NULL);
			_exit(0100);
		default:
			close(pf[rdwr == READ ? 1 : 0]);
			fd = pf[rdwr == READ ? 0 : 1];
			break;
		case -1:
			error("fork failed - try again");
		}
	} else if (rdwr == READ)
		fd = open(fn, O_RDONLY);
	else if (rdwr == EXIST)
		fd = open(fn, O_WRONLY);
	else /*if (rdwr == WRITE)*/
		fd = creat(fn, 0666);
	if (fd >= 0 && rdwr == READ)
		readop = 1;
	if (fd >= 0)
		fstat(fd, &fstbuf);
	return fd;
}

static void
sclose(int fd)
{
	int	status;

	close(fd);
	if (pipid >= 0) {
		while (wait(&status) != pipid);
		pipid = -1;
	}
	readop = 0;
}

static wint_t
GETWC(char *mb)
{
	int	c, n;

	n = 1;
	mb[0] = c = GETC();
	mb[1] = '\0';
	if (mb_cur_max > 1 && c&0200 && c != EOF) {
		int	m;
		wchar_t	wc;

		while ((m = mbtowc(&wc, mb, mb_cur_max)) < 0 && n<mb_cur_max) {
			mb[n++] = c = GETC();
			mb[n] = '\0';
			if (c == '\n' || c == EOF)
				break;
		}
		if (m != n)
			ERROR(67);
		return wc;
	} else
		return c;
}

static void
growlb(const char *msg)
{
	char	*olb = linebuf;
	int	i;

	LBSIZE += 512;
	if ((linebuf = realloc(linebuf, LBSIZE)) == NULL ||
			(genbuf = realloc(genbuf, LBSIZE)) == NULL)
		error(msg);
	if (linebuf != olb) {
		loc1 += linebuf - olb;
		loc2 += linebuf - olb;
		for (i = 0; i < NBRA; i++) {
			if (braslist[i])
				braslist[i] += linebuf - olb;
			if (braelist[i])
				braelist[i] += linebuf - olb;
		}
	}
}

static void
growrhs(const char *msg)
{
	RHSIZE += 256;
	if ((rhsbuf = realloc(rhsbuf, RHSIZE)) == NULL)
		error(msg);
}

static void
growfn(const char *msg)
{
	FNSIZE += 64;
	if ((savedfile = realloc(savedfile, FNSIZE)) == NULL ||
			(file = realloc(file, FNSIZE)) == NULL)
		error(msg);
	if (FNSIZE == 64)
		file[0] = savedfile[0] = 0;
}

union	ptrstore {
	void	*vp;
	char	bp[sizeof (void *)];
};

static void *
fetchptr(const char *bp)
{
	union ptrstore	u;
	int	i;

	for (i = 0; i < sizeof (void *); i++)
		u.bp[i] = bp[i];
	return u.vp;
}

static void
storeptr(void *vp, char *bp)
{
	union ptrstore	u;
	int	i;

	u.vp = vp;
	for (i = 0; i < sizeof (void *); i++)
		bp[i] = u.bp[i];
}

#define	add(c)	((i>=LBSIZE ? (growlb("regular expression overflow"),0) : 0), \
		genbuf[i++] = (c))

#define	copy(s)	{ \
	int	m; \
	for (m = 0; m==0 || s[m]; m++) \
		add(s[m]); \
}

static char *
compile(char *unused, char *ep, const char *endbuf, int seof)
{
	INIT
	int	c, d, i;
	regex_t	*rp;
	char	*op;
	char	mb[MB_LEN_MAX+1];

	op = ep;
	ep += 2;
	if ((rp = fetchptr(ep)) == NULL) {
		if ((rp = calloc(1, sizeof *rp)) == NULL)
			ERROR(50);
		storeptr(rp, ep);
	}
	ep += sizeof (void *);
	i = 0;
	nbra = 0;
	do {
		if ((c = GETWC(mb)) == seof)
			add('\0');
		else if (c == '\\') {
			copy(mb);
			c = GETWC(mb);
			if (c == '(')
				nbra++;
			goto normchar;
		} else if (c == '[') {
			add(c);
			d = EOF;
			do {
				c = GETWC(mb);
				if (c == EOF || c == '\n')
					ERROR(49);
				copy(mb);
				if (d=='[' && (c==':' || c=='.' || c=='=')) {
					d = c;
					do {
						c = GETWC(mb);
						if (c == EOF || c == '\n')
							ERROR(49);
						copy(mb);
					} while (c != d || PEEKC() != ']');
					c = GETWC(mb);
					copy(mb);
					c = EOF;
				}
				d = c;
			} while (c != ']');
		} else {
			if (c == EOF || c == '\n') {
				if (c == '\n')
					UNGETC(c);
				mb[0] = c = '\0';
			}
			if (c == '\0')
				nodelim = 1;
	normchar:	copy(mb);
		}
	} while (genbuf[i-1] != '\0');
	if (genbuf[0]) {
		int	reflags = 0;

#ifdef	REG_ANGLES
		reflags |= REG_ANGLES;
#endif
#ifdef	REG_AVOIDNULL
		reflags |= REG_AVOIDNULL;
#endif
		if (op[0])
			regfree(rp);
		op[0] = 0;
		switch (regcomp(rp, genbuf, reflags)) {
		case 0:
			break;
		case REG_ESUBREG:
			ERROR(25);
			/*NOTREACHED*/
		case REG_EBRACK:
			ERROR(49);
			/*NOTREACHED*/
		case REG_EPAREN:
			ERROR(42);
			/*NOTREACHED*/
		case REG_BADBR:
		case REG_EBRACE:
			ERROR(45);
			/*NOTREACHED*/
		case REG_ERANGE:
			ERROR(11);
			/*NOTREACHED*/
		case REG_ESPACE:
			ERROR(50);
			/*NOTREACHED*/
		default:
			ERROR(-1);
		}
		op[0] = 1;
		circf = op[1] = genbuf[0] == '^';
	} else if (op[0]) {
		circf = op[1];
	} else
		ERROR(41);
	return ep + sizeof (void *);
}

static int
step(const char *lp, const char *ep)
{
	regex_t	*rp;
	regmatch_t	bralist[NBRA+1];
	int	eflag = 0;
	int	res;
	int	i;

	rp = fetchptr(&ep[2]);
	if (ep[0] == 0)
		return 0;
	if (locs)
		eflag |= REG_NOTBOL;
	if ((res = regexec(rp, lp, needsub? NBRA+1 : 0, bralist, eflag)) == 0 &&
			needsub) {
		loc1 = (char *)lp + bralist[0].rm_so;
		loc2 = (char *)lp + bralist[0].rm_eo;
		for (i = 1; i <= NBRA; i++) {
			if (bralist[i].rm_so != -1) {
				braslist[i-1] = (char *)lp + bralist[i].rm_so;
				braelist[i-1] = (char *)lp + bralist[i].rm_eo;
			} else
				braslist[i-1] = braelist[i-1] = NULL;
		}
	}
	return res == 0;
}

static void
help(void)
{
	const char	*desc[] = {
		"(.)a            append up to .",
		"(.)b[n]         browse n lines",
		"(.,.)c          change up to .",
		"(.,.)d          delete lines",
		"e [file]        edit file",
		"E [file]        force edit",
		"f [file]        print or set file",
		"(1,$)g/RE/cmd   global cmd",
		"(1,$)G/RE/      interactive global",
		"h               print last error",
		"H               toggle error messages",
		"help            print this screen",
		"(.)i            insert up to .",
		"(.,.+1)j        join lines",
		"(.)kx           mark line with x",
		"(.,.)l          list lines",
		"(.,.)ma         move lines to a",
		"(.,.)n          number lines",
		"N               revert n and p",
		"(.)o[n]         show n lines of context",
		"(.,.)p          print lines",
		"P               toggle prompt",
		"q               quit",
		"Q               force quit",
		"($)r            read file",
		"(.,.)s/RE/repl/ search and replace",
		"(.,.)s/RE/rp/g  replace all occurrences",
		"(.,.)s/RE/rp/n  replace n-th occurrence",
		"(.,.)ta         transfer lines to a",
		"u               undo last change",
		"(1,$)v/RE/cmd   reverse global",
		"(1,$)V/RE/      reverse i/a global",
		"(1,$)w [file]   write file",
		"(1,$)W [file]   append to file",
		"z               write buffer and quit",
		"($)=            print line number",
		"!command        execute shell command",
		"(.+1)<newline>  print one line",
		"/RE             find RE forwards",
		"?RE             find RE backwards",
		"1               first line",
		".               current line",
		"$               last line",
		",               1,$",
		";               .,$",
		NULL
	};
	char	line[100];
	int	c, half, i, k;

	half = (sizeof desc / sizeof *desc) / 2;
	for (i = 0; i < half && desc[i]; i++) {
		c = 0;
		for (k = 0; desc[i][k]; k++)
			line[c++] = desc[i][k];
		if (desc[i+half]) {
			while (c < 40)
				line[c++] = ' ';
			for (k = 0; desc[i+half][k]; k++)
				line[c++] = desc[i+half][k];
		}
		line[c] = 0;
		puts(line);
	}
}