Browse Source

Merge branch 'upstream-kwsys' into update-kwsys

Brad King 10 years ago
parent
commit
d0915bc86f

+ 5 - 1
Source/kwsys/CMakeLists.txt

@@ -1237,7 +1237,7 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
     IF(NOT CYGWIN)
       SET(KWSYS_TEST_PROCESS_7 7)
     ENDIF()
-    FOREACH(n 1 2 3 4 5 6 ${KWSYS_TEST_PROCESS_7})
+    FOREACH(n 1 2 3 4 5 6 ${KWSYS_TEST_PROCESS_7} 9 10)
       ADD_TEST(kwsys.testProcess-${n} ${EXEC_DIR}/${KWSYS_NAMESPACE}TestProcess ${n})
       SET_PROPERTY(TEST kwsys.testProcess-${n} PROPERTY LABELS ${KWSYS_LABELS_TEST})
       SET_TESTS_PROPERTIES(kwsys.testProcess-${n} PROPERTIES TIMEOUT 120)
@@ -1270,6 +1270,10 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
       MESSAGE(STATUS "GET_TEST_PROPERTY returned: ${wfv}")
     ENDIF()
 
+    # Set up ctest custom configuration file.
+    CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/CTestCustom.cmake.in
+                   ${PROJECT_BINARY_DIR}/CTestCustom.cmake @ONLY)
+
     # Suppress known consistent failures on buggy systems.
     IF(KWSYS_TEST_BOGUS_FAILURES)
       SET_TESTS_PROPERTIES(${KWSYS_TEST_BOGUS_FAILURES} PROPERTIES WILL_FAIL ON)

+ 15 - 0
Source/kwsys/CTestCustom.cmake.in

@@ -0,0 +1,15 @@
+# kwsys.testProcess-10 involves sending SIGINT to a child process, which then
+# exits abnormally via a call to _exit(). (On Windows, a call to ExitProcess).
+# Naturally, this results in plenty of memory being "leaked" by this child
+# process - the memory check results are not meaningful in this case.
+#
+# kwsys.testProcess-9 also tests sending SIGINT to a child process.  However,
+# normal operation of that test involves the child process timing out, and the
+# host process kills (SIGKILL) it as a result.  Since it was SIGKILL'ed, the
+# resulting memory leaks are not logged by valgrind anyway.  Therefore, we
+# don't have to exclude it.
+
+set(CTEST_CUSTOM_MEMCHECK_IGNORE
+  ${CTEST_CUSTOM_MEMCHECK_IGNORE}
+  kwsys.testProcess-10
+  )

+ 78 - 53
Source/kwsys/Process.h.in

@@ -23,58 +23,60 @@
 # define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT
 #endif
 #if !@KWSYS_NAMESPACE@_NAME_IS_KWSYS
-# define kwsysProcess                     kwsys_ns(Process)
-# define kwsysProcess_s                   kwsys_ns(Process_s)
-# define kwsysProcess_New                 kwsys_ns(Process_New)
-# define kwsysProcess_Delete              kwsys_ns(Process_Delete)
-# define kwsysProcess_SetCommand          kwsys_ns(Process_SetCommand)
-# define kwsysProcess_AddCommand          kwsys_ns(Process_AddCommand)
-# define kwsysProcess_SetTimeout          kwsys_ns(Process_SetTimeout)
-# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
-# define kwsysProcess_SetPipeFile         kwsys_ns(Process_SetPipeFile)
-# define kwsysProcess_SetPipeNative       kwsys_ns(Process_SetPipeNative)
-# define kwsysProcess_SetPipeShared       kwsys_ns(Process_SetPipeShared)
-# define kwsysProcess_Option_Detach       kwsys_ns(Process_Option_Detach)
-# define kwsysProcess_Option_HideWindow   kwsys_ns(Process_Option_HideWindow)
-# define kwsysProcess_Option_MergeOutput  kwsys_ns(Process_Option_MergeOutput)
-# define kwsysProcess_Option_Verbatim     kwsys_ns(Process_Option_Verbatim)
-# define kwsysProcess_GetOption           kwsys_ns(Process_GetOption)
-# define kwsysProcess_SetOption           kwsys_ns(Process_SetOption)
-# define kwsysProcess_Option_e            kwsys_ns(Process_Option_e)
-# define kwsysProcess_State_Starting      kwsys_ns(Process_State_Starting)
-# define kwsysProcess_State_Error         kwsys_ns(Process_State_Error)
-# define kwsysProcess_State_Exception     kwsys_ns(Process_State_Exception)
-# define kwsysProcess_State_Executing     kwsys_ns(Process_State_Executing)
-# define kwsysProcess_State_Exited        kwsys_ns(Process_State_Exited)
-# define kwsysProcess_State_Expired       kwsys_ns(Process_State_Expired)
-# define kwsysProcess_State_Killed        kwsys_ns(Process_State_Killed)
-# define kwsysProcess_State_Disowned      kwsys_ns(Process_State_Disowned)
-# define kwsysProcess_GetState            kwsys_ns(Process_GetState)
-# define kwsysProcess_State_e             kwsys_ns(Process_State_e)
-# define kwsysProcess_Exception_None      kwsys_ns(Process_Exception_None)
-# define kwsysProcess_Exception_Fault     kwsys_ns(Process_Exception_Fault)
-# define kwsysProcess_Exception_Illegal   kwsys_ns(Process_Exception_Illegal)
-# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
-# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
-# define kwsysProcess_Exception_Other     kwsys_ns(Process_Exception_Other)
-# define kwsysProcess_GetExitException    kwsys_ns(Process_GetExitException)
-# define kwsysProcess_Exception_e         kwsys_ns(Process_Exception_e)
-# define kwsysProcess_GetExitCode         kwsys_ns(Process_GetExitCode)
-# define kwsysProcess_GetExitValue        kwsys_ns(Process_GetExitValue)
-# define kwsysProcess_GetErrorString      kwsys_ns(Process_GetErrorString)
-# define kwsysProcess_GetExceptionString  kwsys_ns(Process_GetExceptionString)
-# define kwsysProcess_Execute             kwsys_ns(Process_Execute)
-# define kwsysProcess_Disown              kwsys_ns(Process_Disown)
-# define kwsysProcess_WaitForData         kwsys_ns(Process_WaitForData)
-# define kwsysProcess_Pipes_e             kwsys_ns(Process_Pipes_e)
-# define kwsysProcess_Pipe_None           kwsys_ns(Process_Pipe_None)
-# define kwsysProcess_Pipe_STDIN          kwsys_ns(Process_Pipe_STDIN)
-# define kwsysProcess_Pipe_STDOUT         kwsys_ns(Process_Pipe_STDOUT)
-# define kwsysProcess_Pipe_STDERR         kwsys_ns(Process_Pipe_STDERR)
-# define kwsysProcess_Pipe_Timeout        kwsys_ns(Process_Pipe_Timeout)
-# define kwsysProcess_Pipe_Handle         kwsys_ns(Process_Pipe_Handle)
-# define kwsysProcess_WaitForExit         kwsys_ns(Process_WaitForExit)
-# define kwsysProcess_Kill                kwsys_ns(Process_Kill)
+# define kwsysProcess                           kwsys_ns(Process)
+# define kwsysProcess_s                         kwsys_ns(Process_s)
+# define kwsysProcess_New                       kwsys_ns(Process_New)
+# define kwsysProcess_Delete                    kwsys_ns(Process_Delete)
+# define kwsysProcess_SetCommand                kwsys_ns(Process_SetCommand)
+# define kwsysProcess_AddCommand                kwsys_ns(Process_AddCommand)
+# define kwsysProcess_SetTimeout                kwsys_ns(Process_SetTimeout)
+# define kwsysProcess_SetWorkingDirectory       kwsys_ns(Process_SetWorkingDirectory)
+# define kwsysProcess_SetPipeFile               kwsys_ns(Process_SetPipeFile)
+# define kwsysProcess_SetPipeNative             kwsys_ns(Process_SetPipeNative)
+# define kwsysProcess_SetPipeShared             kwsys_ns(Process_SetPipeShared)
+# define kwsysProcess_Option_Detach             kwsys_ns(Process_Option_Detach)
+# define kwsysProcess_Option_HideWindow         kwsys_ns(Process_Option_HideWindow)
+# define kwsysProcess_Option_MergeOutput        kwsys_ns(Process_Option_MergeOutput)
+# define kwsysProcess_Option_Verbatim           kwsys_ns(Process_Option_Verbatim)
+# define kwsysProcess_Option_CreateProcessGroup kwsys_ns(Process_Option_CreateProcessGroup)
+# define kwsysProcess_GetOption                 kwsys_ns(Process_GetOption)
+# define kwsysProcess_SetOption                 kwsys_ns(Process_SetOption)
+# define kwsysProcess_Option_e                  kwsys_ns(Process_Option_e)
+# define kwsysProcess_State_Starting            kwsys_ns(Process_State_Starting)
+# define kwsysProcess_State_Error               kwsys_ns(Process_State_Error)
+# define kwsysProcess_State_Exception           kwsys_ns(Process_State_Exception)
+# define kwsysProcess_State_Executing           kwsys_ns(Process_State_Executing)
+# define kwsysProcess_State_Exited              kwsys_ns(Process_State_Exited)
+# define kwsysProcess_State_Expired             kwsys_ns(Process_State_Expired)
+# define kwsysProcess_State_Killed              kwsys_ns(Process_State_Killed)
+# define kwsysProcess_State_Disowned            kwsys_ns(Process_State_Disowned)
+# define kwsysProcess_GetState                  kwsys_ns(Process_GetState)
+# define kwsysProcess_State_e                   kwsys_ns(Process_State_e)
+# define kwsysProcess_Exception_None            kwsys_ns(Process_Exception_None)
+# define kwsysProcess_Exception_Fault           kwsys_ns(Process_Exception_Fault)
+# define kwsysProcess_Exception_Illegal         kwsys_ns(Process_Exception_Illegal)
+# define kwsysProcess_Exception_Interrupt       kwsys_ns(Process_Exception_Interrupt)
+# define kwsysProcess_Exception_Numerical       kwsys_ns(Process_Exception_Numerical)
+# define kwsysProcess_Exception_Other           kwsys_ns(Process_Exception_Other)
+# define kwsysProcess_GetExitException          kwsys_ns(Process_GetExitException)
+# define kwsysProcess_Exception_e               kwsys_ns(Process_Exception_e)
+# define kwsysProcess_GetExitCode               kwsys_ns(Process_GetExitCode)
+# define kwsysProcess_GetExitValue              kwsys_ns(Process_GetExitValue)
+# define kwsysProcess_GetErrorString            kwsys_ns(Process_GetErrorString)
+# define kwsysProcess_GetExceptionString        kwsys_ns(Process_GetExceptionString)
+# define kwsysProcess_Execute                   kwsys_ns(Process_Execute)
+# define kwsysProcess_Disown                    kwsys_ns(Process_Disown)
+# define kwsysProcess_WaitForData               kwsys_ns(Process_WaitForData)
+# define kwsysProcess_Pipes_e                   kwsys_ns(Process_Pipes_e)
+# define kwsysProcess_Pipe_None                 kwsys_ns(Process_Pipe_None)
+# define kwsysProcess_Pipe_STDIN                kwsys_ns(Process_Pipe_STDIN)
+# define kwsysProcess_Pipe_STDOUT               kwsys_ns(Process_Pipe_STDOUT)
+# define kwsysProcess_Pipe_STDERR               kwsys_ns(Process_Pipe_STDERR)
+# define kwsysProcess_Pipe_Timeout              kwsys_ns(Process_Pipe_Timeout)
+# define kwsysProcess_Pipe_Handle               kwsys_ns(Process_Pipe_Handle)
+# define kwsysProcess_WaitForExit               kwsys_ns(Process_WaitForExit)
+# define kwsysProcess_Interrupt                 kwsys_ns(Process_Interrupt)
+# define kwsysProcess_Kill                      kwsys_ns(Process_Kill)
 #endif
 
 #if defined(__cplusplus)
@@ -199,6 +201,15 @@ kwsysEXPORT void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe,
  *                                 and ignore the rest of the arguments.
  *         0 = No (default)
  *         1 = Yes
+ *
+ *  kwsysProcess_Option_CreateProcessGroup = Whether to place the process in a
+ *                                           new process group.  This is
+ *                                           useful if you want to send Ctrl+C
+ *                                           to the process.  On UNIX, also
+ *                                           places the process in a new
+ *                                           session.
+ *         0 = No (default)
+ *         1 = Yes
  */
 kwsysEXPORT int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
 kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
@@ -208,7 +219,8 @@ enum kwsysProcess_Option_e
   kwsysProcess_Option_HideWindow,
   kwsysProcess_Option_Detach,
   kwsysProcess_Option_MergeOutput,
-  kwsysProcess_Option_Verbatim
+  kwsysProcess_Option_Verbatim,
+  kwsysProcess_Option_CreateProcessGroup
 };
 
 /**
@@ -362,6 +374,17 @@ enum kwsysProcess_Pipes_e
  */
 kwsysEXPORT int kwsysProcess_WaitForExit(kwsysProcess* cp, double* timeout);
 
+/**
+ * Interrupt the process group for the child process that is currently
+ * running by sending it the appropriate operating-system specific signal.
+ * The caller should call WaitForExit after this returns to wait for the
+ * child to terminate.
+ *
+ * WARNING:  If you didn't specify kwsysProcess_Option_CreateProcessGroup,
+ * you will interrupt your own process group.
+ */
+kwsysEXPORT void kwsysProcess_Interrupt(kwsysProcess* cp);
+
 /**
  * Forcefully terminate the child process that is currently running.
  * The caller should call WaitForExit after this returns to wait for
@@ -394,6 +417,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
 #  undef kwsysProcess_Option_HideWindow
 #  undef kwsysProcess_Option_MergeOutput
 #  undef kwsysProcess_Option_Verbatim
+#  undef kwsysProcess_Option_CreateProcessGroup
 #  undef kwsysProcess_GetOption
 #  undef kwsysProcess_SetOption
 #  undef kwsysProcess_Option_e
@@ -430,6 +454,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
 #  undef kwsysProcess_Pipe_Timeout
 #  undef kwsysProcess_Pipe_Handle
 #  undef kwsysProcess_WaitForExit
+#  undef kwsysProcess_Interrupt
 #  undef kwsysProcess_Kill
 # endif
 #endif

+ 307 - 50
Source/kwsys/ProcessUNIX.c

@@ -88,7 +88,7 @@ typedef ssize_t kwsysProcess_ssize_t;
 typedef int kwsysProcess_ssize_t;
 #endif
 
-#if defined(__BEOS__) && !defined(__ZETA__) 
+#if defined(__BEOS__) && !defined(__ZETA__)
 /* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
 # include <be/kernel/OS.h>
 static inline void kwsysProcess_usleep(unsigned int msec)
@@ -151,6 +151,7 @@ typedef struct kwsysProcessCreateInformation_s
 } kwsysProcessCreateInformation;
 
 /*--------------------------------------------------------------------------*/
+static void kwsysProcessVolatileFree(volatile void* p);
 static int kwsysProcessInitialize(kwsysProcess* cp);
 static void kwsysProcessCleanup(kwsysProcess* cp, int error);
 static void kwsysProcessCleanupDescriptor(int* pfd);
@@ -197,7 +198,7 @@ struct kwsysProcess_s
 {
   /* The command lines to execute.  */
   char*** Commands;
-  int NumberOfCommands;
+  volatile int NumberOfCommands;
 
   /* Descriptors for the read ends of the child's output pipes and
      the signal pipe. */
@@ -213,8 +214,10 @@ struct kwsysProcess_s
   /* Buffer for pipe data.  */
   char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE];
 
-  /* Process IDs returned by the calls to fork.  */
-  pid_t* ForkPIDs;
+  /* Process IDs returned by the calls to fork.  Everything is volatile
+     because the signal handler accesses them.  You must be very careful
+     when reaping PIDs or modifying this array to avoid race conditions.  */
+  volatile pid_t* volatile ForkPIDs;
 
   /* Flag for whether the children were terminated by a faild select.  */
   int SelectError;
@@ -237,6 +240,9 @@ struct kwsysProcess_s
   /* Whether to merge stdout/stderr of the child.  */
   int MergeOutput;
 
+  /* Whether to create the process in a new process group.  */
+  volatile sig_atomic_t CreateProcessGroup;
+
   /* Time at which the child started.  Negative for no timeout.  */
   kwsysProcessTime StartTime;
 
@@ -257,8 +263,9 @@ struct kwsysProcess_s
   /* The number of children still executing.  */
   int CommandsLeft;
 
-  /* The current status of the child process. */
-  int State;
+  /* The current status of the child process.  Must be atomic because
+     the signal handler checks this to avoid a race.  */
+  volatile sig_atomic_t State;
 
   /* The exceptional behavior that terminated the child process, if
    * any.  */
@@ -271,7 +278,7 @@ struct kwsysProcess_s
   int ExitValue;
 
   /* Whether the process was killed.  */
-  int Killed;
+  volatile sig_atomic_t Killed;
 
   /* Buffer for error message in case of failure.  */
   char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1];
@@ -649,6 +656,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
     case kwsysProcess_Option_Detach: return cp->OptionDetach;
     case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
     case kwsysProcess_Option_Verbatim: return cp->Verbatim;
+    case kwsysProcess_Option_CreateProcessGroup:
+      return cp->CreateProcessGroup;
     default: return 0;
     }
 }
@@ -666,6 +675,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
     case kwsysProcess_Option_Detach: cp->OptionDetach = value; break;
     case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
     case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
+    case kwsysProcess_Option_CreateProcessGroup:
+      cp->CreateProcessGroup = value; break;
     default: break;
     }
 }
@@ -1489,6 +1500,45 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
   return 1;
 }
 
+/*--------------------------------------------------------------------------*/
+void kwsysProcess_Interrupt(kwsysProcess* cp)
+{
+  int i;
+  /* Make sure we are executing a process.  */
+  if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
+     cp->Killed)
+    {
+    return;
+    }
+
+  /* Interrupt the children.  */
+  if (cp->CreateProcessGroup)
+    {
+    if(cp->ForkPIDs)
+      {
+      for(i=0; i < cp->NumberOfCommands; ++i)
+        {
+        /* Make sure the PID is still valid. */
+        if(cp->ForkPIDs[i])
+          {
+          /* The user created a process group for this process.  The group ID
+             is the process ID for the original process in the group.  */
+          kill(-cp->ForkPIDs[i], SIGINT);
+          }
+        }
+      }
+    }
+  else
+    {
+    /* No process group was created.  Kill our own process group.
+       NOTE:  While one could argue that we could call kill(cp->ForkPIDs[i],
+       SIGINT) as a way to still interrupt the process even though it's not in
+       a special group, this is not an option on Windows.  Therefore, we kill
+       the current process group for consistency with Windows.  */
+    kill(0, SIGINT);
+    }
+}
+
 /*--------------------------------------------------------------------------*/
 void kwsysProcess_Kill(kwsysProcess* cp)
 {
@@ -1538,11 +1588,29 @@ void kwsysProcess_Kill(kwsysProcess* cp)
   cp->CommandsLeft = 0;
 }
 
+/*--------------------------------------------------------------------------*/
+/* Call the free() function with a pointer to volatile without causing
+   compiler warnings.  */
+static void kwsysProcessVolatileFree(volatile void* p)
+{
+  /* clang has made it impossible to free memory that points to volatile
+     without first using special pragmas to disable a warning...  */
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+  free((void*)p); /* The cast will silence most compilers, but not clang.  */
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+}
+
 /*--------------------------------------------------------------------------*/
 /* Initialize a process control structure for kwsysProcess_Execute.  */
 static int kwsysProcessInitialize(kwsysProcess* cp)
 {
   int i;
+  volatile pid_t* oldForkPIDs;
   for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
     {
     cp->PipeReadEnds[i] = -1;
@@ -1571,16 +1639,21 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
   cp->ErrorMessage[0] = 0;
   strcpy(cp->ExitExceptionString, "No exception");
 
-  if(cp->ForkPIDs)
+  oldForkPIDs = cp->ForkPIDs;
+  cp->ForkPIDs = (volatile pid_t*)malloc(
+    sizeof(volatile pid_t)*(size_t)(cp->NumberOfCommands));
+  if(oldForkPIDs)
     {
-    free(cp->ForkPIDs);
+    kwsysProcessVolatileFree(oldForkPIDs);
     }
-  cp->ForkPIDs = (pid_t*)malloc(sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
   if(!cp->ForkPIDs)
     {
     return 0;
     }
-  memset(cp->ForkPIDs, 0, sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
+  for(i=0; i < cp->NumberOfCommands; ++i)
+    {
+    cp->ForkPIDs[i] = 0; /* can't use memset due to volatile */
+    }
 
   if(cp->CommandExitCodes)
     {
@@ -1671,7 +1744,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
   /* Free memory.  */
   if(cp->ForkPIDs)
     {
-    free(cp->ForkPIDs);
+    kwsysProcessVolatileFree(cp->ForkPIDs);
     cp->ForkPIDs = 0;
     }
   if(cp->RealWorkingDirectory)
@@ -1758,15 +1831,49 @@ int decc$set_child_standard_streams(int fd1, int fd2, int fd3);
 static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
                               kwsysProcessCreateInformation* si)
 {
+  sigset_t mask, old_mask;
+  int pgidPipe[2];
+  char tmp;
+  ssize_t readRes;
+
   /* Create the error reporting pipe.  */
   if(pipe(si->ErrorPipe) < 0)
     {
     return 0;
     }
 
-  /* Set close-on-exec flag on the error pipe's write end.  */
-  if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0)
+  /* Create a pipe for detecting that the child process has created a process
+     group and session.  */
+  if(pipe(pgidPipe) < 0)
     {
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+    return 0;
+    }
+
+  /* Set close-on-exec flag on the pipe's write end.  */
+  if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0 ||
+     fcntl(pgidPipe[1], F_SETFD, FD_CLOEXEC) < 0)
+    {
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[1]);
+    return 0;
+    }
+
+  /* Block SIGINT / SIGTERM while we start.  The purpose is so that our signal
+     handler doesn't get called from the child process after the fork and
+     before the exec, and subsequently start kill()'ing PIDs from ForkPIDs. */
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGINT);
+  sigaddset(&mask, SIGTERM);
+  if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
+    {
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[1]);
     return 0;
     }
 
@@ -1774,13 +1881,19 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
 #if defined(__VMS)
   /* VMS needs vfork and execvp to be in the same function because
      they use setjmp/longjmp to run the child startup code in the
-     parent!  TODO: OptionDetach.  */
+     parent!  TODO: OptionDetach.  Also
+     TODO:  CreateProcessGroup.  */
   cp->ForkPIDs[prIndex] = vfork();
 #else
   cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si);
 #endif
   if(cp->ForkPIDs[prIndex] < 0)
     {
+    sigprocmask(SIG_SETMASK, &old_mask, 0);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[1]);
     return 0;
     }
 
@@ -1790,8 +1903,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
     /* Specify standard pipes for child process.  */
     decc$set_child_standard_streams(si->StdIn, si->StdOut, si->StdErr);
 #else
-    /* Close the read end of the error reporting pipe.  */
+    /* Close the read end of the error reporting / process group
+       setup pipe.  */
     close(si->ErrorPipe[0]);
+    close(pgidPipe[0]);
 
     /* Setup the stdin, stdout, and stderr pipes.  */
     if(si->StdIn > 0)
@@ -1819,11 +1934,25 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
 
     /* Restore all default signal handlers. */
     kwsysProcessRestoreDefaultSignalHandlers();
+
+    /* Now that we have restored default signal handling and created the
+       process group, restore mask.  */
+    sigprocmask(SIG_SETMASK, &old_mask, 0);
+
+    /* Create new process group.  We use setsid instead of setpgid to avoid
+       the child getting hung up on signals like SIGTTOU.  (In the real world,
+       this has been observed where "git svn" ends up calling the "resize"
+       program which opens /dev/tty.  */
+    if(cp->CreateProcessGroup && setsid() < 0)
+      {
+      kwsysProcessChildErrorExit(si->ErrorPipe[1]);
+      }
 #endif
 
     /* Execute the real process.  If successful, this does not return.  */
     execvp(cp->Commands[prIndex][0], cp->Commands[prIndex]);
     /* TODO: What does VMS do if the child fails to start?  */
+    /* TODO: On VMS, how do we put the process in a new group?  */
 
     /* Failure.  Report error to parent and terminate.  */
     kwsysProcessChildErrorExit(si->ErrorPipe[1]);
@@ -1834,12 +1963,34 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
   decc$set_child_standard_streams(0, 1, 2);
 #endif
 
+  /* We are done with the error reporting pipe and process group setup pipe
+     write end.  */
+  kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+  kwsysProcessCleanupDescriptor(&pgidPipe[1]);
+
+  /* Make sure the child is in the process group before we proceed.  This
+     avoids race conditions with calls to the kill function that we make for
+     signalling process groups.  */
+  while((readRes = read(pgidPipe[0], &tmp, 1)) > 0);
+  if(readRes < 0)
+    {
+    sigprocmask(SIG_SETMASK, &old_mask, 0);
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+    return 0;
+    }
+  kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+
+  /* Unmask signals.  */
+  if(sigprocmask(SIG_SETMASK, &old_mask, 0) < 0)
+    {
+    kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+    return 0;
+    }
+
   /* A child has been created.  */
   ++cp->CommandsLeft;
 
-  /* We are done with the error reporting pipe write end.  */
-  kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
-
   /* Block until the child's exec call succeeds and closes the error
      pipe or writes data to the pipe to report an error.  */
   {
@@ -1877,6 +2028,17 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
   /* A child process has terminated.  Reap it if it is one handled by
      this object.  */
   int i;
+  /* Temporarily disable signals that access ForkPIDs.  We don't want them to
+     read a reaped PID, and writes to ForkPIDs are not atomic.  */
+  sigset_t mask, old_mask;
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGINT);
+  sigaddset(&mask, SIGTERM);
+  if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
+    {
+    return;
+    }
+
   for(i=0; i < cp->NumberOfCommands; ++i)
     {
     if(cp->ForkPIDs[i])
@@ -1910,6 +2072,9 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
         }
       }
     }
+
+  /* Re-enable signals.  */
+  sigprocmask(SIG_SETMASK, &old_mask, 0);
 }
 
 /*--------------------------------------------------------------------------*/
@@ -1938,7 +2103,7 @@ static int kwsysProcessSetupOutputPipeFile(int* p, const char* name)
 
   /* Assign the replacement descriptor.  */
   *p = fout;
-  return 1;  
+  return 1;
 }
 
 /*--------------------------------------------------------------------------*/
@@ -2582,19 +2747,23 @@ typedef struct kwsysProcessInstances_s
 } kwsysProcessInstances;
 static kwsysProcessInstances kwsysProcesses;
 
-/* The old SIGCHLD handler.  */
+/* The old SIGCHLD / SIGINT / SIGTERM handlers.  */
 static struct sigaction kwsysProcessesOldSigChldAction;
+static struct sigaction kwsysProcessesOldSigIntAction;
+static struct sigaction kwsysProcessesOldSigTermAction;
 
 /*--------------------------------------------------------------------------*/
 static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses)
 {
-  /* Block SIGCHLD while we update the set of pipes to check.
+  /* Block signals while we update the set of pipes to check.
      TODO: sigprocmask is undefined for threaded apps.  See
      pthread_sigmask.  */
   sigset_t newset;
   sigset_t oldset;
   sigemptyset(&newset);
   sigaddset(&newset, SIGCHLD);
+  sigaddset(&newset, SIGINT);
+  sigaddset(&newset, SIGTERM);
   sigprocmask(SIG_BLOCK, &newset, &oldset);
 
   /* Store the new set in that seen by the signal handler.  */
@@ -2686,21 +2855,36 @@ static int kwsysProcessesAdd(kwsysProcess* cp)
     {
     /* Install our handler for SIGCHLD.  Repeat call until it is not
        interrupted.  */
-    struct sigaction newSigChldAction;
-    memset(&newSigChldAction, 0, sizeof(struct sigaction));
+    struct sigaction newSigAction;
+    memset(&newSigAction, 0, sizeof(struct sigaction));
 #if KWSYSPE_USE_SIGINFO
-    newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler;
-    newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
+    newSigAction.sa_sigaction = kwsysProcessesSignalHandler;
+    newSigAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
 # ifdef SA_RESTART
-    newSigChldAction.sa_flags |= SA_RESTART;
+    newSigAction.sa_flags |= SA_RESTART;
 # endif
 #else
-    newSigChldAction.sa_handler = kwsysProcessesSignalHandler;
-    newSigChldAction.sa_flags = SA_NOCLDSTOP;
+    newSigAction.sa_handler = kwsysProcessesSignalHandler;
+    newSigAction.sa_flags = SA_NOCLDSTOP;
 #endif
-    while((sigaction(SIGCHLD, &newSigChldAction,
+    sigemptyset(&newSigAction.sa_mask);
+    while((sigaction(SIGCHLD, &newSigAction,
                      &kwsysProcessesOldSigChldAction) < 0) &&
           (errno == EINTR));
+
+    /* Install our handler for SIGINT / SIGTERM.  Repeat call until
+       it is not interrupted.  */
+    sigemptyset(&newSigAction.sa_mask);
+    sigaddset(&newSigAction.sa_mask, SIGTERM);
+    while((sigaction(SIGINT, &newSigAction,
+                     &kwsysProcessesOldSigIntAction) < 0) &&
+          (errno == EINTR));
+
+    sigemptyset(&newSigAction.sa_mask);
+    sigaddset(&newSigAction.sa_mask, SIGINT);
+    while((sigaction(SIGTERM, &newSigAction,
+                     &kwsysProcessesOldSigIntAction) < 0) &&
+          (errno == EINTR));
     }
   }
 
@@ -2734,10 +2918,14 @@ static void kwsysProcessesRemove(kwsysProcess* cp)
     /* If this was the last process, disable the signal handler.  */
     if(newProcesses.Count == 0)
       {
-      /* Restore the SIGCHLD handler.  Repeat call until it is not
+      /* Restore the signal handlers.  Repeat call until it is not
          interrupted.  */
       while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) &&
             (errno == EINTR));
+      while((sigaction(SIGINT, &kwsysProcessesOldSigIntAction, 0) < 0) &&
+            (errno == EINTR));
+      while((sigaction(SIGTERM, &kwsysProcessesOldSigTermAction, 0) < 0) &&
+            (errno == EINTR));
 
       /* Free the table of process pointers since it is now empty.
          This is safe because the signal handler has been removed.  */
@@ -2763,39 +2951,108 @@ static void kwsysProcessesSignalHandler(int signum
 #endif
   )
 {
-  (void)signum;
+  int i, j, procStatus, old_errno = errno;
 #if KWSYSPE_USE_SIGINFO
   (void)info;
   (void)ucontext;
 #endif
 
   /* Signal all process objects that a child has terminated.  */
-  {
-  int i;
-  for(i=0; i < kwsysProcesses.Count; ++i)
+  switch(signum)
     {
-    /* Set the pipe in a signalled state.  */
-    char buf = 1;
-    kwsysProcess* cp = kwsysProcesses.Processes[i];
-    kwsysProcess_ssize_t status=
-      read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
-    (void)status;
-    status=write(cp->SignalPipe, &buf, 1);
-    (void)status;
+    case SIGCHLD:
+      for(i=0; i < kwsysProcesses.Count; ++i)
+        {
+        /* Set the pipe in a signalled state.  */
+        char buf = 1;
+        kwsysProcess* cp = kwsysProcesses.Processes[i];
+        kwsysProcess_ssize_t pipeStatus=
+          read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
+        (void)pipeStatus;
+        pipeStatus=write(cp->SignalPipe, &buf, 1);
+        (void)pipeStatus;
+        }
+      break;
+    case SIGINT:
+    case SIGTERM:
+      /* Signal child processes that are running in new process groups.  */
+      for(i=0; i < kwsysProcesses.Count; ++i)
+        {
+        kwsysProcess* cp = kwsysProcesses.Processes[i];
+        /* Check Killed to avoid data race condition when killing.
+           Check State to avoid data race condition in kwsysProcessCleanup
+           when there is an error (it leaves a reaped PID).  */
+        if(cp->CreateProcessGroup && !cp->Killed &&
+           cp->State != kwsysProcess_State_Error && cp->ForkPIDs)
+          {
+          for(j=0; j < cp->NumberOfCommands; ++j)
+            {
+            /* Make sure the PID is still valid. */
+            if(cp->ForkPIDs[j])
+              {
+              /* The user created a process group for this process.  The group ID
+                 is the process ID for the original process in the group.  */
+              kill(-cp->ForkPIDs[j], SIGINT);
+              }
+            }
+          }
+        }
+
+      /* Wait for all processes to terminate.  */
+      while(wait(&procStatus) >= 0 || errno != ECHILD)
+        {
+        }
+
+      /* Terminate the process, which is now in an inconsistent state
+         because we reaped all the PIDs that it may have been reaping
+         or may have reaped in the future.  Reraise the signal so that
+         the proper exit code is returned.  */
+      {
+      /* Install default signal handler.  */
+      struct sigaction defSigAction;
+      sigset_t unblockSet;
+      memset(&defSigAction, 0, sizeof(defSigAction));
+      defSigAction.sa_handler = SIG_DFL;
+      sigemptyset(&defSigAction.sa_mask);
+      while((sigaction(signum, &defSigAction, 0) < 0) &&
+            (errno == EINTR));
+      /* Unmask the signal.  */
+      sigemptyset(&unblockSet);
+      sigaddset(&unblockSet, signum);
+      sigprocmask(SIG_UNBLOCK, &unblockSet, 0);
+      /* Raise the signal again.  */
+      raise(signum);
+      /* We shouldn't get here... but if we do... */
+      _exit(1);
+      }
+      /* break omitted to silence unreachable code clang compiler warning.  */
     }
-  }
 
 #if !KWSYSPE_USE_SIGINFO
-  /* Re-Install our handler for SIGCHLD.  Repeat call until it is not
-     interrupted.  */
+  /* Re-Install our handler.  Repeat call until it is not interrupted.  */
   {
-  struct sigaction newSigChldAction;
-  memset(&newSigChldAction, 0, sizeof(struct sigaction));
+  struct sigaction newSigAction;
+  struct sigaction &oldSigAction;
+  memset(&newSigAction, 0, sizeof(struct sigaction));
   newSigChldAction.sa_handler = kwsysProcessesSignalHandler;
   newSigChldAction.sa_flags = SA_NOCLDSTOP;
-  while((sigaction(SIGCHLD, &newSigChldAction,
-                   &kwsysProcessesOldSigChldAction) < 0) &&
+  sigemptyset(&newSigAction.sa_mask);
+  switch(signum)
+    {
+    case SIGCHLD: oldSigAction = &kwsysProcessesOldSigChldAction; break;
+    case SIGINT:
+      sigaddset(&newSigAction.sa_mask, SIGTERM);
+      oldSigAction = &kwsysProcessesOldSigIntAction; break;
+    case SIGTERM:
+      sigaddset(&newSigAction.sa_mask, SIGINT);
+      oldSigAction = &kwsysProcessesOldSigTermAction; break;
+    default: return 0;
+    }
+  while((sigaction(signum, &newSigAction,
+                   oldSigAction) < 0) &&
         (errno == EINTR));
   }
 #endif
+
+  errno = old_errno;
 }

+ 409 - 53
Source/kwsys/ProcessWin32.c

@@ -109,14 +109,15 @@ static DWORD WINAPI kwsysProcessPipeThreadWake(LPVOID ptd);
 static void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp,
                                            kwsysProcessPipeData* td);
 static int kwsysProcessInitialize(kwsysProcess* cp);
-static int kwsysProcessCreate(kwsysProcess* cp, int index,
-                              kwsysProcessCreateInformation* si);
+static DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
+                                kwsysProcessCreateInformation* si);
 static void kwsysProcessDestroy(kwsysProcess* cp, int event);
-static int kwsysProcessSetupOutputPipeFile(PHANDLE handle, const char* name);
+static DWORD kwsysProcessSetupOutputPipeFile(PHANDLE handle,
+                                             const char* name);
 static void kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle);
 static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle);
 static void kwsysProcessCleanupHandle(PHANDLE h);
-static void kwsysProcessCleanup(kwsysProcess* cp, int error);
+static void kwsysProcessCleanup(kwsysProcess* cp, DWORD error);
 static void kwsysProcessCleanErrorMessage(kwsysProcess* cp);
 static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
                                       kwsysProcessTime* timeoutTime);
@@ -133,6 +134,13 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc
 static void kwsysProcessSetExitException(kwsysProcess* cp, int code);
 static void kwsysProcessKillTree(int pid);
 static void kwsysProcessDisablePipeThreads(kwsysProcess* cp);
+static int kwsysProcessesInitialize(void);
+static int kwsysTryEnterCreateProcessSection(void);
+static void kwsysLeaveCreateProcessSection(void);
+static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessId,
+                             int newProcessGroup);
+static void kwsysProcessesRemove(HANDLE hProcess);
+static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType);
 
 /*--------------------------------------------------------------------------*/
 /* A structure containing synchronization data for each thread.  */
@@ -222,6 +230,9 @@ struct kwsysProcess_s
   /* Whether to merge stdout/stderr of the child.  */
   int MergeOutput;
 
+  /* Whether to create the process in a new process group.  */
+  int CreateProcessGroup;
+
   /* Mutex to protect the shared index used by threads to report data.  */
   HANDLE SharedIndexMutex;
 
@@ -321,6 +332,16 @@ kwsysProcess* kwsysProcess_New(void)
   /* Windows version number data.  */
   OSVERSIONINFO osv;
 
+  /* Initialize list of processes before we get any farther.  It's especially
+     important that the console Ctrl handler be added BEFORE starting the
+     first process.  This prevents the risk of an orphaned process being
+     started by the main thread while the default Ctrl handler is in
+     progress.  */
+  if(!kwsysProcessesInitialize())
+    {
+    return 0;
+    }
+
   /* Allocate a process control structure.  */
   cp = (kwsysProcess*)malloc(sizeof(kwsysProcess));
   if(!cp)
@@ -836,6 +857,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
     case kwsysProcess_Option_HideWindow: return cp->HideWindow;
     case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
     case kwsysProcess_Option_Verbatim: return cp->Verbatim;
+    case kwsysProcess_Option_CreateProcessGroup:
+      return cp->CreateProcessGroup;
     default: return 0;
     }
 }
@@ -854,6 +877,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
     case kwsysProcess_Option_HideWindow: cp->HideWindow = value; break;
     case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
     case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
+    case kwsysProcess_Option_CreateProcessGroup:
+      cp->CreateProcessGroup = value; break;
     default: break;
     }
 }
@@ -945,7 +970,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
     if(!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength,
                             cp->RealWorkingDirectory))
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, GetLastError());
       return;
       }
     SetCurrentDirectoryW(cp->WorkingDirectory);
@@ -957,14 +982,16 @@ void kwsysProcess_Execute(kwsysProcess* cp)
     {
     /* Create a handle to read a file for stdin.  */
     wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN);
+    DWORD error;
     cp->PipeChildStd[0] =
       CreateFileW(wstdin, GENERIC_READ|GENERIC_WRITE,
                   FILE_SHARE_READ|FILE_SHARE_WRITE,
                   0, OPEN_EXISTING, 0, 0);
+    error = GetLastError(); /* Check now in case free changes this.  */
     free(wstdin);
     if(cp->PipeChildStd[0] == INVALID_HANDLE_VALUE)
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, error);
       return;
       }
     }
@@ -990,17 +1017,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
   if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read,
                  &cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0))
     {
-    kwsysProcessCleanup(cp, 1);
+    kwsysProcessCleanup(cp, GetLastError());
     return;
     }
 
   if(cp->PipeFileSTDOUT)
     {
     /* Use a file for stdout.  */
-    if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
-                                        cp->PipeFileSTDOUT))
+    DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
+                                                  cp->PipeFileSTDOUT);
+    if(error)
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, error);
       return;
       }
     }
@@ -1023,7 +1051,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
                         GetCurrentProcess(), &cp->PipeChildStd[1],
                         0, FALSE, DUPLICATE_SAME_ACCESS))
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, GetLastError());
       return;
       }
     }
@@ -1034,17 +1062,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
   if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read,
                  &cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0))
     {
-    kwsysProcessCleanup(cp, 1);
+    kwsysProcessCleanup(cp, GetLastError());
     return;
     }
 
   if(cp->PipeFileSTDERR)
     {
     /* Use a file for stderr.  */
-    if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
-                                        cp->PipeFileSTDERR))
+    DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
+                                                  cp->PipeFileSTDERR);
+    if(error)
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, error);
       return;
       }
     }
@@ -1067,7 +1096,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
                         GetCurrentProcess(), &cp->PipeChildStd[2],
                         0, FALSE, DUPLICATE_SAME_ACCESS))
       {
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, GetLastError());
       return;
       }
     }
@@ -1106,11 +1135,12 @@ void kwsysProcess_Execute(kwsysProcess* cp)
       HANDLE p[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
       if (!CreatePipe(&p[0], &p[1], 0, 0))
         {
+        DWORD error = GetLastError();
         if (nextStdInput != cp->PipeChildStd[0])
           {
           kwsysProcessCleanupHandle(&nextStdInput);
           }
-        kwsysProcessCleanup(cp, 1);
+        kwsysProcessCleanup(cp, error);
         return;
         }
       nextStdInput = p[0];
@@ -1119,7 +1149,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
     si.hStdError = cp->MergeOutput? cp->PipeChildStd[1] : cp->PipeChildStd[2];
 
     {
-    int res = kwsysProcessCreate(cp, i, &si);
+    DWORD error = kwsysProcessCreate(cp, i, &si);
 
     /* Close our copies of pipes used between children.  */
     if (si.hStdInput != cp->PipeChildStd[0])
@@ -1134,7 +1164,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
       {
       kwsysProcessCleanupHandle(&si.hStdError);
       }
-    if (res)
+    if (!error)
       {
       cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess;
       }
@@ -1144,7 +1174,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
         {
         kwsysProcessCleanupHandle(&nextStdInput);
         }
-      kwsysProcessCleanup(cp, 1);
+      kwsysProcessCleanup(cp, error);
       return;
       }
     }
@@ -1459,6 +1489,52 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
   return 1;
 }
 
+/*--------------------------------------------------------------------------*/
+void kwsysProcess_Interrupt(kwsysProcess* cp)
+{
+  int i;
+  /* Make sure we are executing a process.  */
+  if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
+     cp->Killed)
+    {
+    KWSYSPE_DEBUG((stderr, "interrupt: child not executing\n"));
+    return;
+    }
+
+  /* Skip actually interrupting the child if it has already terminated.  */
+  if(cp->Terminated)
+    {
+    KWSYSPE_DEBUG((stderr, "interrupt: child already terminated\n"));
+    return;
+    }
+
+  /* Interrupt the children.  */
+  if (cp->CreateProcessGroup)
+    {
+    if(cp->ProcessInformation)
+      {
+      for(i=0; i < cp->NumberOfCommands; ++i)
+        {
+        /* Make sure the process handle isn't closed (e.g. from disowning). */
+        if(cp->ProcessInformation[i].hProcess)
+          {
+          /* The user created a process group for this process.  The group ID
+             is the process ID for the original process in the group.  Note
+             that we have to use Ctrl+Break: Ctrl+C is not allowed for process
+             groups.  */
+          GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
+                                   cp->ProcessInformation[i].dwProcessId);
+          }
+        }
+      }
+    }
+  else
+    {
+    /* No process group was created.  Kill our own process group...  */
+    GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);
+    }
+}
+
 /*--------------------------------------------------------------------------*/
 void kwsysProcess_Kill(kwsysProcess* cp)
 {
@@ -1487,7 +1563,8 @@ void kwsysProcess_Kill(kwsysProcess* cp)
   for(i=0; i < cp->NumberOfCommands; ++i)
     {
     kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId);
-    // close the handle if we kill it
+    /* Remove from global list of processes and close handles.  */
+    kwsysProcessesRemove(cp->ProcessInformation[i].hProcess);
     kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread);
     kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
     }
@@ -1686,7 +1763,7 @@ int kwsysProcessInitialize(kwsysProcess* cp)
 }
 
 /*--------------------------------------------------------------------------*/
-static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
+static DWORD kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
 {
   DWORD flags;
 
@@ -1697,13 +1774,19 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
     if (flags & HANDLE_FLAG_INHERIT)
       {
       *out = in;
-      return 1;
+      return ERROR_SUCCESS;
       }
 
     /* Create an inherited copy of this handle.  */
-    return DuplicateHandle(GetCurrentProcess(), in,
-                           GetCurrentProcess(), out,
-                           0, TRUE, DUPLICATE_SAME_ACCESS);
+    if (DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), out,
+                        0, TRUE, DUPLICATE_SAME_ACCESS))
+      {
+      return ERROR_SUCCESS;
+      }
+    else
+      {
+      return GetLastError();
+      }
     }
   else
     {
@@ -1719,29 +1802,46 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
                         (GENERIC_WRITE | FILE_READ_ATTRIBUTES)),
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        &sa, OPEN_EXISTING, 0, 0);
-    return *out != INVALID_HANDLE_VALUE;
+    return (*out != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError();
     }
-
 }
 
 /*--------------------------------------------------------------------------*/
-int kwsysProcessCreate(kwsysProcess* cp, int index,
-                       kwsysProcessCreateInformation* si)
+DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
+                         kwsysProcessCreateInformation* si)
 {
-  int res =
+  DWORD creationFlags;
+  DWORD error = ERROR_SUCCESS;
+
+  /* Check if we are currently exiting.  */
+  if (!kwsysTryEnterCreateProcessSection())
+    {
+    /* The Ctrl handler is currently working on exiting our process.  Rather
+    than return an error code, which could cause incorrect conclusions to be
+    reached by the caller, we simply hang.  (For example, a CMake try_run
+    configure step might cause the project to configure wrong.)  */
+    Sleep(INFINITE);
+    }
 
-    /* Create inherited copies the handles.  */
-    kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
-                                  si->hStdInput, 1) &&
-    kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
-                                  si->hStdOutput, 0) &&
-    kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
-                                  si->hStdError, 0) &&
+  /* Create the child in a suspended state so we can wait until all
+     children have been created before running any one.  */
+  creationFlags = CREATE_SUSPENDED;
+  if (cp->CreateProcessGroup)
+    {
+    creationFlags |= CREATE_NEW_PROCESS_GROUP;
+    }
 
-    /* Create the child in a suspended state so we can wait until all
-       children have been created before running any one.  */
-    CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, CREATE_SUSPENDED, 0,
-                   0, &si->StartupInfo, &cp->ProcessInformation[index]);
+  /* Create inherited copies of the handles.  */
+  (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
+                                          si->hStdInput, 1)) ||
+  (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
+                                          si->hStdOutput, 0)) ||
+  (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
+                                          si->hStdError, 0)) ||
+  /* Create the process.  */
+  (!CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, creationFlags, 0,
+                  0, &si->StartupInfo, &cp->ProcessInformation[index]) &&
+    (error = GetLastError()));
 
   /* Close the inherited copies of the handles. */
   if (si->StartupInfo.hStdInput != si->hStdInput)
@@ -1757,7 +1857,23 @@ int kwsysProcessCreate(kwsysProcess* cp, int index,
     kwsysProcessCleanupHandle(&si->StartupInfo.hStdError);
     }
 
-  return res;
+  /* Add the process to the global list of processes. */
+  if (!error &&
+      !kwsysProcessesAdd(cp->ProcessInformation[index].hProcess,
+      cp->ProcessInformation[index].dwProcessId, cp->CreateProcessGroup))
+    {
+    /* This failed for some reason.  Kill the suspended process. */
+    TerminateProcess(cp->ProcessInformation[index].hProcess, 1);
+    /* And clean up... */
+    kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess);
+    kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hThread);
+    strcpy(cp->ErrorMessage, "kwsysProcessesAdd function failed");
+    error = ERROR_NOT_ENOUGH_MEMORY; /* Most likely reason.  */
+    }
+
+  /* If the console Ctrl handler is waiting for us, this will release it... */
+  kwsysLeaveCreateProcessSection();
+  return error;
 }
 
 /*--------------------------------------------------------------------------*/
@@ -1779,6 +1895,9 @@ void kwsysProcessDestroy(kwsysProcess* cp, int event)
   GetExitCodeProcess(cp->ProcessInformation[index].hProcess,
                      &cp->CommandExitCodes[index]);
 
+  /* Remove from global list of processes.  */
+  kwsysProcessesRemove(cp->ProcessInformation[index].hProcess);
+
   /* Close the process handle for the terminated process.  */
   kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess);
 
@@ -1813,13 +1932,14 @@ void kwsysProcessDestroy(kwsysProcess* cp, int event)
 }
 
 /*--------------------------------------------------------------------------*/
-int kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
+DWORD kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
 {
   HANDLE fout;
   wchar_t* wname;
+  DWORD error;
   if(!name)
     {
-    return 1;
+    return ERROR_INVALID_PARAMETER;
     }
 
   /* Close the existing handle.  */
@@ -1829,15 +1949,16 @@ int kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
   wname = kwsysEncoding_DupToWide(name);
   fout = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, 0,
                     CREATE_ALWAYS, 0, 0);
+  error = GetLastError();
   free(wname);
   if(fout == INVALID_HANDLE_VALUE)
     {
-    return 0;
+    return error;
     }
 
   /* Assign the replacement handle.  */
   *phandle = fout;
-  return 1;
+  return ERROR_SUCCESS;
 }
 
 /*--------------------------------------------------------------------------*/
@@ -1876,7 +1997,7 @@ void kwsysProcessCleanupHandle(PHANDLE h)
 /*--------------------------------------------------------------------------*/
 
 /* Close all handles created by kwsysProcess_Execute.  */
-void kwsysProcessCleanup(kwsysProcess* cp, int error)
+void kwsysProcessCleanup(kwsysProcess* cp, DWORD error)
 {
   int i;
   /* If this is an error case, report the error.  */
@@ -1886,21 +2007,27 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error)
     if(cp->ErrorMessage[0] == 0)
       {
       /* Format the error message.  */
-      DWORD original = GetLastError();
       wchar_t err_msg[KWSYSPE_PIPE_BUFFER_SIZE];
       DWORD length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
-                                   FORMAT_MESSAGE_IGNORE_INSERTS, 0, original,
+                                   FORMAT_MESSAGE_IGNORE_INSERTS, 0, error,
                                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                    err_msg, KWSYSPE_PIPE_BUFFER_SIZE, 0);
-      WideCharToMultiByte(CP_UTF8, 0, err_msg, -1, cp->ErrorMessage,
-                          KWSYSPE_PIPE_BUFFER_SIZE, NULL, NULL);
       if(length < 1)
         {
         /* FormatMessage failed.  Use a default message.  */
         _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE,
                   "Process execution failed with error 0x%X.  "
                   "FormatMessage failed with error 0x%X",
-                  original, GetLastError());
+                  error, GetLastError());
+        }
+      if(!WideCharToMultiByte(CP_UTF8, 0, err_msg, -1, cp->ErrorMessage,
+                              KWSYSPE_PIPE_BUFFER_SIZE, NULL, NULL))
+        {
+        /* WideCharToMultiByte failed.  Use a default message.  */
+        _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE,
+                  "Process execution failed with error 0x%X.  "
+                  "WideCharToMultiByte failed with error 0x%X",
+                  error, GetLastError());
         }
       }
 
@@ -1923,6 +2050,8 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error)
         }
       for(i=0; i < cp->NumberOfCommands; ++i)
         {
+        /* Remove from global list of processes and close handles.  */
+        kwsysProcessesRemove(cp->ProcessInformation[i].hProcess);
         kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread);
         kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
         }
@@ -2659,3 +2788,230 @@ static void kwsysProcessDisablePipeThreads(kwsysProcess* cp)
     ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0);
     }
 }
+
+/*--------------------------------------------------------------------------*/
+/* Global set of executing processes for use by the Ctrl handler.
+   This global instance will be zero-initialized by the compiler.
+
+   Note that the console Ctrl handler runs on a background thread and so
+   everything it does must be thread safe.  Here, we track the hProcess
+   HANDLEs directly instead of kwsysProcess instances, so that we don't have
+   to make kwsysProcess thread safe.  */
+typedef struct kwsysProcessInstance_s
+{
+  HANDLE hProcess;
+  DWORD dwProcessId;
+  int NewProcessGroup; /* Whether the process was created in a new group.  */
+} kwsysProcessInstance;
+
+typedef struct kwsysProcessInstances_s
+{
+  /* Whether we have initialized key fields below, like critical sections.  */
+  int Initialized;
+
+  /* Ctrl handler runs on a different thread, so we must sync access.  */
+  CRITICAL_SECTION Lock;
+
+  int Exiting;
+  size_t Count;
+  size_t Size;
+  kwsysProcessInstance* Processes;
+} kwsysProcessInstances;
+static kwsysProcessInstances kwsysProcesses;
+
+/*--------------------------------------------------------------------------*/
+/* Initialize critial section and set up console Ctrl handler.  You MUST call
+   this before using any other kwsysProcesses* functions below.  */
+static int kwsysProcessesInitialize(void)
+{
+  /* Initialize everything if not done already.  */
+  if(!kwsysProcesses.Initialized)
+    {
+    InitializeCriticalSection(&kwsysProcesses.Lock);
+
+    /* Set up console ctrl handler.  */
+    if(!SetConsoleCtrlHandler(kwsysCtrlHandler, TRUE))
+      {
+      return 0;
+      }
+
+    kwsysProcesses.Initialized = 1;
+    }
+  return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+/* The Ctrl handler waits on the global list of processes.  To prevent an
+   orphaned process, do not create a new process if the Ctrl handler is
+   already running.  Do so by using this function to check if it is ok to
+   create a process.  */
+static int kwsysTryEnterCreateProcessSection(void)
+{
+  /* Enter main critical section; this means creating a process and the Ctrl
+     handler are mutually exclusive.  */
+  EnterCriticalSection(&kwsysProcesses.Lock);
+  /* Indicate to the caller if they can create a process.  */
+  if(kwsysProcesses.Exiting)
+    {
+    LeaveCriticalSection(&kwsysProcesses.Lock);
+    return 0;
+    }
+  else
+    {
+    return 1;
+    }
+}
+
+/*--------------------------------------------------------------------------*/
+/* Matching function on successful kwsysTryEnterCreateProcessSection return.
+   Make sure you called kwsysProcessesAdd if applicable before calling this.*/
+static void kwsysLeaveCreateProcessSection(void)
+{
+  LeaveCriticalSection(&kwsysProcesses.Lock);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Add new process to global process list.  The Ctrl handler will wait for
+   the process to exit before it returns.  Do not close the process handle
+   until after calling kwsysProcessesRemove.  The newProcessGroup parameter
+   must be set if the process was created with CREATE_NEW_PROCESS_GROUP.  */
+static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessid,
+                             int newProcessGroup)
+{
+  if(!kwsysProcessesInitialize() || !hProcess ||
+      hProcess == INVALID_HANDLE_VALUE)
+    {
+    return 0;
+    }
+
+  /* Enter the critical section. */
+  EnterCriticalSection(&kwsysProcesses.Lock);
+
+  /* Make sure there is enough space for the new process handle.  */
+  if(kwsysProcesses.Count == kwsysProcesses.Size)
+    {
+    size_t newSize;
+    kwsysProcessInstance *newArray;
+    /* Start with enough space for a small number of process handles
+       and double the size each time more is needed.  */
+    newSize = kwsysProcesses.Size? kwsysProcesses.Size*2 : 4;
+
+    /* Try allocating the new block of memory.  */
+    if(newArray = (kwsysProcessInstance*)malloc(
+       newSize*sizeof(kwsysProcessInstance)))
+      {
+      /* Copy the old process handles to the new memory.  */
+      if(kwsysProcesses.Count > 0)
+        {
+        memcpy(newArray, kwsysProcesses.Processes,
+               kwsysProcesses.Count * sizeof(kwsysProcessInstance));
+        }
+      }
+    else
+      {
+      /* Failed to allocate memory for the new process handle set.  */
+      LeaveCriticalSection(&kwsysProcesses.Lock);
+      return 0;
+      }
+
+    /* Free original array. */
+    free(kwsysProcesses.Processes);
+
+    /* Update original structure with new allocation. */
+    kwsysProcesses.Size = newSize;
+    kwsysProcesses.Processes = newArray;
+    }
+
+  /* Append the new process information to the set.  */
+  kwsysProcesses.Processes[kwsysProcesses.Count].hProcess = hProcess;
+  kwsysProcesses.Processes[kwsysProcesses.Count].dwProcessId = dwProcessid;
+  kwsysProcesses.Processes[kwsysProcesses.Count++].NewProcessGroup =
+    newProcessGroup;
+
+  /* Leave critical section and return success. */
+  LeaveCriticalSection(&kwsysProcesses.Lock);
+
+  return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+/* Removes process to global process list.  */
+static void kwsysProcessesRemove(HANDLE hProcess)
+{
+  size_t i;
+
+  if (!hProcess || hProcess == INVALID_HANDLE_VALUE)
+    {
+    return;
+    }
+
+  EnterCriticalSection(&kwsysProcesses.Lock);
+
+  /* Find the given process in the set.  */
+  for(i=0; i < kwsysProcesses.Count; ++i)
+    {
+    if(kwsysProcesses.Processes[i].hProcess == hProcess)
+      {
+      break;
+      }
+    }
+  if(i < kwsysProcesses.Count)
+    {
+    /* Found it!  Remove the process from the set.  */
+    --kwsysProcesses.Count;
+    for(; i < kwsysProcesses.Count; ++i)
+      {
+      kwsysProcesses.Processes[i] = kwsysProcesses.Processes[i+1];
+      }
+
+    /* If this was the last process, free the array.  */
+    if(kwsysProcesses.Count == 0)
+      {
+      kwsysProcesses.Size = 0;
+      free(kwsysProcesses.Processes);
+      kwsysProcesses.Processes = 0;
+      }
+    }
+
+  LeaveCriticalSection(&kwsysProcesses.Lock);
+}
+
+/*--------------------------------------------------------------------------*/
+static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType)
+{
+  size_t i;
+  (void)dwCtrlType;
+  /* Enter critical section.  */
+  EnterCriticalSection(&kwsysProcesses.Lock);
+
+  /* Set flag indicating that we are exiting.  */
+  kwsysProcesses.Exiting = 1;
+
+  /* If some of our processes were created in a new process group, we must
+     manually interrupt them.  They won't otherwise receive a Ctrl+C/Break. */
+  for(i=0; i < kwsysProcesses.Count; ++i)
+    {
+    if(kwsysProcesses.Processes[i].NewProcessGroup)
+      {
+      DWORD groupId = kwsysProcesses.Processes[i].dwProcessId;
+      if(groupId)
+        {
+        GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, groupId);
+        }
+      }
+    }
+
+  /* Wait for each child process to exit.  This is the key step that prevents
+     us from leaving several orphaned children processes running in the
+     background when the user presses Ctrl+C.  */
+  for(i=0; i < kwsysProcesses.Count; ++i)
+    {
+    WaitForSingleObject(kwsysProcesses.Processes[i].hProcess, INFINITE);
+    }
+
+  /* Leave critical section.  */
+  LeaveCriticalSection(&kwsysProcesses.Lock);
+
+  /* Continue on to default Ctrl handler (which calls ExitProcess).  */
+  return FALSE;
+}

+ 224 - 43
Source/kwsys/testProcess.c

@@ -29,26 +29,48 @@
 # include <windows.h>
 #else
 # include <unistd.h>
+# include <signal.h>
 #endif
 
 #if defined(__BORLANDC__)
 # pragma warn -8060 /* possibly incorrect assignment */
 #endif
 
+/* Platform-specific sleep functions. */
+
 #if defined(__BEOS__) && !defined(__ZETA__)
 /* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
 # include <be/kernel/OS.h>
-static inline void testProcess_usleep(unsigned int msec)
+static inline void testProcess_usleep(unsigned int usec)
+{
+  snooze(usec);
+}
+#elif defined(_WIN32)
+/* Windows can only sleep in millisecond intervals. */
+static void testProcess_usleep(unsigned int usec)
 {
-  snooze(msec);
+  Sleep(usec / 1000);
 }
 #else
 # define testProcess_usleep usleep
 #endif
 
+#if defined(_WIN32)
+static void testProcess_sleep(unsigned int sec)
+{
+  Sleep(sec*1000);
+}
+#else
+static void testProcess_sleep(unsigned int sec)
+{
+  sleep(sec);
+}
+#endif
+
 int runChild(const char* cmd[], int state, int exception, int value,
              int share, int output, int delay, double timeout, int poll,
-             int repeat, int disown);
+             int repeat, int disown, int createNewGroup,
+             unsigned int interruptDelay);
 
 static int test1(int argc, const char* argv[])
 {
@@ -73,11 +95,7 @@ static int test3(int argc, const char* argv[])
   fprintf(stderr, "Output before sleep on stderr from timeout test.\n");
   fflush(stdout);
   fflush(stderr);
-#if defined(_WIN32)
-  Sleep(15000);
-#else
-  sleep(15);
-#endif
+  testProcess_sleep(15);
   fprintf(stdout, "Output after sleep on stdout from timeout test.\n");
   fprintf(stderr, "Output after sleep on stderr from timeout test.\n");
   return 0;
@@ -102,7 +120,7 @@ static int test4(int argc, const char* argv[])
 #endif
   (void)argc; (void)argv;
   fprintf(stdout, "Output before crash on stdout from crash test.\n");
-  fprintf(stderr, "Output before crash on stderr from crash test.\n");  
+  fprintf(stderr, "Output before crash on stderr from crash test.\n");
   fflush(stdout);
   fflush(stderr);
   assert(invalidAddress); /* Quiet Clang scan-build. */
@@ -127,7 +145,7 @@ static int test5(int argc, const char* argv[])
   fflush(stdout);
   fflush(stderr);
   r = runChild(cmd, kwsysProcess_State_Exception,
-               kwsysProcess_Exception_Fault, 1, 1, 1, 0, 15, 0, 1, 0);
+               kwsysProcess_Exception_Fault, 1, 1, 1, 0, 15, 0, 1, 0, 0, 0);
   fprintf(stdout, "Output on stdout after recursive test.\n");
   fprintf(stderr, "Output on stderr after recursive test.\n");
   fflush(stdout);
@@ -168,11 +186,7 @@ static int test7(int argc, const char* argv[])
   fflush(stdout);
   fflush(stderr);
   /* Sleep for 1 second.  */
-#if defined(_WIN32)
-  Sleep(1000);
-#else
-  sleep(1);
-#endif
+  testProcess_sleep(1);
   fprintf(stdout, "Output on stdout after sleep.\n");
   fprintf(stderr, "Output on stderr after sleep.\n");
   fflush(stdout);
@@ -196,7 +210,7 @@ static int test8(int argc, const char* argv[])
   fflush(stdout);
   fflush(stderr);
   r = runChild(cmd, kwsysProcess_State_Disowned, kwsysProcess_Exception_None,
-               1, 1, 1, 0, 10, 0, 1, 1);
+               1, 1, 1, 0, 10, 0, 1, 1, 0, 0);
   fprintf(stdout, "Output on stdout after grandchild test.\n");
   fprintf(stderr, "Output on stderr after grandchild test.\n");
   fflush(stdout);
@@ -217,18 +231,137 @@ static int test8_grandchild(int argc, const char* argv[])
      implemented.  */
   fclose(stdout);
   fclose(stderr);
+  testProcess_sleep(15);
+  return 0;
+}
+
+static int test9(int argc, const char* argv[])
+{
+  /* Test Ctrl+C behavior: the root test program will send a Ctrl+C to this
+     process.  Here, we start a child process that sleeps for a long time
+     while ignoring signals.  The test is successful if this process waits
+     for the child to return before exiting from the Ctrl+C handler.
+
+     WARNING:  This test will falsely pass if the share parameter of runChild
+     was set to 0 when invoking the test9 process.  */
+  int r;
+  const char* cmd[4];
+  (void)argc;
+  cmd[0] = argv[0];
+  cmd[1] = "run";
+  cmd[2] = "109";
+  cmd[3] = 0;
+  fprintf(stdout, "Output on stdout before grandchild test.\n");
+  fprintf(stderr, "Output on stderr before grandchild test.\n");
+  fflush(stdout);
+  fflush(stderr);
+  r = runChild(cmd, kwsysProcess_State_Exited,
+               kwsysProcess_Exception_None,
+               0, 1, 1, 0, 30, 0, 1, 0, 0, 0);
+  /* This sleep will avoid a race condition between this function exiting
+     normally and our Ctrl+C handler exiting abnormally after the process
+     exits.  */
+  testProcess_sleep(1);
+  fprintf(stdout, "Output on stdout after grandchild test.\n");
+  fprintf(stderr, "Output on stderr after grandchild test.\n");
+  fflush(stdout);
+  fflush(stderr);
+  return r;
+}
+
 #if defined(_WIN32)
-  Sleep(15000);
+static BOOL WINAPI test9_grandchild_handler(DWORD dwCtrlType)
+{
+  /* Ignore all Ctrl+C/Break signals.  We must use an actual handler function
+     instead of using SetConsoleCtrlHandler(NULL, TRUE) so that we can also
+     ignore Ctrl+Break in addition to Ctrl+C.  */
+  (void)dwCtrlType;
+  return TRUE;
+}
+#endif
+
+static int test9_grandchild(int argc, const char* argv[])
+{
+  /* The grandchild just sleeps for a few seconds while ignoring signals.  */
+  (void)argc; (void)argv;
+#if defined(_WIN32)
+  if(!SetConsoleCtrlHandler(test9_grandchild_handler, TRUE))
+    {
+    return 1;
+    }
 #else
-  sleep(15);
+  struct sigaction sa;
+  memset(&sa, 0, sizeof(sa));
+  sa.sa_handler = SIG_IGN;
+  sigemptyset(&sa.sa_mask);
+  if(sigaction(SIGINT, &sa, 0) < 0)
+    {
+    return 1;
+    }
 #endif
+  fprintf(stdout, "Output on stdout from grandchild before sleep.\n");
+  fprintf(stderr, "Output on stderr from grandchild before sleep.\n");
+  fflush(stdout);
+  fflush(stderr);
+  /* Sleep for 9 seconds.  */
+  testProcess_sleep(9);
+  fprintf(stdout, "Output on stdout from grandchild after sleep.\n");
+  fprintf(stderr, "Output on stderr from grandchild after sleep.\n");
+  fflush(stdout);
+  fflush(stderr);
+  return 0;
+}
+
+static int test10(int argc, const char* argv[])
+{
+  /* Test Ctrl+C behavior: the root test program will send a Ctrl+C to this
+     process.  Here, we start a child process that sleeps for a long time and
+     processes signals normally.  However, this grandchild is created in a new
+     process group - ensuring that Ctrl+C we receive is sent to our process
+     groups.  We make sure it exits anyway.  */
+  int r;
+  const char* cmd[4];
+  (void)argc;
+  cmd[0] = argv[0];
+  cmd[1] = "run";
+  cmd[2] = "110";
+  cmd[3] = 0;
+  fprintf(stdout, "Output on stdout before grandchild test.\n");
+  fprintf(stderr, "Output on stderr before grandchild test.\n");
+  fflush(stdout);
+  fflush(stderr);
+  r = runChild(cmd, kwsysProcess_State_Exception,
+               kwsysProcess_Exception_Interrupt,
+               0, 1, 1, 0, 30, 0, 1, 0, 1, 0);
+  fprintf(stdout, "Output on stdout after grandchild test.\n");
+  fprintf(stderr, "Output on stderr after grandchild test.\n");
+  fflush(stdout);
+  fflush(stderr);
+  return r;
+}
+
+static int test10_grandchild(int argc, const char* argv[])
+{
+  /* The grandchild just sleeps for a few seconds and handles signals.  */
+  (void)argc; (void)argv;
+  fprintf(stdout, "Output on stdout from grandchild before sleep.\n");
+  fprintf(stderr, "Output on stderr from grandchild before sleep.\n");
+  fflush(stdout);
+  fflush(stderr);
+  /* Sleep for 6 seconds.  */
+  testProcess_sleep(6);
+  fprintf(stdout, "Output on stdout from grandchild after sleep.\n");
+  fprintf(stderr, "Output on stderr from grandchild after sleep.\n");
+  fflush(stdout);
+  fflush(stderr);
   return 0;
 }
 
 static int runChild2(kwsysProcess* kp,
               const char* cmd[], int state, int exception, int value,
               int share, int output, int delay, double timeout,
-              int poll, int disown)
+              int poll, int disown, int createNewGroup,
+              unsigned int interruptDelay)
 {
   int result = 0;
   char* data = 0;
@@ -249,6 +382,10 @@ static int runChild2(kwsysProcess* kp,
     {
     kwsysProcess_SetOption(kp, kwsysProcess_Option_Detach, 1);
     }
+  if(createNewGroup)
+    {
+    kwsysProcess_SetOption(kp, kwsysProcess_Option_CreateProcessGroup, 1);
+    }
   kwsysProcess_Execute(kp);
 
   if(poll)
@@ -256,6 +393,12 @@ static int runChild2(kwsysProcess* kp,
     pUserTimeout = &userTimeout;
     }
 
+  if(interruptDelay)
+    {
+    testProcess_sleep(interruptDelay);
+    kwsysProcess_Interrupt(kp);
+    }
+
   if(!share && !disown)
     {
     int p;
@@ -286,17 +429,13 @@ static int runChild2(kwsysProcess* kp,
       if(poll)
         {
         /* Delay to avoid busy loop during polling.  */
-#if defined(_WIN32)
-        Sleep(100);
-#else
         testProcess_usleep(100000);
-#endif
         }
       if(delay)
         {
         /* Purposely sleeping only on Win32 to let pipe fill up.  */
 #if defined(_WIN32)
-        Sleep(100);
+        testProcess_usleep(100000);
 #endif
         }
       }
@@ -337,7 +476,7 @@ static int runChild2(kwsysProcess* kp,
       printf("Error in administrating child process: [%s]\n",
              kwsysProcess_GetErrorString(kp)); break;
     };
-  
+
   if(result)
     {
     if(exception != kwsysProcess_GetExitException(kp))
@@ -353,7 +492,7 @@ static int runChild2(kwsysProcess* kp,
               value, kwsysProcess_GetExitValue(kp));
       }
     }
-  
+
   if(kwsysProcess_GetState(kp) != state)
     {
     fprintf(stderr, "Mismatch in state.  "
@@ -374,9 +513,37 @@ static int runChild2(kwsysProcess* kp,
   return result;
 }
 
+/**
+ * Runs a child process and blocks until it returns.  Arguments as follows:
+ *
+ * cmd            = Command line to run.
+ * state          = Expected return value of kwsysProcess_GetState after exit.
+ * exception      = Expected return value of kwsysProcess_GetExitException.
+ * value          = Expected return value of kwsysProcess_GetExitValue.
+ * share          = Whether to share stdout/stderr child pipes with our pipes
+ *                  by way of kwsysProcess_SetPipeShared.  If false, new pipes
+ *                  are created.
+ * output         = If !share && !disown, whether to write the child's stdout
+ *                  and stderr output to our stdout.
+ * delay          = If !share && !disown, adds an additional short delay to
+ *                  the pipe loop to allow the pipes to fill up; Windows only.
+ * timeout        = Non-zero to sets a timeout in seconds via
+ *                  kwsysProcess_SetTimeout.
+ * poll           = If !share && !disown, we count the number of 0.1 second
+ *                  intervals where the child pipes had no new data.  We fail
+ *                  if not in the bounds of MINPOLL/MAXPOLL.
+ * repeat         = Number of times to run the process.
+ * disown         = If set, the process is disowned.
+ * createNewGroup = If set, the process is created in a new process group.
+ * interruptDelay = If non-zero, number of seconds to delay before
+ *                  interrupting the process.  Note that this delay will occur
+ *                  BEFORE any reading/polling of pipes occurs and before any
+ *                  detachment occurs.
+ */
 int runChild(const char* cmd[], int state, int exception, int value,
              int share, int output, int delay, double timeout,
-             int poll, int repeat, int disown)
+             int poll, int repeat, int disown, int createNewGroup,
+             unsigned int interruptDelay)
 {
   int result = 1;
   kwsysProcess* kp = kwsysProcess_New();
@@ -388,7 +555,8 @@ int runChild(const char* cmd[], int state, int exception, int value,
   while(repeat-- > 0)
     {
     result = runChild2(kp, cmd, state, exception, value, share,
-                       output, delay, timeout, poll, disown);
+                       output, delay, timeout, poll, disown, createNewGroup,
+                       interruptDelay);
     }
   kwsysProcess_Delete(kp);
   return result;
@@ -435,7 +603,7 @@ int main(int argc, const char* argv[])
     n = atoi(argv[2]);
     }
   /* Check arguments.  */
-  if(((n >= 1 && n <= 8) || n == 108) && argc == 3)
+  if(((n >= 1 && n <= 10) || n == 108 || n == 109 || n == 110) && argc == 3)
     {
     /* This is the child process for a requested test number.  */
     switch (n)
@@ -448,15 +616,19 @@ int main(int argc, const char* argv[])
       case 6: test6(argc, argv); return 0;
       case 7: return test7(argc, argv);
       case 8: return test8(argc, argv);
+      case 9: return test9(argc, argv);
+      case 10: return test10(argc, argv);
       case 108: return test8_grandchild(argc, argv);
+      case 109: return test9_grandchild(argc, argv);
+      case 110: return test10_grandchild(argc, argv);
       }
     fprintf(stderr, "Invalid test number %d.\n", n);
     return 1;
     }
-  else if(n >= 1 && n <= 8)
+  else if(n >= 1 && n <= 10)
     {
     /* This is the parent process for a requested test number.  */
-    int states[8] =
+    int states[10] =
     {
       kwsysProcess_State_Exited,
       kwsysProcess_State_Exited,
@@ -465,9 +637,11 @@ int main(int argc, const char* argv[])
       kwsysProcess_State_Exited,
       kwsysProcess_State_Expired,
       kwsysProcess_State_Exited,
-      kwsysProcess_State_Exited
+      kwsysProcess_State_Exited,
+      kwsysProcess_State_Expired, /* Ctrl+C handler test */
+      kwsysProcess_State_Exception /* Process group test */
     };
-    int exceptions[8] =
+    int exceptions[10] =
     {
       kwsysProcess_Exception_None,
       kwsysProcess_Exception_None,
@@ -476,14 +650,19 @@ int main(int argc, const char* argv[])
       kwsysProcess_Exception_None,
       kwsysProcess_Exception_None,
       kwsysProcess_Exception_None,
-      kwsysProcess_Exception_None
+      kwsysProcess_Exception_None,
+      kwsysProcess_Exception_None,
+      kwsysProcess_Exception_Interrupt
     };
-    int values[8] = {0, 123, 1, 1, 0, 0, 0, 0};
-    int outputs[8] = {1, 1, 1, 1, 1, 0, 1, 1};
-    int delays[8] = {0, 0, 0, 0, 0, 1, 0, 0};
-    double timeouts[8] = {10, 10, 10, 30, 30, 10, -1, 10};
-    int polls[8] = {0, 0, 0, 0, 0, 0, 1, 0};
-    int repeat[8] = {2, 1, 1, 1, 1, 1, 1, 1};
+    int values[10] = {0, 123, 1, 1, 0, 0, 0, 0, 1, 1};
+    int shares[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1};
+    int outputs[10] = {1, 1, 1, 1, 1, 0, 1, 1, 1, 1};
+    int delays[10] = {0, 0, 0, 0, 0, 1, 0, 0, 0, 0};
+    double timeouts[10] = {10, 10, 10, 30, 30, 10, -1, 10, 6, 4};
+    int polls[10] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0};
+    int repeat[10] = {2, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+    int createNewGroups[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1};
+    unsigned int interruptDelays[10] = {0, 0, 0, 0, 0, 0, 0, 0, 3, 2};
     int r;
     const char* cmd[4];
 #ifdef _WIN32
@@ -515,9 +694,10 @@ int main(int argc, const char* argv[])
     fprintf(stderr, "Output on stderr before test %d.\n", n);
     fflush(stdout);
     fflush(stderr);
-    r = runChild(cmd, states[n-1], exceptions[n-1], values[n-1], 0,
+    r = runChild(cmd, states[n-1], exceptions[n-1], values[n-1], shares[n-1],
                  outputs[n-1], delays[n-1], timeouts[n-1],
-                 polls[n-1], repeat[n-1], 0);
+                 polls[n-1], repeat[n-1], 0, createNewGroups[n-1],
+                 interruptDelays[n-1]);
     fprintf(stdout, "Output on stdout after test %d.\n", n);
     fprintf(stderr, "Output on stderr after test %d.\n", n);
     fflush(stdout);
@@ -536,7 +716,8 @@ int main(int argc, const char* argv[])
     int exception = kwsysProcess_Exception_None;
     int value = 0;
     double timeout = 0;
-    int r = runChild(cmd, state, exception, value, 0, 1, 0, timeout, 0, 1, 0);
+    int r = runChild(cmd, state, exception, value, 0, 1, 0, timeout,
+      0, 1, 0, 0, 0);
     return r;
     }
   else