|  | // 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, nullptr); | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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. | 
|  | 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, 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. |