//===- 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 #include #include #include #include #include //===----------------------------------------------------------------------===// //=== 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 sys::findProgramByName(StringRef Name, ArrayRef 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 TmpPath; if (std::error_code EC = windows::UTF8ToUTF16(P, TmpPath)) return EC; PathStorage.append(TmpPath.begin(), TmpPath.end()); } Path = PathStorage.c_str(); } SmallVector U16Name; if (std::error_code EC = windows::UTF8ToUTF16(Name, U16Name)) return EC; SmallVector 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 U16Result; DWORD Len = MAX_PATH; for (StringRef Ext : PathExts) { SmallVector U16Ext; if (std::error_code EC = windows::UTF8ToUTF16(Ext, U16Ext)) return EC; 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 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) break; // Found it. } if (Len == 0) return mapWindowsError(::GetLastError()); U16Result.set_size(Len); SmallVector U8Result; if (std::error_code EC = windows::UTF16ToUTF8(U16Result.data(), U16Result.size(), U8Result)) return EC; 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 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 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 Args, Optional> Env, ArrayRef> Redirects, unsigned MemoryLimit, std::string *ErrMsg) { 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). std::string Command = flattenWindowsCommandLine(Args); // The pointer to the environment block for the new process. std::vector 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 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; } 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.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 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; } SmallVector CommandUtf16; if (std::error_code ec = windows::UTF8ToUTF16(Command, 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.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; } } 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 { std::string sys::flattenWindowsCommandLine(ArrayRef Args) { std::string Command; for (StringRef Arg : Args) { if (argNeedsQuotes(Arg)) Command += quoteSingleArg(Arg); else Command += Arg; Command.push_back(' '); } return Command; } ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait, bool WaitUntilChildTerminates, std::string *ErrMsg, Optional *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( toDuration(UserTime)); auto KernelT = std::chrono::duration_cast( 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(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(); } 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_Text); if (EC) return EC; if (Encoding == WEM_UTF8) { OS << Contents; } else if (Encoding == WEM_CurrentCodePage) { SmallVector ArgsUTF16; SmallVector 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 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 Args) { // The documented max length of the command line passed to CreateProcess. static const size_t MaxCommandStringLength = 32768; SmallVector FullArgs; FullArgs.push_back(Program); FullArgs.append(Args.begin(), Args.end()); std::string Result = flattenWindowsCommandLine(FullArgs); return (Result.size() + 1) <= MaxCommandStringLength; } }