Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 29 Jul 2012 18:04:38 +0000 (UTC)
From:      Jilles Tjoelker <jilles@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r238888 - head/bin/sh
Message-ID:  <201207291804.q6TI4cix080438@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jilles
Date: Sun Jul 29 18:04:38 2012
New Revision: 238888
URL: http://svn.freebsd.org/changeset/base/238888

Log:
  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.

Modified:
  head/bin/sh/jobs.c
  head/bin/sh/trap.c
  head/bin/sh/trap.h

Modified: head/bin/sh/jobs.c
==============================================================================
--- head/bin/sh/jobs.c	Sun Jul 29 14:21:42 2012	(r238887)
+++ head/bin/sh/jobs.c	Sun Jul 29 18:04:38 2012	(r238888)
@@ -87,6 +87,10 @@ int in_waitcmd = 0;		/* are we in waitcm
 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 *origstat
 	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 *origstat
 }
 
 
+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)

Modified: head/bin/sh/trap.c
==============================================================================
--- head/bin/sh/trap.c	Sun Jul 29 14:21:42 2012	(r238887)
+++ head/bin/sh/trap.c	Sun Jul 29 18:04:38 2012	(r238888)
@@ -368,6 +368,14 @@ ignoresig(int signo)
 }
 
 
+int
+issigchldtrapped(void)
+{
+
+	return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0');
+}
+
+
 /*
  * Signal handler.
  */

Modified: head/bin/sh/trap.h
==============================================================================
--- head/bin/sh/trap.h	Sun Jul 29 14:21:42 2012	(r238887)
+++ head/bin/sh/trap.h	Sun Jul 29 18:04:38 2012	(r238888)
@@ -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);



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201207291804.q6TI4cix080438>