1mod capture;
4use crate::worker::{CancelToken, Failure, FailureKind};
5pub use capture::{capture, capture_with, CaptureError, CapturePolicy, CommandOutput, StdinPolicy};
6use parking_lot::Mutex;
7use std::io;
8use std::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus};
9use std::sync::Arc;
10
11#[derive(Default)]
12struct Group {
13 pid: Option<u32>,
14 cancelled: bool,
15}
16impl Group {
17 fn signal(&self) -> Result<(), Failure> {
18 #[cfg(unix)]
19 if let Some(pid) = self.pid {
20 if unsafe { libc::kill(-(pid as libc::pid_t), libc::SIGKILL) } == -1 {
24 let error = io::Error::last_os_error();
25 if error.raw_os_error() != Some(libc::ESRCH) {
26 return Err(Failure::new(
27 FailureKind::Io,
28 format!("kill process group: {error}"),
29 ));
30 }
31 }
32 }
33 Ok(())
34 }
35 fn cancel(&mut self) -> Result<(), Failure> {
36 self.cancelled = true;
37 self.signal()
38 }
39}
40
41pub struct OwnedProcess {
44 child: Child,
45 group: Arc<Mutex<Group>>,
46 token: CancelToken,
47 reaped: bool,
48}
49impl OwnedProcess {
50 pub fn spawn(command: &mut Command, token: &CancelToken) -> Result<Self, Failure> {
51 #[cfg(not(unix))]
52 return Err(Failure::new(
53 FailureKind::Unavailable,
54 "process-group supervision requires Unix",
55 ));
56 #[cfg(unix)]
57 {
58 use std::os::unix::process::CommandExt;
59 let group = Arc::new(Mutex::new(Group::default()));
60 let callback = group.clone();
61 token.register_cancel_resource(move || callback.lock().cancel())?;
62 if token.is_cancelled() {
63 token.clear_cancel_resource();
64 return Err(Failure::new(
65 FailureKind::Unavailable,
66 "process cancelled before spawn",
67 ));
68 }
69 let child = match command.process_group(0).spawn() {
70 Ok(child) => child,
71 Err(error) => {
72 token.clear_cancel_resource();
73 return Err(Failure::new(FailureKind::Spawn, error.to_string()));
74 }
75 };
76 let process = Self {
77 child,
78 group,
79 token: token.clone(),
80 reaped: false,
81 };
82 {
83 let mut group = process.group.lock();
84 group.pid = Some(process.child.id());
85 if group.cancelled || token.is_cancelled() {
87 group.cancel()?;
88 }
89 }
90 Ok(process)
91 }
92 }
93 pub fn take_stdin(&mut self) -> Option<ChildStdin> {
94 self.child.stdin.take()
95 }
96 pub fn take_stdout(&mut self) -> Option<ChildStdout> {
97 self.child.stdout.take()
98 }
99 pub fn take_stderr(&mut self) -> Option<ChildStderr> {
100 self.child.stderr.take()
101 }
102 pub fn terminate(&mut self) -> Result<(), Failure> {
103 self.group.lock().signal()
104 }
105
106 pub fn has_exited(&self) -> Result<bool, Failure> {
109 #[cfg(not(unix))]
110 return Err(Failure::new(
111 FailureKind::Unavailable,
112 "process supervision requires Unix",
113 ));
114 #[cfg(unix)]
115 {
116 let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
119 loop {
120 let result = unsafe {
121 libc::waitid(
122 libc::P_PID,
123 self.child.id() as libc::id_t,
124 &mut info,
125 libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
126 )
127 };
128 if result == 0 {
129 return Ok(unsafe { info.si_pid() } != 0);
131 }
132 let error = io::Error::last_os_error();
133 if error.kind() != io::ErrorKind::Interrupted {
134 return Err(Failure::new(FailureKind::Wait, error.to_string()));
135 }
136 }
137 }
138 }
139 pub fn wait(&mut self) -> Result<ExitStatus, Failure> {
142 while !self.reaped && !self.has_exited()? {
145 std::thread::park_timeout(std::time::Duration::from_millis(20));
146 }
147 self.group.lock().pid = None;
148 loop {
149 match self.child.wait() {
150 Ok(status) => {
151 self.reaped = true;
152 self.token.clear_cancel_resource();
153 return Ok(status);
154 }
155 Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
156 Err(error) => return Err(Failure::new(FailureKind::Wait, error.to_string())),
157 }
158 }
159 }
160}
161impl Drop for OwnedProcess {
162 fn drop(&mut self) {
163 if !self.reaped {
164 let _ = self.terminate();
165 let _ = self.child.kill();
167 let _ = self.wait();
168 }
169 self.token.clear_cancel_resource();
170 }
171}