ash: fix "while kill -0 $child; do true; done" looping forever.

This commit is contained in:
Denis Vlasenko 2008-10-27 14:25:52 +00:00
parent 7ff85c53f1
commit be54d6bc60

View File

@ -3762,48 +3762,6 @@ sprint_status(char *s, int status, int sigonly)
return col;
}
/*
* Do a wait system call. If job control is compiled in, we accept
* stopped processes. If block is zero, we return a value of zero
* rather than blocking.
*
* System V doesn't have a non-blocking wait system call. It does
* have a SIGCLD signal that is sent to a process when one of it's
* children dies. The obvious way to use SIGCLD would be to install
* a handler for SIGCLD which simply bumped a counter when a SIGCLD
* was received, and have waitproc bump another counter when it got
* the status of a process. Waitproc would then know that a wait
* system call would not block if the two counters were different.
* This approach doesn't work because if a process has children that
* have not been waited for, System V will send it a SIGCLD when it
* installs a signal handler for SIGCLD. What this means is that when
* a child exits, the shell will be sent SIGCLD signals continuously
* until is runs out of stack space, unless it does a wait call before
* restoring the signal handler. The code below takes advantage of
* this (mis)feature by installing a signal handler for SIGCLD and
* then checking to see whether it was called. If there are any
* children to be waited for, it will be.
*
* If neither SYSV nor BSD is defined, we don't implement nonblocking
* waits at all. In this case, the user will not be informed when
* a background process until the next time she runs a real program
* (as opposed to running a builtin command or just typing return),
* and the jobs command may give out of date information.
*/
static int
waitproc(int wait_flags, int *status)
{
#if JOBS
if (doing_jobctl)
wait_flags |= WUNTRACED;
#endif
/* NB: _not_ safe_waitpid, we need to detect EINTR */
return waitpid(-1, status, wait_flags);
}
/*
* Wait for a process to terminate.
*/
static int
dowait(int wait_flags, struct job *job)
{
@ -3813,9 +3771,15 @@ dowait(int wait_flags, struct job *job)
struct job *thisjob;
int state;
TRACE(("dowait(%d) called\n", wait_flags));
pid = waitproc(wait_flags, &status);
TRACE(("wait returns pid=%d, status=%d\n", pid, status));
TRACE(("dowait(0x%x) called\n", wait_flags));
/* Do a wait system call. If job control is compiled in, we accept
* stopped processes. wait_flags may have WNOHANG, preventing blocking.
* NB: _not_ safe_waitpid, we need to detect EINTR */
pid = waitpid(-1, &status,
(doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags));
TRACE(("wait returns pid=%d, status=0x%x\n", pid, status));
if (pid <= 0) {
/* If we were doing blocking wait and (probably) got EINTR,
* check for pending sigs received while waiting.
@ -8923,7 +8887,10 @@ evalcommand(union node *cmd, int flags)
/* Execute the command. */
switch (cmdentry.cmdtype) {
default:
#if ENABLE_FEATURE_SH_NOFORK
/* Hmmm... shouldn't it happen somewhere in forkshell() instead?
* Why "fork off a child process if necessary" doesn't apply to NOFORK? */
{
/* find_command() encodes applet_no as (-2 - applet_no) */
int applet_no = (- cmdentry.u.index - 2);
@ -8935,7 +8902,6 @@ evalcommand(union node *cmd, int flags)
}
}
#endif
/* Fork off a child process if necessary. */
if (!(flags & EV_EXIT) || trap[0]) {
INT_OFF;
@ -8963,6 +8929,12 @@ evalcommand(union node *cmd, int flags)
}
listsetvar(list, i);
}
/* Tight loop with builtins only:
* "while kill -0 $child; do true; done"
* will never exit even if $child died, unless we do this
* to reap the zombie and make kill detect that it's gone: */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalbltin(cmdentry.u.cmd, argc, argv)) {
int exit_status;
int i = exception;
@ -8984,6 +8956,8 @@ evalcommand(union node *cmd, int flags)
case CMDFUNCTION:
listsetvar(varlist.list, 0);
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
goto raise;
break;