diff options
Diffstat (limited to 'Source/kwsys/ProcessUNIX.c')
-rw-r--r-- | Source/kwsys/ProcessUNIX.c | 2763 |
1 files changed, 2763 insertions, 0 deletions
diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c new file mode 100644 index 0000000..241e295 --- /dev/null +++ b/Source/kwsys/ProcessUNIX.c @@ -0,0 +1,2763 @@ +/*============================================================================ + KWSys - Kitware System Library + Copyright 2000-2009 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "kwsysPrivate.h" +#include KWSYS_HEADER(Process.h) +#include KWSYS_HEADER(System.h) + +/* Work-around CMake dependency scanning limitation. This must + duplicate the above list of headers. */ +#if 0 +# include "Process.h.in" +# include "System.h.in" +#endif + +/* + +Implementation for UNIX + +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: + +We cannot create the pipeline of processes in suspended states. How +do we cleanup processes already started when one fails to load? Right +now we are just killing them, which is probably not the right thing to +do. + +*/ + +#if defined(__CYGWIN__) +/* Increase the file descriptor limit for select() before including + related system headers. (Default: 64) */ +# define FD_SETSIZE 16384 +#endif + +#include <stddef.h> /* ptrdiff_t */ +#include <stdio.h> /* snprintf */ +#include <stdlib.h> /* malloc, free */ +#include <string.h> /* strdup, strerror, memset */ +#include <sys/time.h> /* struct timeval */ +#include <sys/types.h> /* pid_t, fd_set */ +#include <sys/wait.h> /* waitpid */ +#include <sys/stat.h> /* open mode */ +#include <unistd.h> /* pipe, close, fork, execvp, select, _exit */ +#include <fcntl.h> /* fcntl */ +#include <errno.h> /* errno */ +#include <time.h> /* gettimeofday */ +#include <signal.h> /* sigaction */ +#include <dirent.h> /* DIR, dirent */ +#include <ctype.h> /* isspace */ + +#if defined(__VMS) +# define KWSYSPE_VMS_NONBLOCK , O_NONBLOCK +#else +# define KWSYSPE_VMS_NONBLOCK +#endif + +#if defined(KWSYS_C_HAS_PTRDIFF_T) && KWSYS_C_HAS_PTRDIFF_T +typedef ptrdiff_t kwsysProcess_ptrdiff_t; +#else +typedef int kwsysProcess_ptrdiff_t; +#endif + +#if defined(KWSYS_C_HAS_SSIZE_T) && KWSYS_C_HAS_SSIZE_T +typedef ssize_t kwsysProcess_ssize_t; +#else +typedef int kwsysProcess_ssize_t; +#endif + +#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 kwsysProcess_usleep(unsigned int msec) +{ + snooze(msec); +} +#else +# define kwsysProcess_usleep usleep +#endif + +/* + * BeOS's select() works like WinSock: it's for networking only, and + * doesn't work with Unix file handles...socket and file handles are + * different namespaces (the same descriptor means different things in + * each context!) + * + * So on Unix-like systems where select() is flakey, we'll set the + * pipes' file handles to be non-blocking and just poll them directly + * without select(). + */ +#if !defined(__BEOS__) && !defined(__VMS) && !defined(__MINT__) +# define KWSYSPE_USE_SELECT 1 +#endif + +/* Some platforms do not have siginfo on their signal handlers. */ +#if defined(SA_SIGINFO) && !defined(__BEOS__) +# define KWSYSPE_USE_SIGINFO 1 +#endif + +/* The number of pipes for the child's output. The standard stdout + and stderr pipes are the first two. One more pipe is used to + detect when the child process has terminated. The third pipe is + not given to the child process, so it cannot close it until it + terminates. */ +#define KWSYSPE_PIPE_COUNT 3 +#define KWSYSPE_PIPE_STDOUT 0 +#define KWSYSPE_PIPE_STDERR 1 +#define KWSYSPE_PIPE_SIGNAL 2 + +/* The maximum amount to read from a pipe at a time. */ +#define KWSYSPE_PIPE_BUFFER_SIZE 1024 + +/* Keep track of times using a signed representation. Switch to the + native (possibly unsigned) representation only when calling native + functions. */ +typedef struct timeval kwsysProcessTimeNative; +typedef struct kwsysProcessTime_s kwsysProcessTime; +struct kwsysProcessTime_s +{ + long tv_sec; + long tv_usec; +}; + +typedef struct kwsysProcessCreateInformation_s +{ + int StdIn; + int StdOut; + int StdErr; + int ErrorPipe[2]; +} kwsysProcessCreateInformation; + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessInitialize(kwsysProcess* cp); +static void kwsysProcessCleanup(kwsysProcess* cp, int error); +static void kwsysProcessCleanupDescriptor(int* pfd); +static void kwsysProcessClosePipes(kwsysProcess* cp); +static int kwsysProcessSetNonBlocking(int fd); +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 kwsysProcessSetupOutputPipeNative(int* p, int des[2]); +static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, + kwsysProcessTime* timeoutTime); +static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, + double* userTimeout, + kwsysProcessTimeNative* timeoutLength, + int zeroIsExpired); +static kwsysProcessTime kwsysProcessTimeGetCurrent(void); +static double kwsysProcessTimeToDouble(kwsysProcessTime t); +static kwsysProcessTime kwsysProcessTimeFromDouble(double d); +static int kwsysProcessTimeLess(kwsysProcessTime in1, kwsysProcessTime in2); +static kwsysProcessTime kwsysProcessTimeAdd(kwsysProcessTime in1, kwsysProcessTime in2); +static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProcessTime in2); +static void kwsysProcessSetExitException(kwsysProcess* cp, int sig); +static void kwsysProcessChildErrorExit(int errorPipe); +static void kwsysProcessRestoreDefaultSignalHandlers(void); +static pid_t kwsysProcessFork(kwsysProcess* cp, + kwsysProcessCreateInformation* si); +static void kwsysProcessKill(pid_t process_id); +#if defined(__VMS) +static int kwsysProcessSetVMSFeature(const char* name, int value); +#endif +static int kwsysProcessesAdd(kwsysProcess* cp); +static void kwsysProcessesRemove(kwsysProcess* cp); +#if KWSYSPE_USE_SIGINFO +static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, + void* ucontext); +#else +static void kwsysProcessesSignalHandler(int signum); +#endif + +/*--------------------------------------------------------------------------*/ +/* Structure containing data used to implement the child's execution. */ +struct kwsysProcess_s +{ + /* The command lines to execute. */ + char*** Commands; + int NumberOfCommands; + + /* 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]; + + /* Process IDs returned by the calls to fork. */ + pid_t* ForkPIDs; + + /* Flag for whether the children were terminated by a faild select. */ + int SelectError; + + /* The timeout length. */ + double Timeout; + + /* The working directory for the process. */ + char* WorkingDirectory; + + /* Whether to create the child as a detached process. */ + int OptionDetach; + + /* Whether the child was created as a detached process. */ + int Detached; + + /* Whether to treat command lines as verbatim. */ + int Verbatim; + + /* Time at which the child started. Negative for no timeout. */ + kwsysProcessTime StartTime; + + /* Time at which the child will timeout. Negative for no timeout. */ + kwsysProcessTime TimeoutTime; + + /* Flag for whether the timeout expired. */ + int TimeoutExpired; + + /* The number of pipes left open during execution. */ + int PipesLeft; + +#if KWSYSPE_USE_SELECT + /* File descriptor set for call to select. */ + fd_set PipeSet; +#endif + + /* The number of children still executing. */ + int CommandsLeft; + + /* The current status of the child process. */ + int State; + + /* The exceptional behavior that terminated the child process, if + * any. */ + int ExitException; + + /* The exit code of the child process. */ + int ExitCode; + + /* The exit value of the child process, if any. */ + int ExitValue; + + /* Whether the process was killed. */ + int Killed; + + /* Buffer for error message in case of failure. */ + char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1]; + + /* Description for the ExitException. */ + char ExitExceptionString[KWSYSPE_PIPE_BUFFER_SIZE+1]; + + /* The exit codes of each child process in the pipeline. */ + int* CommandExitCodes; + + /* Name of files to which stdin and stdout pipes are attached. */ + char* PipeFileSTDIN; + char* PipeFileSTDOUT; + char* PipeFileSTDERR; + + /* Whether each pipe is shared with the parent process. */ + int PipeSharedSTDIN; + int PipeSharedSTDOUT; + int PipeSharedSTDERR; + + /* Native pipes provided by the user. */ + int PipeNativeSTDIN[2]; + int PipeNativeSTDOUT[2]; + int PipeNativeSTDERR[2]; + + /* The real working directory of this process. */ + int RealWorkingDirectoryLength; + char* RealWorkingDirectory; +}; + +/*--------------------------------------------------------------------------*/ +kwsysProcess* kwsysProcess_New(void) +{ + /* Allocate a process control structure. */ + kwsysProcess* cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); + if(!cp) + { + return 0; + } + memset(cp, 0, sizeof(kwsysProcess)); + + /* Share stdin with the parent process by default. */ + cp->PipeSharedSTDIN = 1; + + /* No native pipes by default. */ + cp->PipeNativeSTDIN[0] = -1; + cp->PipeNativeSTDIN[1] = -1; + cp->PipeNativeSTDOUT[0] = -1; + cp->PipeNativeSTDOUT[1] = -1; + cp->PipeNativeSTDERR[0] = -1; + cp->PipeNativeSTDERR[1] = -1; + + /* Set initial status. */ + cp->State = kwsysProcess_State_Starting; + + return cp; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Delete(kwsysProcess* cp) +{ + /* Make sure we have an instance. */ + if(!cp) + { + return; + } + + /* If the process is executing, wait for it to finish. */ + if(cp->State == kwsysProcess_State_Executing) + { + if(cp->Detached) + { + kwsysProcess_Disown(cp); + } + else + { + kwsysProcess_WaitForExit(cp, 0); + } + } + + /* Free memory. */ + kwsysProcess_SetCommand(cp, 0); + kwsysProcess_SetWorkingDirectory(cp, 0); + kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDIN, 0); + kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDOUT, 0); + kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDERR, 0); + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } + free(cp); +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) +{ + int i; + if(!cp) + { + return 0; + } + for(i=0; i < cp->NumberOfCommands; ++i) + { + char** c = cp->Commands[i]; + while(*c) + { + free(*c++); + } + free(cp->Commands[i]); + } + cp->NumberOfCommands = 0; + if(cp->Commands) + { + free(cp->Commands); + cp->Commands = 0; + } + if(command) + { + return kwsysProcess_AddCommand(cp, command); + } + return 1; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command) +{ + int newNumberOfCommands; + char*** newCommands; + + /* Make sure we have a command to add. */ + if(!cp || !command || !*command) + { + return 0; + } + + /* Allocate a new array for command pointers. */ + newNumberOfCommands = cp->NumberOfCommands + 1; + if(!(newCommands = + (char***)malloc(sizeof(char**) *(size_t)(newNumberOfCommands)))) + { + /* Out of memory. */ + return 0; + } + + /* Copy any existing commands into the new array. */ + { + int i; + for(i=0; i < cp->NumberOfCommands; ++i) + { + newCommands[i] = cp->Commands[i]; + } + } + + /* Add the new command. */ + if(cp->Verbatim) + { + /* In order to run the given command line verbatim we need to + parse it. */ + newCommands[cp->NumberOfCommands] = + kwsysSystem_Parse_CommandForUnix(*command, 0); + if(!newCommands[cp->NumberOfCommands] || + !newCommands[cp->NumberOfCommands][0]) + { + /* Out of memory or no command parsed. */ + free(newCommands); + return 0; + } + } + else + { + /* Copy each argument string individually. */ + char const* const* c = command; + kwsysProcess_ptrdiff_t n = 0; + kwsysProcess_ptrdiff_t i = 0; + while(*c++); + n = c - command - 1; + newCommands[cp->NumberOfCommands] = + (char**)malloc((size_t)(n+1)*sizeof(char*)); + if(!newCommands[cp->NumberOfCommands]) + { + /* Out of memory. */ + free(newCommands); + return 0; + } + for(i=0; i < n; ++i) + { + newCommands[cp->NumberOfCommands][i] = strdup(command[i]); + if(!newCommands[cp->NumberOfCommands][i]) + { + break; + } + } + if(i < n) + { + /* Out of memory. */ + for(;i > 0; --i) + { + free(newCommands[cp->NumberOfCommands][i-1]); + } + free(newCommands); + return 0; + } + newCommands[cp->NumberOfCommands][n] = 0; + } + + /* Successfully allocated new command array. Free the old array. */ + free(cp->Commands); + cp->Commands = newCommands; + cp->NumberOfCommands = newNumberOfCommands; + + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetTimeout(kwsysProcess* cp, double timeout) +{ + if(!cp) + { + return; + } + cp->Timeout = timeout; + if(cp->Timeout < 0) + { + cp->Timeout = 0; + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_SetWorkingDirectory(kwsysProcess* cp, const char* dir) +{ + if(!cp) + { + return 0; + } + if(cp->WorkingDirectory == dir) + { + return 1; + } + if(cp->WorkingDirectory && dir && strcmp(cp->WorkingDirectory, dir) == 0) + { + return 1; + } + if(cp->WorkingDirectory) + { + free(cp->WorkingDirectory); + cp->WorkingDirectory = 0; + } + if(dir) + { + cp->WorkingDirectory = (char*)malloc(strlen(dir) + 1); + if(!cp->WorkingDirectory) + { + return 0; + } + strcpy(cp->WorkingDirectory, dir); + } + return 1; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_SetPipeFile(kwsysProcess* cp, int prPipe, const char* file) +{ + char** pfile; + if(!cp) + { + return 0; + } + switch(prPipe) + { + case kwsysProcess_Pipe_STDIN: pfile = &cp->PipeFileSTDIN; break; + case kwsysProcess_Pipe_STDOUT: pfile = &cp->PipeFileSTDOUT; break; + case kwsysProcess_Pipe_STDERR: pfile = &cp->PipeFileSTDERR; break; + default: return 0; + } + if(*pfile) + { + free(*pfile); + *pfile = 0; + } + if(file) + { + *pfile = malloc(strlen(file)+1); + if(!*pfile) + { + return 0; + } + strcpy(*pfile, file); + } + + /* If we are redirecting the pipe, do not share it or use a native + pipe. */ + if(*pfile) + { + kwsysProcess_SetPipeNative(cp, prPipe, 0); + kwsysProcess_SetPipeShared(cp, prPipe, 0); + } + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetPipeShared(kwsysProcess* cp, int prPipe, int shared) +{ + if(!cp) + { + return; + } + + switch(prPipe) + { + case kwsysProcess_Pipe_STDIN: cp->PipeSharedSTDIN = shared?1:0; break; + case kwsysProcess_Pipe_STDOUT: cp->PipeSharedSTDOUT = shared?1:0; break; + case kwsysProcess_Pipe_STDERR: cp->PipeSharedSTDERR = shared?1:0; break; + default: return; + } + + /* If we are sharing the pipe, do not redirect it to a file or use a + native pipe. */ + if(shared) + { + kwsysProcess_SetPipeFile(cp, prPipe, 0); + kwsysProcess_SetPipeNative(cp, prPipe, 0); + } +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetPipeNative(kwsysProcess* cp, int prPipe, int p[2]) +{ + int* pPipeNative = 0; + + if(!cp) + { + return; + } + + switch(prPipe) + { + case kwsysProcess_Pipe_STDIN: pPipeNative = cp->PipeNativeSTDIN; break; + case kwsysProcess_Pipe_STDOUT: pPipeNative = cp->PipeNativeSTDOUT; break; + case kwsysProcess_Pipe_STDERR: pPipeNative = cp->PipeNativeSTDERR; break; + default: return; + } + + /* Copy the native pipe descriptors provided. */ + if(p) + { + pPipeNative[0] = p[0]; + pPipeNative[1] = p[1]; + } + else + { + pPipeNative[0] = -1; + pPipeNative[1] = -1; + } + + /* If we are using a native pipe, do not share it or redirect it to + a file. */ + if(p) + { + kwsysProcess_SetPipeFile(cp, prPipe, 0); + kwsysProcess_SetPipeShared(cp, prPipe, 0); + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetOption(kwsysProcess* cp, int optionId) +{ + if(!cp) + { + return 0; + } + + switch(optionId) + { + case kwsysProcess_Option_Detach: return cp->OptionDetach; + case kwsysProcess_Option_Verbatim: return cp->Verbatim; + default: return 0; + } +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value) +{ + if(!cp) + { + return; + } + + switch(optionId) + { + case kwsysProcess_Option_Detach: cp->OptionDetach = value; break; + case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break; + default: break; + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetState(kwsysProcess* cp) +{ + return cp? cp->State : kwsysProcess_State_Error; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetExitException(kwsysProcess* cp) +{ + return cp? cp->ExitException : kwsysProcess_Exception_Other; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetExitCode(kwsysProcess* cp) +{ + return cp? cp->ExitCode : 0; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetExitValue(kwsysProcess* cp) +{ + return cp? cp->ExitValue : -1; +} + +/*--------------------------------------------------------------------------*/ +const char* kwsysProcess_GetErrorString(kwsysProcess* cp) +{ + if(!cp) + { + return "Process management structure could not be allocated"; + } + else if(cp->State == kwsysProcess_State_Error) + { + return cp->ErrorMessage; + } + return "Success"; +} + +/*--------------------------------------------------------------------------*/ +const char* kwsysProcess_GetExceptionString(kwsysProcess* cp) +{ + if(!cp) + { + return "GetExceptionString called with NULL process management structure"; + } + else if(cp->State == kwsysProcess_State_Exception) + { + return cp->ExitExceptionString; + } + return "No exception"; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Execute(kwsysProcess* cp) +{ + int i; + kwsysProcessCreateInformation si = {-1, -1, -1, {-1, -1}}; + + /* Do not execute a second copy simultaneously. */ + if(!cp || cp->State == kwsysProcess_State_Executing) + { + return; + } + + /* Make sure we have something to run. */ + if(cp->NumberOfCommands < 1) + { + strcpy(cp->ErrorMessage, "No command"); + cp->State = kwsysProcess_State_Error; + return; + } + + /* Initialize the control structure for a new process. */ + if(!kwsysProcessInitialize(cp)) + { + strcpy(cp->ErrorMessage, "Out of memory"); + cp->State = kwsysProcess_State_Error; + return; + } + +#if defined(__VMS) + /* Make sure pipes behave like streams on VMS. */ + if(!kwsysProcessSetVMSFeature("DECC$STREAM_PIPE", 1)) + { + kwsysProcessCleanup(cp, 1); + return; + } +#endif + + /* Save the real working directory of this process and change to + the working directory for the child processes. This is needed + to make pipe file paths evaluate correctly. */ + if(cp->WorkingDirectory) + { + int r; + if(!getcwd(cp->RealWorkingDirectory, + (size_t)(cp->RealWorkingDirectoryLength))) + { + kwsysProcessCleanup(cp, 1); + return; + } + + /* Some platforms specify that the chdir call may be + interrupted. Repeat the call until it finishes. */ + while(((r = chdir(cp->WorkingDirectory)) < 0) && (errno == EINTR)); + if(r < 0) + { + kwsysProcessCleanup(cp, 1); + return; + } + } + + /* 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) + { + if(!kwsysProcessesAdd(cp)) + { + kwsysProcessCleanup(cp, 1); + return; + } + } + + /* Setup the stderr pipe to be shared by all processes. */ + { + /* Create the pipe. */ + int p[2]; + if(pipe(p KWSYSPE_VMS_NONBLOCK) < 0) + { + kwsysProcessCleanup(cp, 1); + 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; + } + + /* Set to non-blocking in case select lies, or for the polling + implementation. */ + if(!kwsysProcessSetNonBlocking(p[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. */ + if(cp->PipeFileSTDERR) + { + if(!kwsysProcessSetupOutputPipeFile(&si.StdErr, cp->PipeFileSTDERR)) + { + kwsysProcessCleanup(cp, 1); + kwsysProcessCleanupDescriptor(&si.StdErr); + return; + } + } + + /* Replace the stderr pipe with the parent's if requested. In this + case the select call will report that stderr is closed + immediately. */ + if(cp->PipeSharedSTDERR) + { + kwsysProcessCleanupDescriptor(&si.StdErr); + si.StdErr = 2; + } + + /* Replace the stderr pipe with the native pipe provided if any. In + this case the select call will report that stderr is closed + immediately. */ + if(cp->PipeNativeSTDERR[1] >= 0) + { + if(!kwsysProcessSetupOutputPipeNative(&si.StdErr, cp->PipeNativeSTDERR)) + { + kwsysProcessCleanup(cp, 1); + kwsysProcessCleanupDescriptor(&si.StdErr); + return; + } + } + + /* The timeout period starts now. */ + cp->StartTime = kwsysProcessTimeGetCurrent(); + cp->TimeoutTime.tv_sec = -1; + cp->TimeoutTime.tv_usec = -1; + + /* Create the pipeline of processes. */ + { + int readEnd = -1; + int failed = 0; + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(!kwsysProcessCreate(cp, i, &si, &readEnd)) + { + failed = 1; + } + + /* Set the output pipe of the last process to be non-blocking in + case select lies, or for the polling implementation. */ + if(i == (cp->NumberOfCommands-1) && !kwsysProcessSetNonBlocking(readEnd)) + { + failed = 1; + } + + if(failed) + { + kwsysProcessCleanup(cp, 1); + + /* Release resources that may have been allocated for this + process before an error occurred. */ + kwsysProcessCleanupDescriptor(&readEnd); + if(si.StdIn != 0) + { + kwsysProcessCleanupDescriptor(&si.StdIn); + } + if(si.StdOut != 1) + { + kwsysProcessCleanupDescriptor(&si.StdOut); + } + if(si.StdErr != 2) + { + kwsysProcessCleanupDescriptor(&si.StdErr); + } + kwsysProcessCleanupDescriptor(&si.ErrorPipe[0]); + kwsysProcessCleanupDescriptor(&si.ErrorPipe[1]); + return; + } + } + /* Save a handle to the output pipe for the last process. */ + cp->PipeReadEnds[KWSYSPE_PIPE_STDOUT] = readEnd; + } + + /* The parent process does not need the output pipe write ends. */ + if(si.StdErr != 2) + { + kwsysProcessCleanupDescriptor(&si.StdErr); + } + + /* Restore the working directory. */ + if(cp->RealWorkingDirectory) + { + /* Some platforms specify that the chdir call may be + interrupted. Repeat the call until it finishes. */ + while((chdir(cp->RealWorkingDirectory) < 0) && (errno == EINTR)); + free(cp->RealWorkingDirectory); + cp->RealWorkingDirectory = 0; + } + + /* All the pipes are now open. */ + cp->PipesLeft = KWSYSPE_PIPE_COUNT; + + /* The process has now started. */ + cp->State = kwsysProcess_State_Executing; + cp->Detached = cp->OptionDetach; +} + +/*--------------------------------------------------------------------------*/ +kwsysEXPORT void kwsysProcess_Disown(kwsysProcess* cp) +{ + /* Make sure a detached child process is running. */ + if(!cp || !cp->Detached || cp->State != kwsysProcess_State_Executing || + cp->TimeoutExpired || cp->Killed) + { + return; + } + + /* Close all the pipes safely. */ + kwsysProcessClosePipes(cp); + + /* We will not wait for exit, so cleanup now. */ + kwsysProcessCleanup(cp, 0); + + /* The process has been disowned. */ + cp->State = kwsysProcess_State_Disowned; +} + +/*--------------------------------------------------------------------------*/ +typedef struct kwsysProcessWaitData_s +{ + int Expired; + int PipeId; + int User; + double* UserTimeout; + kwsysProcessTime TimeoutTime; +} kwsysProcessWaitData; +static int kwsysProcessWaitForPipe(kwsysProcess* cp, char** data, int* length, + kwsysProcessWaitData* wd); + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, + double* userTimeout) +{ + kwsysProcessTime userStartTime = {0, 0}; + kwsysProcessWaitData wd = + { + 0, + kwsysProcess_Pipe_None, + 0, + 0, + {0, 0} + }; + wd.UserTimeout = userTimeout; + /* Make sure we are executing a process. */ + if(!cp || cp->State != kwsysProcess_State_Executing || cp->Killed || + cp->TimeoutExpired) + { + return kwsysProcess_Pipe_None; + } + + /* Record the time at which user timeout period starts. */ + if(userTimeout) + { + userStartTime = kwsysProcessTimeGetCurrent(); + } + + /* Calculate the time at which a timeout will expire, and whether it + is the user or process timeout. */ + wd.User = kwsysProcessGetTimeoutTime(cp, userTimeout, + &wd.TimeoutTime); + + /* Data can only be available when pipes are open. If the process + is not running, cp->PipesLeft will be 0. */ + while(cp->PipesLeft > 0 && + !kwsysProcessWaitForPipe(cp, data, length, &wd)) {} + + /* Update the user timeout. */ + if(userTimeout) + { + kwsysProcessTime userEndTime = kwsysProcessTimeGetCurrent(); + kwsysProcessTime difference = kwsysProcessTimeSubtract(userEndTime, + userStartTime); + double d = kwsysProcessTimeToDouble(difference); + *userTimeout -= d; + if(*userTimeout < 0) + { + *userTimeout = 0; + } + } + + /* Check what happened. */ + if(wd.PipeId) + { + /* Data are ready on a pipe. */ + return wd.PipeId; + } + else if(wd.Expired) + { + /* A timeout has expired. */ + if(wd.User) + { + /* The user timeout has expired. It has no time left. */ + return kwsysProcess_Pipe_Timeout; + } + else + { + /* The process timeout has expired. Kill the children now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->TimeoutExpired = 1; + return kwsysProcess_Pipe_None; + } + } + else + { + /* No pipes are left open. */ + return kwsysProcess_Pipe_None; + } +} + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessWaitForPipe(kwsysProcess* cp, char** data, int* length, + kwsysProcessWaitData* wd) +{ + int i; + kwsysProcessTimeNative timeoutLength; + +#if KWSYSPE_USE_SELECT + int numReady = 0; + int max = -1; + kwsysProcessTimeNative* timeout = 0; + + /* Check for any open pipes with data reported ready by the last + call to select. According to "man select_tut" we must deal + with all descriptors reported by a call to select before + passing them to another select call. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0 && + FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) + { + kwsysProcess_ssize_t n; + + /* We are handling this pipe now. Remove it from the set. */ + FD_CLR(cp->PipeReadEnds[i], &cp->PipeSet); + + /* The pipe is ready to read without blocking. Keep trying to + read until the operation is not interrupted. */ + while(((n = read(cp->PipeReadEnds[i], cp->PipeBuffer, + KWSYSPE_PIPE_BUFFER_SIZE)) < 0) && (errno == EINTR)); + if(n > 0) + { + /* We have data on this pipe. */ + if(i == KWSYSPE_PIPE_SIGNAL) + { + /* A child process has terminated. */ + kwsysProcessDestroy(cp); + } + else if(data && length) + { + /* Report this data. */ + *data = cp->PipeBuffer; + *length = (int)(n); + switch(i) + { + case KWSYSPE_PIPE_STDOUT: + wd->PipeId = kwsysProcess_Pipe_STDOUT; break; + case KWSYSPE_PIPE_STDERR: + wd->PipeId = kwsysProcess_Pipe_STDERR; break; + }; + return 1; + } + } + else if(n < 0 && errno == EAGAIN) + { + /* No data are really ready. The select call lied. See the + "man select" page on Linux for cases when this occurs. */ + } + else + { + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } + } + + /* If we have data, break early. */ + if(wd->PipeId) + { + return 1; + } + + /* Make sure the set is empty (it should always be empty here + anyway). */ + FD_ZERO(&cp->PipeSet); + + /* Setup a timeout if required. */ + if(wd->TimeoutTime.tv_sec < 0) + { + timeout = 0; + } + else + { + timeout = &timeoutLength; + } + if(kwsysProcessGetTimeoutLeft(&wd->TimeoutTime, + wd->User?wd->UserTimeout:0, + &timeoutLength, 0)) + { + /* Timeout has already expired. */ + wd->Expired = 1; + return 1; + } + + /* Add the pipe reading ends that are still open. */ + max = -1; + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { + FD_SET(cp->PipeReadEnds[i], &cp->PipeSet); + if(cp->PipeReadEnds[i] > max) + { + max = cp->PipeReadEnds[i]; + } + } + } + + /* Make sure we have a non-empty set. */ + if(max < 0) + { + /* All pipes have closed. Child has terminated. */ + return 1; + } + + /* Run select to block until data are available. Repeat call + until it is not interrupted. */ + while(((numReady = select(max+1, &cp->PipeSet, 0, 0, timeout)) < 0) && + (errno == EINTR)); + + /* Check result of select. */ + if(numReady == 0) + { + /* Select's timeout expired. */ + wd->Expired = 1; + return 1; + } + else if(numReady < 0) + { + /* Select returned an error. Leave the error description in the + pipe buffer. */ + strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + + /* Kill the children now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->SelectError = 1; + } + + return 0; +#else + /* Poll pipes for data since we do not have select. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { + const int fd = cp->PipeReadEnds[i]; + int n = read(fd, cp->PipeBuffer, KWSYSPE_PIPE_BUFFER_SIZE); + if(n > 0) + { + /* We have data on this pipe. */ + if(i == KWSYSPE_PIPE_SIGNAL) + { + /* A child process has terminated. */ + kwsysProcessDestroy(cp); + } + else if(data && length) + { + /* Report this data. */ + *data = cp->PipeBuffer; + *length = n; + switch(i) + { + case KWSYSPE_PIPE_STDOUT: + wd->PipeId = kwsysProcess_Pipe_STDOUT; break; + case KWSYSPE_PIPE_STDERR: + wd->PipeId = kwsysProcess_Pipe_STDERR; break; + }; + } + return 1; + } + else if (n == 0) /* EOF */ + { + /* We are done reading from this pipe. */ +#if defined(__VMS) + if(!cp->CommandsLeft) +#endif + { + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } + else if (n < 0) /* error */ + { +#if defined(__VMS) + if(!cp->CommandsLeft) + { + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + else +#endif + if((errno != EINTR) && (errno != EAGAIN)) + { + strncpy(cp->ErrorMessage,strerror(errno), + KWSYSPE_PIPE_BUFFER_SIZE); + /* Kill the children now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->SelectError = 1; + return 1; + } + } + } + } + + /* If we have data, break early. */ + if(wd->PipeId) + { + return 1; + } + + if(kwsysProcessGetTimeoutLeft(&wd->TimeoutTime, wd->User?wd->UserTimeout:0, + &timeoutLength, 1)) + { + /* Timeout has already expired. */ + wd->Expired = 1; + return 1; + } + + /* Sleep a little, try again. */ + { + unsigned int msec = ((timeoutLength.tv_sec * 1000) + + (timeoutLength.tv_usec / 1000)); + if (msec > 100000) + { + msec = 100000; /* do not sleep more than 100 milliseconds at a time */ + } + kwsysProcess_usleep(msec); + } + return 0; +#endif +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) +{ + int status = 0; + int prPipe = 0; + + /* Make sure we are executing a process. */ + if(!cp || cp->State != kwsysProcess_State_Executing) + { + return 1; + } + + /* Wait for all the pipes to close. Ignore all data. */ + while((prPipe = kwsysProcess_WaitForData(cp, 0, 0, userTimeout)) > 0) + { + if(prPipe == kwsysProcess_Pipe_Timeout) + { + return 0; + } + } + + /* Check if there was an error in one of the waitpid calls. */ + if(cp->State == kwsysProcess_State_Error) + { + /* The error message is already in its buffer. Tell + kwsysProcessCleanup to not create it. */ + kwsysProcessCleanup(cp, 0); + return 1; + } + + /* Check whether the child reported an error invoking the process. */ + if(cp->SelectError) + { + /* The error message is already in its buffer. Tell + kwsysProcessCleanup to not create it. */ + kwsysProcessCleanup(cp, 0); + cp->State = kwsysProcess_State_Error; + return 1; + } + + /* Use the status of the last process in the pipeline. */ + status = cp->CommandExitCodes[cp->NumberOfCommands-1]; + + /* Determine the outcome. */ + if(cp->Killed) + { + /* We killed the child. */ + cp->State = kwsysProcess_State_Killed; + } + else if(cp->TimeoutExpired) + { + /* The timeout expired. */ + cp->State = kwsysProcess_State_Expired; + } + else if(WIFEXITED(status)) + { + /* The child exited normally. */ + cp->State = kwsysProcess_State_Exited; + cp->ExitException = kwsysProcess_Exception_None; + cp->ExitCode = status; + cp->ExitValue = (int)WEXITSTATUS(status); + } + else if(WIFSIGNALED(status)) + { + /* The child received an unhandled signal. */ + cp->State = kwsysProcess_State_Exception; + cp->ExitCode = status; + kwsysProcessSetExitException(cp, (int)WTERMSIG(status)); + } + else + { + /* Error getting the child return code. */ + strcpy(cp->ErrorMessage, "Error getting child return code."); + cp->State = kwsysProcess_State_Error; + } + + /* Normal cleanup. */ + kwsysProcessCleanup(cp, 0); + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Kill(kwsysProcess* cp) +{ + int i; + + /* Make sure we are executing a process. */ + if(!cp || cp->State != kwsysProcess_State_Executing) + { + return; + } + + /* First close the child exit report pipe write end to avoid causing a + SIGPIPE when the child terminates and our signal handler tries to + report it after we have already closed the read end. */ + kwsysProcessCleanupDescriptor(&cp->SignalPipe); + +#if !defined(__APPLE__) + /* Close all the pipe read ends. Do this before killing the + children because Cygwin has problems killing processes that are + blocking to wait for writing to their output pipes. */ + kwsysProcessClosePipes(cp); +#endif + + /* Kill the children. */ + 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)); + } + } + +#if defined(__APPLE__) + /* Close all the pipe read ends. Do this after killing the + children because OS X has problems closing pipe read ends whose + pipes are full and still have an open write end. */ + kwsysProcessClosePipes(cp); +#endif + + cp->CommandsLeft = 0; +} + +/*--------------------------------------------------------------------------*/ +/* Initialize a process control structure for kwsysProcess_Execute. */ +static int kwsysProcessInitialize(kwsysProcess* cp) +{ + int i; + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + cp->PipeReadEnds[i] = -1; + } + cp->SignalPipe = -1; + cp->SelectError = 0; + cp->StartTime.tv_sec = -1; + cp->StartTime.tv_usec = -1; + cp->TimeoutTime.tv_sec = -1; + cp->TimeoutTime.tv_usec = -1; + cp->TimeoutExpired = 0; + cp->PipesLeft = 0; + cp->CommandsLeft = 0; +#if KWSYSPE_USE_SELECT + FD_ZERO(&cp->PipeSet); +#endif + cp->State = kwsysProcess_State_Starting; + cp->Killed = 0; + cp->ExitException = kwsysProcess_Exception_None; + cp->ExitCode = 1; + cp->ExitValue = 1; + cp->ErrorMessage[0] = 0; + strcpy(cp->ExitExceptionString, "No exception"); + + if(cp->ForkPIDs) + { + free(cp->ForkPIDs); + } + 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)); + + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } + cp->CommandExitCodes = (int*)malloc(sizeof(int)* + (size_t)(cp->NumberOfCommands)); + if(!cp->CommandExitCodes) + { + return 0; + } + memset(cp->CommandExitCodes, 0, sizeof(int)*(size_t)(cp->NumberOfCommands)); + + /* Allocate memory to save the real working directory. */ + if ( cp->WorkingDirectory ) + { +#if defined(MAXPATHLEN) + cp->RealWorkingDirectoryLength = MAXPATHLEN; +#elif defined(PATH_MAX) + cp->RealWorkingDirectoryLength = PATH_MAX; +#else + cp->RealWorkingDirectoryLength = 4096; +#endif + cp->RealWorkingDirectory = + malloc((size_t)(cp->RealWorkingDirectoryLength)); + if(!cp->RealWorkingDirectory) + { + return 0; + } + } + + return 1; +} + +/*--------------------------------------------------------------------------*/ +/* Free all resources used by the given kwsysProcess instance that were + allocated by kwsysProcess_Execute. */ +static void kwsysProcessCleanup(kwsysProcess* cp, int error) +{ + int i; + + if(error) + { + /* We are cleaning up due to an error. Report the error message + if one has not been provided already. */ + if(cp->ErrorMessage[0] == 0) + { + strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + } + + /* Set the error state. */ + cp->State = kwsysProcess_State_Error; + + /* Kill any children already started. */ + if(cp->ForkPIDs) + { + int status; + for(i=0; i < cp->NumberOfCommands; ++i) + { + 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)); + } + } + } + + /* Restore the working directory. */ + if(cp->RealWorkingDirectory) + { + while((chdir(cp->RealWorkingDirectory) < 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) + { + free(cp->ForkPIDs); + cp->ForkPIDs = 0; + } + if(cp->RealWorkingDirectory) + { + free(cp->RealWorkingDirectory); + cp->RealWorkingDirectory = 0; + } + + /* Close pipe handles. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + } +} + +/*--------------------------------------------------------------------------*/ +/* Close the given file descriptor if it is open. Reset its value to -1. */ +static void kwsysProcessCleanupDescriptor(int* pfd) +{ + if(pfd && *pfd >= 0) + { + /* Keep trying to close until it is not interrupted by a + * signal. */ + while((close(*pfd) < 0) && (errno == EINTR)); + *pfd = -1; + } +} + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessClosePipes(kwsysProcess* cp) +{ + int i; + + /* Close any pipes that are still open. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0) + { +#if KWSYSPE_USE_SELECT + /* If the pipe was reported by the last call to select, we must + read from it. This is needed to satisfy the suggestions from + "man select_tut" and is not needed for the polling + implementation. Ignore the data. */ + if(FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) + { + /* We are handling this pipe now. Remove it from the set. */ + FD_CLR(cp->PipeReadEnds[i], &cp->PipeSet); + + /* The pipe is ready to read without blocking. Keep trying to + read until the operation is not interrupted. */ + while((read(cp->PipeReadEnds[i], cp->PipeBuffer, + KWSYSPE_PIPE_BUFFER_SIZE) < 0) && (errno == EINTR)); + } +#endif + + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } +} + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessSetNonBlocking(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if(flags >= 0) + { + flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + return flags >= 0; +} + +/*--------------------------------------------------------------------------*/ +#if defined(__VMS) +int decc$set_child_standard_streams(int fd1, int fd2, int fd3); +#endif + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, + kwsysProcessCreateInformation* si, int* readEnd) +{ + /* Setup the process's stdin. */ + if(prIndex > 0) + { + si->StdIn = *readEnd; + *readEnd = 0; + } + else if(cp->PipeFileSTDIN) + { + /* Open a file for the child's stdin to read. */ + si->StdIn = open(cp->PipeFileSTDIN, O_RDONLY); + if(si->StdIn < 0) + { + return 0; + } + + /* Set close-on-exec flag on the pipe's end. */ + if(fcntl(si->StdIn, F_SETFD, FD_CLOEXEC) < 0) + { + return 0; + } + } + else if(cp->PipeSharedSTDIN) + { + si->StdIn = 0; + } + else if(cp->PipeNativeSTDIN[0] >= 0) + { + si->StdIn = cp->PipeNativeSTDIN[0]; + + /* Set close-on-exec flag on the pipe's ends. The read end will + be dup2-ed into the stdin descriptor after the fork but before + the exec. */ + if((fcntl(cp->PipeNativeSTDIN[0], F_SETFD, FD_CLOEXEC) < 0) || + (fcntl(cp->PipeNativeSTDIN[1], F_SETFD, FD_CLOEXEC) < 0)) + { + return 0; + } + } + else + { + si->StdIn = -1; + } + + /* Setup the process's stdout. */ + { + /* Create the pipe. */ + int p[2]; + if(pipe(p KWSYSPE_VMS_NONBLOCK) < 0) + { + return 0; + } + *readEnd = p[0]; + si->StdOut = 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)) + { + return 0; + } + } + + /* Replace the stdout pipe with a file if requested. In this case + the select call will report that stdout is closed immediately. */ + if(prIndex == cp->NumberOfCommands-1 && cp->PipeFileSTDOUT) + { + if(!kwsysProcessSetupOutputPipeFile(&si->StdOut, cp->PipeFileSTDOUT)) + { + return 0; + } + } + + /* Replace the stdout pipe with the parent's if requested. In this + case the select call will report that stderr is closed + immediately. */ + if(prIndex == cp->NumberOfCommands-1 && cp->PipeSharedSTDOUT) + { + kwsysProcessCleanupDescriptor(&si->StdOut); + si->StdOut = 1; + } + + /* Replace the stdout pipe with the native pipe provided if any. In + this case the select call will report that stdout is closed + immediately. */ + if(prIndex == cp->NumberOfCommands-1 && cp->PipeNativeSTDOUT[1] >= 0) + { + if(!kwsysProcessSetupOutputPipeNative(&si->StdOut, cp->PipeNativeSTDOUT)) + { + return 0; + } + } + + /* 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) + { + return 0; + } + + /* Fork off a child process. */ +#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. */ + cp->ForkPIDs[prIndex] = vfork(); +#else + cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si); +#endif + if(cp->ForkPIDs[prIndex] < 0) + { + return 0; + } + + if(cp->ForkPIDs[prIndex] == 0) + { +#if defined(__VMS) + /* 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(si->ErrorPipe[0]); + + /* Setup the stdin, stdout, and stderr pipes. */ + if(si->StdIn > 0) + { + dup2(si->StdIn, 0); + } + else if(si->StdIn < 0) + { + close(0); + } + if(si->StdOut != 1) + { + dup2(si->StdOut, 1); + } + if(si->StdErr != 2) + { + dup2(si->StdErr, 2); + } + + /* Clear the close-on-exec flag for stdin, stdout, and stderr. + 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); + + /* Restore all default signal handlers. */ + kwsysProcessRestoreDefaultSignalHandlers(); +#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? */ + + /* Failure. Report error to parent and terminate. */ + kwsysProcessChildErrorExit(si->ErrorPipe[1]); + } + +#if defined(__VMS) + /* Restore the standard pipes of this process. */ + decc$set_child_standard_streams(0, 1, 2); +#endif + + /* 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. */ + { + kwsysProcess_ssize_t total = 0; + kwsysProcess_ssize_t n = 1; + /* Read the entire error message up to the length of our buffer. */ + while(total < KWSYSPE_PIPE_BUFFER_SIZE && n > 0) + { + /* Keep trying to read until the operation is not interrupted. */ + while(((n = read(si->ErrorPipe[0], cp->ErrorMessage+total, + (size_t)(KWSYSPE_PIPE_BUFFER_SIZE-total))) < 0) && + (errno == EINTR)); + if(n > 0) + { + total += n; + } + } + + /* We are done with the error reporting pipe read end. */ + kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]); + + if(total > 0) + { + /* The child failed to execute the process. */ + return 0; + } + } + + /* Successfully created this child process. */ + if(prIndex > 0 || si->StdIn > 0) + { + /* The parent process does not need the input pipe read end. */ + kwsysProcessCleanupDescriptor(&si->StdIn); + } + + /* The parent process does not need the output pipe write ends. */ + if(si->StdOut != 1) + { + kwsysProcessCleanupDescriptor(&si->StdOut); + } + + return 1; +} + +/*--------------------------------------------------------------------------*/ +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; + if(!name) + { + return 1; + } + + /* Close the existing descriptor. */ + kwsysProcessCleanupDescriptor(p); + + /* Open a file for the pipe to write. */ + if((fout = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) + { + return 0; + } + + /* Set close-on-exec flag on the pipe's end. */ + if(fcntl(fout, F_SETFD, FD_CLOEXEC) < 0) + { + return 0; + } + + /* Assign the replacement descriptor. */ + *p = fout; + return 1; +} + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessSetupOutputPipeNative(int* p, int des[2]) +{ + /* Close the existing descriptor. */ + kwsysProcessCleanupDescriptor(p); + + /* Set close-on-exec flag on the pipe's ends. The proper end will + be dup2-ed into the standard descriptor number after fork but + before exec. */ + if((fcntl(des[0], F_SETFD, FD_CLOEXEC) < 0) || + (fcntl(des[1], F_SETFD, FD_CLOEXEC) < 0)) + { + return 0; + } + + /* Assign the replacement descriptor. */ + *p = des[1]; + return 1; +} + +/*--------------------------------------------------------------------------*/ +/* Get the time at which either the process or user timeout will + expire. Returns 1 if the user timeout is first, and 0 otherwise. */ +static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, + kwsysProcessTime* timeoutTime) +{ + /* The first time this is called, we need to calculate the time at + which the child will timeout. */ + if(cp->Timeout > 0 && cp->TimeoutTime.tv_sec < 0) + { + kwsysProcessTime length = kwsysProcessTimeFromDouble(cp->Timeout); + cp->TimeoutTime = kwsysProcessTimeAdd(cp->StartTime, length); + } + + /* Start with process timeout. */ + *timeoutTime = cp->TimeoutTime; + + /* Check if the user timeout is earlier. */ + if(userTimeout) + { + kwsysProcessTime currentTime = kwsysProcessTimeGetCurrent(); + kwsysProcessTime userTimeoutLength = kwsysProcessTimeFromDouble(*userTimeout); + kwsysProcessTime userTimeoutTime = kwsysProcessTimeAdd(currentTime, + userTimeoutLength); + if(timeoutTime->tv_sec < 0 || + kwsysProcessTimeLess(userTimeoutTime, *timeoutTime)) + { + *timeoutTime = userTimeoutTime; + return 1; + } + } + return 0; +} + +/*--------------------------------------------------------------------------*/ +/* Get the length of time before the given timeout time arrives. + Returns 1 if the time has already arrived, and 0 otherwise. */ +static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, + double* userTimeout, + kwsysProcessTimeNative* timeoutLength, + int zeroIsExpired) +{ + if(timeoutTime->tv_sec < 0) + { + /* No timeout time has been requested. */ + return 0; + } + else + { + /* Calculate the remaining time. */ + kwsysProcessTime currentTime = kwsysProcessTimeGetCurrent(); + kwsysProcessTime timeLeft = kwsysProcessTimeSubtract(*timeoutTime, + currentTime); + if(timeLeft.tv_sec < 0 && userTimeout && *userTimeout <= 0) + { + /* Caller has explicitly requested a zero timeout. */ + timeLeft.tv_sec = 0; + timeLeft.tv_usec = 0; + } + + if(timeLeft.tv_sec < 0 || + (timeLeft.tv_sec == 0 && timeLeft.tv_usec == 0 && zeroIsExpired)) + { + /* Timeout has already expired. */ + return 1; + } + else + { + /* There is some time left. */ + timeoutLength->tv_sec = timeLeft.tv_sec; + timeoutLength->tv_usec = timeLeft.tv_usec; + return 0; + } + } +} + +/*--------------------------------------------------------------------------*/ +static kwsysProcessTime kwsysProcessTimeGetCurrent(void) +{ + kwsysProcessTime current; + kwsysProcessTimeNative current_native; + gettimeofday(¤t_native, 0); + current.tv_sec = (long)current_native.tv_sec; + current.tv_usec = (long)current_native.tv_usec; + return current; +} + +/*--------------------------------------------------------------------------*/ +static double kwsysProcessTimeToDouble(kwsysProcessTime t) +{ + return (double)t.tv_sec + (double)(t.tv_usec)*0.000001; +} + +/*--------------------------------------------------------------------------*/ +static kwsysProcessTime kwsysProcessTimeFromDouble(double d) +{ + kwsysProcessTime t; + t.tv_sec = (long)d; + t.tv_usec = (long)((d-(double)(t.tv_sec))*1000000); + return t; +} + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessTimeLess(kwsysProcessTime in1, kwsysProcessTime in2) +{ + return ((in1.tv_sec < in2.tv_sec) || + ((in1.tv_sec == in2.tv_sec) && (in1.tv_usec < in2.tv_usec))); +} + +/*--------------------------------------------------------------------------*/ +static kwsysProcessTime kwsysProcessTimeAdd(kwsysProcessTime in1, kwsysProcessTime in2) +{ + kwsysProcessTime out; + out.tv_sec = in1.tv_sec + in2.tv_sec; + out.tv_usec = in1.tv_usec + in2.tv_usec; + if(out.tv_usec > 1000000) + { + out.tv_usec -= 1000000; + out.tv_sec += 1; + } + return out; +} + +/*--------------------------------------------------------------------------*/ +static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProcessTime in2) +{ + kwsysProcessTime out; + out.tv_sec = in1.tv_sec - in2.tv_sec; + out.tv_usec = in1.tv_usec - in2.tv_usec; + if(out.tv_usec < 0) + { + out.tv_usec += 1000000; + out.tv_sec -= 1; + } + return out; +} + +/*--------------------------------------------------------------------------*/ +#define KWSYSPE_CASE(type, str) \ + cp->ExitException = kwsysProcess_Exception_##type; \ + strcpy(cp->ExitExceptionString, str) +static void kwsysProcessSetExitException(kwsysProcess* cp, int sig) +{ + switch (sig) + { +#ifdef SIGSEGV + case SIGSEGV: KWSYSPE_CASE(Fault, "Segmentation fault"); break; +#endif +#ifdef SIGBUS +# if !defined(SIGSEGV) || SIGBUS != SIGSEGV + case SIGBUS: KWSYSPE_CASE(Fault, "Bus error"); break; +# endif +#endif +#ifdef SIGFPE + case SIGFPE: KWSYSPE_CASE(Numerical, "Floating-point exception"); break; +#endif +#ifdef SIGILL + case SIGILL: KWSYSPE_CASE(Illegal, "Illegal instruction"); break; +#endif +#ifdef SIGINT + case SIGINT: KWSYSPE_CASE(Interrupt, "User interrupt"); break; +#endif +#ifdef SIGABRT + case SIGABRT: KWSYSPE_CASE(Other, "Child aborted"); break; +#endif +#ifdef SIGKILL + case SIGKILL: KWSYSPE_CASE(Other, "Child killed"); break; +#endif +#ifdef SIGTERM + case SIGTERM: KWSYSPE_CASE(Other, "Child terminated"); break; +#endif +#ifdef SIGHUP + case SIGHUP: KWSYSPE_CASE(Other, "SIGHUP"); break; +#endif +#ifdef SIGQUIT + case SIGQUIT: KWSYSPE_CASE(Other, "SIGQUIT"); break; +#endif +#ifdef SIGTRAP + case SIGTRAP: KWSYSPE_CASE(Other, "SIGTRAP"); break; +#endif +#ifdef SIGIOT +# if !defined(SIGABRT) || SIGIOT != SIGABRT + case SIGIOT: KWSYSPE_CASE(Other, "SIGIOT"); break; +# endif +#endif +#ifdef SIGUSR1 + case SIGUSR1: KWSYSPE_CASE(Other, "SIGUSR1"); break; +#endif +#ifdef SIGUSR2 + case SIGUSR2: KWSYSPE_CASE(Other, "SIGUSR2"); break; +#endif +#ifdef SIGPIPE + case SIGPIPE: KWSYSPE_CASE(Other, "SIGPIPE"); break; +#endif +#ifdef SIGALRM + case SIGALRM: KWSYSPE_CASE(Other, "SIGALRM"); break; +#endif +#ifdef SIGSTKFLT + case SIGSTKFLT: KWSYSPE_CASE(Other, "SIGSTKFLT"); break; +#endif +#ifdef SIGCHLD + case SIGCHLD: KWSYSPE_CASE(Other, "SIGCHLD"); break; +#elif defined(SIGCLD) + case SIGCLD: KWSYSPE_CASE(Other, "SIGCLD"); break; +#endif +#ifdef SIGCONT + case SIGCONT: KWSYSPE_CASE(Other, "SIGCONT"); break; +#endif +#ifdef SIGSTOP + case SIGSTOP: KWSYSPE_CASE(Other, "SIGSTOP"); break; +#endif +#ifdef SIGTSTP + case SIGTSTP: KWSYSPE_CASE(Other, "SIGTSTP"); break; +#endif +#ifdef SIGTTIN + case SIGTTIN: KWSYSPE_CASE(Other, "SIGTTIN"); break; +#endif +#ifdef SIGTTOU + case SIGTTOU: KWSYSPE_CASE(Other, "SIGTTOU"); break; +#endif +#ifdef SIGURG + case SIGURG: KWSYSPE_CASE(Other, "SIGURG"); break; +#endif +#ifdef SIGXCPU + case SIGXCPU: KWSYSPE_CASE(Other, "SIGXCPU"); break; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: KWSYSPE_CASE(Other, "SIGXFSZ"); break; +#endif +#ifdef SIGVTALRM + case SIGVTALRM: KWSYSPE_CASE(Other, "SIGVTALRM"); break; +#endif +#ifdef SIGPROF + case SIGPROF: KWSYSPE_CASE(Other, "SIGPROF"); break; +#endif +#ifdef SIGWINCH + case SIGWINCH: KWSYSPE_CASE(Other, "SIGWINCH"); break; +#endif +#ifdef SIGPOLL + case SIGPOLL: KWSYSPE_CASE(Other, "SIGPOLL"); break; +#endif +#ifdef SIGIO +# if !defined(SIGPOLL) || SIGIO != SIGPOLL + case SIGIO: KWSYSPE_CASE(Other, "SIGIO"); break; +# endif +#endif +#ifdef SIGPWR + case SIGPWR: KWSYSPE_CASE(Other, "SIGPWR"); break; +#endif +#ifdef SIGSYS + case SIGSYS: KWSYSPE_CASE(Other, "SIGSYS"); break; +#endif +#ifdef SIGUNUSED +# if !defined(SIGSYS) || SIGUNUSED != SIGSYS + case SIGUNUSED: KWSYSPE_CASE(Other, "SIGUNUSED"); break; +# endif +#endif + default: + cp->ExitException = kwsysProcess_Exception_Other; + sprintf(cp->ExitExceptionString, "Signal %d", sig); + break; + } +} +#undef KWSYSPE_CASE + +/*--------------------------------------------------------------------------*/ +/* When the child process encounters an error before its program is + invoked, this is called to report the error to the parent and + exit. */ +static void kwsysProcessChildErrorExit(int errorPipe) +{ + /* Construct the error message. */ + char buffer[KWSYSPE_PIPE_BUFFER_SIZE]; + kwsysProcess_ssize_t result; + strncpy(buffer, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + + /* Report the error to the parent through the special pipe. */ + result=write(errorPipe, buffer, strlen(buffer)); + (void)result; + + /* Terminate without cleanup. */ + _exit(1); +} + +/*--------------------------------------------------------------------------*/ +/* Restores all signal handlers to their default values. */ +static void kwsysProcessRestoreDefaultSignalHandlers(void) +{ + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_DFL; +#ifdef SIGHUP + sigaction(SIGHUP, &act, 0); +#endif +#ifdef SIGINT + sigaction(SIGINT, &act, 0); +#endif +#ifdef SIGQUIT + sigaction(SIGQUIT, &act, 0); +#endif +#ifdef SIGILL + sigaction(SIGILL, &act, 0); +#endif +#ifdef SIGTRAP + sigaction(SIGTRAP, &act, 0); +#endif +#ifdef SIGABRT + sigaction(SIGABRT, &act, 0); +#endif +#ifdef SIGIOT + sigaction(SIGIOT, &act, 0); +#endif +#ifdef SIGBUS + sigaction(SIGBUS, &act, 0); +#endif +#ifdef SIGFPE + sigaction(SIGFPE, &act, 0); +#endif +#ifdef SIGUSR1 + sigaction(SIGUSR1, &act, 0); +#endif +#ifdef SIGSEGV + sigaction(SIGSEGV, &act, 0); +#endif +#ifdef SIGUSR2 + sigaction(SIGUSR2, &act, 0); +#endif +#ifdef SIGPIPE + sigaction(SIGPIPE, &act, 0); +#endif +#ifdef SIGALRM + sigaction(SIGALRM, &act, 0); +#endif +#ifdef SIGTERM + sigaction(SIGTERM, &act, 0); +#endif +#ifdef SIGSTKFLT + sigaction(SIGSTKFLT, &act, 0); +#endif +#ifdef SIGCLD + sigaction(SIGCLD, &act, 0); +#endif +#ifdef SIGCHLD + sigaction(SIGCHLD, &act, 0); +#endif +#ifdef SIGCONT + sigaction(SIGCONT, &act, 0); +#endif +#ifdef SIGTSTP + sigaction(SIGTSTP, &act, 0); +#endif +#ifdef SIGTTIN + sigaction(SIGTTIN, &act, 0); +#endif +#ifdef SIGTTOU + sigaction(SIGTTOU, &act, 0); +#endif +#ifdef SIGURG + sigaction(SIGURG, &act, 0); +#endif +#ifdef SIGXCPU + sigaction(SIGXCPU, &act, 0); +#endif +#ifdef SIGXFSZ + sigaction(SIGXFSZ, &act, 0); +#endif +#ifdef SIGVTALRM + sigaction(SIGVTALRM, &act, 0); +#endif +#ifdef SIGPROF + sigaction(SIGPROF, &act, 0); +#endif +#ifdef SIGWINCH + sigaction(SIGWINCH, &act, 0); +#endif +#ifdef SIGPOLL + sigaction(SIGPOLL, &act, 0); +#endif +#ifdef SIGIO + sigaction(SIGIO, &act, 0); +#endif +#ifdef SIGPWR + sigaction(SIGPWR, &act, 0); +#endif +#ifdef SIGSYS + sigaction(SIGSYS, &act, 0); +#endif +#ifdef SIGUNUSED + sigaction(SIGUNUSED, &act, 0); +#endif +} + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessExit(void) +{ + _exit(0); +} + +/*--------------------------------------------------------------------------*/ +#if !defined(__VMS) +static pid_t kwsysProcessFork(kwsysProcess* cp, + kwsysProcessCreateInformation* si) +{ + /* Create a detached process if requested. */ + if(cp->OptionDetach) + { + /* Create an intermediate process. */ + pid_t middle_pid = fork(); + if(middle_pid < 0) + { + /* Fork failed. Return as if we were not detaching. */ + return middle_pid; + } + else if(middle_pid == 0) + { + /* This is the intermediate process. Create the real child. */ + pid_t child_pid = fork(); + if(child_pid == 0) + { + /* This is the real child process. There is nothing to do here. */ + return 0; + } + else + { + /* Use the error pipe to report the pid to the real parent. */ + while((write(si->ErrorPipe[1], &child_pid, sizeof(child_pid)) < 0) && + (errno == EINTR)); + + /* Exit without cleanup. The parent holds all resources. */ + kwsysProcessExit(); + return 0; /* Never reached, but avoids SunCC warning. */ + } + } + else + { + /* This is the original parent process. The intermediate + process will use the error pipe to report the pid of the + detached child. */ + pid_t child_pid; + int status; + while((read(si->ErrorPipe[0], &child_pid, sizeof(child_pid)) < 0) && + (errno == EINTR)); + + /* Wait for the intermediate process to exit and clean it up. */ + while((waitpid(middle_pid, &status, 0) < 0) && (errno == EINTR)); + return child_pid; + } + } + else + { + /* Not creating a detached process. Use normal fork. */ + return fork(); + } +} +#endif + +/*--------------------------------------------------------------------------*/ +/* We try to obtain process information by invoking the ps command. + Here we define the command to call on each platform and the + corresponding parsing format string. The parsing format should + have two integers to store: the pid and then the ppid. */ +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \ + || defined(__OpenBSD__) || defined(__GLIBC__) || defined(__GNU__) +# define KWSYSPE_PS_COMMAND "ps axo pid,ppid" +# define KWSYSPE_PS_FORMAT "%d %d\n" +#elif defined(__sun) && (defined(__SVR4) || defined(__svr4__)) /* Solaris */ +# define KWSYSPE_PS_COMMAND "ps -e -o pid,ppid" +# define KWSYSPE_PS_FORMAT "%d %d\n" +#elif defined(__hpux) || defined(__sun__) || defined(__sgi) || defined(_AIX) \ + || defined(__sparc) +# define KWSYSPE_PS_COMMAND "ps -ef" +# define KWSYSPE_PS_FORMAT "%*s %d %d %*[^\n]\n" +#elif defined(__QNX__) +# define KWSYSPE_PS_COMMAND "ps -Af" +# define KWSYSPE_PS_FORMAT "%*d %d %d %*[^\n]\n" +#elif defined(__CYGWIN__) +# define KWSYSPE_PS_COMMAND "ps aux" +# define KWSYSPE_PS_FORMAT "%d %d %*[^\n]\n" +#endif + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessKill(pid_t process_id) +{ +#if defined(__linux__) || defined(__CYGWIN__) + DIR* procdir; +#endif + + /* Suspend the process to be sure it will not create more children. */ + kill(process_id, SIGSTOP); + +#if defined(__CYGWIN__) + /* Some Cygwin versions seem to need help here. Give up our time slice + so that the child can process SIGSTOP before we send SIGKILL. */ + usleep(1); +#endif + + /* Kill all children if we can find them. */ +#if defined(__linux__) || defined(__CYGWIN__) + /* First try using the /proc filesystem. */ + if((procdir = opendir("/proc")) != NULL) + { +#if defined(MAXPATHLEN) + char fname[MAXPATHLEN]; +#elif defined(PATH_MAX) + char fname[PATH_MAX]; +#else + char fname[4096]; +#endif + char buffer[KWSYSPE_PIPE_BUFFER_SIZE+1]; + struct dirent* d; + + /* Each process has a directory in /proc whose name is the pid. + Within this directory is a file called stat that has the + following format: + + pid (command line) status ppid ... + + We want to get the ppid for all processes. Those that have + process_id as their parent should be recursively killed. */ + for(d = readdir(procdir); d; d = readdir(procdir)) + { + int pid; + if(sscanf(d->d_name, "%d", &pid) == 1 && pid != 0) + { + struct stat finfo; + sprintf(fname, "/proc/%d/stat", pid); + if(stat(fname, &finfo) == 0) + { + FILE* f = fopen(fname, "r"); + if(f) + { + size_t nread = fread(buffer, 1, KWSYSPE_PIPE_BUFFER_SIZE, f); + fclose(f); + buffer[nread] = '\0'; + if(nread > 0) + { + const char* rparen = strrchr(buffer, ')'); + int ppid; + if(rparen && (sscanf(rparen+1, "%*s %d", &ppid) == 1)) + { + if(ppid == process_id) + { + /* Recursively kill this child and its children. */ + kwsysProcessKill(pid); + } + } + } + } + } + } + } + closedir(procdir); + } + else +#endif + { +#if defined(KWSYSPE_PS_COMMAND) + /* Try running "ps" to get the process information. */ + FILE* ps = popen(KWSYSPE_PS_COMMAND, "r"); + + /* Make sure the process started and provided a valid header. */ + if(ps && fscanf(ps, "%*[^\n]\n") != EOF) + { + /* Look for processes whose parent is the process being killed. */ + int pid, ppid; + while(fscanf(ps, KWSYSPE_PS_FORMAT, &pid, &ppid) == 2) + { + if(ppid == process_id) + { + /* Recursively kill this child and its children. */ + kwsysProcessKill(pid); + } + } + } + + /* We are done with the ps process. */ + if(ps) + { + pclose(ps); + } +#endif + } + + /* Kill the process. */ + kill(process_id, SIGKILL); + +#if defined(__APPLE__) + /* On OS X 10.3 the above SIGSTOP occasionally prevents the SIGKILL + from working. Just in case, we resume the child and kill it + again. There is a small race condition in this obscure case. If + the child manages to fork again between these two signals, we + will not catch its children. */ + kill(process_id, SIGCONT); + kill(process_id, SIGKILL); +#endif +} + +/*--------------------------------------------------------------------------*/ +#if defined(__VMS) +int decc$feature_get_index(const char* name); +int decc$feature_set_value(int index, int mode, int value); +static int kwsysProcessSetVMSFeature(const char* name, int value) +{ + int i; + errno = 0; + i = decc$feature_get_index(name); + return i >= 0 && (decc$feature_set_value(i, 1, value) >= 0 || errno == 0); +} +#endif + +/*--------------------------------------------------------------------------*/ +/* 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 p[2]; + if(pipe(p KWSYSPE_VMS_NONBLOCK) < 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(!kwsysProcessSetNonBlocking(p[0]) || + !kwsysProcessSetNonBlocking(p[1])) + { + 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((size_t)(newProcesses.Size)* + sizeof(kwsysProcess*))))) + { + /* Copy the old pipe set to the new memory. */ + if(oldProcesses.Count > 0) + { + memcpy(newProcesses.Processes, oldProcesses.Processes, + ((size_t)(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)); +#if KWSYSPE_USE_SIGINFO + newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; +# ifdef SA_RESTART + newSigChldAction.sa_flags |= SA_RESTART; +# endif +#else + newSigChldAction.sa_handler = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP; +#endif + 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]; + } + + /* 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)); + + /* Free the table of process pointers since it is now empty. + This is safe because the signal handler has been removed. */ + newProcesses.Size = 0; + free(newProcesses.Processes); + newProcesses.Processes = 0; + } + + /* Store the new set in that seen by the signal handler. */ + kwsysProcessesUpdate(&newProcesses); + } + } + + /* 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 +#if KWSYSPE_USE_SIGINFO + , siginfo_t* info, void* ucontext +#endif + ) +{ + (void)signum; +#if KWSYSPE_USE_SIGINFO + (void)info; + (void)ucontext; +#endif + + /* Signal all process objects that a child has terminated. */ + { + int i; + for(i=0; i < kwsysProcesses.Count; ++i) + { + /* Set the pipe in a signalled state. */ + char buf = 1; + kwsysProcess* cp = kwsysProcesses.Processes[i]; + kwsysProcess_ssize_t status= + read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); + (void)status; + status=write(cp->SignalPipe, &buf, 1); + (void)status; + } + } + +#if !KWSYSPE_USE_SIGINFO + /* Re-Install our handler for SIGCHLD. Repeat call until it is not + interrupted. */ + { + struct sigaction newSigChldAction; + memset(&newSigChldAction, 0, sizeof(struct sigaction)); + newSigChldAction.sa_handler = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP; + while((sigaction(SIGCHLD, &newSigChldAction, + &kwsysProcessesOldSigChldAction) < 0) && + (errno == EINTR)); + } +#endif +} |