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);
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to