diff options
author | philwo <philwo@google.com> | 2017-05-23 14:02:57 +0200 |
---|---|---|
committer | Irina Iancu <elenairina@google.com> | 2017-05-23 17:39:43 +0200 |
commit | 7f520a8286c39c5145b6d816cd0be5a6b7b18250 (patch) | |
tree | b04ffffefa26098b28bad0aa7625fea076caee64 /src/main/tools | |
parent | b4ff96e861d8749486caf009c1742fc98e20c9f3 (diff) |
Refactor process-wrapper code so the spawn/wait code is pluggable.
In an upcoming change I'll reintroduce the new platform-specific implementations that can kill and wait for all descendant processes spawned by the wrapped process.
This has no functional changes.
PiperOrigin-RevId: 156849610
Diffstat (limited to 'src/main/tools')
-rw-r--r-- | src/main/tools/BUILD | 3 | ||||
-rw-r--r-- | src/main/tools/process-wrapper-legacy.cc | 100 | ||||
-rw-r--r-- | src/main/tools/process-wrapper-legacy.h | 49 | ||||
-rw-r--r-- | src/main/tools/process-wrapper.cc | 94 | ||||
-rw-r--r-- | src/main/tools/process-wrapper.h | 32 |
5 files changed, 189 insertions, 89 deletions
diff --git a/src/main/tools/BUILD b/src/main/tools/BUILD index 72aff6301f..5ed4ac9a12 100644 --- a/src/main/tools/BUILD +++ b/src/main/tools/BUILD @@ -19,6 +19,9 @@ cc_binary( "//src:windows_msvc": ["process-wrapper-windows.cc"], "//conditions:default": [ "process-wrapper.cc", + "process-wrapper.h", + "process-wrapper-legacy.cc", + "process-wrapper-legacy.h", ], }), linkopts = ["-lm"], diff --git a/src/main/tools/process-wrapper-legacy.cc b/src/main/tools/process-wrapper-legacy.cc new file mode 100644 index 0000000000..2ebc593d0c --- /dev/null +++ b/src/main/tools/process-wrapper-legacy.cc @@ -0,0 +1,100 @@ +// Copyright 2017 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-wrapper-legacy.h" + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <vector> + +#include "src/main/tools/logging.h" +#include "src/main/tools/process-tools.h" +#include "src/main/tools/process-wrapper.h" + +pid_t LegacyProcessWrapper::child_pid = 0; +volatile sig_atomic_t LegacyProcessWrapper::last_signal = 0; + +void LegacyProcessWrapper::RunCommand() { + SpawnChild(); + WaitForChild(); +} + +void LegacyProcessWrapper::SpawnChild() { + child_pid = fork(); + if (child_pid < 0) { + DIE("fork"); + } else if (child_pid == 0) { + // In child. + if (setsid() < 0) { + DIE("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. + if (execvp(opt.args[0], opt.args.data()) < 0) { + DIE("execvp(%s, ...)", opt.args[0]); + } + } +} + +void LegacyProcessWrapper::WaitForChild() { + // Set up a signal handler which kills all subprocesses when the given signal + // is triggered. + InstallSignalHandler(SIGALRM, OnSignal); + InstallSignalHandler(SIGTERM, OnSignal); + InstallSignalHandler(SIGINT, OnSignal); + if (opt.timeout_secs > 0) { + SetTimeout(opt.timeout_secs); + } + + int status = WaitChild(child_pid); + + // The child is done for, but may have grandchildren that we still have to + // kill. + kill(-child_pid, SIGKILL); + + if (last_signal > 0) { + // Don't trust the exit code if we got a timeout or signal. + InstallDefaultSignalHandler(last_signal); + raise(last_signal); + } else if (WIFEXITED(status)) { + exit(WEXITSTATUS(status)); + } else { + int sig = WTERMSIG(status); + InstallDefaultSignalHandler(sig); + raise(sig); + } +} + +// Called when timeout or signal occurs. +void LegacyProcessWrapper::OnSignal(int sig) { + last_signal = sig; + + 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(child_pid, true, opt.kill_delay_secs); + } else { + // Signals should kill the process quickly, as it's typically blocking the + // return of the prompt after a user hits "Ctrl-C". + KillEverything(child_pid, false, opt.kill_delay_secs); + } +} diff --git a/src/main/tools/process-wrapper-legacy.h b/src/main/tools/process-wrapper-legacy.h new file mode 100644 index 0000000000..5cea547175 --- /dev/null +++ b/src/main/tools/process-wrapper-legacy.h @@ -0,0 +1,49 @@ +// Copyright 2017 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. + +#ifndef SRC_MAIN_TOOLS_PROCESS_WRAPPER_LEGACY_H_ +#define SRC_MAIN_TOOLS_PROCESS_WRAPPER_LEGACY_H_ + +#include <signal.h> +#include <vector> + +// The process-wrapper implementation that was used until and including Bazel +// 0.4.5. Waits for the wrapped process to exit and then kills its process +// group. Works on all POSIX operating systems (tested on Linux, macOS, +// FreeBSD). +// +// Caveats: +// - Killing just the process group of the spawned child means that daemons or +// other processes spawned by the child may not be killed if they change their +// process group. +// - Does not wait for grandchildren to exit, thus processes spawned by the +// child that could not be killed will linger around in the background. +// - Has a PID reuse race condition, because the kill() to the process group is +// sent after waitpid() was called on the main child. +class LegacyProcessWrapper { + public: + // Run the command specified in the `opt.args` array and kill it after + // `opt.timeout_secs` seconds. + static void RunCommand(); + + private: + static void SpawnChild(); + static void WaitForChild(); + static void OnSignal(int sig); + + static pid_t child_pid; + static volatile sig_atomic_t last_signal; +}; + +#endif diff --git a/src/main/tools/process-wrapper.cc b/src/main/tools/process-wrapper.cc index c60de6e4e3..09c1a88038 100644 --- a/src/main/tools/process-wrapper.cc +++ b/src/main/tools/process-wrapper.cc @@ -22,6 +22,8 @@ // die with raise(SIGTERM) even if the child process handles SIGTERM with // exit(0). +#include "src/main/tools/process-wrapper.h" + #include <err.h> #include <errno.h> #include <signal.h> @@ -38,21 +40,9 @@ #include "src/main/tools/logging.h" #include "src/main/tools/process-tools.h" +#include "src/main/tools/process-wrapper-legacy.h" -static double global_kill_delay; -static pid_t global_child_pid; -static volatile sig_atomic_t global_signal; - -// Options parsing result. -struct Options { - double timeout_secs; - double kill_delay_secs; - std::string stdout_path; - std::string stderr_path; - std::vector<char *> args; -}; - -static struct Options opt; +struct Options opt; // Print out a usage error and exit with EXIT_FAILURE. static void Usage(char *program_name) { @@ -86,83 +76,9 @@ static void ParseCommandLine(std::vector<char *> args) { opt.args.push_back(nullptr); } -// 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(const std::vector<char *> &args, double timeout_secs) { - global_child_pid = fork(); - if (global_child_pid < 0) { - DIE("fork"); - } else if (global_child_pid == 0) { - // In child. - if (setsid() < 0) { - DIE("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. - if (execvp(args[0], args.data()) < 0) { - DIE("execvp(%s, ...)", args[0]); - } - } else { - // In parent. - - // Set up a signal handler which kills all subprocesses when the given - // signal is triggered. - InstallSignalHandler(SIGALRM, OnSignal); - InstallSignalHandler(SIGTERM, OnSignal); - InstallSignalHandler(SIGINT, OnSignal); - if (timeout_secs > 0) { - SetTimeout(timeout_secs); - } - - int status = WaitChild(global_child_pid); - - // 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. - InstallDefaultSignalHandler(global_signal); - raise(global_signal); - } else if (WIFEXITED(status)) { - exit(WEXITSTATUS(status)); - } else { - int sig = WTERMSIG(status); - InstallDefaultSignalHandler(sig); - raise(sig); - } - } -} - int main(int argc, char *argv[]) { std::vector<char *> args(argv, argv + argc); ParseCommandLine(args); - global_kill_delay = opt.kill_delay_secs; SwitchToEuid(); SwitchToEgid(); @@ -170,7 +86,7 @@ int main(int argc, char *argv[]) { Redirect(opt.stdout_path, STDOUT_FILENO); Redirect(opt.stderr_path, STDERR_FILENO); - SpawnCommand(opt.args, opt.timeout_secs); + LegacyProcessWrapper::RunCommand(); return 0; } diff --git a/src/main/tools/process-wrapper.h b/src/main/tools/process-wrapper.h new file mode 100644 index 0000000000..061eeace0e --- /dev/null +++ b/src/main/tools/process-wrapper.h @@ -0,0 +1,32 @@ +// Copyright 2017 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. + +#ifndef SRC_MAIN_TOOLS_PROCESS_WRAPPER_H_ +#define SRC_MAIN_TOOLS_PROCESS_WRAPPER_H_ + +#include <string> +#include <vector> + +// Options parsing result. +struct Options { + double timeout_secs; + double kill_delay_secs; + std::string stdout_path; + std::string stderr_path; + std::vector<char *> args; +}; + +extern struct Options opt; + +#endif |