diff options
Diffstat (limited to 'src/main/tools/process-tools.cc')
-rw-r--r-- | src/main/tools/process-tools.cc | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/src/main/tools/process-tools.cc b/src/main/tools/process-tools.cc new file mode 100644 index 0000000000..7dfb6a7e42 --- /dev/null +++ b/src/main/tools/process-tools.cc @@ -0,0 +1,280 @@ +// 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(__linux__) + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { + DIE("prctl"); + } +#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; +} |