blob: dae919d9e8ee8009cc3507a9a0160b0869be9646 [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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <memory>
#include "src/main/protobuf/execution_statistics.pb.h"
#include "src/main/tools/logging.h"
static volatile sig_atomic_t child_pid_for_signal = 0;
int SwitchToEuid() {
int uid = getuid();
int euid = geteuid();
if (uid != euid) {
if (setreuid(euid, euid) < 0) {
DIE("setreuid");
}
}
return euid;
}
int SwitchToEgid() {
int gid = getgid();
int egid = getegid();
if (gid != egid) {
if (setregid(egid, egid) < 0) {
DIE("setregid");
}
}
return egid;
}
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");
}
}
}
static void ForciblyKillEverything(int signo) {
// We don't update the last_signal field tracked in process-wrapper-legacy.cc,
// which is used to report the signal back to the user, because we want to
// know (from the caller side) the original signal that caused us to stop.
kill(-child_pid_for_signal, SIGKILL);
}
void KillEverything(pid_t pgrp, bool gracefully, double graceful_kill_delay) {
if (gracefully) {
// TODO(jmmv): If we truly want to offer graceful termination, we should
// probably only send SIGTERM to the process group leader and allow it to
// decide what to do. Terminating its subprocesses out of its control might
// not have the right effect.
kill(-pgrp, SIGTERM);
// Previous versions of this code used to loop testing if the process had
// already died by sending it a 0 signal... but that loop would never
// terminate early because sending a signal to a zombie process succeeds
// (and we cannot collect the child's exit status here).
child_pid_for_signal = pgrp;
InstallSignalHandler(SIGALRM, ForciblyKillEverything);
SetTimeout(graceful_kill_delay);
} else {
kill(-pgrp, SIGKILL);
}
}
void InstallSignalHandler(int signum, void (*handler)(int)) {
struct sigaction 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, but report it in debug mode, just in case.
if (sigaction(signum, &sa, nullptr) < 0) {
PRINT_DEBUG("sigaction(%d, &sa, nullptr) failed", signum);
}
}
void IgnoreSignal(int signum) {
// These signals can't be handled, so we'll just not do anything for these.
if (signum != SIGSTOP && signum != SIGKILL) {
InstallSignalHandler(signum, SIG_IGN);
}
}
void InstallDefaultSignalHandler(int signum) {
// These signals can't be handled, so we'll just not do anything for these.
if (signum != SIGSTOP && signum != SIGKILL) {
InstallSignalHandler(signum, SIG_DFL);
}
}
void ClearSignalMask() {
// Use an empty signal mask for the process.
sigset_t empty_sset;
if (sigemptyset(&empty_sset) < 0) {
DIE("sigemptyset");
}
if (sigprocmask(SIG_SETMASK, &empty_sset, nullptr) < 0) {
DIE("sigprocmask");
}
// Set the default signal handler for all signals.
for (int i = 1; i < NSIG; ++i) {
if (i == SIGKILL || i == SIGSTOP) {
continue;
}
struct sigaction sa = {};
sa.sa_handler = SIG_DFL;
if (sigemptyset(&sa.sa_mask) < 0) {
DIE("sigemptyset");
}
// 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 SetTimeout(double timeout_secs) {
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 = static_cast<time_t>(int_val),
timer.it_value.tv_usec = static_cast<suseconds_t>(fraction_val * 1e6);
if (setitimer(ITIMER_REAL, &timer, nullptr) < 0) {
DIE("setitimer");
}
}
int WaitChild(pid_t pid, bool child_subreaper_enabled) {
int err, status;
if (child_subreaper_enabled) {
// Discard any zombies that we may get when the child subreaper feature is
// enabled.
do {
err = wait(&status);
} while (err != pid || (err == -1 && errno == EINTR));
} else {
do {
err = waitpid(pid, &status, 0);
} while (err == -1 && errno == EINTR);
}
if (err == -1) {
DIE("waitpid");
}
return status;
}
int WaitChildWithRusage(pid_t pid, struct rusage *rusage,
bool child_subreaper_enabled) {
int err, status;
if (child_subreaper_enabled) {
// Discard any zombies that we may get when the child subreaper feature is
// enabled.
do {
err = wait3(&status, 0, rusage);
} while (err != pid || (err == -1 && errno == EINTR));
} else {
do {
err = wait4(pid, &status, 0, rusage);
} while (err == -1 && errno == EINTR);
}
if (err == -1) {
DIE("wait4");
}
return status;
}
static std::unique_ptr<tools::protos::ExecutionStatistics>
CreateExecutionStatisticsProto(struct rusage *rusage) {
std::unique_ptr<tools::protos::ExecutionStatistics> execution_statistics(
new tools::protos::ExecutionStatistics);
tools::protos::ResourceUsage *resource_usage =
execution_statistics->mutable_resource_usage();
resource_usage->set_utime_sec(rusage->ru_utime.tv_sec);
resource_usage->set_utime_usec(rusage->ru_utime.tv_usec);
resource_usage->set_stime_sec(rusage->ru_stime.tv_sec);
resource_usage->set_stime_usec(rusage->ru_stime.tv_usec);
resource_usage->set_maxrss(rusage->ru_maxrss);
resource_usage->set_ixrss(rusage->ru_ixrss);
resource_usage->set_idrss(rusage->ru_idrss);
resource_usage->set_isrss(rusage->ru_isrss);
resource_usage->set_minflt(rusage->ru_minflt);
resource_usage->set_majflt(rusage->ru_majflt);
resource_usage->set_nswap(rusage->ru_nswap);
resource_usage->set_inblock(rusage->ru_inblock);
resource_usage->set_oublock(rusage->ru_oublock);
resource_usage->set_msgsnd(rusage->ru_msgsnd);
resource_usage->set_msgrcv(rusage->ru_msgrcv);
resource_usage->set_nsignals(rusage->ru_nsignals);
resource_usage->set_nvcsw(rusage->ru_nvcsw);
resource_usage->set_nivcsw(rusage->ru_nivcsw);
return execution_statistics;
}
// Write execution statistics (e.g. resource usage) to a file.
void WriteStatsToFile(struct rusage *rusage, const std::string &stats_path) {
const int flags = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND;
int fd_out = open(stats_path.c_str(), flags, 0666);
if (fd_out < 0) {
DIE("open(%s)", stats_path.c_str());
}
std::unique_ptr<tools::protos::ExecutionStatistics> execution_statistics =
CreateExecutionStatisticsProto(rusage);
std::string serialized = execution_statistics->SerializeAsString();
if (serialized.empty()) {
DIE("invalid execution statistics message");
}
const char *remaining = serialized.c_str();
ssize_t remaining_size = serialized.size();
while (remaining_size > 0) {
ssize_t written = write(fd_out, remaining, remaining_size);
if (written < 0 && errno != EINTR && errno != EAGAIN) {
DIE("could not write resource usage to file '%s': %s",
stats_path.c_str(), strerror(errno));
}
remaining_size -= written;
remaining += written;
}
close(fd_out);
}