diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2020-09-29 20:35:55 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2020-09-29 20:35:55 +0200 |
| commit | 91e11eba6e3ee21f70905bca857b9b216fb764f6 (patch) | |
| tree | af5ff54c4eae189db990e9fe470f790da4308b99 /shell | |
| parent | 8d5f465a206e307bc47d8157b6c90152ddd2ab3c (diff) | |
| download | busybox-w32-91e11eba6e3ee21f70905bca857b9b216fb764f6.tar.gz busybox-w32-91e11eba6e3ee21f70905bca857b9b216fb764f6.tar.bz2 busybox-w32-91e11eba6e3ee21f70905bca857b9b216fb764f6.zip | |
ash: jobs: Fix waitcmd busy loop
Upstream commit:
Date: Tue, 2 Jun 2020 23:46:48 +1000
jobs: Fix waitcmd busy loop
We need to clear gotsigchld in waitproc because it is used as
a loop conditional for the waitcmd case. Without it waitcmd
may busy loop after a SIGCHLD.
This patch also changes gotsigchld into a volatile sig_atomic_t
to prevent compilers from optimising its accesses away.
Fixes: 6c691b3e5099 ("jobs: Only clear gotsigchld when waiting...")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This change also incorporates other changes to bring us closer to upstream.
function old new delta
dowait 553 636 +83
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/ash.c | 91 |
1 files changed, 34 insertions, 57 deletions
diff --git a/shell/ash.c b/shell/ash.c index 13470b2fa..07aa2da2e 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
| @@ -4273,64 +4273,55 @@ sprint_status48(char *os, int status, int sigonly) | |||
| 4273 | return s - os; | 4273 | return s - os; |
| 4274 | } | 4274 | } |
| 4275 | 4275 | ||
| 4276 | #define DOWAIT_NONBLOCK 0 | ||
| 4277 | #define DOWAIT_BLOCK 1 | ||
| 4278 | #define DOWAIT_BLOCK_OR_SIG 2 | ||
| 4279 | #if BASH_WAIT_N | ||
| 4280 | # define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ | ||
| 4281 | #endif | ||
| 4282 | |||
| 4276 | static int | 4283 | static int |
| 4277 | wait_block_or_sig(int *status) | 4284 | waitproc(int block, int *status) |
| 4278 | { | 4285 | { |
| 4279 | int pid; | 4286 | sigset_t oldmask; |
| 4287 | int flags = block == DOWAIT_BLOCK ? 0 : WNOHANG; | ||
| 4288 | int err; | ||
| 4280 | 4289 | ||
| 4281 | do { | 4290 | #if JOBS |
| 4282 | sigset_t mask; | 4291 | if (doing_jobctl) |
| 4292 | flags |= WUNTRACED; | ||
| 4293 | #endif | ||
| 4283 | 4294 | ||
| 4284 | /* Poll all children for changes in their state */ | 4295 | do { |
| 4285 | got_sigchld = 0; | 4296 | got_sigchld = 0; |
| 4286 | /* if job control is active, accept stopped processes too */ | 4297 | do |
| 4287 | pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG); | 4298 | err = waitpid(-1, status, flags); |
| 4288 | if (pid != 0) | 4299 | while (err < 0 && errno == EINTR); |
| 4289 | break; /* Error (e.g. EINTR, ECHILD) or pid */ | ||
| 4290 | 4300 | ||
| 4291 | /* Children exist, but none are ready. Sleep until interesting signal */ | 4301 | if (err || (err = -!block)) |
| 4292 | #if 1 | 4302 | break; |
| 4293 | sigfillset(&mask); | 4303 | |
| 4294 | sigprocmask2(SIG_SETMASK, &mask); /* mask is updated */ | 4304 | sigfillset(&oldmask); |
| 4295 | while (!got_sigchld && !pending_sig) { | 4305 | sigprocmask2(SIG_SETMASK, &oldmask); /* mask is updated */ |
| 4296 | sigsuspend(&mask); | ||
| 4297 | /* ^^^ add "sigdelset(&mask, SIGCHLD);" before sigsuspend | ||
| 4298 | * to make sure SIGCHLD is not masked off? | ||
| 4299 | * It was reported that this: | ||
| 4300 | * fn() { : | return; } | ||
| 4301 | * shopt -s lastpipe | ||
| 4302 | * fn | ||
| 4303 | * exec ash SCRIPT | ||
| 4304 | * under bash 4.4.23 runs SCRIPT with SIGCHLD masked, | ||
| 4305 | * making "wait" commands in SCRIPT block forever. | ||
| 4306 | */ | ||
| 4307 | } | ||
| 4308 | sigprocmask(SIG_SETMASK, &mask, NULL); | ||
| 4309 | #else /* unsafe: a signal can set pending_sig after check, but before pause() */ | ||
| 4310 | while (!got_sigchld && !pending_sig) | 4306 | while (!got_sigchld && !pending_sig) |
| 4311 | pause(); | 4307 | sigsuspend(&oldmask); |
| 4312 | #endif | 4308 | sigprocmask(SIG_SETMASK, &oldmask, NULL); |
| 4309 | //simpler, but unsafe: a signal can set pending_sig after check, but before pause(): | ||
| 4310 | //while (!got_sigchld && !pending_sig) | ||
| 4311 | // pause(); | ||
| 4313 | 4312 | ||
| 4314 | /* If it was SIGCHLD, poll children again */ | ||
| 4315 | } while (got_sigchld); | 4313 | } while (got_sigchld); |
| 4316 | 4314 | ||
| 4317 | return pid; | 4315 | return err; |
| 4318 | } | 4316 | } |
| 4319 | 4317 | ||
| 4320 | #define DOWAIT_NONBLOCK 0 | ||
| 4321 | #define DOWAIT_BLOCK 1 | ||
| 4322 | #define DOWAIT_BLOCK_OR_SIG 2 | ||
| 4323 | #if BASH_WAIT_N | ||
| 4324 | # define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ | ||
| 4325 | #endif | ||
| 4326 | |||
| 4327 | static int | 4318 | static int |
| 4328 | waitone(int block, struct job *job) | 4319 | waitone(int block, struct job *job) |
| 4329 | { | 4320 | { |
| 4330 | int pid; | 4321 | int pid; |
| 4331 | int status; | 4322 | int status; |
| 4332 | struct job *jp; | 4323 | struct job *jp; |
| 4333 | struct job *thisjob; | 4324 | struct job *thisjob = NULL; |
| 4334 | #if BASH_WAIT_N | 4325 | #if BASH_WAIT_N |
| 4335 | bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS); | 4326 | bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS); |
| 4336 | block = (block & ~DOWAIT_JOBSTATUS); | 4327 | block = (block & ~DOWAIT_JOBSTATUS); |
| @@ -4357,21 +4348,8 @@ waitone(int block, struct job *job) | |||
| 4357 | * SIG_DFL handler does not wake sigsuspend(). | 4348 | * SIG_DFL handler does not wake sigsuspend(). |
| 4358 | */ | 4349 | */ |
| 4359 | INT_OFF; | 4350 | INT_OFF; |
| 4360 | if (block == DOWAIT_BLOCK_OR_SIG) { | 4351 | pid = waitproc(block, &status); |
| 4361 | pid = wait_block_or_sig(&status); | 4352 | TRACE(("wait returns pid %d, status=%d\n", pid, status)); |
| 4362 | } else { | ||
| 4363 | int wait_flags = 0; | ||
| 4364 | if (block == DOWAIT_NONBLOCK) | ||
| 4365 | wait_flags = WNOHANG; | ||
| 4366 | /* if job control is active, accept stopped processes too */ | ||
| 4367 | if (doing_jobctl) | ||
| 4368 | wait_flags |= WUNTRACED; | ||
| 4369 | /* NB: _not_ safe_waitpid, we need to detect EINTR */ | ||
| 4370 | pid = waitpid(-1, &status, wait_flags); | ||
| 4371 | } | ||
| 4372 | TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", | ||
| 4373 | pid, status, errno, strerror(errno))); | ||
| 4374 | thisjob = NULL; | ||
| 4375 | if (pid <= 0) | 4353 | if (pid <= 0) |
| 4376 | goto out; | 4354 | goto out; |
| 4377 | 4355 | ||
| @@ -4466,7 +4444,6 @@ dowait(int block, struct job *jp) | |||
| 4466 | rpid = 1; | 4444 | rpid = 1; |
| 4467 | 4445 | ||
| 4468 | do { | 4446 | do { |
| 4469 | got_sigchld = 0; | ||
| 4470 | pid = waitone(block, jp); | 4447 | pid = waitone(block, jp); |
| 4471 | rpid &= !!pid; | 4448 | rpid &= !!pid; |
| 4472 | 4449 | ||
| @@ -4721,7 +4698,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv) | |||
| 4721 | job = getjob(*argv, 0); | 4698 | job = getjob(*argv, 0); |
| 4722 | } | 4699 | } |
| 4723 | /* loop until process terminated or stopped */ | 4700 | /* loop until process terminated or stopped */ |
| 4724 | dowait(DOWAIT_BLOCK_OR_SIG, NULL); | 4701 | dowait(DOWAIT_BLOCK_OR_SIG, job); |
| 4725 | if (pending_sig) | 4702 | if (pending_sig) |
| 4726 | goto sigout; | 4703 | goto sigout; |
| 4727 | job->waited = 1; | 4704 | job->waited = 1; |
