/*
 *	Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#define static
#define register
#define RUNIO(X)	(/*fprintf(runiofp, "runio(%s,%d)\n", __FILE__, __LINE__), */runio(&X))

/*
 * Runtime interpreter for the shell pseudo-code.
 */

#include "hostenv.h"
#include "mailer.h"
#include <ctype.h>
#include <fcntl.h>
#include <sys/file.h>
#include "interpret.h"
#include "io.h"
#include "shconfig.h"
#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */
#ifdef	MAILER
#include "regexp.h"
#include "sift.h"
#endif	/* MAILER */

int magic_number = 1;		/* check id for precompiled script files */

STATIC int pipefd;		/* read side fd of a pipe, or -1 */

struct osCmd *ib_command[20];	/* max # nested $() in stack */
STATIC int ibt = -1;		/* top of ib_command stack, XXX reset somewh. */

extern char *progname;
extern FILE *runiofp;

extern int reapableTop;

/*
 * Because the structure of the interpreter is to build up a command descriptor
 * and then execute it, and the shell must remain relatively intact I/O-wise,
 * we have to predetermine which I/O actions should be taken to set up the
 * environment for a particular program or command.  To do this, we have to
 * know the effects of I/O-related system calls on the environment.  The only
 * possible effect is to create or destroy (or leave alone) one or more
 * file descriptors.  The algorithms used by the kernel to default file
 * descriptors (e.g. in an open() call) are well specified, so we can simulate
 * all that activity a-priori.  This in turn is necessary in order to know
 * how to undo these actions.  The simulation is aided by an array we maintain
 * of busy (i.e. in-use) file descriptors.
 */

STATIC short	fds[MAXNFILE];			/* could be a bitmap */
#define	FDBUSY(X)	((X) > (sizeof fds / sizeof fds[0]) ? abort(),0 : fds[X])

/*
 * This routine is used to model the effects of I/O operations on the
 * set of file descriptors.
 */

STATIC void
freeio(fioop, mark)
	register struct IOop *fioop;
	int mark;
{
	struct IOop *nfioop;
	int fd;

	for (; fioop != NULL; fioop = nfioop) {
		nfioop = fioop->next;
		if (1) {
			if (fioop->cmd == sIOdup)
				fd = fioop->fd2;
			else
				fd = fioop->fd;
			fds[fd] = (fioop->cmd != sIOclose);

			if (isset('R'))
				fprintf(runiofp,
					"fds[%d] = %d\n",
					fd, (fioop->cmd != sIOclose));
		}
		if (fioop->opflags)
			(void) free((char *)fioop);
	}
}


/*
 * Return the next available filedescriptor, simulating kernel lookup.
 */

STATIC int
findfreefd()
{
        register int fd;
#ifdef  MAILER
        struct stat stbuf;
#endif	/* MAILER */

        for (fd = 0; fd <= (sizeof fds / sizeof fds[0]) ; ++fd)
                if (fds[fd] == 0) {
#ifdef  MAILER
                        /*
                         * This is supposed to compensate for random
                         * fopen's in the application, e.g. when caching
                         * file descriptors keeping a database open.
                         * This is a bit too expensive for my liking,
                         * and assumes these things are static as compared
                         * to shell code execution, so beware of subtle bugs.
                         */
                        if (fstat(fd, &stbuf) == 0)
                                continue;
#endif  /* MAILER */
                        return fd;
                }
        fprintf(stderr, "%s: out of free filedescriptors (%d)!\n",
                        progname, fd);
        abort();
        /* NOTREACHED */
	return 0;
}


#ifdef	MAILER

/*
 * The mailer should call this routine before toplevel entry to the
 * shell, and then be *VERY* careful about opening files in routines
 * that may be called from within shell execution or between apply calls.
 */

int
setfreefd()
{
	register int i;

#if 0
	register int fd;
	struct stat stbuf;
	
	for (fd = 0; fd <= (sizeof fds / sizeof fds[0]) ; ++fd)
		if (fstat(fd, &stbuf) == 0)
			fds[i = fd] = 1;
		else
			fds[fd] = 0;
#else
	i = -1;
#endif
	return i;
}
#endif	/* MAILER */

char *
ename(cmd)
	OutputTokens cmd;
{
	char *s = NULL;
	switch (cmd) {
	case sBufferSet: s = "sBufferSet"; break;
	case sBufferAppend: s = "sBufferAppend"; break;
	case sBufferExpand: s = "sBufferExpand"; break;
	case sBufferQuote: s = "sBufferQuote"; break;
	case sBufferSetFromArgV: s = "sBufferSetFromArgV"; break;
	case sArgVpush: s = "sArgVpush"; break;
	case sArgList: s = "sArgList"; break;
	case sVariablePush: s = "sVariablePush"; break;
	case sVariablePop: s = "sVariablePop"; break;
	case sVariableCdr: s = "sVariableCdr"; break;
	case sVariableBuffer: s = "sVariableBuffer"; break;
	case sVariableAppend: s = "sVariableAppend"; break;
	case sVariableLoopAttach: s = "sVariableLoopAttach"; break;
	case sCommandPush: s = "sCommandPush"; break;
	case sCommandPop: s = "sCommandPop"; break;
	case sCommandCarryBuffer: s = "sCommandCarryBuffer"; break;
	case sIOopen: s = "sIOopen"; break;
	case sIOopenString: s = "sIOopenString"; break;
	case sIOopenPortal: s = "sIOopenPortal"; break;
	case sIOopenPipe: s = "sIOopenPipe"; break;
	case sIOintoBuffer: s = "sIOintoBuffer"; break;
	case sIOclose: s = "sIOclose"; break;
	case sIOdup: s = "sIOdup"; break;
	case sIOsetIn: s = "sIOsetIn"; break;
	case sIOsetInOut: s = "sIOsetInOut"; break;
	case sIOsetOut: s = "sIOsetOut"; break;
	case sIOsetAppend: s = "sIOsetAppend"; break;
	case sIOsetDesc: s = "sIOsetDesc"; break;
	case sIObufIn: s = "sIObufIn"; break;
	case sIObufOut: s = "sIObufOut"; break;
	case sIObufFree: s = "sIObufFree"; break;
	case sIObufString: s = "sIObufString"; break;
	case sAssign: s = "sAssign"; break;
	case sAssignTemporary: s = "sAssignTemporary"; break;
	case sFunction: s = "sFunction"; break;
	case sParameter: s = "sParameter"; break;
	case sJump: s = "sJump"; break;
	case sBranchOrigin: s = "sBranchOrigin"; break;
	case sJumpFork: s = "sJumpFork"; break;
	case sJumpIfFailure: s = "sJumpIfFailure"; break;
	case sJumpIfSuccess: s = "sJumpIfSuccess"; break;
	case sJumpIfNilVariable: s = "sJumpIfNilVariable"; break;
	case sJumpIfMatch: s = "sJumpIfMatch"; break;
	case sJumpIfFindVarNil: s = "sJumpIfFindVarNil"; break;
	case sJumpIfOrValueNil: s = "sJumpIfOrValueNil"; break;
	case sJumpLoopBreak: s = "sJumpLoopBreak"; break;
	case sJumpLoopContinue: s = "sJumpLoopContinue"; break;
	case sLoopEnter: s = "sLoopEnter"; break;
	case sLoopExit: s = "sLoopExit"; break;
	case sLocalVariable: s = "sLocalVariable"; break;
	case sScopePush: s = "sScopePush"; break;
	case sScopePop: s = "sScopePop"; break;
	case sDollarExpand: s = "sDollarExpand"; break;
	case sNoOp: s = "sNoOp"; break;
	case sPrintAndExit: s = "sPrintAndExit"; break;
	case sBackground: s = "sBackground"; break;
	case sSiftPush: s = "sSiftPush"; break;
	case sSiftBody: s = "sSiftBody"; break;
	case sSiftCompileRegexp: s = "sSiftCompileRegexp"; break;
	case sSiftReevaluate: s = "sSiftReevaluate"; break;
	case sSiftPop: s = "sSiftPop"; break;
	case sSiftBufferAppend: s = "sSiftBufferAppend"; break;
	case sJumpIfRegmatch: s = "sJumpIfRegmatch"; break;
	default: break;
	}
	return s;
}

/*
 * Auxiliary routine (referenced by the INSERTIO macro) which links an
 * I/O action into a linked list of same (in reverse order of execution).
 * Since two of the parameters to the insertio() function are always the
 * same, and the list of arguments so long, we also define a macro invocation
 * for this function.
 */

#define	INSERTIO(X,C,Y,FD1,FD2,PERM)	(void) insertio((X),(C),name,(Y),ioflags,(FD1),(FD2),(PERM))

struct IOop *
insertio(ioopp, command, name, cmd, ioflags, fd, fd2, opflags)
	struct IOop **ioopp;
	struct osCmd *command;
	u_char *name;
	OutputTokens cmd;
	int ioflags, fd, fd2, opflags;
{
	register struct IOop *iotmp;

	if (isset('R'))
		fprintf(runiofp,
			"insertio(%x): %s %d %d\n", ioopp, ename(cmd), fd, fd2);
	if (opflags)
		iotmp = (struct IOop *)emalloc(sizeof (struct IOop));
	else
		iotmp = (struct IOop *)tmalloc(sizeof (struct IOop));
	iotmp->command = command;
	if (name == NULL)
		iotmp->name = NULL;
	else
		iotmp->name = (u_char *)strnsave((char *)name, strlen((char *)name));
	iotmp->cmd = cmd;
	iotmp->ioflags = ioflags;
	iotmp->opflags = opflags;
	iotmp->fd = fd;
	iotmp->fd2 = fd2;
	if (ioopp != NULL) {
		iotmp->next = *ioopp;
		*ioopp = iotmp;
	} else
		iotmp->next = NULL;
	return iotmp;
}

/*
 * When actions are executed in a particular order A,B,C, they sometimes
 * need to be undone in the reverse order: undo C, undo B, undo A.  To
 * aid that, the APPENDIO() macro will do the obvious thing, in contrast
 * with INSERTIO().  Due to the io op list reversal in runio, the APPENDIO's
 * have to be done in reverse natural order.  See below.
 */

#define	APPENDIO(X,C,Y,FD1,FD2)	appendio((X),(C),name,(Y),ioflags,(FD1),(FD2))

void
appendio(ioopp, command, name, cmd, ioflags, fd, fd2)
	struct IOop **ioopp;
	struct osCmd *command;
	u_char *name;
	OutputTokens cmd;
	int ioflags, fd, fd2;
{
	register struct IOop *iotmp;

	if (isset('R'))
		fprintf(runiofp,
			"appendio(%x): %s %d %d\n", ioopp, ename(cmd), fd, fd2);
	iotmp = *ioopp;
	if (iotmp != NULL) {
		for (; iotmp->next != NULL; iotmp = iotmp->next)
			continue;
		iotmp->next = insertio((struct IOop **)NULL, command, name,
				       cmd, ioflags, fd, fd2, 0);
	} else
		*ioopp = insertio((struct IOop **)NULL, command, name,
				       cmd, ioflags, fd, fd2, 0);
}


/*
 * This routine is the basic component for building up I/O descriptor
 * manipulation action lists (what a mouthful) that are later carried
 * out by RUNIO().  It models the effects of I/O system calls on the
 * set of available filedescriptors.
 */

void
ioop(cmd, command, name, ioflags, defaultfd, arg1)
	OutputTokens cmd;
	struct osCmd *command;
	u_char *name, *arg1;
	int ioflags, defaultfd;
{
	int	tofd, savefd, intobufflag;

	if (isset('R'))
		(void) fprintf(runiofp, "ioop(%x): %s\n", command, ename(cmd));
	intobufflag = 0;
	tofd = defaultfd;
	if (cmd == sIOdup) {
		defaultfd = atoi((char *)arg1);
		if (!FDBUSY(defaultfd)) {
			(void) fprintf(stderr, "%s: no fd %d!\n",
				       progname, defaultfd);
			return;
		}
	}
	if (FDBUSY(tofd)) {
		/* save current to-fd somewhere else: save-fd */
		savefd = findfreefd();
		INSERTIO(&command->doio, command, sIOdup, tofd, savefd, 0);
		fds[savefd] = 1;
	} else
		savefd = 0;	/* shut up the compiler */
	if (cmd == sIOopenPipe && !(ioflags & O_CREAT)) {
		if ((defaultfd = pipefd) < 0) {
			fprintf(stderr, "%s: no pipe!\n", progname);
			abort();
		}
	} else {
		if (cmd == sIOintoBuffer) {
			cmd = sIOopenPipe;
			command->iocmd = ioIntoBuffer;
			intobufflag = 1;
		} else if (cmd == sIOopenPipe)
			command->iocmd = ioOpenPipe;
		INSERTIO(&command->doio, command, cmd, defaultfd, tofd, 0);
		defaultfd = findfreefd();
	}
	/* obey kernel semantics for fd return from open */
	if ((cmd == sIOopen || cmd == sIOopenPipe
	     || cmd == sIOopenPortal || cmd == sIOopenString)
	    && defaultfd != tofd) {
		/* this is always done for pipes, at least */
		/* set fd2 in prev. open for later checking */
		if (cmd == sIOopenPipe && (ioflags & O_CREAT)) {
			/* pipefd is read end of the pipe */
			pipefd = defaultfd;
			fds[pipefd] = 1;
			/* defaultfd is write end of pipe */
			defaultfd = findfreefd();
			command->doio->fd = defaultfd;
			command->doio->fd2 = pipefd;
			INSERTIO(&command->execio, command,sIOclose,pipefd,0,0);
		} else if (cmd == sIOopenPipe) {
			fds[pipefd] = 0;
			pipefd = -1;
		} else
			command->doio->fd = defaultfd;
		/* copy free fd to tofd */
		INSERTIO(&command->doio, command, sIOdup, defaultfd, tofd, 0);
		/* close free fd */
		INSERTIO(&command->doio, command, sIOclose, defaultfd, 0, 0);
	}
	if (FDBUSY(tofd)) {
		/* arrange to close save-fd inside fork/exec */
		INSERTIO(&command->execio, command, sIOclose, savefd, 0, 0);
		/*
		 * We need to restore original to-fd then close save-fd,
		 * but due to mechanics of these things we have to append
		 * them in reverse order here... don't get confused.
		 */
		/* in parent, need to close save-fd */
		APPENDIO(&command->undoio, command, sIOclose, savefd, 0);
		/* but first need to restore original to-fd */
		if (tofd <= 2
		    && (siofds[tofd] == NULL
				     || siofds[tofd]->_sb_refcnt == 0)) {
			APPENDIO(&command->undoio, command, sIObufFree, tofd,0);
		}
		APPENDIO(&command->undoio, command, sIOdup, savefd,tofd);
		if (tofd <= 2
		    && (siofds[tofd] == NULL
				     || siofds[tofd]->_sb_refcnt == 0)) {
			APPENDIO(&command->undoio, command, sIObufFree, tofd,0);
		}
	}
	fds[tofd] = (cmd != sIOclose);
	/* remember this command said something about tofd */
	if (tofd < ((sizeof command->fdmask) * 8))
		command->fdmask |= (1<<tofd);
	if (intobufflag) {
		cmd = sIOintoBuffer;
		intobufflag = 0;
		/*
		 * After forking the command we want to
		 * sit and wait on the output down in RUNIO.
		 */
		INSERTIO(&command->undoio, command, cmd, pipefd, 0, 0);
		/* and then we have to close it of course */
		INSERTIO(&command->undoio, command, sIOclose, pipefd, 0, 0);
		/*
		 * we can't release pipefd (and fds[pipefd])
		 * until after all the other doio's have been
		 * taken care of... but we don't know when
		 * that will be except for at next CommandPop.
		 */
	}
	if (isset('R'))
		fprintf(runiofp, "end(%x)\n", command);
}

/*
 * The interpreter interface to the execute() routine.
 */

STATIC void
runcommand(c, pc, retcodep)
	struct osCmd *c, *pc;
	int *retcodep;
{
	int ioflags;
	u_char *name;
	extern int execute(), runio();
	extern void trapexit();

	if (c->argv) {	/* from tconc into list */
		c->argv = car(c->argv);
		cdr(c->argv) = 0;
	}
	c->buffer = NULL;
	c->bufferp = &(c->buffer);

	/* in a backquote and output hasn't been explicitly redirected */
	if (ibt >= 0 && c->argv != NULL && !(c->fdmask & (1<<1))) {
		/*
		 * If the current command isn't a builtin and stdout not
		 * yet assigned, set up stdout as a pipe and read it
		 * back at backquote commandpop.  This will work even if
		 * we're dealing with a sequence, e.g. `a | b ; c | d`
		 * and b is the current (non-builtin) command, because multiple
		 * calls to readstring() just append to the buffer.
		 * If any of b or d are builtins, the output will be
		 * that of the latter builtin.
		 */

		/* XX: is this necessary*/
		ib_command[ibt]->doio = ib_command[ibt]->execio = NULL;

		if (c->shcmdp != NULL || c->sfdp != NULL) {
			/* builtin function */
			name = NULL;
			ioflags = 0;

			INSERTIO(&c->doio, c, sIObufOut, 1, 1, 0);
			INSERTIO(&ib_command[ibt]->undoio,
				 ib_command[ibt], sIObufIn, 0, 1, 1);
			INSERTIO(&ib_command[ibt]->undoio,
				 ib_command[ibt], sIObufString, 0, 0, 1);
			INSERTIO(&ib_command[ibt]->undoio,
				 ib_command[ibt], sIObufFree, 0, 0, 1);
		} else {
			/* ok, ok, create the silly pipe! */
			register struct IOop *iop;

			if (c->doio) {
				RUNIO(c->doio);
				freeio(c->doio, 1);
				c->doio = NULL;
			}
			ioop(sIOintoBuffer, ib_command[ibt], (u_char *)NULL,
				O_CREAT|O_WRONLY|O_TRUNC, 1, (u_char *)NULL);
			/* Now copy doio and execio to current command */
			/* first doio */
#if 0
			for (iop = ib_command[ibt]->doio; iop != NULL; iop = iop->next)
				if (iop->next == NULL)
					break;
			if (iop != NULL) {
				iop->next = c->doio;
#endif
				c->doio = ib_command[ibt]->doio;
				ib_command[ibt]->doio = NULL;
#if 0
			}
#endif
			/* then execio */
			for (iop = ib_command[ibt]->execio; iop != NULL; iop = iop->next)
				if (iop->next == NULL)
					break;
			if (iop != NULL) {
				iop->next = c->execio;
				c->execio = ib_command[ibt]->execio;
				ib_command[ibt]->execio = NULL;
			}
		}
		--ibt;
	}

	/* run the command and undo any temporary variables */
	if (retcodep != NULL) {
		*retcodep = execute(c, pc, *retcodep);
		if (*retcodep != 0 && isset('e'))
			trapexit(*retcodep);
	}
	/* else we are ignoring execution of this command */

	/*
	 * We don't need to explicitly free the argv/envold/io
	 * lists since that is taken care of by the setlevel()
	 */
	if (c->doio)
		freeio(c->doio, 1);
	if (c->undoio)
		freeio(c->undoio, 1);
	if (c->execio)
		freeio(c->execio, 0);
}

/*
 * Variable assignment routine.  This is used directly by the interpreter
 * and indirectly through v_set() by most everything else.  All new variables
 * are created in the global (but non-exported) scope unless variables are
 * automatically exported.  The prior value of a variable is sometimes stashed
 * away for later restoration.  This is only done when a command descriptor
 * is passed, since it is used to undo the effect of temporary variable
 * assignments on the command line.
 */

void
assign(sl_lhs, sl_rhs, command)
	struct conscell *sl_lhs, *sl_rhs;
	struct osCmd *command;
{
	register struct conscell *s, *l, *tmp;
	u_char *varname;
	int oval, freerhs;
	extern void v_export(), v_sync();
#ifdef	MAILER
	extern int D_assign, funclevel;
#endif	/* MAILER */

	oval = stickymem;
	stickymem = MEM_MALLOC;
	/*
	 * Add (lhs oldvalue) to a list "envold" kept in the command.
	 * Variable values are really (potentially) lists.
	 */
	varname = sl_lhs->string;
	if ((l = s_copy_tree(sl_rhs)) != NULL)
		sl_rhs = l;
	if ((s = v_find(varname)) == NULL) {
		/* create the variable in the global but non-exported scope */
		for (l = car(envarlist); cddr(l) != NULL; l = cdr(l))
			continue;
		/* l points at the next-to-last sublist of envarlist */
		if (isset('a'))
			l = cdr(l);	/* or the list of exports */
		cdr(sl_rhs) = car(l);
		s = newstring((u_char *)strnsave((char *)varname,
						 strlen((char *)varname)));
		s_set_prev(s, sl_rhs);
		cdr(s) = sl_rhs;
		sl_rhs->pflags = 1;	/* cdr(sl_rhs->prev) == sl_rhs */
		car(l) = s;
		l = NIL;
		freerhs = 0;
	} else {
#if 1
		l = copycell(cdr(s));
		cdr(l) = NULL;
		/*
		if (command == NULL) {
			if (LIST(cdr(s)))
				s_free_tree(cadr(s));
			else
				(void) free(cdr(s)->string);
		}
		*/
		cdr(s)->flags = sl_rhs->flags;
		if (LIST(sl_rhs)) {
			cadr(s) = car(sl_rhs);
			s_set_prev(cdr(s), cadr(s));
		} else
			cdr(s)->string = sl_rhs->string;
		freerhs = 1;
		/* since s isn't changing, we don't need to set cdr(s)->prev */
#else
		l = cdr(s);
		tmp = cdr(l);
		cdr(l) = NULL;
		s_set_prev(s, sl_rhs);
		cdr(s) = sl_rhs;
		sl_rhs->pflags = 1;	/* cdr(sl_rhs->prev) == sl_rhs */
		cdr(sl_rhs) = tmp;
		freerhs = 0;
#endif
#ifdef	MAILER
		if (v_accessed)
			v_written(s);
#endif	/* MAILER */
		if (isset('a'))
			v_export(varname);	/* ick! */
	}
	/* stash old value */
	if (command != NULL) {
		cdr(l) = command->envold;
		command->envold = l;
		s = conststring(varname);
		cdr(s) = command->envold;
		command->envold = s;
	} else
		s_free_tree(l);	/* was free(l) */
	stickymem = oval;
	/* fvcache.namesymbol = 0; */
	v_sync(varname);

	if (isset('I')) {
		fprintf(runiofp, "Assign %s = ", varname);
		s_grind(sl_rhs, runiofp);
		putc('\n', runiofp);
	}
#ifdef	MAILER
	if (D_assign) {
		fprintf(stderr, "%*s%s=", 4*funclevel, " ", varname);
		s_grind(sl_rhs, stderr);
		fputc('\n', stderr);
	}
#endif	/* MAILER */
	if (freerhs)
		(void) free((char *)sl_rhs);
}

/*
 * Discard the definition of the function with the given name.  If this was
 * the last definition for the stored code table, free the table.
 */

STATIC void
undefun(fname)
	char *fname;
{
	struct spblk *spl;
	extern struct sptree *spt_funclist;
	extern struct spblk *sp_lookup();
	extern void xundefun();
	
	if ((spl = sp_lookup(symbol((u_char *)fname), spt_funclist)) == NULL)
		return;
	xundefun(spl);
}

/*
 * A version of undefun() we can call from inside sp_scan()
 */

void
xundefun(spl)
	struct spblk *spl;
{
	struct sslfuncdef *sfdp, **psfdp;
#ifdef	MAILER
	int idx;
	regexp **rep, **repstart;
#endif	/* MAILER */
	
	if (sfdp = (struct sslfuncdef *)spl->data) {
		psfdp = &(sfdp->tabledesc->functions);
		for (sfdp = *psfdp;
		     sfdp != NULL; psfdp = &sfdp->next, sfdp = *psfdp) {
			if (strcmp(sfdp->name,
			    ((struct sslfuncdef *)spl->data)->name) == 0) {
				*psfdp = sfdp->next;
				break;
			}
		}
		if (sfdp->tabledesc->functions == NULL
		    && sfdp->tabledesc->oktofree) {
#ifdef	MAILER
			if ((repstart = sfdp->tabledesc->rearray) != NULL) {
				idx = sfdp->tabledesc->rearray_idx;
				rep = repstart;
				while (rep - repstart < idx && *rep != NULL)
					(void) free((char *)*rep++);
				(void) free((char *)sfdp->tabledesc->rearray);
			}
#endif	/* MAILER */
			(void) free((char *)sfdp->tabledesc->table);
			(void) free((char *)sfdp->tabledesc);
		}
		(void) free((char *)sfdp);
		/*
		 * This should really be sp_delete, but if it is we won't
		 * be able to call undefun() inside an sp_scan...
		 */
		spl->data = NULL;
	}
}

/*
 * Define an interpreted function.  Sets up mutual links between the function
 * descriptor and the code table descriptor.
 */

STATIC void
defun(cdp, fname, position, eofunc)
	struct codedesc *cdp;
	char	*fname;
	u_char	*position, *eofunc;
{
	struct sslfuncdef *sfdp;
	extern struct sptree *spt_funclist;
	
	undefun(fname);
	sfdp = (struct sslfuncdef *)emalloc(sizeof (struct sslfuncdef));
	/*
	 * The function name string is known to be part of the function
	 * pseudo-code and will not disappear unless the data block containing
	 * the function definition goes bye-bye.
	 */
	sfdp->name = fname;
	sfdp->pos = position;
	sfdp->eot = eofunc;
	sfdp->tabledesc = cdp;	/* will be set properly at end of interpret() */
	sfdp->next = cdp->functions;
	cdp->functions = sfdp;
	(void) sp_install(symbol((u_char *)fname), (u_char *)sfdp, 0, spt_funclist);
}

/*
 * This routine determines possible internal definitions of a function.
 */

void
functype(fname, shcmdpp, sfdpp)
	register char *fname;
	struct shCmd **shcmdpp;
	struct sslfuncdef **sfdpp;
{
	u_int symid;
	struct sslfuncdef *sfdp;
	struct spblk *spl;
	extern struct sptree *spt_builtins, *spt_funclist;

	symid = symbol((u_char *)fname);
	/* is it a defined function? */
	if ((spl = sp_lookup(symid, spt_funclist)) == NULL)
		sfdp = NULL;
	else
		sfdp = (struct sslfuncdef *)spl->data;
	if (sfdpp != NULL)
		*sfdpp = sfdp;
	/* behaviour in execute() requires we continue, not return */

	/* is it a builtin command? */
	if (shcmdpp != NULL) {
		if ((spl = sp_lookup(symid, spt_builtins)) != NULL)
			*shcmdpp = (struct shCmd *)spl->data;
		else
			*shcmdpp = NULL;
	}
	/* it must be a unix program */
}


/*
 * Coalesces all the small buffers in command->buffer into a single buffer.
 */

void
coalesce(command)
	struct osCmd *command;
{
	struct conscell *s;

	if (command->buffer == NULL)
		return;
	for (s = command->buffer; s != NULL; s = cdr(s))
		if (LIST(s))
			return;
	command->buffer->flags |= QUOTEDSTRING;	/* so result will be too */
	if (cdr(command->buffer) != NULL)
		/* this is only done for LARGE intoBuffer outputs */
		*(command->buffer) = *s_catstring(command->buffer);
	command->bufferp = &cdr(command->buffer);
}


/*
 * Chop off any leading and trailing whitespace.
 */

void
flushwhite(command)
	struct osCmd *command;
{
	register struct conscell *s, *p;
	register u_char *cp;

	if (command->buffer == NULL)
		return;
	if (ISELEMENT(command->buffer))
		return;
	for (s = command->buffer; s != NULL; s = cdr(s))
		if (LIST(s))
			return;

#if 0
	/* strip leading whitespace */
	s = command->buffer;
	do {
		for (cp = command->buffer->string;
		     *cp != '\0' && WHITESPACE(*cp);
		     ++cp)
			continue;
	} while ((*cp == '\0') && (command->buffer = cdr(command->buffer)));
	if (command->buffer == NULL) {
		command->bufferp = &command->buffer;
		return;
	}
	if (s->string != cp) {
		command->buffer->string =
			(u_char *)strnsave((char *)cp, strlen((char *)cp));
	}
#endif

	/* strip trailing whitespace */
	p = NULL;
	do {
		for (s = command->buffer; cdr(s) != p; s = cdr(s))
			continue;
		for (cp = s->string + strlen((char *)s->string) - 1; cp >= s->string; --cp)
			if (*cp != '\n' /* !WHITESPACE(*cp) */)
				break;
		p = s;
	} while ((cp < s->string) && (s != command->buffer));
	if (cp < s->string) {
		command->buffer = NULL;
		command->bufferp = &command->buffer;
	} else {
		*++cp = '\0';
		cdr(s) = NULL;
		command->bufferp = &cdr(s);
	}
}

void
setsubexps(sepp, re)
	struct si_retab **sepp;
	regexp *re;
{
	register struct si_retab *sep, *psep;
	register int i;

	for (sep = *sepp, psep = NULL; sep != NULL; psep = sep, sep = sep->next)
		if (sep->rep == re)
			break;
	if (sep == NULL) {
		sep = (struct si_retab *)tmalloc(sizeof (struct si_retab));
		for (i=0; i < (sizeof sep->startp)/(sizeof sep->startp[0]);++i)
			sep->startp[i] = sep->endp[i] = NULL;
		bzero((char *)sep, sizeof (struct si_retab));
		sep->rep = re;
		sep->next = *sepp;
	} else if (psep != NULL) {
		psep->next = sep->next;
		sep->next = *sepp;
	}
	*sepp = sep;
	re->startp = sep->startp;
	re->endp = sep->endp;
}

#if 0
/*
 * This is a cache structure for v_find() which has appreciable locality.
 * Every time an assignment is made or a scope pushed/popped, this cache
 * is invalidated.
 */
struct {
	int	namesymbol;
	struct conscell *location;
} fvcache = { 0, NULL };
#endif

struct loopinfo {
	int	brk;		/* relative pc address if we want to break */
	int	cont;		/* relative pc address if we want to continue */
	short	cmdindex;	/* index of active command at this place */
	short	varindex;	/* index of active loop variable */
};

struct token *
scanstring(s)
	u_char *s;
{
	u_char *cp, *bp, *buf;
	int len;
	struct token *t;

	t = HDR_SCANNER(s);
	if (t != NULL && t->t_next == NULL && t->t_type == String) {
		/* we need to de-quote the quoted-string */
		len = TOKENLEN(t);
		bp = buf = (u_char *)tmalloc(len+1);
		for (cp = t->t_pname; cp - t->t_pname < len ; ++cp) {
			if (*cp == '\\' && cp - t->t_pname < len-1)
				*bp++ = *++cp;
			else
				*bp++ = *cp;
		}
		*bp = '\0';
		t = HDR_SCANNER(buf);
	}
	return t;
}

/*
 * Interpret Shell pseudo-code generated from S/SL description.
 *
 * Memory allocation and freeing in this code is a bit funny.  To avoid
 * a complicated reference-count or other GC scheme, we maintain a stack
 * in memory (type MEM_SHCMD).  We unravel the stack as we pop commands.
 * That takes care of scratch allocations, which is everything except
 * variable names and values.  We have to be careful about freeing that
 * data (which is malloc()'ed) whenever we discard it.
 */

struct codedesc *
interpret(code, eocode, entry, caller, retcodep, cdp)
	u_char	*code, *eocode, *entry;
	struct osCmd *caller;
	int *retcodep;
	struct codedesc *cdp;
{
	register struct conscell *d, *tmp;
	register u_char	*pc;
	register OutputTokens cmd;
	register struct osCmd *command;
	register int commandIndex, variableIndex, i;
	u_char	*arg1, *name;
	char	*origlevel;
	int	*iname;
	int	argi1, dye, childpid, dollar, ioflags, oval, oval2;
	int	defaultfd, quote, quoted, nloop, ignore_level;
	struct loopinfo loop[30];	/* max # nested loops */
	struct conscell *variable, *l, *margin;
	struct osCmd *prevcommand, *pcommand;
	struct osCmd commandStack[30];
	struct conscell *varStack[30];
	struct conscell *varmeter, *vmeterStack[30];
#ifdef	MAILER
	struct siftinfo sift[30];
	int nsift = -1;
	regexp *re = NULL;
	extern int funclevel;
#endif	/* MAILER */
	extern int optind, sprung, interrupted;
	extern char globchars[];
	extern void assign(), defun(), ioop(), trapped(), jc_report();
	extern void freeTokens(), trapexit();
	extern int squish(), glob_matcch(), glob_match(), runio();
	extern struct conscell *expand(), *sh_return();
	extern FILE *runiofp;

#ifdef	MAILER
	++funclevel;
#endif	/* MAILER */
	optind = 0;	/* for getopts */
	if (cdp == NULL) {
		cdp = (struct codedesc *)emalloc(sizeof (struct codedesc));
		cdp->table = (u_char *)code;
		cdp->eotable = (u_char *)eocode;
		cdp->functions = NULL;
#ifdef	MAILER
		cdp->rearray = NULL;
		cdp->rearray_size = 0;
		cdp->rearray_idx = -1;
#endif	/* MAILER */
		cdp->oktofree = 0;
	}
	commandIndex = variableIndex = -1;
	command = &commandStack[++commandIndex];
	command->buffer = NULL;
	command->bufferp = &command->buffer;
	command->flag = 0;
	command->rval = NULL;
	variable = NULL; ioflags = 0; defaultfd = 0; /* to shut up lint */
	dye = dollar = quote = 0;
	varmeter = NULL;
	fds[0] = fds[1] = fds[2] = 1;   /* XX: this isn't recursive, eh?? */
	if (entry == NULL)
		pipefd = -1;
	prevcommand = NULL;
	margin = car(envarlist);/* so we can be sure to pop scopes on exit */
#define	MAGIC_LARGE_IGNORE_LEVEL	123435	/* >> any valid ignore level */
	ignore_level = MAGIC_LARGE_IGNORE_LEVEL;
#define	MAGIC_LARGE_ADDRESS	9827432		/* >> any valid address */
	loop[0].brk = loop[0].cont = MAGIC_LARGE_ADDRESS;
	loop[0].varindex = -1;
	loop[0].cmdindex = 0;	/* this is important to unravel on "return" */
	nloop = 0;
	oval2 = stickymem;
	origlevel = getlevel(MEM_SHCMD);
	stickymem = MEM_SHCMD;
	/* shut up the compiler */
	oval = 0; d = NULL; argi1 = 0;
	/* funcall tracing could be done here */
	/* if (caller != NULL) grindef("ARGV = ", caller->argv); */
	if (isset('R'))
		fds[fileno(runiofp)] = 1;
	for (pc = (entry == NULL ? code : entry) ; pc < eocode; ++pc) {
		if (sprung) {
			trapped();
			if (interrupted)
				break;
		}
		cmd = (OutputTokens)*pc;
		if (isset('I'))
			fprintf(runiofp, "'%d\t%s\n", pc-code, TOKEN_NAME(cmd));
		switch (TOKEN_NARGS(cmd)) {
		case 0: break;
		case 1:
			arg1 = ++pc;
			while (*pc != '\0')
				++pc;
			break;
		case -1:
			argi1 = *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
			argi1 <<= 8;
			argi1 |= *++pc;
			break;
		}
		switch (cmd) {
		case sBufferSetFromArgV:
			dollar = 1;
			quote = 1;
			arg1 = (u_char *)"@";
			/* this gives "$@" */
			/* FALL THROUGH */
		case sBufferSet:
			/* The buffer points at a linked list of strings */
			command->buffer = 0;
			command->bufferp = &command->buffer;
			/* FALL THROUGH */
		case sBufferAppend:
			if (dollar) {
				dollar = 0;
				d = v_expand(arg1, caller, *retcodep);
				if (d == NULL) {
					if (isset('u')) {
						fprintf(stderr,
						    "%s: parameter not set\n",
						    arg1);
						ignore_level = commandIndex;
					}
					d = conststring((u_char *)"");
				}
				if (!quote && STRING(d) && *(d->string) == '\0')
					break;
			} else if (*arg1 != '\0' || quote)
				d = conststring(arg1);
			else
				break;	/* it is a null string! */
			*command->bufferp = d;
			for (tmp = d; cdr(tmp) != NULL; tmp = cdr(tmp))
				continue;
			command->bufferp = &cdr(tmp);
			if (quote) {
				while (d != NULL) {
					if (STRING(d) && !ISDONTQUOTE(d))
						d->flags |= QUOTEDSTRING;
					d = cdr(d);
				}
				quote = 0;
			}
			if (isset('I'))
				grindef("Buffer = ", command->buffer);
			break;
		case sBufferExpand:
			if (command->buffer == NULL)
				d = conststring((u_char *)"");
			else if (cdr(command->buffer))
				d = s_catstring(command->buffer);
			else
				d = command->buffer;
			quoted = ISQUOTED(d);
			d = v_expand(d->string, caller, *retcodep);
			if (d == NULL) {
				if (isset('u')) {
					fprintf(stderr,
						"%s: parameter not set\n",
						arg1);
					ignore_level = commandIndex;
				}
				d = conststring((u_char *)"");
			}
			command->buffer = d;
			while (d != NULL) {
				if (quoted && STRING(d))
					d->flags |= QUOTEDSTRING;
				if (cdr(d) == NULL)
					break;
			}
			if (d == NULL)
				command->bufferp = &command->buffer;
			else {
				for (tmp = d; cdr(tmp) != NULL; tmp = cdr(tmp))
					continue;
				command->bufferp = &cdr(tmp);
			}
			if (isset('I'))
				grindef("Expanded Buffer = ", command->buffer);
		case sArgVpush:
			if (command->buffer == NULL)
				break;
			d = expand(command->buffer);
			if (command->argv == NULL && STRING(d)) {
				/* what kind of command is this? */
				functype((char *)d->string,
					 &command->shcmdp, &command->sfdp);
				if (command->sfdp != NULL)
					command->shcmdp = NULL;
				if (prevcommand != NULL) {
					prevcommand->next = command;
					prevcommand->reaperTop = reapableTop;
					if (command->shcmdp == NULL
					    && command->sfdp == NULL) {
						/* create pipe for prev. cmd */
						ioop(sIOopenPipe, prevcommand,
							(u_char*)0,O_CREAT,1,
							(u_char*)0);
						/* --- */
						RUNIO(prevcommand->doio);
						freeio(prevcommand->doio, 1);
						prevcommand->doio = NULL;
						/* --- */
						/* add inpipe to this command */
						ioop(sIOopenPipe, command,
							(u_char*)0,O_RDONLY,0,
							(u_char*)0);
					} else if (command->sfdp != NULL
					    || (command->shcmdp != NULL
					        && (command->shcmdp->sptr != NULL
					            || prevcommand->shcmdp->sptr!=NULL))){
						/* create stringbuffer */
						name = NULL;
						INSERTIO(&prevcommand->doio,
							 prevcommand,
							 sIObufOut, 1, 1, 0);
						/* --- */
						RUNIO(prevcommand->doio);
						freeio(prevcommand->doio, 1);
						prevcommand->doio = NULL;
						/* --- */
						INSERTIO(&command->doio,
							 command,
							 sIObufIn, 0, 1, 0);
#if 1
						if (nloop > 0 &&
						    loop[nloop].cmdindex == 
							commandIndex-1)
			    INSERTIO(&commandStack[commandIndex-2].undoio,
				 &commandStack[commandIndex-2],
				 sIObufFree, 0, 0, 0);
						else
#endif
						    INSERTIO(&command->undoio,
							 command,
							 sIObufFree, 0, 0, 0);
					}
					/*
					 * ... else we're connecting two
					 * list-valued functions, which is ok.
					 */
					runcommand(prevcommand, caller,
						ignore_level > commandIndex ?
						retcodep : (int *)NULL);
					command->rval = prevcommand->rval;
					if (command->prev == prevcommand) {
						command->prev = NULL;
						command->flag |= OSCMD_SKIPIT;
					}
					(void) free((char *)prevcommand);
					prevcommand = NULL;
					/* X:shouldn't prevcommand be stacked?*/
				}
			}
			if (command->argv == NULL) {
				command->argv = newcell();
				command->argv->flags = 0;
				command->argv->pflags = 0;
				command->argv->prev = 0;
				cdr(command->argv) = NULL;
				car(command->argv) = newcell();
				car(command->argv)->flags = 0;
				car(command->argv)->pflags = 0;
				car(command->argv)->prev = 0;
				cdar(command->argv) = NULL;
				caar(command->argv) = d;
				cdar(command->argv) = s_last(d);
			} else {
				cddar(command->argv) = d;
				cdar(command->argv) = s_last(d);
			}
			if (command->iocmd == ioPipeLater) {
				/* we saw an openPipe but it was too early...*/
				command->iocmd = ioNil;
				if (command->shcmdp != NULL
				    || command->sfdp != NULL) {
					/* set prevcommand in the CommandPop */
					command->iocmd = ioPipeOutput;
				} else {
					command->reaperTop = reapableTop;
					ioop(sIOopenPipe, command, (u_char *)0,
							  O_CREAT, 1, 0);
				}
			}
			if (isset('I'))
				grindef("Argv = ", command->argv);
			break;
		case sArgList:
			/* take the remaining arguments in caller->argv
			   and stick them in the local variable "argv" in
			   the current scope */
			break;
		case sVariableCdr:
			if (variable != NULL)
				car(variable) = cdar(variable);
			if (varmeter) {
				if (variable && car(variable)) {
					car(varmeter) = caar(variable);
					varmeter->flags = car(variable)->flags;
				} else {
					car(varmeter) = NULL;
					varmeter->flags = 0;
				}
			}
			if (isset('I'))
				grindef("Variable = ", variable);
			break;
		case sVariablePush:
			if (variableIndex >= 0) {
				varStack[variableIndex] = variable;
				vmeterStack[variableIndex] = varmeter;
			}
			++variableIndex;
			if (command->buffer == NULL)
				variable = NULL;
			else {
				variable = expand(command->buffer);
				variable = ncons(variable);
			}
			varmeter = NULL;
			if (isset('I'))
				grindef("Variable = ", variable);
			break;
		case sVariablePop:
			--variableIndex;
			if (variableIndex >= 0) {
				variable = varStack[variableIndex];
				varmeter = vmeterStack[variableIndex];
				if (isset('I'))
					grindef("Variable = ", variable);
			}
			break;
		case sVariableBuffer:
			if (variable != NULL) {
				command->buffer = ncons(car(variable));
			} else
				command->buffer = NIL;
			if (isset('I'))
				grindef("Buffer = ", command->buffer);
			break;
		case sVariableAppend:
			if (command->buffer == NULL)
				break;
			d = expand(command->buffer);
			if (variable == NULL)
				variable = ncons(d);
			else if (car(variable) == NULL)
				car(variable) = d;
			else
				cdr(s_last(car(variable))) = d; /* XX */
			if (isset('I'))
				grindef("Variable = ", variable);
			break;
		case sVariableLoopAttach:
			varmeter = cdaar(envarlist);
			if (variable != NULL) {
				car(varmeter) = caar(variable);
				varmeter->flags = car(variable)->flags;
			}
			break;
		case sCommandPush:
			if (commandIndex > 0) {
				if (command->iocmd == ioPipeLater) {
					/* see above at end of ArgV */
					command->iocmd = ioNil;
					if (command->shcmdp != NULL
					    || command->sfdp != NULL)
						command->iocmd = ioPipeOutput;
					else {
						command->reaperTop =
							reapableTop;
						ioop(sIOopenPipe, command,
							(u_char *)0,O_CREAT,1,
							(u_char *)0);
					}
				}
				if (command->doio != NULL) {
					(void) RUNIO(command->doio);
					freeio(command->doio, 1);
					command->doio = NULL;
					if (prevcommand != NULL) /* gross */
						prevcommand->doio = NULL;
				}
			}
			pcommand = command;
			command = &commandStack[++commandIndex];
			command->buffer = NULL;
			command->bufferp = &command->buffer;
			command->argv = command->envold = NULL;
			command->doio = command->undoio = command->execio = 0;
			command->iocmd = ioNil;
			command->fdmask = 0;
			command->pgrp = 0;
			command->flag = 0;
			if (prevcommand != NULL) {	/* in pipe */
				command->memlevel = prevcommand->memlevel;
				command->pgrpp = prevcommand->pgrpp;
			} else {
				command->memlevel = getlevel(MEM_SHCMD);
				command->pgrpp = NULL;
			}
			command->shcmdp = NULL;
			command->sfdp = NULL;
			command->next = NULL;
			command->prev = prevcommand;
			if (commandIndex > 1) {
				/*
				 * This value is inherited, NULL or not.  It
				 * is reset by a null return from list-valued
				 * function, or by any string-valued function.
				 */
				command->rval = pcommand->rval;

				command->pgrpp = pcommand->pgrpp;
				command->fdmask = pcommand->fdmask;

				/* mark child commands of `...` */
				if (pcommand->iocmd == ioIntoBuffer) {
					pcommand->reaperTop = reapableTop;
					command->reaperTop = reapableTop;
				} else if (pcommand->reaperTop > -1)
					command->reaperTop =
						pcommand->reaperTop;
				else
					command->reaperTop = -1;
			} else {
				command->reaperTop = -1;
				if (command->prev != NULL)
					command->rval = command->prev->rval;
				else
					command->rval = NULL;
			}
			break;
		case sCommandPop:
			if (pipefd >= 0)   /* IOintoBuffer relies on this */
				fds[pipefd] = 0;
			if (command->iocmd == ioPipeOutput) {
				/*
				 * This command pipes its output into
				 * another one.
				 */
				command->iocmd = ioNil;
				prevcommand =
				  (struct osCmd *)emalloc(sizeof(struct osCmd));
				*prevcommand = *command;
				command->flag |= OSCMD_SKIPIT;
				break;
			}
			argi1 = *retcodep;
			if (command->iocmd != ioCarryBuffer) {
				runcommand(command, caller,
					ignore_level > commandIndex ?
					retcodep : (int *)NULL);
				tmp = *(command->bufferp);
			}
			if (ignore_level == commandIndex)
				ignore_level = MAGIC_LARGE_IGNORE_LEVEL;
			if (command->shcmdp != NULL
			    && (command->shcmdp->flag == SH_INTERNAL
				|| command->shcmdp->lptr == sh_return)) {
				/* this was break, continue, return or exit */
				/* any optional argument is now in *retcodep */
				if (*command->shcmdp->name == 'b' /* break */
				    || *command->shcmdp->name == 'c') {/*cont'*/
					if (*retcodep > 0) {
						*retcodep -= 1;
						if (*retcodep <= nloop)
							nloop -= *retcodep;
					} else
						*retcodep = 0;
					if (nloop < 0)
						goto nobreak;
					if (*command->shcmdp->name == 'b')
						pc = code + loop[nloop].brk;
					else
						pc = code + loop[nloop].cont;
					--pc;
				} else if (*command->shcmdp->name == 'e') {
					if (cdar(command->argv))
						trapexit(*retcodep);
					else
						trapexit(argi1);
				}
#if 0
				else {
					std_printf("ix = %d\nrval = %x\n",
					       commandIndex, command->rval);
				}
#endif
				/* if not in a loop this is done for returns */
				while (commandIndex > loop[nloop].cmdindex) {
					pcommand = &commandStack[commandIndex];
					if (pcommand->undoio) {
						RUNIO(pcommand->undoio);
						freeio(pcommand->undoio, 1);
					}
					/* this is done after getout, below */
					/*setlevel(MEM_SHCMD, pcommand->memlevel);*/
					--commandIndex;
				}
				while (variableIndex > loop[nloop].varindex) {
					if (varmeter) {
						car(varmeter) = NULL;
						varmeter->flags = 0;
					}
					--variableIndex;
					variable = varStack[variableIndex];
					varmeter = vmeterStack[variableIndex];
				}
				if (*command->shcmdp->name == 'b'
				    && variableIndex >= 0 && varmeter != NULL) {
					car(varmeter) = NULL;
					varmeter->flags = 0;
					variableIndex--;
				}
				if (*command->shcmdp->name == 'r') {
#if 0
					std_printf("idx = %d\n", commandIndex);
					if (commandIndex >= 0)
						std_printf("rval %x\n",
							command->rval);
#endif
					goto getout;
				} else {
					command = &commandStack[commandIndex];
					break;
				}
			}
nobreak:
			if (command->prev != NULL
			    || (command->flag & OSCMD_SKIPIT))
				--commandIndex;
			--commandIndex;
			/*
			 * If we are returning data, we shouldn't free dtpr
			 * or do a setlevel(), since the value may be used in
			 * parent command.  Eventually a setlevel() will be
			 * done anyway to reclaim space... we hope.
			 */
			if (command->memlevel && command->rval == NULL
			    && command->buffer == NULL
			    && command->iocmd != ioIntoBuffer) {
				/*setlevel(MEM_SHCMD, command->memlevel);*/
				if (command->pgrp > 0)
					jc_report(command->pgrp);
			}
			if (commandIndex > 0) {
				pcommand = &commandStack[commandIndex];
				while (command->argv != NULL
					&& pcommand->flag & OSCMD_SKIPIT) {
					pcommand =
						&commandStack[--commandIndex];
				}
				pcommand->rval = command->rval;
				if (command->buffer != NULL) {
					if (command->flag & OSCMD_QUOTEOUTPUT)
						coalesce(command);
					else
						flushwhite(command);
					*(pcommand->bufferp) = command->buffer;
					pcommand->bufferp = command->bufferp;
				}
				command = pcommand;
				if (isset('I'))
					grindef("Command = ", command->argv);
			}
			break;
		case sCommandCarryBuffer:
			command->iocmd = ioCarryBuffer;
			break;
		case sIOopen:
		case sIOopenPortal:
		case sIOopenString:
			if (command->buffer == NULL)
				name = (u_char *)"";
			else if (cdr(command->buffer))
				name = (s_catstring(command->buffer))->string;
			else if (command->buffer)
				/* always true, need else below */
				name = command->buffer->string;
			else
			/* FALL THROUGH */
		case sIOopenPipe:
			if (cmd == sIOopenPipe) {
				if (ioflags & O_CREAT) {
					if (command->argv == NULL) {
						/* defer dealing with this */
						command->iocmd = ioPipeLater;
						break;
					} else if (command->shcmdp != NULL
						   || command->sfdp != NULL) {
						command->iocmd = ioPipeOutput;
						break;
					}
				} else if (prevcommand != NULL)
					break;
				name = NULL;
			} else		/* continuation of above fallthrough */
			/* FALL THROUGH */
		case sIOdup:
		case sIOclose:
				name = NULL;
			if (cmd == sIOclose)
				ioflags = 0;
			ioop(cmd, command, name, ioflags, defaultfd, arg1);
			break;
		case sIOintoBuffer:
			command->fdmask = 0;
			if (quote) {
				command->flag |= OSCMD_QUOTEOUTPUT;
				quote = 0;
			}
			ib_command[++ibt] = command;
			break;
		case sIOsetIn:
			defaultfd = 0;
			ioflags = O_RDONLY;
			break;
		case sIOsetInOut:
			defaultfd = 0;
			ioflags = O_CREAT|O_RDWR;
			break;
		case sIOsetOut:
			defaultfd = 1;
			ioflags = O_CREAT|O_WRONLY|O_TRUNC;
			break;
		case sIOsetAppend:
			defaultfd = 1;
			ioflags = O_CREAT|O_WRONLY|O_APPEND;
			break;
		case sIOsetDesc:
			defaultfd = atoi((char *)arg1);
			break;
		case sParameter:
			if (caller != NULL && caller->argv != NULL) {
				l = car(caller->argv);
				if ((d = cdr(l)) != NULL) {
					cdr(l) = cddr(l);
					if (!LIST(d))
						name = d->string;
				} else
					name = (u_char *)"";
				oval = stickymem;
				stickymem = MEM_MALLOC;
				if (d != NULL && LIST(d)) {
					cdr(d) = NULL;
					d = s_copy_tree(d);
					s_set_prev(d, car(d));
				} else
					d = newstring((u_char *)
							strnsave((char *)name,
							       strlen((char *)name)));
				/* create the variable in the current scope */
				l = car(envarlist);
				cdr(d) = car(l);
				if (*arg1)
					car(l) = conststring(arg1);
				else
					car(l) = conststring((u_char *)"");
				cdar(l) = d;
				stickymem = oval;
				/* grindef("ARGV = ", caller->argv);
				grindef("VARS = ", envarlist);
				grindef("TMPO = ", l); */
			} else {
				fprintf(stderr, "parameter without call\n");
				abort();
			}
			break;
		case sAssign:
		case sAssignTemporary:
			if (command->argv == NULL
					  || cdar(command->argv) == NULL)
				break;
			for (d = caar(command->argv); cdr(d) != NULL; )
				d = cdr(d);
			coalesce(command);
			if (command->buffer == NULL)
				command->buffer = conststring((u_char *)"");
			if (nsift >= 0)
				v_accessed = sift[nsift].accessed;
			assign(d, command->buffer,
				cmd==sAssign ? (struct osCmd *)NULL : command);
			/* do NOT reset *retcodep here */
			command->bufferp = &command->buffer;
			command->argv = NULL;
			command->shcmdp = NULL;
			if (isset('I'))
				grindef("Argv = ", command->argv);
			break;
		case sFunction:
			if (isset('I'))
				fprintf(runiofp,
					"defining '%s', entry@ %d, exit@ %d\n",
					command->buffer->string,
					pc+1 - code, argi1);
			defun(cdp, (char *)command->buffer->string,
				pc+1, code + argi1);
			/* FALL THROUGH */
		case sJump:
			pc = code + argi1 - 1;
			break;
		case sBranchOrigin:
			fprintf(stderr, "%s: unpatched branch at %d\n",
					progname, pc-code);
			break;
		case sJumpFork:
			if (command->doio != NULL) {
				(void) RUNIO(command->doio);
				freeio(command->doio, 1);
				command->doio = NULL;
			}
			if ((childpid = fork()) == 0) {
				/* in child */
				dye = 1;
				eocode = code + argi1;
				if (command->execio != NULL) {
					(void) RUNIO(command->execio);
					freeio(command->execio, 0);
					command->execio = NULL;
				}
				command->reaperTop = -1;
				--ibt;
			} else if (childpid > 0) {
				/* in parent */
#ifdef	USE_UNIONWAIT
				while (wait((union wait *)0) != childpid)
					continue;
#else	/* !USE_UNIONWAIT */
				while (wait((union wait *)0) != childpid)
					continue;
#endif	/* USE_UNIONWAIT */
				pc = code + argi1 - 1;
			} else if (childpid < 0) {
				/* error */
			}
			break;
		case sJumpIfFailure:
			if (*retcodep > 0)
				pc = code + argi1 - 1;
			break;
		case sJumpIfSuccess:
			if (*retcodep == 0)
				pc = code + argi1 - 1;
			break;
		case sJumpIfNilVariable:
			if (variable == NULL || car(variable) == NULL)
				pc = code + argi1 - 1;
			break;
		case sJumpIfMatch:
			if (command->buffer == NULL)
				break;
			if (variable == NULL) {
				variable = conststring((u_char *)"");
				variable = ncons(variable);
			} else if (car(variable)->string == NULL)
				break;
			globchars['|'] = 1;
			switch (squish(command->buffer, &name, &iname)) {
			case -1:
				if (STRING(command->buffer)
				    && strcmp((char *)command->buffer->string,
					      (char *)car(variable)->string) == 0)
					pc = code + argi1 - 1;
				break;
			case 0:
				if (strcmp((char *)name, (char *)car(variable)->string) == 0)
					pc = code + argi1 - 1;
				break;
			case 1:
				i = 0;
				do {
					int fi = i;
					while (iname[i] != 0
					    && iname[i] != (u_int)'|')
						++i;
					if (glob_match(&iname[fi], &iname[i],
						   car(variable)->string)) {
						pc = code + argi1 - 1;
						break;
					}
				} while (iname[i++] != 0);
				break;
			}
			globchars['|'] = 0;
			break;
		case sJumpIfFindVarNil:
			if (command->buffer == NULL)
				d = conststring((u_char *)"");
			else if (cdr(command->buffer))
				d = s_catstring(command->buffer);
			else
				d = command->buffer;
			if ((d = v_find(d->string)) == NULL)
				pc = code + argi1 - 1;
			break;
		case sJumpIfOrValueNil:
			if (d == NULL
			    || (d = cdr(d)) == NULL
			    || (STRING(d)
				&& (d->string == NULL || *d->string == '\0'))
			    || (LIST(d) && car(d) == NULL))
				pc = code + argi1 - 1;
			break;
		case sJumpLoopBreak:
			loop[nloop].brk = argi1;
			break;
		case sJumpLoopContinue:
			loop[nloop].cont = argi1;
			break;
		case sLoopEnter:
			++nloop;
			loop[nloop].cont = 0;
			loop[nloop].brk = 0;
			loop[nloop].cmdindex = commandIndex;
			loop[nloop].varindex = variableIndex;
			if (prevcommand != NULL) {
				prevcommand->next = command;
				prevcommand->reaperTop = reapableTop;
				/* create stringbuffer */
				name = NULL;
				INSERTIO(&prevcommand->doio,
					 prevcommand,
					 sIObufOut, 1, 1, 0);
				/* --- */
				RUNIO(prevcommand->doio);
				freeio(prevcommand->doio, 1);
				prevcommand->doio = NULL;
				/* --- */
				INSERTIO(&command->doio,
					 command,
					 sIObufIn, 0, 1, 0);
				INSERTIO(&command->undoio,
					 command,
					 sIObufFree, 0, 0, 0);
				/*
				 * ... else we're connecting two
				 * list-valued functions, which is ok.
				 */
				runcommand(prevcommand, caller,
					ignore_level > commandIndex ?
					retcodep : (int *)NULL);
				command->rval = prevcommand->rval;
				if (command->prev == prevcommand)
					command->prev = NULL;
				(void) free((char *)prevcommand);
				prevcommand = NULL;
				/* X:shouldn't prevcommand be stacked?*/
			}
			break;
		case sLoopExit:
			if (nloop >= 0)
				--nloop;
			break;
		case sLocalVariable:
			/* create the variable in the current scope */
			oval = stickymem;
			stickymem = MEM_MALLOC;
			d = NIL;
			tmp = conststring(arg1);
			s_set_prev(tmp, d);
			cdr(d) = caar(envarlist);
			cdr(tmp) = d;
			caar(envarlist) = tmp;
			stickymem = oval;
			if (isset('I'))
				grindef("Scopes = ", envarlist);
			break;
		case sScopePush:
			oval = stickymem;
			stickymem = MEM_MALLOC;
			d = NIL;
			(void) s_push(d, envarlist);
			stickymem = oval;
			/* fvcache.namesymbol = 0; */
			break;
		case sScopePop:
			d = car(envarlist);
			car(envarlist) = cdar(envarlist);
			cdr(d) = NULL;
			s_free_tree(d);
			/* fvcache.namesymbol = 0; */
			break;
		case sDollarExpand:
			dollar = 1;
			break;
		case sBufferQuote:
			quote = 1;
			break;
		case sBackground:
			/*
			 * 1. tell child and pipeline commands to background.
			 * 2. find place to stash pid of this command for pgrp.
			 * 3. call hook when this command finishes to print
			 *    pids of all commands and job number.
			 */
			command->pgrpp = &command->pgrp;
			break;
		case sPrintAndExit:
			if (command->buffer == NULL)
				d = conststring((u_char *)"");
			else if (cdr(command->buffer))
				d = s_catstring(command->buffer);
			else
				d = command->buffer;
			fprintf(stderr, "%s: %s\n", progname,
					*(d->string) == '\0' ?
					"parameter null or not set"
					: (char *)d->string);
			if (!isset('i'))
				trapexit(1);
			break;
#ifdef	MAILER
		case sSiftPush:
			v_record = 1;
			if (nsift >= 0)
				sift[nsift].program = re;
			if (++nsift >= (sizeof sift / sizeof sift[0]))
				abort();
			sift[nsift].tlist = NULL;
			sift[nsift].label = pc+1 - code;
			sift[nsift].subexps = NULL;
			v_accessed = NULL;
			break;
		case sSiftBody:
			/* we don't *need* to free tokens because they are
			   allocated off our MEM_SHCMD memory stack */
			if (sift[nsift].tlist)
				freeTokens(sift[nsift].tlist, MEM_SHCMD);
			sift[nsift].tlist = NULL;
			if (command->buffer != NULL) {
				if (cdr(command->buffer))
					d = s_catstring(command->buffer);
				else
					d = command->buffer;
				if (STRING(d)) {
					arg1 = d->string;
					sift[nsift].tlist = scanstring(arg1);
				}
			}
			sift[nsift].accessed = v_accessed;  /* nop 2nd time */
			if (v_record == 0)	/* we've been here before */
				pc = code + loop[nloop].cont - 1;
			else
				v_record = 0;
			v_changed = 0;
			break;
		case sSiftCompileRegexp:
			if (argi1) {
				re = cdp->rearray[argi1-1];
#if 0
std_printf("found %x at %d\n", re, argi1-1);
#endif
				break;
			}
			re = NULL;
			if (command->buffer != NULL) {
				if (cdr(command->buffer)) {
					int oval = stickymem;
					stickymem = MEM_PERM;
					d = s_catstring(command->buffer);
					stickymem = oval;
				} else
					d = command->buffer;
				if (STRING(d))
					re = regcomp((char *)d->string);
			}
			if (re == NULL)
				break;
			if (cdp->rearray_size == 0) {
#define	RECLICK 25
				cdp->rearray_size = RECLICK;
				cdp->rearray = (regexp **)
					emalloc(RECLICK*sizeof(regexp *));
			} else if (cdp->rearray_idx >= cdp->rearray_size-2) {
				/* 1 spare */
				cdp->rearray_size *= 2;
				cdp->rearray =
					(regexp **)erealloc((char*)cdp->rearray,
					    cdp->rearray_size*sizeof(regexp *));
			}
			cdp->rearray[++cdp->rearray_idx] = re;
#if 0
std_printf("set %x at %d\n", re, cdp->rearray_idx);
#endif

			/* NB! we are writing into the table */
			++cdp->rearray_idx;
			*(pc-3) = (cdp->rearray_idx>>24) & 0xff;
			*(pc-2) = (cdp->rearray_idx>>16) & 0xff;
			*(pc-1) = (cdp->rearray_idx>>8) & 0xff;
			*(pc) = cdp->rearray_idx & 0xff;
			--cdp->rearray_idx;
			break;
		case sSiftReevaluate:
			if (v_changed)
				/* jump to sift expression evaluation
				   followed by sSiftBody */
				pc = sift[nsift].label-1 + code;
			break;
		case sSiftPop:
			/* see comment above about freeing tokens */
			if (sift[nsift].tlist)
				freeTokens(sift[nsift].tlist, MEM_SHCMD);
			for (v_accessed = sift[nsift].accessed;
			     v_accessed != NULL;
			     v_accessed = sift[nsift].accessed) {
				sift[nsift].accessed = v_accessed->next;
				(void) free((char *)v_accessed);
			}
			--nsift;
			if (nsift >= 0) {
				v_accessed = sift[nsift].accessed;
				re = sift[nsift].program;
			}
			break;
		case sSiftBufferAppend:
			if (arg1 == NULL || re == NULL || !isdigit(*arg1))
				break;
			if (nsift > 0 && sift[nsift].subexps == NULL)
				setsubexps(&sift[nsift-1].subexps, re);
			else
				setsubexps(&sift[nsift].subexps, re);
			if ((arg1 = regsub(re, atoi((char *)arg1))) != NULL) {
				tmp = conststring(arg1);
				tmp->flags |= QUOTEDSTRING;
				/* cdr(tmp) = command->buffer; */
				*command->bufferp = tmp;
				command->bufferp = &cdr(tmp);
			}
			break;
		case sJumpIfRegmatch:
			if (sift[nsift].tlist == NULL)
				sift[nsift].tlist = makeToken((u_char *)"", 0);
			setsubexps(&sift[nsift].subexps, re);
			if (!regexec(re, sift[nsift].tlist))
				pc = code + argi1 - 1;
			break;
#endif	/* MAILER */
		default:
			fprintf(stderr,
				"Hey, you forgot to update the interpreter!\n");
			fprintf(stderr,
				"Illegal command token %d\n", (int)cmd);
			if (!isset('i'))
				exit(1);
			break;
		}
	}
getout:
	if (dye /* I know this is misspelled */)
		trapexit(0);
	if (isset('I'))
		grindef("Vars = ", envarlist);
	/* null any loop variable values so we can free scopes below */
	while (variableIndex > -1) {
		variable = varStack[variableIndex];
		if (varmeter) {
			car(varmeter) = NULL;
			varmeter->flags = 0;
		}
		varmeter = vmeterStack[variableIndex];
		--variableIndex;
	}
	while (nsift >= 0) {
		for (v_accessed = sift[nsift].accessed;
		     v_accessed != NULL;
		     v_accessed = sift[nsift].accessed) {
			sift[nsift].accessed = v_accessed->next;
			(void) free((char *)v_accessed);
		}
		--nsift;
	}
	while (commandIndex > 0) {
		pcommand = &commandStack[commandIndex];
		if (pcommand->undoio) {
			RUNIO(pcommand->undoio);
			freeio(pcommand->undoio, 1);
		}
		/* The setlevel is done below */
		/* setlevel(MEM_SHCMD, pcommand->memlevel); */
		--commandIndex;
	}
	setlevel(MEM_SHCMD, origlevel);
#ifdef	MAILER
	/*
	 * This is pretty dicey; we rely on setlevel() not changing the
	 * stuff that was just freed.  We need to do this to avoid
	 * malloc'ing stuff unnecessarily.  For example we usually just
	 * want to access the return value in the context of the caller.
	 */
	if (command->rval != NULL) {
		if (caller != NULL) {
			/*
			 * XXXX: note: there is no guarantee of non-overlap.
			 * This is done on a wing and a prayer...
			 */
			oval = stickymem;
			stickymem = MEM_TEMP;
			caller->rval = s_copy_tree(command->rval);
			stickymem = oval;
		}
	}
#endif	/* MAILER */
	while (margin != car(envarlist)) {
		d = car(envarlist);
		car(envarlist) = cdr(d);
		cdr(d) = NULL;
		s_free_tree(d);
	}
	stickymem = oval2;
#ifdef	MAILER
	--funclevel;
#endif	/* MAILER */
	if (cdp->functions == NULL) {
		if (cdp->rearray != NULL) {
			while (cdp->rearray_idx >= 0)
				(void) free((char *)cdp->rearray[cdp->rearray_idx--]);
			(void) free((char *)cdp->rearray);
		}
		(void) free((char *)cdp->table);
		(void) free((char *)cdp);
		return NULL;
	}
	cdp->oktofree = 1;
	
	return cdp;
}


/*
 * Doing an apply() outside the interpreter() is only a safe thing to do
 * when outside the interpreter (...), i.e. at interactive prompt level.
 * We want to call an arbitrary shell or builtin function with some number
 * of (string) arguments.
 */

int
fapply(shcmdp, l)
	struct shCmd *shcmdp;
	struct conscell *l;
{
	register struct conscell *ll;
	int argc = 0;
	char *argv[20];	/* XX: argc never to exceed magic number */

	if (shcmdp->sptr != NULL) {
		argv[argc++] = shcmdp->name;
		for (ll = car(l); ll != NULL; ll = cdr(l)) {
			if (STRING(ll))
				argv[argc++] = (char *)ll->string;
		}
		argv[argc] = NULL;
		return (*(shcmdp->sptr))(argc, argv);
	}
	/* XX: we don't need to support this, yet */
	/* else if (shcmdp->lptr != NULL) {
		return -1;
	} */
	return -1;
}

int
lapply(fname, l)
	char *fname;
	struct conscell *l;
{
	int retcode, oval;
	struct sslfuncdef *sfdp;
	struct spblk *spl;
	struct osCmd avc;
	struct conscell *ll, *tmp;
	extern struct osCmd avcmd;
	extern struct sptree *spt_funclist, *spt_builtins;
	extern int stickymem;
	extern struct spblk *sp_lookup();
	extern struct conscell **return_valuep;

	if ((spl = sp_lookup(symbol((u_char *)fname), spt_funclist)) == NULL) {
		if ((spl = sp_lookup(symbol((u_char *)fname), spt_builtins)) == NULL)
			return -1;
		return fapply((struct shCmd *)spl->data, l);
	}
	if ((sfdp = (struct sslfuncdef *)spl->data) == NULL)
		return -1;
	avc = avcmd;
	if (l != NULL) {
		ll = conststring((u_char *)fname);
		cdr(ll) = car(l);
		car(l) = ll;
		avc.argv = l;
	}

	(void) interpret(sfdp->tabledesc->table,
			 sfdp->eot, sfdp->pos,
			 &avc, &retcode, sfdp->tabledesc);
	if (return_valuep != NULL) {
		if (*return_valuep != NULL)
			s_free_tree(*return_valuep);
		if (avc.rval == NULL)
			*return_valuep = NULL;
		else {
			oval = stickymem;
			stickymem = MEM_MALLOC;
			*return_valuep = s_copy_tree(avc.rval);
			stickymem = oval;
		}
	}
	avc.rval = NULL;
	return retcode;
}

int
apply(argc, argv)
	int argc;
	char *argv[];
{
	return lapply(argv[0],
		      argc > 1
			   ? s_listify(argc-1, &argv[1])
	/* if argc == 0, don't change avc.argv even if there are arguments */
			   : (struct conscell *)NULL);
}


/*
 * A cheap way of calling (e.g.) prompt-generating functions without
 * having to set up argv lists and such.  Same restrictions as apply().
 */

int
funcall(fname)
	char *fname;
{
	char *av[1];

	av[0] = fname;
	return apply(0, &av[0]);
}

