/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc., Insight Software Consortium Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmWin32ProcessExecution.h" #include "cmSystemTools.h" #include #include #include #include #include #if defined(__BORLANDC__) # define STRICMP stricmp # define TO_INTPTR(x) ((long)(x)) #endif // Borland #if defined(_MSC_VER) // Visual studio # if ( _MSC_VER >= 1300 ) # include # define TO_INTPTR(x) ((intptr_t)(x)) # else // Visual Studio 6 # define TO_INTPTR(x) ((long)(x)) # endif // Visual studio .NET # define STRICMP _stricmp #endif // Visual Studio #if defined(__MINGW32__) # include # define TO_INTPTR(x) ((intptr_t)(x)) # define STRICMP _stricmp #endif // MinGW #define POPEN_1 1 #define POPEN_2 2 #define POPEN_3 3 #define POPEN_4 4 #define cmMAX(x,y) (((x)<(y))?(y):(x)) void DisplayErrorMessage() { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); // Process any inserts in lpMsgBuf. // ... // Display the string. MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION ); // Free the buffer. LocalFree( lpMsgBuf ); } // Code from a Borland web site with the following explaination : /* In this article, I will explain how to spawn a console application * and redirect its standard input/output using anonymous pipes. An * anonymous pipe is a pipe that goes only in one direction (read * pipe, write pipe, etc.). Maybe you are asking, "why would I ever * need to do this sort of thing?" One example would be a Windows * telnet server, where you spawn a shell and listen on a port and * send and receive data between the shell and the socket * client. (Windows does not really have a built-in remote * shell). First, we should talk about pipes. A pipe in Windows is * simply a method of communication, often between process. The SDK * defines a pipe as "a communication conduit with two ends; a process * with a handle to one end can communicate with a process having a * handle to the other end." In our case, we are using "anonymous" * pipes, one-way pipes that "transfer data between a parent process * and a child process or between two child processes of the same * parent process." It's easiest to imagine a pipe as its namesake. An * actual pipe running between processes that can carry data. We are * using anonymous pipes because the console app we are spawning is a * child process. We use the CreatePipe function which will create an * anonymous pipe and return a read handle and a write handle. We will * create two pipes, on for stdin and one for stdout. We will then * monitor the read end of the stdout pipe to check for display on our * child process. Every time there is something availabe for reading, * we will display it in our app. Consequently, we check for input in * our app and send it off to the write end of the stdin pipe. */ inline bool IsWinNT() //check if we're running NT { OSVERSIONINFO osv; osv.dwOSVersionInfoSize = sizeof(osv); GetVersionEx(&osv); return (osv.dwPlatformId == VER_PLATFORM_WIN32_NT); } //--------------------------------------------------------------------------- bool cmWin32ProcessExecution::BorlandRunCommand( const char* command, const char* dir, std::string& output, int& retVal, bool verbose, int /* timeout */, bool hideWindows) { //verbose = true; //std::cerr << std::endl // << "WindowsRunCommand(" << command << ")" << std::endl // << std::flush; const int BUFFER_SIZE = 4096; char buf[BUFFER_SIZE]; //i/o buffer STARTUPINFO si; SECURITY_ATTRIBUTES sa; SECURITY_DESCRIPTOR sd; //security information for pipes PROCESS_INFORMATION pi; HANDLE newstdin,newstdout,read_stdout,write_stdin; //pipe handles if (IsWinNT()) //initialize security descriptor (Windows NT) { InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(&sd, true, NULL, false); sa.lpSecurityDescriptor = &sd; } else sa.lpSecurityDescriptor = NULL; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = true; //allow inheritable handles if (!CreatePipe(&newstdin,&write_stdin,&sa,0)) //create stdin pipe { return false; } if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) //create stdout pipe { CloseHandle(newstdin); CloseHandle(write_stdin); return false; } GetStartupInfo(&si); //set startupinfo for the spawned process /* The dwFlags member tells CreateProcess how to make the * process. STARTF_USESTDHANDLES validates the hStd* * members. STARTF_USESHOWWINDOW validates the wShowWindow * member. */ si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; si.hStdOutput = newstdout; si.hStdError = newstdout; si.wShowWindow = SW_SHOWDEFAULT; if(hideWindows) { si.wShowWindow = SW_HIDE; } //set the new handles for the child process si.hStdInput = newstdin; char* commandAndArgs = strcpy(new char[strlen(command)+1], command); if (!CreateProcess(NULL,commandAndArgs,NULL,NULL,TRUE, 0, // CREATE_NEW_CONSOLE, NULL,dir,&si,&pi)) { std::cerr << "CreateProcess failed " << commandAndArgs << std::endl; CloseHandle(newstdin); CloseHandle(newstdout); CloseHandle(read_stdout); CloseHandle(write_stdin); delete [] commandAndArgs; return false; } delete [] commandAndArgs; unsigned long exit=0; //process exit code unsigned unsigned long bread; //bytes read unsigned unsigned long avail; //bytes available memset(buf, 0, sizeof(buf)); for(;;) //main program loop { Sleep(10); //check to see if there is any data to read from stdout //std::cout << "Peek for data..." << std::endl; PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL); if (bread != 0) { memset(buf, 0, sizeof(buf)); if (avail > 1023) { while (bread >= 1023) { //std::cout << "Read data..." << std::endl; ReadFile(read_stdout,buf,1023,&bread,NULL); //read the stdout pipe memset(buf, 0, sizeof(buf)); output += buf; if (verbose) { cmSystemTools::Stdout(buf); } } } else { ReadFile(read_stdout,buf,1023,&bread,NULL); output += buf; if(verbose) { cmSystemTools::Stdout(buf); } } } //std::cout << "Check for process..." << std::endl; GetExitCodeProcess(pi.hProcess,&exit); //while the process is running if (exit != STILL_ACTIVE) break; } WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess,&exit); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(newstdin); //clean stuff up CloseHandle(newstdout); CloseHandle(read_stdout); CloseHandle(write_stdin); retVal = exit; return true; } bool cmWin32ProcessExecution::StartProcess( const char* cmd, const char* path, bool verbose) { this->Initialize(); this->Verbose = verbose; return this->PrivateOpen(cmd, path, _O_RDONLY | _O_TEXT, POPEN_3); } bool cmWin32ProcessExecution::Wait(int timeout) { return this->PrivateClose(timeout); } static BOOL RealPopenCreateProcess(const char *cmdstring, const char *path, const char *szConsoleSpawn, HANDLE hStdin, HANDLE hStdout, HANDLE hStderr, HANDLE *hProcess, bool hideWindows, std::string& output) { PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; char *s1=0,*s2=0; const char *s3 = " /c "; int i = GetEnvironmentVariable("COMSPEC",NULL,0); if (i) { char *comshell; s1 = (char *)malloc(i); int x = GetEnvironmentVariable("COMSPEC", s1, i); if (!x) { free(s1); 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 + (int)strlen(s3) + (int)strlen(cmdstring) + 1; s2 = (char *)malloc(x); ZeroMemory(s2, x); //sprintf(s2, "%s%s%s", s1, s3, cmdstring); sprintf(s2, "%s", cmdstring); } 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 '" << modulepath << "' which is needed " "for popen to work with your shell " "or platform." << std::endl; free(s1); free(s2); return FALSE; } } x = i + (int)strlen(s3) + (int)strlen(cmdstring) + 1 + (int)strlen(modulepath) + (int)strlen(szConsoleSpawn) + 1; if(s2) { free(s2); } s2 = (char *)malloc(x); ZeroMemory(s2, x); sprintf( s2, "%s %s%s%s", modulepath, s1, s3, cmdstring); sprintf( s2, "%s %s", modulepath, 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; free(s2); free(s1); 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_SHOWDEFAULT; if(hideWindows) { siStartInfo.wShowWindow = SW_HIDE; } //std::cout << "Create process: " << s2 << std::endl; if (CreateProcess(NULL, s2, NULL, NULL, TRUE, 0, //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; //std::cout << "Process created..." << std::endl; free(s2); free(s1); return TRUE; } output += "CreateProcessError: "; { /* Format the error message. */ char message[1024]; DWORD original = GetLastError(); DWORD length = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, original, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message, 1023, 0); if(length < 1) { /* FormatMessage failed. Use a default message. */ _snprintf(message, 1023, "Process execution failed with error 0x%X. " "FormatMessage failed with error 0x%X", original, GetLastError()); } output += message; } output += "\n"; output += "for command: "; output += s2; if(path) { output += "\nin dir: "; output += path; } output += "\n"; free(s2); free(s1); 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 hProcess; SECURITY_ATTRIBUTES saAttr; BOOL fSuccess; int fd1, fd2, fd3; this->hChildStdinRd = 0; this->hChildStdinWr = 0; this->hChildStdoutRd = 0; this->hChildStdoutWr = 0; this->hChildStderrRd = 0; this->hChildStderrWr = 0; this->hChildStdinWrDup = 0; this->hChildStdoutRdDup = 0; this->hChildStderrRdDup = 0; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; fd1 = 0; fd2 = 0; fd3 = 0; if (!CreatePipe(&this->hChildStdinRd, &this->hChildStdinWr, &saAttr, 0)) { this->Output += "CreatePipeError\n"; return false; } /* Create new output read handle and the input write handle. Set * the inheritance properties to FALSE. Otherwise, the child inherits * these handles; resulting in non-closeable handles to the pipes * being created. */ fSuccess = DuplicateHandle(GetCurrentProcess(), this->hChildStdinWr, GetCurrentProcess(), &this->hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS); if (!fSuccess) { this->Output += "DuplicateHandleError\n"; return false; } /* Close the inheritable version of ChildStdin that we're using. */ CloseHandle(hChildStdinWr); if (!CreatePipe(&this->hChildStdoutRd, &this->hChildStdoutWr, &saAttr, 0)) { this->Output += "CreatePipeError\n"; return false; } fSuccess = DuplicateHandle(GetCurrentProcess(), this->hChildStdoutRd, GetCurrentProcess(), &this->hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS); if (!fSuccess) { this->Output += "DuplicateHandleError\n"; return false; } /* Close the inheritable version of ChildStdout that we're using. */ CloseHandle(hChildStdoutRd); if (n != POPEN_4) { if (!CreatePipe(&this->hChildStderrRd, &this->hChildStderrWr, &saAttr, 0)) { this->Output += "CreatePipeError\n"; return false; } fSuccess = DuplicateHandle(GetCurrentProcess(), this->hChildStderrRd, GetCurrentProcess(), &this->hChildStderrRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS); if (!fSuccess) { this->Output += "DuplicateHandleError\n"; return false; } /* 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(TO_INTPTR(this->hChildStdinWrDup), mode); /* We don't care about these pipes anymore, so close them. */ break; case _O_RDONLY | _O_TEXT: /* Case for reading from child Stdout in text mode. */ fd1 = _open_osfhandle(TO_INTPTR(this->hChildStdoutRdDup), mode); /* We don't care about these pipes anymore, so close them. */ break; case _O_RDONLY | _O_BINARY: /* Case for readinig from child Stdout in binary mode. */ fd1 = _open_osfhandle(TO_INTPTR(this->hChildStdoutRdDup), mode); /* We don't care about these pipes anymore, so close them. */ break; case _O_WRONLY | _O_BINARY: /* Case for writing to child Stdin in binary mode. */ fd1 = _open_osfhandle(TO_INTPTR(this->hChildStdinWrDup), mode); /* We don't care about these pipes anymore, so close them. */ break; } break; case POPEN_2: case POPEN_4: //if ( 1 ) { fd1 = _open_osfhandle(TO_INTPTR(this->hChildStdinWrDup), mode); fd2 = _open_osfhandle(TO_INTPTR(this->hChildStdoutRdDup), mode); break; } case POPEN_3: //if ( 1) { fd1 = _open_osfhandle(TO_INTPTR(this->hChildStdinWrDup), mode); fd2 = _open_osfhandle(TO_INTPTR(this->hChildStdoutRdDup), mode); fd3 = _open_osfhandle(TO_INTPTR(this->hChildStderrRdDup), mode); break; } } if (n == POPEN_4) { if (!RealPopenCreateProcess(cmdstring, path, this->ConsoleSpawn.c_str(), this->hChildStdinRd, this->hChildStdoutWr, this->hChildStdoutWr, &hProcess, this->HideWindows, this->Output)) { if(fd1 >= 0) { close(fd1); } if(fd2 >= 0) { close(fd2); } if(fd3 >= 0) { close(fd3); } return 0; } } else { if (!RealPopenCreateProcess(cmdstring, path, this->ConsoleSpawn.c_str(), this->hChildStdinRd, this->hChildStdoutWr, this->hChildStderrWr, &hProcess, this->HideWindows, this->Output)) { if(fd1 >= 0) { close(fd1); } if(fd2 >= 0) { close(fd2); } if(fd3 >= 0) { close(fd3); } return 0; } } /* 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. */ this->ProcessHandle = hProcess; if ( fd1 >= 0 ) { this->pStdIn = fd1; } if ( fd2 >= 0 ) { this->pStdOut = fd2; } if ( fd3 >= 0 ) { this->pStdErr = fd3; } return true; } bool cmWin32ProcessExecution::CloseHandles() { if(this->pStdErr != -1 ) { // this will close this as well: this->hChildStderrRdDup _close(this->pStdErr); this->pStdErr = -1; this->hChildStderrRdDup = 0; } if(this->pStdIn != -1 ) { // this will close this as well: this->hChildStdinWrDup _close(this->pStdIn); this->pStdIn = -1; this->hChildStdinWrDup = 0; } if(this->pStdOut != -1 ) { // this will close this as well: this->hChildStdoutRdDup _close(this->pStdOut); this->pStdOut = -1; this->hChildStdoutRdDup = 0; } bool ret = true; if (this->hChildStdinRd && !CloseHandle(this->hChildStdinRd)) { ret = false; } this->hChildStdinRd = 0; // now close these two if (this->hChildStdoutWr && !CloseHandle(this->hChildStdoutWr)) { ret = false; } this->hChildStdoutWr = 0; if (this->hChildStderrWr && !CloseHandle(this->hChildStderrWr)) { ret = false; } this->hChildStderrWr = 0; return ret; } cmWin32ProcessExecution::~cmWin32ProcessExecution() { this->CloseHandles(); } bool cmWin32ProcessExecution::PrivateClose(int /* timeout */) { HANDLE hProcess = this->ProcessHandle; int result = -1; DWORD exit_code; std::string output = ""; bool done = false; while(!done) { Sleep(10); bool have_some = false; struct _stat fsout; struct _stat fserr; int rout = _fstat(this->pStdOut, &fsout); int rerr = _fstat(this->pStdErr, &fserr); if ( rout && rerr ) { break; } if (fserr.st_size > 0) { char buffer[1024]; int len = read(this->pStdErr, buffer, 1023); buffer[len] = 0; if ( this->Verbose ) { cmSystemTools::Stdout(buffer); } output += buffer; have_some = true; } if (fsout.st_size > 0) { char buffer[1024]; int len = read(this->pStdOut, buffer, 1023); buffer[len] = 0; if ( this->Verbose ) { cmSystemTools::Stdout(buffer); } output += buffer; have_some = true; } unsigned long exitCode; if ( ! have_some ) { 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->ExitValue = result; this->Output += output; bool ret = this->CloseHandles(); if ( result < 0 || !ret) { 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 * app = 0; char* cmd = new char[ strlen(command) + 1 ]; strcpy(cmd, command); bRet = CreateProcess( app, cmd, 0, 0, TRUE, 0, 0, 0, &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; }