| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing#kwsys for details. */ |
| #include "kwsysPrivate.h" |
| #include KWSYS_HEADER(Process.h) |
| #include KWSYS_HEADER(Encoding.h) |
| |
| /* Work-around CMake dependency scanning limitation. This must |
| duplicate the above list of headers. */ |
| #if 0 |
| # include "Encoding.h.in" |
| # include "Process.h.in" |
| #endif |
| |
| /* |
| |
| 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. |
| |
| */ |
| |
| #ifdef _MSC_VER |
| # pragma warning(push, 1) |
| #endif |
| #include <windows.h> /* Windows API */ |
| #if defined(_MSC_VER) && _MSC_VER >= 1800 |
| # define KWSYS_WINDOWS_DEPRECATED_GetVersionEx |
| #endif |
| #include <io.h> /* _unlink */ |
| #include <stdio.h> /* sprintf */ |
| #include <string.h> /* strlen, strdup */ |
| #ifdef __WATCOMC__ |
| # define _unlink unlink |
| #endif |
| |
| #ifndef _MAX_FNAME |
| # define _MAX_FNAME 4096 |
| #endif |
| #ifndef _MAX_PATH |
| # define _MAX_PATH 4096 |
| #endif |
| |
| #ifdef _MSC_VER |
| # pragma warning(pop) |
| # pragma warning(disable : 4514) |
| # pragma warning(disable : 4706) |
| #endif |
| |
| #if defined(__BORLANDC__) |
| # pragma warn - 8004 /* assigned a value that is never used */ |
| # pragma warn - 8060 /* Assignment inside if() condition. */ |
| #endif |
| |
| /* There are pipes for the process pipeline's stdout and stderr. */ |
| #define KWSYSPE_PIPE_COUNT 2 |
| #define KWSYSPE_PIPE_STDOUT 0 |
| #define KWSYSPE_PIPE_STDERR 1 |
| |
| /* The maximum amount to read from a pipe at a time. */ |
| #define KWSYSPE_PIPE_BUFFER_SIZE 1024 |
| |
| /* Debug output macro. */ |
| #if 0 |
| # define KWSYSPE_DEBUG(x) \ |
| ((void*)cp == (void*)0x00226DE0 \ |
| ? (fprintf(stderr, "%d/%p/%d ", (int)GetCurrentProcessId(), cp, \ |
| __LINE__), \ |
| fprintf x, fflush(stderr), 1) \ |
| : (1)) |
| #else |
| # define KWSYSPE_DEBUG(x) (void)1 |
| #endif |
| |
| typedef LARGE_INTEGER kwsysProcessTime; |
| |
| typedef struct kwsysProcessCreateInformation_s |
| { |
| /* Windows child startup control data. */ |
| STARTUPINFOW StartupInfo; |
| |
| /* Original handles before making inherited duplicates. */ |
| HANDLE hStdInput; |
| HANDLE hStdOutput; |
| HANDLE hStdError; |
| } kwsysProcessCreateInformation; |
| |
| typedef struct kwsysProcessPipeData_s kwsysProcessPipeData; |
| static DWORD WINAPI kwsysProcessPipeThreadRead(LPVOID ptd); |
| static void kwsysProcessPipeThreadReadPipe(kwsysProcess* cp, |
| kwsysProcessPipeData* td); |
| static DWORD WINAPI kwsysProcessPipeThreadWake(LPVOID ptd); |
| static void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp, |
| kwsysProcessPipeData* td); |
| static int kwsysProcessInitialize(kwsysProcess* cp); |
| static DWORD kwsysProcessCreate(kwsysProcess* cp, int index, |
| kwsysProcessCreateInformation* si); |
| static void kwsysProcessDestroy(kwsysProcess* cp, int event); |
| static DWORD kwsysProcessSetupOutputPipeFile(PHANDLE handle, const char* name); |
| static void kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle); |
| static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle); |
| static void kwsysProcessCleanupHandle(PHANDLE h); |
| static void kwsysProcessCleanup(kwsysProcess* cp, DWORD error); |
| static void kwsysProcessCleanErrorMessage(kwsysProcess* cp); |
| static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, |
| kwsysProcessTime* timeoutTime); |
| static int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, |
| double* userTimeout, |
| kwsysProcessTime* timeoutLength); |
| static kwsysProcessTime kwsysProcessTimeGetCurrent(void); |
| 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); |
| static void kwsysProcessSetExitExceptionByIndex(kwsysProcess* cp, int code, |
| int idx); |
| static void kwsysProcessKillTree(int pid); |
| static void kwsysProcessDisablePipeThreads(kwsysProcess* cp); |
| static int kwsysProcessesInitialize(void); |
| static int kwsysTryEnterCreateProcessSection(void); |
| static void kwsysLeaveCreateProcessSection(void); |
| static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessId, |
| int newProcessGroup); |
| static void kwsysProcessesRemove(HANDLE hProcess); |
| static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType); |
| |
| /* A structure containing synchronization data for each thread. */ |
| typedef struct kwsysProcessPipeSync_s kwsysProcessPipeSync; |
| struct kwsysProcessPipeSync_s |
| { |
| /* Handle to the thread. */ |
| HANDLE Thread; |
| |
| /* Semaphore indicating to the thread that a process has started. */ |
| HANDLE Ready; |
| |
| /* Semaphore indicating to the thread that it should begin work. */ |
| HANDLE Go; |
| |
| /* Semaphore indicating thread has reset for another process. */ |
| HANDLE Reset; |
| }; |
| |
| /* A structure containing data for each pipe's threads. */ |
| struct kwsysProcessPipeData_s |
| { |
| /* ------------- Data managed per instance of kwsysProcess ------------- */ |
| |
| /* Synchronization data for reading thread. */ |
| kwsysProcessPipeSync Reader; |
| |
| /* Synchronization data for waking thread. */ |
| kwsysProcessPipeSync Waker; |
| |
| /* 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[KWSYSPE_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; |
| }; |
| |
| /* A structure containing results data for each process. */ |
| typedef struct kwsysProcessResults_s kwsysProcessResults; |
| struct kwsysProcessResults_s |
| { |
| /* The status of the process. */ |
| int State; |
| |
| /* The exceptional behavior that terminated the process, if any. */ |
| int ExitException; |
| |
| /* The process exit code. */ |
| DWORD ExitCode; |
| |
| /* The process return code, if any. */ |
| int ExitValue; |
| |
| /* Description for the ExitException. */ |
| char ExitExceptionString[KWSYSPE_PIPE_BUFFER_SIZE + 1]; |
| }; |
| |
| /* Structure containing data used to implement the child's execution. */ |
| struct kwsysProcess_s |
| { |
| /* ------------- Data managed per instance of kwsysProcess ------------- */ |
| |
| /* The status of the process structure. */ |
| int State; |
| |
| /* The command lines to execute. */ |
| wchar_t** Commands; |
| int NumberOfCommands; |
| |
| /* The exit code of each command. */ |
| DWORD* CommandExitCodes; |
| |
| /* The working directory for the child process. */ |
| wchar_t* WorkingDirectory; |
| |
| /* Whether to create the child as a detached process. */ |
| int OptionDetach; |
| |
| /* Whether the child was created as a detached process. */ |
| int Detached; |
| |
| /* Whether to hide the child process's window. */ |
| int HideWindow; |
| |
| /* Whether to treat command lines as verbatim. */ |
| int Verbatim; |
| |
| /* Whether to merge stdout/stderr of the child. */ |
| int MergeOutput; |
| |
| /* Whether to create the process in a new process group. */ |
| int CreateProcessGroup; |
| |
| /* Mutex to protect the shared index used by threads to report data. */ |
| HANDLE SharedIndexMutex; |
| |
| /* Semaphore used by threads to signal data ready. */ |
| HANDLE Full; |
| |
| /* Whether we are currently deleting this kwsysProcess instance. */ |
| int Deleting; |
| |
| /* Data specific to each pipe and its thread. */ |
| kwsysProcessPipeData Pipe[KWSYSPE_PIPE_COUNT]; |
| |
| /* Name of files to which stdin and stdout pipes are attached. */ |
| char* PipeFileSTDIN; |
| char* PipeFileSTDOUT; |
| char* PipeFileSTDERR; |
| |
| /* Whether each pipe is shared with the parent process. */ |
| int PipeSharedSTDIN; |
| int PipeSharedSTDOUT; |
| int PipeSharedSTDERR; |
| |
| /* Native pipes provided by the user. */ |
| HANDLE PipeNativeSTDIN[2]; |
| HANDLE PipeNativeSTDOUT[2]; |
| HANDLE PipeNativeSTDERR[2]; |
| |
| /* ------------- Data managed per call to Execute ------------- */ |
| |
| /* 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. */ |
| char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE + 1]; |
| |
| /* process results. */ |
| kwsysProcessResults* ProcessResults; |
| |
| /* Windows process information data. */ |
| PROCESS_INFORMATION* ProcessInformation; |
| |
| /* Data and process termination events for which to wait. */ |
| PHANDLE ProcessEvents; |
| int ProcessEventsLength; |
| |
| /* Real working directory of our own process. */ |
| DWORD RealWorkingDirectoryLength; |
| wchar_t* RealWorkingDirectory; |
| |
| /* Own handles for the child's ends of the pipes in the parent process. |
| Used temporarily during process creation. */ |
| HANDLE PipeChildStd[3]; |
| }; |
| |
| kwsysProcess* kwsysProcess_New(void) |
| { |
| int i; |
| |
| /* Process control structure. */ |
| kwsysProcess* cp; |
| |
| /* Windows version number data. */ |
| OSVERSIONINFO osv; |
| |
| /* Initialize list of processes before we get any farther. It's especially |
| important that the console Ctrl handler be added BEFORE starting the |
| first process. This prevents the risk of an orphaned process being |
| started by the main thread while the default Ctrl handler is in |
| progress. */ |
| if (!kwsysProcessesInitialize()) { |
| return 0; |
| } |
| |
| /* Allocate a process control structure. */ |
| cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); |
| if (!cp) { |
| /* Could not allocate memory for the control structure. */ |
| return 0; |
| } |
| ZeroMemory(cp, sizeof(*cp)); |
| |
| /* Share stdin with the parent process by default. */ |
| cp->PipeSharedSTDIN = 1; |
| |
| /* Set initial status. */ |
| cp->State = kwsysProcess_State_Starting; |
| |
| /* Choose a method of running the child based on version of |
| windows. */ |
| ZeroMemory(&osv, sizeof(osv)); |
| osv.dwOSVersionInfoSize = sizeof(osv); |
| #ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx |
| # pragma warning(push) |
| # ifdef __INTEL_COMPILER |
| # pragma warning(disable : 1478) |
| # elif defined __clang__ |
| # pragma clang diagnostic push |
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| # else |
| # pragma warning(disable : 4996) |
| # endif |
| #endif |
| GetVersionEx(&osv); |
| #ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx |
| # ifdef __clang__ |
| # pragma clang diagnostic pop |
| # else |
| # pragma warning(pop) |
| # endif |
| #endif |
| if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { |
| /* Win9x no longer supported. */ |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| |
| /* 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; |
| } |
| |
| /* Create the thread to read each pipe. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++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; |
| |
| /* No process is yet running. Initialize semaphore to 0. */ |
| if (!(cp->Pipe[i].Reader.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].Reader.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].Reader.Go = CreateSemaphore(0, 1, 1, 0))) { |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| |
| /* Create the reading thread. It will block immediately. The |
| thread will not make deeply nested calls, so we need only a |
| small stack. */ |
| if (!(cp->Pipe[i].Reader.Thread = CreateThread( |
| 0, 1024, kwsysProcessPipeThreadRead, &cp->Pipe[i], 0, &dummy))) { |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| |
| /* No process is yet running. Initialize semaphore to 0. */ |
| if (!(cp->Pipe[i].Waker.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].Waker.Reset = CreateSemaphore(0, 0, 1, 0))) { |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| |
| /* The waker should not wake immediately. Initialize semaphore to 0. */ |
| if (!(cp->Pipe[i].Waker.Go = CreateSemaphore(0, 0, 1, 0))) { |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| |
| /* Create the waking thread. It will block immediately. The |
| thread will not make deeply nested calls, so we need only a |
| small stack. */ |
| if (!(cp->Pipe[i].Waker.Thread = CreateThread( |
| 0, 1024, kwsysProcessPipeThreadWake, &cp->Pipe[i], 0, &dummy))) { |
| kwsysProcess_Delete(cp); |
| return 0; |
| } |
| } |
| for (i = 0; i < 3; ++i) { |
| cp->PipeChildStd[i] = INVALID_HANDLE_VALUE; |
| } |
| |
| return cp; |
| } |
| |
| void kwsysProcess_Delete(kwsysProcess* cp) |
| { |
| int i; |
| |
| /* Make sure we have an instance. */ |
| if (!cp) { |
| return; |
| } |
| |
| /* If the process is executing, wait for it to finish. */ |
| if (cp->State == kwsysProcess_State_Executing) { |
| if (cp->Detached) { |
| kwsysProcess_Disown(cp); |
| } else { |
| kwsysProcess_WaitForExit(cp, 0); |
| } |
| } |
| |
| /* We are deleting the kwsysProcess instance. */ |
| cp->Deleting = 1; |
| |
| /* Terminate each of the threads. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| /* Terminate this reading thread. */ |
| if (cp->Pipe[i].Reader.Thread) { |
| /* Signal the thread we are ready for it. It will terminate |
| immediately since Deleting is set. */ |
| ReleaseSemaphore(cp->Pipe[i].Reader.Ready, 1, 0); |
| |
| /* Wait for the thread to exit. */ |
| WaitForSingleObject(cp->Pipe[i].Reader.Thread, INFINITE); |
| |
| /* Close the handle to the thread. */ |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Reader.Thread); |
| } |
| |
| /* Terminate this waking thread. */ |
| if (cp->Pipe[i].Waker.Thread) { |
| /* Signal the thread we are ready for it. It will terminate |
| immediately since Deleting is set. */ |
| ReleaseSemaphore(cp->Pipe[i].Waker.Ready, 1, 0); |
| |
| /* Wait for the thread to exit. */ |
| WaitForSingleObject(cp->Pipe[i].Waker.Thread, INFINITE); |
| |
| /* Close the handle to the thread. */ |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Waker.Thread); |
| } |
| |
| /* Cleanup the pipe's semaphores. */ |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Reader.Ready); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Reader.Go); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Reader.Reset); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Waker.Ready); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Waker.Go); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Waker.Reset); |
| } |
| |
| /* Close the shared semaphores. */ |
| kwsysProcessCleanupHandle(&cp->SharedIndexMutex); |
| kwsysProcessCleanupHandle(&cp->Full); |
| |
| /* Free memory. */ |
| kwsysProcess_SetCommand(cp, 0); |
| kwsysProcess_SetWorkingDirectory(cp, 0); |
| kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDIN, 0); |
| kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDOUT, 0); |
| kwsysProcess_SetPipeFile(cp, kwsysProcess_Pipe_STDERR, 0); |
| free(cp->CommandExitCodes); |
| free(cp->ProcessResults); |
| free(cp); |
| } |
| |
| int kwsysProcess_SetCommand(kwsysProcess* cp, char const* const* command) |
| { |
| int i; |
| if (!cp) { |
| return 0; |
| } |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| free(cp->Commands[i]); |
| } |
| cp->NumberOfCommands = 0; |
| if (cp->Commands) { |
| free(cp->Commands); |
| cp->Commands = 0; |
| } |
| if (command) { |
| return kwsysProcess_AddCommand(cp, command); |
| } |
| return 1; |
| } |
| |
| int kwsysProcess_AddCommand(kwsysProcess* cp, char const* const* command) |
| { |
| int newNumberOfCommands; |
| wchar_t** newCommands; |
| |
| /* Make sure we have a command to add. */ |
| if (!cp || !command || !*command) { |
| return 0; |
| } |
| |
| /* Allocate a new array for command pointers. */ |
| newNumberOfCommands = cp->NumberOfCommands + 1; |
| if (!(newCommands = |
| (wchar_t**)malloc(sizeof(wchar_t*) * newNumberOfCommands))) { |
| /* Out of memory. */ |
| return 0; |
| } |
| |
| /* Copy any existing commands into the new array. */ |
| { |
| int i; |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| newCommands[i] = cp->Commands[i]; |
| } |
| } |
| |
| if (cp->Verbatim) { |
| /* Copy the verbatim command line into the buffer. */ |
| newCommands[cp->NumberOfCommands] = kwsysEncoding_DupToWide(*command); |
| } else { |
| /* Encode the arguments so CommandLineToArgvW can decode |
| them from the command line string in the child. */ |
| char buffer[32768]; /* CreateProcess max command-line length. */ |
| char* end = buffer + sizeof(buffer); |
| char* out = buffer; |
| char const* const* a; |
| for (a = command; *a; ++a) { |
| int quote = !**a; /* Quote the empty string. */ |
| int slashes = 0; |
| char const* c; |
| if (a != command && out != end) { |
| *out++ = ' '; |
| } |
| for (c = *a; !quote && *c; ++c) { |
| quote = (*c == ' ' || *c == '\t'); |
| } |
| if (quote && out != end) { |
| *out++ = '"'; |
| } |
| for (c = *a; *c; ++c) { |
| if (*c == '\\') { |
| ++slashes; |
| } else { |
| if (*c == '"') { |
| // Add n+1 backslashes to total 2n+1 before internal '"'. |
| while (slashes-- >= 0 && out != end) { |
| *out++ = '\\'; |
| } |
| } |
| slashes = 0; |
| } |
| if (out != end) { |
| *out++ = *c; |
| } |
| } |
| if (quote) { |
| // Add n backslashes to total 2n before ending '"'. |
| while (slashes-- > 0 && out != end) { |
| *out++ = '\\'; |
| } |
| if (out != end) { |
| *out++ = '"'; |
| } |
| } |
| } |
| if (out != end) { |
| *out = '\0'; |
| newCommands[cp->NumberOfCommands] = kwsysEncoding_DupToWide(buffer); |
| } else { |
| newCommands[cp->NumberOfCommands] = 0; |
| } |
| } |
| if (!newCommands[cp->NumberOfCommands]) { |
| /* Out of memory or command line too long. */ |
| free(newCommands); |
| return 0; |
| } |
| |
| /* Save the new array of commands. */ |
| free(cp->Commands); |
| cp->Commands = newCommands; |
| cp->NumberOfCommands = newNumberOfCommands; |
| return 1; |
| } |
| |
| void kwsysProcess_SetTimeout(kwsysProcess* cp, double timeout) |
| { |
| if (!cp) { |
| return; |
| } |
| cp->Timeout = timeout; |
| if (cp->Timeout < 0) { |
| cp->Timeout = 0; |
| } |
| // Force recomputation of TimeoutTime. |
| cp->TimeoutTime.QuadPart = -1; |
| } |
| |
| int kwsysProcess_SetWorkingDirectory(kwsysProcess* cp, const char* dir) |
| { |
| if (!cp) { |
| return 0; |
| } |
| if (cp->WorkingDirectory) { |
| free(cp->WorkingDirectory); |
| cp->WorkingDirectory = 0; |
| } |
| if (dir && dir[0]) { |
| wchar_t* wdir = kwsysEncoding_DupToWide(dir); |
| /* We must convert the working directory to a full path. */ |
| DWORD length = GetFullPathNameW(wdir, 0, 0, 0); |
| if (length > 0) { |
| wchar_t* work_dir = malloc(length * sizeof(wchar_t)); |
| if (!work_dir) { |
| free(wdir); |
| return 0; |
| } |
| if (!GetFullPathNameW(wdir, length, work_dir, 0)) { |
| free(work_dir); |
| free(wdir); |
| return 0; |
| } |
| cp->WorkingDirectory = work_dir; |
| } |
| free(wdir); |
| } |
| return 1; |
| } |
| |
| int kwsysProcess_SetPipeFile(kwsysProcess* cp, int pipe, const char* file) |
| { |
| char** pfile; |
| if (!cp) { |
| return 0; |
| } |
| switch (pipe) { |
| case kwsysProcess_Pipe_STDIN: |
| pfile = &cp->PipeFileSTDIN; |
| break; |
| case kwsysProcess_Pipe_STDOUT: |
| pfile = &cp->PipeFileSTDOUT; |
| break; |
| case kwsysProcess_Pipe_STDERR: |
| pfile = &cp->PipeFileSTDERR; |
| break; |
| default: |
| return 0; |
| } |
| if (*pfile) { |
| free(*pfile); |
| *pfile = 0; |
| } |
| if (file) { |
| *pfile = strdup(file); |
| if (!*pfile) { |
| return 0; |
| } |
| } |
| |
| /* If we are redirecting the pipe, do not share it or use a native |
| pipe. */ |
| if (*pfile) { |
| kwsysProcess_SetPipeNative(cp, pipe, 0); |
| kwsysProcess_SetPipeShared(cp, pipe, 0); |
| } |
| |
| return 1; |
| } |
| |
| void kwsysProcess_SetPipeShared(kwsysProcess* cp, int pipe, int shared) |
| { |
| if (!cp) { |
| return; |
| } |
| |
| switch (pipe) { |
| case kwsysProcess_Pipe_STDIN: |
| cp->PipeSharedSTDIN = shared ? 1 : 0; |
| break; |
| case kwsysProcess_Pipe_STDOUT: |
| cp->PipeSharedSTDOUT = shared ? 1 : 0; |
| break; |
| case kwsysProcess_Pipe_STDERR: |
| cp->PipeSharedSTDERR = shared ? 1 : 0; |
| break; |
| default: |
| return; |
| } |
| |
| /* If we are sharing the pipe, do not redirect it to a file or use a |
| native pipe. */ |
| if (shared) { |
| kwsysProcess_SetPipeFile(cp, pipe, 0); |
| kwsysProcess_SetPipeNative(cp, pipe, 0); |
| } |
| } |
| |
| void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe, const HANDLE p[2]) |
| { |
| HANDLE* pPipeNative = 0; |
| |
| if (!cp) { |
| return; |
| } |
| |
| switch (pipe) { |
| case kwsysProcess_Pipe_STDIN: |
| pPipeNative = cp->PipeNativeSTDIN; |
| break; |
| case kwsysProcess_Pipe_STDOUT: |
| pPipeNative = cp->PipeNativeSTDOUT; |
| break; |
| case kwsysProcess_Pipe_STDERR: |
| pPipeNative = cp->PipeNativeSTDERR; |
| break; |
| default: |
| return; |
| } |
| |
| /* Copy the native pipe handles provided. */ |
| if (p) { |
| pPipeNative[0] = p[0]; |
| pPipeNative[1] = p[1]; |
| } else { |
| pPipeNative[0] = 0; |
| pPipeNative[1] = 0; |
| } |
| |
| /* If we are using a native pipe, do not share it or redirect it to |
| a file. */ |
| if (p) { |
| kwsysProcess_SetPipeFile(cp, pipe, 0); |
| kwsysProcess_SetPipeShared(cp, pipe, 0); |
| } |
| } |
| |
| int kwsysProcess_GetOption(kwsysProcess* cp, int optionId) |
| { |
| if (!cp) { |
| return 0; |
| } |
| |
| switch (optionId) { |
| case kwsysProcess_Option_Detach: |
| return cp->OptionDetach; |
| case kwsysProcess_Option_HideWindow: |
| return cp->HideWindow; |
| case kwsysProcess_Option_MergeOutput: |
| return cp->MergeOutput; |
| case kwsysProcess_Option_Verbatim: |
| return cp->Verbatim; |
| case kwsysProcess_Option_CreateProcessGroup: |
| return cp->CreateProcessGroup; |
| default: |
| return 0; |
| } |
| } |
| |
| void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value) |
| { |
| if (!cp) { |
| return; |
| } |
| |
| switch (optionId) { |
| case kwsysProcess_Option_Detach: |
| cp->OptionDetach = value; |
| break; |
| case kwsysProcess_Option_HideWindow: |
| cp->HideWindow = value; |
| break; |
| case kwsysProcess_Option_MergeOutput: |
| cp->MergeOutput = value; |
| break; |
| case kwsysProcess_Option_Verbatim: |
| cp->Verbatim = value; |
| break; |
| case kwsysProcess_Option_CreateProcessGroup: |
| cp->CreateProcessGroup = value; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| int kwsysProcess_GetState(kwsysProcess* cp) |
| { |
| return cp ? cp->State : kwsysProcess_State_Error; |
| } |
| |
| int kwsysProcess_GetExitException(kwsysProcess* cp) |
| { |
| return (cp && cp->ProcessResults && (cp->NumberOfCommands > 0)) |
| ? cp->ProcessResults[cp->NumberOfCommands - 1].ExitException |
| : kwsysProcess_Exception_Other; |
| } |
| |
| int kwsysProcess_GetExitValue(kwsysProcess* cp) |
| { |
| return (cp && cp->ProcessResults && (cp->NumberOfCommands > 0)) |
| ? cp->ProcessResults[cp->NumberOfCommands - 1].ExitValue |
| : -1; |
| } |
| |
| int kwsysProcess_GetExitCode(kwsysProcess* cp) |
| { |
| return (cp && cp->ProcessResults && (cp->NumberOfCommands > 0)) |
| ? cp->ProcessResults[cp->NumberOfCommands - 1].ExitCode |
| : 0; |
| } |
| |
| const char* kwsysProcess_GetErrorString(kwsysProcess* cp) |
| { |
| if (!cp) { |
| return "Process management structure could not be allocated"; |
| } else if (cp->State == kwsysProcess_State_Error) { |
| return cp->ErrorMessage; |
| } |
| return "Success"; |
| } |
| |
| const char* kwsysProcess_GetExceptionString(kwsysProcess* cp) |
| { |
| if (!(cp && cp->ProcessResults && (cp->NumberOfCommands > 0))) { |
| return "GetExceptionString called with NULL process management structure"; |
| } else if (cp->State == kwsysProcess_State_Exception) { |
| return cp->ProcessResults[cp->NumberOfCommands - 1].ExitExceptionString; |
| } |
| return "No exception"; |
| } |
| |
| /* the index should be in array bound. */ |
| #define KWSYSPE_IDX_CHK(RET) \ |
| if (!cp || idx >= cp->NumberOfCommands || idx < 0) { \ |
| KWSYSPE_DEBUG((stderr, "array index out of bound\n")); \ |
| return RET; \ |
| } |
| |
| int kwsysProcess_GetStateByIndex(kwsysProcess* cp, int idx) |
| { |
| KWSYSPE_IDX_CHK(kwsysProcess_State_Error) |
| return cp->ProcessResults[idx].State; |
| } |
| |
| int kwsysProcess_GetExitExceptionByIndex(kwsysProcess* cp, int idx) |
| { |
| KWSYSPE_IDX_CHK(kwsysProcess_Exception_Other) |
| return cp->ProcessResults[idx].ExitException; |
| } |
| |
| int kwsysProcess_GetExitValueByIndex(kwsysProcess* cp, int idx) |
| { |
| KWSYSPE_IDX_CHK(-1) |
| return cp->ProcessResults[idx].ExitValue; |
| } |
| |
| int kwsysProcess_GetExitCodeByIndex(kwsysProcess* cp, int idx) |
| { |
| KWSYSPE_IDX_CHK(-1) |
| return cp->CommandExitCodes[idx]; |
| } |
| |
| const char* kwsysProcess_GetExceptionStringByIndex(kwsysProcess* cp, int idx) |
| { |
| KWSYSPE_IDX_CHK("GetExceptionString called with NULL process management " |
| "structure or index out of bound") |
| if (cp->ProcessResults[idx].State == kwsysProcess_StateByIndex_Exception) { |
| return cp->ProcessResults[idx].ExitExceptionString; |
| } |
| return "No exception"; |
| } |
| |
| #undef KWSYSPE_IDX_CHK |
| |
| void kwsysProcess_Execute(kwsysProcess* cp) |
| { |
| int i; |
| |
| /* Do not execute a second time. */ |
| if (!cp || cp->State == kwsysProcess_State_Executing) { |
| return; |
| } |
| |
| /* Make sure we have something to run. */ |
| if (cp->NumberOfCommands < 1) { |
| strcpy(cp->ErrorMessage, "No command"); |
| cp->State = kwsysProcess_State_Error; |
| return; |
| } |
| |
| /* Initialize the control structure for a new process. */ |
| if (!kwsysProcessInitialize(cp)) { |
| strcpy(cp->ErrorMessage, "Out of memory"); |
| cp->State = kwsysProcess_State_Error; |
| return; |
| } |
| |
| /* Save the real working directory of this process and change to |
| the working directory for the child processes. This is needed |
| to make pipe file paths evaluate correctly. */ |
| if (cp->WorkingDirectory) { |
| if (!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength, |
| cp->RealWorkingDirectory)) { |
| kwsysProcessCleanup(cp, GetLastError()); |
| return; |
| } |
| SetCurrentDirectoryW(cp->WorkingDirectory); |
| } |
| |
| /* Setup the stdin pipe for the first process. */ |
| if (cp->PipeFileSTDIN) { |
| /* Create a handle to read a file for stdin. */ |
| wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN); |
| DWORD error; |
| cp->PipeChildStd[0] = |
| CreateFileW(wstdin, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, |
| OPEN_EXISTING, 0, 0); |
| error = GetLastError(); /* Check now in case free changes this. */ |
| free(wstdin); |
| if (cp->PipeChildStd[0] == INVALID_HANDLE_VALUE) { |
| kwsysProcessCleanup(cp, error); |
| return; |
| } |
| } else if (cp->PipeSharedSTDIN) { |
| /* Share this process's stdin with the child. */ |
| kwsysProcessSetupSharedPipe(STD_INPUT_HANDLE, &cp->PipeChildStd[0]); |
| } else if (cp->PipeNativeSTDIN[0]) { |
| /* Use the provided native pipe. */ |
| kwsysProcessSetupPipeNative(cp->PipeNativeSTDIN[0], &cp->PipeChildStd[0]); |
| } else { |
| /* Explicitly give the child no stdin. */ |
| cp->PipeChildStd[0] = INVALID_HANDLE_VALUE; |
| } |
| |
| /* Create the output pipe for the last process. |
| We always create this so the pipe thread can run even if we |
| do not end up giving the write end to the child below. */ |
| if (!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read, |
| &cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0)) { |
| kwsysProcessCleanup(cp, GetLastError()); |
| return; |
| } |
| |
| if (cp->PipeFileSTDOUT) { |
| /* Use a file for stdout. */ |
| DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1], |
| cp->PipeFileSTDOUT); |
| if (error) { |
| kwsysProcessCleanup(cp, error); |
| return; |
| } |
| } else if (cp->PipeSharedSTDOUT) { |
| /* Use the parent stdout. */ |
| kwsysProcessSetupSharedPipe(STD_OUTPUT_HANDLE, &cp->PipeChildStd[1]); |
| } else if (cp->PipeNativeSTDOUT[1]) { |
| /* Use the given handle for stdout. */ |
| kwsysProcessSetupPipeNative(cp->PipeNativeSTDOUT[1], &cp->PipeChildStd[1]); |
| } else { |
| /* Use our pipe for stdout. Duplicate the handle since our waker |
| thread will use the original. Do not make it inherited yet. */ |
| if (!DuplicateHandle(GetCurrentProcess(), |
| cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, |
| GetCurrentProcess(), &cp->PipeChildStd[1], 0, FALSE, |
| DUPLICATE_SAME_ACCESS)) { |
| kwsysProcessCleanup(cp, GetLastError()); |
| return; |
| } |
| } |
| |
| /* Create stderr pipe to be shared by all processes in the pipeline. |
| We always create this so the pipe thread can run even if we do not |
| end up giving the write end to the child below. */ |
| if (!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read, |
| &cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0)) { |
| kwsysProcessCleanup(cp, GetLastError()); |
| return; |
| } |
| |
| if (cp->PipeFileSTDERR) { |
| /* Use a file for stderr. */ |
| DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2], |
| cp->PipeFileSTDERR); |
| if (error) { |
| kwsysProcessCleanup(cp, error); |
| return; |
| } |
| } else if (cp->PipeSharedSTDERR) { |
| /* Use the parent stderr. */ |
| kwsysProcessSetupSharedPipe(STD_ERROR_HANDLE, &cp->PipeChildStd[2]); |
| } else if (cp->PipeNativeSTDERR[1]) { |
| /* Use the given handle for stderr. */ |
| kwsysProcessSetupPipeNative(cp->PipeNativeSTDERR[1], &cp->PipeChildStd[2]); |
| } else { |
| /* Use our pipe for stderr. Duplicate the handle since our waker |
| thread will use the original. Do not make it inherited yet. */ |
| if (!DuplicateHandle(GetCurrentProcess(), |
| cp->Pipe[KWSYSPE_PIPE_STDERR].Write, |
| GetCurrentProcess(), &cp->PipeChildStd[2], 0, FALSE, |
| DUPLICATE_SAME_ACCESS)) { |
| kwsysProcessCleanup(cp, GetLastError()); |
| return; |
| } |
| } |
| |
| /* Create the pipeline of processes. */ |
| { |
| /* Child startup control data. */ |
| kwsysProcessCreateInformation si; |
| HANDLE nextStdInput = cp->PipeChildStd[0]; |
| |
| /* Initialize startup info data. */ |
| ZeroMemory(&si, sizeof(si)); |
| si.StartupInfo.cb = sizeof(si.StartupInfo); |
| |
| /* Decide whether a child window should be shown. */ |
| si.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; |
| si.StartupInfo.wShowWindow = |
| (unsigned short)(cp->HideWindow ? SW_HIDE : SW_SHOWDEFAULT); |
| |
| /* Connect the child's output pipes to the threads. */ |
| si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; |
| |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| /* Setup the process's pipes. */ |
| si.hStdInput = nextStdInput; |
| if (i == cp->NumberOfCommands - 1) { |
| /* The last child gets the overall stdout. */ |
| nextStdInput = INVALID_HANDLE_VALUE; |
| si.hStdOutput = cp->PipeChildStd[1]; |
| } else { |
| /* Create a pipe to sit between the children. */ |
| HANDLE p[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; |
| if (!CreatePipe(&p[0], &p[1], 0, 0)) { |
| DWORD error = GetLastError(); |
| if (nextStdInput != cp->PipeChildStd[0]) { |
| kwsysProcessCleanupHandle(&nextStdInput); |
| } |
| kwsysProcessCleanup(cp, error); |
| return; |
| } |
| nextStdInput = p[0]; |
| si.hStdOutput = p[1]; |
| } |
| si.hStdError = |
| cp->MergeOutput ? cp->PipeChildStd[1] : cp->PipeChildStd[2]; |
| |
| { |
| DWORD error = kwsysProcessCreate(cp, i, &si); |
| |
| /* Close our copies of pipes used between children. */ |
| if (si.hStdInput != cp->PipeChildStd[0]) { |
| kwsysProcessCleanupHandle(&si.hStdInput); |
| } |
| if (si.hStdOutput != cp->PipeChildStd[1]) { |
| kwsysProcessCleanupHandle(&si.hStdOutput); |
| } |
| if (si.hStdError != cp->PipeChildStd[2] && !cp->MergeOutput) { |
| kwsysProcessCleanupHandle(&si.hStdError); |
| } |
| if (!error) { |
| cp->ProcessEvents[i + 1] = cp->ProcessInformation[i].hProcess; |
| } else { |
| if (nextStdInput != cp->PipeChildStd[0]) { |
| kwsysProcessCleanupHandle(&nextStdInput); |
| } |
| kwsysProcessCleanup(cp, error); |
| return; |
| } |
| } |
| } |
| } |
| |
| /* The parent process does not need the child's pipe ends. */ |
| for (i = 0; i < 3; ++i) { |
| kwsysProcessCleanupHandle(&cp->PipeChildStd[i]); |
| } |
| |
| /* Restore the working directory. */ |
| if (cp->RealWorkingDirectory) { |
| SetCurrentDirectoryW(cp->RealWorkingDirectory); |
| free(cp->RealWorkingDirectory); |
| cp->RealWorkingDirectory = 0; |
| } |
| |
| /* The timeout period starts now. */ |
| cp->StartTime = kwsysProcessTimeGetCurrent(); |
| cp->TimeoutTime = kwsysProcessTimeFromDouble(-1); |
| |
| /* All processes in the pipeline have been started in suspended |
| mode. Resume them all now. */ |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| ResumeThread(cp->ProcessInformation[i].hThread); |
| } |
| |
| /* ---- It is no longer safe to call kwsysProcessCleanup. ----- */ |
| /* Tell the pipe threads that a process has started. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| ReleaseSemaphore(cp->Pipe[i].Reader.Ready, 1, 0); |
| ReleaseSemaphore(cp->Pipe[i].Waker.Ready, 1, 0); |
| } |
| |
| /* We don't care about the children's main threads. */ |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); |
| } |
| |
| /* No pipe has reported data. */ |
| cp->CurrentIndex = KWSYSPE_PIPE_COUNT; |
| cp->PipesLeft = KWSYSPE_PIPE_COUNT; |
| |
| /* The process has now started. */ |
| cp->State = kwsysProcess_State_Executing; |
| cp->Detached = cp->OptionDetach; |
| } |
| |
| void kwsysProcess_Disown(kwsysProcess* cp) |
| { |
| int i; |
| |
| /* Make sure we are executing a detached process. */ |
| if (!cp || !cp->Detached || cp->State != kwsysProcess_State_Executing || |
| cp->TimeoutExpired || cp->Killed || cp->Terminated) { |
| return; |
| } |
| |
| /* Disable the reading threads. */ |
| kwsysProcessDisablePipeThreads(cp); |
| |
| /* Wait for all pipe threads to reset. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| WaitForSingleObject(cp->Pipe[i].Reader.Reset, INFINITE); |
| WaitForSingleObject(cp->Pipe[i].Waker.Reset, INFINITE); |
| } |
| |
| /* We will not wait for exit, so cleanup now. */ |
| kwsysProcessCleanup(cp, 0); |
| |
| /* The process has been disowned. */ |
| cp->State = kwsysProcess_State_Disowned; |
| } |
| |
| int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, |
| double* userTimeout) |
| { |
| kwsysProcessTime userStartTime; |
| kwsysProcessTime timeoutLength; |
| kwsysProcessTime timeoutTime; |
| DWORD timeout; |
| int user; |
| int done = 0; |
| int expired = 0; |
| int pipeId = kwsysProcess_Pipe_None; |
| DWORD w; |
| |
| /* Make sure we are executing a process. */ |
| if (!cp || cp->State != kwsysProcess_State_Executing || cp->Killed || |
| cp->TimeoutExpired) { |
| return kwsysProcess_Pipe_None; |
| } |
| |
| /* Record the time at which user timeout period starts. */ |
| 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 < KWSYSPE_PIPE_COUNT) { |
| KWSYSPE_DEBUG((stderr, "releasing reader %d\n", cp->CurrentIndex)); |
| ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); |
| cp->CurrentIndex = KWSYSPE_PIPE_COUNT; |
| } |
| |
| /* Setup a timeout if required. */ |
| if (kwsysProcessGetTimeoutLeft(&timeoutTime, user ? userTimeout : 0, |
| &timeoutLength)) { |
| /* Timeout has already expired. */ |
| expired = 1; |
| break; |
| } |
| if (timeoutTime.QuadPart < 0) { |
| timeout = INFINITE; |
| } else { |
| timeout = kwsysProcessTimeToDWORD(timeoutLength); |
| } |
| |
| /* Wait for a pipe's thread to signal or a process to terminate. */ |
| w = WaitForMultipleObjects(cp->ProcessEventsLength, cp->ProcessEvents, 0, |
| timeout); |
| if (w == WAIT_TIMEOUT) { |
| /* Timeout has expired. */ |
| 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 at the write end. Close the read end and |
| inform the wakeup thread it is done with this process. */ |
| kwsysProcessCleanupHandle(&cp->Pipe[cp->CurrentIndex].Read); |
| ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Waker.Go, 1, 0); |
| KWSYSPE_DEBUG((stderr, "wakeup %d\n", cp->CurrentIndex)); |
| --cp->PipesLeft; |
| } else if (data && length) { |
| /* Report this data. */ |
| *data = cp->Pipe[cp->CurrentIndex].DataBuffer; |
| *length = cp->Pipe[cp->CurrentIndex].DataLength; |
| switch (cp->CurrentIndex) { |
| case KWSYSPE_PIPE_STDOUT: |
| pipeId = kwsysProcess_Pipe_STDOUT; |
| break; |
| case KWSYSPE_PIPE_STDERR: |
| pipeId = kwsysProcess_Pipe_STDERR; |
| break; |
| } |
| done = 1; |
| } |
| } else { |
| /* A process has terminated. */ |
| kwsysProcessDestroy(cp, w - WAIT_OBJECT_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_Pipe_Timeout; |
| } else { |
| /* The process timeout has expired. Kill the child now. */ |
| KWSYSPE_DEBUG((stderr, "killing child because timeout expired\n")); |
| kwsysProcess_Kill(cp); |
| cp->TimeoutExpired = 1; |
| cp->Killed = 0; |
| return kwsysProcess_Pipe_None; |
| } |
| } else { |
| /* The children have terminated and no more data are available. */ |
| return kwsysProcess_Pipe_None; |
| } |
| } |
| |
| int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) |
| { |
| int i; |
| int pipe; |
| |
| /* Make sure we are executing a process. */ |
| if (!cp || cp->State != kwsysProcess_State_Executing) { |
| return 1; |
| } |
| |
| /* Wait for the process to terminate. Ignore all data. */ |
| while ((pipe = kwsysProcess_WaitForData(cp, 0, 0, userTimeout)) > 0) { |
| if (pipe == kwsysProcess_Pipe_Timeout) { |
| /* The user timeout has expired. */ |
| return 0; |
| } |
| } |
| |
| KWSYSPE_DEBUG((stderr, "no more data\n")); |
| |
| /* When the last pipe closes in WaitForData, the loop terminates |
| without releasing the pipe's thread. Release it now. */ |
| if (cp->CurrentIndex < KWSYSPE_PIPE_COUNT) { |
| KWSYSPE_DEBUG((stderr, "releasing reader %d\n", cp->CurrentIndex)); |
| ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); |
| cp->CurrentIndex = KWSYSPE_PIPE_COUNT; |
| } |
| |
| /* Wait for all pipe threads to reset. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| KWSYSPE_DEBUG((stderr, "waiting reader reset %d\n", i)); |
| WaitForSingleObject(cp->Pipe[i].Reader.Reset, INFINITE); |
| KWSYSPE_DEBUG((stderr, "waiting waker reset %d\n", i)); |
| WaitForSingleObject(cp->Pipe[i].Waker.Reset, INFINITE); |
| } |
| |
| /* ---- It is now safe again to call kwsysProcessCleanup. ----- */ |
| /* Close all the pipes. */ |
| kwsysProcessCleanup(cp, 0); |
| |
| /* Determine the outcome. */ |
| if (cp->Killed) { |
| /* We killed the child. */ |
| cp->State = kwsysProcess_State_Killed; |
| } else if (cp->TimeoutExpired) { |
| /* The timeout expired. */ |
| cp->State = kwsysProcess_State_Expired; |
| } else { |
| /* The children exited. Report the outcome of the child processes. */ |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| cp->ProcessResults[i].ExitCode = cp->CommandExitCodes[i]; |
| if ((cp->ProcessResults[i].ExitCode & 0xF0000000) == 0xC0000000) { |
| /* Child terminated due to exceptional behavior. */ |
| cp->ProcessResults[i].State = kwsysProcess_StateByIndex_Exception; |
| cp->ProcessResults[i].ExitValue = 1; |
| kwsysProcessSetExitExceptionByIndex(cp, cp->ProcessResults[i].ExitCode, |
| i); |
| } else { |
| /* Child exited without exception. */ |
| cp->ProcessResults[i].State = kwsysProcess_StateByIndex_Exited; |
| cp->ProcessResults[i].ExitException = kwsysProcess_Exception_None; |
| cp->ProcessResults[i].ExitValue = cp->ProcessResults[i].ExitCode; |
| } |
| } |
| /* support legacy state status value */ |
| cp->State = cp->ProcessResults[cp->NumberOfCommands - 1].State; |
| } |
| |
| return 1; |
| } |
| |
| void kwsysProcess_Interrupt(kwsysProcess* cp) |
| { |
| int i; |
| /* Make sure we are executing a process. */ |
| if (!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired || |
| cp->Killed) { |
| KWSYSPE_DEBUG((stderr, "interrupt: child not executing\n")); |
| return; |
| } |
| |
| /* Skip actually interrupting the child if it has already terminated. */ |
| if (cp->Terminated) { |
| KWSYSPE_DEBUG((stderr, "interrupt: child already terminated\n")); |
| return; |
| } |
| |
| /* Interrupt the children. */ |
| if (cp->CreateProcessGroup) { |
| if (cp->ProcessInformation) { |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| /* Make sure the process handle isn't closed (e.g. from disowning). */ |
| if (cp->ProcessInformation[i].hProcess) { |
| /* The user created a process group for this process. The group ID |
| is the process ID for the original process in the group. Note |
| that we have to use Ctrl+Break: Ctrl+C is not allowed for process |
| groups. */ |
| GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, |
| cp->ProcessInformation[i].dwProcessId); |
| } |
| } |
| } |
| } else { |
| /* No process group was created. Kill our own process group... */ |
| GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0); |
| } |
| } |
| |
| void kwsysProcess_Kill(kwsysProcess* cp) |
| { |
| int i; |
| /* Make sure we are executing a process. */ |
| if (!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired || |
| cp->Killed) { |
| KWSYSPE_DEBUG((stderr, "kill: child not executing\n")); |
| return; |
| } |
| |
| /* Disable the reading threads. */ |
| KWSYSPE_DEBUG((stderr, "kill: disabling pipe threads\n")); |
| kwsysProcessDisablePipeThreads(cp); |
| |
| /* Skip actually killing the child if it has already terminated. */ |
| if (cp->Terminated) { |
| KWSYSPE_DEBUG((stderr, "kill: child already terminated\n")); |
| return; |
| } |
| |
| /* Kill the children. */ |
| cp->Killed = 1; |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId); |
| /* Remove from global list of processes and close handles. */ |
| kwsysProcessesRemove(cp->ProcessInformation[i].hProcess); |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); |
| } |
| |
| /* We are killing the children and ignoring all data. Do not wait |
| for them to exit. */ |
| } |
| |
| void kwsysProcess_KillPID(unsigned long process_id) |
| { |
| kwsysProcessKillTree((DWORD)process_id); |
| } |
| |
| /* |
| Function executed for each pipe's thread. Argument is a pointer to |
| the kwsysProcessPipeData instance for this thread. |
| */ |
| DWORD WINAPI kwsysProcessPipeThreadRead(LPVOID ptd) |
| { |
| kwsysProcessPipeData* td = (kwsysProcessPipeData*)ptd; |
| kwsysProcess* cp = td->Process; |
| |
| /* Wait for a process to be ready. */ |
| while ((WaitForSingleObject(td->Reader.Ready, INFINITE), !cp->Deleting)) { |
| /* Read output from the process for this thread's pipe. */ |
| kwsysProcessPipeThreadReadPipe(cp, td); |
| |
| /* Signal the main thread we have reset for a new process. */ |
| ReleaseSemaphore(td->Reader.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 ((KWSYSPE_DEBUG((stderr, "wait for read %d\n", td->Index)), |
| WaitForSingleObject(td->Reader.Go, INFINITE), !td->Closed)) { |
| KWSYSPE_DEBUG((stderr, "reading %d\n", td->Index)); |
| |
| /* Read data from the pipe. This may block until data are available. */ |
| if (!ReadFile(td->Read, td->DataBuffer, KWSYSPE_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; |
| KWSYSPE_DEBUG((stderr, "read closed %d\n", td->Index)); |
| } |
| |
| KWSYSPE_DEBUG((stderr, "read %d\n", td->Index)); |
| |
| /* Wait for our turn to be handled by the main thread. */ |
| WaitForSingleObject(cp->SharedIndexMutex, INFINITE); |
| |
| KWSYSPE_DEBUG((stderr, "reporting read %d\n", td->Index)); |
| |
| /* Tell the main thread we have something to report. */ |
| cp->SharedIndex = td->Index; |
| ReleaseSemaphore(cp->Full, 1, 0); |
| } |
| |
| /* We were signalled to exit with our buffer empty. Reset the |
| mutex for a new process. */ |
| KWSYSPE_DEBUG((stderr, "self releasing reader %d\n", td->Index)); |
| ReleaseSemaphore(td->Reader.Go, 1, 0); |
| } |
| |
| /* |
| Function executed for each pipe's thread. Argument is a pointer to |
| the kwsysProcessPipeData instance for this thread. |
| */ |
| DWORD WINAPI kwsysProcessPipeThreadWake(LPVOID ptd) |
| { |
| kwsysProcessPipeData* td = (kwsysProcessPipeData*)ptd; |
| kwsysProcess* cp = td->Process; |
| |
| /* Wait for a process to be ready. */ |
| while ((WaitForSingleObject(td->Waker.Ready, INFINITE), !cp->Deleting)) { |
| /* Wait for a possible wakeup. */ |
| kwsysProcessPipeThreadWakePipe(cp, td); |
| |
| /* Signal the main thread we have reset for a new process. */ |
| ReleaseSemaphore(td->Waker.Reset, 1, 0); |
| } |
| return 0; |
| } |
| |
| /* |
| Function called in each pipe's thread to handle reading thread |
| wakeup for one execution of a subprocess. |
| */ |
| void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp, kwsysProcessPipeData* td) |
| { |
| (void)cp; |
| |
| /* Wait for a possible wake command. */ |
| KWSYSPE_DEBUG((stderr, "wait for wake %d\n", td->Index)); |
| WaitForSingleObject(td->Waker.Go, INFINITE); |
| KWSYSPE_DEBUG((stderr, "waking %d\n", td->Index)); |
| |
| /* If the pipe is not closed, we need to wake up the reading thread. */ |
| if (!td->Closed) { |
| DWORD dummy; |
| KWSYSPE_DEBUG((stderr, "waker %d writing byte\n", td->Index)); |
| WriteFile(td->Write, "", 1, &dummy, 0); |
| KWSYSPE_DEBUG((stderr, "waker %d wrote byte\n", td->Index)); |
| } |
| } |
| |
| /* Initialize a process control structure for kwsysProcess_Execute. */ |
| int kwsysProcessInitialize(kwsysProcess* cp) |
| { |
| int i; |
| /* Reset internal status flags. */ |
| cp->TimeoutExpired = 0; |
| cp->Terminated = 0; |
| cp->Killed = 0; |
| |
| free(cp->ProcessResults); |
| /* Allocate process result information for each process. */ |
| cp->ProcessResults = (kwsysProcessResults*)malloc( |
| sizeof(kwsysProcessResults) * (cp->NumberOfCommands)); |
| if (!cp->ProcessResults) { |
| return 0; |
| } |
| ZeroMemory(cp->ProcessResults, |
| sizeof(kwsysProcessResults) * cp->NumberOfCommands); |
| for (i = 0; i < cp->NumberOfCommands; i++) { |
| cp->ProcessResults[i].ExitException = kwsysProcess_Exception_None; |
| cp->ProcessResults[i].State = kwsysProcess_StateByIndex_Starting; |
| cp->ProcessResults[i].ExitCode = 1; |
| cp->ProcessResults[i].ExitValue = 1; |
| strcpy(cp->ProcessResults[i].ExitExceptionString, "No exception"); |
| } |
| |
| /* Allocate process information for each process. */ |
| free(cp->ProcessInformation); |
| cp->ProcessInformation = (PROCESS_INFORMATION*)malloc( |
| sizeof(PROCESS_INFORMATION) * cp->NumberOfCommands); |
| if (!cp->ProcessInformation) { |
| return 0; |
| } |
| ZeroMemory(cp->ProcessInformation, |
| sizeof(PROCESS_INFORMATION) * cp->NumberOfCommands); |
| free(cp->CommandExitCodes); |
| cp->CommandExitCodes = (DWORD*)malloc(sizeof(DWORD) * cp->NumberOfCommands); |
| if (!cp->CommandExitCodes) { |
| return 0; |
| } |
| ZeroMemory(cp->CommandExitCodes, sizeof(DWORD) * cp->NumberOfCommands); |
| |
| /* Allocate event wait array. The first event is cp->Full, the rest |
| are the process termination events. */ |
| cp->ProcessEvents = |
| (PHANDLE)malloc(sizeof(HANDLE) * (cp->NumberOfCommands + 1)); |
| if (!cp->ProcessEvents) { |
| return 0; |
| } |
| ZeroMemory(cp->ProcessEvents, sizeof(HANDLE) * (cp->NumberOfCommands + 1)); |
| cp->ProcessEvents[0] = cp->Full; |
| cp->ProcessEventsLength = cp->NumberOfCommands + 1; |
| |
| /* Allocate space to save the real working directory of this process. */ |
| if (cp->WorkingDirectory) { |
| cp->RealWorkingDirectoryLength = GetCurrentDirectoryW(0, 0); |
| if (cp->RealWorkingDirectoryLength > 0) { |
| cp->RealWorkingDirectory = |
| malloc(cp->RealWorkingDirectoryLength * sizeof(wchar_t)); |
| if (!cp->RealWorkingDirectory) { |
| return 0; |
| } |
| } |
| } |
| { |
| for (i = 0; i < 3; ++i) { |
| cp->PipeChildStd[i] = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static DWORD kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn) |
| { |
| DWORD flags; |
| |
| /* Check whether the handle is valid for this process. */ |
| if (in != INVALID_HANDLE_VALUE && GetHandleInformation(in, &flags)) { |
| /* Use the handle as-is if it is already inherited. */ |
| if (flags & HANDLE_FLAG_INHERIT) { |
| *out = in; |
| return ERROR_SUCCESS; |
| } |
| |
| /* Create an inherited copy of this handle. */ |
| if (DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), out, 0, |
| TRUE, DUPLICATE_SAME_ACCESS)) { |
| return ERROR_SUCCESS; |
| } else { |
| return GetLastError(); |
| } |
| } else { |
| /* The given handle is not valid for this process. Some child |
| processes may break if they do not have a valid standard handle, |
| so open NUL to give to the child. */ |
| SECURITY_ATTRIBUTES sa; |
| ZeroMemory(&sa, sizeof(sa)); |
| sa.nLength = (DWORD)sizeof(sa); |
| sa.bInheritHandle = 1; |
| *out = CreateFileW( |
| L"NUL", |
| (isStdIn ? GENERIC_READ : (GENERIC_WRITE | FILE_READ_ATTRIBUTES)), |
| FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0); |
| return (*out != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError(); |
| } |
| } |
| |
| DWORD kwsysProcessCreate(kwsysProcess* cp, int index, |
| kwsysProcessCreateInformation* si) |
| { |
| DWORD creationFlags; |
| DWORD error = ERROR_SUCCESS; |
| |
| /* Check if we are currently exiting. */ |
| if (!kwsysTryEnterCreateProcessSection()) { |
| /* The Ctrl handler is currently working on exiting our process. Rather |
| than return an error code, which could cause incorrect conclusions to be |
| reached by the caller, we simply hang. (For example, a CMake try_run |
| configure step might cause the project to configure wrong.) */ |
| Sleep(INFINITE); |
| } |
| |
| /* Create the child in a suspended state so we can wait until all |
| children have been created before running any one. */ |
| creationFlags = CREATE_SUSPENDED; |
| if (cp->CreateProcessGroup) { |
| creationFlags |= CREATE_NEW_PROCESS_GROUP; |
| } |
| |
| /* Create inherited copies of the handles. */ |
| (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput, |
| si->hStdInput, 1)) || |
| (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput, |
| si->hStdOutput, 0)) || |
| (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError, |
| si->hStdError, 0)) || |
| /* Create the process. */ |
| (!CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, creationFlags, 0, 0, |
| &si->StartupInfo, &cp->ProcessInformation[index]) && |
| (error = GetLastError())); |
| |
| /* Close the inherited copies of the handles. */ |
| if (si->StartupInfo.hStdInput != si->hStdInput) { |
| kwsysProcessCleanupHandle(&si->StartupInfo.hStdInput); |
| } |
| if (si->StartupInfo.hStdOutput != si->hStdOutput) { |
| kwsysProcessCleanupHandle(&si->StartupInfo.hStdOutput); |
| } |
| if (si->StartupInfo.hStdError != si->hStdError) { |
| kwsysProcessCleanupHandle(&si->StartupInfo.hStdError); |
| } |
| |
| /* Add the process to the global list of processes. */ |
| if (!error && |
| !kwsysProcessesAdd(cp->ProcessInformation[index].hProcess, |
| cp->ProcessInformation[index].dwProcessId, |
| cp->CreateProcessGroup)) { |
| /* This failed for some reason. Kill the suspended process. */ |
| TerminateProcess(cp->ProcessInformation[index].hProcess, 1); |
| /* And clean up... */ |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hThread); |
| strcpy(cp->ErrorMessage, "kwsysProcessesAdd function failed"); |
| error = ERROR_NOT_ENOUGH_MEMORY; /* Most likely reason. */ |
| } |
| |
| /* If the console Ctrl handler is waiting for us, this will release it... */ |
| kwsysLeaveCreateProcessSection(); |
| return error; |
| } |
| |
| void kwsysProcessDestroy(kwsysProcess* cp, int event) |
| { |
| int i; |
| int index; |
| |
| /* Find the process index for the termination event. */ |
| for (index = 0; index < cp->NumberOfCommands; ++index) { |
| if (cp->ProcessInformation[index].hProcess == cp->ProcessEvents[event]) { |
| break; |
| } |
| } |
| |
| /* Check the exit code of the process. */ |
| GetExitCodeProcess(cp->ProcessInformation[index].hProcess, |
| &cp->CommandExitCodes[index]); |
| |
| /* Remove from global list of processes. */ |
| kwsysProcessesRemove(cp->ProcessInformation[index].hProcess); |
| |
| /* Close the process handle for the terminated process. */ |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); |
| |
| /* Remove the process from the available events. */ |
| cp->ProcessEventsLength -= 1; |
| for (i = event; i < cp->ProcessEventsLength; ++i) { |
| cp->ProcessEvents[i] = cp->ProcessEvents[i + 1]; |
| } |
| |
| /* Check if all processes have terminated. */ |
| if (cp->ProcessEventsLength == 1) { |
| cp->Terminated = 1; |
| |
| /* Close our copies of the pipe write handles so the pipe threads |
| can detect end-of-data. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| /* TODO: If the child created its own child (our grandchild) |
| which inherited a copy of the pipe write-end then the pipe |
| may not close and we will still need the waker write pipe. |
| However we still want to be able to detect end-of-data in the |
| normal case. The reader thread will have to switch to using |
| PeekNamedPipe to read the last bit of data from the pipe |
| without blocking. This is equivalent to using a non-blocking |
| read on posix. */ |
| KWSYSPE_DEBUG((stderr, "closing wakeup write %d\n", i)); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Write); |
| } |
| } |
| } |
| |
| DWORD kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name) |
| { |
| HANDLE fout; |
| wchar_t* wname; |
| DWORD error; |
| if (!name) { |
| return ERROR_INVALID_PARAMETER; |
| } |
| |
| /* Close the existing handle. */ |
| kwsysProcessCleanupHandle(phandle); |
| |
| /* Create a handle to write a file for the pipe. */ |
| wname = kwsysEncoding_DupToWide(name); |
| fout = |
| CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); |
| error = GetLastError(); |
| free(wname); |
| if (fout == INVALID_HANDLE_VALUE) { |
| return error; |
| } |
| |
| /* Assign the replacement handle. */ |
| *phandle = fout; |
| return ERROR_SUCCESS; |
| } |
| |
| void kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle) |
| { |
| /* Close the existing handle. */ |
| kwsysProcessCleanupHandle(handle); |
| /* Store the new standard handle. */ |
| *handle = GetStdHandle(nStdHandle); |
| } |
| |
| void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle) |
| { |
| /* Close the existing handle. */ |
| kwsysProcessCleanupHandle(handle); |
| /* Store the new given handle. */ |
| *handle = native; |
| } |
| |
| /* Close the given handle if it is open. Reset its value to 0. */ |
| void kwsysProcessCleanupHandle(PHANDLE h) |
| { |
| if (h && *h && *h != INVALID_HANDLE_VALUE && |
| *h != GetStdHandle(STD_INPUT_HANDLE) && |
| *h != GetStdHandle(STD_OUTPUT_HANDLE) && |
| *h != GetStdHandle(STD_ERROR_HANDLE)) { |
| CloseHandle(*h); |
| *h = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| /* Close all handles created by kwsysProcess_Execute. */ |
| void kwsysProcessCleanup(kwsysProcess* cp, DWORD error) |
| { |
| int i; |
| /* If this is an error case, report the error. */ |
| if (error) { |
| /* Construct an error message if one has not been provided already. */ |
| if (cp->ErrorMessage[0] == 0) { |
| /* Format the error message. */ |
| wchar_t err_msg[KWSYSPE_PIPE_BUFFER_SIZE]; |
| DWORD length = FormatMessageW( |
| FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_msg, |
| KWSYSPE_PIPE_BUFFER_SIZE, 0); |
| if (length < 1) { |
| /* FormatMessage failed. Use a default message. */ |
| _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, |
| "Process execution failed with error 0x%X. " |
| "FormatMessage failed with error 0x%X", |
| error, GetLastError()); |
| } |
| if (!WideCharToMultiByte(CP_UTF8, 0, err_msg, -1, cp->ErrorMessage, |
| KWSYSPE_PIPE_BUFFER_SIZE, NULL, NULL)) { |
| /* WideCharToMultiByte failed. Use a default message. */ |
| _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE, |
| "Process execution failed with error 0x%X. " |
| "WideCharToMultiByte failed with error 0x%X", |
| error, GetLastError()); |
| } |
| } |
| |
| /* Remove trailing period and newline, if any. */ |
| kwsysProcessCleanErrorMessage(cp); |
| |
| /* Set the error state. */ |
| cp->State = kwsysProcess_State_Error; |
| |
| /* Cleanup any processes already started in a suspended state. */ |
| if (cp->ProcessInformation) { |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| if (cp->ProcessInformation[i].hProcess) { |
| TerminateProcess(cp->ProcessInformation[i].hProcess, 255); |
| WaitForSingleObject(cp->ProcessInformation[i].hProcess, INFINITE); |
| } |
| } |
| for (i = 0; i < cp->NumberOfCommands; ++i) { |
| /* Remove from global list of processes and close handles. */ |
| kwsysProcessesRemove(cp->ProcessInformation[i].hProcess); |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); |
| kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); |
| } |
| } |
| |
| /* Restore the working directory. */ |
| if (cp->RealWorkingDirectory) { |
| SetCurrentDirectoryW(cp->RealWorkingDirectory); |
| } |
| } |
| |
| /* Free memory. */ |
| if (cp->ProcessInformation) { |
| free(cp->ProcessInformation); |
| cp->ProcessInformation = 0; |
| } |
| if (cp->ProcessEvents) { |
| free(cp->ProcessEvents); |
| cp->ProcessEvents = 0; |
| } |
| if (cp->RealWorkingDirectory) { |
| free(cp->RealWorkingDirectory); |
| cp->RealWorkingDirectory = 0; |
| } |
| |
| /* Close each pipe. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Write); |
| kwsysProcessCleanupHandle(&cp->Pipe[i].Read); |
| cp->Pipe[i].Closed = 0; |
| } |
| for (i = 0; i < 3; ++i) { |
| kwsysProcessCleanupHandle(&cp->PipeChildStd[i]); |
| } |
| } |
| |
| void kwsysProcessCleanErrorMessage(kwsysProcess* cp) |
| { |
| /* Remove trailing period and newline, if any. */ |
| size_t length = strlen(cp->ErrorMessage); |
| if (cp->ErrorMessage[length - 1] == '\n') { |
| cp->ErrorMessage[length - 1] = 0; |
| --length; |
| if (length > 0 && cp->ErrorMessage[length - 1] == '\r') { |
| cp->ErrorMessage[length - 1] = 0; |
| --length; |
| } |
| } |
| if (length > 0 && cp->ErrorMessage[length - 1] == '.') { |
| cp->ErrorMessage[length - 1] = 0; |
| } |
| } |
| |
| /* 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 (timeoutTime->QuadPart < 0 || |
| kwsysProcessTimeLess(userTimeoutTime, *timeoutTime)) { |
| *timeoutTime = userTimeoutTime; |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| /* Get the length of time before the given timeout time arrives. |
| Returns 1 if the time has already arrived, and 0 otherwise. */ |
| int kwsysProcessGetTimeoutLeft(kwsysProcessTime* timeoutTime, |
| double* userTimeout, |
| 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 && userTimeout && *userTimeout <= 0) { |
| /* Caller has explicitly requested a zero timeout. */ |
| timeoutLength->QuadPart = 0; |
| } |
| |
| 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; |
| } |
| |
| #define KWSYSPE_CASE(type, str) \ |
| cp->ProcessResults[idx].ExitException = kwsysProcess_Exception_##type; \ |
| strcpy(cp->ProcessResults[idx].ExitExceptionString, str) |
| static void kwsysProcessSetExitExceptionByIndex(kwsysProcess* cp, int code, |
| int idx) |
| { |
| switch (code) { |
| case STATUS_CONTROL_C_EXIT: |
| KWSYSPE_CASE(Interrupt, "User interrupt"); |
| break; |
| |
| case STATUS_FLOAT_DENORMAL_OPERAND: |
| KWSYSPE_CASE(Numerical, "Floating-point exception (denormal operand)"); |
| break; |
| case STATUS_FLOAT_DIVIDE_BY_ZERO: |
| KWSYSPE_CASE(Numerical, "Divide-by-zero"); |
| break; |
| case STATUS_FLOAT_INEXACT_RESULT: |
| KWSYSPE_CASE(Numerical, "Floating-point exception (inexact result)"); |
| break; |
| case STATUS_FLOAT_INVALID_OPERATION: |
| KWSYSPE_CASE(Numerical, "Invalid floating-point operation"); |
| break; |
| case STATUS_FLOAT_OVERFLOW: |
| KWSYSPE_CASE(Numerical, "Floating-point overflow"); |
| break; |
| case STATUS_FLOAT_STACK_CHECK: |
| KWSYSPE_CASE(Numerical, "Floating-point stack check failed"); |
| break; |
| case STATUS_FLOAT_UNDERFLOW: |
| KWSYSPE_CASE(Numerical, "Floating-point underflow"); |
| break; |
| #ifdef STATUS_FLOAT_MULTIPLE_FAULTS |
| case STATUS_FLOAT_MULTIPLE_FAULTS: |
| KWSYSPE_CASE(Numerical, "Floating-point exception (multiple faults)"); |
| break; |
| #endif |
| #ifdef STATUS_FLOAT_MULTIPLE_TRAPS |
| case STATUS_FLOAT_MULTIPLE_TRAPS: |
| KWSYSPE_CASE(Numerical, "Floating-point exception (multiple traps)"); |
| break; |
| #endif |
| case STATUS_INTEGER_DIVIDE_BY_ZERO: |
| KWSYSPE_CASE(Numerical, "Integer divide-by-zero"); |
| break; |
| case STATUS_INTEGER_OVERFLOW: |
| KWSYSPE_CASE(Numerical, "Integer overflow"); |
| break; |
| |
| case STATUS_DATATYPE_MISALIGNMENT: |
| KWSYSPE_CASE(Fault, "Datatype misalignment"); |
| break; |
| case STATUS_ACCESS_VIOLATION: |
| KWSYSPE_CASE(Fault, "Access violation"); |
| break; |
| case STATUS_IN_PAGE_ERROR: |
| KWSYSPE_CASE(Fault, "In-page error"); |
| break; |
| case STATUS_INVALID_HANDLE: |
| KWSYSPE_CASE(Fault, "Invalid hanlde"); |
| break; |
| case STATUS_NONCONTINUABLE_EXCEPTION: |
| KWSYSPE_CASE(Fault, "Noncontinuable exception"); |
| break; |
| case STATUS_INVALID_DISPOSITION: |
| KWSYSPE_CASE(Fault, "Invalid disposition"); |
| break; |
| case STATUS_ARRAY_BOUNDS_EXCEEDED: |
| KWSYSPE_CASE(Fault, "Array bounds exceeded"); |
| break; |
| case STATUS_STACK_OVERFLOW: |
| KWSYSPE_CASE(Fault, "Stack overflow"); |
| break; |
| |
| case STATUS_ILLEGAL_INSTRUCTION: |
| KWSYSPE_CASE(Illegal, "Illegal instruction"); |
| break; |
| case STATUS_PRIVILEGED_INSTRUCTION: |
| KWSYSPE_CASE(Illegal, "Privileged instruction"); |
| break; |
| |
| case STATUS_NO_MEMORY: |
| default: |
| cp->ProcessResults[idx].ExitException = kwsysProcess_Exception_Other; |
| _snprintf(cp->ProcessResults[idx].ExitExceptionString, |
| KWSYSPE_PIPE_BUFFER_SIZE, "Exit code 0x%x\n", code); |
| break; |
| } |
| } |
| #undef KWSYSPE_CASE |
| |
| typedef struct kwsysProcess_List_s kwsysProcess_List; |
| static kwsysProcess_List* kwsysProcess_List_New(void); |
| static void kwsysProcess_List_Delete(kwsysProcess_List* self); |
| static int kwsysProcess_List_Update(kwsysProcess_List* self); |
| static int kwsysProcess_List_NextProcess(kwsysProcess_List* self); |
| static int kwsysProcess_List_GetCurrentProcessId(kwsysProcess_List* self); |
| static int kwsysProcess_List_GetCurrentParentId(kwsysProcess_List* self); |
| |
| /* Windows NT 4 API definitions. */ |
| #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) |
| typedef LONG NTSTATUS; |
| typedef LONG KPRIORITY; |
| typedef struct _UNICODE_STRING UNICODE_STRING; |
| struct _UNICODE_STRING |
| { |
| USHORT Length; |
| USHORT MaximumLength; |
| PWSTR Buffer; |
| }; |
| |
| /* The process information structure. Declare only enough to get |
| process identifiers. The rest may be ignored because we use the |
| NextEntryDelta to move through an array of instances. */ |
| typedef struct _SYSTEM_PROCESS_INFORMATION SYSTEM_PROCESS_INFORMATION; |
| typedef SYSTEM_PROCESS_INFORMATION* PSYSTEM_PROCESS_INFORMATION; |
| struct _SYSTEM_PROCESS_INFORMATION |
| { |
| ULONG NextEntryDelta; |
| ULONG ThreadCount; |
| ULONG Reserved1[6]; |
| LARGE_INTEGER CreateTime; |
| LARGE_INTEGER UserTime; |
| LARGE_INTEGER KernelTime; |
| UNICODE_STRING ProcessName; |
| KPRIORITY BasePriority; |
| ULONG ProcessId; |
| ULONG InheritedFromProcessId; |
| }; |
| |
| /* Toolhelp32 API definitions. */ |
| #define TH32CS_SNAPPROCESS 0x00000002 |
| #if defined(_WIN64) |
| typedef unsigned __int64 ProcessULONG_PTR; |
| #else |
| typedef unsigned long ProcessULONG_PTR; |
| #endif |
| typedef struct tagPROCESSENTRY32 PROCESSENTRY32; |
| typedef PROCESSENTRY32* LPPROCESSENTRY32; |
| struct tagPROCESSENTRY32 |
| { |
| DWORD dwSize; |
| DWORD cntUsage; |
| DWORD th32ProcessID; |
| ProcessULONG_PTR th32DefaultHeapID; |
| DWORD th32ModuleID; |
| DWORD cntThreads; |
| DWORD th32ParentProcessID; |
| LONG pcPriClassBase; |
| DWORD dwFlags; |
| char szExeFile[MAX_PATH]; |
| }; |
| |
| /* Windows API function types. */ |
| typedef HANDLE(WINAPI* CreateToolhelp32SnapshotType)(DWORD, DWORD); |
| typedef BOOL(WINAPI* Process32FirstType)(HANDLE, LPPROCESSENTRY32); |
| typedef BOOL(WINAPI* Process32NextType)(HANDLE, LPPROCESSENTRY32); |
| typedef NTSTATUS(WINAPI* ZwQuerySystemInformationType)(ULONG, PVOID, ULONG, |
| PULONG); |
| |
| static int kwsysProcess_List__New_NT4(kwsysProcess_List* self); |
| static int kwsysProcess_List__New_Snapshot(kwsysProcess_List* self); |
| static void kwsysProcess_List__Delete_NT4(kwsysProcess_List* self); |
| static void kwsysProcess_List__Delete_Snapshot(kwsysProcess_List* self); |
| static int kwsysProcess_List__Update_NT4(kwsysProcess_List* self); |
| static int kwsysProcess_List__Update_Snapshot(kwsysProcess_List* self); |
| static int kwsysProcess_List__Next_NT4(kwsysProcess_List* self); |
| static int kwsysProcess_List__Next_Snapshot(kwsysProcess_List* self); |
| static int kwsysProcess_List__GetProcessId_NT4(kwsysProcess_List* self); |
| static int kwsysProcess_List__GetProcessId_Snapshot(kwsysProcess_List* self); |
| static int kwsysProcess_List__GetParentId_NT4(kwsysProcess_List* self); |
| static int kwsysProcess_List__GetParentId_Snapshot(kwsysProcess_List* self); |
| |
| struct kwsysProcess_List_s |
| { |
| /* Implementation switches at runtime based on version of Windows. */ |
| int NT4; |
| |
| /* Implementation functions and data for NT 4. */ |
| ZwQuerySystemInformationType P_ZwQuerySystemInformation; |
| char* Buffer; |
| int BufferSize; |
| PSYSTEM_PROCESS_INFORMATION CurrentInfo; |
| |
| /* Implementation functions and data for other Windows versions. */ |
| CreateToolhelp32SnapshotType P_CreateToolhelp32Snapshot; |
| Process32FirstType P_Process32First; |
| Process32NextType P_Process32Next; |
| HANDLE Snapshot; |
| PROCESSENTRY32 CurrentEntry; |
| }; |
| |
| static kwsysProcess_List* kwsysProcess_List_New(void) |
| { |
| OSVERSIONINFO osv; |
| kwsysProcess_List* self; |
| |
| /* Allocate and initialize the list object. */ |
| if (!(self = (kwsysProcess_List*)malloc(sizeof(kwsysProcess_List)))) { |
| return 0; |
| } |
| memset(self, 0, sizeof(*self)); |
| |
| /* Select an implementation. */ |
| ZeroMemory(&osv, sizeof(osv)); |
| osv.dwOSVersionInfoSize = sizeof(osv); |
| #ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx |
| # pragma warning(push) |
| # ifdef __INTEL_COMPILER |
| # pragma warning(disable : 1478) |
| # elif defined __clang__ |
| # pragma clang diagnostic push |
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| # else |
| # pragma warning(disable : 4996) |
| # endif |
| #endif |
| GetVersionEx(&osv); |
| #ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx |
| # ifdef __clang__ |
| # pragma clang diagnostic pop |
| # else |
| # pragma warning(pop) |
| # endif |
| #endif |
| self->NT4 = |
| (osv.dwPlatformId == VER_PLATFORM_WIN32_NT && osv.dwMajorVersion < 5) ? 1 |
| : 0; |
| |
| /* Initialize the selected implementation. */ |
| if (!(self->NT4 ? kwsysProcess_List__New_NT4(self) |
| : kwsysProcess_List__New_Snapshot(self))) { |
| kwsysProcess_List_Delete(self); |
| return 0; |
| } |
| |
| /* Update to the current set of processes. */ |
| if (!kwsysProcess_List_Update(self)) { |
| kwsysProcess_List_Delete(self); |
| return 0; |
| } |
| return self; |
| } |
| |
| static void kwsysProcess_List_Delete(kwsysProcess_List* self) |
| { |
| if (self) { |
| if (self->NT4) { |
| kwsysProcess_List__Delete_NT4(self); |
| } else { |
| kwsysProcess_List__Delete_Snapshot(self); |
| } |
| free(self); |
| } |
| } |
| |
| static int kwsysProcess_List_Update(kwsysProcess_List* self) |
| { |
| return self ? (self->NT4 ? kwsysProcess_List__Update_NT4(self) |
| : kwsysProcess_List__Update_Snapshot(self)) |
| : 0; |
| } |
| |
| static int kwsysProcess_List_GetCurrentProcessId(kwsysProcess_List* self) |
| { |
| return self ? (self->NT4 ? kwsysProcess_List__GetProcessId_NT4(self) |
| : kwsysProcess_List__GetProcessId_Snapshot(self)) |
| : -1; |
| } |
| |
| static int kwsysProcess_List_GetCurrentParentId(kwsysProcess_List* self) |
| { |
| return self ? (self->NT4 ? kwsysProcess_List__GetParentId_NT4(self) |
| : kwsysProcess_List__GetParentId_Snapshot(self)) |
| : -1; |
| } |
| |
| static int kwsysProcess_List_NextProcess(kwsysProcess_List* self) |
| { |
| return (self ? (self->NT4 ? kwsysProcess_List__Next_NT4(self) |
| : kwsysProcess_List__Next_Snapshot(self)) |
| : 0); |
| } |
| |
| static int kwsysProcess_List__New_NT4(kwsysProcess_List* self) |
| { |
| /* Get a handle to the NT runtime module that should already be |
| loaded in this program. This does not actually increment the |
| reference count to the module so we do not need to close the |
| handle. */ |
| HMODULE hNT = GetModuleHandleW(L"ntdll.dll"); |
| if (hNT) { |
| /* Get pointers to the needed API functions. */ |
| self->P_ZwQuerySystemInformation = |
| ((ZwQuerySystemInformationType)GetProcAddress( |
| hNT, "ZwQuerySystemInformation")); |
| } |
| if (!self->P_ZwQuerySystemInformation) { |
| return 0; |
| } |
| |
| /* Allocate an initial process information buffer. */ |
| self->BufferSize = 32768; |
| self->Buffer = (char*)malloc(self->BufferSize); |
| return self->Buffer ? 1 : 0; |
| } |
| |
| static void kwsysProcess_List__Delete_NT4(kwsysProcess_List* self) |
| { |
| /* Free the process information buffer. */ |
| free(self->Buffer); |
| } |
| |
| static int kwsysProcess_List__Update_NT4(kwsysProcess_List* self) |
| { |
| self->CurrentInfo = 0; |
| for (;;) { |
| /* Query number 5 is for system process list. */ |
| NTSTATUS status = |
| self->P_ZwQuerySystemInformation(5, self->Buffer, self->BufferSize, 0); |
| if (status == STATUS_INFO_LENGTH_MISMATCH) { |
| /* The query requires a bigger buffer. */ |
| int newBufferSize = self->BufferSize * 2; |
| char* newBuffer = (char*)malloc(newBufferSize); |
| if (newBuffer) { |
| free(self->Buffer); |
| self->Buffer = newBuffer; |
| self->BufferSize = newBufferSize; |
| } else { |
| return 0; |
| } |
| } else if (status >= 0) { |
| /* The query succeeded. Initialize traversal of the process list. */ |
| self->CurrentInfo = (PSYSTEM_PROCESS_INFORMATION)self->Buffer; |
| return 1; |
| } else { |
| /* The query failed. */ |
| return 0; |
| } |
| } |
| } |
| |
| static int kwsysProcess_List__Next_NT4(kwsysProcess_List* self) |
| { |
| if (self->CurrentInfo) { |
| if (self->CurrentInfo->NextEntryDelta > 0) { |
| self->CurrentInfo = ((PSYSTEM_PROCESS_INFORMATION)( |
| (char*)self->CurrentInfo + self->CurrentInfo->NextEntryDelta)); |
| return 1; |
| } |
| self->CurrentInfo = 0; |
| } |
| return 0; |
| } |
| |
| static int kwsysProcess_List__GetProcessId_NT4(kwsysProcess_List* self) |
| { |
| return self->CurrentInfo ? self->CurrentInfo->ProcessId : -1; |
| } |
| |
| static int kwsysProcess_List__GetParentId_NT4(kwsysProcess_List* self) |
| { |
| return self->CurrentInfo ? self->CurrentInfo->InheritedFromProcessId : -1; |
| } |
| |
| static int kwsysProcess_List__New_Snapshot(kwsysProcess_List* self) |
| { |
| /* Get a handle to the Windows runtime module that should already be |
| loaded in this program. This does not actually increment the |
| reference count to the module so we do not need to close the |
| handle. */ |
| HMODULE hKernel = GetModuleHandleW(L"kernel32.dll"); |
| if (hKernel) { |
| self->P_CreateToolhelp32Snapshot = |
| ((CreateToolhelp32SnapshotType)GetProcAddress( |
| hKernel, "CreateToolhelp32Snapshot")); |
| self->P_Process32First = |
| ((Process32FirstType)GetProcAddress(hKernel, "Process32First")); |
| self->P_Process32Next = |
| ((Process32NextType)GetProcAddress(hKernel, "Process32Next")); |
| } |
| return (self->P_CreateToolhelp32Snapshot && self->P_Process32First && |
| self->P_Process32Next) |
| ? 1 |
| : 0; |
| } |
| |
| static void kwsysProcess_List__Delete_Snapshot(kwsysProcess_List* self) |
| { |
| if (self->Snapshot) { |
| CloseHandle(self->Snapshot); |
| } |
| } |
| |
| static int kwsysProcess_List__Update_Snapshot(kwsysProcess_List* self) |
| { |
| if (self->Snapshot) { |
| CloseHandle(self->Snapshot); |
| } |
| if (!(self->Snapshot = |
| self->P_CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0))) { |
| return 0; |
| } |
| ZeroMemory(&self->CurrentEntry, sizeof(self->CurrentEntry)); |
| self->CurrentEntry.dwSize = sizeof(self->CurrentEntry); |
| if (!self->P_Process32First(self->Snapshot, &self->CurrentEntry)) { |
| CloseHandle(self->Snapshot); |
| self->Snapshot = 0; |
| return 0; |
| } |
| return 1; |
| } |
| |
| static int kwsysProcess_List__Next_Snapshot(kwsysProcess_List* self) |
| { |
| if (self->Snapshot) { |
| if (self->P_Process32Next(self->Snapshot, &self->CurrentEntry)) { |
| return 1; |
| } |
| CloseHandle(self->Snapshot); |
| self->Snapshot = 0; |
| } |
| return 0; |
| } |
| |
| static int kwsysProcess_List__GetProcessId_Snapshot(kwsysProcess_List* self) |
| { |
| return self->Snapshot ? self->CurrentEntry.th32ProcessID : -1; |
| } |
| |
| static int kwsysProcess_List__GetParentId_Snapshot(kwsysProcess_List* self) |
| { |
| return self->Snapshot ? self->CurrentEntry.th32ParentProcessID : -1; |
| } |
| |
| static void kwsysProcessKill(DWORD pid) |
| { |
| HANDLE h = OpenProcess(PROCESS_TERMINATE, 0, pid); |
| if (h) { |
| TerminateProcess(h, 255); |
| WaitForSingleObject(h, INFINITE); |
| CloseHandle(h); |
| } |
| } |
| |
| static void kwsysProcessKillTree(int pid) |
| { |
| kwsysProcess_List* plist = kwsysProcess_List_New(); |
| kwsysProcessKill(pid); |
| if (plist) { |
| do { |
| if (kwsysProcess_List_GetCurrentParentId(plist) == pid) { |
| int ppid = kwsysProcess_List_GetCurrentProcessId(plist); |
| kwsysProcessKillTree(ppid); |
| } |
| } while (kwsysProcess_List_NextProcess(plist)); |
| kwsysProcess_List_Delete(plist); |
| } |
| } |
| |
| static void kwsysProcessDisablePipeThreads(kwsysProcess* cp) |
| { |
| int i; |
| |
| /* If data were just reported data, release the pipe's thread. */ |
| if (cp->CurrentIndex < KWSYSPE_PIPE_COUNT) { |
| KWSYSPE_DEBUG((stderr, "releasing reader %d\n", cp->CurrentIndex)); |
| ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); |
| cp->CurrentIndex = KWSYSPE_PIPE_COUNT; |
| } |
| |
| /* Wakeup all reading threads that are not on closed pipes. */ |
| for (i = 0; i < KWSYSPE_PIPE_COUNT; ++i) { |
| /* The wakeup threads will write one byte to the pipe write ends. |
| If there are no data in the pipe then this is enough to wakeup |
| the reading threads. If there are already data in the pipe |
| this may block. We cannot use PeekNamedPipe to check whether |
| there are data because an outside process might still be |
| writing data if we are disowning it. Also, PeekNamedPipe will |
| block if checking a pipe on which the reading thread is |
| currently calling ReadPipe. Therefore we need a separate |
| thread to call WriteFile. If it blocks, that is okay because |
| it will unblock when we close the read end and break the pipe |
| below. */ |
| if (cp->Pipe[i].Read) { |
| KWSYSPE_DEBUG((stderr, "releasing waker %d\n", i)); |
| ReleaseSemaphore(cp->Pipe[i].Waker.Go, 1, 0); |
| } |
| } |
| |
| /* Tell pipe threads to reset until we run another process. */ |
| while (cp->PipesLeft > 0) { |
| /* The waking threads will cause all reading threads to report. |
| Wait for the next one and save its index. */ |
| KWSYSPE_DEBUG((stderr, "waiting for reader\n")); |
| WaitForSingleObject(cp->Full, INFINITE); |
| cp->CurrentIndex = cp->SharedIndex; |
| ReleaseSemaphore(cp->SharedIndexMutex, 1, 0); |
| KWSYSPE_DEBUG((stderr, "got reader %d\n", cp->CurrentIndex)); |
| |
| /* We are done reading this pipe. Close its read handle. */ |
| cp->Pipe[cp->CurrentIndex].Closed = 1; |
| kwsysProcessCleanupHandle(&cp->Pipe[cp->CurrentIndex].Read); |
| --cp->PipesLeft; |
| |
| /* Tell the reading thread we are done with the data. It will |
| reset immediately because the pipe is closed. */ |
| ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); |
| } |
| } |
| |
| /* Global set of executing processes for use by the Ctrl handler. |
| This global instance will be zero-initialized by the compiler. |
| |
| Note that the console Ctrl handler runs on a background thread and so |
| everything it does must be thread safe. Here, we track the hProcess |
| HANDLEs directly instead of kwsysProcess instances, so that we don't have |
| to make kwsysProcess thread safe. */ |
| typedef struct kwsysProcessInstance_s |
| { |
| HANDLE hProcess; |
| DWORD dwProcessId; |
| int NewProcessGroup; /* Whether the process was created in a new group. */ |
| } kwsysProcessInstance; |
| |
| typedef struct kwsysProcessInstances_s |
| { |
| /* Whether we have initialized key fields below, like critical sections. */ |
| int Initialized; |
| |
| /* Ctrl handler runs on a different thread, so we must sync access. */ |
| CRITICAL_SECTION Lock; |
| |
| int Exiting; |
| size_t Count; |
| size_t Size; |
| kwsysProcessInstance* Processes; |
| } kwsysProcessInstances; |
| static kwsysProcessInstances kwsysProcesses; |
| |
| /* Initialize critial section and set up console Ctrl handler. You MUST call |
| this before using any other kwsysProcesses* functions below. */ |
| static int kwsysProcessesInitialize(void) |
| { |
| /* Initialize everything if not done already. */ |
| if (!kwsysProcesses.Initialized) { |
| InitializeCriticalSection(&kwsysProcesses.Lock); |
| |
| /* Set up console ctrl handler. */ |
| if (!SetConsoleCtrlHandler(kwsysCtrlHandler, TRUE)) { |
| return 0; |
| } |
| |
| kwsysProcesses.Initialized = 1; |
| } |
| return 1; |
| } |
| |
| /* The Ctrl handler waits on the global list of processes. To prevent an |
| orphaned process, do not create a new process if the Ctrl handler is |
| already running. Do so by using this function to check if it is ok to |
| create a process. */ |
| static int kwsysTryEnterCreateProcessSection(void) |
| { |
| /* Enter main critical section; this means creating a process and the Ctrl |
| handler are mutually exclusive. */ |
| EnterCriticalSection(&kwsysProcesses.Lock); |
| /* Indicate to the caller if they can create a process. */ |
| if (kwsysProcesses.Exiting) { |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
| |
| /* Matching function on successful kwsysTryEnterCreateProcessSection return. |
| Make sure you called kwsysProcessesAdd if applicable before calling this.*/ |
| static void kwsysLeaveCreateProcessSection(void) |
| { |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| } |
| |
| /* Add new process to global process list. The Ctrl handler will wait for |
| the process to exit before it returns. Do not close the process handle |
| until after calling kwsysProcessesRemove. The newProcessGroup parameter |
| must be set if the process was created with CREATE_NEW_PROCESS_GROUP. */ |
| static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessid, |
| int newProcessGroup) |
| { |
| if (!kwsysProcessesInitialize() || !hProcess || |
| hProcess == INVALID_HANDLE_VALUE) { |
| return 0; |
| } |
| |
| /* Enter the critical section. */ |
| EnterCriticalSection(&kwsysProcesses.Lock); |
| |
| /* Make sure there is enough space for the new process handle. */ |
| if (kwsysProcesses.Count == kwsysProcesses.Size) { |
| size_t newSize; |
| kwsysProcessInstance* newArray; |
| /* Start with enough space for a small number of process handles |
| and double the size each time more is needed. */ |
| newSize = kwsysProcesses.Size ? kwsysProcesses.Size * 2 : 4; |
| |
| /* Try allocating the new block of memory. */ |
| if ((newArray = (kwsysProcessInstance*)malloc( |
| newSize * sizeof(kwsysProcessInstance)))) { |
| /* Copy the old process handles to the new memory. */ |
| if (kwsysProcesses.Count > 0) { |
| memcpy(newArray, kwsysProcesses.Processes, |
| kwsysProcesses.Count * sizeof(kwsysProcessInstance)); |
| } |
| } else { |
| /* Failed to allocate memory for the new process handle set. */ |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| return 0; |
| } |
| |
| /* Free original array. */ |
| free(kwsysProcesses.Processes); |
| |
| /* Update original structure with new allocation. */ |
| kwsysProcesses.Size = newSize; |
| kwsysProcesses.Processes = newArray; |
| } |
| |
| /* Append the new process information to the set. */ |
| kwsysProcesses.Processes[kwsysProcesses.Count].hProcess = hProcess; |
| kwsysProcesses.Processes[kwsysProcesses.Count].dwProcessId = dwProcessid; |
| kwsysProcesses.Processes[kwsysProcesses.Count++].NewProcessGroup = |
| newProcessGroup; |
| |
| /* Leave critical section and return success. */ |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| |
| return 1; |
| } |
| |
| /* Removes process to global process list. */ |
| static void kwsysProcessesRemove(HANDLE hProcess) |
| { |
| size_t i; |
| |
| if (!hProcess || hProcess == INVALID_HANDLE_VALUE) { |
| return; |
| } |
| |
| EnterCriticalSection(&kwsysProcesses.Lock); |
| |
| /* Find the given process in the set. */ |
| for (i = 0; i < kwsysProcesses.Count; ++i) { |
| if (kwsysProcesses.Processes[i].hProcess == hProcess) { |
| break; |
| } |
| } |
| if (i < kwsysProcesses.Count) { |
| /* Found it! Remove the process from the set. */ |
| --kwsysProcesses.Count; |
| for (; i < kwsysProcesses.Count; ++i) { |
| kwsysProcesses.Processes[i] = kwsysProcesses.Processes[i + 1]; |
| } |
| |
| /* If this was the last process, free the array. */ |
| if (kwsysProcesses.Count == 0) { |
| kwsysProcesses.Size = 0; |
| free(kwsysProcesses.Processes); |
| kwsysProcesses.Processes = 0; |
| } |
| } |
| |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| } |
| |
| static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType) |
| { |
| size_t i; |
| (void)dwCtrlType; |
| /* Enter critical section. */ |
| EnterCriticalSection(&kwsysProcesses.Lock); |
| |
| /* Set flag indicating that we are exiting. */ |
| kwsysProcesses.Exiting = 1; |
| |
| /* If some of our processes were created in a new process group, we must |
| manually interrupt them. They won't otherwise receive a Ctrl+C/Break. */ |
| for (i = 0; i < kwsysProcesses.Count; ++i) { |
| if (kwsysProcesses.Processes[i].NewProcessGroup) { |
| DWORD groupId = kwsysProcesses.Processes[i].dwProcessId; |
| if (groupId) { |
| GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, groupId); |
| } |
| } |
| } |
| |
| /* Wait for each child process to exit. This is the key step that prevents |
| us from leaving several orphaned children processes running in the |
| background when the user presses Ctrl+C. */ |
| for (i = 0; i < kwsysProcesses.Count; ++i) { |
| WaitForSingleObject(kwsysProcesses.Processes[i].hProcess, INFINITE); |
| } |
| |
| /* Leave critical section. */ |
| LeaveCriticalSection(&kwsysProcesses.Lock); |
| |
| /* Continue on to default Ctrl handler (which calls ExitProcess). */ |
| return FALSE; |
| } |
| |
| void kwsysProcess_ResetStartTime(kwsysProcess* cp) |
| { |
| if (!cp) { |
| return; |
| } |
| /* Reset start time. */ |
| cp->StartTime = kwsysProcessTimeGetCurrent(); |
| } |