1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 18:54:02 +01:00
llvm-mirror/lib/Support/Windows/Program.inc
Abhina Sreeskantharajan eda191ef72 [SystemZ][z/OS][Windows] Add new functions that set Text/Binary mode for Stdin and Stdout based on OpenFlags
On Windows, we want to open a file in Binary mode if OF_CRLF bit is not set. On z/OS, we want to open a file in Binary mode if the OF_Text bit is not set.

This patch creates two new functions called ChangeStdinMode and ChangeStdoutMode which will take OpenFlags as an arg to determine which mode to set stdin and stdout to. This will enable patches like https://reviews.llvm.org/D100056 to not affect Windows when setting the OF_Text flag for raw_fd_streams.

Reviewed By: rnk

Differential Revision: https://reviews.llvm.org/D100130
2021-04-16 08:09:19 -04:00

576 lines
18 KiB
C++

//===- Win32/Program.cpp - Win32 Program Implementation ------- -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file provides the Win32 specific implementation of the Program class.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Windows/WindowsSupport.h"
#include "llvm/Support/WindowsError.h"
#include "llvm/Support/raw_ostream.h"
#include <psapi.h>
#include <cstdio>
#include <fcntl.h>
#include <io.h>
#include <malloc.h>
#include <numeric>
//===----------------------------------------------------------------------===//
//=== WARNING: Implementation here must contain only Win32 specific code
//=== and must not be UNIX code
//===----------------------------------------------------------------------===//
namespace llvm {
ProcessInfo::ProcessInfo() : Pid(0), Process(0), ReturnCode(0) {}
ErrorOr<std::string> sys::findProgramByName(StringRef Name,
ArrayRef<StringRef> Paths) {
assert(!Name.empty() && "Must have a name!");
if (Name.find_first_of("/\\") != StringRef::npos)
return std::string(Name);
const wchar_t *Path = nullptr;
std::wstring PathStorage;
if (!Paths.empty()) {
PathStorage.reserve(Paths.size() * MAX_PATH);
for (unsigned i = 0; i < Paths.size(); ++i) {
if (i)
PathStorage.push_back(L';');
StringRef P = Paths[i];
SmallVector<wchar_t, MAX_PATH> TmpPath;
if (std::error_code EC = windows::UTF8ToUTF16(P, TmpPath))
return EC;
PathStorage.append(TmpPath.begin(), TmpPath.end());
}
Path = PathStorage.c_str();
}
SmallVector<wchar_t, MAX_PATH> U16Name;
if (std::error_code EC = windows::UTF8ToUTF16(Name, U16Name))
return EC;
SmallVector<StringRef, 12> PathExts;
PathExts.push_back("");
PathExts.push_back(".exe"); // FIXME: This must be in %PATHEXT%.
if (const char *PathExtEnv = std::getenv("PATHEXT"))
SplitString(PathExtEnv, PathExts, ";");
SmallVector<char, MAX_PATH> U8Result;
for (StringRef Ext : PathExts) {
SmallVector<wchar_t, MAX_PATH> U16Result;
DWORD Len = MAX_PATH;
do {
U16Result.reserve(Len);
// Lets attach the extension manually. That is needed for files
// with a point in name like aaa.bbb. SearchPathW will not add extension
// from its argument to such files because it thinks they already had one.
SmallVector<wchar_t, MAX_PATH> U16NameExt;
if (std::error_code EC =
windows::UTF8ToUTF16(Twine(Name + Ext).str(), U16NameExt))
return EC;
Len = ::SearchPathW(Path, c_str(U16NameExt), nullptr,
U16Result.capacity(), U16Result.data(), nullptr);
} while (Len > U16Result.capacity());
if (Len == 0)
continue;
U16Result.set_size(Len);
if (std::error_code EC =
windows::UTF16ToUTF8(U16Result.data(), U16Result.size(), U8Result))
return EC;
if (sys::fs::can_execute(U8Result))
break; // Found it.
U8Result.clear();
}
if (U8Result.empty())
return mapWindowsError(::GetLastError());
return std::string(U8Result.begin(), U8Result.end());
}
bool MakeErrMsg(std::string *ErrMsg, const std::string &prefix) {
if (!ErrMsg)
return true;
char *buffer = NULL;
DWORD LastError = GetLastError();
DWORD R = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, LastError, 0, (LPSTR)&buffer, 1, NULL);
if (R)
*ErrMsg = prefix + ": " + buffer;
else
*ErrMsg = prefix + ": Unknown error";
*ErrMsg += " (0x" + llvm::utohexstr(LastError) + ")";
LocalFree(buffer);
return R != 0;
}
static HANDLE RedirectIO(Optional<StringRef> Path, int fd,
std::string *ErrMsg) {
HANDLE h;
if (!Path) {
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 = std::string(*Path);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
SmallVector<wchar_t, 128> fnameUnicode;
if (Path->empty()) {
// Don't play long-path tricks on "NUL".
if (windows::UTF8ToUTF16(fname, fnameUnicode))
return INVALID_HANDLE_VALUE;
} else {
if (sys::windows::widenPath(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, fname + ": Can't open file for " +
(fd ? "input" : "output"));
}
return h;
}
}
static bool Execute(ProcessInfo &PI, StringRef Program,
ArrayRef<StringRef> Args, Optional<ArrayRef<StringRef>> Env,
ArrayRef<Optional<StringRef>> Redirects,
unsigned MemoryLimit, std::string *ErrMsg,
BitVector *AffinityMask) {
if (!sys::fs::can_execute(Program)) {
if (ErrMsg)
*ErrMsg = "program not executable";
return false;
}
// can_execute may succeed by looking at Program + ".exe". CreateProcessW
// will implicitly add the .exe if we provide a command line without an
// executable path, but since we use an explicit executable, we have to add
// ".exe" ourselves.
SmallString<64> ProgramStorage;
if (!sys::fs::exists(Program))
Program = Twine(Program + ".exe").toStringRef(ProgramStorage);
// 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).
auto Result = flattenWindowsCommandLine(Args);
if (std::error_code ec = Result.getError()) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg, std::string("Unable to convert command-line to UTF-16"));
return false;
}
std::wstring Command = *Result;
// The pointer to the environment block for the new process.
std::vector<wchar_t> EnvBlock;
if (Env) {
// 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 (StringRef E : *Env) {
SmallVector<wchar_t, MAX_PATH> EnvString;
if (std::error_code ec = windows::UTF8ToUTF16(E, EnvString)) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg, "Unable to convert environment variable to UTF-16");
return false;
}
llvm::append_range(EnvBlock, EnvString);
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.empty()) {
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 = sys::windows::widenPath(Program, ProgramUtf16)) {
SetLastError(ec.value());
MakeErrMsg(ErrMsg,
std::string("Unable to convert application name to UTF-16"));
return false;
}
unsigned CreateFlags = CREATE_UNICODE_ENVIRONMENT;
if (AffinityMask)
CreateFlags |= CREATE_SUSPENDED;
std::vector<wchar_t> CommandUtf16(Command.size() + 1, 0);
std::copy(Command.begin(), Command.end(), CommandUtf16.begin());
BOOL rc = CreateProcessW(ProgramUtf16.data(), CommandUtf16.data(), 0, 0, TRUE,
CreateFlags, 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.Process = 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;
}
}
// Set the affinity mask
if (AffinityMask) {
::SetProcessAffinityMask(pi.hProcess,
(DWORD_PTR)AffinityMask->getData().front());
::ResumeThread(pi.hThread);
}
return true;
}
static bool argNeedsQuotes(StringRef Arg) {
if (Arg.empty())
return true;
return StringRef::npos != Arg.find_first_of("\t \"&\'()*<>\\`^|\n");
}
static std::string quoteSingleArg(StringRef Arg) {
std::string Result;
Result.push_back('"');
while (!Arg.empty()) {
size_t FirstNonBackslash = Arg.find_first_not_of('\\');
size_t BackslashCount = FirstNonBackslash;
if (FirstNonBackslash == StringRef::npos) {
// The entire remainder of the argument is backslashes. Escape all of
// them and just early out.
BackslashCount = Arg.size();
Result.append(BackslashCount * 2, '\\');
break;
}
if (Arg[FirstNonBackslash] == '\"') {
// This is an embedded quote. Escape all preceding backslashes, then
// add one additional backslash to escape the quote.
Result.append(BackslashCount * 2 + 1, '\\');
Result.push_back('\"');
} else {
// This is just a normal character. Don't escape any of the preceding
// backslashes, just append them as they are and then append the
// character.
Result.append(BackslashCount, '\\');
Result.push_back(Arg[FirstNonBackslash]);
}
// Drop all the backslashes, plus the following character.
Arg = Arg.drop_front(FirstNonBackslash + 1);
}
Result.push_back('"');
return Result;
}
namespace llvm {
ErrorOr<std::wstring> sys::flattenWindowsCommandLine(ArrayRef<StringRef> Args) {
std::string Command;
for (StringRef Arg : Args) {
if (argNeedsQuotes(Arg))
Command += quoteSingleArg(Arg);
else
Command += Arg;
Command.push_back(' ');
}
SmallVector<wchar_t, MAX_PATH> CommandUtf16;
if (std::error_code ec = windows::UTF8ToUTF16(Command, CommandUtf16))
return ec;
return std::wstring(CommandUtf16.begin(), CommandUtf16.end());
}
ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait,
bool WaitUntilChildTerminates, std::string *ErrMsg,
Optional<ProcessStatistics> *ProcStat) {
assert(PI.Pid && "invalid pid to wait on, process not started?");
assert((PI.Process && PI.Process != INVALID_HANDLE_VALUE) &&
"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;
if (ProcStat)
ProcStat->reset();
DWORD WaitStatus = WaitForSingleObject(PI.Process, milliSecondsToWait);
if (WaitStatus == WAIT_TIMEOUT) {
if (SecondsToWait) {
if (!TerminateProcess(PI.Process, 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.Process);
return WaitResult;
}
WaitForSingleObject(PI.Process, INFINITE);
CloseHandle(PI.Process);
} else {
// Non-blocking wait.
return ProcessInfo();
}
}
// Get process execution statistics.
if (ProcStat) {
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
PROCESS_MEMORY_COUNTERS MemInfo;
if (GetProcessTimes(PI.Process, &CreationTime, &ExitTime, &KernelTime,
&UserTime) &&
GetProcessMemoryInfo(PI.Process, &MemInfo, sizeof(MemInfo))) {
auto UserT = std::chrono::duration_cast<std::chrono::microseconds>(
toDuration(UserTime));
auto KernelT = std::chrono::duration_cast<std::chrono::microseconds>(
toDuration(KernelTime));
uint64_t PeakMemory = MemInfo.PeakPagefileUsage / 1024;
*ProcStat = ProcessStatistics{UserT + KernelT, UserT, PeakMemory};
}
}
// Get its exit status.
DWORD status;
BOOL rc = GetExitCodeProcess(PI.Process, &status);
DWORD err = GetLastError();
if (err != ERROR_INVALID_HANDLE)
CloseHandle(PI.Process);
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 llvm::sys::ChangeStdinMode(sys::fs::OpenFlags Flags){
if (!(Flags & fs::OF_CRLF))
return ChangeStdinToBinary();
return std::error_code();
}
std::error_code llvm::sys::ChangeStdoutMode(sys::fs::OpenFlags Flags){
if (!(Flags & fs::OF_CRLF))
return ChangeStdoutToBinary();
return std::error_code();
}
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();
}
std::error_code
llvm::sys::writeFileWithEncoding(StringRef FileName, StringRef Contents,
WindowsEncodingMethod Encoding) {
std::error_code EC;
llvm::raw_fd_ostream OS(FileName, EC, llvm::sys::fs::OF_TextWithCRLF);
if (EC)
return EC;
if (Encoding == WEM_UTF8) {
OS << Contents;
} else if (Encoding == WEM_CurrentCodePage) {
SmallVector<wchar_t, 1> ArgsUTF16;
SmallVector<char, 1> ArgsCurCP;
if ((EC = windows::UTF8ToUTF16(Contents, ArgsUTF16)))
return EC;
if ((EC = windows::UTF16ToCurCP(
ArgsUTF16.data(), ArgsUTF16.size(), ArgsCurCP)))
return EC;
OS.write(ArgsCurCP.data(), ArgsCurCP.size());
} else if (Encoding == WEM_UTF16) {
SmallVector<wchar_t, 1> ArgsUTF16;
if ((EC = windows::UTF8ToUTF16(Contents, ArgsUTF16)))
return EC;
// Endianness guessing
char BOM[2];
uint16_t src = UNI_UTF16_BYTE_ORDER_MARK_NATIVE;
memcpy(BOM, &src, 2);
OS.write(BOM, 2);
OS.write((char *)ArgsUTF16.data(), ArgsUTF16.size() << 1);
} else {
llvm_unreachable("Unknown encoding");
}
if (OS.has_error())
return make_error_code(errc::io_error);
return EC;
}
bool llvm::sys::commandLineFitsWithinSystemLimits(StringRef Program,
ArrayRef<StringRef> Args) {
// The documentation on CreateProcessW states that the size of the argument
// lpCommandLine must not be greater than 32767 characters, including the
// Unicode terminating null character. We use smaller value to reduce risk
// of getting invalid command line due to unaccounted factors.
static const size_t MaxCommandStringLength = 32000;
SmallVector<StringRef, 8> FullArgs;
FullArgs.push_back(Program);
FullArgs.append(Args.begin(), Args.end());
auto Result = flattenWindowsCommandLine(FullArgs);
assert(!Result.getError());
return (Result->size() + 1) <= MaxCommandStringLength;
}
}