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
This commit is contained in:
parent
dc822da815
commit
1feafc643b
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
27
Process.h.in
27
Process.h.in
|
@ -38,6 +38,7 @@
|
|||
# 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)
|
||||
|
@ -74,6 +75,7 @@
|
|||
# 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
|
||||
|
||||
|
@ -199,6 +201,15 @@ kwsysEXPORT void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe,
|
|||
* and ignore the rest of the arguments.
|
||||
* 0 = No (default)
|
||||
* 1 = Yes
|
||||
*
|
||||
* kwsysProcess_Option_CreateProcessGroup = Whether to place the process in a
|
||||
* new process group. This is
|
||||
* useful if you want to send Ctrl+C
|
||||
* to the process. On UNIX, also
|
||||
* places the process in a new
|
||||
* session.
|
||||
* 0 = No (default)
|
||||
* 1 = Yes
|
||||
*/
|
||||
kwsysEXPORT int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
|
||||
kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
|
||||
|
@ -208,7 +219,8 @@ enum kwsysProcess_Option_e
|
|||
kwsysProcess_Option_HideWindow,
|
||||
kwsysProcess_Option_Detach,
|
||||
kwsysProcess_Option_MergeOutput,
|
||||
kwsysProcess_Option_Verbatim
|
||||
kwsysProcess_Option_Verbatim,
|
||||
kwsysProcess_Option_CreateProcessGroup
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -362,6 +374,17 @@ enum kwsysProcess_Pipes_e
|
|||
*/
|
||||
kwsysEXPORT int kwsysProcess_WaitForExit(kwsysProcess* cp, double* timeout);
|
||||
|
||||
/**
|
||||
* Interrupt the process group for the child process that is currently
|
||||
* running by sending it the appropriate operating-system specific signal.
|
||||
* The caller should call WaitForExit after this returns to wait for the
|
||||
* child to terminate.
|
||||
*
|
||||
* WARNING: If you didn't specify kwsysProcess_Option_CreateProcessGroup,
|
||||
* you will interrupt your own process group.
|
||||
*/
|
||||
kwsysEXPORT void kwsysProcess_Interrupt(kwsysProcess* cp);
|
||||
|
||||
/**
|
||||
* Forcefully terminate the child process that is currently running.
|
||||
* The caller should call WaitForExit after this returns to wait for
|
||||
|
@ -394,6 +417,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
|
|||
# undef kwsysProcess_Option_HideWindow
|
||||
# undef kwsysProcess_Option_MergeOutput
|
||||
# undef kwsysProcess_Option_Verbatim
|
||||
# undef kwsysProcess_Option_CreateProcessGroup
|
||||
# undef kwsysProcess_GetOption
|
||||
# undef kwsysProcess_SetOption
|
||||
# undef kwsysProcess_Option_e
|
||||
|
@ -430,6 +454,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
|
|||
# undef kwsysProcess_Pipe_Timeout
|
||||
# undef kwsysProcess_Pipe_Handle
|
||||
# undef kwsysProcess_WaitForExit
|
||||
# undef kwsysProcess_Interrupt
|
||||
# undef kwsysProcess_Kill
|
||||
# endif
|
||||
#endif
|
||||
|
|
341
ProcessUNIX.c
341
ProcessUNIX.c
|
@ -151,6 +151,7 @@ typedef struct kwsysProcessCreateInformation_s
|
|||
} kwsysProcessCreateInformation;
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
static void kwsysProcessVolatileFree(volatile void* p);
|
||||
static int kwsysProcessInitialize(kwsysProcess* cp);
|
||||
static void kwsysProcessCleanup(kwsysProcess* cp, int error);
|
||||
static void kwsysProcessCleanupDescriptor(int* pfd);
|
||||
|
@ -197,7 +198,7 @@ struct kwsysProcess_s
|
|||
{
|
||||
/* The command lines to execute. */
|
||||
char*** Commands;
|
||||
int NumberOfCommands;
|
||||
volatile int NumberOfCommands;
|
||||
|
||||
/* Descriptors for the read ends of the child's output pipes and
|
||||
the signal pipe. */
|
||||
|
@ -213,8 +214,10 @@ struct kwsysProcess_s
|
|||
/* Buffer for pipe data. */
|
||||
char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE];
|
||||
|
||||
/* Process IDs returned by the calls to fork. */
|
||||
pid_t* ForkPIDs;
|
||||
/* Process IDs returned by the calls to fork. Everything is volatile
|
||||
because the signal handler accesses them. You must be very careful
|
||||
when reaping PIDs or modifying this array to avoid race conditions. */
|
||||
volatile pid_t* volatile ForkPIDs;
|
||||
|
||||
/* Flag for whether the children were terminated by a faild select. */
|
||||
int SelectError;
|
||||
|
@ -237,6 +240,9 @@ struct kwsysProcess_s
|
|||
/* Whether to merge stdout/stderr of the child. */
|
||||
int MergeOutput;
|
||||
|
||||
/* Whether to create the process in a new process group. */
|
||||
volatile sig_atomic_t CreateProcessGroup;
|
||||
|
||||
/* Time at which the child started. Negative for no timeout. */
|
||||
kwsysProcessTime StartTime;
|
||||
|
||||
|
@ -257,8 +263,9 @@ struct kwsysProcess_s
|
|||
/* The number of children still executing. */
|
||||
int CommandsLeft;
|
||||
|
||||
/* The current status of the child process. */
|
||||
int State;
|
||||
/* The current status of the child process. Must be atomic because
|
||||
the signal handler checks this to avoid a race. */
|
||||
volatile sig_atomic_t State;
|
||||
|
||||
/* The exceptional behavior that terminated the child process, if
|
||||
* any. */
|
||||
|
@ -271,7 +278,7 @@ struct kwsysProcess_s
|
|||
int ExitValue;
|
||||
|
||||
/* Whether the process was killed. */
|
||||
int Killed;
|
||||
volatile sig_atomic_t Killed;
|
||||
|
||||
/* Buffer for error message in case of failure. */
|
||||
char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1];
|
||||
|
@ -649,6 +656,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
|
|||
case kwsysProcess_Option_Detach: return cp->OptionDetach;
|
||||
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
|
||||
case kwsysProcess_Option_Verbatim: return cp->Verbatim;
|
||||
case kwsysProcess_Option_CreateProcessGroup:
|
||||
return cp->CreateProcessGroup;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
@ -666,6 +675,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
|
|||
case kwsysProcess_Option_Detach: cp->OptionDetach = value; break;
|
||||
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
|
||||
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
|
||||
case kwsysProcess_Option_CreateProcessGroup:
|
||||
cp->CreateProcessGroup = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -1489,6 +1500,45 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
|
|||
return 1;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
void kwsysProcess_Interrupt(kwsysProcess* cp)
|
||||
{
|
||||
int i;
|
||||
/* Make sure we are executing a process. */
|
||||
if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
|
||||
cp->Killed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Interrupt the children. */
|
||||
if (cp->CreateProcessGroup)
|
||||
{
|
||||
if(cp->ForkPIDs)
|
||||
{
|
||||
for(i=0; i < cp->NumberOfCommands; ++i)
|
||||
{
|
||||
/* Make sure the PID is still valid. */
|
||||
if(cp->ForkPIDs[i])
|
||||
{
|
||||
/* The user created a process group for this process. The group ID
|
||||
is the process ID for the original process in the group. */
|
||||
kill(-cp->ForkPIDs[i], SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No process group was created. Kill our own process group.
|
||||
NOTE: While one could argue that we could call kill(cp->ForkPIDs[i],
|
||||
SIGINT) as a way to still interrupt the process even though it's not in
|
||||
a special group, this is not an option on Windows. Therefore, we kill
|
||||
the current process group for consistency with Windows. */
|
||||
kill(0, SIGINT);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
void kwsysProcess_Kill(kwsysProcess* cp)
|
||||
{
|
||||
|
@ -1538,11 +1588,29 @@ void kwsysProcess_Kill(kwsysProcess* cp)
|
|||
cp->CommandsLeft = 0;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* Call the free() function with a pointer to volatile without causing
|
||||
compiler warnings. */
|
||||
static void kwsysProcessVolatileFree(volatile void* p)
|
||||
{
|
||||
/* clang has made it impossible to free memory that points to volatile
|
||||
without first using special pragmas to disable a warning... */
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wcast-qual"
|
||||
#endif
|
||||
free((void*)p); /* The cast will silence most compilers, but not clang. */
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* Initialize a process control structure for kwsysProcess_Execute. */
|
||||
static int kwsysProcessInitialize(kwsysProcess* cp)
|
||||
{
|
||||
int i;
|
||||
volatile pid_t* oldForkPIDs;
|
||||
for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
|
||||
{
|
||||
cp->PipeReadEnds[i] = -1;
|
||||
|
@ -1571,16 +1639,21 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
|
|||
cp->ErrorMessage[0] = 0;
|
||||
strcpy(cp->ExitExceptionString, "No exception");
|
||||
|
||||
if(cp->ForkPIDs)
|
||||
oldForkPIDs = cp->ForkPIDs;
|
||||
cp->ForkPIDs = (volatile pid_t*)malloc(
|
||||
sizeof(volatile pid_t)*(size_t)(cp->NumberOfCommands));
|
||||
if(oldForkPIDs)
|
||||
{
|
||||
free(cp->ForkPIDs);
|
||||
kwsysProcessVolatileFree(oldForkPIDs);
|
||||
}
|
||||
cp->ForkPIDs = (pid_t*)malloc(sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
|
||||
if(!cp->ForkPIDs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
memset(cp->ForkPIDs, 0, sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
|
||||
for(i=0; i < cp->NumberOfCommands; ++i)
|
||||
{
|
||||
cp->ForkPIDs[i] = 0; /* can't use memset due to volatile */
|
||||
}
|
||||
|
||||
if(cp->CommandExitCodes)
|
||||
{
|
||||
|
@ -1671,7 +1744,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
|
|||
/* Free memory. */
|
||||
if(cp->ForkPIDs)
|
||||
{
|
||||
free(cp->ForkPIDs);
|
||||
kwsysProcessVolatileFree(cp->ForkPIDs);
|
||||
cp->ForkPIDs = 0;
|
||||
}
|
||||
if(cp->RealWorkingDirectory)
|
||||
|
@ -1758,15 +1831,49 @@ int decc$set_child_standard_streams(int fd1, int fd2, int fd3);
|
|||
static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
|
||||
kwsysProcessCreateInformation* si)
|
||||
{
|
||||
sigset_t mask, old_mask;
|
||||
int pgidPipe[2];
|
||||
char tmp;
|
||||
ssize_t readRes;
|
||||
|
||||
/* Create the error reporting pipe. */
|
||||
if(pipe(si->ErrorPipe) < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set close-on-exec flag on the error pipe's write end. */
|
||||
if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0)
|
||||
/* Create a pipe for detecting that the child process has created a process
|
||||
group and session. */
|
||||
if(pipe(pgidPipe) < 0)
|
||||
{
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set close-on-exec flag on the pipe's write end. */
|
||||
if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0 ||
|
||||
fcntl(pgidPipe[1], F_SETFD, FD_CLOEXEC) < 0)
|
||||
{
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Block SIGINT / SIGTERM while we start. The purpose is so that our signal
|
||||
handler doesn't get called from the child process after the fork and
|
||||
before the exec, and subsequently start kill()'ing PIDs from ForkPIDs. */
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGINT);
|
||||
sigaddset(&mask, SIGTERM);
|
||||
if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
|
||||
{
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1774,13 +1881,19 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
|
|||
#if defined(__VMS)
|
||||
/* VMS needs vfork and execvp to be in the same function because
|
||||
they use setjmp/longjmp to run the child startup code in the
|
||||
parent! TODO: OptionDetach. */
|
||||
parent! TODO: OptionDetach. Also
|
||||
TODO: CreateProcessGroup. */
|
||||
cp->ForkPIDs[prIndex] = vfork();
|
||||
#else
|
||||
cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si);
|
||||
#endif
|
||||
if(cp->ForkPIDs[prIndex] < 0)
|
||||
{
|
||||
sigprocmask(SIG_SETMASK, &old_mask, 0);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1790,8 +1903,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
|
|||
/* Specify standard pipes for child process. */
|
||||
decc$set_child_standard_streams(si->StdIn, si->StdOut, si->StdErr);
|
||||
#else
|
||||
/* Close the read end of the error reporting pipe. */
|
||||
/* Close the read end of the error reporting / process group
|
||||
setup pipe. */
|
||||
close(si->ErrorPipe[0]);
|
||||
close(pgidPipe[0]);
|
||||
|
||||
/* Setup the stdin, stdout, and stderr pipes. */
|
||||
if(si->StdIn > 0)
|
||||
|
@ -1819,11 +1934,25 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
|
|||
|
||||
/* Restore all default signal handlers. */
|
||||
kwsysProcessRestoreDefaultSignalHandlers();
|
||||
|
||||
/* Now that we have restored default signal handling and created the
|
||||
process group, restore mask. */
|
||||
sigprocmask(SIG_SETMASK, &old_mask, 0);
|
||||
|
||||
/* Create new process group. We use setsid instead of setpgid to avoid
|
||||
the child getting hung up on signals like SIGTTOU. (In the real world,
|
||||
this has been observed where "git svn" ends up calling the "resize"
|
||||
program which opens /dev/tty. */
|
||||
if(cp->CreateProcessGroup && setsid() < 0)
|
||||
{
|
||||
kwsysProcessChildErrorExit(si->ErrorPipe[1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Execute the real process. If successful, this does not return. */
|
||||
execvp(cp->Commands[prIndex][0], cp->Commands[prIndex]);
|
||||
/* TODO: What does VMS do if the child fails to start? */
|
||||
/* TODO: On VMS, how do we put the process in a new group? */
|
||||
|
||||
/* Failure. Report error to parent and terminate. */
|
||||
kwsysProcessChildErrorExit(si->ErrorPipe[1]);
|
||||
|
@ -1834,12 +1963,34 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
|
|||
decc$set_child_standard_streams(0, 1, 2);
|
||||
#endif
|
||||
|
||||
/* We are done with the error reporting pipe and process group setup pipe
|
||||
write end. */
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[1]);
|
||||
|
||||
/* Make sure the child is in the process group before we proceed. This
|
||||
avoids race conditions with calls to the kill function that we make for
|
||||
signalling process groups. */
|
||||
while((readRes = read(pgidPipe[0], &tmp, 1)) > 0);
|
||||
if(readRes < 0)
|
||||
{
|
||||
sigprocmask(SIG_SETMASK, &old_mask, 0);
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[0]);
|
||||
return 0;
|
||||
}
|
||||
kwsysProcessCleanupDescriptor(&pgidPipe[0]);
|
||||
|
||||
/* Unmask signals. */
|
||||
if(sigprocmask(SIG_SETMASK, &old_mask, 0) < 0)
|
||||
{
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A child has been created. */
|
||||
++cp->CommandsLeft;
|
||||
|
||||
/* We are done with the error reporting pipe write end. */
|
||||
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
|
||||
|
||||
/* Block until the child's exec call succeeds and closes the error
|
||||
pipe or writes data to the pipe to report an error. */
|
||||
{
|
||||
|
@ -1877,6 +2028,17 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
|
|||
/* A child process has terminated. Reap it if it is one handled by
|
||||
this object. */
|
||||
int i;
|
||||
/* Temporarily disable signals that access ForkPIDs. We don't want them to
|
||||
read a reaped PID, and writes to ForkPIDs are not atomic. */
|
||||
sigset_t mask, old_mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGINT);
|
||||
sigaddset(&mask, SIGTERM);
|
||||
if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(i=0; i < cp->NumberOfCommands; ++i)
|
||||
{
|
||||
if(cp->ForkPIDs[i])
|
||||
|
@ -1910,6 +2072,9 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-enable signals. */
|
||||
sigprocmask(SIG_SETMASK, &old_mask, 0);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
@ -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. */
|
||||
switch(signum)
|
||||
{
|
||||
int i;
|
||||
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 status=
|
||||
kwsysProcess_ssize_t pipeStatus=
|
||||
read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
|
||||
(void)status;
|
||||
status=write(cp->SignalPipe, &buf, 1);
|
||||
(void)status;
|
||||
(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !KWSYSPE_USE_SIGINFO
|
||||
/* Re-Install our handler for SIGCHLD. Repeat call until it is not
|
||||
interrupted. */
|
||||
/* Wait for all processes to terminate. */
|
||||
while(wait(&procStatus) >= 0 || errno != ECHILD)
|
||||
{
|
||||
struct sigaction newSigChldAction;
|
||||
memset(&newSigChldAction, 0, sizeof(struct sigaction));
|
||||
}
|
||||
|
||||
/* 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. Repeat call until it is not interrupted. */
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
454
ProcessWin32.c
454
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,
|
||||
static DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
|
||||
kwsysProcessCreateInformation* si);
|
||||
static void kwsysProcessDestroy(kwsysProcess* cp, int event);
|
||||
static int kwsysProcessSetupOutputPipeFile(PHANDLE handle, const char* name);
|
||||
static DWORD kwsysProcessSetupOutputPipeFile(PHANDLE handle,
|
||||
const char* name);
|
||||
static void kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle);
|
||||
static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle);
|
||||
static void kwsysProcessCleanupHandle(PHANDLE h);
|
||||
static void kwsysProcessCleanup(kwsysProcess* cp, int error);
|
||||
static void kwsysProcessCleanup(kwsysProcess* cp, DWORD error);
|
||||
static void kwsysProcessCleanErrorMessage(kwsysProcess* cp);
|
||||
static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
|
||||
kwsysProcessTime* timeoutTime);
|
||||
|
@ -133,6 +134,13 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc
|
|||
static void kwsysProcessSetExitException(kwsysProcess* cp, int code);
|
||||
static void kwsysProcessKillTree(int pid);
|
||||
static void kwsysProcessDisablePipeThreads(kwsysProcess* cp);
|
||||
static int kwsysProcessesInitialize(void);
|
||||
static int kwsysTryEnterCreateProcessSection(void);
|
||||
static void kwsysLeaveCreateProcessSection(void);
|
||||
static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessId,
|
||||
int newProcessGroup);
|
||||
static void kwsysProcessesRemove(HANDLE hProcess);
|
||||
static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType);
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* A structure containing synchronization data for each thread. */
|
||||
|
@ -222,6 +230,9 @@ struct kwsysProcess_s
|
|||
/* Whether to merge stdout/stderr of the child. */
|
||||
int MergeOutput;
|
||||
|
||||
/* Whether to create the process in a new process group. */
|
||||
int CreateProcessGroup;
|
||||
|
||||
/* Mutex to protect the shared index used by threads to report data. */
|
||||
HANDLE SharedIndexMutex;
|
||||
|
||||
|
@ -321,6 +332,16 @@ kwsysProcess* kwsysProcess_New(void)
|
|||
/* Windows version number data. */
|
||||
OSVERSIONINFO osv;
|
||||
|
||||
/* Initialize list of processes before we get any farther. It's especially
|
||||
important that the console Ctrl handler be added BEFORE starting the
|
||||
first process. This prevents the risk of an orphaned process being
|
||||
started by the main thread while the default Ctrl handler is in
|
||||
progress. */
|
||||
if(!kwsysProcessesInitialize())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate a process control structure. */
|
||||
cp = (kwsysProcess*)malloc(sizeof(kwsysProcess));
|
||||
if(!cp)
|
||||
|
@ -836,6 +857,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
|
|||
case kwsysProcess_Option_HideWindow: return cp->HideWindow;
|
||||
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
|
||||
case kwsysProcess_Option_Verbatim: return cp->Verbatim;
|
||||
case kwsysProcess_Option_CreateProcessGroup:
|
||||
return cp->CreateProcessGroup;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
@ -854,6 +877,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
|
|||
case kwsysProcess_Option_HideWindow: cp->HideWindow = value; break;
|
||||
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
|
||||
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
|
||||
case kwsysProcess_Option_CreateProcessGroup:
|
||||
cp->CreateProcessGroup = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -945,7 +970,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
if(!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength,
|
||||
cp->RealWorkingDirectory))
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, GetLastError());
|
||||
return;
|
||||
}
|
||||
SetCurrentDirectoryW(cp->WorkingDirectory);
|
||||
|
@ -957,14 +982,16 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
{
|
||||
/* Create a handle to read a file for stdin. */
|
||||
wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN);
|
||||
DWORD error;
|
||||
cp->PipeChildStd[0] =
|
||||
CreateFileW(wstdin, GENERIC_READ|GENERIC_WRITE,
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||||
0, OPEN_EXISTING, 0, 0);
|
||||
error = GetLastError(); /* Check now in case free changes this. */
|
||||
free(wstdin);
|
||||
if(cp->PipeChildStd[0] == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -990,17 +1017,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read,
|
||||
&cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0))
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
if(cp->PipeFileSTDOUT)
|
||||
{
|
||||
/* Use a file for stdout. */
|
||||
if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
|
||||
cp->PipeFileSTDOUT))
|
||||
DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
|
||||
cp->PipeFileSTDOUT);
|
||||
if(error)
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1023,7 +1051,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
GetCurrentProcess(), &cp->PipeChildStd[1],
|
||||
0, FALSE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1034,17 +1062,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read,
|
||||
&cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0))
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
if(cp->PipeFileSTDERR)
|
||||
{
|
||||
/* Use a file for stderr. */
|
||||
if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
|
||||
cp->PipeFileSTDERR))
|
||||
DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
|
||||
cp->PipeFileSTDERR);
|
||||
if(error)
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1067,7 +1096,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
GetCurrentProcess(), &cp->PipeChildStd[2],
|
||||
0, FALSE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1106,11 +1135,12 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
HANDLE p[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
|
||||
if (!CreatePipe(&p[0], &p[1], 0, 0))
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (nextStdInput != cp->PipeChildStd[0])
|
||||
{
|
||||
kwsysProcessCleanupHandle(&nextStdInput);
|
||||
}
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, error);
|
||||
return;
|
||||
}
|
||||
nextStdInput = p[0];
|
||||
|
@ -1119,7 +1149,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
si.hStdError = cp->MergeOutput? cp->PipeChildStd[1] : cp->PipeChildStd[2];
|
||||
|
||||
{
|
||||
int res = kwsysProcessCreate(cp, i, &si);
|
||||
DWORD error = kwsysProcessCreate(cp, i, &si);
|
||||
|
||||
/* Close our copies of pipes used between children. */
|
||||
if (si.hStdInput != cp->PipeChildStd[0])
|
||||
|
@ -1134,7 +1164,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
{
|
||||
kwsysProcessCleanupHandle(&si.hStdError);
|
||||
}
|
||||
if (res)
|
||||
if (!error)
|
||||
{
|
||||
cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess;
|
||||
}
|
||||
|
@ -1144,7 +1174,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
|
|||
{
|
||||
kwsysProcessCleanupHandle(&nextStdInput);
|
||||
}
|
||||
kwsysProcessCleanup(cp, 1);
|
||||
kwsysProcessCleanup(cp, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1459,6 +1489,52 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
|
|||
return 1;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
void kwsysProcess_Interrupt(kwsysProcess* cp)
|
||||
{
|
||||
int i;
|
||||
/* Make sure we are executing a process. */
|
||||
if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
|
||||
cp->Killed)
|
||||
{
|
||||
KWSYSPE_DEBUG((stderr, "interrupt: child not executing\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Skip actually interrupting the child if it has already terminated. */
|
||||
if(cp->Terminated)
|
||||
{
|
||||
KWSYSPE_DEBUG((stderr, "interrupt: child already terminated\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Interrupt the children. */
|
||||
if (cp->CreateProcessGroup)
|
||||
{
|
||||
if(cp->ProcessInformation)
|
||||
{
|
||||
for(i=0; i < cp->NumberOfCommands; ++i)
|
||||
{
|
||||
/* Make sure the process handle isn't closed (e.g. from disowning). */
|
||||
if(cp->ProcessInformation[i].hProcess)
|
||||
{
|
||||
/* The user created a process group for this process. The group ID
|
||||
is the process ID for the original process in the group. Note
|
||||
that we have to use Ctrl+Break: Ctrl+C is not allowed for process
|
||||
groups. */
|
||||
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
|
||||
cp->ProcessInformation[i].dwProcessId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No process group was created. Kill our own process group... */
|
||||
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
void kwsysProcess_Kill(kwsysProcess* cp)
|
||||
{
|
||||
|
@ -1487,7 +1563,8 @@ void kwsysProcess_Kill(kwsysProcess* cp)
|
|||
for(i=0; i < cp->NumberOfCommands; ++i)
|
||||
{
|
||||
kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId);
|
||||
// close the handle if we kill it
|
||||
/* Remove from global list of processes and close handles. */
|
||||
kwsysProcessesRemove(cp->ProcessInformation[i].hProcess);
|
||||
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread);
|
||||
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
|
||||
}
|
||||
|
@ -1686,7 +1763,7 @@ int kwsysProcessInitialize(kwsysProcess* cp)
|
|||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
|
||||
static DWORD kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
|
||||
{
|
||||
DWORD flags;
|
||||
|
||||
|
@ -1697,13 +1774,19 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
|
|||
if (flags & HANDLE_FLAG_INHERIT)
|
||||
{
|
||||
*out = in;
|
||||
return 1;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/* Create an inherited copy of this handle. */
|
||||
return DuplicateHandle(GetCurrentProcess(), in,
|
||||
GetCurrentProcess(), out,
|
||||
0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||
if (DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), out,
|
||||
0, TRUE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetLastError();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1719,29 +1802,46 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
|
|||
(GENERIC_WRITE | FILE_READ_ATTRIBUTES)),
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||||
&sa, OPEN_EXISTING, 0, 0);
|
||||
return *out != INVALID_HANDLE_VALUE;
|
||||
return (*out != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int kwsysProcessCreate(kwsysProcess* cp, int index,
|
||||
DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
|
||||
kwsysProcessCreateInformation* si)
|
||||
{
|
||||
int res =
|
||||
DWORD creationFlags;
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
|
||||
/* 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) &&
|
||||
/* 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 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]);
|
||||
creationFlags = CREATE_SUSPENDED;
|
||||
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. */
|
||||
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;
|
||||
}
|
||||
|
|
263
testProcess.c
263
testProcess.c
|
@ -29,26 +29,48 @@
|
|||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
# include <signal.h>
|
||||
#endif
|
||||
|
||||
#if defined(__BORLANDC__)
|
||||
# pragma warn -8060 /* possibly incorrect assignment */
|
||||
#endif
|
||||
|
||||
/* Platform-specific sleep functions. */
|
||||
|
||||
#if defined(__BEOS__) && !defined(__ZETA__)
|
||||
/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
|
||||
# include <be/kernel/OS.h>
|
||||
static inline void testProcess_usleep(unsigned int msec)
|
||||
static inline void testProcess_usleep(unsigned int usec)
|
||||
{
|
||||
snooze(msec);
|
||||
snooze(usec);
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
/* Windows can only sleep in millisecond intervals. */
|
||||
static void testProcess_usleep(unsigned int usec)
|
||||
{
|
||||
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;
|
||||
|
@ -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);
|
||||
#else
|
||||
sleep(15);
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue