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