process.rs

 1use anyhow::{Context as _, Result};
 2use std::process::Stdio;
 3
 4/// A wrapper around `smol::process::Child` that ensures all subprocesses
 5/// are killed when the process is terminated by using process groups.
 6pub struct Child {
 7    process: smol::process::Child,
 8}
 9
10impl std::ops::Deref for Child {
11    type Target = smol::process::Child;
12
13    fn deref(&self) -> &Self::Target {
14        &self.process
15    }
16}
17
18impl std::ops::DerefMut for Child {
19    fn deref_mut(&mut self) -> &mut Self::Target {
20        &mut self.process
21    }
22}
23
24impl Child {
25    #[cfg(not(windows))]
26    pub fn spawn(
27        mut command: std::process::Command,
28        stdin: Stdio,
29        stdout: Stdio,
30        stderr: Stdio,
31    ) -> Result<Self> {
32        crate::set_pre_exec_to_start_new_session(&mut command);
33        let mut command = smol::process::Command::from(command);
34        let process = command
35            .stdin(stdin)
36            .stdout(stdout)
37            .stderr(stderr)
38            .spawn()
39            .with_context(|| {
40                format!(
41                    "failed to spawn command {}",
42                    crate::redact::redact_command(&format!("{command:?}"))
43                )
44            })?;
45        Ok(Self { process })
46    }
47
48    #[cfg(windows)]
49    pub fn spawn(
50        command: std::process::Command,
51        stdin: Stdio,
52        stdout: Stdio,
53        stderr: Stdio,
54    ) -> Result<Self> {
55        // TODO(windows): create a job object and add the child process handle to it,
56        // see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
57        let mut command = smol::process::Command::from(command);
58        let process = command
59            .stdin(stdin)
60            .stdout(stdout)
61            .stderr(stderr)
62            .spawn()
63            .with_context(|| {
64                format!(
65                    "failed to spawn command {}",
66                    crate::redact::redact_command(&format!("{command:?}"))
67                )
68            })?;
69
70        Ok(Self { process })
71    }
72
73    pub fn into_inner(self) -> smol::process::Child {
74        self.process
75    }
76
77    #[cfg(not(windows))]
78    pub fn kill(&mut self) -> Result<()> {
79        let pid = self.process.id();
80        unsafe {
81            libc::killpg(pid as i32, libc::SIGKILL);
82        }
83        Ok(())
84    }
85
86    #[cfg(windows)]
87    pub fn kill(&mut self) -> Result<()> {
88        // TODO(windows): terminate the job object in kill
89        self.process.kill()?;
90        Ok(())
91    }
92}