/*
 * $Id: exec.c,v 3.1.0.6 1996/12/14 02:43:57 hzoli Exp $
 *
 * exec.c - command execution
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1996 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zsh.h"

#define execerr() if (!forked) { lastval = 1; return; } else _exit(1)
	

static LinkList args;
static int doneps4;

/* parse string into a list */

/**/
List
parse_string(char *s)
{
    List l;

    lexsave();
    inpush(s, 0, NULL);
    strinbeg();
    stophist = 2;
    l = parse_list();
    strinend();
    inpop();
    lexrestore();
    return l;
}

#ifdef HAVE_GETRLIMIT
/**/
int
zsetlimit(int limnum, char *nam)
{
    if (limits[limnum].rlim_max != current_limits[limnum].rlim_max ||
	limits[limnum].rlim_cur != current_limits[limnum].rlim_cur) {
	if (setrlimit(limnum, limits + limnum)) {
	    if (nam)
		zwarnnam(nam, "setrlimit failed: %e", NULL, errno);
	    return -1;
	}
	current_limits[limnum] = limits[limnum];
    }
    return 0;
}

/**/
int
setlimits(char *nam)
{
    int limnum;
    int ret = 0;

    for (limnum = 0; limnum < RLIM_NLIMITS; limnum++)
	if (zsetlimit(limnum, nam))
	    ret++;
    return ret;
}
#endif

/* fork and set limits */

/**/
pid_t
zfork(void)
{
    pid_t pid;

    if (thisjob >= MAXJOB - 1) {
	zerr("job table full", NULL, 0);
	return -1;
    }
    pid = fork();
    if (pid == -1) {
	zerr("fork failed: %e", NULL, errno);
	return -1;
    }
#ifdef HAVE_GETRLIMIT
    if (!pid)
	/* set resource limits for the child process */
	setlimits(NULL);
#endif
    return pid;
}


int list_pipe = 0, simple_pline = 0;

static pid_t list_pipe_pid;
static int nowait, pline_level = 0;
static int list_pipe_child = 0, list_pipe_job;
static char list_pipe_text[JOBTEXTSIZE];

/* execute a current shell command */

/**/
int
execcursh(Cmd cmd)
{
    if (!list_pipe)
	deletejob(jobtab + thisjob);
    execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC);
    cmd->u.list = NULL;
    return lastval;
}

/* execve after handling $_ and #! */

#define POUNDBANGLIMIT 64

/**/
int
zexecve(char *pth, char **argv)
{
#ifdef __EMX__
    struct stat statbuf;
#endif
    int eno;
    static char buf[PATH_MAX * 2];
    char **eep;

    unmetafy(pth, NULL);
    for (eep = argv; *eep; eep++)
	if (*eep != pth)
	    unmetafy(*eep, NULL);
    for (eep = environ; *eep; eep++)
	if (**eep == '_' && (*eep)[1] == '=')
	    break;
    buf[0] = '_';
    buf[1] = '=';
    if (*pth == '/')
	strcpy(buf + 2, pth);
    else
	sprintf(buf + 2, "%s/%s", pwd, pth);
    if (!*eep)
	eep[1] = NULL;
    *eep = buf;
    execve(pth, argv, environ);

    /* If the execve returns (which in general shouldn't happen),   *
     * then check for an errno equal to ENOEXEC.  This errno is set *
     * if the process file has the appropriate access permission,   *
     * but has an invalid magic number in its header.               */
#ifdef __EMX__
    /* EMX doen't seem to return ENOEXEC if the file isn't an .exe  */
    if (!stat(pth,&statbuf)) {
#else
    if ((eno = errno) == ENOEXEC) {
#endif
	char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
	int fd, ct, t0;

#ifdef __EMX__
	if ((fd = open(pth, O_BINARY|O_RDONLY)) >= 0) {
#else
	if ((fd = open(pth, O_RDONLY)) >= 0) {
#endif
	    argv0 = *argv;
	    *argv = pth;
	    ct = read(fd, execvebuf, POUNDBANGLIMIT);
	    close(fd);
	    if (ct > 0) {
		if (execvebuf[0] == '#') {
		    if (execvebuf[1] == '!') {
			for (t0 = 0; t0 != ct; t0++)
			    if (execvebuf[t0] == '\n')
				execvebuf[t0] = '\0';
			execvebuf[POUNDBANGLIMIT] = '\0';
			for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
			for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
			if (*ptr) {
			    *ptr = '\0';
			    argv[-2] = ptr2;
			    argv[-1] = ptr + 1;
			    execve(ptr2, argv - 2, environ);
			} else {
			    argv[-1] = ptr2;
			    execve(ptr2, argv - 1, environ);
			}
		    } else {
			argv[-1] = "sh";
			execve("/bin/sh", argv - 1, environ);
		    }
		} else {
		    for (t0 = 0; t0 != ct; t0++)
			if (!execvebuf[t0])
			    break;
		    if (t0 == ct) {
			argv[-1] = "sh";
			execve("/bin/sh", argv - 1, environ);
		    }
		}
	    } else
		eno = errno;
	    *argv = argv0;
	} else
	    eno = errno;
    }
    /* restore the original arguments and path but do not bother with *
     * null characters as these cannot be passed to external commands *
     * anyway.  So the result is truncated at the first null char.    */
    pth = metafy(pth, -1, META_NOALLOC);
    for (eep = argv; *eep; eep++)
	if (*eep != pth)
	    (void) metafy(*eep, -1, META_NOALLOC);
    return eno;
}

#define MAXCMDLEN (PATH_MAX*4)

/* test whether we really want to believe the error number */

/**/
int
isgooderr(int e, char *dir)
{
    /*
     * Maybe the directory was unreadable, or maybe it wasn't
     * even a directory. 
     */
    return ((e != EACCES || !access(dir, X_OK)) &&
	    e != ENOENT && e != ENOTDIR); 
}

/* execute an external command */

/**/
void
execute(Cmdnam not_used_yet, int dash)
{
    Cmdnam cn;
    static LinkList exargs;
    char buf[MAXCMDLEN], buf2[MAXCMDLEN];
    char *s, *z, *arg0;
    char **argv, **pp;
    int eno = 0, ee;

    /* If the parameter STTY is set in the command's environment, *
     * we first run the stty command with the value of this       *
     * parameter as it arguments.                                 */
    if (!exargs && (s = zgetenv("STTY")) && isatty(0)) {
	char *t;

	exargs = args;	/* this prevents infinite recursion */
	args = NULL;
	t = tricat("stty", " ", s);
	execstring(t, 1, 0);
	zsfree(t);
	args = exargs;
	exargs = NULL;
    }

    arg0 = (char *) peekfirst(args);
    cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);

    /* If ARGV0 is in the commands environment, we use *
     * that as argv[0] for this external command       */
    if ((z = zgetenv("ARGV0"))) {
	setdata(firstnode(args), (void *) ztrdup(z));
	delenv(z - 6);
    } else if (dash) {
    /* Else if the pre-command `-' was given, we add `-' *
     * to the front of argv[0] for this command.         */
	sprintf(buf2, "-%s", arg0);
	setdata(firstnode(args), (void *) ztrdup(buf2));
    }

    argv = makecline(args);
    child_unblock();
    if ((int) strlen(arg0) >= PATH_MAX) {
	zerr("command too long: %s", arg0, 0);
	_exit(1);
    }
    for (s = arg0; *s; s++)
	if (*s == '/') {
	    errno = zexecve(arg0, argv);
	    if (arg0 == s || unset(PATHDIRS) ||
		(arg0[0] == '.' && (arg0 + 1 == s ||
				    (arg0[1] == '.' && arg0 + 2 == s)))) {
		zerr("%e: %s", arg0, errno);
		_exit(1);
	    }
	    break;
	}

    if (cn) {
	char nn[PATH_MAX], *dptr;

	if (cn->flags & HASHED)
	    strcpy(nn, cn->u.cmd);
	else {
	    for (pp = path; pp < cn->u.name; pp++)
		if (!**pp || (**pp == '.' && (*pp)[1] == '\0')) {
		    ee = zexecve(arg0, argv);
		    if (isgooderr(ee, *pp))
			eno = ee;
		} else if (**pp != '/') {
		    z = buf;
		    strucpy(&z, *pp);
		    *z++ = '/';
		    strcpy(z, arg0);
		    ee = zexecve(buf, argv);
		    if (isgooderr(ee, *pp))
			eno = ee;
		}
	    strcpy(nn, cn->u.name ? *(cn->u.name) : "");
	    strcat(nn, "/");
	    strcat(nn, cn->nam);
	}
	ee = zexecve(nn, argv);

	if ((dptr = strrchr(nn, '/')))
	    *dptr = '\0';
	if (isgooderr(ee, *nn ? nn : "/"))
	    eno = ee;
    }
    for (pp = path; *pp; pp++)
	if (!(*pp)[0] || ((*pp)[0] == '.' && !(*pp)[1])) {
	    ee = zexecve(arg0, argv);
	    if (isgooderr(ee, *pp))
		eno = ee;
	} else {
	    z = buf;
	    strucpy(&z, *pp);
	    *z++ = '/';
	    strcpy(z, arg0);
	    ee = zexecve(buf, argv);
	    if (isgooderr(ee, *pp))
		eno = ee;
	}
    if (eno)
	zerr("%e: %s", arg0, eno);
    else
	zerr("command not found: %s", arg0, 0);
    _exit(1);
}

#define try(X) { if (iscom(X)) return ztrdup(X); }

/* get the full pathname of an external command */

/**/
char *
findcmd(char *arg0)
{
    char **pp;
    char *z, *s, buf[MAXCMDLEN];
    Cmdnam cn;

    cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
    if (!cn && isset(HASHCMDS))
	cn = hashcmd(arg0, path);
    if ((int) strlen(arg0) > PATH_MAX)
	return NULL;
    for (s = arg0; *s; s++)
	if (*s == '/') {
	    try(arg0);
	    if (arg0 == s || unset(PATHDIRS)) {
		return NULL;
	    }
	    break;
	}
    if (cn) {
	char nn[PATH_MAX];

	if (cn->flags & HASHED)
	    strcpy(nn, cn->u.cmd);
	else {
	    for (pp = path; pp < cn->u.name; pp++)
		if (**pp != '/') {
		    z = buf;
		    if (**pp) {
			strucpy(&z, *pp);
			*z++ = '/';
		    }
		    strcpy(z, arg0);
		    try(buf);
		}
	    strcpy(nn, cn->u.name ? *(cn->u.name) : "");
	    strcat(nn, "/");
	    strcat(nn, cn->nam);
	}
	try(nn);
    }
    for (pp = path; *pp; pp++) {
	z = buf;
	if (**pp) {
	    strucpy(&z, *pp);
	    *z++ = '/';
	}
	strcpy(z, arg0);
	try(buf);
    }
    return NULL;
}

/**/
int
iscom(char *s)
{
    struct stat statbuf;
    char *us = unmeta(s);

    return (access(us, X_OK) == 0 && stat(us, &statbuf) >= 0 &&
	    S_ISREG(statbuf.st_mode));
}

/**/
int
isrelative(char *s)
{
    if (*s != '/')
	return 1;
    for (; *s; s++)
	if (*s == '.' && s[-1] == '/' &&
	    (s[1] == '/' || s[1] == '\0' ||
	     (s[1] == '.' && (s[2] == '/' || s[2] == '\0'))))
	    return 1;
    return 0;
}

/**/
Cmdnam
hashcmd(char *arg0, char **pp)
{
    Cmdnam cn;
    char *s, buf[PATH_MAX];
    char **pq;

    for (; *pp; pp++)
	if (**pp == '/') {
	    s = buf;
	    strucpy(&s, *pp);
	    *s++ = '/';
	    if ((s - buf) + strlen(arg0) >= PATH_MAX)
		continue;
	    strcpy(s, arg0);
	    if (iscom(buf))
		break;
	}

    if (!*pp)
	return NULL;

    cn = (Cmdnam) zcalloc(sizeof *cn);
    cn->flags = 0;
    cn->u.name = pp;
    cmdnamtab->addnode(cmdnamtab, ztrdup(arg0), cn);

    if (isset(HASHDIRS)) {
	for (pq = pathchecked; pq <= pp; pq++)
	    hashdir(pq);
	pathchecked = pp + 1;
    }

    return cn;
}

/* execute a string */

/**/
void
execstring(char *s, int dont_change_job, int exiting)
{
    List list;

    pushheap();
    if ((list = parse_string(s)))
	execlist(list, dont_change_job, exiting);
    popheap();
}

/* Main routine for executing a list.                                *
 * exiting means that the (sub)shell we are in is a definite goner   *
 * after the current list is finished, so we may be able to exec the *
 * last command directly instead of forking.  If dont_change_job is  *
 * nonzero, then restore the current job number after executing the  *
 * list.                                                             */

/**/
void
execlist(List list, int dont_change_job, int exiting)
{
    Sublist slist;
    static int donetrap;
    int ret, cj;
    int old_pline_level, old_list_pipe;

    cj = thisjob;
    old_pline_level = pline_level;
    old_list_pipe = list_pipe;

    if (sourcelevel && unset(SHINSTDIN))
	pline_level = list_pipe = 0;

    /* Loop over all sets of comands separated by newline, *
     * semi-colon or ampersand (`sublists').               */
    while (list && list != &dummy_list && !breaks && !retflag) {
	/* Reset donetrap:  this ensures that a trap is only *
	 * called once for each sublist that fails.          */
	donetrap = 0;
	simplifyright(list);
	slist = list->left;

	/* Loop through code followed by &&, ||, or end of sublist. */
	while (slist) {
	    switch (slist->type) {
	    case END:
		/* End of sublist; just execute, ignoring status. */
		execpline(slist, list->type, !list->right && exiting);
		goto sublist_done;
		break;
	    case ANDNEXT:
		/* If the return code is non-zero, we skip pipelines until *
		 * we find a sublist followed by ORNEXT.                   */
		if ((ret = execpline(slist, Z_SYNC, 0))) {
		    while ((slist = slist->right))
			if (slist->type == ORNEXT)
			    break;
		    if (!slist) {
			/* We've skipped to the end of the list, not executing *
			 * the final pipeline, so don't perform error handling *
			 * for this sublist.                                   */
			donetrap = 1;
			goto sublist_done;
		    }
		}
		break;
	    case ORNEXT:
		/* If the return code is zero, we skip pipelines until *
		 * we find a sublist followed by ANDNEXT.              */
		if (!(ret = execpline(slist, Z_SYNC, 0))) {
		    while ((slist = slist->right))
			if (slist->type == ANDNEXT)
			    break;
		    if (!slist) {
			/* We've skipped to the end of the list, not executing *
			 * the final pipeline, so don't perform error handling *
			 * for this sublist.                                   */
			donetrap = 1;
			goto sublist_done;
		     }
		}
		break;
	    }
	    slist = slist->right;
	}
sublist_done:

	if (sigtrapped[SIGDEBUG])
	    dotrap(SIGDEBUG);

	/* Check whether we are suppressing traps/errexit *
	 * (typically in init scripts) and if we haven't  *
	 * already performed them for this sublist.       */
	if (!noerrexit && !donetrap) {
	    if (sigtrapped[SIGZERR] && lastval) {
		dotrap(SIGZERR);
		donetrap = 1;
	    }
	    if (lastval && isset(ERREXIT)) {
		if (sigtrapped[SIGEXIT])
		    dotrap(SIGEXIT);
		if (mypid != getpid())
		    _exit(lastval);
		else
		    exit(lastval);
	    }
	}

	list = list->right;
    }

    pline_level = old_pline_level;
    list_pipe = old_list_pipe;
    if (dont_change_job)
	thisjob = cj;
}

/* Execute a pipeline.                                                *
 * last1 is a flag that this command is the last command in a shell   *
 * that is about to exit, so we can exec instead of forking.  It gets *
 * passed all the way down to execcmd() which actually makes the      *
 * decision.  A 0 is always passed if the command is not the last in  *
 * the pipeline.  This function assumes that the sublist is not NULL. *
 * If last1 is zero but the command is at the end of a pipeline, we   *
 * pass 2 down to execcmd().                                          *
 */

/**/
int
execpline(Sublist l, int how, int last1)
{
    int ipipe[2], opipe[2];
    int pj, newjob;
    static int lastwj;

    if (!l->left)
	return lastval = (l->flags & PFLAG_NOT) != 0;

    pj = thisjob;
    ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
    child_block();

    /* get free entry in job table and initialize it */
    if ((thisjob = newjob = initjob()) == -1)
	return 1;
    if (how & Z_TIMED)
	jobtab[thisjob].stat |= STAT_TIMED;

    if (l->flags & PFLAG_COPROC) {
	how = Z_ASYNC;
	if (coprocin >= 0) {
	    zclose(coprocin);
	    zclose(coprocout);
	}
	mpipe(ipipe);
	mpipe(opipe);
	coprocin = ipipe[0];
	coprocout = opipe[1];
	fdtable[coprocin] = fdtable[coprocout] = 0;
    }
    if (!pline_level++) {
	list_pipe_job = newjob;
	nowait = 0;
    }
    list_pipe_pid = lastwj = 0;
    if (pline_level == 1)
	simple_pline = (l->left->type == END);
    execpline2(l->left, how, opipe[0], ipipe[1], last1);
    pline_level--;
    if (how & Z_ASYNC) {
	lastwj = newjob;
	if (l->flags & PFLAG_COPROC)
	    zclose(ipipe[1]);
	if (how & Z_DISOWN) {
	    deletejob(jobtab + thisjob);
	    thisjob = -1;
	}
	else
	    spawnjob();
	child_unblock();
	return 0;
    } else {
	if (newjob != lastwj) {
	    Job jn = jobtab + newjob;

	    if (newjob == list_pipe_job && list_pipe_child)
		_exit(0);

	    lastwj = thisjob = newjob;

	    if (list_pipe)
		jn->stat |= STAT_NOPRINT;

	    if (nowait) {
		if(!pline_level) {
		    struct process *pn, *qn;

		    curjob = newjob;
		    addproc(list_pipe_pid, list_pipe_text);

		    for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
			if (WIFSTOPPED(pn->status))
			    break;

		    if (pn) {
			for (qn = jn->procs; qn->next; qn = qn->next);
			qn->status = pn->status;
		    }

		    jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
		    jn->stat |= STAT_STOPPED | STAT_CHANGED;
		    printjob(jn, !!isset(LONGLISTJOBS), 1);
		}
		else
		    deletejob(jn);
	    }

	    for (; !nowait;) {
		if (list_pipe_child) {
		    jn->stat |= STAT_NOPRINT;
		    makerunning(jn);
		}
		if (!(jn->stat & STAT_LOCKED))
		    waitjobs();

		if (list_pipe_child &&
		    jn->stat & STAT_DONE &&
		    lastval2 & 0200)
		    killpg(mypgrp, lastval2 & ~0200);
		if ((list_pipe || last1) && !list_pipe_child &&
		    jn->stat & STAT_STOPPED) {
		    pid_t pid;
		    int synch[2];

		    pipe(synch);

		    if ((pid = fork()) == -1) {
			trashzle();
			close(synch[0]);
			close(synch[1]);
			putc('\n', stderr);
			fprintf(stderr, "zsh: job can't be suspended\n");
			fflush(stderr);
			makerunning(jn);
#ifndef __EMX__
			killjb(jn, SIGCONT);
#endif
			thisjob = newjob;
		    }
		    else if (pid) {
			char dummy;

			list_pipe_pid = pid;
			nowait = errflag = 1;
			breaks = loops;
			close(synch[1]);
			read(synch[0], &dummy, 1);
			close(synch[0]);
			jobtab[list_pipe_job].other = newjob;
			jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
			jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
			jn->other = pid;
#ifndef __EMX__
			killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
#endif
			break;
		    }
		    else {
			close(synch[0]);
			entersubsh(Z_ASYNC, 0, 0);
			setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
			close(synch[1]);
#ifndef __EMX__
			kill(getpid(), SIGSTOP);
#endif
			list_pipe = 0;
			list_pipe_child = 1;
			break;
		    }
		}
		else if (subsh && jn->stat & STAT_STOPPED)
		    thisjob = newjob;
		else
		    break;
	    }
	    child_unblock();

	    if (list_pipe && (lastval & 0200) && pj >= 0 &&
		(!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
		jn = jobtab + pj;
		jn->stat |= STAT_NOPRINT;
		killjb(jobtab + pj, lastval & ~0200);
	    }
	    if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
		deletejob(jn);
	    thisjob = pj;

	}
	if (l->flags & PFLAG_NOT)
	    lastval = !lastval;
    }
    if (!pline_level)
	simple_pline = 0;
    return lastval;
}

int subsh_close = -1;

/* execute pipeline.  This function assumes the `pline' is not NULL. */

/**/
void
execpline2(Pline pline, int how, int input, int output, int last1)
{
    pid_t pid;
    int pipes[2];
    int oldlineno;

    if (breaks || retflag)
	return;

    oldlineno = lineno;
    lineno = pline->left->lineno;

    if (pline_level == 1)
	strcpy(list_pipe_text, getjobtext((void *) pline->left));
    if (pline->type == END) {
	execcmd(pline->left, input, output, how, last1 ? 1 : 2);
	pline->left = NULL;
    } else {
	int old_list_pipe = list_pipe;

	mpipe(pipes);

	/* if we are doing "foo | bar" where foo is a current *
	 * shell command, do foo in a subshell and do the     *
	 * rest of the pipeline in the current shell.         */
	if (pline->left->type >= CURSH && (how & Z_SYNC)) {
	    int synch[2];

	    pipe(synch);
	    if ((pid = fork()) == -1) {
		close(synch[0]);
		close(synch[1]);
		zerr("fork failed: %e", NULL, errno);
	    } else if (pid) {
		char dummy, *text;

		text = getjobtext((void *) pline->left);
		addproc(pid, text);
		close(synch[1]);
		read(synch[0], &dummy, 1);
		close(synch[0]);
	    } else {
		zclose(pipes[0]);
		close(synch[0]);
		entersubsh(how, 2, 0);
		close(synch[1]);
		execcmd(pline->left, input, pipes[1], how, 0);
		_exit(lastval);
	    }
	} else {
	/* otherwise just do the pipeline normally. */
	    subsh_close = pipes[0];
	    execcmd(pline->left, input, pipes[1], how, 0);
	}
	pline->left = NULL;
	zclose(pipes[1]);
	if (pline->right) {
	    /* if another execpline() is invoked because the command is *
	     * a list it must know that we're already in a pipeline     */
	    list_pipe = 1;
	    execpline2(pline->right, how, pipes[0], output, last1);
	    list_pipe = old_list_pipe;
	    zclose(pipes[0]);
	    subsh_close = -1;
	}
    }

    lineno = oldlineno;
}

/* make the argv array */

/**/
char **
makecline(LinkList list)
{
    LinkNode node;
    char **argv, **ptr;

    /* A bigger argv is necessary for executing scripts */
    ptr  =
    argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *));
    if (isset(XTRACE)) {
	if (!doneps4)
	    fprintf(stderr, "%s", (prompt4) ? prompt4 : "");

	for (node = firstnode(list); node; incnode(node)) {
	    *ptr++ = (char *)getdata(node);
	    zputs(getdata(node), stderr);
	    if (nextnode(node))
		fputc(' ', stderr);
	}
	fputc('\n', stderr);
	fflush(stderr);
    } else {
	for (node = firstnode(list); node; incnode(node))
	    *ptr++ = (char *)getdata(node);
    }
    *ptr = NULL;
    return (argv);
}

/**/
void
untokenize(char *s)
{
    for (; *s; s++)
	if (itok(*s))
	    if (*s == Nularg)
		chuck(s--);
	    else
		*s = ztokens[*s - Pound];
}

/* Open a file for writing redicection */

/**/
int
clobber_open(struct redir *f)
{
    struct stat buf;
    int fd;

    if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
	return open(unmeta(f->name), O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (stat(unmeta(f->name), &buf) || S_ISREG(buf.st_mode))
	return open(unmeta(f->name), O_WRONLY | O_CREAT | O_EXCL, 0666);
    if ((fd = open(unmeta(f->name), O_WRONLY | O_APPEND)) < 0)
	return fd;
    if (!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
	return fd;
    /* someone replaced the file between stat and open */
    close(fd);
    errno = EEXIST;
    return -1;
}

/* size of buffer for tee and cat processes */
#define TCBUFSIZE 4092

/* close an multio (success) */

/**/
void
closemn(struct multio **mfds, int fd)
{
    struct multio *mn = mfds[fd];
    char buf[TCBUFSIZE];
    int len, i;

    if (fd < 0 || !mfds[fd] || mfds[fd]->ct < 2)
	return;
    if (zfork()) {
	for (i = 0; i < mn->ct; i++)
	    zclose(mn->fds[i]);
	zclose(mn->pipe);
	mn->ct = 1;
	mn->fds[0] = fd;
	return;
    }
    /* pid == 0 */
    closeallelse(mn);
    if (mn->rflag) {
	/* tee process */
	while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0)
	    for (i = 0; i < mn->ct; i++)
		write(mn->fds[i], buf, len);
    } else {
	/* cat process */
	for (i = 0; i < mn->ct; i++)
	    while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0)
		write(mn->pipe, buf, len);
    }
    _exit(0);
}

/* close all the mnodes (failure) */

/**/
void
closemnodes(struct multio **mfds)
{
    int i, j;

    for (i = 0; i < 10; i++)
	if (mfds[i]) {
	    for (j = 0; j < mfds[i]->ct; j++)
		zclose(mfds[i]->fds[j]);
	    mfds[i] = NULL;
	}
}

/**/
void
closeallelse(struct multio *mn)
{
    int i, j;

    for (i = 0; i < OPEN_MAX; i++)
	if (mn->pipe != i) {
	    for (j = 0; j < mn->ct; j++)
		if (mn->fds[j] == i)
		    break;
	    if (j == mn->ct)
		zclose(i);
	}
}

/* A multio is a list of fds associated with a certain fd.       *
 * Thus if you do "foo >bar >ble", the multio for fd 1 will have *
 * two fds, the result of open("bar",...), and the result of     *
 * open("ble",....).                                             */

/* Add a fd to an multio.  fd1 must be < 10, and may be in any state. *
 * fd2 must be open, and is `consumed' by this function.  Note that   *
 * fd1 == fd2 is possible, and indicates that fd1 was really closed.  *
 * We effectively do `fd2 = movefd(fd2)' at the beginning of this     *
 * function, but in most cases we can avoid an extra dup by delaying  *
 * the movefd: we only >need< to move it if we're actually doing a    *
 * multiple redirection.                                              */

/**/
void
addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
{
    int pipes[2];

    if (!mfds[fd1] || unset(MULTIOS)) {
	if(!mfds[fd1]) {		/* starting a new multio */
	    mfds[fd1] = (struct multio *) alloc(sizeof(struct multio));
	    if (!forked && save[fd1] == -2)
		save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
	}
	redup(fd2, fd1);
	mfds[fd1]->ct = 1;
	mfds[fd1]->fds[0] = fd1;
	mfds[fd1]->rflag = rflag;
    } else {
	if (mfds[fd1]->rflag != rflag) {
	    zerr("file mode mismatch on fd %d", NULL, fd1);
	    return;
	}
	if (mfds[fd1]->ct == 1) {	/* split the stream */
	    mfds[fd1]->fds[0] = movefd(fd1);
	    mfds[fd1]->fds[1] = movefd(fd2);
	    mpipe(pipes);
	    mfds[fd1]->pipe = pipes[1 - rflag];
	    redup(pipes[rflag], fd1);
	    mfds[fd1]->ct = 2;
	} else {		/* add another fd to an already split stream */
	    if(!(mfds[fd1]->ct % MULTIOUNIT)) {
		int new = sizeof(struct multio) + sizeof(int) * mfds[fd1]->ct;
		int old = new - sizeof(int) * MULTIOUNIT;
		mfds[fd1] = hrealloc((char *)mfds[fd1], old, new);
	    }
	    mfds[fd1]->fds[mfds[fd1]->ct++] = movefd(fd2);
	}
    }
    if (subsh_close >= 0 && !fdtable[subsh_close])
	subsh_close = -1;
}

/**/
void
addvars(LinkList l, int export)
{
    Varasg v;
    LinkList vl;
    int xtr;
    char **arr, **ptr;

    xtr = isset(XTRACE);
    if (xtr && nonempty(l)) {
	fprintf(stderr, "%s", prompt4 ? prompt4 : "");
	doneps4 = 1;
    }

    while (nonempty(l)) {
	v = (Varasg) ugetnode(l);
	singsub(&v->name);
	if (errflag)
	    return;
	untokenize(v->name);
	if (xtr)
	    fprintf(stderr, "%s=", v->name);
	if (v->type == PM_SCALAR) {
	    vl = newlinklist();
	    addlinknode(vl, v->str);
	} else
	    vl = v->arr;
	prefork(vl, v->type == PM_SCALAR ? 7 : 3);
	if (errflag)
	    return;
	if (isset(GLOBASSIGN) || v->type != PM_SCALAR)
	    globlist(vl);
	if (errflag)
	    return;
	if (v->type == PM_SCALAR && (empty(vl) || !nextnode(firstnode(vl)))) {
	    Param pm;
	    char *val;
	    int allexp;

	    if (empty(vl))
		val = ztrdup("");
	    else {
		untokenize(peekfirst(vl));
		val = ztrdup(ugetnode(vl));
	    }
	    if (xtr)
		fprintf(stderr, "%s ", val);
	    if (export) {
		if (export < 0)
		    /* We are going to fork so do not bother freeing this */
		    paramtab->removenode(paramtab, v->name);
		allexp = opts[ALLEXPORT];
		opts[ALLEXPORT] = 1;
		pm = setsparam(v->name, ztrdup(val));
		opts[ALLEXPORT] = allexp;
	    } else
		pm = setsparam(v->name, ztrdup(val));
	    if (errflag)
		return;
	    zsfree(val);
	    continue;
	}
	ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1));

	while (nonempty(vl))
	    *ptr++ = ztrdup((char *) ugetnode(vl));

	*ptr = NULL;
	if (xtr) {
	    fprintf(stderr, "( ");
	    for (ptr = arr; *ptr; ptr++)
		fprintf(stderr, "%s ", *ptr);
	    fprintf(stderr, ") ");
	}
	setaparam(v->name, arr);
	if (errflag)
	    return;
    }
}

/**/
void
execcmd(Cmd cmd, int input, int output, int how, int last1)
{
    HashNode hn = NULL;
    LinkNode node;
    Redir fn;
    struct multio *mfds[10];
    char *text;
    int save[10];
    int fil, dfil, is_cursh, type, i;
    int nullexec = 0, assign = 0, forked = 0;
    int is_shfunc = 0, is_builtin = 0, is_exec = 0;
    /* Various flags to the command. */
    int cflags = 0, checked = 0;

    doneps4 = 0;
    args = cmd->args;
    type = cmd->type;

    for (i = 0; i < 10; i++) {
	save[i] = -2;
	mfds[i] = NULL;
    }

    /* If the command begins with `%', then assume it is a *
     * reference to a job in the job table.                */
    if (type == SIMPLE && nonempty(args) &&
	*(char *)peekfirst(args) == '%') {
	pushnode(args, dupstring((how & Z_DISOWN)
				 ? "disown" : (how & Z_ASYNC) ? "bg" : "fg"));
	how = Z_SYNC;
    }

    /* If AUTORESUME is set, the command is SIMPLE, and doesn't have *
     * any redirections, then check if it matches as a prefix of a   *
     * job currently in the job table.  If it does, then we treat it *
     * as a command to resume this job.                              */
    if (isset(AUTORESUME) && type == SIMPLE && (how & Z_SYNC) &&
	nonempty(args) && empty(cmd->redir) && !input &&
	!nextnode(firstnode(args))) {
	if (unset(NOTIFY))
	    scanjobs();
	if (findjobnam(peekfirst(args)) != -1)
	    pushnode(args, dupstring("fg"));
    }

    /* Check if it's a builtin needing automatic MAGIC_EQUALS_SUBST      *
     * handling.  Things like typeset need this.  We can't detect the    *
     * command if it contains some tokens (e.g. x=ex; ${x}port), so this *
     * only works in simple cases.  has_token() is called to make sure   *
     * this really is a simple case.                                     */
    if (type == SIMPLE) {
	while (nonempty(args)) {
	    char *cmdarg = (char *) peekfirst(args);
	    checked = !has_token(cmdarg);
	    if (!checked)
		break;
	    if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
		(hn = shfunctab->getnode(shfunctab, cmdarg))) {
		is_shfunc = 1;
		break;
	    }
	    if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
		checked = !(cflags & BINF_BUILTIN);
		break;
	    }
	    if (!(hn->flags & BINF_PREFIX)) {
		is_builtin = 1;
		assign = (hn->flags & BINF_MAGICEQUALS);
		break;
	    }
	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
	    cflags |= hn->flags;
	    uremnode(args, firstnode(args));
	    hn = NULL;
	    checked = 0;
	    if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
		break;
	}
    }

    /* Do prefork substitutions */
    prefork(args, (((type == CCASE) ? 4 : 0)
		   | (assign ? 2 : isset(MAGICEQUALSUBST))));

    if (type == SIMPLE) {
	int unglobbed = 0;

	for (;;) {
	    char *cmdarg;

	    if (!(cflags & BINF_NOGLOB))
		while (!checked && !errflag && nonempty(args) &&
		       has_token((char *) peekfirst(args)))
		    glob(args, firstnode(args));
	    else if (!unglobbed) {
		for (node = firstnode(args); node; incnode(node))
		    untokenize((char *) getdata(node));
		unglobbed = 1;
	    }

	    /* Current shell should not fork unless the *
	     * exec occurs at the end of a pipeline.    */
	    if ((cflags & BINF_EXEC) && last1 == 2)
		cmd->flags |= CFLAG_EXEC;

	    /* Empty command */
	    if (empty(args)) {
		if (nonempty(cmd->redir)) {
		    if (cmd->flags & CFLAG_EXEC) {
			/* Was this "exec < foobar"? */
			nullexec = 1;
			break;
		    } else if (!nullcmd || !*nullcmd ||
			       (cflags & BINF_PREFIX)) {
			zerr("redirection with no command", NULL, 0);
			errflag = lastval = 1;
			return;
		    } else if (readnullcmd && *readnullcmd &&
			       ((Redir) peekfirst(cmd->redir))->type == READ &&
			       !nextnode(firstnode(cmd->redir))) {
			addlinknode(args, dupstring(readnullcmd));
		    } else
			addlinknode(args, dupstring(nullcmd));
		} else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
		    lastval = 0;
		    return;
		} else {
		    cmdoutval = 0;
		    addvars(cmd->vars, 0);
		    if (errflag)
			lastval = errflag;
		    else
			lastval = cmdoutval;
		    if (isset(XTRACE)) {
			fputc('\n', stderr);
			fflush(stderr);
		    }
		    return;
		}
	    }

	    if (errflag || checked ||
		(unset(POSIXBUILTINS) && (cflags & BINF_COMMAND)))
		break;

	    cmdarg = (char *) peekfirst(args);
	    if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
		(hn = shfunctab->getnode(shfunctab, cmdarg))) {
		is_shfunc = 1;
		break;
	    }
	    if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
		if (cflags & BINF_BUILTIN) {
		    zerr("no such builtin: %s", cmdarg, 0);
		    errflag = lastval = 1;
		    return;
		}
		break;
	    }
	    if (!(hn->flags & BINF_PREFIX)) {
		is_builtin = 1;
		break;
	    }
	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
	    cflags |= hn->flags;
	    uremnode(args, firstnode(args));
	    hn = NULL;
	}
    }

    if (errflag) {
	lastval = 1;
	return;
    }

    /* Get the text associated with this command. */
    if (jobbing || (how & Z_TIMED))
	text = getjobtext((void *) cmd);
    else
	text = NULL;

    /* Set up special parameter $_ */
    zsfree(underscore);
    if (nonempty(args)
	&& (underscore = ztrdup((char *) getdata(lastnode(args)))))
	untokenize(underscore); 
    else
  	underscore = ztrdup("");

    /* Warn about "rm *" */
    if (type == SIMPLE && interact && unset(RMSTARSILENT)
	&& isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args))
	&& !strcmp(peekfirst(args), "rm")) {
	LinkNode node, next;

	for (node = nextnode(firstnode(args)); node && !errflag; node = next) {
	    char *s = (char *) getdata(node);
	    int l = strlen(s);

	    next = nextnode(node);
	    if (s[0] == Star && !s[1]) {
		if (!checkrmall(pwd))
		    uremnode(args, node);
	    } else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) {
		char t = s[l - 2];

		s[l - 2] = 0;
		if (!checkrmall(s))
		    uremnode(args, node);
		s[l - 2] = t;
	    }
	}
	if (!nextnode(firstnode(args)))
	    errflag = 1;
    }

    if (errflag) {
	lastval = 1;
	return;
    }

    if (type == SIMPLE && !nullexec) {
	char *s;

	DPUTS(empty(args), "BUG: empty(args) in exec.c");
	if (!hn) {
	    /* Resolve external commands */
	    char *cmdarg = (char *) peekfirst(args);

	    hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
	    if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) {
		for (s = cmdarg; *s && *s != '/'; s++);
		if (!*s)
		    hn = (HashNode) hashcmd(cmdarg, pathchecked);
	    }
	}

	/* If no command found yet, see if it  *
	 * is a directory we should AUTOCD to. */
	if (!hn && isset(AUTOCD) && isset(SHINSTDIN) && empty(cmd->redir)
	    && !nextnode(firstnode(args)) && *(char *)peekfirst(args)
	    && (s = cancd(peekfirst(args)))) {
	    peekfirst(args) = (void *) s;
	    pushnode(args, dupstring("cd"));
	    if ((hn = builtintab->getnode(builtintab, "cd")))
		is_builtin = 1;
	}
    }

    /* This is nonzero if the command is a current shell procedure? */
    is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec);

    /**************************************************************************
     * Do we need to fork?  We need to fork if:                               *
     * 1) The command is supposed to run in the background. (or)              *
     * 2) There is no `exec' flag, and either:                                *
     *    a) This is a builtin or shell function with output piped somewhere. *
     *    b) This is an external command and we can't do a `fake exec'.       *
     *                                                                        *
     * A `fake exec' is possible if we have all the following conditions:     *
     * 1) last1 flag is 1.  This indicates that the current shell will not    *
     *    be needed after the current command.  This is typically the case    *
     *    when when the command is the last stage in a subshell, or is the    *
     *    last command after the option `-c'.                                 *
     * 2) We are not trapping EXIT or ZERR.                                   *
     * 3) We don't have any files to delete.                                  *
     *                                                                        *
     * The condition above for a `fake exec' will also work for a current     *
     * shell command such as a builtin, but doesn't really buy us anything    *
     * (doesn't save us a process), since it is already running in the        *
     * current shell.                                                         *
     **************************************************************************/

    if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) &&
       (((is_builtin || is_shfunc) && output) ||
       (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
        sigtrapped[SIGEXIT] || havefiles()))))) {

	pid_t pid;
	int synch[2];
	char dummy;

	child_block();
	pipe(synch);

	if ((pid = zfork()) == -1) {
	    close(synch[0]);
	    close(synch[1]);
	    return;
	} if (pid) {
	    close(synch[1]);
	    read(synch[0], &dummy, 1);
	    close(synch[0]);
#ifdef HAVE_DEV_FD
	    closem(2);
#endif
	    if (how & Z_ASYNC) {
		lastpid = (long) pid;
	    } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) {
		/* search for STTY=... */
		while (nonempty(cmd->vars))
		    if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) {
			jobtab[thisjob].stty_in_env = 1;
			break;
		    }
	    }
	    addproc(pid, text);
	    return;
	}
	/* pid == 0 */
	close(synch[0]);
	entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0);
	close(synch[1]);
	forked = 1;
	if (sigtrapped[SIGINT] & ZSIG_IGNORED)
	    holdintr();
	/* Check if we should run background jobs at a lower priority. */
	if ((how & Z_ASYNC) && isset(BGNICE))
	    nice(5);

    } else if (is_cursh) {
	/* This is a current shell procedure that didn't need to fork.    *
	 * This includes current shell procedures that are being exec'ed, *
	 * as well as null execs.                                         */
	jobtab[thisjob].stat |= STAT_CURSH;
    } else {
	/* This is an exec (real or fake) for an external command.    *
	 * Note that any form of exec means that the subshell is fake *
	 * (but we may be in a subshell already).                     */
	is_exec = 1;
    }

    if (!(cflags & BINF_NOGLOB))
	globlist(args);
    if (errflag) {
	lastval = 1;
	goto err;
    }

    /* Add pipeline input/output to mnodes */
    if (input)
	addfd(forked, save, mfds, 0, input, 0);
    if (output)
	addfd(forked, save, mfds, 1, output, 1);

    /* Do process substitutions */
    spawnpipes(cmd->redir);

    /* Do io redirections */
    while (nonempty(cmd->redir)) {
	fn = (Redir) ugetnode(cmd->redir);
	DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH,
	      "BUG: unexpanded here document\n");
	if (fn->type == INPIPE) {
	    if (fn->fd2 == -1) {
		closemnodes(mfds);
		fixfds(save);
		execerr();
	    }
	    addfd(forked, save, mfds, fn->fd1, fn->fd2, 0);
	} else if (fn->type == OUTPIPE) {
	    if (fn->fd2 == -1) {
		closemnodes(mfds);
		fixfds(save);
		execerr();
	    }
	    addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
	} else {
	    if (fn->type != HERESTR && xpandredir(fn, cmd->redir))
		continue;
	    if (errflag) {
		closemnodes(mfds);
		fixfds(save);
		execerr();
	    }
	    switch(fn->type) {
	    case HERESTR:
		fil = getherestr(fn);
		if (fil == -1) {
		    closemnodes(mfds);
		    fixfds(save);
		    if (errno != EINTR)
			zerr("%e", NULL, errno);
		    execerr();
		}
		addfd(forked, save, mfds, fn->fd1, fil, 0);
		break;
	    case READ:
	    case READWRITE:
		if (fn->type == READ)
		    fil = open(unmeta(fn->name), O_RDONLY);
		else
		    fil = open(unmeta(fn->name), O_RDWR | O_CREAT, 0666);
		if (fil == -1) {
		    closemnodes(mfds);
		    fixfds(save);
		    if (errno != EINTR)
			zerr("%e: %s", fn->name, errno);
		    execerr();
		}
		addfd(forked, save, mfds, fn->fd1, fil, 0);
		/* If this is 'exec < file', read from stdin, *
		 * not terminal, unless `file' is a terminal. */
		if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact)
		    init_io();
		break;
	    case CLOSE:
		if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2)
		    save[fn->fd1] = movefd(fn->fd1);
		closemn(mfds, fn->fd1);
		zclose(fn->fd1);
		break;
	    case MERGEIN:
	    case MERGEOUT:
		if(fn->fd2 < 10)
		    closemn(mfds, fn->fd2);
		fil = dup(fn->fd2);
		if (fil == -1) {
		    char fdstr[4];

		    closemnodes(mfds);
		    fixfds(save);
		    sprintf(fdstr, "%d", fn->fd2);
		    zerr("%s: %e", fdstr, errno);
		    execerr();
		}
		addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT);
		break;
	    default:
		if (IS_APPEND_REDIR(fn->type))
		    fil = open(unmeta(fn->name),
			       (unset(CLOBBER) && !IS_CLOBBER_REDIR(fn->type)) ?
			       O_WRONLY | O_APPEND : O_WRONLY | O_APPEND | O_CREAT, 0666);
		else
		    fil = clobber_open(fn);
		if(fil != -1 && IS_ERROR_REDIR(fn->type))
		    dfil = dup(fil);
		else
		    dfil = 0;
		if (fil == -1 || dfil == -1) {
		    if(fil != -1)
			close(fil);
		    closemnodes(mfds);
		    fixfds(save);
		    if (errno != EINTR)
			zerr("%e: %s", fn->name, errno);
		    execerr();
		}
		addfd(forked, save, mfds, fn->fd1, fil, 1);
		if(IS_ERROR_REDIR(fn->type))
		    addfd(forked, save, mfds, 2, dfil, 1);
		break;
	    }
	}
    }

    /* We are done with redirection.  close the mnodes, *
     * spawning tee/cat processes as necessary.         */
    for (i = 0; i < 10; i++)
	closemn(mfds, i);

    if (nullexec) {
	for (i = 0; i < 10; i++)
	    if (save[i] != -2)
		zclose(save[i]);
	/*
	 * Here we specifically *don't* restore the original fd's
	 * before returning.
	 */
	return;
    }

    if (isset(EXECOPT) && !errflag) {
	/*
	 * We delay the entersubsh() to here when we are exec'ing
	 * the current shell (including a fake exec to run a builtin then
	 * exit) in case there is an error return.
	 */
	if (is_exec)
	    entersubsh(how, type != SUBSH ? 2 : 1, 1);
	if (type >= CURSH) {
	    static int (*func[]) _((Cmd)) = {
		execcursh, exectime, execfuncdef, execfor, execwhile,
		execrepeat, execif, execcase, execselect, execcond,
		execarith
	    };

	    if (last1 == 1)
		cmd->flags |= CFLAG_EXEC;
	    lastval = (func[type - CURSH]) (cmd);
	} else if (is_builtin || is_shfunc) {
	    LinkList restorelist = 0, removelist = 0;
	    /* builtin or shell function */

	    if (!forked && ((cflags & BINF_COMMAND) ||
			    (unset(POSIXBUILTINS) && !assign) ||
			    (isset(POSIXBUILTINS) && !is_shfunc &&
			     !(hn->flags & BINF_PSPECIAL))))
		save_params(cmd, &restorelist, &removelist);

	    if (cmd->vars) {
		/* Export this if the command is a shell function,
		 * but not if it's a builtin.
		 */
		addvars(cmd->vars, is_shfunc);
		if (errflag) {
		    restore_params(restorelist, removelist);
		    lastval = 1;
		    fixfds(save);
		    return;
		}
	    }

	    if (is_shfunc) {
		/* It's a shell function */
#ifdef HAVE_DEV_FD
		int i;

		for (i = 10; i <= max_zsh_fd; i++)
		    if (fdtable[i] > 1)
			fdtable[i]++;
#endif
		if (subsh_close >= 0)
		    zclose(subsh_close);
		subsh_close = -1;
		execshfunc(cmd, (Shfunc) hn);
#ifdef HAVE_DEV_FD
		for (i = 10; i <= max_zsh_fd; i++)
		    if (fdtable[i] > 1)
			if (--(fdtable[i]) <= 2)
			    zclose(i);
#endif
	    } else {
		/* It's a builtin */
		if (forked)
		    closem(1);
		lastval = execbuiltin(args, (Builtin) hn);
#ifdef HAVE_DEV_FD
		closem(2);
#endif
		if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && lastval && !subsh) {
		    fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
		}
		fflush(stdout);
		if (save[1] == -2) {
		    if (ferror(stdout)) {
			zerr("write error: %e", NULL, errno);
			clearerr(stdout);
			errflag = 0;
		    }
		} else
		    clearerr(stdout);
	    }

	    if (cmd->flags & CFLAG_EXEC) {
		if (subsh)
		    _exit(lastval);

		/* If we are exec'ing a command, and we are not in a subshell, *
		 * then check if we should save the history file.              */
		if (isset(RCS) && interact && !nohistsave)
		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
		exit(lastval);
	    }

	    restore_params(restorelist, removelist);

	} else {
	    if (cmd->flags & CFLAG_EXEC) {
		setiparam("SHLVL", --shlvl);
		/* If we are exec'ing a command, and we are not *
		 * in a subshell, then save the history file.   */
		if (!subsh && isset(RCS) && interact && !nohistsave)
		    savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
	    }
	    if (type == SIMPLE) {
		if (cmd->vars) {
		    addvars(cmd->vars, -1);
		    if (errflag)
			_exit(1);
		}
		closem(1);
		if (coprocin)
		    zclose(coprocin);
		if (coprocout)
		    zclose(coprocout);
#ifdef HAVE_GETRLIMIT
		if (!forked)
		    setlimits(NULL);
#endif
		execute((Cmdnam) hn, cflags & BINF_DASH);
	    } else {		/* ( ... ) */
		DPUTS(cmd->vars && nonempty(cmd->vars),
		      "BUG: assigment before complex command.");
		list_pipe = 0;
		if (subsh_close >= 0)
		    zclose(subsh_close);
                subsh_close = -1;
		/* If we're forked (and we should be), no need to return */
		DPUTS(last1 != 1 && !forked, "BUG? not exiting?");
		execlist(cmd->u.list, 0, 1);
	    }
	}
    }

  err:
    if (forked)
	_exit(lastval);
    fixfds(save);
}

/* Arrange to have variables restored. */

/**/
void
save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
{
    Param pm;
    LinkNode node;
    char *s;

    MUSTUSEHEAP("save_params()");

    *restore_p = newlinklist();
    *remove_p = newlinklist();

    for (node = firstnode(cmd->vars); node; incnode(node)) {
	s = ((Varasg) getdata(node))->name;
	if ((pm = (Param) paramtab->getnode(paramtab, s))) {
	    if (!(pm->flags & PM_SPECIAL)) {
		paramtab->removenode(paramtab, s);
	    } else if (!(pm->flags & PM_READONLY)) {
		Param tpm = (Param) alloc(sizeof *tpm);

		tpm->nam = s;
		tpm->flags = pm->flags;
		switch (PM_TYPE(pm->flags)) {
		case PM_SCALAR:
		    tpm->u.str = ztrdup(pm->gets.cfn(pm));
		    break;
		case PM_INTEGER:
		    tpm->u.val = pm->gets.ifn(pm);
		    break;
		case PM_ARRAY:
		    PERMALLOC {
			tpm->u.arr = arrdup(pm->gets.afn(pm));
		    } LASTALLOC;
		    break;
		}
		pm = tpm;
	    }
	    addlinknode(*remove_p, s);
	    addlinknode(*restore_p, pm);
	} else {
	    addlinknode(*remove_p, s);
	}
    }
}

/* Restore saved parameters after executing a shfunc or builtin */

/**/
void
restore_params(LinkList restorelist, LinkList removelist)
{
    Param pm;
    char *s;

    if (removelist) {
	/* remove temporary parameters */
	while ((s = (char *) ugetnode(removelist)))
	    unsetparam(s);
    }

    if (restorelist) {
	/* restore saved parameters */
	while ((pm = (Param) ugetnode(restorelist))) {
	    if (pm->flags & PM_SPECIAL) {
		Param tpm = (Param) paramtab->getnode(paramtab, pm->nam);

		DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
		      !(pm->flags & PM_SPECIAL),
		      "BUG in restoring special parameters.");
		tpm->flags = pm->flags;
		switch (PM_TYPE(pm->flags)) {
		case PM_SCALAR:
		    tpm->sets.cfn(tpm, pm->u.str);
		    break;
		case PM_INTEGER:
		    tpm->sets.ifn(tpm, pm->u.val);
		    break;
		case PM_ARRAY:
		    tpm->sets.afn(tpm, pm->u.arr);
		    break;
		}
	    } else
		paramtab->addnode(paramtab, pm->nam, pm);
	    if (pm->flags & PM_EXPORTED)
		pm->env = addenv(pm->nam, getsparam(pm->nam));
	}
    }
}

/* restore fds after redirecting a builtin */

/**/
void
fixfds(int *save)
{
    int old_errno = errno;
    int i;

    for (i = 0; i != 10; i++)
	if (save[i] != -2)
	    redup(save[i], i);
    errno = old_errno;
}

/**/
void
entersubsh(int how, int cl, int fake)
{
    int sig;

    if (cl != 2)
	for (sig = 0; sig < VSIGCOUNT; sig++)
	    if (!(sigtrapped[sig] & ZSIG_FUNC))
		unsettrap(sig);
    if (unset(MONITOR)) {
	if (how & Z_ASYNC) {
	    settrap(SIGINT, NULL);
	    settrap(SIGQUIT, NULL);
	    if (isatty(0)) {
		close(0);
		if (open("/dev/null", O_RDWR)) {
		    zerr("can't open /dev/null: %e", NULL, errno);
		    _exit(1);
		}
	    }
	}
    } else if (thisjob != -1 && cl) {
	if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
	    if (kill(jobtab[list_pipe_job].gleader, 0) == -1 ||
		setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) {
		jobtab[list_pipe_job].gleader =
		    jobtab[thisjob].gleader = mypgrp;
		setpgrp(0L, mypgrp);

		if (how & Z_SYNC)
		    attachtty(jobtab[thisjob].gleader);
	    }
	}
	else if (!jobtab[thisjob].gleader ||
		 (setpgrp(0L, jobtab[thisjob].gleader) == -1)) {
	    jobtab[thisjob].gleader = getpid();
	    if (list_pipe_job != thisjob &&
		!jobtab[list_pipe_job].gleader)
		jobtab[list_pipe_job].gleader = jobtab[thisjob].gleader;
	    setpgrp(0L, jobtab[thisjob].gleader);
	    if (how & Z_SYNC)
		attachtty(jobtab[thisjob].gleader);
	}
    }
    if (!fake)
	subsh = 1;
    if (SHTTY != -1) {
	zclose(SHTTY);
	SHTTY = -1;
    }
    if (isset(MONITOR)) {
#ifndef __EMX__
	signal_default(SIGTTOU);
	signal_default(SIGTTIN);
	signal_default(SIGTSTP);
#endif
	signal_default(SIGPIPE);
    }
    if (interact) {
	signal_default(SIGTERM);
	if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
	    signal_default(SIGINT);
    }
    if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
	signal_default(SIGQUIT);
    opts[MONITOR] = opts[USEZLE] = 0;
    zleactive = 0;
    if (cl)
	clearjobtab();
    times(&shtms);
}

/* close internal shell fds */

/**/
void
closem(int how)
{
    int i;

    for (i = 10; i <= max_zsh_fd; i++)
	if (fdtable[i] && (!how || fdtable[i] == how))
	    zclose(i);
}

/* convert here document into a here string */

/**/
char *
gethere(char *str, int typ)
{
    char *buf;
    int bsiz, qt = 0, strip = 0;
    char *s, *t, *bptr, c;

    for (s = str; *s; s++)
	if (INULL(*s)) {
	    *s = Nularg;
	    qt = 1;
	}
    untokenize(str);
    if (typ == HEREDOCDASH) {
	strip = 1;
	while (*str == '\t')
	    str++;
    }
    bptr = buf = zalloc(bsiz = 256);
    for (;;) {
	t = bptr;

	while ((c = hgetc()) == '\t' && strip)
	    ;
	for (;;) {
	    if (bptr == buf + bsiz) {
		buf = realloc(buf, 2 * bsiz);
		t = buf + bsiz - (bptr - t);
		bptr = buf + bsiz;
		bsiz *= 2;
	    }
	    if (lexstop || c == '\n')
		break;
	    *bptr++ = c;
	    c = hgetc();
	}
	*bptr = '\0';
	if (!strcmp(t, str))
	    break;
	if (lexstop) {
	    t = bptr;
	    break;
	}
	*bptr++ = '\n';
    }
    if (t > buf && t[-1] == '\n')
	t--;
    *t = '\0';
    if (!qt)
	parsestr(buf);
    s = dupstring(buf);
    zfree(buf, bsiz);
    return s;
}

/* open here string fd */

/**/
int
getherestr(struct redir *fn)
{
    char *s, *t;
    int fd, len;

    t = fn->name;
    singsub(&t);
    untokenize(t);
    unmetafy(t, &len);
    t[len++] = '\n';
    s = gettempname();
    if (!s || (fd = open(s, O_CREAT | O_WRONLY | O_EXCL, 0600)) == -1)
	return -1;
    write(fd, t, len);
    close(fd);
    fd = open(s, O_RDONLY);
    unlink(s);
    return fd;
}

/* $(...) */

/**/
LinkList
getoutput(char *cmd, int qt)
{
    List list;
    int pipes[2];
    pid_t pid;
    Cmd c;
    Redir r;

    if (!(list = parse_string(cmd)))
	return NULL;
    if (list != &dummy_list && !list->right && !list->left->flags &&
	list->left->type == END && list->left->left->type == END &&
	(c = list->left->left->left)->type == SIMPLE && empty(c->args) &&
	empty(c->vars) && nonempty(c->redir) &&
	!nextnode(firstnode(c->redir)) &&
	(r = (Redir) getdata(firstnode(c->redir)))->fd1 == 0 &&
	r->type == READ) {
	/* $(< word) */
	int stream;
	char *s = r->name;

	singsub(&s);
	if (errflag)
	    return NULL;
	untokenize(s);
	if ((stream = open(unmeta(s), O_RDONLY)) == -1) {
	    zerr("%e: %s", s, errno);
	    return NULL;
	}
	return readoutput(stream, qt);
    }

    mpipe(pipes);
    child_block();
    cmdoutval = 0;
    if ((cmdoutpid = pid = zfork()) == -1) {
	/* fork error */
	zclose(pipes[0]);
	zclose(pipes[1]);
	errflag = 1;
	cmdoutpid = 0;
	child_unblock();
	return NULL;
    } else if (pid) {
	LinkList retval;

	zclose(pipes[1]);
	retval = readoutput(pipes[0], qt);
	fdtable[pipes[0]] = 0;
	child_suspend(0);		/* unblocks */
	lastval = cmdoutval;
	return retval;
    }

    /* pid == 0 */
    child_unblock();
    zclose(pipes[0]);
    redup(pipes[1], 1);
    entersubsh(Z_SYNC, 1, 0);
#ifndef __EMX__
    signal_ignore(SIGTSTP);
#endif
    execlist(list, 0, 1);
    close(1);
    _exit(lastval);
    zerr("exit returned in child!!", NULL, 0);
    kill(getpid(), SIGKILL);
    return NULL;
}

/* read output of command substitution */

/**/
LinkList
readoutput(int in, int qt)
{
    LinkList ret;
    char *buf, *ptr;
    int bsiz, c, cnt = 0;
    FILE *fin;

    fin = fdopen(in, "r");
    ret = newlinklist();
    ptr = buf = (char *) ncalloc(bsiz = 64);
    while ((c = fgetc(fin)) != EOF) {
	if (imeta(c)) {
	    *ptr++ = Meta;
	    c ^= 32;
	    cnt++;
	}
	if (++cnt >= bsiz) {
	    char *pp = (char *) ncalloc(bsiz *= 2);

	    memcpy(pp, buf, cnt - 1);
	    ptr = (buf = pp) + cnt - 1;
	}
	*ptr++ = c;
    }
    fclose(fin);
    while (cnt && ptr[-1] == '\n')
	ptr--, cnt--;
    *ptr = '\0';
    if (qt) {
	if (!cnt) {
	    *ptr++ = Nularg;
	    *ptr = '\0';
	}
	addlinknode(ret, buf);
    } else {
	char **words = spacesplit(buf, 0);

	while (*words) {
	    if (isset(GLOBSUBST))
		tokenize(*words);
	    addlinknode(ret, *words++);
	}
    }
    return ret;
}

/**/
List
parsecmd(char *cmd)
{
    char *str;
    List list;

    for (str = cmd + 2; *str && *str != Outpar; str++);
    if (!*str || cmd[1] != Inpar) {
	zerr("oops.", NULL, 0);
	return NULL;
    }
    *str = '\0';
    if (str[1] || !(list = parse_string(cmd + 2))) {
	zerr("parse error in process substitution", NULL, 0);
	return NULL;
    }
    return list;
}

/* =(...) */

/**/
char *
getoutputfile(char *cmd)
{
    pid_t pid;
    char *nam;
    List list;
    int fd;

    if (thisjob == -1)
	return NULL;
    if (!(list = parsecmd(cmd)))
	return NULL;
    if (!(nam = gettempname()))
	return NULL;

    nam = ztrdup(nam);
    PERMALLOC {
	if (!jobtab[thisjob].filelist)
	    jobtab[thisjob].filelist = newlinklist();
	addlinknode(jobtab[thisjob].filelist, nam);
    } LASTALLOC;
    child_block();
    fd = open(nam, O_WRONLY | O_CREAT | O_EXCL, 0600); /* create the file */

    if (fd < 0 || (cmdoutpid = pid = zfork()) == -1) {
	/* fork or open error */
	child_unblock();
	return nam;
    } else if (pid) {
	int os;

	close(fd);
	os = jobtab[thisjob].stat;
	waitforpid(pid);
	cmdoutval = 0;
	jobtab[thisjob].stat = os;
	return nam;
    }

    /* pid == 0 */
    redup(fd, 1);
    entersubsh(Z_SYNC, 1, 0);
#ifndef __EMX__
    signal_ignore(SIGTSTP);
#endif
    execlist(list, 0, 1);
    close(1);
    _exit(lastval);
    zerr("exit returned in child!!", NULL, 0);
    kill(getpid(), SIGKILL);
    return NULL;
}

#if !defined(HAVE_DEV_FD) && defined(HAVE_FIFOS)
/* get a temporary named pipe */

/**/
char *
namedpipe(void)
{
    char *tnam = gettempname();

# ifdef HAVE_MKFIFO
    if (mkfifo(tnam, 0600) < 0)
# else
    if (mknod(tnam, 0010600, 0) < 0)
# endif
	return NULL;
    return tnam;
}
#endif /* ! HAVE_DEV_FD && HAVE_FIFOS */

/* <(...) or >(...) */

/**/
char *
getproc(char *cmd)
{
#if !defined(HAVE_FIFOS) && !defined(HAVE_DEV_FD)
    zerr("doesn't look like your system supports FIFOs.", NULL, 0);
    return NULL;
#else
    List list;
    int out = *cmd == Inang;
    char *pnam;
#ifndef HAVE_DEV_FD
    int fd;
#else
    int pipes[2];
#endif

    if (thisjob == -1)
	return NULL;
#ifndef HAVE_DEV_FD
    if (!(pnam = namedpipe()))
	return NULL;
#else
    pnam = ncalloc(12);
#endif
    if (!(list = parsecmd(cmd)))
	return NULL;
#ifndef HAVE_DEV_FD
    PERMALLOC {
	if (!jobtab[thisjob].filelist)
	    jobtab[thisjob].filelist = newlinklist();
	addlinknode(jobtab[thisjob].filelist, ztrdup(pnam));
    } LASTALLOC;
    if (zfork()) {
#else
    mpipe(pipes);
    if (zfork()) {
	sprintf(pnam, "/dev/fd/%d", pipes[!out]);
	zclose(pipes[out]);
	fdtable[pipes[!out]] = 2;
#endif
	return pnam;
    }
#ifndef HAVE_DEV_FD
    closem(0);
    fd = open(pnam, out ? O_WRONLY : O_RDONLY);
    if (fd == -1) {
	zerr("can't open %s: %e", pnam, errno);
	_exit(1);
    }
    entersubsh(Z_ASYNC, 1, 0);
    redup(fd, out);
#else
    entersubsh(Z_ASYNC, 1, 0);
    redup(pipes[out], out);
    closem(0);   /* this closes pipes[!out] as well */
#endif
    execlist(list, 0, 1);
    zclose(out);
    _exit(lastval);
    return NULL;
#endif   /* HAVE_FIFOS and HAVE_DEV_FD not defined */
}

/* > >(...) or < <(...) (does not use named pipes) */

/**/
int
getpipe(char *cmd)
{
    List list;
    int pipes[2], out = *cmd == Inang;

    if (!(list = parsecmd(cmd)))
	return -1;
    mpipe(pipes);
    if (zfork()) {
	zclose(pipes[out]);
	return pipes[!out];
    }
    entersubsh(Z_ASYNC, 1, 0);
    redup(pipes[out], out);
    closem(0);	/* this closes pipes[!out] as well */
    execlist(list, 0, 1);
    _exit(lastval);
    return 0;
}

/* open pipes with fds >= 10 */

/**/
void
mpipe(int *pp)
{
    pipe(pp);
    pp[0] = movefd(pp[0]);
    pp[1] = movefd(pp[1]);
}

/* Do process substitution with redirection */

/**/
void
spawnpipes(LinkList l)
{
    LinkNode n;
    Redir f;
    char *str;

    n = firstnode(l);
    for (; n; incnode(n)) {
	f = (Redir) getdata(n);
	if (f->type == OUTPIPE || f->type == INPIPE) {
	    str = f->name;
	    f->fd2 = getpipe(str);
	}
    }
}

/* evaluate a [[ ... ]] */

/**/
int
execcond(Cmd cmd)
{
    return !evalcond(cmd->u.cond);
}

/* evaluate a ((...)) arithmetic command */

/**/
int
execarith(Cmd cmd)
{
    char *e;
    long val = 0;

    while ((e = (char *) ugetnode(cmd->args)))
	val = matheval(e);
    errflag = 0;
    return !val;
}

/* perform time ... command */

/**/
int
exectime(Cmd cmd)
{
    int jb;

    jb = thisjob;
    if (!cmd->u.pline) {
	shelltime();
	return 0;
    }
    execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0);
    thisjob = jb;
    return lastval;
}

/* Define a shell function */

/**/
int
execfuncdef(Cmd cmd)
{
    Shfunc shf;
    char *s;
    int signum;

    PERMALLOC {
	while ((s = (char *) ugetnode(cmd->args))) {
	    shf = (Shfunc) zalloc(sizeof *shf);
	    shf->funcdef = (List) dupstruct(cmd->u.list);
	    shf->flags = 0;

	    /* is this shell function a signal trap? */
	    if (!strncmp(s, "TRAP", 4) && (signum = getsignum(s + 4)) != -1) {
		if (settrap(signum, shf->funcdef)) {
		    freestruct(shf->funcdef);
		    zfree(shf, sizeof *shf);
		    LASTALLOC_RETURN 1;
		}
		sigtrapped[signum] |= ZSIG_FUNC;
	    }
	    shfunctab->addnode(shfunctab, ztrdup(s), shf);
	}
    } LASTALLOC;
    return 0;
}

/* Main entry point to execute a shell function.  It will retrieve *
 * an autoloaded shell function if it is currently undefined.      */

/**/
void
execshfunc(Cmd cmd, Shfunc shf)
{
    List funcdef;
    char *nam;
    LinkList last_file_list = NULL;

    if (errflag)
	return;

    if (!list_pipe) {
	/* Without this deletejob the process table *
	 * would be filled by a recursive function. */
	last_file_list = jobtab[thisjob].filelist;
	jobtab[thisjob].filelist = NULL;
	deletejob(jobtab + thisjob);
    }

    /* Are we dealing with an autoloaded shell function? */
    if (shf->flags & PM_UNDEFINED) {
	nam = (char *) peekfirst(cmd->args);
	if (!(funcdef = getfpfunc(nam))) {
	    zerr("function not found: %s", nam, 0);
	    lastval = 1;
	} else {
	    PERMALLOC {
		shf->flags &= ~PM_UNDEFINED;
		funcdef = shf->funcdef = (List) dupstruct(funcdef);
	    } LASTALLOC;

	    /* Execute the function definition, we just retrived */
	    doshfunc(shf->funcdef, cmd->args, shf->flags, 0);

	    /* See if this file defined the autoloaded function *
	     * by name.  If so, we execute it again.            */
	    if ((shf = (Shfunc) shfunctab->getnode(shfunctab, nam))
		&& shf->funcdef && shf->funcdef != funcdef)
		doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
	}
    } else
	/* Normal shell function execution */
	doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
    if (!list_pipe)
	deletefilelist(last_file_list);
}

/* execute a shell function */

/**/
void
doshfunc(List list, LinkList doshargs, int flags, int noreturnval)
/* If noreturnval is nonzero, then reset the current return *
 * value (lastval) to its value before the shell function   *
 * was executed.                                            */
{
    Param pm;
    char **tab, **x, *oargv0 = NULL;
    int xexittr, oldzoptind, oldlastval;
    LinkList olist;
    char *s, *ou;
    void *xexitfn;
    char saveopts[OPT_SIZE];

    HEAPALLOC {
	pushheap();
	if (trapreturn < 0)
	    trapreturn--;
	oldlastval = lastval;
	xexittr = sigtrapped[SIGEXIT];
	if (xexittr & ZSIG_FUNC)
	    xexitfn = shfunctab->removenode(shfunctab, "TRAPEXIT");
	else
	    xexitfn = sigfuncs[SIGEXIT];
	sigtrapped[SIGEXIT] = 0;
	sigfuncs[SIGEXIT] = NULL;
	tab = pparams;
	oldzoptind = zoptind;
	zoptind = 1;

	/* We need to save the current options even if LOCALOPTIONS is *
	 * not currently set.  That's because if it gets set in the    *
	 * function we need to restore the original options on exit.   */
	memcpy(saveopts, opts, sizeof(opts));

	if (flags & PM_TAGGED)
	    opts[XTRACE] = 1;
	opts[PRINTEXITVALUE] = 0;
	if (doshargs) {
	    LinkNode node;

	    node = doshargs->first;
	    pparams = x = (char **) zcalloc(((sizeof *x) * (1 + countlinknodes(doshargs))));
	    if (isset(FUNCTIONARGZERO)) {
		oargv0 = argzero;
		argzero = ztrdup((char *) node->dat);
	    }
	    node = node->next;
	    for (; node; node = node->next, x++)
		*x = ztrdup((char *) node->dat);
	} else {
	    pparams = (char **) zcalloc(sizeof *pparams);
	    if (isset(FUNCTIONARGZERO)) {
		oargv0 = argzero;
		argzero = ztrdup(argzero);
	    }
	}
	PERMALLOC {
	    olist = locallist;		/* save the old locallist since shell functions may be nested */
	    locallist = newlinklist();	/* make a new list of local variables that we have to destroy */
	} LASTALLOC;
	locallevel++;
	ou = underscore;
	underscore = ztrdup(underscore);
	execlist(dupstruct(list), 1, 0);
	zsfree(underscore);
	underscore = ou;
	locallevel--;

	/* destroy the local variables we have created in the shell function */
	while ((s = (char *) getlinknode(locallist))) {
	    if((pm = (Param) paramtab->getnode(paramtab, s)) && (pm->level > locallevel))
		unsetparam_pm(pm, 0);
	    zsfree(s);
	}
	zfree(locallist, sizeof(struct linklist));

	locallist = olist;	/* restore the old list of local variables */
	breaks = retflag = 0;
	freearray(pparams);
	if (oargv0) {
	    zsfree(argzero);
	    argzero = oargv0;
	}
	zoptind = oldzoptind;
	pparams = tab;

	if (isset(LOCALOPTIONS)) {
	    /* restore all shell options except PRIVILEGED */
	    saveopts[PRIVILEGED] = opts[PRIVILEGED];
	    memcpy(opts, saveopts, sizeof(opts));
	} else {
	    /* just restore a couple. */
	    opts[XTRACE] = saveopts[XTRACE];
	    opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
	    opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
	}

	/* Did the shell function define a trap on SIGEXIT? */
	if (sigfuncs[SIGEXIT]) {
	    dotrap(SIGEXIT);
	    unsettrap(SIGEXIT);
	}

	sigtrapped[SIGEXIT] = xexittr;
	if (xexittr & ZSIG_FUNC) {
	    shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn);
	    sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef;
	} else
	    sigfuncs[SIGEXIT] = (List) xexitfn;
	if (trapreturn < -1)
	    trapreturn++;
	if (noreturnval)
	    lastval = oldlastval;
	popheap();
    } LASTALLOC;
}

/* Search fpath for an undefined function. */

/**/
List
getfpfunc(char *s)
{
    char **pp, buf[PATH_MAX];
    off_t len;
    char *d;
    List r;
    int fd;

    pp = fpath;
    for (; *pp; pp++) {
	if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
	    continue;
	if (**pp)
	    sprintf(buf, "%s/%s", *pp, s);
	else
	    strcpy(buf, s);
	unmetafy(buf, NULL);
	if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY)) != -1) {
	    if ((len = lseek(fd, 0, 2)) != -1) {
		lseek(fd, 0, 0);
		d = (char *) zcalloc(len + 1);
		if (read(fd, d, len) == len) {
		    close(fd);
		    d = metafy(d, len, META_REALLOC);
		    HEAPALLOC {
			r = parse_string(d);
		    } LASTALLOC;
		    zfree(d, len + 1);
		    return r;
		} else {
		    zfree(d, len + 1);
		    close(fd);
		}
	    } else {
		close(fd);
	    }
	}
    }
    return NULL;
}

/* check to see if AUTOCD applies here */

extern int doprintdir;

/**/
char *
cancd(char *s)
{
    int nocdpath = s[0] == '.' &&
    (s[1] == '/' || !s[1] || (s[1] == '.' && (s[2] == '/' || !s[1])));
    char *t;

    if (*s != '/') {
	char sbuf[PATH_MAX], **cp;

	if (cancd2(s))
	    return s;
	if (access(unmeta(s), X_OK) == 0)
	    return NULL;
	if (!nocdpath)
	    for (cp = cdpath; *cp; cp++) {
		if (strlen(*cp) + strlen(s) + 1 >= PATH_MAX)
		    continue;
		if (**cp)
		    sprintf(sbuf, "%s/%s", *cp, s);
		else
		    strcpy(sbuf, s);
		if (cancd2(sbuf)) {
		    doprintdir = -1;
		    return dupstring(sbuf);
		}
	    }
	if ((t = cd_able_vars(s))) {
	    if (cancd2(t)) {
		doprintdir = -1;
		return t;
	    }
	}
	return NULL;
    }
    return cancd2(s) ? s : NULL;
}

/**/
int
cancd2(char *s)
{
    struct stat buf;
    char *us = unmeta(s);

    return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
}

/**/
void
execsave(void)
{
    struct execstack *es;

    es = (struct execstack *) malloc(sizeof(struct execstack));
    es->args = args;
    es->list_pipe_pid = list_pipe_pid;
    es->nowait = nowait;
    es->pline_level = pline_level;
    es->list_pipe_child = list_pipe_child;
    es->list_pipe_job = list_pipe_job;
    strcpy(es->list_pipe_text, list_pipe_text);
    es->lastval = lastval;
    es->noeval = noeval;
    es->badcshglob = badcshglob;
    es->cmdoutpid = cmdoutpid;
    es->cmdoutval = cmdoutval;
    es->trapreturn = trapreturn;
    es->noerrs = noerrs;
    es->subsh_close = subsh_close;
    es->underscore = underscore;
    underscore = ztrdup(underscore);
    es->next = exstack;
    exstack = es;
    noerrs = cmdoutpid = 0;
}

/**/
void
execrestore(void)
{
    struct execstack *en;

    DPUTS(!exstack, "BUG: execrestore() without execsave()");
    args = exstack->args;
    list_pipe_pid = exstack->list_pipe_pid;
    nowait = exstack->nowait;
    pline_level = exstack->pline_level;
    list_pipe_child = exstack->list_pipe_child;
    list_pipe_job = exstack->list_pipe_job;
    strcpy(list_pipe_text, exstack->list_pipe_text);
    lastval = exstack->lastval;
    noeval = exstack->noeval;
    badcshglob = exstack->badcshglob;
    cmdoutpid = exstack->cmdoutpid;
    cmdoutval = exstack->cmdoutval;
    trapreturn = exstack->trapreturn;
    noerrs = exstack->noerrs;
    subsh_close = exstack->subsh_close;
    zsfree(underscore);
    underscore = exstack->underscore;
    en = exstack->next;
    free(exstack);
    exstack = en;
}
