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
Test2
Background1: BUG!
Test2 exit code was not 1
Test3
Test3 exit code was not 0
Test3 finished in less than 2s
sh: can't kill pid 2074018: 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
static.dowait                                          -      80     +80
waitcmd                                              277     334     +57
dowait                                               608       -    -608
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 1/0 up/down: 586/-608)          Total: -22 bytes
   text   data     bss     dec     hex  filename
 785950  14292    1800  802042   c3cfa  busybox_old
 785928  14292    1800  802020   c3ce4  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:
 - apply Wolfgang's suggestion for test wording, add his tested-by

 Config.in                           |  7 ----
 shell/ash.c                         | 59 +++++++++++++++--------------
 shell/ash_test/ash-misc/wait8.right |  4 ++
 shell/ash_test/ash-misc/wait8.tests | 47 +++++++++++++++++++++++
 4 files changed, 81 insertions(+), 36 deletions(-)
 create mode 100644 shell/ash_test/ash-misc/wait8.right
 create mode 100755 shell/ash_test/ash-misc/wait8.tests

diff --git a/Config.in b/Config.in
index 6b925b517dd5..ad0cd1e26f01 100644
--- a/Config.in
+++ b/Config.in
@@ -204,13 +204,6 @@ config FEATURE_INSTALLER
        busybox at runtime to create hard links or symlinks for all the
        applets that are compiled into busybox.
 
-config FEATURE_VERSION
-       bool "Support --version"
-       default y
-       depends on BUSYBOX
-       help
-       Enable 'busybox --version' support.
-
 config INSTALL_NO_USR
        bool "Don't use /usr"
        default n
diff --git a/shell/ash.c b/shell/ash.c
index 841ffe8808fc..4f5ca0d02fb7 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,47 @@ 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;
+               }
+       } 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 +4802,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:$?
-- 
2.47.3

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

Reply via email to