[git commit] hush: implement "wait %jobspec"

Denys Vlasenko vda.linux at googlemail.com
Mon Nov 7 21:12:18 UTC 2016


commit: https://git.busybox.net/busybox/commit/?id=62b717b75ebff3ab00aae2fee0087a8106208a39
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

function                                             old     new   delta
parse_jobspec                                          -      83     +83
job_exited_or_stopped                                  -      79     +79
builtin_wait                                         236     302     +66
wait_for_child_or_signal                             199     228     +29
checkjobs                                            142     158     +16
builtin_jobs                                          59      68      +9
process_wait_result                                  453     408     -45
builtin_fg_bg                                        272     203     -69
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 4/2 up/down: 282/-114)          Total: 168 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/hush.c | 117 ++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 88 insertions(+), 29 deletions(-)

diff --git a/shell/hush.c b/shell/hush.c
index 3b87d28..b842d6e 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -45,7 +45,6 @@
  *      tilde expansion
  *      aliases
  *      kill %jobspec
- *      wait %jobspec
  *      follow IFS rules more precisely, including update semantics
  *      builtins mandated by standards we don't support:
  *          [un]alias, command, fc, getopts, newgrp, readonly, times
@@ -7065,6 +7064,27 @@ static void delete_finished_bg_job(struct pipe *pi)
 }
 #endif /* JOB */
 
+static int job_exited_or_stopped(struct pipe *pi)
+{
+	int rcode, i;
+
+	if (pi->alive_cmds != pi->stopped_cmds)
+		return -1;
+
+	/* All processes in fg pipe have exited or stopped */
+	rcode = 0;
+	i = pi->num_cmds;
+	while (--i >= 0) {
+		rcode = pi->cmds[i].cmd_exitcode;
+		/* usually last process gives overall exitstatus,
+		 * but with "set -o pipefail", last *failed* process does */
+		if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
+			break;
+	}
+	IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+	return rcode;
+}
+
 static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 {
 #if ENABLE_HUSH_JOB
@@ -7088,7 +7108,10 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 	/* Were we asked to wait for a fg pipe? */
 	if (fg_pipe) {
 		i = fg_pipe->num_cmds;
+
 		while (--i >= 0) {
+			int rcode;
+
 			debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
 			if (fg_pipe->cmds[i].pid != childpid)
 				continue;
@@ -7117,18 +7140,8 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 			}
 			debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
 					fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
-			if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) {
-				/* All processes in fg pipe have exited or stopped */
-				int rcode = 0;
-				i = fg_pipe->num_cmds;
-				while (--i >= 0) {
-					rcode = fg_pipe->cmds[i].cmd_exitcode;
-					/* usually last process gives overall exitstatus,
-					 * but with "set -o pipefail", last *failed* process does */
-					if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
-						break;
-				}
-				IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+			rcode = job_exited_or_stopped(fg_pipe);
+			if (rcode >= 0) {
 /* Note: *non-interactive* bash does not continue if all processes in fg pipe
  * are stopped. Testcase: "cat | cat" in a script (not on command line!)
  * and "killall -STOP cat" */
@@ -7185,9 +7198,18 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 
 /* Check to see if any processes have exited -- if they have,
  * figure out why and see if a job has completed.
- * Alternatively (fg_pipe == NULL, waitfor_pid != 0),
- * wait for a specific pid to complete, return exitcode+1
- * (this allows to distinguish zero as "no children exited" result).
+ *
+ * If non-NULL fg_pipe: wait for its completion or stop.
+ * Return its exitcode or zero if stopped.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid != 0):
+ * waitpid(WNOHANG), if waitfor_pid exits or stops, return exitcode+1,
+ * else return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid == 0),
+ * return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
  */
 static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
 {
@@ -7256,9 +7278,13 @@ static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
 			break;
 		}
 		if (childpid == waitfor_pid) {
+			debug_printf_exec("childpid==waitfor_pid:%d status:0x%08x\n", childpid, status);
 			rcode = WEXITSTATUS(status);
 			if (WIFSIGNALED(status))
 				rcode = 128 + WTERMSIG(status);
+			if (WIFSTOPPED(status))
+				/* bash: "cmd & wait $!" and cmd stops: $? = 128 + stopsig */
+				rcode = 128 + WSTOPSIG(status);
 			rcode++;
 			break; /* "wait PID" called us, give it exitcode+1 */
 		}
@@ -9329,6 +9355,7 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
 	struct pipe *job;
 	const char *status_string;
 
+	checkjobs(NULL, 0 /*(no pid to wait for)*/);
 	for (job = G.job_list; job; job = job->next) {
 		if (job->alive_cmds == job->stopped_cmds)
 			status_string = "Stopped";
@@ -9481,12 +9508,15 @@ static int FAST_FUNC builtin_umask(char **argv)
 }
 
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
-static int wait_for_child_or_signal(pid_t waitfor_pid)
+#if !ENABLE_HUSH_JOB
+# define wait_for_child_or_signal(pipe,pid) wait_for_child_or_signal(pid)
+#endif
+static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid)
 {
 	int ret = 0;
 	for (;;) {
 		int sig;
-		sigset_t oldset, allsigs;
+		sigset_t oldset;
 
 		/* waitpid is not interruptible by SA_RESTARTed
 		 * signals which we use. Thus, this ugly dance:
@@ -9495,10 +9525,10 @@ static int wait_for_child_or_signal(pid_t waitfor_pid)
 		/* Make sure possible SIGCHLD is stored in kernel's
 		 * pending signal mask before we call waitpid.
 		 * Or else we may race with SIGCHLD, lose it,
-		 * and get stuck in sigwaitinfo...
+		 * and get stuck in sigsuspend...
 		 */
-		sigfillset(&allsigs);
-		sigprocmask(SIG_SETMASK, &allsigs, &oldset);
+		sigfillset(&oldset); /* block all signals, remember old set */
+		sigprocmask(SIG_SETMASK, &oldset, &oldset);
 
 		if (!sigisemptyset(&G.pending_set)) {
 			/* Crap! we raced with some signal! */
@@ -9507,19 +9537,31 @@ static int wait_for_child_or_signal(pid_t waitfor_pid)
 		}
 
 		/*errno = 0; - checkjobs does this */
+/* Can't pass waitfor_pipe into checkjobs(): it won't be interruptible */
 		ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */
+		debug_printf_exec("checkjobs:%d\n", ret);
+#if ENABLE_HUSH_JOB
+		if (waitfor_pipe) {
+			int rcode = job_exited_or_stopped(waitfor_pipe);
+			debug_printf_exec("job_exited_or_stopped:%d\n", rcode);
+			if (rcode >= 0) {
+				ret = rcode;
+				sigprocmask(SIG_SETMASK, &oldset, NULL);
+				break;
+			}
+		}
+#endif
 		/* if ECHILD, there are no children (ret is -1 or 0) */
 		/* if ret == 0, no children changed state */
 		/* if ret != 0, it's exitcode+1 of exited waitfor_pid child */
-		if (errno == ECHILD || ret--) {
-			if (ret < 0) /* if ECHILD, may need to fix */
+		if (errno == ECHILD || ret) {
+			ret--;
+			if (ret < 0) /* if ECHILD, may need to fix "ret" */
 				ret = 0;
 			sigprocmask(SIG_SETMASK, &oldset, NULL);
 			break;
 		}
-
 		/* Wait for SIGCHLD or any other signal */
-		//sig = sigwaitinfo(&allsigs, NULL);
 		/* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
 		/* Note: sigsuspend invokes signal handler */
 		sigsuspend(&oldset);
@@ -9544,6 +9586,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 {
 	int ret;
 	int status;
+	struct pipe *wait_pipe = NULL;
 
 	argv = skip_dash_dash(argv);
 	if (argv[0] == NULL) {
@@ -9563,18 +9606,27 @@ static int FAST_FUNC builtin_wait(char **argv)
 		 * ^C <-- after ~4 sec from keyboard
 		 * $
 		 */
-		return wait_for_child_or_signal(0 /*(no pid to wait for)*/);
+		return wait_for_child_or_signal(NULL, 0 /*(no job and no pid to wait for)*/);
 	}
 
-	/* TODO: support "wait %jobspec" */
 	do {
 		pid_t pid = bb_strtou(*argv, NULL, 10);
 		if (errno || pid <= 0) {
+#if ENABLE_HUSH_JOB
+			if (argv[0][0] == '%') {
+				wait_pipe = parse_jobspec(*argv);
+				if (wait_pipe) {
+					pid = - wait_pipe->pgrp;
+					goto do_wait;
+				}
+			}
+#endif
 			/* mimic bash message */
 			bb_error_msg("wait: '%s': not a pid or valid job spec", *argv);
 			ret = EXIT_FAILURE;
 			continue; /* bash checks all argv[] */
 		}
+ IF_HUSH_JOB(do_wait:)
 		/* Do we have such child? */
 		ret = waitpid(pid, &status, WNOHANG);
 		if (ret < 0) {
@@ -9599,13 +9651,20 @@ static int FAST_FUNC builtin_wait(char **argv)
 		}
 		if (ret == 0) {
 			/* Yes, and it still runs */
-			ret = wait_for_child_or_signal(pid);
+			ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid);
 		} else {
 			/* Yes, and it just exited */
-			process_wait_result(NULL, pid, status);
+			process_wait_result(NULL, ret, status);
 			ret = WEXITSTATUS(status);
 			if (WIFSIGNALED(status))
 				ret = 128 + WTERMSIG(status);
+#if ENABLE_HUSH_JOB
+			if (wait_pipe) {
+				ret = job_exited_or_stopped(wait_pipe);
+				if (ret < 0)
+					goto do_wait;
+			}
+#endif
 		}
 	} while (*++argv);
 


More information about the busybox-cvs mailing list