Index: include/llvm/Support/Program.h =================================================================== --- include/llvm/Support/Program.h +++ include/llvm/Support/Program.h @@ -23,34 +23,126 @@ namespace llvm { namespace sys { - /// This is the OS-specific separator for PATH like environment variables: - // a colon on Unix or a semicolon on Windows. +/// Some return codes Clang or other LLVM tools can return +enum class ReturnCode { + Success = 0, + Failure = 1, // General program failure. + // Also used by Windows' TaskManager when a program is killed + // manually by the user. Clang also uses this as time-out + // termination code for its children processes. +#ifdef _WIN32 + Win32Abort = 3, // Returned by abort() on Windows. +#endif + GenerateCrashDiagnostic = + 70, // EX_SOFTWARE on BSD, used by Clang to forcibly generate a crash dump +#if LLVM_ON_UNIX + BrokenPipe = 74, // EX_IOERR + UnixNotAnExecutable = 126, + UnixExecutableNotFound = 127, +#endif + BugpointClientFail = 255 +}; + +/// This is the OS-specific separator for PATH like environment variables: +// a colon on Unix or a semicolon on Windows. #if defined(LLVM_ON_UNIX) - const char EnvPathSeparator = ':'; -#elif defined (_WIN32) - const char EnvPathSeparator = ';'; +const char EnvPathSeparator = ':'; +#elif defined(_WIN32) +const char EnvPathSeparator = ';'; #endif +/// The program execution status, as a finite state machine: +/// +/// /--> ExecutionFailure +/// NotStarted ---> StartedAndExecuting ---> CompletedSuccess +/// \--> CompletedWithError +/// \--> Crashed +/// \--> Killed +enum class ExecutionStatus : uint8_t { + NotStarted = 0, + StartedAndExecuting, // process is up and running + CompletedSuccess, // process completed with no error + CompletedWithError, // process completed with error, see ReturnCode + ExecutionFailure, // process can't be started + Crashed, // process crashed + TimedOut // process was forcibly terminated +}; + +/// This struct encapsulates information about a process. +class Program { +public: #if defined(_WIN32) - typedef unsigned long procid_t; // Must match the type of DWORD on Windows. - typedef void *process_t; // Must match the type of HANDLE on Windows. + typedef void *HandleType; // Must match the type of HANDLE on Windows. #else - typedef pid_t procid_t; - typedef procid_t process_t; + typedef pid_t HandleType; #endif - /// This struct encapsulates information about a process. - struct ProcessInfo { - enum : procid_t { InvalidPid = 0 }; + HandleType Handle{}; /// Platform-dependent process object. - procid_t Pid; /// The process identifier. - process_t Process; /// Platform-dependent process object. + /// The program execution status + ExecutionStatus Status = ExecutionStatus::NotStarted; - /// The return code, set after execution. - int ReturnCode; +private: + /// The return code after execution, only set if the program terminates with + /// an error + int ReturnCode = (int)ReturnCode::Success; + + /// A possible error message after execution + std::string ErrMsg; + +public: + static Program makeExecuting(HandleType H); + static Program makeSuccessResult(); + static Program makeErrorResult(int RC); + static Program makeFailureResult(const std::string &S); + static Program makeCrashedResult(int RC, const std::string &S); + + static Program executeAndWait(StringRef Program, ArrayRef Args, + Optional> Env = None, + ArrayRef> Redirects = {}, + unsigned SecondsToWait = 0, + unsigned MemoryLimit = 0); + + static Program executeNoWait(StringRef Program, ArrayRef Args, + Optional> Env = None, + ArrayRef> Redirects = {}, + unsigned MemoryLimit = 0); + + void waitCompletion(unsigned SecondsToWait, bool WaitUntilTerminates); + + /// true if the program is running or has completed successfully + explicit operator bool() const; + + /// Program was started + bool started() const; + + /// Program was started and still running + bool running() const; + + /// Program has completed with errors + bool completedError() const; + + /// Program has completed with successfully + bool completedSuccess() const; + + /// Program can't start, has crashed, or has been killed + bool notCompletedOrFailed() const; + + /// Program can't start + bool executionFailure() const; + + /// Program has crashed + bool crashed(bool ExpectCrash = false) const; + + /// Program timed out a specified delay and was killed + bool timedOut() const; + + void clear(); + + int ret() const; - ProcessInfo(); - }; + const std::string &errMsg() const; +}; /// Find the first executable file \p Name in \p Paths. /// @@ -119,16 +211,16 @@ bool *ExecutionFailed = nullptr); /// Similar to ExecuteAndWait, but returns immediately. - /// @returns The \see ProcessInfo of the newly launced process. + /// @returns The \see ProcessInfo of the newly launched process. /// \note On Microsoft Windows systems, users will need to either call /// \see Wait until the process finished execution or win32 CloseHandle() API /// on ProcessInfo.ProcessHandle to avoid memory leaks. - ProcessInfo ExecuteNoWait(StringRef Program, ArrayRef Args, - Optional> Env, - ArrayRef> Redirects = {}, - unsigned MemoryLimit = 0, - std::string *ErrMsg = nullptr, - bool *ExecutionFailed = nullptr); + Program ExecuteNoWait(StringRef Program, ArrayRef Args, + Optional> Env = None, + ArrayRef> Redirects = {}, + unsigned MemoryLimit = 0, + std::string *ErrMsg = nullptr, + bool *ExecutionFailed = nullptr); /// Return true if the given arguments fit within system-specific /// argument length limits. @@ -181,8 +273,8 @@ /// \li 0 if the child process has not changed state. /// \note Users of this function should always check the ReturnCode member of /// the \see ProcessInfo returned from this function. - ProcessInfo Wait( - const ProcessInfo &PI, ///< The child process that should be waited on. + Program Wait( + const Program &P, ///< The child process that should be waited on. unsigned SecondsToWait, ///< If non-zero, this specifies the amount of ///< time to wait for the child process to exit. If the time expires, the ///< child is killed and this function returns. If zero, this function @@ -203,6 +295,8 @@ std::string flattenWindowsCommandLine(ArrayRef Args); #endif } + + std::string toString(const sys::Program &P); } #endif Index: lib/Support/Program.cpp =================================================================== --- lib/Support/Program.cpp +++ lib/Support/Program.cpp @@ -12,8 +12,11 @@ #include "llvm/Support/Program.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Config/llvm-config.h" +#include "llvm/Support/ErrorHandling.h" #include + using namespace llvm; using namespace sys; @@ -22,10 +25,32 @@ //=== independent code. //===----------------------------------------------------------------------===// -static bool Execute(ProcessInfo &PI, StringRef Program, - ArrayRef Args, Optional> Env, - ArrayRef> Redirects, - unsigned MemoryLimit, std::string *ErrMsg); +static sys::Program Execute(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit); + +sys::Program +sys::Program::executeAndWait(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned SecondsToWait, unsigned MemoryLimit) { + assert(Redirects.empty() || Redirects.size() == 3); + sys::Program P = Execute(Program, Args, Env, Redirects, MemoryLimit); + if (!P) + return P; + P.waitCompletion(SecondsToWait, SecondsToWait == 0); + return P; +} + +sys::Program +sys::Program::executeNoWait(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit) { + assert(Redirects.empty() || Redirects.size() == 3); + return Execute(Program, Args, Env, Redirects, MemoryLimit); +} int sys::ExecuteAndWait(StringRef Program, ArrayRef Args, Optional> Env, @@ -33,35 +58,46 @@ unsigned SecondsToWait, unsigned MemoryLimit, std::string *ErrMsg, bool *ExecutionFailed) { assert(Redirects.empty() || Redirects.size() == 3); - ProcessInfo PI; - if (Execute(PI, Program, Args, Env, Redirects, MemoryLimit, ErrMsg)) { + sys::Program P = Execute(Program, Args, Env, Redirects, MemoryLimit); + if (!P) { + if (ErrMsg && (P.completedError() || P.notCompletedOrFailed())) + *ErrMsg = P.errMsg(); if (ExecutionFailed) - *ExecutionFailed = false; - ProcessInfo Result = Wait( - PI, SecondsToWait, /*WaitUntilTerminates=*/SecondsToWait == 0, ErrMsg); - return Result.ReturnCode; + *ExecutionFailed = !P; + if (P.completedError() || P.crashed()) + return P.ret(); + if (P.timedOut()) + return -2; + if (P.notCompletedOrFailed()) + return -1; + return 0; } - + P.waitCompletion(SecondsToWait, SecondsToWait == 0); + if (ErrMsg && (P.completedError() || P.notCompletedOrFailed())) + *ErrMsg = P.errMsg(); if (ExecutionFailed) - *ExecutionFailed = true; - - return -1; + *ExecutionFailed = !P; + if (P.completedError() || P.crashed()) + return P.ret(); + return 0; } -ProcessInfo sys::ExecuteNoWait(StringRef Program, ArrayRef Args, - Optional> Env, - ArrayRef> Redirects, - unsigned MemoryLimit, std::string *ErrMsg, - bool *ExecutionFailed) { +sys::Program sys::ExecuteNoWait(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit, std::string *ErrMsg, + bool *ExecutionFailed) { assert(Redirects.empty() || Redirects.size() == 3); - ProcessInfo PI; - if (ExecutionFailed) - *ExecutionFailed = false; - if (!Execute(PI, Program, Args, Env, Redirects, MemoryLimit, ErrMsg)) - if (ExecutionFailed) - *ExecutionFailed = true; + return Execute(Program, Args, Env, Redirects, MemoryLimit); +} - return PI; +Program sys::Wait(const Program &P, unsigned SecondsToWait, + bool WaitUntilTerminates, std::string *ErrMsg) { + Program WaitResult = P; + WaitResult.waitCompletion(SecondsToWait, WaitUntilTerminates); + if (!WaitResult && ErrMsg && (P.completedError() || P.notCompletedOrFailed())) + *ErrMsg = WaitResult.errMsg(); + return WaitResult; } bool sys::commandLineFitsWithinSystemLimits(StringRef Program, @@ -73,6 +109,122 @@ return commandLineFitsWithinSystemLimits(Program, StringRefArgs); } +Program Program::makeFailureResult(const std::string &S) { + Program P; + P.Status = ExecutionStatus::ExecutionFailure; + P.ReturnCode = (int)ReturnCode::Failure; + P.ErrMsg = S; + return P; +} + +Program Program::makeCrashedResult(int RC, const std::string &S) { + Program P; + P.Status = ExecutionStatus::Crashed; + P.ErrMsg = S; + P.ReturnCode = RC; + return P; +} + +Program Program::makeErrorResult(int RC) { + Program P; + P.Status = ExecutionStatus::CompletedWithError; + P.ReturnCode = RC; + return P; +} + +Program Program::makeSuccessResult() { + Program P; + P.Status = ExecutionStatus::CompletedSuccess; + return P; +} + +Program Program::makeExecuting(HandleType H) { + sys::Program P; + P.Status = sys::ExecutionStatus::StartedAndExecuting; + P.Handle = H; + return P; +} + +Program::operator bool() const { + return Status == ExecutionStatus::StartedAndExecuting || + Status == ExecutionStatus::CompletedSuccess; +} + +bool Program::started() const { return Status > ExecutionStatus::NotStarted; } + +bool Program::running() const { + return Status == ExecutionStatus::StartedAndExecuting; +} + +bool Program::completedError() const { + return Status == ExecutionStatus::CompletedWithError; +} + +bool Program::completedSuccess() const { + return Status == ExecutionStatus::CompletedSuccess; +} + +bool Program::notCompletedOrFailed() const { + return Status >= ExecutionStatus::ExecutionFailure; +} + +bool Program::executionFailure() const { + return Status == ExecutionStatus::ExecutionFailure; +} + +bool Program::crashed(bool ExpectCrash) const { + if (Status == ExecutionStatus::Crashed) + return true; + if (!ReturnCode) + return false; + if (ReturnCode < 0 || ReturnCode == (int)ReturnCode::GenerateCrashDiagnostic) + return true; +#ifdef _WIN32 + // Handle abort() in msvcrt -- It has exit code as 3. abort(), aka + // unreachable, should be recognized as a crash. However, some binaries use + // exit code 3 on non-crash failure paths, so only do this if we expect a + // crash. + if (ExpectCrash && ReturnCode == (int)ReturnCode::Win32Abort) + return true; +#endif + return false; +} + +bool Program::timedOut() const { return Status == ExecutionStatus::TimedOut; } + +void Program::clear() { *this = Program(); } + +int Program::ret() const { + assert(Status == ExecutionStatus::CompletedWithError || + Status == ExecutionStatus::Crashed); + return ReturnCode; +} + +const std::string &Program::errMsg() const { + assert(Status >= ExecutionStatus::CompletedWithError); + return ErrMsg; +} + +std::string llvm::toString(const sys::Program &P) { + switch (P.Status) { + case ExecutionStatus::NotStarted: + return "not started"; + case ExecutionStatus::StartedAndExecuting: + return "running"; + case ExecutionStatus::CompletedSuccess: + return "success"; + case ExecutionStatus::CompletedWithError: + return ("exited with code (" + Twine(P.ret()) + ")").str(); + case ExecutionStatus::ExecutionFailure: + return "execute failed: " + P.errMsg(); + case ExecutionStatus::Crashed: + return ("crashed (" + Twine(P.ret()) + ") " + P.errMsg()).str(); + case ExecutionStatus::TimedOut: + return "timed out"; + } + llvm_unreachable("Unhandled execution status!"); +} + // Include the platform-specific parts of this class. #ifdef LLVM_ON_UNIX #include "Unix/Program.inc" Index: lib/Support/Unix/Program.inc =================================================================== --- lib/Support/Unix/Program.inc +++ lib/Support/Unix/Program.inc @@ -63,8 +63,6 @@ using namespace sys; -ProcessInfo::ProcessInfo() : Pid(0), ReturnCode(0) {} - ErrorOr sys::findProgramByName(StringRef Name, ArrayRef Paths) { assert(!Name.empty() && "Must have a name!"); @@ -93,9 +91,9 @@ return errc::no_such_file_or_directory; } -static bool RedirectIO(Optional Path, int FD, std::string* ErrMsg) { +static Optional RedirectIO(Optional Path, int FD) { if (!Path) // Noop - return false; + return None; std::string File; if (Path->empty()) // Redirect empty paths to /dev/null @@ -105,27 +103,27 @@ // Open the file int InFD = open(File.c_str(), FD == 0 ? O_RDONLY : O_WRONLY|O_CREAT, 0666); - if (InFD == -1) { - MakeErrMsg(ErrMsg, "Cannot open file '" + File + "' for " - + (FD == 0 ? "input" : "output")); - return true; - } + if (InFD == -1) + return Program::makeFailureResult( + MakeErrMsg("Cannot open file '" + File + "' for " + + (FD == 0 ? "input" : "output"))); // Install it as the requested FD if (dup2(InFD, FD) == -1) { - MakeErrMsg(ErrMsg, "Cannot dup2"); + int errnum = errno; close(InFD); - return true; + return Program::makeFailureResult(MakeErrMsg("Cannot dup2", errnum)); } close(InFD); // Close the original FD - return false; + return None; } #ifdef HAVE_POSIX_SPAWN -static bool RedirectIO_PS(const std::string *Path, int FD, std::string *ErrMsg, - posix_spawn_file_actions_t *FileActions) { +static Optional +RedirectIO_PS(const std::string *Path, int FD, + posix_spawn_file_actions_t *FileActions) { if (!Path) // Noop - return false; + return None; const char *File; if (Path->empty()) // Redirect empty paths to /dev/null @@ -136,8 +134,9 @@ if (int Err = posix_spawn_file_actions_addopen( FileActions, FD, File, FD == 0 ? O_RDONLY : O_WRONLY | O_CREAT, 0666)) - return MakeErrMsg(ErrMsg, "Cannot dup2", Err); - return false; + return Program::makeFailureResult(MakeErrMsg("Cannot dup2", Err)); + + return None; } #endif @@ -173,16 +172,12 @@ return Result; } -static bool Execute(ProcessInfo &PI, StringRef Program, - ArrayRef Args, Optional> Env, - ArrayRef> Redirects, - unsigned MemoryLimit, std::string *ErrMsg) { - if (!llvm::sys::fs::exists(Program)) { - if (ErrMsg) - *ErrMsg = std::string("Executable \"") + Program.str() + - std::string("\" doesn't exist!"); - return false; - } +static Program Execute(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit) { + if (!llvm::sys::fs::exists(Program)) + return Program::makeFailureResult("Executable \"" + Program.str() + "\" doesn't exist!"); BumpPtrAllocator Allocator; StringSaver Saver(Allocator); @@ -222,18 +217,22 @@ posix_spawn_file_actions_init(FileActions); // Redirect stdin/stdout. - if (RedirectIO_PS(RedirectsStr[0], 0, ErrMsg, FileActions) || - RedirectIO_PS(RedirectsStr[1], 1, ErrMsg, FileActions)) - return false; + if (auto E = RedirectIO_PS(RedirectsStr[0], 0, FileActions)) + return *E; + + if (auto E = RedirectIO_PS(RedirectsStr[1], 1, FileActions)) + return *E; + if (!Redirects[1] || !Redirects[2] || *Redirects[1] != *Redirects[2]) { // Just redirect stderr - if (RedirectIO_PS(RedirectsStr[2], 2, ErrMsg, FileActions)) - return false; + if (auto E = RedirectIO_PS(RedirectsStr[2], 2, FileActions)) + return *E; } else { // If stdout and stderr should go to the same place, redirect stderr // to the FD already open for stdout. if (int Err = posix_spawn_file_actions_adddup2(FileActions, 1, 2)) - return !MakeErrMsg(ErrMsg, "Can't redirect stderr to stdout", Err); + return Program::makeFailureResult( + MakeErrMsg("Can't redirect stderr to stdout", Err)); } } @@ -260,12 +259,9 @@ posix_spawn_file_actions_destroy(FileActions); if (Err) - return !MakeErrMsg(ErrMsg, "posix_spawn failed", Err); + return Program::makeFailureResult(MakeErrMsg("posix_spawn failed", Err)); - PI.Pid = PID; - PI.Process = PID; - - return true; + return Program::makeExecuting(PID); } #endif @@ -274,27 +270,28 @@ switch (child) { // An error occurred: Return to the caller. case -1: - MakeErrMsg(ErrMsg, "Couldn't fork"); - return false; + return Program::makeFailureResult("Couldn't fork"); // Child process: Execute the program. case 0: { // Redirect file descriptors... if (!Redirects.empty()) { // Redirect stdin - if (RedirectIO(Redirects[0], 0, ErrMsg)) { return false; } + if (auto E = RedirectIO(Redirects[0], 0)) + return *E; // Redirect stdout - if (RedirectIO(Redirects[1], 1, ErrMsg)) { return false; } + if (auto E = RedirectIO(Redirects[1], 1)) + return *E; if (Redirects[1] && Redirects[2] && *Redirects[1] == *Redirects[2]) { // If stdout and stderr should go to the same place, redirect stderr // to the FD already open for stdout. if (-1 == dup2(1,2)) { - MakeErrMsg(ErrMsg, "Can't redirect stderr to stdout"); - return false; + return Program::makeFailureResult("Can't redirect stderr to stdout"); } } else { // Just redirect stderr - if (RedirectIO(Redirects[2], 2, ErrMsg)) { return false; } + if (auto E = RedirectIO(Redirects[2], 2)) + return *E; } } @@ -316,29 +313,26 @@ // object destructors cloned from the parent process aren't // redundantly run, and so that any data buffered in stdio buffers // cloned from the parent aren't redundantly written out. - _exit(errno == ENOENT ? 127 : 126); + _exit(errno == ENOENT ? (int)ReturnCode::UnixExecutableNotFound + : (int)ReturnCode::UnixNotAnExecutable); } // Parent process: Break out of the switch to do our processing. default: break; } - - PI.Pid = child; - PI.Process = child; - - return true; + return Program::makeExecuting(child); } namespace llvm { -ProcessInfo sys::Wait(const ProcessInfo &PI, unsigned SecondsToWait, - bool WaitUntilTerminates, std::string *ErrMsg) { +void sys::Program::waitCompletion(unsigned SecondsToWait, bool WaitUntilTerminates) { struct sigaction Act, Old; - assert(PI.Pid && "invalid pid to wait on, process not started?"); + assert(Handle && "invalid pid to wait on, process not started?"); + assert(Status == ExecutionStatus::StartedAndExecuting); int WaitPidOptions = 0; - pid_t ChildPid = PI.Pid; + pid_t ChildPid = Handle; if (WaitUntilTerminates) { SecondsToWait = 0; } else if (SecondsToWait) { @@ -350,25 +344,26 @@ sigemptyset(&Act.sa_mask); sigaction(SIGALRM, &Act, &Old); alarm(SecondsToWait); - } else if (SecondsToWait == 0) + } else if (SecondsToWait == 0) { WaitPidOptions = WNOHANG; + } // Parent process: Wait for the child process to terminate. int status; - ProcessInfo WaitResult; + pid_t WaitResult; do { - WaitResult.Pid = waitpid(ChildPid, &status, WaitPidOptions); - } while (WaitUntilTerminates && WaitResult.Pid == -1 && errno == EINTR); + WaitResult = waitpid(ChildPid, &status, WaitPidOptions); + } while (WaitUntilTerminates && WaitResult == -1 && errno == EINTR); - if (WaitResult.Pid != PI.Pid) { - if (WaitResult.Pid == 0) { + if (WaitResult != Handle) { + if (WaitResult == 0) { // Non-blocking wait. - return WaitResult; + return; } else { if (SecondsToWait && errno == EINTR) { - // Kill the child. - kill(PI.Pid, SIGKILL); + // Timeout, kill the child process. + kill(Handle, SIGKILL); // Turn off the alarm and restore the signal handler alarm(0); @@ -376,16 +371,16 @@ // Wait for child to die if (wait(&status) != ChildPid) - MakeErrMsg(ErrMsg, "Child timed out but wouldn't die"); + ErrMsg = MakeErrMsg("Child timed out but wouldn't die"); else - MakeErrMsg(ErrMsg, "Child timed out", 0); + ErrMsg = MakeErrMsg("Child timed out", 0); - WaitResult.ReturnCode = -2; // Timeout detected - return WaitResult; + Status = ExecutionStatus::TimedOut; + return; } else if (errno != EINTR) { - MakeErrMsg(ErrMsg, "Error waiting for child process"); - WaitResult.ReturnCode = -1; - return WaitResult; + ErrMsg = MakeErrMsg("Error waiting for child process"); + Status = ExecutionStatus::Crashed; + return; } } } @@ -401,33 +396,30 @@ int result = 0; if (WIFEXITED(status)) { result = WEXITSTATUS(status); - WaitResult.ReturnCode = result; + ReturnCode = result; - if (result == 127) { - if (ErrMsg) - *ErrMsg = llvm::sys::StrError(ENOENT); - WaitResult.ReturnCode = -1; - return WaitResult; + if (ReturnCode == 0) + Status = ExecutionStatus::CompletedSuccess; + else + Status = ExecutionStatus::CompletedWithError; + + if (result == (int)sys::ReturnCode::UnixExecutableNotFound) { + ErrMsg = llvm::sys::StrError(ENOENT); } - if (result == 126) { - if (ErrMsg) - *ErrMsg = "Program could not be executed"; - WaitResult.ReturnCode = -1; - return WaitResult; + else if (result == (int)sys::ReturnCode::UnixNotAnExecutable) { + ErrMsg = "Program could not be executed"; } } else if (WIFSIGNALED(status)) { - if (ErrMsg) { - *ErrMsg = strsignal(WTERMSIG(status)); + ErrMsg = strsignal(WTERMSIG(status)); #ifdef WCOREDUMP - if (WCOREDUMP(status)) - *ErrMsg += " (core dumped)"; + if (WCOREDUMP(status)) + ErrMsg += " (core dumped)"; #endif - } // Return a special value to indicate that the process received an unhandled // signal during execution as opposed to failing to execute. - WaitResult.ReturnCode = -2; + Status = ExecutionStatus::Crashed; } - return WaitResult; + return; } std::error_code sys::ChangeStdinToBinary() { @@ -497,4 +489,10 @@ return true; } + +std::string MakeErrMsg(Twine prefix, int errnum) { + if (errnum == -1) + errnum = errno; + return (prefix + ": " + llvm::sys::StrError(errnum)).str(); +} } Index: lib/Support/Unix/Signals.inc =================================================================== --- lib/Support/Unix/Signals.inc +++ lib/Support/Unix/Signals.inc @@ -336,7 +336,7 @@ // Send a special return code that drivers can check for, from sysexits.h. if (Sig == SIGPIPE) - exit(EX_IOERR); + exit((int)llvm::sys::ReturnCode::BrokenPipe); raise(Sig); // Execute the default handler. return; Index: lib/Support/Unix/Unix.h =================================================================== --- lib/Support/Unix/Unix.h +++ lib/Support/Unix/Unix.h @@ -52,24 +52,17 @@ # include #endif -/// This function builds an error message into \p ErrMsg using the \p prefix +namespace llvm { + +/// This function returns an error message using the \p prefix /// string and the Unix error number given by \p errnum. If errnum is -1, the /// default then the value of errno is used. /// Make an error message /// /// If the error number can be converted to a string, it will be /// separated from prefix by ": ". -static inline bool MakeErrMsg( - std::string* ErrMsg, const std::string& prefix, int errnum = -1) { - if (!ErrMsg) - return true; - if (errnum == -1) - errnum = errno; - *ErrMsg = prefix + ": " + llvm::sys::StrError(errnum); - return true; -} +std::string MakeErrMsg(Twine prefix, int errnum = -1); -namespace llvm { namespace sys { /// Convert a struct timeval to a duration. Note that timeval can be used both Index: lib/Support/Windows/DynamicLibrary.inc =================================================================== --- lib/Support/Windows/DynamicLibrary.inc +++ lib/Support/Windows/DynamicLibrary.inc @@ -41,13 +41,15 @@ SmallVector FileUnicode; if (std::error_code ec = windows::UTF8ToUTF16(File, FileUnicode)) { SetLastError(ec.value()); - MakeErrMsg(Err, std::string(File) + ": Can't convert to UTF-16"); + if (Err) + *Err = MakeErrMsg(Twine(File) + ": Can't convert to UTF-16"); return &DynamicLibrary::Invalid; } HMODULE Handle = LoadLibraryW(FileUnicode.data()); if (Handle == NULL) { - MakeErrMsg(Err, std::string(File) + ": Can't open"); + if (Err) + *Err = MakeErrMsg(Twine(File) + ": Can't open"); return &DynamicLibrary::Invalid; } @@ -78,9 +80,7 @@ !EnumProcessModules(H, Data, Bytes, &Bytes) #endif ) { - std::string Err; - if (MakeErrMsg(&Err, "EnumProcessModules failure")) - llvm::errs() << Err << "\n"; + llvm::errs() << MakeErrMsg("EnumProcessModules failure") << "\n"; return false; } return true; Index: lib/Support/Windows/Process.inc =================================================================== --- lib/Support/Windows/Process.inc +++ lib/Support/Windows/Process.inc @@ -441,8 +441,7 @@ // Include GetLastError() in a fatal error message. static void ReportLastErrorFatal(const char *Msg) { - std::string ErrMsg; - MakeErrMsg(&ErrMsg, Msg); + std::string ErrMsg = MakeErrMsg(Msg); report_fatal_error(ErrMsg); } Index: lib/Support/Windows/Program.inc =================================================================== --- lib/Support/Windows/Program.inc +++ lib/Support/Windows/Program.inc @@ -31,8 +31,6 @@ namespace llvm { -ProcessInfo::ProcessInfo() : Pid(0), Process(0), ReturnCode(0) {} - ErrorOr sys::findProgramByName(StringRef Name, ArrayRef Paths) { assert(!Name.empty() && "Must have a name!"); @@ -104,27 +102,25 @@ return std::string(U8Result.begin(), U8Result.end()); } -bool MakeErrMsg(std::string *ErrMsg, const std::string &prefix) { - if (!ErrMsg) - return true; +std::string MakeErrMsg(Twine prefix) { 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); + Twine ErrMsg; if (R) - *ErrMsg = prefix + ": " + buffer; + ErrMsg.concat(prefix + ": " + buffer); else - *ErrMsg = prefix + ": Unknown error"; - *ErrMsg += " (0x" + llvm::utohexstr(LastError) + ")"; + ErrMsg.concat(prefix + ": Unknown error"); + ErrMsg.concat(" (0x" + llvm::utohexstr(LastError) + ")"); LocalFree(buffer); - return R != 0; + return ErrMsg.str(); } -static HANDLE RedirectIO(Optional Path, int fd, - std::string *ErrMsg) { +static HANDLE RedirectIO(Optional Path, int fd) { HANDLE h; if (!Path) { if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)_get_osfhandle(fd), @@ -157,25 +153,17 @@ 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; - } +static Program Execute(StringRef Program, ArrayRef Args, + Optional> Env, + ArrayRef> Redirects, + unsigned MemoryLimit) { + if (!sys::fs::can_execute(Program)) + return Program::makeFailureResult("program not executable"); // can_execute may succeed by looking at Program + ".exe". CreateProcessW // will implicitly add the .exe if we provide a command line without an @@ -201,8 +189,8 @@ 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; + return Program::makeFailureResult( + MakeErrMsg("Unable to convert environment variable to UTF-16")); } EnvBlock.insert(EnvBlock.end(), EnvString.begin(), EnvString.end()); @@ -222,16 +210,14 @@ if (!Redirects.empty()) { si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = RedirectIO(Redirects[0], 0, ErrMsg); + si.hStdInput = RedirectIO(Redirects[0], 0); if (si.hStdInput == INVALID_HANDLE_VALUE) { - MakeErrMsg(ErrMsg, "can't redirect stdin"); - return false; + return Program::makeFailureResult(MakeErrMsg("can't redirect stdin")); } - si.hStdOutput = RedirectIO(Redirects[1], 1, ErrMsg); + si.hStdOutput = RedirectIO(Redirects[1], 1); if (si.hStdOutput == INVALID_HANDLE_VALUE) { CloseHandle(si.hStdInput); - MakeErrMsg(ErrMsg, "can't redirect stdout"); - return false; + return Program::makeFailureResult(MakeErrMsg("can't redirect stdout")); } if (Redirects[1] && Redirects[2] && *Redirects[1] == *Redirects[2]) { // If stdout and stderr should go to the same place, redirect stderr @@ -241,17 +227,16 @@ 0, TRUE, DUPLICATE_SAME_ACCESS)) { CloseHandle(si.hStdInput); CloseHandle(si.hStdOutput); - MakeErrMsg(ErrMsg, "can't dup stderr to stdout"); - return false; + return Program::makeFailureResult( + MakeErrMsg("can't dup stderr to stdout")); } } else { // Just redirect stderr - si.hStdError = RedirectIO(Redirects[2], 2, ErrMsg); + si.hStdError = RedirectIO(Redirects[2], 2); if (si.hStdError == INVALID_HANDLE_VALUE) { CloseHandle(si.hStdInput); CloseHandle(si.hStdOutput); - MakeErrMsg(ErrMsg, "can't redirect stderr"); - return false; + return Program::makeFailureResult(MakeErrMsg("can't redirect stderr")); } } } @@ -265,17 +250,15 @@ SmallVector ProgramUtf16; if (std::error_code ec = path::widenPath(Program, ProgramUtf16)) { SetLastError(ec.value()); - MakeErrMsg(ErrMsg, - std::string("Unable to convert application name to UTF-16")); - return false; + return Program::makeFailureResult( + MakeErrMsg("Unable to convert application name to UTF-16")); } 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; + return Program::makeFailureResult( + MakeErrMsg("Unable to convert command-line to UTF-16")); } BOOL rc = CreateProcessW(ProgramUtf16.data(), CommandUtf16.data(), 0, 0, @@ -293,14 +276,10 @@ // 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; + return Program::makeFailureResult( + MakeErrMsg("Couldn't execute program '" + Program + "'")); } - PI.Pid = pi.dwProcessId; - PI.Process = pi.hProcess; - // Make sure these get closed no matter what. ScopedCommonHandle hThread(pi.hThread); @@ -322,14 +301,13 @@ } if (!success) { SetLastError(GetLastError()); - MakeErrMsg(ErrMsg, std::string("Unable to set memory limit")); + std::string ErrMsg = MakeErrMsg("Unable to set memory limit"); TerminateProcess(pi.hProcess, 1); WaitForSingleObject(pi.hProcess, INFINITE); - return false; + return Program::makeFailureResult(ErrMsg); } } - - return true; + return Program::makeExecuting(pi.hProcess); } static bool argNeedsQuotes(StringRef Arg) { @@ -374,6 +352,41 @@ return Result; } +static bool CompleteProcess(sys::Program *P) { + DWORD status; + BOOL rc = GetExitCodeProcess(P->Handle, &status); + if (rc && status == STILL_ACTIVE) { + assert(P->Status == ExecutionStatus::StartedAndExecuting); + return true; + } + + DWORD err = GetLastError(); + if (err != ERROR_INVALID_HANDLE) { + CloseHandle(P->Handle); + } + P->Handle = Program::HandleType(); + + if (!rc) { + SetLastError(err); + *P = Program::makeCrashedResult(-1, MakeErrMsg("Failed getting return status for program")); + return false; + } + + if (!status) { + *P = Program::makeSuccessResult(); + return true; + } + + if ((status & 0xBFFF0000U) == 0x80000000U) + *P = Program::makeCrashedResult(static_cast(status), ""); + else if (status & 0xFF) + *P = Program::makeErrorResult(status & 0x7FFFFFFF); + else + *P = Program::makeErrorResult((int)ReturnCode::Failure); + + return true; +} + namespace llvm { std::string sys::flattenWindowsCommandLine(ArrayRef Args) { std::string Command; @@ -389,67 +402,39 @@ return Command; } -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.Process && PI.Process != INVALID_HANDLE_VALUE) && +void sys::Program::waitCompletion(unsigned SecondsToWait, bool WaitUntilChildTerminates) { + assert((Handle && Handle != INVALID_HANDLE_VALUE) && "invalid process handle to wait on, process not started?"); + assert(Status == ExecutionStatus::StartedAndExecuting); + DWORD milliSecondsToWait = 0; if (WaitUntilChildTerminates) milliSecondsToWait = INFINITE; else if (SecondsToWait > 0) milliSecondsToWait = SecondsToWait * 1000; - ProcessInfo WaitResult = PI; - DWORD WaitStatus = WaitForSingleObject(PI.Process, milliSecondsToWait); + DWORD WaitStatus = WaitForSingleObject(Handle , 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; + if (!TerminateProcess(Handle, 1)) { + ErrMsg = MakeErrMsg("Failed to terminate timed-out program"); + Status = ExecutionStatus::TimedOut; + CloseHandle(Handle); + Handle = HandleType(); + return; } - WaitForSingleObject(PI.Process, INFINITE); - CloseHandle(PI.Process); + WaitForSingleObject(Handle, INFINITE); // wait for process termination + Status = ExecutionStatus::TimedOut; + CloseHandle(Handle); + Handle = HandleType(); + return; } else { // Non-blocking wait. - return ProcessInfo(); + return; } } - // 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; + CompleteProcess(this); } std::error_code sys::ChangeStdinToBinary() { Index: lib/Support/Windows/WindowsSupport.h =================================================================== --- lib/Support/Windows/WindowsSupport.h +++ lib/Support/Windows/WindowsSupport.h @@ -63,7 +63,7 @@ /// be useful for working around certain kernel bugs. llvm::VersionTuple GetWindowsOSVersion(); -bool MakeErrMsg(std::string *ErrMsg, const std::string &prefix); +std::string MakeErrMsg(Twine prefix); template class ScopedHandle { Index: unittests/Support/ProgramTest.cpp =================================================================== --- unittests/Support/ProgramTest.cpp +++ unittests/Support/ProgramTest.cpp @@ -207,10 +207,10 @@ std::string Error; bool ExecutionFailed; - ProcessInfo PI1 = ExecuteNoWait(Executable, argv, getEnviron(), {}, 0, &Error, - &ExecutionFailed); + Program PI1 = ExecuteNoWait(Executable, argv, getEnviron(), {}, 0, &Error, + &ExecutionFailed); ASSERT_FALSE(ExecutionFailed) << Error; - ASSERT_NE(PI1.Pid, ProcessInfo::InvalidPid) << "Invalid process id"; + ASSERT_NE(PI1.Handle, Program::HandleType{}) << "Invalid process id"; unsigned LoopCount = 0; @@ -218,26 +218,26 @@ // LoopCount should only be incremented once. while (true) { ++LoopCount; - ProcessInfo WaitResult = llvm::sys::Wait(PI1, 0, true, &Error); + Program WaitResult = llvm::sys::Wait(PI1, 0, true, &Error); ASSERT_TRUE(Error.empty()); - if (WaitResult.Pid == PI1.Pid) + if (WaitResult.Handle == PI1.Handle) break; } EXPECT_EQ(LoopCount, 1u) << "LoopCount should be 1"; - ProcessInfo PI2 = ExecuteNoWait(Executable, argv, getEnviron(), {}, 0, &Error, - &ExecutionFailed); + Program PI2 = ExecuteNoWait(Executable, argv, getEnviron(), {}, 0, &Error, + &ExecutionFailed); ASSERT_FALSE(ExecutionFailed) << Error; - ASSERT_NE(PI2.Pid, ProcessInfo::InvalidPid) << "Invalid process id"; + ASSERT_NE(PI2.Handle, Program::HandleType{}) << "Invalid process id"; // Test that Wait() with SecondsToWait=0 performs a non-blocking wait. In this // cse, LoopCount should be greater than 1 (more than one increment occurs). while (true) { ++LoopCount; - ProcessInfo WaitResult = llvm::sys::Wait(PI2, 0, false, &Error); + Program WaitResult = llvm::sys::Wait(PI2, 0, false, &Error); ASSERT_TRUE(Error.empty()); - if (WaitResult.Pid == PI2.Pid) + if (WaitResult.Handle == PI2.Handle) break; } @@ -286,10 +286,10 @@ { std::string Error; bool ExecutionFailed; - ProcessInfo PI = ExecuteNoWait(Executable, argv, llvm::None, {}, 0, &Error, - &ExecutionFailed); - ASSERT_EQ(PI.Pid, ProcessInfo::InvalidPid) - << "On error ExecuteNoWait should return an invalid ProcessInfo"; + Program PI = ExecuteNoWait(Executable, argv, llvm::None, {}, 0, &Error, + &ExecutionFailed); + ASSERT_EQ(PI.Handle, Program::HandleType{}) + << "On error ExecuteNoWait should return an invalid Program"; ASSERT_TRUE(ExecutionFailed); ASSERT_FALSE(Error.empty()); }