about summary refs log tree commit diff
path: root/Src/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/exec.c')
-rw-r--r--Src/exec.c2965
1 files changed, 2965 insertions, 0 deletions
diff --git a/Src/exec.c b/Src/exec.c
new file mode 100644
index 000000000..1b355d028
--- /dev/null
+++ b/Src/exec.c
@@ -0,0 +1,2965 @@
+/*
+ * exec.c - command execution
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 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.mdh"
+#include "exec.pro"
+
+/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */
+
+/**/
+int noerrexit;
+
+/* suppress error messages */
+ 
+/**/
+int noerrs;
+ 
+/* do not save history on exec and exit */
+
+/**/
+int nohistsave;
+ 
+/* error/break flag */
+ 
+/**/
+int errflag;
+ 
+/* Status of return from a trap */
+ 
+/**/
+int trapreturn;
+ 
+/* != 0 if this is a subshell */
+ 
+/**/
+int subsh;
+ 
+/* != 0 if we have a return pending */
+ 
+/**/
+int retflag;
+
+/**/
+long lastval2;
+ 
+/* The table of file descriptors.  A table element is zero if the  *
+ * corresponding fd is not used by the shell.  It is greater than  *
+ * 1 if the fd is used by a <(...) or >(...) substitution and 1 if *
+ * it is an internal file descriptor which must be closed before   *
+ * executing an external command.  The first ten elements of the   *
+ * table is not used.  A table element is set by movefd and cleard *
+ * by zclose.                                                      */
+
+/**/
+char *fdtable;
+
+/* The allocated size of fdtable */
+
+/**/
+int fdtable_size;
+
+/* The highest fd that marked with nonzero in fdtable */
+
+/**/
+int max_zsh_fd;
+
+/* input fd from the coprocess */
+
+/**/
+int coprocin;
+
+/* output fd from the coprocess */
+
+/**/
+int coprocout;
+
+/* != 0 if the line editor is active */
+
+/**/
+int zleactive;
+
+/* pid of process undergoing 'process substitution' */
+ 
+/**/
+pid_t cmdoutpid;
+ 
+/* exit status of process undergoing 'process substitution' */
+ 
+/**/
+int cmdoutval;
+ 
+/* Stack to save some variables before executing a signal handler function */
+
+/**/
+struct execstack *exstack;
+
+#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
+
+/* the resource limits for the shell and its children */
+
+/**/
+struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+ 
+/**/
+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 /* HAVE_GETRLIMIT */
+
+/* fork and set limits */
+
+/**/
+static 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 */
+
+/**/
+static 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
+
+/**/
+static int
+zexecve(char *pth, char **argv)
+{
+    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.               */
+    if ((eno = errno) == ENOEXEC) {
+	char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
+	int fd, ct, t0;
+
+	if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
+	    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 */
+
+/**/
+static 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;
+
+    arg0 = (char *) peekfirst(args);
+    if (isset(RESTRICTED) && strchr(arg0, '/')) {
+	zerr("%s: restricted", arg0, 0);
+	_exit(1);
+    }
+
+    /* 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;
+    }
+
+    cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+
+    /* If ARGV0 is in the commands environment, we use *
+     * that as argv[0] for this external command       */
+    if (unset(RESTRICTED) && (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
+isreallycom(Cmdnam cn)
+{
+    char fullnam[MAXCMDLEN];
+
+    strcpy(fullnam, cn->u.name ? *(cn->u.name) : "");
+    strcat(fullnam, "/");
+    strcat(fullnam, cn->nam);
+    return iscom(fullnam);
+}
+
+/**/
+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().                                          *
+ */
+
+/**/
+static int
+execpline(Sublist l, int how, int last1)
+{
+    int ipipe[2], opipe[2];
+    int pj, newjob;
+    int old_simple_pline = simple_pline;
+    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;
+	jobtab[thisjob].stat |= STAT_NOSTTY;
+	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);
+			killjb(jn, SIGCONT);
+			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;
+			killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
+			break;
+		    }
+		    else {
+			close(synch[0]);
+			entersubsh(Z_ASYNC, 0, 0);
+			setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
+			close(synch[1]);
+			kill(getpid(), SIGSTOP);
+			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 = old_simple_pline;
+    return lastval;
+}
+
+static int subsh_close = -1;
+
+/* execute pipeline.  This function assumes the `pline' is not NULL. */
+
+/**/
+static 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 */
+
+/**/
+static 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 */
+
+/**/
+static int
+clobber_open(struct redir *f)
+{
+    struct stat buf;
+    int fd, oerrno;
+
+    /* If clobbering, just open. */
+    if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
+	return open(unmeta(f->name),
+		O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666);
+
+    /* If not clobbering, attempt to create file exclusively. */
+    if ((fd = open(unmeta(f->name),
+		   O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0)
+	return fd;
+
+    /* If that fails, we are still allowed to open non-regular files. *
+     * Try opening, and if it's a regular file then close it again    *
+     * because we weren't supposed to open it.                        */
+    oerrno = errno;
+    if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) {
+	if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
+	    return fd;
+	close(fd);
+    }
+    errno = oerrno;
+    return -1;
+}
+
+/* size of buffer for tee and cat processes */
+#define TCBUFSIZE 4092
+
+/* close an multio (success) */
+
+/**/
+static 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) */
+
+/**/
+static 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;
+	}
+}
+
+/**/
+static 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.                                              */
+
+/**/
+static 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;
+}
+
+/**/
+static 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 */
+		    pm = (Param) paramtab->removenode(paramtab, v->name);
+		    if (isset(RESTRICTED) && (pm->flags & PM_RESTRICTED)) {
+			zerr("%s: restricted", pm->nam, 0);
+			zsfree(val);
+			return;
+		    }
+		}
+		allexp = opts[ALLEXPORT];
+		opts[ALLEXPORT] = 1;
+		pm = setsparam(v->name, val);
+		opts[ALLEXPORT] = allexp;
+	    } else
+		pm = setsparam(v->name, val);
+	    if (errflag)
+		return;
+	    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;
+    }
+}
+
+/**/
+static 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;
+#ifdef DYNAMIC
+		/* autoload the builtin if necessary */
+		if (!((Builtin) hn)->handlerfunc) {
+		    load_module(((Builtin) hn)->optstr);
+		    hn = builtintab->getnode(builtintab, cmdarg);
+		}
+#endif
+		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, 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;
+		}
+	    } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) &&
+		       (cmd->flags & CFLAG_EXEC)) {
+		zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0);
+		lastval = 1;
+		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;
+#ifdef DYNAMIC
+		/* autoload the builtin if necessary */
+		if (!((Builtin) hn)->handlerfunc)
+		    load_module(((Builtin) hn)->optstr);
+#endif
+		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;
+	char trycd = (isset(AUTOCD) && isset(SHINSTDIN)
+		      && empty(cmd->redir) && !empty(args)
+		      && !nextnode(firstnode(args))
+		      && *(char *)peekfirst(args));
+
+	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 && trycd && !isreallycom((Cmdnam)hn)) {
+		cmdnamtab->removenode(cmdnamtab, cmdarg);
+		cmdnamtab->freenode(hn);
+		hn = NULL;
+	    }
+	    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 && trycd && (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 PATH_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();
+#ifdef HAVE_NICE
+	/* Check if we should run background jobs at a lower priority. */
+	if ((how & Z_ASYNC) && isset(BGNICE))
+	    nice(5);
+#endif /* HAVE_NICE */
+
+    } 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");
+	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();
+	    }
+	    if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
+		zerr("writing redirection not allowed in restricted mode", NULL, 0);
+		execerr();
+	    }
+	    if (unset(EXECOPT))
+		continue;
+	    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 | O_NOCTTY);
+		else
+		    fil = open(unmeta(fn->name),
+			       O_RDWR | O_CREAT | O_NOCTTY, 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_NOCTTY :
+			       O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 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, execautofn
+	    };
+
+	    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 PATH_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 PATH_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 PATH_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. */
+
+/**/
+static 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) &&
+		       (unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) {
+		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 */
+
+/**/
+static void
+restore_params(LinkList restorelist, LinkList removelist)
+{
+    Param pm;
+    char *s;
+
+    if (removelist) {
+	/* remove temporary parameters */
+	while ((s = (char *) ugetnode(removelist))) {
+	    if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+		!(pm->flags & PM_SPECIAL)) {
+		pm->flags &= ~PM_READONLY;
+		unsetparam_pm(pm, 0, 0);
+	    }
+	}
+    }
+
+    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 */
+
+/**/
+static 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;
+}
+
+/**/
+static 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 | O_NOCTTY)) {
+		    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)) {
+	signal_default(SIGTTOU);
+	signal_default(SIGTTIN);
+	signal_default(SIGTSTP);
+    }
+    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 */
+
+/**/
+static 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|O_NOCTTY, 0600)) == -1)
+	return -1;
+    write(fd, t, len);
+    close(fd);
+    fd = open(s, O_RDONLY | O_NOCTTY);
+    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 | O_NOCTTY)) == -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);
+    opts[MONITOR] = 0;
+    entersubsh(Z_SYNC, 1, 0);
+    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 */
+
+/**/
+static 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 || errno == EINTR) {
+	if (c == EOF) {
+	    errno = 0;
+	    clearerr(fin);
+	    continue;
+	}
+	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;
+}
+
+/**/
+static 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 | O_NOCTTY, 0600);
+
+    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);
+    opts[MONITOR] = 0;
+    entersubsh(Z_SYNC, 1, 0);
+    execlist(list, 0, 1);
+    close(1);
+    _exit(lastval);
+    zerr("exit returned in child!!", NULL, 0);
+    kill(getpid(), SIGKILL);
+    return NULL;
+}
+
+#if !defined(PATH_DEV_FD) && defined(HAVE_FIFOS)
+/* get a temporary named pipe */
+
+static 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 /* ! PATH_DEV_FD && HAVE_FIFOS */
+
+/* <(...) or >(...) */
+
+/**/
+char *
+getproc(char *cmd)
+{
+#if !defined(HAVE_FIFOS) && !defined(PATH_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 PATH_DEV_FD
+    int fd;
+#else
+    int pipes[2];
+#endif
+
+    if (thisjob == -1)
+	return NULL;
+#ifndef PATH_DEV_FD
+    if (!(pnam = namedpipe()))
+	return NULL;
+#else
+    pnam = ncalloc(strlen(PATH_DEV_FD) + 6);
+#endif
+    if (!(list = parsecmd(cmd)))
+	return NULL;
+#ifndef PATH_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, "%s/%d", PATH_DEV_FD, pipes[!out]);
+	zclose(pipes[out]);
+	fdtable[pipes[!out]] = 2;
+#endif
+	return pnam;
+    }
+#ifndef PATH_DEV_FD
+    closem(0);
+    fd = open(pnam, out ? O_WRONLY | O_NOCTTY : O_RDONLY | O_NOCTTY);
+    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 PATH_DEV_FD not defined */
+}
+
+/* > >(...) or < <(...) (does not use named pipes) */
+
+/**/
+static 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 */
+
+/**/
+static void
+mpipe(int *pp)
+{
+    pipe(pp);
+    pp[0] = movefd(pp[0]);
+    pp[1] = movefd(pp[1]);
+}
+
+/* Do process substitution with redirection */
+
+/**/
+static 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 [[ ... ]] */
+
+/**/
+static int
+execcond(Cmd cmd)
+{
+    return !evalcond(cmd->u.cond);
+}
+
+/* evaluate a ((...)) arithmetic command */
+
+/**/
+static 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 */
+
+/**/
+static 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 */
+
+/**/
+static 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;
+    if(isset(HISTNOFUNCTIONS))
+	remhist();
+    return 0;
+}
+
+/* Main entry point to execute a shell function. */
+
+/**/
+static void
+execshfunc(Cmd cmd, Shfunc shf)
+{
+    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);
+    }
+
+    doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
+
+    if (!list_pipe)
+	deletefilelist(last_file_list);
+}
+
+/* Function to execute the special type of command that represents an *
+ * autoloaded shell function.  The command structure tells us which   *
+ * function it is.  This function is actually called as part of the   *
+ * execution of the autoloaded function itself, so when the function  *
+ * has been autoloaded, its list is just run with no frills.          */
+
+/**/
+static int
+execautofn(Cmd cmd)
+{
+    Shfunc shf = cmd->u.autofn->shf;
+    List l = getfpfunc(shf->nam);
+    if(l == &dummy_list) {
+	zerr("%s: function definition file not found", shf->nam, 0);
+	return 1;
+    }
+    if(isset(KSHAUTOLOAD)) {
+	VARARR(char, n, strlen(shf->nam) + 1);
+	strcpy(n, shf->nam);
+	execlist(l, 1, 0);
+	shf = (Shfunc) shfunctab->getnode(shfunctab, n);
+	if(!shf || (shf->flags & PM_UNDEFINED)) {
+	    zerr("%s: function not defined by file", n, 0);
+	    return 1;
+	}
+    } else {
+	freestruct(shf->funcdef);
+	PERMALLOC {
+	    shf->funcdef = dupstruct(stripkshdef(l, shf->nam));
+	} LASTALLOC;
+	shf->flags &= ~PM_UNDEFINED;
+    }
+    HEAPALLOC {
+	execlist(dupstruct(shf->funcdef), 1, 0);
+    } LASTALLOC;
+    return lastval;
+}
+
+/* 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.                                            */
+{
+    char **tab, **x, *oargv0 = NULL;
+    int xexittr, newexittr, oldzoptind, oldlastval;
+    char *ou;
+    void *xexitfn, *newexitfn;
+    char saveopts[OPT_SIZE];
+    int obreaks = breaks;
+
+    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);
+	    }
+	}
+	startparamscope();
+	ou = underscore;
+	underscore = ztrdup(underscore);
+	execlist(dupstruct(list), 1, 0);
+	zsfree(underscore);
+	underscore = ou;
+	endparamscope();
+
+	if (retflag) {
+	    retflag = 0;
+	    breaks = obreaks;
+	}
+	freearray(pparams);
+	if (oargv0) {
+	    zsfree(argzero);
+	    argzero = oargv0;
+	}
+	zoptind = oldzoptind;
+	pparams = tab;
+
+	if (isset(LOCALOPTIONS)) {
+	    /* restore all shell options except PRIVILEGED and RESTRICTED */
+	    saveopts[PRIVILEGED] = opts[PRIVILEGED];
+	    saveopts[RESTRICTED] = opts[RESTRICTED];
+	    memcpy(opts, saveopts, sizeof(opts));
+	} else {
+	    /* just restore a couple. */
+	    opts[XTRACE] = saveopts[XTRACE];
+	    opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
+	    opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
+	}
+
+	/*
+	 * The trap '...' EXIT runs in the environment of the caller,
+	 * so remember it here but run it after resetting the
+	 * traps for the parent.
+	 */
+	newexittr = sigtrapped[SIGEXIT];
+	newexitfn = sigfuncs[SIGEXIT];
+	if (newexittr & ZSIG_FUNC)
+	    shfunctab->removenode(shfunctab, "TRAPEXIT");
+
+	sigtrapped[SIGEXIT] = xexittr;
+	if (xexittr & ZSIG_FUNC) {
+	    shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn);
+	    sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef;
+	} else
+	    sigfuncs[SIGEXIT] = (List) xexitfn;
+
+	if (newexitfn) {
+	    dotrapargs(SIGEXIT, &newexittr, newexitfn);
+	    freestruct(newexitfn);
+	}
+
+	if (trapreturn < -1)
+	    trapreturn++;
+	if (noreturnval)
+	    lastval = oldlastval;
+	popheap();
+    } LASTALLOC;
+}
+
+/* Search fpath for an undefined function.  Finds the file, and returns the *
+ * list of its contents.                                                    */
+
+/**/
+static 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 | O_NOCTTY)) != -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 &dummy_list;
+}
+
+/* Handle the most common type of ksh-style autoloading, when doing a      *
+ * zsh-style autoload.  Given the list read from an autoload file, and the *
+ * name of the function being defined, check to see if the file consists   *
+ * entirely of a single definition for that function.  If so, use the      *
+ * contents of that definition.  Otherwise, use the entire file.           */
+
+/**/
+static List
+stripkshdef(List l, char *name)
+{
+    Sublist s;
+    Pline p;
+    Cmd c;
+    if(!l)
+	return NULL;
+    if(l->type != Z_SYNC || l->right)
+	return l;
+    s = l->left;
+    if(s->flags || s->right)
+	return l;
+    p = s->left;
+    if(p->right)
+	return l;
+    c = p->left;
+    if(c->type != FUNCDEF || c->flags ||
+	nonempty(c->redir) || nonempty(c->vars) ||
+	empty(c->args) || lastnode(c->args) != firstnode(c->args) ||
+	strcmp(name, peekfirst(c->args)))
+	return l;
+    return c->u.list;
+}
+
+/* check to see if AUTOCD applies here */
+
+/**/
+static 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;
+}
+
+/**/
+static 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;
+}