diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2016-10-28 21:57:31 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2016-10-28 21:59:09 +0200 |
commit | 7e6753609d102b68a625072fb1660065246a54e2 (patch) | |
tree | cd18eb18be9402da2bc1daf6ac9b53acba94dcbf | |
parent | 8f7b0248adca9a88351fd7f3dd208775242f3fe6 (diff) | |
download | busybox-w32-7e6753609d102b68a625072fb1660065246a54e2.tar.gz busybox-w32-7e6753609d102b68a625072fb1660065246a54e2.tar.bz2 busybox-w32-7e6753609d102b68a625072fb1660065246a54e2.zip |
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>
-rw-r--r-- | shell/hush.c | 404 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait1.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait1.tests | 3 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait2.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait2.tests | 4 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/wait3.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/wait3.tests | 3 |
7 files changed, 246 insertions, 174 deletions
diff --git a/shell/hush.c b/shell/hush.c index c80429d5c..1f8a3e607 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -44,6 +44,8 @@ | |||
44 | * special variables (done: PWD, PPID, RANDOM) | 44 | * special variables (done: PWD, PPID, RANDOM) |
45 | * tilde expansion | 45 | * tilde expansion |
46 | * aliases | 46 | * aliases |
47 | * kill %jobspec | ||
48 | * wait %jobspec | ||
47 | * follow IFS rules more precisely, including update semantics | 49 | * follow IFS rules more precisely, including update semantics |
48 | * builtins mandated by standards we don't support: | 50 | * builtins mandated by standards we don't support: |
49 | * [un]alias, command, fc, getopts, newgrp, readonly, times | 51 | * [un]alias, command, fc, getopts, newgrp, readonly, times |
@@ -7041,16 +7043,134 @@ static void delete_finished_bg_job(struct pipe *pi) | |||
7041 | } | 7043 | } |
7042 | #endif /* JOB */ | 7044 | #endif /* JOB */ |
7043 | 7045 | ||
7044 | /* Check to see if any processes have exited -- if they | 7046 | static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) |
7045 | * have, figure out why and see if a job has completed */ | ||
7046 | static int checkjobs(struct pipe *fg_pipe) | ||
7047 | { | 7047 | { |
7048 | int attributes; | ||
7049 | int status; | ||
7050 | #if ENABLE_HUSH_JOB | 7048 | #if ENABLE_HUSH_JOB |
7051 | struct pipe *pi; | 7049 | struct pipe *pi; |
7052 | #endif | 7050 | #endif |
7053 | pid_t childpid; | 7051 | int i, dead; |
7052 | |||
7053 | dead = WIFEXITED(status) || WIFSIGNALED(status); | ||
7054 | |||
7055 | #if DEBUG_JOBS | ||
7056 | if (WIFSTOPPED(status)) | ||
7057 | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", | ||
7058 | childpid, WSTOPSIG(status), WEXITSTATUS(status)); | ||
7059 | if (WIFSIGNALED(status)) | ||
7060 | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", | ||
7061 | childpid, WTERMSIG(status), WEXITSTATUS(status)); | ||
7062 | if (WIFEXITED(status)) | ||
7063 | debug_printf_jobs("pid %d exited, exitcode %d\n", | ||
7064 | childpid, WEXITSTATUS(status)); | ||
7065 | #endif | ||
7066 | /* Were we asked to wait for a fg pipe? */ | ||
7067 | if (fg_pipe) { | ||
7068 | i = fg_pipe->num_cmds; | ||
7069 | while (--i >= 0) { | ||
7070 | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); | ||
7071 | if (fg_pipe->cmds[i].pid != childpid) | ||
7072 | continue; | ||
7073 | if (dead) { | ||
7074 | int ex; | ||
7075 | fg_pipe->cmds[i].pid = 0; | ||
7076 | fg_pipe->alive_cmds--; | ||
7077 | ex = WEXITSTATUS(status); | ||
7078 | /* bash prints killer signal's name for *last* | ||
7079 | * process in pipe (prints just newline for SIGINT/SIGPIPE). | ||
7080 | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) | ||
7081 | */ | ||
7082 | if (WIFSIGNALED(status)) { | ||
7083 | int sig = WTERMSIG(status); | ||
7084 | if (i == fg_pipe->num_cmds-1) | ||
7085 | /* TODO: use strsignal() instead for bash compat? but that's bloat... */ | ||
7086 | puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig)); | ||
7087 | /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */ | ||
7088 | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? | ||
7089 | * Maybe we need to use sig | 128? */ | ||
7090 | ex = sig + 128; | ||
7091 | } | ||
7092 | fg_pipe->cmds[i].cmd_exitcode = ex; | ||
7093 | } else { | ||
7094 | fg_pipe->stopped_cmds++; | ||
7095 | } | ||
7096 | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", | ||
7097 | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); | ||
7098 | if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { | ||
7099 | /* All processes in fg pipe have exited or stopped */ | ||
7100 | int rcode = 0; | ||
7101 | i = fg_pipe->num_cmds; | ||
7102 | while (--i >= 0) { | ||
7103 | rcode = fg_pipe->cmds[i].cmd_exitcode; | ||
7104 | /* usually last process gives overall exitstatus, | ||
7105 | * but with "set -o pipefail", last *failed* process does */ | ||
7106 | if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) | ||
7107 | break; | ||
7108 | } | ||
7109 | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) | ||
7110 | /* Note: *non-interactive* bash does not continue if all processes in fg pipe | ||
7111 | * are stopped. Testcase: "cat | cat" in a script (not on command line!) | ||
7112 | * and "killall -STOP cat" */ | ||
7113 | if (G_interactive_fd) { | ||
7114 | #if ENABLE_HUSH_JOB | ||
7115 | if (fg_pipe->alive_cmds != 0) | ||
7116 | insert_bg_job(fg_pipe); | ||
7117 | #endif | ||
7118 | return rcode; | ||
7119 | } | ||
7120 | if (fg_pipe->alive_cmds == 0) | ||
7121 | return rcode; | ||
7122 | } | ||
7123 | /* There are still running processes in the fg_pipe */ | ||
7124 | return -1; | ||
7125 | } | ||
7126 | /* It wasnt in fg_pipe, look for process in bg pipes */ | ||
7127 | } | ||
7128 | |||
7129 | #if ENABLE_HUSH_JOB | ||
7130 | /* We were asked to wait for bg or orphaned children */ | ||
7131 | /* No need to remember exitcode in this case */ | ||
7132 | for (pi = G.job_list; pi; pi = pi->next) { | ||
7133 | for (i = 0; i < pi->num_cmds; i++) { | ||
7134 | if (pi->cmds[i].pid == childpid) | ||
7135 | goto found_pi_and_prognum; | ||
7136 | } | ||
7137 | } | ||
7138 | /* Happens when shell is used as init process (init=/bin/sh) */ | ||
7139 | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); | ||
7140 | return -1; /* this wasn't a process from fg_pipe */ | ||
7141 | |||
7142 | found_pi_and_prognum: | ||
7143 | if (dead) { | ||
7144 | /* child exited */ | ||
7145 | pi->cmds[i].pid = 0; | ||
7146 | pi->cmds[i].cmd_exitcode = WEXITSTATUS(status); | ||
7147 | if (WIFSIGNALED(status)) | ||
7148 | pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status); | ||
7149 | pi->alive_cmds--; | ||
7150 | if (!pi->alive_cmds) { | ||
7151 | if (G_interactive_fd) | ||
7152 | printf(JOB_STATUS_FORMAT, pi->jobid, | ||
7153 | "Done", pi->cmdtext); | ||
7154 | delete_finished_bg_job(pi); | ||
7155 | } | ||
7156 | } else { | ||
7157 | /* child stopped */ | ||
7158 | pi->stopped_cmds++; | ||
7159 | } | ||
7160 | #endif | ||
7161 | return -1; /* this wasn't a process from fg_pipe */ | ||
7162 | } | ||
7163 | |||
7164 | /* Check to see if any processes have exited -- if they have, | ||
7165 | * figure out why and see if a job has completed. | ||
7166 | * Alternatively (fg_pipe == NULL, waitfor_pid != 0), | ||
7167 | * wait for a specific pid to complete, return exitcode+1 | ||
7168 | * (this allows to distinguish zero as "no children exited" result). | ||
7169 | */ | ||
7170 | static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid) | ||
7171 | { | ||
7172 | int attributes; | ||
7173 | int status; | ||
7054 | int rcode = 0; | 7174 | int rcode = 0; |
7055 | 7175 | ||
7056 | debug_printf_jobs("checkjobs %p\n", fg_pipe); | 7176 | debug_printf_jobs("checkjobs %p\n", fg_pipe); |
@@ -7087,12 +7207,10 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7087 | * 1 <========== bg pipe is not fully done, but exitcode is already known! | 7207 | * 1 <========== bg pipe is not fully done, but exitcode is already known! |
7088 | * [hush 1.14.0: yes we do it right] | 7208 | * [hush 1.14.0: yes we do it right] |
7089 | */ | 7209 | */ |
7090 | wait_more: | ||
7091 | while (1) { | 7210 | while (1) { |
7092 | int i; | 7211 | pid_t childpid; |
7093 | int dead; | ||
7094 | |||
7095 | #if ENABLE_HUSH_FAST | 7212 | #if ENABLE_HUSH_FAST |
7213 | int i; | ||
7096 | i = G.count_SIGCHLD; | 7214 | i = G.count_SIGCHLD; |
7097 | #endif | 7215 | #endif |
7098 | childpid = waitpid(-1, &status, attributes); | 7216 | childpid = waitpid(-1, &status, attributes); |
@@ -7106,112 +7224,24 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7106 | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); | 7224 | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
7107 | } | 7225 | } |
7108 | #endif | 7226 | #endif |
7227 | /* ECHILD (no children), or 0 (no change in children status) */ | ||
7228 | rcode = childpid; | ||
7109 | break; | 7229 | break; |
7110 | } | 7230 | } |
7111 | dead = WIFEXITED(status) || WIFSIGNALED(status); | 7231 | rcode = process_wait_result(fg_pipe, childpid, status); |
7112 | 7232 | if (rcode >= 0) { | |
7113 | #if DEBUG_JOBS | 7233 | /* fg_pipe exited or stopped */ |
7114 | if (WIFSTOPPED(status)) | 7234 | break; |
7115 | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", | ||
7116 | childpid, WSTOPSIG(status), WEXITSTATUS(status)); | ||
7117 | if (WIFSIGNALED(status)) | ||
7118 | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", | ||
7119 | childpid, WTERMSIG(status), WEXITSTATUS(status)); | ||
7120 | if (WIFEXITED(status)) | ||
7121 | debug_printf_jobs("pid %d exited, exitcode %d\n", | ||
7122 | childpid, WEXITSTATUS(status)); | ||
7123 | #endif | ||
7124 | /* Were we asked to wait for fg pipe? */ | ||
7125 | if (fg_pipe) { | ||
7126 | i = fg_pipe->num_cmds; | ||
7127 | while (--i >= 0) { | ||
7128 | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); | ||
7129 | if (fg_pipe->cmds[i].pid != childpid) | ||
7130 | continue; | ||
7131 | if (dead) { | ||
7132 | int ex; | ||
7133 | fg_pipe->cmds[i].pid = 0; | ||
7134 | fg_pipe->alive_cmds--; | ||
7135 | ex = WEXITSTATUS(status); | ||
7136 | /* bash prints killer signal's name for *last* | ||
7137 | * process in pipe (prints just newline for SIGINT/SIGPIPE). | ||
7138 | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) | ||
7139 | */ | ||
7140 | if (WIFSIGNALED(status)) { | ||
7141 | int sig = WTERMSIG(status); | ||
7142 | if (i == fg_pipe->num_cmds-1) | ||
7143 | /* TODO: use strsignal() instead for bash compat? but that's bloat... */ | ||
7144 | puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig)); | ||
7145 | /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */ | ||
7146 | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? | ||
7147 | * Maybe we need to use sig | 128? */ | ||
7148 | ex = sig + 128; | ||
7149 | } | ||
7150 | fg_pipe->cmds[i].cmd_exitcode = ex; | ||
7151 | } else { | ||
7152 | fg_pipe->stopped_cmds++; | ||
7153 | } | ||
7154 | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", | ||
7155 | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); | ||
7156 | if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { | ||
7157 | /* All processes in fg pipe have exited or stopped */ | ||
7158 | i = fg_pipe->num_cmds; | ||
7159 | while (--i >= 0) { | ||
7160 | rcode = fg_pipe->cmds[i].cmd_exitcode; | ||
7161 | /* usually last process gives overall exitstatus, | ||
7162 | * but with "set -o pipefail", last *failed* process does */ | ||
7163 | if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) | ||
7164 | break; | ||
7165 | } | ||
7166 | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) | ||
7167 | /* Note: *non-interactive* bash does not continue if all processes in fg pipe | ||
7168 | * are stopped. Testcase: "cat | cat" in a script (not on command line!) | ||
7169 | * and "killall -STOP cat" */ | ||
7170 | if (G_interactive_fd) { | ||
7171 | #if ENABLE_HUSH_JOB | ||
7172 | if (fg_pipe->alive_cmds != 0) | ||
7173 | insert_bg_job(fg_pipe); | ||
7174 | #endif | ||
7175 | return rcode; | ||
7176 | } | ||
7177 | if (fg_pipe->alive_cmds == 0) | ||
7178 | return rcode; | ||
7179 | } | ||
7180 | /* There are still running processes in the fg pipe */ | ||
7181 | goto wait_more; /* do waitpid again */ | ||
7182 | } | ||
7183 | /* it wasnt fg_pipe, look for process in bg pipes */ | ||
7184 | } | ||
7185 | |||
7186 | #if ENABLE_HUSH_JOB | ||
7187 | /* We asked to wait for bg or orphaned children */ | ||
7188 | /* No need to remember exitcode in this case */ | ||
7189 | for (pi = G.job_list; pi; pi = pi->next) { | ||
7190 | for (i = 0; i < pi->num_cmds; i++) { | ||
7191 | if (pi->cmds[i].pid == childpid) | ||
7192 | goto found_pi_and_prognum; | ||
7193 | } | ||
7194 | } | 7235 | } |
7195 | /* Happens when shell is used as init process (init=/bin/sh) */ | 7236 | if (childpid == waitfor_pid) { |
7196 | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); | 7237 | rcode = WEXITSTATUS(status); |
7197 | continue; /* do waitpid again */ | 7238 | if (WIFSIGNALED(status)) |
7198 | 7239 | rcode = 128 + WTERMSIG(status); | |
7199 | found_pi_and_prognum: | 7240 | rcode++; |
7200 | if (dead) { | 7241 | break; /* "wait PID" called us, give it exitcode+1 */ |
7201 | /* child exited */ | ||
7202 | pi->cmds[i].pid = 0; | ||
7203 | pi->alive_cmds--; | ||
7204 | if (!pi->alive_cmds) { | ||
7205 | if (G_interactive_fd) | ||
7206 | printf(JOB_STATUS_FORMAT, pi->jobid, | ||
7207 | "Done", pi->cmdtext); | ||
7208 | delete_finished_bg_job(pi); | ||
7209 | } | ||
7210 | } else { | ||
7211 | /* child stopped */ | ||
7212 | pi->stopped_cmds++; | ||
7213 | } | 7242 | } |
7214 | #endif | 7243 | /* This wasn't one of our processes, or */ |
7244 | /* fg_pipe still has running processes, do waitpid again */ | ||
7215 | } /* while (waitpid succeeds)... */ | 7245 | } /* while (waitpid succeeds)... */ |
7216 | 7246 | ||
7217 | return rcode; | 7247 | return rcode; |
@@ -7221,7 +7251,7 @@ static int checkjobs(struct pipe *fg_pipe) | |||
7221 | static int checkjobs_and_fg_shell(struct pipe *fg_pipe) | 7251 | static int checkjobs_and_fg_shell(struct pipe *fg_pipe) |
7222 | { | 7252 | { |
7223 | pid_t p; | 7253 | pid_t p; |
7224 | int rcode = checkjobs(fg_pipe); | 7254 | int rcode = checkjobs(fg_pipe, 0 /*(no pid to wait for)*/); |
7225 | if (G_saved_tty_pgrp) { | 7255 | if (G_saved_tty_pgrp) { |
7226 | /* Job finished, move the shell to the foreground */ | 7256 | /* Job finished, move the shell to the foreground */ |
7227 | p = getpgrp(); /* our process group id */ | 7257 | p = getpgrp(); /* our process group id */ |
@@ -7893,7 +7923,7 @@ static int run_list(struct pipe *pi) | |||
7893 | /* else: e.g. "continue 2" should *break* once, *then* continue */ | 7923 | /* else: e.g. "continue 2" should *break* once, *then* continue */ |
7894 | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ | 7924 | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ |
7895 | if (G.depth_break_continue != 0 || fbc == BC_BREAK) { | 7925 | if (G.depth_break_continue != 0 || fbc == BC_BREAK) { |
7896 | checkjobs(NULL); | 7926 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7897 | break; | 7927 | break; |
7898 | } | 7928 | } |
7899 | /* "continue": simulate end of loop */ | 7929 | /* "continue": simulate end of loop */ |
@@ -7902,7 +7932,7 @@ static int run_list(struct pipe *pi) | |||
7902 | } | 7932 | } |
7903 | #endif | 7933 | #endif |
7904 | if (G_flag_return_in_progress == 1) { | 7934 | if (G_flag_return_in_progress == 1) { |
7905 | checkjobs(NULL); | 7935 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7906 | break; | 7936 | break; |
7907 | } | 7937 | } |
7908 | } else if (pi->followup == PIPE_BG) { | 7938 | } else if (pi->followup == PIPE_BG) { |
@@ -7929,7 +7959,7 @@ static int run_list(struct pipe *pi) | |||
7929 | } else | 7959 | } else |
7930 | #endif | 7960 | #endif |
7931 | { /* This one just waits for completion */ | 7961 | { /* This one just waits for completion */ |
7932 | rcode = checkjobs(pi); | 7962 | rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); |
7933 | debug_printf_exec(": checkjobs exitcode %d\n", rcode); | 7963 | debug_printf_exec(": checkjobs exitcode %d\n", rcode); |
7934 | check_and_run_traps(); | 7964 | check_and_run_traps(); |
7935 | } | 7965 | } |
@@ -7943,7 +7973,7 @@ static int run_list(struct pipe *pi) | |||
7943 | cond_code = rcode; | 7973 | cond_code = rcode; |
7944 | #endif | 7974 | #endif |
7945 | check_jobs_and_continue: | 7975 | check_jobs_and_continue: |
7946 | checkjobs(NULL); | 7976 | checkjobs(NULL, 0 /*(no pid to wait for)*/); |
7947 | dont_check_jobs_but_continue: ; | 7977 | dont_check_jobs_but_continue: ; |
7948 | #if ENABLE_HUSH_LOOPS | 7978 | #if ENABLE_HUSH_LOOPS |
7949 | /* Beware of "while false; true; do ..."! */ | 7979 | /* Beware of "while false; true; do ..."! */ |
@@ -9408,9 +9438,68 @@ static int FAST_FUNC builtin_umask(char **argv) | |||
9408 | } | 9438 | } |
9409 | 9439 | ||
9410 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ | 9440 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ |
9441 | static int wait_for_child_or_signal(pid_t waitfor_pid) | ||
9442 | { | ||
9443 | int ret = 0; | ||
9444 | for (;;) { | ||
9445 | int sig; | ||
9446 | sigset_t oldset, allsigs; | ||
9447 | |||
9448 | /* waitpid is not interruptible by SA_RESTARTed | ||
9449 | * signals which we use. Thus, this ugly dance: | ||
9450 | */ | ||
9451 | |||
9452 | /* Make sure possible SIGCHLD is stored in kernel's | ||
9453 | * pending signal mask before we call waitpid. | ||
9454 | * Or else we may race with SIGCHLD, lose it, | ||
9455 | * and get stuck in sigwaitinfo... | ||
9456 | */ | ||
9457 | sigfillset(&allsigs); | ||
9458 | sigprocmask(SIG_SETMASK, &allsigs, &oldset); | ||
9459 | |||
9460 | if (!sigisemptyset(&G.pending_set)) { | ||
9461 | /* Crap! we raced with some signal! */ | ||
9462 | // sig = 0; | ||
9463 | goto restore; | ||
9464 | } | ||
9465 | |||
9466 | /*errno = 0; - checkjobs does this */ | ||
9467 | ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */ | ||
9468 | /* if ECHILD, there are no children (ret is -1 or 0) */ | ||
9469 | /* if ret == 0, no children changed state */ | ||
9470 | /* if ret != 0, it's exitcode+1 of exited waitfor_pid child */ | ||
9471 | if (errno == ECHILD || ret--) { | ||
9472 | if (ret < 0) /* if ECHILD, may need to fix */ | ||
9473 | ret = 0; | ||
9474 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9475 | break; | ||
9476 | } | ||
9477 | |||
9478 | /* Wait for SIGCHLD or any other signal */ | ||
9479 | //sig = sigwaitinfo(&allsigs, NULL); | ||
9480 | /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */ | ||
9481 | /* Note: sigsuspend invokes signal handler */ | ||
9482 | sigsuspend(&oldset); | ||
9483 | restore: | ||
9484 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9485 | |||
9486 | /* So, did we get a signal? */ | ||
9487 | //if (sig > 0) | ||
9488 | // raise(sig); /* run handler */ | ||
9489 | sig = check_and_run_traps(); | ||
9490 | if (sig /*&& sig != SIGCHLD - always true */) { | ||
9491 | /* see note 2 */ | ||
9492 | ret = 128 + sig; | ||
9493 | break; | ||
9494 | } | ||
9495 | /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */ | ||
9496 | } | ||
9497 | return ret; | ||
9498 | } | ||
9499 | |||
9411 | static int FAST_FUNC builtin_wait(char **argv) | 9500 | static int FAST_FUNC builtin_wait(char **argv) |
9412 | { | 9501 | { |
9413 | int ret = EXIT_SUCCESS; | 9502 | int ret; |
9414 | int status; | 9503 | int status; |
9415 | 9504 | ||
9416 | argv = skip_dash_dash(argv); | 9505 | argv = skip_dash_dash(argv); |
@@ -9431,74 +9520,41 @@ static int FAST_FUNC builtin_wait(char **argv) | |||
9431 | * ^C <-- after ~4 sec from keyboard | 9520 | * ^C <-- after ~4 sec from keyboard |
9432 | * $ | 9521 | * $ |
9433 | */ | 9522 | */ |
9434 | while (1) { | 9523 | return wait_for_child_or_signal(0 /*(no pid to wait for)*/); |
9435 | int sig; | ||
9436 | sigset_t oldset, allsigs; | ||
9437 | |||
9438 | /* waitpid is not interruptible by SA_RESTARTed | ||
9439 | * signals which we use. Thus, this ugly dance: | ||
9440 | */ | ||
9441 | |||
9442 | /* Make sure possible SIGCHLD is stored in kernel's | ||
9443 | * pending signal mask before we call waitpid. | ||
9444 | * Or else we may race with SIGCHLD, lose it, | ||
9445 | * and get stuck in sigwaitinfo... | ||
9446 | */ | ||
9447 | sigfillset(&allsigs); | ||
9448 | sigprocmask(SIG_SETMASK, &allsigs, &oldset); | ||
9449 | |||
9450 | if (!sigisemptyset(&G.pending_set)) { | ||
9451 | /* Crap! we raced with some signal! */ | ||
9452 | // sig = 0; | ||
9453 | goto restore; | ||
9454 | } | ||
9455 | |||
9456 | checkjobs(NULL); /* waitpid(WNOHANG) inside */ | ||
9457 | if (errno == ECHILD) { | ||
9458 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9459 | break; | ||
9460 | } | ||
9461 | |||
9462 | /* Wait for SIGCHLD or any other signal */ | ||
9463 | //sig = sigwaitinfo(&allsigs, NULL); | ||
9464 | /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */ | ||
9465 | /* Note: sigsuspend invokes signal handler */ | ||
9466 | sigsuspend(&oldset); | ||
9467 | restore: | ||
9468 | sigprocmask(SIG_SETMASK, &oldset, NULL); | ||
9469 | |||
9470 | /* So, did we get a signal? */ | ||
9471 | //if (sig > 0) | ||
9472 | // raise(sig); /* run handler */ | ||
9473 | sig = check_and_run_traps(); | ||
9474 | if (sig /*&& sig != SIGCHLD - always true */) { | ||
9475 | /* see note 2 */ | ||
9476 | ret = 128 + sig; | ||
9477 | break; | ||
9478 | } | ||
9479 | /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */ | ||
9480 | } | ||
9481 | return ret; | ||
9482 | } | 9524 | } |
9483 | 9525 | ||
9484 | /* This is probably buggy wrt interruptible-ness */ | 9526 | /* TODO: support "wait %jobspec" */ |
9485 | while (*argv) { | 9527 | do { |
9486 | pid_t pid = bb_strtou(*argv, NULL, 10); | 9528 | pid_t pid = bb_strtou(*argv, NULL, 10); |
9487 | if (errno) { | 9529 | if (errno || pid <= 0) { |
9488 | /* mimic bash message */ | 9530 | /* mimic bash message */ |
9489 | bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); | 9531 | bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); |
9490 | return EXIT_FAILURE; | 9532 | return EXIT_FAILURE; |
9491 | } | 9533 | } |
9492 | if (waitpid(pid, &status, 0) == pid) { | 9534 | /* Do we have such child? */ |
9535 | ret = waitpid(pid, &status, WNOHANG); | ||
9536 | if (ret < 0) { | ||
9537 | /* No */ | ||
9538 | if (errno == ECHILD) { | ||
9539 | /* Example: "wait 1". mimic bash message */ | ||
9540 | bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); | ||
9541 | } else { | ||
9542 | /* ??? */ | ||
9543 | bb_perror_msg("wait %s", *argv); | ||
9544 | } | ||
9545 | ret = 127; | ||
9546 | } else if (ret == 0) { | ||
9547 | /* Yes, and it still runs */ | ||
9548 | ret = wait_for_child_or_signal(pid); | ||
9549 | } else { | ||
9550 | /* Yes, and it just exited */ | ||
9551 | process_wait_result(NULL, pid, status); | ||
9493 | ret = WEXITSTATUS(status); | 9552 | ret = WEXITSTATUS(status); |
9494 | if (WIFSIGNALED(status)) | 9553 | if (WIFSIGNALED(status)) |
9495 | ret = 128 + WTERMSIG(status); | 9554 | ret = 128 + WTERMSIG(status); |
9496 | } else { | ||
9497 | bb_perror_msg("wait %s", *argv); | ||
9498 | ret = 127; | ||
9499 | } | 9555 | } |
9500 | argv++; | 9556 | argv++; |
9501 | } | 9557 | } while (*argv); |
9502 | 9558 | ||
9503 | return ret; | 9559 | return ret; |
9504 | } | 9560 | } |
diff --git a/shell/hush_test/hush-misc/wait1.right b/shell/hush_test/hush-misc/wait1.right new file mode 100644 index 000000000..20f514da0 --- /dev/null +++ b/shell/hush_test/hush-misc/wait1.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 0 | ||
2 | [1] Running sleep 2 | ||
diff --git a/shell/hush_test/hush-misc/wait1.tests b/shell/hush_test/hush-misc/wait1.tests new file mode 100755 index 000000000..f9cf6d48c --- /dev/null +++ b/shell/hush_test/hush-misc/wait1.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | sleep 2 & sleep 1 & wait $! | ||
2 | echo $? | ||
3 | jobs | ||
diff --git a/shell/hush_test/hush-misc/wait2.right b/shell/hush_test/hush-misc/wait2.right new file mode 100644 index 000000000..f018c2c98 --- /dev/null +++ b/shell/hush_test/hush-misc/wait2.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 0 | ||
2 | [1] Running sleep 3 | ||
diff --git a/shell/hush_test/hush-misc/wait2.tests b/shell/hush_test/hush-misc/wait2.tests new file mode 100755 index 000000000..be20f95a5 --- /dev/null +++ b/shell/hush_test/hush-misc/wait2.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | sleep 3 & sleep 2 & sleep 1 | ||
2 | wait $! | ||
3 | echo $? | ||
4 | jobs | ||
diff --git a/shell/hush_test/hush-misc/wait3.right b/shell/hush_test/hush-misc/wait3.right new file mode 100644 index 000000000..5437b1d73 --- /dev/null +++ b/shell/hush_test/hush-misc/wait3.right | |||
@@ -0,0 +1,2 @@ | |||
1 | 3 | ||
2 | [1] Running sleep 2 | ||
diff --git a/shell/hush_test/hush-misc/wait3.tests b/shell/hush_test/hush-misc/wait3.tests new file mode 100755 index 000000000..ac541c3fc --- /dev/null +++ b/shell/hush_test/hush-misc/wait3.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | sleep 2 & (sleep 1;exit 3) & wait $! | ||
2 | echo $? | ||
3 | jobs | ||