summaryrefslogtreecommitdiff
path: root/dotfiles/system/.zsh/modules/Src/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/exec.c')
-rw-r--r--dotfiles/system/.zsh/modules/Src/exec.c6250
1 files changed, 6250 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/exec.c b/dotfiles/system/.zsh/modules/Src/exec.c
new file mode 100644
index 0000000..615a508
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/exec.c
@@ -0,0 +1,6250 @@
+/*
+ * 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"
+
+/* Flags for last argument of addvars */
+
+enum {
+ /* Export the variable for "VAR=val cmd ..." */
+ ADDVAR_EXPORT = 1 << 0,
+ /* Apply restrictions for variable */
+ ADDVAR_RESTRICT = 1 << 1,
+ /* Variable list is being restored later */
+ ADDVAR_RESTORE = 1 << 2
+};
+
+/* Structure in which to save values around shell function call */
+
+struct funcsave {
+ char opts[OPT_SIZE];
+ char *argv0;
+ int zoptind, lastval, optcind, numpipestats;
+ int *pipestats;
+ char *scriptname;
+ int breaks, contflag, loops, emulation, noerrexit, oflags, restore_sticky;
+ Emulation_options sticky;
+ struct funcstack fstack;
+};
+typedef struct funcsave *Funcsave;
+
+/*
+ * used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT.
+ * Bits from noerrexit_bits.
+ */
+
+/**/
+int noerrexit;
+
+/* used to suppress ERREXIT or ERRRETURN for one occurrence: 0 or 1 */
+
+/**/
+int this_noerrexit;
+
+/*
+ * noerrs = 1: suppress error messages
+ * noerrs = 2: don't set errflag on parse error, either
+ */
+
+/**/
+mod_export int noerrs;
+
+/* do not save history on exec and exit */
+
+/**/
+int nohistsave;
+
+/* error flag: bits from enum errflag_bits */
+
+/**/
+mod_export int errflag;
+
+/*
+ * State of trap return value. Value is from enum trap_state.
+ */
+
+/**/
+int trap_state;
+
+/*
+ * Value associated with return from a trap.
+ * This is only active if we are inside a trap, else its value
+ * is irrelevant. It is initialised to -1 for a function trap and
+ * -2 for a non-function trap and if negative is decremented as
+ * we go deeper into functions and incremented as we come back up.
+ * The value is used to decide if an explicit "return" should cause
+ * a return from the caller of the trap; it does this by setting
+ * trap_return to a status (i.e. a non-negative value).
+ *
+ * In summary, trap_return is
+ * - zero unless we are in a trap
+ * - negative in a trap unless it has triggered. Code uses this
+ * to detect an active trap.
+ * - non-negative in a trap once it was triggered. It should remain
+ * non-negative until restored after execution of the trap.
+ */
+
+/**/
+int trap_return;
+
+/* != 0 if this is a subshell */
+
+/**/
+int subsh;
+
+/* != 0 if we have a return pending */
+
+/**/
+mod_export 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. */
+
+/**/
+mod_export unsigned char *fdtable;
+
+/* The allocated size of fdtable */
+
+/**/
+int fdtable_size;
+
+/* The highest fd that marked with nonzero in fdtable */
+
+/**/
+mod_export int max_zsh_fd;
+
+/* input fd from the coprocess */
+
+/**/
+mod_export int coprocin;
+
+/* output fd from the coprocess */
+
+/**/
+mod_export int coprocout;
+
+/* count of file locks recorded in fdtable */
+
+/**/
+int fdtable_flocks;
+
+
+/* != 0 if the line editor is active */
+
+/**/
+mod_export int zleactive;
+
+/* pid of process undergoing 'process substitution' */
+
+/**/
+pid_t cmdoutpid;
+
+/* pid of last process started by <(...), >(...) */
+
+/**/
+mod_export pid_t procsubstpid;
+
+/* exit status of process undergoing 'process substitution' */
+
+/**/
+int cmdoutval;
+
+/*
+ * This is set by an exiting $(...) substitution to indicate we need
+ * to retain the status. We initialize it to zero if we think we need
+ * to reset the status for a command.
+ */
+
+/**/
+int use_cmdoutval;
+
+/* The context in which a shell function is called, see SFC_* in zsh.h. */
+
+/**/
+mod_export int sfcontext;
+
+/* Stack to save some variables before executing a signal handler function */
+
+/**/
+struct execstack *exstack;
+
+/* Stack with names of function calls, 'source' calls, and 'eval' calls
+ * currently active. */
+
+/**/
+mod_export Funcstack funcstack;
+
+#define execerr() \
+ do { \
+ if (!forked) { \
+ redir_err = lastval = 1; \
+ goto done; \
+ } else { \
+ _exit(1); \
+ } \
+ } while (0)
+
+static int doneps4;
+static char *STTYval;
+static char *blank_env[] = { NULL };
+
+/* Execution functions. */
+
+static int (*execfuncs[WC_COUNT-WC_CURSH]) _((Estate, int)) = {
+ execcursh, exectime, NULL /* execfuncdef handled specially */,
+ execfor, execselect,
+ execwhile, execrepeat, execcase, execif, execcond,
+ execarith, execautofn, exectry
+};
+
+/* structure for command builtin for when it is used with -v or -V */
+static struct builtin commandbn =
+ BUILTIN("command", 0, bin_whence, 0, -1, BIN_COMMAND, "pvV", NULL);
+
+/* parse string into a list */
+
+/**/
+mod_export Eprog
+parse_string(char *s, int reset_lineno)
+{
+ Eprog p;
+ zlong oldlineno;
+
+ zcontext_save();
+ inpush(s, INP_LINENO, NULL);
+ strinbeg(0);
+ oldlineno = lineno;
+ if (reset_lineno)
+ lineno = 1;
+ p = parse_list();
+ lineno = oldlineno;
+ if (tok == LEXERR && !lastval)
+ lastval = 1;
+ strinend();
+ inpop();
+ zcontext_restore();
+ return p;
+}
+
+/**/
+#ifdef HAVE_GETRLIMIT
+
+/* the resource limits for the shell and its children */
+
+/**/
+mod_export struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+
+/**/
+mod_export 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", errno);
+ limits[limnum] = current_limits[limnum];
+ return -1;
+ }
+ current_limits[limnum] = limits[limnum];
+ }
+ return 0;
+}
+
+/**/
+mod_export 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(struct timeval *tv)
+{
+ pid_t pid;
+ struct timezone dummy_tz;
+
+ /*
+ * Is anybody willing to explain this test?
+ */
+ if (thisjob != -1 && thisjob >= jobtabsize - 1 && !expandjobtab()) {
+ zerr("job table full");
+ return -1;
+ }
+ if (tv)
+ gettimeofday(tv, &dummy_tz);
+ /*
+ * Queueing signals is necessary on Linux because fork()
+ * manipulates mutexes, leading to deadlock in memory
+ * allocation. We don't expect fork() to be particularly
+ * zippy anyway.
+ */
+ queue_signals();
+ pid = fork();
+ unqueue_signals();
+ if (pid == -1) {
+ zerr("fork failed: %e", errno);
+ return -1;
+ }
+#ifdef HAVE_GETRLIMIT
+ if (!pid)
+ /* set resource limits for the child process */
+ setlimits(NULL);
+#endif
+ return pid;
+}
+
+/*
+ * Allen Edeln gebiet ich Andacht,
+ * Hohen und Niedern von Heimdalls Geschlecht;
+ * Ich will list_pipe's Wirken kuenden
+ * Die aeltesten Sagen, der ich mich entsinne...
+ *
+ * In most shells, if you do something like:
+ *
+ * cat foo | while read a; do grep $a bar; done
+ *
+ * the shell forks and executes the loop in the sub-shell thus created.
+ * In zsh this traditionally executes the loop in the current shell, which
+ * is nice to have if the loop does something to change the shell, like
+ * setting parameters or calling builtins.
+ * Putting the loop in a sub-shell makes life easy, because the shell only
+ * has to put it into the job-structure and then treats it as a normal
+ * process. Suspending and interrupting is no problem then.
+ * Some years ago, zsh either couldn't suspend such things at all, or
+ * it got really messed up when users tried to do it. As a solution, we
+ * implemented the list_pipe-stuff, which has since then become a reason
+ * for many nightmares.
+ * Pipelines like the one above are executed by the functions in this file
+ * which call each other (and sometimes recursively). The one above, for
+ * example would lead to a function call stack roughly like:
+ *
+ * execlist->execpline->execcmd->execwhile->execlist->execpline
+ *
+ * (when waiting for the grep, ignoring execpline2 for now). At this time,
+ * zsh has built two job-table entries for it: one for the cat and one for
+ * the grep. If the user hits ^Z at this point (and jobbing is used), the
+ * shell is notified that the grep was suspended. The list_pipe flag is
+ * used to tell the execpline where it was waiting that it was in a pipeline
+ * with a shell construct at the end (which may also be a shell function or
+ * several other things). When zsh sees the suspended grep, it forks to let
+ * the sub-shell execute the rest of the while loop. The parent shell walks
+ * up in the function call stack to the first execpline. There it has to find
+ * out that it has just forked and then has to add information about the sub-
+ * shell (its pid and the text for it) in the job entry of the cat. The pid
+ * is passed down in the list_pipe_pid variable.
+ * But there is a problem: the suspended grep is a child of the parent shell
+ * and can't be adopted by the sub-shell. So the parent shell also has to
+ * keep the information about this process (more precisely: this pipeline)
+ * by keeping the job table entry it created for it. The fact that there
+ * are two jobs which have to be treated together is remembered by setting
+ * the STAT_SUPERJOB flag in the entry for the cat-job (which now also
+ * contains a process-entry for the whole loop -- the sub-shell) and by
+ * setting STAT_SUBJOB in the job of the grep-job. With that we can keep
+ * sub-jobs from being displayed and we can handle an fg/bg on the super-
+ * job correctly. When the super-job is continued, the shell also wakes up
+ * the sub-job. But then, the grep will exit sometime. Now the parent shell
+ * has to remember not to try to wake it up again (in case of another ^Z).
+ * It also has to wake up the sub-shell (which suspended itself immediately
+ * after creation), so that the rest of the loop is executed by it.
+ * But there is more: when the sub-shell is created, the cat may already
+ * have exited, so we can't put the sub-shell in the process group of it.
+ * In this case, we put the sub-shell in the process group of the parent
+ * shell and in any case, the sub-shell has to put all commands executed
+ * by it into its own process group, because only this way the parent
+ * shell can control them since it only knows the process group of the sub-
+ * shell. Of course, this information is also important when putting a job
+ * in the foreground, where we have to attach its process group to the
+ * controlling tty.
+ * All this is made more difficult because we have to handle return values
+ * correctly. If the grep is signaled, its exit status has to be propagated
+ * back to the parent shell which needs it to set the exit status of the
+ * super-job. And of course, when the grep is signaled (including ^C), the
+ * loop has to be stopped, etc.
+ * The code for all this is distributed over three files (exec.c, jobs.c,
+ * and signals.c) and none of them is a simple one. So, all in all, there
+ * may still be bugs, but considering the complexity (with race conditions,
+ * signal handling, and all that), this should probably be expected.
+ */
+
+/**/
+int list_pipe = 0, simple_pline = 0;
+
+static pid_t list_pipe_pid;
+static struct timeval list_pipe_start;
+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(Estate state, int do_exec)
+{
+ Wordcode end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
+
+ /* Skip word only used for try/always */
+ state->pc++;
+
+ /*
+ * The test thisjob != -1 was added because sometimes thisjob
+ * can be invalid at this point. The case in question was
+ * in a precmd function after operations involving background
+ * jobs.
+ *
+ * This is because sometimes we bypass job control to execute
+ * very simple functions via execssimple().
+ */
+ if (!list_pipe && thisjob != -1 && thisjob != list_pipe_job &&
+ !hasprocs(thisjob))
+ deletejob(jobtab + thisjob, 0);
+ cmdpush(CS_CURSH);
+ execlist(state, 1, do_exec);
+ cmdpop();
+
+ state->pc = end;
+ this_noerrexit = 1;
+
+ return lastval;
+}
+
+/* execve after handling $_ and #! */
+
+#define POUNDBANGLIMIT 64
+
+/**/
+static int
+zexecve(char *pth, char **argv, char **newenvp)
+{
+ int eno;
+ static char buf[PATH_MAX * 2+1];
+ char **eep;
+
+ unmetafy(pth, NULL);
+ for (eep = argv; *eep; eep++)
+ if (*eep != pth)
+ unmetafy(*eep, NULL);
+ buf[0] = '_';
+ buf[1] = '=';
+ if (*pth == '/')
+ strcpy(buf + 2, pth);
+ else
+ sprintf(buf + 2, "%s/%s", pwd, pth);
+ zputenv(buf);
+#ifndef FD_CLOEXEC
+ closedumps();
+#endif
+
+ if (newenvp == NULL)
+ newenvp = environ;
+ winch_unblock();
+ execve(pth, argv, newenvp);
+
+ /* 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 || eno == ENOENT) {
+ char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
+ int fd, ct, t0;
+
+ if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
+ argv0 = *argv;
+ *argv = pth;
+ execvebuf[0] = '\0';
+ 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')
+ break;
+ while (inblank(execvebuf[t0]))
+ execvebuf[t0--] = '\0';
+ execvebuf[POUNDBANGLIMIT] = '\0';
+ for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
+ for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
+ if (eno == ENOENT) {
+ char *pprog;
+ if (*ptr)
+ *ptr = '\0';
+ if (*ptr2 != '/' &&
+ (pprog = pathprog(ptr2, NULL))) {
+ argv[-2] = ptr2;
+ argv[-1] = ptr + 1;
+ winch_unblock();
+ execve(pprog, argv - 2, newenvp);
+ }
+ zerr("%s: bad interpreter: %s: %e", pth, ptr2,
+ eno);
+ } else if (*ptr) {
+ *ptr = '\0';
+ argv[-2] = ptr2;
+ argv[-1] = ptr + 1;
+ winch_unblock();
+ execve(ptr2, argv - 2, newenvp);
+ } else {
+ argv[-1] = ptr2;
+ winch_unblock();
+ execve(ptr2, argv - 1, newenvp);
+ }
+ } else if (eno == ENOEXEC) {
+ argv[-1] = "sh";
+ winch_unblock();
+ execve("/bin/sh", argv - 1, newenvp);
+ }
+ } else if (eno == ENOEXEC) {
+ for (t0 = 0; t0 != ct; t0++)
+ if (!execvebuf[t0])
+ break;
+ if (t0 == ct) {
+ argv[-1] = "sh";
+ winch_unblock();
+ execve("/bin/sh", argv - 1, newenvp);
+ }
+ }
+ } 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);
+}
+
+/*
+ * Attempt to handle command not found.
+ * Return 0 if the condition was handled, non-zero otherwise.
+ */
+
+/**/
+static int
+commandnotfound(char *arg0, LinkList args)
+{
+ Shfunc shf = (Shfunc)
+ shfunctab->getnode(shfunctab, "command_not_found_handler");
+
+ if (!shf) {
+ lastval = 127;
+ return 1;
+ }
+
+ pushnode(args, arg0);
+ lastval = doshfunc(shf, args, 1);
+ return 0;
+}
+
+/*
+ * Search the default path for cmd.
+ * pbuf of length plen is the buffer to use.
+ * Return NULL if not found.
+ */
+
+static char *
+search_defpath(char *cmd, char *pbuf, int plen)
+{
+ char *ps = DEFAULT_PATH, *pe = NULL, *s;
+
+ for (ps = DEFAULT_PATH; ps; ps = pe ? pe+1 : NULL) {
+ pe = strchr(ps, ':');
+ if (*ps == '/') {
+ s = pbuf;
+ if (pe) {
+ if (pe - ps >= plen)
+ continue;
+ struncpy(&s, ps, pe-ps);
+ } else {
+ if (strlen(ps) >= plen)
+ continue;
+ strucpy(&s, ps);
+ }
+ *s++ = '/';
+ if ((s - pbuf) + strlen(cmd) >= plen)
+ continue;
+ strucpy(&s, cmd);
+ if (iscom(pbuf))
+ return pbuf;
+ }
+ }
+ return NULL;
+}
+
+/* execute an external command */
+
+/**/
+static void
+execute(LinkList args, int flags, int defpath)
+{
+ Cmdnam cn;
+ char buf[MAXCMDLEN+1], buf2[MAXCMDLEN+1];
+ char *s, *z, *arg0;
+ char **argv, **pp, **newenvp = NULL;
+ int eno = 0, ee;
+
+ arg0 = (char *) peekfirst(args);
+ if (isset(RESTRICTED) && (strchr(arg0, '/') || defpath)) {
+ zerr("%s: restricted", arg0);
+ _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 ((s = STTYval) && isatty(0) && (GETPGRP() == getpid())) {
+ char *t = tricat("stty", " ", s);
+
+ STTYval = 0; /* this prevents infinite recursion */
+ zsfree(s);
+ execstring(t, 1, 0, "stty");
+ zsfree(t);
+ } else if (s) {
+ STTYval = 0;
+ zsfree(s);
+ }
+
+ /* 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));
+ /*
+ * Note we don't do anything with the parameter structure
+ * for ARGV0: that's OK since we're about to exec or exit
+ * on failure.
+ */
+#ifdef USE_SET_UNSET_ENV
+ unsetenv("ARGV0");
+#else
+ delenvvalue(z - 6);
+#endif
+ } else if (flags & BINF_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);
+ if (flags & BINF_CLEARENV)
+ newenvp = blank_env;
+
+ /*
+ * Note that we don't close fd's attached to process substitution
+ * here, which should be visible to external processes.
+ */
+ closem(FDT_XTRACE, 0);
+#ifndef FD_CLOEXEC
+ if (SHTTY != -1) {
+ close(SHTTY);
+ SHTTY = -1;
+ }
+#endif
+ child_unblock();
+ if ((int) strlen(arg0) >= PATH_MAX) {
+ zerr("command too long: %s", arg0);
+ _exit(1);
+ }
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ int lerrno = zexecve(arg0, argv, newenvp);
+ if (arg0 == s || unset(PATHDIRS) ||
+ (arg0[0] == '.' && (arg0 + 1 == s ||
+ (arg0[1] == '.' && arg0 + 2 == s)))) {
+ zerr("%e: %s", lerrno, arg0);
+ _exit((lerrno == EACCES || lerrno == ENOEXEC) ? 126 : 127);
+ }
+ break;
+ }
+
+ /* for command -p, search the default path */
+ if (defpath) {
+ char pbuf[PATH_MAX+1];
+ char *dptr;
+
+ if (!search_defpath(arg0, pbuf, PATH_MAX)) {
+ if (commandnotfound(arg0, args) == 0)
+ _exit(lastval);
+ zerr("command not found: %s", arg0);
+ _exit(127);
+ }
+
+ ee = zexecve(pbuf, argv, newenvp);
+
+ if ((dptr = strrchr(pbuf, '/')))
+ *dptr = '\0';
+ if (isgooderr(ee, *pbuf ? pbuf : "/"))
+ eno = ee;
+
+ } else {
+
+ if ((cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0))) {
+ char nn[PATH_MAX+1], *dptr;
+
+ if (cn->node.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, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else if (**pp != '/') {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->node.nam);
+ }
+ ee = zexecve(nn, argv, newenvp);
+
+ 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, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ }
+
+ if (eno)
+ zerr("%e: %s", eno, arg0);
+ else if (commandnotfound(arg0, args) == 0)
+ _exit(lastval);
+ else
+ zerr("command not found: %s", arg0);
+ _exit((eno == EACCES || eno == ENOEXEC) ? 126 : 127);
+}
+
+#define RET_IF_COM(X) { if (iscom(X)) return docopy ? dupstring(X) : arg0; }
+
+/*
+ * Get the full pathname of an external command.
+ * If the second argument is zero, return the first argument if found;
+ * if non-zero, return the path using heap memory. (RET_IF_COM(X),
+ * above).
+ * If the third argument is non-zero, use the system default path
+ * instead of the current path.
+ */
+
+/**/
+mod_export char *
+findcmd(char *arg0, int docopy, int default_path)
+{
+ char **pp;
+ char *z, *s, buf[MAXCMDLEN];
+ Cmdnam cn;
+
+ if (default_path)
+ {
+ if (search_defpath(arg0, buf, MAXCMDLEN))
+ return docopy ? dupstring(buf) : arg0;
+ return NULL;
+ }
+ cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+ if (!cn && isset(HASHCMDS) && !isrelative(arg0))
+ cn = hashcmd(arg0, path);
+ if ((int) strlen(arg0) > PATH_MAX)
+ return NULL;
+ if ((s = strchr(arg0, '/'))) {
+ RET_IF_COM(arg0);
+ if (arg0 == s || unset(PATHDIRS) || !strncmp(arg0, "./", 2) ||
+ !strncmp(arg0, "../", 3)) {
+ return NULL;
+ }
+ }
+ if (cn) {
+ char nn[PATH_MAX+1];
+
+ if (cn->node.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);
+ RET_IF_COM(buf);
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->node.nam);
+ }
+ RET_IF_COM(nn);
+ }
+ for (pp = path; *pp; pp++) {
+ z = buf;
+ if (**pp) {
+ strucpy(&z, *pp);
+ *z++ = '/';
+ }
+ strcpy(z, arg0);
+ RET_IF_COM(buf);
+ }
+ return NULL;
+}
+
+/*
+ * Return TRUE if the given path denotes an executable regular file, or a
+ * symlink to one.
+ */
+
+/**/
+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];
+
+ if (cn->node.flags & HASHED)
+ strcpy(fullnam, cn->u.cmd);
+ else if (!cn->u.name)
+ return 0;
+ else {
+ strcpy(fullnam, *(cn->u.name));
+ strcat(fullnam, "/");
+ strcat(fullnam, cn->node.nam);
+ }
+ return iscom(fullnam);
+}
+
+/*
+ * Return TRUE if the given path contains a dot or dot-dot component
+ * and does not start with a slash.
+ */
+
+/**/
+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;
+}
+
+/**/
+mod_export Cmdnam
+hashcmd(char *arg0, char **pp)
+{
+ Cmdnam cn;
+ char *s, buf[PATH_MAX+1];
+ char **pq;
+
+ for (; *pp; pp++)
+ if (**pp == '/') {
+ s = buf;
+ struncpy(&s, *pp, PATH_MAX);
+ *s++ = '/';
+ if ((s - buf) + strlen(arg0) >= PATH_MAX)
+ continue;
+ strcpy(s, arg0);
+ if (iscom(buf))
+ break;
+ }
+
+ if (!*pp)
+ return NULL;
+
+ cn = (Cmdnam) zshcalloc(sizeof *cn);
+ cn->node.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;
+}
+
+/**/
+int
+forklevel;
+
+/* Arguments to entersubsh() */
+enum {
+ /* Subshell is to be run asynchronously (else synchronously) */
+ ESUB_ASYNC = 0x01,
+ /*
+ * Perform process group and tty handling and clear the
+ * (real) job table, since it won't be any longer valid
+ */
+ ESUB_PGRP = 0x02,
+ /* Don't unset traps */
+ ESUB_KEEPTRAP = 0x04,
+ /* This is only a fake entry to a subshell */
+ ESUB_FAKE = 0x08,
+ /* Release the process group if pid is the shell's process group */
+ ESUB_REVERTPGRP = 0x10,
+ /* Don't handle the MONITOR option even if previously set */
+ ESUB_NOMONITOR = 0x20,
+ /* This is a subshell where job control is allowed */
+ ESUB_JOB_CONTROL = 0x40
+};
+
+/**/
+static void
+entersubsh(int flags)
+{
+ int i, sig, monitor, job_control_ok;
+
+ if (!(flags & ESUB_KEEPTRAP))
+ for (sig = 0; sig < SIGCOUNT; sig++)
+ if (!(sigtrapped[sig] & ZSIG_FUNC))
+ unsettrap(sig);
+ monitor = isset(MONITOR);
+ job_control_ok = monitor && (flags & ESUB_JOB_CONTROL) && isset(POSIXJOBS);
+ if (flags & ESUB_NOMONITOR)
+ opts[MONITOR] = 0;
+ if (!isset(MONITOR)) {
+ if (flags & ESUB_ASYNC) {
+ settrap(SIGINT, NULL, 0);
+ settrap(SIGQUIT, NULL, 0);
+ if (isatty(0)) {
+ close(0);
+ if (open("/dev/null", O_RDWR | O_NOCTTY)) {
+ zerr("can't open /dev/null: %e", errno);
+ _exit(1);
+ }
+ }
+ }
+ } else if (thisjob != -1 && (flags & ESUB_PGRP)) {
+ if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
+ if (setpgrp(0L, jobtab[list_pipe_job].gleader) == -1 ||
+ killpg(jobtab[list_pipe_job].gleader, 0) == -1) {
+ jobtab[list_pipe_job].gleader =
+ jobtab[thisjob].gleader = (list_pipe_child ? mypgrp : getpid());
+ setpgrp(0L, jobtab[list_pipe_job].gleader);
+ if (!(flags & ESUB_ASYNC))
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ else if (!jobtab[thisjob].gleader ||
+ setpgrp(0L, jobtab[thisjob].gleader) == -1) {
+ /*
+ * This is the standard point at which a newly started
+ * process gets put into the foreground by taking over
+ * the terminal. Note that in normal circumstances we do
+ * this only from the process itself. This only works if
+ * we are still ignoring SIGTTOU at this point; in this
+ * case ignoring the signal has the special effect that
+ * the operation is allowed to work (in addition to not
+ * causing the shell to be suspended).
+ */
+ 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 (!(flags & ESUB_ASYNC))
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ if (!(flags & ESUB_FAKE))
+ subsh = 1;
+ /*
+ * Increment the visible parameter ZSH_SUBSHELL even if this
+ * is a fake subshell because we are exec'ing at the end.
+ * Logically this should be equivalent to a real subshell so
+ * we don't hang out the dirty washing.
+ */
+ zsh_subshell++;
+ if ((flags & ESUB_REVERTPGRP) && getpid() == mypgrp)
+ release_pgrp();
+ shout = NULL;
+ if (flags & ESUB_NOMONITOR) {
+ /*
+ * Allowing any form of interactive signalling here is
+ * actively harmful as we are in a context where there is no
+ * control over the process.
+ */
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTTIN);
+ signal_ignore(SIGTSTP);
+ } else if (!job_control_ok) {
+ /*
+ * If this process is not going to be doing job control,
+ * we don't want to do special things with the corresponding
+ * signals. If it is, we need to keep the special behaviour:
+ * see note about attachtty() above.
+ */
+ signal_default(SIGTTOU);
+ signal_default(SIGTTIN);
+ signal_default(SIGTSTP);
+ }
+ if (interact) {
+ signal_default(SIGTERM);
+ if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
+ signal_default(SIGINT);
+ if (!(sigtrapped[SIGPIPE]))
+ signal_default(SIGPIPE);
+ }
+ if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
+ signal_default(SIGQUIT);
+ /*
+ * sigtrapped[sig] == ZSIG_IGNORED for signals that remain ignored,
+ * but other trapped signals are temporarily blocked when intrap,
+ * and must be unblocked before continuing into the subshell. This
+ * is orthogonal to what the default handler for the signal may be.
+ *
+ * Start loop at 1 because 0 is SIGEXIT
+ */
+ if (intrap)
+ for (sig = 1; sig < SIGCOUNT; sig++)
+ if (sigtrapped[sig] && sigtrapped[sig] != ZSIG_IGNORED)
+ signal_unblock(signal_mask(sig));
+ if (!job_control_ok)
+ opts[MONITOR] = 0;
+ opts[USEZLE] = 0;
+ zleactive = 0;
+ /*
+ * If we've saved fd's for later restoring, we're never going
+ * to restore them now, so just close them.
+ */
+ for (i = 10; i <= max_zsh_fd; i++) {
+ if (fdtable[i] & FDT_SAVED_MASK)
+ zclose(i);
+ }
+ if (flags & ESUB_PGRP)
+ clearjobtab(monitor);
+ get_usage();
+ forklevel = locallevel;
+}
+
+/* execute a string */
+
+/**/
+mod_export void
+execstring(char *s, int dont_change_job, int exiting, char *context)
+{
+ Eprog prog;
+
+ pushheap();
+ if (isset(VERBOSE)) {
+ zputs(s, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+ if ((prog = parse_string(s, 0)))
+ execode(prog, dont_change_job, exiting, context);
+ popheap();
+}
+
+/**/
+mod_export void
+execode(Eprog p, int dont_change_job, int exiting, char *context)
+{
+ struct estate s;
+ static int zsh_eval_context_len;
+ int alen;
+
+ if (!zsh_eval_context_len) {
+ zsh_eval_context_len = 16;
+ alen = 0;
+ zsh_eval_context = (char **)zalloc(zsh_eval_context_len *
+ sizeof(*zsh_eval_context));
+ } else {
+ alen = arrlen(zsh_eval_context);
+ if (zsh_eval_context_len == alen + 1) {
+ zsh_eval_context_len *= 2;
+ zsh_eval_context = zrealloc(zsh_eval_context,
+ zsh_eval_context_len *
+ sizeof(*zsh_eval_context));
+ }
+ }
+ zsh_eval_context[alen] = context;
+ zsh_eval_context[alen+1] = NULL;
+
+ s.prog = p;
+ s.pc = p->prog;
+ s.strs = p->strs;
+ useeprog(p); /* Mark as in use */
+
+ execlist(&s, dont_change_job, exiting);
+
+ freeeprog(p); /* Free if now unused */
+
+ /*
+ * zsh_eval_context may have been altered by a recursive
+ * call, but that's OK since we're using the global value.
+ */
+ zsh_eval_context[alen] = NULL;
+}
+
+/* Execute a simplified command. This is used to execute things that
+ * will run completely in the shell, so that we can by-pass all that
+ * nasty job-handling and redirection stuff in execpline and execcmd. */
+
+/**/
+static int
+execsimple(Estate state)
+{
+ wordcode code = *state->pc++;
+ int lv, otj;
+
+ if (errflag)
+ return (lastval = 1);
+
+ if (!isset(EXECOPT))
+ return lastval = 0;
+
+ /* In evaluated traps, don't modify the line number. */
+ if (!IN_EVAL_TRAP() && !ineval && code)
+ lineno = code - 1;
+
+ code = wc_code(*state->pc++);
+
+ /*
+ * Because we're bypassing job control, ensure the called
+ * code doesn't see the current job.
+ */
+ otj = thisjob;
+ thisjob = -1;
+
+ if (code == WC_ASSIGN) {
+ cmdoutval = 0;
+ addvars(state, state->pc - 1, 0);
+ setunderscore("");
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ lv = (errflag ? errflag : cmdoutval);
+ } else {
+ int q = queue_signal_level();
+ dont_queue_signals();
+ if (code == WC_FUNCDEF)
+ lv = execfuncdef(state, NULL);
+ else
+ lv = (execfuncs[code - WC_CURSH])(state, 0);
+ restore_queue_signals(q);
+ }
+
+ thisjob = otj;
+
+ return lastval = lv;
+}
+
+/* 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(Estate state, int dont_change_job, int exiting)
+{
+ static int donetrap;
+ Wordcode next;
+ wordcode code;
+ int ret, cj, csp, ltype;
+ int old_pline_level, old_list_pipe, old_list_pipe_job;
+ char *old_list_pipe_text;
+ zlong oldlineno;
+ /*
+ * ERREXIT only forces the shell to exit if the last command in a &&
+ * or || fails. This is the case even if an earlier command is a
+ * shell function or other current shell structure, so we have to set
+ * noerrexit here if the sublist is not of type END.
+ */
+ int oldnoerrexit = noerrexit;
+
+ queue_signals();
+
+ cj = thisjob;
+ old_pline_level = pline_level;
+ old_list_pipe = list_pipe;
+ old_list_pipe_job = list_pipe_job;
+ if (*list_pipe_text)
+ old_list_pipe_text = ztrdup(list_pipe_text);
+ else
+ old_list_pipe_text = NULL;
+ oldlineno = lineno;
+
+ if (sourcelevel && unset(SHINSTDIN)) {
+ pline_level = list_pipe = list_pipe_job = 0;
+ *list_pipe_text = '\0';
+ }
+
+ /* Loop over all sets of comands separated by newline, *
+ * semi-colon or ampersand (`sublists'). */
+ code = *state->pc++;
+ if (wc_code(code) != WC_LIST) {
+ /* Empty list; this returns status zero. */
+ lastval = 0;
+ }
+ while (wc_code(code) == WC_LIST && !breaks && !retflag && !errflag) {
+ int donedebug;
+ int this_donetrap = 0;
+ this_noerrexit = 0;
+
+ ltype = WC_LIST_TYPE(code);
+ csp = cmdsp;
+
+ if (!IN_EVAL_TRAP() && !ineval) {
+ /*
+ * Ensure we have a valid line number for debugging,
+ * unless we are in an evaluated trap in which case
+ * we retain the line number from the context.
+ * This was added for DEBUGBEFORECMD but I've made
+ * it unconditional to keep dependencies to a minimum.
+ *
+ * The line number is updated for individual pipelines.
+ * This isn't necessary for debug traps since they only
+ * run once per sublist.
+ */
+ wordcode code2 = *state->pc, lnp1 = 0;
+ if (ltype & Z_SIMPLE) {
+ lnp1 = code2;
+ } else if (wc_code(code2) == WC_SUBLIST) {
+ if (WC_SUBLIST_FLAGS(code2) == WC_SUBLIST_SIMPLE)
+ lnp1 = state->pc[1];
+ else
+ lnp1 = WC_PIPE_LINENO(state->pc[1]);
+ }
+ if (lnp1)
+ lineno = lnp1 - 1;
+ }
+
+ if (sigtrapped[SIGDEBUG] && isset(DEBUGBEFORECMD) && !intrap) {
+ Wordcode pc2 = state->pc;
+ int oerrexit_opt = opts[ERREXIT];
+ Param pm;
+ opts[ERREXIT] = 0;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ if (ltype & Z_SIMPLE) /* skip the line number */
+ pc2++;
+ pm = assignsparam("ZSH_DEBUG_CMD",
+ getpermtext(state->prog, pc2, 0),
+ 0);
+
+ exiting = donetrap;
+ ret = lastval;
+ dotrap(SIGDEBUG);
+ if (!retflag)
+ lastval = ret;
+ donetrap = exiting;
+ noerrexit = oldnoerrexit;
+ /*
+ * Only execute the trap once per sublist, even
+ * if the DEBUGBEFORECMD option changes.
+ */
+ donedebug = isset(ERREXIT) ? 2 : 1;
+ opts[ERREXIT] = oerrexit_opt;
+ if (pm)
+ unsetparam_pm(pm, 0, 1);
+ } else
+ donedebug = intrap ? 1 : 0;
+
+ /* Reset donetrap: this ensures that a trap is only *
+ * called once for each sublist that fails. */
+ donetrap = 0;
+ if (ltype & Z_SIMPLE) {
+ next = state->pc + WC_LIST_SKIP(code);
+ if (donedebug != 2)
+ execsimple(state);
+ state->pc = next;
+ goto sublist_done;
+ }
+
+ /* Loop through code followed by &&, ||, or end of sublist. */
+ code = *state->pc++;
+ if (donedebug == 2) {
+ /* Skip sublist. */
+ while (wc_code(code) == WC_SUBLIST) {
+ state->pc = state->pc + WC_SUBLIST_SKIP(code);
+ if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END)
+ break;
+ code = *state->pc++;
+ }
+ donetrap = 1;
+ /* yucky but consistent... */
+ goto sublist_done;
+ }
+ while (wc_code(code) == WC_SUBLIST) {
+ int isend = (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END);
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ if (!oldnoerrexit)
+ noerrexit = isend ? 0 : NOERREXIT_EXIT | NOERREXIT_RETURN;
+ if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_NOT) {
+ /* suppress errexit for "! this_command" */
+ if (isend)
+ this_noerrexit = 1;
+ /* suppress errexit for ! <list-of-shell-commands> */
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ }
+ switch (WC_SUBLIST_TYPE(code)) {
+ case WC_SUBLIST_END:
+ /* End of sublist; just execute, ignoring status. */
+ if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE)
+ execsimple(state);
+ else
+ execpline(state, code, ltype, (ltype & Z_END) && exiting);
+ state->pc = next;
+ goto sublist_done;
+ break;
+ case WC_SUBLIST_AND:
+ /* If the return code is non-zero, we skip pipelines until *
+ * we find a sublist followed by ORNEXT. */
+ if ((ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+ execsimple(state) :
+ execpline(state, code, Z_SYNC, 0)))) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ while (wc_code(code) == WC_SUBLIST &&
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_AND) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ }
+ if (wc_code(code) != WC_SUBLIST) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ this_donetrap = 1;
+ goto sublist_done;
+ } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
+ this_donetrap = 1;
+ /*
+ * Treat this in the same way as if we reached
+ * the end of the sublist normally.
+ */
+ state->pc = next;
+ goto sublist_done;
+ }
+ }
+ cmdpush(CS_CMDAND);
+ break;
+ case WC_SUBLIST_OR:
+ /* If the return code is zero, we skip pipelines until *
+ * we find a sublist followed by ANDNEXT. */
+ if (!(ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+ execsimple(state) :
+ execpline(state, code, Z_SYNC, 0)))) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ while (wc_code(code) == WC_SUBLIST &&
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ }
+ if (wc_code(code) != WC_SUBLIST) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ this_donetrap = 1;
+ goto sublist_done;
+ } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
+ this_donetrap = 1;
+ /*
+ * Treat this in the same way as if we reached
+ * the end of the sublist normally.
+ */
+ state->pc = next;
+ goto sublist_done;
+ }
+ }
+ cmdpush(CS_CMDOR);
+ break;
+ }
+ state->pc = next;
+ code = *state->pc++;
+ }
+ state->pc--;
+sublist_done:
+
+ /*
+ * See hairy code near the end of execif() for the
+ * following. "noerrexit " only applies until
+ * we hit execcmd on the way down. We're now
+ * on the way back up, so don't restore it.
+ */
+ if (!(oldnoerrexit & NOERREXIT_UNTIL_EXEC))
+ noerrexit = oldnoerrexit;
+
+ if (sigtrapped[SIGDEBUG] && !isset(DEBUGBEFORECMD) && !donedebug) {
+ /*
+ * Save and restore ERREXIT for consistency with
+ * DEBUGBEFORECMD, even though it's not used.
+ */
+ int oerrexit_opt = opts[ERREXIT];
+ opts[ERREXIT] = 0;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ exiting = donetrap;
+ ret = lastval;
+ dotrap(SIGDEBUG);
+ if (!retflag)
+ lastval = ret;
+ donetrap = exiting;
+ noerrexit = oldnoerrexit;
+ opts[ERREXIT] = oerrexit_opt;
+ }
+
+ cmdsp = csp;
+
+ /* Check whether we are suppressing traps/errexit *
+ * (typically in init scripts) and if we haven't *
+ * already performed them for this sublist. */
+ if (!this_noerrexit && !donetrap && !this_donetrap) {
+ if (sigtrapped[SIGZERR] && lastval &&
+ !(noerrexit & NOERREXIT_EXIT)) {
+ dotrap(SIGZERR);
+ donetrap = 1;
+ }
+ if (lastval) {
+ int errreturn = isset(ERRRETURN) &&
+ (isset(INTERACTIVE) || locallevel || sourcelevel) &&
+ !(noerrexit & NOERREXIT_RETURN);
+ int errexit = (isset(ERREXIT) ||
+ (isset(ERRRETURN) && !errreturn)) &&
+ !(noerrexit & NOERREXIT_EXIT);
+ if (errexit) {
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ if (mypid != getpid())
+ _exit(lastval);
+ else
+ exit(lastval);
+ }
+ if (errreturn) {
+ retflag = 1;
+ breaks = loops;
+ }
+ }
+ }
+ if (ltype & Z_END)
+ break;
+ code = *state->pc++;
+ }
+ pline_level = old_pline_level;
+ list_pipe = old_list_pipe;
+ list_pipe_job = old_list_pipe_job;
+ if (old_list_pipe_text) {
+ strcpy(list_pipe_text, old_list_pipe_text);
+ zsfree(old_list_pipe_text);
+ } else {
+ *list_pipe_text = '\0';
+ }
+ lineno = oldlineno;
+ if (dont_change_job)
+ thisjob = cj;
+
+ if (exiting && sigtrapped[SIGEXIT]) {
+ dotrap(SIGEXIT);
+ /* Make sure this doesn't get executed again. */
+ sigtrapped[SIGEXIT] = 0;
+ }
+
+ unqueue_signals();
+}
+
+/* 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(Estate state, wordcode slcode, int how, int last1)
+{
+ int ipipe[2], opipe[2];
+ int pj, newjob;
+ int old_simple_pline = simple_pline;
+ int slflags = WC_SUBLIST_FLAGS(slcode);
+ wordcode code = *state->pc++;
+ static int lastwj, lpforked;
+
+ if (wc_code(code) != WC_PIPE)
+ return lastval = (slflags & WC_SUBLIST_NOT) != 0;
+ else if (slflags & WC_SUBLIST_NOT)
+ last1 = 0;
+
+ /* If trap handlers are allowed to run here, they may start another
+ * external job in the middle of us starting this one, which can
+ * result in jobs being reaped before their job table entries have
+ * been initialized, which in turn leads to waiting forever for
+ * jobs that no longer exist. So don't do that.
+ */
+ queue_signals();
+
+ pj = thisjob;
+ ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
+ child_block();
+
+ /*
+ * Get free entry in job table and initialize it. This is currently
+ * the only call to initjob() (apart from a minor exception in
+ * clearjobtab()), so this is also the only place where we can
+ * expand the job table under us.
+ */
+ if ((thisjob = newjob = initjob()) == -1) {
+ child_unblock();
+ unqueue_signals();
+ return 1;
+ }
+ if (how & Z_TIMED)
+ jobtab[thisjob].stat |= STAT_TIMED;
+
+ if (slflags & WC_SUBLIST_COPROC) {
+ how = Z_ASYNC;
+ if (coprocin >= 0) {
+ zclose(coprocin);
+ zclose(coprocout);
+ }
+ if (mpipe(ipipe) < 0) {
+ coprocin = coprocout = -1;
+ slflags &= ~WC_SUBLIST_COPROC;
+ } else if (mpipe(opipe) < 0) {
+ close(ipipe[0]);
+ close(ipipe[1]);
+ coprocin = coprocout = -1;
+ slflags &= ~WC_SUBLIST_COPROC;
+ } else {
+ coprocin = ipipe[0];
+ coprocout = opipe[1];
+ fdtable[coprocin] = fdtable[coprocout] = FDT_UNUSED;
+ }
+ }
+ /* This used to set list_pipe_pid=0 unconditionally, but in things
+ * like `ls|if true; then sleep 20; cat; fi' where the sleep was
+ * stopped, the top-level execpline() didn't get the pid for the
+ * sub-shell because it was overwritten. */
+ if (!pline_level++) {
+ list_pipe_pid = 0;
+ nowait = 0;
+ simple_pline = (WC_PIPE_TYPE(code) == WC_PIPE_END);
+ list_pipe_job = newjob;
+ }
+ lastwj = lpforked = 0;
+ execpline2(state, code, how, opipe[0], ipipe[1], last1);
+ pline_level--;
+ if (how & Z_ASYNC) {
+ lastwj = newjob;
+
+ if (thisjob == list_pipe_job)
+ list_pipe_job = 0;
+ jobtab[thisjob].stat |= STAT_NOSTTY;
+ if (slflags & WC_SUBLIST_COPROC) {
+ zclose(ipipe[1]);
+ zclose(opipe[0]);
+ }
+ if (how & Z_DISOWN) {
+ pipecleanfilelist(jobtab[thisjob].filelist, 0);
+ deletejob(jobtab + thisjob, 1);
+ thisjob = -1;
+ }
+ else
+ spawnjob();
+ child_unblock();
+ unqueue_signals();
+ /* Executing background code resets shell status */
+ return lastval = 0;
+ } else {
+ if (newjob != lastwj) {
+ Job jn = jobtab + newjob;
+ int updated;
+
+ if (newjob == list_pipe_job && list_pipe_child)
+ _exit(0);
+
+ lastwj = thisjob = newjob;
+
+ if (list_pipe || (pline_level && !(how & Z_TIMED)))
+ jn->stat |= STAT_NOPRINT;
+
+ if (nowait) {
+ if(!pline_level) {
+ int jobsub;
+ struct process *pn, *qn;
+
+ curjob = newjob;
+ DPUTS(!list_pipe_pid, "invalid list_pipe_pid");
+ addproc(list_pipe_pid, list_pipe_text, 0,
+ &list_pipe_start);
+
+ /* If the super-job contains only the sub-shell, the
+ sub-shell is the group leader. */
+ if (!jn->procs->next || lpforked == 2) {
+ jn->gleader = list_pipe_pid;
+ jn->stat |= STAT_SUBLEADER;
+ /*
+ * Pick up any subjob that's still lying around
+ * as it's now our responsibility.
+ * If we find it we're a SUPERJOB.
+ */
+ for (jobsub = 1; jobsub <= maxjob; jobsub++) {
+ Job jnsub = jobtab + jobsub;
+ if (jnsub->stat & STAT_SUBJOB_ORPHANED) {
+ jn->other = jobsub;
+ jn->stat |= STAT_SUPERJOB;
+ jnsub->stat &= ~STAT_SUBJOB_ORPHANED;
+ jnsub->other = list_pipe_pid;
+ }
+ }
+ }
+ 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 | STAT_LOCKED |
+ STAT_INUSE;
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ }
+ else if (newjob != list_pipe_job)
+ deletejob(jn, 0);
+ else
+ lastwj = -1;
+ }
+
+ errbrk_saved = 0;
+ for (; !nowait;) {
+ if (list_pipe_child) {
+ jn->stat |= STAT_NOPRINT;
+ makerunning(jn);
+ }
+ if (!(jn->stat & STAT_LOCKED)) {
+ updated = hasprocs(thisjob);
+ waitjobs(); /* deals with signal queue */
+ child_block();
+ } else
+ updated = 0;
+ if (!updated &&
+ list_pipe_job && hasprocs(list_pipe_job) &&
+ !(jobtab[list_pipe_job].stat & STAT_STOPPED)) {
+ int q = queue_signal_level();
+ child_unblock();
+ child_block();
+ dont_queue_signals();
+ restore_queue_signals(q);
+ }
+ if (list_pipe_child &&
+ jn->stat & STAT_DONE &&
+ lastval2 & 0200)
+ killpg(mypgrp, lastval2 & ~0200);
+ if (!list_pipe_child && !lpforked && !subsh && jobbing &&
+ (list_pipe || last1 || pline_level) &&
+ ((jn->stat & STAT_STOPPED) ||
+ (list_pipe_job && pline_level &&
+ (jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
+ pid_t pid = 0;
+ int synch[2];
+ struct timeval bgtime;
+
+ /*
+ * A pipeline with the shell handling the right
+ * hand side was stopped. We'll fork to allow
+ * it to continue.
+ */
+ if (pipe(synch) < 0 || (pid = zfork(&bgtime)) == -1) {
+ /* Failure */
+ if (pid < 0) {
+ close(synch[0]);
+ close(synch[1]);
+ } else
+ zerr("pipe failed: %e", errno);
+ zleentry(ZLE_CMD_TRASH);
+ fprintf(stderr, "zsh: job can't be suspended\n");
+ fflush(stderr);
+ makerunning(jn);
+ killjb(jn, SIGCONT);
+ thisjob = newjob;
+ }
+ else if (pid) {
+ /*
+ * Parent: job control is here. If the job
+ * started for the RHS of the pipeline is still
+ * around, then its a SUBJOB and the job for
+ * earlier parts of the pipeeline is its SUPERJOB.
+ * The newly forked shell isn't recorded as a
+ * separate job here, just as list_pipe_pid.
+ * If the superjob exits (it may already have
+ * done so, see child branch below), we'll use
+ * list_pipe_pid to form the basis of a
+ * replacement job --- see SUBLEADER code above.
+ */
+ char dummy;
+
+ lpforked =
+ (killpg(jobtab[list_pipe_job].gleader, 0) == -1 ? 2 : 1);
+ list_pipe_pid = pid;
+ list_pipe_start = bgtime;
+ nowait = 1;
+ errflag |= ERRFLAG_ERROR;
+ breaks = loops;
+ close(synch[1]);
+ read_loop(synch[0], &dummy, 1);
+ close(synch[0]);
+ /* If this job has finished, we leave it as a
+ * normal (non-super-) job. */
+ if (!(jn->stat & STAT_DONE)) {
+ jobtab[list_pipe_job].other = newjob;
+ jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
+ jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
+ jn->other = list_pipe_pid; /* see zsh.h */
+ if (hasprocs(list_pipe_job))
+ jn->gleader = jobtab[list_pipe_job].gleader;
+ }
+ if ((list_pipe || last1) && hasprocs(list_pipe_job))
+ killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
+ break;
+ }
+ else {
+ close(synch[0]);
+ entersubsh(ESUB_ASYNC);
+ /*
+ * At this point, we used to attach this process
+ * to the process group of list_pipe_job (the
+ * new superjob) any time that was still available.
+ * That caused problems in at least two
+ * cases because this forked shell was then
+ * suspended with the right hand side of the
+ * pipeline, and the SIGSTOP below suspended
+ * it a second time when it was continued.
+ *
+ * It's therefore not clear entirely why you'd ever
+ * do anything other than the following, but no
+ * doubt we'll find out...
+ */
+ setpgrp(0L, mypgrp = getpid());
+ close(synch[1]);
+ kill(getpid(), SIGSTOP);
+ list_pipe = 0;
+ list_pipe_child = 1;
+ opts[INTERACTIVE] = 0;
+ if (errbrk_saved) {
+ /*
+ * Keep any user interrupt bit in errflag.
+ */
+ errflag = prev_errflag | (errflag & ERRFLAG_INT);
+ breaks = prev_breaks;
+ }
+ break;
+ }
+ }
+ else if (subsh && jn->stat & STAT_STOPPED)
+ thisjob = newjob;
+ else
+ break;
+ }
+ child_unblock();
+ unqueue_signals();
+
+ if (list_pipe && (lastval & 0200) && pj >= 0 &&
+ (!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
+ deletejob(jn, 0);
+ jn = jobtab + pj;
+ if (jn->gleader)
+ killjb(jn, lastval & ~0200);
+ }
+ if (list_pipe_child ||
+ ((jn->stat & STAT_DONE) &&
+ (list_pipe || (pline_level && !(jn->stat & STAT_SUBJOB)))))
+ deletejob(jn, 0);
+ thisjob = pj;
+ }
+ else
+ unqueue_signals();
+ if ((slflags & WC_SUBLIST_NOT) && !errflag)
+ lastval = !lastval;
+ }
+ if (!pline_level)
+ simple_pline = old_simple_pline;
+ return lastval;
+}
+
+/* execute pipeline. This function assumes the `pline' is not NULL. */
+
+/**/
+static void
+execpline2(Estate state, wordcode pcode,
+ int how, int input, int output, int last1)
+{
+ struct execcmd_params eparams;
+
+ if (breaks || retflag)
+ return;
+
+ /* In evaluated traps, don't modify the line number. */
+ if (!IN_EVAL_TRAP() && !ineval && WC_PIPE_LINENO(pcode))
+ lineno = WC_PIPE_LINENO(pcode) - 1;
+
+ if (pline_level == 1) {
+ if ((how & Z_ASYNC) || !sfcontext)
+ strcpy(list_pipe_text,
+ getjobtext(state->prog,
+ state->pc + (WC_PIPE_TYPE(pcode) == WC_PIPE_END ?
+ 0 : 1)));
+ else
+ list_pipe_text[0] = '\0';
+ }
+ if (WC_PIPE_TYPE(pcode) == WC_PIPE_END) {
+ execcmd_analyse(state, &eparams);
+ execcmd_exec(state, &eparams, input, output, how, last1 ? 1 : 2, -1);
+ } else {
+ int pipes[2];
+ int old_list_pipe = list_pipe;
+ Wordcode next = state->pc + (*state->pc);
+
+ ++state->pc;
+ execcmd_analyse(state, &eparams);
+
+ if (mpipe(pipes) < 0) {
+ /* FIXME */
+ }
+
+ addfilelist(NULL, pipes[0]);
+ execcmd_exec(state, &eparams, input, pipes[1], how, 0, pipes[0]);
+ zclose(pipes[1]);
+ state->pc = next;
+
+ /* if another execpline() is invoked because the command is *
+ * a list it must know that we're already in a pipeline */
+ cmdpush(CS_PIPE);
+ list_pipe = 1;
+ execpline2(state, *state->pc++, how, pipes[0], output, last1);
+ list_pipe = old_list_pipe;
+ cmdpop();
+ }
+}
+
+/* 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 **) hcalloc((countlinknodes(list) + 4) *
+ sizeof(char *));
+
+ if (isset(XTRACE)) {
+ if (!doneps4)
+ printprompt4();
+
+ for (node = firstnode(list); node; incnode(node)) {
+ *ptr++ = (char *)getdata(node);
+ quotedzputs(getdata(node), xtrerr);
+ if (nextnode(node))
+ fputc(' ', xtrerr);
+ }
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ } else {
+ for (node = firstnode(list); node; incnode(node))
+ *ptr++ = (char *)getdata(node);
+ }
+ *ptr = NULL;
+ return (argv);
+}
+
+/**/
+mod_export void
+untokenize(char *s)
+{
+ if (*s) {
+ int c;
+
+ while ((c = *s++))
+ if (itok(c)) {
+ char *p = s - 1;
+
+ if (c != Nularg)
+ *p++ = ztokens[c - Pound];
+
+ while ((c = *s++)) {
+ if (itok(c)) {
+ if (c != Nularg)
+ *p++ = ztokens[c - Pound];
+ } else
+ *p++ = c;
+ }
+ *p = '\0';
+ break;
+ }
+ }
+}
+
+
+/*
+ * Given a tokenized string, output it to standard output in
+ * such a way that it's clear which tokens are active.
+ * Hence Star becomes an unquoted "*", while a "*" becomes "\*".
+ *
+ * The code here is a kind of amalgamation of the tests in
+ * zshtokenize() and untokenize() with some outputting.
+ */
+
+/**/
+void
+quote_tokenized_output(char *str, FILE *file)
+{
+ char *s = str;
+
+ for (; *s; s++) {
+ switch (*s) {
+ case Meta:
+ putc(*++s ^ 32, file);
+ continue;
+
+ case Nularg:
+ /* Do nothing. I think. */
+ continue;
+
+ case '\\':
+ case '<':
+ case '>':
+ case '(':
+ case '|':
+ case ')':
+ case '^':
+ case '#':
+ case '~':
+ case '[':
+ case ']':
+ case '*':
+ case '?':
+ case '$':
+ case ' ':
+ putc('\\', file);
+ break;
+
+ case '\t':
+ fputs("$'\\t'", file);
+ continue;
+
+ case '\n':
+ fputs("$'\\n'", file);
+ continue;
+
+ case '\r':
+ fputs("$'\\r'", file);
+ continue;
+
+ case '=':
+ if (s == str)
+ putc('\\', file);
+ break;
+
+ default:
+ if (itok(*s)) {
+ putc(ztokens[*s - Pound], file);
+ continue;
+ }
+ break;
+ }
+
+ putc(*s, file);
+ }
+}
+
+/* Check that we can use a parameter for allocating a file descriptor. */
+
+static int
+checkclobberparam(struct redir *f)
+{
+ struct value vbuf;
+ Value v;
+ char *s = f->varid;
+ int fd;
+
+ if (!s)
+ return 1;
+
+ if (!(v = getvalue(&vbuf, &s, 0)))
+ return 1;
+
+ if (v->pm->node.flags & PM_READONLY) {
+ zwarn("can't allocate file descriptor to readonly parameter %s",
+ f->varid);
+ /* don't flag a system error for this */
+ errno = 0;
+ return 0;
+ }
+
+ /*
+ * We can't clobber the value in the parameter if it's
+ * already an opened file descriptor --- that means it's a decimal
+ * integer corresponding to an opened file descriptor,
+ * not merely an expression that evaluates to a file descriptor.
+ */
+ if (!isset(CLOBBER) && (s = getstrvalue(v)) &&
+ (fd = (int)zstrtol(s, &s, 10)) >= 0 && !*s &&
+ fd <= max_zsh_fd && fdtable[fd] == FDT_EXTERNAL) {
+ zwarn("can't clobber parameter %s containing file descriptor %d",
+ f->varid, fd);
+ /* don't flag a system error for this */
+ errno = 0;
+ return 0;
+ }
+ return 1;
+}
+
+/* Open a file for writing redirection */
+
+/**/
+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, int type)
+{
+ if (fd >= 0 && mfds[fd] && mfds[fd]->ct >= 2) {
+ struct multio *mn = mfds[fd];
+ char buf[TCBUFSIZE];
+ int len, i;
+ pid_t pid;
+ struct timeval bgtime;
+
+ /*
+ * We need to block SIGCHLD in case the process
+ * we are spawning terminates before the job table
+ * is set up to handle it.
+ */
+ child_block();
+ if ((pid = zfork(&bgtime))) {
+ for (i = 0; i < mn->ct; i++)
+ zclose(mn->fds[i]);
+ zclose(mn->pipe);
+ if (pid == -1) {
+ mfds[fd] = NULL;
+ child_unblock();
+ return;
+ }
+ mn->ct = 1;
+ mn->fds[0] = fd;
+ addproc(pid, NULL, 1, &bgtime);
+ child_unblock();
+ return;
+ }
+ /* pid == 0 */
+ child_unblock();
+ closeallelse(mn);
+ if (mn->rflag) {
+ /* tee process */
+ while ((len = read(mn->pipe, buf, TCBUFSIZE)) != 0) {
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ }
+ for (i = 0; i < mn->ct; i++)
+ write_loop(mn->fds[i], buf, len);
+ }
+ } else {
+ /* cat process */
+ for (i = 0; i < mn->ct; i++)
+ while ((len = read(mn->fds[i], buf, TCBUFSIZE)) != 0) {
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ }
+ write_loop(mn->pipe, buf, len);
+ }
+ }
+ _exit(0);
+ } else if (fd >= 0 && type == REDIR_CLOSE)
+ mfds[fd] = NULL;
+}
+
+/* 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;
+ long openmax;
+
+ openmax = fdtable_size;
+
+ for (i = 0; i < openmax; 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.
+ *
+ * If varid is not NULL, we open an fd above 10 and set the parameter
+ * named varid to that value. fd1 is not used.
+ */
+
+/**/
+static void
+addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
+ char *varid)
+{
+ int pipes[2];
+
+ if (varid) {
+ /* fd will be over 10, don't touch mfds */
+ fd1 = movefd(fd2);
+ if (fd1 == -1) {
+ zerr("cannot moved fd %d: %e", fd2, errno);
+ return;
+ } else {
+ fdtable[fd1] = FDT_EXTERNAL;
+ setiparam(varid, (zlong)fd1);
+ /*
+ * If setting the parameter failed, close the fd else
+ * it will leak.
+ */
+ if (errflag)
+ zclose(fd1);
+ }
+ } else if (!mfds[fd1] || unset(MULTIOS)) {
+ if(!mfds[fd1]) { /* starting a new multio */
+ mfds[fd1] = (struct multio *) zhalloc(sizeof(struct multio));
+ if (!forked && save[fd1] == -2) {
+ if (fd1 == fd2)
+ save[fd1] = -1;
+ else {
+ int fdN = movefd(fd1);
+ /*
+ * fd1 may already be closed here, so
+ * ignore bad file descriptor error
+ */
+ if (fdN < 0) {
+ if (errno != EBADF) {
+ zerr("cannot duplicate fd %d: %e", fd1, errno);
+ mfds[fd1] = NULL;
+ closemnodes(mfds);
+ return;
+ }
+ } else {
+ DPUTS(fdtable[fdN] != FDT_INTERNAL,
+ "Saved file descriptor not marked as internal");
+ fdtable[fdN] |= FDT_SAVED_MASK;
+ }
+ save[fd1] = fdN;
+ }
+ }
+ }
+ if (!varid)
+ 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", fd1);
+ closemnodes(mfds);
+ return;
+ }
+ if (mfds[fd1]->ct == 1) { /* split the stream */
+ int fdN = movefd(fd1);
+ if (fdN < 0) {
+ zerr("multio failed for fd %d: %e", fd1, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[0] = fdN;
+ fdN = movefd(fd2);
+ if (fdN < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[1] = fdN;
+ if (mpipe(pipes) < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->pipe = pipes[1 - rflag];
+ redup(pipes[rflag], fd1);
+ mfds[fd1]->ct = 2;
+ } else { /* add another fd to an already split stream */
+ int fdN;
+ 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);
+ }
+ if ((fdN = movefd(fd2)) < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[mfds[fd1]->ct++] = fdN;
+ }
+ }
+}
+
+/**/
+static void
+addvars(Estate state, Wordcode pc, int addflags)
+{
+ LinkList vl;
+ int xtr, isstr, htok = 0;
+ char **arr, **ptr, *name;
+ int flags;
+
+ Wordcode opc = state->pc;
+ wordcode ac;
+ local_list1(svl);
+
+ /*
+ * Warn when creating a global without using typeset -g in a
+ * function. Don't do this if there is a list of variables marked
+ * to be restored after the command, since then the assignment
+ * is implicitly scoped.
+ */
+ flags = !(addflags & ADDVAR_RESTORE) ? ASSPM_WARN : 0;
+ xtr = isset(XTRACE);
+ if (xtr) {
+ printprompt4();
+ doneps4 = 1;
+ }
+ state->pc = pc;
+ while (wc_code(ac = *state->pc++) == WC_ASSIGN) {
+ int myflags = flags;
+ name = ecgetstr(state, EC_DUPTOK, &htok);
+ if (htok)
+ untokenize(name);
+ if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC)
+ myflags |= ASSPM_AUGMENT;
+ if (xtr)
+ fprintf(xtrerr,
+ WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC ? "%s+=" : "%s=", name);
+ if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
+ init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
+ vl = &svl;
+ } else {
+ vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+
+ if (vl && htok) {
+ int prefork_ret = 0;
+ prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
+ PREFORK_ASSIGN), &prefork_ret);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ if (prefork_ret & PREFORK_KEY_VALUE)
+ myflags |= ASSPM_KEY_VALUE;
+ if (!isstr || (isset(GLOBASSIGN) && isstr &&
+ haswilds((char *)getdata(firstnode(vl))))) {
+ globlist(vl, prefork_ret);
+ /* Unset the parameter to force it to be recreated
+ * as either scalar or array depending on how many
+ * matches were found for the glob.
+ */
+ if (isset(GLOBASSIGN) && isstr)
+ unsetparam(name);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+ }
+ if (isstr && (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) {
+ quotedzputs(val, xtrerr);
+ fputc(' ', xtrerr);
+ }
+ if ((addflags & ADDVAR_EXPORT) && !strchr(name, '[')) {
+ if ((addflags & ADDVAR_RESTRICT) && isset(RESTRICTED) &&
+ (pm = (Param) paramtab->removenode(paramtab, name)) &&
+ (pm->node.flags & PM_RESTRICTED)) {
+ zerr("%s: restricted", pm->node.nam);
+ zsfree(val);
+ state->pc = opc;
+ return;
+ }
+ if (strcmp(name, "STTY") == 0) {
+ zsfree(STTYval);
+ STTYval = ztrdup(val);
+ }
+ allexp = opts[ALLEXPORT];
+ opts[ALLEXPORT] = 1;
+ if (isset(KSHARRAYS))
+ unsetparam(name);
+ pm = assignsparam(name, val, myflags);
+ opts[ALLEXPORT] = allexp;
+ } else
+ pm = assignsparam(name, val, myflags);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ continue;
+ }
+ if (vl) {
+ ptr = arr = (char **) zalloc(sizeof(char *) *
+ (countlinknodes(vl) + 1));
+
+ while (nonempty(vl))
+ *ptr++ = ztrdup((char *) ugetnode(vl));
+ } else
+ ptr = arr = (char **) zalloc(sizeof(char *));
+
+ *ptr = NULL;
+ if (xtr) {
+ fprintf(xtrerr, "( ");
+ for (ptr = arr; *ptr; ptr++) {
+ quotedzputs(*ptr, xtrerr);
+ fputc(' ', xtrerr);
+ }
+ fprintf(xtrerr, ") ");
+ }
+ assignaparam(name, arr, myflags);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+ state->pc = opc;
+}
+
+/**/
+void
+setunderscore(char *str)
+{
+ queue_signals();
+ if (str && *str) {
+ int l = strlen(str) + 1, nl = (l + 31) & ~31;
+
+ if (nl > underscorelen || (underscorelen - nl) > 64) {
+ zfree(zunderscore, underscorelen);
+ zunderscore = (char *) zalloc(underscorelen = nl);
+ }
+ strcpy(zunderscore, str);
+ underscoreused = l;
+ } else {
+ if (underscorelen > 128) {
+ zfree(zunderscore, underscorelen);
+ zunderscore = (char *) zalloc(underscorelen = 32);
+ }
+ *zunderscore = '\0';
+ underscoreused = 1;
+ }
+ unqueue_signals();
+}
+
+/* These describe the type of expansions that need to be done on the words
+ * used in the thing we are about to execute. They are set in execcmd() and
+ * used in execsubst() which might be called from one of the functions
+ * called from execcmd() (like execfor() and so on). */
+
+static int esprefork, esglob = 1;
+
+/**/
+void
+execsubst(LinkList strs)
+{
+ if (strs) {
+ prefork(strs, esprefork, NULL);
+ if (esglob && !errflag) {
+ LinkList ostrs = strs;
+ globlist(strs, 0);
+ strs = ostrs;
+ }
+ }
+}
+
+/*
+ * Check if a builtin requires an autoload and if so
+ * deal with it. This may return NULL.
+ */
+
+/**/
+static HashNode
+resolvebuiltin(const char *cmdarg, HashNode hn)
+{
+ if (!((Builtin) hn)->handlerfunc) {
+ char *modname = dupstring(((Builtin) hn)->optstr);
+ /*
+ * Ensure the module is loaded and the
+ * feature corresponding to the builtin
+ * is enabled.
+ */
+ (void)ensurefeature(modname, "b:",
+ (hn->flags & BINF_AUTOALL) ? NULL :
+ hn->nam);
+ hn = builtintab->getnode(builtintab, cmdarg);
+ if (!hn) {
+ lastval = 1;
+ zerr("autoloading module %s failed to define builtin: %s",
+ modname, cmdarg);
+ return NULL;
+ }
+ }
+ return hn;
+}
+
+/*
+ * We are about to execute a command at the lowest level of the
+ * hierarchy. Analyse the parameters from the wordcode.
+ */
+
+/**/
+static void
+execcmd_analyse(Estate state, Execcmd_params eparams)
+{
+ wordcode code;
+ int i;
+
+ eparams->beg = state->pc;
+ eparams->redir =
+ (wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
+ if (wc_code(*state->pc) == WC_ASSIGN) {
+ cmdoutval = 0;
+ eparams->varspc = state->pc;
+ while (wc_code((code = *state->pc)) == WC_ASSIGN)
+ state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(code) + 2);
+ } else
+ eparams->varspc = NULL;
+
+ code = *state->pc++;
+
+ eparams->type = wc_code(code);
+ eparams->postassigns = 0;
+
+ /* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
+ * But for that we would need to check/change all builtins so that
+ * they don't modify their argument strings. */
+ switch (eparams->type) {
+ case WC_SIMPLE:
+ eparams->args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP,
+ &eparams->htok);
+ eparams->assignspc = NULL;
+ break;
+
+ case WC_TYPESET:
+ eparams->args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP,
+ &eparams->htok);
+ eparams->postassigns = *state->pc++;
+ eparams->assignspc = state->pc;
+ for (i = 0; i < eparams->postassigns; i++) {
+ code = *state->pc;
+ DPUTS(wc_code(code) != WC_ASSIGN,
+ "BUG: miscounted typeset assignments");
+ state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(code) + 2);
+ }
+ break;
+
+ default:
+ eparams->args = NULL;
+ eparams->assignspc = NULL;
+ eparams->htok = 0;
+ break;
+ }
+}
+
+/*
+ * Transfer the first node of args to preargs, performing
+ * prefork expansion on the way if necessary.
+ */
+static void execcmd_getargs(LinkList preargs, LinkList args, int expand)
+{
+ if (!firstnode(args)) {
+ return;
+ } else if (expand) {
+ local_list0(svl);
+ init_list0(svl);
+ /* not init_list1, as we need real nodes */
+ addlinknode(&svl, uremnode(args, firstnode(args)));
+ /* Analysing commands, so vanilla options to prefork */
+ prefork(&svl, 0, NULL);
+ joinlists(preargs, &svl);
+ } else {
+ addlinknode(preargs, uremnode(args, firstnode(args)));
+ }
+}
+
+/**/
+static int
+execcmd_fork(Estate state, int how, int type, Wordcode varspc,
+ LinkList *filelistp, char *text, int oautocont,
+ int close_if_forked)
+{
+ pid_t pid;
+ int synch[2], flags;
+ char dummy;
+ struct timeval bgtime;
+
+ child_block();
+
+ if (pipe(synch) < 0) {
+ zerr("pipe failed: %e", errno);
+ return -1;
+ } else if ((pid = zfork(&bgtime)) == -1) {
+ close(synch[0]);
+ close(synch[1]);
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ return -1;
+ }
+ if (pid) {
+ close(synch[1]);
+ read_loop(synch[0], &dummy, 1);
+ close(synch[0]);
+ if (how & Z_ASYNC) {
+ lastpid = (zlong) pid;
+ } else if (!jobtab[thisjob].stty_in_env && varspc) {
+ /* search for STTY=... */
+ Wordcode p = varspc;
+ wordcode ac;
+
+ while (wc_code(ac = *p) == WC_ASSIGN) {
+ if (!strcmp(ecrawstr(state->prog, p + 1, NULL), "STTY")) {
+ jobtab[thisjob].stty_in_env = 1;
+ break;
+ }
+ p += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(ac) + 2);
+ }
+ }
+ addproc(pid, text, 0, &bgtime);
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ pipecleanfilelist(jobtab[thisjob].filelist, 1);
+ return pid;
+ }
+
+ /* pid == 0 */
+ close(synch[0]);
+ flags = ((how & Z_ASYNC) ? ESUB_ASYNC : 0) | ESUB_PGRP;
+ if ((type != WC_SUBSH) && !(how & Z_ASYNC))
+ flags |= ESUB_KEEPTRAP;
+ if (type == WC_SUBSH && !(how & Z_ASYNC))
+ flags |= ESUB_JOB_CONTROL;
+ *filelistp = jobtab[thisjob].filelist;
+ entersubsh(flags);
+ close(synch[1]);
+ zclose(close_if_forked);
+
+ if (sigtrapped[SIGINT] & ZSIG_IGNORED)
+ holdintr();
+ /*
+ * EXIT traps shouldn't be called even if we forked to run
+ * shell code as this isn't the main shell.
+ */
+ sigtrapped[SIGEXIT] = 0;
+#ifdef HAVE_NICE
+ /* Check if we should run background jobs at a lower priority. */
+ if ((how & Z_ASYNC) && isset(BGNICE))
+ if (nice(5) < 0)
+ zwarn("nice(5) failed: %e", errno);
+#endif /* HAVE_NICE */
+
+ return 0;
+}
+
+/*
+ * Execute a command at the lowest level of the hierarchy.
+ */
+
+/**/
+static void
+execcmd_exec(Estate state, Execcmd_params eparams,
+ int input, int output, int how, int last1, int close_if_forked)
+{
+ HashNode hn = NULL;
+ LinkList filelist = NULL;
+ LinkNode node;
+ Redir fn;
+ struct multio *mfds[10];
+ char *text;
+ int save[10];
+ int fil, dfil, is_cursh, do_exec = 0, redir_err = 0, i;
+ int nullexec = 0, magic_assign = 0, forked = 0, old_lastval;
+ int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
+ /* Various flags to the command. */
+ int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
+ FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
+ /*
+ * Retrieve parameters for quick reference (they are unique
+ * to us so we can modify the structure if we want).
+ */
+ LinkList args = eparams->args;
+ LinkList redir = eparams->redir;
+ Wordcode varspc = eparams->varspc;
+ int type = eparams->type;
+ /*
+ * preargs comes from expanding the head of the args list
+ * in order to check for prefix commands.
+ */
+ LinkList preargs;
+
+ doneps4 = 0;
+
+ /*
+ * If assignment but no command get the status from variable
+ * assignment.
+ */
+ old_lastval = lastval;
+ if (!args && varspc)
+ lastval = errflag ? errflag : cmdoutval;
+ /*
+ * If there are arguments, we should reset the status for the
+ * command before execution---unless we are using the result of a
+ * command substitution, which will be indicated by setting
+ * use_cmdoutval to 1. We haven't kicked those off yet, so
+ * there's no race.
+ */
+ use_cmdoutval = !args;
+
+ 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 == WC_SIMPLE || type == WC_TYPESET) && args && nonempty(args) &&
+ *(char *)peekfirst(args) == '%') {
+ if (how & Z_DISOWN) {
+ oautocont = opts[AUTOCONTINUE];
+ opts[AUTOCONTINUE] = 1;
+ }
+ 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 == WC_SIMPLE && (how & Z_SYNC) &&
+ args && nonempty(args) && (!redir || empty(redir)) && !input &&
+ !nextnode(firstnode(args))) {
+ if (unset(NOTIFY))
+ scanjobs();
+ if (findjobnam(peekfirst(args)) != -1)
+ pushnode(args, dupstring("fg"));
+ }
+
+ if ((how & Z_ASYNC) || output) {
+ /*
+ * If running in the background, or not the last command in a
+ * pipeline, we don't need any of the rest of this function to
+ * affect the state in the main shell, so fork immediately.
+ *
+ * In other cases we may need to process the command line
+ * a bit further before we make the decision.
+ */
+ text = getjobtext(state->prog, eparams->beg);
+ switch (execcmd_fork(state, how, type, varspc, &filelist,
+ text, oautocont, close_if_forked)) {
+ case -1:
+ goto fatal;
+ case 0:
+ break;
+ default:
+ return;
+ }
+ last1 = forked = 1;
+ } else
+ text = NULL;
+
+ /* 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 == WC_SIMPLE || type == WC_TYPESET) && args) {
+ /*
+ * preargs contains args that have been expanded by prefork.
+ * Running execcmd_getargs() causes any argument available
+ * in args to be exanded where necessary and transferred to
+ * preargs. We call execcmd_getargs() every time we need to
+ * analyse an argument not available in preargs, though there is
+ * no guarantee a further argument will be available.
+ */
+ preargs = newlinklist();
+ execcmd_getargs(preargs, args, eparams->htok);
+ while (nonempty(preargs)) {
+ char *cmdarg = (char *) peekfirst(preargs);
+ checked = !has_token(cmdarg);
+ if (!checked)
+ break;
+ if (type == WC_TYPESET &&
+ (hn = builtintab->getnode2(builtintab, cmdarg))) {
+ /*
+ * If reserved word for typeset command found (and so
+ * enabled), use regardless of whether builtin is
+ * enabled as we share the implementation.
+ *
+ * Reserved words take precedence over shell functions.
+ */
+ checked = 1;
+ } else if (isset(POSIXBUILTINS) && (cflags & BINF_EXEC)) {
+ /*
+ * POSIX doesn't allow "exec" to operate on builtins
+ * or shell functions.
+ */
+ break;
+ } else {
+ 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;
+ }
+ }
+ orig_cflags |= cflags;
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+
+ /* autoload the builtin if necessary */
+ if (!(hn = resolvebuiltin(cmdarg, hn))) {
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ if (type != WC_TYPESET)
+ magic_assign = (hn->flags & BINF_MAGICEQUALS);
+ break;
+ }
+ checked = 0;
+ /*
+ * We usually don't need the argument containing the
+ * precommand modifier itself. Exception: when "command"
+ * will implemented by a call to "whence", in which case
+ * we'll simply re-insert the argument.
+ */
+ uremnode(preargs, firstnode(preargs));
+ if (!firstnode(preargs)) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ if (!firstnode(preargs))
+ break;
+ }
+ if ((cflags & BINF_COMMAND)) {
+ /*
+ * Check for options to "command".
+ * If just -p, this is handled here: use the default
+ * path to execute.
+ * If -v or -V, possibly with -p, dispatch to bin_whence
+ * but with flag to indicate special handling of -p.
+ * Otherwise, just leave marked as BINF_COMMAND
+ * modifier with no additional action.
+ */
+ LinkNode argnode, oldnode, pnode = NULL;
+ char *argdata, *cmdopt;
+ int has_p = 0, has_vV = 0, has_other = 0;
+ argnode = firstnode(preargs);
+ argdata = (char *) getdata(argnode);
+ while (IS_DASH(*argdata)) {
+ /* Just to be definite, stop on single "-", too, */
+ if (!argdata[1] ||
+ (IS_DASH(argdata[1]) && !argdata[2]))
+ break;
+ for (cmdopt = argdata+1; *cmdopt; cmdopt++) {
+ switch (*cmdopt) {
+ case 'p':
+ /*
+ * If we've got this multiple times (command
+ * -p -p) we'll treat the second -p as a
+ * command because we only remove one below.
+ * Don't think that's a big issue, and it's
+ * also traditional behaviour.
+ */
+ has_p = 1;
+ pnode = argnode;
+ break;
+ case 'v':
+ case 'V':
+ has_vV = 1;
+ break;
+ default:
+ has_other = 1;
+ break;
+ }
+ }
+ if (has_other) {
+ /* Don't know how to handle this, so don't */
+ has_p = has_vV = 0;
+ break;
+ }
+
+ oldnode = argnode;
+ argnode = nextnode(argnode);
+ if (!argnode) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ if (!(argnode = nextnode(oldnode)))
+ break;
+ }
+ argdata = (char *) getdata(argnode);
+ }
+ if (has_vV) {
+ /*
+ * Leave everything alone, dispatch to whence.
+ * We need to put the name back in the list.
+ */
+ pushnode(preargs, "command");
+ hn = &commandbn.node;
+ is_builtin = 1;
+ break;
+ } else if (has_p) {
+ /* Use default path */
+ use_defpath = 1;
+ /*
+ * We don't need this node as we're not treating
+ * "command" as a builtin this time.
+ */
+ if (pnode)
+ uremnode(preargs, pnode);
+ }
+ /*
+ * Else just any trailing
+ * end-of-options marker. This can only occur
+ * if we just had -p or something including more
+ * than just -p, -v and -V, in which case we behave
+ * as if this is command [non-option-stuff]. This
+ * isn't a good place for standard option handling.
+ */
+ if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
+ uremnode(preargs, argnode);
+ } else if (cflags & BINF_EXEC) {
+ /*
+ * Check for compatibility options to exec builtin.
+ * It would be nice to do these more generically,
+ * but currently we don't have a mechanism for
+ * precommand modifiers.
+ */
+ LinkNode argnode = firstnode(preargs), oldnode;
+ char *argdata = (char *) getdata(argnode);
+ char *cmdopt, *exec_argv0 = NULL;
+ /*
+ * Careful here: we want to make sure a final dash
+ * is passed through in order that it still behaves
+ * as a precommand modifier (zsh equivalent of -l).
+ * It has to be last, but I think that's OK since
+ * people aren't likely to mix the option style
+ * with the zsh style.
+ */
+ while (argdata && IS_DASH(*argdata) && strlen(argdata) >= 2) {
+ oldnode = argnode;
+ argnode = nextnode(oldnode);
+ if (!argnode) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ argnode = nextnode(oldnode);
+ }
+ if (!argnode) {
+ zerr("exec requires a command to execute");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ uremnode(preargs, oldnode);
+ if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
+ break;
+ for (cmdopt = &argdata[1]; *cmdopt; ++cmdopt) {
+ switch (*cmdopt) {
+ case 'a':
+ /* argument is ARGV0 string */
+ if (cmdopt[1]) {
+ exec_argv0 = cmdopt+1;
+ /* position on last non-NULL character */
+ cmdopt += strlen(cmdopt+1);
+ } else {
+ if (!argnode) {
+ zerr("exec requires a command to execute");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ if (!nextnode(argnode))
+ execcmd_getargs(preargs, args,
+ eparams->htok);
+ if (!nextnode(argnode)) {
+ zerr("exec flag -a requires a parameter");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ exec_argv0 = (char *) getdata(argnode);
+ oldnode = argnode;
+ argnode = nextnode(argnode);
+ uremnode(args, oldnode);
+ }
+ break;
+ case 'c':
+ cflags |= BINF_CLEARENV;
+ break;
+ case 'l':
+ cflags |= BINF_DASH;
+ break;
+ default:
+ zerr("unknown exec flag -%c", *cmdopt);
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ }
+ if (!argnode)
+ break;
+ argdata = (char *) getdata(argnode);
+ }
+ if (exec_argv0) {
+ char *str, *s;
+ exec_argv0 = dupstring(exec_argv0);
+ remnulargs(exec_argv0);
+ untokenize(exec_argv0);
+ size_t sz = strlen(exec_argv0);
+ str = s = zalloc(5 + 1 + sz + 1);
+ strcpy(s, "ARGV0=");
+ s+=6;
+ strcpy(s, exec_argv0);
+ zputenv(str);
+ }
+ }
+ hn = NULL;
+ if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
+ break;
+ if (!nonempty(preargs))
+ execcmd_getargs(preargs, args, eparams->htok);
+ }
+ } else
+ preargs = NULL;
+
+ /* if we get this far, it is OK to pay attention to lastval again */
+ if (noerrexit & NOERREXIT_UNTIL_EXEC)
+ noerrexit = 0;
+
+ /* Do prefork substitutions.
+ *
+ * Decide if we need "magic" handling of ~'s etc. in
+ * assignment-like arguments.
+ * - If magic_assign is set, we are using a builtin of the
+ * tyepset family, but did not recognise this as a keyword,
+ * so need guess-o-matic behaviour.
+ * - Otherwise, if we did recognise the keyword, we never need
+ * guess-o-matic behaviour as the argument was properly parsed
+ * as such.
+ * - Otherwise, use the behaviour specified by the MAGIC_EQUAL_SUBST
+ * option.
+ */
+ esprefork = (magic_assign ||
+ (isset(MAGICEQUALSUBST) && type != WC_TYPESET)) ?
+ PREFORK_TYPESET : 0;
+
+ if (args) {
+ if (eparams->htok)
+ prefork(args, esprefork, NULL);
+ if (preargs)
+ args = joinlists(preargs, args);
+ }
+
+ if (type == WC_SIMPLE || type == WC_TYPESET) {
+ int unglobbed = 0;
+
+ for (;;) {
+ char *cmdarg;
+
+ if (!(cflags & BINF_NOGLOB))
+ while (!checked && !errflag && args && nonempty(args) &&
+ has_token((char *) peekfirst(args)))
+ zglob(args, firstnode(args), 0);
+ 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)
+ do_exec = 1;
+
+ /* Empty command */
+ if (!args || empty(args)) {
+ if (redir && nonempty(redir)) {
+ if (do_exec) {
+ /* Was this "exec < foobar"? */
+ nullexec = 1;
+ break;
+ } else if (varspc) {
+ nullexec = 2;
+ break;
+ } else if (!nullcmd || !*nullcmd || opts[CSHNULLCMD] ||
+ (cflags & BINF_PREFIX)) {
+ zerr("redirection with no command");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ if (forked)
+ _exit(lastval);
+ return;
+ } else if (!nullcmd || !*nullcmd || opts[SHNULLCMD]) {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(":"));
+ } else if (readnullcmd && *readnullcmd &&
+ ((Redir) peekfirst(redir))->type == REDIR_READ &&
+ !nextnode(firstnode(redir))) {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(readnullcmd));
+ } else {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(nullcmd));
+ }
+ } else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
+ lastval = 0;
+ if (forked)
+ _exit(lastval);
+ return;
+ } else {
+ /*
+ * No arguments. Reset the status if there were
+ * arguments before and no command substitution
+ * has provided a status.
+ */
+ if (badcshglob == 1) {
+ zerr("no match");
+ lastval = 1;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ cmdoutval = use_cmdoutval ? lastval : 0;
+ if (varspc) {
+ /* Make sure $? is still correct for assignment */
+ lastval = old_lastval;
+ addvars(state, varspc, 0);
+ }
+ if (errflag)
+ lastval = 1;
+ else
+ lastval = cmdoutval;
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) && do_exec) {
+ zerrnam("exec", "%s: restricted",
+ (char *) getdata(firstnode(args)));
+ lastval = 1;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ /*
+ * Quit looking for a command if:
+ * - there was an error; or
+ * - we checked the simple cases needing MAGIC_EQUAL_SUBST; or
+ * - we know we already found a builtin (because either:
+ * - we loaded a builtin from a module, or
+ * - we have determined there are options which would
+ * require us to use the "command" builtin); or
+ * - we aren't using POSIX and so BINF_COMMAND indicates a zsh
+ * precommand modifier is being used in place of the
+ * builtin
+ * - we are using POSIX and this is an EXEC, so we can't
+ * execute a builtin or function.
+ */
+ if (errflag || checked || is_builtin ||
+ (isset(POSIXBUILTINS) ?
+ (cflags & BINF_EXEC) : (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) {
+ zwarn("no such builtin: %s", cmdarg);
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ break;
+ }
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+
+ /* autoload the builtin if necessary */
+ if (!(hn = resolvebuiltin(cmdarg, hn))) {
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ break;
+ }
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ uremnode(args, firstnode(args));
+ hn = NULL;
+ }
+ }
+
+ if (errflag) {
+ if (!lastval)
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ /* Get the text associated with this command. */
+ if (!text &&
+ (!sfcontext && (jobbing || (how & Z_TIMED))))
+ text = getjobtext(state->prog, eparams->beg);
+
+ /*
+ * Set up special parameter $_
+ * For execfuncdef we may need to take account of an
+ * anonymous function with arguments.
+ */
+ if (type != WC_FUNCDEF)
+ setunderscore((args && nonempty(args)) ?
+ ((char *) getdata(lastnode(args))) : "");
+
+ /* Warn about "rm *" */
+ if (type == WC_SIMPLE && interact && unset(RMSTARSILENT) &&
+ isset(SHINSTDIN) && args && 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)) {
+ errflag |= ERRFLAG_ERROR;
+ break;
+ }
+ } else if (l >= 2 && s[l - 2] == '/' && s[l - 1] == Star) {
+ char t = s[l - 2];
+ int rmall;
+
+ s[l - 2] = 0;
+ rmall = checkrmall(s);
+ s[l - 2] = t;
+
+ if (!rmall) {
+ errflag |= ERRFLAG_ERROR;
+ break;
+ }
+ }
+ }
+ }
+
+ if (type == WC_FUNCDEF) {
+ /*
+ * The first word of a function definition is a list of
+ * names. If this is empty, we're doing an anonymous function:
+ * in that case redirections are handled normally.
+ * If not, it's a function definition: then we don't do
+ * redirections here but pass in the list of redirections to
+ * be stored for recall with the function.
+ */
+ if (*state->pc != 0) {
+ /* Nonymous, don't do redirections here */
+ redir = NULL;
+ }
+ } else if (is_shfunc || type == WC_AUTOFN) {
+ Shfunc shf;
+ if (is_shfunc)
+ shf = (Shfunc)hn;
+ else {
+ shf = loadautofn(state->prog->shf, 1, 0, 0);
+ if (shf)
+ state->prog->shf = shf;
+ else {
+ /*
+ * This doesn't set errflag, so just return now.
+ */
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ }
+ /*
+ * A function definition may have a list of additional
+ * redirections to apply, so retrieve it.
+ */
+ if (shf->redir) {
+ struct estate s;
+ LinkList redir2;
+
+ s.prog = shf->redir;
+ s.pc = shf->redir->prog;
+ s.strs = shf->redir->strs;
+ redir2 = ecgetredirs(&s);
+ if (!redir)
+ redir = redir2;
+ else {
+ while (nonempty(redir2))
+ addlinknode(redir, ugetnode(redir2));
+ }
+ }
+ }
+
+ if (errflag) {
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ if ((type == WC_SIMPLE || type == WC_TYPESET) && !nullexec) {
+ char *s;
+ char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
+ (!redir || empty(redir)) && args && !empty(args) &&
+ !nextnode(firstnode(args)) && *(char *)peekfirst(args));
+
+ DPUTS((!args || empty(args)), "BUG: empty(args) in exec.c");
+ if (!hn) {
+ /* Resolve external commands */
+ char *cmdarg = (char *) peekfirst(args);
+ char **checkpath = pathchecked;
+ int dohashcmd = isset(HASHCMDS);
+
+ hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
+ if (hn && trycd && !isreallycom((Cmdnam)hn)) {
+ if (!(((Cmdnam)hn)->node.flags & HASHED)) {
+ checkpath = path;
+ dohashcmd = 1;
+ }
+ cmdnamtab->removenode(cmdnamtab, cmdarg);
+ cmdnamtab->freenode(hn);
+ hn = NULL;
+ }
+ if (!hn && dohashcmd && strcmp(cmdarg, "..")) {
+ for (s = cmdarg; *s && *s != '/'; s++);
+ if (!*s)
+ hn = (HashNode) hashcmd(cmdarg, checkpath);
+ }
+ }
+
+ /* 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("--"));
+ 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 || nullexec || type >= WC_CURSH);
+
+ /**************************************************************************
+ * Do we need to fork? We need to fork if: *
+ * 1) The command is supposed to run in the background. This *
+ * case is now handled above (forked = 1 here). (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 the command is the last stage in a subshell, or is the *
+ * last command after the option `-c'. *
+ * 2) We don't have any traps set. *
+ * 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 (!forked) {
+ if (!do_exec &&
+ (((is_builtin || is_shfunc) && output) ||
+ (!is_cursh && (last1 != 1 || nsigtrapped || havefiles() ||
+ fdtable_flocks)))) {
+ switch (execcmd_fork(state, how, type, varspc, &filelist,
+ text, oautocont, close_if_forked)) {
+ case -1:
+ goto fatal;
+ case 0:
+ break;
+ default:
+ return;
+ }
+ forked = 1;
+ } 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;
+ if (!jobtab[thisjob].procs)
+ jobtab[thisjob].stat |= STAT_NOPRINT;
+ if (is_builtin)
+ jobtab[thisjob].stat |= STAT_BUILTIN;
+ } 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 we are in a subshell environment anyway, say we're forked,
+ * even if we're actually not forked because we know the
+ * subshell is exiting. This ensures SHLVL reflects the current
+ * shell, and also optimises out any save/restore we'd need to
+ * do if we were returning to the main shell.
+ */
+ if (type == WC_SUBSH)
+ forked = 1;
+ }
+ }
+
+ if ((esglob = !(cflags & BINF_NOGLOB)) && args && eparams->htok) {
+ LinkList oargs = args;
+ globlist(args, 0);
+ args = oargs;
+ }
+ if (errflag) {
+ lastval = 1;
+ goto err;
+ }
+
+ /* Make a copy of stderr for xtrace output before redirecting */
+ fflush(xtrerr);
+ if (isset(XTRACE) && xtrerr == stderr &&
+ (type < WC_SUBSH || type == WC_TIMED)) {
+ if ((newxtrerr = fdopen(movefd(dup(fileno(stderr))), "w"))) {
+ xtrerr = newxtrerr;
+ fdtable[fileno(xtrerr)] = FDT_XTRACE;
+ }
+ }
+
+ /* Add pipeline input/output to mnodes */
+ if (input)
+ addfd(forked, save, mfds, 0, input, 0, NULL);
+ if (output)
+ addfd(forked, save, mfds, 1, output, 1, NULL);
+
+ /* Do process substitutions */
+ if (redir)
+ spawnpipes(redir, nullexec);
+
+ /* Do io redirections */
+ while (redir && nonempty(redir)) {
+ fn = (Redir) ugetnode(redir);
+
+ DPUTS(fn->type == REDIR_HEREDOC || fn->type == REDIR_HEREDOCDASH,
+ "BUG: unexpanded here document");
+ if (fn->type == REDIR_INPIPE) {
+ if (!checkclobberparam(fn) || fn->fd2 == -1) {
+ if (fn->fd2 != -1)
+ zclose(fn->fd2);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 0, fn->varid);
+ } else if (fn->type == REDIR_OUTPIPE) {
+ if (!checkclobberparam(fn) || fn->fd2 == -1) {
+ if (fn->fd2 != -1)
+ zclose(fn->fd2);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 1, fn->varid);
+ } else {
+ int closed;
+ if (fn->type != REDIR_HERESTR && xpandredir(fn, redir))
+ continue;
+ if (errflag) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
+ zwarn("writing redirection not allowed in restricted mode");
+ execerr();
+ }
+ if (unset(EXECOPT))
+ continue;
+ switch(fn->type) {
+ case REDIR_HERESTR:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else
+ fil = getherestr(fn);
+ if (fil == -1) {
+ if (errno && errno != EINTR)
+ zwarn("can't create temp file for here document: %e",
+ errno);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
+ break;
+ case REDIR_READ:
+ case REDIR_READWRITE:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (fn->type == REDIR_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)
+ zwarn("%e: %s", errno, fn->name);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
+ /* If this is 'exec < file', read from stdin, *
+ * not terminal, unless `file' is a terminal. */
+ if (nullexec == 1 && fn->fd1 == 0 &&
+ isset(SHINSTDIN) && interact && !zleactive)
+ init_io(NULL);
+ break;
+ case REDIR_CLOSE:
+ if (fn->varid) {
+ char *s = fn->varid, *t;
+ struct value vbuf;
+ Value v;
+ int bad = 0;
+
+ if (!(v = getvalue(&vbuf, &s, 0))) {
+ bad = 1;
+ } else if (v->pm->node.flags & PM_READONLY) {
+ bad = 2;
+ } else {
+ s = getstrvalue(v);
+ if (errflag)
+ bad = 1;
+ else {
+ fn->fd1 = zstrtol(s, &t, 0);
+ if (s == t)
+ bad = 1;
+ else if (*t) {
+ /* Check for base#number format */
+ if (*t == '#' && *s != '0')
+ fn->fd1 = zstrtol(s = t+1, &t, fn->fd1);
+ if (s == t || *t)
+ bad = 1;
+ }
+ if (!bad && fn->fd1 <= max_zsh_fd) {
+ if (fn->fd1 >= 10 &&
+ (fdtable[fn->fd1] & FDT_TYPE_MASK) ==
+ FDT_INTERNAL)
+ bad = 3;
+ }
+ }
+ }
+ if (bad) {
+ const char *bad_msg[] = {
+ "parameter %s does not contain a file descriptor",
+ "can't close file descriptor from readonly parameter %s",
+ "file descriptor %d used by shell, not closed"
+ };
+ if (bad > 2)
+ zwarn(bad_msg[bad-1], fn->fd1);
+ else
+ zwarn(bad_msg[bad-1], fn->varid);
+ execerr();
+ }
+ }
+ /*
+ * Note we may attempt to close an fd beyond max_zsh_fd:
+ * OK as long as we never look in fdtable for it.
+ */
+ closed = 0;
+ if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2) {
+ save[fn->fd1] = movefd(fn->fd1);
+ if (save[fn->fd1] >= 0) {
+ /*
+ * The original fd is now closed, we don't need
+ * to do it below.
+ */
+ closed = 1;
+ }
+ }
+ if (fn->fd1 < 10)
+ closemn(mfds, fn->fd1, REDIR_CLOSE);
+ /*
+ * Only report failures to close file descriptors
+ * if they're under user control as we don't know
+ * what the previous status of others was.
+ */
+ if (!closed && zclose(fn->fd1) < 0 && fn->varid) {
+ zwarn("failed to close file descriptor %d: %e",
+ fn->fd1, errno);
+ }
+ break;
+ case REDIR_MERGEIN:
+ case REDIR_MERGEOUT:
+ if (fn->fd2 < 10)
+ closemn(mfds, fn->fd2, fn->type);
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (fn->fd2 > 9 &&
+ /*
+ * If the requested fd is > max_zsh_fd,
+ * the shell doesn't know about it.
+ * Just assume the user knows what they're
+ * doing.
+ */
+ (fn->fd2 <= max_zsh_fd &&
+ ((fdtable[fn->fd2] != FDT_UNUSED &&
+ fdtable[fn->fd2] != FDT_EXTERNAL) ||
+ fn->fd2 == coprocin ||
+ fn->fd2 == coprocout))) {
+ fil = -1;
+ errno = EBADF;
+ } else {
+ int fd = fn->fd2;
+ if(fd == -2)
+ fd = (fn->type == REDIR_MERGEOUT) ? coprocout : coprocin;
+ fil = movefd(dup(fd));
+ }
+ if (fil == -1) {
+ char fdstr[DIGBUFSIZE];
+
+ closemnodes(mfds);
+ fixfds(save);
+ if (fn->fd2 != -2)
+ sprintf(fdstr, "%d", fn->fd2);
+ if (errno)
+ zwarn("%s: %e", fn->fd2 == -2 ? "coprocess" : fdstr,
+ errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil,
+ fn->type == REDIR_MERGEOUT, fn->varid);
+ break;
+ default:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (IS_APPEND_REDIR(fn->type))
+ fil = open(unmeta(fn->name),
+ ((unset(CLOBBER) && unset(APPENDCREATE)) &&
+ !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 = movefd(dup(fil));
+ else
+ dfil = 0;
+ if (fil == -1 || dfil == -1) {
+ if(fil != -1)
+ close(fil);
+ closemnodes(mfds);
+ fixfds(save);
+ if (errno && errno != EINTR)
+ zwarn("%e: %s", errno, fn->name);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 1, fn->varid);
+ if(IS_ERROR_REDIR(fn->type))
+ addfd(forked, save, mfds, 2, dfil, 1, NULL);
+ break;
+ }
+ /* May be error in addfd due to setting parameter. */
+ if (errflag) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ }
+ }
+
+ /* We are done with redirection. close the mnodes, *
+ * spawning tee/cat processes as necessary. */
+ for (i = 0; i < 10; i++)
+ if (mfds[i] && mfds[i]->ct >= 2)
+ closemn(mfds, i, REDIR_CLOSE);
+
+ if (nullexec) {
+ /*
+ * If nullexec is 2, we have variables to add with the redirections
+ * in place. If nullexec is 1, we may have variables but they
+ * need the standard restore logic.
+ */
+ if (varspc) {
+ LinkList restorelist = 0, removelist = 0;
+ if (!isset(POSIXBUILTINS) && nullexec != 2)
+ save_params(state, varspc, &restorelist, &removelist);
+ addvars(state, varspc, 0);
+ if (restorelist)
+ restore_params(restorelist, removelist);
+ }
+ lastval = errflag ? errflag : cmdoutval;
+ if (nullexec == 1) {
+ /*
+ * If nullexec is 1 we specifically *don't* restore the original
+ * fd's before returning.
+ */
+ for (i = 0; i < 10; i++)
+ if (save[i] != -2)
+ zclose(save[i]);
+ goto done;
+ }
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ } else if (isset(EXECOPT) && !errflag) {
+ int q = queue_signal_level();
+ /*
+ * 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) {
+ int flags = ((how & Z_ASYNC) ? ESUB_ASYNC : 0) |
+ ESUB_PGRP | ESUB_FAKE;
+ if (type != WC_SUBSH)
+ flags |= ESUB_KEEPTRAP;
+ if ((do_exec || (type >= WC_CURSH && last1 == 1))
+ && !forked)
+ flags |= ESUB_REVERTPGRP;
+ entersubsh(flags);
+ }
+ if (type == WC_FUNCDEF) {
+ Eprog redir_prog;
+ if (!redir && wc_code(*eparams->beg) == WC_REDIR) {
+ /*
+ * We're not using a redirection from the currently
+ * parsed environment, which is what we'd do for an
+ * anonymous function, but there are redirections we
+ * should store with the new function.
+ */
+ struct estate s;
+
+ s.prog = state->prog;
+ s.pc = eparams->beg;
+ s.strs = state->prog->strs;
+
+ /*
+ * The copy uses the wordcode parsing area, so save and
+ * restore state.
+ */
+ zcontext_save();
+ redir_prog = eccopyredirs(&s);
+ zcontext_restore();
+ } else
+ redir_prog = NULL;
+
+ dont_queue_signals();
+ lastval = execfuncdef(state, redir_prog);
+ restore_queue_signals(q);
+ }
+ else if (type >= WC_CURSH) {
+ if (last1 == 1)
+ do_exec = 1;
+ dont_queue_signals();
+ if (type == WC_AUTOFN) {
+ /*
+ * We pre-loaded this to get any redirs.
+ * So we execuate a simplified function here.
+ */
+ lastval = execautofn_basic(state, do_exec);
+ } else
+ lastval = (execfuncs[type - WC_CURSH])(state, do_exec);
+ restore_queue_signals(q);
+ } else if (is_builtin || is_shfunc) {
+ LinkList restorelist = 0, removelist = 0;
+ int do_save = 0;
+ /* builtin or shell function */
+
+ if (!forked) {
+ if (isset(POSIXBUILTINS)) {
+ /*
+ * If it's a function or special builtin --- save
+ * if it's got "command" in front.
+ * If it's a normal command --- save.
+ */
+ if (is_shfunc || (hn->flags & (BINF_PSPECIAL|BINF_ASSIGN)))
+ do_save = (orig_cflags & BINF_COMMAND);
+ else
+ do_save = 1;
+ } else {
+ /*
+ * Save if it's got "command" in front or it's
+ * not a magic-equals assignment.
+ */
+ if ((cflags & (BINF_COMMAND|BINF_ASSIGN)) || !magic_assign)
+ do_save = 1;
+ }
+ if (do_save && varspc)
+ save_params(state, varspc, &restorelist, &removelist);
+ }
+ if (varspc) {
+ /* Export this if the command is a shell function,
+ * but not if it's a builtin.
+ */
+ int flags = 0;
+ if (is_shfunc)
+ flags |= ADDVAR_EXPORT;
+ if (restorelist)
+ flags |= ADDVAR_RESTORE;
+
+ addvars(state, varspc, flags);
+ if (errflag) {
+ if (restorelist)
+ restore_params(restorelist, removelist);
+ lastval = 1;
+ fixfds(save);
+ goto done;
+ }
+ }
+
+ if (is_shfunc) {
+ /* It's a shell function */
+ pipecleanfilelist(filelist, 0);
+ execshfunc((Shfunc) hn, args);
+ } else {
+ /* It's a builtin */
+ LinkList assigns = (LinkList)0;
+ int postassigns = eparams->postassigns;
+ if (forked)
+ closem(FDT_INTERNAL, 0);
+ if (postassigns) {
+ Wordcode opc = state->pc;
+ state->pc = eparams->assignspc;
+ assigns = newlinklist();
+ while (postassigns--) {
+ int htok;
+ wordcode ac = *state->pc++;
+ char *name = ecgetstr(state, EC_DUPTOK, &htok);
+ Asgment asg;
+ local_list1(svl);
+
+ DPUTS(wc_code(ac) != WC_ASSIGN,
+ "BUG: bad assignment list for typeset");
+ if (htok) {
+ init_list1(svl, name);
+ if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR &&
+ WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+ char *data;
+ /*
+ * Special case: this is a name only, so
+ * it's not required to be a single
+ * expansion. Furthermore, for
+ * consistency with the builtin
+ * interface, it may expand into
+ * scalar assignments:
+ * ass=(one=two three=four)
+ * typeset a=b $ass
+ */
+ /* Unused dummy value for name */
+ (void)ecgetstr(state, EC_DUPTOK, &htok);
+ prefork(&svl, PREFORK_TYPESET, NULL);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ globlist(&svl, 0);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ while ((data = ugetnode(&svl))) {
+ char *ptr;
+ asg = (Asgment)zhalloc(sizeof(struct asgment));
+ asg->flags = 0;
+ if ((ptr = strchr(data, '='))) {
+ *ptr++ = '\0';
+ asg->name = data;
+ asg->value.scalar = ptr;
+ } else {
+ asg->name = data;
+ asg->value.scalar = NULL;
+ }
+ uaddlinknode(assigns, &asg->node);
+ }
+ continue;
+ }
+ prefork(&svl, PREFORK_SINGLE, NULL);
+ name = empty(&svl) ? "" :
+ (char *)getdata(firstnode(&svl));
+ }
+ untokenize(name);
+ asg = (Asgment)zhalloc(sizeof(struct asgment));
+ asg->name = name;
+ if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
+ char *val = ecgetstr(state, EC_DUPTOK, &htok);
+ asg->flags = 0;
+ if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+ /* Fake assignment, no value */
+ asg->value.scalar = NULL;
+ } else {
+ if (htok) {
+ init_list1(svl, val);
+ prefork(&svl,
+ PREFORK_SINGLE|PREFORK_ASSIGN,
+ NULL);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ /*
+ * No globassign for typeset
+ * arguments, thank you
+ */
+ val = empty(&svl) ? "" :
+ (char *)getdata(firstnode(&svl));
+ }
+ untokenize(val);
+ asg->value.scalar = val;
+ }
+ } else {
+ asg->flags = ASG_ARRAY;
+ asg->value.array =
+ ecgetlist(state, WC_ASSIGN_NUM(ac),
+ EC_DUPTOK, &htok);
+ if (asg->value.array)
+ {
+ if (!errflag) {
+ int prefork_ret = 0;
+ prefork(asg->value.array, PREFORK_ASSIGN,
+ &prefork_ret);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ if (prefork_ret & PREFORK_KEY_VALUE)
+ asg->flags |= ASG_KEY_VALUE;
+ globlist(asg->value.array, prefork_ret);
+ }
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ }
+ }
+
+ uaddlinknode(assigns, &asg->node);
+ }
+ state->pc = opc;
+ }
+ dont_queue_signals();
+ if (!errflag) {
+ int ret = execbuiltin(args, assigns, (Builtin) hn);
+ /*
+ * In case of interruption assume builtin status
+ * is less useful than what interrupt set.
+ */
+ if (!(errflag & ERRFLAG_INT))
+ lastval = ret;
+ }
+ if (do_save & BINF_COMMAND)
+ errflag &= ~ERRFLAG_ERROR;
+ restore_queue_signals(q);
+ fflush(stdout);
+ if (save[1] == -2) {
+ if (ferror(stdout)) {
+ zwarn("write error: %e", errno);
+ clearerr(stdout);
+ }
+ } else
+ clearerr(stdout);
+ }
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+ lastval && !subsh) {
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ fprintf(stderr, "zsh: exit %lld\n", lastval);
+#else
+ fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+#endif
+ fflush(stderr);
+ }
+
+ if (do_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(NULL, 1, HFILE_USE_OPTIONS);
+ exit(lastval);
+ }
+ if (restorelist)
+ restore_params(restorelist, removelist);
+
+ } else {
+ if (!subsh) {
+ /* for either implicit or explicit "exec", decrease $SHLVL
+ * as we're now done as a shell */
+ if (!forked)
+ setiparam("SHLVL", --shlvl);
+
+ /* If we are exec'ing a command, and we are not *
+ * in a subshell, then save the history file. */
+ if (do_exec && isset(RCS) && interact && !nohistsave)
+ savehistfile(NULL, 1, HFILE_USE_OPTIONS);
+ }
+ if (type == WC_SIMPLE || type == WC_TYPESET) {
+ if (varspc) {
+ int addflags = ADDVAR_EXPORT|ADDVAR_RESTRICT;
+ if (forked)
+ addflags |= ADDVAR_RESTORE;
+ addvars(state, varspc, addflags);
+ if (errflag)
+ _exit(1);
+ }
+ closem(FDT_INTERNAL, 0);
+ if (coprocin != -1) {
+ zclose(coprocin);
+ coprocin = -1;
+ }
+ if (coprocout != -1) {
+ zclose(coprocout);
+ coprocout = -1;
+ }
+#ifdef HAVE_GETRLIMIT
+ if (!forked)
+ setlimits(NULL);
+#endif
+ if (how & Z_ASYNC) {
+ zsfree(STTYval);
+ STTYval = 0;
+ }
+ execute(args, cflags, use_defpath);
+ } else { /* ( ... ) */
+ DPUTS(varspc,
+ "BUG: assignment before complex command");
+ list_pipe = 0;
+ pipecleanfilelist(filelist, 0);
+ /* If we're forked (and we should be), no need to return */
+ DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
+ DPUTS(type != WC_SUBSH, "Not sure what we're doing.");
+ /* Skip word only used for try/always blocks */
+ state->pc++;
+ execlist(state, 0, 1);
+ }
+ }
+ }
+
+ err:
+ if (forked) {
+ /*
+ * So what's going on here then? Well, I'm glad you asked.
+ *
+ * If we create multios for use in a subshell we do
+ * this after forking, in this function above. That
+ * means that the current (sub)process is responsible
+ * for clearing them up. However, the processes won't
+ * go away until we have closed the fd's talking to them.
+ * Since we're about to exit the shell there's nothing
+ * to stop us closing all fd's (including the ones 0 to 9
+ * that we usually leave alone).
+ *
+ * Then we wait for any processes. When we forked,
+ * we cleared the jobtable and started a new job just for
+ * any oddments like this, so if there aren't any we won't
+ * need to wait. The result of not waiting is that
+ * the multios haven't flushed the fd's properly, leading
+ * to obscure missing data.
+ *
+ * It would probably be cleaner to ensure that the
+ * parent shell handled multios, but that requires
+ * some architectural changes which are likely to be
+ * hairy.
+ */
+ for (i = 0; i < 10; i++)
+ if (fdtable[i] != FDT_UNUSED)
+ close(i);
+ closem(FDT_UNUSED, 1);
+ if (thisjob != -1)
+ waitjobs();
+ _exit(lastval);
+ }
+ fixfds(save);
+
+ done:
+ if (isset(POSIXBUILTINS) &&
+ (cflags & (BINF_PSPECIAL|BINF_EXEC)) &&
+ !(orig_cflags & BINF_COMMAND)) {
+ /*
+ * For POSIX-compatible behaviour with special
+ * builtins (including exec which we don't usually
+ * classify as a builtin) we treat all errors as fatal.
+ * The "command" builtin is not special so resets this behaviour.
+ */
+ forked |= zsh_subshell;
+ fatal:
+ if (redir_err || errflag) {
+ if (!isset(INTERACTIVE)) {
+ if (forked)
+ _exit(1);
+ else
+ exit(1);
+ }
+ errflag |= ERRFLAG_ERROR;
+ }
+ }
+ if (newxtrerr) {
+ fil = fileno(newxtrerr);
+ fclose(newxtrerr);
+ xtrerr = oxtrerr;
+ zclose(fil);
+ }
+
+ zsfree(STTYval);
+ STTYval = 0;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+}
+
+/* Arrange to have variables restored. */
+
+/**/
+static void
+save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
+{
+ Param pm;
+ char *s;
+ wordcode ac;
+
+ *restore_p = newlinklist();
+ *remove_p = newlinklist();
+
+ while (wc_code(ac = *pc) == WC_ASSIGN) {
+ s = ecrawstr(state->prog, pc + 1, NULL);
+ if ((pm = (Param) paramtab->getnode(paramtab, s))) {
+ Param tpm;
+ if (pm->env)
+ delenv(pm);
+ if (!(pm->node.flags & PM_SPECIAL)) {
+ /*
+ * We used to remove ordinary parameters from the
+ * table, but that meant "HELLO=$HELLO shellfunc"
+ * failed because the expansion of $HELLO hasn't
+ * been done at this point. Instead, copy the
+ * parameter: in this case, we'll insert the
+ * copied parameter straight back into the parameter
+ * table so we want to be sure everything is
+ * properly set up and in permanent memory.
+ */
+ tpm = (Param) zshcalloc(sizeof *tpm);
+ tpm->node.nam = ztrdup(pm->node.nam);
+ copyparam(tpm, pm, 0);
+ pm = tpm;
+ } else if (!(pm->node.flags & PM_READONLY) &&
+ (unset(RESTRICTED) || !(pm->node.flags & PM_RESTRICTED))) {
+ /*
+ * In this case we're just saving parts of
+ * the parameter in a tempory, so use heap allocation
+ * and don't bother copying every detail.
+ */
+ tpm = (Param) hcalloc(sizeof *tpm);
+ tpm->node.nam = pm->node.nam;
+ copyparam(tpm, pm, 1);
+ pm = tpm;
+ }
+ addlinknode(*remove_p, dupstring(s));
+ addlinknode(*restore_p, pm);
+ } else
+ addlinknode(*remove_p, dupstring(s));
+
+ pc += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(ac) + 2);
+ }
+}
+
+/* Restore saved parameters after executing a shfunc or builtin */
+
+/**/
+static void
+restore_params(LinkList restorelist, LinkList removelist)
+{
+ Param pm;
+ char *s;
+
+ /* remove temporary parameters */
+ while ((s = (char *) ugetnode(removelist))) {
+ if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+ !(pm->node.flags & PM_SPECIAL)) {
+ pm->node.flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 0);
+ }
+ }
+
+ if (restorelist) {
+ /* restore saved parameters */
+ while ((pm = (Param) ugetnode(restorelist))) {
+ if (pm->node.flags & PM_SPECIAL) {
+ Param tpm = (Param) paramtab->getnode(paramtab, pm->node.nam);
+
+ DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) ||
+ !(pm->node.flags & PM_SPECIAL),
+ "BUG: in restoring special parameters");
+ if (!pm->env && tpm->env)
+ delenv(tpm);
+ tpm->node.flags = pm->node.flags;
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ tpm->gsu.s->setfn(tpm, pm->u.str);
+ break;
+ case PM_INTEGER:
+ tpm->gsu.i->setfn(tpm, pm->u.val);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ tpm->gsu.f->setfn(tpm, pm->u.dval);
+ break;
+ case PM_ARRAY:
+ tpm->gsu.a->setfn(tpm, pm->u.arr);
+ break;
+ case PM_HASHED:
+ tpm->gsu.h->setfn(tpm, pm->u.hash);
+ break;
+ }
+ pm = tpm;
+ } else {
+ paramtab->addnode(paramtab, pm->node.nam, pm);
+ }
+ if ((pm->node.flags & PM_EXPORTED) && ((s = getsparam(pm->node.nam))))
+ addenv(pm, s);
+ }
+ }
+}
+
+/* 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;
+}
+
+/*
+ * Close internal shell fds.
+ *
+ * Close any that are marked as used if "how" is FDT_UNUSED, else
+ * close any with the value "how".
+ *
+ * If "all" is zero, we'll skip cases where we need the file
+ * descriptor to be visible externally.
+ */
+
+/**/
+mod_export void
+closem(int how, int all)
+{
+ int i;
+
+ for (i = 10; i <= max_zsh_fd; i++)
+ if (fdtable[i] != FDT_UNUSED &&
+ /*
+ * Process substitution needs to be visible to user;
+ * fd's are explicitly cleaned up by filelist handling.
+ */
+ (all || fdtable[i] != FDT_PROC_SUBST) &&
+ (how == FDT_UNUSED || (fdtable[i] & FDT_TYPE_MASK) == how)) {
+ if (i == SHTTY)
+ SHTTY = -1;
+ zclose(i);
+ }
+}
+
+/* convert here document into a here string */
+
+/**/
+char *
+gethere(char **strp, int typ)
+{
+ char *buf;
+ int bsiz, qt = 0, strip = 0;
+ char *s, *t, *bptr, c;
+ char *str = *strp;
+
+ for (s = str; *s; s++)
+ if (inull(*s)) {
+ qt = 1;
+ break;
+ }
+ str = quotesubst(str);
+ untokenize(str);
+ if (typ == REDIR_HEREDOCDASH) {
+ strip = 1;
+ while (*str == '\t')
+ str++;
+ }
+ *strp = str;
+ bptr = buf = zalloc(bsiz = 256);
+ for (;;) {
+ t = bptr;
+
+ while ((c = hgetc()) == '\t' && strip)
+ ;
+ for (;;) {
+ if (bptr >= buf + bsiz - 2) {
+ ptrdiff_t toff = t - buf;
+ ptrdiff_t bptroff = bptr - buf;
+ char *newbuf = realloc(buf, 2 * bsiz);
+ if (!newbuf) {
+ /* out of memory */
+ zfree(buf, bsiz);
+ return NULL;
+ }
+ buf = newbuf;
+ t = buf + toff;
+ bptr = buf + bptroff;
+ bsiz *= 2;
+ }
+ if (lexstop || c == '\n')
+ break;
+ if (!qt && c == '\\') {
+ *bptr++ = c;
+ c = hgetc();
+ if (c == '\n') {
+ bptr--;
+ c = hgetc();
+ continue;
+ }
+ }
+ *bptr++ = c;
+ c = hgetc();
+ }
+ *bptr = '\0';
+ if (!strcmp(t, str))
+ break;
+ if (lexstop) {
+ t = bptr;
+ break;
+ }
+ *bptr++ = '\n';
+ }
+ *t = '\0';
+ s = buf;
+ buf = dupstring(buf);
+ zfree(s, bsiz);
+ if (!qt) {
+ int ef = errflag;
+
+ parsestr(&buf);
+
+ if (!(errflag & ERRFLAG_ERROR)) {
+ /* Retain any user interrupt error */
+ errflag = ef | (errflag & ERRFLAG_INT);
+ }
+ }
+ return buf;
+}
+
+/* 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);
+ /*
+ * For real here-strings we append a newline, as if the
+ * string given was a complete command line.
+ *
+ * For here-strings from here documents, we use the original
+ * text exactly.
+ */
+ if (!(fn->flags & REDIRF_FROM_HEREDOC))
+ t[len++] = '\n';
+ if ((fd = gettempfile(NULL, 1, &s)) < 0)
+ return -1;
+ write_loop(fd, t, len);
+ close(fd);
+ fd = open(s, O_RDONLY | O_NOCTTY);
+ unlink(s);
+ return fd;
+}
+
+/*
+ * Test if some wordcode starts with a simple redirection of type
+ * redir_type. If it does, return the name of the file, copied onto
+ * the heap. If it doesn't, return NULL.
+ */
+
+static char *
+simple_redir_name(Eprog prog, int redir_type)
+{
+ Wordcode pc;
+
+ pc = prog->prog;
+ if (prog != &dummy_eprog &&
+ wc_code(pc[0]) == WC_LIST && (WC_LIST_TYPE(pc[0]) & Z_END) &&
+ wc_code(pc[1]) == WC_SUBLIST && !WC_SUBLIST_FLAGS(pc[1]) &&
+ WC_SUBLIST_TYPE(pc[1]) == WC_SUBLIST_END &&
+ wc_code(pc[2]) == WC_PIPE && WC_PIPE_TYPE(pc[2]) == WC_PIPE_END &&
+ wc_code(pc[3]) == WC_REDIR && WC_REDIR_TYPE(pc[3]) == redir_type &&
+ !WC_REDIR_VARID(pc[3]) &&
+ !pc[4] &&
+ wc_code(pc[6]) == WC_SIMPLE && !WC_SIMPLE_ARGC(pc[6])) {
+ return dupstring(ecrawstr(prog, pc + 5, NULL));
+ }
+
+ return NULL;
+}
+
+/* $(...) */
+
+/**/
+LinkList
+getoutput(char *cmd, int qt)
+{
+ Eprog prog;
+ int pipes[2];
+ pid_t pid;
+ char *s;
+
+ int onc = nocomments;
+ nocomments = (interact && unset(INTERACTIVECOMMENTS));
+ prog = parse_string(cmd, 0);
+ nocomments = onc;
+
+ if (!prog)
+ return NULL;
+
+ if ((s = simple_redir_name(prog, REDIR_READ))) {
+ /* $(< word) */
+ int stream;
+ LinkList retval;
+ int readerror;
+
+ singsub(&s);
+ if (errflag)
+ return NULL;
+ untokenize(s);
+ if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
+ zwarn("%e: %s", errno, s);
+ lastval = cmdoutval = 1;
+ return newlinklist();
+ }
+ retval = readoutput(stream, qt, &readerror);
+ if (readerror) {
+ zwarn("error when reading %s: %e", s, readerror);
+ lastval = cmdoutval = 1;
+ }
+ return retval;
+ }
+ if (mpipe(pipes) < 0) {
+ errflag |= ERRFLAG_ERROR;
+ cmdoutpid = 0;
+ return NULL;
+ }
+ child_block();
+ cmdoutval = 0;
+ if ((cmdoutpid = pid = zfork(NULL)) == -1) {
+ /* fork error */
+ zclose(pipes[0]);
+ zclose(pipes[1]);
+ errflag |= ERRFLAG_ERROR;
+ cmdoutpid = 0;
+ child_unblock();
+ return NULL;
+ } else if (pid) {
+ LinkList retval;
+
+ zclose(pipes[1]);
+ retval = readoutput(pipes[0], qt, NULL);
+ fdtable[pipes[0]] = FDT_UNUSED;
+ waitforpid(pid, 0); /* unblocks */
+ lastval = cmdoutval;
+ return retval;
+ }
+ /* pid == 0 */
+ child_unblock();
+ zclose(pipes[0]);
+ redup(pipes[1], 1);
+ entersubsh(ESUB_PGRP|ESUB_NOMONITOR);
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, "cmdsubst");
+ cmdpop();
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!");
+ kill(getpid(), SIGKILL);
+ return NULL;
+}
+
+/* read output of command substitution */
+
+/**/
+mod_export LinkList
+readoutput(int in, int qt, int *readerror)
+{
+ LinkList ret;
+ char *buf, *ptr;
+ int bsiz, c, cnt = 0;
+ FILE *fin;
+ int q = queue_signal_level();
+
+ fin = fdopen(in, "r");
+ ret = newlinklist();
+ ptr = buf = (char *) hcalloc(bsiz = 64);
+ /*
+ * We need to be sensitive to SIGCHLD else we can be
+ * stuck forever with important processes unreaped.
+ * The case that triggered this was where the exiting
+ * process is group leader of the foreground process and we need
+ * to reclaim the terminal else ^C doesn't work.
+ */
+ dont_queue_signals();
+ child_unblock();
+ 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;
+ queue_signals();
+ pp = (char *) hcalloc(bsiz *= 2);
+ dont_queue_signals();
+
+ memcpy(pp, buf, cnt - 1);
+ ptr = (buf = pp) + cnt - 1;
+ }
+ *ptr++ = c;
+ }
+ child_block();
+ restore_queue_signals(q);
+ if (readerror)
+ *readerror = ferror(fin) ? errno : 0;
+ 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, 1, 0);
+
+ while (*words) {
+ if (isset(GLOBSUBST))
+ shtokenize(*words);
+ addlinknode(ret, *words++);
+ }
+ }
+ return ret;
+}
+
+/**/
+static Eprog
+parsecmd(char *cmd, char **eptr)
+{
+ char *str;
+ Eprog prog;
+
+ for (str = cmd + 2; *str && *str != Outpar; str++);
+ if (!*str || cmd[1] != Inpar) {
+ /*
+ * This can happen if the expression is being parsed
+ * inside another construct, e.g. as a value within ${..:..} etc.
+ * So print a proper error message instead of the not very
+ * useful but traditional "oops".
+ */
+ char *errstr = dupstrpfx(cmd, 2);
+ untokenize(errstr);
+ zerr("unterminated `%s...)'", errstr);
+ return NULL;
+ }
+ *str = '\0';
+ if (eptr)
+ *eptr = str+1;
+ if (!(prog = parse_string(cmd + 2, 0))) {
+ zerr("parse error in process substitution");
+ return NULL;
+ }
+ return prog;
+}
+
+/* =(...) */
+
+/**/
+char *
+getoutputfile(char *cmd, char **eptr)
+{
+ pid_t pid;
+ char *nam;
+ Eprog prog;
+ int fd;
+ char *s;
+
+ if (thisjob == -1){
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ if (!(nam = gettempname(NULL, 1)))
+ return NULL;
+
+ if ((s = simple_redir_name(prog, REDIR_HERESTR))) {
+ /*
+ * =(<<<stuff). Optimise a la $(<file). It's
+ * effectively the reverse, converting a string into a file name
+ * rather than vice versa.
+ */
+ singsub(&s);
+ if (errflag)
+ s = NULL;
+ else
+ untokenize(s);
+ }
+
+ if (!s) /* Unclear why we need to do this before open() */
+ child_block(); /* but it has been so for a long time: leave it */
+
+ if ((fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600)) < 0) {
+ zerr("process substitution failed: %e", errno);
+ free(nam);
+ if (!s)
+ child_unblock();
+ return NULL;
+ } else {
+ char *suffix = getsparam("TMPSUFFIX");
+ if (suffix && *suffix && !strstr(suffix, "/")) {
+ suffix = dyncat(nam, unmeta(suffix));
+ if (link(nam, suffix) == 0) {
+ addfilelist(nam, 0);
+ nam = suffix;
+ }
+ }
+ }
+ addfilelist(nam, 0);
+
+ if (s) {
+ /* optimised here-string */
+ int len;
+ unmetafy(s, &len);
+ write_loop(fd, s, len);
+ close(fd);
+ return nam;
+ }
+
+ if ((cmdoutpid = pid = zfork(NULL)) == -1) {
+ /* fork or open error */
+ child_unblock();
+ return nam;
+ } else if (pid) {
+ int os;
+
+ close(fd);
+ os = jobtab[thisjob].stat;
+ waitforpid(pid, 0);
+ cmdoutval = 0;
+ jobtab[thisjob].stat = os;
+ return nam;
+ }
+
+ /* pid == 0 */
+ redup(fd, 1);
+ entersubsh(ESUB_PGRP|ESUB_NOMONITOR);
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, "equalsubst");
+ cmdpop();
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!");
+ 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(NULL, 1);
+
+ if (!tnam) {
+ zerr("failed to create named pipe: %e", errno);
+ return NULL;
+ }
+# ifdef HAVE_MKFIFO
+ if (mkfifo(tnam, 0600) < 0){
+# else
+ if (mknod(tnam, 0010600, 0) < 0){
+# endif
+ zerr("failed to create named pipe: %s, %e", tnam, errno);
+ return NULL;
+ }
+ return tnam;
+}
+#endif /* ! PATH_DEV_FD && HAVE_FIFOS */
+
+/* <(...) or >(...) */
+
+/**/
+char *
+getproc(char *cmd, char **eptr)
+{
+#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD)
+ zerr("doesn't look like your system supports FIFOs.");
+ return NULL;
+#else
+ Eprog prog;
+ int out = *cmd == Inang;
+ char *pnam;
+ pid_t pid;
+ struct timeval bgtime;
+
+#ifndef PATH_DEV_FD
+ int fd;
+ if (thisjob == -1) {
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ if (!(pnam = namedpipe()))
+ return NULL;
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ addfilelist(pnam, 0);
+
+ if ((pid = zfork(&bgtime))) {
+ if (pid == -1)
+ return NULL;
+ if (!out)
+ addproc(pid, NULL, 1, &bgtime);
+ procsubstpid = pid;
+ return pnam;
+ }
+ closem(FDT_UNUSED, 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(ESUB_ASYNC|ESUB_PGRP);
+ redup(fd, out);
+#else /* PATH_DEV_FD */
+ int pipes[2], fd;
+
+ if (thisjob == -1) {
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ pnam = zhalloc(strlen(PATH_DEV_FD) + 1 + DIGBUFSIZE);
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ if (mpipe(pipes) < 0)
+ return NULL;
+ if ((pid = zfork(&bgtime))) {
+ sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]);
+ zclose(pipes[out]);
+ if (pid == -1)
+ {
+ zclose(pipes[!out]);
+ return NULL;
+ }
+ fd = pipes[!out];
+ fdtable[fd] = FDT_PROC_SUBST;
+ addfilelist(NULL, fd);
+ if (!out)
+ {
+ addproc(pid, NULL, 1, &bgtime);
+ }
+ procsubstpid = pid;
+ return pnam;
+ }
+ entersubsh(ESUB_ASYNC|ESUB_PGRP);
+ redup(pipes[out], out);
+ closem(FDT_UNUSED, 0); /* this closes pipes[!out] as well */
+#endif /* PATH_DEV_FD */
+
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, out ? "outsubst" : "insubst");
+ cmdpop();
+ zclose(out);
+ _exit(lastval);
+ return NULL;
+#endif /* HAVE_FIFOS and PATH_DEV_FD not defined */
+}
+
+/*
+ * > >(...) or < <(...) (does not use named pipes)
+ *
+ * If the second argument is 1, this is part of
+ * an "exec < <(...)" or "exec > >(...)" and we shouldn't
+ * wait for the job to finish before continuing.
+ */
+
+/**/
+static int
+getpipe(char *cmd, int nullexec)
+{
+ Eprog prog;
+ int pipes[2], out = *cmd == Inang;
+ pid_t pid;
+ struct timeval bgtime;
+ char *ends;
+
+ if (!(prog = parsecmd(cmd, &ends)))
+ return -1;
+ if (*ends) {
+ zerr("invalid syntax for process substitution in redirection");
+ return -1;
+ }
+ if (mpipe(pipes) < 0)
+ return -1;
+ if ((pid = zfork(&bgtime))) {
+ zclose(pipes[out]);
+ if (pid == -1) {
+ zclose(pipes[!out]);
+ return -1;
+ }
+ if (!nullexec)
+ addproc(pid, NULL, 1, &bgtime);
+ procsubstpid = pid;
+ return pipes[!out];
+ }
+ entersubsh(ESUB_PGRP);
+ redup(pipes[out], out);
+ closem(FDT_UNUSED, 0); /* this closes pipes[!out] as well */
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, out ? "outsubst" : "insubst");
+ cmdpop();
+ _exit(lastval);
+ return 0;
+}
+
+/* open pipes with fds >= 10 */
+
+/**/
+static int
+mpipe(int *pp)
+{
+ if (pipe(pp) < 0) {
+ zerr("pipe failed: %e", errno);
+ return -1;
+ }
+ pp[0] = movefd(pp[0]);
+ pp[1] = movefd(pp[1]);
+ return 0;
+}
+
+/*
+ * Do process substitution with redirection
+ *
+ * If the second argument is 1, this is part of
+ * an "exec < <(...)" or "exec > >(...)" and we shouldn't
+ * wait for the job to finish before continuing.
+ * Likewise, we shouldn't wait if we are opening the file
+ * descriptor using the {fd}>>(...) notation since it stays
+ * valid for subsequent commands.
+ */
+
+/**/
+static void
+spawnpipes(LinkList l, int nullexec)
+{
+ LinkNode n;
+ Redir f;
+ char *str;
+
+ n = firstnode(l);
+ for (; n; incnode(n)) {
+ f = (Redir) getdata(n);
+ if (f->type == REDIR_OUTPIPE || f->type == REDIR_INPIPE) {
+ str = f->name;
+ f->fd2 = getpipe(str, nullexec || f->varid);
+ }
+ }
+}
+
+/* evaluate a [[ ... ]] */
+
+/**/
+static int
+execcond(Estate state, UNUSED(int do_exec))
+{
+ int stat;
+
+ state->pc--;
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "[[");
+ tracingcond++;
+ }
+ cmdpush(CS_COND);
+ stat = evalcond(state, NULL);
+ /*
+ * 2 indicates a syntax error. For compatibility, turn this
+ * into a shell error.
+ */
+ if (stat == 2)
+ errflag |= ERRFLAG_ERROR;
+ cmdpop();
+ if (isset(XTRACE)) {
+ fprintf(xtrerr, " ]]\n");
+ fflush(xtrerr);
+ tracingcond--;
+ }
+ return stat;
+}
+
+/* evaluate a ((...)) arithmetic command */
+
+/**/
+static int
+execarith(Estate state, UNUSED(int do_exec))
+{
+ char *e;
+ mnumber val = zero_mnumber;
+ int htok = 0;
+
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "((");
+ }
+ cmdpush(CS_MATH);
+ e = ecgetstr(state, EC_DUPTOK, &htok);
+ if (htok)
+ singsub(&e);
+ if (isset(XTRACE))
+ fprintf(xtrerr, " %s", e);
+
+ val = matheval(e);
+
+ cmdpop();
+
+ if (isset(XTRACE)) {
+ fprintf(xtrerr, " ))\n");
+ fflush(xtrerr);
+ }
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ return 2;
+ }
+ /* should test for fabs(val.u.d) < epsilon? */
+ return (val.type == MN_INTEGER) ? val.u.l == 0 : val.u.d == 0.0;
+}
+
+/* perform time ... command */
+
+/**/
+static int
+exectime(Estate state, UNUSED(int do_exec))
+{
+ int jb;
+
+ jb = thisjob;
+ if (WC_TIMED_TYPE(state->pc[-1]) == WC_TIMED_EMPTY) {
+ shelltime();
+ return 0;
+ }
+ execpline(state, *state->pc++, Z_TIMED|Z_SYNC, 0);
+ thisjob = jb;
+ return lastval;
+}
+
+/* Define a shell function */
+
+static const char *const ANONYMOUS_FUNCTION_NAME = "(anon)";
+
+/**/
+static int
+execfuncdef(Estate state, Eprog redir_prog)
+{
+ Shfunc shf;
+ char *s = NULL;
+ int signum, nprg, sbeg, nstrs, npats, len, plen, i, htok = 0, ret = 0;
+ int anon_func = 0;
+ Wordcode beg = state->pc, end;
+ Eprog prog;
+ Patprog *pp;
+ LinkList names;
+
+ end = beg + WC_FUNCDEF_SKIP(state->pc[-1]);
+ names = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok);
+ nprg = end - beg;
+ sbeg = *state->pc++;
+ nstrs = *state->pc++;
+ npats = *state->pc++;
+
+ nprg = (end - state->pc);
+ plen = nprg * sizeof(wordcode);
+ len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+ if (htok && names) {
+ execsubst(names);
+ if (errflag) {
+ state->pc = end;
+ return 1;
+ }
+ }
+
+ DPUTS(!names && redir_prog,
+ "Passing redirection to anon function definition.");
+ while (!names || (s = (char *) ugetnode(names))) {
+ if (!names) {
+ prog = (Eprog) zhalloc(sizeof(*prog));
+ prog->nref = -1; /* on the heap */
+ } else {
+ prog = (Eprog) zalloc(sizeof(*prog));
+ prog->nref = 1; /* allocated from permanent storage */
+ }
+ prog->npats = npats;
+ prog->len = len;
+ if (state->prog->dump || !names) {
+ if (!names) {
+ prog->flags = EF_HEAP;
+ prog->dump = NULL;
+ prog->pats = pp = (Patprog *) zhalloc(npats * sizeof(Patprog));
+ } else {
+ prog->flags = EF_MAP;
+ incrdumpcount(state->prog->dump);
+ prog->dump = state->prog->dump;
+ prog->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+ }
+ prog->prog = state->pc;
+ prog->strs = state->strs + sbeg;
+ } else {
+ prog->flags = EF_REAL;
+ prog->pats = pp = (Patprog *) zalloc(len);
+ prog->prog = (Wordcode) (prog->pats + npats);
+ prog->strs = (char *) (prog->prog + nprg);
+ prog->dump = NULL;
+ memcpy(prog->prog, state->pc, plen);
+ memcpy(prog->strs, state->strs + sbeg, nstrs);
+ }
+ for (i = npats; i--; pp++)
+ *pp = dummy_patprog1;
+ prog->shf = NULL;
+
+ shf = (Shfunc) zalloc(sizeof(*shf));
+ shf->funcdef = prog;
+ shf->node.flags = 0;
+ /* No dircache here, not a directory */
+ shf->filename = ztrdup(scriptfilename);
+ shf->lineno =
+ (funcstack && (funcstack->tp == FS_FUNC ||
+ funcstack->tp == FS_EVAL)) ?
+ funcstack->flineno + lineno :
+ lineno;
+ /*
+ * redir_prog is permanently allocated --- but if
+ * this function has multiple names we need an additional
+ * one. Original redir_prog used with the last name
+ * because earlier functions are freed in case of duplicate
+ * names.
+ */
+ if (names && nonempty(names) && redir_prog)
+ shf->redir = dupeprog(redir_prog, 0);
+ else {
+ shf->redir = redir_prog;
+ redir_prog = 0;
+ }
+ shfunc_set_sticky(shf);
+
+ if (!names) {
+ /*
+ * Anonymous function, execute immediately.
+ * Function name is "(anon)".
+ */
+ LinkList args;
+
+ anon_func = 1;
+ shf->node.flags |= PM_ANONYMOUS;
+
+ state->pc = end;
+ end += *state->pc++;
+ args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok);
+
+ if (htok && args) {
+ execsubst(args);
+ if (errflag) {
+ freeeprog(shf->funcdef);
+ if (shf->redir) /* shouldn't be */
+ freeeprog(shf->redir);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ state->pc = end;
+ return 1;
+ }
+ }
+
+ setunderscore((args && nonempty(args)) ?
+ ((char *) getdata(lastnode(args))) : "");
+
+ if (!args)
+ args = newlinklist();
+ shf->node.nam = (char *) ANONYMOUS_FUNCTION_NAME;
+ pushnode(args, shf->node.nam);
+
+ execshfunc(shf, args);
+ ret = lastval;
+
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+ lastval) {
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ fprintf(stderr, "zsh: exit %lld\n", lastval);
+#else
+ fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+#endif
+ fflush(stderr);
+ }
+
+ freeeprog(shf->funcdef);
+ if (shf->redir) /* shouldn't be */
+ freeeprog(shf->redir);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ break;
+ } else {
+ /* is this shell function a signal trap? */
+ if (!strncmp(s, "TRAP", 4) &&
+ (signum = getsignum(s + 4)) != -1) {
+ if (settrap(signum, NULL, ZSIG_FUNC)) {
+ freeeprog(shf->funcdef);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ state->pc = end;
+ return 1;
+ }
+
+ /*
+ * Remove the old node explicitly in case it has
+ * an alternative name
+ */
+ removetrapnode(signum);
+ }
+ shfunctab->addnode(shfunctab, ztrdup(s), shf);
+ }
+ }
+ if (!anon_func)
+ setunderscore("");
+ if (redir_prog) {
+ /* For completeness, shouldn't happen */
+ freeeprog(redir_prog);
+ }
+ state->pc = end;
+ return ret;
+}
+
+/* Duplicate a sticky emulation */
+
+/**/
+
+mod_export Emulation_options
+sticky_emulation_dup(Emulation_options src, int useheap)
+{
+ Emulation_options newsticky = useheap ?
+ hcalloc(sizeof(*src)) : zshcalloc(sizeof(*src));
+ newsticky->emulation = src->emulation;
+ if (src->n_on_opts) {
+ size_t sz = src->n_on_opts * sizeof(*src->on_opts);
+ newsticky->n_on_opts = src->n_on_opts;
+ newsticky->on_opts = useheap ? zhalloc(sz) : zalloc(sz);
+ memcpy(newsticky->on_opts, src->on_opts, sz);
+ }
+ if (src->n_off_opts) {
+ size_t sz = src->n_off_opts * sizeof(*src->off_opts);
+ newsticky->n_off_opts = src->n_off_opts;
+ newsticky->off_opts = useheap ? zhalloc(sz) : zalloc(sz);
+ memcpy(newsticky->off_opts, src->off_opts, sz);
+ }
+
+ return newsticky;
+}
+
+/* Set the sticky emulation attributes for a shell function */
+
+/**/
+
+mod_export void
+shfunc_set_sticky(Shfunc shf)
+{
+ if (sticky)
+ shf->sticky = sticky_emulation_dup(sticky, 0);
+ else
+ shf->sticky = NULL;
+}
+
+
+/* Main entry point to execute a shell function. */
+
+/**/
+static void
+execshfunc(Shfunc shf, LinkList args)
+{
+ LinkList last_file_list = NULL;
+ unsigned char *ocs;
+ int ocsp, osfc;
+
+ if (errflag)
+ return;
+
+ /* thisjob may be invalid if we're called via execsimple: see execcursh */
+ if (!list_pipe && thisjob != -1 && thisjob != list_pipe_job &&
+ !hasprocs(thisjob)) {
+ /* 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, 0);
+ }
+
+ if (isset(XTRACE)) {
+ LinkNode lptr;
+ printprompt4();
+ if (args)
+ for (lptr = firstnode(args); lptr; incnode(lptr)) {
+ if (lptr != firstnode(args))
+ fputc(' ', xtrerr);
+ quotedzputs((char *)getdata(lptr), xtrerr);
+ }
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ queue_signals();
+ ocs = cmdstack;
+ ocsp = cmdsp;
+ cmdstack = (unsigned char *) zalloc(CMDSTACKSZ);
+ cmdsp = 0;
+ if ((osfc = sfcontext) == SFC_NONE)
+ sfcontext = SFC_DIRECT;
+ xtrerr = stderr;
+
+ doshfunc(shf, args, 0);
+
+ sfcontext = osfc;
+ free(cmdstack);
+ cmdstack = ocs;
+ cmdsp = ocsp;
+
+ if (!list_pipe)
+ deletefilelist(last_file_list, 0);
+ unqueue_signals();
+}
+
+/*
+ * 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.
+ *
+ * There are two cases because if we are doing all-singing, all-dancing
+ * non-simple code we load the shell function early in execcmd() (the
+ * action also present in the non-basic version) to check if
+ * there are redirections that need to be handled at that point.
+ * Then we call execautofn_basic() to do the rest.
+ */
+
+/**/
+static int
+execautofn_basic(Estate state, UNUSED(int do_exec))
+{
+ Shfunc shf;
+ char *oldscriptname, *oldscriptfilename;
+
+ shf = state->prog->shf;
+
+ /*
+ * Probably we didn't know the filename where this function was
+ * defined yet.
+ */
+ if (funcstack && !funcstack->filename)
+ funcstack->filename = getshfuncfile(shf);
+
+ oldscriptname = scriptname;
+ oldscriptfilename = scriptfilename;
+ scriptname = dupstring(shf->node.nam);
+ scriptfilename = getshfuncfile(shf);
+ execode(shf->funcdef, 1, 0, "loadautofunc");
+ scriptname = oldscriptname;
+ scriptfilename = oldscriptfilename;
+
+ return lastval;
+}
+
+/**/
+static int
+execautofn(Estate state, UNUSED(int do_exec))
+{
+ Shfunc shf;
+
+ if (!(shf = loadautofn(state->prog->shf, 1, 0, 0)))
+ return 1;
+
+ state->prog->shf = shf;
+ return execautofn_basic(state, 0);
+}
+
+/*
+ * Helper function to install the source file name of a shell function
+ * just autoloaded.
+ *
+ * We attempt to do this efficiently as the typical case is the
+ * directory part is a well-known directory, which is cached, and
+ * the non-directory part is the same as the node name.
+ */
+
+/**/
+static void
+loadautofnsetfile(Shfunc shf, char *fdir)
+{
+ /*
+ * If shf->filename is already the load directory ---
+ * keep it as we can still use it to get the load file.
+ * This makes autoload with an absolute path particularly efficient.
+ */
+ if (!(shf->node.flags & PM_LOADDIR) ||
+ strcmp(shf->filename, fdir) != 0) {
+ /* Old directory name not useful... */
+ dircache_set(&shf->filename, NULL);
+ if (fdir) {
+ /* ...can still cache directory */
+ shf->node.flags |= PM_LOADDIR;
+ dircache_set(&shf->filename, fdir);
+ } else {
+ /* ...no separate directory part to cache, for some reason. */
+ shf->node.flags &= ~PM_LOADDIR;
+ shf->filename = ztrdup(shf->node.nam);
+ }
+ }
+}
+
+/**/
+Shfunc
+loadautofn(Shfunc shf, int fksh, int autol, int current_fpath)
+{
+ int noalias = noaliases, ksh = 1;
+ Eprog prog;
+ char *fdir; /* Directory path where func found */
+
+ pushheap();
+
+ noaliases = (shf->node.flags & PM_UNALIASED);
+ if (shf->filename && shf->filename[0] == '/' &&
+ (shf->node.flags & PM_LOADDIR))
+ {
+ char *spec_path[2];
+ spec_path[0] = dupstring(shf->filename);
+ spec_path[1] = NULL;
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, spec_path, 0);
+ if (prog == &dummy_eprog &&
+ (current_fpath || (shf->node.flags & PM_CUR_FPATH)))
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
+ }
+ else
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
+ noaliases = noalias;
+
+ if (ksh == 1) {
+ ksh = fksh;
+ if (ksh == 1)
+ ksh = (shf->node.flags & PM_KSHSTORED) ? 2 :
+ (shf->node.flags & PM_ZSHSTORED) ? 0 : 1;
+ }
+
+ if (prog == &dummy_eprog) {
+ /* We're not actually in the function; decrement locallevel */
+ locallevel--;
+ zwarn("%s: function definition file not found", shf->node.nam);
+ locallevel++;
+ popheap();
+ return NULL;
+ }
+ if (!prog) {
+ popheap();
+ return NULL;
+ }
+ if (ksh == 2 || (ksh == 1 && isset(KSHAUTOLOAD))) {
+ if (autol) {
+ prog->flags |= EF_RUN;
+
+ freeeprog(shf->funcdef);
+ if (prog->flags & EF_MAP)
+ shf->funcdef = prog;
+ else
+ shf->funcdef = dupeprog(prog, 0);
+ shf->node.flags &= ~PM_UNDEFINED;
+ loadautofnsetfile(shf, fdir);
+ } else {
+ VARARR(char, n, strlen(shf->node.nam) + 1);
+ strcpy(n, shf->node.nam);
+ execode(prog, 1, 0, "evalautofunc");
+ shf = (Shfunc) shfunctab->getnode(shfunctab, n);
+ if (!shf || (shf->node.flags & PM_UNDEFINED)) {
+ /* We're not actually in the function; decrement locallevel */
+ locallevel--;
+ zwarn("%s: function not defined by file", n);
+ locallevel++;
+ popheap();
+ return NULL;
+ }
+ }
+ } else {
+ freeeprog(shf->funcdef);
+ if (prog->flags & EF_MAP)
+ shf->funcdef = stripkshdef(prog, shf->node.nam);
+ else
+ shf->funcdef = dupeprog(stripkshdef(prog, shf->node.nam), 0);
+ shf->node.flags &= ~PM_UNDEFINED;
+ loadautofnsetfile(shf, fdir);
+ }
+ popheap();
+
+ return shf;
+}
+
+/*
+ * Check if a sticky emulation differs from the current one.
+ */
+
+/**/
+
+int sticky_emulation_differs(Emulation_options sticky2)
+{
+ /* If no new sticky emulation, not a different emulation */
+ if (!sticky2)
+ return 0;
+ /* If no current sticky emulation, different */
+ if (!sticky)
+ return 1;
+ /* If basic emulation different, different */
+ if (sticky->emulation != sticky2->emulation)
+ return 1;
+ /* If differing numbers of options, different */
+ if (sticky->n_on_opts != sticky2->n_on_opts ||
+ sticky->n_off_opts != sticky2->n_off_opts)
+ return 1;
+ /*
+ * We need to compare option arrays, if non-null.
+ * We made parseopts() create the list of options in option
+ * order to make this easy.
+ */
+ /* If different options turned on, different */
+ if (sticky->n_on_opts &&
+ memcmp(sticky->on_opts, sticky2->on_opts,
+ sticky->n_on_opts * sizeof(*sticky->on_opts)) != 0)
+ return 1;
+ /* If different options turned on, different */
+ if (sticky->n_off_opts &&
+ memcmp(sticky->off_opts, sticky2->off_opts,
+ sticky->n_off_opts * sizeof(*sticky->off_opts)) != 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * execute a shell function
+ *
+ * name is the name of the function
+ *
+ * prog is the code to execute
+ *
+ * doshargs, if set, are parameters to pass to the function,
+ * in which the first element is the function name (even if
+ * FUNCTIONARGZERO is set as this is handled inside this function).
+ *
+ * If noreturnval is nonzero, then reset the current return
+ * value (lastval) to its value before the shell function
+ * was executed. However, in any case return the status value
+ * from the function (i.e. if noreturnval is not set, this
+ * will be the same as lastval).
+ */
+
+/**/
+mod_export int
+doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
+{
+ char **pptab, **x;
+ int ret;
+ char *name = shfunc->node.nam;
+ int flags = shfunc->node.flags;
+ char *fname = dupstring(name);
+ Eprog prog;
+ static int oflags;
+ static int funcdepth;
+ Heap funcheap;
+
+ queue_signals(); /* Lots of memory and global state changes coming */
+
+ NEWHEAPS(funcheap) {
+ /*
+ * Save data in heap rather than on stack to keep recursive
+ * function cost down --- use of heap memory should be efficient
+ * at this point. Saving is not actually massive.
+ */
+ Funcsave funcsave = zhalloc(sizeof(struct funcsave));
+ funcsave->scriptname = scriptname;
+ funcsave->argv0 = NULL;
+ funcsave->breaks = breaks;
+ funcsave->contflag = contflag;
+ funcsave->loops = loops;
+ funcsave->lastval = lastval;
+ funcsave->pipestats = NULL;
+ funcsave->numpipestats = numpipestats;
+ funcsave->noerrexit = noerrexit;
+ if (trap_state == TRAP_STATE_PRIMED)
+ trap_return--;
+ /*
+ * Suppression of ERR_RETURN is turned off in function scope.
+ */
+ noerrexit &= ~NOERREXIT_RETURN;
+ if (noreturnval) {
+ /*
+ * Easiest to use the heap here since we're bracketed
+ * immediately by a pushheap/popheap pair.
+ */
+ size_t bytes = sizeof(int)*numpipestats;
+ funcsave->pipestats = (int *)zhalloc(bytes);
+ memcpy(funcsave->pipestats, pipestats, bytes);
+ }
+
+ starttrapscope();
+ startpatternscope();
+
+ pptab = pparams;
+ if (!(flags & PM_UNDEFINED))
+ scriptname = dupstring(name);
+ funcsave->zoptind = zoptind;
+ funcsave->optcind = optcind;
+ if (!isset(POSIXBUILTINS)) {
+ zoptind = 1;
+ optcind = 0;
+ }
+
+ /* 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(funcsave->opts, opts, sizeof(opts));
+ funcsave->emulation = emulation;
+ funcsave->sticky = sticky;
+
+ if (sticky_emulation_differs(shfunc->sticky)) {
+ /*
+ * Function is marked for sticky emulation.
+ * Enable it now.
+ *
+ * We deliberately do not do this if the sticky emulation
+ * in effect is the same as that requested. This enables
+ * option setting naturally within emulation environments.
+ * Note that a difference in EMULATE_FULLY (emulate with
+ * or without -R) counts as a different environment.
+ *
+ * This propagates the sticky emulation to subfunctions.
+ */
+ sticky = sticky_emulation_dup(shfunc->sticky, 1);
+ emulation = sticky->emulation;
+ funcsave->restore_sticky = 1;
+ installemulation(emulation, opts);
+ if (sticky->n_on_opts) {
+ OptIndex *onptr;
+ for (onptr = sticky->on_opts;
+ onptr < sticky->on_opts + sticky->n_on_opts;
+ onptr++)
+ opts[*onptr] = 1;
+ }
+ if (sticky->n_off_opts) {
+ OptIndex *offptr;
+ for (offptr = sticky->off_opts;
+ offptr < sticky->off_opts + sticky->n_off_opts;
+ offptr++)
+ opts[*offptr] = 0;
+ }
+ /* All emulations start with pattern disables clear */
+ clearpatterndisables();
+ } else
+ funcsave->restore_sticky = 0;
+
+ if (flags & (PM_TAGGED|PM_TAGGED_LOCAL))
+ opts[XTRACE] = 1;
+ else if (oflags & PM_TAGGED_LOCAL) {
+ if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME /* pointer comparison */)
+ flags |= PM_TAGGED_LOCAL;
+ else
+ opts[XTRACE] = 0;
+ }
+ if (flags & PM_WARNNESTED)
+ opts[WARNNESTEDVAR] = 1;
+ else if (oflags & PM_WARNNESTED) {
+ if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME)
+ flags |= PM_WARNNESTED;
+ else
+ opts[WARNNESTEDVAR] = 0;
+ }
+ funcsave->oflags = oflags;
+ /*
+ * oflags is static, because we compare it on the next recursive
+ * call. Hence also we maintain a saved version for restoring
+ * the previous value of oflags after the call.
+ */
+ oflags = flags;
+ opts[PRINTEXITVALUE] = 0;
+ if (doshargs) {
+ LinkNode node;
+
+ node = firstnode(doshargs);
+ pparams = x = (char **) zshcalloc(((sizeof *x) *
+ (1 + countlinknodes(doshargs))));
+ if (isset(FUNCTIONARGZERO)) {
+ funcsave->argv0 = argzero;
+ argzero = ztrdup(getdata(node));
+ }
+ /* first node contains name regardless of option */
+ node = node->next;
+ for (; node; node = node->next, x++)
+ *x = ztrdup(getdata(node));
+ } else {
+ pparams = (char **) zshcalloc(sizeof *pparams);
+ if (isset(FUNCTIONARGZERO)) {
+ funcsave->argv0 = argzero;
+ argzero = ztrdup(argzero);
+ }
+ }
+ ++funcdepth;
+ if (zsh_funcnest >= 0 && funcdepth > zsh_funcnest) {
+ zerr("maximum nested function level reached; increase FUNCNEST?");
+ lastval = 1;
+ goto undoshfunc;
+ }
+ funcsave->fstack.name = dupstring(name);
+ /*
+ * The caller is whatever is immediately before on the stack,
+ * unless we're at the top, in which case it's the script
+ * or interactive shell name.
+ */
+ funcsave->fstack.caller = funcstack ? funcstack->name :
+ dupstring(funcsave->argv0 ? funcsave->argv0 : argzero);
+ funcsave->fstack.lineno = lineno;
+ funcsave->fstack.prev = funcstack;
+ funcsave->fstack.tp = FS_FUNC;
+ funcstack = &funcsave->fstack;
+
+ funcsave->fstack.flineno = shfunc->lineno;
+ funcsave->fstack.filename = getshfuncfile(shfunc);
+
+ prog = shfunc->funcdef;
+ if (prog->flags & EF_RUN) {
+ Shfunc shf;
+
+ prog->flags &= ~EF_RUN;
+
+ runshfunc(prog, NULL, funcsave->fstack.name);
+
+ if (!(shf = (Shfunc) shfunctab->getnode(shfunctab,
+ (name = fname)))) {
+ zwarn("%s: function not defined by file", name);
+ if (noreturnval)
+ errflag |= ERRFLAG_ERROR;
+ else
+ lastval = 1;
+ goto doneshfunc;
+ }
+ prog = shf->funcdef;
+ }
+ runshfunc(prog, wrappers, funcsave->fstack.name);
+ doneshfunc:
+ funcstack = funcsave->fstack.prev;
+ undoshfunc:
+ --funcdepth;
+ if (retflag) {
+ /*
+ * This function is forced to return.
+ */
+ retflag = 0;
+ /*
+ * The calling function isn't necessarily forced to return,
+ * but it should be made sensitive to ERR_EXIT and
+ * ERR_RETURN as the assumptions we made at the end of
+ * constructs within this function no longer apply. If
+ * there are cases where this is not true, they need adding
+ * to C03traps.ztst.
+ */
+ this_noerrexit = 0;
+ breaks = funcsave->breaks;
+ }
+ freearray(pparams);
+ if (funcsave->argv0) {
+ zsfree(argzero);
+ argzero = funcsave->argv0;
+ }
+ pparams = pptab;
+ if (!isset(POSIXBUILTINS)) {
+ zoptind = funcsave->zoptind;
+ optcind = funcsave->optcind;
+ }
+ scriptname = funcsave->scriptname;
+ oflags = funcsave->oflags;
+
+ endpatternscope(); /* before restoring old LOCALPATTERNS */
+
+ if (funcsave->restore_sticky) {
+ /*
+ * If we switched to an emulation environment just for
+ * this function, we interpret the option and emulation
+ * switch as being a firewall between environments.
+ */
+ memcpy(opts, funcsave->opts, sizeof(opts));
+ emulation = funcsave->emulation;
+ sticky = funcsave->sticky;
+ } else if (isset(LOCALOPTIONS)) {
+ /* restore all shell options except PRIVILEGED and RESTRICTED */
+ funcsave->opts[PRIVILEGED] = opts[PRIVILEGED];
+ funcsave->opts[RESTRICTED] = opts[RESTRICTED];
+ memcpy(opts, funcsave->opts, sizeof(opts));
+ emulation = funcsave->emulation;
+ } else {
+ /* just restore a couple. */
+ opts[XTRACE] = funcsave->opts[XTRACE];
+ opts[PRINTEXITVALUE] = funcsave->opts[PRINTEXITVALUE];
+ opts[LOCALOPTIONS] = funcsave->opts[LOCALOPTIONS];
+ opts[LOCALLOOPS] = funcsave->opts[LOCALLOOPS];
+ opts[WARNNESTEDVAR] = funcsave->opts[WARNNESTEDVAR];
+ }
+
+ if (opts[LOCALLOOPS]) {
+ if (contflag)
+ zwarn("`continue' active at end of function scope");
+ if (breaks)
+ zwarn("`break' active at end of function scope");
+ breaks = funcsave->breaks;
+ contflag = funcsave->contflag;
+ loops = funcsave->loops;
+ }
+
+ endtrapscope();
+
+ if (trap_state == TRAP_STATE_PRIMED)
+ trap_return++;
+ ret = lastval;
+ noerrexit = funcsave->noerrexit;
+ if (noreturnval) {
+ lastval = funcsave->lastval;
+ numpipestats = funcsave->numpipestats;
+ memcpy(pipestats, funcsave->pipestats, sizeof(int)*numpipestats);
+ }
+ } OLDHEAPS;
+
+ unqueue_signals();
+
+ /*
+ * Exit with a tidy up.
+ * Only leave if we're at the end of the appropriate function ---
+ * not a nested function. As we usually skip the function body,
+ * the only likely case where we need that second test is
+ * when we have an "always" block. The endparamscope() has
+ * already happened, hence the "+1" here.
+ *
+ * If we are in an exit trap, finish it first... we wouldn't set
+ * exit_pending if we were already in one.
+ */
+ if (exit_pending && exit_level >= locallevel+1 && !in_exit_trap) {
+ if (locallevel > forklevel) {
+ /* Still functions to return: force them to do so. */
+ retflag = 1;
+ breaks = loops;
+ } else {
+ /*
+ * All functions finished: time to exit the shell.
+ * We already did the `stopmsg' test when the
+ * exit command was handled.
+ */
+ stopmsg = 1;
+ zexit(exit_pending >> 1, 0);
+ }
+ }
+
+ return ret;
+}
+
+/* This finally executes a shell function and any function wrappers *
+ * defined by modules. This works by calling the wrapper function which *
+ * in turn has to call back this function with the arguments it gets. */
+
+/**/
+mod_export void
+runshfunc(Eprog prog, FuncWrap wrap, char *name)
+{
+ int cont, ouu;
+ char *ou;
+
+ queue_signals();
+
+ ou = zalloc(ouu = underscoreused);
+ if (ou)
+ memcpy(ou, zunderscore, underscoreused);
+
+ while (wrap) {
+ wrap->module->wrapper++;
+ cont = wrap->handler(prog, wrap->next, name);
+ wrap->module->wrapper--;
+
+ if (!wrap->module->wrapper &&
+ (wrap->module->node.flags & MOD_UNLOAD))
+ unload_module(wrap->module);
+
+ if (!cont) {
+ if (ou)
+ zfree(ou, ouu);
+ unqueue_signals();
+ return;
+ }
+ wrap = wrap->next;
+ }
+ startparamscope();
+ execode(prog, 1, 0, "shfunc"); /* handles signal unqueueing */
+ if (ou) {
+ setunderscore(ou);
+ zfree(ou, ouu);
+ }
+ endparamscope();
+
+ unqueue_signals();
+}
+
+/*
+ * Search fpath for an undefined function. Finds the file, and returns the
+ * list of its contents.
+ *
+ * If test is 0, load the function.
+ *
+ * If test_only is 1, don't load function, just test for it:
+ * Non-null return means function was found
+ *
+ * *fdir points to path at which found (as passed in, not duplicated)
+ */
+
+/**/
+Eprog
+getfpfunc(char *s, int *ksh, char **fdir, char **alt_path, int test_only)
+{
+ char **pp, buf[PATH_MAX+1];
+ off_t len;
+ off_t rlen;
+ char *d;
+ Eprog r;
+ int fd;
+
+ pp = alt_path ? alt_path : 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);
+ if ((r = try_dump_file(*pp, s, buf, ksh, test_only))) {
+ if (fdir)
+ *fdir = *pp;
+ return r;
+ }
+ unmetafy(buf, NULL);
+ if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
+ struct stat st;
+ if (!fstat(fd, &st) && S_ISREG(st.st_mode) &&
+ (len = lseek(fd, 0, 2)) != -1) {
+ if (test_only) {
+ close(fd);
+ if (fdir)
+ *fdir = *pp;
+ return &dummy_eprog;
+ }
+ d = (char *) zalloc(len + 1);
+ lseek(fd, 0, 0);
+ if ((rlen = read(fd, d, len)) >= 0) {
+ char *oldscriptname = scriptname;
+
+ close(fd);
+ d[rlen] = '\0';
+ d = metafy(d, rlen, META_REALLOC);
+
+ scriptname = dupstring(s);
+ r = parse_string(d, 1);
+ scriptname = oldscriptname;
+
+ if (fdir)
+ *fdir = *pp;
+
+ zfree(d, len + 1);
+
+ return r;
+ } else
+ close(fd);
+
+ zfree(d, len + 1);
+ } else
+ close(fd);
+ }
+ }
+ return test_only ? NULL : &dummy_eprog;
+}
+
+/* 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. */
+
+/**/
+Eprog
+stripkshdef(Eprog prog, char *name)
+{
+ Wordcode pc;
+ wordcode code;
+ char *ptr1, *ptr2;
+
+ if (!prog)
+ return NULL;
+ pc = prog->prog;
+ code = *pc++;
+ if (wc_code(code) != WC_LIST ||
+ (WC_LIST_TYPE(code) & (Z_SYNC|Z_END|Z_SIMPLE)) != (Z_SYNC|Z_END|Z_SIMPLE))
+ return prog;
+ pc++;
+ code = *pc++;
+ if (wc_code(code) != WC_FUNCDEF || *pc != 1)
+ return prog;
+
+ /*
+ * See if name of function requested (name) is same as
+ * name of function in word code. name may still have "-"
+ * tokenised. The word code shouldn't, as function names should be
+ * untokenised, but reports say it sometimes does.
+ */
+ ptr1 = name;
+ ptr2 = ecrawstr(prog, pc + 1, NULL);
+ while (*ptr1 && *ptr2) {
+ if (*ptr1 != *ptr2 && *ptr1 != Dash && *ptr1 != '-' &&
+ *ptr2 != Dash && *ptr2 != '-')
+ break;
+ ptr1++;
+ ptr2++;
+ }
+ if (*ptr1 || *ptr2)
+ return prog;
+
+ {
+ Eprog ret;
+ Wordcode end = pc + WC_FUNCDEF_SKIP(code);
+ int sbeg = pc[2], nstrs = pc[3], nprg, npats = pc[4], plen, len, i;
+ Patprog *pp;
+
+ pc += 5;
+
+ nprg = end - pc;
+ plen = nprg * sizeof(wordcode);
+ len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+ if (prog->flags & EF_MAP) {
+ ret = prog;
+ free(prog->pats);
+ ret->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+ ret->prog = pc;
+ ret->strs = prog->strs + sbeg;
+ } else {
+ ret = (Eprog) zhalloc(sizeof(*ret));
+ ret->flags = EF_HEAP;
+ ret->pats = pp = (Patprog *) zhalloc(len);
+ ret->prog = (Wordcode) (ret->pats + npats);
+ ret->strs = (char *) (ret->prog + nprg);
+ memcpy(ret->prog, pc, plen);
+ memcpy(ret->strs, prog->strs + sbeg, nstrs);
+ ret->dump = NULL;
+ }
+ ret->len = len;
+ ret->npats = npats;
+ for (i = npats; i--; pp++)
+ *pp = dummy_patprog1;
+ ret->shf = NULL;
+
+ return ret;
+ }
+}
+
+/* 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+1], **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, *us2 = NULL;
+ int ret;
+
+ /*
+ * If CHASEDOTS and CHASELINKS are not set, we want to rationalize the
+ * path by removing foo/.. combinations in the logical rather than
+ * the physical path. If either is set, we test the physical path.
+ */
+ if (!isset(CHASEDOTS) && !isset(CHASELINKS)) {
+ if (*s != '/')
+ us = tricat(pwd[1] ? pwd : "", "/", s);
+ else
+ us = ztrdup(s);
+ fixdir(us2 = us);
+ } else
+ us = unmeta(s);
+ ret = !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+ if (us2)
+ free(us2);
+ return ret;
+}
+
+/**/
+void
+execsave(void)
+{
+ struct execstack *es;
+
+ es = (struct execstack *) zalloc(sizeof(struct execstack));
+ 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->use_cmdoutval = use_cmdoutval;
+ es->procsubstpid = procsubstpid;
+ es->trap_return = trap_return;
+ es->trap_state = trap_state;
+ es->trapisfunc = trapisfunc;
+ es->traplocallevel = traplocallevel;
+ es->noerrs = noerrs;
+ es->this_noerrexit = this_noerrexit;
+ es->underscore = ztrdup(zunderscore);
+ es->next = exstack;
+ exstack = es;
+ noerrs = cmdoutpid = 0;
+}
+
+/**/
+void
+execrestore(void)
+{
+ struct execstack *en = exstack;
+
+ DPUTS(!exstack, "BUG: execrestore() without execsave()");
+
+ queue_signals();
+ exstack = exstack->next;
+
+ list_pipe_pid = en->list_pipe_pid;
+ nowait = en->nowait;
+ pline_level = en->pline_level;
+ list_pipe_child = en->list_pipe_child;
+ list_pipe_job = en->list_pipe_job;
+ strcpy(list_pipe_text, en->list_pipe_text);
+ lastval = en->lastval;
+ noeval = en->noeval;
+ badcshglob = en->badcshglob;
+ cmdoutpid = en->cmdoutpid;
+ cmdoutval = en->cmdoutval;
+ use_cmdoutval = en->use_cmdoutval;
+ procsubstpid = en->procsubstpid;
+ trap_return = en->trap_return;
+ trap_state = en->trap_state;
+ trapisfunc = en->trapisfunc;
+ traplocallevel = en->traplocallevel;
+ noerrs = en->noerrs;
+ this_noerrexit = en->this_noerrexit;
+ setunderscore(en->underscore);
+ zsfree(en->underscore);
+ free(en);
+
+ unqueue_signals();
+}