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(|| format!("failed to spawn command {command:?}"))?;
40 Ok(Self { process })
41 }
42
43 #[cfg(windows)]
44 pub fn spawn(
45 command: std::process::Command,
46 stdin: Stdio,
47 stdout: Stdio,
48 stderr: Stdio,
49 ) -> Result<Self> {
50 // TODO(windows): create a job object and add the child process handle to it,
51 // see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
52 let mut command = smol::process::Command::from(command);
53 let process = command
54 .stdin(stdin)
55 .stdout(stdout)
56 .stderr(stderr)
57 .spawn()
58 .with_context(|| format!("failed to spawn command {command:?}"))?;
59
60 Ok(Self { process })
61 }
62
63 pub fn into_inner(self) -> smol::process::Child {
64 self.process
65 }
66
67 #[cfg(not(windows))]
68 pub fn kill(&mut self) -> Result<()> {
69 let pid = self.process.id();
70 unsafe {
71 libc::killpg(pid as i32, libc::SIGKILL);
72 }
73 Ok(())
74 }
75
76 #[cfg(windows)]
77 pub fn kill(&mut self) -> Result<()> {
78 // TODO(windows): terminate the job object in kill
79 self.process.kill()?;
80 Ok(())
81 }
82}