reverie_process/child.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use core::fmt;
10use core::future::Future;
11use core::pin::Pin;
12use core::task::Context;
13use core::task::Poll;
14use std::io;
15
16use nix::sys::signal::Signal;
17use serde::Deserialize;
18use serde::Serialize;
19use syscalls::Errno;
20
21use super::Command;
22use super::ExitStatus;
23use super::Pid;
24use super::seccomp::SeccompNotif;
25use super::stdio::ChildStderr;
26use super::stdio::ChildStdin;
27use super::stdio::ChildStdout;
28use super::stdio::Stdio;
29
30/// Represents a child process.
31///
32/// NOTE: The child process is not killed or waited on when `Child` is dropped.
33/// If `Child` is not waited on before dropped, the child will continue to run in
34/// the background and may become a "zombie" after the parent exits. It is
35/// therefore best practice to always wait on child processes.
36#[derive(Debug)]
37pub struct Child {
38 /// The child's process ID.
39 pub(super) pid: Pid,
40
41 /// The child's exit status. `Some` if the child has exited already, `None`
42 /// otherwise.
43 pub(super) exit_status: Option<ExitStatus>,
44
45 /// A handle to the seccomp notifier, if requested. This *must* be consumed
46 /// or dropped for the child to be able to exit.
47 pub seccomp_notif: Option<SeccompNotif>,
48
49 /// The handle for writing to the child's standard input (stdin), if it has
50 /// been captured.
51 pub stdin: Option<ChildStdin>,
52
53 /// The handle for reading from the child's standard output (stdout), if it
54 /// has been captured.
55 pub stdout: Option<ChildStdout>,
56
57 /// The handle for reading from the child's standard error (stderr), if it
58 /// has been captured.
59 pub stderr: Option<ChildStderr>,
60}
61
62/// The output of a finished process.
63#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
64pub struct Output {
65 /// The exit status of the process.
66 pub status: ExitStatus,
67 /// The bytes that the process wrote to stdout.
68 pub stdout: Vec<u8>,
69 /// The bytes that the process wrote to stderr.
70 pub stderr: Vec<u8>,
71}
72
73impl fmt::Debug for Output {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 let stdout = core::str::from_utf8(&self.stdout);
76 let stdout: &dyn fmt::Debug = match stdout {
77 Ok(ref s) => s,
78 Err(_) => &self.stdout,
79 };
80
81 let stderr = core::str::from_utf8(&self.stderr);
82 let stderr: &dyn fmt::Debug = match stderr {
83 Ok(ref s) => s,
84 Err(_) => &self.stderr,
85 };
86
87 f.debug_struct("Output")
88 .field("status", &self.status)
89 .field("stdout", stdout)
90 .field("stderr", stderr)
91 .finish()
92 }
93}
94
95impl Child {
96 /// Returns the PID of the child.
97 pub fn id(&self) -> Pid {
98 self.pid
99 }
100
101 /// Attempts to collect the exit status of the child if it has already
102 /// exited.
103 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
104 match self.exit_status {
105 Some(exit_status) => Ok(Some(exit_status)),
106 None => {
107 let mut status = 0;
108 let ret = Errno::result(unsafe {
109 libc::waitpid(self.pid.as_raw(), &mut status, libc::WNOHANG)
110 })?;
111
112 if ret == 0 {
113 Ok(None)
114 } else {
115 let exit_status = ExitStatus::from_raw(status);
116 self.exit_status = Some(exit_status);
117 Ok(Some(exit_status))
118 }
119 }
120 }
121 }
122
123 /// Waits for the child to exit completely, returning its exit status. This
124 /// function will continue to return the same exit status after the child
125 /// process has fully exited.
126 ///
127 /// To avoid deadlocks, the child's stdin handle, if any, will be closed
128 /// before waiting. Otherwise, the child could block waiting for input from
129 /// the parent while the parent is waiting for the child. To keep the stdin
130 /// handle open and control it explicitly, the caller can `.take()` it before
131 /// calling `.wait()`.
132 pub async fn wait(&mut self) -> io::Result<ExitStatus> {
133 // Ensure stdin is closed.
134 drop(self.stdin.take());
135
136 WaitForChild::new(self)?.await
137 }
138
139 /// Blocks until the child process exits.
140 pub fn wait_blocking(&mut self) -> io::Result<ExitStatus> {
141 drop(self.stdin.take());
142
143 let mut status = 0;
144
145 let ret = loop {
146 match Errno::result(unsafe { libc::waitpid(self.pid.as_raw(), &mut status, 0) }) {
147 Ok(ret) => break ret,
148 Err(Errno::EINTR) => continue,
149 Err(err) => return Err(err.into()),
150 }
151 };
152
153 debug_assert_ne!(ret, 0);
154
155 Ok(ExitStatus::from_raw(status))
156 }
157
158 /// Simultaneously waits for the child to exit and collect all remaining
159 /// output on the stdout/stderr handles, returning an `Output` instance.
160 ///
161 /// To avoid deadlocks, the child's stdin handle, if any, will be closed
162 /// before waiting. Otherwise, the child could block waiting for input from
163 /// the parent while the parent is waiting for the child.
164 ///
165 /// By default, stdin, stdout and stderr are inherited from the parent. In
166 /// order to capture the output into this `Result<Output>` it is necessary to
167 /// create new pipes between parent and child. Use `stdout(Stdio::piped())`
168 /// or `stderr(Stdio::piped())`, respectively.
169 pub async fn wait_with_output(mut self) -> io::Result<Output> {
170 use futures::future::try_join3;
171 use tokio::io::AsyncRead;
172 use tokio::io::AsyncReadExt;
173
174 async fn read_to_end<A: AsyncRead + Unpin>(io: Option<A>) -> io::Result<Vec<u8>> {
175 let mut vec = Vec::new();
176 if let Some(mut io) = io {
177 io.read_to_end(&mut vec).await?;
178 }
179 Ok(vec)
180 }
181
182 let stdout_fut = read_to_end(self.stdout.take());
183 let stderr_fut = read_to_end(self.stderr.take());
184
185 let (status, stdout, stderr) = try_join3(self.wait(), stdout_fut, stderr_fut).await?;
186
187 Ok(Output {
188 status,
189 stdout,
190 stderr,
191 })
192 }
193
194 /// Sends a signal to the child. If the child has already been waited on,
195 /// this does nothing and returns success.
196 pub fn signal(&self, sig: Signal) -> io::Result<()> {
197 if self.exit_status.is_none() {
198 Errno::result(unsafe { libc::kill(self.pid.as_raw(), sig as i32) })?;
199 }
200
201 Ok(())
202 }
203}
204
205impl Command {
206 /// Executes the command, waiting for it to finish and collecting its exit
207 /// status.
208 pub async fn status(&mut self) -> io::Result<ExitStatus> {
209 let mut child = self.spawn()?;
210
211 // Ensure we close any stdio handles so we can't deadlock waiting on the
212 // child which may be waiting to read/write to a pipe we're holding.
213 drop(child.stdin.take());
214 drop(child.stdout.take());
215 drop(child.stderr.take());
216
217 child.wait().await
218 }
219
220 /// Executes the command, waiting for it to finish while collecting its
221 /// stdout and stderr into buffers.
222 pub async fn output(&mut self) -> io::Result<Output> {
223 self.stdout(Stdio::piped());
224 self.stderr(Stdio::piped());
225
226 let child = self.spawn();
227
228 child?.wait_with_output().await
229 }
230}
231
232struct WaitForChild<'a> {
233 /// Signal future. Used to get notified asynchronously of a child exiting.
234 signal: tokio::signal::unix::Signal,
235 child: &'a mut Child,
236}
237
238impl<'a> WaitForChild<'a> {
239 fn new(child: &'a mut Child) -> io::Result<Self> {
240 use tokio::signal::unix::SignalKind;
241 use tokio::signal::unix::signal;
242
243 Ok(Self {
244 signal: signal(SignalKind::child())?,
245 child,
246 })
247 }
248}
249
250impl<'a> Future for WaitForChild<'a> {
251 type Output = io::Result<ExitStatus>;
252
253 fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
254 loop {
255 // Register an interest in SIGCHLD signals. We can't just call
256 // `try_wait` right away. We might miss a signal event if the child
257 // hasn't exited yet. Thus, we poll the signal stream to tell Tokio
258 // we're interested in signal events.
259 let sig = self.signal.poll_recv(cx);
260
261 if let Some(status) = self.child.try_wait()? {
262 return Poll::Ready(Ok(status));
263 }
264
265 if sig.is_pending() {
266 return Poll::Pending;
267 }
268 }
269 }
270}