ash: eval: Reap zombies after built-in commands and functions
authorDenys Vlasenko <vda.linux@googlemail.com>
Tue, 18 Feb 2020 13:28:30 +0000 (14:28 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Tue, 18 Feb 2020 13:28:30 +0000 (14:28 +0100)
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>
shell/ash.c

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