// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define _WITH_DPRINTF #include "src/main/cpp/blaze_util_platform.h" #include #include #include #include // PATH_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/main/cpp/blaze_util.h" #include "src/main/cpp/startup_options.h" #include "src/main/cpp/util/errors.h" #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/md5.h" #include "src/main/cpp/util/numbers.h" #include "src/main/cpp/util/path.h" #include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" namespace blaze { using blaze_util::GetLastErrorString; using std::set; using std::string; using std::vector; namespace embedded_binaries { class PosixDumper : public Dumper { public: static PosixDumper* Create(string* error); ~PosixDumper() { Finish(nullptr); } void Dump(const void* data, const size_t size, const string& path) override; bool Finish(string* error) override; private: PosixDumper() : was_error_(false) {} set dir_cache_; string error_msg_; bool was_error_; }; Dumper* Create(string* error) { return PosixDumper::Create(error); } PosixDumper* PosixDumper::Create(string* error) { return new PosixDumper(); } void PosixDumper::Dump(const void* data, const size_t size, const string& path) { if (was_error_) { return; } string dirname = blaze_util::Dirname(path); // Performance optimization: memoize the paths we already created a // directory for, to spare a stat in attempting to recreate an already // existing directory. if (dir_cache_.insert(dirname).second) { if (!blaze_util::MakeDirectories(dirname, 0777)) { was_error_ = true; string msg = GetLastErrorString(); error_msg_ = string("couldn't create '") + path + "': " + msg; } } if (was_error_) { return; } if (!blaze_util::WriteFile(data, size, path, 0755)) { was_error_ = true; string msg = GetLastErrorString(); error_msg_ = string("Failed to write zipped file '") + path + "': " + msg; } } bool PosixDumper::Finish(string* error) { if (was_error_ && error) { *error = error_msg_; } return !was_error_; } } // namespace embedded_binaries SignalHandler SignalHandler::INSTANCE; // The number of the last received signal that should cause the client // to shutdown. This is saved so that the client's WTERMSIG can be set // correctly. (Currently only SIGPIPE uses this mechanism.) static volatile sig_atomic_t signal_handler_received_signal = 0; // Signal handler. static void handler(int signum) { int saved_errno = errno; static volatile sig_atomic_t sigint_count = 0; switch (signum) { case SIGINT: if (++sigint_count >= 3) { SigPrintf( "\n%s caught third interrupt signal; server killed. (This may be " "expensive, see https://bazel.build/advanced/performance/" "iteration-speed#avoid-ctrl-c.)\n\n", SignalHandler::Get().GetProductName().c_str()); if (SignalHandler::Get().GetServerProcessInfo()->server_pid_ != -1) { KillServerProcess( SignalHandler::Get().GetServerProcessInfo()->server_pid_, SignalHandler::Get().GetOutputBase()); } _exit(1); } SigPrintf( "\n%s caught interrupt signal; cancelling pending invocation.\n\n", SignalHandler::Get().GetProductName().c_str()); SignalHandler::Get().CancelServer(); break; case SIGTERM: SigPrintf( "\n%s caught terminate signal; cancelling pending invocation.\n\n", SignalHandler::Get().GetProductName().c_str()); SignalHandler::Get().CancelServer(); break; case SIGPIPE: signal_handler_received_signal = SIGPIPE; break; case SIGQUIT: SigPrintf("\nSending SIGQUIT to JVM process %d (see %s).\n\n", SignalHandler::Get().GetServerProcessInfo()->server_pid_, SignalHandler::Get() .GetServerProcessInfo() ->jvm_log_file_.AsNativePath() .c_str()); kill(SignalHandler::Get().GetServerProcessInfo()->server_pid_, SIGQUIT); break; } errno = saved_errno; } void SignalHandler::Install(const string& product_name, const blaze_util::Path& output_base, const ServerProcessInfo* server_process_info, SignalHandler::Callback cancel_server) { product_name_ = product_name; output_base_ = output_base; server_process_info_ = server_process_info; cancel_server_ = cancel_server; // Unblock all signals. sigset_t sigset; sigemptyset(&sigset); sigprocmask(SIG_SETMASK, &sigset, nullptr); // SIGWINCH is reserved for Bazel server internal use and cannot be passed to // it. The JVM is not attached to a terminal, making a signal insufficient to // react to window size change event anyway. signal(SIGINT, handler); signal(SIGTERM, handler); signal(SIGPIPE, handler); signal(SIGQUIT, handler); } ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { if (signal_handler_received_signal) { // Kill ourselves with the same signal, so that callers see the // right WTERMSIG value. signal(signal_handler_received_signal, SIG_DFL); raise(signal_handler_received_signal); exit(1); // (in case raise didn't kill us for some reason) } else { exit(exit_code); } } string GetProcessIdAsString() { return blaze_util::ToString(getpid()); } string GetHomeDir() { return GetPathEnv("HOME"); } string GetJavaBinaryUnderJavabase() { return "bin/java"; } string Which(const string& executable) { const string path = GetPathEnv("PATH"); if (path.empty()) { return ""; } const vector pieces = blaze_util::Split(path, ':'); for (string piece : pieces) { if (piece.empty()) { piece = "."; } struct stat file_stat; const string candidate = blaze_util::JoinPath(piece, executable); if (access(candidate.c_str(), X_OK) == 0 && stat(candidate.c_str(), &file_stat) == 0 && S_ISREG(file_stat.st_mode)) { return candidate; } } return ""; } // Converter of C++ data structures to a C-style array of strings. // // The primary consumer of this class is the execv family of functions // (including posix_spawn) which require mutable arrays as their inputs even // if they do not modify them. class CharPP { public: // Constructs a new CharPP from a list of arguments. explicit CharPP(const vector& args) { charpp_ = static_cast(malloc(sizeof(char*) * (args.size() + 1))); size_t i = 0; for (; i < args.size(); i++) { charpp_[i] = strdup(args[i].c_str()); } charpp_[i] = nullptr; } // Constructs a new CharPP from a list of environment variables. explicit CharPP(const std::map& env) { charpp_ = static_cast(malloc(sizeof(char*) * (env.size() + 1))); size_t i = 0; for (const auto& iter : env) { const string& var = iter.first; const EnvVarValue& value = iter.second; switch (value.action) { case SET: charpp_[i] = strdup((var + "=" + value.value).c_str()); i++; break; case UNSET: break; default: assert(false); } } charpp_[i] = nullptr; } // Deletes all memory held by the CharPP. ~CharPP() { for (char** ptr = charpp_; *ptr != nullptr; ptr++) { free(*ptr); } free(charpp_); } // Obtains the raw pointer to the array of strings. char** get() { return charpp_; } // Prevent copies as we manually manage memory. CharPP(const CharPP&) = delete; CharPP& operator=(const CharPP&) = delete; private: char** charpp_; }; ATTRIBUTE_NORETURN static void ExecuteProgram( const blaze_util::Path& exe, const vector& args_vector) { BAZEL_LOG(INFO) << "Invoking binary " << exe.AsPrintablePath() << " in " << blaze_util::GetCwd(); // TODO(jmmv): This execution does not respect any settings we might apply // to the server process with ConfigureDaemonProcess when executed in the // background as a daemon. Because we use that function to lower the // priority of Bazel on macOS from a QoS perspective, this could have // adverse scheduling effects on any tools invoked via ExecuteProgram. CharPP argv(args_vector); execv(exe.AsNativePath().c_str(), argv.get()); string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "execv of '" << exe.AsPrintablePath() << "' failed: " << err; // TODO(jmmv): Mark BAZEL_DIE as ATTRIBUTE_NORETURN so that the following // code is not necessary. std::abort(); // Not reachable. } void ExecuteServerJvm(const blaze_util::Path& exe, const std::vector& server_jvm_args) { ExecuteProgram(exe, server_jvm_args); } void ExecuteRunRequest(const blaze_util::Path& exe, const std::vector& run_request_args) { ExecuteProgram(exe, run_request_args); } const char kListSeparator = ':'; bool SymlinkDirectories(const string& target, const blaze_util::Path& link) { return symlink(target.c_str(), link.AsNativePath().c_str()) == 0; } // Notifies the client about the death of the server process by keeping a socket // open in the server. If the server dies for any reason, the socket will be // closed, which can be detected by the client. class SocketBlazeServerStartup : public BlazeServerStartup { public: SocketBlazeServerStartup(int pipe_fd); virtual ~SocketBlazeServerStartup(); virtual bool IsStillAlive(); private: int fd; }; SocketBlazeServerStartup::SocketBlazeServerStartup(int fd) : fd(fd) { } SocketBlazeServerStartup::~SocketBlazeServerStartup() { close(fd); } bool SocketBlazeServerStartup::IsStillAlive() { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLIN; int result; do { result = poll(&pfd, 1, 0); } while (result < 0 && errno == EINTR); if (result == 0) { // Timeout, server is still alive return true; } else { // Whether it's an error or pfd.revents & POLLHUP > 0, we assume child is // dead. return false; } } // Sets platform-specific attributes for the creation of the daemon process. // // Returns zero on success or -1 on error, in which case errno is set to the // corresponding error details. int ConfigureDaemonProcess(posix_spawnattr_t* attrp, const StartupOptions &options); void WriteSystemSpecificProcessIdentifier(const blaze_util::Path& server_dir, pid_t server_pid); int ExecuteDaemon(const blaze_util::Path& exe, const std::vector& args_vector, const std::map& env, const blaze_util::Path& daemon_output, const bool daemon_output_append, const string& binaries_dir, const blaze_util::Path& server_dir, const StartupOptions& options, BlazeServerStartup** server_startup) { const blaze_util::Path pid_file = server_dir.GetRelative(kServerPidFile); const string daemonize = blaze_util::JoinPath(binaries_dir, "daemonize"); std::vector daemonize_args = {"daemonize", "-l", daemon_output.AsNativePath(), "-p", pid_file.AsNativePath()}; if (daemon_output_append) { daemonize_args.push_back("-a"); } daemonize_args.push_back("--"); daemonize_args.push_back(exe.AsNativePath()); std::copy(args_vector.begin(), args_vector.end(), std::back_inserter(daemonize_args)); int fds[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "socket creation failed: " << GetLastErrorString(); } posix_spawn_file_actions_t file_actions; if (posix_spawn_file_actions_init(&file_actions) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to create posix_spawn_file_actions: " << GetLastErrorString(); } if (posix_spawn_file_actions_addclose(&file_actions, fds[0]) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to modify posix_spawn_file_actions: "<< GetLastErrorString(); } posix_spawnattr_t attrp; if (posix_spawnattr_init(&attrp) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to create posix_spawnattr: " << GetLastErrorString(); } if (ConfigureDaemonProcess(&attrp, options) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to modify posix_spawnattr: " << GetLastErrorString(); } pid_t transient_pid; if (posix_spawn(&transient_pid, daemonize.c_str(), &file_actions, &attrp, CharPP(daemonize_args).get(), CharPP(env).get()) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to execute JVM via " << daemonize << ": " << GetLastErrorString(); } close(fds[1]); posix_spawnattr_destroy(&attrp); posix_spawn_file_actions_destroy(&file_actions); // Wait for daemonize to exit. This guarantees that the pid file exists. int status; if (waitpid(transient_pid, &status, 0) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "waitpid failed: " << GetLastErrorString(); } if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "daemonize didn't exit cleanly: " << GetLastErrorString(); } std::ifstream pid_reader(pid_file.AsNativePath()); if (!pid_reader) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to open " << pid_file.AsPrintablePath() << ": " << err; } pid_t server_pid; pid_reader >> server_pid; if (pid_reader.fail()) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "Failed to read pid from: " << pid_file.AsPrintablePath(); } WriteSystemSpecificProcessIdentifier(server_dir, server_pid); *server_startup = new SocketBlazeServerStartup(fds[0]); return server_pid; } string GetHashedBaseDir(const string& root, const string& hashable) { unsigned char buf[blaze_util::Md5Digest::kDigestLength]; blaze_util::Md5Digest digest; digest.Update(hashable.data(), hashable.size()); digest.Finish(buf); return blaze_util::JoinPath(root, digest.String()); } void CreateSecureOutputRoot(const blaze_util::Path& path) { struct stat fileinfo = {}; if (!blaze_util::MakeDirectories(path, 0755)) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "mkdir('" << path.AsPrintablePath() << "'): " << err; } // The path already exists. // Check ownership and mode, and verify that it is a directory. if (lstat(path.AsNativePath().c_str(), &fileinfo) < 0) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "lstat('" << path.AsPrintablePath() << "'): " << err; } if (fileinfo.st_uid != geteuid()) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "'" << path.AsPrintablePath() << "' is not owned by me"; } if ((fileinfo.st_mode & 022) != 0) { int new_mode = fileinfo.st_mode & (~022); if (chmod(path.AsNativePath().c_str(), new_mode) < 0) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "'" << path.AsPrintablePath() << "' has mode " << (fileinfo.st_mode & 07777) << ", chmod to " << new_mode << " failed"; } } if (stat(path.AsNativePath().c_str(), &fileinfo) < 0) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "stat('" << path.AsPrintablePath() << "'): " << err; } if (!S_ISDIR(fileinfo.st_mode)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "'" << path.AsPrintablePath() << "' is not a directory"; } ExcludePathFromBackup(path); } string GetEnv(const string& name) { char* result = getenv(name.c_str()); return result != nullptr ? string(result) : ""; } string GetPathEnv(const string& name) { return GetEnv(name); } bool ExistsEnv(const string& name) { return getenv(name.c_str()) != nullptr; } void SetEnv(const string& name, const string& value) { setenv(name.c_str(), value.c_str(), 1); } void UnsetEnv(const string& name) { unsetenv(name.c_str()); } bool WarnIfStartedFromDesktop() { return false; } void SetupStdStreams() { // Set non-buffered output mode for stderr/stdout. The server already // line-buffers messages where it makes sense, so there's no need to do set // line-buffering here. On the other hand the server sometimes sends binary // output (when for example a query returns results as proto), in which case // we must not perform line buffering on the client side. So turn off // buffering here completely. setvbuf(stdout, nullptr, _IONBF, 0); setvbuf(stderr, nullptr, _IONBF, 0); // Ensure we have three open fds. Otherwise we can end up with // bizarre things like stdout going to the lock file, etc. if (fcntl(STDIN_FILENO, F_GETFL) == -1) open("/dev/null", O_RDONLY); if (fcntl(STDOUT_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); if (fcntl(STDERR_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); } // A signal-safe version of fprintf(stderr, ...). // // WARNING: any output from the blaze client may be interleaved // with output from the blaze server. In --curses mode, // the Blaze server often erases the previous line of output. // So, be sure to end each such message with TWO newlines, // otherwise it may be erased by the next message from the // Blaze server. // Also, it's a good idea to start each message with a newline, // in case the Blaze server has written a partial line. void SigPrintf(const char *format, ...) { char buf[1024]; va_list ap; va_start(ap, format); int r = vsnprintf(buf, sizeof buf, format, ap); va_end(ap); if (write(STDERR_FILENO, buf, r) <= 0) { // We don't care, just placate the compiler. } } static int setlk(int fd, struct flock *lock) { #ifdef __linux__ // If we're building with glibc <2.20, or another libc which predates // OFD locks, define the constant ourselves. This assumes that the libc // and kernel definitions for struct flock are identical. #ifndef F_OFD_SETLK #define F_OFD_SETLK 37 #endif #endif #ifdef F_OFD_SETLK // Prefer OFD locks if available. POSIX locks can be lost "accidentally" // due to any close() on the lock file, and are not reliably preserved // across execve() on Linux, which we need for --batch mode. if (fcntl(fd, F_OFD_SETLK, lock) == 0) return 0; if (errno != EINVAL) { if (errno != EACCES && errno != EAGAIN) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "unexpected result from F_OFD_SETLK: " << GetLastErrorString(); } return -1; } // F_OFD_SETLK was added in Linux 3.15. Older kernels return EINVAL. // Fall back to F_SETLK in that case. #endif if (fcntl(fd, F_SETLK, lock) == 0) return 0; if (errno != EACCES && errno != EAGAIN) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "unexpected result from F_SETLK: " << GetLastErrorString(); } return -1; } uint64_t AcquireLock(const blaze_util::Path& output_base, bool batch_mode, bool block, BlazeLock* blaze_lock) { blaze_util::Path lockfile = output_base.GetRelative("lock"); int lockfd = open(lockfile.AsNativePath().c_str(), O_CREAT | O_RDWR, 0644); if (lockfd < 0) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "cannot open lockfile '" << lockfile.AsPrintablePath() << "' for writing: " << err; } // Keep server from inheriting a useless fd if we are not in batch mode if (!batch_mode) { string err = GetLastErrorString(); if (fcntl(lockfd, F_SETFD, FD_CLOEXEC) == -1) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "fcntl(F_SETFD) failed for lockfile: " << err; } } struct flock lock = {}; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; // This doesn't really matter now, but allows us to subdivide the lock // later if that becomes meaningful. (Ranges beyond EOF can be locked.) lock.l_len = 4096; // Take the exclusive server lock. If we fail, we busy-wait until the lock // becomes available. // // We used to rely on fcntl(F_SETLKW) to lazy-wait for the lock to become // available, which is theoretically fine, but doing so prevents us from // determining if the PID of the server holding the lock has changed under the // hood. There have been multiple bug reports where users (especially macOS // ones) mention that the Blaze invocation hangs on a non-existent PID. This // should help troubleshoot those scenarios in case there really is a bug // somewhere. bool multiple_attempts = false; string owner; const uint64_t start_time = GetMillisecondsMonotonic(); while (setlk(lockfd, &lock) == -1) { string buffer(4096, 0); ssize_t r = pread(lockfd, &buffer[0], buffer.size(), 0); if (r < 0) { BAZEL_LOG(WARNING) << "pread() lock file: " << strerror(errno); r = 0; } buffer.resize(r); if (owner != buffer) { // Each time we learn a new lock owner, print it out. owner = buffer; BAZEL_LOG(USER) << "Another command holds the client lock: \n" << owner; if (block) { BAZEL_LOG(USER) << "Waiting for it to complete..."; fflush(stderr); } } if (!block) { BAZEL_DIE(blaze_exit_code::LOCK_HELD_NOBLOCK_FOR_LOCK) << "Exiting because the lock is held and --noblock_for_lock was " "given."; } TrySleep(500); multiple_attempts = true; } const uint64_t end_time = GetMillisecondsMonotonic(); // If we took the lock on the first try, force the reported wait time to 0 to // avoid unnecessary noise in the logs. In this metric, we are only // interested in knowing how long it took for other commands to complete, not // how fast acquiring a lock is. const uint64_t wait_time = !multiple_attempts ? 0 : end_time - start_time; // Identify ourselves in the lockfile. // The contents are printed for human consumption when another client // fails to take the lock, but not parsed otherwise. (void) ftruncate(lockfd, 0); lseek(lockfd, 0, SEEK_SET); // Arguably we should ensure this fits in the 4KB we lock. In practice no one // will have a cwd long enough to overflow that, and nothing currently uses // the rest of the lock file anyway. dprintf(lockfd, "pid=%d\nowner=client\n", getpid()); string cwd = blaze_util::GetCwd(); dprintf(lockfd, "cwd=%s\n", cwd.c_str()); if (const char *tty = ttyname(STDIN_FILENO)) { // NOLINT (single-threaded) dprintf(lockfd, "tty=%s\n", tty); } blaze_lock->lockfd = lockfd; return wait_time; } void ReleaseLock(BlazeLock* blaze_lock) { close(blaze_lock->lockfd); } bool KillServerProcess(int pid, const blaze_util::Path& output_base) { // Kill the process and make sure it's dead before proceeding. errno = 0; if (killpg(pid, SIGKILL) == -1) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "Attempted to kill stale server process (pid=" << pid << ") using SIGKILL: " << GetLastErrorString(); } if (!AwaitServerProcessTermination(pid, output_base, kPostKillGracePeriodSeconds)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "Attempted to kill stale server process (pid=" << pid << ") using SIGKILL, but it did not die in a timely fashion."; } return true; } void TrySleep(unsigned int milliseconds) { time_t seconds_part = (time_t)(milliseconds / 1000); long nanoseconds_part = ((long)(milliseconds % 1000)) * 1000 * 1000; struct timespec sleeptime = {seconds_part, nanoseconds_part}; nanosleep(&sleeptime, nullptr); } string GetUserName() { string user = GetEnv("USER"); if (!user.empty()) { return user; } errno = 0; passwd *pwent = getpwuid(getuid()); // NOLINT (single-threaded) if (pwent == nullptr || pwent->pw_name == nullptr) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "$USER is not set, and unable to look up name of current user: " << GetLastErrorString(); } return pwent->pw_name; } bool IsEmacsTerminal() { string emacs = GetEnv("EMACS"); // GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't // do that and instead sets INSIDE_EMACS= (where can look like // e.g. "25.1.1,comint"). So we check both variables for maximum // compatibility. return emacs == "t" || ExistsEnv("INSIDE_EMACS"); } bool IsStandardTerminal() { string term = GetEnv("TERM"); bool isEmacs = IsEmacsTerminal(); // Emacs 22+ terminal emulation uses 'eterm-color' as its terminfo name and, // more importantly, supports color in terminals. // see https://github.com/emacs-mirror/emacs/blob/master/etc/NEWS.22#L331-L333 if (isEmacs && term == "eterm-color") { return true; } if (term.empty() || term == "dumb" || term == "emacs" || term == "xterm-mono" || term == "symbolics" || term == "9term" || isEmacs) { return false; } return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO); } int GetTerminalColumns() { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) { return ws.ws_col; } string columns_env = GetEnv("COLUMNS"); if (!columns_env.empty()) { char* endptr; int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10); if (*endptr == '\0') { // $COLUMNS is a valid number return columns; } } return 80; // default if not a terminal. } // Raises a resource limit to the maximum allowed value. // // This function raises the limit of the resource given in "resource" from its // soft limit to its hard limit. If the hard limit is unlimited and // allow_infinity is false, uses the kernel-level limit because setting the // soft limit to unlimited may not work. // // Note that this is a best-effort operation. Any failure during this process // will result in a warning but execution will continue. static bool UnlimitResource(const int resource, const bool allow_infinity) { struct rlimit rl; if (getrlimit(resource, &rl) == -1) { BAZEL_LOG(WARNING) << "failed to get resource limit " << resource << ": " << strerror(errno); return false; } if (rl.rlim_cur == rl.rlim_max) { // Nothing to do. Return early to prevent triggering any warnings caused by // the code below. This way, we will only show warnings the first time the // Blaze server is started and not on each command invocation. return true; } const rlim_t explicit_limit = GetExplicitSystemLimit(resource); if (explicit_limit <= 0) { // If not implemented (-1) or on an error (0), do nothing and try to // increase the soft limit to the hard one. This might fail, but it's good // to try anyway. rl.rlim_cur = rl.rlim_max; } else { if ((rl.rlim_max == RLIM_INFINITY && !allow_infinity) || rl.rlim_max > explicit_limit) { rl.rlim_cur = explicit_limit; } else { rl.rlim_cur = rl.rlim_max; } } if (setrlimit(resource, &rl) == -1) { BAZEL_LOG(WARNING) << "failed to raise resource limit " << resource << " to " << static_cast(rl.rlim_cur) << ": " << strerror(errno); return false; } return true; } bool UnlimitResources() { bool success = true; success &= UnlimitResource(RLIMIT_NOFILE, false); success &= UnlimitResource(RLIMIT_NPROC, false); return success; } bool UnlimitCoredumps() { return UnlimitResource(RLIMIT_CORE, true); } void EnsurePythonPathOption(vector* options) { // do nothing. } } // namespace blaze.