hush: implement "wait %jobspec"

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>
This commit is contained in:
Denys Vlasenko 2016-11-07 22:12:18 +01:00
parent 4e1c8b4f6a
commit 62b717b75e

View File

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