summaryrefslogtreecommitdiffstats
path: root/Source/kwsys/ProcessUNIX.c
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2006-05-21 14:26:28 (GMT)
committerBrad King <brad.king@kitware.com>2006-05-21 14:26:28 (GMT)
commit0d594a453845e083207e6a85797a9b96ec278c75 (patch)
treee06808fbe4f0d7245b866b00c10c8bf5e0a445c2 /Source/kwsys/ProcessUNIX.c
parent3dd70af5a0114ec8708c2ab06be647277a3067ac (diff)
downloadCMake-0d594a453845e083207e6a85797a9b96ec278c75.zip
CMake-0d594a453845e083207e6a85797a9b96ec278c75.tar.gz
CMake-0d594a453845e083207e6a85797a9b96ec278c75.tar.bz2
ENH: Implemented handling of SIGCHLD to detect the termination of immediate children. This allows grandchildren to remain running after the children exit.
Diffstat (limited to 'Source/kwsys/ProcessUNIX.c')
-rw-r--r--Source/kwsys/ProcessUNIX.c390
1 files changed, 310 insertions, 80 deletions
diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c
index 9eedad0..6f9dd84 100644
--- a/Source/kwsys/ProcessUNIX.c
+++ b/Source/kwsys/ProcessUNIX.c
@@ -24,17 +24,18 @@
Implementation for UNIX
-On UNIX, a child process is forked to exec the program. Three
-output pipes from the child are read by the parent process using a
-select call to block until data are ready. Two of the pipes are
-stdout and stderr for the child. The third is a special error pipe
-that has two purposes. First, if the child cannot exec the program,
-the error is reported through the error pipe. Second, the error
-pipe is left open until the child exits. This is used in
-conjunction with the timeout on the select call to implement a
-timeout for program even when it closes stdout and stderr.
+On UNIX, a child process is forked to exec the program. Three output
+pipes are read by the parent process using a select call to block
+until data are ready. Two of the pipes are stdout and stderr for the
+child. The third is a special pipe populated by a signal handler to
+indicate that a child has terminated. This is used in conjunction
+with the timeout on the select call to implement a timeout for program
+even when it closes stdout and stderr and at the same time avoiding
+races.
+
*/
+
/*
TODO:
@@ -68,7 +69,7 @@ do.
#define KWSYSPE_PIPE_COUNT 3
#define KWSYSPE_PIPE_STDOUT 0
#define KWSYSPE_PIPE_STDERR 1
-#define KWSYSPE_PIPE_TERM 2
+#define KWSYSPE_PIPE_SIGNAL 2
/* The maximum amount to read from a pipe at a time. */
#define KWSYSPE_PIPE_BUFFER_SIZE 1024
@@ -89,7 +90,6 @@ typedef struct kwsysProcessCreateInformation_s
int StdIn;
int StdOut;
int StdErr;
- int TermPipe;
int ErrorPipe[2];
} kwsysProcessCreateInformation;
@@ -99,6 +99,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error);
static void kwsysProcessCleanupDescriptor(int* pfd);
static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
kwsysProcessCreateInformation* si, int* readEnd);
+static void kwsysProcessDestroy(kwsysProcess* cp);
static int kwsysProcessSetupOutputPipeFile(int* p, const char* name);
static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
kwsysProcessTime* timeoutTime);
@@ -117,6 +118,10 @@ static void kwsysProcessRestoreDefaultSignalHandlers(void);
static pid_t kwsysProcessFork(kwsysProcess* cp,
kwsysProcessCreateInformation* si);
static void kwsysProcessKill(pid_t process_id);
+static int kwsysProcessesAdd(kwsysProcess* cp);
+static void kwsysProcessesRemove(kwsysProcess* cp);
+static void kwsysProcessesSignalHandler(int signum, siginfo_t* info,
+ void* ucontext);
/*--------------------------------------------------------------------------*/
/* Structure containing data used to implement the child's execution. */
@@ -126,9 +131,13 @@ struct kwsysProcess_s
char*** Commands;
int NumberOfCommands;
- /* Descriptors for the read ends of the child's output pipes. */
+ /* Descriptors for the read ends of the child's output pipes and
+ the signal pipe. */
int PipeReadEnds[KWSYSPE_PIPE_COUNT];
+ /* Write descriptor for child termination signal pipe. */
+ int SignalPipe;
+
/* Buffer for pipe data. */
char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE];
@@ -159,15 +168,15 @@ struct kwsysProcess_s
/* Flag for whether the timeout expired. */
int TimeoutExpired;
- /* The old SIGCHLD handler. */
- struct sigaction OldSigChldAction;
-
/* The number of pipes left open during execution. */
int PipesLeft;
/* File descriptor set for call to select. */
fd_set PipeSet;
+ /* The number of children still executing. */
+ int CommandsLeft;
+
/* The current status of the child process. */
int State;
@@ -558,8 +567,7 @@ const char* kwsysProcess_GetExceptionString(kwsysProcess* cp)
void kwsysProcess_Execute(kwsysProcess* cp)
{
int i;
- struct sigaction newSigChldAction;
- kwsysProcessCreateInformation si = {-1, -1, -1, -1, {-1, -1}};
+ kwsysProcessCreateInformation si = {-1, -1, -1, {-1, -1}};
/* Do not execute a second copy simultaneously. */
if(!cp || cp->State == kwsysProcess_State_Executing)
@@ -597,45 +605,41 @@ void kwsysProcess_Execute(kwsysProcess* cp)
}
}
- /* We want no special handling of SIGCHLD. Repeat call until it is
- not interrupted. */
- memset(&newSigChldAction, 0, sizeof(struct sigaction));
- newSigChldAction.sa_handler = SIG_DFL;
- while((sigaction(SIGCHLD, &newSigChldAction, &cp->OldSigChldAction) < 0) &&
- (errno == EINTR));
-
- /* Setup the stderr and termination pipes to be shared by all processes. */
- for(i=KWSYSPE_PIPE_STDERR; i < KWSYSPE_PIPE_COUNT; ++i)
+ /* If not running a detached child, add this object to the global
+ set of process objects that wish to be notified when a child
+ exits. */
+ if(!cp->OptionDetach)
{
- /* Create the pipe. */
- int p[2];
- if(pipe(p) < 0)
+ if(!kwsysProcessesAdd(cp))
{
kwsysProcessCleanup(cp, 1);
return;
}
+ }
- /* Store the pipe. */
- cp->PipeReadEnds[i] = p[0];
- if(i == KWSYSPE_PIPE_STDERR)
- {
- si.StdErr = p[1];
- }
- else
- {
- si.TermPipe = p[1];
- }
+ /* Setup the stderr pipe to be shared by all processes. */
+ {
+ /* Create the pipe. */
+ int p[2];
+ if(pipe(p) < 0)
+ {
+ kwsysProcessCleanup(cp, 1);
+ return;
+ }
- /* Set close-on-exec flag on the pipe's ends. */
- if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) ||
- (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0))
- {
- kwsysProcessCleanup(cp, 1);
- kwsysProcessCleanupDescriptor(&si.StdErr);
- kwsysProcessCleanupDescriptor(&si.TermPipe);
- return;
- }
+ /* Store the pipe. */
+ cp->PipeReadEnds[KWSYSPE_PIPE_STDERR] = p[0];
+ si.StdErr = p[1];
+
+ /* Set close-on-exec flag on the pipe's ends. */
+ if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) ||
+ (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0))
+ {
+ kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanupDescriptor(&si.StdErr);
+ return;
}
+ }
/* Replace the stderr pipe with a file if requested. In this case
the select call will report that stderr is closed immediately. */
@@ -645,7 +649,6 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
kwsysProcessCleanup(cp, 1);
kwsysProcessCleanupDescriptor(&si.StdErr);
- kwsysProcessCleanupDescriptor(&si.TermPipe);
return;
}
}
@@ -688,7 +691,6 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
kwsysProcessCleanupDescriptor(&si.StdErr);
}
- kwsysProcessCleanupDescriptor(&si.TermPipe);
kwsysProcessCleanupDescriptor(&si.ErrorPipe[0]);
kwsysProcessCleanupDescriptor(&si.ErrorPipe[1]);
return;
@@ -703,7 +705,6 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
kwsysProcessCleanupDescriptor(&si.StdErr);
}
- kwsysProcessCleanupDescriptor(&si.TermPipe);
/* Restore the working directory. */
if(cp->RealWorkingDirectory)
@@ -823,9 +824,10 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
if(n > 0)
{
/* We have data on this pipe. */
- if(i == KWSYSPE_PIPE_TERM)
+ if(i == KWSYSPE_PIPE_SIGNAL)
{
- /* This is data on the special termination pipe. Ignore it. */
+ /* A child process has terminated. */
+ kwsysProcessDestroy(cp);
}
else if(data && length)
{
@@ -970,7 +972,6 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length,
/*--------------------------------------------------------------------------*/
int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
{
- int result = 0;
int status = 0;
int prPipe = 0;
@@ -989,26 +990,6 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
}
}
- /* Wait for each child to terminate. The process should have
- already exited because KWSYSPE_PIPE_TERM has been closed by this
- point. Repeat the call until it is not interrupted. */
- if(!cp->Detached)
- {
- int i;
- for(i=0; i < cp->NumberOfCommands; ++i)
- {
- while(((result = waitpid(cp->ForkPIDs[i],
- &cp->CommandExitCodes[i], 0)) < 0) &&
- (errno == EINTR));
- if(result <= 0 && cp->State != kwsysProcess_State_Error)
- {
- /* Unexpected error. Report the first time this happens. */
- strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE);
- cp->State = kwsysProcess_State_Error;
- }
- }
- }
-
/* Check if there was an error in one of the waitpid calls. */
if(cp->State == kwsysProcess_State_Error)
{
@@ -1084,11 +1065,18 @@ void kwsysProcess_Kill(kwsysProcess* cp)
cp->Killed = 1;
for(i=0; i < cp->NumberOfCommands; ++i)
{
+ int status;
if(cp->ForkPIDs[i])
{
+ /* Kill the child. */
kwsysProcessKill(cp->ForkPIDs[i]);
+
+ /* Reap the child. Keep trying until the call is not
+ interrupted. */
+ while((waitpid(cp->ForkPIDs[i], &status, 0) < 0) && (errno == EINTR));
}
}
+ cp->CommandsLeft = 0;
/* Close all the pipe read ends. */
for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
@@ -1107,6 +1095,7 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
{
cp->PipeReadEnds[i] = -1;
}
+ cp->SignalPipe = -1;
cp->SelectError = 0;
cp->StartTime.tv_sec = -1;
cp->StartTime.tv_usec = -1;
@@ -1114,6 +1103,7 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
cp->TimeoutTime.tv_usec = -1;
cp->TimeoutExpired = 0;
cp->PipesLeft = 0;
+ cp->CommandsLeft = 0;
FD_ZERO(&cp->PipeSet);
cp->State = kwsysProcess_State_Starting;
cp->Killed = 0;
@@ -1194,6 +1184,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
{
/* Kill the child. */
kwsysProcessKill(cp->ForkPIDs[i]);
+
/* Reap the child. Keep trying until the call is not
interrupted. */
while((waitpid(cp->ForkPIDs[i], &status, 0) < 0) &&
@@ -1209,9 +1200,13 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
}
}
- /* Restore the SIGCHLD handler. */
- while((sigaction(SIGCHLD, &cp->OldSigChldAction, 0) < 0) &&
- (errno == EINTR));
+ /* If not creating a detached child, remove this object from the
+ global set of process objects that wish to be notified when a
+ child exits. */
+ if(!cp->OptionDetach)
+ {
+ kwsysProcessesRemove(cp);
+ }
/* Free memory. */
if(cp->ForkPIDs)
@@ -1360,12 +1355,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
}
/* Clear the close-on-exec flag for stdin, stdout, and stderr.
- Also clear it for the termination pipe. All other pipe handles
- will be closed when exec succeeds. */
+ All other pipe handles will be closed when exec succeeds. */
fcntl(0, F_SETFD, 0);
fcntl(1, F_SETFD, 0);
fcntl(2, F_SETFD, 0);
- fcntl(si->TermPipe, F_SETFD, 0);
/* Restore all default signal handlers. */
kwsysProcessRestoreDefaultSignalHandlers();
@@ -1377,6 +1370,9 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
kwsysProcessChildErrorExit(si->ErrorPipe[1]);
}
+ /* A child has been created. */
+ ++cp->CommandsLeft;
+
/* We are done with the error reporting pipe write end. */
kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
@@ -1425,6 +1421,47 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
}
/*--------------------------------------------------------------------------*/
+static void kwsysProcessDestroy(kwsysProcess* cp)
+{
+ /* A child process has terminated. Reap it if it is one handled by
+ this object. */
+ int i;
+ for(i=0; i < cp->NumberOfCommands; ++i)
+ {
+ if(cp->ForkPIDs[i])
+ {
+ int result;
+ while(((result = waitpid(cp->ForkPIDs[i],
+ &cp->CommandExitCodes[i], WNOHANG)) < 0) &&
+ (errno == EINTR));
+ if(result > 0)
+ {
+ /* This child has termianted. */
+ cp->ForkPIDs[i] = 0;
+ if(--cp->CommandsLeft == 0)
+ {
+ /* All children have terminated. Close the signal pipe
+ write end so that no more notifications are sent to this
+ object. */
+ kwsysProcessCleanupDescriptor(&cp->SignalPipe);
+
+ /* TODO: Once the children have terminated, switch
+ WaitForData to use a non-blocking read to get the
+ rest of the data from the pipe. This is needed when
+ grandchildren keep the output pipes open. */
+ }
+ }
+ else if(result < 0 && cp->State != kwsysProcess_State_Error)
+ {
+ /* Unexpected error. Report the first time this happens. */
+ strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE);
+ cp->State = kwsysProcess_State_Error;
+ }
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------*/
static int kwsysProcessSetupOutputPipeFile(int* p, const char* name)
{
int fout;
@@ -2014,3 +2051,196 @@ static void kwsysProcessKill(pid_t process_id)
/* Kill the process. */
kill(process_id, SIGKILL);
}
+
+/*--------------------------------------------------------------------------*/
+/* Global set of executing processes for use by the signal handler.
+ This global instance will be zero-initialized by the compiler. */
+typedef struct kwsysProcessInstances_s
+{
+ int Count;
+ int Size;
+ kwsysProcess** Processes;
+} kwsysProcessInstances;
+static kwsysProcessInstances kwsysProcesses;
+
+/* The old SIGCHLD handler. */
+static struct sigaction kwsysProcessesOldSigChldAction;
+
+/*--------------------------------------------------------------------------*/
+static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses)
+{
+ /* Block SIGCHLD 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);
+ sigprocmask(SIG_BLOCK, &newset, &oldset);
+
+ /* Store the new set in that seen by the signal handler. */
+ kwsysProcesses = *newProcesses;
+
+ /* Restore the signal mask to the previous setting. */
+ sigprocmask(SIG_SETMASK, &oldset, 0);
+}
+
+/*--------------------------------------------------------------------------*/
+static int kwsysProcessesAdd(kwsysProcess* cp)
+{
+ /* Create a pipe through which the signal handler can notify the
+ given process object that a child has exited. */
+ {
+ /* Create the pipe. */
+ int oldfl[2];
+ int p[2];
+ if(pipe(p) < 0)
+ {
+ return 0;
+ }
+
+ /* Store the pipes now to be sure they are cleaned up later. */
+ cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL] = p[0];
+ cp->SignalPipe = p[1];
+
+ /* Switch the pipe to non-blocking mode so that reading a byte can
+ be an atomic test-and-set. */
+ if((oldfl[0] = fcntl(p[0], F_GETFL) < 0) ||
+ (oldfl[1] = fcntl(p[1], F_GETFL) < 0) ||
+ (fcntl(p[0], F_SETFL, oldfl[0] | O_NONBLOCK) < 0) ||
+ (fcntl(p[1], F_SETFL, oldfl[1] | O_NONBLOCK) < 0))
+ {
+ return 0;
+ }
+
+ /* The children do not need this pipe. Set close-on-exec flag on
+ the pipe's ends. */
+ if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) ||
+ (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0))
+ {
+ return 0;
+ }
+ }
+
+ /* Attempt to add the given signal pipe to the signal handler set. */
+ {
+
+ /* Make sure there is enough space for the new signal pipe. */
+ kwsysProcessInstances oldProcesses = kwsysProcesses;
+ kwsysProcessInstances newProcesses = oldProcesses;
+ if(oldProcesses.Count == oldProcesses.Size)
+ {
+ /* Start with enough space for a small number of process instances
+ and double the size each time more is needed. */
+ newProcesses.Size = oldProcesses.Size? oldProcesses.Size*2 : 4;
+
+ /* Try allocating the new block of memory. */
+ if((newProcesses.Processes = ((kwsysProcess**)
+ malloc(newProcesses.Size*
+ sizeof(kwsysProcess*)))))
+ {
+ /* Copy the old pipe set to the new memory. */
+ if(oldProcesses.Count > 0)
+ {
+ memcpy(newProcesses.Processes, oldProcesses.Processes,
+ (oldProcesses.Count * sizeof(kwsysProcess*)));
+ }
+ }
+ else
+ {
+ /* Failed to allocate memory for the new signal pipe set. */
+ return 0;
+ }
+ }
+
+ /* Append the new signal pipe to the set. */
+ newProcesses.Processes[newProcesses.Count++] = cp;
+
+ /* Store the new set in that seen by the signal handler. */
+ kwsysProcessesUpdate(&newProcesses);
+
+ /* Free the original pipes if new ones were allocated. */
+ if(newProcesses.Processes != oldProcesses.Processes)
+ {
+ free(oldProcesses.Processes);
+ }
+
+ /* If this is the first process, enable the signal handler. */
+ if(newProcesses.Count == 1)
+ {
+ /* Install our handler for SIGCHLD. Repeat call until it is not
+ interrupted. */
+ struct sigaction newSigChldAction;
+ memset(&newSigChldAction, 0, sizeof(struct sigaction));
+ newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler;
+ newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO;
+ while((sigaction(SIGCHLD, &newSigChldAction,
+ &kwsysProcessesOldSigChldAction) < 0) &&
+ (errno == EINTR));
+ }
+ }
+
+ return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+static void kwsysProcessesRemove(kwsysProcess* cp)
+{
+ /* Attempt to remove the given signal pipe from the signal handler set. */
+ {
+ /* Find the given process in the set. */
+ kwsysProcessInstances newProcesses = kwsysProcesses;
+ int i;
+ for(i=0; i < newProcesses.Count; ++i)
+ {
+ if(newProcesses.Processes[i] == cp)
+ {
+ break;
+ }
+ }
+ if(i < newProcesses.Count)
+ {
+ /* Remove the process from the set. */
+ --newProcesses.Count;
+ for(; i < newProcesses.Count; ++i)
+ {
+ newProcesses.Processes[i] = newProcesses.Processes[i+1];
+ }
+
+ /* Store the new set in that seen by the signal handler. */
+ kwsysProcessesUpdate(&newProcesses);
+
+ /* If this was the last process, disable the signal handler. */
+ if(newProcesses.Count == 0)
+ {
+ /* Restore the SIGCHLD handler. Repeat call until it is not
+ interrupted. */
+ while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) &&
+ (errno == EINTR));
+ }
+ }
+ }
+
+ /* Close the pipe through which the signal handler may have notified
+ the given process object that a child has exited. */
+ kwsysProcessCleanupDescriptor(&cp->SignalPipe);
+}
+
+/*--------------------------------------------------------------------------*/
+static void kwsysProcessesSignalHandler(int signum, siginfo_t* info,
+ void* ucontext)
+{
+ /* Signal all process objects that a child has terminated. */
+ int i;
+ (void)signum;
+ (void)info;
+ (void)ucontext;
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ /* Set the pipe in a signalled state. */
+ char buf = 1;
+ kwsysProcess* cp = kwsysProcesses.Processes[i];
+ read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
+ write(cp->SignalPipe, &buf, 1);
+ }
+}