Skip to main content

compio_process/
lib.rs

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