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