• 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 "perfetto/base/build_config.h"
21 
22 // This is a #if as opposite to a GN condition, because GN conditions aren't propagated when
23 // translating to Bazel or other build systems, as they get resolved at translation time. Without
24 // this, the Bazel build breaks on Windows.
25 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
26     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
27     PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
28 #define PERFETTO_HAS_SUBPROCESS() 1
29 #else
30 #define PERFETTO_HAS_SUBPROCESS() 0
31 #endif
32 
33 #include <functional>
34 #include <initializer_list>
35 #include <string>
36 #include <thread>
37 #include <vector>
38 
39 #include "perfetto/base/logging.h"
40 #include "perfetto/base/proc_utils.h"
41 #include "perfetto/ext/base/pipe.h"
42 #include "perfetto/ext/base/scoped_file.h"
43 
44 namespace perfetto {
45 namespace base {
46 
47 // Handles creation and lifecycle management of subprocesses, taking care of
48 // all subtleties involved in handling processes on UNIX.
49 // This class allows to deal with macro two use-cases:
50 // 1) fork() + exec() equivalent: for spawning a brand new process image.
51 //    This happens when |args.exec_cmd| is not empty.
52 //    This is safe to use even in a multi-threaded environment.
53 // 2) fork(): for spawning a process and running a function.
54 //    This happens when |args.entrypoint_for_testing| is not empty.
55 //    This is intended only for tests as it is extremely subtle.
56 //    This mode must be used with extreme care. Before the entrypoint is
57 //    invoked all file descriptors other than stdin/out/err and the ones
58 //    specified in |args.preserve_fds| will be closed, to avoid each process
59 //    retaining a dupe of other subprocesses pipes. This however means that
60 //    any non trivial calls (including logging) must be avoided as they might
61 //    refer to FDs that are now closed. The entrypoint should really be used
62 //    just to signal a pipe or similar for synchronizing sequencing in tests.
63 
64 //
65 // This class allows to control stdin/out/err pipe redirection and takes care
66 // of keeping all the pipes pumped (stdin) / drained (stdout/err), in a similar
67 // fashion of python's subprocess.Communicate()
68 // stdin: is always piped and closed once the |args.input| buffer is written.
69 // stdout/err can be either:
70 //   - dup()ed onto the parent process stdout/err.
71 //   - redirected onto /dev/null.
72 //   - piped onto a buffer (see output() method). There is only one output
73 //     buffer in total. If both stdout and stderr are set to kBuffer mode, they
74 //     will be merged onto the same. There doesn't seem any use case where they
75 //     are needed distinctly.
76 //
77 // Some caveats worth mentioning:
78 // - It always waitpid()s, to avoid leaving zombies around. If the process is
79 //   not terminated by the time the destructor is reached, the dtor will
80 //   send a SIGKILL and wait for the termination.
81 // - After fork()-ing it will close all file descriptors, preserving only
82 //   stdin/out/err and the fds listed in |args.preserve_fds|.
83 // - On Linux/Android, the child process will be SIGKILL-ed if the calling
84 //   thread exists, even if the Subprocess is std::move()-d onto another thread.
85 //   This happens by virtue PR_SET_PDEATHSIG, which is used to avoid that
86 //   child processes are leaked in the case of a crash of the parent (frequent
87 //   in tests). However, the child process might still be leaked if execing
88 //   a setuid/setgid binary (see man 2 prctl).
89 //
90 // Usage:
91 // base::Subprocess p({"/bin/cat", "-"});
92 // (or equivalently:
93 //     base::Subprocess p;
94 //     p.args.exec_cmd.push_back("/bin/cat");
95 //     p.args.exec_cmd.push_back("-");
96 //  )
97 // p.args.stdout_mode = base::Subprocess::kBuffer;
98 // p.args.stderr_mode = base::Subprocess::kInherit;
99 // p.args.input = "stdin contents";
100 // p.Call();
101 // (or equivalently:
102 //     p.Start();
103 //     p.Wait();
104 // )
105 // EXPECT_EQ(p.status(), base::Subprocess::kExited);
106 // EXPECT_EQ(p.returncode(), 0);
107 class Subprocess {
108  public:
109   enum Status {
110     kNotStarted = 0,  // Before calling Start() or Call().
111     kRunning,         // After calling Start(), before Wait().
112     kExited,          // The subprocess exited (either succesully or not).
113     kKilledBySignal,  // The subprocess has been killed by a signal.
114   };
115 
116   enum OutputMode {
117     kInherit = 0,  // Inherit's the caller process stdout/stderr.
118     kDevNull,      // dup() onto /dev/null
119     kBuffer,       // dup() onto a pipe and move it into the output() buffer.
120     kFd,           // dup() onto the passed args.fd.
121   };
122 
123   // Input arguments for configuring the subprocess behavior.
124   struct Args {
exec_cmdArgs125     Args(std::initializer_list<std::string> _cmd = {}) : exec_cmd(_cmd) {}
126     Args(Args&&) noexcept;
127     Args& operator=(Args&&);
128     // If non-empty this will cause an exec() when Start()/Call() are called.
129     std::vector<std::string> exec_cmd;
130 
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 argv0_override;
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.
139     // It is valid to specify both |exec_cmd| AND |entrypoint_for_testing|.
140     // In this case |entrypoint_for_testing| will be invoked just before the
141     // exec() call, but after having closed all fds % stdin/out/err.
142     // This is for synchronization barriers in tests.
143     std::function<void()> entrypoint_for_testing;
144 
145     // If non-empty, replaces the environment passed to exec().
146     std::vector<std::string> env;
147 
148     // The file descriptors in this list will not be closed.
149     std::vector<int> preserve_fds;
150 
151     // The data to push in the child process stdin.
152     std::string input;
153 
154     OutputMode stdout_mode = kInherit;
155     OutputMode stderr_mode = kInherit;
156 
157     base::ScopedFile out_fd;
158 
159     // Returns " ".join(exec_cmd), quoting arguments.
160     std::string GetCmdString() const;
161   };
162 
163   struct ResourceUsage {
164     uint32_t cpu_utime_ms = 0;
165     uint32_t cpu_stime_ms = 0;
166     uint32_t max_rss_kb = 0;
167     uint32_t min_page_faults = 0;
168     uint32_t maj_page_faults = 0;
169     uint32_t vol_ctx_switch = 0;
170     uint32_t invol_ctx_switch = 0;
171 
cpu_time_msResourceUsage172     uint32_t cpu_time_ms() const { return cpu_utime_ms + cpu_stime_ms; }
173   };
174 
175   explicit Subprocess(std::initializer_list<std::string> exec_cmd = {});
176   Subprocess(Subprocess&&) noexcept;
177   Subprocess& operator=(Subprocess&&);
178   ~Subprocess();  // It will KillAndWaitForTermination() if still alive.
179 
180   // Starts the subprocess but doesn't wait for its termination. The caller
181   // is expected to either call Wait() or Poll() after this call.
182   void Start();
183 
184   // Wait for process termination. Can be called more than once.
185   // Args:
186   //   |timeout_ms| = 0: wait indefinitely.
187   //   |timeout_ms| > 0: wait for at most |timeout_ms|.
188   // Returns:
189   //  True: The process terminated. See status() and returncode().
190   //  False: Timeout reached, the process is still running. In this case the
191   //         process will be left in the kRunning state.
192   bool Wait(int timeout_ms = 0);
193 
194   // Equivalent of Start() + Wait();
195   // Returns true if the process exited cleanly with return code 0. False in
196   // any othe case.
197   bool Call(int timeout_ms = 0);
198 
199   Status Poll();
200 
201   // Sends a signal (SIGKILL if not specified) and wait for process termination.
202   void KillAndWaitForTermination(int sig_num = 0);
203 
pid()204   PlatformProcessId pid() const { return s_.pid; }
205 
206   // The accessors below are updated only after a call to Poll(), Wait() or
207   // KillAndWaitForTermination().
208   // In most cases you want to call Poll() rather than these accessors.
209 
status()210   Status status() const { return s_.status; }
returncode()211   int returncode() const { return s_.returncode; }
212 
213   // This contains both stdout and stderr (if the corresponding _mode ==
214   // kBuffer). It's non-const so the caller can std::move() it.
output()215   std::string& output() { return s_.output; }
rusage()216   const ResourceUsage& rusage() const { return *s_.rusage; }
217 
218   Args args;
219 
220  private:
221   Subprocess(const Subprocess&) = delete;
222   Subprocess& operator=(const Subprocess&) = delete;
223   void TryPushStdin();
224   void TryReadStdoutAndErr();
225   void TryReadExitStatus();
226   void KillAtMostOnce();
227   bool PollInternal(int poll_timeout_ms);
228 
229   // This is to deal robustly with the move operators, without having to
230   // manually maintain member-wise move instructions.
231   struct MovableState {
232     base::Pipe stdin_pipe;
233     base::Pipe stdouterr_pipe;
234     base::Pipe exit_status_pipe;
235     PlatformProcessId pid;
236     size_t input_written = 0;
237     Status status = kNotStarted;
238     int returncode = -1;
239     std::string output;  // Stdin+stderr. Only when kBuffer.
240     std::thread waitpid_thread;
241     std::unique_ptr<ResourceUsage> rusage;
242   };
243 
244   MovableState s_;
245 };
246 
247 }  // namespace base
248 }  // namespace perfetto
249 
250 #endif  // INCLUDE_PERFETTO_EXT_BASE_SUBPROCESS_H_
251