581 lines · plain
1//===- Win32/Program.cpp - Win32 Program Implementation ------- -*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file provides the Win32 specific implementation of the Program class.10//11//===----------------------------------------------------------------------===//12 13#include "llvm/ADT/BitVector.h"14#include "llvm/ADT/StringExtras.h"15#include "llvm/Support/ConvertUTF.h"16#include "llvm/Support/Errc.h"17#include "llvm/Support/FileSystem.h"18#include "llvm/Support/Path.h"19#include "llvm/Support/Windows/WindowsSupport.h"20#include "llvm/Support/WindowsError.h"21#include "llvm/Support/raw_ostream.h"22#include <cstdio>23#include <fcntl.h>24#include <io.h>25#include <malloc.h>26#include <psapi.h>27 28//===----------------------------------------------------------------------===//29//=== WARNING: Implementation here must contain only Win32 specific code30//=== and must not be UNIX code31//===----------------------------------------------------------------------===//32 33namespace llvm {34 35ProcessInfo::ProcessInfo() : Pid(0), Process(0), ReturnCode(0) {}36 37ErrorOr<std::string> sys::findProgramByName(StringRef Name,38 ArrayRef<StringRef> Paths) {39 assert(!Name.empty() && "Must have a name!");40 41 if (Name.find_first_of("/\\") != StringRef::npos)42 return std::string(Name);43 44 const wchar_t *Path = nullptr;45 std::wstring PathStorage;46 if (!Paths.empty()) {47 PathStorage.reserve(Paths.size() * MAX_PATH);48 for (unsigned i = 0; i < Paths.size(); ++i) {49 if (i)50 PathStorage.push_back(L';');51 StringRef P = Paths[i];52 SmallVector<wchar_t, MAX_PATH> TmpPath;53 if (std::error_code EC = windows::UTF8ToUTF16(P, TmpPath))54 return EC;55 PathStorage.append(TmpPath.begin(), TmpPath.end());56 }57 Path = PathStorage.c_str();58 }59 60 SmallVector<wchar_t, MAX_PATH> U16Name;61 if (std::error_code EC = windows::UTF8ToUTF16(Name, U16Name))62 return EC;63 64 SmallVector<StringRef, 12> PathExts;65 PathExts.push_back("");66 PathExts.push_back(".exe"); // FIXME: This must be in %PATHEXT%.67 if (const char *PathExtEnv = std::getenv("PATHEXT"))68 SplitString(PathExtEnv, PathExts, ";");69 70 SmallVector<char, MAX_PATH> U8Result;71 for (StringRef Ext : PathExts) {72 SmallVector<wchar_t, MAX_PATH> U16Result;73 DWORD Len = MAX_PATH;74 do {75 U16Result.resize_for_overwrite(Len);76 // Lets attach the extension manually. That is needed for files77 // with a point in name like aaa.bbb. SearchPathW will not add extension78 // from its argument to such files because it thinks they already had one.79 SmallVector<wchar_t, MAX_PATH> U16NameExt;80 if (std::error_code EC =81 windows::UTF8ToUTF16(Twine(Name + Ext).str(), U16NameExt))82 return EC;83 84 Len = ::SearchPathW(Path, c_str(U16NameExt), nullptr, U16Result.size(),85 U16Result.data(), nullptr);86 } while (Len > U16Result.size());87 88 if (Len == 0)89 continue;90 91 U16Result.truncate(Len);92 93 if (std::error_code EC =94 windows::UTF16ToUTF8(U16Result.data(), U16Result.size(), U8Result))95 return EC;96 97 if (sys::fs::can_execute(U8Result))98 break; // Found it.99 100 U8Result.clear();101 }102 103 if (U8Result.empty())104 return mapWindowsError(::GetLastError());105 106 llvm::sys::path::make_preferred(U8Result);107 return std::string(U8Result.begin(), U8Result.end());108}109 110bool MakeErrMsg(std::string *ErrMsg, const std::string &prefix) {111 if (!ErrMsg)112 return true;113 char *buffer = NULL;114 DWORD LastError = GetLastError();115 DWORD R = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |116 FORMAT_MESSAGE_FROM_SYSTEM |117 FORMAT_MESSAGE_MAX_WIDTH_MASK,118 NULL, LastError, 0, (LPSTR)&buffer, 1, NULL);119 if (R)120 *ErrMsg = prefix + ": " + buffer;121 else122 *ErrMsg = prefix + ": Unknown error";123 *ErrMsg += " (0x" + llvm::utohexstr(LastError) + ")";124 125 LocalFree(buffer);126 return R != 0;127}128 129static HANDLE RedirectIO(std::optional<StringRef> Path, int fd,130 std::string *ErrMsg) {131 HANDLE h;132 if (!Path) {133 if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)_get_osfhandle(fd),134 GetCurrentProcess(), &h, 0, TRUE,135 DUPLICATE_SAME_ACCESS))136 return INVALID_HANDLE_VALUE;137 return h;138 }139 140 std::string fname;141 if (Path->empty())142 fname = "NUL";143 else144 fname = std::string(*Path);145 146 SECURITY_ATTRIBUTES sa;147 sa.nLength = sizeof(sa);148 sa.lpSecurityDescriptor = 0;149 sa.bInheritHandle = TRUE;150 151 SmallVector<wchar_t, 128> fnameUnicode;152 if (Path->empty()) {153 // Don't play long-path tricks on "NUL".154 if (windows::UTF8ToUTF16(fname, fnameUnicode))155 return INVALID_HANDLE_VALUE;156 } else {157 if (sys::windows::widenPath(fname, fnameUnicode))158 return INVALID_HANDLE_VALUE;159 }160 h = CreateFileW(fnameUnicode.data(), fd ? GENERIC_WRITE : GENERIC_READ,161 FILE_SHARE_READ, &sa, fd == 0 ? OPEN_EXISTING : CREATE_ALWAYS,162 FILE_ATTRIBUTE_NORMAL, NULL);163 if (h == INVALID_HANDLE_VALUE) {164 MakeErrMsg(ErrMsg,165 fname + ": Can't open file for " + (fd ? "input" : "output"));166 }167 168 return h;169}170 171} // namespace llvm172 173static bool Execute(ProcessInfo &PI, StringRef Program,174 ArrayRef<StringRef> Args,175 std::optional<ArrayRef<StringRef>> Env,176 ArrayRef<std::optional<StringRef>> Redirects,177 unsigned MemoryLimit, std::string *ErrMsg,178 BitVector *AffinityMask, bool DetachProcess) {179 if (!sys::fs::can_execute(Program)) {180 if (ErrMsg)181 *ErrMsg = "program not executable";182 return false;183 }184 185 // can_execute may succeed by looking at Program + ".exe". CreateProcessW186 // will implicitly add the .exe if we provide a command line without an187 // executable path, but since we use an explicit executable, we have to add188 // ".exe" ourselves.189 SmallString<64> ProgramStorage;190 if (!sys::fs::exists(Program))191 Program = Twine(Program + ".exe").toStringRef(ProgramStorage);192 193 // Windows wants a command line, not an array of args, to pass to the new194 // process. We have to concatenate them all, while quoting the args that195 // have embedded spaces (or are empty).196 auto Result = flattenWindowsCommandLine(Args);197 if (std::error_code ec = Result.getError()) {198 SetLastError(ec.value());199 MakeErrMsg(ErrMsg, std::string("Unable to convert command-line to UTF-16"));200 return false;201 }202 std::wstring Command = *Result;203 204 // The pointer to the environment block for the new process.205 std::vector<wchar_t> EnvBlock;206 207 if (Env) {208 // An environment block consists of a null-terminated block of209 // null-terminated strings. Convert the array of environment variables to210 // an environment block by concatenating them.211 for (StringRef E : *Env) {212 SmallVector<wchar_t, MAX_PATH> EnvString;213 if (std::error_code ec = windows::UTF8ToUTF16(E, EnvString)) {214 SetLastError(ec.value());215 MakeErrMsg(ErrMsg, "Unable to convert environment variable to UTF-16");216 return false;217 }218 219 llvm::append_range(EnvBlock, EnvString);220 EnvBlock.push_back(0);221 }222 // Empty environments need to be terminated with two nulls.223 if (Env->size() == 0)224 EnvBlock.push_back(0);225 EnvBlock.push_back(0);226 }227 228 // Create a child process.229 STARTUPINFOW si;230 memset(&si, 0, sizeof(si));231 si.cb = sizeof(si);232 si.hStdInput = INVALID_HANDLE_VALUE;233 si.hStdOutput = INVALID_HANDLE_VALUE;234 si.hStdError = INVALID_HANDLE_VALUE;235 236 if (!Redirects.empty()) {237 si.dwFlags = STARTF_USESTDHANDLES;238 239 si.hStdInput = RedirectIO(Redirects[0], 0, ErrMsg);240 if (si.hStdInput == INVALID_HANDLE_VALUE) {241 MakeErrMsg(ErrMsg, "can't redirect stdin");242 return false;243 }244 si.hStdOutput = RedirectIO(Redirects[1], 1, ErrMsg);245 if (si.hStdOutput == INVALID_HANDLE_VALUE) {246 CloseHandle(si.hStdInput);247 MakeErrMsg(ErrMsg, "can't redirect stdout");248 return false;249 }250 if (Redirects[1] && Redirects[2] && *Redirects[1] == *Redirects[2]) {251 // If stdout and stderr should go to the same place, redirect stderr252 // to the handle already open for stdout.253 if (!DuplicateHandle(GetCurrentProcess(), si.hStdOutput,254 GetCurrentProcess(), &si.hStdError, 0, TRUE,255 DUPLICATE_SAME_ACCESS)) {256 CloseHandle(si.hStdInput);257 CloseHandle(si.hStdOutput);258 MakeErrMsg(ErrMsg, "can't dup stderr to stdout");259 return false;260 }261 } else {262 // Just redirect stderr263 si.hStdError = RedirectIO(Redirects[2], 2, ErrMsg);264 if (si.hStdError == INVALID_HANDLE_VALUE) {265 CloseHandle(si.hStdInput);266 CloseHandle(si.hStdOutput);267 MakeErrMsg(ErrMsg, "can't redirect stderr");268 return false;269 }270 }271 }272 273 PROCESS_INFORMATION pi;274 memset(&pi, 0, sizeof(pi));275 276 fflush(stdout);277 fflush(stderr);278 279 SmallVector<wchar_t, MAX_PATH> ProgramUtf16;280 if (std::error_code ec = sys::windows::widenPath(Program, ProgramUtf16)) {281 SetLastError(ec.value());282 MakeErrMsg(ErrMsg,283 std::string("Unable to convert application name to UTF-16"));284 return false;285 }286 287 unsigned CreateFlags = CREATE_UNICODE_ENVIRONMENT;288 if (AffinityMask)289 CreateFlags |= CREATE_SUSPENDED;290 if (DetachProcess)291 CreateFlags |= DETACHED_PROCESS;292 293 std::vector<wchar_t> CommandUtf16(Command.size() + 1, 0);294 std::copy(Command.begin(), Command.end(), CommandUtf16.begin());295 BOOL rc = CreateProcessW(ProgramUtf16.data(), CommandUtf16.data(), 0, 0, TRUE,296 CreateFlags, EnvBlock.empty() ? 0 : EnvBlock.data(),297 0, &si, &pi);298 DWORD err = GetLastError();299 300 // Regardless of whether the process got created or not, we are done with301 // the handles we created for it to inherit.302 CloseHandle(si.hStdInput);303 CloseHandle(si.hStdOutput);304 CloseHandle(si.hStdError);305 306 // Now return an error if the process didn't get created.307 if (!rc) {308 SetLastError(err);309 MakeErrMsg(ErrMsg,310 std::string("Couldn't execute program '") + Program.str() + "'");311 return false;312 }313 314 PI.Pid = pi.dwProcessId;315 PI.Process = pi.hProcess;316 317 // Make sure these get closed no matter what.318 ScopedCommonHandle hThread(pi.hThread);319 320 // Assign the process to a job if a memory limit is defined.321 ScopedJobHandle hJob;322 if (MemoryLimit != 0) {323 hJob = CreateJobObjectW(0, 0);324 bool success = false;325 if (hJob) {326 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;327 memset(&jeli, 0, sizeof(jeli));328 jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;329 jeli.ProcessMemoryLimit = uintptr_t(MemoryLimit) * 1048576;330 if (SetInformationJobObject(hJob, JobObjectExtendedLimitInformation,331 &jeli, sizeof(jeli))) {332 if (AssignProcessToJobObject(hJob, pi.hProcess))333 success = true;334 }335 }336 if (!success) {337 SetLastError(GetLastError());338 MakeErrMsg(ErrMsg, std::string("Unable to set memory limit"));339 TerminateProcess(pi.hProcess, 1);340 WaitForSingleObject(pi.hProcess, INFINITE);341 return false;342 }343 }344 345 // Set the affinity mask346 if (AffinityMask) {347 ::SetProcessAffinityMask(pi.hProcess,348 (DWORD_PTR)AffinityMask->getData().front());349 ::ResumeThread(pi.hThread);350 }351 352 return true;353}354 355static bool argNeedsQuotes(StringRef Arg) {356 if (Arg.empty())357 return true;358 return StringRef::npos != Arg.find_first_of("\t \"&\'()*<>\\`^|\n");359}360 361static std::string quoteSingleArg(StringRef Arg) {362 std::string Result;363 Result.push_back('"');364 365 while (!Arg.empty()) {366 size_t FirstNonBackslash = Arg.find_first_not_of('\\');367 size_t BackslashCount = FirstNonBackslash;368 if (FirstNonBackslash == StringRef::npos) {369 // The entire remainder of the argument is backslashes. Escape all of370 // them and just early out.371 BackslashCount = Arg.size();372 Result.append(BackslashCount * 2, '\\');373 break;374 }375 376 if (Arg[FirstNonBackslash] == '\"') {377 // This is an embedded quote. Escape all preceding backslashes, then378 // add one additional backslash to escape the quote.379 Result.append(BackslashCount * 2 + 1, '\\');380 Result.push_back('\"');381 } else {382 // This is just a normal character. Don't escape any of the preceding383 // backslashes, just append them as they are and then append the384 // character.385 Result.append(BackslashCount, '\\');386 Result.push_back(Arg[FirstNonBackslash]);387 }388 389 // Drop all the backslashes, plus the following character.390 Arg = Arg.drop_front(FirstNonBackslash + 1);391 }392 393 Result.push_back('"');394 return Result;395}396 397namespace llvm {398ErrorOr<std::wstring> sys::flattenWindowsCommandLine(ArrayRef<StringRef> Args) {399 std::string Command;400 for (StringRef Arg : Args) {401 if (argNeedsQuotes(Arg))402 Command += quoteSingleArg(Arg);403 else404 Command += Arg;405 406 Command.push_back(' ');407 }408 409 SmallVector<wchar_t, MAX_PATH> CommandUtf16;410 if (std::error_code ec = windows::UTF8ToUTF16(Command, CommandUtf16))411 return ec;412 413 return std::wstring(CommandUtf16.begin(), CommandUtf16.end());414}415 416ProcessInfo sys::Wait(const ProcessInfo &PI,417 std::optional<unsigned> SecondsToWait,418 std::string *ErrMsg,419 std::optional<ProcessStatistics> *ProcStat,420 bool Polling) {421 assert(PI.Pid && "invalid pid to wait on, process not started?");422 assert((PI.Process && PI.Process != INVALID_HANDLE_VALUE) &&423 "invalid process handle to wait on, process not started?");424 DWORD milliSecondsToWait = SecondsToWait ? *SecondsToWait * 1000 : INFINITE;425 426 ProcessInfo WaitResult = PI;427 if (ProcStat)428 ProcStat->reset();429 DWORD WaitStatus = WaitForSingleObject(PI.Process, milliSecondsToWait);430 if (WaitStatus == WAIT_TIMEOUT) {431 if (!Polling && *SecondsToWait > 0) {432 if (!TerminateProcess(PI.Process, 1)) {433 if (ErrMsg)434 MakeErrMsg(ErrMsg, "Failed to terminate timed-out program");435 436 // -2 indicates a crash or timeout as opposed to failure to execute.437 WaitResult.ReturnCode = -2;438 CloseHandle(PI.Process);439 return WaitResult;440 }441 WaitForSingleObject(PI.Process, INFINITE);442 CloseHandle(PI.Process);443 } else {444 // Non-blocking wait.445 return ProcessInfo();446 }447 }448 449 // Get process execution statistics.450 if (ProcStat) {451 FILETIME CreationTime, ExitTime, KernelTime, UserTime;452 PROCESS_MEMORY_COUNTERS MemInfo;453 if (GetProcessTimes(PI.Process, &CreationTime, &ExitTime, &KernelTime,454 &UserTime) &&455 GetProcessMemoryInfo(PI.Process, &MemInfo, sizeof(MemInfo))) {456 auto UserT = std::chrono::duration_cast<std::chrono::microseconds>(457 toDuration(UserTime));458 auto KernelT = std::chrono::duration_cast<std::chrono::microseconds>(459 toDuration(KernelTime));460 uint64_t PeakMemory = MemInfo.PeakPagefileUsage / 1024;461 *ProcStat = ProcessStatistics{UserT + KernelT, UserT, PeakMemory};462 }463 }464 465 // Get its exit status.466 DWORD status;467 BOOL rc = GetExitCodeProcess(PI.Process, &status);468 DWORD err = GetLastError();469 if (err != ERROR_INVALID_HANDLE)470 CloseHandle(PI.Process);471 472 if (!rc) {473 SetLastError(err);474 if (ErrMsg)475 MakeErrMsg(ErrMsg, "Failed getting status for program");476 477 // -2 indicates a crash or timeout as opposed to failure to execute.478 WaitResult.ReturnCode = -2;479 return WaitResult;480 }481 482 if (!status)483 return WaitResult;484 485 // Pass 10(Warning) and 11(Error) to the callee as negative value.486 if ((status & 0xBFFF0000U) == 0x80000000U)487 WaitResult.ReturnCode = static_cast<int>(status);488 else if (status & 0xFF)489 WaitResult.ReturnCode = status & 0x7FFFFFFF;490 else491 WaitResult.ReturnCode = 1;492 493 return WaitResult;494}495 496std::error_code llvm::sys::ChangeStdinMode(sys::fs::OpenFlags Flags) {497 if (!(Flags & fs::OF_CRLF))498 return ChangeStdinToBinary();499 return std::error_code();500}501 502std::error_code llvm::sys::ChangeStdoutMode(sys::fs::OpenFlags Flags) {503 if (!(Flags & fs::OF_CRLF))504 return ChangeStdoutToBinary();505 return std::error_code();506}507 508std::error_code sys::ChangeStdinToBinary() {509 int result = _setmode(_fileno(stdin), _O_BINARY);510 if (result == -1)511 return errnoAsErrorCode();512 return std::error_code();513}514 515std::error_code sys::ChangeStdoutToBinary() {516 int result = _setmode(_fileno(stdout), _O_BINARY);517 if (result == -1)518 return errnoAsErrorCode();519 return std::error_code();520}521 522std::error_code523llvm::sys::writeFileWithEncoding(StringRef FileName, StringRef Contents,524 WindowsEncodingMethod Encoding) {525 std::error_code EC;526 llvm::raw_fd_ostream OS(FileName, EC, llvm::sys::fs::OF_TextWithCRLF);527 if (EC)528 return EC;529 530 if (Encoding == WEM_UTF8) {531 OS << Contents;532 } else if (Encoding == WEM_CurrentCodePage) {533 SmallVector<wchar_t, 1> ArgsUTF16;534 SmallVector<char, 1> ArgsCurCP;535 536 if ((EC = windows::UTF8ToUTF16(Contents, ArgsUTF16)))537 return EC;538 539 if ((EC = windows::UTF16ToCurCP(ArgsUTF16.data(), ArgsUTF16.size(),540 ArgsCurCP)))541 return EC;542 543 OS.write(ArgsCurCP.data(), ArgsCurCP.size());544 } else if (Encoding == WEM_UTF16) {545 SmallVector<wchar_t, 1> ArgsUTF16;546 547 if ((EC = windows::UTF8ToUTF16(Contents, ArgsUTF16)))548 return EC;549 550 // Endianness guessing551 char BOM[2];552 uint16_t src = UNI_UTF16_BYTE_ORDER_MARK_NATIVE;553 memcpy(BOM, &src, 2);554 OS.write(BOM, 2);555 OS.write((char *)ArgsUTF16.data(), ArgsUTF16.size() << 1);556 } else {557 llvm_unreachable("Unknown encoding");558 }559 560 if (OS.has_error())561 return make_error_code(errc::io_error);562 563 return EC;564}565 566bool llvm::sys::commandLineFitsWithinSystemLimits(StringRef Program,567 ArrayRef<StringRef> Args) {568 // The documentation on CreateProcessW states that the size of the argument569 // lpCommandLine must not be greater than 32767 characters, including the570 // Unicode terminating null character. We use smaller value to reduce risk571 // of getting invalid command line due to unaccounted factors.572 static const size_t MaxCommandStringLength = 32000;573 SmallVector<StringRef, 8> FullArgs;574 FullArgs.push_back(Program);575 FullArgs.append(Args.begin(), Args.end());576 auto Result = flattenWindowsCommandLine(FullArgs);577 assert(!Result.getError());578 return (Result->size() + 1) <= MaxCommandStringLength;579}580} // namespace llvm581