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