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}