// 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 "src/main/tools/process-tools.h" #include #include #include #include #include #include #include #if defined(__linux__) #include #endif #if defined(__FreeBSD__) #include #endif #include #include #include #include #include #include using std::vector; // Drops privileges irrevocably to the real uid / gid by setting the effective // and saved uid / gid to the real uid / gid. Useful if we happen to have been // called as a setuid-/setgid-root binary. void DropPrivileges() { if (setgid(getgid()) < 0) { DIE("setgid"); } if (setuid(getuid()) < 0) { DIE("setuid"); } } void Redirect(const std::string &target_path, int fd) { if (!target_path.empty() && target_path != "-") { const int flags = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND; int fd_out = open(target_path.c_str(), flags, 0666); if (fd_out < 0) { DIE("open(%s)", target_path.c_str()); } // If we were launched with less than 3 fds (stdin, stdout, stderr) open, // but redirection is still requested via a command-line flag, something is // wacky and the following code would not do what we intend to do, so let's // bail. if (fd_out < 3) { DIE("open(%s) returned a handle that is reserved for stdin / stdout / " "stderr", target_path.c_str()); } if (dup2(fd_out, fd) < 0) { DIE("dup2()"); } if (close(fd_out) < 0) { DIE("close()"); } } } void WriteFile(const std::string &filename, const char *fmt, ...) { FILE *stream = fopen(filename.c_str(), "w"); if (stream == nullptr) { DIE("fopen(%s)", filename.c_str()); } va_list ap; va_start(ap, fmt); // Use a local variable to make sure we call va_end before DIE() in case this // returns an error. int r = vfprintf(stream, fmt, ap); va_end(ap); if (r < 0) { DIE("vfprintf"); } if (fclose(stream) != 0) { DIE("fclose(%s)", filename.c_str()); } } void SetTimeout(double timeout_secs) { if (timeout_secs <= 0) { DIE("timeout_secs must be positive"); } double int_val, fraction_val; fraction_val = modf(timeout_secs, &int_val); struct itimerval timer; timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 0; timer.it_value.tv_sec = (time_t)int_val, timer.it_value.tv_usec = (suseconds_t)(fraction_val * 1e6); if (setitimer(ITIMER_REAL, &timer, nullptr) < 0) { DIE("setitimer"); } } void InstallSignalHandler(int signum, void (*handler)(int)) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; if (handler == SIG_IGN || handler == SIG_DFL) { // No point in blocking signals when using the default handler or ignoring // the signal. if (sigemptyset(&sa.sa_mask) < 0) { DIE("sigemptyset"); } } else { // When using a custom handler, block all signals from firing while the // handler is running. if (sigfillset(&sa.sa_mask) < 0) { DIE("sigfillset"); } } // sigaction may fail for certain reserved signals. Ignore failure in this // case. sigaction(signum, &sa, nullptr); } void IgnoreSignal(int signum) { InstallSignalHandler(signum, SIG_IGN); } void RestoreSignalHandlersAndMask() { // Use an empty signal mask for the process (= unblock all signals). sigset_t empty_set; if (sigemptyset(&empty_set) < 0) { DIE("sigemptyset"); } if (sigprocmask(SIG_SETMASK, &empty_set, nullptr) < 0) { DIE("sigprocmask(SIG_SETMASK, , nullptr)"); } // Set the default signal handler for all signals. struct sigaction sa; memset(&sa, 0, sizeof(sa)); if (sigemptyset(&sa.sa_mask) < 0) { DIE("sigemptyset"); } sa.sa_handler = SIG_DFL; for (int i = 1; i < NSIG; ++i) { // Ignore possible errors, because we might not be allowed to set the // handler for certain signals, but we still want to try. sigaction(i, &sa, nullptr); } } void KillMeWhenMyParentDies(int signum) { #if defined(__linux__) if (prctl(PR_SET_PDEATHSIG, signum) < 0) { DIE("prctl"); } #endif } void BecomeSubreaper() { #if defined(__FreeBSD__) if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, 0) < 0) { DIE("procctl"); } #endif #if defined(PR_SET_CHILD_SUBREAPER) // The "child subreaper" feature needs Linux 3.4 or higher. // TODO(philwo) print a warning when this fails. prctl(PR_SET_CHILD_SUBREAPER, 1); #endif } int SpawnCommand(const vector &args) { int child_pid = fork(); if (child_pid < 0) { DIE("fork"); } else if (child_pid == 0) { // Put the child into its own process group. if (setpgid(0, 0) < 0) { DIE("setpgid"); } // Try to assign our terminal to the child process. if (tcsetpgrp(STDIN_FILENO, getpgrp()) < 0 && errno != ENOTTY) { DIE("tcsetpgrp") } // Unblock all signals, restore default handlers. RestoreSignalHandlersAndMask(); // Force umask to include read and execute for everyone, to make output // permissions predictable. umask(022); if (execvp(args[0], args.data()) < 0) { DIE("execvp(%s, %p)", args[0], args.data()); } } return child_pid; } static void KillAllRemainingChildren(int main_child_pid) { // If the child process we spawned earlier terminated, we want to make // sure all remaining (grand)children are killed, too. if (getpid() == 1) { // If we're PID 1, this is easy. if (kill(-1, SIGKILL) < 0 && errno != ESRCH) { DIE("kill"); } } else { #if defined(__FreeBSD__) // FreeBSD is cool, because it has an API to kill all our descendants in one // go. struct procctl_reaper_kill data; data.rk_sig = SIGKILL; if (procctl(P_PID, getpid(), PROC_REAP_KILL, &data) < 0 && errno != ESRCH) { DIE("procctl") } #else // On other operating systems, we have to resort to sending SIGKILL to the // process group of our child and hope that this kills them all. // TODO(philwo) - what if a child switched to a different process group // and we can't kill it like this? Maybe parse /proc/*/stat and filter // by "their PPID = my PID"? if (kill(-main_child_pid, SIGKILL) < 0 && errno != ESRCH) { DIE("kill"); } #endif } } int WaitForChild(int main_child_pid) { // This will be overwritten by the real exitcode from the child in the loop // below. In case something goes horribly wrong and that doesn't happen, at // least exit with a failure. int exitcode = EXIT_FAILURE; while (1) { // Check for zombies to be reaped and exit, if our own child exited. int status; pid_t killed_pid = wait(&status); if (killed_pid < 0) { // Our PID1 process got a signal that interrupted the wait() call and that // was either ignored or forwarded to the child. This is expected and // fine, just continue waiting. if (errno == EINTR) { continue; } else if (errno == ECHILD) { // No children left to wait for, we're done here. break; } DIE("waitpid") } else { if (killed_pid == main_child_pid) { KillAllRemainingChildren(main_child_pid); if (WIFSIGNALED(status)) { exitcode = 128 + WTERMSIG(status); } else { exitcode = WEXITSTATUS(status); } } } } return exitcode; }