brintos

brintos / llvm-project-archived public Read only

0
0
Text · 18.6 KiB · 5dcd2c9 Raw
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