wasi_net/process/
command.rs

1use std::{ffi::OsStr, io};
2
3use super::*;
4
5/// A process builder, providing fine-grained control
6/// over how a new process should be spawned.
7///
8/// A default configuration can be
9/// generated using `Command::new(program)`, where `program` gives a path to the
10/// program to be executed. Additional builder methods allow the configuration
11/// to be changed (for example, by adding arguments) prior to spawning:
12///
13/// ```
14/// use wasi_net::Command;
15///
16/// let output = if cfg!(target_os = "windows") {
17///     Command::new("cmd")
18///             .args(&["/C", "echo hello"])
19///             .output()
20///             .expect("failed to execute process")
21/// } else {
22///     Command::new("sh")
23///             .arg("-c")
24///             .arg("echo hello")
25///             .output()
26///             .expect("failed to execute process")
27/// };
28///
29/// let hello = output.stdout;
30/// ```
31///
32/// `Command` can be reused to spawn multiple processes. The builder methods
33/// change the command without needing to immediately spawn the process.
34///
35/// ```no_run
36/// use wasi_net::Command;
37///
38/// let mut echo_hello = Command::new("sh");
39/// echo_hello.arg("-c")
40///           .arg("echo hello");
41/// let hello_1 = echo_hello.output().expect("failed to execute process");
42/// let hello_2 = echo_hello.output().expect("failed to execute process");
43/// ```
44///
45/// Similarly, you can call builder methods after spawning a process and then
46/// spawn a new process with the modified settings.
47///
48/// ```no_run
49/// use wasi_net::Command;
50///
51/// let mut list_dir = Command::new("ls");
52///
53/// // Execute `ls` in the current directory of the program.
54/// list_dir.status().expect("process failed to execute");
55/// ```
56#[derive(Debug, Clone)]
57pub struct Command {
58    pub(super) path: String,
59    pub(super) args: Vec<String>,
60    pub(super) current_dir: Option<String>,
61    pub(super) stdin: Option<Stdio>,
62    pub(super) stdout: Option<Stdio>,
63    pub(super) stderr: Option<Stdio>,
64    pub(super) pre_open: Vec<String>,
65}
66
67impl Command {
68    /// Constructs a new `Command` for launching the program at
69    /// path `program`, with the following default configuration:
70    ///
71    /// * No arguments to the program
72    /// * Inherit the current process's environment
73    /// * Inherit the current process's working directory
74    /// * Inherit stdin/stdout/stderr for `spawn` or `status`, but create pipes for `output`
75    ///
76    /// Builder methods are provided to change these defaults and
77    /// otherwise configure the process.
78    ///
79    /// If `program` is not an absolute path, the `PATH` will be searched in
80    /// an OS-defined way.
81    ///
82    /// # Examples
83    ///
84    /// Basic usage:
85    ///
86    /// ```no_run
87    /// use wasi_net::Command;
88    ///
89    /// Command::new("sh")
90    ///         .spawn()
91    ///         .expect("sh command failed to start");
92    /// ```
93    pub fn new(path: &str) -> Command {
94        Command {
95            path: path.to_string(),
96            args: Vec::new(),
97            current_dir: None,
98            stdin: None,
99            stdout: None,
100            stderr: None,
101            pre_open: Vec::new(),
102        }
103    }
104
105    /// Adds an argument to pass to the program.
106    ///
107    /// Only one argument can be passed per use. So instead of:
108    ///
109    /// ```no_run
110    /// # wasi_net::Command::new("sh")
111    /// .arg("-C /path/to/repo")
112    /// # ;
113    /// ```
114    ///
115    /// usage would be:
116    ///
117    /// ```no_run
118    /// # wasi_net::Command::new("sh")
119    /// .arg("-C")
120    /// .arg("/path/to/repo")
121    /// # ;
122    /// ```
123    ///
124    /// To pass multiple arguments see [`args`].
125    ///
126    /// [`args`]: Command::args
127    ///
128    /// Note that the argument is not passed through a shell, but given
129    /// literally to the program. This means that shell syntax like quotes,
130    /// escaped characters, word splitting, glob patterns, substitution, etc.
131    /// have no effect.
132    ///
133    /// # Examples
134    ///
135    /// Basic usage:
136    ///
137    /// ```no_run
138    /// use wasi_net::Command;
139    ///
140    /// Command::new("ls")
141    ///         .arg("-l")
142    ///         .arg("-a")
143    ///         .spawn()
144    ///         .expect("ls command failed to start");
145    /// ```
146    pub fn arg(&mut self, arg: &str) -> &mut Command {
147        self.args.push(arg.to_string());
148        self
149    }
150
151    /// Adds multiple arguments to pass to the program.
152    ///
153    /// To pass a single argument see [`arg`].
154    ///
155    /// [`arg`]: Command::arg
156    ///
157    /// Note that the arguments are not passed through a shell, but given
158    /// literally to the program. This means that shell syntax like quotes,
159    /// escaped characters, word splitting, glob patterns, substitution, etc.
160    /// have no effect.
161    ///
162    /// # Examples
163    ///
164    /// Basic usage:
165    ///
166    /// ```no_run
167    /// use wasi_net::Command;
168    ///
169    /// Command::new("ls")
170    ///         .args(&["-l", "-a"])
171    ///         .spawn()
172    ///         .expect("ls command failed to start");
173    /// ```
174    pub fn args<I, S>(&mut self, args: I) -> &mut Command
175    where
176        I: IntoIterator<Item = S>,
177        S: AsRef<OsStr>,
178    {
179        for arg in args {
180            self.args.push(arg.as_ref().to_string_lossy().into_owned());
181        }
182        self
183    }
184
185    /// Sets the working directory for the child process.
186    ///
187    /// # Platform-specific behavior
188    ///
189    /// If the program path is relative (e.g., `"./script.sh"`), it's ambiguous
190    /// whether it should be interpreted relative to the parent's working
191    /// directory or relative to `current_dir`. The behavior in this case is
192    /// platform specific and unstable, and it's recommended to use
193    /// [`canonicalize`] to get an absolute program path instead.
194    ///
195    /// # Examples
196    ///
197    /// Basic usage:
198    ///
199    /// ```no_run
200    /// use wasi_net::Command;
201    ///
202    /// Command::new("ls")
203    ///         .current_dir("/bin")
204    ///         .spawn()
205    ///         .expect("ls command failed to start");
206    /// ```
207    ///
208    /// [`canonicalize`]: crate::fs::canonicalize
209    pub fn current_dir(&mut self, dir: &str) -> &mut Command {
210        self.current_dir = Some(dir.to_string());
211        self
212    }
213
214    /// Configuration for the child process's standard input (stdin) handle.
215    ///
216    /// Defaults to [`inherit`] when used with `spawn` or `status`, and
217    /// defaults to [`piped`] when used with `output`.
218    ///
219    /// [`inherit`]: Stdio::inherit
220    /// [`piped`]: Stdio::piped
221    ///
222    /// # Examples
223    ///
224    /// Basic usage:
225    ///
226    /// ```no_run
227    /// use wasi_net::{Command, Stdio};
228    ///
229    /// Command::new("ls")
230    ///         .stdin(Stdio::null())
231    ///         .spawn()
232    ///         .expect("ls command failed to start");
233    /// ```
234    pub fn stdin(&mut self, cfg: Stdio) -> &mut Command {
235        self.stdin = Some(cfg);
236        self
237    }
238
239    /// Configuration for the child process's standard output (stdout) handle.
240    ///
241    /// Defaults to [`inherit`] when used with `spawn` or `status`, and
242    /// defaults to [`piped`] when used with `output`.
243    ///
244    /// [`inherit`]: Stdio::inherit
245    /// [`piped`]: Stdio::piped
246    ///
247    /// # Examples
248    ///
249    /// Basic usage:
250    ///
251    /// ```no_run
252    /// use std::process::{Command, Stdio};
253    ///
254    /// Command::new("ls")
255    ///         .stdout(Stdio::null())
256    ///         .spawn()
257    ///         .expect("ls command failed to start");
258    /// ```
259    pub fn stdout(&mut self, cfg: Stdio) -> &mut Command {
260        self.stdout = Some(cfg);
261        self
262    }
263
264    /// Configuration for the child process's standard error (stderr) handle.
265    ///
266    /// Defaults to [`inherit`] when used with `spawn` or `status`, and
267    /// defaults to [`piped`] when used with `output`.
268    ///
269    /// [`inherit`]: Stdio::inherit
270    /// [`piped`]: Stdio::piped
271    ///
272    /// # Examples
273    ///
274    /// Basic usage:
275    ///
276    /// ```no_run
277    /// use std::process::{Command, Stdio};
278    ///
279    /// Command::new("ls")
280    ///         .stderr(Stdio::null())
281    ///         .spawn()
282    ///         .expect("ls command failed to start");
283    /// ```
284    pub fn stderr(&mut self, cfg: Stdio) -> &mut Command {
285        self.stderr = Some(cfg);
286        self
287    }
288
289    /// Pre-opens a directory before launching the process
290    pub fn pre_open(&mut self, path: String) -> &mut Command {
291        self.pre_open.push(path);
292        self
293    }
294
295    /// Executes the command as a child process, returning a handle to it.
296    ///
297    /// By default, stdin, stdout and stderr are inherited from the parent.
298    ///
299    /// # Examples
300    ///
301    /// Basic usage:
302    ///
303    /// ```no_run
304    /// use wasi_net::Command;
305    ///
306    /// Command::new("ls")
307    ///         .spawn()
308    ///         .expect("ls command failed to start");
309    /// ```
310    pub fn spawn(&self) -> io::Result<Child> {
311        let stdin = self.stdin.as_ref().map(|a| a.clone()).unwrap_or(Stdio::inherit());
312        let stdout = self.stdout.as_ref().map(|a| a.clone()).unwrap_or(Stdio::inherit());
313        let stderr = self.stderr.as_ref().map(|a| a.clone()).unwrap_or(Stdio::inherit());
314        let preopen = self.pre_open.clone();
315        Child::new(self, stdin.mode, stdout.mode, stderr.mode, preopen)
316    }
317
318    /// Executes the command as a child process, waiting for it to finish and
319    /// collecting all of its output.
320    ///
321    /// By default, stdout and stderr are captured (and used to provide the
322    /// resulting output). Stdin is not inherited from the parent and any
323    /// attempt by the child process to read from the stdin stream will result
324    /// in the stream immediately closing.
325    ///
326    /// # Examples
327    ///
328    /// ```should_panic
329    /// use wasi_net::Command;
330    /// use std::io::{self, Write};
331    /// let output = Command::new("/bin/cat")
332    ///                      .arg("file.txt")
333    ///                      .output()
334    ///                      .expect("failed to execute process");
335    ///
336    /// println!("status: {}", output.status);
337    /// io::stdout().write_all(&output.stdout).unwrap();
338    /// io::stderr().write_all(&output.stderr).unwrap();
339    ///
340    /// assert!(output.status.success());
341    /// ```
342    pub fn output(&mut self) -> io::Result<Output> {
343        if self.stdin.is_none() {
344            self.stdin = Some(Stdio::null());
345        }
346        if self.stdout.is_none() {
347            self.stdout = Some(Stdio::piped());
348        }
349        if self.stderr.is_none() {
350            self.stderr = Some(Stdio::piped());
351        }
352        Ok(self.spawn()?.wait_with_output()?)
353    }
354
355    /// Executes a command as a child process, waiting for it to finish and
356    /// collecting its status.
357    ///
358    /// By default, stdin, stdout and stderr are inherited from the parent.
359    ///
360    /// # Examples
361    ///
362    /// ```should_panic
363    /// use wasi_net::Command;
364    ///
365    /// let status = Command::new("/bin/cat")
366    ///                      .arg("file.txt")
367    ///                      .status()
368    ///                      .expect("failed to execute process");
369    ///
370    /// println!("process finished with: {}", status);
371    ///
372    /// assert!(status.success());
373    /// ```
374    pub fn status(&mut self) -> io::Result<ExitStatus> {
375        Ok(self.spawn()?.wait()?)
376    }
377}