blob: 2733152fd2756e336d8c821db6303ca150f2daa3 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_
#define INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_
#include "perfetto/base/build_config.h"
// This is a #if as opposite to a GN condition, because GN conditions aren't propagated when
// translating to Bazel or other build systems, as they get resolved at translation time. Without
// this, the Bazel build breaks on Windows.
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
#define PERFETTO_HAS_SUBPROCESS() 1
#else
#define PERFETTO_HAS_SUBPROCESS() 0
#endif
#include <functional>
#include <initializer_list>
#include <string>
#include <thread>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/proc_utils.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/scoped_file.h"
namespace perfetto {
namespace base {
// Handles creation and lifecycle management of subprocesses, taking care of
// all subtleties involved in handling processes on UNIX.
// This class allows to deal with macro two use-cases:
// 1) fork() + exec() equivalent: for spawning a brand new process image.
// This happens when |args.exec_cmd| is not empty.
// This is safe to use even in a multi-threaded environment.
// 2) fork(): for spawning a process and running a function.
// This happens when |args.entrypoint_for_testing| is not empty.
// This is intended only for tests as it is extremely subtle.
// This mode must be used with extreme care. Before the entrypoint is
// invoked all file descriptors other than stdin/out/err and the ones
// specified in |args.preserve_fds| will be closed, to avoid each process
// retaining a dupe of other subprocesses pipes. This however means that
// any non trivial calls (including logging) must be avoided as they might
// refer to FDs that are now closed. The entrypoint should really be used
// just to signal a pipe or similar for synchronizing sequencing in tests.
//
// This class allows to control stdin/out/err pipe redirection and takes care
// of keeping all the pipes pumped (stdin) / drained (stdout/err), in a similar
// fashion of python's subprocess.Communicate()
// stdin: is always piped and closed once the |args.input| buffer is written.
// stdout/err can be either:
// - dup()ed onto the parent process stdout/err.
// - redirected onto /dev/null.
// - piped onto a buffer (see output() method). There is only one output
// buffer in total. If both stdout and stderr are set to kBuffer mode, they
// will be merged onto the same. There doesn't seem any use case where they
// are needed distinctly.
//
// Some caveats worth mentioning:
// - It always waitpid()s, to avoid leaving zombies around. If the process is
// not terminated by the time the destructor is reached, the dtor will
// send a SIGKILL and wait for the termination.
// - After fork()-ing it will close all file descriptors, preserving only
// stdin/out/err and the fds listed in |args.preserve_fds|.
// - On Linux/Android, the child process will be SIGKILL-ed if the calling
// thread exists, even if the Subprocess is std::move()-d onto another thread.
// This happens by virtue PR_SET_PDEATHSIG, which is used to avoid that
// child processes are leaked in the case of a crash of the parent (frequent
// in tests). However, the child process might still be leaked if execing
// a setuid/setgid binary (see man 2 prctl).
//
// Usage:
// base::Subprocess p({"/bin/cat", "-"});
// (or equivalently:
// base::Subprocess p;
// p.args.exec_cmd.push_back("/bin/cat");
// p.args.exec_cmd.push_back("-");
// )
// p.args.stdout_mode = base::Subprocess::kBuffer;
// p.args.stderr_mode = base::Subprocess::kInherit;
// p.args.input = "stdin contents";
// p.Call();
// (or equivalently:
// p.Start();
// p.Wait();
// )
// EXPECT_EQ(p.status(), base::Subprocess::kExited);
// EXPECT_EQ(p.returncode(), 0);
class Subprocess {
public:
enum Status {
kNotStarted = 0, // Before calling Start() or Call().
kRunning, // After calling Start(), before Wait().
kExited, // The subprocess exited (either succesully or not).
kKilledBySignal, // The subprocess has been killed by a signal.
};
enum OutputMode {
kInherit = 0, // Inherit's the caller process stdout/stderr.
kDevNull, // dup() onto /dev/null
kBuffer // dup() onto a pipe and move it into the output() buffer.
};
// Input arguments for configuring the subprocess behavior.
struct Args {
Args(std::initializer_list<std::string> _cmd = {}) : exec_cmd(_cmd) {}
Args(Args&&) noexcept;
Args& operator=(Args&&);
// If non-empty this will cause an exec() when Start()/Call() are called.
std::vector<std::string> exec_cmd;
// If non-empty, it changes the argv[0] argument passed to exec. If
// unset, argv[0] == exec_cmd[0]. This is to handle cases like:
// exec_cmd = {"/proc/self/exec"}, argv0: "my_custom_test_override".
std::string argv0_override;
// If non-empty this will be invoked on the fork()-ed child process, after
// stdin/out/err has been redirected and all other file descriptor are
// closed.
// It is valid to specify both |exec_cmd| AND |entrypoint_for_testing|.
// In this case |entrypoint_for_testing| will be invoked just before the
// exec() call, but after having closed all fds % stdin/out/err.
// This is for synchronization barriers in tests.
std::function<void()> entrypoint_for_testing;
// If non-empty, replaces the environment passed to exec().
std::vector<std::string> env;
// The file descriptors in this list will not be closed.
std::vector<int> preserve_fds;
// The data to push in the child process stdin.
std::string input;
OutputMode stdout_mode = kInherit;
OutputMode stderr_mode = kInherit;
// Returns " ".join(exec_cmd), quoting arguments.
std::string GetCmdString() const;
};
explicit Subprocess(std::initializer_list<std::string> exec_cmd = {});
Subprocess(Subprocess&&) noexcept;
Subprocess& operator=(Subprocess&&);
~Subprocess(); // It will KillAndWaitForTermination() if still alive.
// Starts the subprocess but doesn't wait for its termination. The caller
// is expected to either call Wait() or Poll() after this call.
void Start();
// Wait for process termination. Can be called more than once.
// Args:
// |timeout_ms| = 0: wait indefinitely.
// |timeout_ms| > 0: wait for at most |timeout_ms|.
// Returns:
// True: The process terminated. See status() and returncode().
// False: Timeout reached, the process is still running. In this case the
// process will be left in the kRunning state.
bool Wait(int timeout_ms = 0);
// Equivalent of Start() + Wait();
// Returns true if the process exited cleanly with return code 0. False in
// any othe case.
bool Call(int timeout_ms = 0);
Status Poll();
// Sends a SIGKILL and wait to see the process termination.
void KillAndWaitForTermination();
PlatformProcessId pid() const { return pid_; }
Status status() const { return status_; }
int returncode() const { return returncode_; }
// This contains both stdout and stderr (if the corresponding _mode ==
// kBuffer). It's non-const so the caller can std::move() it.
std::string& output() { return output_; }
Args args;
private:
Subprocess(const Subprocess&) = delete;
Subprocess& operator=(const Subprocess&) = delete;
void TryPushStdin();
void TryReadStdoutAndErr();
void TryReadExitStatus();
void KillAtMostOnce();
bool PollInternal(int poll_timeout_ms);
base::Pipe stdin_pipe_;
base::Pipe stdouterr_pipe_;
base::Pipe exit_status_pipe_;
PlatformProcessId pid_;
size_t input_written_ = 0;
Status status_ = kNotStarted;
int returncode_ = -1;
std::string output_; // Stdin+stderr. Only when kBuffer.
std::thread waitpid_thread_;
};
} // namespace base
} // namespace perfetto
#endif // INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_