blob: 1cbea528c5aaf1893136de208aaaa827ff730bfe [file] [log] [blame]
// 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h> // PATH_MAX
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <spawn.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <cassert>
#include <cinttypes>
#include <cstdlib>
#include <fstream>
#include <iterator>
#include <set>
#include <string>
#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_exit_code::INTERNAL_ERROR;
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<string> 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; killed.\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; shutting down.\n\n",
SignalHandler::Get().GetProductName().c_str());
SignalHandler::Get().CancelServer();
break;
case SIGTERM:
SigPrintf(
"\n%s caught terminate signal; shutting down.\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, NULL);
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<string> 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<string>& args) {
charpp_ = static_cast<char**>(malloc(sizeof(char*) * (args.size() + 1)));
size_t i = 0;
for (; i < args.size(); i++) {
charpp_[i] = strdup(args[i].c_str());
}
charpp_[i] = NULL;
}
// Constructs a new CharPP from a list of environment variables.
explicit CharPP(const std::map<string, EnvVarValue>& env) {
charpp_ = static_cast<char**>(malloc(sizeof(char*) * (env.size() + 1)));
size_t i = 0;
for (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] = NULL;
}
// Deletes all memory held by the CharPP.
~CharPP() {
for (char** ptr = charpp_; *ptr != NULL; 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<string>& 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<string>& server_jvm_args) {
ExecuteProgram(exe, server_jvm_args);
}
void ExecuteRunRequest(const blaze_util::Path& exe,
const std::vector<string>& 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<string>& args_vector,
const std::map<string, EnvVarValue>& 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<string> 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;
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 != NULL ? string(result) : "";
}
string GetPathEnv(const string& name) { return GetEnv(name); }
bool ExistsEnv(const string& name) {
return getenv(name.c_str()) != NULL;
}
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, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _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.
killpg(pid, SIGKILL);
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, NULL);
}
string GetUserName() {
string user = GetEnv("USER");
if (!user.empty()) {
return user;
}
errno = 0;
passwd *pwent = getpwuid(getuid()); // NOLINT (single-threaded)
if (pwent == NULL || pwent->pw_name == NULL) {
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=<stuff> (where <stuff> 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<intmax_t>(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<string>* options) {
// do nothing.
}
} // namespace blaze.