Coverage Report

Created: 2025-11-16 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/perfetto/include/perfetto/ext/base/subprocess.h
Line
Count
Source
1
/*
2
 * Copyright (C) 2020 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#ifndef INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_
18
#define INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_
19
20
#include <condition_variable>
21
#include <functional>
22
#include <initializer_list>
23
#include <memory>
24
#include <mutex>
25
#include <optional>
26
#include <string>
27
#include <thread>
28
#include <vector>
29
30
#include "perfetto/base/build_config.h"
31
#include "perfetto/base/logging.h"
32
#include "perfetto/base/platform_handle.h"
33
#include "perfetto/base/proc_utils.h"
34
#include "perfetto/ext/base/event_fd.h"
35
#include "perfetto/ext/base/pipe.h"
36
#include "perfetto/ext/base/scoped_file.h"
37
38
namespace perfetto {
39
namespace base {
40
41
// Handles creation and lifecycle management of subprocesses, taking care of
42
// all subtleties involved in handling processes on UNIX.
43
// This class allows to deal with macro two use-cases:
44
// 1) fork() + exec() equivalent: for spawning a brand new process image.
45
//    This happens when |args.exec_cmd| is not empty.
46
//    This is safe to use even in a multi-threaded environment.
47
// 2) fork(): for spawning a process and running a function.
48
//    This happens when |args.posix_entrypoint_for_testing| is not empty.
49
//    This is intended only for tests as it is extremely subtle.
50
//    This mode must be used with extreme care. Before the entrypoint is
51
//    invoked all file descriptors other than stdin/out/err and the ones
52
//    specified in |args.preserve_fds| will be closed, to avoid each process
53
//    retaining a dupe of other subprocesses pipes. This however means that
54
//    any non trivial calls (including logging) must be avoided as they might
55
//    refer to FDs that are now closed. The entrypoint should really be used
56
//    just to signal a pipe or similar for synchronizing sequencing in tests.
57
58
//
59
// This class allows to control stdin/out/err pipe redirection and takes care
60
// of keeping all the pipes pumped (stdin) / drained (stdout/err), in a similar
61
// fashion of python's subprocess.Communicate()
62
// stdin: is always piped and closed once the |args.input| buffer is written.
63
// stdout/err can be either:
64
//   - dup()ed onto the parent process stdout/err.
65
//   - redirected onto /dev/null.
66
//   - piped onto a buffer (see output() method). There is only one output
67
//     buffer in total. If both stdout and stderr are set to kBuffer mode, they
68
//     will be merged onto the same. There doesn't seem any use case where they
69
//     are needed distinctly.
70
//
71
// Some caveats worth mentioning:
72
// - It always waitpid()s, to avoid leaving zombies around. If the process is
73
//   not terminated by the time the destructor is reached, the dtor will
74
//   send a SIGKILL and wait for the termination.
75
// - After fork()-ing it will close all file descriptors, preserving only
76
//   stdin/out/err and the fds listed in |args.preserve_fds|.
77
// - On Linux/Android, the child process will be SIGKILL-ed if the calling
78
//   thread exists, even if the Subprocess is std::move()-d onto another thread.
79
//   This happens by virtue PR_SET_PDEATHSIG, which is used to avoid that
80
//   child processes are leaked in the case of a crash of the parent (frequent
81
//   in tests). However, the child process might still be leaked if execing
82
//   a setuid/setgid binary (see man 2 prctl).
83
//
84
// Usage:
85
// base::Subprocess p({"/bin/cat", "-"});
86
// (or equivalently:
87
//     base::Subprocess p;
88
//     p.args.exec_cmd.push_back("/bin/cat");
89
//     p.args.exec_cmd.push_back("-");
90
//  )
91
// p.args.stdout_mode = base::Subprocess::kBuffer;
92
// p.args.stderr_mode = base::Subprocess::kInherit;
93
// p.args.input = "stdin contents";
94
// p.Call();
95
// (or equivalently:
96
//     p.Start();
97
//     p.Wait();
98
// )
99
// EXPECT_EQ(p.status(), base::Subprocess::kTerminated);
100
// EXPECT_EQ(p.returncode(), 0);
101
class Subprocess {
102
 public:
103
  enum Status {
104
    kNotStarted = 0,  // Before calling Start() or Call().
105
    kRunning,         // After calling Start(), before Wait().
106
    kTerminated,      // The subprocess terminated, either successfully or not.
107
                      // This includes crashes or other signals on UNIX.
108
  };
109
110
  enum class OutputMode {
111
    kInherit = 0,  // Inherit's the caller process stdout/stderr.
112
    kDevNull,      // dup() onto /dev/null.
113
    kBuffer,       // dup() onto a pipe and move it into the output() buffer.
114
    kFd,           // dup() onto the passed args.fd.
115
  };
116
117
  enum class InputMode {
118
    kBuffer = 0,  // dup() onto a pipe and write args.input on it.
119
    kDevNull,     // dup() onto /dev/null.
120
  };
121
122
  // Input arguments for configuring the subprocess behavior.
123
  struct Args {
124
0
    Args(std::initializer_list<std::string> _cmd = {}) : exec_cmd(_cmd) {}
125
    Args(Args&&) noexcept;
126
    Args& operator=(Args&&);
127
    // If non-empty this will cause an exec() when Start()/Call() are called.
128
    std::vector<std::string> exec_cmd;
129
130
#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
131
    // If non-empty, it changes the argv[0] argument passed to exec. If
132
    // unset, argv[0] == exec_cmd[0]. This is to handle cases like:
133
    // exec_cmd = {"/proc/self/exec"}, argv0: "my_custom_test_override".
134
    std::string posix_argv0_override_for_testing;
135
136
    // If non-empty this will be invoked on the fork()-ed child process, after
137
    // stdin/out/err has been redirected and all other file descriptor are
138
    // closed. It is valid to specify both |exec_cmd| AND
139
    // |posix_entrypoint_for_testing|. In this case the latter will be invoked
140
    // just before the exec() call, but after having closed all fds % stdin/o/e.
141
    // This is for synchronization barriers in tests.
142
    std::function<void()> posix_entrypoint_for_testing;
143
144
    // When set, will will move the process to the given process group. If set
145
    // and zero, it will create a new process group. Effectively this calls
146
    // setpgid(0 /*self_pid*/, posix_proc_group_id).
147
    // This can be used to avoid that subprocesses receive CTRL-C from the
148
    // terminal, while still living in the same session.
149
    std::optional<pid_t> posix_proc_group_id{};
150
#endif
151
152
    // If non-empty, replaces the environment passed to exec().
153
    std::vector<std::string> env;
154
155
    // The file descriptors in this list will not be closed.
156
    std::vector<int> preserve_fds;
157
158
    // The data to push in the child process stdin, if input_mode ==
159
    // InputMode::kBuffer.
160
    std::string input;
161
162
    InputMode stdin_mode = InputMode::kBuffer;
163
    OutputMode stdout_mode = OutputMode::kInherit;
164
    OutputMode stderr_mode = OutputMode::kInherit;
165
166
    base::ScopedPlatformHandle out_fd;
167
168
    // Returns " ".join(exec_cmd), quoting arguments.
169
    std::string GetCmdString() const;
170
  };
171
172
  struct ResourceUsage {
173
    uint32_t cpu_utime_ms = 0;
174
    uint32_t cpu_stime_ms = 0;
175
    uint32_t max_rss_kb = 0;
176
    uint32_t min_page_faults = 0;
177
    uint32_t maj_page_faults = 0;
178
    uint32_t vol_ctx_switch = 0;
179
    uint32_t invol_ctx_switch = 0;
180
181
0
    uint32_t cpu_time_ms() const { return cpu_utime_ms + cpu_stime_ms; }
182
  };
183
184
  explicit Subprocess(std::initializer_list<std::string> exec_cmd = {});
185
  Subprocess(Subprocess&&) noexcept;
186
  Subprocess& operator=(Subprocess&&);
187
  ~Subprocess();  // It will KillAndWaitForTermination() if still alive.
188
189
  // Starts the subprocess but doesn't wait for its termination. The caller
190
  // is expected to either call Wait() or Poll() after this call.
191
  void Start();
192
193
  // Wait for process termination. Can be called more than once.
194
  // Args:
195
  //   |timeout_ms| = 0: wait indefinitely.
196
  //   |timeout_ms| > 0: wait for at most |timeout_ms|.
197
  // Returns:
198
  //  True: The process terminated. See status() and returncode().
199
  //  False: Timeout reached, the process is still running. In this case the
200
  //         process will be left in the kRunning state.
201
  bool Wait(int timeout_ms = 0);
202
203
  // Equivalent of Start() + Wait();
204
  // Returns true if the process exited cleanly with return code 0. False in
205
  // any othe case.
206
  bool Call(int timeout_ms = 0);
207
208
  Status Poll();
209
210
  // Sends a signal (SIGKILL if not specified) and wait for process termination.
211
  void KillAndWaitForTermination(int sig_num = 0);
212
213
0
  PlatformProcessId pid() const { return s_->pid; }
214
215
  // The accessors below are updated only after a call to Poll(), Wait() or
216
  // KillAndWaitForTermination().
217
  // In most cases you want to call Poll() rather than these accessors.
218
219
0
  Status status() const { return s_->status; }
220
0
  int returncode() const { return s_->returncode; }
221
0
  bool timed_out() const { return s_->timed_out; }
222
223
  // This contains both stdout and stderr (if the corresponding _mode ==
224
  // OutputMode::kBuffer). It's non-const so the caller can std::move() it.
225
0
  std::string& output() { return s_->output; }
226
0
  const std::string& output() const { return s_->output; }
227
228
0
  const ResourceUsage& posix_rusage() const { return *s_->rusage; }
229
230
  Args args;
231
232
 private:
233
  // The signal/exit code used when killing the process in case of a timeout.
234
  static const int kTimeoutSignal;
235
236
  Subprocess(const Subprocess&) = delete;
237
  Subprocess& operator=(const Subprocess&) = delete;
238
239
  // This is to deal robustly with the move operators, without having to
240
  // manually maintain member-wise move instructions.
241
  struct MovableState {
242
    base::Pipe stdin_pipe;
243
    base::Pipe stdouterr_pipe;
244
    PlatformProcessId pid;
245
    Status status = kNotStarted;
246
    int returncode = -1;
247
    std::string output;  // Stdin+stderr. Only when OutputMode::kBuffer.
248
    std::unique_ptr<ResourceUsage> rusage{new ResourceUsage()};
249
    bool timed_out = false;
250
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
251
    std::thread stdouterr_thread;
252
    std::thread stdin_thread;
253
    ScopedPlatformHandle win_proc_handle;
254
    ScopedPlatformHandle win_thread_handle;
255
256
    base::EventFd stdouterr_done_event;
257
    std::mutex mutex;  // Protects locked_outerr_buf and the two pipes.
258
    std::string locked_outerr_buf;
259
#else
260
    base::Pipe exit_status_pipe;
261
    size_t input_written = 0;
262
    std::thread waitpid_thread;
263
#endif
264
  };
265
266
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
267
  static void StdinThread(MovableState*, std::string input);
268
  static void StdoutErrThread(MovableState*);
269
#else
270
  void TryPushStdin();
271
  void TryReadStdoutAndErr();
272
  void TryReadExitStatus();
273
  bool PollInternal(int poll_timeout_ms);
274
#endif
275
276
  std::unique_ptr<MovableState> s_;
277
};
278
279
}  // namespace base
280
}  // namespace perfetto
281
282
#endif  // INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_