diff options
Diffstat (limited to 'Src/exec.c')
-rw-r--r-- | Src/exec.c | 98 |
1 files changed, 93 insertions, 5 deletions
diff --git a/Src/exec.c b/Src/exec.c index da2bf9fe2..7d15facfd 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -213,6 +213,78 @@ zfork(void) 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 live 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 build 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; @@ -625,6 +697,13 @@ execlist(List list, int dont_change_job, int exiting) static int donetrap; int ret, cj; int old_pline_level, old_list_pipe; + /* + * 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; cj = thisjob; old_pline_level = pline_level; @@ -644,6 +723,8 @@ execlist(List list, int dont_change_job, int exiting) /* Loop through code followed by &&, ||, or end of sublist. */ while (slist) { + if (!oldnoerrexit) + noerrexit = (slist->type != END); switch (slist->type) { case END: /* End of sublist; just execute, ignoring status. */ @@ -687,6 +768,8 @@ execlist(List list, int dont_change_job, int exiting) } sublist_done: + noerrexit = oldnoerrexit; + if (sigtrapped[SIGDEBUG]) dotrap(SIGDEBUG); @@ -761,13 +844,17 @@ execpline(Sublist l, int how, int last1) coprocout = opipe[1]; fdtable[coprocin] = fdtable[coprocout] = 0; } + /* 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_job = newjob; + list_pipe_pid = 0; nowait = 0; - } - list_pipe_pid = lastwj = 0; - if (pline_level == 1) simple_pline = (l->left->type == END); + } + lastwj = 0; execpline2(l->left, how, opipe[0], ipipe[1], last1); pline_level--; if (how & Z_ASYNC) { @@ -800,6 +887,7 @@ execpline(Sublist l, int how, int last1) struct process *pn, *qn; curjob = newjob; + DPUTS(!list_pipe_pid, "invalid list_pipe_pid"); addproc(list_pipe_pid, list_pipe_text); for (pn = jobtab[jn->other].procs; pn; pn = pn->next) @@ -1588,7 +1676,7 @@ execcmd(Cmd cmd, int input, int output, int how, int last1) closem(2); #endif if (how & Z_ASYNC) { - lastpid = (long) pid; + lastpid = (zlong) pid; } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) { /* search for STTY=... */ while (nonempty(cmd->vars)) @@ -2548,7 +2636,7 @@ static int execarith(Cmd cmd) { char *e; - long val = 0; + zlong val = 0; if (isset(XTRACE)) fprintf(stderr, "%s((", prompt4 ? prompt4 : ""); |