rightkit_process/
process_handle.rs1use std::{
2 io,
3 process::{Child, ExitStatus},
4 sync::{Arc, Mutex, MutexGuard},
5 thread,
6 time::{Duration, Instant, SystemTime},
7};
8
9#[derive(Debug)]
13pub struct ProcessHandle {
14 #[cfg(unix)]
15 child: Arc<Mutex<Child>>,
16 #[cfg(windows)]
17 handle: std::os::windows::io::OwnedHandle,
18 creation_time: Option<SystemTime>,
19 #[cfg(windows)]
20 creation_time_ticks: Option<u64>,
21}
22
23pub(crate) fn lock_child(child: &Mutex<Child>) -> MutexGuard<'_, Child> {
24 child.lock().unwrap_or_else(|poison| poison.into_inner())
25}
26
27impl ProcessHandle {
28 pub(crate) fn new(child: &Arc<Mutex<Child>>, spawned_at: SystemTime) -> io::Result<Self> {
29 #[cfg(unix)]
30 {
31 #[cfg(target_os = "macos")]
32 let creation_time = macos_creation_time(lock_child(child).id());
33 #[cfg(not(target_os = "macos"))]
34 let creation_time = Some(spawned_at);
35 let _ = spawned_at;
36 Ok(Self {
37 child: Arc::clone(child),
38 creation_time,
39 })
40 }
41 #[cfg(windows)]
42 {
43 use std::os::windows::io::AsHandle;
44 let handle = lock_child(child).as_handle().try_clone_to_owned()?;
45 let ticks = windows_creation_time(&handle);
46 let creation_time = ticks.and_then(|ticks| {
47 let ticks = ticks.checked_sub(116_444_736_000_000_000)?;
49 SystemTime::UNIX_EPOCH.checked_add(Duration::new(
50 ticks / 10_000_000,
51 ((ticks % 10_000_000) * 100) as u32,
52 ))
53 });
54 let _ = spawned_at;
55 Ok(Self {
56 handle,
57 creation_time,
58 creation_time_ticks: ticks,
59 })
60 }
61 }
62
63 pub fn try_clone(&self) -> io::Result<Self> {
64 Ok(Self {
65 #[cfg(unix)]
66 child: Arc::clone(&self.child),
67 #[cfg(windows)]
68 handle: self.handle.try_clone()?,
69 creation_time: self.creation_time,
70 #[cfg(windows)]
71 creation_time_ticks: self.creation_time_ticks,
72 })
73 }
74
75 pub fn creation_time(&self) -> Option<SystemTime> {
77 self.creation_time
78 }
79
80 #[cfg(windows)]
82 pub fn creation_time_ticks(&self) -> Option<u64> {
83 self.creation_time_ticks
84 }
85
86 pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
87 #[cfg(unix)]
88 {
89 lock_child(&self.child).try_wait()
90 }
91 #[cfg(windows)]
92 {
93 use std::os::windows::{io::AsRawHandle, process::ExitStatusExt};
94 use windows::Win32::{
95 Foundation::{HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT},
96 System::Threading::{GetExitCodeProcess, WaitForSingleObject},
97 };
98 let handle = HANDLE(self.handle.as_raw_handle() as _);
99 match unsafe { WaitForSingleObject(handle, 0) } {
100 WAIT_TIMEOUT => Ok(None),
101 WAIT_OBJECT_0 => {
102 let mut code = 0;
103 unsafe { GetExitCodeProcess(handle, &mut code) }.map_err(io::Error::other)?;
104 Ok(Some(ExitStatus::from_raw(code)))
105 }
106 _ => Err(io::Error::last_os_error()),
107 }
108 }
109 }
110
111 pub fn wait(&self) -> io::Result<ExitStatus> {
112 loop {
113 if let Some(status) = self.try_wait()? {
114 return Ok(status);
115 }
116 thread::sleep(Duration::from_millis(5));
117 }
118 }
119
120 pub fn wait_timeout(&self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
121 let started = Instant::now();
122 loop {
123 if let Some(status) = self.try_wait()? {
124 return Ok(Some(status));
125 }
126 if started.elapsed() >= timeout {
127 return Ok(None);
128 }
129 thread::sleep(Duration::from_millis(5).min(timeout.saturating_sub(started.elapsed())));
130 }
131 }
132}
133
134#[cfg(windows)]
135impl std::os::windows::io::AsHandle for ProcessHandle {
136 fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
137 std::os::windows::io::AsHandle::as_handle(&self.handle)
138 }
139}
140
141#[cfg(windows)]
142fn windows_creation_time(handle: &std::os::windows::io::OwnedHandle) -> Option<u64> {
143 use std::os::windows::io::AsRawHandle;
144 use windows::Win32::{
145 Foundation::{FILETIME, HANDLE},
146 System::Threading::GetProcessTimes,
147 };
148 let (mut creation, mut exit, mut kernel, mut user) = (
149 FILETIME::default(),
150 FILETIME::default(),
151 FILETIME::default(),
152 FILETIME::default(),
153 );
154 unsafe {
155 GetProcessTimes(
156 HANDLE(handle.as_raw_handle() as _),
157 &mut creation,
158 &mut exit,
159 &mut kernel,
160 &mut user,
161 )
162 }
163 .ok()?;
164 Some((u64::from(creation.dwHighDateTime) << 32) | u64::from(creation.dwLowDateTime))
165}
166
167#[cfg(target_os = "macos")]
168fn macos_creation_time(pid: u32) -> Option<SystemTime> {
169 use nix::libc;
170 let mut info = std::mem::MaybeUninit::<libc::proc_bsdinfo>::zeroed();
171 let size = std::mem::size_of::<libc::proc_bsdinfo>();
172 let written = unsafe {
174 libc::proc_pidinfo(
175 pid as i32,
176 libc::PROC_PIDTBSDINFO,
177 0,
178 info.as_mut_ptr().cast(),
179 size as i32,
180 )
181 };
182 if written != size as i32 {
183 return None;
184 }
185 let info = unsafe { info.assume_init() };
186 SystemTime::UNIX_EPOCH
187 .checked_add(Duration::from_secs(info.pbi_start_tvsec))?
188 .checked_add(Duration::from_micros(info.pbi_start_tvusec))
189}