summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authorjilles <jilles@FreeBSD.org>2012-07-29 18:04:38 +0000
committerjilles <jilles@FreeBSD.org>2012-07-29 18:04:38 +0000
commit0df7adbcbe3c3afbf692f3f73ca539615d63b08b (patch)
treea060f3bd159c1ec22e9713a49fd8f32e73254f50 /bin
parentdebac64ecce4854e463c0578506d8fec17363684 (diff)
downloadFreeBSD-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.c44
-rw-r--r--bin/sh/trap.c8
-rw-r--r--bin/sh/trap.h1
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);
OpenPOWER on IntegriCloud