| //===- Win32/Program.cpp - Win32 Program Implementation ------- -*- C++ -*-===// | |
| // | |
| // The LLVM Compiler Infrastructure | |
| // | |
| // This file is distributed under the University of Illinois Open Source | |
| // License. See LICENSE.TXT for details. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file provides the Win32 specific implementation of the Program class. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "Windows.h" | |
| #include <cstdio> | |
| #include <malloc.h> | |
| #include <io.h> | |
| #include <fcntl.h> | |
| //===----------------------------------------------------------------------===// | |
| //=== WARNING: Implementation here must contain only Win32 specific code | |
| //=== and must not be UNIX code | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct Win32ProcessInfo { | |
| HANDLE hProcess; | |
| DWORD dwProcessId; | |
| }; | |
| } | |
| namespace llvm { | |
| using namespace sys; | |
| Program::Program() : Data_(0) {} | |
| Program::~Program() { | |
| if (Data_) { | |
| Win32ProcessInfo* wpi = reinterpret_cast<Win32ProcessInfo*>(Data_); | |
| CloseHandle(wpi->hProcess); | |
| delete wpi; | |
| Data_ = 0; | |
| } | |
| } | |
| unsigned Program::GetPid() const { | |
| Win32ProcessInfo* wpi = reinterpret_cast<Win32ProcessInfo*>(Data_); | |
| return wpi->dwProcessId; | |
| } | |
| // This function just uses the PATH environment variable to find the program. | |
| Path | |
| Program::FindProgramByName(const std::string& progName) { | |
| // Check some degenerate cases | |
| if (progName.length() == 0) // no program | |
| return Path(); | |
| Path temp; | |
| if (!temp.set(progName)) // invalid name | |
| return Path(); | |
| // Return paths with slashes verbatim. | |
| if (progName.find('\\') != std::string::npos || | |
| progName.find('/') != std::string::npos) | |
| return temp; | |
| // At this point, the file name is valid and does not contain slashes. | |
| // Let Windows search for it. | |
| char buffer[MAX_PATH]; | |
| char *dummy = NULL; | |
| DWORD len = SearchPath(NULL, progName.c_str(), ".exe", MAX_PATH, | |
| buffer, &dummy); | |
| // See if it wasn't found. | |
| if (len == 0) | |
| return Path(); | |
| // See if we got the entire path. | |
| if (len < MAX_PATH) | |
| return Path(buffer); | |
| // Buffer was too small; grow and retry. | |
| while (true) { | |
| char *b = reinterpret_cast<char *>(_alloca(len+1)); | |
| DWORD len2 = SearchPath(NULL, progName.c_str(), ".exe", len+1, b, &dummy); | |
| // It is unlikely the search failed, but it's always possible some file | |
| // was added or removed since the last search, so be paranoid... | |
| if (len2 == 0) | |
| return Path(); | |
| else if (len2 <= len) | |
| return Path(b); | |
| len = len2; | |
| } | |
| } | |
| static HANDLE RedirectIO(const Path *path, int fd, std::string* ErrMsg) { | |
| HANDLE h; | |
| if (path == 0) { | |
| DuplicateHandle(GetCurrentProcess(), (HANDLE)_get_osfhandle(fd), | |
| GetCurrentProcess(), &h, | |
| 0, TRUE, DUPLICATE_SAME_ACCESS); | |
| return h; | |
| } | |
| const char *fname; | |
| if (path->isEmpty()) | |
| fname = "NUL"; | |
| else | |
| fname = path->c_str(); | |
| SECURITY_ATTRIBUTES sa; | |
| sa.nLength = sizeof(sa); | |
| sa.lpSecurityDescriptor = 0; | |
| sa.bInheritHandle = TRUE; | |
| h = CreateFile(fname, fd ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ, | |
| &sa, fd == 0 ? OPEN_EXISTING : CREATE_ALWAYS, | |
| FILE_ATTRIBUTE_NORMAL, NULL); | |
| if (h == INVALID_HANDLE_VALUE) { | |
| MakeErrMsg(ErrMsg, std::string(fname) + ": Can't open file for " + | |
| (fd ? "input: " : "output: ")); | |
| } | |
| return h; | |
| } | |
| /// ArgNeedsQuotes - Check whether argument needs to be quoted when calling | |
| /// CreateProcess. | |
| static bool ArgNeedsQuotes(const char *Str) { | |
| return Str[0] == '\0' || strpbrk(Str, "\t \"&\'()*<>\\`^|") != 0; | |
| } | |
| /// ArgLenWithQuotes - Check whether argument needs to be quoted when calling | |
| /// CreateProcess and returns length of quoted arg with escaped quotes | |
| static unsigned int ArgLenWithQuotes(const char *Str) { | |
| unsigned int len = ArgNeedsQuotes(Str) ? 2 : 0; | |
| while (*Str != '\0') { | |
| if (*Str == '\"') | |
| ++len; | |
| ++len; | |
| ++Str; | |
| } | |
| return len; | |
| } | |
| bool | |
| Program::Execute(const Path& path, | |
| const char** args, | |
| const char** envp, | |
| const Path** redirects, | |
| unsigned memoryLimit, | |
| std::string* ErrMsg) { | |
| if (Data_) { | |
| Win32ProcessInfo* wpi = reinterpret_cast<Win32ProcessInfo*>(Data_); | |
| CloseHandle(wpi->hProcess); | |
| delete wpi; | |
| Data_ = 0; | |
| } | |
| if (!path.canExecute()) { | |
| if (ErrMsg) | |
| *ErrMsg = "program not executable"; | |
| return false; | |
| } | |
| // Windows wants a command line, not an array of args, to pass to the new | |
| // process. We have to concatenate them all, while quoting the args that | |
| // have embedded spaces (or are empty). | |
| // First, determine the length of the command line. | |
| unsigned len = 0; | |
| for (unsigned i = 0; args[i]; i++) { | |
| len += ArgLenWithQuotes(args[i]) + 1; | |
| } | |
| // Now build the command line. | |
| char *command = reinterpret_cast<char *>(_alloca(len+1)); | |
| char *p = command; | |
| for (unsigned i = 0; args[i]; i++) { | |
| const char *arg = args[i]; | |
| bool needsQuoting = ArgNeedsQuotes(arg); | |
| if (needsQuoting) | |
| *p++ = '"'; | |
| while (*arg != '\0') { | |
| if (*arg == '\"') | |
| *p++ = '\\'; | |
| *p++ = *arg++; | |
| } | |
| if (needsQuoting) | |
| *p++ = '"'; | |
| *p++ = ' '; | |
| } | |
| *p = 0; | |
| // The pointer to the environment block for the new process. | |
| char *envblock = 0; | |
| if (envp) { | |
| // An environment block consists of a null-terminated block of | |
| // null-terminated strings. Convert the array of environment variables to | |
| // an environment block by concatenating them. | |
| // First, determine the length of the environment block. | |
| len = 0; | |
| for (unsigned i = 0; envp[i]; i++) | |
| len += strlen(envp[i]) + 1; | |
| // Now build the environment block. | |
| envblock = reinterpret_cast<char *>(_alloca(len+1)); | |
| p = envblock; | |
| for (unsigned i = 0; envp[i]; i++) { | |
| const char *ev = envp[i]; | |
| size_t len = strlen(ev) + 1; | |
| memcpy(p, ev, len); | |
| p += len; | |
| } | |
| *p = 0; | |
| } | |
| // Create a child process. | |
| STARTUPINFO si; | |
| memset(&si, 0, sizeof(si)); | |
| si.cb = sizeof(si); | |
| si.hStdInput = INVALID_HANDLE_VALUE; | |
| si.hStdOutput = INVALID_HANDLE_VALUE; | |
| si.hStdError = INVALID_HANDLE_VALUE; | |
| if (redirects) { | |
| si.dwFlags = STARTF_USESTDHANDLES; | |
| si.hStdInput = RedirectIO(redirects[0], 0, ErrMsg); | |
| if (si.hStdInput == INVALID_HANDLE_VALUE) { | |
| MakeErrMsg(ErrMsg, "can't redirect stdin"); | |
| return false; | |
| } | |
| si.hStdOutput = RedirectIO(redirects[1], 1, ErrMsg); | |
| if (si.hStdOutput == INVALID_HANDLE_VALUE) { | |
| CloseHandle(si.hStdInput); | |
| MakeErrMsg(ErrMsg, "can't redirect stdout"); | |
| return false; | |
| } | |
| if (redirects[1] && redirects[2] && *(redirects[1]) == *(redirects[2])) { | |
| // If stdout and stderr should go to the same place, redirect stderr | |
| // to the handle already open for stdout. | |
| DuplicateHandle(GetCurrentProcess(), si.hStdOutput, | |
| GetCurrentProcess(), &si.hStdError, | |
| 0, TRUE, DUPLICATE_SAME_ACCESS); | |
| } else { | |
| // Just redirect stderr | |
| si.hStdError = RedirectIO(redirects[2], 2, ErrMsg); | |
| if (si.hStdError == INVALID_HANDLE_VALUE) { | |
| CloseHandle(si.hStdInput); | |
| CloseHandle(si.hStdOutput); | |
| MakeErrMsg(ErrMsg, "can't redirect stderr"); | |
| return false; | |
| } | |
| } | |
| } | |
| PROCESS_INFORMATION pi; | |
| memset(&pi, 0, sizeof(pi)); | |
| fflush(stdout); | |
| fflush(stderr); | |
| BOOL rc = CreateProcess(path.c_str(), command, NULL, NULL, TRUE, 0, | |
| envblock, NULL, &si, &pi); | |
| DWORD err = GetLastError(); | |
| // Regardless of whether the process got created or not, we are done with | |
| // the handles we created for it to inherit. | |
| CloseHandle(si.hStdInput); | |
| CloseHandle(si.hStdOutput); | |
| CloseHandle(si.hStdError); | |
| // Now return an error if the process didn't get created. | |
| if (!rc) { | |
| SetLastError(err); | |
| MakeErrMsg(ErrMsg, std::string("Couldn't execute program '") + | |
| path.str() + "'"); | |
| return false; | |
| } | |
| Win32ProcessInfo* wpi = new Win32ProcessInfo; | |
| wpi->hProcess = pi.hProcess; | |
| wpi->dwProcessId = pi.dwProcessId; | |
| Data_ = wpi; | |
| // Make sure these get closed no matter what. | |
| AutoHandle hThread(pi.hThread); | |
| // Assign the process to a job if a memory limit is defined. | |
| AutoHandle hJob(0); | |
| if (memoryLimit != 0) { | |
| hJob = CreateJobObject(0, 0); | |
| bool success = false; | |
| if (hJob != 0) { | |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli; | |
| memset(&jeli, 0, sizeof(jeli)); | |
| jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY; | |
| jeli.ProcessMemoryLimit = uintptr_t(memoryLimit) * 1048576; | |
| if (SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, | |
| &jeli, sizeof(jeli))) { | |
| if (AssignProcessToJobObject(hJob, pi.hProcess)) | |
| success = true; | |
| } | |
| } | |
| if (!success) { | |
| SetLastError(GetLastError()); | |
| MakeErrMsg(ErrMsg, std::string("Unable to set memory limit")); | |
| TerminateProcess(pi.hProcess, 1); | |
| WaitForSingleObject(pi.hProcess, INFINITE); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| int | |
| Program::Wait(const Path &path, | |
| unsigned secondsToWait, | |
| std::string* ErrMsg) { | |
| if (Data_ == 0) { | |
| MakeErrMsg(ErrMsg, "Process not started!"); | |
| return -1; | |
| } | |
| Win32ProcessInfo* wpi = reinterpret_cast<Win32ProcessInfo*>(Data_); | |
| HANDLE hProcess = wpi->hProcess; | |
| // Wait for the process to terminate. | |
| DWORD millisecondsToWait = INFINITE; | |
| if (secondsToWait > 0) | |
| millisecondsToWait = secondsToWait * 1000; | |
| if (WaitForSingleObject(hProcess, millisecondsToWait) == WAIT_TIMEOUT) { | |
| if (!TerminateProcess(hProcess, 1)) { | |
| MakeErrMsg(ErrMsg, "Failed to terminate timed-out program."); | |
| // -2 indicates a crash or timeout as opposed to failure to execute. | |
| return -2; | |
| } | |
| WaitForSingleObject(hProcess, INFINITE); | |
| } | |
| // Get its exit status. | |
| DWORD status; | |
| BOOL rc = GetExitCodeProcess(hProcess, &status); | |
| DWORD err = GetLastError(); | |
| if (!rc) { | |
| SetLastError(err); | |
| MakeErrMsg(ErrMsg, "Failed getting status for program."); | |
| // -2 indicates a crash or timeout as opposed to failure to execute. | |
| return -2; | |
| } | |
| return status; | |
| } | |
| bool | |
| Program::Kill(std::string* ErrMsg) { | |
| if (Data_ == 0) { | |
| MakeErrMsg(ErrMsg, "Process not started!"); | |
| return true; | |
| } | |
| Win32ProcessInfo* wpi = reinterpret_cast<Win32ProcessInfo*>(Data_); | |
| HANDLE hProcess = wpi->hProcess; | |
| if (TerminateProcess(hProcess, 1) == 0) { | |
| MakeErrMsg(ErrMsg, "The process couldn't be killed!"); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool Program::ChangeStdinToBinary(){ | |
| int result = _setmode( _fileno(stdin), _O_BINARY ); | |
| return result == -1; | |
| } | |
| bool Program::ChangeStdoutToBinary(){ | |
| int result = _setmode( _fileno(stdout), _O_BINARY ); | |
| return result == -1; | |
| } | |
| bool Program::ChangeStderrToBinary(){ | |
| int result = _setmode( _fileno(stderr), _O_BINARY ); | |
| return result == -1; | |
| } | |
| } |