about summary refs log tree commit diff
path: root/Src/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/exec.c')
-rw-r--r--Src/exec.c98
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 : "");