|  | // 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). | 
|  |  | 
|  | #define _GNU_SOURCE | 
|  |  | 
|  | #include <err.h> | 
|  | #include <errno.h> | 
|  | #include <signal.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/wait.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include "process-tools.h" | 
|  |  | 
|  | // Not in headers on OSX. | 
|  | extern char **environ; | 
|  |  | 
|  | static double global_kill_delay; | 
|  | static int global_child_pid; | 
|  | static volatile sig_atomic_t global_signal; | 
|  |  | 
|  | // Options parsing result. | 
|  | struct Options { | 
|  | double timeout_secs; | 
|  | double kill_delay_secs; | 
|  | const char *stdout_path; | 
|  | const char *stderr_path; | 
|  | char *const *args; | 
|  | }; | 
|  |  | 
|  | // Print out a usage error. argc and argv are the argument counter and vector, | 
|  | // fmt is a format, | 
|  | // string for the error message to print. | 
|  | static void Usage(char *const *argv) { | 
|  | fprintf(stderr, | 
|  | "Usage: %s <timeout-secs> <kill-delay-secs> <stdout-redirect> " | 
|  | "<stderr-redirect> <command> [args] ...\n", | 
|  | argv[0]); | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  |  | 
|  | // Parse the command line flags and return the result in an Options structure | 
|  | // passed as argument. | 
|  | static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) { | 
|  | if (argc <= 5) { | 
|  | Usage(argv); | 
|  | } | 
|  |  | 
|  | argv++; | 
|  | if (sscanf(*argv++, "%lf", &opt->timeout_secs) != 1) { | 
|  | DIE("timeout_secs is not a real number.\n"); | 
|  | } | 
|  | if (sscanf(*argv++, "%lf", &opt->kill_delay_secs) != 1) { | 
|  | DIE("kill_delay_secs is not a real number.\n"); | 
|  | } | 
|  | opt->stdout_path = *argv++; | 
|  | opt->stderr_path = *argv++; | 
|  | opt->args = argv; | 
|  | } | 
|  |  | 
|  | // Called when timeout or signal occurs. | 
|  | void OnSignal(int sig) { | 
|  | global_signal = sig; | 
|  |  | 
|  | // Nothing to do if we received a signal before spawning the child. | 
|  | if (global_child_pid == -1) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (sig == SIGALRM) { | 
|  | // SIGALRM represents a timeout, so we should give the process a bit of | 
|  | // time to die gracefully if it needs it. | 
|  | KillEverything(global_child_pid, true, global_kill_delay); | 
|  | } else { | 
|  | // Signals should kill the process quickly, as it's typically blocking | 
|  | // the return of the prompt after a user hits "Ctrl-C". | 
|  | KillEverything(global_child_pid, false, global_kill_delay); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Run the command specified by the argv array and kill it after timeout | 
|  | // seconds. | 
|  | static void SpawnCommand(char *const *argv, double timeout_secs) { | 
|  | CHECK_CALL(global_child_pid = fork()); | 
|  | if (global_child_pid == 0) { | 
|  | // In child. | 
|  | CHECK_CALL(setsid()); | 
|  | ClearSignalMask(); | 
|  |  | 
|  | // Force umask to include read and execute for everyone, to make | 
|  | // output permissions predictable. | 
|  | umask(022); | 
|  |  | 
|  | // Does not return unless something went wrong. | 
|  | execvp(argv[0], argv); | 
|  | err(EXIT_FAILURE, "execvp(\"%s\", ...)", argv[0]); | 
|  | } else { | 
|  | // In parent. | 
|  |  | 
|  | // Set up a signal handler which kills all subprocesses when the given | 
|  | // signal is triggered. | 
|  | HandleSignal(SIGALRM, OnSignal); | 
|  | HandleSignal(SIGTERM, OnSignal); | 
|  | HandleSignal(SIGINT, OnSignal); | 
|  | SetTimeout(timeout_secs); | 
|  |  | 
|  | int status = WaitChild(global_child_pid, argv[0]); | 
|  |  | 
|  | // The child is done for, but may have grandchildren that we still have to | 
|  | // kill. | 
|  | kill(-global_child_pid, SIGKILL); | 
|  |  | 
|  | if (global_signal > 0) { | 
|  | // Don't trust the exit code if we got a timeout or signal. | 
|  | UnHandle(global_signal); | 
|  | raise(global_signal); | 
|  | } else if (WIFEXITED(status)) { | 
|  | exit(WEXITSTATUS(status)); | 
|  | } else { | 
|  | int sig = WTERMSIG(status); | 
|  | UnHandle(sig); | 
|  | raise(sig); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int main(int argc, char *argv[]) { | 
|  | struct Options opt; | 
|  | memset(&opt, 0, sizeof(opt)); | 
|  |  | 
|  | ParseCommandLine(argc, argv, &opt); | 
|  | global_kill_delay = opt.kill_delay_secs; | 
|  |  | 
|  | SwitchToEuid(); | 
|  | SwitchToEgid(); | 
|  |  | 
|  | RedirectStdout(opt.stdout_path); | 
|  | RedirectStderr(opt.stderr_path); | 
|  |  | 
|  | SpawnCommand(opt.args, opt.timeout_secs); | 
|  |  | 
|  | return 0; | 
|  | } |