diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2020-09-29 20:35:55 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2021-01-01 14:30:58 +0100 |
commit | e7629ffbf5c697a0fa805e2439c278e019d84835 (patch) | |
tree | ce0165773d1c957643b9c4968a180a3cafc998d0 | |
parent | e4c7cf3f8cc67bd2b9aeb4d622c38bad515b09a0 (diff) | |
download | busybox-w32-e7629ffbf5c697a0fa805e2439c278e019d84835.tar.gz busybox-w32-e7629ffbf5c697a0fa805e2439c278e019d84835.tar.bz2 busybox-w32-e7629ffbf5c697a0fa805e2439c278e019d84835.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>
-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; |