diff options
author | Brad King <brad.king@kitware.com> | 2003-06-10 19:46:31 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2003-06-10 19:46:31 (GMT) |
commit | 96ccaed54dcf97e802c2e958185cf321dfa7f1e4 (patch) | |
tree | fdeb2ff895a72232e4f290f39126faad8ab7b68f /Source/kwsys/ProcessWin32.c | |
parent | 89cf5d538c53af72c6f212d86e74011277233067 (diff) | |
download | CMake-96ccaed54dcf97e802c2e958185cf321dfa7f1e4.zip CMake-96ccaed54dcf97e802c2e958185cf321dfa7f1e4.tar.gz CMake-96ccaed54dcf97e802c2e958185cf321dfa7f1e4.tar.bz2 |
ENH: Added Process execution implementation.
Diffstat (limited to 'Source/kwsys/ProcessWin32.c')
-rw-r--r-- | Source/kwsys/ProcessWin32.c | 1234 |
1 files changed, 1234 insertions, 0 deletions
diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c new file mode 100644 index 0000000..5a168da --- /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 <Process.h> + +/* + +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.h> /* Windows API */ +#include <string.h> /* strlen, strdup */ +#include <stdio.h> /* sprintf */ +#include <process.h> /* _getpid */ +#include <io.h> /* _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; +} |