hush: implement "wait %jobspec"
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 7 Nov 2016 21:12:18 +0000 (22:12 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 7 Nov 2016 21:12:18 +0000 (22:12 +0100)
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@googlemail.com>
shell/hush.c

index 3b87d283c665d0a591515eb2e55bc32653176315..b842d6eec6d0fc35a04e5d74353a406e51dfbd8f 100644 (file)
@@ -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);