Merge branch 'upstream-kwsys' into update-kwsys

This commit is contained in:
Brad King 2015-07-31 09:26:35 -04:00
commit d0915bc86f
6 changed files with 1039 additions and 201 deletions

View File

@ -1237,7 +1237,7 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
IF(NOT CYGWIN) IF(NOT CYGWIN)
SET(KWSYS_TEST_PROCESS_7 7) SET(KWSYS_TEST_PROCESS_7 7)
ENDIF() 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}) ADD_TEST(kwsys.testProcess-${n} ${EXEC_DIR}/${KWSYS_NAMESPACE}TestProcess ${n})
SET_PROPERTY(TEST kwsys.testProcess-${n} PROPERTY LABELS ${KWSYS_LABELS_TEST}) SET_PROPERTY(TEST kwsys.testProcess-${n} PROPERTY LABELS ${KWSYS_LABELS_TEST})
SET_TESTS_PROPERTIES(kwsys.testProcess-${n} PROPERTIES TIMEOUT 120) 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}") MESSAGE(STATUS "GET_TEST_PROPERTY returned: ${wfv}")
ENDIF() 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. # Suppress known consistent failures on buggy systems.
IF(KWSYS_TEST_BOGUS_FAILURES) IF(KWSYS_TEST_BOGUS_FAILURES)
SET_TESTS_PROPERTIES(${KWSYS_TEST_BOGUS_FAILURES} PROPERTIES WILL_FAIL ON) SET_TESTS_PROPERTIES(${KWSYS_TEST_BOGUS_FAILURES} PROPERTIES WILL_FAIL ON)

View File

@ -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
)

View File

@ -23,58 +23,60 @@
# define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT # define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT
#endif #endif
#if !@KWSYS_NAMESPACE@_NAME_IS_KWSYS #if !@KWSYS_NAMESPACE@_NAME_IS_KWSYS
# define kwsysProcess kwsys_ns(Process) # define kwsysProcess kwsys_ns(Process)
# define kwsysProcess_s kwsys_ns(Process_s) # define kwsysProcess_s kwsys_ns(Process_s)
# define kwsysProcess_New kwsys_ns(Process_New) # define kwsysProcess_New kwsys_ns(Process_New)
# define kwsysProcess_Delete kwsys_ns(Process_Delete) # define kwsysProcess_Delete kwsys_ns(Process_Delete)
# define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand) # define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand)
# define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand) # define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand)
# define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout) # define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout)
# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory) # define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
# define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile) # define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile)
# define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative) # define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative)
# define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared) # define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared)
# define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach) # define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach)
# define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow) # define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow)
# define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput) # define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput)
# define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim) # define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim)
# define kwsysProcess_GetOption kwsys_ns(Process_GetOption) # define kwsysProcess_Option_CreateProcessGroup kwsys_ns(Process_Option_CreateProcessGroup)
# define kwsysProcess_SetOption kwsys_ns(Process_SetOption) # define kwsysProcess_GetOption kwsys_ns(Process_GetOption)
# define kwsysProcess_Option_e kwsys_ns(Process_Option_e) # define kwsysProcess_SetOption kwsys_ns(Process_SetOption)
# define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting) # define kwsysProcess_Option_e kwsys_ns(Process_Option_e)
# define kwsysProcess_State_Error kwsys_ns(Process_State_Error) # define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting)
# define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception) # define kwsysProcess_State_Error kwsys_ns(Process_State_Error)
# define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing) # define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception)
# define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited) # define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing)
# define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired) # define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited)
# define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed) # define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired)
# define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned) # define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed)
# define kwsysProcess_GetState kwsys_ns(Process_GetState) # define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned)
# define kwsysProcess_State_e kwsys_ns(Process_State_e) # define kwsysProcess_GetState kwsys_ns(Process_GetState)
# define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None) # define kwsysProcess_State_e kwsys_ns(Process_State_e)
# define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault) # define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None)
# define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal) # define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault)
# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt) # define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal)
# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical) # define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
# define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other) # define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
# define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException) # define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other)
# define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e) # define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException)
# define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode) # define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e)
# define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue) # define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode)
# define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString) # define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue)
# define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString) # define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString)
# define kwsysProcess_Execute kwsys_ns(Process_Execute) # define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString)
# define kwsysProcess_Disown kwsys_ns(Process_Disown) # define kwsysProcess_Execute kwsys_ns(Process_Execute)
# define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData) # define kwsysProcess_Disown kwsys_ns(Process_Disown)
# define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e) # define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData)
# define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None) # define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e)
# define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN) # define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None)
# define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT) # define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN)
# define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR) # define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT)
# define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout) # define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR)
# define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle) # define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout)
# define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit) # define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle)
# define kwsysProcess_Kill kwsys_ns(Process_Kill) # define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit)
# define kwsysProcess_Interrupt kwsys_ns(Process_Interrupt)
# define kwsysProcess_Kill kwsys_ns(Process_Kill)
#endif #endif
#if defined(__cplusplus) #if defined(__cplusplus)
@ -199,6 +201,15 @@ kwsysEXPORT void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe,
* and ignore the rest of the arguments. * and ignore the rest of the arguments.
* 0 = No (default) * 0 = No (default)
* 1 = Yes * 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 int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
kwsysEXPORT void kwsysProcess_SetOption(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_HideWindow,
kwsysProcess_Option_Detach, kwsysProcess_Option_Detach,
kwsysProcess_Option_MergeOutput, 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); 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. * Forcefully terminate the child process that is currently running.
* The caller should call WaitForExit after this returns to wait for * 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_HideWindow
# undef kwsysProcess_Option_MergeOutput # undef kwsysProcess_Option_MergeOutput
# undef kwsysProcess_Option_Verbatim # undef kwsysProcess_Option_Verbatim
# undef kwsysProcess_Option_CreateProcessGroup
# undef kwsysProcess_GetOption # undef kwsysProcess_GetOption
# undef kwsysProcess_SetOption # undef kwsysProcess_SetOption
# undef kwsysProcess_Option_e # undef kwsysProcess_Option_e
@ -430,6 +454,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
# undef kwsysProcess_Pipe_Timeout # undef kwsysProcess_Pipe_Timeout
# undef kwsysProcess_Pipe_Handle # undef kwsysProcess_Pipe_Handle
# undef kwsysProcess_WaitForExit # undef kwsysProcess_WaitForExit
# undef kwsysProcess_Interrupt
# undef kwsysProcess_Kill # undef kwsysProcess_Kill
# endif # endif
#endif #endif

View File

@ -151,6 +151,7 @@ typedef struct kwsysProcessCreateInformation_s
} kwsysProcessCreateInformation; } kwsysProcessCreateInformation;
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
static void kwsysProcessVolatileFree(volatile void* p);
static int kwsysProcessInitialize(kwsysProcess* cp); static int kwsysProcessInitialize(kwsysProcess* cp);
static void kwsysProcessCleanup(kwsysProcess* cp, int error); static void kwsysProcessCleanup(kwsysProcess* cp, int error);
static void kwsysProcessCleanupDescriptor(int* pfd); static void kwsysProcessCleanupDescriptor(int* pfd);
@ -197,7 +198,7 @@ struct kwsysProcess_s
{ {
/* The command lines to execute. */ /* The command lines to execute. */
char*** Commands; char*** Commands;
int NumberOfCommands; volatile int NumberOfCommands;
/* Descriptors for the read ends of the child's output pipes and /* Descriptors for the read ends of the child's output pipes and
the signal pipe. */ the signal pipe. */
@ -213,8 +214,10 @@ struct kwsysProcess_s
/* Buffer for pipe data. */ /* Buffer for pipe data. */
char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE]; char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE];
/* Process IDs returned by the calls to fork. */ /* Process IDs returned by the calls to fork. Everything is volatile
pid_t* ForkPIDs; 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. */ /* Flag for whether the children were terminated by a faild select. */
int SelectError; int SelectError;
@ -237,6 +240,9 @@ struct kwsysProcess_s
/* Whether to merge stdout/stderr of the child. */ /* Whether to merge stdout/stderr of the child. */
int MergeOutput; 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. */ /* Time at which the child started. Negative for no timeout. */
kwsysProcessTime StartTime; kwsysProcessTime StartTime;
@ -257,8 +263,9 @@ struct kwsysProcess_s
/* The number of children still executing. */ /* The number of children still executing. */
int CommandsLeft; int CommandsLeft;
/* The current status of the child process. */ /* The current status of the child process. Must be atomic because
int State; the signal handler checks this to avoid a race. */
volatile sig_atomic_t State;
/* The exceptional behavior that terminated the child process, if /* The exceptional behavior that terminated the child process, if
* any. */ * any. */
@ -271,7 +278,7 @@ struct kwsysProcess_s
int ExitValue; int ExitValue;
/* Whether the process was killed. */ /* Whether the process was killed. */
int Killed; volatile sig_atomic_t Killed;
/* Buffer for error message in case of failure. */ /* Buffer for error message in case of failure. */
char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1]; 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_Detach: return cp->OptionDetach;
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput; case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
case kwsysProcess_Option_Verbatim: return cp->Verbatim; case kwsysProcess_Option_Verbatim: return cp->Verbatim;
case kwsysProcess_Option_CreateProcessGroup:
return cp->CreateProcessGroup;
default: return 0; 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_Detach: cp->OptionDetach = value; break;
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break; case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break; case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
case kwsysProcess_Option_CreateProcessGroup:
cp->CreateProcessGroup = value; break;
default: break; default: break;
} }
} }
@ -1489,6 +1500,45 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
return 1; 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) void kwsysProcess_Kill(kwsysProcess* cp)
{ {
@ -1538,11 +1588,29 @@ void kwsysProcess_Kill(kwsysProcess* cp)
cp->CommandsLeft = 0; 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. */ /* Initialize a process control structure for kwsysProcess_Execute. */
static int kwsysProcessInitialize(kwsysProcess* cp) static int kwsysProcessInitialize(kwsysProcess* cp)
{ {
int i; int i;
volatile pid_t* oldForkPIDs;
for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
{ {
cp->PipeReadEnds[i] = -1; cp->PipeReadEnds[i] = -1;
@ -1571,16 +1639,21 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
cp->ErrorMessage[0] = 0; cp->ErrorMessage[0] = 0;
strcpy(cp->ExitExceptionString, "No exception"); 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) if(!cp->ForkPIDs)
{ {
return 0; 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) if(cp->CommandExitCodes)
{ {
@ -1671,7 +1744,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
/* Free memory. */ /* Free memory. */
if(cp->ForkPIDs) if(cp->ForkPIDs)
{ {
free(cp->ForkPIDs); kwsysProcessVolatileFree(cp->ForkPIDs);
cp->ForkPIDs = 0; cp->ForkPIDs = 0;
} }
if(cp->RealWorkingDirectory) 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, static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
kwsysProcessCreateInformation* si) kwsysProcessCreateInformation* si)
{ {
sigset_t mask, old_mask;
int pgidPipe[2];
char tmp;
ssize_t readRes;
/* Create the error reporting pipe. */ /* Create the error reporting pipe. */
if(pipe(si->ErrorPipe) < 0) if(pipe(si->ErrorPipe) < 0)
{ {
return 0; return 0;
} }
/* Set close-on-exec flag on the error pipe's write end. */ /* Create a pipe for detecting that the child process has created a process
if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0) 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; return 0;
} }
@ -1774,13 +1881,19 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
#if defined(__VMS) #if defined(__VMS)
/* VMS needs vfork and execvp to be in the same function because /* VMS needs vfork and execvp to be in the same function because
they use setjmp/longjmp to run the child startup code in the 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(); cp->ForkPIDs[prIndex] = vfork();
#else #else
cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si); cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si);
#endif #endif
if(cp->ForkPIDs[prIndex] < 0) 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; return 0;
} }
@ -1790,8 +1903,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
/* Specify standard pipes for child process. */ /* Specify standard pipes for child process. */
decc$set_child_standard_streams(si->StdIn, si->StdOut, si->StdErr); decc$set_child_standard_streams(si->StdIn, si->StdOut, si->StdErr);
#else #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(si->ErrorPipe[0]);
close(pgidPipe[0]);
/* Setup the stdin, stdout, and stderr pipes. */ /* Setup the stdin, stdout, and stderr pipes. */
if(si->StdIn > 0) if(si->StdIn > 0)
@ -1819,11 +1934,25 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
/* Restore all default signal handlers. */ /* Restore all default signal handlers. */
kwsysProcessRestoreDefaultSignalHandlers(); 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 #endif
/* Execute the real process. If successful, this does not return. */ /* Execute the real process. If successful, this does not return. */
execvp(cp->Commands[prIndex][0], cp->Commands[prIndex]); execvp(cp->Commands[prIndex][0], cp->Commands[prIndex]);
/* TODO: What does VMS do if the child fails to start? */ /* 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. */ /* Failure. Report error to parent and terminate. */
kwsysProcessChildErrorExit(si->ErrorPipe[1]); kwsysProcessChildErrorExit(si->ErrorPipe[1]);
@ -1834,12 +1963,34 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
decc$set_child_standard_streams(0, 1, 2); decc$set_child_standard_streams(0, 1, 2);
#endif #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. */ /* A child has been created. */
++cp->CommandsLeft; ++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 /* Block until the child's exec call succeeds and closes the error
pipe or writes data to the pipe to report an 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 /* A child process has terminated. Reap it if it is one handled by
this object. */ this object. */
int i; 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) for(i=0; i < cp->NumberOfCommands; ++i)
{ {
if(cp->ForkPIDs[i]) if(cp->ForkPIDs[i])
@ -1910,6 +2072,9 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
} }
} }
} }
/* Re-enable signals. */
sigprocmask(SIG_SETMASK, &old_mask, 0);
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@ -2582,19 +2747,23 @@ typedef struct kwsysProcessInstances_s
} kwsysProcessInstances; } kwsysProcessInstances;
static kwsysProcessInstances kwsysProcesses; static kwsysProcessInstances kwsysProcesses;
/* The old SIGCHLD handler. */ /* The old SIGCHLD / SIGINT / SIGTERM handlers. */
static struct sigaction kwsysProcessesOldSigChldAction; static struct sigaction kwsysProcessesOldSigChldAction;
static struct sigaction kwsysProcessesOldSigIntAction;
static struct sigaction kwsysProcessesOldSigTermAction;
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses) 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 TODO: sigprocmask is undefined for threaded apps. See
pthread_sigmask. */ pthread_sigmask. */
sigset_t newset; sigset_t newset;
sigset_t oldset; sigset_t oldset;
sigemptyset(&newset); sigemptyset(&newset);
sigaddset(&newset, SIGCHLD); sigaddset(&newset, SIGCHLD);
sigaddset(&newset, SIGINT);
sigaddset(&newset, SIGTERM);
sigprocmask(SIG_BLOCK, &newset, &oldset); sigprocmask(SIG_BLOCK, &newset, &oldset);
/* Store the new set in that seen by the signal handler. */ /* 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 /* Install our handler for SIGCHLD. Repeat call until it is not
interrupted. */ interrupted. */
struct sigaction newSigChldAction; struct sigaction newSigAction;
memset(&newSigChldAction, 0, sizeof(struct sigaction)); memset(&newSigAction, 0, sizeof(struct sigaction));
#if KWSYSPE_USE_SIGINFO #if KWSYSPE_USE_SIGINFO
newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler; newSigAction.sa_sigaction = kwsysProcessesSignalHandler;
newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; newSigAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
# ifdef SA_RESTART # ifdef SA_RESTART
newSigChldAction.sa_flags |= SA_RESTART; newSigAction.sa_flags |= SA_RESTART;
# endif # endif
#else #else
newSigChldAction.sa_handler = kwsysProcessesSignalHandler; newSigAction.sa_handler = kwsysProcessesSignalHandler;
newSigChldAction.sa_flags = SA_NOCLDSTOP; newSigAction.sa_flags = SA_NOCLDSTOP;
#endif #endif
while((sigaction(SIGCHLD, &newSigChldAction, sigemptyset(&newSigAction.sa_mask);
while((sigaction(SIGCHLD, &newSigAction,
&kwsysProcessesOldSigChldAction) < 0) && &kwsysProcessesOldSigChldAction) < 0) &&
(errno == EINTR)); (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 this was the last process, disable the signal handler. */
if(newProcesses.Count == 0) 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. */ interrupted. */
while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) && while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) &&
(errno == EINTR)); (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. /* Free the table of process pointers since it is now empty.
This is safe because the signal handler has been removed. */ This is safe because the signal handler has been removed. */
@ -2763,39 +2951,108 @@ static void kwsysProcessesSignalHandler(int signum
#endif #endif
) )
{ {
(void)signum; int i, j, procStatus, old_errno = errno;
#if KWSYSPE_USE_SIGINFO #if KWSYSPE_USE_SIGINFO
(void)info; (void)info;
(void)ucontext; (void)ucontext;
#endif #endif
/* Signal all process objects that a child has terminated. */ /* Signal all process objects that a child has terminated. */
{ switch(signum)
int i;
for(i=0; i < kwsysProcesses.Count; ++i)
{ {
/* Set the pipe in a signalled state. */ case SIGCHLD:
char buf = 1; for(i=0; i < kwsysProcesses.Count; ++i)
kwsysProcess* cp = kwsysProcesses.Processes[i]; {
kwsysProcess_ssize_t status= /* Set the pipe in a signalled state. */
read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); char buf = 1;
(void)status; kwsysProcess* cp = kwsysProcesses.Processes[i];
status=write(cp->SignalPipe, &buf, 1); kwsysProcess_ssize_t pipeStatus=
(void)status; 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 #if !KWSYSPE_USE_SIGINFO
/* Re-Install our handler for SIGCHLD. Repeat call until it is not /* Re-Install our handler. Repeat call until it is not interrupted. */
interrupted. */
{ {
struct sigaction newSigChldAction; struct sigaction newSigAction;
memset(&newSigChldAction, 0, sizeof(struct sigaction)); struct sigaction &oldSigAction;
memset(&newSigAction, 0, sizeof(struct sigaction));
newSigChldAction.sa_handler = kwsysProcessesSignalHandler; newSigChldAction.sa_handler = kwsysProcessesSignalHandler;
newSigChldAction.sa_flags = SA_NOCLDSTOP; newSigChldAction.sa_flags = SA_NOCLDSTOP;
while((sigaction(SIGCHLD, &newSigChldAction, sigemptyset(&newSigAction.sa_mask);
&kwsysProcessesOldSigChldAction) < 0) && 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)); (errno == EINTR));
} }
#endif #endif
errno = old_errno;
} }

View File

@ -109,14 +109,15 @@ static DWORD WINAPI kwsysProcessPipeThreadWake(LPVOID ptd);
static void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp, static void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp,
kwsysProcessPipeData* td); kwsysProcessPipeData* td);
static int kwsysProcessInitialize(kwsysProcess* cp); static int kwsysProcessInitialize(kwsysProcess* cp);
static int kwsysProcessCreate(kwsysProcess* cp, int index, static DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
kwsysProcessCreateInformation* si); kwsysProcessCreateInformation* si);
static void kwsysProcessDestroy(kwsysProcess* cp, int event); 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 kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle);
static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle); static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle);
static void kwsysProcessCleanupHandle(PHANDLE h); 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 void kwsysProcessCleanErrorMessage(kwsysProcess* cp);
static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
kwsysProcessTime* timeoutTime); kwsysProcessTime* timeoutTime);
@ -133,6 +134,13 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc
static void kwsysProcessSetExitException(kwsysProcess* cp, int code); static void kwsysProcessSetExitException(kwsysProcess* cp, int code);
static void kwsysProcessKillTree(int pid); static void kwsysProcessKillTree(int pid);
static void kwsysProcessDisablePipeThreads(kwsysProcess* cp); 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. */ /* A structure containing synchronization data for each thread. */
@ -222,6 +230,9 @@ struct kwsysProcess_s
/* Whether to merge stdout/stderr of the child. */ /* Whether to merge stdout/stderr of the child. */
int MergeOutput; 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. */ /* Mutex to protect the shared index used by threads to report data. */
HANDLE SharedIndexMutex; HANDLE SharedIndexMutex;
@ -321,6 +332,16 @@ kwsysProcess* kwsysProcess_New(void)
/* Windows version number data. */ /* Windows version number data. */
OSVERSIONINFO osv; 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. */ /* Allocate a process control structure. */
cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); cp = (kwsysProcess*)malloc(sizeof(kwsysProcess));
if(!cp) if(!cp)
@ -836,6 +857,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
case kwsysProcess_Option_HideWindow: return cp->HideWindow; case kwsysProcess_Option_HideWindow: return cp->HideWindow;
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput; case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
case kwsysProcess_Option_Verbatim: return cp->Verbatim; case kwsysProcess_Option_Verbatim: return cp->Verbatim;
case kwsysProcess_Option_CreateProcessGroup:
return cp->CreateProcessGroup;
default: return 0; 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_HideWindow: cp->HideWindow = value; break;
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break; case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break; case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
case kwsysProcess_Option_CreateProcessGroup:
cp->CreateProcessGroup = value; break;
default: break; default: break;
} }
} }
@ -945,7 +970,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength, if(!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength,
cp->RealWorkingDirectory)) cp->RealWorkingDirectory))
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, GetLastError());
return; return;
} }
SetCurrentDirectoryW(cp->WorkingDirectory); SetCurrentDirectoryW(cp->WorkingDirectory);
@ -957,14 +982,16 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{ {
/* Create a handle to read a file for stdin. */ /* Create a handle to read a file for stdin. */
wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN); wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN);
DWORD error;
cp->PipeChildStd[0] = cp->PipeChildStd[0] =
CreateFileW(wstdin, GENERIC_READ|GENERIC_WRITE, CreateFileW(wstdin, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
0, OPEN_EXISTING, 0, 0); 0, OPEN_EXISTING, 0, 0);
error = GetLastError(); /* Check now in case free changes this. */
free(wstdin); free(wstdin);
if(cp->PipeChildStd[0] == INVALID_HANDLE_VALUE) if(cp->PipeChildStd[0] == INVALID_HANDLE_VALUE)
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, error);
return; return;
} }
} }
@ -990,17 +1017,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read, if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read,
&cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0)) &cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0))
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, GetLastError());
return; return;
} }
if(cp->PipeFileSTDOUT) if(cp->PipeFileSTDOUT)
{ {
/* Use a file for stdout. */ /* Use a file for stdout. */
if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1], DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
cp->PipeFileSTDOUT)) cp->PipeFileSTDOUT);
if(error)
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, error);
return; return;
} }
} }
@ -1023,7 +1051,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
GetCurrentProcess(), &cp->PipeChildStd[1], GetCurrentProcess(), &cp->PipeChildStd[1],
0, FALSE, DUPLICATE_SAME_ACCESS)) 0, FALSE, DUPLICATE_SAME_ACCESS))
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, GetLastError());
return; return;
} }
} }
@ -1034,17 +1062,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read, if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read,
&cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0)) &cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0))
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, GetLastError());
return; return;
} }
if(cp->PipeFileSTDERR) if(cp->PipeFileSTDERR)
{ {
/* Use a file for stderr. */ /* Use a file for stderr. */
if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2], DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
cp->PipeFileSTDERR)) cp->PipeFileSTDERR);
if(error)
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, error);
return; return;
} }
} }
@ -1067,7 +1096,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
GetCurrentProcess(), &cp->PipeChildStd[2], GetCurrentProcess(), &cp->PipeChildStd[2],
0, FALSE, DUPLICATE_SAME_ACCESS)) 0, FALSE, DUPLICATE_SAME_ACCESS))
{ {
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, GetLastError());
return; return;
} }
} }
@ -1106,11 +1135,12 @@ void kwsysProcess_Execute(kwsysProcess* cp)
HANDLE p[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; HANDLE p[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
if (!CreatePipe(&p[0], &p[1], 0, 0)) if (!CreatePipe(&p[0], &p[1], 0, 0))
{ {
DWORD error = GetLastError();
if (nextStdInput != cp->PipeChildStd[0]) if (nextStdInput != cp->PipeChildStd[0])
{ {
kwsysProcessCleanupHandle(&nextStdInput); kwsysProcessCleanupHandle(&nextStdInput);
} }
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, error);
return; return;
} }
nextStdInput = p[0]; nextStdInput = p[0];
@ -1119,7 +1149,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
si.hStdError = cp->MergeOutput? cp->PipeChildStd[1] : cp->PipeChildStd[2]; 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. */ /* Close our copies of pipes used between children. */
if (si.hStdInput != cp->PipeChildStd[0]) if (si.hStdInput != cp->PipeChildStd[0])
@ -1134,7 +1164,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{ {
kwsysProcessCleanupHandle(&si.hStdError); kwsysProcessCleanupHandle(&si.hStdError);
} }
if (res) if (!error)
{ {
cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess; cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess;
} }
@ -1144,7 +1174,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{ {
kwsysProcessCleanupHandle(&nextStdInput); kwsysProcessCleanupHandle(&nextStdInput);
} }
kwsysProcessCleanup(cp, 1); kwsysProcessCleanup(cp, error);
return; return;
} }
} }
@ -1459,6 +1489,52 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
return 1; 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) void kwsysProcess_Kill(kwsysProcess* cp)
{ {
@ -1487,7 +1563,8 @@ void kwsysProcess_Kill(kwsysProcess* cp)
for(i=0; i < cp->NumberOfCommands; ++i) for(i=0; i < cp->NumberOfCommands; ++i)
{ {
kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId); 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].hThread);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); 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; DWORD flags;
@ -1697,13 +1774,19 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
if (flags & HANDLE_FLAG_INHERIT) if (flags & HANDLE_FLAG_INHERIT)
{ {
*out = in; *out = in;
return 1; return ERROR_SUCCESS;
} }
/* Create an inherited copy of this handle. */ /* Create an inherited copy of this handle. */
return DuplicateHandle(GetCurrentProcess(), in, if (DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), out,
GetCurrentProcess(), out, 0, TRUE, DUPLICATE_SAME_ACCESS))
0, TRUE, DUPLICATE_SAME_ACCESS); {
return ERROR_SUCCESS;
}
else
{
return GetLastError();
}
} }
else else
{ {
@ -1719,29 +1802,46 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
(GENERIC_WRITE | FILE_READ_ATTRIBUTES)), (GENERIC_WRITE | FILE_READ_ATTRIBUTES)),
FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
&sa, OPEN_EXISTING, 0, 0); &sa, OPEN_EXISTING, 0, 0);
return *out != INVALID_HANDLE_VALUE; return (*out != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError();
} }
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
int kwsysProcessCreate(kwsysProcess* cp, int index, DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
kwsysProcessCreateInformation* si) kwsysProcessCreateInformation* si)
{ {
int res = DWORD creationFlags;
DWORD error = ERROR_SUCCESS;
/* Create inherited copies the handles. */ /* Check if we are currently exiting. */
kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput, if (!kwsysTryEnterCreateProcessSection())
si->hStdInput, 1) && {
kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput, /* The Ctrl handler is currently working on exiting our process. Rather
si->hStdOutput, 0) && than return an error code, which could cause incorrect conclusions to be
kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError, reached by the caller, we simply hang. (For example, a CMake try_run
si->hStdError, 0) && configure step might cause the project to configure wrong.) */
Sleep(INFINITE);
}
/* Create the child in a suspended state so we can wait until all /* Create the child in a suspended state so we can wait until all
children have been created before running any one. */ children have been created before running any one. */
CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, CREATE_SUSPENDED, 0, creationFlags = CREATE_SUSPENDED;
0, &si->StartupInfo, &cp->ProcessInformation[index]); if (cp->CreateProcessGroup)
{
creationFlags |= CREATE_NEW_PROCESS_GROUP;
}
/* 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. */ /* Close the inherited copies of the handles. */
if (si->StartupInfo.hStdInput != si->hStdInput) if (si->StartupInfo.hStdInput != si->hStdInput)
@ -1757,7 +1857,23 @@ int kwsysProcessCreate(kwsysProcess* cp, int index,
kwsysProcessCleanupHandle(&si->StartupInfo.hStdError); 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, GetExitCodeProcess(cp->ProcessInformation[index].hProcess,
&cp->CommandExitCodes[index]); &cp->CommandExitCodes[index]);
/* Remove from global list of processes. */
kwsysProcessesRemove(cp->ProcessInformation[index].hProcess);
/* Close the process handle for the terminated process. */ /* Close the process handle for the terminated process. */
kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); 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; HANDLE fout;
wchar_t* wname; wchar_t* wname;
DWORD error;
if(!name) if(!name)
{ {
return 1; return ERROR_INVALID_PARAMETER;
} }
/* Close the existing handle. */ /* Close the existing handle. */
@ -1829,15 +1949,16 @@ int kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
wname = kwsysEncoding_DupToWide(name); wname = kwsysEncoding_DupToWide(name);
fout = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, 0, fout = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, 0,
CREATE_ALWAYS, 0, 0); CREATE_ALWAYS, 0, 0);
error = GetLastError();
free(wname); free(wname);
if(fout == INVALID_HANDLE_VALUE) if(fout == INVALID_HANDLE_VALUE)
{ {
return 0; return error;
} }
/* Assign the replacement handle. */ /* Assign the replacement handle. */
*phandle = fout; *phandle = fout;
return 1; return ERROR_SUCCESS;
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@ -1876,7 +1997,7 @@ void kwsysProcessCleanupHandle(PHANDLE h)
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/* Close all handles created by kwsysProcess_Execute. */ /* Close all handles created by kwsysProcess_Execute. */
void kwsysProcessCleanup(kwsysProcess* cp, int error) void kwsysProcessCleanup(kwsysProcess* cp, DWORD error)
{ {
int i; int i;
/* If this is an error case, report the error. */ /* If this is an error case, report the error. */
@ -1886,21 +2007,27 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error)
if(cp->ErrorMessage[0] == 0) if(cp->ErrorMessage[0] == 0)
{ {
/* Format the error message. */ /* Format the error message. */
DWORD original = GetLastError();
wchar_t err_msg[KWSYSPE_PIPE_BUFFER_SIZE]; wchar_t err_msg[KWSYSPE_PIPE_BUFFER_SIZE];
DWORD length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | DWORD length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, 0, original, FORMAT_MESSAGE_IGNORE_INSERTS, 0, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
err_msg, KWSYSPE_PIPE_BUFFER_SIZE, 0); 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) if(length < 1)
{ {
/* FormatMessage failed. Use a default message. */ /* FormatMessage failed. Use a default message. */
_snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE,
"Process execution failed with error 0x%X. " "Process execution failed with error 0x%X. "
"FormatMessage 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) 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].hThread);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
} }
@ -2659,3 +2788,230 @@ static void kwsysProcessDisablePipeThreads(kwsysProcess* cp)
ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); 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;
}

View File

@ -29,26 +29,48 @@
# include <windows.h> # include <windows.h>
#else #else
# include <unistd.h> # include <unistd.h>
# include <signal.h>
#endif #endif
#if defined(__BORLANDC__) #if defined(__BORLANDC__)
# pragma warn -8060 /* possibly incorrect assignment */ # pragma warn -8060 /* possibly incorrect assignment */
#endif #endif
/* Platform-specific sleep functions. */
#if defined(__BEOS__) && !defined(__ZETA__) #if defined(__BEOS__) && !defined(__ZETA__)
/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */ /* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
# include <be/kernel/OS.h> # include <be/kernel/OS.h>
static inline void testProcess_usleep(unsigned int msec) static inline void testProcess_usleep(unsigned int usec)
{ {
snooze(msec); snooze(usec);
}
#elif defined(_WIN32)
/* Windows can only sleep in millisecond intervals. */
static void testProcess_usleep(unsigned int usec)
{
Sleep(usec / 1000);
} }
#else #else
# define testProcess_usleep usleep # define testProcess_usleep usleep
#endif #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 runChild(const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout, int poll, 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[]) 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"); fprintf(stderr, "Output before sleep on stderr from timeout test.\n");
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
#if defined(_WIN32) testProcess_sleep(15);
Sleep(15000);
#else
sleep(15);
#endif
fprintf(stdout, "Output after sleep on stdout from timeout test.\n"); fprintf(stdout, "Output after sleep on stdout from timeout test.\n");
fprintf(stderr, "Output after sleep on stderr from timeout test.\n"); fprintf(stderr, "Output after sleep on stderr from timeout test.\n");
return 0; return 0;
@ -127,7 +145,7 @@ static int test5(int argc, const char* argv[])
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
r = runChild(cmd, kwsysProcess_State_Exception, 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(stdout, "Output on stdout after recursive test.\n");
fprintf(stderr, "Output on stderr after recursive test.\n"); fprintf(stderr, "Output on stderr after recursive test.\n");
fflush(stdout); fflush(stdout);
@ -168,11 +186,7 @@ static int test7(int argc, const char* argv[])
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
/* Sleep for 1 second. */ /* Sleep for 1 second. */
#if defined(_WIN32) testProcess_sleep(1);
Sleep(1000);
#else
sleep(1);
#endif
fprintf(stdout, "Output on stdout after sleep.\n"); fprintf(stdout, "Output on stdout after sleep.\n");
fprintf(stderr, "Output on stderr after sleep.\n"); fprintf(stderr, "Output on stderr after sleep.\n");
fflush(stdout); fflush(stdout);
@ -196,7 +210,7 @@ static int test8(int argc, const char* argv[])
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
r = runChild(cmd, kwsysProcess_State_Disowned, kwsysProcess_Exception_None, 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(stdout, "Output on stdout after grandchild test.\n");
fprintf(stderr, "Output on stderr after grandchild test.\n"); fprintf(stderr, "Output on stderr after grandchild test.\n");
fflush(stdout); fflush(stdout);
@ -217,18 +231,137 @@ static int test8_grandchild(int argc, const char* argv[])
implemented. */ implemented. */
fclose(stdout); fclose(stdout);
fclose(stderr); 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) #if defined(_WIN32)
Sleep(15000); static BOOL WINAPI test9_grandchild_handler(DWORD dwCtrlType)
#else {
sleep(15); /* 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 #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
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; return 0;
} }
static int runChild2(kwsysProcess* kp, static int runChild2(kwsysProcess* kp,
const char* cmd[], int state, int exception, int value, const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout, int share, int output, int delay, double timeout,
int poll, int disown) int poll, int disown, int createNewGroup,
unsigned int interruptDelay)
{ {
int result = 0; int result = 0;
char* data = 0; char* data = 0;
@ -249,6 +382,10 @@ static int runChild2(kwsysProcess* kp,
{ {
kwsysProcess_SetOption(kp, kwsysProcess_Option_Detach, 1); kwsysProcess_SetOption(kp, kwsysProcess_Option_Detach, 1);
} }
if(createNewGroup)
{
kwsysProcess_SetOption(kp, kwsysProcess_Option_CreateProcessGroup, 1);
}
kwsysProcess_Execute(kp); kwsysProcess_Execute(kp);
if(poll) if(poll)
@ -256,6 +393,12 @@ static int runChild2(kwsysProcess* kp,
pUserTimeout = &userTimeout; pUserTimeout = &userTimeout;
} }
if(interruptDelay)
{
testProcess_sleep(interruptDelay);
kwsysProcess_Interrupt(kp);
}
if(!share && !disown) if(!share && !disown)
{ {
int p; int p;
@ -286,17 +429,13 @@ static int runChild2(kwsysProcess* kp,
if(poll) if(poll)
{ {
/* Delay to avoid busy loop during polling. */ /* Delay to avoid busy loop during polling. */
#if defined(_WIN32)
Sleep(100);
#else
testProcess_usleep(100000); testProcess_usleep(100000);
#endif
} }
if(delay) if(delay)
{ {
/* Purposely sleeping only on Win32 to let pipe fill up. */ /* Purposely sleeping only on Win32 to let pipe fill up. */
#if defined(_WIN32) #if defined(_WIN32)
Sleep(100); testProcess_usleep(100000);
#endif #endif
} }
} }
@ -374,9 +513,37 @@ static int runChild2(kwsysProcess* kp,
return result; 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 runChild(const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout, 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; int result = 1;
kwsysProcess* kp = kwsysProcess_New(); kwsysProcess* kp = kwsysProcess_New();
@ -388,7 +555,8 @@ int runChild(const char* cmd[], int state, int exception, int value,
while(repeat-- > 0) while(repeat-- > 0)
{ {
result = runChild2(kp, cmd, state, exception, value, share, result = runChild2(kp, cmd, state, exception, value, share,
output, delay, timeout, poll, disown); output, delay, timeout, poll, disown, createNewGroup,
interruptDelay);
} }
kwsysProcess_Delete(kp); kwsysProcess_Delete(kp);
return result; return result;
@ -435,7 +603,7 @@ int main(int argc, const char* argv[])
n = atoi(argv[2]); n = atoi(argv[2]);
} }
/* Check arguments. */ /* 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. */ /* This is the child process for a requested test number. */
switch (n) switch (n)
@ -448,15 +616,19 @@ int main(int argc, const char* argv[])
case 6: test6(argc, argv); return 0; case 6: test6(argc, argv); return 0;
case 7: return test7(argc, argv); case 7: return test7(argc, argv);
case 8: return test8(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 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); fprintf(stderr, "Invalid test number %d.\n", n);
return 1; return 1;
} }
else if(n >= 1 && n <= 8) else if(n >= 1 && n <= 10)
{ {
/* This is the parent process for a requested test number. */ /* This is the parent process for a requested test number. */
int states[8] = int states[10] =
{ {
kwsysProcess_State_Exited, kwsysProcess_State_Exited,
kwsysProcess_State_Exited, kwsysProcess_State_Exited,
@ -465,9 +637,11 @@ int main(int argc, const char* argv[])
kwsysProcess_State_Exited, kwsysProcess_State_Exited,
kwsysProcess_State_Expired, kwsysProcess_State_Expired,
kwsysProcess_State_Exited, 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,
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_None kwsysProcess_Exception_None,
kwsysProcess_Exception_None,
kwsysProcess_Exception_Interrupt
}; };
int values[8] = {0, 123, 1, 1, 0, 0, 0, 0}; int values[10] = {0, 123, 1, 1, 0, 0, 0, 0, 1, 1};
int outputs[8] = {1, 1, 1, 1, 1, 0, 1, 1}; int shares[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1};
int delays[8] = {0, 0, 0, 0, 0, 1, 0, 0}; int outputs[10] = {1, 1, 1, 1, 1, 0, 1, 1, 1, 1};
double timeouts[8] = {10, 10, 10, 30, 30, 10, -1, 10}; int delays[10] = {0, 0, 0, 0, 0, 1, 0, 0, 0, 0};
int polls[8] = {0, 0, 0, 0, 0, 0, 1, 0}; double timeouts[10] = {10, 10, 10, 30, 30, 10, -1, 10, 6, 4};
int repeat[8] = {2, 1, 1, 1, 1, 1, 1, 1}; 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; int r;
const char* cmd[4]; const char* cmd[4];
#ifdef _WIN32 #ifdef _WIN32
@ -515,9 +694,10 @@ int main(int argc, const char* argv[])
fprintf(stderr, "Output on stderr before test %d.\n", n); fprintf(stderr, "Output on stderr before test %d.\n", n);
fflush(stdout); fflush(stdout);
fflush(stderr); 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], 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(stdout, "Output on stdout after test %d.\n", n);
fprintf(stderr, "Output on stderr after test %d.\n", n); fprintf(stderr, "Output on stderr after test %d.\n", n);
fflush(stdout); fflush(stdout);
@ -536,7 +716,8 @@ int main(int argc, const char* argv[])
int exception = kwsysProcess_Exception_None; int exception = kwsysProcess_Exception_None;
int value = 0; int value = 0;
double timeout = 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; return r;
} }
else else