| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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 KUDU_UTIL_SUBPROCESS_H |
| #define KUDU_UTIL_SUBPROCESS_H |
| |
| #include <signal.h> |
| #include <unistd.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <gtest/gtest_prod.h> |
| |
| #include "kudu/gutil/macros.h" |
| #include "kudu/gutil/port.h" |
| #include "kudu/util/status.h" |
| |
| namespace kudu { |
| |
| // Wrapper around a spawned subprocess. |
| // |
| // program will be treated as an absolute path unless it begins with a dot or a |
| // slash. |
| // |
| // This takes care of creating pipes to/from the subprocess and offers |
| // basic functionality to wait on it or send signals. |
| // By default, child process only has stdin captured and separate from the parent. |
| // The stdout/stderr streams are shared with the parent by default. |
| // |
| // The process may only be started and waited on/killed once. |
| // |
| // Optionally, user may change parent/child stream sharing. Also, a user may disable |
| // a subprocess stream. A user cannot do both. |
| // |
| // Note that, when the Subprocess object is destructed, the child process |
| // will be forcibly SIGKILLed to avoid orphaning processes. |
| class Subprocess { |
| public: |
| // Constructs a new Subprocess that will execute 'argv' on Start(). |
| // |
| // If the process isn't explicitly killed, 'sig_on_destroy' will be delivered |
| // to it when the Subprocess goes out of scope. |
| explicit Subprocess(std::vector<std::string> argv, int sig_on_destruct = SIGKILL); |
| ~Subprocess(); |
| |
| // Disables subprocess stream output. Is mutually exclusive with stream sharing. |
| // |
| // Must be called before subprocess starts. |
| void DisableStderr(); |
| void DisableStdout(); |
| |
| // Configures the subprocess to share the parent's stream. Is mutually |
| // exclusive with stream disabling. |
| // |
| // Must be called before subprocess starts. |
| void ShareParentStdin(bool share = true) { SetFdShared(STDIN_FILENO, share); } |
| void ShareParentStdout(bool share = true) { SetFdShared(STDOUT_FILENO, share); } |
| void ShareParentStderr(bool share = true) { SetFdShared(STDERR_FILENO, share); } |
| |
| // Add environment variables to be set before executing the subprocess. |
| // |
| // These environment variables are merged into the existing environment |
| // of the parent process. In other words, there is no need to prime this |
| // map with the current environment; instead, just specify any variables |
| // that should be overridden. |
| // |
| // Repeated calls to this function replace earlier calls. |
| void SetEnvVars(std::map<std::string, std::string> env); |
| |
| // Set the initial current working directory of the subprocess. |
| // |
| // Must be set before starting the subprocess. |
| void SetCurrentDir(std::string cwd); |
| |
| // Start the subprocess. Can only be called once. |
| // |
| // This returns a bad Status if the fork() fails. However, |
| // note that if the executable path was incorrect such that |
| // exec() fails, this will still return Status::OK. You must |
| // use Wait() to check for failure. |
| Status Start() WARN_UNUSED_RESULT; |
| |
| // Wait for the subprocess to exit. The return value is the same as |
| // that of the waitpid() syscall. Only call after starting. |
| // |
| // NOTE: unlike the standard wait(2) call, this may be called multiple |
| // times. If the process has exited, it will repeatedly return the same |
| // exit code. |
| Status Wait(int* wait_status = nullptr) WARN_UNUSED_RESULT; |
| |
| // Like the above, but does not block. This returns Status::TimedOut |
| // immediately if the child has not exited. Otherwise returns Status::OK |
| // and sets *ret. Only call after starting. |
| // |
| // NOTE: unlike the standard wait(2) call, this may be called multiple |
| // times. If the process has exited, it will repeatedly return the same |
| // exit code. |
| Status WaitNoBlock(int* wait_status = nullptr) WARN_UNUSED_RESULT; |
| |
| // Like Wait, but it also checks the exit code is 0. If it's not, or if it's |
| // not a clean exit, it returns RemoteError. |
| Status WaitAndCheckExitCode() WARN_UNUSED_RESULT; |
| |
| // Send a signal to the subprocess. |
| // Note that this does not reap the process -- you must still Wait() |
| // in order to reap it. Only call after starting. |
| Status Kill(int signal) WARN_UNUSED_RESULT; |
| |
| // Sends a signal to the subprocess and waits for it to exit. |
| // |
| // If the signal is not SIGKILL and the process doesn't appear to be exiting, |
| // retries with SIGKILL. |
| Status KillAndWait(int signal); |
| |
| // Retrieve exit status of the process awaited by Wait() and/or WaitNoBlock() |
| // methods. Must be called only after calling Wait()/WaitNoBlock(). |
| Status GetExitStatus(int* exit_status, std::string* info_str = nullptr) const |
| WARN_UNUSED_RESULT; |
| |
| // Helper method that creates a Subprocess, issues a Start() then a Wait(). |
| // Expects a blank-separated list of arguments, with the first being the |
| // full path to the executable. |
| // The returned Status will only be OK if all steps were successful and |
| // the return code was 0. |
| static Status Call(const std::string& arg_str) WARN_UNUSED_RESULT; |
| |
| // Same as above, but accepts a vector that includes the path to the |
| // executable as argv[0] and the arguments to the program in argv[1..n]. |
| // |
| // Writes the value of 'stdin_in' to the subprocess' stdin. The length of |
| // 'stdin_in' should be limited to 64kib. |
| // |
| // Also collects the output from the child process stdout and stderr into |
| // 'stdout_out' and 'stderr_out' respectively. |
| // |
| // Optionally allows a passed map of environment variables to be set |
| // on the subprocess via `env_vars`. |
| static Status Call(const std::vector<std::string>& argv, |
| const std::string& stdin_in = "", |
| std::string* stdout_out = nullptr, |
| std::string* stderr_out = nullptr, |
| std::map<std::string, std::string> env_vars = {}) |
| WARN_UNUSED_RESULT; |
| |
| // Return the pipe fd to the child's standard stream. |
| // Stream should not be disabled or shared. |
| int to_child_stdin_fd() const { return CheckAndOffer(STDIN_FILENO); } |
| int from_child_stdout_fd() const { return CheckAndOffer(STDOUT_FILENO); } |
| int from_child_stderr_fd() const { return CheckAndOffer(STDERR_FILENO); } |
| |
| // Release control of the file descriptor for the child's stream, only if piped. |
| // Writes to this FD show up on stdin in the subprocess |
| int ReleaseChildStdinFd() { return ReleaseChildFd(STDIN_FILENO ); } |
| // Reads from this FD come from stdout of the subprocess |
| int ReleaseChildStdoutFd() { return ReleaseChildFd(STDOUT_FILENO); } |
| // Reads from this FD come from stderr of the subprocess |
| int ReleaseChildStderrFd() { return ReleaseChildFd(STDERR_FILENO); } |
| |
| pid_t pid() const; |
| const std::string& argv0() const { return argv_[0]; } |
| |
| bool IsStarted(); |
| |
| private: |
| FRIEND_TEST(SubprocessTest, TestGetProcfsState); |
| |
| enum State { |
| kNotStarted, |
| kRunning, |
| kExited |
| }; |
| enum StreamMode {SHARED, DISABLED, PIPED}; |
| enum WaitMode {BLOCKING, NON_BLOCKING}; |
| |
| // Process state according to /proc/<pid>/stat. |
| enum class ProcfsState { |
| // "T Stopped (on a signal) or (before Linux 2.6.33) trace stopped" |
| PAUSED, |
| |
| // Every other process state. |
| RUNNING, |
| }; |
| |
| // Extracts the process state for /proc/<pid>/stat. |
| // |
| // Returns an error if /proc/</pid>/stat doesn't exist or if parsing failed. |
| static Status GetProcfsState(int pid, ProcfsState* state); |
| |
| Status DoWait(int* wait_status, WaitMode mode) WARN_UNUSED_RESULT; |
| void SetFdShared(int stdfd, bool share); |
| int CheckAndOffer(int stdfd) const; |
| int ReleaseChildFd(int stdfd); |
| |
| std::string program_; |
| std::vector<std::string> argv_; |
| std::map<std::string, std::string> env_; |
| State state_; |
| int child_pid_; |
| enum StreamMode fd_state_[3]; |
| int child_fds_[3]; |
| std::string cwd_; |
| |
| // The cached wait status if Wait()/WaitNoBlock() has been called. |
| // Only valid if state_ == kExited. |
| int wait_status_; |
| |
| // Custom signal to deliver when the subprocess goes out of scope, provided |
| // the process hasn't already been killed. |
| int sig_on_destruct_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Subprocess); |
| }; |
| |
| } // namespace kudu |
| #endif /* KUDU_UTIL_SUBPROCESS_H */ |