hush: fix "wait PID"
It was not properly interruptible, and did not update job status (the exited processes were still thought of as running). function old new delta process_wait_result - 453 +453 wait_for_child_or_signal - 199 +199 run_list 996 1002 +6 checkjobs_and_fg_shell 41 43 +2 builtin_wait 328 215 -113 checkjobs 516 142 -374 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 2/2 up/down: 660/-487) Total: 173 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
8f7b0248ad
commit
7e6753609d
356
shell/hush.c
356
shell/hush.c
@ -44,6 +44,8 @@
|
||||
* special variables (done: PWD, PPID, RANDOM)
|
||||
* 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
|
||||
@ -7041,16 +7043,134 @@ static void delete_finished_bg_job(struct pipe *pi)
|
||||
}
|
||||
#endif /* JOB */
|
||||
|
||||
/* Check to see if any processes have exited -- if they
|
||||
* have, figure out why and see if a job has completed */
|
||||
static int checkjobs(struct pipe *fg_pipe)
|
||||
static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
|
||||
{
|
||||
int attributes;
|
||||
int status;
|
||||
#if ENABLE_HUSH_JOB
|
||||
struct pipe *pi;
|
||||
#endif
|
||||
pid_t childpid;
|
||||
int i, dead;
|
||||
|
||||
dead = WIFEXITED(status) || WIFSIGNALED(status);
|
||||
|
||||
#if DEBUG_JOBS
|
||||
if (WIFSTOPPED(status))
|
||||
debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
|
||||
childpid, WSTOPSIG(status), WEXITSTATUS(status));
|
||||
if (WIFSIGNALED(status))
|
||||
debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n",
|
||||
childpid, WTERMSIG(status), WEXITSTATUS(status));
|
||||
if (WIFEXITED(status))
|
||||
debug_printf_jobs("pid %d exited, exitcode %d\n",
|
||||
childpid, WEXITSTATUS(status));
|
||||
#endif
|
||||
/* Were we asked to wait for a fg pipe? */
|
||||
if (fg_pipe) {
|
||||
i = fg_pipe->num_cmds;
|
||||
while (--i >= 0) {
|
||||
debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
|
||||
if (fg_pipe->cmds[i].pid != childpid)
|
||||
continue;
|
||||
if (dead) {
|
||||
int ex;
|
||||
fg_pipe->cmds[i].pid = 0;
|
||||
fg_pipe->alive_cmds--;
|
||||
ex = WEXITSTATUS(status);
|
||||
/* bash prints killer signal's name for *last*
|
||||
* process in pipe (prints just newline for SIGINT/SIGPIPE).
|
||||
* Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
|
||||
*/
|
||||
if (WIFSIGNALED(status)) {
|
||||
int sig = WTERMSIG(status);
|
||||
if (i == fg_pipe->num_cmds-1)
|
||||
/* TODO: use strsignal() instead for bash compat? but that's bloat... */
|
||||
puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
|
||||
/* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
|
||||
/* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
|
||||
* Maybe we need to use sig | 128? */
|
||||
ex = sig + 128;
|
||||
}
|
||||
fg_pipe->cmds[i].cmd_exitcode = ex;
|
||||
} else {
|
||||
fg_pipe->stopped_cmds++;
|
||||
}
|
||||
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;)
|
||||
/* 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" */
|
||||
if (G_interactive_fd) {
|
||||
#if ENABLE_HUSH_JOB
|
||||
if (fg_pipe->alive_cmds != 0)
|
||||
insert_bg_job(fg_pipe);
|
||||
#endif
|
||||
return rcode;
|
||||
}
|
||||
if (fg_pipe->alive_cmds == 0)
|
||||
return rcode;
|
||||
}
|
||||
/* There are still running processes in the fg_pipe */
|
||||
return -1;
|
||||
}
|
||||
/* It wasnt in fg_pipe, look for process in bg pipes */
|
||||
}
|
||||
|
||||
#if ENABLE_HUSH_JOB
|
||||
/* We were asked to wait for bg or orphaned children */
|
||||
/* No need to remember exitcode in this case */
|
||||
for (pi = G.job_list; pi; pi = pi->next) {
|
||||
for (i = 0; i < pi->num_cmds; i++) {
|
||||
if (pi->cmds[i].pid == childpid)
|
||||
goto found_pi_and_prognum;
|
||||
}
|
||||
}
|
||||
/* Happens when shell is used as init process (init=/bin/sh) */
|
||||
debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
|
||||
return -1; /* this wasn't a process from fg_pipe */
|
||||
|
||||
found_pi_and_prognum:
|
||||
if (dead) {
|
||||
/* child exited */
|
||||
pi->cmds[i].pid = 0;
|
||||
pi->cmds[i].cmd_exitcode = WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status))
|
||||
pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status);
|
||||
pi->alive_cmds--;
|
||||
if (!pi->alive_cmds) {
|
||||
if (G_interactive_fd)
|
||||
printf(JOB_STATUS_FORMAT, pi->jobid,
|
||||
"Done", pi->cmdtext);
|
||||
delete_finished_bg_job(pi);
|
||||
}
|
||||
} else {
|
||||
/* child stopped */
|
||||
pi->stopped_cmds++;
|
||||
}
|
||||
#endif
|
||||
return -1; /* this wasn't a process from fg_pipe */
|
||||
}
|
||||
|
||||
/* 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).
|
||||
*/
|
||||
static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
|
||||
{
|
||||
int attributes;
|
||||
int status;
|
||||
int rcode = 0;
|
||||
|
||||
debug_printf_jobs("checkjobs %p\n", fg_pipe);
|
||||
@ -7087,12 +7207,10 @@ static int checkjobs(struct pipe *fg_pipe)
|
||||
* 1 <========== bg pipe is not fully done, but exitcode is already known!
|
||||
* [hush 1.14.0: yes we do it right]
|
||||
*/
|
||||
wait_more:
|
||||
while (1) {
|
||||
int i;
|
||||
int dead;
|
||||
|
||||
pid_t childpid;
|
||||
#if ENABLE_HUSH_FAST
|
||||
int i;
|
||||
i = G.count_SIGCHLD;
|
||||
#endif
|
||||
childpid = waitpid(-1, &status, attributes);
|
||||
@ -7106,112 +7224,24 @@ static int checkjobs(struct pipe *fg_pipe)
|
||||
//bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
|
||||
}
|
||||
#endif
|
||||
/* ECHILD (no children), or 0 (no change in children status) */
|
||||
rcode = childpid;
|
||||
break;
|
||||
}
|
||||
dead = WIFEXITED(status) || WIFSIGNALED(status);
|
||||
|
||||
#if DEBUG_JOBS
|
||||
if (WIFSTOPPED(status))
|
||||
debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
|
||||
childpid, WSTOPSIG(status), WEXITSTATUS(status));
|
||||
rcode = process_wait_result(fg_pipe, childpid, status);
|
||||
if (rcode >= 0) {
|
||||
/* fg_pipe exited or stopped */
|
||||
break;
|
||||
}
|
||||
if (childpid == waitfor_pid) {
|
||||
rcode = WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status))
|
||||
debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n",
|
||||
childpid, WTERMSIG(status), WEXITSTATUS(status));
|
||||
if (WIFEXITED(status))
|
||||
debug_printf_jobs("pid %d exited, exitcode %d\n",
|
||||
childpid, WEXITSTATUS(status));
|
||||
#endif
|
||||
/* Were we asked to wait for fg pipe? */
|
||||
if (fg_pipe) {
|
||||
i = fg_pipe->num_cmds;
|
||||
while (--i >= 0) {
|
||||
debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
|
||||
if (fg_pipe->cmds[i].pid != childpid)
|
||||
continue;
|
||||
if (dead) {
|
||||
int ex;
|
||||
fg_pipe->cmds[i].pid = 0;
|
||||
fg_pipe->alive_cmds--;
|
||||
ex = WEXITSTATUS(status);
|
||||
/* bash prints killer signal's name for *last*
|
||||
* process in pipe (prints just newline for SIGINT/SIGPIPE).
|
||||
* Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
|
||||
*/
|
||||
if (WIFSIGNALED(status)) {
|
||||
int sig = WTERMSIG(status);
|
||||
if (i == fg_pipe->num_cmds-1)
|
||||
/* TODO: use strsignal() instead for bash compat? but that's bloat... */
|
||||
puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
|
||||
/* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
|
||||
/* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
|
||||
* Maybe we need to use sig | 128? */
|
||||
ex = sig + 128;
|
||||
rcode = 128 + WTERMSIG(status);
|
||||
rcode++;
|
||||
break; /* "wait PID" called us, give it exitcode+1 */
|
||||
}
|
||||
fg_pipe->cmds[i].cmd_exitcode = ex;
|
||||
} else {
|
||||
fg_pipe->stopped_cmds++;
|
||||
}
|
||||
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 */
|
||||
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
|
||||
* are stopped. Testcase: "cat | cat" in a script (not on command line!)
|
||||
* and "killall -STOP cat" */
|
||||
if (G_interactive_fd) {
|
||||
#if ENABLE_HUSH_JOB
|
||||
if (fg_pipe->alive_cmds != 0)
|
||||
insert_bg_job(fg_pipe);
|
||||
#endif
|
||||
return rcode;
|
||||
}
|
||||
if (fg_pipe->alive_cmds == 0)
|
||||
return rcode;
|
||||
}
|
||||
/* There are still running processes in the fg pipe */
|
||||
goto wait_more; /* do waitpid again */
|
||||
}
|
||||
/* it wasnt fg_pipe, look for process in bg pipes */
|
||||
}
|
||||
|
||||
#if ENABLE_HUSH_JOB
|
||||
/* We asked to wait for bg or orphaned children */
|
||||
/* No need to remember exitcode in this case */
|
||||
for (pi = G.job_list; pi; pi = pi->next) {
|
||||
for (i = 0; i < pi->num_cmds; i++) {
|
||||
if (pi->cmds[i].pid == childpid)
|
||||
goto found_pi_and_prognum;
|
||||
}
|
||||
}
|
||||
/* Happens when shell is used as init process (init=/bin/sh) */
|
||||
debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
|
||||
continue; /* do waitpid again */
|
||||
|
||||
found_pi_and_prognum:
|
||||
if (dead) {
|
||||
/* child exited */
|
||||
pi->cmds[i].pid = 0;
|
||||
pi->alive_cmds--;
|
||||
if (!pi->alive_cmds) {
|
||||
if (G_interactive_fd)
|
||||
printf(JOB_STATUS_FORMAT, pi->jobid,
|
||||
"Done", pi->cmdtext);
|
||||
delete_finished_bg_job(pi);
|
||||
}
|
||||
} else {
|
||||
/* child stopped */
|
||||
pi->stopped_cmds++;
|
||||
}
|
||||
#endif
|
||||
/* This wasn't one of our processes, or */
|
||||
/* fg_pipe still has running processes, do waitpid again */
|
||||
} /* while (waitpid succeeds)... */
|
||||
|
||||
return rcode;
|
||||
@ -7221,7 +7251,7 @@ static int checkjobs(struct pipe *fg_pipe)
|
||||
static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
|
||||
{
|
||||
pid_t p;
|
||||
int rcode = checkjobs(fg_pipe);
|
||||
int rcode = checkjobs(fg_pipe, 0 /*(no pid to wait for)*/);
|
||||
if (G_saved_tty_pgrp) {
|
||||
/* Job finished, move the shell to the foreground */
|
||||
p = getpgrp(); /* our process group id */
|
||||
@ -7893,7 +7923,7 @@ static int run_list(struct pipe *pi)
|
||||
/* else: e.g. "continue 2" should *break* once, *then* continue */
|
||||
} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
|
||||
if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
|
||||
checkjobs(NULL);
|
||||
checkjobs(NULL, 0 /*(no pid to wait for)*/);
|
||||
break;
|
||||
}
|
||||
/* "continue": simulate end of loop */
|
||||
@ -7902,7 +7932,7 @@ static int run_list(struct pipe *pi)
|
||||
}
|
||||
#endif
|
||||
if (G_flag_return_in_progress == 1) {
|
||||
checkjobs(NULL);
|
||||
checkjobs(NULL, 0 /*(no pid to wait for)*/);
|
||||
break;
|
||||
}
|
||||
} else if (pi->followup == PIPE_BG) {
|
||||
@ -7929,7 +7959,7 @@ static int run_list(struct pipe *pi)
|
||||
} else
|
||||
#endif
|
||||
{ /* This one just waits for completion */
|
||||
rcode = checkjobs(pi);
|
||||
rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
|
||||
debug_printf_exec(": checkjobs exitcode %d\n", rcode);
|
||||
check_and_run_traps();
|
||||
}
|
||||
@ -7943,7 +7973,7 @@ static int run_list(struct pipe *pi)
|
||||
cond_code = rcode;
|
||||
#endif
|
||||
check_jobs_and_continue:
|
||||
checkjobs(NULL);
|
||||
checkjobs(NULL, 0 /*(no pid to wait for)*/);
|
||||
dont_check_jobs_but_continue: ;
|
||||
#if ENABLE_HUSH_LOOPS
|
||||
/* Beware of "while false; true; do ..."! */
|
||||
@ -9408,30 +9438,10 @@ static int FAST_FUNC builtin_umask(char **argv)
|
||||
}
|
||||
|
||||
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
|
||||
static int FAST_FUNC builtin_wait(char **argv)
|
||||
static int wait_for_child_or_signal(pid_t waitfor_pid)
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
int status;
|
||||
|
||||
argv = skip_dash_dash(argv);
|
||||
if (argv[0] == NULL) {
|
||||
/* Don't care about wait results */
|
||||
/* Note 1: must wait until there are no more children */
|
||||
/* Note 2: must be interruptible */
|
||||
/* Examples:
|
||||
* $ sleep 3 & sleep 6 & wait
|
||||
* [1] 30934 sleep 3
|
||||
* [2] 30935 sleep 6
|
||||
* [1] Done sleep 3
|
||||
* [2] Done sleep 6
|
||||
* $ sleep 3 & sleep 6 & wait
|
||||
* [1] 30936 sleep 3
|
||||
* [2] 30937 sleep 6
|
||||
* [1] Done sleep 3
|
||||
* ^C <-- after ~4 sec from keyboard
|
||||
* $
|
||||
*/
|
||||
while (1) {
|
||||
int ret = 0;
|
||||
for (;;) {
|
||||
int sig;
|
||||
sigset_t oldset, allsigs;
|
||||
|
||||
@ -9453,8 +9463,14 @@ static int FAST_FUNC builtin_wait(char **argv)
|
||||
goto restore;
|
||||
}
|
||||
|
||||
checkjobs(NULL); /* waitpid(WNOHANG) inside */
|
||||
if (errno == ECHILD) {
|
||||
/*errno = 0; - checkjobs does this */
|
||||
ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */
|
||||
/* 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 */
|
||||
ret = 0;
|
||||
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
||||
break;
|
||||
}
|
||||
@ -9479,26 +9495,66 @@ static int FAST_FUNC builtin_wait(char **argv)
|
||||
/* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int FAST_FUNC builtin_wait(char **argv)
|
||||
{
|
||||
int ret;
|
||||
int status;
|
||||
|
||||
argv = skip_dash_dash(argv);
|
||||
if (argv[0] == NULL) {
|
||||
/* Don't care about wait results */
|
||||
/* Note 1: must wait until there are no more children */
|
||||
/* Note 2: must be interruptible */
|
||||
/* Examples:
|
||||
* $ sleep 3 & sleep 6 & wait
|
||||
* [1] 30934 sleep 3
|
||||
* [2] 30935 sleep 6
|
||||
* [1] Done sleep 3
|
||||
* [2] Done sleep 6
|
||||
* $ sleep 3 & sleep 6 & wait
|
||||
* [1] 30936 sleep 3
|
||||
* [2] 30937 sleep 6
|
||||
* [1] Done sleep 3
|
||||
* ^C <-- after ~4 sec from keyboard
|
||||
* $
|
||||
*/
|
||||
return wait_for_child_or_signal(0 /*(no pid to wait for)*/);
|
||||
}
|
||||
|
||||
/* This is probably buggy wrt interruptible-ness */
|
||||
while (*argv) {
|
||||
/* TODO: support "wait %jobspec" */
|
||||
do {
|
||||
pid_t pid = bb_strtou(*argv, NULL, 10);
|
||||
if (errno) {
|
||||
if (errno || pid <= 0) {
|
||||
/* mimic bash message */
|
||||
bb_error_msg("wait: '%s': not a pid or valid job spec", *argv);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (waitpid(pid, &status, 0) == pid) {
|
||||
/* Do we have such child? */
|
||||
ret = waitpid(pid, &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
/* No */
|
||||
if (errno == ECHILD) {
|
||||
/* Example: "wait 1". mimic bash message */
|
||||
bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
|
||||
} else {
|
||||
/* ??? */
|
||||
bb_perror_msg("wait %s", *argv);
|
||||
}
|
||||
ret = 127;
|
||||
} else if (ret == 0) {
|
||||
/* Yes, and it still runs */
|
||||
ret = wait_for_child_or_signal(pid);
|
||||
} else {
|
||||
/* Yes, and it just exited */
|
||||
process_wait_result(NULL, pid, status);
|
||||
ret = WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status))
|
||||
ret = 128 + WTERMSIG(status);
|
||||
} else {
|
||||
bb_perror_msg("wait %s", *argv);
|
||||
ret = 127;
|
||||
}
|
||||
argv++;
|
||||
}
|
||||
} while (*argv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
2
shell/hush_test/hush-misc/wait1.right
Normal file
2
shell/hush_test/hush-misc/wait1.right
Normal file
@ -0,0 +1,2 @@
|
||||
0
|
||||
[1] Running sleep 2
|
3
shell/hush_test/hush-misc/wait1.tests
Executable file
3
shell/hush_test/hush-misc/wait1.tests
Executable file
@ -0,0 +1,3 @@
|
||||
sleep 2 & sleep 1 & wait $!
|
||||
echo $?
|
||||
jobs
|
2
shell/hush_test/hush-misc/wait2.right
Normal file
2
shell/hush_test/hush-misc/wait2.right
Normal file
@ -0,0 +1,2 @@
|
||||
0
|
||||
[1] Running sleep 3
|
4
shell/hush_test/hush-misc/wait2.tests
Executable file
4
shell/hush_test/hush-misc/wait2.tests
Executable file
@ -0,0 +1,4 @@
|
||||
sleep 3 & sleep 2 & sleep 1
|
||||
wait $!
|
||||
echo $?
|
||||
jobs
|
2
shell/hush_test/hush-misc/wait3.right
Normal file
2
shell/hush_test/hush-misc/wait3.right
Normal file
@ -0,0 +1,2 @@
|
||||
3
|
||||
[1] Running sleep 2
|
3
shell/hush_test/hush-misc/wait3.tests
Executable file
3
shell/hush_test/hush-misc/wait3.tests
Executable file
@ -0,0 +1,3 @@
|
||||
sleep 2 & (sleep 1;exit 3) & wait $!
|
||||
echo $?
|
||||
jobs
|
Loading…
Reference in New Issue
Block a user