ash: fix "while kill -0 $child; do true; done" looping forever.
This commit is contained in:
parent
7ff85c53f1
commit
be54d6bc60
66
shell/ash.c
66
shell/ash.c
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user