diff options
author | jilles <jilles@FreeBSD.org> | 2012-07-29 18:04:38 +0000 |
---|---|---|
committer | jilles <jilles@FreeBSD.org> | 2012-07-29 18:04:38 +0000 |
commit | 0df7adbcbe3c3afbf692f3f73ca539615d63b08b (patch) | |
tree | a060f3bd159c1ec22e9713a49fd8f32e73254f50 /bin | |
parent | debac64ecce4854e463c0578506d8fec17363684 (diff) | |
download | FreeBSD-src-0df7adbcbe3c3afbf692f3f73ca539615d63b08b.zip FreeBSD-src-0df7adbcbe3c3afbf692f3f73ca539615d63b08b.tar.gz |
sh: Fix EINTR race condition in "wait" and "set -T" using sigsuspend().
When waiting for child processes using "wait" or if "set -T" is in effect, a
signal interrupts the wait. Make sure there is no window where the signal
handler may be invoked (setting a flag) just before going to sleep.
There is a similar race condition in the shell language, but scripts can
avoid it by exiting from the trap handler or enforcing synchronization using
a fifo.
If SIGCHLD is not trapped, a signal handler must be installed for it. Only
install this handler for the duration of the wait to avoid triggering
unexpected [EINTR] errors elsewhere.
Note that for some reason only SIGINT and SIGQUIT interrupt a "wait"
command. This remains the case.
Diffstat (limited to 'bin')
-rw-r--r-- | bin/sh/jobs.c | 44 | ||||
-rw-r--r-- | bin/sh/trap.c | 8 | ||||
-rw-r--r-- | bin/sh/trap.h | 1 |
3 files changed, 49 insertions, 4 deletions
diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c index a8ed079..99efc80 100644 --- a/bin/sh/jobs.c +++ b/bin/sh/jobs.c @@ -87,6 +87,10 @@ int in_waitcmd = 0; /* are we in waitcmd()? */ volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */ static int ttyfd = -1; +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signals */ + #if JOBS static void restartjob(struct job *); #endif @@ -518,7 +522,7 @@ waitcmd(int argc, char **argv) break; } } - } while (dowait(1, (struct job *)NULL) != -1); + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); in_waitcmd--; return 0; @@ -965,7 +969,7 @@ waitforjob(struct job *jp, int *origstatus) INTOFF; TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); while (jp->state == 0) - if (dowait(1, jp) == -1) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG : 0), jp) == -1) dotrap(); #if JOBS if (jp->jobctl) { @@ -1003,14 +1007,20 @@ waitforjob(struct job *jp, int *origstatus) } +static void +dummy_handler(int sig) +{ +} /* * Wait for a process to terminate. */ static pid_t -dowait(int block, struct job *job) +dowait(int mode, struct job *job) { + struct sigaction sa, osa; + sigset_t mask, omask; pid_t pid; int status; struct procstat *sp; @@ -1021,8 +1031,22 @@ dowait(int block, struct job *job) int sig; int coredump; int wflags; + int restore_sigchld; TRACE(("dowait(%d) called\n", block)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } do { #if JOBS if (iflag) @@ -1030,13 +1054,25 @@ dowait(int block, struct job *job) else #endif wflags = 0; - if (block == 0) + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) wflags |= WNOHANG; pid = wait3(&status, wflags, (struct rusage *)NULL); TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + sigsuspend(&omask); + pid = -1; + if (int_pending()) + break; + } } while (pid == -1 && errno == EINTR && breakwaitcmd == 0); if (pid == -1 && errno == ECHILD && job != NULL) job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } if (breakwaitcmd != 0) { breakwaitcmd = 0; if (pid <= 0) diff --git a/bin/sh/trap.c b/bin/sh/trap.c index 94cf129..521c511 100644 --- a/bin/sh/trap.c +++ b/bin/sh/trap.c @@ -368,6 +368,14 @@ ignoresig(int signo) } +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + /* * Signal handler. */ diff --git a/bin/sh/trap.h b/bin/sh/trap.h index bf1250a..61a17ec 100644 --- a/bin/sh/trap.h +++ b/bin/sh/trap.h @@ -41,6 +41,7 @@ void clear_traps(void); int have_traps(void); void setsignal(int); void ignoresig(int); +int issigchldtrapped(void); void onsig(int); void dotrap(void); void setinteractive(int); |