wasi_net/process/
child.rs

1use std::fs::File;
2use std::io::{self, Read, Write};
3use std::sync::mpsc;
4use std::sync::Arc;
5use std::sync::Mutex;
6
7use super::*;
8use crate::backend::{StdioMode, utils::*};
9use crate::backend::Command as BackendCommand;
10use crate::backend::MessageProcess;
11use crate::backend::Response as BackendResponse;
12
13/// Representation of a running or exited child process.
14///
15/// This structure is used to represent and manage child processes. A child
16/// process is created via the [`Command`] struct, which configures the
17/// spawning process and can itself be constructed using a builder-style
18/// interface.
19///
20/// There is no implementation of [`Drop`] for child processes,
21/// so if you do not ensure the `Child` has exited then it will continue to
22/// run, even after the `Child` handle to the child process has gone out of
23/// scope.
24///
25/// Calling [`wait`] (or other functions that wrap around it) will make
26/// the parent process wait until the child has actually exited before
27/// continuing.
28///
29/// # Examples
30///
31/// ```should_panic
32/// use std::process::Command;
33///
34/// let mut child = Command::new("/bin/cat")
35///                         .arg("file.txt")
36///                         .spawn()
37///                         .expect("failed to execute child");
38///
39/// let ecode = child.wait()
40///                  .expect("failed to wait on child");
41///
42/// assert!(ecode.success());
43/// ```
44///
45/// [`wait`]: Child::wait
46#[derive(Debug)]
47pub struct Child {
48    pid: u32,
49    cmd: Command,
50    worker: Arc<Mutex<Worker>>,
51    rx_exit: mpsc::Receiver<ExitStatus>,
52    stdin_mode: StdioMode,
53    stdout_mode: StdioMode,
54    stderr_mode: StdioMode,
55    pre_open: Vec<String>,
56
57    /// The handle for writing to the child's standard input (stdin), if it has
58    /// been captured. To avoid partially moving
59    /// the `child` and thus blocking yourself from calling
60    /// functions on `child` while using `stdin`,
61    /// you might find it helpful:
62    ///
63    /// ```compile_fail,E0425
64    /// let stdin = child.stdin.take().unwrap();
65    /// ```
66    pub stdin: Option<ChildStdin>,
67
68    /// The handle for reading from the child's standard output (stdout), if it
69    /// has been captured. You might find it helpful to do
70    ///
71    /// ```compile_fail,E0425
72    /// let stdout = child.stdout.take().unwrap();
73    /// ```
74    ///
75    /// to avoid partially moving the `child` and thus blocking yourself from calling
76    /// functions on `child` while using `stdout`.
77    pub stdout: Option<ChildStdout>,
78
79    /// The handle for reading from the child's standard error (stderr), if it
80    /// has been captured. You might find it helpful to do
81    ///
82    /// ```compile_fail,E0425
83    /// let stderr = child.stderr.take().unwrap();
84    /// ```
85    ///
86    /// to avoid partially moving the `child` and thus blocking yourself from calling
87    /// functions on `child` while using `stderr`.
88    pub stderr: Option<ChildStderr>,
89}
90
91impl Child {
92    // Starts the child process
93    pub(super) fn new(cmd: &Command, stdin_mode: StdioMode, stdout_mode: StdioMode, stderr_mode: StdioMode, pre_open: Vec<String>,) -> Result<Child> {
94        let submit = BackendCommand::SpawnProcessVersion1 {
95            path: cmd.path.clone(),
96            current_dir: cmd.current_dir.clone(),
97            args: cmd.args.clone(),
98            stdin_mode,
99            stdout_mode,
100            stderr_mode,
101            pre_open: pre_open.clone()
102        };
103        let mut submit = submit.serialize()?;
104        submit += "\n";
105
106        let mut file = File::open("/dev/exec")?;
107
108        let _ = file.write_all(submit.as_bytes());
109
110        let res = read_response(&mut file)?;
111        let pid = match res {
112            BackendResponse::SpawnedProcessVersion1 { pid } => pid,
113            _ => {
114                return Err(std::io::Error::new(
115                    std::io::ErrorKind::Other,
116                    "the socket does not support this response type",
117                ));
118            }
119        };
120
121        let (worker, stdin, stdout, stderr, rx_exit)
122            = Worker::new(file);
123
124        Ok(Child {
125            pid,
126            worker,
127            cmd: cmd.clone(),
128            stdin_mode,
129            stdout_mode,
130            stderr_mode,
131            stdin: if stdin_mode == StdioMode::Piped { Some(stdin) } else { None },
132            stdout: if stdout_mode == StdioMode::Piped { Some(stdout) } else { None },
133            stderr: if stderr_mode == StdioMode::Piped { Some(stderr) } else { None },
134            rx_exit,
135            pre_open,
136        })
137    }
138
139    /// Forces the child process to exit. If the child has already exited, an [`InvalidInput`]
140    /// error is returned.
141    ///
142    /// The mapping to [`ErrorKind`]s is not part of the compatibility contract of the function,
143    /// especially the [`Other`] kind might change to more specific kinds in the future.
144    ///
145    /// This is equivalent to sending a SIGKILL on Unix platforms.
146    ///
147    /// # Examples
148    ///
149    /// Basic usage:
150    ///
151    /// ```no_run
152    /// use wasi_net::Command;
153    ///
154    /// let mut command = Command::new("yes");
155    /// if let Ok(mut child) = command.spawn() {
156    ///     child.kill().expect("command wasn't running");
157    /// } else {
158    ///     println!("yes command didn't start");
159    /// }
160    /// ```
161    ///
162    /// [`ErrorKind`]: io::ErrorKind
163    /// [`InvalidInput`]: io::ErrorKind::InvalidInput
164    /// [`Other`]: io::ErrorKind::Other
165    pub fn kill(&mut self) -> io::Result<()> {
166        self.worker
167            .lock()
168            .unwrap()
169            .send(MessageProcess::Kill)
170            .or_else(|_| {
171                Err(std::io::Error::new(
172                    std::io::ErrorKind::BrokenPipe,
173                    "failed to notify the process to kill itself",
174                ))
175            })?;
176        Ok(())
177    }
178
179    /// Returns the OS-assigned process identifier associated with this child.
180    ///
181    /// # Examples
182    ///
183    /// Basic usage:
184    ///
185    /// ```no_run
186    /// use wasi_net::Command;
187    ///
188    /// let mut command = Command::new("ls");
189    /// if let Ok(child) = command.spawn() {
190    ///     println!("Child's ID is {}", child.id());
191    /// } else {
192    ///     println!("ls command didn't start");
193    /// }
194    /// ```
195    pub fn id(&self) -> u32 {
196        self.pid
197    }
198
199    /// Waits for the child to exit completely, returning the status that it
200    /// exited with. This function will continue to have the same return value
201    /// after it has been called at least once.
202    ///
203    /// The stdin handle to the child process, if any, will be closed
204    /// before waiting. This helps avoid deadlock: it ensures that the
205    /// child does not block waiting for input from the parent, while
206    /// the parent waits for the child to exit.
207    ///
208    /// # Examples
209    ///
210    /// Basic usage:
211    ///
212    /// ```no_run
213    /// use wasi_net::Command;
214    ///
215    /// let mut command = Command::new("ls");
216    /// if let Ok(mut child) = command.spawn() {
217    ///     child.wait().expect("command wasn't running");
218    ///     println!("Child has finished its execution!");
219    /// } else {
220    ///     println!("ls command didn't start");
221    /// }
222    /// ```
223    pub fn wait(&mut self) -> io::Result<ExitStatus> {
224        loop {
225            match self.worker.lock().unwrap().work() {
226                Ok(_) => {}
227                Err(err) if err.kind() == ErrorKind::WouldBlock => {
228                    std::thread::sleep(std::time::Duration::from_millis(1));
229                }
230                Err(err) => {
231                    return Err(err);
232                }
233            };
234            match self.rx_exit.try_recv() {
235                Ok(exitcode) => {
236                    return Ok(exitcode);
237                }
238                Err(mpsc::TryRecvError::Disconnected) => {
239                    return Err(std::io::Error::new(
240                        std::io::ErrorKind::BrokenPipe,
241                        "the process worker already exited",
242                    ));
243                }
244                _ => {}
245            }
246        }
247    }
248
249    /// Attempts to collect the exit status of the child if it has already
250    /// exited.
251    ///
252    /// This function will not block the calling thread and will only
253    /// check to see if the child process has exited or not. If the child has
254    /// exited then on Unix the process ID is reaped. This function is
255    /// guaranteed to repeatedly return a successful exit status so long as the
256    /// child has already exited.
257    ///
258    /// If the child has exited, then `Ok(Some(status))` is returned. If the
259    /// exit status is not available at this time then `Ok(None)` is returned.
260    /// If an error occurs, then that error is returned.
261    ///
262    /// Note that unlike `wait`, this function will not attempt to drop stdin.
263    ///
264    /// # Examples
265    ///
266    /// Basic usage:
267    ///
268    /// ```no_run
269    /// use wasi_net::Command;
270    ///
271    /// let mut child = Command::new("ls").spawn().unwrap();
272    ///
273    /// match child.try_wait() {
274    ///     Ok(Some(status)) => println!("exited with: {}", status),
275    ///     Ok(None) => {
276    ///         println!("status not ready yet, let's really wait");
277    ///         let res = child.wait();
278    ///         println!("result: {:?}", res);
279    ///     }
280    ///     Err(e) => println!("error attempting to wait: {}", e),
281    /// }
282    /// ```
283    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
284        match self.rx_exit.try_recv() {
285            Ok(exitcode) => Ok(Some(exitcode)),
286            Err(mpsc::TryRecvError::Disconnected) => Err(std::io::Error::new(
287                std::io::ErrorKind::BrokenPipe,
288                "the process worker exited",
289            )),
290            Err(mpsc::TryRecvError::Empty) => Ok(None),
291        }
292    }
293
294    /// Simultaneously waits for the child to exit and collect all remaining
295    /// output on the stdout/stderr handles, returning an `Output`
296    /// instance.
297    ///
298    /// The stdin handle to the child process, if any, will be closed
299    /// before waiting. This helps avoid deadlock: it ensures that the
300    /// child does not block waiting for input from the parent, while
301    /// the parent waits for the child to exit.
302    ///
303    /// By default, stdin, stdout and stderr are inherited from the parent.
304    /// In order to capture the output into this `Result<Output>` it is
305    /// necessary to create new pipes between parent and child. Use
306    /// `stdout(Stdio::piped())` or `stderr(Stdio::piped())`, respectively.
307    ///
308    /// # Examples
309    ///
310    /// ```should_panic
311    /// use wasi_net::{Command, Stdio};
312    ///
313    /// let child = Command::new("/bin/cat")
314    ///     .arg("file.txt")
315    ///     .stdout(Stdio::piped())
316    ///     .spawn()
317    ///     .expect("failed to execute child");
318    ///
319    /// let output = child
320    ///     .wait_with_output()
321    ///     .expect("failed to wait on child");
322    ///
323    /// assert!(output.status.success());
324    /// ```
325    ///
326    pub fn wait_with_output(mut self) -> io::Result<Output> {
327        drop(self.stdin.take());
328
329        let status = self.wait()?;
330
331        let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
332        match (self.stdout.take(), self.stderr.take()) {
333            (None, None) => {}
334            (Some(mut out), None) => {
335                out.read_to_end(&mut stdout).unwrap();
336            }
337            (None, Some(mut err)) => {
338                err.read_to_end(&mut stderr).unwrap();
339            }
340            (Some(mut out), Some(mut err)) => {
341                out.read_to_end(&mut stdout).unwrap();
342                err.read_to_end(&mut stderr).unwrap();
343            }
344        }
345
346        Ok(Output {
347            status,
348            stdout,
349            stderr,
350        })
351    }
352}