rightkit_child_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 = crate::unix_child::UnixChild;
9#[cfg(windows)]
10type PlatformChild = crate::spawn_windows::WindowsChild;
11#[cfg(unix)]
12use std::time::Instant;
13
14#[derive(Debug)]
29pub struct ProcessHandle {
30 #[cfg(unix)]
31 child: Arc<Mutex<PlatformChild>>,
32 #[cfg(windows)]
33 handle: std::os::windows::io::OwnedHandle,
34 creation_time: Option<SystemTime>,
35 #[cfg(windows)]
36 creation_time_ticks: Option<u64>,
37}
38
39pub(crate) fn lock_child<T>(child: &Mutex<T>) -> MutexGuard<'_, T> {
40 child.lock().unwrap_or_else(|poison| poison.into_inner())
41}
42
43impl ProcessHandle {
44 pub(crate) fn new(
45 child: &Arc<Mutex<PlatformChild>>,
46 spawned_at: SystemTime,
47 ) -> io::Result<Self> {
48 #[cfg(unix)]
49 {
50 #[cfg(target_os = "macos")]
51 let creation_time = macos_creation_time(lock_child(child).id());
52 #[cfg(not(target_os = "macos"))]
53 let creation_time = Some(spawned_at);
54 let _ = spawned_at;
55 Ok(Self {
56 child: Arc::clone(child),
57 creation_time,
58 })
59 }
60 #[cfg(windows)]
61 {
62 use std::os::windows::io::AsHandle;
63 let handle = lock_child(child).as_handle().try_clone_to_owned()?;
64 let ticks = windows_creation_time(&handle);
65 let creation_time = ticks.and_then(|ticks| {
66 let ticks = ticks.checked_sub(116_444_736_000_000_000)?;
68 SystemTime::UNIX_EPOCH.checked_add(Duration::new(
69 ticks / 10_000_000,
70 ((ticks % 10_000_000) * 100) as u32,
71 ))
72 });
73 let _ = spawned_at;
74 Ok(Self {
75 handle,
76 creation_time,
77 creation_time_ticks: ticks,
78 })
79 }
80 }
81
82 pub fn try_clone(&self) -> io::Result<Self> {
83 Ok(Self {
84 #[cfg(unix)]
85 child: Arc::clone(&self.child),
86 #[cfg(windows)]
87 handle: self.handle.try_clone()?,
88 creation_time: self.creation_time,
89 #[cfg(windows)]
90 creation_time_ticks: self.creation_time_ticks,
91 })
92 }
93
94 pub fn creation_time(&self) -> Option<SystemTime> {
96 self.creation_time
97 }
98
99 #[cfg(windows)]
101 pub fn creation_time_ticks(&self) -> Option<u64> {
102 self.creation_time_ticks
103 }
104
105 pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
106 #[cfg(unix)]
107 {
108 lock_child(&self.child).try_wait()
109 }
110 #[cfg(windows)]
111 {
112 self.windows_wait(0)
113 }
114 }
115
116 pub fn wait(&self) -> io::Result<ExitStatus> {
127 #[cfg(unix)]
128 {
129 self.wait_until(None)?
130 .ok_or_else(|| io::Error::other("infinite process wait unexpectedly timed out"))
131 }
132 #[cfg(windows)]
133 {
134 self.windows_wait(u32::MAX)?
135 .ok_or_else(|| io::Error::other("infinite process wait unexpectedly timed out"))
136 }
137 }
138
139 #[cfg(unix)]
141 fn wait_until(&self, deadline: Option<Instant>) -> io::Result<Option<ExitStatus>> {
142 crate::unix_child::wait_shared(&self.child, deadline)
143 }
144
145 pub fn wait_exited(&self) -> io::Result<()> {
158 #[cfg(unix)]
159 {
160 if !crate::unix_child::CAN_OBSERVE_WITHOUT_REAPING {
161 return self.wait().map(|_| ());
162 }
163 self.observe_exit(None).map(|_| ())
164 }
165 #[cfg(windows)]
166 {
167 self.windows_wait(u32::MAX)?
168 .map(|_| ())
169 .ok_or_else(|| io::Error::other("infinite process wait unexpectedly timed out"))
170 }
171 }
172
173 pub fn wait_exited_timeout(&self, timeout: Duration) -> io::Result<bool> {
179 #[cfg(unix)]
180 {
181 if !crate::unix_child::CAN_OBSERVE_WITHOUT_REAPING {
182 return self.wait_timeout(timeout).map(|status| status.is_some());
183 }
184 self.observe_exit(Some(timeout))
185 }
186 #[cfg(windows)]
187 {
188 self.wait_timeout(timeout).map(|status| status.is_some())
189 }
190 }
191
192 #[cfg(unix)]
194 fn observe_exit(&self, limit: Option<Duration>) -> io::Result<bool> {
195 let deadline = limit.and_then(|limit| Instant::now().checked_add(limit));
196 crate::unix_child::observe_shared(&self.child, deadline)
197 }
198
199 pub fn wait_timeout(&self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
200 #[cfg(windows)]
201 {
202 let started = std::time::Instant::now();
203 loop {
204 let remaining = timeout.saturating_sub(started.elapsed());
205 let millis = remaining.as_millis()
207 + u128::from(!remaining.subsec_nanos().is_multiple_of(1_000_000));
208 let millis = millis.min(u128::from(u32::MAX - 1)) as u32;
209 if let Some(status) = self.windows_wait(millis)? {
210 return Ok(Some(status));
211 }
212 if started.elapsed() >= timeout {
213 return Ok(None);
214 }
215 }
216 }
217 #[cfg(unix)]
218 {
219 self.wait_until(Instant::now().checked_add(timeout))
220 }
221 }
222
223 #[cfg(windows)]
224 fn windows_wait(&self, millis: u32) -> io::Result<Option<ExitStatus>> {
225 use std::os::windows::{io::AsRawHandle, process::ExitStatusExt};
226 use windows::Win32::{
227 Foundation::{HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT},
228 System::Threading::{GetExitCodeProcess, WaitForSingleObject},
229 };
230 let handle = HANDLE(self.handle.as_raw_handle() as _);
231 match unsafe { WaitForSingleObject(handle, millis) } {
232 WAIT_TIMEOUT => Ok(None),
233 WAIT_OBJECT_0 => {
234 let mut code = 0;
235 unsafe { GetExitCodeProcess(handle, &mut code) }
236 .map_err(crate::process_windows::os_error)?;
237 Ok(Some(ExitStatus::from_raw(code)))
238 }
239 _ => Err(io::Error::last_os_error()),
240 }
241 }
242}
243
244#[cfg(windows)]
245impl std::os::windows::io::AsHandle for ProcessHandle {
246 fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
247 std::os::windows::io::AsHandle::as_handle(&self.handle)
248 }
249}
250
251#[cfg(windows)]
252fn windows_creation_time(handle: &std::os::windows::io::OwnedHandle) -> Option<u64> {
253 use std::os::windows::io::AsRawHandle;
254 use windows::Win32::{
255 Foundation::{FILETIME, HANDLE},
256 System::Threading::GetProcessTimes,
257 };
258 let (mut creation, mut exit, mut kernel, mut user) = (
259 FILETIME::default(),
260 FILETIME::default(),
261 FILETIME::default(),
262 FILETIME::default(),
263 );
264 unsafe {
265 GetProcessTimes(
266 HANDLE(handle.as_raw_handle() as _),
267 &mut creation,
268 &mut exit,
269 &mut kernel,
270 &mut user,
271 )
272 }
273 .ok()?;
274 Some((u64::from(creation.dwHighDateTime) << 32) | u64::from(creation.dwLowDateTime))
275}
276
277#[cfg(target_os = "macos")]
278fn macos_creation_time(pid: u32) -> Option<SystemTime> {
279 let mut info = std::mem::MaybeUninit::<libc::proc_bsdinfo>::zeroed();
280 let size = std::mem::size_of::<libc::proc_bsdinfo>();
281 let written = unsafe {
283 libc::proc_pidinfo(
284 pid as i32,
285 libc::PROC_PIDTBSDINFO,
286 0,
287 info.as_mut_ptr().cast(),
288 size as i32,
289 )
290 };
291 if written != size as i32 {
292 return None;
293 }
294 let info = unsafe { info.assume_init() };
295 SystemTime::UNIX_EPOCH
296 .checked_add(Duration::from_secs(info.pbi_start_tvsec))?
297 .checked_add(Duration::from_micros(info.pbi_start_tvusec))
298}