| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <math.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #if defined(__linux__) |
| #include <sys/prctl.h> |
| #endif |
| #if defined(__FreeBSD__) |
| #include <sys/procctl.h> |
| #endif |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <vector> |
| |
| 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, <empty set>, 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<char *> &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; |
| } |