llvm-6502/lib/Support/Windows/Program.inc
Rafael Espindola 749c73842d Refactor argument serialization logic when executing process. NFC.
This patch refactors the argument serialization logic used in the Execute
function, used to launch new Windows processes. There is a critical step that
joins char** arguments into a single string, building the command line used to
launch the new process, and the readability of this code is improved if this
part is refactored in its own helper function.

Patch by Rafael Auler!

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@216411 91177308-0d34-0410-b5e6-96231b3b80d8
2014-08-25 22:15:06 +00:00

459 lines
14 KiB
C++

//===- 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 "WindowsSupport.h"
#include "llvm/Support/FileSystem.h"
#include <cstdio>
#include <fcntl.h>
#include <io.h>
#include <malloc.h>
//===----------------------------------------------------------------------===//
//=== WARNING: Implementation here must contain only Win32 specific code
//=== and must not be UNIX code
//===----------------------------------------------------------------------===//
namespace llvm {
using namespace sys;
ProcessInfo::ProcessInfo() : ProcessHandle(0), Pid(0), ReturnCode(0) {}
// This function just uses the PATH environment variable to find the program.
std::string sys::FindProgramByName(const std::string &progName) {
// Check some degenerate cases
if (progName.length() == 0) // no program
return "";
std::string temp = progName;
// 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.
SmallVector<wchar_t, MAX_PATH> progNameUnicode;
if (windows::UTF8ToUTF16(progName, progNameUnicode))
return "";
SmallVector<wchar_t, MAX_PATH> buffer;
DWORD len = MAX_PATH;
do {
buffer.reserve(len);
len = ::SearchPathW(NULL, progNameUnicode.data(), L".exe",
buffer.capacity(), buffer.data(), NULL);
// See if it wasn't found.
if (len == 0)
return "";
// Buffer was too small; grow and retry.
} while (len > buffer.capacity());
buffer.set_size(len);
SmallVector<char, MAX_PATH> result;
if (windows::UTF16ToUTF8(buffer.begin(), buffer.size(), result))
return "";
return std::string(result.data(), result.size());
}
static HANDLE RedirectIO(const StringRef *path, int fd, std::string* ErrMsg) {
HANDLE h;
if (path == 0) {
if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)_get_osfhandle(fd),
GetCurrentProcess(), &h,
0, TRUE, DUPLICATE_SAME_ACCESS))
return INVALID_HANDLE_VALUE;
return h;
}
std::string fname;
if (path->empty())
fname = "NUL";
else
fname = *path;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
SmallVector<wchar_t, 128> fnameUnicode;
if (windows::UTF8ToUTF16(fname, fnameUnicode))
return INVALID_HANDLE_VALUE;
h = CreateFileW(fnameUnicode.data(), 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;
}
/// CountPrecedingBackslashes - Returns the number of backslashes preceding Cur
/// in the C string Start.
static unsigned int CountPrecedingBackslashes(const char *Start,
const char *Cur) {
unsigned int Count = 0;
--Cur;
while (Cur >= Start && *Cur == '\\') {
++Count;
--Cur;
}
return Count;
}
/// EscapePrecedingEscapes - Append a backslash to Dst for every backslash
/// preceding Cur in the Start string. Assumes Dst has enough space.
static char *EscapePrecedingEscapes(char *Dst, const char *Start,
const char *Cur) {
unsigned PrecedingEscapes = CountPrecedingBackslashes(Start, Cur);
while (PrecedingEscapes > 0) {
*Dst++ = '\\';
--PrecedingEscapes;
}
return Dst;
}
/// 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) {
const char *Start = Str;
bool Quoted = ArgNeedsQuotes(Str);
unsigned int len = Quoted ? 2 : 0;
while (*Str != '\0') {
if (*Str == '\"') {
// We need to add a backslash, but ensure that it isn't escaped.
unsigned PrecedingEscapes = CountPrecedingBackslashes(Start, Str);
len += PrecedingEscapes + 1;
}
// Note that we *don't* need to escape runs of backslashes that don't
// precede a double quote! See MSDN:
// http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx
++len;
++Str;
}
if (Quoted) {
// Make sure the closing quote doesn't get escaped by a trailing backslash.
unsigned PrecedingEscapes = CountPrecedingBackslashes(Start, Str);
len += PrecedingEscapes + 1;
}
return len;
}
}
static std::unique_ptr<char[]> flattenArgs(const char **args) {
// 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.
std::unique_ptr<char[]> command(new char[len+1]);
char *p = command.get();
for (unsigned i = 0; args[i]; i++) {
const char *arg = args[i];
const char *start = arg;
bool needsQuoting = ArgNeedsQuotes(arg);
if (needsQuoting)
*p++ = '"';
while (*arg != '\0') {
if (*arg == '\"') {
// Escape all preceding escapes (if any), and then escape the quote.
p = EscapePrecedingEscapes(p, start, arg);
*p++ = '\\';
}
*p++ = *arg++;
}
if (needsQuoting) {
// Make sure our quote doesn't get escaped by a trailing backslash.
p = EscapePrecedingEscapes(p, start, arg);
*p++ = '"';
}
*p++ = ' ';
}
*p = 0;
return command;
}
static bool Execute(ProcessInfo &PI, StringRef Program, const char **args,
const char **envp, const StringRef **redirects,
unsigned memoryLimit, std::string *ErrMsg) {
if (!sys::fs::can_execute(Program)) {
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).
std::unique_ptr<char[]> command = flattenArgs(args);
// The pointer to the environment block for the new process.
std::vector<wchar_t> EnvBlock;
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.
for (unsigned i = 0; envp[i]; ++i) {
SmallVector<wchar_t, MAX_PATH> EnvString;
if (std::error_code ec = windows::UTF8ToUTF16(envp[i], EnvString)) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg, "Unable to convert environment variable to UTF-16");
return false;
}
EnvBlock.insert(EnvBlock.end(), EnvString.begin(), EnvString.end());
EnvBlock.push_back(0);
}
EnvBlock.push_back(0);
}
// Create a child process.
STARTUPINFOW 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.
if (!DuplicateHandle(GetCurrentProcess(), si.hStdOutput,
GetCurrentProcess(), &si.hStdError,
0, TRUE, DUPLICATE_SAME_ACCESS)) {
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
MakeErrMsg(ErrMsg, "can't dup stderr to stdout");
return false;
}
} 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);
SmallVector<wchar_t, MAX_PATH> ProgramUtf16;
if (std::error_code ec = windows::UTF8ToUTF16(Program, ProgramUtf16)) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg,
std::string("Unable to convert application name to UTF-16"));
return false;
}
SmallVector<wchar_t, MAX_PATH> CommandUtf16;
if (std::error_code ec = windows::UTF8ToUTF16(command.get(), CommandUtf16)) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg,
std::string("Unable to convert command-line to UTF-16"));
return false;
}
BOOL rc = CreateProcessW(ProgramUtf16.data(), CommandUtf16.data(), 0, 0,
TRUE, CREATE_UNICODE_ENVIRONMENT,
EnvBlock.empty() ? 0 : EnvBlock.data(), 0, &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 '") +
Program.str() + "'");
return false;
}
PI.Pid = pi.dwProcessId;
PI.ProcessHandle = pi.hProcess;
// Make sure these get closed no matter what.
ScopedCommonHandle hThread(pi.hThread);
// Assign the process to a job if a memory limit is defined.
ScopedJobHandle hJob;
if (memoryLimit != 0) {
hJob = CreateJobObjectW(0, 0);
bool success = false;
if (hJob) {
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;
}
namespace llvm {
ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait,
bool WaitUntilChildTerminates, std::string *ErrMsg) {
assert(PI.Pid && "invalid pid to wait on, process not started?");
assert(PI.ProcessHandle &&
"invalid process handle to wait on, process not started?");
DWORD milliSecondsToWait = 0;
if (WaitUntilChildTerminates)
milliSecondsToWait = INFINITE;
else if (SecondsToWait > 0)
milliSecondsToWait = SecondsToWait * 1000;
ProcessInfo WaitResult = PI;
DWORD WaitStatus = WaitForSingleObject(PI.ProcessHandle, milliSecondsToWait);
if (WaitStatus == WAIT_TIMEOUT) {
if (SecondsToWait) {
if (!TerminateProcess(PI.ProcessHandle, 1)) {
if (ErrMsg)
MakeErrMsg(ErrMsg, "Failed to terminate timed-out program.");
// -2 indicates a crash or timeout as opposed to failure to execute.
WaitResult.ReturnCode = -2;
CloseHandle(PI.ProcessHandle);
return WaitResult;
}
WaitForSingleObject(PI.ProcessHandle, INFINITE);
CloseHandle(PI.ProcessHandle);
} else {
// Non-blocking wait.
return ProcessInfo();
}
}
// Get its exit status.
DWORD status;
BOOL rc = GetExitCodeProcess(PI.ProcessHandle, &status);
DWORD err = GetLastError();
CloseHandle(PI.ProcessHandle);
if (!rc) {
SetLastError(err);
if (ErrMsg)
MakeErrMsg(ErrMsg, "Failed getting status for program.");
// -2 indicates a crash or timeout as opposed to failure to execute.
WaitResult.ReturnCode = -2;
return WaitResult;
}
if (!status)
return WaitResult;
// Pass 10(Warning) and 11(Error) to the callee as negative value.
if ((status & 0xBFFF0000U) == 0x80000000U)
WaitResult.ReturnCode = static_cast<int>(status);
else if (status & 0xFF)
WaitResult.ReturnCode = status & 0x7FFFFFFF;
else
WaitResult.ReturnCode = 1;
return WaitResult;
}
std::error_code sys::ChangeStdinToBinary(){
int result = _setmode( _fileno(stdin), _O_BINARY );
if (result == -1)
return std::error_code(errno, std::generic_category());
return std::error_code();
}
std::error_code sys::ChangeStdoutToBinary(){
int result = _setmode( _fileno(stdout), _O_BINARY );
if (result == -1)
return std::error_code(errno, std::generic_category());
return std::error_code();
}
bool llvm::sys::argumentsFitWithinSystemLimits(ArrayRef<const char*> Args) {
// The documented max length of the command line passed to CreateProcess.
static const size_t MaxCommandStringLength = 32768;
size_t ArgLength = 0;
for (ArrayRef<const char*>::iterator I = Args.begin(), E = Args.end();
I != E; ++I) {
// Account for the trailing space for every arg but the last one and the
// trailing NULL of the last argument.
ArgLength += ArgLenWithQuotes(*I) + 1;
if (ArgLength > MaxCommandStringLength) {
return false;
}
}
return true;
}
}