compio_process/
lib.rs

1//! Process library for compio. It is an extension to [`std::process`].
2
3#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4#![cfg_attr(
5    all(feature = "linux_pidfd", target_os = "linux"),
6    feature(linux_pidfd)
7)]
8#![warn(missing_docs)]
9
10cfg_if::cfg_if! {
11    if #[cfg(windows)] {
12        #[path = "windows.rs"]
13        mod sys;
14    } else if #[cfg(target_os = "linux")] {
15        #[path = "linux.rs"]
16        mod sys;
17    } else {
18        #[path = "unix.rs"]
19        mod sys;
20    }
21}
22
23#[cfg(unix)]
24use std::os::unix::process::CommandExt;
25#[cfg(windows)]
26use std::os::windows::process::CommandExt;
27use std::{ffi::OsStr, io, path::Path, process};
28
29use compio_buf::{BufResult, IntoInner};
30use compio_io::AsyncReadExt;
31use compio_runtime::Attacher;
32use futures_util::future::Either;
33
34/// A process builder, providing fine-grained control
35/// over how a new process should be spawned.
36///
37/// A default configuration can be
38/// generated using `Command::new(program)`, where `program` gives a path to the
39/// program to be executed. Additional builder methods allow the configuration
40/// to be changed (for example, by adding arguments) prior to spawning:
41///
42/// ```
43/// use compio_process::Command;
44///
45/// # compio_runtime::Runtime::new().unwrap().block_on(async move {
46/// let output = if cfg!(windows) {
47///     Command::new("cmd")
48///         .args(["/C", "echo hello"])
49///         .output()
50///         .await
51///         .expect("failed to execute process")
52/// } else {
53///     Command::new("sh")
54///         .args(["-c", "echo hello"])
55///         .output()
56///         .await
57///         .expect("failed to execute process")
58/// };
59///
60/// let hello = output.stdout;
61/// # })
62/// ```
63///
64/// `Command` can be reused to spawn multiple processes. The builder methods
65/// change the command without needing to immediately spawn the process.
66///
67/// ```no_run
68/// use compio_process::Command;
69///
70/// # compio_runtime::Runtime::new().unwrap().block_on(async move {
71/// let mut echo_hello = Command::new("sh");
72/// echo_hello.arg("-c").arg("echo hello");
73/// let hello_1 = echo_hello
74///     .output()
75///     .await
76///     .expect("failed to execute process");
77/// let hello_2 = echo_hello
78///     .output()
79///     .await
80///     .expect("failed to execute process");
81/// # })
82/// ```
83///
84/// Similarly, you can call builder methods after spawning a process and then
85/// spawn a new process with the modified settings.
86///
87/// ```no_run
88/// use compio_process::Command;
89///
90/// # compio_runtime::Runtime::new().unwrap().block_on(async move {
91/// let mut list_dir = Command::new("ls");
92///
93/// // Execute `ls` in the current directory of the program.
94/// list_dir.status().await.expect("process failed to execute");
95///
96/// println!();
97///
98/// // Change `ls` to execute in the root directory.
99/// list_dir.current_dir("/");
100///
101/// // And then execute `ls` again but in the root directory.
102/// list_dir.status().await.expect("process failed to execute");
103/// # })
104/// ```
105///
106/// See [`std::process::Command`] for detailed documents.
107#[derive(Debug)]
108pub struct Command(process::Command);
109
110impl Command {
111    /// Create [`Command`].
112    pub fn new(program: impl AsRef<OsStr>) -> Self {
113        Self(process::Command::new(program))
114    }
115
116    /// Adds an argument to pass to the program.
117    pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
118        self.0.arg(arg);
119        self
120    }
121
122    /// Adds multiple arguments to pass to the program.
123    pub fn args<I, S>(&mut self, args: I) -> &mut Self
124    where
125        I: IntoIterator<Item = S>,
126        S: AsRef<OsStr>,
127    {
128        self.0.args(args);
129        self
130    }
131
132    /// Inserts or updates an explicit environment variable mapping.
133    pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
134    where
135        K: AsRef<OsStr>,
136        V: AsRef<OsStr>,
137    {
138        self.0.env(key, val);
139        self
140    }
141
142    /// Inserts or updates multiple explicit environment variable mappings.
143    pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
144    where
145        I: IntoIterator<Item = (K, V)>,
146        K: AsRef<OsStr>,
147        V: AsRef<OsStr>,
148    {
149        self.0.envs(vars);
150        self
151    }
152
153    /// Removes an explicitly set environment variable and prevents inheriting
154    /// it from a parent process.
155    pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
156        self.0.env_remove(key);
157        self
158    }
159
160    /// Clears all explicitly set environment variables and prevents inheriting
161    /// any parent process environment variables.
162    pub fn env_clear(&mut self) -> &mut Self {
163        self.0.env_clear();
164        self
165    }
166
167    /// Sets the working directory for the child process.
168    pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
169        self.0.current_dir(dir);
170        self
171    }
172
173    /// Configuration for the child process’s standard input (stdin) handle.
174    pub fn stdin<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
175        self.0.stdin(cfg.try_into()?);
176        Ok(self)
177    }
178
179    /// Configuration for the child process’s standard output (stdout) handle.
180    pub fn stdout<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
181        self.0.stdout(cfg.try_into()?);
182        Ok(self)
183    }
184
185    /// Configuration for the child process’s standard error (stderr) handle.
186    pub fn stderr<S: TryInto<process::Stdio>>(&mut self, cfg: S) -> Result<&mut Self, S::Error> {
187        self.0.stderr(cfg.try_into()?);
188        Ok(self)
189    }
190
191    /// Returns the path to the program.
192    pub fn get_program(&self) -> &OsStr {
193        self.0.get_program()
194    }
195
196    /// Returns an iterator of the arguments that will be passed to the program.
197    pub fn get_args(&self) -> process::CommandArgs {
198        self.0.get_args()
199    }
200
201    /// Returns an iterator of the environment variables explicitly set for the
202    /// child process.
203    pub fn get_envs(&self) -> process::CommandEnvs {
204        self.0.get_envs()
205    }
206
207    /// Returns the working directory for the child process.
208    pub fn get_current_dir(&self) -> Option<&Path> {
209        self.0.get_current_dir()
210    }
211
212    /// Executes the command as a child process, returning a handle to it.
213    pub fn spawn(&mut self) -> io::Result<Child> {
214        #[cfg(all(target_os = "linux", feature = "linux_pidfd"))]
215        {
216            use std::os::linux::process::CommandExt;
217            self.0.create_pidfd(true);
218        }
219        let mut child = self.0.spawn()?;
220        let stdin = if let Some(stdin) = child.stdin.take() {
221            Some(ChildStdin::new(stdin)?)
222        } else {
223            None
224        };
225        let stdout = if let Some(stdout) = child.stdout.take() {
226            Some(ChildStdout::new(stdout)?)
227        } else {
228            None
229        };
230        let stderr = if let Some(stderr) = child.stderr.take() {
231            Some(ChildStderr::new(stderr)?)
232        } else {
233            None
234        };
235        Ok(Child {
236            child,
237            stdin,
238            stdout,
239            stderr,
240        })
241    }
242
243    /// Executes a command as a child process, waiting for it to finish and
244    /// collecting its status. The output of child stdout and child stderr will
245    /// be ignored.
246    pub async fn status(&mut self) -> io::Result<process::ExitStatus> {
247        let child = self.spawn()?;
248        child.wait().await
249    }
250
251    /// Executes the command as a child process, waiting for it to finish and
252    /// collecting all of its output.
253    pub async fn output(&mut self) -> io::Result<process::Output> {
254        let child = self.spawn()?;
255        child.wait_with_output().await
256    }
257}
258
259#[cfg(windows)]
260impl Command {
261    /// Sets the [process creation flags][1] to be passed to `CreateProcess`.
262    ///
263    /// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT`.
264    ///
265    /// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
266    pub fn creation_flags(&mut self, flags: u32) -> &mut Self {
267        self.0.creation_flags(flags);
268        self
269    }
270
271    /// Append literal text to the command line without any quoting or escaping.
272    pub fn raw_arg(&mut self, text_to_append_as_is: impl AsRef<OsStr>) -> &mut Self {
273        self.0.raw_arg(text_to_append_as_is);
274        self
275    }
276}
277
278#[cfg(unix)]
279impl Command {
280    /// Sets the child process’s user ID. This translates to a `setuid`` call in
281    /// the child process. Failure in the `setuid` call will cause the spawn to
282    /// fail.
283    pub fn uid(&mut self, id: u32) -> &mut Self {
284        self.0.uid(id);
285        self
286    }
287
288    /// Similar to `uid`, but sets the group ID of the child process. This has
289    /// the same semantics as the `uid` field.
290    pub fn gid(&mut self, id: u32) -> &mut Self {
291        self.0.gid(id);
292        self
293    }
294
295    /// Schedules a closure to be run just before the `exec` function is
296    /// invoked.
297    ///
298    /// # Safety
299    ///
300    /// See [`CommandExt::pre_exec`].
301    pub unsafe fn pre_exec(
302        &mut self,
303        f: impl FnMut() -> io::Result<()> + Send + Sync + 'static,
304    ) -> &mut Self {
305        self.0.pre_exec(f);
306        self
307    }
308
309    /// `exec` the command without `fork`.
310    pub fn exec(&mut self) -> io::Error {
311        self.0.exec()
312    }
313
314    /// Set the first process argument, `argv[0]`, to something other than the
315    /// default executable path.
316    pub fn arg0(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
317        self.0.arg0(arg);
318        self
319    }
320
321    /// Sets the process group ID (PGID) of the child process.
322    pub fn process_group(&mut self, pgroup: i32) -> &mut Self {
323        self.0.process_group(pgroup);
324        self
325    }
326}
327
328/// Representation of a running or exited child process.
329///
330/// This structure is used to represent and manage child processes. A child
331/// process is created via the [`Command`] struct, which configures the
332/// spawning process and can itself be constructed using a builder-style
333/// interface.
334///
335/// There is no implementation of [`Drop`] for child processes,
336/// so if you do not ensure the `Child` has exited then it will continue to
337/// run, even after the `Child` handle to the child process has gone out of
338/// scope.
339///
340/// Calling [`Child::wait`] (or other functions that wrap around it) will make
341/// the parent process wait until the child has actually exited before
342/// continuing.
343///
344/// See [`std::process::Child`] for detailed documents.
345pub struct Child {
346    child: process::Child,
347    /// The handle for writing to the child’s standard input (stdin).
348    pub stdin: Option<ChildStdin>,
349    /// The handle for reading from the child’s standard output (stdout).
350    pub stdout: Option<ChildStdout>,
351    /// The handle for reading from the child’s standard error (stderr).
352    pub stderr: Option<ChildStderr>,
353}
354
355impl Child {
356    /// Forces the child process to exit. If the child has already exited,
357    /// `Ok(())`` is returned.
358    pub fn kill(&mut self) -> io::Result<()> {
359        self.child.kill()
360    }
361
362    /// Returns the OS-assigned process identifier associated with this child.
363    pub fn id(&self) -> u32 {
364        self.child.id()
365    }
366
367    /// Waits for the child to exit completely, returning the status that it
368    /// exited with. This function will consume the child. To get the output,
369    /// either take `stdout` and `stderr` out before calling it, or call
370    /// [`Child::wait_with_output`].
371    pub async fn wait(self) -> io::Result<process::ExitStatus> {
372        sys::child_wait(self.child).await
373    }
374
375    /// Simultaneously waits for the child to exit and collect all remaining
376    /// output on the stdout/stderr handles, returning an Output instance.
377    pub async fn wait_with_output(mut self) -> io::Result<process::Output> {
378        let status = sys::child_wait(self.child);
379        let stdout = if let Some(stdout) = &mut self.stdout {
380            Either::Left(stdout.read_to_end(vec![]))
381        } else {
382            Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
383        };
384        let stderr = if let Some(stderr) = &mut self.stderr {
385            Either::Left(stderr.read_to_end(vec![]))
386        } else {
387            Either::Right(std::future::ready(BufResult(Ok(0), vec![])))
388        };
389        let (status, BufResult(out_res, stdout), BufResult(err_res, stderr)) =
390            futures_util::future::join3(status, stdout, stderr).await;
391        let status = status?;
392        out_res?;
393        err_res?;
394        Ok(process::Output {
395            status,
396            stdout,
397            stderr,
398        })
399    }
400}
401
402/// A handle to a child process's standard output (stdout). See
403/// [`std::process::ChildStdout`].
404pub struct ChildStdout(Attacher<process::ChildStdout>);
405
406impl ChildStdout {
407    fn new(stdout: process::ChildStdout) -> io::Result<Self> {
408        Attacher::new(stdout).map(Self)
409    }
410}
411
412impl TryFrom<ChildStdout> for process::Stdio {
413    type Error = ChildStdout;
414
415    fn try_from(value: ChildStdout) -> Result<Self, ChildStdout> {
416        value
417            .0
418            .into_inner()
419            .try_unwrap()
420            .map(Self::from)
421            .map_err(|fd| ChildStdout(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
422    }
423}
424
425/// A handle to a child process's stderr. See [`std::process::ChildStderr`].
426pub struct ChildStderr(Attacher<process::ChildStderr>);
427
428impl ChildStderr {
429    fn new(stderr: process::ChildStderr) -> io::Result<Self> {
430        Attacher::new(stderr).map(Self)
431    }
432}
433
434impl TryFrom<ChildStderr> for process::Stdio {
435    type Error = ChildStderr;
436
437    fn try_from(value: ChildStderr) -> Result<Self, ChildStderr> {
438        value
439            .0
440            .into_inner()
441            .try_unwrap()
442            .map(Self::from)
443            .map_err(|fd| ChildStderr(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
444    }
445}
446
447/// A handle to a child process's standard input (stdin). See
448/// [`std::process::ChildStdin`].
449pub struct ChildStdin(Attacher<process::ChildStdin>);
450
451impl ChildStdin {
452    fn new(stdin: process::ChildStdin) -> io::Result<Self> {
453        Attacher::new(stdin).map(Self)
454    }
455}
456
457impl TryFrom<ChildStdin> for process::Stdio {
458    type Error = ChildStdin;
459
460    fn try_from(value: ChildStdin) -> Result<Self, ChildStdin> {
461        value
462            .0
463            .into_inner()
464            .try_unwrap()
465            .map(Self::from)
466            .map_err(|fd| ChildStdin(unsafe { Attacher::from_shared_fd_unchecked(fd) }))
467    }
468}