mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-29 02:32:11 +01:00
2965 lines
68 KiB
C
2965 lines
68 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"
|
|
|
|
/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */
|
|
|
|
/**/
|
|
int noerrexit;
|
|
|
|
/* suppress error messages */
|
|
|
|
/**/
|
|
int noerrs;
|
|
|
|
/* do not save history on exec and exit */
|
|
|
|
/**/
|
|
int nohistsave;
|
|
|
|
/* error/break flag */
|
|
|
|
/**/
|
|
int errflag;
|
|
|
|
/* Status of return from a trap */
|
|
|
|
/**/
|
|
int trapreturn;
|
|
|
|
/* != 0 if this is a subshell */
|
|
|
|
/**/
|
|
int subsh;
|
|
|
|
/* != 0 if we have a return pending */
|
|
|
|
/**/
|
|
int retflag;
|
|
|
|
/**/
|
|
long lastval2;
|
|
|
|
/* The table of file descriptors. A table element is zero if the *
|
|
* corresponding fd is not used by the shell. It is greater than *
|
|
* 1 if the fd is used by a <(...) or >(...) substitution and 1 if *
|
|
* it is an internal file descriptor which must be closed before *
|
|
* executing an external command. The first ten elements of the *
|
|
* table is not used. A table element is set by movefd and cleard *
|
|
* by zclose. */
|
|
|
|
/**/
|
|
char *fdtable;
|
|
|
|
/* The allocated size of fdtable */
|
|
|
|
/**/
|
|
int fdtable_size;
|
|
|
|
/* The highest fd that marked with nonzero in fdtable */
|
|
|
|
/**/
|
|
int max_zsh_fd;
|
|
|
|
/* input fd from the coprocess */
|
|
|
|
/**/
|
|
int coprocin;
|
|
|
|
/* output fd from the coprocess */
|
|
|
|
/**/
|
|
int coprocout;
|
|
|
|
/* != 0 if the line editor is active */
|
|
|
|
/**/
|
|
int zleactive;
|
|
|
|
/* pid of process undergoing 'process substitution' */
|
|
|
|
/**/
|
|
pid_t cmdoutpid;
|
|
|
|
/* exit status of process undergoing 'process substitution' */
|
|
|
|
/**/
|
|
int cmdoutval;
|
|
|
|
/* Stack to save some variables before executing a signal handler function */
|
|
|
|
/**/
|
|
struct execstack *exstack;
|
|
|
|
#define execerr() if (!forked) { lastval = 1; return; } else _exit(1)
|
|
|
|
static LinkList args;
|
|
static int doneps4;
|
|
|
|
/* parse string into a list */
|
|
|
|
/**/
|
|
List
|
|
parse_string(char *s)
|
|
{
|
|
List l;
|
|
|
|
lexsave();
|
|
inpush(s, 0, NULL);
|
|
strinbeg();
|
|
stophist = 2;
|
|
l = parse_list();
|
|
strinend();
|
|
inpop();
|
|
lexrestore();
|
|
return l;
|
|
}
|
|
|
|
#ifdef HAVE_GETRLIMIT
|
|
|
|
/* the resource limits for the shell and its children */
|
|
|
|
/**/
|
|
struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
|
|
|
|
/**/
|
|
int
|
|
zsetlimit(int limnum, char *nam)
|
|
{
|
|
if (limits[limnum].rlim_max != current_limits[limnum].rlim_max ||
|
|
limits[limnum].rlim_cur != current_limits[limnum].rlim_cur) {
|
|
if (setrlimit(limnum, limits + limnum)) {
|
|
if (nam)
|
|
zwarnnam(nam, "setrlimit failed: %e", NULL, errno);
|
|
return -1;
|
|
}
|
|
current_limits[limnum] = limits[limnum];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
setlimits(char *nam)
|
|
{
|
|
int limnum;
|
|
int ret = 0;
|
|
|
|
for (limnum = 0; limnum < RLIM_NLIMITS; limnum++)
|
|
if (zsetlimit(limnum, nam))
|
|
ret++;
|
|
return ret;
|
|
}
|
|
|
|
#endif /* HAVE_GETRLIMIT */
|
|
|
|
/* fork and set limits */
|
|
|
|
/**/
|
|
static pid_t
|
|
zfork(void)
|
|
{
|
|
pid_t pid;
|
|
|
|
if (thisjob >= MAXJOB - 1) {
|
|
zerr("job table full", NULL, 0);
|
|
return -1;
|
|
}
|
|
pid = fork();
|
|
if (pid == -1) {
|
|
zerr("fork failed: %e", NULL, errno);
|
|
return -1;
|
|
}
|
|
#ifdef HAVE_GETRLIMIT
|
|
if (!pid)
|
|
/* set resource limits for the child process */
|
|
setlimits(NULL);
|
|
#endif
|
|
return pid;
|
|
}
|
|
|
|
|
|
/**/
|
|
int list_pipe = 0, simple_pline = 0;
|
|
|
|
static pid_t list_pipe_pid;
|
|
static int nowait, pline_level = 0;
|
|
static int list_pipe_child = 0, list_pipe_job;
|
|
static char list_pipe_text[JOBTEXTSIZE];
|
|
|
|
/* execute a current shell command */
|
|
|
|
/**/
|
|
static int
|
|
execcursh(Cmd cmd)
|
|
{
|
|
if (!list_pipe)
|
|
deletejob(jobtab + thisjob);
|
|
execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC);
|
|
cmd->u.list = NULL;
|
|
return lastval;
|
|
}
|
|
|
|
/* execve after handling $_ and #! */
|
|
|
|
#define POUNDBANGLIMIT 64
|
|
|
|
/**/
|
|
static int
|
|
zexecve(char *pth, char **argv)
|
|
{
|
|
int eno;
|
|
static char buf[PATH_MAX * 2];
|
|
char **eep;
|
|
|
|
unmetafy(pth, NULL);
|
|
for (eep = argv; *eep; eep++)
|
|
if (*eep != pth)
|
|
unmetafy(*eep, NULL);
|
|
for (eep = environ; *eep; eep++)
|
|
if (**eep == '_' && (*eep)[1] == '=')
|
|
break;
|
|
buf[0] = '_';
|
|
buf[1] = '=';
|
|
if (*pth == '/')
|
|
strcpy(buf + 2, pth);
|
|
else
|
|
sprintf(buf + 2, "%s/%s", pwd, pth);
|
|
if (!*eep)
|
|
eep[1] = NULL;
|
|
*eep = buf;
|
|
execve(pth, argv, environ);
|
|
|
|
/* If the execve returns (which in general shouldn't happen), *
|
|
* then check for an errno equal to ENOEXEC. This errno is set *
|
|
* if the process file has the appropriate access permission, *
|
|
* but has an invalid magic number in its header. */
|
|
if ((eno = errno) == ENOEXEC) {
|
|
char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
|
|
int fd, ct, t0;
|
|
|
|
if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
|
|
argv0 = *argv;
|
|
*argv = pth;
|
|
ct = read(fd, execvebuf, POUNDBANGLIMIT);
|
|
close(fd);
|
|
if (ct > 0) {
|
|
if (execvebuf[0] == '#') {
|
|
if (execvebuf[1] == '!') {
|
|
for (t0 = 0; t0 != ct; t0++)
|
|
if (execvebuf[t0] == '\n')
|
|
execvebuf[t0] = '\0';
|
|
execvebuf[POUNDBANGLIMIT] = '\0';
|
|
for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
|
|
for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
|
|
if (*ptr) {
|
|
*ptr = '\0';
|
|
argv[-2] = ptr2;
|
|
argv[-1] = ptr + 1;
|
|
execve(ptr2, argv - 2, environ);
|
|
} else {
|
|
argv[-1] = ptr2;
|
|
execve(ptr2, argv - 1, environ);
|
|
}
|
|
} else {
|
|
argv[-1] = "sh";
|
|
execve("/bin/sh", argv - 1, environ);
|
|
}
|
|
} else {
|
|
for (t0 = 0; t0 != ct; t0++)
|
|
if (!execvebuf[t0])
|
|
break;
|
|
if (t0 == ct) {
|
|
argv[-1] = "sh";
|
|
execve("/bin/sh", argv - 1, environ);
|
|
}
|
|
}
|
|
} else
|
|
eno = errno;
|
|
*argv = argv0;
|
|
} else
|
|
eno = errno;
|
|
}
|
|
/* restore the original arguments and path but do not bother with *
|
|
* null characters as these cannot be passed to external commands *
|
|
* anyway. So the result is truncated at the first null char. */
|
|
pth = metafy(pth, -1, META_NOALLOC);
|
|
for (eep = argv; *eep; eep++)
|
|
if (*eep != pth)
|
|
(void) metafy(*eep, -1, META_NOALLOC);
|
|
return eno;
|
|
}
|
|
|
|
#define MAXCMDLEN (PATH_MAX*4)
|
|
|
|
/* test whether we really want to believe the error number */
|
|
|
|
/**/
|
|
static int
|
|
isgooderr(int e, char *dir)
|
|
{
|
|
/*
|
|
* Maybe the directory was unreadable, or maybe it wasn't
|
|
* even a directory.
|
|
*/
|
|
return ((e != EACCES || !access(dir, X_OK)) &&
|
|
e != ENOENT && e != ENOTDIR);
|
|
}
|
|
|
|
/* execute an external command */
|
|
|
|
/**/
|
|
void
|
|
execute(Cmdnam not_used_yet, int dash)
|
|
{
|
|
Cmdnam cn;
|
|
static LinkList exargs;
|
|
char buf[MAXCMDLEN], buf2[MAXCMDLEN];
|
|
char *s, *z, *arg0;
|
|
char **argv, **pp;
|
|
int eno = 0, ee;
|
|
|
|
arg0 = (char *) peekfirst(args);
|
|
if (isset(RESTRICTED) && strchr(arg0, '/')) {
|
|
zerr("%s: restricted", arg0, 0);
|
|
_exit(1);
|
|
}
|
|
|
|
/* If the parameter STTY is set in the command's environment, *
|
|
* we first run the stty command with the value of this *
|
|
* parameter as it arguments. */
|
|
if (!exargs && (s = zgetenv("STTY")) && isatty(0)) {
|
|
char *t;
|
|
|
|
exargs = args; /* this prevents infinite recursion */
|
|
args = NULL;
|
|
t = tricat("stty", " ", s);
|
|
execstring(t, 1, 0);
|
|
zsfree(t);
|
|
args = exargs;
|
|
exargs = NULL;
|
|
}
|
|
|
|
cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
|
|
|
|
/* If ARGV0 is in the commands environment, we use *
|
|
* that as argv[0] for this external command */
|
|
if (unset(RESTRICTED) && (z = zgetenv("ARGV0"))) {
|
|
setdata(firstnode(args), (void *) ztrdup(z));
|
|
delenv(z - 6);
|
|
} else if (dash) {
|
|
/* Else if the pre-command `-' was given, we add `-' *
|
|
* to the front of argv[0] for this command. */
|
|
sprintf(buf2, "-%s", arg0);
|
|
setdata(firstnode(args), (void *) ztrdup(buf2));
|
|
}
|
|
|
|
argv = makecline(args);
|
|
child_unblock();
|
|
if ((int) strlen(arg0) >= PATH_MAX) {
|
|
zerr("command too long: %s", arg0, 0);
|
|
_exit(1);
|
|
}
|
|
for (s = arg0; *s; s++)
|
|
if (*s == '/') {
|
|
errno = zexecve(arg0, argv);
|
|
if (arg0 == s || unset(PATHDIRS) ||
|
|
(arg0[0] == '.' && (arg0 + 1 == s ||
|
|
(arg0[1] == '.' && arg0 + 2 == s)))) {
|
|
zerr("%e: %s", arg0, errno);
|
|
_exit(1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (cn) {
|
|
char nn[PATH_MAX], *dptr;
|
|
|
|
if (cn->flags & HASHED)
|
|
strcpy(nn, cn->u.cmd);
|
|
else {
|
|
for (pp = path; pp < cn->u.name; pp++)
|
|
if (!**pp || (**pp == '.' && (*pp)[1] == '\0')) {
|
|
ee = zexecve(arg0, argv);
|
|
if (isgooderr(ee, *pp))
|
|
eno = ee;
|
|
} else if (**pp != '/') {
|
|
z = buf;
|
|
strucpy(&z, *pp);
|
|
*z++ = '/';
|
|
strcpy(z, arg0);
|
|
ee = zexecve(buf, argv);
|
|
if (isgooderr(ee, *pp))
|
|
eno = ee;
|
|
}
|
|
strcpy(nn, cn->u.name ? *(cn->u.name) : "");
|
|
strcat(nn, "/");
|
|
strcat(nn, cn->nam);
|
|
}
|
|
ee = zexecve(nn, argv);
|
|
|
|
if ((dptr = strrchr(nn, '/')))
|
|
*dptr = '\0';
|
|
if (isgooderr(ee, *nn ? nn : "/"))
|
|
eno = ee;
|
|
}
|
|
for (pp = path; *pp; pp++)
|
|
if (!(*pp)[0] || ((*pp)[0] == '.' && !(*pp)[1])) {
|
|
ee = zexecve(arg0, argv);
|
|
if (isgooderr(ee, *pp))
|
|
eno = ee;
|
|
} else {
|
|
z = buf;
|
|
strucpy(&z, *pp);
|
|
*z++ = '/';
|
|
strcpy(z, arg0);
|
|
ee = zexecve(buf, argv);
|
|
if (isgooderr(ee, *pp))
|
|
eno = ee;
|
|
}
|
|
if (eno)
|
|
zerr("%e: %s", arg0, eno);
|
|
else
|
|
zerr("command not found: %s", arg0, 0);
|
|
_exit(1);
|
|
}
|
|
|
|
#define try(X) { if (iscom(X)) return ztrdup(X); }
|
|
|
|
/* get the full pathname of an external command */
|
|
|
|
/**/
|
|
char *
|
|
findcmd(char *arg0)
|
|
{
|
|
char **pp;
|
|
char *z, *s, buf[MAXCMDLEN];
|
|
Cmdnam cn;
|
|
|
|
cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
|
|
if (!cn && isset(HASHCMDS))
|
|
cn = hashcmd(arg0, path);
|
|
if ((int) strlen(arg0) > PATH_MAX)
|
|
return NULL;
|
|
for (s = arg0; *s; s++)
|
|
if (*s == '/') {
|
|
try(arg0);
|
|
if (arg0 == s || unset(PATHDIRS)) {
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (cn) {
|
|
char nn[PATH_MAX];
|
|
|
|
if (cn->flags & HASHED)
|
|
strcpy(nn, cn->u.cmd);
|
|
else {
|
|
for (pp = path; pp < cn->u.name; pp++)
|
|
if (**pp != '/') {
|
|
z = buf;
|
|
if (**pp) {
|
|
strucpy(&z, *pp);
|
|
*z++ = '/';
|
|
}
|
|
strcpy(z, arg0);
|
|
try(buf);
|
|
}
|
|
strcpy(nn, cn->u.name ? *(cn->u.name) : "");
|
|
strcat(nn, "/");
|
|
strcat(nn, cn->nam);
|
|
}
|
|
try(nn);
|
|
}
|
|
for (pp = path; *pp; pp++) {
|
|
z = buf;
|
|
if (**pp) {
|
|
strucpy(&z, *pp);
|
|
*z++ = '/';
|
|
}
|
|
strcpy(z, arg0);
|
|
try(buf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
iscom(char *s)
|
|
{
|
|
struct stat statbuf;
|
|
char *us = unmeta(s);
|
|
|
|
return (access(us, X_OK) == 0 && stat(us, &statbuf) >= 0 &&
|
|
S_ISREG(statbuf.st_mode));
|
|
}
|
|
|
|
/**/
|
|
int
|
|
isreallycom(Cmdnam cn)
|
|
{
|
|
char fullnam[MAXCMDLEN];
|
|
|
|
strcpy(fullnam, cn->u.name ? *(cn->u.name) : "");
|
|
strcat(fullnam, "/");
|
|
strcat(fullnam, cn->nam);
|
|
return iscom(fullnam);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
isrelative(char *s)
|
|
{
|
|
if (*s != '/')
|
|
return 1;
|
|
for (; *s; s++)
|
|
if (*s == '.' && s[-1] == '/' &&
|
|
(s[1] == '/' || s[1] == '\0' ||
|
|
(s[1] == '.' && (s[2] == '/' || s[2] == '\0'))))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
Cmdnam
|
|
hashcmd(char *arg0, char **pp)
|
|
{
|
|
Cmdnam cn;
|
|
char *s, buf[PATH_MAX];
|
|
char **pq;
|
|
|
|
for (; *pp; pp++)
|
|
if (**pp == '/') {
|
|
s = buf;
|
|
strucpy(&s, *pp);
|
|
*s++ = '/';
|
|
if ((s - buf) + strlen(arg0) >= PATH_MAX)
|
|
continue;
|
|
strcpy(s, arg0);
|
|
if (iscom(buf))
|
|
break;
|
|
}
|
|
|
|
if (!*pp)
|
|
return NULL;
|
|
|
|
cn = (Cmdnam) zcalloc(sizeof *cn);
|
|
cn->flags = 0;
|
|
cn->u.name = pp;
|
|
cmdnamtab->addnode(cmdnamtab, ztrdup(arg0), cn);
|
|
|
|
if (isset(HASHDIRS)) {
|
|
for (pq = pathchecked; pq <= pp; pq++)
|
|
hashdir(pq);
|
|
pathchecked = pp + 1;
|
|
}
|
|
|
|
return cn;
|
|
}
|
|
|
|
/* execute a string */
|
|
|
|
/**/
|
|
void
|
|
execstring(char *s, int dont_change_job, int exiting)
|
|
{
|
|
List list;
|
|
|
|
pushheap();
|
|
if ((list = parse_string(s)))
|
|
execlist(list, dont_change_job, exiting);
|
|
popheap();
|
|
}
|
|
|
|
/* Main routine for executing a list. *
|
|
* exiting means that the (sub)shell we are in is a definite goner *
|
|
* after the current list is finished, so we may be able to exec the *
|
|
* last command directly instead of forking. If dont_change_job is *
|
|
* nonzero, then restore the current job number after executing the *
|
|
* list. */
|
|
|
|
/**/
|
|
void
|
|
execlist(List list, int dont_change_job, int exiting)
|
|
{
|
|
Sublist slist;
|
|
static int donetrap;
|
|
int ret, cj;
|
|
int old_pline_level, old_list_pipe;
|
|
|
|
cj = thisjob;
|
|
old_pline_level = pline_level;
|
|
old_list_pipe = list_pipe;
|
|
|
|
if (sourcelevel && unset(SHINSTDIN))
|
|
pline_level = list_pipe = 0;
|
|
|
|
/* Loop over all sets of comands separated by newline, *
|
|
* semi-colon or ampersand (`sublists'). */
|
|
while (list && list != &dummy_list && !breaks && !retflag) {
|
|
/* Reset donetrap: this ensures that a trap is only *
|
|
* called once for each sublist that fails. */
|
|
donetrap = 0;
|
|
simplifyright(list);
|
|
slist = list->left;
|
|
|
|
/* Loop through code followed by &&, ||, or end of sublist. */
|
|
while (slist) {
|
|
switch (slist->type) {
|
|
case END:
|
|
/* End of sublist; just execute, ignoring status. */
|
|
execpline(slist, list->type, !list->right && exiting);
|
|
goto sublist_done;
|
|
break;
|
|
case ANDNEXT:
|
|
/* If the return code is non-zero, we skip pipelines until *
|
|
* we find a sublist followed by ORNEXT. */
|
|
if ((ret = execpline(slist, Z_SYNC, 0))) {
|
|
while ((slist = slist->right))
|
|
if (slist->type == ORNEXT)
|
|
break;
|
|
if (!slist) {
|
|
/* We've skipped to the end of the list, not executing *
|
|
* the final pipeline, so don't perform error handling *
|
|
* for this sublist. */
|
|
donetrap = 1;
|
|
goto sublist_done;
|
|
}
|
|
}
|
|
break;
|
|
case ORNEXT:
|
|
/* If the return code is zero, we skip pipelines until *
|
|
* we find a sublist followed by ANDNEXT. */
|
|
if (!(ret = execpline(slist, Z_SYNC, 0))) {
|
|
while ((slist = slist->right))
|
|
if (slist->type == ANDNEXT)
|
|
break;
|
|
if (!slist) {
|
|
/* We've skipped to the end of the list, not executing *
|
|
* the final pipeline, so don't perform error handling *
|
|
* for this sublist. */
|
|
donetrap = 1;
|
|
goto sublist_done;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
slist = slist->right;
|
|
}
|
|
sublist_done:
|
|
|
|
if (sigtrapped[SIGDEBUG])
|
|
dotrap(SIGDEBUG);
|
|
|
|
/* Check whether we are suppressing traps/errexit *
|
|
* (typically in init scripts) and if we haven't *
|
|
* already performed them for this sublist. */
|
|
if (!noerrexit && !donetrap) {
|
|
if (sigtrapped[SIGZERR] && lastval) {
|
|
dotrap(SIGZERR);
|
|
donetrap = 1;
|
|
}
|
|
if (lastval && isset(ERREXIT)) {
|
|
if (sigtrapped[SIGEXIT])
|
|
dotrap(SIGEXIT);
|
|
if (mypid != getpid())
|
|
_exit(lastval);
|
|
else
|
|
exit(lastval);
|
|
}
|
|
}
|
|
|
|
list = list->right;
|
|
}
|
|
|
|
pline_level = old_pline_level;
|
|
list_pipe = old_list_pipe;
|
|
if (dont_change_job)
|
|
thisjob = cj;
|
|
}
|
|
|
|
/* Execute a pipeline. *
|
|
* last1 is a flag that this command is the last command in a shell *
|
|
* that is about to exit, so we can exec instead of forking. It gets *
|
|
* passed all the way down to execcmd() which actually makes the *
|
|
* decision. A 0 is always passed if the command is not the last in *
|
|
* the pipeline. This function assumes that the sublist is not NULL. *
|
|
* If last1 is zero but the command is at the end of a pipeline, we *
|
|
* pass 2 down to execcmd(). *
|
|
*/
|
|
|
|
/**/
|
|
static int
|
|
execpline(Sublist l, int how, int last1)
|
|
{
|
|
int ipipe[2], opipe[2];
|
|
int pj, newjob;
|
|
int old_simple_pline = simple_pline;
|
|
static int lastwj;
|
|
|
|
if (!l->left)
|
|
return lastval = (l->flags & PFLAG_NOT) != 0;
|
|
|
|
pj = thisjob;
|
|
ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
|
|
child_block();
|
|
|
|
/* get free entry in job table and initialize it */
|
|
if ((thisjob = newjob = initjob()) == -1)
|
|
return 1;
|
|
if (how & Z_TIMED)
|
|
jobtab[thisjob].stat |= STAT_TIMED;
|
|
|
|
if (l->flags & PFLAG_COPROC) {
|
|
how = Z_ASYNC;
|
|
if (coprocin >= 0) {
|
|
zclose(coprocin);
|
|
zclose(coprocout);
|
|
}
|
|
mpipe(ipipe);
|
|
mpipe(opipe);
|
|
coprocin = ipipe[0];
|
|
coprocout = opipe[1];
|
|
fdtable[coprocin] = fdtable[coprocout] = 0;
|
|
}
|
|
if (!pline_level++) {
|
|
list_pipe_job = newjob;
|
|
nowait = 0;
|
|
}
|
|
list_pipe_pid = lastwj = 0;
|
|
if (pline_level == 1)
|
|
simple_pline = (l->left->type == END);
|
|
execpline2(l->left, how, opipe[0], ipipe[1], last1);
|
|
pline_level--;
|
|
if (how & Z_ASYNC) {
|
|
lastwj = newjob;
|
|
jobtab[thisjob].stat |= STAT_NOSTTY;
|
|
if (l->flags & PFLAG_COPROC)
|
|
zclose(ipipe[1]);
|
|
if (how & Z_DISOWN) {
|
|
deletejob(jobtab + thisjob);
|
|
thisjob = -1;
|
|
}
|
|
else
|
|
spawnjob();
|
|
child_unblock();
|
|
return 0;
|
|
} else {
|
|
if (newjob != lastwj) {
|
|
Job jn = jobtab + newjob;
|
|
|
|
if (newjob == list_pipe_job && list_pipe_child)
|
|
_exit(0);
|
|
|
|
lastwj = thisjob = newjob;
|
|
|
|
if (list_pipe)
|
|
jn->stat |= STAT_NOPRINT;
|
|
|
|
if (nowait) {
|
|
if(!pline_level) {
|
|
struct process *pn, *qn;
|
|
|
|
curjob = newjob;
|
|
addproc(list_pipe_pid, list_pipe_text);
|
|
|
|
for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
|
|
if (WIFSTOPPED(pn->status))
|
|
break;
|
|
|
|
if (pn) {
|
|
for (qn = jn->procs; qn->next; qn = qn->next);
|
|
qn->status = pn->status;
|
|
}
|
|
|
|
jn->stat &= ~(STAT_DONE | STAT_NOPRINT);
|
|
jn->stat |= STAT_STOPPED | STAT_CHANGED;
|
|
printjob(jn, !!isset(LONGLISTJOBS), 1);
|
|
}
|
|
else
|
|
deletejob(jn);
|
|
}
|
|
|
|
for (; !nowait;) {
|
|
if (list_pipe_child) {
|
|
jn->stat |= STAT_NOPRINT;
|
|
makerunning(jn);
|
|
}
|
|
if (!(jn->stat & STAT_LOCKED))
|
|
waitjobs();
|
|
|
|
if (list_pipe_child &&
|
|
jn->stat & STAT_DONE &&
|
|
lastval2 & 0200)
|
|
killpg(mypgrp, lastval2 & ~0200);
|
|
if ((list_pipe || last1) && !list_pipe_child &&
|
|
jn->stat & STAT_STOPPED) {
|
|
pid_t pid;
|
|
int synch[2];
|
|
|
|
pipe(synch);
|
|
|
|
if ((pid = fork()) == -1) {
|
|
trashzle();
|
|
close(synch[0]);
|
|
close(synch[1]);
|
|
putc('\n', stderr);
|
|
fprintf(stderr, "zsh: job can't be suspended\n");
|
|
fflush(stderr);
|
|
makerunning(jn);
|
|
killjb(jn, SIGCONT);
|
|
thisjob = newjob;
|
|
}
|
|
else if (pid) {
|
|
char dummy;
|
|
|
|
list_pipe_pid = pid;
|
|
nowait = errflag = 1;
|
|
breaks = loops;
|
|
close(synch[1]);
|
|
read(synch[0], &dummy, 1);
|
|
close(synch[0]);
|
|
jobtab[list_pipe_job].other = newjob;
|
|
jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
|
|
jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
|
|
jn->other = pid;
|
|
killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
|
|
break;
|
|
}
|
|
else {
|
|
close(synch[0]);
|
|
entersubsh(Z_ASYNC, 0, 0);
|
|
setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader);
|
|
close(synch[1]);
|
|
kill(getpid(), SIGSTOP);
|
|
list_pipe = 0;
|
|
list_pipe_child = 1;
|
|
break;
|
|
}
|
|
}
|
|
else if (subsh && jn->stat & STAT_STOPPED)
|
|
thisjob = newjob;
|
|
else
|
|
break;
|
|
}
|
|
child_unblock();
|
|
|
|
if (list_pipe && (lastval & 0200) && pj >= 0 &&
|
|
(!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
|
|
jn = jobtab + pj;
|
|
jn->stat |= STAT_NOPRINT;
|
|
killjb(jobtab + pj, lastval & ~0200);
|
|
}
|
|
if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE)))
|
|
deletejob(jn);
|
|
thisjob = pj;
|
|
|
|
}
|
|
if (l->flags & PFLAG_NOT)
|
|
lastval = !lastval;
|
|
}
|
|
if (!pline_level)
|
|
simple_pline = old_simple_pline;
|
|
return lastval;
|
|
}
|
|
|
|
static int subsh_close = -1;
|
|
|
|
/* execute pipeline. This function assumes the `pline' is not NULL. */
|
|
|
|
/**/
|
|
static void
|
|
execpline2(Pline pline, int how, int input, int output, int last1)
|
|
{
|
|
pid_t pid;
|
|
int pipes[2];
|
|
int oldlineno;
|
|
|
|
if (breaks || retflag)
|
|
return;
|
|
|
|
oldlineno = lineno;
|
|
lineno = pline->left->lineno;
|
|
|
|
if (pline_level == 1)
|
|
strcpy(list_pipe_text, getjobtext((void *) pline->left));
|
|
if (pline->type == END) {
|
|
execcmd(pline->left, input, output, how, last1 ? 1 : 2);
|
|
pline->left = NULL;
|
|
} else {
|
|
int old_list_pipe = list_pipe;
|
|
|
|
mpipe(pipes);
|
|
|
|
/* if we are doing "foo | bar" where foo is a current *
|
|
* shell command, do foo in a subshell and do the *
|
|
* rest of the pipeline in the current shell. */
|
|
if (pline->left->type >= CURSH && (how & Z_SYNC)) {
|
|
int synch[2];
|
|
|
|
pipe(synch);
|
|
if ((pid = fork()) == -1) {
|
|
close(synch[0]);
|
|
close(synch[1]);
|
|
zerr("fork failed: %e", NULL, errno);
|
|
} else if (pid) {
|
|
char dummy, *text;
|
|
|
|
text = getjobtext((void *) pline->left);
|
|
addproc(pid, text);
|
|
close(synch[1]);
|
|
read(synch[0], &dummy, 1);
|
|
close(synch[0]);
|
|
} else {
|
|
zclose(pipes[0]);
|
|
close(synch[0]);
|
|
entersubsh(how, 2, 0);
|
|
close(synch[1]);
|
|
execcmd(pline->left, input, pipes[1], how, 0);
|
|
_exit(lastval);
|
|
}
|
|
} else {
|
|
/* otherwise just do the pipeline normally. */
|
|
subsh_close = pipes[0];
|
|
execcmd(pline->left, input, pipes[1], how, 0);
|
|
}
|
|
pline->left = NULL;
|
|
zclose(pipes[1]);
|
|
if (pline->right) {
|
|
/* if another execpline() is invoked because the command is *
|
|
* a list it must know that we're already in a pipeline */
|
|
list_pipe = 1;
|
|
execpline2(pline->right, how, pipes[0], output, last1);
|
|
list_pipe = old_list_pipe;
|
|
zclose(pipes[0]);
|
|
subsh_close = -1;
|
|
}
|
|
}
|
|
|
|
lineno = oldlineno;
|
|
}
|
|
|
|
/* make the argv array */
|
|
|
|
/**/
|
|
static char **
|
|
makecline(LinkList list)
|
|
{
|
|
LinkNode node;
|
|
char **argv, **ptr;
|
|
|
|
/* A bigger argv is necessary for executing scripts */
|
|
ptr =
|
|
argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *));
|
|
if (isset(XTRACE)) {
|
|
if (!doneps4)
|
|
fprintf(stderr, "%s", (prompt4) ? prompt4 : "");
|
|
|
|
for (node = firstnode(list); node; incnode(node)) {
|
|
*ptr++ = (char *)getdata(node);
|
|
zputs(getdata(node), stderr);
|
|
if (nextnode(node))
|
|
fputc(' ', stderr);
|
|
}
|
|
fputc('\n', stderr);
|
|
fflush(stderr);
|
|
} else {
|
|
for (node = firstnode(list); node; incnode(node))
|
|
*ptr++ = (char *)getdata(node);
|
|
}
|
|
*ptr = NULL;
|
|
return (argv);
|
|
}
|
|
|
|
/**/
|
|
void
|
|
untokenize(char *s)
|
|
{
|
|
for (; *s; s++)
|
|
if (itok(*s))
|
|
if (*s == Nularg)
|
|
chuck(s--);
|
|
else
|
|
*s = ztokens[*s - Pound];
|
|
}
|
|
|
|
/* Open a file for writing redicection */
|
|
|
|
/**/
|
|
static int
|
|
clobber_open(struct redir *f)
|
|
{
|
|
struct stat buf;
|
|
int fd, oerrno;
|
|
|
|
/* If clobbering, just open. */
|
|
if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type))
|
|
return open(unmeta(f->name),
|
|
O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666);
|
|
|
|
/* If not clobbering, attempt to create file exclusively. */
|
|
if ((fd = open(unmeta(f->name),
|
|
O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0)
|
|
return fd;
|
|
|
|
/* If that fails, we are still allowed to open non-regular files. *
|
|
* Try opening, and if it's a regular file then close it again *
|
|
* because we weren't supposed to open it. */
|
|
oerrno = errno;
|
|
if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) {
|
|
if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode))
|
|
return fd;
|
|
close(fd);
|
|
}
|
|
errno = oerrno;
|
|
return -1;
|
|
}
|
|
|
|
/* size of buffer for tee and cat processes */
|
|
#define TCBUFSIZE 4092
|
|
|
|
/* close an multio (success) */
|
|
|
|
/**/
|
|
static void
|
|
closemn(struct multio **mfds, int fd)
|
|
{
|
|
struct multio *mn = mfds[fd];
|
|
char buf[TCBUFSIZE];
|
|
int len, i;
|
|
|
|
if (fd < 0 || !mfds[fd] || mfds[fd]->ct < 2)
|
|
return;
|
|
if (zfork()) {
|
|
for (i = 0; i < mn->ct; i++)
|
|
zclose(mn->fds[i]);
|
|
zclose(mn->pipe);
|
|
mn->ct = 1;
|
|
mn->fds[0] = fd;
|
|
return;
|
|
}
|
|
/* pid == 0 */
|
|
closeallelse(mn);
|
|
if (mn->rflag) {
|
|
/* tee process */
|
|
while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0)
|
|
for (i = 0; i < mn->ct; i++)
|
|
write(mn->fds[i], buf, len);
|
|
} else {
|
|
/* cat process */
|
|
for (i = 0; i < mn->ct; i++)
|
|
while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0)
|
|
write(mn->pipe, buf, len);
|
|
}
|
|
_exit(0);
|
|
}
|
|
|
|
/* close all the mnodes (failure) */
|
|
|
|
/**/
|
|
static void
|
|
closemnodes(struct multio **mfds)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < 10; i++)
|
|
if (mfds[i]) {
|
|
for (j = 0; j < mfds[i]->ct; j++)
|
|
zclose(mfds[i]->fds[j]);
|
|
mfds[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
closeallelse(struct multio *mn)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < OPEN_MAX; i++)
|
|
if (mn->pipe != i) {
|
|
for (j = 0; j < mn->ct; j++)
|
|
if (mn->fds[j] == i)
|
|
break;
|
|
if (j == mn->ct)
|
|
zclose(i);
|
|
}
|
|
}
|
|
|
|
/* A multio is a list of fds associated with a certain fd. *
|
|
* Thus if you do "foo >bar >ble", the multio for fd 1 will have *
|
|
* two fds, the result of open("bar",...), and the result of *
|
|
* open("ble",....). */
|
|
|
|
/* Add a fd to an multio. fd1 must be < 10, and may be in any state. *
|
|
* fd2 must be open, and is `consumed' by this function. Note that *
|
|
* fd1 == fd2 is possible, and indicates that fd1 was really closed. *
|
|
* We effectively do `fd2 = movefd(fd2)' at the beginning of this *
|
|
* function, but in most cases we can avoid an extra dup by delaying *
|
|
* the movefd: we only >need< to move it if we're actually doing a *
|
|
* multiple redirection. */
|
|
|
|
/**/
|
|
static void
|
|
addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag)
|
|
{
|
|
int pipes[2];
|
|
|
|
if (!mfds[fd1] || unset(MULTIOS)) {
|
|
if(!mfds[fd1]) { /* starting a new multio */
|
|
mfds[fd1] = (struct multio *) alloc(sizeof(struct multio));
|
|
if (!forked && save[fd1] == -2)
|
|
save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1);
|
|
}
|
|
redup(fd2, fd1);
|
|
mfds[fd1]->ct = 1;
|
|
mfds[fd1]->fds[0] = fd1;
|
|
mfds[fd1]->rflag = rflag;
|
|
} else {
|
|
if (mfds[fd1]->rflag != rflag) {
|
|
zerr("file mode mismatch on fd %d", NULL, fd1);
|
|
return;
|
|
}
|
|
if (mfds[fd1]->ct == 1) { /* split the stream */
|
|
mfds[fd1]->fds[0] = movefd(fd1);
|
|
mfds[fd1]->fds[1] = movefd(fd2);
|
|
mpipe(pipes);
|
|
mfds[fd1]->pipe = pipes[1 - rflag];
|
|
redup(pipes[rflag], fd1);
|
|
mfds[fd1]->ct = 2;
|
|
} else { /* add another fd to an already split stream */
|
|
if(!(mfds[fd1]->ct % MULTIOUNIT)) {
|
|
int new = sizeof(struct multio) + sizeof(int) * mfds[fd1]->ct;
|
|
int old = new - sizeof(int) * MULTIOUNIT;
|
|
mfds[fd1] = hrealloc((char *)mfds[fd1], old, new);
|
|
}
|
|
mfds[fd1]->fds[mfds[fd1]->ct++] = movefd(fd2);
|
|
}
|
|
}
|
|
if (subsh_close >= 0 && !fdtable[subsh_close])
|
|
subsh_close = -1;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
addvars(LinkList l, int export)
|
|
{
|
|
Varasg v;
|
|
LinkList vl;
|
|
int xtr;
|
|
char **arr, **ptr;
|
|
|
|
xtr = isset(XTRACE);
|
|
if (xtr && nonempty(l)) {
|
|
fprintf(stderr, "%s", prompt4 ? prompt4 : "");
|
|
doneps4 = 1;
|
|
}
|
|
|
|
while (nonempty(l)) {
|
|
v = (Varasg) ugetnode(l);
|
|
singsub(&v->name);
|
|
if (errflag)
|
|
return;
|
|
untokenize(v->name);
|
|
if (xtr)
|
|
fprintf(stderr, "%s=", v->name);
|
|
if (v->type == PM_SCALAR) {
|
|
vl = newlinklist();
|
|
addlinknode(vl, v->str);
|
|
} else
|
|
vl = v->arr;
|
|
prefork(vl, v->type == PM_SCALAR ? 7 : 3);
|
|
if (errflag)
|
|
return;
|
|
if (isset(GLOBASSIGN) || v->type != PM_SCALAR)
|
|
globlist(vl);
|
|
if (errflag)
|
|
return;
|
|
if (v->type == PM_SCALAR && (empty(vl) || !nextnode(firstnode(vl)))) {
|
|
Param pm;
|
|
char *val;
|
|
int allexp;
|
|
|
|
if (empty(vl))
|
|
val = ztrdup("");
|
|
else {
|
|
untokenize(peekfirst(vl));
|
|
val = ztrdup(ugetnode(vl));
|
|
}
|
|
if (xtr)
|
|
fprintf(stderr, "%s ", val);
|
|
if (export) {
|
|
if (export < 0) {
|
|
/* We are going to fork so do not bother freeing this */
|
|
pm = (Param) paramtab->removenode(paramtab, v->name);
|
|
if (isset(RESTRICTED) && (pm->flags & PM_RESTRICTED)) {
|
|
zerr("%s: restricted", pm->nam, 0);
|
|
zsfree(val);
|
|
return;
|
|
}
|
|
}
|
|
allexp = opts[ALLEXPORT];
|
|
opts[ALLEXPORT] = 1;
|
|
pm = setsparam(v->name, val);
|
|
opts[ALLEXPORT] = allexp;
|
|
} else
|
|
pm = setsparam(v->name, val);
|
|
if (errflag)
|
|
return;
|
|
continue;
|
|
}
|
|
ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1));
|
|
|
|
while (nonempty(vl))
|
|
*ptr++ = ztrdup((char *) ugetnode(vl));
|
|
|
|
*ptr = NULL;
|
|
if (xtr) {
|
|
fprintf(stderr, "( ");
|
|
for (ptr = arr; *ptr; ptr++)
|
|
fprintf(stderr, "%s ", *ptr);
|
|
fprintf(stderr, ") ");
|
|
}
|
|
setaparam(v->name, arr);
|
|
if (errflag)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
execcmd(Cmd cmd, int input, int output, int how, int last1)
|
|
{
|
|
HashNode hn = NULL;
|
|
LinkNode node;
|
|
Redir fn;
|
|
struct multio *mfds[10];
|
|
char *text;
|
|
int save[10];
|
|
int fil, dfil, is_cursh, type, i;
|
|
int nullexec = 0, assign = 0, forked = 0;
|
|
int is_shfunc = 0, is_builtin = 0, is_exec = 0;
|
|
/* Various flags to the command. */
|
|
int cflags = 0, checked = 0;
|
|
|
|
doneps4 = 0;
|
|
args = cmd->args;
|
|
type = cmd->type;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
save[i] = -2;
|
|
mfds[i] = NULL;
|
|
}
|
|
|
|
/* If the command begins with `%', then assume it is a *
|
|
* reference to a job in the job table. */
|
|
if (type == SIMPLE && nonempty(args) &&
|
|
*(char *)peekfirst(args) == '%') {
|
|
pushnode(args, dupstring((how & Z_DISOWN)
|
|
? "disown" : (how & Z_ASYNC) ? "bg" : "fg"));
|
|
how = Z_SYNC;
|
|
}
|
|
|
|
/* If AUTORESUME is set, the command is SIMPLE, and doesn't have *
|
|
* any redirections, then check if it matches as a prefix of a *
|
|
* job currently in the job table. If it does, then we treat it *
|
|
* as a command to resume this job. */
|
|
if (isset(AUTORESUME) && type == SIMPLE && (how & Z_SYNC) &&
|
|
nonempty(args) && empty(cmd->redir) && !input &&
|
|
!nextnode(firstnode(args))) {
|
|
if (unset(NOTIFY))
|
|
scanjobs();
|
|
if (findjobnam(peekfirst(args)) != -1)
|
|
pushnode(args, dupstring("fg"));
|
|
}
|
|
|
|
/* Check if it's a builtin needing automatic MAGIC_EQUALS_SUBST *
|
|
* handling. Things like typeset need this. We can't detect the *
|
|
* command if it contains some tokens (e.g. x=ex; ${x}port), so this *
|
|
* only works in simple cases. has_token() is called to make sure *
|
|
* this really is a simple case. */
|
|
if (type == SIMPLE) {
|
|
while (nonempty(args)) {
|
|
char *cmdarg = (char *) peekfirst(args);
|
|
checked = !has_token(cmdarg);
|
|
if (!checked)
|
|
break;
|
|
if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
|
|
(hn = shfunctab->getnode(shfunctab, cmdarg))) {
|
|
is_shfunc = 1;
|
|
break;
|
|
}
|
|
if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
|
|
checked = !(cflags & BINF_BUILTIN);
|
|
break;
|
|
}
|
|
if (!(hn->flags & BINF_PREFIX)) {
|
|
is_builtin = 1;
|
|
#ifdef DYNAMIC
|
|
/* autoload the builtin if necessary */
|
|
if (!((Builtin) hn)->handlerfunc) {
|
|
load_module(((Builtin) hn)->optstr);
|
|
hn = builtintab->getnode(builtintab, cmdarg);
|
|
}
|
|
#endif
|
|
assign = (hn->flags & BINF_MAGICEQUALS);
|
|
break;
|
|
}
|
|
cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
|
|
cflags |= hn->flags;
|
|
uremnode(args, firstnode(args));
|
|
hn = NULL;
|
|
checked = 0;
|
|
if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Do prefork substitutions */
|
|
prefork(args, assign ? 2 : isset(MAGICEQUALSUBST));
|
|
|
|
if (type == SIMPLE) {
|
|
int unglobbed = 0;
|
|
|
|
for (;;) {
|
|
char *cmdarg;
|
|
|
|
if (!(cflags & BINF_NOGLOB))
|
|
while (!checked && !errflag && nonempty(args) &&
|
|
has_token((char *) peekfirst(args)))
|
|
glob(args, firstnode(args));
|
|
else if (!unglobbed) {
|
|
for (node = firstnode(args); node; incnode(node))
|
|
untokenize((char *) getdata(node));
|
|
unglobbed = 1;
|
|
}
|
|
|
|
/* Current shell should not fork unless the *
|
|
* exec occurs at the end of a pipeline. */
|
|
if ((cflags & BINF_EXEC) && last1 == 2)
|
|
cmd->flags |= CFLAG_EXEC;
|
|
|
|
/* Empty command */
|
|
if (empty(args)) {
|
|
if (nonempty(cmd->redir)) {
|
|
if (cmd->flags & CFLAG_EXEC) {
|
|
/* Was this "exec < foobar"? */
|
|
nullexec = 1;
|
|
break;
|
|
} else if (!nullcmd || !*nullcmd ||
|
|
(cflags & BINF_PREFIX)) {
|
|
zerr("redirection with no command", NULL, 0);
|
|
errflag = lastval = 1;
|
|
return;
|
|
} else if (readnullcmd && *readnullcmd &&
|
|
((Redir) peekfirst(cmd->redir))->type == READ &&
|
|
!nextnode(firstnode(cmd->redir))) {
|
|
addlinknode(args, dupstring(readnullcmd));
|
|
} else
|
|
addlinknode(args, dupstring(nullcmd));
|
|
} else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
|
|
lastval = 0;
|
|
return;
|
|
} else {
|
|
cmdoutval = 0;
|
|
addvars(cmd->vars, 0);
|
|
if (errflag)
|
|
lastval = errflag;
|
|
else
|
|
lastval = cmdoutval;
|
|
if (isset(XTRACE)) {
|
|
fputc('\n', stderr);
|
|
fflush(stderr);
|
|
}
|
|
return;
|
|
}
|
|
} else if (isset(RESTRICTED) && (cflags & BINF_EXEC) &&
|
|
(cmd->flags & CFLAG_EXEC)) {
|
|
zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0);
|
|
lastval = 1;
|
|
return;
|
|
}
|
|
|
|
if (errflag || checked ||
|
|
(unset(POSIXBUILTINS) && (cflags & BINF_COMMAND)))
|
|
break;
|
|
|
|
cmdarg = (char *) peekfirst(args);
|
|
if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
|
|
(hn = shfunctab->getnode(shfunctab, cmdarg))) {
|
|
is_shfunc = 1;
|
|
break;
|
|
}
|
|
if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
|
|
if (cflags & BINF_BUILTIN) {
|
|
zerr("no such builtin: %s", cmdarg, 0);
|
|
errflag = lastval = 1;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
if (!(hn->flags & BINF_PREFIX)) {
|
|
is_builtin = 1;
|
|
#ifdef DYNAMIC
|
|
/* autoload the builtin if necessary */
|
|
if (!((Builtin) hn)->handlerfunc)
|
|
load_module(((Builtin) hn)->optstr);
|
|
#endif
|
|
break;
|
|
}
|
|
cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
|
|
cflags |= hn->flags;
|
|
uremnode(args, firstnode(args));
|
|
hn = NULL;
|
|
}
|
|
}
|
|
|
|
if (errflag) {
|
|
lastval = 1;
|
|
return;
|
|
}
|
|
|
|
/* Get the text associated with this command. */
|
|
if (jobbing || (how & Z_TIMED))
|
|
text = getjobtext((void *) cmd);
|
|
else
|
|
text = NULL;
|
|
|
|
/* Set up special parameter $_ */
|
|
zsfree(underscore);
|
|
if (nonempty(args)
|
|
&& (underscore = ztrdup((char *) getdata(lastnode(args)))))
|
|
untokenize(underscore);
|
|
else
|
|
underscore = ztrdup("");
|
|
|
|
/* Warn about "rm *" */
|
|
if (type == SIMPLE && interact && unset(RMSTARSILENT)
|
|
&& isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args))
|
|
&& !strcmp(peekfirst(args), "rm")) {
|
|
LinkNode node, next;
|
|
|
|
for (node = nextnode(firstnode(args)); node && !errflag; node = next) {
|
|
char *s = (char *) getdata(node);
|
|
int l = strlen(s);
|
|
|
|
next = nextnode(node);
|
|
if (s[0] == Star && !s[1]) {
|
|
if (!checkrmall(pwd))
|
|
uremnode(args, node);
|
|
} else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) {
|
|
char t = s[l - 2];
|
|
|
|
s[l - 2] = 0;
|
|
if (!checkrmall(s))
|
|
uremnode(args, node);
|
|
s[l - 2] = t;
|
|
}
|
|
}
|
|
if (!nextnode(firstnode(args)))
|
|
errflag = 1;
|
|
}
|
|
|
|
if (errflag) {
|
|
lastval = 1;
|
|
return;
|
|
}
|
|
|
|
if (type == SIMPLE && !nullexec) {
|
|
char *s;
|
|
char trycd = (isset(AUTOCD) && isset(SHINSTDIN)
|
|
&& empty(cmd->redir) && !empty(args)
|
|
&& !nextnode(firstnode(args))
|
|
&& *(char *)peekfirst(args));
|
|
|
|
DPUTS(empty(args), "BUG: empty(args) in exec.c");
|
|
if (!hn) {
|
|
/* Resolve external commands */
|
|
char *cmdarg = (char *) peekfirst(args);
|
|
|
|
hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
|
|
if (hn && trycd && !isreallycom((Cmdnam)hn)) {
|
|
cmdnamtab->removenode(cmdnamtab, cmdarg);
|
|
cmdnamtab->freenode(hn);
|
|
hn = NULL;
|
|
}
|
|
if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) {
|
|
for (s = cmdarg; *s && *s != '/'; s++);
|
|
if (!*s)
|
|
hn = (HashNode) hashcmd(cmdarg, pathchecked);
|
|
}
|
|
}
|
|
|
|
/* If no command found yet, see if it *
|
|
* is a directory we should AUTOCD to. */
|
|
if (!hn && trycd && (s = cancd(peekfirst(args)))) {
|
|
peekfirst(args) = (void *) s;
|
|
pushnode(args, dupstring("cd"));
|
|
if ((hn = builtintab->getnode(builtintab, "cd")))
|
|
is_builtin = 1;
|
|
}
|
|
}
|
|
|
|
/* This is nonzero if the command is a current shell procedure? */
|
|
is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec);
|
|
|
|
/**************************************************************************
|
|
* Do we need to fork? We need to fork if: *
|
|
* 1) The command is supposed to run in the background. (or) *
|
|
* 2) There is no `exec' flag, and either: *
|
|
* a) This is a builtin or shell function with output piped somewhere. *
|
|
* b) This is an external command and we can't do a `fake exec'. *
|
|
* *
|
|
* A `fake exec' is possible if we have all the following conditions: *
|
|
* 1) last1 flag is 1. This indicates that the current shell will not *
|
|
* be needed after the current command. This is typically the case *
|
|
* when when the command is the last stage in a subshell, or is the *
|
|
* last command after the option `-c'. *
|
|
* 2) We are not trapping EXIT or ZERR. *
|
|
* 3) We don't have any files to delete. *
|
|
* *
|
|
* The condition above for a `fake exec' will also work for a current *
|
|
* shell command such as a builtin, but doesn't really buy us anything *
|
|
* (doesn't save us a process), since it is already running in the *
|
|
* current shell. *
|
|
**************************************************************************/
|
|
|
|
if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) &&
|
|
(((is_builtin || is_shfunc) && output) ||
|
|
(!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] ||
|
|
sigtrapped[SIGEXIT] || havefiles()))))) {
|
|
|
|
pid_t pid;
|
|
int synch[2];
|
|
char dummy;
|
|
|
|
child_block();
|
|
pipe(synch);
|
|
|
|
if ((pid = zfork()) == -1) {
|
|
close(synch[0]);
|
|
close(synch[1]);
|
|
return;
|
|
} if (pid) {
|
|
close(synch[1]);
|
|
read(synch[0], &dummy, 1);
|
|
close(synch[0]);
|
|
#ifdef PATH_DEV_FD
|
|
closem(2);
|
|
#endif
|
|
if (how & Z_ASYNC) {
|
|
lastpid = (long) pid;
|
|
} else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) {
|
|
/* search for STTY=... */
|
|
while (nonempty(cmd->vars))
|
|
if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) {
|
|
jobtab[thisjob].stty_in_env = 1;
|
|
break;
|
|
}
|
|
}
|
|
addproc(pid, text);
|
|
return;
|
|
}
|
|
/* pid == 0 */
|
|
close(synch[0]);
|
|
entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0);
|
|
close(synch[1]);
|
|
forked = 1;
|
|
if (sigtrapped[SIGINT] & ZSIG_IGNORED)
|
|
holdintr();
|
|
#ifdef HAVE_NICE
|
|
/* Check if we should run background jobs at a lower priority. */
|
|
if ((how & Z_ASYNC) && isset(BGNICE))
|
|
nice(5);
|
|
#endif /* HAVE_NICE */
|
|
|
|
} else if (is_cursh) {
|
|
/* This is a current shell procedure that didn't need to fork. *
|
|
* This includes current shell procedures that are being exec'ed, *
|
|
* as well as null execs. */
|
|
jobtab[thisjob].stat |= STAT_CURSH;
|
|
} else {
|
|
/* This is an exec (real or fake) for an external command. *
|
|
* Note that any form of exec means that the subshell is fake *
|
|
* (but we may be in a subshell already). */
|
|
is_exec = 1;
|
|
}
|
|
|
|
if (!(cflags & BINF_NOGLOB))
|
|
globlist(args);
|
|
if (errflag) {
|
|
lastval = 1;
|
|
goto err;
|
|
}
|
|
|
|
/* Add pipeline input/output to mnodes */
|
|
if (input)
|
|
addfd(forked, save, mfds, 0, input, 0);
|
|
if (output)
|
|
addfd(forked, save, mfds, 1, output, 1);
|
|
|
|
/* Do process substitutions */
|
|
spawnpipes(cmd->redir);
|
|
|
|
/* Do io redirections */
|
|
while (nonempty(cmd->redir)) {
|
|
fn = (Redir) ugetnode(cmd->redir);
|
|
DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH,
|
|
"BUG: unexpanded here document");
|
|
if (fn->type == INPIPE) {
|
|
if (fn->fd2 == -1) {
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fn->fd2, 0);
|
|
} else if (fn->type == OUTPIPE) {
|
|
if (fn->fd2 == -1) {
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fn->fd2, 1);
|
|
} else {
|
|
if (fn->type != HERESTR && xpandredir(fn, cmd->redir))
|
|
continue;
|
|
if (errflag) {
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
execerr();
|
|
}
|
|
if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
|
|
zerr("writing redirection not allowed in restricted mode", NULL, 0);
|
|
execerr();
|
|
}
|
|
if (unset(EXECOPT))
|
|
continue;
|
|
switch(fn->type) {
|
|
case HERESTR:
|
|
fil = getherestr(fn);
|
|
if (fil == -1) {
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
if (errno != EINTR)
|
|
zerr("%e", NULL, errno);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fil, 0);
|
|
break;
|
|
case READ:
|
|
case READWRITE:
|
|
if (fn->type == READ)
|
|
fil = open(unmeta(fn->name), O_RDONLY | O_NOCTTY);
|
|
else
|
|
fil = open(unmeta(fn->name),
|
|
O_RDWR | O_CREAT | O_NOCTTY, 0666);
|
|
if (fil == -1) {
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
if (errno != EINTR)
|
|
zerr("%e: %s", fn->name, errno);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fil, 0);
|
|
/* If this is 'exec < file', read from stdin, *
|
|
* not terminal, unless `file' is a terminal. */
|
|
if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact)
|
|
init_io();
|
|
break;
|
|
case CLOSE:
|
|
if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2)
|
|
save[fn->fd1] = movefd(fn->fd1);
|
|
closemn(mfds, fn->fd1);
|
|
zclose(fn->fd1);
|
|
break;
|
|
case MERGEIN:
|
|
case MERGEOUT:
|
|
if(fn->fd2 < 10)
|
|
closemn(mfds, fn->fd2);
|
|
fil = dup(fn->fd2);
|
|
if (fil == -1) {
|
|
char fdstr[4];
|
|
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
sprintf(fdstr, "%d", fn->fd2);
|
|
zerr("%s: %e", fdstr, errno);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT);
|
|
break;
|
|
default:
|
|
if (IS_APPEND_REDIR(fn->type))
|
|
fil = open(unmeta(fn->name),
|
|
(unset(CLOBBER) && !IS_CLOBBER_REDIR(fn->type)) ?
|
|
O_WRONLY | O_APPEND | O_NOCTTY :
|
|
O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0666);
|
|
else
|
|
fil = clobber_open(fn);
|
|
if(fil != -1 && IS_ERROR_REDIR(fn->type))
|
|
dfil = dup(fil);
|
|
else
|
|
dfil = 0;
|
|
if (fil == -1 || dfil == -1) {
|
|
if(fil != -1)
|
|
close(fil);
|
|
closemnodes(mfds);
|
|
fixfds(save);
|
|
if (errno != EINTR)
|
|
zerr("%e: %s", fn->name, errno);
|
|
execerr();
|
|
}
|
|
addfd(forked, save, mfds, fn->fd1, fil, 1);
|
|
if(IS_ERROR_REDIR(fn->type))
|
|
addfd(forked, save, mfds, 2, dfil, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are done with redirection. close the mnodes, *
|
|
* spawning tee/cat processes as necessary. */
|
|
for (i = 0; i < 10; i++)
|
|
closemn(mfds, i);
|
|
|
|
if (nullexec) {
|
|
for (i = 0; i < 10; i++)
|
|
if (save[i] != -2)
|
|
zclose(save[i]);
|
|
/*
|
|
* Here we specifically *don't* restore the original fd's
|
|
* before returning.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (isset(EXECOPT) && !errflag) {
|
|
/*
|
|
* We delay the entersubsh() to here when we are exec'ing
|
|
* the current shell (including a fake exec to run a builtin then
|
|
* exit) in case there is an error return.
|
|
*/
|
|
if (is_exec)
|
|
entersubsh(how, type != SUBSH ? 2 : 1, 1);
|
|
if (type >= CURSH) {
|
|
static int (*func[]) _((Cmd)) = {
|
|
execcursh, exectime, execfuncdef, execfor, execwhile,
|
|
execrepeat, execif, execcase, execselect, execcond,
|
|
execarith, execautofn
|
|
};
|
|
|
|
if (last1 == 1)
|
|
cmd->flags |= CFLAG_EXEC;
|
|
lastval = (func[type - CURSH]) (cmd);
|
|
} else if (is_builtin || is_shfunc) {
|
|
LinkList restorelist = 0, removelist = 0;
|
|
/* builtin or shell function */
|
|
|
|
if (!forked && ((cflags & BINF_COMMAND) ||
|
|
(unset(POSIXBUILTINS) && !assign) ||
|
|
(isset(POSIXBUILTINS) && !is_shfunc &&
|
|
!(hn->flags & BINF_PSPECIAL))))
|
|
save_params(cmd, &restorelist, &removelist);
|
|
|
|
if (cmd->vars) {
|
|
/* Export this if the command is a shell function,
|
|
* but not if it's a builtin.
|
|
*/
|
|
addvars(cmd->vars, is_shfunc);
|
|
if (errflag) {
|
|
restore_params(restorelist, removelist);
|
|
lastval = 1;
|
|
fixfds(save);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (is_shfunc) {
|
|
/* It's a shell function */
|
|
#ifdef PATH_DEV_FD
|
|
int i;
|
|
|
|
for (i = 10; i <= max_zsh_fd; i++)
|
|
if (fdtable[i] > 1)
|
|
fdtable[i]++;
|
|
#endif
|
|
if (subsh_close >= 0)
|
|
zclose(subsh_close);
|
|
subsh_close = -1;
|
|
execshfunc(cmd, (Shfunc) hn);
|
|
#ifdef PATH_DEV_FD
|
|
for (i = 10; i <= max_zsh_fd; i++)
|
|
if (fdtable[i] > 1)
|
|
if (--(fdtable[i]) <= 2)
|
|
zclose(i);
|
|
#endif
|
|
} else {
|
|
/* It's a builtin */
|
|
if (forked)
|
|
closem(1);
|
|
lastval = execbuiltin(args, (Builtin) hn);
|
|
#ifdef PATH_DEV_FD
|
|
closem(2);
|
|
#endif
|
|
if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && lastval && !subsh) {
|
|
fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
|
|
}
|
|
fflush(stdout);
|
|
if (save[1] == -2) {
|
|
if (ferror(stdout)) {
|
|
zerr("write error: %e", NULL, errno);
|
|
clearerr(stdout);
|
|
errflag = 0;
|
|
}
|
|
} else
|
|
clearerr(stdout);
|
|
}
|
|
|
|
if (cmd->flags & CFLAG_EXEC) {
|
|
if (subsh)
|
|
_exit(lastval);
|
|
|
|
/* If we are exec'ing a command, and we are not in a subshell, *
|
|
* then check if we should save the history file. */
|
|
if (isset(RCS) && interact && !nohistsave)
|
|
savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
|
|
exit(lastval);
|
|
}
|
|
|
|
restore_params(restorelist, removelist);
|
|
|
|
} else {
|
|
if (cmd->flags & CFLAG_EXEC) {
|
|
setiparam("SHLVL", --shlvl);
|
|
/* If we are exec'ing a command, and we are not *
|
|
* in a subshell, then save the history file. */
|
|
if (!subsh && isset(RCS) && interact && !nohistsave)
|
|
savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0);
|
|
}
|
|
if (type == SIMPLE) {
|
|
if (cmd->vars) {
|
|
addvars(cmd->vars, -1);
|
|
if (errflag)
|
|
_exit(1);
|
|
}
|
|
closem(1);
|
|
if (coprocin)
|
|
zclose(coprocin);
|
|
if (coprocout)
|
|
zclose(coprocout);
|
|
#ifdef HAVE_GETRLIMIT
|
|
if (!forked)
|
|
setlimits(NULL);
|
|
#endif
|
|
execute((Cmdnam) hn, cflags & BINF_DASH);
|
|
} else { /* ( ... ) */
|
|
DPUTS(cmd->vars && nonempty(cmd->vars),
|
|
"BUG: assigment before complex command");
|
|
list_pipe = 0;
|
|
if (subsh_close >= 0)
|
|
zclose(subsh_close);
|
|
subsh_close = -1;
|
|
/* If we're forked (and we should be), no need to return */
|
|
DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
|
|
execlist(cmd->u.list, 0, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
err:
|
|
if (forked)
|
|
_exit(lastval);
|
|
fixfds(save);
|
|
}
|
|
|
|
/* Arrange to have variables restored. */
|
|
|
|
/**/
|
|
static void
|
|
save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p)
|
|
{
|
|
Param pm;
|
|
LinkNode node;
|
|
char *s;
|
|
|
|
MUSTUSEHEAP("save_params()");
|
|
|
|
*restore_p = newlinklist();
|
|
*remove_p = newlinklist();
|
|
|
|
for (node = firstnode(cmd->vars); node; incnode(node)) {
|
|
s = ((Varasg) getdata(node))->name;
|
|
if ((pm = (Param) paramtab->getnode(paramtab, s))) {
|
|
if (!(pm->flags & PM_SPECIAL)) {
|
|
paramtab->removenode(paramtab, s);
|
|
} else if (!(pm->flags & PM_READONLY) &&
|
|
(unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) {
|
|
Param tpm = (Param) alloc(sizeof *tpm);
|
|
|
|
tpm->nam = s;
|
|
tpm->flags = pm->flags;
|
|
switch (PM_TYPE(pm->flags)) {
|
|
case PM_SCALAR:
|
|
tpm->u.str = ztrdup(pm->gets.cfn(pm));
|
|
break;
|
|
case PM_INTEGER:
|
|
tpm->u.val = pm->gets.ifn(pm);
|
|
break;
|
|
case PM_ARRAY:
|
|
PERMALLOC {
|
|
tpm->u.arr = arrdup(pm->gets.afn(pm));
|
|
} LASTALLOC;
|
|
break;
|
|
}
|
|
pm = tpm;
|
|
}
|
|
addlinknode(*remove_p, s);
|
|
addlinknode(*restore_p, pm);
|
|
} else {
|
|
addlinknode(*remove_p, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Restore saved parameters after executing a shfunc or builtin */
|
|
|
|
/**/
|
|
static void
|
|
restore_params(LinkList restorelist, LinkList removelist)
|
|
{
|
|
Param pm;
|
|
char *s;
|
|
|
|
if (removelist) {
|
|
/* remove temporary parameters */
|
|
while ((s = (char *) ugetnode(removelist))) {
|
|
if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
|
|
!(pm->flags & PM_SPECIAL)) {
|
|
pm->flags &= ~PM_READONLY;
|
|
unsetparam_pm(pm, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (restorelist) {
|
|
/* restore saved parameters */
|
|
while ((pm = (Param) ugetnode(restorelist))) {
|
|
if (pm->flags & PM_SPECIAL) {
|
|
Param tpm = (Param) paramtab->getnode(paramtab, pm->nam);
|
|
|
|
DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
|
|
!(pm->flags & PM_SPECIAL),
|
|
"BUG: in restoring special parameters");
|
|
tpm->flags = pm->flags;
|
|
switch (PM_TYPE(pm->flags)) {
|
|
case PM_SCALAR:
|
|
tpm->sets.cfn(tpm, pm->u.str);
|
|
break;
|
|
case PM_INTEGER:
|
|
tpm->sets.ifn(tpm, pm->u.val);
|
|
break;
|
|
case PM_ARRAY:
|
|
tpm->sets.afn(tpm, pm->u.arr);
|
|
break;
|
|
}
|
|
} else
|
|
paramtab->addnode(paramtab, pm->nam, pm);
|
|
if (pm->flags & PM_EXPORTED)
|
|
pm->env = addenv(pm->nam, getsparam(pm->nam));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* restore fds after redirecting a builtin */
|
|
|
|
/**/
|
|
static void
|
|
fixfds(int *save)
|
|
{
|
|
int old_errno = errno;
|
|
int i;
|
|
|
|
for (i = 0; i != 10; i++)
|
|
if (save[i] != -2)
|
|
redup(save[i], i);
|
|
errno = old_errno;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
entersubsh(int how, int cl, int fake)
|
|
{
|
|
int sig;
|
|
|
|
if (cl != 2)
|
|
for (sig = 0; sig < VSIGCOUNT; sig++)
|
|
if (!(sigtrapped[sig] & ZSIG_FUNC))
|
|
unsettrap(sig);
|
|
if (unset(MONITOR)) {
|
|
if (how & Z_ASYNC) {
|
|
settrap(SIGINT, NULL);
|
|
settrap(SIGQUIT, NULL);
|
|
if (isatty(0)) {
|
|
close(0);
|
|
if (open("/dev/null", O_RDWR | O_NOCTTY)) {
|
|
zerr("can't open /dev/null: %e", NULL, errno);
|
|
_exit(1);
|
|
}
|
|
}
|
|
}
|
|
} else if (thisjob != -1 && cl) {
|
|
if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
|
|
if (kill(jobtab[list_pipe_job].gleader, 0) == -1 ||
|
|
setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) {
|
|
jobtab[list_pipe_job].gleader =
|
|
jobtab[thisjob].gleader = mypgrp;
|
|
setpgrp(0L, mypgrp);
|
|
|
|
if (how & Z_SYNC)
|
|
attachtty(jobtab[thisjob].gleader);
|
|
}
|
|
}
|
|
else if (!jobtab[thisjob].gleader ||
|
|
(setpgrp(0L, jobtab[thisjob].gleader) == -1)) {
|
|
jobtab[thisjob].gleader = getpid();
|
|
if (list_pipe_job != thisjob &&
|
|
!jobtab[list_pipe_job].gleader)
|
|
jobtab[list_pipe_job].gleader = jobtab[thisjob].gleader;
|
|
setpgrp(0L, jobtab[thisjob].gleader);
|
|
if (how & Z_SYNC)
|
|
attachtty(jobtab[thisjob].gleader);
|
|
}
|
|
}
|
|
if (!fake)
|
|
subsh = 1;
|
|
if (SHTTY != -1) {
|
|
zclose(SHTTY);
|
|
SHTTY = -1;
|
|
}
|
|
if (isset(MONITOR)) {
|
|
signal_default(SIGTTOU);
|
|
signal_default(SIGTTIN);
|
|
signal_default(SIGTSTP);
|
|
}
|
|
if (interact) {
|
|
signal_default(SIGTERM);
|
|
if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
|
|
signal_default(SIGINT);
|
|
}
|
|
if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
|
|
signal_default(SIGQUIT);
|
|
opts[MONITOR] = opts[USEZLE] = 0;
|
|
zleactive = 0;
|
|
if (cl)
|
|
clearjobtab();
|
|
times(&shtms);
|
|
}
|
|
|
|
/* close internal shell fds */
|
|
|
|
/**/
|
|
void
|
|
closem(int how)
|
|
{
|
|
int i;
|
|
|
|
for (i = 10; i <= max_zsh_fd; i++)
|
|
if (fdtable[i] && (!how || fdtable[i] == how))
|
|
zclose(i);
|
|
}
|
|
|
|
/* convert here document into a here string */
|
|
|
|
/**/
|
|
char *
|
|
gethere(char *str, int typ)
|
|
{
|
|
char *buf;
|
|
int bsiz, qt = 0, strip = 0;
|
|
char *s, *t, *bptr, c;
|
|
|
|
for (s = str; *s; s++)
|
|
if (INULL(*s)) {
|
|
*s = Nularg;
|
|
qt = 1;
|
|
}
|
|
untokenize(str);
|
|
if (typ == HEREDOCDASH) {
|
|
strip = 1;
|
|
while (*str == '\t')
|
|
str++;
|
|
}
|
|
bptr = buf = zalloc(bsiz = 256);
|
|
for (;;) {
|
|
t = bptr;
|
|
|
|
while ((c = hgetc()) == '\t' && strip)
|
|
;
|
|
for (;;) {
|
|
if (bptr == buf + bsiz) {
|
|
buf = realloc(buf, 2 * bsiz);
|
|
t = buf + bsiz - (bptr - t);
|
|
bptr = buf + bsiz;
|
|
bsiz *= 2;
|
|
}
|
|
if (lexstop || c == '\n')
|
|
break;
|
|
*bptr++ = c;
|
|
c = hgetc();
|
|
}
|
|
*bptr = '\0';
|
|
if (!strcmp(t, str))
|
|
break;
|
|
if (lexstop) {
|
|
t = bptr;
|
|
break;
|
|
}
|
|
*bptr++ = '\n';
|
|
}
|
|
if (t > buf && t[-1] == '\n')
|
|
t--;
|
|
*t = '\0';
|
|
if (!qt)
|
|
parsestr(buf);
|
|
s = dupstring(buf);
|
|
zfree(buf, bsiz);
|
|
return s;
|
|
}
|
|
|
|
/* open here string fd */
|
|
|
|
/**/
|
|
static int
|
|
getherestr(struct redir *fn)
|
|
{
|
|
char *s, *t;
|
|
int fd, len;
|
|
|
|
t = fn->name;
|
|
singsub(&t);
|
|
untokenize(t);
|
|
unmetafy(t, &len);
|
|
t[len++] = '\n';
|
|
s = gettempname();
|
|
if (!s || (fd = open(s, O_CREAT|O_WRONLY|O_EXCL|O_NOCTTY, 0600)) == -1)
|
|
return -1;
|
|
write(fd, t, len);
|
|
close(fd);
|
|
fd = open(s, O_RDONLY | O_NOCTTY);
|
|
unlink(s);
|
|
return fd;
|
|
}
|
|
|
|
/* $(...) */
|
|
|
|
/**/
|
|
LinkList
|
|
getoutput(char *cmd, int qt)
|
|
{
|
|
List list;
|
|
int pipes[2];
|
|
pid_t pid;
|
|
Cmd c;
|
|
Redir r;
|
|
|
|
if (!(list = parse_string(cmd)))
|
|
return NULL;
|
|
if (list != &dummy_list && !list->right && !list->left->flags &&
|
|
list->left->type == END && list->left->left->type == END &&
|
|
(c = list->left->left->left)->type == SIMPLE && empty(c->args) &&
|
|
empty(c->vars) && nonempty(c->redir) &&
|
|
!nextnode(firstnode(c->redir)) &&
|
|
(r = (Redir) getdata(firstnode(c->redir)))->fd1 == 0 &&
|
|
r->type == READ) {
|
|
/* $(< word) */
|
|
int stream;
|
|
char *s = r->name;
|
|
|
|
singsub(&s);
|
|
if (errflag)
|
|
return NULL;
|
|
untokenize(s);
|
|
if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
|
|
zerr("%e: %s", s, errno);
|
|
return NULL;
|
|
}
|
|
return readoutput(stream, qt);
|
|
}
|
|
|
|
mpipe(pipes);
|
|
child_block();
|
|
cmdoutval = 0;
|
|
if ((cmdoutpid = pid = zfork()) == -1) {
|
|
/* fork error */
|
|
zclose(pipes[0]);
|
|
zclose(pipes[1]);
|
|
errflag = 1;
|
|
cmdoutpid = 0;
|
|
child_unblock();
|
|
return NULL;
|
|
} else if (pid) {
|
|
LinkList retval;
|
|
|
|
zclose(pipes[1]);
|
|
retval = readoutput(pipes[0], qt);
|
|
fdtable[pipes[0]] = 0;
|
|
child_suspend(0); /* unblocks */
|
|
lastval = cmdoutval;
|
|
return retval;
|
|
}
|
|
|
|
/* pid == 0 */
|
|
child_unblock();
|
|
zclose(pipes[0]);
|
|
redup(pipes[1], 1);
|
|
opts[MONITOR] = 0;
|
|
entersubsh(Z_SYNC, 1, 0);
|
|
execlist(list, 0, 1);
|
|
close(1);
|
|
_exit(lastval);
|
|
zerr("exit returned in child!!", NULL, 0);
|
|
kill(getpid(), SIGKILL);
|
|
return NULL;
|
|
}
|
|
|
|
/* read output of command substitution */
|
|
|
|
/**/
|
|
static LinkList
|
|
readoutput(int in, int qt)
|
|
{
|
|
LinkList ret;
|
|
char *buf, *ptr;
|
|
int bsiz, c, cnt = 0;
|
|
FILE *fin;
|
|
|
|
fin = fdopen(in, "r");
|
|
ret = newlinklist();
|
|
ptr = buf = (char *) ncalloc(bsiz = 64);
|
|
while ((c = fgetc(fin)) != EOF || errno == EINTR) {
|
|
if (c == EOF) {
|
|
errno = 0;
|
|
clearerr(fin);
|
|
continue;
|
|
}
|
|
if (imeta(c)) {
|
|
*ptr++ = Meta;
|
|
c ^= 32;
|
|
cnt++;
|
|
}
|
|
if (++cnt >= bsiz) {
|
|
char *pp = (char *) ncalloc(bsiz *= 2);
|
|
|
|
memcpy(pp, buf, cnt - 1);
|
|
ptr = (buf = pp) + cnt - 1;
|
|
}
|
|
*ptr++ = c;
|
|
}
|
|
fclose(fin);
|
|
while (cnt && ptr[-1] == '\n')
|
|
ptr--, cnt--;
|
|
*ptr = '\0';
|
|
if (qt) {
|
|
if (!cnt) {
|
|
*ptr++ = Nularg;
|
|
*ptr = '\0';
|
|
}
|
|
addlinknode(ret, buf);
|
|
} else {
|
|
char **words = spacesplit(buf, 0);
|
|
|
|
while (*words) {
|
|
if (isset(GLOBSUBST))
|
|
tokenize(*words);
|
|
addlinknode(ret, *words++);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**/
|
|
static List
|
|
parsecmd(char *cmd)
|
|
{
|
|
char *str;
|
|
List list;
|
|
|
|
for (str = cmd + 2; *str && *str != Outpar; str++);
|
|
if (!*str || cmd[1] != Inpar) {
|
|
zerr("oops.", NULL, 0);
|
|
return NULL;
|
|
}
|
|
*str = '\0';
|
|
if (str[1] || !(list = parse_string(cmd + 2))) {
|
|
zerr("parse error in process substitution", NULL, 0);
|
|
return NULL;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/* =(...) */
|
|
|
|
/**/
|
|
char *
|
|
getoutputfile(char *cmd)
|
|
{
|
|
pid_t pid;
|
|
char *nam;
|
|
List list;
|
|
int fd;
|
|
|
|
if (thisjob == -1)
|
|
return NULL;
|
|
if (!(list = parsecmd(cmd)))
|
|
return NULL;
|
|
if (!(nam = gettempname()))
|
|
return NULL;
|
|
|
|
nam = ztrdup(nam);
|
|
PERMALLOC {
|
|
if (!jobtab[thisjob].filelist)
|
|
jobtab[thisjob].filelist = newlinklist();
|
|
addlinknode(jobtab[thisjob].filelist, nam);
|
|
} LASTALLOC;
|
|
child_block();
|
|
fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600);
|
|
|
|
if (fd < 0 || (cmdoutpid = pid = zfork()) == -1) {
|
|
/* fork or open error */
|
|
child_unblock();
|
|
return nam;
|
|
} else if (pid) {
|
|
int os;
|
|
|
|
close(fd);
|
|
os = jobtab[thisjob].stat;
|
|
waitforpid(pid);
|
|
cmdoutval = 0;
|
|
jobtab[thisjob].stat = os;
|
|
return nam;
|
|
}
|
|
|
|
/* pid == 0 */
|
|
redup(fd, 1);
|
|
opts[MONITOR] = 0;
|
|
entersubsh(Z_SYNC, 1, 0);
|
|
execlist(list, 0, 1);
|
|
close(1);
|
|
_exit(lastval);
|
|
zerr("exit returned in child!!", NULL, 0);
|
|
kill(getpid(), SIGKILL);
|
|
return NULL;
|
|
}
|
|
|
|
#if !defined(PATH_DEV_FD) && defined(HAVE_FIFOS)
|
|
/* get a temporary named pipe */
|
|
|
|
static char *
|
|
namedpipe(void)
|
|
{
|
|
char *tnam = gettempname();
|
|
|
|
# ifdef HAVE_MKFIFO
|
|
if (mkfifo(tnam, 0600) < 0)
|
|
# else
|
|
if (mknod(tnam, 0010600, 0) < 0)
|
|
# endif
|
|
return NULL;
|
|
return tnam;
|
|
}
|
|
#endif /* ! PATH_DEV_FD && HAVE_FIFOS */
|
|
|
|
/* <(...) or >(...) */
|
|
|
|
/**/
|
|
char *
|
|
getproc(char *cmd)
|
|
{
|
|
#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD)
|
|
zerr("doesn't look like your system supports FIFOs.", NULL, 0);
|
|
return NULL;
|
|
#else
|
|
List list;
|
|
int out = *cmd == Inang;
|
|
char *pnam;
|
|
#ifndef PATH_DEV_FD
|
|
int fd;
|
|
#else
|
|
int pipes[2];
|
|
#endif
|
|
|
|
if (thisjob == -1)
|
|
return NULL;
|
|
#ifndef PATH_DEV_FD
|
|
if (!(pnam = namedpipe()))
|
|
return NULL;
|
|
#else
|
|
pnam = ncalloc(strlen(PATH_DEV_FD) + 6);
|
|
#endif
|
|
if (!(list = parsecmd(cmd)))
|
|
return NULL;
|
|
#ifndef PATH_DEV_FD
|
|
PERMALLOC {
|
|
if (!jobtab[thisjob].filelist)
|
|
jobtab[thisjob].filelist = newlinklist();
|
|
addlinknode(jobtab[thisjob].filelist, ztrdup(pnam));
|
|
} LASTALLOC;
|
|
if (zfork()) {
|
|
#else
|
|
mpipe(pipes);
|
|
if (zfork()) {
|
|
sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]);
|
|
zclose(pipes[out]);
|
|
fdtable[pipes[!out]] = 2;
|
|
#endif
|
|
return pnam;
|
|
}
|
|
#ifndef PATH_DEV_FD
|
|
closem(0);
|
|
fd = open(pnam, out ? O_WRONLY | O_NOCTTY : O_RDONLY | O_NOCTTY);
|
|
if (fd == -1) {
|
|
zerr("can't open %s: %e", pnam, errno);
|
|
_exit(1);
|
|
}
|
|
entersubsh(Z_ASYNC, 1, 0);
|
|
redup(fd, out);
|
|
#else
|
|
entersubsh(Z_ASYNC, 1, 0);
|
|
redup(pipes[out], out);
|
|
closem(0); /* this closes pipes[!out] as well */
|
|
#endif
|
|
execlist(list, 0, 1);
|
|
zclose(out);
|
|
_exit(lastval);
|
|
return NULL;
|
|
#endif /* HAVE_FIFOS and PATH_DEV_FD not defined */
|
|
}
|
|
|
|
/* > >(...) or < <(...) (does not use named pipes) */
|
|
|
|
/**/
|
|
static int
|
|
getpipe(char *cmd)
|
|
{
|
|
List list;
|
|
int pipes[2], out = *cmd == Inang;
|
|
|
|
if (!(list = parsecmd(cmd)))
|
|
return -1;
|
|
mpipe(pipes);
|
|
if (zfork()) {
|
|
zclose(pipes[out]);
|
|
return pipes[!out];
|
|
}
|
|
entersubsh(Z_ASYNC, 1, 0);
|
|
redup(pipes[out], out);
|
|
closem(0); /* this closes pipes[!out] as well */
|
|
execlist(list, 0, 1);
|
|
_exit(lastval);
|
|
return 0;
|
|
}
|
|
|
|
/* open pipes with fds >= 10 */
|
|
|
|
/**/
|
|
static void
|
|
mpipe(int *pp)
|
|
{
|
|
pipe(pp);
|
|
pp[0] = movefd(pp[0]);
|
|
pp[1] = movefd(pp[1]);
|
|
}
|
|
|
|
/* Do process substitution with redirection */
|
|
|
|
/**/
|
|
static void
|
|
spawnpipes(LinkList l)
|
|
{
|
|
LinkNode n;
|
|
Redir f;
|
|
char *str;
|
|
|
|
n = firstnode(l);
|
|
for (; n; incnode(n)) {
|
|
f = (Redir) getdata(n);
|
|
if (f->type == OUTPIPE || f->type == INPIPE) {
|
|
str = f->name;
|
|
f->fd2 = getpipe(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* evaluate a [[ ... ]] */
|
|
|
|
/**/
|
|
static int
|
|
execcond(Cmd cmd)
|
|
{
|
|
return !evalcond(cmd->u.cond);
|
|
}
|
|
|
|
/* evaluate a ((...)) arithmetic command */
|
|
|
|
/**/
|
|
static int
|
|
execarith(Cmd cmd)
|
|
{
|
|
char *e;
|
|
long val = 0;
|
|
|
|
while ((e = (char *) ugetnode(cmd->args)))
|
|
val = matheval(e);
|
|
errflag = 0;
|
|
return !val;
|
|
}
|
|
|
|
/* perform time ... command */
|
|
|
|
/**/
|
|
static int
|
|
exectime(Cmd cmd)
|
|
{
|
|
int jb;
|
|
|
|
jb = thisjob;
|
|
if (!cmd->u.pline) {
|
|
shelltime();
|
|
return 0;
|
|
}
|
|
execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0);
|
|
thisjob = jb;
|
|
return lastval;
|
|
}
|
|
|
|
/* Define a shell function */
|
|
|
|
/**/
|
|
static int
|
|
execfuncdef(Cmd cmd)
|
|
{
|
|
Shfunc shf;
|
|
char *s;
|
|
int signum;
|
|
|
|
PERMALLOC {
|
|
while ((s = (char *) ugetnode(cmd->args))) {
|
|
shf = (Shfunc) zalloc(sizeof *shf);
|
|
shf->funcdef = (List) dupstruct(cmd->u.list);
|
|
shf->flags = 0;
|
|
|
|
/* is this shell function a signal trap? */
|
|
if (!strncmp(s, "TRAP", 4) && (signum = getsignum(s + 4)) != -1) {
|
|
if (settrap(signum, shf->funcdef)) {
|
|
freestruct(shf->funcdef);
|
|
zfree(shf, sizeof *shf);
|
|
LASTALLOC_RETURN 1;
|
|
}
|
|
sigtrapped[signum] |= ZSIG_FUNC;
|
|
}
|
|
shfunctab->addnode(shfunctab, ztrdup(s), shf);
|
|
}
|
|
} LASTALLOC;
|
|
if(isset(HISTNOFUNCTIONS))
|
|
remhist();
|
|
return 0;
|
|
}
|
|
|
|
/* Main entry point to execute a shell function. */
|
|
|
|
/**/
|
|
static void
|
|
execshfunc(Cmd cmd, Shfunc shf)
|
|
{
|
|
LinkList last_file_list = NULL;
|
|
|
|
if (errflag)
|
|
return;
|
|
|
|
if (!list_pipe) {
|
|
/* Without this deletejob the process table *
|
|
* would be filled by a recursive function. */
|
|
last_file_list = jobtab[thisjob].filelist;
|
|
jobtab[thisjob].filelist = NULL;
|
|
deletejob(jobtab + thisjob);
|
|
}
|
|
|
|
doshfunc(shf->funcdef, cmd->args, shf->flags, 0);
|
|
|
|
if (!list_pipe)
|
|
deletefilelist(last_file_list);
|
|
}
|
|
|
|
/* Function to execute the special type of command that represents an *
|
|
* autoloaded shell function. The command structure tells us which *
|
|
* function it is. This function is actually called as part of the *
|
|
* execution of the autoloaded function itself, so when the function *
|
|
* has been autoloaded, its list is just run with no frills. */
|
|
|
|
/**/
|
|
static int
|
|
execautofn(Cmd cmd)
|
|
{
|
|
Shfunc shf = cmd->u.autofn->shf;
|
|
List l = getfpfunc(shf->nam);
|
|
if(l == &dummy_list) {
|
|
zerr("%s: function definition file not found", shf->nam, 0);
|
|
return 1;
|
|
}
|
|
if(isset(KSHAUTOLOAD)) {
|
|
VARARR(char, n, strlen(shf->nam) + 1);
|
|
strcpy(n, shf->nam);
|
|
execlist(l, 1, 0);
|
|
shf = (Shfunc) shfunctab->getnode(shfunctab, n);
|
|
if(!shf || (shf->flags & PM_UNDEFINED)) {
|
|
zerr("%s: function not defined by file", n, 0);
|
|
return 1;
|
|
}
|
|
} else {
|
|
freestruct(shf->funcdef);
|
|
PERMALLOC {
|
|
shf->funcdef = dupstruct(stripkshdef(l, shf->nam));
|
|
} LASTALLOC;
|
|
shf->flags &= ~PM_UNDEFINED;
|
|
}
|
|
HEAPALLOC {
|
|
execlist(dupstruct(shf->funcdef), 1, 0);
|
|
} LASTALLOC;
|
|
return lastval;
|
|
}
|
|
|
|
/* execute a shell function */
|
|
|
|
/**/
|
|
void
|
|
doshfunc(List list, LinkList doshargs, int flags, int noreturnval)
|
|
/* If noreturnval is nonzero, then reset the current return *
|
|
* value (lastval) to its value before the shell function *
|
|
* was executed. */
|
|
{
|
|
char **tab, **x, *oargv0 = NULL;
|
|
int xexittr, newexittr, oldzoptind, oldlastval;
|
|
char *ou;
|
|
void *xexitfn, *newexitfn;
|
|
char saveopts[OPT_SIZE];
|
|
int obreaks = breaks;
|
|
|
|
HEAPALLOC {
|
|
pushheap();
|
|
if (trapreturn < 0)
|
|
trapreturn--;
|
|
oldlastval = lastval;
|
|
xexittr = sigtrapped[SIGEXIT];
|
|
if (xexittr & ZSIG_FUNC)
|
|
xexitfn = shfunctab->removenode(shfunctab, "TRAPEXIT");
|
|
else
|
|
xexitfn = sigfuncs[SIGEXIT];
|
|
sigtrapped[SIGEXIT] = 0;
|
|
sigfuncs[SIGEXIT] = NULL;
|
|
tab = pparams;
|
|
oldzoptind = zoptind;
|
|
zoptind = 1;
|
|
|
|
/* We need to save the current options even if LOCALOPTIONS is *
|
|
* not currently set. That's because if it gets set in the *
|
|
* function we need to restore the original options on exit. */
|
|
memcpy(saveopts, opts, sizeof(opts));
|
|
|
|
if (flags & PM_TAGGED)
|
|
opts[XTRACE] = 1;
|
|
opts[PRINTEXITVALUE] = 0;
|
|
if (doshargs) {
|
|
LinkNode node;
|
|
|
|
node = doshargs->first;
|
|
pparams = x = (char **) zcalloc(((sizeof *x) * (1 + countlinknodes(doshargs))));
|
|
if (isset(FUNCTIONARGZERO)) {
|
|
oargv0 = argzero;
|
|
argzero = ztrdup((char *) node->dat);
|
|
}
|
|
node = node->next;
|
|
for (; node; node = node->next, x++)
|
|
*x = ztrdup((char *) node->dat);
|
|
} else {
|
|
pparams = (char **) zcalloc(sizeof *pparams);
|
|
if (isset(FUNCTIONARGZERO)) {
|
|
oargv0 = argzero;
|
|
argzero = ztrdup(argzero);
|
|
}
|
|
}
|
|
startparamscope();
|
|
ou = underscore;
|
|
underscore = ztrdup(underscore);
|
|
execlist(dupstruct(list), 1, 0);
|
|
zsfree(underscore);
|
|
underscore = ou;
|
|
endparamscope();
|
|
|
|
if (retflag) {
|
|
retflag = 0;
|
|
breaks = obreaks;
|
|
}
|
|
freearray(pparams);
|
|
if (oargv0) {
|
|
zsfree(argzero);
|
|
argzero = oargv0;
|
|
}
|
|
zoptind = oldzoptind;
|
|
pparams = tab;
|
|
|
|
if (isset(LOCALOPTIONS)) {
|
|
/* restore all shell options except PRIVILEGED and RESTRICTED */
|
|
saveopts[PRIVILEGED] = opts[PRIVILEGED];
|
|
saveopts[RESTRICTED] = opts[RESTRICTED];
|
|
memcpy(opts, saveopts, sizeof(opts));
|
|
} else {
|
|
/* just restore a couple. */
|
|
opts[XTRACE] = saveopts[XTRACE];
|
|
opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
|
|
opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
|
|
}
|
|
|
|
/*
|
|
* The trap '...' EXIT runs in the environment of the caller,
|
|
* so remember it here but run it after resetting the
|
|
* traps for the parent.
|
|
*/
|
|
newexittr = sigtrapped[SIGEXIT];
|
|
newexitfn = sigfuncs[SIGEXIT];
|
|
if (newexittr & ZSIG_FUNC)
|
|
shfunctab->removenode(shfunctab, "TRAPEXIT");
|
|
|
|
sigtrapped[SIGEXIT] = xexittr;
|
|
if (xexittr & ZSIG_FUNC) {
|
|
shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn);
|
|
sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef;
|
|
} else
|
|
sigfuncs[SIGEXIT] = (List) xexitfn;
|
|
|
|
if (newexitfn) {
|
|
dotrapargs(SIGEXIT, &newexittr, newexitfn);
|
|
freestruct(newexitfn);
|
|
}
|
|
|
|
if (trapreturn < -1)
|
|
trapreturn++;
|
|
if (noreturnval)
|
|
lastval = oldlastval;
|
|
popheap();
|
|
} LASTALLOC;
|
|
}
|
|
|
|
/* Search fpath for an undefined function. Finds the file, and returns the *
|
|
* list of its contents. */
|
|
|
|
/**/
|
|
static List
|
|
getfpfunc(char *s)
|
|
{
|
|
char **pp, buf[PATH_MAX];
|
|
off_t len;
|
|
char *d;
|
|
List r;
|
|
int fd;
|
|
|
|
pp = fpath;
|
|
for (; *pp; pp++) {
|
|
if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX)
|
|
continue;
|
|
if (**pp)
|
|
sprintf(buf, "%s/%s", *pp, s);
|
|
else
|
|
strcpy(buf, s);
|
|
unmetafy(buf, NULL);
|
|
if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
|
|
if ((len = lseek(fd, 0, 2)) != -1) {
|
|
lseek(fd, 0, 0);
|
|
d = (char *) zcalloc(len + 1);
|
|
if (read(fd, d, len) == len) {
|
|
close(fd);
|
|
d = metafy(d, len, META_REALLOC);
|
|
HEAPALLOC {
|
|
r = parse_string(d);
|
|
} LASTALLOC;
|
|
zfree(d, len + 1);
|
|
return r;
|
|
} else {
|
|
zfree(d, len + 1);
|
|
close(fd);
|
|
}
|
|
} else {
|
|
close(fd);
|
|
}
|
|
}
|
|
}
|
|
return &dummy_list;
|
|
}
|
|
|
|
/* Handle the most common type of ksh-style autoloading, when doing a *
|
|
* zsh-style autoload. Given the list read from an autoload file, and the *
|
|
* name of the function being defined, check to see if the file consists *
|
|
* entirely of a single definition for that function. If so, use the *
|
|
* contents of that definition. Otherwise, use the entire file. */
|
|
|
|
/**/
|
|
static List
|
|
stripkshdef(List l, char *name)
|
|
{
|
|
Sublist s;
|
|
Pline p;
|
|
Cmd c;
|
|
if(!l)
|
|
return NULL;
|
|
if(l->type != Z_SYNC || l->right)
|
|
return l;
|
|
s = l->left;
|
|
if(s->flags || s->right)
|
|
return l;
|
|
p = s->left;
|
|
if(p->right)
|
|
return l;
|
|
c = p->left;
|
|
if(c->type != FUNCDEF || c->flags ||
|
|
nonempty(c->redir) || nonempty(c->vars) ||
|
|
empty(c->args) || lastnode(c->args) != firstnode(c->args) ||
|
|
strcmp(name, peekfirst(c->args)))
|
|
return l;
|
|
return c->u.list;
|
|
}
|
|
|
|
/* check to see if AUTOCD applies here */
|
|
|
|
/**/
|
|
static char *
|
|
cancd(char *s)
|
|
{
|
|
int nocdpath = s[0] == '.' &&
|
|
(s[1] == '/' || !s[1] || (s[1] == '.' && (s[2] == '/' || !s[1])));
|
|
char *t;
|
|
|
|
if (*s != '/') {
|
|
char sbuf[PATH_MAX], **cp;
|
|
|
|
if (cancd2(s))
|
|
return s;
|
|
if (access(unmeta(s), X_OK) == 0)
|
|
return NULL;
|
|
if (!nocdpath)
|
|
for (cp = cdpath; *cp; cp++) {
|
|
if (strlen(*cp) + strlen(s) + 1 >= PATH_MAX)
|
|
continue;
|
|
if (**cp)
|
|
sprintf(sbuf, "%s/%s", *cp, s);
|
|
else
|
|
strcpy(sbuf, s);
|
|
if (cancd2(sbuf)) {
|
|
doprintdir = -1;
|
|
return dupstring(sbuf);
|
|
}
|
|
}
|
|
if ((t = cd_able_vars(s))) {
|
|
if (cancd2(t)) {
|
|
doprintdir = -1;
|
|
return t;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
return cancd2(s) ? s : NULL;
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
cancd2(char *s)
|
|
{
|
|
struct stat buf;
|
|
char *us = unmeta(s);
|
|
|
|
return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
|
|
}
|
|
|
|
/**/
|
|
void
|
|
execsave(void)
|
|
{
|
|
struct execstack *es;
|
|
|
|
es = (struct execstack *) malloc(sizeof(struct execstack));
|
|
es->args = args;
|
|
es->list_pipe_pid = list_pipe_pid;
|
|
es->nowait = nowait;
|
|
es->pline_level = pline_level;
|
|
es->list_pipe_child = list_pipe_child;
|
|
es->list_pipe_job = list_pipe_job;
|
|
strcpy(es->list_pipe_text, list_pipe_text);
|
|
es->lastval = lastval;
|
|
es->noeval = noeval;
|
|
es->badcshglob = badcshglob;
|
|
es->cmdoutpid = cmdoutpid;
|
|
es->cmdoutval = cmdoutval;
|
|
es->trapreturn = trapreturn;
|
|
es->noerrs = noerrs;
|
|
es->subsh_close = subsh_close;
|
|
es->underscore = underscore;
|
|
underscore = ztrdup(underscore);
|
|
es->next = exstack;
|
|
exstack = es;
|
|
noerrs = cmdoutpid = 0;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
execrestore(void)
|
|
{
|
|
struct execstack *en;
|
|
|
|
DPUTS(!exstack, "BUG: execrestore() without execsave()");
|
|
args = exstack->args;
|
|
list_pipe_pid = exstack->list_pipe_pid;
|
|
nowait = exstack->nowait;
|
|
pline_level = exstack->pline_level;
|
|
list_pipe_child = exstack->list_pipe_child;
|
|
list_pipe_job = exstack->list_pipe_job;
|
|
strcpy(list_pipe_text, exstack->list_pipe_text);
|
|
lastval = exstack->lastval;
|
|
noeval = exstack->noeval;
|
|
badcshglob = exstack->badcshglob;
|
|
cmdoutpid = exstack->cmdoutpid;
|
|
cmdoutval = exstack->cmdoutval;
|
|
trapreturn = exstack->trapreturn;
|
|
noerrs = exstack->noerrs;
|
|
subsh_close = exstack->subsh_close;
|
|
zsfree(underscore);
|
|
underscore = exstack->underscore;
|
|
en = exstack->next;
|
|
free(exstack);
|
|
exstack = en;
|
|
}
|