// 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. #include #include #include // PATH_MAX #include #include #include #include #include #include #include #include #include #include "src/main/cpp/blaze_util.h" #include "src/main/cpp/blaze_util_platform.h" #include "src/main/cpp/global_variables.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/file_platform.h" #include "src/main/cpp/util/md5.h" #include "src/main/cpp/util/numbers.h" namespace blaze { using blaze_util::die; using blaze_util::pdie; using std::string; using std::vector; 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().GetGlobals()->options->product_name.c_str()); if (SignalHandler::Get().GetGlobals()->server_pid != -1) { KillServerProcess(SignalHandler::Get().GetGlobals()->server_pid); } ExitImmediately(1); } SigPrintf( "\n%s caught interrupt signal; shutting down.\n\n", SignalHandler::Get().GetGlobals()->options->product_name.c_str()); SignalHandler::Get().CancelServer(); break; case SIGTERM: SigPrintf( "\n%s caught terminate signal; shutting down.\n\n", SignalHandler::Get().GetGlobals()->options->product_name.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().GetGlobals()->server_pid, SignalHandler::Get().GetGlobals()->jvm_log_file.c_str()); kill(SignalHandler::Get().GetGlobals()->server_pid, SIGQUIT); break; } errno = saved_errno; } void SignalHandler::Install(GlobalVariables* globals, SignalHandler::Callback cancel_server) { _globals = globals; _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 ToString(getpid()); } void ExecuteProgram(const string &exe, const vector &args_vector) { if (VerboseLogging()) { string dbg; for (const auto &s : args_vector) { dbg.append(s); dbg.append(" "); } string cwd = blaze_util::GetCwd(); fprintf(stderr, "Invoking binary %s in %s:\n %s\n", exe.c_str(), cwd.c_str(), dbg.c_str()); } // Copy to a char* array for execv: int n = args_vector.size(); const char **argv = new const char *[n + 1]; for (int i = 0; i < n; ++i) { argv[i] = args_vector[i].c_str(); } argv[n] = NULL; execv(exe.c_str(), const_cast(argv)); } std::string ConvertPath(const std::string &path) { return path; } std::string ConvertPathList(const std::string& path_list) { return path_list; } std::string ListSeparator() { return ":"; } bool SymlinkDirectories(const string &target, const string &link) { return symlink(target.c_str(), link.c_str()) == 0; } // Causes the current process to become a daemon (i.e. a child of // init, detached from the terminal, in its own session.) We don't // change cwd, though. static void Daemonize(const string& daemon_output) { // Don't call die() or exit() in this function; we're already in a // child process so it won't work as expected. Just don't do // anything that can possibly fail. :) signal(SIGHUP, SIG_IGN); if (fork() > 0) { // This second fork is required iff there's any chance cmd will // open an specific tty explicitly, e.g., open("/dev/tty23"). If // not, this fork can be removed. _exit(blaze_exit_code::SUCCESS); } setsid(); close(0); close(1); close(2); open("/dev/null", O_RDONLY); // stdin // stdout: if (open(daemon_output.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666) == -1) { // In a daemon, no-one can hear you scream. open("/dev/null", O_WRONLY); } (void) dup(STDOUT_FILENO); // stderr (2>&1) } class PipeBlazeServerStartup : public BlazeServerStartup { public: PipeBlazeServerStartup(int pipe_fd); virtual ~PipeBlazeServerStartup(); virtual bool IsStillAlive(); private: int pipe_fd; }; PipeBlazeServerStartup::PipeBlazeServerStartup(int pipe_fd) { this->pipe_fd = pipe_fd; if (fcntl(pipe_fd, F_SETFL, O_NONBLOCK | fcntl(pipe_fd, F_GETFL))) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "Failed: fcntl to enable O_NONBLOCK on pipe"); } } PipeBlazeServerStartup::~PipeBlazeServerStartup() { close(pipe_fd); } bool PipeBlazeServerStartup::IsStillAlive() { char c; return read(this->pipe_fd, &c, 1) == -1 && errno == EAGAIN; } void WriteSystemSpecificProcessIdentifier(const string& server_dir); void ExecuteDaemon(const string& exe, const std::vector& args_vector, const string& daemon_output, const string& server_dir, BlazeServerStartup** server_startup) { int fds[2]; if (pipe(fds)) { pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed"); } int child = fork(); if (child == -1) { pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed"); } else if (child > 0) { // we're the parent close(fds[1]); // parent keeps only the reading side int unused_status; waitpid(child, &unused_status, 0); // child double-forks *server_startup = new PipeBlazeServerStartup(fds[0]); return; } else { close(fds[0]); // child keeps only the writing side } Daemonize(daemon_output); string pid_string = GetProcessIdAsString(); string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile); if (!blaze_util::WriteFile(pid_string, pid_file)) { // The exit code does not matter because we are already in the daemonized // server. The output of this operation will end up in jvm.out . pdie(0, "Cannot write PID file"); } WriteSystemSpecificProcessIdentifier(server_dir); ExecuteProgram(exe, args_vector); pdie(0, "Cannot execute %s", exe.c_str()); } string RunProgram(const string& exe, const std::vector& args_vector) { int fds[2]; if (pipe(fds)) { pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed"); } int recv_socket = fds[0]; int send_socket = fds[1]; int child = fork(); if (child == -1) { pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed"); } else if (child > 0) { // we're the parent close(send_socket); // parent keeps only the reading side string result; bool success = blaze_util::ReadFrom( [recv_socket](void* buf, int size) { return read(recv_socket, buf, size); }, &result); close(recv_socket); if (!success) { pdie(blaze_exit_code::INTERNAL_ERROR, "Cannot read subprocess output"); } return result; } else { // We're the child close(recv_socket); // child keeps only the writing side // Redirect output to the writing side of the dup. dup2(send_socket, STDOUT_FILENO); dup2(send_socket, STDERR_FILENO); // Execute the binary ExecuteProgram(exe, args_vector); pdie(blaze_exit_code::INTERNAL_ERROR, "Failed to run %s", exe.c_str()); } return string(""); // We cannot reach here, just placate the compiler. } bool ReadDirectorySymlink(const string &name, string* result) { char buf[PATH_MAX + 1]; int len = readlink(name.c_str(), buf, PATH_MAX); if (len < 0) { return false; } buf[len] = 0; *result = buf; return true; } bool CompareAbsolutePaths(const string& a, const string& b) { return a == b; } 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 root + "/" + digest.String(); } void CreateSecureOutputRoot(const string& path) { const char* root = path.c_str(); struct stat fileinfo = {}; if (!MakeDirectories(root, 0755)) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "mkdir('%s')", root); } // The path already exists. // Check ownership and mode, and verify that it is a directory. if (lstat(root, &fileinfo) < 0) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "lstat('%s')", root); } if (fileinfo.st_uid != geteuid()) { die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not owned by me", root); } if ((fileinfo.st_mode & 022) != 0) { int new_mode = fileinfo.st_mode & (~022); if (chmod(root, new_mode) < 0) { die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' has mode %o, chmod to %o failed", root, fileinfo.st_mode & 07777, new_mode); } } if (stat(root, &fileinfo) < 0) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "stat('%s')", root); } if (!S_ISDIR(fileinfo.st_mode)) { die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not a directory", root); } ExcludePathFromBackup(root); } // Runs "stat" on `path`. Returns -1 and sets errno if stat fails or // `path` isn't a directory. If check_perms is true, this will also // make sure that `path` is owned by the current user and has `mode` // permissions (observing the umask). It attempts to run chmod to // correct the mode if necessary. If `path` is a symlink, this will // check ownership of the link, not the underlying directory. static bool GetDirectoryStat(const string& path, mode_t mode, bool check_perms) { struct stat filestat = {}; if (stat(path.c_str(), &filestat) == -1) { return false; } if (!S_ISDIR(filestat.st_mode)) { errno = ENOTDIR; return false; } if (check_perms) { // If this is a symlink, run checks on the link. (If we did lstat above // then it would return false for ISDIR). struct stat linkstat = {}; if (lstat(path.c_str(), &linkstat) != 0) { return false; } if (linkstat.st_uid != geteuid()) { // The directory isn't owned by me. errno = EACCES; return false; } mode_t mask = umask(022); umask(mask); mode = (mode & ~mask); if ((filestat.st_mode & 0777) != mode && chmod(path.c_str(), mode) == -1) { // errno set by chmod. return false; } } return true; } static bool MakeDirectories(const string& path, mode_t mode, bool childmost) { if (path.empty() || path == "/") { errno = EACCES; return false; } bool stat_succeeded = GetDirectoryStat(path, mode, childmost); if (stat_succeeded) { return true; } if (errno == ENOENT) { // Path does not exist, attempt to create its parents, then it. string parent = blaze_util::Dirname(path); if (!MakeDirectories(parent, mode, false)) { // errno set by stat. return false; } if (mkdir(path.c_str(), mode) == -1) { if (errno == EEXIST) { if (childmost) { // If there are multiple bazel calls at the same time then the // directory could be created between the MakeDirectories and mkdir // calls. This is okay, but we still have to check the permissions. return GetDirectoryStat(path, mode, childmost); } else { // If this isn't the childmost directory, we don't care what the // permissions were. If it's not even a directory then that error will // get caught when we attempt to create the next directory down the // chain. return true; } } // errno set by mkdir. return false; } return true; } return stat_succeeded; } // mkdir -p path. Returns 0 if the path was created or already exists and could // be chmod-ed to exactly the given permissions. If final part of the path is a // symlink, this ensures that the destination of the symlink has the desired // permissions. It also checks that the directory or symlink is owned by us. // On failure, this returns -1 and sets errno. bool MakeDirectories(const string& path, unsigned int mode) { return MakeDirectories(path, mode, true); } string GetEnv(const string& name) { char* result = getenv(name.c_str()); return result != NULL ? string(result) : ""; } 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()); } ATTRIBUTE_NORETURN void ExitImmediately(int exit_code) { _exit(exit_code); } 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. } } uint64_t AcquireLock(const string& output_base, bool batch_mode, bool block, BlazeLock* blaze_lock) { string lockfile = output_base + "/lock"; int lockfd = open(lockfile.c_str(), O_CREAT|O_RDWR, 0644); if (lockfd < 0) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "cannot open lockfile '%s' for writing", lockfile.c_str()); } // Keep server from inheriting a useless fd if we are not in batch mode if (!batch_mode) { if (fcntl(lockfd, F_SETFD, FD_CLOEXEC) == -1) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "fcntl(F_SETFD) failed for lockfile"); } } 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; uint64_t wait_time = 0; // Try to take the lock, without blocking. if (fcntl(lockfd, F_SETLK, &lock) == -1) { if (errno != EACCES && errno != EAGAIN) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "unexpected result from F_SETLK"); } // We didn't get the lock. Find out who has it. struct flock probe = lock; probe.l_pid = 0; if (fcntl(lockfd, F_GETLK, &probe) == -1) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "unexpected result from F_GETLK"); } if (!block) { die(blaze_exit_code::BAD_ARGV, "Another command is running (pid=%d). Exiting immediately.", probe.l_pid); } fprintf(stderr, "Another command is running (pid = %d). " "Waiting for it to complete...", probe.l_pid); fflush(stderr); // Take a clock sample for that start of the waiting time uint64_t st = GetMillisecondsMonotonic(); // Try to take the lock again (blocking). int r; do { r = fcntl(lockfd, F_SETLKW, &lock); } while (r == -1 && errno == EINTR); fprintf(stderr, "\n"); if (r == -1) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "couldn't acquire file lock"); } // Take another clock sample, calculate elapsed uint64_t et = GetMillisecondsMonotonic(); wait_time = et - st; } // Identify ourselves in the lockfile. (void) ftruncate(lockfd, 0); const char *tty = ttyname(STDIN_FILENO); // NOLINT (single-threaded) string msg = "owner=launcher\npid=" + ToString(getpid()) + "\ntty=" + (tty ? tty : "") + "\n"; // The contents are currently meant only for debugging. (void) write(lockfd, msg.data(), msg.size()); blaze_lock->lockfd = lockfd; return wait_time; } void ReleaseLock(BlazeLock* blaze_lock) { close(blaze_lock->lockfd); } 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) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "$USER is not set, and unable to look up name of current user"); } return pwent->pw_name; } bool IsEmacsTerminal() { string emacs = GetEnv("EMACS"); string inside_emacs = GetEnv("INSIDE_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" || !inside_emacs.empty(); } // Returns true iff both stdout and stderr are connected to a // terminal, and it can support color and cursor movement // (this is computed heuristically based on the values of // environment variables). bool IsStandardTerminal() { string term = GetEnv("TERM"); if (term.empty() || term == "dumb" || term == "emacs" || term == "xterm-mono" || term == "symbolics" || term == "9term" || IsEmacsTerminal()) { return false; } return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO); } // Returns the number of columns of the terminal to which stdout is // connected, or $COLUMNS (default 80) if there is no such terminal. 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. } } // namespace blaze.