blob: a51d8516d5117ecf488aaef26d5e69ea029d584e [file] [log] [blame]
// 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;
}