• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
exec_cmdArgs124     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 
cpu_time_msResourceUsage181     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 
pid()213   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 
status()219   Status status() const { return s_->status; }
returncode()220   int returncode() const { return s_->returncode; }
timed_out()221   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.
output()225   std::string& output() { return s_->output; }
output()226   const std::string& output() const { return s_->output; }
227 
posix_rusage()228   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_
283