Skip to main content

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}