| // 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_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; 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<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] = nullptr; | |
| } | |
| // 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 (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<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; | |
| 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=<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. |