mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-09-10 12:40:58 +02:00
There are two underlying ideas here: (1) Keeping signals queued around anything that's doing memory management (including push/pop of the heap) has become crucial. (2) Anytime the shell is going to run a command, be it buitin or external, it must be both safe and necessary to process any queued signals, so that the apparent order of signal arrival and command execution is preserved.
5621 lines
141 KiB
C
5621 lines
141 KiB
C
/*
|
|
* 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
|
|
};
|
|
|
|
/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */
|
|
|
|
/**/
|
|
int 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;
|
|
|
|
/* 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 functions 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(0, 0, bin_whence, 0, -1, BIN_COMMAND, "vV", 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;
|
|
|
|
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];
|
|
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;
|
|
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)
|
|
return 127;
|
|
|
|
pushnode(args, arg0);
|
|
return doshfunc(shf, args, 1);
|
|
}
|
|
|
|
/* execute an external command */
|
|
|
|
/**/
|
|
static void
|
|
execute(LinkList args, int flags, int defpath)
|
|
{
|
|
Cmdnam cn;
|
|
char buf[MAXCMDLEN], buf2[MAXCMDLEN];
|
|
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);
|
|
#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 *s, pbuf[PATH_MAX];
|
|
char *dptr, *pe, *ps = DEFAULT_PATH;
|
|
|
|
for(;ps;ps = pe ? pe+1 : NULL) {
|
|
pe = strchr(ps, ':');
|
|
if (*ps == '/') {
|
|
s = pbuf;
|
|
if (pe)
|
|
struncpy(&s, ps, pe-ps);
|
|
else
|
|
strucpy(&s, ps);
|
|
*s++ = '/';
|
|
if ((s - pbuf) + strlen(arg0) >= PATH_MAX)
|
|
continue;
|
|
strucpy(&s, arg0);
|
|
if (iscom(pbuf))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ps) {
|
|
if (commandnotfound(arg0, args) == 0)
|
|
_exit(0);
|
|
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], *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(0);
|
|
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).
|
|
*/
|
|
|
|
/**/
|
|
mod_export char *
|
|
findcmd(char *arg0, int docopy)
|
|
{
|
|
char **pp;
|
|
char *z, *s, buf[MAXCMDLEN];
|
|
Cmdnam cn;
|
|
|
|
cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
|
|
if (!cn && isset(HASHCMDS))
|
|
cn = hashcmd(arg0, path);
|
|
if ((int) strlen(arg0) > PATH_MAX)
|
|
return NULL;
|
|
for (s = arg0; *s; s++)
|
|
if (*s == '/') {
|
|
RET_IF_COM(arg0);
|
|
if (arg0 == s || unset(PATHDIRS)) {
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (cn) {
|
|
char nn[PATH_MAX];
|
|
|
|
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;
|
|
}
|
|
|
|
/**/
|
|
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);
|
|
}
|
|
|
|
/**/
|
|
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];
|
|
char **pq;
|
|
|
|
for (; *pp; pp++)
|
|
if (**pp == '/') {
|
|
s = buf;
|
|
strucpy(&s, *pp);
|
|
*s++ = '/';
|
|
if ((s - buf) + strlen(arg0) >= PATH_MAX)
|
|
continue;
|
|
strcpy(s, arg0);
|
|
if (iscom(buf))
|
|
break;
|
|
}
|
|
|
|
if (!*pp)
|
|
return NULL;
|
|
|
|
cn = (Cmdnam) 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 sig, monitor, job_control_ok;
|
|
|
|
if (!(flags & ESUB_KEEPTRAP))
|
|
for (sig = 0; sig < VSIGCOUNT; sig++)
|
|
if (!(sigtrapped[sig] & ZSIG_FUNC) &&
|
|
sig != SIGDEBUG && sig != SIGZERR)
|
|
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 (!job_control_ok) {
|
|
/*
|
|
* If this process is not goign 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);
|
|
if (!job_control_ok)
|
|
opts[MONITOR] = 0;
|
|
opts[USEZLE] = 0;
|
|
zleactive = 0;
|
|
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;
|
|
|
|
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 = 1;
|
|
if (ltype & Z_SIMPLE) /* skip the line number */
|
|
pc2++;
|
|
pm = setsparam("ZSH_DEBUG_CMD", getpermtext(state->prog, pc2, 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) {
|
|
next = state->pc + WC_SUBLIST_SKIP(code);
|
|
if (!oldnoerrexit)
|
|
noerrexit = (WC_SUBLIST_TYPE(code) != WC_SUBLIST_END);
|
|
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. */
|
|
donetrap = 1;
|
|
goto sublist_done;
|
|
} else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
|
|
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. */
|
|
donetrap = 1;
|
|
goto sublist_done;
|
|
} else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
|
|
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 == 2" only applies until
|
|
* we hit execcmd on the way down. We're now
|
|
* on the way back up, so don't restore it.
|
|
*/
|
|
noerrexit = (oldnoerrexit == 2) ? 0 : 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 = 1;
|
|
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 (!noerrexit && !donetrap) {
|
|
if (sigtrapped[SIGZERR] && lastval) {
|
|
dotrap(SIGZERR);
|
|
donetrap = 1;
|
|
}
|
|
if (lastval) {
|
|
int errreturn = isset(ERRRETURN) &&
|
|
(isset(INTERACTIVE) || locallevel || sourcelevel);
|
|
int errexit = isset(ERREXIT) ||
|
|
(isset(ERRRETURN) && !errreturn);
|
|
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) {
|
|
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) {
|
|
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;
|
|
}
|
|
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;
|
|
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();
|
|
dont_queue_signals();
|
|
child_block();
|
|
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;
|
|
|
|
if (pipe(synch) < 0 || (pid = zfork(&bgtime)) == -1) {
|
|
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) {
|
|
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 = pid;
|
|
}
|
|
if ((list_pipe || last1) && hasprocs(list_pipe_job))
|
|
killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
|
|
break;
|
|
}
|
|
else {
|
|
close(synch[0]);
|
|
entersubsh(ESUB_ASYNC);
|
|
if (jobtab[list_pipe_job].procs) {
|
|
if (setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader)
|
|
== -1) {
|
|
setpgrp(0L, mypgrp = getpid());
|
|
}
|
|
} else
|
|
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;
|
|
}
|
|
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)
|
|
{
|
|
pid_t pid;
|
|
int pipes[2];
|
|
|
|
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 && !sourcelevel))
|
|
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(state, input, output, how, last1 ? 1 : 2);
|
|
else {
|
|
int old_list_pipe = list_pipe;
|
|
int subsh_close = -1;
|
|
Wordcode next = state->pc + (*state->pc), pc;
|
|
wordcode code;
|
|
|
|
state->pc++;
|
|
for (pc = state->pc; wc_code(code = *pc) == WC_REDIR;
|
|
pc += WC_REDIR_WORDS(code));
|
|
|
|
if (mpipe(pipes) < 0) {
|
|
/* FIXME */
|
|
}
|
|
|
|
/* if we are doing "foo | bar" where foo is a current *
|
|
* shell command, do foo in a subshell and do the *
|
|
* rest of the pipeline in the current shell. */
|
|
if (wc_code(code) >= WC_CURSH && (how & Z_SYNC)) {
|
|
int synch[2];
|
|
struct timeval bgtime;
|
|
|
|
if (pipe(synch) < 0) {
|
|
zerr("pipe failed: %e", errno);
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
return;
|
|
} else if ((pid = zfork(&bgtime)) == -1) {
|
|
close(synch[0]);
|
|
close(synch[1]);
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
return;
|
|
} else if (pid) {
|
|
char dummy, *text;
|
|
|
|
text = getjobtext(state->prog, state->pc);
|
|
addproc(pid, text, 0, &bgtime);
|
|
close(synch[1]);
|
|
read_loop(synch[0], &dummy, 1);
|
|
close(synch[0]);
|
|
} else {
|
|
zclose(pipes[0]);
|
|
close(synch[0]);
|
|
entersubsh(((how & Z_ASYNC) ? ESUB_ASYNC : 0)
|
|
| ESUB_PGRP | ESUB_KEEPTRAP);
|
|
close(synch[1]);
|
|
execcmd(state, input, pipes[1], how, 1);
|
|
_exit(lastval);
|
|
}
|
|
} else {
|
|
/* otherwise just do the pipeline normally. */
|
|
addfilelist(NULL, pipes[0]);
|
|
subsh_close = pipes[0];
|
|
execcmd(state, input, pipes[1], how, 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();
|
|
if (subsh_close != pipes[0])
|
|
zclose(pipes[0]);
|
|
}
|
|
}
|
|
|
|
/* 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 && errno != EBADF) {
|
|
zerr("cannot duplicate fd %d: %e", fd1, errno);
|
|
mfds[fd1] = NULL;
|
|
closemnodes(mfds);
|
|
return;
|
|
}
|
|
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) &&
|
|
locallevel > 0 && isset(WARNCREATEGLOBAL)) ?
|
|
ASSPM_WARN_CREATE : 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 (vl && htok) {
|
|
prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
|
|
PREFORK_ASSIGN));
|
|
if (errflag) {
|
|
state->pc = opc;
|
|
return;
|
|
}
|
|
if (!isstr || (isset(GLOBASSIGN) && isstr &&
|
|
haswilds((char *)getdata(firstnode(vl))))) {
|
|
globlist(vl, 0);
|
|
/* 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);
|
|
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;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
execcmd(Estate state, int input, int output, int how, int last1)
|
|
{
|
|
HashNode hn = NULL;
|
|
LinkList args, filelist = NULL;
|
|
LinkNode node;
|
|
Redir fn;
|
|
struct multio *mfds[10];
|
|
char *text;
|
|
int save[10];
|
|
int fil, dfil, is_cursh, type, do_exec = 0, redir_err = 0, i, htok = 0;
|
|
int nullexec = 0, assign = 0, forked = 0, postassigns = 0;
|
|
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;
|
|
LinkList redir;
|
|
wordcode code;
|
|
Wordcode beg = state->pc, varspc, assignspc = (Wordcode)0;
|
|
FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
|
|
|
|
doneps4 = 0;
|
|
redir = (wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
|
|
if (wc_code(*state->pc) == WC_ASSIGN) {
|
|
cmdoutval = 0;
|
|
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
|
|
varspc = NULL;
|
|
|
|
code = *state->pc++;
|
|
|
|
type = wc_code(code);
|
|
|
|
/* 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 (type) {
|
|
case WC_SIMPLE:
|
|
args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok);
|
|
break;
|
|
|
|
case WC_TYPESET:
|
|
args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP, &htok);
|
|
postassigns = *state->pc++;
|
|
assignspc = state->pc;
|
|
for (i = 0; i < 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:
|
|
args = NULL;
|
|
}
|
|
|
|
/*
|
|
* If assignment but no command get the status from variable
|
|
* assignment.
|
|
*/
|
|
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"));
|
|
}
|
|
|
|
/* 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) {
|
|
while (args && nonempty(args)) {
|
|
char *cmdarg = (char *) peekfirst(args);
|
|
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 (!(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)))
|
|
return;
|
|
assign = (hn->flags & BINF_MAGICEQUALS);
|
|
break;
|
|
}
|
|
checked = 0;
|
|
if ((cflags & BINF_COMMAND) && nextnode(firstnode(args))) {
|
|
/* check for options to command builtin */
|
|
char *next = (char *) getdata(nextnode(firstnode(args)));
|
|
char *cmdopt;
|
|
if (next && *next == '-' && strlen(next) == 2 &&
|
|
(cmdopt = strchr("pvV", next[1])))
|
|
{
|
|
if (*cmdopt == 'p') {
|
|
uremnode(args, firstnode(args));
|
|
use_defpath = 1;
|
|
if (nextnode(firstnode(args)))
|
|
next = (char *) getdata(nextnode(firstnode(args)));
|
|
} else {
|
|
hn = &commandbn.node;
|
|
is_builtin = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!strcmp(next, "--"))
|
|
uremnode(args, firstnode(args));
|
|
}
|
|
if ((cflags & BINF_EXEC) && nextnode(firstnode(args))) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
char *next = (char *) getdata(nextnode(firstnode(args)));
|
|
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 (next && *next == '-' && strlen(next) >= 2) {
|
|
if (!firstnode(args)) {
|
|
zerr("exec requires a command to execute");
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
goto done;
|
|
}
|
|
uremnode(args, firstnode(args));
|
|
if (!strcmp(next, "--"))
|
|
break;
|
|
for (cmdopt = &next[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 (!firstnode(args)) {
|
|
zerr("exec requires a command to execute");
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
goto done;
|
|
}
|
|
if (!nextnode(firstnode(args))) {
|
|
zerr("exec flag -a requires a parameter");
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
goto done;
|
|
}
|
|
exec_argv0 = (char *)
|
|
getdata(nextnode(firstnode(args)));
|
|
uremnode(args, firstnode(args));
|
|
}
|
|
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;
|
|
return;
|
|
}
|
|
}
|
|
if (firstnode(args) && nextnode(firstnode(args)))
|
|
next = (char *) getdata(nextnode(firstnode(args)));
|
|
}
|
|
if (exec_argv0) {
|
|
char *str, *s;
|
|
size_t sz = strlen(exec_argv0);
|
|
str = s = zalloc(5 + 1 + sz + 1);
|
|
strcpy(s, "ARGV0=");
|
|
s+=6;
|
|
strcpy(s, exec_argv0);
|
|
zputenv(str);
|
|
}
|
|
}
|
|
uremnode(args, firstnode(args));
|
|
hn = NULL;
|
|
if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if we get this far, it is OK to pay attention to lastval again */
|
|
if (noerrexit == 2 && !is_shfunc)
|
|
noerrexit = 0;
|
|
|
|
/* Do prefork substitutions */
|
|
esprefork = (assign || isset(MAGICEQUALSUBST)) ? PREFORK_TYPESET : 0;
|
|
if (args && htok)
|
|
prefork(args, esprefork);
|
|
|
|
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;
|
|
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;
|
|
return;
|
|
} else {
|
|
/*
|
|
* No arguments. Reset the status if there were
|
|
* arguments before and no command substitution
|
|
* has provided a status.
|
|
*/
|
|
cmdoutval = use_cmdoutval ? lastval : 0;
|
|
if (varspc)
|
|
addvars(state, varspc, 0);
|
|
if (errflag)
|
|
lastval = 1;
|
|
else
|
|
lastval = cmdoutval;
|
|
if (isset(XTRACE)) {
|
|
fputc('\n', xtrerr);
|
|
fflush(xtrerr);
|
|
}
|
|
return;
|
|
}
|
|
} else if (isset(RESTRICTED) && (cflags & BINF_EXEC) && do_exec) {
|
|
zerrnam("exec", "%s: restricted",
|
|
(char *) getdata(firstnode(args)));
|
|
lastval = 1;
|
|
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
|
|
*/
|
|
if (errflag || checked || is_builtin ||
|
|
(unset(POSIXBUILTINS) && (cflags & BINF_COMMAND)))
|
|
break;
|
|
|
|
cmdarg = (char *) peekfirst(args);
|
|
if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
|
|
(hn = shfunctab->getnode(shfunctab, cmdarg))) {
|
|
is_shfunc = 1;
|
|
break;
|
|
}
|
|
if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
|
|
if (cflags & BINF_BUILTIN) {
|
|
zwarn("no such builtin: %s", cmdarg);
|
|
lastval = 1;
|
|
if (oautocont >= 0)
|
|
opts[AUTOCONTINUE] = oautocont;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
if (!(hn->flags & BINF_PREFIX)) {
|
|
is_builtin = 1;
|
|
|
|
/* autoload the builtin if necessary */
|
|
if (!(hn = resolvebuiltin(cmdarg, hn)))
|
|
return;
|
|
break;
|
|
}
|
|
cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
|
|
cflags |= hn->flags;
|
|
uremnode(args, firstnode(args));
|
|
hn = NULL;
|
|
}
|
|
}
|
|
|
|
if (errflag) {
|
|
lastval = 1;
|
|
if (oautocont >= 0)
|
|
opts[AUTOCONTINUE] = oautocont;
|
|
return;
|
|
}
|
|
|
|
/* Get the text associated with this command. */
|
|
if ((how & Z_ASYNC) ||
|
|
(!sfcontext && !sourcelevel && (jobbing || (how & Z_TIMED))))
|
|
text = getjobtext(state->prog, beg);
|
|
else
|
|
text = NULL;
|
|
|
|
/*
|
|
* 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))
|
|
uremnode(args, node);
|
|
} else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) {
|
|
char t = s[l - 2];
|
|
|
|
s[l - 2] = 0;
|
|
if (!checkrmall(s))
|
|
uremnode(args, node);
|
|
s[l - 2] = t;
|
|
}
|
|
}
|
|
if (!nextnode(firstnode(args)))
|
|
errflag |= ERRFLAG_ERROR;
|
|
}
|
|
|
|
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);
|
|
if (shf)
|
|
state->prog->shf = shf;
|
|
else {
|
|
/*
|
|
* This doesn't set errflag, so just return now.
|
|
*/
|
|
lastval = 1;
|
|
if (oautocont >= 0)
|
|
opts[AUTOCONTINUE] = oautocont;
|
|
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;
|
|
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. (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 ((how & Z_ASYNC) ||
|
|
(!do_exec &&
|
|
(((is_builtin || is_shfunc) && output) ||
|
|
(!is_cursh && (last1 != 1 || nsigtrapped || havefiles() ||
|
|
fdtable_flocks))))) {
|
|
|
|
pid_t pid;
|
|
int synch[2], flags;
|
|
char dummy;
|
|
struct timeval bgtime;
|
|
|
|
child_block();
|
|
|
|
if (pipe(synch) < 0) {
|
|
zerr("pipe failed: %e", errno);
|
|
goto fatal;
|
|
} else if ((pid = zfork(&bgtime)) == -1) {
|
|
close(synch[0]);
|
|
close(synch[1]);
|
|
lastval = 1;
|
|
errflag |= ERRFLAG_ERROR;
|
|
goto fatal;
|
|
}
|
|
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 == 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;
|
|
filelist = jobtab[thisjob].filelist;
|
|
entersubsh(flags);
|
|
close(synch[1]);
|
|
forked = 1;
|
|
if (sigtrapped[SIGINT] & ZSIG_IGNORED)
|
|
holdintr();
|
|
#ifdef HAVE_NICE
|
|
/* Check if we should run background jobs at a lower priority. */
|
|
if ((how & Z_ASYNC) && isset(BGNICE))
|
|
if (nice(5) < 0)
|
|
zwarn("nice(5) failed: %e", errno);
|
|
#endif /* HAVE_NICE */
|
|
|
|
} else if (is_cursh) {
|
|
/* This is a current shell procedure that didn't need to fork. *
|
|
* This includes current shell procedures that are being exec'ed, *
|
|
* as well as null execs. */
|
|
jobtab[thisjob].stat |= STAT_CURSH;
|
|
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 && 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();
|
|
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_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(*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 = 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;
|
|
/* builtin or shell function */
|
|
|
|
if (!forked && varspc) {
|
|
int do_save = 0;
|
|
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))
|
|
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) || !assign)
|
|
do_save = 1;
|
|
}
|
|
if (do_save)
|
|
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;
|
|
if (forked)
|
|
closem(FDT_INTERNAL);
|
|
if (postassigns) {
|
|
Wordcode opc = state->pc;
|
|
state->pc = assignspc;
|
|
assigns = newlinklist();
|
|
while (postassigns--) {
|
|
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);
|
|
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->is_array = 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);
|
|
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->is_array = 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);
|
|
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->is_array = 1;
|
|
asg->value.array =
|
|
ecgetlist(state, WC_ASSIGN_NUM(ac),
|
|
EC_DUPTOK, &htok);
|
|
if (asg->value.array)
|
|
{
|
|
prefork(asg->value.array, PREFORK_ASSIGN);
|
|
if (errflag) {
|
|
state->pc = opc;
|
|
break;
|
|
}
|
|
globlist(asg->value.array, 0);
|
|
if (errflag) {
|
|
state->pc = opc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uaddlinknode(assigns, &asg->node);
|
|
}
|
|
state->pc = opc;
|
|
}
|
|
dont_queue_signals();
|
|
lastval = execbuiltin(args, assigns, (Builtin) hn);
|
|
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 (!forked)
|
|
setiparam("SHLVL", --shlvl);
|
|
if (do_exec) {
|
|
/* If we are exec'ing a command, and we are not *
|
|
* in a subshell, then save the history file. */
|
|
if (!subsh && isset(RCS) && interact && !nohistsave)
|
|
savehistfile(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);
|
|
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);
|
|
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.
|
|
*/
|
|
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".
|
|
*/
|
|
|
|
/**/
|
|
mod_export void
|
|
closem(int how)
|
|
{
|
|
int i;
|
|
|
|
for (i = 10; i <= max_zsh_fd; i++)
|
|
if (fdtable[i] != FDT_UNUSED &&
|
|
(how == FDT_UNUSED || fdtable[i] == 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) {
|
|
char *newbuf = realloc(buf, 2 * bsiz);
|
|
if (!newbuf) {
|
|
/* out of memory */
|
|
zfree(buf, bsiz);
|
|
return NULL;
|
|
}
|
|
buf = newbuf;
|
|
t = buf + bsiz - (bptr - t);
|
|
bptr = buf + bsiz;
|
|
bsiz *= 2;
|
|
}
|
|
if (lexstop || c == '\n')
|
|
break;
|
|
*bptr++ = c;
|
|
c = hgetc();
|
|
}
|
|
*bptr = '\0';
|
|
if (!strcmp(t, str))
|
|
break;
|
|
if (lexstop) {
|
|
t = bptr;
|
|
break;
|
|
}
|
|
*bptr++ = '\n';
|
|
}
|
|
*t = '\0';
|
|
s = buf;
|
|
buf = dupstring(buf);
|
|
zfree(s, bsiz);
|
|
if (!qt) {
|
|
int ef = errflag;
|
|
|
|
parsestr(&buf);
|
|
|
|
if (!errflag) {
|
|
/* 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;
|
|
|
|
if (!(prog = parse_string(cmd, 0)))
|
|
return NULL;
|
|
|
|
if ((s = simple_redir_name(prog, REDIR_READ))) {
|
|
/* $(< word) */
|
|
int stream;
|
|
|
|
singsub(&s);
|
|
if (errflag)
|
|
return NULL;
|
|
untokenize(s);
|
|
if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
|
|
zwarn("%e: %s", errno, s);
|
|
return newlinklist();
|
|
}
|
|
return readoutput(stream, qt);
|
|
}
|
|
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);
|
|
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)
|
|
{
|
|
LinkList ret;
|
|
char *buf, *ptr;
|
|
int bsiz, c, cnt = 0;
|
|
FILE *fin;
|
|
|
|
fin = fdopen(in, "r");
|
|
ret = newlinklist();
|
|
ptr = buf = (char *) hcalloc(bsiz = 64);
|
|
while ((c = fgetc(fin)) != EOF || errno == EINTR) {
|
|
if (c == EOF) {
|
|
errno = 0;
|
|
clearerr(fin);
|
|
continue;
|
|
}
|
|
if (imeta(c)) {
|
|
*ptr++ = Meta;
|
|
c ^= 32;
|
|
cnt++;
|
|
}
|
|
if (++cnt >= bsiz) {
|
|
char *pp = (char *) hcalloc(bsiz *= 2);
|
|
|
|
memcpy(pp, buf, cnt - 1);
|
|
ptr = (buf = pp) + cnt - 1;
|
|
}
|
|
*ptr++ = c;
|
|
}
|
|
fclose(fin);
|
|
while (cnt && ptr[-1] == '\n')
|
|
ptr--, cnt--;
|
|
*ptr = '\0';
|
|
if (qt) {
|
|
if (!cnt) {
|
|
*ptr++ = Nularg;
|
|
*ptr = '\0';
|
|
}
|
|
addlinknode(ret, buf);
|
|
} else {
|
|
char **words = spacesplit(buf, 0, 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, 0)))
|
|
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);
|
|
}
|
|
|
|
addfilelist(nam, 0);
|
|
|
|
if (!s)
|
|
child_block();
|
|
fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
|
|
|
|
if (s) {
|
|
/* optimised here-string */
|
|
int len;
|
|
unmetafy(s, &len);
|
|
write_loop(fd, s, len);
|
|
close(fd);
|
|
return nam;
|
|
}
|
|
|
|
if (fd < 0 || (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);
|
|
return pnam;
|
|
}
|
|
closem(FDT_UNUSED);
|
|
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 = hcalloc(strlen(PATH_DEV_FD) + 6);
|
|
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);
|
|
}
|
|
return pnam;
|
|
}
|
|
entersubsh(ESUB_ASYNC|ESUB_PGRP);
|
|
redup(pipes[out], out);
|
|
closem(FDT_UNUSED); /* 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);
|
|
return pipes[!out];
|
|
}
|
|
entersubsh(ESUB_PGRP);
|
|
redup(pipes[out], out);
|
|
closem(FDT_UNUSED); /* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern int tracingcond;
|
|
|
|
/* 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 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 nfunc = 0, 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;
|
|
shf->filename = ztrdup(scriptfilename);
|
|
shf->lineno = lineno;
|
|
/*
|
|
* redir_prog is permanently allocated --- but if
|
|
* this function has multiple names we need an additional
|
|
* one.
|
|
*/
|
|
if (nfunc++ && redir_prog)
|
|
shf->redir = dupeprog(redir_prog, 0);
|
|
else
|
|
shf->redir = redir_prog;
|
|
shfunc_set_sticky(shf);
|
|
|
|
if (!names) {
|
|
/*
|
|
* Anonymous function, execute immediately.
|
|
* Function name is "(anon)".
|
|
*/
|
|
LinkList args;
|
|
|
|
anon_func = 1;
|
|
|
|
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);
|
|
zsfree(shf->filename);
|
|
zfree(shf, sizeof(*shf));
|
|
state->pc = end;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
setunderscore((args && nonempty(args)) ?
|
|
((char *) getdata(lastnode(args))) : "");
|
|
|
|
if (!args)
|
|
args = newlinklist();
|
|
shf->node.nam = "(anon)";
|
|
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);
|
|
zsfree(shf->filename);
|
|
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);
|
|
zsfree(shf->filename);
|
|
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 (!nfunc && 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 = dupstring(shf->filename);
|
|
|
|
oldscriptname = scriptname;
|
|
oldscriptfilename = scriptfilename;
|
|
scriptname = scriptfilename = dupstring(shf->node.nam);
|
|
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)))
|
|
return 1;
|
|
|
|
state->prog->shf = shf;
|
|
return execautofn_basic(state, 0);
|
|
}
|
|
|
|
/**/
|
|
Shfunc
|
|
loadautofn(Shfunc shf, int fksh, int autol)
|
|
{
|
|
int noalias = noaliases, ksh = 1;
|
|
Eprog prog;
|
|
char *fname;
|
|
|
|
pushheap();
|
|
|
|
noaliases = (shf->node.flags & PM_UNALIASED);
|
|
prog = getfpfunc(shf->node.nam, &ksh, &fname);
|
|
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) {
|
|
zsfree(fname);
|
|
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;
|
|
shf->filename = fname;
|
|
} 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();
|
|
zsfree(fname);
|
|
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;
|
|
shf->filename = fname;
|
|
}
|
|
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, *oargv0;
|
|
int oldzoptind, oldlastval, oldoptcind, oldnumpipestats, ret;
|
|
int *oldpipestats = NULL;
|
|
char saveopts[OPT_SIZE], *oldscriptname = scriptname;
|
|
char *name = shfunc->node.nam;
|
|
int flags = shfunc->node.flags, ooflags;
|
|
char *fname = dupstring(name);
|
|
int obreaks, ocontflag, oloops, saveemulation, restore_sticky;
|
|
Eprog prog;
|
|
struct funcstack fstack;
|
|
static int oflags;
|
|
Emulation_options save_sticky = NULL;
|
|
#ifdef MAX_FUNCTION_DEPTH
|
|
static int funcdepth;
|
|
#endif
|
|
|
|
queue_signals(); /* Lots of memory and global state changes coming */
|
|
|
|
pushheap();
|
|
|
|
oargv0 = NULL;
|
|
obreaks = breaks;
|
|
ocontflag = contflag;
|
|
oloops = loops;
|
|
if (trap_state == TRAP_STATE_PRIMED)
|
|
trap_return--;
|
|
oldlastval = lastval;
|
|
oldnumpipestats = numpipestats;
|
|
if (noreturnval) {
|
|
/*
|
|
* Easiest to use the heap here since we're bracketed
|
|
* immediately by a pushheap/popheap pair.
|
|
*/
|
|
size_t bytes = sizeof(int)*numpipestats;
|
|
oldpipestats = (int *)zhalloc(bytes);
|
|
memcpy(oldpipestats, pipestats, bytes);
|
|
}
|
|
|
|
starttrapscope();
|
|
startpatternscope();
|
|
|
|
pptab = pparams;
|
|
if (!(flags & PM_UNDEFINED))
|
|
scriptname = dupstring(name);
|
|
oldzoptind = zoptind;
|
|
oldoptcind = 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(saveopts, opts, sizeof(opts));
|
|
saveemulation = emulation;
|
|
save_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;
|
|
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
|
|
restore_sticky = 0;
|
|
|
|
if (flags & (PM_TAGGED|PM_TAGGED_LOCAL))
|
|
opts[XTRACE] = 1;
|
|
else if (oflags & PM_TAGGED_LOCAL)
|
|
opts[XTRACE] = 0;
|
|
ooflags = oflags;
|
|
/*
|
|
* oflags is static, because we compare it on the next recursive
|
|
* call. Hence also we maintain ooflags 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)) {
|
|
oargv0 = 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)) {
|
|
oargv0 = argzero;
|
|
argzero = ztrdup(argzero);
|
|
}
|
|
}
|
|
#ifdef MAX_FUNCTION_DEPTH
|
|
if(++funcdepth > MAX_FUNCTION_DEPTH)
|
|
{
|
|
zerr("maximum nested function level reached");
|
|
goto undoshfunc;
|
|
}
|
|
#endif
|
|
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.
|
|
*/
|
|
fstack.caller = funcstack ? funcstack->name :
|
|
dupstring(oargv0 ? oargv0 : argzero);
|
|
fstack.lineno = lineno;
|
|
fstack.prev = funcstack;
|
|
fstack.tp = FS_FUNC;
|
|
funcstack = &fstack;
|
|
|
|
fstack.flineno = shfunc->lineno;
|
|
fstack.filename = dupstring(shfunc->filename);
|
|
|
|
prog = shfunc->funcdef;
|
|
if (prog->flags & EF_RUN) {
|
|
Shfunc shf;
|
|
|
|
prog->flags &= ~EF_RUN;
|
|
|
|
runshfunc(prog, NULL, 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, fstack.name);
|
|
doneshfunc:
|
|
funcstack = fstack.prev;
|
|
#ifdef MAX_FUNCTION_DEPTH
|
|
undoshfunc:
|
|
--funcdepth;
|
|
#endif
|
|
if (retflag) {
|
|
retflag = 0;
|
|
breaks = obreaks;
|
|
}
|
|
freearray(pparams);
|
|
if (oargv0) {
|
|
zsfree(argzero);
|
|
argzero = oargv0;
|
|
}
|
|
pparams = pptab;
|
|
if (!isset(POSIXBUILTINS)) {
|
|
zoptind = oldzoptind;
|
|
optcind = oldoptcind;
|
|
}
|
|
scriptname = oldscriptname;
|
|
oflags = ooflags;
|
|
|
|
endpatternscope(); /* before restoring old LOCALPATTERNS */
|
|
|
|
if (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, saveopts, sizeof(opts));
|
|
emulation = saveemulation;
|
|
sticky = save_sticky;
|
|
} else if (isset(LOCALOPTIONS)) {
|
|
/* restore all shell options except PRIVILEGED and RESTRICTED */
|
|
saveopts[PRIVILEGED] = opts[PRIVILEGED];
|
|
saveopts[RESTRICTED] = opts[RESTRICTED];
|
|
memcpy(opts, saveopts, sizeof(opts));
|
|
emulation = saveemulation;
|
|
} else {
|
|
/* just restore a couple. */
|
|
opts[XTRACE] = saveopts[XTRACE];
|
|
opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
|
|
opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
|
|
opts[LOCALLOOPS] = saveopts[LOCALLOOPS];
|
|
}
|
|
|
|
if (opts[LOCALLOOPS]) {
|
|
if (contflag)
|
|
zwarn("`continue' active at end of function scope");
|
|
if (breaks)
|
|
zwarn("`break' active at end of function scope");
|
|
breaks = obreaks;
|
|
contflag = ocontflag;
|
|
loops = oloops;
|
|
}
|
|
|
|
endtrapscope();
|
|
|
|
if (trap_state == TRAP_STATE_PRIMED)
|
|
trap_return++;
|
|
ret = lastval;
|
|
if (noreturnval) {
|
|
lastval = oldlastval;
|
|
numpipestats = oldnumpipestats;
|
|
memcpy(pipestats, oldpipestats, sizeof(int)*numpipestats);
|
|
}
|
|
popheap();
|
|
|
|
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 (exit_pending && exit_level >= locallevel+1) {
|
|
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);
|
|
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. */
|
|
|
|
/**/
|
|
Eprog
|
|
getfpfunc(char *s, int *ksh, char **fname)
|
|
{
|
|
char **pp, buf[PATH_MAX];
|
|
off_t len;
|
|
off_t rlen;
|
|
char *d;
|
|
Eprog r;
|
|
int fd;
|
|
|
|
pp = fpath;
|
|
for (; *pp; pp++) {
|
|
if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
|
|
continue;
|
|
if (**pp)
|
|
sprintf(buf, "%s/%s", *pp, s);
|
|
else
|
|
strcpy(buf, s);
|
|
if ((r = try_dump_file(*pp, s, buf, ksh))) {
|
|
if (fname)
|
|
*fname = ztrdup(buf);
|
|
return r;
|
|
}
|
|
unmetafy(buf, NULL);
|
|
if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
|
|
if ((len = lseek(fd, 0, 2)) != -1) {
|
|
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 (fname)
|
|
*fname = ztrdup(buf);
|
|
|
|
zfree(d, len + 1);
|
|
|
|
return r;
|
|
} else
|
|
close(fd);
|
|
|
|
zfree(d, len + 1);
|
|
} else
|
|
close(fd);
|
|
}
|
|
}
|
|
return &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;
|
|
|
|
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 || strcmp(name, ecrawstr(prog, pc + 1, NULL)))
|
|
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], **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->trap_return = trap_return;
|
|
es->trap_state = trap_state;
|
|
es->trapisfunc = trapisfunc;
|
|
es->traplocallevel = traplocallevel;
|
|
es->noerrs = noerrs;
|
|
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;
|
|
trap_return = en->trap_return;
|
|
trap_state = en->trap_state;
|
|
trapisfunc = en->trapisfunc;
|
|
traplocallevel = en->traplocallevel;
|
|
noerrs = en->noerrs;
|
|
setunderscore(en->underscore);
|
|
zsfree(en->underscore);
|
|
free(en);
|
|
|
|
unqueue_signals();
|
|
}
|