ash: eval: Reap zombies after built-in commands and functions

Upstream commit:

    Date: Mon, 26 Mar 2018 23:55:50 +0800
    eval: Reap zombies after built-in commands and functions

    Currently dash does not reap dead children after built-in commands
    or functions.  This means that if you construct a loop consisting
    of solely built-in commands and functions, then zombies can hang
    around indefinitely.

    This patch fixes this by reaping when necessary after each built-in
    command and function.

    Reported-by: Denys Vlasenko <vda.linux@googlemail.com>
    Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2020-02-18 14:28:30 +01:00
parent 22c75924da
commit d81af7216b

View File

@ -5355,10 +5355,10 @@ waitforjob(struct job *jp)
{ {
int st; int st;
TRACE(("waitforjob(%%%d) called\n", jobno(jp))); TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
INT_OFF; INT_OFF;
while (jp->state == JOBRUNNING) { while ((jp && jp->state == JOBRUNNING) || got_sigchld) {
/* In non-interactive shells, we _can_ get /* In non-interactive shells, we _can_ get
* a keyboard signal here and be EINTRed, * a keyboard signal here and be EINTRed,
* but we just loop back, waiting for command to complete. * but we just loop back, waiting for command to complete.
@ -5393,6 +5393,8 @@ waitforjob(struct job *jp)
} }
INT_ON; INT_ON;
if (!jp)
return exitstatus;
st = getstatus(jp); st = getstatus(jp);
#if JOBS #if JOBS
if (jp->jobctl) { if (jp->jobctl) {
@ -10311,6 +10313,8 @@ evalcommand(union node *cmd, int flags)
goto out; goto out;
} }
jp = NULL;
/* Execute the command. */ /* Execute the command. */
switch (cmdentry.cmdtype) { switch (cmdentry.cmdtype) {
default: { default: {
@ -10365,7 +10369,6 @@ evalcommand(union node *cmd, int flags)
jp = makejob(/*cmd,*/ 1); jp = makejob(/*cmd,*/ 1);
if (forkshell(jp, cmd, FORK_FG) != 0) { if (forkshell(jp, cmd, FORK_FG) != 0) {
/* parent */ /* parent */
status = waitforjob(jp);
INT_ON; INT_ON;
TRACE(("forked child exited with %d\n", status)); TRACE(("forked child exited with %d\n", status));
break; break;
@ -10384,33 +10387,24 @@ evalcommand(union node *cmd, int flags)
if (cmd_is_exec && argc > 1) if (cmd_is_exec && argc > 1)
listsetvar(varlist.list, VEXPORT); listsetvar(varlist.list, VEXPORT);
} }
/* 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, flags)) { if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
if (exception_type == EXERROR && spclbltin <= 0) { if (exception_type == EXERROR && spclbltin <= 0) {
FORCE_INT_ON; FORCE_INT_ON;
goto readstatus; break;
} }
raise: raise:
longjmp(exception_handler->loc, 1); longjmp(exception_handler->loc, 1);
} }
goto readstatus; break;
case CMDFUNCTION: case CMDFUNCTION:
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags)) if (evalfun(cmdentry.u.func, argc, argv, flags))
goto raise; goto raise;
readstatus:
status = exitstatus;
break; break;
} /* switch */ } /* switch */
status = waitforjob(jp);
out: out:
if (cmd->ncmd.redirect) if (cmd->ncmd.redirect)
popredir(/*drop:*/ cmd_is_exec); popredir(/*drop:*/ cmd_is_exec);