From: Dominique Martinet <[email protected]>

'wait -n' had (at least) a couple of bugs:

1/ Waiting for jobs with non-zero status

if waiting for a command that returns non-zero, dowait() would keep
waiting for the next command.

That means something like the following waits 2 seconds instead of 1:
    time busybox sh -c 'sh -c "sleep 1; false" & sleep 2 & wait -n'

2/ Waiting for jobs with multiple processes

While fixing the above, I noticed this comment:
 /* wait -n waits for one _job_, not one _process_.
  *  date; sleep 3 & sleep 2 | sleep 1 & wait -n; date
  * should wait for 2 seconds. Not 1 or 3.
  */

It turns out, that does not work:
    time busybox sh -c 'sleep 3 & sleep 2 | sleep 1 & wait -n'

The current code returns -1 if a job is not finished, and dowait() stops
looping immediately on -1 since commit 8d5f465a206e ("ash: jobs: Fix
infinite loop in waitproc"), breaking this behaviour.

A test was added testing both of these cases, outputing the following
errors before patch:
```
Test1 (wait with zero exit status of bg process)
Test2 (wait with non-zero exit status of bg process)
Background1: BUG!
Test2 exit code was not 1: 0
Test3 (pipeline wait for whole job)
Test3 exit code was not 0: 129
Test3 finished in less than 2s
sh: can't kill pid 440510: No such process
Ok:1
```

This patch splits dowait() into two functions because the variant that
wants the pid is different enough to benefit from specialization.

function                                             old     new   delta
waitone                                                -     449    +449
waitcmd                                              277     340     +63
dowait                                               608      80    -528
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/1 up/down: 512/-528)          Total: -16 bytes
   text   data     bss     dec     hex  filename
 786254  14292    1800  802346   c3e2a  busybox_old
 786238  14292    1800  802330   c3e1a  busybox_unstripped

Fixes: 8d5f465a206e ("ash: jobs: Fix infinite loop in waitproc")
Tested-by: Wolfgang Zekoll <[email protected]>
Signed-off-by: Dominique Martinet <[email protected]>
---
v1 -> v2:
 - fix test wording/add tested-by
v2 -> v3:
 - fix breaking out of wait if a signal comes in,
   and add wait9 test to check this keeps working.
Hopefully won't be more like this!

 shell/ash.c                         | 61 +++++++++++++++--------------
 shell/ash_test/ash-misc/wait8.right |  4 ++
 shell/ash_test/ash-misc/wait8.tests | 47 ++++++++++++++++++++++
 shell/ash_test/ash-misc/wait9.right |  6 +++
 shell/ash_test/ash-misc/wait9.tests | 21 ++++++++++
 5 files changed, 110 insertions(+), 29 deletions(-)
 create mode 100644 shell/ash_test/ash-misc/wait8.right
 create mode 100755 shell/ash_test/ash-misc/wait8.tests
 create mode 100644 shell/ash_test/ash-misc/wait9.right
 create mode 100644 shell/ash_test/ash-misc/wait9.tests

diff --git a/shell/ash.c b/shell/ash.c
index 841ffe8808fc..ee2d5c47c8b4 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -4418,9 +4418,6 @@ sprint_status48(char *os, int status, int sigonly)
 #define DOWAIT_NONBLOCK 0      /* waitpid() will use WNOHANG and won't wait 
for signals */
 #define DOWAIT_BLOCK    1      /* waitpid() will NOT use WNOHANG */
 #define DOWAIT_CHILD_OR_SIG 2  /* waitpid() will use WNOHANG and if got 0, 
will wait for signals, then loop back */
-#if BASH_WAIT_N
-# define DOWAIT_JOBSTATUS 0x10  /* OR this to get job's exitstatus instead of 
pid */
-#endif
 
 static int
 waitproc(int block, int *status)
@@ -4472,10 +4469,6 @@ static int waitone(int block, struct job *job)
        int status;
        struct job *jp;
        struct job *thisjob = NULL;
-#if BASH_WAIT_N
-       bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS);
-       block = (block & ~DOWAIT_JOBSTATUS);
-#endif
 
        TRACE(("dowait(0x%x) called\n", block));
 
@@ -4501,7 +4494,7 @@ static int waitone(int block, struct job *job)
        pid = waitproc(block, &status);
        TRACE(("wait returns pid %d, status=%d\n", pid, status));
        if (pid <= 0)
-               goto out;
+               return pid;
 
        for (jp = curjob; jp; jp = jp->prev_job) {
                int jobstate;
@@ -4554,13 +4547,6 @@ static int waitone(int block, struct job *job)
  out:
        INTON;
 
-#if BASH_WAIT_N
-       if (want_jobexitstatus) {
-               pid = -1;
-               if (thisjob && thisjob->state == JOBDONE)
-                       pid = thisjob->ps[thisjob->nprocs - 1].ps_status;
-       }
-#endif
        if (thisjob && thisjob == job) {
                char s[48 + 1];
                int len;
@@ -4572,32 +4558,49 @@ static int waitone(int block, struct job *job)
                        out2str(s);
                }
        }
-       return pid;
+
+       status = INT_MAX;
+       if (thisjob && thisjob->state == JOBDONE)
+               status = thisjob->ps[thisjob->nprocs - 1].ps_status;
+
+       return status;
 }
 
-static int dowait(int block, struct job *jp)
+static int dowait_status(void)
+{
+       int status, rstatus = -ENOENT;
+       int block = DOWAIT_CHILD_OR_SIG;
+
+       do {
+               status = waitone(block, NULL);
+               if (status >= 0 && status < INT_MAX) {
+                       rstatus = status;
+                       block = DOWAIT_NONBLOCK;
+               }
+               if (pending_sig)
+                       break;
+       } while (status >= 0);
+
+       return rstatus;
+}
+
+static void dowait(int block, struct job *jp)
 {
        smallint gotchld = *(volatile smallint *)&gotsigchld;
-       int rpid;
-       int pid;
+       int status;
 
        if (jp && jp->state != JOBRUNNING)
                block = DOWAIT_NONBLOCK;
 
        if (block == DOWAIT_NONBLOCK && !gotchld)
-               return 1;
-
-       rpid = 1;
+               return;
 
        do {
-               pid = waitone(block, jp);
-               rpid &= !!pid;
+               status = waitone(block, jp);
 
-               if (!pid || (jp && jp->state != JOBRUNNING))
+               if (!status || (jp && jp->state != JOBRUNNING))
                        block = DOWAIT_NONBLOCK;
-       } while (pid >= 0);
-
-       return rpid;
+       } while (status >= 0);
 }
 
 #if JOBS
@@ -4801,7 +4804,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
         * the trap is executed."
         */
 #if BASH_WAIT_N
-                       status = dowait(DOWAIT_CHILD_OR_SIG | DOWAIT_JOBSTATUS, 
NULL);
+                       status = dowait_status();
 #else
                        dowait(DOWAIT_CHILD_OR_SIG, NULL);
 #endif
diff --git a/shell/ash_test/ash-misc/wait8.right 
b/shell/ash_test/ash-misc/wait8.right
new file mode 100644
index 000000000000..5d29b8cc7dbf
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait8.right
@@ -0,0 +1,4 @@
+Test1 (wait with zero exit status of bg process)
+Test2 (wait with non-zero exit status of bg process)
+Test3 (pipeline wait for whole job)
+Ok:0
diff --git a/shell/ash_test/ash-misc/wait8.tests 
b/shell/ash_test/ash-misc/wait8.tests
new file mode 100755
index 000000000000..d29490260001
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait8.tests
@@ -0,0 +1,47 @@
+# wait -n tests
+
+# should never wait for this one
+sleep 5 && echo "Background1: BUG!" &
+bg=$!
+
+# normal process
+echo "Test1 (wait with zero exit status of bg process)"
+sleep 1 &
+wait -n
+rc=$?
+if [ "$rc" != 0 ]; then
+       echo "Test1 exit code was not 0: $rc"
+fi
+
+# non-zero return code
+echo "Test2 (wait with non-zero exit status of bg process)"
+sh -c 'sleep 1; false' &
+pid2=$!
+wait -n
+rc=$?
+if [ "$rc" != 1 ]; then
+       echo "Test2 exit code was not 1: $rc"
+fi
+
+wait $pid2
+rc=$?
+if [ "$rc" != 1 ]; then
+       echo "Test2 exit code was not 1 on recheck: $rc"
+fi
+
+# pipeline... not too fast
+echo "Test3 (pipeline wait for whole job)"
+d1=$(date +%s)
+sleep 2 | sleep 1 &
+wait -n
+rc=$?
+if [ "$rc" != 0 ]; then
+       echo "Test3 exit code was not 0: $rc"
+fi
+d2=$(date +%s)
+if [ "$d2" -lt "$((d1 + 2))" ]; then
+       echo "Test3 finished in less than 2s"
+fi
+
+kill $bg
+echo Ok:$?
diff --git a/shell/ash_test/ash-misc/wait9.right 
b/shell/ash_test/ash-misc/wait9.right
new file mode 100644
index 000000000000..23db7738d02c
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait9.right
@@ -0,0 +1,6 @@
+wait -n
+from USR1
+prints after USR1
+wait
+from USR1
+prints after USR1 (2)
diff --git a/shell/ash_test/ash-misc/wait9.tests 
b/shell/ash_test/ash-misc/wait9.tests
new file mode 100644
index 000000000000..3dda2e133831
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait9.tests
@@ -0,0 +1,21 @@
+# tests wait and signals
+
+trap 'echo from USR1' USR1
+
+echo "wait -n"
+{ sleep 1; kill -USR1 $$; sleep 1; echo prints after USR1; } &
+wait -n
+rc=$?
+[ "$rc" = 138 ] || echo "rc not 138: $?"
+wait -n
+rc=$?
+[ "$rc" = 0 ] || echo "rc not 0: $?"
+
+echo "wait"
+{ sleep 1; kill -USR1 $$; sleep 1; echo "prints after USR1 (2)"; } &
+wait
+rc=$?
+[ "$rc" = 138 ] || echo "rc not 138: $?"
+wait
+rc=$?
+[ "$rc" = 0 ] || echo "rc not 0: $?"
-- 
2.47.3

_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox

Reply via email to