Commit ef517b19 authored by James Johnston's avatar James Johnston Committed by Brad King

Process: Added initial support for process groups.

kwsysProcess_SetOption now allows you to specify a new
kwsysProcess_Option_CreateProcessGroup option, which creates the
process in a new process group (Windows/UNIX) and a new session
(UNIX).  Child process groups receive signals separately from their
parents.  This allowed for the introduction of the new
kwsysProcess_Interrupt function, which allows one to safely request
the child process in its own group to terminate.  The Ctrl+C handler
also manually sends that signal to child process groups, since it's
no longer automatic.

Change-Id: Id0a420ad65f1b1c1d299ac0eb95fbb8b50a52409
parent faff2ab0
......@@ -23,58 +23,60 @@
# define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT
#endif
#if !@KWSYS_NAMESPACE@_NAME_IS_KWSYS
# define kwsysProcess kwsys_ns(Process)
# define kwsysProcess_s kwsys_ns(Process_s)
# define kwsysProcess_New kwsys_ns(Process_New)
# define kwsysProcess_Delete kwsys_ns(Process_Delete)
# define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand)
# define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand)
# define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout)
# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
# define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile)
# define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative)
# define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared)
# define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach)
# define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow)
# define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput)
# define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim)
# define kwsysProcess_GetOption kwsys_ns(Process_GetOption)
# define kwsysProcess_SetOption kwsys_ns(Process_SetOption)
# define kwsysProcess_Option_e kwsys_ns(Process_Option_e)
# define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting)
# define kwsysProcess_State_Error kwsys_ns(Process_State_Error)
# define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception)
# define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing)
# define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited)
# define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired)
# define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed)
# define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned)
# define kwsysProcess_GetState kwsys_ns(Process_GetState)
# define kwsysProcess_State_e kwsys_ns(Process_State_e)
# define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None)
# define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault)
# define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal)
# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
# define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other)
# define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException)
# define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e)
# define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode)
# define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue)
# define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString)
# define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString)
# define kwsysProcess_Execute kwsys_ns(Process_Execute)
# define kwsysProcess_Disown kwsys_ns(Process_Disown)
# define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData)
# define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e)
# define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None)
# define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN)
# define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT)
# define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR)
# define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout)
# define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle)
# define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit)
# define kwsysProcess_Kill kwsys_ns(Process_Kill)
# define kwsysProcess kwsys_ns(Process)
# define kwsysProcess_s kwsys_ns(Process_s)
# define kwsysProcess_New kwsys_ns(Process_New)
# define kwsysProcess_Delete kwsys_ns(Process_Delete)
# define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand)
# define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand)
# define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout)
# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
# define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile)
# define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative)
# define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared)
# define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach)
# define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow)
# define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput)
# define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim)
# define kwsysProcess_Option_CreateProcessGroup kwsys_ns(Process_Option_CreateProcessGroup)
# define kwsysProcess_GetOption kwsys_ns(Process_GetOption)
# define kwsysProcess_SetOption kwsys_ns(Process_SetOption)
# define kwsysProcess_Option_e kwsys_ns(Process_Option_e)
# define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting)
# define kwsysProcess_State_Error kwsys_ns(Process_State_Error)
# define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception)
# define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing)
# define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited)
# define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired)
# define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed)
# define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned)
# define kwsysProcess_GetState kwsys_ns(Process_GetState)
# define kwsysProcess_State_e kwsys_ns(Process_State_e)
# define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None)
# define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault)
# define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal)
# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
# define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other)
# define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException)
# define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e)
# define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode)
# define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue)
# define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString)
# define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString)
# define kwsysProcess_Execute kwsys_ns(Process_Execute)
# define kwsysProcess_Disown kwsys_ns(Process_Disown)
# define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData)
# define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e)
# define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None)
# define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN)
# define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT)
# define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR)
# define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout)
# define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle)
# define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit)
# define kwsysProcess_Interrupt kwsys_ns(Process_Interrupt)
# define kwsysProcess_Kill kwsys_ns(Process_Kill)
#endif
#if defined(__cplusplus)
......@@ -199,6 +201,15 @@ kwsysEXPORT void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe,
* and ignore the rest of the arguments.
* 0 = No (default)
* 1 = Yes
*
* kwsysProcess_Option_CreateProcessGroup = Whether to place the process in a
* new process group. This is
* useful if you want to send Ctrl+C
* to the process. On UNIX, also
* places the process in a new
* session.
* 0 = No (default)
* 1 = Yes
*/
kwsysEXPORT int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
......@@ -208,7 +219,8 @@ enum kwsysProcess_Option_e
kwsysProcess_Option_HideWindow,
kwsysProcess_Option_Detach,
kwsysProcess_Option_MergeOutput,
kwsysProcess_Option_Verbatim
kwsysProcess_Option_Verbatim,
kwsysProcess_Option_CreateProcessGroup
};
/**
......@@ -362,6 +374,17 @@ enum kwsysProcess_Pipes_e
*/
kwsysEXPORT int kwsysProcess_WaitForExit(kwsysProcess* cp, double* timeout);
/**
* Interrupt the process group for the child process that is currently
* running by sending it the appropriate operating-system specific signal.
* The caller should call WaitForExit after this returns to wait for the
* child to terminate.
*
* WARNING: If you didn't specify kwsysProcess_Option_CreateProcessGroup,
* you will interrupt your own process group.
*/
kwsysEXPORT void kwsysProcess_Interrupt(kwsysProcess* cp);
/**
* Forcefully terminate the child process that is currently running.
* The caller should call WaitForExit after this returns to wait for
......@@ -394,6 +417,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
# undef kwsysProcess_Option_HideWindow
# undef kwsysProcess_Option_MergeOutput
# undef kwsysProcess_Option_Verbatim
# undef kwsysProcess_Option_CreateProcessGroup
# undef kwsysProcess_GetOption
# undef kwsysProcess_SetOption
# undef kwsysProcess_Option_e
......@@ -430,6 +454,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
# undef kwsysProcess_Pipe_Timeout
# undef kwsysProcess_Pipe_Handle
# undef kwsysProcess_WaitForExit
# undef kwsysProcess_Interrupt
# undef kwsysProcess_Kill
# endif
#endif
......
......@@ -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,33 @@ 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;
}
/* 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 +2027,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 +2071,9 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
}
}
}
/* Re-enable signals. */
sigprocmask(SIG_SETMASK, &old_mask, 0);
}
/*--------------------------------------------------------------------------*/
......@@ -2590,13 +2754,15 @@ 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. */
......@@ -2784,7 +2950,7 @@ static void kwsysProcessesSignalHandler(int signum
#endif
)
{
int i, procStatus, old_errno = errno;
int i, j, procStatus, old_errno = errno;
#if KWSYSPE_USE_SIGINFO
(void)info;
(void)ucontext;
......@@ -2808,10 +2974,34 @@ static void kwsysProcessesSignalHandler(int signum
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
......
......@@ -136,7 +136,8 @@ static void kwsysProcessDisablePipeThreads(kwsysProcess* cp);
static int kwsysProcessesInitialize(void);
static int kwsysTryEnterCreateProcessSection(void);
static void kwsysLeaveCreateProcessSection(void);
static int kwsysProcessesAdd(HANDLE hProcess);
static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessId,
int newProcessGroup);
static void kwsysProcessesRemove(HANDLE hProcess);
static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType);
......@@ -228,6 +229,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;
......@@ -852,6 +856,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;
}
}
......@@ -870,6 +876,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;
}
}
......@@ -1475,6 +1483,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;