diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/exec.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/exec.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/exec.c | 6250 |
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(); +} |
