diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt index 83ebe227d..68bd8c03d 100644 --- a/Source/kwsys/CMakeLists.txt +++ b/Source/kwsys/CMakeLists.txt @@ -29,9 +29,19 @@ SET(KWSYS_NO_ANSI_STRING_STREAM ${CMAKE_NO_ANSI_STRING_STREAM}) SET(KWSYS_NO_ANSI_FOR_SCOPE ${CMAKE_NO_ANSI_FOR_SCOPE}) SET(CLASSES Directory RegularExpression SystemTools) -SET(HEADERS Configure) +SET(H Process) +SET(HXX Configure) + +IF(NOT UNIX) + ADD_EXECUTABLE(${KWSYS_NAMESPACE}ProcessFwd9x ProcessFwd9x.c) + ADD_EXECUTABLE(${KWSYS_NAMESPACE}EncodeExecutable EncodeExecutable.c) + SET(SRCS ProcessWin32.c ${CMAKE_CURRENT_BINARY_DIR}/${KWSYS_NAMESPACE}ProcessFwd9xEnc.c) + SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_BINARY_DIR}/${KWSYS_NAMESPACE}ProcessFwd9xEnc.c + PROPERTIES GENERATED 1) +ELSE(NOT UNIX) + SET(SRCS ProcessUNIX.c) +ENDIF(NOT UNIX) -SET(SRCS) SET(KWSYS_INCLUDES) FOREACH(c ${CLASSES}) SET(SRCS ${SRCS} ${c}.cxx) @@ -42,7 +52,15 @@ FOREACH(c ${CLASSES}) ${PROJECT_BINARY_DIR}/../${KWSYS_NAMESPACE}/${c}.hxx) ENDFOREACH(c) -FOREACH(h ${HEADERS}) +FOREACH(h ${H}) + CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/${h}.h.in + ${PROJECT_BINARY_DIR}/../${KWSYS_NAMESPACE}/${h}.h + @ONLY IMMEDIATE) + SET(KWSYS_INCLUDES ${KWSYS_INCLUDES} + ${PROJECT_BINARY_DIR}/../${KWSYS_NAMESPACE}/${h}.h) +ENDFOREACH(h) + +FOREACH(h ${HXX}) CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/${h}.hxx.in ${PROJECT_BINARY_DIR}/../${KWSYS_NAMESPACE}/${h}.hxx @ONLY IMMEDIATE) @@ -93,3 +111,29 @@ IF(KWSYS_DEFAULTS) ADD_EXECUTABLE(test1 test1.cxx) TARGET_LINK_LIBRARIES(test1 ${KWSYS_NAMESPACE}) ENDIF(KWSYS_DEFAULTS) + +IF(NOT UNIX) + SET(BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}) + IF(EXECUTABLE_OUTPUT_PATH) + SET(BIN_DIR ${EXECUTABLE_OUTPUT_PATH}) + ENDIF(EXECUTABLE_OUTPUT_PATH) + + SET(MAKE_SYSTEM) + SET(CFG_INTDIR "/${CMAKE_CFG_INTDIR}") + IF(CMAKE_BUILD_TOOL MATCHES "make") + SET(CFG_INTDIR "") + ENDIF(CMAKE_BUILD_TOOL MATCHES "make") + + SET(CMD ${BIN_DIR}${CFG_INTDIR}/${KWSYS_NAMESPACE}EncodeExecutable.exe) + SET(FWD ${BIN_DIR}${CFG_INTDIR}/${KWSYS_NAMESPACE}ProcessFwd9x.exe) + ADD_CUSTOM_COMMAND( + TARGET ${KWSYS_NAMESPACE} + SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/ProcessFwd9x.c + COMMAND ${CMD} + ARGS ${FWD} ${CMAKE_CURRENT_BINARY_DIR}/${KWSYS_NAMESPACE}ProcessFwd9xEnc.c + ${KWSYS_NAMESPACE} ProcessFwd9x + OUTPUTS ${CMAKE_CURRENT_BINARY_DIR}/${KWSYS_NAMESPACE}ProcessFwd9xEnc.c + DEPENDS ${CMD} ${FWD}) + ADD_DEPENDENCIES(${KWSYS_NAMESPACE} ${KWSYS_NAMESPACE}ProcessFwd9x + ${KWSYS_NAMESPACE}EncodeExecutable) +ENDIF(NOT UNIX) diff --git a/Source/kwsys/EncodeExecutable.c b/Source/kwsys/EncodeExecutable.c new file mode 100644 index 000000000..ef76eb266 --- /dev/null +++ b/Source/kwsys/EncodeExecutable.c @@ -0,0 +1,99 @@ +/*========================================================================= + +Program: KWSys - Kitware System Library +Module: $RCSfile$ +Language: C++ +Date: $Date$ +Version: $Revision$ + +Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +See http://www.cmake.org/HTML/Copyright.html for details. + +This software is distributed WITHOUT ANY WARRANTY; without even +the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include + +int main(int argc, char* argv[]) +{ + FILE* ifp; + FILE* ofp; + int i; + int n; + int count = 0; + unsigned char buffer[1024]; + + /* Check arguments. */ + if(argc != 5) + { + fprintf(stderr, "Usage: %s \n", + argv[0]); + return 1; + } + + /* Open the input file. */ + ifp = fopen(argv[1], "rb"); + if(!ifp) + { + fprintf(stderr, "Cannot open input file: \"%s\"\n", argv[1]); + return 2; + } + ofp = fopen(argv[2], "w"); + if(!ofp) + { + fprintf(stderr, "Cannot open output file: \"%s\"\n", argv[2]); + return 2; + } + + /* Prepend header comment. */ + fprintf(ofp, "/*\n * DO NOT EDIT\n * This file is generated by:\n"); + fprintf(ofp, " * %s\n */\n\n", argv[0]); + fprintf(ofp, "#include \n\n"); + + /* Split file up in 1024-byte chunks. */ + while((n = fread(buffer, 1, 1024, ifp)) > 0) + { + fprintf(ofp, "static unsigned char kwsysEncodedArray%s_%d[%d] = {\n", + argv[4], count++, n); + for(i=0; i < n-1; ++i) + { + fprintf(ofp, "0x%02X", buffer[i]); + if(i%10 == 9) + { + fprintf(ofp, ",\n"); + } + else + { + fprintf(ofp, ", "); + } + } + fprintf(ofp, "0x%02X};\n\n", buffer[n-1]); + } + fclose(ifp); + + /* Provide a function to write the data to a file. */ + fprintf(ofp, "extern int %sEncodedWriteArray%s(const char* fname)\n", + argv[3], argv[4]); + fprintf(ofp, "{\n"); + fprintf(ofp, " FILE* ofp = fopen(fname, \"wb\");\n"); + fprintf(ofp, " if(!ofp) { return 0; }\n"); + for(i=0; i < count; ++i) + { + fprintf(ofp, " if(fwrite(kwsysEncodedArray%s_%d, 1,\n" + " sizeof(kwsysEncodedArray%s_%d), ofp) !=\n" + " sizeof(kwsysEncodedArray%s_%d))\n", + argv[4], i, argv[4], i, argv[4], i); + fprintf(ofp, " {\n"); + fprintf(ofp, " fclose(ofp);\n"); + fprintf(ofp, " _unlink(fname);\n"); + fprintf(ofp, " return 0;\n"); + fprintf(ofp, " }\n"); + } + fprintf(ofp, " fclose(ofp);\n"); + fprintf(ofp, " return 1;\n"); + fprintf(ofp, "}\n"); + fclose(ofp); + return 0; +} diff --git a/Source/kwsys/Process.h.in b/Source/kwsys/Process.h.in new file mode 100644 index 000000000..11c63ffb4 --- /dev/null +++ b/Source/kwsys/Process.h.in @@ -0,0 +1,172 @@ +/*========================================================================= + + Program: KWSys - Kitware System Library + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef @KWSYS_NAMESPACE@_Process_h +#define @KWSYS_NAMESPACE@_Process_h + +#define kwsys(x) @KWSYS_NAMESPACE@##x +#define kwsysProcess_STDOUT kwsys(Process_STDOUT) +#define kwsysProcess_STDERR kwsys(Process_STDERR) +#define kwsysProcess_Timeout kwsys(Process_Timeout) +#define kwsysProcess_Starting kwsys(Process_Starting) +#define kwsysProcess_Executing kwsys(Process_Executing) +#define kwsysProcess_Expired kwsys(Process_Expired) +#define kwsysProcess_Exited kwsys(Process_Exited) +#define kwsysProcess_Killed kwsys(Process_Killed) +#define kwsysProcess_Signalled kwsys(Process_Signalled) +#define kwsysProcess_Error kwsys(Process_Error) +#define kwsysProcess_State kwsys(Process_State) +#define kwsysProcess_Pipes_e kwsys(Process_Pipes_e) +#define kwsysProcess_State_e kwsys(Process_State_e) +#define kwsysProcess_s kwsys(Process_s) +#define kwsysProcess kwsys(Process) +#define kwsysProcess_New kwsys(Process_New) +#define kwsysProcess_Delete kwsys(Process_Delete) +#define kwsysProcess_SetCommand kwsys(Process_SetCommand) +#define kwsysProcess_SetTimeout kwsys(Process_SetTimeout) +#define kwsysProcess_GetState kwsys(Process_GetState) +#define kwsysProcess_GetExitCode kwsys(Process_GetExitCode) +#define kwsysProcess_GetErrorString kwsys(Process_GetErrorString) +#define kwsysProcess_Execute kwsys(Process_Execute) +#define kwsysProcess_WaitForData kwsys(Process_WaitForData) +#define kwsysProcess_WaitForExit kwsys(Process_WaitForExit) +#define kwsysProcess_Kill kwsys(Process_Kill) + +#if defined(__cplusplus) +extern "C" +{ +#endif + +typedef enum kwsysProcess_Pipes_e +{ + kwsysProcess_STDOUT=1, + kwsysProcess_STDERR=2, + kwsysProcess_Timeout=255 +} kwsysProcess_Pipes; + +typedef enum kwsysProcess_State_e +{ + kwsysProcess_Starting, /* Between New and Execute; No process run yet */ + kwsysProcess_Executing, /* Process is running */ + kwsysProcess_Expired, /* Process timeout expired and was killed */ + kwsysProcess_Exited, /* Process exited */ + kwsysProcess_Killed, /* Process was killed by Kill */ + kwsysProcess_Signalled, /* Process was terminated by a signal (crash / ctrl-C) */ + kwsysProcess_Error /* Internal error of Process */ +} kwsysProcess_State; + +typedef struct kwsysProcess_s kwsysProcess; + +kwsysProcess* kwsysProcess_New(); + +void kwsysProcess_Delete(kwsysProcess* cp); + +void kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command); + +void kwsysProcess_SetTimeout(kwsysProcess* cp, double timeout); + +/* + * Get the current internal state of the kwsysProcess instance + */ +int kwsysProcess_GetState(kwsysProcess* cp); + +/* + * Get process return code or when signalled, get the signal code + */ +int kwsysProcess_GetExitCode(kwsysProcess* cp); + +/* + * On kwsysProcess_Error get the error message + */ +const char* kwsysProcess_GetErrorString(kwsysProcess* cp); + +void kwsysProcess_Execute(kwsysProcess* cp); + +/* + * Block until data available on requested pipe or one of the timeouts expired, + * or the process exits. If the pipe is not specified, data on that pipe are + * ignored. + * + * pipes - a list of interested pipes - kwsysProcess_STDOUT | kwsysProcess_STDERR + * data - returns pointer to data if read, NULL otherwise + * length - length of the returned data + * userTimeout - timeout for the current kwsysProcess_WaitForData call + * the userTimeout will contain the remaining time + * + * Returns: + * 0 - Process exited or killed or process timeout expired with no data + * available, or no process running. + * PIPE id otherwise: + * kwsysProcess_STDOUT - if stdout is returned + * kwsysProcess_STDERR - if stderr is returned + * kwsysProcess_Timeout - if user timeout expired + */ +int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* length, + double* userTimeout); + +/* + * Block until the process exits or the timeout expires. If no process is + * running, return immediatly. + * + * Returns: + * 0 - When user timeout expires + * 1 - Otherwise + */ +int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout); + +/* + * Kills the process. kwsysProcess_WaitForExit should still be called + * after kwsysProcess_Kill. + */ +void kwsysProcess_Kill(kwsysProcess* cp); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + + +/* If we are building a kwsysProcess .c file, let it use these macros. */ +#if !defined(KWSYS_IN_PROCESS_C) +# undef kwsys +# undef kwsysProcess_STDOUT +# undef kwsysProcess_STDERR +# undef kwsysProcess_Timeout +# undef kwsysProcess_Starting +# undef kwsysProcess_Executing +# undef kwsysProcess_Expired +# undef kwsysProcess_Exited +# undef kwsysProcess_Killed +# undef kwsysProcess_Signalled +# undef kwsysProcess_Error +# undef kwsysProcess_State +# undef kwsysProcess_Pipes_e +# undef kwsysProcess_State_e +# undef kwsysProcess_s +# undef kwsysProcess +# undef kwsysProcess_New +# undef kwsysProcess_Delete +# undef kwsysProcess_SetCommand +# undef kwsysProcess_SetTimeout +# undef kwsysProcess_GetState +# undef kwsysProcess_GetExitCode +# undef kwsysProcess_GetErrorString +# undef kwsysProcess_Execute +# undef kwsysProcess_WaitForData +# undef kwsysProcess_WaitForExit +# undef kwsysProcess_Kill +#endif + +#endif diff --git a/Source/kwsys/ProcessFwd9x.c b/Source/kwsys/ProcessFwd9x.c new file mode 100644 index 000000000..a6a428aac --- /dev/null +++ b/Source/kwsys/ProcessFwd9x.c @@ -0,0 +1,145 @@ +/*========================================================================= + +Program: KWSys - Kitware System Library +Module: $RCSfile$ +Language: C++ +Date: $Date$ +Version: $Revision$ + +Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +See http://www.cmake.org/HTML/Copyright.html for details. + +This software is distributed WITHOUT ANY WARRANTY; without even +the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + +/* + On Windows9x platforms, this executable is spawned between a parent + process and the child it is invoking to work around a bug. See the + Win32 implementation file for details. +*/ + +#include +#include + +int main() +{ + /* Process startup information for the real child. */ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + /* The result of waiting for the child to exit. */ + DWORD waitResult; + + /* The child's process return code. */ + DWORD retVal; + + /* The command line used to invoke this process. */ + LPSTR commandLine = GetCommandLine(); + + /* Pointer that will be advanced to the beginning of the command + line of the real child process. */ + LPSTR cmdLine = commandLine; + + /* Handle to the error reporting pipe provided by the parent. This + is parsed off the command line. */ + HANDLE errorPipe = 0; + + /* Handle to the event the parent uses to tell us to kill the child. + This is parsed off the command line. */ + HANDLE killEvent = 0; + + /* An array of the handles on which we wait when the child is + running. */ + HANDLE waitHandles[2] = {0, 0}; + + /* Move the pointer past the name of this executable. */ + if(*cmdLine == '"') + { + ++cmdLine; + while(*cmdLine && *cmdLine != '"') { ++cmdLine; } + if(*cmdLine) { ++cmdLine; } + } + else + { + while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } + } + + /* Parse the error pipe handle. */ + while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } + sscanf(cmdLine, "%d", &errorPipe); + + /* Parse the kill event handle. */ + while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } + while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } + sscanf(cmdLine, "%d", &killEvent); + + /* Skip to the beginning of the command line of the real child. */ + while(*cmdLine && *cmdLine != ' ') { ++cmdLine; } + while(*cmdLine && *cmdLine == ' ') { ++cmdLine; } + + /* Create the subprocess. */ + ZeroMemory(&si, sizeof(si)); + ZeroMemory(&pi, sizeof(pi)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWDEFAULT; + 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)) + { + /* 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 ); + return 1; + } + 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) + { + /* We were asked to kill the child. */ + TerminateProcess(pi.hProcess, -1); + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + return 1; + } + else if(GetExitCodeProcess(pi.hProcess, &retVal)) + { + /* The child exited and we could get the return code. */ + CloseHandle(pi.hProcess); + return retVal; + } + 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); + CloseHandle(pi.hProcess); + return -1; + } +} diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c new file mode 100644 index 000000000..218931542 --- /dev/null +++ b/Source/kwsys/ProcessUNIX.c @@ -0,0 +1,924 @@ +/*========================================================================= + +Program: KWSys - Kitware System Library +Module: $RCSfile$ +Language: C++ +Date: $Date$ +Version: $Revision$ + +Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +See http://www.cmake.org/HTML/Copyright.html for details. + +This software is distributed WITHOUT ANY WARRANTY; without even +the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#define KWSYS_IN_PROCESS_C +#include + +/* + +Implementation for UNIX + +On UNIX, a child process is forked to exec the program. Three +output pipes from the child are read by the parent process using a +select call to block until data are ready. Two of the pipes are +stdout and stderr for the child. The third is a special error pipe +that has two purposes. First, if the child cannot exec the program, +the error is reported through the error pipe. Second, the error +pipe is left open until the child exits. This is used in +conjunction with the timeout on the select call to implement a +timeout for program even when it closes stdout and stderr. +*/ + +#include /* snprintf */ +#include /* malloc, free */ +#include /* strdup, strerror, memset */ +#include /* struct timeval */ +#include /* pid_t, fd_set */ +#include /* waitpid */ +#include /* pipe, close, fork, execvp, select, _exit */ +#include /* fcntl */ +#include /* errno */ +#include /* gettimeofday */ +#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. */ +#define KWSYSPE_PIPE_COUNT 3 +#define KWSYSPE_PIPE_STDOUT 0 +#define KWSYSPE_PIPE_STDERR 1 +#define KWSYSPE_PIPE_ERROR 2 + +/* The maximum amount to read from a pipe at a time. */ +#define KWSYSPE_PIPE_BUFFER_SIZE 1024 + +typedef struct timeval kwsysProcessTime; + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessInitialize(kwsysProcess* cp); +static void kwsysProcessCleanup(kwsysProcess* cp, int error); +static void kwsysProcessCleanupDescriptor(int* pfd); +static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, + kwsysProcessTime* timeoutTime); +static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, + kwsysProcessTime* timeoutLength); +static kwsysProcessTime kwsysProcessTimeGetCurrent(); +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 kwsysProcessChildErrorExit(kwsysProcess* cp); +static void kwsysProcessRestoreDefaultSignalHandlers(); + +/*--------------------------------------------------------------------------*/ +/* Structure containing data used to implement the child's execution. */ +struct kwsysProcess_s +{ + /* The command line to execute. */ + char** Command; + + /* 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; + + /* Flag for whether the child reported an error. */ + int ChildError; + + /* The timeout length. */ + float Timeout; + + /* 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 old SIGCHLD handler. */ + struct sigaction OldSigChldAction; + + /* The number of pipes left open during execution. */ + int PipesLeft; + + /* File descriptor set for call to select. */ + fd_set PipeSet; + + /* The current status of the child process. */ + int State; + + /* The exit code of the child process, if any. */ + int ExitCode; + + /* Whether the process was killed. */ + int Killed; + + /* Buffer for error message in case of failure. */ + char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1]; + int ErrorMessageLength; +}; + +/*--------------------------------------------------------------------------*/ +kwsysProcess* kwsysProcess_New() +{ + /* Allocate a process control structure. */ + kwsysProcess* cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); + if(!cp) + { + return 0; + } + memset(cp, 0, sizeof(kwsysProcess)); + cp->State = kwsysProcess_Starting; + return cp; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Delete(kwsysProcess* cp) +{ + /* If the process is executing, wait for it to finish. */ + if(cp->State == kwsysProcess_Executing) + { + kwsysProcess_WaitForExit(cp, 0); + } + + /* Free memory. */ + kwsysProcess_SetCommand(cp, 0); + free(cp); +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) +{ + if(cp->Command) + { + char** c = cp->Command; + while(*c) + { + free(*c++); + } + free(cp->Command); + cp->Command = 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) + { + cp->Command[i] = strdup(command[i]); + } + cp->Command[n] = 0; + } +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetTimeout(kwsysProcess* cp, double timeout) +{ + cp->Timeout = timeout; + if(cp->Timeout < 0) + { + cp->Timeout = 0; + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetState(kwsysProcess* cp) +{ + return cp->State; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetExitCode(kwsysProcess* cp) +{ + return cp->ExitCode; +} + +/*--------------------------------------------------------------------------*/ +const char* kwsysProcess_GetErrorString(kwsysProcess* cp) +{ + if(cp->State == kwsysProcess_Error) + { + return cp->PipeBuffer; + } + return ""; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Execute(kwsysProcess* cp) +{ + int i; + struct sigaction newSigChldAction; + + /* Do not execute a second copy simultaneously. */ + if(cp->State == kwsysProcess_Executing) + { + return; + } + + /* Initialize the control structure for a new process. */ + kwsysProcessInitialize(cp); + + /* We want no special handling of SIGCHLD. Repeat call until it is + not interrupted. */ + newSigChldAction.sa_handler = SIG_DFL; + while((sigaction(SIGCHLD, &newSigChldAction, &cp->OldSigChldAction) < 0) && + (errno == EINTR)); + + /* Create pipes for subprocess output. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + int p[2]; + + /* Create the pipe. */ + if(pipe(p) < 0) + { + kwsysProcessCleanup(cp, 1); + return; + } + + /* Set close-on-exec flag on the pipe's ends. */ + if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) || + (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0)) + { + kwsysProcessCleanup(cp, 1); + return; + } + + /* Store the pipe. */ + cp->PipeReadEnds[i] = p[0]; + cp->PipeWriteEnds[i] = p[1]; + } + + /* The timeout period starts now. */ + cp->StartTime = kwsysProcessTimeGetCurrent(); + 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) + { + /* Close stdin. */ + 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(); + + /* 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); + } + + /* The parent process does not need the pipe write ends. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + kwsysProcessCleanupDescriptor(&cp->PipeWriteEnds[i]); + } + + /* All the pipes are now open. */ + cp->PipesLeft = KWSYSPE_PIPE_COUNT; + + /* The process has now started. */ + cp->State = kwsysProcess_Executing; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* length, + double* userTimeout) +{ + int i; + int max = -1; + kwsysProcessTime* timeout = 0; + kwsysProcessTime timeoutLength; + kwsysProcessTime timeoutTime; + kwsysProcessTime userStartTime; + int user = 0; + int expired = 0; + int pipeId = 0; + int numReady = 0; + + /* 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. */ + user = kwsysProcessGetTimeoutTime(cp, userTimeout, &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) + { + /* Check for any open pipes with data reported ready by the last + call to select. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + if(cp->PipeReadEnds[i] >= 0 && + FD_ISSET(cp->PipeReadEnds[i], &cp->PipeSet)) + { + int 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_ERROR) + { + /* 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; + } + } + else if(pipes & (1 << i)) + { + /* Caller wants this data. Report it. */ + *data = cp->PipeBuffer; + *length = n; + pipeId = (1 << i); + break; + } + } + else + { + /* We are done reading from this pipe. */ + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + --cp->PipesLeft; + } + } + } + + /* If we have data, break early. */ + if(pipeId) + { + break; + } + + /* Make sure the set is empty (it should always be empty here + anyway). */ + FD_ZERO(&cp->PipeSet); + + /* 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. */ + break; + } + + /* Setup a timeout if required. */ + if(timeoutTime.tv_sec < 0) + { + timeout = 0; + } + else + { + timeout = &timeoutLength; + } + if(kwsysProcessGetTimeoutLeft(&timeoutTime, &timeoutLength)) + { + /* Timeout has already expired. */ + expired = 1; + break; + } + + /* 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. */ + expired = 1; + break; + } + else if(numReady < 0) + { + /* Select returned an error. Leave the error description in the + pipe buffer. */ + snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, + "%s", strerror(errno)); + + /* Kill the child now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->ChildError = 1; + cp->PipesLeft = 0; + } + } + + /* 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(pipeId) + { + /* Data are ready on a pipe. */ + return pipeId; + } + else if(expired) + { + /* A timeout has expired. */ + if(user) + { + /* The user timeout has expired. It has no time left. */ + return kwsysProcess_Timeout; + } + else + { + /* The process timeout has expired. Kill the child now. */ + kwsysProcess_Kill(cp); + cp->Killed = 0; + cp->TimeoutExpired = 1; + cp->PipesLeft = 0; + return 0; + } + } + else + { + /* No pipes are left open. */ + return 0; + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) +{ + int result = 0; + int status = 0; + int pipe = 0; + + /* Make sure we are executing a process. */ + if(cp->State != kwsysProcess_Executing) + { + return 1; + } + + /* Wait for all the pipes to close. Ignore all data. */ + while((pipe = kwsysProcess_WaitForData(cp, 0, 0, 0, userTimeout)) > 0) + { + if(pipe == kwsysProcess_Timeout) + { + return 0; + } + } + + /* 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) + { + /* Unexpected error. */ + kwsysProcessCleanup(cp, 1); + return 1; + } + + /* Check whether the child reported an error invoking the process. */ + if(cp->ChildError) + { + /* The error message is already in its buffer. Tell + kwsysProcessCleanup to not create it. */ + kwsysProcessCleanup(cp, 0); + cp->State = kwsysProcess_Error; + return 1; + } + + /* Determine the outcome. */ + if(cp->Killed) + { + /* We killed the child. */ + cp->State = kwsysProcess_Killed; + } + else if(cp->TimeoutExpired) + { + /* The timeout expired. */ + cp->State = kwsysProcess_Expired; + } + else if(WIFEXITED(status)) + { + /* The child exited. */ + cp->State = kwsysProcess_Exited; + cp->ExitCode = (int)WEXITSTATUS(status); + } + else if(WIFSIGNALED(status)) + { + /* The child received an unhandled signal. */ + cp->State = kwsysProcess_Signalled; + cp->ExitCode = (int)WTERMSIG(status); + } + else + { + /* Error getting the child return code. */ + strcpy(cp->ErrorMessage, "Error getting child return code."); + cp->State = kwsysProcess_Error; + } + + /* Normal cleanup. */ + kwsysProcessCleanup(cp, 0); + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Kill(kwsysProcess* cp) +{ + /* Make sure we are executing a process. */ + if(cp->State != kwsysProcess_Executing) + { + return; + } + + /* Kill the child. */ + cp->Killed = 1; + kill(cp->ForkPID, SIGKILL); +} + +/*--------------------------------------------------------------------------*/ +/* Initialize a process control structure for kwsysProcess_Execute. */ +void 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->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; + FD_ZERO(&cp->PipeSet); + cp->State = kwsysProcess_Starting; + cp->Killed = 0; + cp->ExitCode = 0; + cp->ErrorMessage[0] = 0; + cp->ErrorMessageLength = 0; +} + +/*--------------------------------------------------------------------------*/ +/* Free all resources used by the given kwsysProcess instance that were + allocated by kwsysProcess_Execute. */ +void kwsysProcessCleanup(kwsysProcess* cp, int error) +{ + int i; + + /* If cleaning up due to an error, report the error message. */ + if(error) + { + snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, "%s", strerror(errno)); + cp->State = kwsysProcess_Error; + } + + /* Restore the SIGCHLD handler. */ + while((sigaction(SIGCHLD, &cp->OldSigChldAction, 0) < 0) && + (errno == EINTR)); + + /* Close pipe handles. */ + for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) + { + kwsysProcessCleanupDescriptor(&cp->PipeReadEnds[i]); + kwsysProcessCleanupDescriptor(&cp->PipeWriteEnds[i]); + } +} + +/*--------------------------------------------------------------------------*/ +/* Close the given file descriptor if it is open. Reset its value to -1. */ +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; + } +} + +/*--------------------------------------------------------------------------*/ +/* Get the time at which either the process or user timeout will + expire. Returns 1 if the user timeout is first, and 0 otherwise. */ +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 && 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(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. */ +int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, + kwsysProcessTime* timeoutLength) +{ + if(timeoutTime->tv_sec < 0) + { + /* No timeout time has been requested. */ + return 0; + } + else + { + /* Calculate the remaining time. */ + kwsysProcessTime currentTime = kwsysProcessTimeGetCurrent(); + *timeoutLength = kwsysProcessTimeSubtract(*timeoutTime, currentTime); + if(timeoutLength->tv_sec < 0) + { + /* Timeout has already expired. */ + return 1; + } + else + { + /* There is some time left. */ + return 0; + } + } +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeGetCurrent() +{ + kwsysProcessTime current; + gettimeofday(¤t, 0); + return current; +} + +/*--------------------------------------------------------------------------*/ +double kwsysProcessTimeToDouble(kwsysProcessTime t) +{ + return (double)t.tv_sec + t.tv_usec*0.000001; +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeFromDouble(double d) +{ + kwsysProcessTime t; + t.tv_sec = (long)d; + t.tv_usec = (long)((d-t.tv_sec)*1000000); + return t; +} + +/*--------------------------------------------------------------------------*/ +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))); +} + +/*--------------------------------------------------------------------------*/ +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; +} + +/*--------------------------------------------------------------------------*/ +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; +} + +/*--------------------------------------------------------------------------*/ +/* When the child process encounters an error before its program is + invoked, this is called to report the error to the parent and + exit. */ +void kwsysProcessChildErrorExit(kwsysProcess* cp) +{ + /* Construct the error message. */ + char buffer[KWSYSPE_PIPE_BUFFER_SIZE]; + snprintf(buffer, KWSYSPE_PIPE_BUFFER_SIZE, "%s", strerror(errno)); + + /* Report the error to the parent through the special pipe. */ + write(cp->PipeWriteEnds[KWSYSPE_PIPE_ERROR], buffer, strlen(buffer)); + + /* Terminate without cleanup. */ + _exit(1); +} + +/*--------------------------------------------------------------------------*/ +/* Restores all signal handlers to their default values. */ +void kwsysProcessRestoreDefaultSignalHandlers() +{ + struct sigaction act; + 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 +} diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c new file mode 100644 index 000000000..5a168da31 --- /dev/null +++ b/Source/kwsys/ProcessWin32.c @@ -0,0 +1,1234 @@ +/*========================================================================= + +Program: KWSys - Kitware System Library +Module: $RCSfile$ +Language: C++ +Date: $Date$ +Version: $Revision$ + +Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. +See http://www.cmake.org/HTML/Copyright.html for details. + +This software is distributed WITHOUT ANY WARRANTY; without even +the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#define KWSYS_IN_PROCESS_C +#include + +/* + +Implementation for Windows + +On windows, a thread is created to wait for data on each pipe. The +threads are synchronized with the main thread to simulate the use of +a UNIX-style select system call. + +On Windows9x platforms, a small WIN32 console application is spawned +in-between the calling process and the actual child to be executed. +This is to work-around a problem with connecting pipes from WIN16 +console applications to WIN32 applications. + +For more information, please check Microsoft Knowledge Base Articles +Q190351 and Q150956. + +*/ + +#include /* Windows API */ +#include /* strlen, strdup */ +#include /* sprintf */ +#include /* _getpid */ +#include /* _unlink */ + +/* 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 + +/* The maximum amount to read from a pipe at a time. */ +#define CMPE_PIPE_BUFFER_SIZE 1024 + +#define kwsysEncodedWriteArrayProcessFwd kwsys(EncodedWriteArrayProcessFwd) + +typedef LARGE_INTEGER kwsysProcessTime; + +/*--------------------------------------------------------------------------*/ +typedef struct kwsysProcessPipeData_s kwsysProcessPipeData; +static DWORD WINAPI kwsysProcessPipeThread(LPVOID ptd); +static void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, kwsysProcessPipeData* td); +static void kwsysProcessCleanupHandle(PHANDLE h); +static void kwsysProcessCleanup(kwsysProcess* cp, int error); +static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, + kwsysProcessTime* timeoutTime); +static int kwsysProcessGetTimeoutLeft(kwsysProcess* cp, kwsysProcessTime* timeoutTime, + kwsysProcessTime* timeoutLength); +static kwsysProcessTime kwsysProcessTimeGetCurrent(); +static DWORD kwsysProcessTimeToDWORD(kwsysProcessTime t); +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); +extern int kwsysEncodedWriteArrayProcessFwd9x(const char* fname); + +/*--------------------------------------------------------------------------*/ +/* A structure containing data for each pipe's thread. */ +struct kwsysProcessPipeData_s +{ + /* ------------- Data managed per instance of kwsysProcess ------------- */ + + /* Handle for the thread for this pipe. */ + HANDLE Thread; + + /* Semaphore indicating a process and pipe are available. */ + HANDLE Ready; + + /* Semaphore indicating when this thread's buffer is empty. */ + HANDLE Empty; + + /* Semaphore indicating a pipe thread has reset for another process. */ + HANDLE Reset; + + /* Index of this pipe. */ + int Index; + + /* The kwsysProcess instance owning this pipe. */ + kwsysProcess* Process; + + /* ------------- Data managed per call to Execute ------------- */ + + /* Buffer for data read in this pipe's thread. */ + char DataBuffer[CMPE_PIPE_BUFFER_SIZE]; + + /* The length of the data stored in the buffer. */ + DWORD DataLength; + + /* Whether the pipe has been closed. */ + int Closed; + + /* Handle for the read end of this pipe. */ + HANDLE Read; + + /* Handle for the write end of this pipe. */ + HANDLE Write; +}; + +/*--------------------------------------------------------------------------*/ +/* Structure containing data used to implement the child's execution. */ +struct kwsysProcess_s +{ + /* ------------- Data managed per instance of kwsysProcess ------------- */ + + /* The status of the process. */ + int State; + + /* The command line to execute. */ + char* Command; + + /* On Win9x platforms, the path to the forwarding executable. */ + char* Win9x; + + /* On Win9x platforms, the kill event for the forwarding executable. */ + HANDLE Win9xKillEvent; + + /* Mutex to protect the shared index used by threads to report data. */ + HANDLE SharedIndexMutex; + + /* 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]; + + /* ------------- Data managed per call to Execute ------------- */ + + /* The process exit code, if any. */ + int ExitCode; + + /* Index of last pipe to report data, if any. */ + int CurrentIndex; + + /* Index shared by threads to report data. */ + int SharedIndex; + + /* The timeout length. */ + double Timeout; + + /* Time at which the child started. */ + kwsysProcessTime StartTime; + + /* Time at which the child will timeout. Negative for no timeout. */ + kwsysProcessTime TimeoutTime; + + /* Flag for whether the process was killed. */ + int Killed; + + /* Flag for whether the timeout expired. */ + int TimeoutExpired; + + /* Flag for whether the process has terminated. */ + int Terminated; + + /* The number of pipes still open during execution and while waiting + for pipes to close after process termination. */ + 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; + + /* Windows process information data. */ + PROCESS_INFORMATION ProcessInformation; +}; + +/*--------------------------------------------------------------------------*/ +kwsysProcess* kwsysProcess_New() +{ + int i; + + /* Process control structure. */ + kwsysProcess* cp; + + /* Path to Win9x forwarding executable. */ + char* win9x = 0; + + /* Windows version number data. */ + OSVERSIONINFO osv; + + /* Allocate a process control structure. */ + cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); + ZeroMemory(cp, sizeof(*cp)); + + /* Set initial status. */ + cp->State = kwsysProcess_Starting; + + /* Choose a method of running the child based on version of + windows. */ + ZeroMemory(&osv, sizeof(osv)); + osv.dwOSVersionInfoSize = sizeof(osv); + GetVersionEx(&osv); + if(osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) + { + /* This is Win9x. We need the console forwarding executable to + work-around a Windows 9x bug. */ + char fwdName[_MAX_FNAME+1] = ""; + char tempDir[_MAX_PATH+1] = ""; + + /* We will try putting the executable in the system temp + directory. */ + DWORD length = GetEnvironmentVariable("TEMP", tempDir, _MAX_PATH); + + /* Construct the executable name from the process id and kwsysProcess + instance. This should be unique. */ + sprintf(fwdName, "cmw9xfwd_%u_%p.exe", _getpid(), cp); + + /* If the environment variable "TEMP" gave us a directory, use it. */ + if(length > 0 && length <= _MAX_PATH) + { + /* Make sure there is no trailing slash. */ + size_t tdlen = strlen(tempDir); + if(tempDir[tdlen-1] == '/' || tempDir[tdlen-1] == '\\') + { + tempDir[tdlen-1] = 0; + --tdlen; + } + + /* Allocate a buffer to hold the forwarding executable path. */ + win9x = (char*)malloc(tdlen + strlen(fwdName) + 2); + if(!win9x) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* Construct the full path to the forwarding executable. */ + sprintf(win9x, "%s/%s", tempDir, fwdName); + } + + /* If we found a place to put the forwarding executable, try to + write it. */ + if(win9x) + { + if(!kwsysEncodedWriteArrayProcessFwd9x(win9x)) + { + /* Failed to create forwarding executable. Give up. */ + free(win9x); + kwsysProcess_Delete(cp); + return 0; + } + } + else + { + /* Failed to find a place to put forwarding executable. */ + kwsysProcess_Delete(cp); + return 0; + } + } + + /* We need the extra error pipe on Win9x. */ + 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))) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* Initially no data are available. Initialize semaphore to 0. */ + if(!(cp->Full = CreateSemaphore(0, 0, 1, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + + 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; + if(!(cp->Win9xKillEvent = CreateEvent(&sa, TRUE, 0, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + } + + /* Create the thread to read each pipe. */ + for(i=0; i < cp->PipeCount; ++i) + { + DWORD dummy=0; + + /* Assign the thread its index. */ + cp->Pipe[i].Index = i; + + /* Give the thread a pointer back to the kwsysProcess instance. */ + cp->Pipe[i].Process = cp; + + /* The pipe is not yet ready to read. Initialize semaphore to 0. */ + if(!(cp->Pipe[i].Ready = CreateSemaphore(0, 0, 1, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* The pipe is not yet reset. Initialize semaphore to 0. */ + if(!(cp->Pipe[i].Reset = CreateSemaphore(0, 0, 1, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* The thread's buffer is initially empty. Initialize semaphore to 1. */ + if(!(cp->Pipe[i].Empty = CreateSemaphore(0, 1, 1, 0))) + { + kwsysProcess_Delete(cp); + return 0; + } + + /* Create the thread. It will block immediately. */ + if(!(cp->Pipe[i].Thread = CreateThread(0, 0, kwsysProcessPipeThread, + &cp->Pipe[i], 0, &dummy))) + { + kwsysProcess_Delete(cp); + return 0; + } + } + + return cp; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Delete(kwsysProcess* cp) +{ + int i; + + /* If the process is executing, wait for it to finish. */ + if(cp->State == kwsysProcess_Executing) + { + kwsysProcess_WaitForExit(cp, 0); + } + + /* We are deleting the kwsysProcess instance. */ + cp->Deleting = 1; + + /* Terminate each of the threads. */ + for(i=0; i < cp->PipeCount; ++i) + { + if(cp->Pipe[i].Thread) + { + /* Signal the thread we are ready for it. It will terminate + immediately since Deleting is set. */ + ReleaseSemaphore(cp->Pipe[i].Ready, 1, 0); + + /* Wait for the thread to exit. */ + WaitForSingleObject(cp->Pipe[i].Thread, INFINITE); + + /* Close the handle to the thread. */ + kwsysProcessCleanupHandle(&cp->Pipe[i].Thread); + } + + /* Cleanup the pipe's semaphores. */ + kwsysProcessCleanupHandle(&cp->Pipe[i].Ready); + kwsysProcessCleanupHandle(&cp->Pipe[i].Empty); + } + + /* Close the shared semaphores. */ + kwsysProcessCleanupHandle(&cp->SharedIndexMutex); + kwsysProcessCleanupHandle(&cp->Full); + + /* Close the Win9x kill event handle. */ + if(cp->Win9x) + { + kwsysProcessCleanupHandle(&cp->Win9xKillEvent); + } + + /* Free memory. */ + kwsysProcess_SetCommand(cp, 0); + if(cp->Win9x) + { + _unlink(cp->Win9x); + free(cp->Win9x); + } + free(cp); +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) +{ + if(cp->Command) + { + free(cp->Command); + cp->Command = 0; + } + if(command) + { + /* We need to construct a single string representing the command + and its arguments. We will surround each argument with + double-quotes so it can contain spaces. 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; + const char* c; + + /* Add the length of the argument, plus 3 for the double quotes + and space separating the arguments. */ + length += strlen(*arg) + 3; + + /* Scan the string to find characters that need escaping. */ + for(c=*arg; *c; ++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; + } + } + + /* We need to escape all ending backslashes. */ + length += backslashes; + } + + /* 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); + + /* 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; + const char* c; + + /* Add the separating space if this is not the first argument. */ + if(arg != command) + { + *cmd++ = ' '; + } + + /* Add the opening double-quote for this argument. */ + *cmd++ = '"'; + + /* Add the characters of the argument, possibly escaping them. */ + for(c=*arg; *c; ++c) + { + if(*c == '\\') + { + /* Found a backslash. It may need to be escaped later. */ + ++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 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 enough backslashes to escape any trailing ones. */ + while(backslashes > 0) + { + --backslashes; + *cmd++ = '\\'; + } + + /* Add the opening double-quote for this argument. */ + *cmd++ = '"'; + } + + /* Add the terminating null character to the command line. */ + *cmd++ = 0; + } +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_SetTimeout(kwsysProcess* cp, double timeout) +{ + cp->Timeout = timeout; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetState(kwsysProcess* cp) +{ + return cp->State; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_GetExitCode(kwsysProcess* cp) +{ + return cp->ExitCode; +} + +/*--------------------------------------------------------------------------*/ +const char* kwsysProcess_GetErrorString(kwsysProcess* cp) +{ + return cp->ErrorMessage; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Execute(kwsysProcess* cp) +{ + int i=0; + + /* Windows child startup control data. */ + STARTUPINFO si; + + /* Do not execute a second time. */ + if(cp->State == kwsysProcess_Executing) + { + 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; + + /* Reset error data. */ + cp->ErrorMessage[0] = 0; + cp->ErrorMessageLength = 0; + + /* Reset the Win9x kill event. */ + if(cp->Win9x) + { + if(!ResetEvent(cp->Win9xKillEvent)) + { + kwsysProcessCleanup(cp, 1); + return; + } + } + + /* Create a pipe for each child output. */ + for(i=0; i < cp->PipeCount; ++i) + { + HANDLE writeEnd; + DWORD dummy=0; + + /* The pipe is not closed. */ + cp->Pipe[i].Closed = 0; + + /* Create the pipe. Neither end is directly inherited. */ + if(!CreatePipe(&cp->Pipe[i].Read, &writeEnd, 0, 0)) + { + kwsysProcessCleanup(cp, 1); + return; + } + + /* 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))) + { + kwsysProcessCleanup(cp, 1); + CloseHandle(writeEnd); + return; + } + } + + /* Construct the real command line. */ + 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 %d %d %s", cp->Win9x, + cp->Pipe[CMPE_PIPE_ERROR].Write, + cp->Win9xKillEvent, cp->Command); + } + else + { + /* Not Windows 9x */ + cp->RealCommand = strdup(cp->Command); + } + + /* Connect the child's output pipes to the threads. */ + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = cp->Pipe[CMPE_PIPE_STDOUT].Write; + si.hStdError = cp->Pipe[CMPE_PIPE_STDERR].Write; + + /* Hide the forwarding executable console on Windows 9x. */ + si.dwFlags |= STARTF_USESHOWWINDOW; + if(cp->Win9x) + { + si.wShowWindow = SW_HIDE; + } + else + { + si.wShowWindow = 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, CREATE_NEW_CONSOLE, 0, + 0, &si, &cp->ProcessInformation)) + { + kwsysProcessCleanup(cp, 1); + return; + } + + /* ---- It is no longer safe to call kwsysProcessCleanup. ----- */ + /* Tell the pipe threads that a process has started. */ + for(i=0; i < cp->PipeCount; ++i) + { + ReleaseSemaphore(cp->Pipe[i].Ready, 1, 0); + } + + /* We don't care about the child's main thread. */ + kwsysProcessCleanupHandle(&cp->ProcessInformation.hThread); + + /* No pipe has reported data. */ + cp->CurrentIndex = CMPE_PIPE_COUNT; + cp->PipesLeft = cp->PipeCount; + + /* The process has now started. */ + cp->State = kwsysProcess_Executing; +} + +/*--------------------------------------------------------------------------*/ + +int kwsysProcess_WaitForData(kwsysProcess* cp, int pipes, char** data, int* length, + double* userTimeout) +{ + kwsysProcessTime userStartTime; + kwsysProcessTime timeoutLength; + kwsysProcessTime timeoutTime; + DWORD timeout; + int user; + int done = 0; + int expired = 0; + int pipeId = 0; + DWORD w; + HANDLE events[2]; + + /* Make sure we are executing a process. */ + if(cp->State != kwsysProcess_Executing || cp->Killed || cp->TimeoutExpired) + { + 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. */ + if(userTimeout) + { + userStartTime = kwsysProcessTimeGetCurrent(); + } + + /* Calculate the time at which a timeout will expire, and whether it + is the user or process timeout. */ + user = kwsysProcessGetTimeoutTime(cp, userTimeout, &timeoutTime); + + /* Loop until we have a reason to return. */ + while(!done && cp->PipesLeft > 0) + { + /* If we previously got data from a thread, let it know we are + done with the data. */ + if(cp->CurrentIndex < CMPE_PIPE_COUNT) + { + ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); + cp->CurrentIndex = CMPE_PIPE_COUNT; + } + + /* Setup a timeout if required. */ + if(kwsysProcessGetTimeoutLeft(cp, &timeoutTime, &timeoutLength)) + { + /* Timeout has already expired. */ + expired = 1; + done = 1; + break; + } + if(timeoutTime.QuadPart < 0) + { + timeout = INFINITE; + } + else + { + 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); + if(w == WAIT_TIMEOUT) + { + /* Timeout has expired. */ + expired = 1; + done = 1; + } + else if(w == WAIT_OBJECT_0) + { + /* Save the index of the reporting thread and release the mutex. + The thread will block until we signal its Empty mutex. */ + cp->CurrentIndex = cp->SharedIndex; + ReleaseSemaphore(cp->SharedIndexMutex, 1, 0); + + /* Data are available or a pipe closed. */ + if(cp->Pipe[cp->CurrentIndex].Closed) + { + /* 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. */ + *data = cp->Pipe[cp->CurrentIndex].DataBuffer; + *length = cp->Pipe[cp->CurrentIndex].DataLength; + pipeId = (1 << cp->CurrentIndex); + done = 1; + } + else + { + /* Caller does not care about this pipe. Ignore the data. */ + } + } + 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); + } + } + } + + /* 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(pipeId) + { + /* Data are ready on a pipe. */ + return pipeId; + } + else if(expired) + { + /* A timeout has expired. */ + if(user) + { + /* The user timeout has expired. It has no time left. */ + return kwsysProcess_Timeout; + } + else + { + /* The process timeout has expired. Kill the child now. */ + kwsysProcess_Kill(cp); + cp->TimeoutExpired = 1; + cp->Killed = 0; + return 0; + } + } + else + { + /* The process has terminated and no more data are available. */ + return 0; + } +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) +{ + int i; + int pipe = 0; + int pipesLeft = cp->PipeCount; + + /* Buffer for child's return value. */ + int childReturnValue = 0; + + /* Make sure we are executing a process. */ + if(cp->State != kwsysProcess_Executing) + { + return 1; + } + + /* Wait for the process to terminate. Ignore all data. */ + while((pipe = kwsysProcess_WaitForData(cp, 0, 0, 0, userTimeout)) > 0) + { + if(pipe == kwsysProcess_Timeout) + { + /* The user timeout has expired. */ + return 0; + } + } + + /* 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) + { + ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); + cp->CurrentIndex = CMPE_PIPE_COUNT; + } + + /* Wait for all pipe threads to reset. */ + for(i=0; i < cp->PipeCount; ++i) + { + WaitForSingleObject(cp->Pipe[i].Reset, INFINITE); + } + + /* ---- It is now safe again to call kwsysProcessCleanup. ----- */ + /* 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_Killed; + } + else if(cp->ErrorMessageLength) + { + /* Failed to run the process. */ + cp->State = kwsysProcess_Error; + } + else if(cp->TimeoutExpired) + { + /* The timeout expired. */ + cp->State = kwsysProcess_Expired; + } + else if(GetExitCodeProcess(cp->ProcessInformation.hProcess, + &childReturnValue)) + { + /* The child exited. */ + cp->State = kwsysProcess_Exited; + cp->ExitCode = childReturnValue; + } + else + { + /* Error getting the child return code. */ + strcpy(cp->ErrorMessage, "Error getting child return code."); + cp->State = kwsysProcess_Error; + } + + /* The child process is terminated. */ + CloseHandle(cp->ProcessInformation.hProcess); + + return 1; +} + +/*--------------------------------------------------------------------------*/ +void kwsysProcess_Kill(kwsysProcess* cp) +{ + int i; + + /* Make sure we are executing a process. */ + if(cp->State != kwsysProcess_Executing || cp->TimeoutExpired || cp->Killed || + cp->Terminated) + { + return; + } + + /* If we are killing a process that just reported data, release + the pipe's thread. */ + if(cp->CurrentIndex < CMPE_PIPE_COUNT) + { + ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); + cp->CurrentIndex = CMPE_PIPE_COUNT; + } + + /* Wake up all the pipe threads with dummy data. */ + for(i=0; i < cp->PipeCount; ++i) + { + DWORD dummy; + WriteFile(cp->Pipe[i].Write, "", 1, &dummy, 0); + } + + /* Tell pipe threads to reset until we run another process. */ + while(cp->PipesLeft > 0) + { + WaitForSingleObject(cp->Full, INFINITE); + cp->CurrentIndex = cp->SharedIndex; + ReleaseSemaphore(cp->SharedIndexMutex, 1, 0); + cp->Pipe[cp->CurrentIndex].Closed = 1; + ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Empty, 1, 0); + --cp->PipesLeft; + } + + /* Kill the child. */ + cp->Killed = 1; + if(cp->Win9x) + { + /* Windows 9x. Tell the forwarding executable to kill the child. */ + SetEvent(cp->Win9xKillEvent); + } + else + { + /* Not Windows 9x. Just terminate the child. */ + TerminateProcess(cp->ProcessInformation.hProcess, -1); + } +} + +/*--------------------------------------------------------------------------*/ + +/* + Function executed for each pipe's thread. Argument is a pointer to + the kwsysProcessPipeData instance for this thread. +*/ +DWORD WINAPI kwsysProcessPipeThread(LPVOID ptd) +{ + kwsysProcessPipeData* td = (kwsysProcessPipeData*)ptd; + kwsysProcess* cp = td->Process; + + /* Wait for a process to be ready. */ + while((WaitForSingleObject(td->Ready, INFINITE), !cp->Deleting)) + { + /* Read output from the process for this thread's pipe. */ + kwsysProcessPipeThreadReadPipe(cp, td); + + /* We were signalled to exit with our buffer empty. Reset the + mutex for a new process. */ + ReleaseSemaphore(td->Empty, 1, 0); + + /* Signal the main thread we have reset for a new process. */ + ReleaseSemaphore(td->Reset, 1, 0); + } + return 0; +} + +/*--------------------------------------------------------------------------*/ + +/* + Function called in each pipe's thread to handle data for one + execution of a subprocess. +*/ +void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, kwsysProcessPipeData* td) +{ + /* Wait for space in the thread's buffer. */ + 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, + &td->DataLength, 0)) + { + if(GetLastError() != ERROR_BROKEN_PIPE) + { + /* UNEXPECTED failure to read the pipe. */ + } + + /* The pipe closed. There are no more data to read. */ + td->Closed = 1; + } + + /* Wait for our turn to be handled by the main thread. */ + WaitForSingleObject(cp->SharedIndexMutex, INFINITE); + + /* Tell the main thread we have something to report. */ + cp->SharedIndex = td->Index; + ReleaseSemaphore(cp->Full, 1, 0); + } +} + +/*--------------------------------------------------------------------------*/ + +/* Close the given handle if it is open. Reset its value to 0. */ +void kwsysProcessCleanupHandle(PHANDLE h) +{ + if(h && *h) + { + CloseHandle(*h); + *h = 0; + } +} + +/*--------------------------------------------------------------------------*/ + +/* Close all handles created by kwsysProcess_Execute. */ +void kwsysProcessCleanup(kwsysProcess* cp, int error) +{ + int i; + + /* If this is an error case, report the error. */ + if(error) + { + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + cp->ErrorMessage, CMPE_PIPE_BUFFER_SIZE, 0); + cp->State = kwsysProcess_Error; + } + + /* Free memory. */ + if(cp->RealCommand) + { + free(cp->RealCommand); + cp->RealCommand = 0; + } + + /* Close each pipe. */ + for(i=0; i < cp->PipeCount; ++i) + { + kwsysProcessCleanupHandle(&cp->Pipe[i].Write); + kwsysProcessCleanupHandle(&cp->Pipe[i].Read); + } +} + +/*--------------------------------------------------------------------------*/ +/* Get the time at which either the process or user timeout will + expire. Returns 1 if the user timeout is first, and 0 otherwise. */ +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 && cp->TimeoutTime.QuadPart < 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(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. */ +int kwsysProcessGetTimeoutLeft(kwsysProcess* cp, kwsysProcessTime* timeoutTime, + kwsysProcessTime* timeoutLength) +{ + if(timeoutTime->QuadPart < 0) + { + /* No timeout time has been requested. */ + return 0; + } + else + { + /* Calculate the remaining time. */ + kwsysProcessTime currentTime = kwsysProcessTimeGetCurrent(); + *timeoutLength = kwsysProcessTimeSubtract(*timeoutTime, currentTime); + if(timeoutLength->QuadPart < 0) + { + /* Timeout has already expired. */ + return 1; + } + else + { + /* There is some time left. */ + return 0; + } + } +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeGetCurrent() +{ + kwsysProcessTime current; + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + current.LowPart = ft.dwLowDateTime; + current.HighPart = ft.dwHighDateTime; + return current; +} + +/*--------------------------------------------------------------------------*/ +DWORD kwsysProcessTimeToDWORD(kwsysProcessTime t) +{ + return (DWORD)(t.QuadPart * 0.0001); +} + +/*--------------------------------------------------------------------------*/ +double kwsysProcessTimeToDouble(kwsysProcessTime t) +{ + return t.QuadPart * 0.0000001; +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeFromDouble(double d) +{ + kwsysProcessTime t; + t.QuadPart = (LONGLONG)(d*10000000); + return t; +} + +/*--------------------------------------------------------------------------*/ +int kwsysProcessTimeLess(kwsysProcessTime in1, kwsysProcessTime in2) +{ + return in1.QuadPart < in2.QuadPart; +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeAdd(kwsysProcessTime in1, kwsysProcessTime in2) +{ + kwsysProcessTime out; + out.QuadPart = in1.QuadPart + in2.QuadPart; + return out; +} + +/*--------------------------------------------------------------------------*/ +kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProcessTime in2) +{ + kwsysProcessTime out; + out.QuadPart = in1.QuadPart - in2.QuadPart; + return out; +}