| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> // PATH_MAX |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #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" |
| |
| 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; |
| |
| // A signal-safe version of fprintf(stderr, ...). |
| static void sigprintf(const char *format, ...); |
| |
| // 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<string> &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<char **>(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<string>& 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<string>& 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. |
| static 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); |
| } |
| |
| } // namespace blaze. |