← Commit list

db7dfff80

Denys Vlasenko
Denys Vlasenko
2026-10-05
SUCCESS Duration: 193s

Subject

ash: make "wait -n" pick up last job, if there is no running processes

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

shell/ash.c+68 -36
@@ -4380,15 +4380,21 @@ waitproc(int block, int *status)
return pid;
}
-static int waitone(int block, struct job *job)
+#if !BASH_WAIT_N
+#define waitone(block, status, job) \
+ waitone(block, job)
+#endif
+static int waitone(int block, int *status, struct job *job)
{
int pid;
- int status;
struct job *jp;
struct job *thisjob = NULL;
#if BASH_WAIT_N
bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS);
block = (block & ~DOWAIT_JOBSTATUS);
+#else
+ int nstatus;
+#define status (&nstatus)
#endif
TRACE(("waitone(0x%x) called\n", block));
@@ -4412,8 +4418,8 @@ static int waitone(int block, struct job *job)
* SIG_DFL handler does not wake sigsuspend().
*/
INTOFF;
- pid = waitproc(block, &status);
- TRACE(("waitproc returns pid %d, status=%d\n", pid, status));
+ pid = waitproc(block, status);
+ TRACE(("waitproc returns pid %d, status=%d\n", pid, *status));
if (pid <= 0)
goto out;
@@ -4428,8 +4434,8 @@ static int waitone(int block, struct job *job)
sp = jp->ps;
do {
if (sp->ps_pid == pid) {
- TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->ps_status, status));
- sp->ps_status = status;
+ TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->ps_status, *status));
+ sp->ps_status = *status;
thisjob = jp;
}
if (sp->ps_status == -1)
@@ -4470,19 +4476,20 @@ static int waitone(int block, struct job *job)
#if BASH_WAIT_N
if (want_jobexitstatus) {
- pid = -1;
if (thisjob && thisjob->state == JOBDONE)
- pid = thisjob->ps[thisjob->nprocs - 1].ps_status;
- /* else: the job is not found, or not fully completed:
- * "wait -n" should NOT yet finish. Giving it -1, it will retry.
- */
+ *status = thisjob->ps[thisjob->nprocs - 1].ps_status;
+ else {
+ /* The job is not found, or not fully completed:
+ * "wait -n" should NOT yet finish. Giving it -1, it will retry. */
+ *status = -1;
+ }
}
#endif
if (thisjob && thisjob == job) {
char s[48 + 1];
int len;
- len = sprint_status48(s, status, 1);
+ len = sprint_status48(s, *status, 1);
if (len) {
s[len] = '\n';
s[len + 1] = '\0';
@@ -4490,19 +4497,29 @@ static int waitone(int block, struct job *job)
}
}
return pid;
+#undef status
}
-static int dowait(int block, struct job *jp)
+#if !BASH_WAIT_N
+#define dowait(block, status, jp) \
+ dowait(block, jp)
+#endif
+static int dowait(int block, int *status, struct job *jp)
{
smallint gotchld = *(volatile smallint *)&gotsigchld;
int pid;
+ int local_status;
if (jp && jp->state != JOBRUNNING)
block = DOWAIT_NONBLOCK;
-
if (block == DOWAIT_NONBLOCK && !gotchld)
return 1;
+#if BASH_WAIT_N
+ if (!status)
+ status = &local_status;
+#endif
+
/* In what cases we loop here:
* = If we waited in blocking mode and got pid > 0,
* but the job we waiting for is still running
@@ -4520,9 +4537,9 @@ static int dowait(int block, struct job *jp)
* one nonblocking wait and if it gives nothing, wait for signals.
*/
do {
- pid = waitone(block, jp);
+ pid = waitone(block, status, jp);
if (block & DOWAIT_JOBSTATUS)
- break; /* waitone() gave us the status, not pid (for "wait -n") */
+ break;
/* on signals, unknown pids, or not-fully completed jobs we get -1 */
/* it is always DOWAIT_CHILD_OR_SIG in this case */
if (pid == 0)
@@ -4594,7 +4611,7 @@ waitforjob(struct job *jp)
* ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
* $ _
*/
- dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp);
+ dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, /*status:*/ NULL, jp);
if (!jp)
return exitstatus;
@@ -4709,7 +4726,7 @@ showjobs(int mode)
TRACE(("showjobs(0x%x) called\n", mode));
/* Handle all finished jobs */
- dowait(DOWAIT_NONBLOCK, NULL);
+ dowait(DOWAIT_NONBLOCK, /*status:*/ NULL, /*job:*/ NULL);
for (jp = curjob; jp; jp = jp->prev_job) {
if (!(mode & SHOW_CHANGED) || jp->changed) {
@@ -4841,11 +4858,13 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
{
struct job *job;
int retval;
+ pid_t pid;
struct job *jp;
#if BASH_WAIT_N
int status;
- char one = nextopt("n");
+ char wait_n = nextopt("n");
#else
+ const int wait_n = 0;
nextopt(nullstr);
#endif
retval = 0;
@@ -4855,14 +4874,12 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
/* wait for all jobs / one job if -n */
for (;;) {
jp = curjob;
-#if BASH_WAIT_N
- if (one && !jp)
- /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */
- retval = 127;
-#endif
while (1) {
- if (!jp) /* no running procs */
+ if (!jp) { /* no running procs */
+ if (wait_n) /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */
+ retval = 127;
goto ret;
+ }
if (jp->state == JOBRUNNING)
break;
jp->waited = 1;
@@ -4876,9 +4893,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
* the trap is executed."
*/
#if BASH_WAIT_N
- status = dowait(DOWAIT_CHILD_OR_SIG | DOWAIT_JOBSTATUS, NULL);
+ pid = dowait(DOWAIT_CHILD_OR_SIG | DOWAIT_JOBSTATUS, &status, /*job:*/ NULL);
#else
- dowait(DOWAIT_CHILD_OR_SIG, NULL);
+ dowait(DOWAIT_CHILD_OR_SIG, /*status:*/ NULL, /*job:*/ NULL);
#endif
/* if child sends us a signal *and immediately exits*,
* dowait() returns pid > 0. Check this case,
@@ -4887,26 +4904,41 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
if (pending_sig)
goto sigout;
#if BASH_WAIT_N
- if (one) {
+ if (wait_n) {
/* 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.
*/
- if (status != -1 && !WIFSTOPPED(status)) {
- retval = WEXITSTATUS(status);
- if (WIFSIGNALED(status))
- retval = 128 | WTERMSIG(status);
- goto ret;
+ if (pid > 0) { /* some process did exit */
+ if (status != -1) {
+# if JOBS /* only possible if JOBS */
+ if (WIFSTOPPED(status))
+ continue; /* not interested in stops, find another process */
+# endif
+ retval = WEXITSTATUS(status);
+ if (WIFSIGNALED(status))
+ retval = 128 | WTERMSIG(status);
+ goto ret;
+ }
+ /* else: not entire job has exited (only a process), wait more */
+ } else {
+ /* There are no alive jobs. Are there dead ones? */
+ /* (bash-5.3.9 does this) */
+ if (curjob) {
+ retval = getstatus(curjob);
+ freejob(curjob);
+ goto ret;
+ }
}
}
#endif
- }
+ } /* for (;;) */
}
retval = 127;
do {
if (**argv != '%') {
- pid_t pid = number(*argv);
+ pid = number(*argv);
job = curjob;
while (1) {
if (!job)
@@ -4919,7 +4951,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
job = getjob(*argv, 0);
}
/* loop until process terminated or stopped */
- dowait(DOWAIT_CHILD_OR_SIG, job);
+ dowait(DOWAIT_CHILD_OR_SIG, /*status:*/ NULL, job);
if (pending_sig)
goto sigout;
job->waited = 1;
shell/ash_test/ash-misc/wait_and_signals.right+6 -0
@@ -0,0 +1,6 @@
+wait -n
+from USR1
+prints after USR1
+wait
+from USR1
+prints after USR1 (2)
shell/ash_test/ash-misc/wait_and_signals.tests+21 -0
@@ -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: $?"
shell/ash_test/ash-misc/wait_n_waits_for_already_waited_for_job.right+5 -0
@@ -0,0 +1,5 @@
+Sent SIGTERM:0
+Test: wait -n exit code for sigkilled (and possibly already waited-for) pipe
+wait -n #1: 143 (should be 143 = 128+SIGTERM)
+Test: wait -n exits 127 if nothing to wait for
+wait -n #1: 127 (should be 127)
shell/ash_test/ash-misc/wait_n_waits_for_already_waited_for_job.tests+15 -0
@@ -0,0 +1,15 @@
+sleep 1 && echo 'Should not be reached' &
+bg=$!
+
+kill $bg
+echo "Sent SIGTERM:$?"
+
+# The internal state depends on whether shell manages to see the death of $bg before entering "wait -n":
+# will the job be already "dead and waited-for"? In the latter case, the correctness is harder to achieve.
+echo "Test: wait -n exit code for sigkilled (and possibly already waited-for) pipe"
+wait -n
+echo "wait -n #1: $? (should be 143 = 128+SIGTERM)"
+echo "Test: wait -n exits 127 if nothing to wait for"
+wait -n
+echo "wait -n #1: $? (should be 127)"
+
shell/hush_test/hush-misc/wait_and_signals.right+6 -0
@@ -0,0 +1,6 @@
+wait -n
+from USR1
+prints after USR1
+wait
+from USR1
+prints after USR1 (2)
shell/hush_test/hush-misc/wait_and_signals.tests+21 -0
@@ -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: $?"