From 1feafc643b1c50fd0fa8171a4170065ca39d4d4c Mon Sep 17 00:00:00 2001 From: KWSys Robot Date: Thu, 30 Jul 2015 09:08:41 -0400 Subject: KWSys 2015-07-30 (f63febb7) Extract upstream KWSys using the following shell commands. $ git archive --prefix=upstream-kwsys/ f63febb7 | tar x $ git shortlog --no-merges --abbrev=8 --format='%h %s' c9336bcf..f63febb7 Brad King (1): 83b4a6b8 Process: Fix conversion warning in testProcess.c James Johnston (7): 4cd8846c Process: Remove trailing whitespace in ProcessUNIX.c b1c44c58 Process: Refactor sleeping code in testProcess.c. faff2ab0 Process: Wait for children to terminate on Ctrl+C. ef517b19 Process: Added initial support for process groups. 906c2cae Process: Added test cases for testing Ctrl+C and process groups. 52874e6a Process: Fix leaked file descriptor in ProcessUNIX f63febb7 Process: Fix error message for startup failure on Windows --- CMakeLists.txt | 6 +- CTestCustom.cmake.in | 15 ++ Process.h.in | 131 +++++++++------ ProcessUNIX.c | 357 +++++++++++++++++++++++++++++++++------ ProcessWin32.c | 462 +++++++++++++++++++++++++++++++++++++++++++++------ testProcess.c | 267 ++++++++++++++++++++++++----- 6 files changed, 1038 insertions(+), 200 deletions(-) create mode 100644 CTestCustom.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eb678b..017d619 100644 --- a/CMakeLists.txt +++ b/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) diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in new file mode 100644 index 0000000..d6f802e --- /dev/null +++ b/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 + ) diff --git a/Process.h.in b/Process.h.in index e35939f..c5ebc97 100644 --- a/Process.h.in +++ b/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 }; /** @@ -363,6 +375,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 * the child to terminate. @@ -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 diff --git a/ProcessUNIX.c b/ProcessUNIX.c index 0393a6d..6d9b109 100644 --- a/ProcessUNIX.c +++ b/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 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; } } @@ -1490,6 +1501,45 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) } /*--------------------------------------------------------------------------*/ +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) { int i; @@ -1539,10 +1589,28 @@ void kwsysProcess_Kill(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ +/* 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; } diff --git a/ProcessWin32.c b/ProcessWin32.c index a7dd2ca..1f8749f 100644 --- a/ProcessWin32.c +++ b/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; } } @@ -1460,6 +1490,52 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) } /*--------------------------------------------------------------------------*/ +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) { int i; @@ -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; +} diff --git a/testProcess.c b/testProcess.c index 47c3fb0..d0e20c1 100644 --- a/testProcess.c +++ b/testProcess.c @@ -29,26 +29,48 @@ # include #else # include +# include #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 -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 -- cgit v0.12