From ad8bc4b1a43dd017d361f189ec2b38d97e7de987 Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 3 Dec 2003 09:20:05 -0500 Subject: ENH: Merged changes from KWSys-MultiProcess-bp to KWSys-MultiProcess-b2t-1-mp to main tree. This introduces support for process pipelines. --- Source/kwsys/Process.h.in | 19 +- Source/kwsys/ProcessFwd9x.c | 109 +++-- Source/kwsys/ProcessUNIX.c | 543 +++++++++++++++++------ Source/kwsys/ProcessWin32.c | 1001 +++++++++++++++++++++++++++++-------------- 4 files changed, 1182 insertions(+), 490 deletions(-) diff --git a/Source/kwsys/Process.h.in b/Source/kwsys/Process.h.in index c97212f..36d24f0 100644 --- a/Source/kwsys/Process.h.in +++ b/Source/kwsys/Process.h.in @@ -30,6 +30,7 @@ #define kwsysProcess_New kwsys(Process_New) #define kwsysProcess_Delete kwsys(Process_Delete) #define kwsysProcess_SetCommand kwsys(Process_SetCommand) +#define kwsysProcess_AddCommand kwsys(Process_AddCommand) #define kwsysProcess_SetTimeout kwsys(Process_SetTimeout) #define kwsysProcess_SetWorkingDirectory kwsys(Process_SetWorkingDirectory) #define kwsysProcess_Option_HideWindow kwsys(Process_Option_HideWindow) @@ -89,10 +90,21 @@ kwsysEXPORT void kwsysProcess_Delete(kwsysProcess* cp); /** * Set the command line to be executed. Argument is an array of * pointers to the command and each argument. Ths array must end with - * a NULL pointer. + * a NULL pointer. Any previous command lines are removed. Returns + * 1 for success and 0 otherwise. */ -kwsysEXPORT void kwsysProcess_SetCommand(kwsysProcess* cp, - char const* const* command); +kwsysEXPORT int kwsysProcess_SetCommand(kwsysProcess* cp, + char const* const* command); + +/** + * Add a command line to be executed. Argument is an array of + * pointers to the command and each argument. Ths array must end with + * a NULL pointer. If this is not the first command added, its + * standard input will be connected to the standard output of the + * previous command. Returns 1 for success and 0 otherwise. + */ +kwsysEXPORT int kwsysProcess_AddCommand(kwsysProcess* cp, + char const* const* command); /** * Set the timeout for the child process. The timeout period begins @@ -280,6 +292,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp); # undef kwsysProcess_New # undef kwsysProcess_Delete # undef kwsysProcess_SetCommand +# undef kwsysProcess_AddCommand # undef kwsysProcess_SetTimeout # undef kwsysProcess_SetWorkingDirectory # undef kwsysProcess_Option_HideWindow diff --git a/Source/kwsys/ProcessFwd9x.c b/Source/kwsys/ProcessFwd9x.c index 7c03a68..689fbb0 100644 --- a/Source/kwsys/ProcessFwd9x.c +++ b/Source/kwsys/ProcessFwd9x.c @@ -27,6 +27,8 @@ PURPOSE. See the above copyright notices for more information. #include #include +void ReportLastError(HANDLE errorPipe); + int main() { /* Process startup information for the real child. */ @@ -49,6 +51,11 @@ int main() /* Handle to the error reporting pipe provided by the parent. This is parsed off the command line. */ HANDLE errorPipe = 0; + HANDLE errorPipeOrig = 0; + + /* Handle to the event the parent uses to tell us to resume the child. + This is parsed off the command line. */ + HANDLE resumeEvent = 0; /* Handle to the event the parent uses to tell us to kill the child. This is parsed off the command line. */ @@ -75,7 +82,12 @@ int main() /* Parse the error pipe handle. */ while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } - sscanf(cmdLine, "%p", &errorPipe); + sscanf(cmdLine, "%p", &errorPipeOrig); + + /* Parse the resume event handle. */ + while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } + while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } + sscanf(cmdLine, "%p", &resumeEvent); /* Parse the kill event handle. */ while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } @@ -91,6 +103,22 @@ int main() while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } + /* Create a non-inherited copy of the error pipe. We do not want + the child to get it. */ + if(DuplicateHandle(GetCurrentProcess(), errorPipeOrig, + GetCurrentProcess(), &errorPipe, + 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + /* Have a non-inherited duplicate. Close the inherited one. */ + CloseHandle(errorPipeOrig); + } + else + { + /* Could not duplicate handle. Report the error. */ + ReportLastError(errorPipeOrig); + return 1; + } + /* Create the subprocess. */ ZeroMemory(&si, sizeof(si)); ZeroMemory(&pi, sizeof(pi)); @@ -100,32 +128,23 @@ int main() si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - if(!CreateProcess(0, cmdLine, 0, 0, TRUE, 0, 0, 0, &si, &pi)) + if(CreateProcess(0, cmdLine, 0, 0, TRUE, CREATE_SUSPENDED, 0, 0, &si, &pi)) + { + /* Process created successfully. Close the error reporting pipe + to notify the parent of success. */ + CloseHandle(errorPipe); + } + else { /* Error creating the process. Report the error to the parent process through the special error reporting pipe. */ - LPVOID lpMsgBuf; - DWORD n; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, - 0, - NULL - ); - WriteFile(errorPipe, lpMsgBuf, strlen(lpMsgBuf)+1, &n, 0); - LocalFree( lpMsgBuf ); + ReportLastError(errorPipe); return 1; } - CloseHandle(pi.hThread); - /* Wait for subprocess to exit or for kill event from parent. */ + /* Wait for resume or kill event from parent. */ waitHandles[0] = killEvent; - waitHandles[1] = pi.hProcess; + waitHandles[1] = resumeEvent; waitResult = WaitForMultipleObjects(2, waitHandles, 0, INFINITE); /* Check what happened. */ @@ -135,22 +154,54 @@ int main() TerminateProcess(pi.hProcess, 255); WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); return 1; } - else if(GetExitCodeProcess(pi.hProcess, &retVal)) + else + { + /* We were asked to resume the child. */ + ResumeThread(pi.hThread); + CloseHandle(pi.hThread); + } + + /* Wait for subprocess to exit or for kill event from parent. */ + waitHandles[0] = killEvent; + waitHandles[1] = pi.hProcess; + waitResult = WaitForMultipleObjects(2, waitHandles, 0, INFINITE); + + /* Check what happened. */ + if(waitResult == WAIT_OBJECT_0) { - /* The child exited and we could get the return code. */ + /* We were asked to kill the child. */ + TerminateProcess(pi.hProcess, 255); + WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); - return retVal; + return 1; } else { - /* The child exited and we could not get the return code. Report - the problem to the parent process. */ - DWORD n; - const char* msg = "Failed to get process return code."; - WriteFile(errorPipe, msg, strlen(msg)+1, &n, 0); + /* The child exited. Get the return code. */ + GetExitCodeProcess(pi.hProcess, &retVal); CloseHandle(pi.hProcess); - return -1; + return retVal; } } + +void ReportLastError(HANDLE errorPipe) +{ + LPVOID lpMsgBuf; + DWORD n; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + WriteFile(errorPipe, lpMsgBuf, strlen(lpMsgBuf)+1, &n, 0); + LocalFree( lpMsgBuf ); +} diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index b1c40ab..c5bae27 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -33,6 +33,17 @@ conjunction with the timeout on the select call to implement a timeout for program even when it closes stdout and stderr. */ +/* + +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. + +*/ + #include /* snprintf */ #include /* malloc, free */ #include /* strdup, strerror, memset */ @@ -46,23 +57,35 @@ timeout for program even when it closes stdout and stderr. #include /* sigaction */ /* The number of pipes for the child's output. The standard stdout - and stderr pipes are the first two. One more pipe is used for the - child to report errors to the parent before the real process is - invoked. */ + 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_ERROR 2 +#define KWSYSPE_PIPE_TERM 2 /* The maximum amount to read from a pipe at a time. */ #define KWSYSPE_PIPE_BUFFER_SIZE 1024 typedef struct timeval kwsysProcessTime; +typedef struct kwsysProcessCreateInformation_s +{ + int stdin; + int stdout; + int stderr; + int term; + int error[2]; +} kwsysProcessCreateInformation; + /*--------------------------------------------------------------------------*/ -static void kwsysProcessInitialize(kwsysProcess* cp); +static int kwsysProcessInitialize(kwsysProcess* cp); static void kwsysProcessCleanup(kwsysProcess* cp, int error); static void kwsysProcessCleanupDescriptor(int* pfd); +static int kwsysProcessCreate(kwsysProcess* cp, int index, + kwsysProcessCreateInformation* si, int* readEnd); static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, kwsysProcessTime* timeoutTime); static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, @@ -73,30 +96,28 @@ 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 kwsysProcessChildErrorExit(kwsysProcess* cp); +static void kwsysProcessChildErrorExit(int errorPipe); static void kwsysProcessRestoreDefaultSignalHandlers(); /*--------------------------------------------------------------------------*/ /* Structure containing data used to implement the child's execution. */ struct kwsysProcess_s { - /* The command line to execute. */ - char** Command; + /* The command lines to execute. */ + char*** Commands; + int NumberOfCommands; /* Descriptors for the read ends of the child's output pipes. */ int PipeReadEnds[KWSYSPE_PIPE_COUNT]; - /* Descriptors for the write ends of the child's output pipes. */ - int PipeWriteEnds[KWSYSPE_PIPE_COUNT]; - /* Buffer for pipe data. */ char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE]; - /* Process ID returned by the fork. */ - pid_t ForkPID; + /* Process IDs returned by the calls to fork. */ + pid_t* ForkPIDs; - /* Flag for whether the child reported an error. */ - int ChildError; + /* Flag for whether the children were terminated by a faild select. */ + int SelectError; /* The timeout length. */ double Timeout; @@ -140,7 +161,9 @@ struct kwsysProcess_s /* Buffer for error message in case of failure. */ char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1]; - int ErrorMessageLength; + + /* The exit codes of each child process in the pipeline. */ + int* CommandExitCodes; }; /*--------------------------------------------------------------------------*/ @@ -169,36 +192,109 @@ void kwsysProcess_Delete(kwsysProcess* cp) /* Free memory. */ kwsysProcess_SetCommand(cp, 0); kwsysProcess_SetWorkingDirectory(cp, 0); + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } free(cp); } /*--------------------------------------------------------------------------*/ -void kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) +int kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) { - if(cp->Command) + int i; + for(i=0; i < cp->NumberOfCommands; ++i) { - char** c = cp->Command; + char** c = cp->Commands[i]; while(*c) { free(*c++); } - free(cp->Command); - cp->Command = 0; + free(cp->Commands[i]); + } + cp->NumberOfCommands = 0; + if(cp->Commands) + { + free(cp->Commands); + cp->Commands = 0; } if(command) { - char const* const* c = command; - int n = 0; - int i = 0; - while(*c++); - n = c - command - 1; - cp->Command = (char**)malloc((n+1)*sizeof(char*)); - for(i=0; i < n; ++i) + 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(!command) + { + return 0; + } + + /* Allocate a new array for command pointers. */ + newNumberOfCommands = cp->NumberOfCommands + 1; + if(!(newCommands = (char***)malloc(sizeof(char**) * 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. */ + { + char const* const* c = command; + int n = 0; + int i = 0; + while(*c++); + n = c - command - 1; + newCommands[cp->NumberOfCommands] = (char**)malloc((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) { - cp->Command[i] = strdup(command[i]); + free(newCommands[cp->NumberOfCommands][i-1]); } - cp->Command[n] = 0; + 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; } /*--------------------------------------------------------------------------*/ @@ -289,6 +385,7 @@ void kwsysProcess_Execute(kwsysProcess* cp) { int i; struct sigaction newSigChldAction; + kwsysProcessCreateInformation si = {-1, -1, -1, -1, {-1, -1}}; /* Do not execute a second copy simultaneously. */ if(cp->State == kwsysProcess_State_Executing) @@ -297,7 +394,12 @@ void kwsysProcess_Execute(kwsysProcess* cp) } /* Initialize the control structure for a new process. */ - kwsysProcessInitialize(cp); + if(!kwsysProcessInitialize(cp)) + { + strcpy(cp->ErrorMessage, "Out of memory"); + cp->State = kwsysProcess_State_Error; + return; + } /* We want no special handling of SIGCHLD. Repeat call until it is not interrupted. */ @@ -306,29 +408,37 @@ void kwsysProcess_Execute(kwsysProcess* cp) while((sigaction(SIGCHLD, &newSigChldAction, &cp->OldSigChldAction) < 0) && (errno == EINTR)); - /* Create pipes for subprocess output. */ - for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + /* Setup the stderr and termination pipes to be shared by all processes. */ + for(i=KWSYSPE_PIPE_STDERR; i < KWSYSPE_PIPE_COUNT; ++i) { - int p[2]; - /* Create the pipe. */ + int p[2]; if(pipe(p) < 0) { kwsysProcessCleanup(cp, 1); return; } + /* Store the pipe. */ + cp->PipeReadEnds[i] = p[0]; + if(i == KWSYSPE_PIPE_STDERR) + { + si.stderr = p[1]; + } + else + { + si.term = 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); + kwsysProcessCleanupDescriptor(&si.term); return; } - - /* Store the pipe. */ - cp->PipeReadEnds[i] = p[0]; - cp->PipeWriteEnds[i] = p[1]; } /* The timeout period starts now. */ @@ -336,62 +446,37 @@ void kwsysProcess_Execute(kwsysProcess* cp) cp->TimeoutTime.tv_sec = -1; cp->TimeoutTime.tv_usec = -1; - /* Fork off a child process. */ - cp->ForkPID = fork(); - if(cp->ForkPID < 0) - { - kwsysProcessCleanup(cp, 1); - return; - } - - /* If this is the child process, run the real process. */ - if(cp->ForkPID == 0) + /* Create the pipeline of processes. */ + { + int readEnd = 0; + for(i=0; i < cp->NumberOfCommands; ++i) { - /* We used to close stdin, but some programs do not like being run - without stdin. Just use whatever stdin the parent program is - using. */ - /*close(0);*/ - - /* Setup the stdout/stderr pipes. */ - dup2(cp->PipeWriteEnds[KWSYSPE_PIPE_STDOUT], 1); - dup2(cp->PipeWriteEnds[KWSYSPE_PIPE_STDERR], 2); - - /* Clear the close-on-exec flag for stdout, stderr, and the child - error report pipe. All other pipe handles will be closed when - exec succeeds. */ - fcntl(1, F_SETFD, 0); - fcntl(2, F_SETFD, 0); - fcntl(cp->PipeWriteEnds[KWSYSPE_PIPE_ERROR], F_SETFD, 0); - - /* Restore all default signal handlers. */ - kwsysProcessRestoreDefaultSignalHandlers(); - - /* Change to the working directory specified, if any. */ - if(cp->WorkingDirectory) + if(!kwsysProcessCreate(cp, i, &si, &readEnd)) { - /* Some platforms specify that the chdir call may be - interrupted. Repeat the call until it finishes. */ - int r; - while(((r = chdir(cp->WorkingDirectory)) < 0) && (errno == EINTR)); - if(r < 0) + kwsysProcessCleanup(cp, 1); + + /* Release resources that may have been allocated for this + process before an error occurred. */ + kwsysProcessCleanupDescriptor(&readEnd); + if(i > 0) { - /* Failure. Report error to parent and terminate. */ - kwsysProcessChildErrorExit(cp); + kwsysProcessCleanupDescriptor(&si.stdin); } + kwsysProcessCleanupDescriptor(&si.stdout); + kwsysProcessCleanupDescriptor(&si.stderr); + kwsysProcessCleanupDescriptor(&si.term); + kwsysProcessCleanupDescriptor(&si.error[0]); + kwsysProcessCleanupDescriptor(&si.error[1]); + return; } - - /* Execute the real process. If successful, this does not return. */ - execvp(cp->Command[0], cp->Command); - - /* Failure. Report error to parent and terminate. */ - kwsysProcessChildErrorExit(cp); } + /* Save a handle to the output pipe for the last process. */ + cp->PipeReadEnds[KWSYSPE_PIPE_STDOUT] = readEnd; + } - /* The parent process does not need the pipe write ends. */ - for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) - { - kwsysProcessCleanupDescriptor(&cp->PipeWriteEnds[i]); - } + /* The parent process does not need the output pipe write ends. */ + kwsysProcessCleanupDescriptor(&si.stderr); + kwsysProcessCleanupDescriptor(&si.term); /* All the pipes are now open. */ cp->PipesLeft = KWSYSPE_PIPE_COUNT; @@ -401,8 +486,8 @@ void kwsysProcess_Execute(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ -int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* length, - double* userTimeout) +int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, + int* length, double* userTimeout) { int i; int max = -1; @@ -448,22 +533,9 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng if(n > 0) { /* We have data on this pipe. */ - if(i == KWSYSPE_PIPE_ERROR) + if(i == KWSYSPE_PIPE_TERM) { - /* This is data on the special error reporting pipe. The - child process failed to execute the program. */ - cp->ChildError = 1; - if(n > KWSYSPE_PIPE_BUFFER_SIZE - cp->ErrorMessageLength) - { - n = KWSYSPE_PIPE_BUFFER_SIZE - cp->ErrorMessageLength; - } - if(n > 0) - { - memcpy(cp->ErrorMessage+cp->ErrorMessageLength, - cp->PipeBuffer, n); - cp->ErrorMessageLength += n; - cp->ErrorMessage[cp->ErrorMessageLength] = 0; - } + /* This is data on the special termination pipe. Ignore it. */ } else if(pipes & (1 << i)) { @@ -548,10 +620,10 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng pipe buffer. */ strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); - /* Kill the child now. */ + /* Kill the children now. */ kwsysProcess_Kill(cp); cp->Killed = 0; - cp->ChildError = 1; + cp->SelectError = 1; cp->PipesLeft = 0; } } @@ -586,7 +658,7 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng } else { - /* The process timeout has expired. Kill the child now. */ + /* The process timeout has expired. Kill the children now. */ kwsysProcess_Kill(cp); cp->Killed = 0; cp->TimeoutExpired = 1; @@ -623,19 +695,36 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) } } - /* Wait for the child to terminate. The process should have already - exited because KWSYSPE_PIPE_ERROR has been closed by this point. - Repeat the call until it is not interrupted. */ - while(((result = waitpid(cp->ForkPID, &status, 0)) < 0) && (errno == EINTR)); - if(result <= 0) + /* 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. */ + { + int i; + for(i=0; i < cp->NumberOfCommands; ++i) { - /* Unexpected error. */ - kwsysProcessCleanup(cp, 1); + 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) + { + /* 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->ChildError) + if(cp->SelectError) { /* The error message is already in its buffer. Tell kwsysProcessCleanup to not create it. */ @@ -644,6 +733,9 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) return 1; } + /* Use the status of the last process in the pipeline. */ + status = cp->CommandExitCodes[cp->NumberOfCommands-1]; + /* Determine the outcome. */ if(cp->Killed) { @@ -703,29 +795,35 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) /*--------------------------------------------------------------------------*/ void kwsysProcess_Kill(kwsysProcess* cp) { + int i; + /* Make sure we are executing a process. */ if(cp->State != kwsysProcess_State_Executing) { return; } - /* Kill the child. */ + /* Kill the children. */ cp->Killed = 1; - kill(cp->ForkPID, SIGKILL); + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(cp->ForkPIDs[i]) + { + kill(cp->ForkPIDs[i], SIGKILL); + } + } } /*--------------------------------------------------------------------------*/ /* Initialize a process control structure for kwsysProcess_Execute. */ -static void kwsysProcessInitialize(kwsysProcess* cp) +static int kwsysProcessInitialize(kwsysProcess* cp) { int i; for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { cp->PipeReadEnds[i] = -1; - cp->PipeWriteEnds[i] = -1; } - cp->ForkPID = -1; - cp->ChildError = 0; + cp->SelectError = 0; cp->StartTime.tv_sec = -1; cp->StartTime.tv_usec = -1; cp->TimeoutTime.tv_sec = -1; @@ -739,7 +837,30 @@ static void kwsysProcessInitialize(kwsysProcess* cp) cp->ExitCode = 1; cp->ExitValue = 1; cp->ErrorMessage[0] = 0; - cp->ErrorMessageLength = 0; + + if(cp->ForkPIDs) + { + free(cp->ForkPIDs); + } + cp->ForkPIDs = (pid_t*)malloc(sizeof(pid_t)*cp->NumberOfCommands); + if(!cp->ForkPIDs) + { + return 0; + } + memset(cp->ForkPIDs, 0, sizeof(pid_t)*cp->NumberOfCommands); + + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } + cp->CommandExitCodes = (int*)malloc(sizeof(int)*cp->NumberOfCommands); + if(!cp->CommandExitCodes) + { + return 0; + } + memset(cp->CommandExitCodes, 0, sizeof(int)*cp->NumberOfCommands); + + return 1; } /*--------------------------------------------------------------------------*/ @@ -749,22 +870,46 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error) { int i; - /* If cleaning up due to an error, report the error message. */ if(error) { - strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + /* 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) + { + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(cp->ForkPIDs[i]) + { + kill(cp->ForkPIDs[i], SIGKILL); + } + } + } } /* Restore the SIGCHLD handler. */ while((sigaction(SIGCHLD, &cp->OldSigChldAction, 0) < 0) && (errno == EINTR)); + /* Free memory. */ + if(cp->ForkPIDs) + { + free(cp->ForkPIDs); + cp->ForkPIDs = 0; + } + /* Close pipe handles. */ for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); - kwsysProcessCleanupDescriptor(&cp->PipeWriteEnds[i]); } } @@ -782,6 +927,148 @@ static void kwsysProcessCleanupDescriptor(int* pfd) } /*--------------------------------------------------------------------------*/ +int kwsysProcessCreate(kwsysProcess* cp, int index, + kwsysProcessCreateInformation* si, int* readEnd) +{ + /* Setup the process's stdin. */ + if(index > 0) + { + si->stdin = *readEnd; + *readEnd = 0; + } + else + { + si->stdin = 0; + } + + /* Setup the process's stdout. */ + { + /* Create the pipe. */ + int p[2]; + if(pipe(p) < 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; + } + } + + /* Create the error reporting pipe. */ + if(pipe(si->error) < 0) + { + return 0; + } + + /* Set close-on-exec flag on the error pipe's write end. */ + if(fcntl(si->error[1], F_SETFD, FD_CLOEXEC) < 0) + { + return 0; + } + + /* Fork off a child process. */ + cp->ForkPIDs[index] = fork(); + if(cp->ForkPIDs[index] < 0) + { + return 0; + } + + if(cp->ForkPIDs[index] == 0) + { + /* Close the read end of the error reporting pipe. */ + close(si->error[0]); + + /* Setup the stdin, stdout, and stderr pipes. */ + if(index > 0) + { + dup2(si->stdin, 0); + } + dup2(si->stdout, 1); + dup2(si->stderr, 2); + + /* 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. */ + fcntl(0, F_SETFD, 0); + fcntl(1, F_SETFD, 0); + fcntl(2, F_SETFD, 0); + fcntl(si->term, F_SETFD, 0); + + /* Restore all default signal handlers. */ + kwsysProcessRestoreDefaultSignalHandlers(); + + /* Change to the working directory specified, if any. */ + if(cp->WorkingDirectory) + { + /* Some platforms specify that the chdir call may be + interrupted. Repeat the call until it finishes. */ + int r; + while(((r = chdir(cp->WorkingDirectory)) < 0) && (errno == EINTR)); + if(r < 0) + { + /* Failure. Report error to parent and terminate. */ + kwsysProcessChildErrorExit(si->error[1]); + } + } + + /* Execute the real process. If successful, this does not return. */ + execvp(cp->Commands[index][0], cp->Commands[index]); + + /* Failure. Report error to parent and terminate. */ + kwsysProcessChildErrorExit(si->error[1]); + } + + /* We are done with the error reporting pipe write end. */ + kwsysProcessCleanupDescriptor(&si->error[1]); + + /* Block until the child's exec call succeeds and closes the error + pipe or writes data to the pipe to report an error. */ + { + int total = 0; + int 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->error[0], cp->ErrorMessage+total, + 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->error[0]); + + if(total > 0) + { + /* The child failed to execute the process. */ + return 0; + } + } + + /* Successfully created this child process. */ + if(index > 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. */ + kwsysProcessCleanupDescriptor(&si->stdout); + + 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, @@ -905,14 +1192,14 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc /* 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(kwsysProcess* cp) +static void kwsysProcessChildErrorExit(int errorPipe) { /* Construct the error message. */ char buffer[KWSYSPE_PIPE_BUFFER_SIZE]; strncpy(buffer, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); /* Report the error to the parent through the special pipe. */ - write(cp->PipeWriteEnds[KWSYSPE_PIPE_ERROR], buffer, strlen(buffer)); + write(errorPipe, buffer, strlen(buffer)); /* Terminate without cleanup. */ _exit(1); diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c index 1253faa..a0f8e08 100644 --- a/Source/kwsys/ProcessWin32.c +++ b/Source/kwsys/ProcessWin32.c @@ -49,25 +49,37 @@ Q190351 and Q150956. #pragma warning (disable: 4706) #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 on Win9x - for the forwarding executable to use in reporting problems. */ -#define CMPE_PIPE_COUNT 3 -#define CMPE_PIPE_STDOUT 0 -#define CMPE_PIPE_STDERR 1 -#define CMPE_PIPE_ERROR 2 +/* There are pipes for the process pipeline's stdout and stderr. */ +#define KWSYSPE_PIPE_COUNT 2 +#define KWSYSPE_PIPE_STDOUT 0 +#define KWSYSPE_PIPE_STDERR 1 /* The maximum amount to read from a pipe at a time. */ -#define CMPE_PIPE_BUFFER_SIZE 1024 +#define KWSYSPE_PIPE_BUFFER_SIZE 1024 #define kwsysEncodedWriteArrayProcessFwd9x kwsys(EncodedWriteArrayProcessFwd9x) typedef LARGE_INTEGER kwsysProcessTime; +typedef struct kwsysProcessCreateInformation_s +{ + /* Windows child startup control data. */ + STARTUPINFO StartupInfo; + + /* Special error reporting pipe for Win9x forwarding executable. */ + HANDLE ErrorPipeRead; + HANDLE ErrorPipeWrite; +} kwsysProcessCreateInformation; + /*--------------------------------------------------------------------------*/ typedef struct kwsysProcessPipeData_s kwsysProcessPipeData; static DWORD WINAPI kwsysProcessPipeThread(LPVOID ptd); static void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, kwsysProcessPipeData* td); +static int kwsysProcessInitialize(kwsysProcess* cp); +static int kwsysProcessCreate(kwsysProcess* cp, int index, + kwsysProcessCreateInformation* si, + PHANDLE readEnd); +static void kwsysProcessDestroy(kwsysProcess* cp, int event); static void kwsysProcessCleanupHandle(PHANDLE h); static void kwsysProcessCleanup(kwsysProcess* cp, int error); static void kwsysProcessCleanErrorMessage(kwsysProcess* cp); @@ -111,7 +123,7 @@ struct kwsysProcessPipeData_s /* ------------- Data managed per call to Execute ------------- */ /* Buffer for data read in this pipe's thread. */ - char DataBuffer[CMPE_PIPE_BUFFER_SIZE]; + char DataBuffer[KWSYSPE_PIPE_BUFFER_SIZE]; /* The length of the data stored in the buffer. */ DWORD DataLength; @@ -132,11 +144,15 @@ struct kwsysProcess_s { /* ------------- Data managed per instance of kwsysProcess ------------- */ - /* The status of the process. */ + /* The status of the process structure. */ int State; - /* The command line to execute. */ - char* Command; + /* The command lines to execute. */ + char** Commands; + int NumberOfCommands; + + /* The exit code of each command. */ + DWORD* CommandExitCodes; /* The working directory for the child process. */ char* WorkingDirectory; @@ -147,6 +163,9 @@ struct kwsysProcess_s /* On Win9x platforms, the path to the forwarding executable. */ char* Win9x; + /* On Win9x platforms, the resume event for the forwarding executable. */ + HANDLE Win9xResumeEvent; + /* On Win9x platforms, the kill event for the forwarding executable. */ HANDLE Win9xKillEvent; @@ -156,15 +175,11 @@ struct kwsysProcess_s /* Semaphore used by threads to signal data ready. */ HANDLE Full; - /* The number of pipes needed to implement the child's execution. - This is 3 on Win9x and 2 otherwise. */ - int PipeCount; - /* Whether we are currently deleting this kwsysProcess instance. */ int Deleting; /* Data specific to each pipe and its thread. */ - kwsysProcessPipeData Pipe[CMPE_PIPE_COUNT]; + kwsysProcessPipeData Pipe[KWSYSPE_PIPE_COUNT]; /* ------------- Data managed per call to Execute ------------- */ @@ -206,14 +221,14 @@ struct kwsysProcess_s int PipesLeft; /* Buffer for error messages (possibly from Win9x child). */ - char ErrorMessage[CMPE_PIPE_BUFFER_SIZE+1]; - int ErrorMessageLength; - - /* The actual command line that will be used to create the process. */ - char* RealCommand; + char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1]; /* Windows process information data. */ - PROCESS_INFORMATION ProcessInformation; + PROCESS_INFORMATION* ProcessInformation; + + /* Data and process termination events for which to wait. */ + PHANDLE ProcessEvents; + int ProcessEventsLength; }; /*--------------------------------------------------------------------------*/ @@ -299,9 +314,8 @@ kwsysProcess* kwsysProcess_New() } } - /* We need the extra error pipe on Win9x. */ + /* Save the path to the forwarding executable. */ cp->Win9x = win9x; - cp->PipeCount = cp->Win9x? 3:2; /* Initially no thread owns the mutex. Initialize semaphore to 1. */ if(!(cp->SharedIndexMutex = CreateSemaphore(0, 1, 1, 0))) @@ -319,12 +333,21 @@ kwsysProcess* kwsysProcess_New() if(cp->Win9x) { - /* Create an event to tell the forwarding executable to kill the - child. */ SECURITY_ATTRIBUTES sa; ZeroMemory(&sa, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; + + /* Create an event to tell the forwarding executable to resume the + child. */ + if(!(cp->Win9xResumeEvent = CreateEvent(&sa, TRUE, 0, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* Create an event to tell the forwarding executable to kill the + child. */ if(!(cp->Win9xKillEvent = CreateEvent(&sa, TRUE, 0, 0))) { kwsysProcess_Delete(cp); @@ -333,7 +356,7 @@ kwsysProcess* kwsysProcess_New() } /* Create the thread to read each pipe. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { DWORD dummy=0; @@ -393,7 +416,7 @@ void kwsysProcess_Delete(kwsysProcess* cp) cp->Deleting = 1; /* Terminate each of the threads. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { if(cp->Pipe[i].Thread) { @@ -417,15 +440,20 @@ void kwsysProcess_Delete(kwsysProcess* cp) kwsysProcessCleanupHandle(&cp->SharedIndexMutex); kwsysProcessCleanupHandle(&cp->Full); - /* Close the Win9x kill event handle. */ + /* Close the Win9x resume and kill event handles. */ if(cp->Win9x) { + kwsysProcessCleanupHandle(&cp->Win9xResumeEvent); kwsysProcessCleanupHandle(&cp->Win9xKillEvent); } /* Free memory. */ kwsysProcess_SetCommand(cp, 0); kwsysProcess_SetWorkingDirectory(cp, 0); + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } if(cp->Win9x) { _unlink(cp->Win9x); @@ -435,178 +463,231 @@ void kwsysProcess_Delete(kwsysProcess* cp) } /*--------------------------------------------------------------------------*/ -void kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) +int kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) { - if(cp->Command) + int i; + for(i=0; i < cp->NumberOfCommands; ++i) + { + free(cp->Commands[i]); + } + cp->NumberOfCommands = 0; + if(cp->Commands) { - free(cp->Command); - cp->Command = 0; + free(cp->Commands); + cp->Commands = 0; } if(command) { - /* We need to construct a single string representing the 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(!command) + { + return 0; + } + + /* Allocate a new array for command pointers. */ + newNumberOfCommands = cp->NumberOfCommands + 1; + if(!(newCommands = (char**)malloc(sizeof(char*) * 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]; + } + } + + /* We need to construct a single string representing the command and its arguments. We will surround each argument containing spaces with double-quotes. Inside a double-quoted argument, we need to escape double-quotes and all backslashes before them. We also need to escape backslashes at the end of an argument because they come before the closing double-quote for the argument. */ - char* cmd; - char const* const* arg; - int length = 0; - /* First determine the length of the final string. */ - for(arg = command; *arg; ++arg) - { - /* Keep track of how many backslashes have been encountered in a - row in this argument. */ - int backslashes = 0; - int spaces = 0; - const char* c; + { + char* cmd; + char const* const* arg; + int length = 0; + /* First determine the length of the final string. */ + for(arg = command; *arg; ++arg) + { + /* Keep track of how many backslashes have been encountered in a + row in this argument. */ + int backslashes = 0; + int spaces = 0; + const char* c; - /* Scan the string for spaces. If there are no spaces, we can + /* Scan the string for spaces. If there are no spaces, we can pass the argument verbatim. */ - for(c=*arg; *c; ++c) + for(c=*arg; *c; ++c) + { + if(*c == ' ' || *c == '\t') { - if(*c == ' ' || *c == '\t') - { - spaces = 1; - break; - } + spaces = 1; + break; } + } - /* Add the length of the argument, plus 1 for the space + /* Add the length of the argument, plus 1 for the space separating the arguments. */ - length += (int)strlen(*arg) + 1; + length += (int)strlen(*arg) + 1; - if(spaces) - { - /* Add 2 for double quotes since spaces are present. */ - length += 2; + if(spaces) + { + /* Add 2 for double quotes since spaces are present. */ + length += 2; /* Scan the string to find characters that need escaping. */ - for(c=*arg; *c; ++c) + for(c=*arg; *c; ++c) + { + if(*c == '\\') { - if(*c == '\\') - { - /* Found a backslash. It may need to be escaped later. */ - ++backslashes; - } - else if(*c == '"') - { - /* Found a double-quote. We need to escape it and all - immediately preceding backslashes. */ - length += backslashes + 1; - backslashes = 0; - } - else - { - /* Found another character. This eliminates the possibility - that any immediately preceding backslashes will be - escaped. */ - backslashes = 0; - } + /* Found a backslash. It may need to be escaped later. */ + ++backslashes; + } + else if(*c == '"') + { + /* Found a double-quote. We need to escape it and all + immediately preceding backslashes. */ + length += backslashes + 1; + backslashes = 0; + } + else + { + /* Found another character. This eliminates the possibility + that any immediately preceding backslashes will be + escaped. */ + backslashes = 0; } - - /* We need to escape all ending backslashes. */ - length += backslashes; } + + /* We need to escape all ending backslashes. */ + length += backslashes; } + } - /* Allocate enough space for the command. We do not need an extra + /* Allocate enough space for the command. We do not need an extra byte for the terminating null because we allocated a space for the first argument that we will not use. */ - cp->Command = (char*)malloc(length); + newCommands[cp->NumberOfCommands] = (char*)malloc(length); + if(!newCommands[cp->NumberOfCommands]) + { + /* Out of memory. */ + free(newCommands); + return 0; + } - /* Construct the command line in the allocated buffer. */ - cmd = cp->Command; - for(arg = command; *arg; ++arg) - { - /* Keep track of how many backslashes have been encountered in a - row in an argument. */ - int backslashes = 0; - int spaces = 0; - const char* c; + /* Construct the command line in the allocated buffer. */ + cmd = newCommands[cp->NumberOfCommands]; + for(arg = command; *arg; ++arg) + { + /* Keep track of how many backslashes have been encountered in a + row in an argument. */ + int backslashes = 0; + int spaces = 0; + const char* c; - /* Scan the string for spaces. If there are no spaces, we can + /* Scan the string for spaces. If there are no spaces, we can pass the argument verbatim. */ - for(c=*arg; *c; ++c) - { - if(*c == ' ' || *c == '\t') - { - spaces = 1; - break; - } - } - - /* Add the separating space if this is not the first argument. */ - if(arg != command) + for(c=*arg; *c; ++c) + { + if(*c == ' ' || *c == '\t') { - *cmd++ = ' '; + spaces = 1; + break; } + } - if(spaces) - { - /* Add the opening double-quote for this argument. */ - *cmd++ = '"'; + /* Add the separating space if this is not the first argument. */ + if(arg != command) + { + *cmd++ = ' '; + } + + if(spaces) + { + /* Add the opening double-quote for this argument. */ + *cmd++ = '"'; /* Add the characters of the argument, possibly escaping them. */ - for(c=*arg; *c; ++c) + for(c=*arg; *c; ++c) + { + if(*c == '\\') + { + /* Found a backslash. It may need to be escaped later. */ + ++backslashes; + *cmd++ = '\\'; + } + else if(*c == '"') { - if(*c == '\\') + /* Add enough backslashes to escape any that preceded the + double-quote. */ + while(backslashes > 0) { - /* Found a backslash. It may need to be escaped later. */ - ++backslashes; + --backslashes; *cmd++ = '\\'; } - else if(*c == '"') - { - /* Add enough backslashes to escape any that preceded the - double-quote. */ - while(backslashes > 0) - { - --backslashes; - *cmd++ = '\\'; - } - /* Add the backslash to escape the double-quote. */ - *cmd++ = '\\'; + /* Add the backslash to escape the double-quote. */ + *cmd++ = '\\'; - /* Add the double-quote itself. */ - *cmd++ = '"'; - } - else - { - /* We encountered a normal character. This eliminates any - escaping needed for preceding backslashes. Add the - character. */ - backslashes = 0; - *cmd++ = *c; - } + /* Add the double-quote itself. */ + *cmd++ = '"'; } - - /* Add enough backslashes to escape any trailing ones. */ - while(backslashes > 0) + else { - --backslashes; - *cmd++ = '\\'; + /* We encountered a normal character. This eliminates any + escaping needed for preceding backslashes. Add the + character. */ + backslashes = 0; + *cmd++ = *c; } - - /* Add the closing double-quote for this argument. */ - *cmd++ = '"'; } - else + + /* Add enough backslashes to escape any trailing ones. */ + while(backslashes > 0) { - /* No spaces. Add the argument verbatim. */ - for(c=*arg; *c; ++c) - { - *cmd++ = *c; - } + --backslashes; + *cmd++ = '\\'; + } + + /* Add the closing double-quote for this argument. */ + *cmd++ = '"'; + } + else + { + /* No spaces. Add the argument verbatim. */ + for(c=*arg; *c; ++c) + { + *cmd++ = *c; } } - - /* Add the terminating null character to the command line. */ - *cmd = 0; } + + /* Add the terminating null character to the command line. */ + *cmd = 0; + } + + /* Save the new array of commands. */ + free(cp->Commands); + cp->Commands = newCommands; + cp->NumberOfCommands = newNumberOfCommands; + return 1; } /*--------------------------------------------------------------------------*/ @@ -698,122 +779,141 @@ void kwsysProcess_Execute(kwsysProcess* cp) { int i; - /* Windows child startup control data. */ - STARTUPINFO si; - DWORD dwCreationFlags=0; + /* Child startup control data. */ + kwsysProcessCreateInformation si; /* Do not execute a second time. */ if(cp->State == kwsysProcess_State_Executing) { return; } + + /* Initialize the control structure for a new process. */ + if(!kwsysProcessInitialize(cp)) + { + strcpy(cp->ErrorMessage, "Out of memory"); + cp->State = kwsysProcess_State_Error; + return; + } - /* Initialize startup info data. */ - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - /* Reset internal status flags. */ - cp->TimeoutExpired = 0; - cp->Terminated = 0; - cp->Killed = 0; - cp->ExitException = kwsysProcess_Exception_None; - cp->ExitCode = 1; - cp->ExitValue = 1; - - /* Reset error data. */ - cp->ErrorMessage[0] = 0; - cp->ErrorMessageLength = 0; - - /* Reset the Win9x kill event. */ + /* Reset the Win9x resume and kill events. */ if(cp->Win9x) { + if(!ResetEvent(cp->Win9xResumeEvent)) + { + kwsysProcessCleanup(cp, 1); + return; + } if(!ResetEvent(cp->Win9xKillEvent)) { kwsysProcessCleanup(cp, 1); return; } } + + /* Initialize startup info data. */ + ZeroMemory(&si, sizeof(si)); + si.StartupInfo.cb = sizeof(si.StartupInfo); + + /* Decide whether a child window should be shown. */ + si.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; + si.StartupInfo.wShowWindow = + (unsigned short)(cp->HideWindow?SW_HIDE:SW_SHOWDEFAULT); - /* Create a pipe for each child output. */ - for(i=0; i < cp->PipeCount; ++i) + /* Connect the child's output pipes to the threads. */ + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + + /* Create stderr pipe to be shared by all processes in the pipeline. + Neither end is directly inherited. */ + if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read, + &cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0)) { - HANDLE writeEnd; - - /* The pipe is not closed. */ - cp->Pipe[i].Closed = 0; + kwsysProcessCleanup(cp, 1); + return; + } - /* Create the pipe. Neither end is directly inherited. */ - if(!CreatePipe(&cp->Pipe[i].Read, &writeEnd, 0, 0)) + /* Create an inherited duplicate of the write end, but do not + close the non-inherited version. We need to keep it open + to use in waking up the pipe threads. */ + if(!DuplicateHandle(GetCurrentProcess(), cp->Pipe[KWSYSPE_PIPE_STDERR].Write, + GetCurrentProcess(), &si.StartupInfo.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + kwsysProcessCleanup(cp, 1); + kwsysProcessCleanupHandle(&si.StartupInfo.hStdError); + return; + } + + /* Create the pipeline of processes. */ + { + HANDLE readEnd = 0; + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(kwsysProcessCreate(cp, i, &si, &readEnd)) { - kwsysProcessCleanup(cp, 1); - return; + cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess; } - - /* Create an inherited duplicate of the write end. This also closes - the non-inherited version. */ - if(!DuplicateHandle(GetCurrentProcess(), writeEnd, - GetCurrentProcess(), &cp->Pipe[i].Write, - 0, TRUE, (DUPLICATE_CLOSE_SOURCE | - DUPLICATE_SAME_ACCESS))) + else { kwsysProcessCleanup(cp, 1); + + /* Release resources that may have been allocated for this + process before an error occurred. */ + kwsysProcessCleanupHandle(&readEnd); + if(i > 0) + { + kwsysProcessCleanupHandle(&si.StartupInfo.hStdInput); + } + kwsysProcessCleanupHandle(&si.StartupInfo.hStdOutput); + kwsysProcessCleanupHandle(&si.StartupInfo.hStdError); + kwsysProcessCleanupHandle(&si.ErrorPipeRead); + kwsysProcessCleanupHandle(&si.ErrorPipeWrite); return; } } + + /* Save a handle to the output pipe for the last process. */ + cp->Pipe[KWSYSPE_PIPE_STDOUT].Read = readEnd; + } + + /* Close the inherited handles to the stderr pipe shared by all + processes in the pipeline. */ + kwsysProcessCleanupHandle(&si.StartupInfo.hStdError); + + /* The timeout period starts now. */ + cp->StartTime = kwsysProcessTimeGetCurrent(); + cp->TimeoutTime = kwsysProcessTimeFromDouble(-1); - /* Construct the real command line. */ + /* All processes in the pipeline have been started in suspended + mode. Resume them all now. */ if(cp->Win9x) { - /* Windows 9x */ - - /* The forwarding executable is given a handle to the error pipe - and a handle to the kill event. */ - cp->RealCommand = malloc(strlen(cp->Win9x)+strlen(cp->Command)+100); - sprintf(cp->RealCommand, "%s %p %p %d %s", cp->Win9x, - cp->Pipe[CMPE_PIPE_ERROR].Write, cp->Win9xKillEvent, - cp->HideWindow, cp->Command); + SetEvent(cp->Win9xResumeEvent); } else { - /* Not Windows 9x */ - cp->RealCommand = strdup(cp->Command); - } - - /* Connect the child's output pipes to the threads. */ - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - si.hStdOutput = cp->Pipe[CMPE_PIPE_STDOUT].Write; - si.hStdError = cp->Pipe[CMPE_PIPE_STDERR].Write; - - /* Decide whether a child window should be shown. */ - si.dwFlags |= STARTF_USESHOWWINDOW; - si.wShowWindow = (unsigned short)(cp->HideWindow?SW_HIDE:SW_SHOWDEFAULT); - - /* The timeout period starts now. */ - cp->StartTime = kwsysProcessTimeGetCurrent(); - cp->TimeoutTime = kwsysProcessTimeFromDouble(-1); - - /* CREATE THE CHILD PROCESS */ - if(!CreateProcess(0, cp->RealCommand, 0, 0, TRUE, dwCreationFlags, 0, - cp->WorkingDirectory, &si, &cp->ProcessInformation)) - { - kwsysProcessCleanup(cp, 1); - return; + for(i=0; i < cp->NumberOfCommands; ++i) + { + ResumeThread(cp->ProcessInformation[i].hThread); + } } - - /* ---- It is no longer safe to call kwsysProcessCleanup. ----- */ + + /* ---- It is no longer safe to call kwsysProcessCleanup. ----- */ /* Tell the pipe threads that a process has started. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { ReleaseSemaphore(cp->Pipe[i].Ready, 1, 0); } - - /* We don't care about the child's main thread. */ - kwsysProcessCleanupHandle(&cp->ProcessInformation.hThread); + + /* We don't care about the children's main threads. */ + for(i=0; i < cp->NumberOfCommands; ++i) + { + kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); + } /* No pipe has reported data. */ - cp->CurrentIndex = CMPE_PIPE_COUNT; - cp->PipesLeft = cp->PipeCount; + cp->CurrentIndex = KWSYSPE_PIPE_COUNT; + cp->PipesLeft = KWSYSPE_PIPE_COUNT; /* The process has now started. */ cp->State = kwsysProcess_State_Executing; @@ -833,7 +933,6 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng int expired = 0; int pipeId = 0; DWORD w; - HANDLE events[2]; /* Make sure we are executing a process. */ if(cp->State != kwsysProcess_State_Executing || cp->Killed || @@ -842,11 +941,6 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng return 0; } - /* We will wait for data until the process termiantes or data are - available. */ - events[0] = cp->Full; - events[1] = cp->ProcessInformation.hProcess; - /* Record the time at which user timeout period starts. */ userStartTime = kwsysProcessTimeGetCurrent(); @@ -859,10 +953,10 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng { /* If we previously got data from a thread, let it know we are done with the data. */ - if(cp->CurrentIndex < CMPE_PIPE_COUNT) + if(cp->CurrentIndex < KWSYSPE_PIPE_COUNT) { ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); - cp->CurrentIndex = CMPE_PIPE_COUNT; + cp->CurrentIndex = KWSYSPE_PIPE_COUNT; } /* Setup a timeout if required. */ @@ -881,9 +975,9 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng timeout = kwsysProcessTimeToDWORD(timeoutLength); } - /* Wait for a pipe's thread to signal or the application to - terminate. */ - w = WaitForMultipleObjects(cp->Terminated?1:2, events, 0, timeout); + /* Wait for a pipe's thread to signal or a process to terminate. */ + w = WaitForMultipleObjects(cp->ProcessEventsLength, cp->ProcessEvents, + 0, timeout); if(w == WAIT_TIMEOUT) { /* Timeout has expired. */ @@ -903,26 +997,6 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng /* The pipe closed. */ --cp->PipesLeft; } - else if(cp->CurrentIndex == CMPE_PIPE_ERROR) - { - /* This is data on the special error reporting pipe for Win9x. - Append it to the error buffer. */ - int length = cp->Pipe[cp->CurrentIndex].DataLength; - if(length > CMPE_PIPE_BUFFER_SIZE - cp->ErrorMessageLength) - { - length = CMPE_PIPE_BUFFER_SIZE - cp->ErrorMessageLength; - } - if(length > 0) - { - memcpy(cp->ErrorMessage+cp->ErrorMessageLength, - cp->Pipe[cp->CurrentIndex].DataBuffer, length); - cp->ErrorMessageLength += length; - } - else - { - cp->ErrorMessage[cp->ErrorMessageLength] = 0; - } - } else if(pipes & (1 << cp->CurrentIndex)) { /* Caller wants this data. Report it. */ @@ -938,17 +1012,8 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng } else { - int i; - - /* Process has terminated. */ - cp->Terminated = 1; - - /* Close our copies of the pipe write handles so the pipe - threads can detect end-of-data. */ - for(i=0; i < cp->PipeCount; ++i) - { - kwsysProcessCleanupHandle(&cp->Pipe[i].Write); - } + /* A process has terminated. */ + kwsysProcessDestroy(cp, w-WAIT_OBJECT_0); } } @@ -991,7 +1056,7 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* leng } else { - /* The process has terminated and no more data are available. */ + /* The children have terminated and no more data are available. */ return 0; } } @@ -1020,14 +1085,14 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) /* When the last pipe closes in WaitForData, the loop terminates without releaseing the pipe's thread. Release it now. */ - if(cp->CurrentIndex < CMPE_PIPE_COUNT) + if(cp->CurrentIndex < KWSYSPE_PIPE_COUNT) { ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); - cp->CurrentIndex = CMPE_PIPE_COUNT; + cp->CurrentIndex = KWSYSPE_PIPE_COUNT; } /* Wait for all pipe threads to reset. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { WaitForSingleObject(cp->Pipe[i].Reset, INFINITE); } @@ -1036,35 +1101,21 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) /* Close all the pipes. */ kwsysProcessCleanup(cp, 0); - /* We are done reading all data. Wait for the child to terminate. - This will only block if we killed the child and are waiting for - it to cleanup. */ - WaitForSingleObject(cp->ProcessInformation.hProcess, INFINITE); - /* Determine the outcome. */ if(cp->Killed) { /* We killed the child. */ cp->State = kwsysProcess_State_Killed; } - else if(cp->ErrorMessageLength) - { - /* The Win9x forwarding executing repored data on the special - error pipe. Failed to run the process. */ - cp->State = kwsysProcess_State_Error; - - /* Remove trailing period and newline from message, if any. */ - kwsysProcessCleanErrorMessage(cp); - } else if(cp->TimeoutExpired) { /* The timeout expired. */ cp->State = kwsysProcess_State_Expired; } - else if(GetExitCodeProcess(cp->ProcessInformation.hProcess, - &cp->ExitCode)) + else { - /* The child exited. */ + /* The children exited. Report the outcome of the last process. */ + cp->ExitCode = cp->CommandExitCodes[cp->NumberOfCommands-1]; if(cp->ExitCode & 0xC0000000) { /* Child terminated due to exceptional behavior. */ @@ -1111,16 +1162,7 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) cp->ExitValue = cp->ExitCode & 0x000000FF; } } - else - { - /* Error getting the child return code. */ - strcpy(cp->ErrorMessage, "Error getting child return code"); - cp->State = kwsysProcess_State_Error; - } - - /* The child process is terminated. */ - CloseHandle(cp->ProcessInformation.hProcess); - + return 1; } @@ -1138,14 +1180,14 @@ void kwsysProcess_Kill(kwsysProcess* cp) /* If we are killing a process that just reported data, release the pipe's thread. */ - if(cp->CurrentIndex < CMPE_PIPE_COUNT) + if(cp->CurrentIndex < KWSYSPE_PIPE_COUNT) { ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); - cp->CurrentIndex = CMPE_PIPE_COUNT; + cp->CurrentIndex = KWSYSPE_PIPE_COUNT; } /* Wake up all the pipe threads with dummy data. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { DWORD dummy; WriteFile(cp->Pipe[i].Write, "", 1, &dummy, 0); @@ -1162,7 +1204,7 @@ void kwsysProcess_Kill(kwsysProcess* cp) --cp->PipesLeft; } - /* Kill the child. */ + /* Kill the children. */ cp->Killed = 1; if(cp->Win9x) { @@ -1171,8 +1213,17 @@ void kwsysProcess_Kill(kwsysProcess* cp) } else { - /* Not Windows 9x. Just terminate the child. */ - TerminateProcess(cp->ProcessInformation.hProcess, 255); + /* Not Windows 9x. Just terminate the children. */ + for(i=0; i < cp->NumberOfCommands; ++i) + { + TerminateProcess(cp->ProcessInformation[i].hProcess, 255); + } + } + + /* Wait for windows to finish cleaning up the children. */ + for(i=0; i < cp->NumberOfCommands; ++i) + { + WaitForSingleObject(cp->ProcessInformation[i].hProcess, INFINITE); } } @@ -1215,7 +1266,7 @@ void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, kwsysProcessPipeData* td) while((WaitForSingleObject(td->Empty, INFINITE), !td->Closed)) { /* Read data from the pipe. This may block until data are available. */ - if(!ReadFile(td->Read, td->DataBuffer, CMPE_PIPE_BUFFER_SIZE, + if(!ReadFile(td->Read, td->DataBuffer, KWSYSPE_PIPE_BUFFER_SIZE, &td->DataLength, 0)) { if(GetLastError() != ERROR_BROKEN_PIPE) @@ -1237,6 +1288,262 @@ void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, kwsysProcessPipeData* td) } /*--------------------------------------------------------------------------*/ +/* Initialize a process control structure for kwsysProcess_Execute. */ +int kwsysProcessInitialize(kwsysProcess* cp) +{ + /* Reset internal status flags. */ + cp->TimeoutExpired = 0; + cp->Terminated = 0; + cp->Killed = 0; + cp->ExitException = kwsysProcess_Exception_None; + cp->ExitCode = 1; + cp->ExitValue = 1; + + /* Reset error data. */ + cp->ErrorMessage[0] = 0; + + /* Allocate process information for each process. */ + cp->ProcessInformation = + (PROCESS_INFORMATION*)malloc(sizeof(PROCESS_INFORMATION) * + cp->NumberOfCommands); + if(!cp->ProcessInformation) + { + return 0; + } + ZeroMemory(cp->ProcessInformation, + sizeof(PROCESS_INFORMATION) * cp->NumberOfCommands); + if(cp->CommandExitCodes) + { + free(cp->CommandExitCodes); + } + cp->CommandExitCodes = (DWORD*)malloc(sizeof(DWORD)*cp->NumberOfCommands); + if(!cp->CommandExitCodes) + { + return 0; + } + ZeroMemory(cp->CommandExitCodes, sizeof(DWORD)*cp->NumberOfCommands); + + /* Allocate event wait array. The first event is cp->Full, the rest + are the process termination events. */ + cp->ProcessEvents = (PHANDLE)malloc(sizeof(HANDLE)*(cp->NumberOfCommands+1)); + if(!cp->ProcessEvents) + { + return 0; + } + ZeroMemory(cp->ProcessEvents, sizeof(HANDLE) * (cp->NumberOfCommands+1)); + cp->ProcessEvents[0] = cp->Full; + cp->ProcessEventsLength = cp->NumberOfCommands+1; + + return 1; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcessCreate(kwsysProcess* cp, int index, + kwsysProcessCreateInformation* si, + PHANDLE readEnd) +{ + /* Setup the process's stdin. */ + if(*readEnd) + { + /* Create an inherited duplicate of the read end from the output + pipe of the previous process. This also closes the + non-inherited version. */ + if(!DuplicateHandle(GetCurrentProcess(), *readEnd, + GetCurrentProcess(), readEnd, + 0, TRUE, (DUPLICATE_CLOSE_SOURCE | + DUPLICATE_SAME_ACCESS))) + { + return 0; + } + si->StartupInfo.hStdInput = *readEnd; + + /* This function is done with this handle. */ + *readEnd = 0; + } + else + { + si->StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + + /* Setup the process's stdout. */ + { + DWORD maybeClose = DUPLICATE_CLOSE_SOURCE; + HANDLE writeEnd; + + /* Create the output pipe for this process. Neither end is directly + inherited. */ + if(!CreatePipe(readEnd, &writeEnd, 0, 0)) + { + return 0; + } + + /* Create an inherited duplicate of the write end. Close the + non-inherited version unless this is the last process. Save the + non-inherited write end of the last process. */ + if(index == cp->NumberOfCommands-1) + { + cp->Pipe[KWSYSPE_PIPE_STDOUT].Write = writeEnd; + maybeClose = 0; + } + if(!DuplicateHandle(GetCurrentProcess(), writeEnd, + GetCurrentProcess(), &si->StartupInfo.hStdOutput, + 0, TRUE, (maybeClose | DUPLICATE_SAME_ACCESS))) + { + return 0; + } + } + + /* Create the child process. */ + { + BOOL r; + char* realCommand; + if(cp->Win9x) + { + /* Create an error reporting pipe for the forwarding executable. + Neither end is directly inherited. */ + if(!CreatePipe(&si->ErrorPipeRead, &si->ErrorPipeWrite, 0, 0)) + { + return 0; + } + + /* Create an inherited duplicate of the write end. This also closes + the non-inherited version. */ + if(!DuplicateHandle(GetCurrentProcess(), si->ErrorPipeWrite, + GetCurrentProcess(), &si->ErrorPipeWrite, + 0, TRUE, (DUPLICATE_CLOSE_SOURCE | + DUPLICATE_SAME_ACCESS))) + { + return 0; + } + + /* The forwarding executable is given a handle to the error pipe + and resume and kill events. */ + realCommand = malloc(strlen(cp->Win9x)+strlen(cp->Commands[index])+100); + if(!realCommand) + { + return 0; + } + sprintf(realCommand, "%s %p %p %p %d %s", cp->Win9x, + si->ErrorPipeWrite, cp->Win9xResumeEvent, cp->Win9xKillEvent, + cp->HideWindow, cp->Commands[index]); + } + else + { + realCommand = cp->Commands[index]; + } + + /* Create the child in a suspended state so we can wait until all + children have been created before running any one. */ + r = CreateProcess(0, realCommand, 0, 0, TRUE, + cp->Win9x? 0 : CREATE_SUSPENDED, 0, + cp->WorkingDirectory, &si->StartupInfo, + &cp->ProcessInformation[index]); + + if(cp->Win9x) + { + /* Free memory. */ + free(realCommand); + + /* Close the error pipe write end so we can detect when the + forwarding executable closes it. */ + kwsysProcessCleanupHandle(&si->ErrorPipeWrite); + if(r) + { + /* Wait for the forwarding executable to report an error or + close the error pipe to report success. */ + DWORD total = 0; + DWORD n = 1; + while(total < KWSYSPE_PIPE_BUFFER_SIZE && n > 0) + { + if(ReadFile(si->ErrorPipeRead, cp->ErrorMessage+total, + KWSYSPE_PIPE_BUFFER_SIZE-total, &n, 0)) + { + total += n; + } + else + { + n = 0; + } + } + if(total > 0 || GetLastError() != ERROR_BROKEN_PIPE) + { + /* The forwarding executable could not run the process, or + there was an error reading from its error pipe. Preserve + the last error while cleaning up the forwarding executable + so the cleanup our caller does reports the proper error. */ + DWORD error = GetLastError(); + kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hThread); + kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); + SetLastError(error); + return 0; + } + } + kwsysProcessCleanupHandle(&si->ErrorPipeRead); + } + + if(!r) + { + return 0; + } + } + + /* Successfully created this child process. */ + if(index > 0) + { + /* Close our handle to the input pipe for the current process. */ + kwsysProcessCleanupHandle(&si->StartupInfo.hStdInput); + } + + /* The parent process does not need the inhertied pipe write end. */ + kwsysProcessCleanupHandle(&si->StartupInfo.hStdOutput); + + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcessDestroy(kwsysProcess* cp, int event) +{ + int i; + int index; + + /* Find the process index for the termination event. */ + for(index=0; index < cp->NumberOfCommands; ++index) + { + if(cp->ProcessInformation[index].hProcess == cp->ProcessEvents[event]) + { + break; + } + } + + /* Check the exit code of the process. */ + GetExitCodeProcess(cp->ProcessInformation[index].hProcess, + &cp->CommandExitCodes[index]); + + /* Close the process handle for the terminated process. */ + kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); + + /* Remove the process from the available events. */ + cp->ProcessEventsLength -= 1; + for(i=event; i < cp->ProcessEventsLength; ++i) + { + cp->ProcessEvents[i] = cp->ProcessEvents[i+1]; + } + + /* Check if all processes have terminated. */ + if(cp->ProcessEventsLength == 1) + { + cp->Terminated = 1; + + /* Close our copies of the pipe write handles so the pipe threads + can detect end-of-data. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + kwsysProcessCleanupHandle(&cp->Pipe[i].Write); + } + } +} + +/*--------------------------------------------------------------------------*/ /* Close the given handle if it is open. Reset its value to 0. */ void kwsysProcessCleanupHandle(PHANDLE h) @@ -1258,37 +1565,71 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error) /* If this is an error case, report the error. */ if(error) { - /* Format the error message. */ - DWORD original = GetLastError(); - DWORD length = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, 0, original, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - cp->ErrorMessage, CMPE_PIPE_BUFFER_SIZE, 0); - if(length < 1) + /* Construct an error message if one has not been provided already. */ + if(cp->ErrorMessage[0] == 0) { - /* FormatMessage failed. Use a default message. */ - _snprintf(cp->ErrorMessage, CMPE_PIPE_BUFFER_SIZE, - "Process execution failed with error 0x%X. " - "FormatMessage failed with error 0x%X", - original, GetLastError()); + /* Format the error message. */ + DWORD original = GetLastError(); + DWORD length = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, 0, original, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, 0); + if(length < 1) + { + /* FormatMessage failed. Use a default message. */ + _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, + "Process execution failed with error 0x%X. " + "FormatMessage failed with error 0x%X", + original, GetLastError()); + } } + /* Remove trailing period and newline, if any. */ + kwsysProcessCleanErrorMessage(cp); + /* Set the error state. */ cp->State = kwsysProcess_State_Error; - /* Remove trailing period and newline, if any. */ - kwsysProcessCleanErrorMessage(cp); + /* Cleanup any processes already started in a suspended state. */ + if(cp->ProcessInformation) + { + if(cp->Win9x) + { + SetEvent(cp->Win9xKillEvent); + } + else + { + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(cp->ProcessInformation[i].hProcess) + { + TerminateProcess(cp->ProcessInformation[i].hProcess, 255); + WaitForSingleObject(cp->ProcessInformation[i].hProcess, INFINITE); + } + } + } + for(i=0; i < cp->NumberOfCommands; ++i) + { + kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); + kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); + } + } } - + /* Free memory. */ - if(cp->RealCommand) + if(cp->ProcessInformation) + { + free(cp->ProcessInformation); + cp->ProcessInformation = 0; + } + if(cp->ProcessEvents) { - free(cp->RealCommand); - cp->RealCommand = 0; + free(cp->ProcessEvents); + cp->ProcessEvents = 0; } /* Close each pipe. */ - for(i=0; i < cp->PipeCount; ++i) + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) { kwsysProcessCleanupHandle(&cp->Pipe[i].Write); kwsysProcessCleanupHandle(&cp->Pipe[i].Read); -- cgit v0.12