rightkit_process/
process_handle.rs1use std::{
2 io,
3 process::ExitStatus,
4 sync::{Arc, Mutex, MutexGuard},
5 time::{Duration, SystemTime},
6};
7#[cfg(unix)]
8type PlatformChild = std::process::Child;
9#[cfg(windows)]
10type PlatformChild = crate::spawn_windows::WindowsChild;
11#[cfg(unix)]
12use std::{thread, time::Instant};
13
14#[derive(Debug)]
18pub struct ProcessHandle {
19 #[cfg(unix)]
20 child: Arc<Mutex<PlatformChild>>,
21 #[cfg(unix)]
22 pid: u32,
23 #[cfg(windows)]
24 handle: std::os::windows::io::OwnedHandle,
25 creation_time: Option<SystemTime>,
26 #[cfg(windows)]
27 creation_time_ticks: Option<u64>,
28}
29
30pub(crate) fn lock_child<T>(child: &Mutex<T>) -> MutexGuard<'_, T> {
31 child.lock().unwrap_or_else(|poison| poison.into_inner())
32}
33
34impl ProcessHandle {
35 pub(crate) fn new(
36 child: &Arc<Mutex<PlatformChild>>,
37 spawned_at: SystemTime,
38 ) -> io::Result<Self> {
39 #[cfg(unix)]
40 {
41 #[cfg(target_os = "macos")]
42 let creation_time = macos_creation_time(lock_child(child).id());
43 #[cfg(not(target_os = "macos"))]
44 let creation_time = Some(spawned_at);
45 let _ = spawned_at;
46 Ok(Self {
47 child: Arc::clone(child),
48 pid: lock_child(child).id(),
49 creation_time,
50 })
51 }
52 #[cfg(windows)]
53 {
54 use std::os::windows::io::AsHandle;
55 let handle = lock_child(child).as_handle().try_clone_to_owned()?;
56 let ticks = windows_creation_time(&handle);
57 let creation_time = ticks.and_then(|ticks| {
58 let ticks = ticks.checked_sub(116_444_736_000_000_000)?;
60 SystemTime::UNIX_EPOCH.checked_add(Duration::new(
61 ticks / 10_000_000,
62 ((ticks % 10_000_000) * 100) as u32,
63 ))
64 });
65 let _ = spawned_at;
66 Ok(Self {
67 handle,
68 creation_time,
69 creation_time_ticks: ticks,
70 })
71 }
72 }
73
74 pub fn try_clone(&self) -> io::Result<Self> {
75 Ok(Self {
76 #[cfg(unix)]
77 child: Arc::clone(&self.child),
78 #[cfg(unix)]
79 pid: self.pid,
80 #[cfg(windows)]
81 handle: self.handle.try_clone()?,
82 creation_time: self.creation_time,
83 #[cfg(windows)]
84 creation_time_ticks: self.creation_time_ticks,
85 })
86 }
87
88 pub fn creation_time(&self) -> Option<SystemTime> {
90 self.creation_time
91 }
92
93 #[cfg(windows)]
95 pub fn creation_time_ticks(&self) -> Option<u64> {
96 self.creation_time_ticks
97 }
98
99 pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
100 #[cfg(unix)]
101 {
102 lock_child(&self.child).try_wait()
103 }
104 #[cfg(windows)]
105 {
106 self.windows_wait(0)
107 }
108 }
109
110 pub fn wait(&self) -> io::Result<ExitStatus> {
111 #[cfg(unix)]
112 {
113 #[cfg(any(
114 target_os = "linux",
115 target_os = "android",
116 target_os = "macos",
117 target_os = "ios",
118 target_os = "freebsd",
119 target_os = "openbsd",
120 target_os = "netbsd",
121 target_os = "dragonfly",
122 target_os = "solaris",
123 target_os = "illumos"
124 ))]
125 {
126 if let Some(status) = self.try_wait()? {
127 return Ok(status);
128 }
129 loop {
130 let mut info = std::mem::MaybeUninit::<nix::libc::siginfo_t>::uninit();
131 if unsafe {
135 nix::libc::waitid(
136 nix::libc::P_PID,
137 self.pid as nix::libc::id_t,
138 info.as_mut_ptr(),
139 nix::libc::WEXITED | nix::libc::WNOWAIT,
140 )
141 } == 0
142 {
143 return self.try_wait()?.ok_or_else(|| {
144 io::Error::other("completed process wait had no exit status")
145 });
146 }
147 let error = io::Error::last_os_error();
148 if error.raw_os_error() == Some(nix::libc::EINTR) {
149 continue;
150 }
151 if let Some(status) = self.try_wait()? {
154 return Ok(status);
155 }
156 return Err(error);
157 }
158 }
159 #[cfg(not(any(
160 target_os = "linux",
161 target_os = "android",
162 target_os = "macos",
163 target_os = "ios",
164 target_os = "freebsd",
165 target_os = "openbsd",
166 target_os = "netbsd",
167 target_os = "dragonfly",
168 target_os = "solaris",
169 target_os = "illumos"
170 )))]
171 {
172 let mut child = lock_child(&self.child);
174 let stdin = child.stdin.take();
177 let result = child.wait();
178 child.stdin = stdin;
179 result
180 }
181 }
182 #[cfg(windows)]
183 {
184 self.windows_wait(u32::MAX)?
185 .ok_or_else(|| io::Error::other("infinite process wait unexpectedly timed out"))
186 }
187 }
188
189 pub fn wait_timeout(&self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
190 #[cfg(windows)]
191 {
192 let started = std::time::Instant::now();
193 loop {
194 let remaining = timeout.saturating_sub(started.elapsed());
195 let millis = remaining.as_millis()
197 + u128::from(!remaining.subsec_nanos().is_multiple_of(1_000_000));
198 let millis = millis.min(u128::from(u32::MAX - 1)) as u32;
199 if let Some(status) = self.windows_wait(millis)? {
200 return Ok(Some(status));
201 }
202 if started.elapsed() >= timeout {
203 return Ok(None);
204 }
205 }
206 }
207 #[cfg(unix)]
208 {
209 let started = Instant::now();
210 loop {
211 if let Some(status) = self.try_wait()? {
212 return Ok(Some(status));
213 }
214 if started.elapsed() >= timeout {
215 return Ok(None);
216 }
217 thread::sleep(
218 Duration::from_millis(5).min(timeout.saturating_sub(started.elapsed())),
219 );
220 }
221 }
222 }
223
224 #[cfg(windows)]
225 fn windows_wait(&self, millis: u32) -> io::Result<Option<ExitStatus>> {
226 use std::os::windows::{io::AsRawHandle, process::ExitStatusExt};
227 use windows::Win32::{
228 Foundation::{HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT},
229 System::Threading::{GetExitCodeProcess, WaitForSingleObject},
230 };
231 let handle = HANDLE(self.handle.as_raw_handle() as _);
232 match unsafe { WaitForSingleObject(handle, millis) } {
233 WAIT_TIMEOUT => Ok(None),
234 WAIT_OBJECT_0 => {
235 let mut code = 0;
236 unsafe { GetExitCodeProcess(handle, &mut code) }
237 .map_err(crate::process_windows::os_error)?;
238 Ok(Some(ExitStatus::from_raw(code)))
239 }
240 _ => Err(io::Error::last_os_error()),
241 }
242 }
243}
244
245#[cfg(windows)]
246impl std::os::windows::io::AsHandle for ProcessHandle {
247 fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
248 std::os::windows::io::AsHandle::as_handle(&self.handle)
249 }
250}
251
252#[cfg(windows)]
253fn windows_creation_time(handle: &std::os::windows::io::OwnedHandle) -> Option<u64> {
254 use std::os::windows::io::AsRawHandle;
255 use windows::Win32::{
256 Foundation::{FILETIME, HANDLE},
257 System::Threading::GetProcessTimes,
258 };
259 let (mut creation, mut exit, mut kernel, mut user) = (
260 FILETIME::default(),
261 FILETIME::default(),
262 FILETIME::default(),
263 FILETIME::default(),
264 );
265 unsafe {
266 GetProcessTimes(
267 HANDLE(handle.as_raw_handle() as _),
268 &mut creation,
269 &mut exit,
270 &mut kernel,
271 &mut user,
272 )
273 }
274 .ok()?;
275 Some((u64::from(creation.dwHighDateTime) << 32) | u64::from(creation.dwLowDateTime))
276}
277
278#[cfg(target_os = "macos")]
279fn macos_creation_time(pid: u32) -> Option<SystemTime> {
280 use nix::libc;
281 let mut info = std::mem::MaybeUninit::<libc::proc_bsdinfo>::zeroed();
282 let size = std::mem::size_of::<libc::proc_bsdinfo>();
283 let written = unsafe {
285 libc::proc_pidinfo(
286 pid as i32,
287 libc::PROC_PIDTBSDINFO,
288 0,
289 info.as_mut_ptr().cast(),
290 size as i32,
291 )
292 };
293 if written != size as i32 {
294 return None;
295 }
296 let info = unsafe { info.assume_init() };
297 SystemTime::UNIX_EPOCH
298 .checked_add(Duration::from_secs(info.pbi_start_tvsec))?
299 .checked_add(Duration::from_micros(info.pbi_start_tvusec))
300}