diff options
author | Andy Cedilnik <andy.cedilnik@kitware.com> | 2002-09-27 21:26:37 (GMT) |
---|---|---|
committer | Andy Cedilnik <andy.cedilnik@kitware.com> | 2002-09-27 21:26:37 (GMT) |
commit | c310600df56e7896cb0b996eb0dd9d24203d94fb (patch) | |
tree | 68dc9528d41e962c706c243cc3e3599b870b2503 | |
parent | 2f1052b0e526350a9ce7b6b02b65b94b25d59060 (diff) | |
download | CMake-c310600df56e7896cb0b996eb0dd9d24203d94fb.zip CMake-c310600df56e7896cb0b996eb0dd9d24203d94fb.tar.gz CMake-c310600df56e7896cb0b996eb0dd9d24203d94fb.tar.bz2 |
Add class for process execution on Windows
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmWin32ProcessExecution.cxx | 614 | ||||
-rw-r--r-- | Source/cmWin32ProcessExecution.h | 85 |
3 files changed, 701 insertions, 0 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 6a108b9..a1bc408 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -72,6 +72,8 @@ IF (WIN32) cmLocalVisualStudio6Generator.h cmGlobalVisualStudio7Generator.h cmLocalVisualStudio7Generator.h + cmWin32ProcessExecution.cxx + cmWin32ProcessExecution.h ) IF( NOT BORLAND ) LINK_LIBRARIES( rpcrt4.lib ) diff --git a/Source/cmWin32ProcessExecution.cxx b/Source/cmWin32ProcessExecution.cxx new file mode 100644 index 0000000..916693a --- /dev/null +++ b/Source/cmWin32ProcessExecution.cxx @@ -0,0 +1,614 @@ +/*========================================================================= + + Program: Insight Segmentation & Registration Toolkit + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Insight Consortium. All rights reserved. + See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include "cmWin32ProcessExecution.h" + +#include "cmSystemTools.h" + +#include <malloc.h> +#include <io.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <windows.h> + +#define POPEN_1 1 +#define POPEN_2 2 +#define POPEN_3 3 +#define POPEN_4 4 + +#define cmMAX(x,y) (((x)<(y))?(y):(x)) + +#define win32_error(x,y) std::cout << "Win32_Error(" << x << ", " << y << ")" << std::endl, false + +bool cmWin32ProcessExecution::StartProcess( + const char* cmd, const char* path, bool verbose) +{ + this->Initialize(); + this->m_Verbose = verbose; + return this->PrivateOpen(cmd, path, _O_RDONLY | _O_TEXT, POPEN_3); +} + +bool cmWin32ProcessExecution::Wait(int timeout) +{ + return this->PrivateClose(timeout); +} + +/* + * Internal dictionary mapping popen* file pointers to process handles, + * for use when retrieving the process exit code. See _PyPclose() below + * for more information on this dictionary's use. + */ +static void *_PyPopenProcs = NULL; + +static BOOL RealPopenCreateProcess(const char *cmdstring, + const char *path, + const char *szConsoleSpawn, + HANDLE hStdin, + HANDLE hStdout, + HANDLE hStderr, + HANDLE *hProcess) +{ + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + char *s1,*s2, *s3 = " /c "; + int i; + int x; + + if (i = GetEnvironmentVariable("COMSPEC",NULL,0)) + { + char *comshell; + + s1 = (char *)_alloca(i); + if (!(x = GetEnvironmentVariable("COMSPEC", s1, i))) + return x; + + /* Explicitly check if we are using COMMAND.COM. If we are + * then use the w9xpopen hack. + */ + comshell = s1 + x; + while (comshell >= s1 && *comshell != '\\') + --comshell; + ++comshell; + + if (GetVersion() < 0x80000000 && + _stricmp(comshell, "command.com") != 0) + { + /* NT/2000 and not using command.com. */ + x = i + strlen(s3) + strlen(cmdstring) + 1; + s2 = (char *)_alloca(x); + ZeroMemory(s2, x); + sprintf(s2, "%s%s%s", s1, s3, cmdstring); + //std::cout << "s1: " << s1 << " s2: " << s2 << " s3: " << s3 + // << std::endl; + } + else + { + /* + * Oh gag, we're on Win9x or using COMMAND.COM. Use + * the workaround listed in KB: Q150956 + */ + char modulepath[_MAX_PATH]; + struct stat statinfo; + GetModuleFileName(NULL, modulepath, sizeof(modulepath)); + for (i = x = 0; modulepath[i]; i++) + if (modulepath[i] == '\\') + x = i+1; + modulepath[x] = '\0'; + /* Create the full-name to w9xpopen, so we can test it exists */ + strncat(modulepath, + szConsoleSpawn, + (sizeof(modulepath)/sizeof(modulepath[0])) + -strlen(modulepath)); + if (stat(modulepath, &statinfo) != 0) + { + /* Eeek - file-not-found - possibly an embedding + situation - see if we can locate it in sys.prefix + */ + strncpy(modulepath, + ".", + sizeof(modulepath)/sizeof(modulepath[0])); + if (modulepath[strlen(modulepath)-1] != '\\') + strcat(modulepath, "\\"); + strncat(modulepath, + szConsoleSpawn, + (sizeof(modulepath)/sizeof(modulepath[0])) + -strlen(modulepath)); + /* No where else to look - raise an easily identifiable + error, rather than leaving Windows to report + "file not found" - as the user is probably blissfully + unaware this shim EXE is used, and it will confuse them. + (well, it confused me for a while ;-) + */ + if (stat(modulepath, &statinfo) != 0) + { + std::cout + << "Can not locate '" << szConsoleSpawn + << "' which is needed " + "for popen to work with your shell " + "or platform." << std::endl; + return FALSE; + } + } + x = i + strlen(s3) + strlen(cmdstring) + 1 + + strlen(modulepath) + + strlen(szConsoleSpawn) + 1; + + s2 = (char *)_alloca(x); + ZeroMemory(s2, x); + sprintf( + s2, + "%s \"%s%s%s\"", + modulepath, + s1, + s3, + cmdstring); + } + } + + /* Could be an else here to try cmd.exe / command.com in the path + Now we'll just error out.. */ + else + { + std::cout << "Cannot locate a COMSPEC environment variable to " + << "use as the shell" << std::endl; + return FALSE; + } + + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + siStartInfo.hStdInput = hStdin; + siStartInfo.hStdOutput = hStdout; + siStartInfo.hStdError = hStderr; + siStartInfo.wShowWindow = SW_HIDE; + + if (CreateProcess(NULL, + s2, + NULL, + NULL, + TRUE, + CREATE_NEW_CONSOLE, + NULL, + path, + &siStartInfo, + &piProcInfo) ) + { + /* Close the handles now so anyone waiting is woken. */ + CloseHandle(piProcInfo.hThread); + + /* Return process handle */ + *hProcess = piProcInfo.hProcess; + return TRUE; + } + win32_error("CreateProcess", s2); + return FALSE; +} + +/* The following code is based off of KB: Q190351 */ + +bool cmWin32ProcessExecution::PrivateOpen(const char *cmdstring, + const char* path, + int mode, + int n) +{ + HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr, + hChildStderrRd, hChildStderrWr, hChildStdinWrDup, hChildStdoutRdDup, + hChildStderrRdDup, hProcess; /* hChildStdoutWrDup; */ + + SECURITY_ATTRIBUTES saAttr; + BOOL fSuccess; + int fd1, fd2, fd3; + FILE *f1, *f2, *f3; + long file_count; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) + return win32_error("CreatePipe", NULL); + + /* Create new output read handle and the input write handle. Set + * the inheritance properties to FALSE. Otherwise, the child inherits + * the these handles; resulting in non-closeable handles to the pipes + * being created. */ + fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr, + GetCurrentProcess(), &hChildStdinWrDup, 0, + FALSE, + DUPLICATE_SAME_ACCESS); + if (!fSuccess) + return win32_error("DuplicateHandle", NULL); + + /* Close the inheritable version of ChildStdin + that we're using. */ + CloseHandle(hChildStdinWr); + + if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) + return win32_error("CreatePipe", NULL); + + fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd, + GetCurrentProcess(), &hChildStdoutRdDup, 0, + FALSE, DUPLICATE_SAME_ACCESS); + if (!fSuccess) + return win32_error("DuplicateHandle", NULL); + + /* Close the inheritable version of ChildStdout + that we're using. */ + CloseHandle(hChildStdoutRd); + + if (n != POPEN_4) + { + if (!CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0)) + return win32_error("CreatePipe", NULL); + fSuccess = DuplicateHandle(GetCurrentProcess(), + hChildStderrRd, + GetCurrentProcess(), + &hChildStderrRdDup, 0, + FALSE, DUPLICATE_SAME_ACCESS); + if (!fSuccess) + return win32_error("DuplicateHandle", NULL); + /* Close the inheritable version of ChildStdErr that we're using. */ + CloseHandle(hChildStderrRd); + } + + switch (n) + { + case POPEN_1: + switch (mode & (_O_RDONLY | _O_TEXT | _O_BINARY | _O_WRONLY)) + { + case _O_WRONLY | _O_TEXT: + /* Case for writing to child Stdin in text mode. */ + fd1 = _open_osfhandle((long)hChildStdinWrDup, mode); + f1 = _fdopen(fd1, "w"); + /* We don't care about these pipes anymore, + so close them. */ + CloseHandle(hChildStdoutRdDup); + CloseHandle(hChildStderrRdDup); + break; + + case _O_RDONLY | _O_TEXT: + /* Case for reading from child Stdout in text mode. */ + fd1 = _open_osfhandle((long)hChildStdoutRdDup, mode); + f1 = _fdopen(fd1, "r"); + /* We don't care about these pipes anymore, + so close them. */ + CloseHandle(hChildStdinWrDup); + CloseHandle(hChildStderrRdDup); + break; + + case _O_RDONLY | _O_BINARY: + /* Case for readinig from child Stdout in + binary mode. */ + fd1 = _open_osfhandle((long)hChildStdoutRdDup, mode); + f1 = _fdopen(fd1, "rb"); + /* We don't care about these pipes anymore, + so close them. */ + CloseHandle(hChildStdinWrDup); + CloseHandle(hChildStderrRdDup); + break; + + case _O_WRONLY | _O_BINARY: + /* Case for writing to child Stdin in binary mode. */ + fd1 = _open_osfhandle((long)hChildStdinWrDup, mode); + f1 = _fdopen(fd1, "wb"); + /* We don't care about these pipes anymore, + so close them. */ + CloseHandle(hChildStdoutRdDup); + CloseHandle(hChildStderrRdDup); + break; + } + file_count = 1; + break; + + case POPEN_2: + case POPEN_4: + if ( 1 ) + { + char *m1, *m2; + + if (mode && _O_TEXT) + { + m1 = "r"; + m2 = "w"; + } + else + { + m1 = "rb"; + m2 = "wb"; + } + + fd1 = _open_osfhandle((long)hChildStdinWrDup, mode); + f1 = _fdopen(fd1, m2); + fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode); + f2 = _fdopen(fd2, m1); + + if (n != 4) + CloseHandle(hChildStderrRdDup); + + file_count = 2; + break; + } + + case POPEN_3: + if ( 1) + { + char *m1, *m2; + + if (mode && _O_TEXT) + { + m1 = "r"; + m2 = "w"; + } + else + { + m1 = "rb"; + m2 = "wb"; + } + + fd1 = _open_osfhandle((long)hChildStdinWrDup, mode); + f1 = _fdopen(fd1, m2); + fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode); + f2 = _fdopen(fd2, m1); + fd3 = _open_osfhandle((long)hChildStderrRdDup, mode); + f3 = _fdopen(fd3, m1); + + file_count = 3; + break; + } + } + + if (n == POPEN_4) + { + if (!RealPopenCreateProcess(cmdstring, + path, + this->m_ConsoleSpawn.c_str(), + hChildStdinRd, + hChildStdoutWr, + hChildStdoutWr, + &hProcess)) + return NULL; + } + else + { + if (!RealPopenCreateProcess(cmdstring, + path, + this->m_ConsoleSpawn.c_str(), + hChildStdinRd, + hChildStdoutWr, + hChildStderrWr, + &hProcess)) + return NULL; + } + + /* + * Insert the files we've created into the process dictionary + * all referencing the list with the process handle and the + * initial number of files (see description below in _PyPclose). + * Since if _PyPclose later tried to wait on a process when all + * handles weren't closed, it could create a deadlock with the + * child, we spend some energy here to try to ensure that we + * either insert all file handles into the dictionary or none + * at all. It's a little clumsy with the various popen modes + * and variable number of files involved. + */ + + /* Child is launched. Close the parents copy of those pipe + * handles that only the child should have open. You need to + * make sure that no handles to the write end of the output pipe + * are maintained in this process or else the pipe will not close + * when the child process exits and the ReadFile will hang. */ + + if (!CloseHandle(hChildStdinRd)) + return win32_error("CloseHandle", NULL); + + if (!CloseHandle(hChildStdoutWr)) + return win32_error("CloseHandle", NULL); + + if ((n != 4) && (!CloseHandle(hChildStderrWr))) + return win32_error("CloseHandle", NULL); + + this->m_ProcessHandle = hProcess; + if ( f1 ) + { + this->m_StdIn = f1; + this->m_pStdIn = fd1; + } + if ( f2 ) + { + this->m_StdOut = f2; + this->m_pStdOut = fd2; + } + if ( f3 ) + { + this->m_StdErr = f3; + this->m_pStdErr = fd3; + } + + return true; +} + +/* + * Wrapper for fclose() to use for popen* files, so we can retrieve the + * exit code for the child process and return as a result of the close. + * + * This function uses the _PyPopenProcs dictionary in order to map the + * input file pointer to information about the process that was + * originally created by the popen* call that created the file pointer. + * The dictionary uses the file pointer as a key (with one entry + * inserted for each file returned by the original popen* call) and a + * single list object as the value for all files from a single call. + * The list object contains the Win32 process handle at [0], and a file + * count at [1], which is initialized to the total number of file + * handles using that list. + * + * This function closes whichever handle it is passed, and decrements + * the file count in the dictionary for the process handle pointed to + * by this file. On the last close (when the file count reaches zero), + * this function will wait for the child process and then return its + * exit code as the result of the close() operation. This permits the + * files to be closed in any order - it is always the close() of the + * final handle that will return the exit code. + */ + + /* RED_FLAG 31-Aug-2000 Tim + * This is always called (today!) between a pair of + * Py_BEGIN_ALLOW_THREADS/ Py_END_ALLOW_THREADS + * macros. So the thread running this has no valid thread state, as + * far as Python is concerned. However, this calls some Python API + * functions that cannot be called safely without a valid thread + * state, in particular PyDict_GetItem. + * As a temporary hack (although it may last for years ...), we + * *rely* on not having a valid thread state in this function, in + * order to create our own "from scratch". + * This will deadlock if _PyPclose is ever called by a thread + * holding the global lock. + */ + +bool cmWin32ProcessExecution::PrivateClose(int timeout) +{ + HANDLE hProcess = this->m_ProcessHandle; + + int result = -1; + DWORD exit_code; + + std::string output = ""; + bool done = false; + int stdoutloc = 0; + int stderrloc = 0; + while(!done) + { + Sleep(10); + struct _stat fsout; + struct _stat fserr; + int rout = _fstat(this->m_pStdOut, &fsout); + int rerr = _fstat(this->m_pStdErr, &fserr); + if ( rout && rerr ) + { + break; + } + if (fserr.st_size > 0) + { + int dist = fserr.st_size; + char buffer[1023]; + int len = read(this->m_pStdErr, buffer, 1023); + buffer[len] = 0; + if ( this->m_Verbose ) + { + std::cout << buffer << std::flush; + } + output += buffer; + } + if (fsout.st_size > 0) + { + int dist = fsout.st_size; + char buffer[1023]; + int len = read(this->m_pStdOut, buffer, 1023); + buffer[len] = 0; + if ( this->m_Verbose ) + { + std::cout << buffer << std::flush; + } + output += buffer; + } + unsigned long exitCode; + GetExitCodeProcess(hProcess,&exitCode); + if (exitCode != STILL_ACTIVE) + { + break; + } + } + + if (WaitForSingleObject(hProcess, INFINITE) != WAIT_FAILED && + GetExitCodeProcess(hProcess, &exit_code)) + { + result = exit_code; + } + else + { + /* Indicate failure - this will cause the file object + * to raise an I/O error and translate the last Win32 + * error code from errno. We do have a problem with + * last errors that overlap the normal errno table, + * but that's a consistent problem with the file object. + */ + if (result != EOF) + { + /* If the error wasn't from the fclose(), then + * set errno for the file object error handling. + */ + errno = GetLastError(); + } + result = -1; + } + + /* Free up the native handle at this point */ + CloseHandle(hProcess); + this->m_ExitValue = result; + this->m_Output = output; + if ( result < 0 ) + { + return false; + } + return true; +} + +int cmWin32ProcessExecution::Windows9xHack(const char* command) +{ + BOOL bRet; + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD exit_code=0; + + if (!command) + { + cmSystemTools::Error("Windows9xHack: Command not specified"); + return 1; + } + + /* Make child process use this app's standard files. */ + ZeroMemory(&si, sizeof si); + si.cb = sizeof si; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + char* cmd = new char[ strlen(command) + 1 ]; + strcpy(cmd, command); + + bRet = CreateProcess( + NULL, cmd, + NULL, NULL, + TRUE, 0, + NULL, NULL, + &si, &pi + ); + delete [] cmd; + + if (bRet) + { + if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) + { + GetExitCodeProcess(pi.hProcess, &exit_code); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return exit_code; + } + + return 1; +} diff --git a/Source/cmWin32ProcessExecution.h b/Source/cmWin32ProcessExecution.h new file mode 100644 index 0000000..acc214b --- /dev/null +++ b/Source/cmWin32ProcessExecution.h @@ -0,0 +1,85 @@ +/*========================================================================= + + Program: Insight Segmentation & Registration Toolkit + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Insight Consortium. All rights reserved. + See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmWin32ProcessExecution_h +#define cmWin32ProcessExecution_h + +/* + * Portable 'popen' replacement for Win32. + * + * Written by Bill Tutt <billtut@microsoft.com>. Minor tweaks + * and 2.0 integration by Fredrik Lundh <fredrik@pythonware.com> + * Return code handling by David Bolen <db3l@fitlinxx.com>. + */ + +#include "cmStandardIncludes.h" +#include "windows.h" + +class cmMakefile; + +/** \class cmWin32ProcessExecution + * \brief A process executor for windows + * + * cmWin32ProcessExecution is a class that provides a "clean" way of + * executing processes on Windows. + */ +class cmWin32ProcessExecution +{ +public: + cmWin32ProcessExecution() + { + this->SetConsoleSpawn("w9xpopen.exe"); + this->Initialize(); + } + void Initialize() + { + this->m_ProcessHandle = 0; + this->m_ExitValue = -1; + this->m_StdIn = 0; + this->m_StdOut = 0; + this->m_StdErr = 0; + } + bool StartProcess(const char*, const char* path, bool verbose); + bool Wait(int timeout); + const std::string GetOutput() const { return this->m_Output; } + int GetExitValue() const { return this->m_ExitValue; } + + void SetConsoleSpawn(const char* prog) { this->m_ConsoleSpawn = prog; } + + static int Windows9xHack(const char* command); + +private: + bool PrivateOpen(const char*, const char*, int, int); + bool PrivateClose(int timeout); + + HANDLE m_ProcessHandle; + FILE* m_StdIn; + FILE* m_StdOut; + FILE* m_StdErr; + + int m_pStdIn; + int m_pStdOut; + int m_pStdErr; + + int m_ExitValue; + + std::string m_Output; + std::string m_ConsoleSpawn; + bool m_Verbose; +}; + + +#endif |