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