560f4e
From 223ac53797d33b0473323efc0d5a44d1dceaf746 Mon Sep 17 00:00:00 2001
560f4e
From: Peter Stephenson <p.w.stephenson@ntlworld.com>
560f4e
Date: Sun, 26 Oct 2014 17:47:42 +0000
560f4e
Subject: [PATCH 1/2] 33531 with additions: retain status of exited background
560f4e
 jobs.
560f4e
560f4e
Add linked list of unwaited-for background jobs.
560f4e
Truncate at value of _SC_CHILD_MAX discarding oldest.
560f4e
Remove old lastpid_status mechanism for latest exited process only.
560f4e
Slightly tighten safety of permanently allocated linked lists so
560f4e
that this doesn't compromise signal handling.
560f4e
560f4e
Upstream-commit: b4f7ccecd93ca9e64c3c3c774fdaefae83d7204a
560f4e
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
560f4e
---
560f4e
 Doc/Zsh/builtins.yo |  16 ++++++
560f4e
 Doc/Zsh/options.yo  |   8 +--
560f4e
 Doc/zshoptions.1    |   8 +--
560f4e
 Src/exec.c          |   2 -
560f4e
 Src/init.c          |   1 -
560f4e
 Src/jobs.c          | 138 ++++++++++++++++++++++++++++++++++++++++++++--------
560f4e
 Src/linklist.c      |   4 ++
560f4e
 Src/signals.c       |  14 +++---
560f4e
 8 files changed, 152 insertions(+), 39 deletions(-)
560f4e
560f4e
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
560f4e
index 46f40cc..edc335e 100644
560f4e
--- a/Doc/Zsh/builtins.yo
560f4e
+++ b/Doc/Zsh/builtins.yo
560f4e
@@ -1905,6 +1905,22 @@ then all currently active child processes are waited for.
560f4e
 Each var(job) can be either a job specification or the process ID
560f4e
 of a job in the job table.
560f4e
 The exit status from this command is that of the job waited for.
560f4e
+
560f4e
+It is possible to wait for recent processes (specified by process ID,
560f4e
+not by job) that were running in the background even if the process has
560f4e
+exited.  Typically the process ID will be recorded by capturing the
560f4e
+value of the variable tt($!) immediately after the process has been
560f4e
+started.  There is a limit on the number of process IDs remembered by
560f4e
+the shell; this is given by the value of the system configuration
560f4e
+parameter tt(CHILD_MAX).  When this limit is reached, older process IDs
560f4e
+are discarded, least recently started processes first.
560f4e
+
560f4e
+Note there is no protection against the process ID wrapping, i.e. if the
560f4e
+wait is not executed soon enough there is a chance the process waited
560f4e
+for is the wrong one.  A conflict implies both process IDs have been
560f4e
+generated by the shell, as other processes are not recorded, and that
560f4e
+the user is potentially interested in both, so this problem is intrinsic
560f4e
+to process IDs.
560f4e
 )
560f4e
 findex(whence)
560f4e
 item(tt(whence) [ tt(-vcwfpams) ] var(name) ...)(
560f4e
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
560f4e
index 068a253..452b258 100644
560f4e
--- a/Doc/Zsh/options.yo
560f4e
+++ b/Doc/Zsh/options.yo
560f4e
@@ -1401,10 +1401,10 @@ shell is saved for output within a subshell (for example, within a
560f4e
 pipeline).  When the option is set, the output of tt(jobs) is empty
560f4e
 until a job is started within the subshell.
560f4e
 
560f4e
-When the option is set, it becomes possible to use the tt(wait) builtin to
560f4e
-wait for the last job started in the background (as given by tt($!)) even
560f4e
-if that job has already exited.  This works even if the option is turned
560f4e
-on temporarily around the use of the tt(wait) builtin.
560f4e
+In previous versions of the shell, it was necessary to enable
560f4e
+tt(POSIX_JOBS) in order for the builtin command tt(wait) to return the
560f4e
+status of background jobs that had already exited.  This is no longer
560f4e
+the case.
560f4e
 )
560f4e
 enditem()
560f4e
 
560f4e
diff --git a/Doc/zshoptions.1 b/Doc/zshoptions.1
560f4e
index dc8f2f8..17f9e97 100644
560f4e
--- a/Doc/zshoptions.1
560f4e
+++ b/Doc/zshoptions.1
560f4e
@@ -867,10 +867,10 @@ shell is saved for output within a subshell (for example, within a
560f4e
 pipeline)\&.  When the option is set, the output of \fBjobs\fP is empty
560f4e
 until a job is started within the subshell\&.
560f4e
 .PP
560f4e
-When the option is set, it becomes possible to use the \fBwait\fP builtin to
560f4e
-wait for the last job started in the background (as given by \fB$!\fP) even
560f4e
-if that job has already exited\&.  This works even if the option is turned
560f4e
-on temporarily around the use of the \fBwait\fP builtin\&.
560f4e
+In previous versions of the shell, it was necessary to enable
560f4e
+\fBPOSIX_JOBS\fP in order for the builtin command \fBwait\fP to return the
560f4e
+status of background jobs that had already exited\&.  This is no longer
560f4e
+the case\&.
560f4e
 .RE
560f4e
 .RE
560f4e
 .PP
560f4e
diff --git a/Src/exec.c b/Src/exec.c
560f4e
index d0fadd6..a9c4688 100644
560f4e
--- a/Src/exec.c
560f4e
+++ b/Src/exec.c
560f4e
@@ -2852,8 +2852,6 @@ execcmd(Estate state, int input, int output, int how, int last1)
560f4e
 #endif
560f4e
 	    if (how & Z_ASYNC) {
560f4e
 		lastpid = (zlong) pid;
560f4e
-		/* indicate it's possible to set status for lastpid */
560f4e
-		lastpid_status = -2L;
560f4e
 	    } else if (!jobtab[thisjob].stty_in_env && varspc) {
560f4e
 		/* search for STTY=... */
560f4e
 		Wordcode p = varspc;
560f4e
diff --git a/Src/init.c b/Src/init.c
560f4e
index c26d887..6666f98 100644
560f4e
--- a/Src/init.c
560f4e
+++ b/Src/init.c
560f4e
@@ -1018,7 +1018,6 @@ setupvals(void)
560f4e
     bufstack = znewlinklist();
560f4e
     hsubl = hsubr = NULL;
560f4e
     lastpid = 0;
560f4e
-    lastpid_status = -1L;
560f4e
 
560f4e
     get_usage();
560f4e
 
560f4e
diff --git a/Src/jobs.c b/Src/jobs.c
560f4e
index bd95afb..18bb648 100644
560f4e
--- a/Src/jobs.c
560f4e
+++ b/Src/jobs.c
560f4e
@@ -104,15 +104,6 @@ int prev_errflag, prev_breaks, errbrk_saved;
560f4e
 /**/
560f4e
 int numpipestats, pipestats[MAX_PIPESTATS];
560f4e
 
560f4e
-/*
560f4e
- * The status associated with the process lastpid.
560f4e
- * -1 if not set and no associated lastpid
560f4e
- * -2 if lastpid is set and status isn't yet
560f4e
- * else the value returned by wait().
560f4e
- */
560f4e
-/**/
560f4e
-long lastpid_status;
560f4e
-
560f4e
 /* Diff two timevals for elapsed-time computations */
560f4e
 
560f4e
 /**/
560f4e
@@ -1210,14 +1201,6 @@ addproc(pid_t pid, char *text, int aux, struct timeval *bgtime)
560f4e
 {
560f4e
     Process pn, *pnlist;
560f4e
 
560f4e
-    if (pid == lastpid && lastpid_status != -2L) {
560f4e
-	/*
560f4e
-	 * The status for the previous lastpid is invalid.
560f4e
-	 * Presumably process numbers have wrapped.
560f4e
-	 */
560f4e
-	lastpid_status = -1L;
560f4e
-    }
560f4e
-
560f4e
     DPUTS(thisjob == -1, "No valid job in addproc.");
560f4e
     pn = (Process) zshcalloc(sizeof *pn);
560f4e
     pn->pid = pid;
560f4e
@@ -1826,6 +1809,122 @@ maybeshrinkjobtab(void)
560f4e
     unqueue_signals();
560f4e
 }
560f4e
 
560f4e
+/*
560f4e
+ * Definitions for the background process stuff recorded below.
560f4e
+ * This would be more efficient as a hash, but
560f4e
+ * - that's quite heavyweight for something not needed very often
560f4e
+ * - we need some kind of ordering as POSIX allows us to limit
560f4e
+ *   the size of the list to the value of _SC_CHILD_MAX and clearly
560f4e
+ *   we want to clear the oldest first
560f4e
+ * - cases with a long list of background jobs where the user doesn't
560f4e
+ *   wait for a large number, and then does wait for one (the only
560f4e
+ *   inefficient case) are rare
560f4e
+ * - in the context of waiting for an external process, looping
560f4e
+ *   over a list isn't so very inefficient.
560f4e
+ * Enough excuses already.
560f4e
+ */
560f4e
+
560f4e
+/* Data in the link list, a key (process ID) / value (exit status) pair. */
560f4e
+struct bgstatus {
560f4e
+    pid_t pid;
560f4e
+    int status;
560f4e
+};
560f4e
+typedef struct bgstatus *Bgstatus;
560f4e
+/* The list of those entries */
560f4e
+LinkList bgstatus_list;
560f4e
+/* Count of entries.  Reaches value of _SC_CHILD_MAX and stops. */
560f4e
+long bgstatus_count;
560f4e
+
560f4e
+/*
560f4e
+ * Remove and free a bgstatus entry.
560f4e
+ */
560f4e
+static void rembgstatus(LinkNode node)
560f4e
+{
560f4e
+    zfree(remnode(bgstatus_list, node), sizeof(struct bgstatus));
560f4e
+    bgstatus_count--;
560f4e
+}
560f4e
+
560f4e
+/*
560f4e
+ * Record the status of a background process that exited so we
560f4e
+ * can execute the builtin wait for it.
560f4e
+ *
560f4e
+ * We can't execute the wait builtin for something that exited in the
560f4e
+ * foreground as it's not visible to the user, so don't bother recording.
560f4e
+ */
560f4e
+
560f4e
+/**/
560f4e
+void
560f4e
+addbgstatus(pid_t pid, int status)
560f4e
+{
560f4e
+    static long child_max;
560f4e
+    Bgstatus bgstatus_entry;
560f4e
+
560f4e
+    if (!child_max) {
560f4e
+#ifdef _SC_CHILD_MAX
560f4e
+	child_max = sysconf(_SC_CHILD_MAX);
560f4e
+	if (!child_max) /* paranoia */
560f4e
+#endif
560f4e
+	{
560f4e
+	    /* Be inventive */
560f4e
+	    child_max = 1024L;
560f4e
+	}
560f4e
+    }
560f4e
+
560f4e
+    if (!bgstatus_list) {
560f4e
+	bgstatus_list = znewlinklist();
560f4e
+	/*
560f4e
+	 * We're not always robust about memory failures, but
560f4e
+	 * this is pretty deep in the shell basics to be failing owing
560f4e
+	 * to memory, and a failure to wait is reported loudly, so test
560f4e
+	 * and fail silently here.
560f4e
+	 */
560f4e
+	if (!bgstatus_list)
560f4e
+	    return;
560f4e
+    }
560f4e
+    if (bgstatus_count == child_max) {
560f4e
+	/* Overflow.  List is in order, remove first */
560f4e
+	rembgstatus(firstnode(bgstatus_list));
560f4e
+    }
560f4e
+    bgstatus_entry = (Bgstatus)zalloc(sizeof(*bgstatus_entry));
560f4e
+    if (!bgstatus_entry) {
560f4e
+	/* See note above */
560f4e
+	return;
560f4e
+    }
560f4e
+    bgstatus_entry->pid = pid;
560f4e
+    bgstatus_entry->status = status;
560f4e
+    if (!zaddlinknode(bgstatus_list, bgstatus_entry)) {
560f4e
+	zfree(bgstatus_entry, sizeof(*bgstatus_entry));
560f4e
+	return;
560f4e
+    }
560f4e
+    bgstatus_count++;
560f4e
+}
560f4e
+
560f4e
+/*
560f4e
+ * See if pid has a recorded exit status.
560f4e
+ * Note we make no guarantee that the PIDs haven't wrapped, so this
560f4e
+ * may not be the right process.
560f4e
+ *
560f4e
+ * This is only used by wait, which must only work on each
560f4e
+ * pid once, so we need to remove the entry if we find it.
560f4e
+ */
560f4e
+
560f4e
+static int getbgstatus(pid_t pid)
560f4e
+{
560f4e
+    LinkNode node;
560f4e
+    Bgstatus bgstatus_entry;
560f4e
+
560f4e
+    if (!bgstatus_list)
560f4e
+	return -1;
560f4e
+    for (node = firstnode(bgstatus_list); node; incnode(node)) {
560f4e
+	bgstatus_entry = (Bgstatus)getdata(node);
560f4e
+	if (bgstatus_entry->pid == pid) {
560f4e
+	    int status = bgstatus_entry->status;
560f4e
+	    rembgstatus(node);
560f4e
+	    return status;
560f4e
+	}
560f4e
+    }
560f4e
+    return -1;
560f4e
+}
560f4e
 
560f4e
 /* bg, disown, fg, jobs, wait: most of the job control commands are     *
560f4e
  * here.  They all take the same type of argument.  Exception: wait can *
560f4e
@@ -1971,10 +2070,7 @@ bin_fg(char *name, char **argv, Options ops, int func)
560f4e
 		}
560f4e
 		if (retval == 0)
560f4e
 		    retval = lastval2;
560f4e
-	    } else if (isset(POSIXJOBS) &&
560f4e
-		       pid == lastpid && lastpid_status >= 0L) {
560f4e
-		retval = (int)lastpid_status;
560f4e
-	    } else {
560f4e
+	    } else if ((retval = getbgstatus(pid)) < 0) {
560f4e
 		zwarnnam(name, "pid %d is not a child of this shell", pid);
560f4e
 		/* presumably lastval2 doesn't tell us a heck of a lot? */
560f4e
 		retval = 1;
560f4e
diff --git a/Src/linklist.c b/Src/linklist.c
560f4e
index 1e364fb..3aa8125 100644
560f4e
--- a/Src/linklist.c
560f4e
+++ b/Src/linklist.c
560f4e
@@ -118,6 +118,8 @@ znewlinklist(void)
560f4e
     LinkList list;
560f4e
 
560f4e
     list = (LinkList) zalloc(sizeof *list);
560f4e
+    if (!list)
560f4e
+	return NULL;
560f4e
     list->list.first = NULL;
560f4e
     list->list.last = &list->node;
560f4e
     list->list.flags = 0;
560f4e
@@ -152,6 +154,8 @@ zinsertlinknode(LinkList list, LinkNode node, void *dat)
560f4e
 
560f4e
     tmp = node->next;
560f4e
     node->next = new = (LinkNode) zalloc(sizeof *tmp);
560f4e
+    if (!new)
560f4e
+	return NULL;
560f4e
     new->prev = node;
560f4e
     new->dat = dat;
560f4e
     new->next = tmp;
560f4e
diff --git a/Src/signals.c b/Src/signals.c
560f4e
index 2df69f9..e728505 100644
560f4e
--- a/Src/signals.c
560f4e
+++ b/Src/signals.c
560f4e
@@ -520,14 +520,14 @@ wait_for_processes(void)
560f4e
 	    get_usage();
560f4e
 	}
560f4e
 	/*
560f4e
-	 * Remember the status associated with $!, so we can
560f4e
-	 * wait for it even if it's exited.  This value is
560f4e
-	 * only used if we can't find the PID in the job table,
560f4e
-	 * so it doesn't matter that the value we save here isn't
560f4e
-	 * useful until the process has exited.
560f4e
+	 * Accumulate a list of older jobs.  We only do this for
560f4e
+	 * background jobs, which is something in the job table
560f4e
+	 * that's not marked as in the current shell or as shell builtin
560f4e
+	 * and is not equal to the current foreground job.
560f4e
 	 */
560f4e
-	if (pn != NULL && pid == lastpid && lastpid_status != -1L)
560f4e
-	    lastpid_status = lastval2;
560f4e
+	if (jn && !(jn->stat & (STAT_CURSH|STAT_BUILTIN)) &&
560f4e
+	    jn - jobtab != thisjob)
560f4e
+	    addbgstatus(pid, (int)lastval2);
560f4e
     }
560f4e
 }
560f4e
 
560f4e
-- 
560f4e
2.1.0
560f4e
560f4e
560f4e
From 2d59469450ba80b69449dc2777f0fc0673e0fbd6 Mon Sep 17 00:00:00 2001
560f4e
From: Peter Stephenson <p.w.stephenson@ntlworld.com>
560f4e
Date: Sun, 26 Oct 2014 19:04:47 +0000
560f4e
Subject: [PATCH 2/2] 33542: test logic for waiting for already exited
560f4e
 processes
560f4e
560f4e
Upstream-commit: 9a551ca85999ff329714fd2cca138ce2f7d3c3d9
560f4e
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
560f4e
---
560f4e
 Test/A05execution.ztst | 29 +++++++++++++++++++++++++++--
560f4e
 1 file changed, 27 insertions(+), 2 deletions(-)
560f4e
560f4e
diff --git a/Test/A05execution.ztst b/Test/A05execution.ztst
560f4e
index ca97f4f..589815f 100644
560f4e
--- a/Test/A05execution.ztst
560f4e
+++ b/Test/A05execution.ztst
560f4e
@@ -178,3 +178,28 @@
560f4e
   kill $!
560f4e
 0:Status reset by starting a backgrounded command
560f4e
 >0
560f4e
+
560f4e
+# This tests that we record the status of processes that have already exited
560f4e
+# for when we wait for them.
560f4e
+#
560f4e
+# Actually, we don't guarantee here that the jobs have already exited, but
560f4e
+# the order of the waits means it's highly likely we do need to recall a
560f4e
+# previous status, barring accidents which shouldn't happen very often.  In
560f4e
+# other words, we rely on the test working repeatedly rather than just
560f4e
+# once.  The monitor option is irrelevant to the logic, so we'll make
560f4e
+# our job easier by turning it off.
560f4e
+  unsetopt monitor
560f4e
+  (exit 1) &
560f4e
+  one=$!
560f4e
+  (exit 2) &
560f4e
+  two=$!
560f4e
+  (exit 3) &
560f4e
+  three=$!
560f4e
+  wait $three
560f4e
+  print $?
560f4e
+  wait $two
560f4e
+  print $?
560f4e
+  wait $one
560f4e
+1:The status of recently exited background jobs is recorded
560f4e
+>3
560f4e
+>2
560f4e
-- 
560f4e
2.1.0
560f4e