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}