/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_ |