//===- Unix/Process.cpp - Unix Process 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 generic Unix implementation of the Process class. // //===----------------------------------------------------------------------===// #include "Unix.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/StringRef.h" #include "llvm/Config/config.h" #include #include #if HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_RESOURCE_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #if HAVE_SIGNAL_H #include #endif #if defined(HAVE_MALLINFO) || defined(HAVE_MALLINFO2) #include #endif #if defined(HAVE_MALLCTL) #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_TERMIOS_H #include #endif //===----------------------------------------------------------------------===// //=== WARNING: Implementation here must contain only generic UNIX code that //=== is guaranteed to work on *all* UNIX variants. //===----------------------------------------------------------------------===// using namespace llvm; using namespace sys; static std::pair getRUsageTimes() { #if defined(HAVE_GETRUSAGE) struct rusage RU; ::getrusage(RUSAGE_SELF, &RU); return {toDuration(RU.ru_utime), toDuration(RU.ru_stime)}; #else #warning Cannot get usage times on this platform return {std::chrono::microseconds::zero(), std::chrono::microseconds::zero()}; #endif } Process::Pid Process::getProcessId() { static_assert(sizeof(Pid) >= sizeof(pid_t), "Process::Pid should be big enough to store pid_t"); return Pid(::getpid()); } // On Cygwin, getpagesize() returns 64k(AllocationGranularity) and // offset in mmap(3) should be aligned to the AllocationGranularity. Expected Process::getPageSize() { #if defined(HAVE_GETPAGESIZE) static const int page_size = ::getpagesize(); #elif defined(HAVE_SYSCONF) static long page_size = ::sysconf(_SC_PAGE_SIZE); #else #error Cannot get the page size on this machine #endif if (page_size == -1) return errorCodeToError(std::error_code(errno, std::generic_category())); return static_cast(page_size); } size_t Process::GetMallocUsage() { #if defined(HAVE_MALLINFO2) struct mallinfo2 mi; mi = ::mallinfo2(); return mi.uordblks; #elif defined(HAVE_MALLINFO) struct mallinfo mi; mi = ::mallinfo(); return mi.uordblks; #elif defined(HAVE_MALLOC_ZONE_STATISTICS) && defined(HAVE_MALLOC_MALLOC_H) malloc_statistics_t Stats; malloc_zone_statistics(malloc_default_zone(), &Stats); return Stats.size_in_use; // darwin #elif defined(HAVE_MALLCTL) size_t alloc, sz; sz = sizeof(size_t); if (mallctl("stats.allocated", &alloc, &sz, NULL, 0) == 0) return alloc; return 0; #elif defined(HAVE_SBRK) // Note this is only an approximation and more closely resembles // the value returned by mallinfo in the arena field. static char *StartOfMemory = reinterpret_cast(::sbrk(0)); char *EndOfMemory = (char *)sbrk(0); if (EndOfMemory != ((char *)-1) && StartOfMemory != ((char *)-1)) return EndOfMemory - StartOfMemory; return 0; #else #warning Cannot get malloc info on this platform return 0; #endif } void Process::GetTimeUsage(TimePoint<> &elapsed, std::chrono::nanoseconds &user_time, std::chrono::nanoseconds &sys_time) { elapsed = std::chrono::system_clock::now(); std::tie(user_time, sys_time) = getRUsageTimes(); } #if defined(HAVE_MACH_MACH_H) && !defined(__GNU__) #include #endif // Some LLVM programs such as bugpoint produce core files as a normal part of // their operation. To prevent the disk from filling up, this function // does what's necessary to prevent their generation. void Process::PreventCoreFiles() { #if HAVE_SETRLIMIT struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 0; setrlimit(RLIMIT_CORE, &rlim); #endif #if defined(HAVE_MACH_MACH_H) && !defined(__GNU__) // Disable crash reporting on Mac OS X 10.0-10.4 // get information about the original set of exception ports for the task mach_msg_type_number_t Count = 0; exception_mask_t OriginalMasks[EXC_TYPES_COUNT]; exception_port_t OriginalPorts[EXC_TYPES_COUNT]; exception_behavior_t OriginalBehaviors[EXC_TYPES_COUNT]; thread_state_flavor_t OriginalFlavors[EXC_TYPES_COUNT]; kern_return_t err = task_get_exception_ports( mach_task_self(), EXC_MASK_ALL, OriginalMasks, &Count, OriginalPorts, OriginalBehaviors, OriginalFlavors); if (err == KERN_SUCCESS) { // replace each with MACH_PORT_NULL. for (unsigned i = 0; i != Count; ++i) task_set_exception_ports(mach_task_self(), OriginalMasks[i], MACH_PORT_NULL, OriginalBehaviors[i], OriginalFlavors[i]); } // Disable crash reporting on Mac OS X 10.5 signal(SIGABRT, _exit); signal(SIGILL, _exit); signal(SIGFPE, _exit); signal(SIGSEGV, _exit); signal(SIGBUS, _exit); #endif coreFilesPrevented = true; } std::optional Process::GetEnv(StringRef Name) { std::string NameStr = Name.str(); const char *Val = ::getenv(NameStr.c_str()); if (!Val) return std::nullopt; return std::string(Val); } namespace { class FDCloser { public: FDCloser(int &FD) : FD(FD), KeepOpen(false) {} void keepOpen() { KeepOpen = true; } ~FDCloser() { if (!KeepOpen && FD >= 0) ::close(FD); } private: FDCloser(const FDCloser &) = delete; void operator=(const FDCloser &) = delete; int &FD; bool KeepOpen; }; } // namespace std::error_code Process::FixupStandardFileDescriptors() { int NullFD = -1; FDCloser FDC(NullFD); const int StandardFDs[] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; for (int StandardFD : StandardFDs) { struct stat st; errno = 0; if (RetryAfterSignal(-1, ::fstat, StandardFD, &st) < 0) { assert(errno && "expected errno to be set if fstat failed!"); // fstat should return EBADF if the file descriptor is closed. if (errno != EBADF) return std::error_code(errno, std::generic_category()); } // if fstat succeeds, move on to the next FD. if (!errno) continue; assert(errno == EBADF && "expected errno to have EBADF at this point!"); if (NullFD < 0) { // Call ::open in a lambda to avoid overload resolution in // RetryAfterSignal when open is overloaded, such as in Bionic. auto Open = [&]() { return ::open("/dev/null", O_RDWR); }; if ((NullFD = RetryAfterSignal(-1, Open)) < 0) return std::error_code(errno, std::generic_category()); } if (NullFD == StandardFD) FDC.keepOpen(); else if (dup2(NullFD, StandardFD) < 0) return std::error_code(errno, std::generic_category()); } return std::error_code(); } std::error_code Process::SafelyCloseFileDescriptor(int FD) { // Create a signal set filled with *all* signals. sigset_t FullSet, SavedSet; if (sigfillset(&FullSet) < 0 || sigfillset(&SavedSet) < 0) return std::error_code(errno, std::generic_category()); // Atomically swap our current signal mask with a full mask. #if LLVM_ENABLE_THREADS if (int EC = pthread_sigmask(SIG_SETMASK, &FullSet, &SavedSet)) return std::error_code(EC, std::generic_category()); #else if (sigprocmask(SIG_SETMASK, &FullSet, &SavedSet) < 0) return std::error_code(errno, std::generic_category()); #endif // Attempt to close the file descriptor. // We need to save the error, if one occurs, because our subsequent call to // pthread_sigmask might tamper with errno. int ErrnoFromClose = 0; if (::close(FD) < 0) ErrnoFromClose = errno; // Restore the signal mask back to what we saved earlier. int EC = 0; #if LLVM_ENABLE_THREADS EC = pthread_sigmask(SIG_SETMASK, &SavedSet, nullptr); #else if (sigprocmask(SIG_SETMASK, &SavedSet, nullptr) < 0) EC = errno; #endif // The error code from close takes precedence over the one from // pthread_sigmask. if (ErrnoFromClose) return std::error_code(ErrnoFromClose, std::generic_category()); return std::error_code(EC, std::generic_category()); } bool Process::StandardInIsUserInput() { return FileDescriptorIsDisplayed(STDIN_FILENO); } bool Process::StandardOutIsDisplayed() { return FileDescriptorIsDisplayed(STDOUT_FILENO); } bool Process::StandardErrIsDisplayed() { return FileDescriptorIsDisplayed(STDERR_FILENO); } bool Process::FileDescriptorIsDisplayed(int fd) { #if HAVE_ISATTY return isatty(fd); #else // If we don't have isatty, just return false. return false; #endif } static unsigned getColumns() { // If COLUMNS is defined in the environment, wrap to that many columns. if (const char *ColumnsStr = std::getenv("COLUMNS")) { int Columns = std::atoi(ColumnsStr); if (Columns > 0) return Columns; } // We used to call ioctl TIOCGWINSZ to determine the width. It is considered // unuseful. return 0; } unsigned Process::StandardOutColumns() { if (!StandardOutIsDisplayed()) return 0; return getColumns(); } unsigned Process::StandardErrColumns() { if (!StandardErrIsDisplayed()) return 0; return getColumns(); } #ifdef LLVM_ENABLE_TERMINFO // We manually declare these extern functions because finding the correct // headers from various terminfo, curses, or other sources is harder than // writing their specs down. extern "C" int setupterm(char *term, int filedes, int *errret); extern "C" struct term *set_curterm(struct term *termp); extern "C" int del_curterm(struct term *termp); extern "C" int tigetnum(char *capname); #endif bool checkTerminalEnvironmentForColors() { if (const char *TermStr = std::getenv("TERM")) { return StringSwitch(TermStr) .Case("ansi", true) .Case("cygwin", true) .Case("linux", true) .StartsWith("screen", true) .StartsWith("xterm", true) .StartsWith("vt100", true) .StartsWith("rxvt", true) .EndsWith("color", true) .Default(false); } return false; } static bool terminalHasColors(int fd) { #ifdef LLVM_ENABLE_TERMINFO // First, acquire a global lock because these C routines are thread hostile. static std::mutex TermColorMutex; std::lock_guard G(TermColorMutex); struct term *previous_term = set_curterm(nullptr); int errret = 0; if (setupterm(nullptr, fd, &errret) != 0) // Regardless of why, if we can't get terminfo, we shouldn't try to print // colors. return false; // Test whether the terminal as set up supports color output. How to do this // isn't entirely obvious. We can use the curses routine 'has_colors' but it // would be nice to avoid a dependency on curses proper when we can make do // with a minimal terminfo parsing library. Also, we don't really care whether // the terminal supports the curses-specific color changing routines, merely // if it will interpret ANSI color escape codes in a reasonable way. Thus, the // strategy here is just to query the baseline colors capability and if it // supports colors at all to assume it will translate the escape codes into // whatever range of colors it does support. We can add more detailed tests // here if users report them as necessary. // // The 'tigetnum' routine returns -2 or -1 on errors, and might return 0 if // the terminfo says that no colors are supported. int colors_ti = tigetnum(const_cast("colors")); bool HasColors = colors_ti >= 0 ? colors_ti : checkTerminalEnvironmentForColors(); // Now extract the structure allocated by setupterm and free its memory // through a really silly dance. struct term *termp = set_curterm(previous_term); (void)del_curterm(termp); // Drop any errors here. // Return true if we found a color capabilities for the current terminal. return HasColors; #else // When the terminfo database is not available, check if the current terminal // is one of terminals that are known to support ANSI color escape codes. return checkTerminalEnvironmentForColors(); #endif } bool Process::FileDescriptorHasColors(int fd) { // A file descriptor has colors if it is displayed and the terminal has // colors. return FileDescriptorIsDisplayed(fd) && terminalHasColors(fd); } bool Process::StandardOutHasColors() { return FileDescriptorHasColors(STDOUT_FILENO); } bool Process::StandardErrHasColors() { return FileDescriptorHasColors(STDERR_FILENO); } void Process::UseANSIEscapeCodes(bool /*enable*/) { // No effect. } bool Process::ColorNeedsFlush() { // No, we use ANSI escape sequences. return false; } const char *Process::OutputColor(char code, bool bold, bool bg) { return colorcodes[bg ? 1 : 0][bold ? 1 : 0][code & 7]; } const char *Process::OutputBold(bool bg) { return "\033[1m"; } const char *Process::OutputReverse() { return "\033[7m"; } const char *Process::ResetColor() { return "\033[0m"; } #if !HAVE_DECL_ARC4RANDOM static unsigned GetRandomNumberSeed() { // Attempt to get the initial seed from /dev/urandom, if possible. int urandomFD = open("/dev/urandom", O_RDONLY); if (urandomFD != -1) { unsigned seed; // Don't use a buffered read to avoid reading more data // from /dev/urandom than we need. int count = read(urandomFD, (void *)&seed, sizeof(seed)); close(urandomFD); // Return the seed if the read was successful. if (count == sizeof(seed)) return seed; } // Otherwise, swizzle the current time and the process ID to form a reasonable // seed. const auto Now = std::chrono::high_resolution_clock::now(); return hash_combine(Now.time_since_epoch().count(), ::getpid()); } #endif unsigned llvm::sys::Process::GetRandomNumber() { #if HAVE_DECL_ARC4RANDOM return arc4random(); #else static int x = (static_cast(::srand(GetRandomNumberSeed())), 0); (void)x; return ::rand(); #endif } [[noreturn]] void Process::ExitNoCleanup(int RetCode) { _Exit(RetCode); }