diff options
Diffstat (limited to 'src/main/tools/process-wrapper.cc')
-rw-r--r-- | src/main/tools/process-wrapper.cc | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/main/tools/process-wrapper.cc b/src/main/tools/process-wrapper.cc new file mode 100644 index 0000000000..9d02eae79f --- /dev/null +++ b/src/main/tools/process-wrapper.cc @@ -0,0 +1,186 @@ +// Copyright 2014 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. + +// process-wrapper runs a subprocess with a given timeout (optional), +// redirecting stdout and stderr to given files. Upon exit, whether +// from normal termination or timeout, the subprocess (and any of its children) +// is killed. +// +// The exit status of this program is whatever the child process returned, +// unless process-wrapper receives a signal. ie, on SIGTERM this program will +// die with raise(SIGTERM) even if the child process handles SIGTERM with +// exit(0). + +#include "src/main/tools/process-tools.h" + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> +#include <vector> + +using std::vector; + +// Not in headers on OSX. +extern char **environ; + +// The pid of the spawned child process. +static volatile sig_atomic_t global_child_pid; + +// The signal that will be sent to the child when a timeout occurs. +static volatile sig_atomic_t global_next_timeout_signal = SIGTERM; + +// Whether the child was killed due to a timeout. +static volatile sig_atomic_t global_timeout_occurred; + +// Options parsing result. +struct Options { + double timeout_secs; + double kill_delay_secs; + std::string stdout_path; + std::string stderr_path; + bool debug; + vector<char *> args; +}; + +static struct Options opt; + +// Print out a usage error and exit with EXIT_FAILURE. +static void Usage(char *program_name) { + fprintf(stderr, + "Usage: %s <timeout-secs> <kill-delay-secs> <stdout-redirect> " + "<stderr-redirect> <command> [args] ...\n", + program_name); + exit(EXIT_FAILURE); +} + +// Parse the command line flags and put the results in the global opt variable. +static void ParseCommandLine(vector<char *> args) { + if (args.size() <= 5) { + Usage(args.front()); + } + + int optind = 1; + + if (sscanf(args[optind++], "%lf", &opt.timeout_secs) != 1) { + DIE("timeout_secs is not a real number.\n"); + } + if (sscanf(args[optind++], "%lf", &opt.kill_delay_secs) != 1) { + DIE("kill_delay_secs is not a real number.\n"); + } + opt.stdout_path.assign(args[optind++]); + opt.stderr_path.assign(args[optind++]); + opt.args.assign(args.begin() + optind, args.end()); + + // argv[] passed to execve() must be a null-terminated array. + opt.args.push_back(nullptr); +} + +static void OnTimeout(int signum) { + global_timeout_occurred = true; + kill(-global_child_pid, global_next_timeout_signal); + if (global_next_timeout_signal == SIGTERM && opt.kill_delay_secs > 0) { + global_next_timeout_signal = SIGKILL; + SetTimeout(opt.kill_delay_secs); + } +} + +static void ForwardSignal(int signum) { + if (global_child_pid > 0) { + kill(-global_child_pid, signum); + } +} + +static void SetupSignalHandlers() { + RestoreSignalHandlersAndMask(); + + for (int signum = 1; signum < NSIG; signum++) { + switch (signum) { + // Some signals should indeed kill us and not be forwarded to the child, + // thus we can use the default handler. + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + case SIGSYS: + case SIGTRAP: + break; + // It's fine to use the default handler for SIGCHLD, because we use wait() + // in the main loop to wait for children to die anyway. + case SIGCHLD: + break; + // One does not simply install a signal handler for these two signals + case SIGKILL: + case SIGSTOP: + break; + // Ignore SIGTTIN and SIGTTOU, as we hand off the terminal to the child in + // SpawnChild(). + case SIGTTIN: + case SIGTTOU: + IgnoreSignal(signum); + break; + // We need a special signal handler for this if we use a timeout. + case SIGALRM: + if (opt.timeout_secs > 0) { + InstallSignalHandler(signum, OnTimeout); + } else { + InstallSignalHandler(signum, ForwardSignal); + } + break; + // All other signals should be forwarded to the child. + default: + InstallSignalHandler(signum, ForwardSignal); + break; + } + } +} + +int main(int argc, char *argv[]) { + KillMeWhenMyParentDies(SIGTERM); + DropPrivileges(); + + vector<char *> args(argv, argv + argc); + ParseCommandLine(args); + + Redirect(opt.stdout_path, STDOUT_FILENO); + Redirect(opt.stderr_path, STDERR_FILENO); + + SetupSignalHandlers(); + BecomeSubreaper(); + global_child_pid = SpawnCommand(opt.args); + + if (opt.timeout_secs > 0) { + SetTimeout(opt.timeout_secs); + } + + int exitcode = WaitForChild(global_child_pid); + if (global_timeout_occurred) { + return 128 + SIGALRM; + } + + return exitcode; +} |