base: introduce Subprocess

Introduce a class to handle fork() and
fork() + exec. See comment in the header for
usage and caveats.

Bug: 141917389
Change-Id: I3deb0245dac378eba4cd007d4ebd303f431ae45b
diff --git a/Android.bp b/Android.bp
index 7c7278f..af169ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5526,6 +5526,7 @@
     "src/base/string_splitter.cc",
     "src/base/string_utils.cc",
     "src/base/string_view.cc",
+    "src/base/subprocess.cc",
     "src/base/temp_file.cc",
     "src/base/thread_checker.cc",
     "src/base/thread_task_runner.cc",
@@ -5563,6 +5564,7 @@
     "src/base/string_utils_unittest.cc",
     "src/base/string_view_unittest.cc",
     "src/base/string_writer_unittest.cc",
+    "src/base/subprocess_unittest.cc",
     "src/base/task_runner_unittest.cc",
     "src/base/temp_file_unittest.cc",
     "src/base/thread_checker_unittest.cc",
diff --git a/BUILD b/BUILD
index ff8586a..0108eac 100644
--- a/BUILD
+++ b/BUILD
@@ -288,6 +288,7 @@
         "include/perfetto/ext/base/string_utils.h",
         "include/perfetto/ext/base/string_view.h",
         "include/perfetto/ext/base/string_writer.h",
+        "include/perfetto/ext/base/subprocess.h",
         "include/perfetto/ext/base/temp_file.h",
         "include/perfetto/ext/base/thread_annotations.h",
         "include/perfetto/ext/base/thread_checker.h",
@@ -534,6 +535,7 @@
         "src/base/string_splitter.cc",
         "src/base/string_utils.cc",
         "src/base/string_view.cc",
+        "src/base/subprocess.cc",
         "src/base/temp_file.cc",
         "src/base/thread_checker.cc",
         "src/base/thread_task_runner.cc",
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 052d277..6a3bead 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -34,6 +34,7 @@
     "string_utils.h",
     "string_view.h",
     "string_writer.h",
+    "subprocess.h",
     "temp_file.h",
     "thread_annotations.h",
     "thread_checker.h",
diff --git a/include/perfetto/ext/base/subprocess.h b/include/perfetto/ext/base/subprocess.h
new file mode 100644
index 0000000..be813b3
--- /dev/null
+++ b/include/perfetto/ext/base/subprocess.h
@@ -0,0 +1,207 @@
+/*
+ * 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 <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) {}
+    // 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_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index b1e4062..0ade077 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -46,6 +46,7 @@
     sources += [
       "event_fd.cc",
       "pipe.cc",
+      "subprocess.cc",
       "temp_file.cc",
       "thread_task_runner.cc",
       "unix_task_runner.cc",
@@ -135,6 +136,7 @@
   if (!is_win) {
     sources += [
       "metatrace_unittest.cc",
+      "subprocess_unittest.cc",
       "task_runner_unittest.cc",
       "temp_file_unittest.cc",
       "thread_checker_unittest.cc",
diff --git a/src/base/subprocess.cc b/src/base/subprocess.cc
new file mode 100644
index 0000000..45471fd
--- /dev/null
+++ b/src/base/subprocess.cc
@@ -0,0 +1,429 @@
+/*
+ * 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.
+ */
+
+#include "perfetto/ext/base/subprocess.h"
+
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/time.h"
+#include "perfetto/ext/base/utils.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/prctl.h>
+#endif
+
+// In MacOS this is not defined in any header.
+extern "C" char** environ;
+
+namespace perfetto {
+namespace base {
+
+namespace {
+
+struct ChildProcessArgs {
+  Subprocess::Args* create_args;
+  const char* exec_cmd = nullptr;
+  std::vector<char*> argv;
+  std::vector<char*> env;
+  int stdin_pipe_rd = -1;
+  int stdouterr_pipe_wr = -1;
+};
+
+// Don't add any dynamic allocation in this function. This will be invoked
+// under a fork(), potentially in a state where the allocator lock is held.
+void __attribute__((noreturn)) ChildProcess(ChildProcessArgs* args) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  // In no case we want a child process to outlive its parent process. This is
+  // relevant for tests, so that a test failure/crash doesn't leave child
+  // processes around that get reparented to init.
+  prctl(PR_SET_PDEATHSIG, SIGKILL);
+#endif
+
+  auto die = [args](const char* err) __attribute__((noreturn)) {
+    base::ignore_result(write(args->stdouterr_pipe_wr, err, strlen(err)));
+    base::ignore_result(write(args->stdouterr_pipe_wr, "\n", 1));
+    // From https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
+    // "In particular, the value 128 is used to indicate failure to execute
+    // another program in a subprocess. This convention is not universally
+    // obeyed, but it is a good idea to follow it in your programs."
+    _exit(128);
+  };
+
+  auto set_fd_close_on_exec = [&die](int fd, bool close_on_exec) {
+    int flags = fcntl(fd, F_GETFD, 0);
+    if (flags < 0)
+      die("fcntl(F_GETFD) failed");
+    flags = close_on_exec ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC);
+    if (fcntl(fd, F_SETFD, flags) < 0)
+      die("fcntl(F_SETFD) failed");
+  };
+
+  if (getppid() == 1)
+    die("terminating because parent process died");
+
+  if (dup2(args->stdin_pipe_rd, STDIN_FILENO) == -1)
+    die("Failed to dup2(STDIN)");
+  close(args->stdin_pipe_rd);
+
+  switch (args->create_args->stdout_mode) {
+    case Subprocess::kInherit:
+      break;
+    case Subprocess::kDevNull: {
+      if (dup2(open("/dev/null", O_RDWR), STDOUT_FILENO) == -1)
+        die("Failed to dup2(STDOUT)");
+      break;
+    }
+    case Subprocess::kBuffer:
+      if (dup2(args->stdouterr_pipe_wr, STDOUT_FILENO) == -1)
+        die("Failed to dup2(STDOUT)");
+      break;
+  }
+
+  switch (args->create_args->stderr_mode) {
+    case Subprocess::kInherit:
+      break;
+    case Subprocess::kDevNull: {
+      if (dup2(open("/dev/null", O_RDWR), STDERR_FILENO) == -1)
+        die("Failed to dup2(STDERR)");
+      break;
+    }
+    case Subprocess::kBuffer:
+      if (dup2(args->stdouterr_pipe_wr, STDERR_FILENO) == -1)
+        die("Failed to dup2(STDERR)");
+      break;
+  }
+
+  // Close all FDs % stdin/out/err and the ones that the client explicitly
+  // asked to retain. The reason for this is twofold:
+  // 1. For exec-only (i.e. entrypoint == empty) cases: it avoids leaking FDs
+  //    that didn't get marked as O_CLOEXEC by accident.
+  // 2. In fork() mode (entrypoint not empty) avoids retaining a dup of eventfds
+  //    that would prevent the parent process to receive EOFs (tests usually use
+  //    pipes as a synchronization mechanism between subprocesses).
+  const auto& preserve_fds = args->create_args->preserve_fds;
+  for (int i = 0; i < 512; i++) {
+    if (i != STDIN_FILENO && i != STDERR_FILENO && i != STDOUT_FILENO &&
+        i != args->stdouterr_pipe_wr &&
+        !std::count(preserve_fds.begin(), preserve_fds.end(), i)) {
+      close(i);
+    }
+  }
+
+  // Clears O_CLOEXEC from stdin/out/err. These are the only FDs that we want
+  // to be preserved after the exec().
+  set_fd_close_on_exec(STDIN_FILENO, false);
+  set_fd_close_on_exec(STDOUT_FILENO, false);
+  set_fd_close_on_exec(STDERR_FILENO, false);
+
+  // If the caller specified a std::function entrypoint, run that first.
+  if (args->create_args->entrypoint_for_testing)
+    args->create_args->entrypoint_for_testing();
+
+  // If the caller specified only an entrypoint, without any args, exit now.
+  // Otherwise proceed with the exec() below.
+  if (!args->exec_cmd)
+    _exit(0);
+
+  // If |args[0]| is a path use execv() (which takes a path), othewise use
+  // exevp(), which uses the shell and follows PATH.
+  if (strchr(args->exec_cmd, '/')) {
+    char** env = args->env.empty() ? environ : args->env.data();
+    execve(args->exec_cmd, args->argv.data(), env);
+  } else {
+    // There is no execvpe() on Mac.
+    if (!args->env.empty())
+      die("A full path is required for |exec_cmd| when setting |env|");
+    execvp(args->exec_cmd, args->argv.data());
+  }
+
+  // Reached only if execv fails.
+  die("execve() failed");
+}
+
+}  // namespace
+
+Subprocess::Subprocess(std::initializer_list<std::string> _args)
+    : args(_args) {}
+
+Subprocess::Subprocess(Subprocess&&) noexcept = default;
+Subprocess& Subprocess::operator=(Subprocess&&) = default;
+
+Subprocess::~Subprocess() {
+  if (status_ == kRunning)
+    KillAndWaitForTermination();
+  PERFETTO_CHECK(!waitpid_thread_.joinable());
+}
+
+void Subprocess::Start() {
+  ChildProcessArgs proc_args;
+  proc_args.create_args = &args;
+
+  // Setup argv.
+  if (!args.exec_cmd.empty()) {
+    proc_args.exec_cmd = args.exec_cmd[0].c_str();
+    for (const std::string& arg : args.exec_cmd)
+      proc_args.argv.push_back(const_cast<char*>(arg.c_str()));
+    proc_args.argv.push_back(nullptr);
+
+    if (!args.argv0_override.empty())
+      proc_args.argv[0] = const_cast<char*>(args.argv0_override.c_str());
+  }
+
+  // Setup env.
+  if (!args.env.empty()) {
+    for (const std::string& str : args.env)
+      proc_args.env.push_back(const_cast<char*>(str.c_str()));
+    proc_args.env.push_back(nullptr);
+  }
+
+  // Setup the pipes for stdin/err redirection.
+  stdin_pipe_ = base::Pipe::Create(base::Pipe::kWrNonBlock);
+  proc_args.stdin_pipe_rd = *stdin_pipe_.rd;
+  stdouterr_pipe_ = base::Pipe::Create(base::Pipe::kRdNonBlock);
+  proc_args.stdouterr_pipe_wr = *stdouterr_pipe_.wr;
+
+  // Spawn the child process that will exec().
+  pid_ = fork();
+  PERFETTO_CHECK(pid_ >= 0);
+  if (pid_ == 0) {
+    // Close the parent-ends of the pipes.
+    stdin_pipe_.wr.reset();
+    stdouterr_pipe_.rd.reset();
+    ChildProcess(&proc_args);
+    // ChildProcess() doesn't return, not even in case of failures.
+    PERFETTO_FATAL("not reached");
+  }
+
+  status_ = kRunning;
+
+  // Close the child-end of the pipes.
+  // Deliberately NOT closing the stdin_pipe_.rd. This is to avoid crashing
+  // with a SIGPIPE if the process exits without consuming its stdin, while
+  // the parent tries to write() on the other end of the stdin pipe.
+  stdouterr_pipe_.wr.reset();
+
+  // Spawn a thread that is blocked on waitpid() and writes the termination
+  // status onto a pipe. The problem here is that waipid() doesn't have a
+  // timeout option and can't be passed to poll(). The alternative would be
+  // using a SIGCHLD handler, but anecdotally signal handlers introduce more
+  // problems than what they solve.
+  exit_status_pipe_ = base::Pipe::Create(base::Pipe::kRdNonBlock);
+
+  // Both ends of the pipe are closed after the thread.join().
+  int pid = pid_;
+  int exit_status_pipe_wr = exit_status_pipe_.wr.release();
+  waitpid_thread_ = std::thread([pid, exit_status_pipe_wr] {
+    int pid_stat = -1;
+    int wait_res = PERFETTO_EINTR(waitpid(pid, &pid_stat, 0));
+    PERFETTO_CHECK(wait_res == pid);
+    base::ignore_result(PERFETTO_EINTR(
+        write(exit_status_pipe_wr, &pid_stat, sizeof(pid_stat))));
+    PERFETTO_CHECK(PERFETTO_EINTR(close(exit_status_pipe_wr)) == 0);
+  });
+}
+
+Subprocess::Status Subprocess::Poll() {
+  if (status_ != kRunning)
+    return status_;  // Nothing to poll.
+  while (PollInternal(0 /* don't block*/)) {
+  }
+  return status_;
+}
+
+// |timeout_ms| semantic:
+//   -1: Block indefinitely.
+//    0: Don't block, return immediately.
+//   >0: Block for at most X ms.
+// Returns:
+//  True: Read at least one fd (so there might be more queued).
+//  False: if all fds reached quiescent (no data to read/write).
+bool Subprocess::PollInternal(int poll_timeout_ms) {
+  struct pollfd fds[3]{};
+  size_t num_fds = 0;
+  if (exit_status_pipe_.rd) {
+    fds[num_fds].fd = *exit_status_pipe_.rd;
+    fds[num_fds].events = POLLIN;
+    num_fds++;
+  }
+  if (stdouterr_pipe_.rd) {
+    fds[num_fds].fd = *stdouterr_pipe_.rd;
+    fds[num_fds].events = POLLIN;
+    num_fds++;
+  }
+  if (stdin_pipe_.wr) {
+    fds[num_fds].fd = *stdin_pipe_.wr;
+    fds[num_fds].events = POLLOUT;
+    num_fds++;
+  }
+
+  if (num_fds == 0)
+    return false;
+
+  auto nfds = static_cast<nfds_t>(num_fds);
+  int poll_res = PERFETTO_EINTR(poll(fds, nfds, poll_timeout_ms));
+  PERFETTO_CHECK(poll_res >= 0);
+
+  TryReadStdoutAndErr();
+  TryPushStdin();
+  TryReadExitStatus();
+
+  return poll_res > 0;
+}
+
+bool Subprocess::Wait(int timeout_ms) {
+  PERFETTO_CHECK(status_ != kNotStarted);
+
+  // Break out of the loop only after both conditions are satisfied:
+  // - All stdout/stderr data has been read (if kBuffer).
+  // - The process exited.
+  // Note that the two events can happen arbitrary order. After the process
+  // exits, there might be still data in the pipe buffer, which we want to
+  // read fully.
+  //
+  // Instead, don't wait on the stdin to be fully written. The child process
+  // might exit prematurely (or crash). If that happens, we can end up in a
+  // state where the write(stdin_pipe_.wr) will never unblock.
+
+  const int64_t t_start = base::GetWallTimeMs().count();
+  while (exit_status_pipe_.rd || stdouterr_pipe_.rd) {
+    int poll_timeout_ms = -1;  // Block until a FD is ready.
+    if (timeout_ms > 0) {
+      const int64_t now = GetWallTimeMs().count();
+      poll_timeout_ms = timeout_ms - static_cast<int>(now - t_start);
+      if (poll_timeout_ms <= 0)
+        return false;
+    }
+    PollInternal(poll_timeout_ms);
+  }  // while(...)
+  return true;
+}
+
+bool Subprocess::Call(int timeout_ms) {
+  PERFETTO_CHECK(status_ == kNotStarted);
+  Start();
+
+  if (!Wait(timeout_ms)) {
+    KillAndWaitForTermination();
+    // TryReadExitStatus must have joined the thread.
+    PERFETTO_DCHECK(!waitpid_thread_.joinable());
+  }
+  PERFETTO_DCHECK(status_ != kRunning);
+  return status_ == kExited && returncode_ == 0;
+}
+
+void Subprocess::TryReadExitStatus() {
+  if (!exit_status_pipe_.rd)
+    return;
+
+  int pid_stat = -1;
+  int64_t rsize =
+      PERFETTO_EINTR(read(*exit_status_pipe_.rd, &pid_stat, sizeof(pid_stat)));
+  if (rsize < 0 && errno == EAGAIN)
+    return;
+
+  if (rsize > 0) {
+    PERFETTO_CHECK(rsize == sizeof(pid_stat));
+  } else if (rsize < 0) {
+    PERFETTO_PLOG("Subprocess read(exit_status_pipe_) failed");
+  }
+  waitpid_thread_.join();
+  exit_status_pipe_.rd.reset();
+
+  if (WIFEXITED(pid_stat)) {
+    returncode_ = WEXITSTATUS(pid_stat);
+    status_ = kExited;
+  } else if (WIFSIGNALED(pid_stat)) {
+    returncode_ = 128 + WTERMSIG(pid_stat);  // Follow bash convention.
+    status_ = kKilledBySignal;
+  } else {
+    PERFETTO_FATAL("waitpid() returned an unexpected value (0x%x)", pid_stat);
+  }
+}
+
+// If the stidn pipe is still open, push input data and close it at the end.
+void Subprocess::TryPushStdin() {
+  if (!stdin_pipe_.wr)
+    return;
+
+  PERFETTO_DCHECK(args.input.empty() || input_written_ < args.input.size());
+  if (args.input.size()) {
+    int64_t wsize =
+        PERFETTO_EINTR(write(*stdin_pipe_.wr, &args.input[input_written_],
+                             args.input.size() - input_written_));
+    if (wsize < 0 && errno == EAGAIN)
+      return;
+
+    if (wsize >= 0) {
+      // Whether write() can return 0 is one of the greatest mysteries of UNIX.
+      // Just ignore it.
+      input_written_ += static_cast<size_t>(wsize);
+    } else {
+      PERFETTO_PLOG("Subprocess write(stdin) failed");
+      stdin_pipe_.wr.reset();
+    }
+  }
+  PERFETTO_DCHECK(input_written_ <= args.input.size());
+  if (input_written_ == args.input.size())
+    stdin_pipe_.wr.reset();  // Close stdin.
+}
+
+void Subprocess::TryReadStdoutAndErr() {
+  if (!stdouterr_pipe_.rd)
+    return;
+  char buf[4096];
+  int64_t rsize = PERFETTO_EINTR(read(*stdouterr_pipe_.rd, buf, sizeof(buf)));
+  if (rsize < 0 && errno == EAGAIN)
+    return;
+
+  if (rsize > 0) {
+    output_.append(buf, static_cast<size_t>(rsize));
+  } else if (rsize == 0 /* EOF */) {
+    stdouterr_pipe_.rd.reset();
+  } else {
+    PERFETTO_PLOG("Subprocess read(stdout/err) failed");
+    stdouterr_pipe_.rd.reset();
+  }
+}
+
+void Subprocess::KillAndWaitForTermination() {
+  kill(pid_, SIGKILL);
+  Wait();
+}
+
+std::string Subprocess::Args::GetCmdString() const {
+  std::string str;
+  for (size_t i = 0; i < exec_cmd.size(); i++) {
+    str += i > 0 ? " \"" : "";
+    str += exec_cmd[i];
+    str += i > 0 ? "\"" : "";
+  }
+  return str;
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/subprocess_unittest.cc b/src/base/subprocess_unittest.cc
new file mode 100644
index 0000000..e09e290
--- /dev/null
+++ b/src/base/subprocess_unittest.cc
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "perfetto/ext/base/subprocess.h"
+
+#include <thread>
+
+#include <signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+std::string GenLargeString() {
+  std::string contents;
+  for (int i = 0; i < 4096; i++) {
+    contents += "very long text " + std::to_string(i) + "\n";
+  }
+  // Make sure that |contents| is > the default pipe buffer on Linux (4 pages).
+  PERFETTO_DCHECK(contents.size() > 4096 * 4);
+  return contents;
+}
+
+TEST(SubprocessTest, InvalidPath) {
+  Subprocess p({"/usr/bin/invalid_1337"});
+  EXPECT_FALSE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 128);
+  EXPECT_EQ(p.output(), "execve() failed\n");
+}
+
+TEST(SubprocessTest, StdoutOnly) {
+  Subprocess p({"sh", "-c", "(echo skip_err >&2); echo out_only"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.stderr_mode = Subprocess::kDevNull;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.output(), "out_only\n");
+}
+
+TEST(SubprocessTest, StderrOnly) {
+  Subprocess p({"sh", "-c", "(echo err_only >&2); echo skip_out"});
+  p.args.stdout_mode = Subprocess::kDevNull;
+  p.args.stderr_mode = Subprocess::kBuffer;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.output(), "err_only\n");
+}
+
+TEST(SubprocessTest, BothStdoutAndStderr) {
+  Subprocess p({"sh", "-c", "echo out; (echo err >&2); echo out2"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.stderr_mode = Subprocess::kBuffer;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.output(), "out\nerr\nout2\n");
+}
+
+TEST(SubprocessTest, BinTrue) {
+  Subprocess p({"true"});
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 0);
+}
+
+TEST(SubprocessTest, BinFalse) {
+  Subprocess p({"false"});
+  EXPECT_FALSE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 1);
+}
+
+TEST(SubprocessTest, Echo) {
+  Subprocess p({"echo", "-n", "foobar"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 0);
+  EXPECT_EQ(p.output(), "foobar");
+}
+
+TEST(SubprocessTest, FeedbackLongInput) {
+  std::string contents = GenLargeString();
+  Subprocess p({"cat", "-"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.input = contents;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 0);
+  EXPECT_EQ(p.output(), contents);
+}
+
+TEST(SubprocessTest, CatLargeFile) {
+  std::string contents = GenLargeString();
+  TempFile tf = TempFile::Create();
+  WriteAll(tf.fd(), contents.data(), contents.size());
+  FlushFile(tf.fd());
+  Subprocess p({"cat", tf.path().c_str()});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.output(), contents);
+}
+
+TEST(SubprocessTest, Timeout) {
+  Subprocess p({"sleep", "60"});
+  EXPECT_FALSE(p.Call(/*timeout_ms=*/1));
+  EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
+}
+
+TEST(SubprocessTest, TimeoutNotHit) {
+  Subprocess p({"sleep", "0.01"});
+  EXPECT_TRUE(p.Call(/*timeout_ms=*/100000));
+}
+
+TEST(SubprocessTest, TimeoutStopOutput) {
+  Subprocess p({"sh", "-c", "while true; do echo stuff; done"});
+  p.args.stdout_mode = Subprocess::kDevNull;
+  EXPECT_FALSE(p.Call(/*timeout_ms=*/10));
+  EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
+}
+
+TEST(SubprocessTest, ExitBeforeReadingStdin) {
+  // 'sh -c' is to avoid closing stdin (sleep closes it before sleeping).
+  Subprocess p({"sh", "-c", "sleep 0.01"});
+  p.args.stdout_mode = Subprocess::kDevNull;
+  p.args.stderr_mode = Subprocess::kDevNull;
+  p.args.input = GenLargeString();
+  EXPECT_TRUE(p.Call());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 0);
+}
+
+TEST(SubprocessTest, StdinWriteStall) {
+  // 'sh -c' is to avoid closing stdin (sleep closes it before sleeping).
+  // This causes a situation where the write on the stdin will stall because
+  // nobody reads it and the pipe buffer fills up. In this situation we should
+  // still handle the timeout properly.
+  Subprocess p({"sh", "-c", "sleep 10"});
+  p.args.stdout_mode = Subprocess::kDevNull;
+  p.args.stderr_mode = Subprocess::kDevNull;
+  p.args.input = GenLargeString();
+  EXPECT_FALSE(p.Call(/*timeout_ms=*/10));
+  EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
+}
+
+TEST(SubprocessTest, StartAndWait) {
+  Subprocess p({"sleep", "1000"});
+  p.Start();
+  EXPECT_EQ(p.Poll(), Subprocess::kRunning);
+  p.KillAndWaitForTermination();
+  EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
+  EXPECT_EQ(p.Poll(), Subprocess::kKilledBySignal);
+  EXPECT_EQ(p.returncode(), 128 + SIGKILL);
+}
+
+TEST(SubprocessTest, PollBehavesProperly) {
+  Subprocess p({"sh", "-c", "echo foobar"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.input = "ignored";
+  p.Start();
+
+  // Here we use kill() as a way to tell if the process is still running.
+  // SIGWINCH is ignored by default.
+  while (kill(p.pid(), SIGWINCH) == 0) {
+    usleep(1000);
+  }
+
+  // At this point Poll() must detect the termination.
+  EXPECT_EQ(p.Poll(), Subprocess::kExited);
+  EXPECT_EQ(p.returncode(), 0);
+}
+
+// Test the case of passing a lambda in |entrypoint| but no cmd.c
+TEST(SubprocessTest, Entrypoint) {
+  Subprocess p;
+  p.args.input = "ping\n";
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.entrypoint_for_testing = [] {
+    char buf[32]{};
+    fgets(buf, sizeof(buf), stdin);
+    PERFETTO_CHECK(strcmp(buf, "ping\n") == 0);
+    printf("pong\n");
+    fflush(stdout);
+    _exit(42);
+  };
+  EXPECT_FALSE(p.Call());
+  EXPECT_EQ(p.returncode(), 42);
+  EXPECT_EQ(p.output(), "pong\n");
+}
+
+// Test the case of passing both a lambda entrypoint and a process to exec.
+TEST(SubprocessTest, EntrypointAndExec) {
+  base::Pipe pipe1 = base::Pipe::Create();
+  base::Pipe pipe2 = base::Pipe::Create();
+  int pipe1_wr = *pipe1.wr;
+  int pipe2_wr = *pipe2.wr;
+
+  Subprocess p({"echo", "123"});
+  p.args.stdout_mode = Subprocess::kBuffer;
+  p.args.preserve_fds.push_back(pipe2_wr);
+  p.args.entrypoint_for_testing = [pipe1_wr, pipe2_wr] {
+    base::ignore_result(write(pipe1_wr, "fail", 4));
+    base::ignore_result(write(pipe2_wr, "pass", 4));
+  };
+
+  p.Start();
+  pipe1.wr.reset();
+  pipe2.wr.reset();
+
+  char buf[8];
+  EXPECT_LE(read(*pipe1.rd, buf, sizeof(buf)), 0);
+  EXPECT_EQ(read(*pipe2.rd, buf, sizeof(buf)), 4);
+  buf[4] = '\0';
+  EXPECT_STREQ(buf, "pass");
+  EXPECT_TRUE(p.Wait());
+  EXPECT_EQ(p.status(), Subprocess::kExited);
+  EXPECT_EQ(p.output(), "123\n");
+}
+
+TEST(SubprocessTest, Wait) {
+  Subprocess p({"sleep", "10000"});
+  p.Start();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_FALSE(p.Wait(1 /*ms*/));
+    EXPECT_EQ(p.status(), Subprocess::kRunning);
+  }
+  kill(p.pid(), SIGBUS);
+  EXPECT_TRUE(p.Wait(30000 /*ms*/));
+  EXPECT_TRUE(p.Wait());  // Should be a no-op.
+  EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
+  EXPECT_EQ(p.returncode(), 128 + SIGBUS);
+}
+
+TEST(SubprocessTest, KillOnDtor) {
+  // Here we use kill(SIGWINCH) as a way to tell if the process is still alive.
+  // SIGWINCH is one of the few signals that has default ignore disposition.
+  int pid;
+  {
+    Subprocess p({"sleep", "10000"});
+    p.Start();
+    pid = p.pid();
+    EXPECT_EQ(kill(pid, SIGWINCH), 0);
+  }
+  EXPECT_EQ(kill(pid, SIGWINCH), -1);
+}
+
+}  // namespace
+}  // namespace base
+}  // namespace perfetto