Skip to main content

rightkit_process/
process_handle.rs

1use std::{
2    io,
3    process::{Child, ExitStatus},
4    sync::{Arc, Mutex, MutexGuard},
5    thread,
6    time::{Duration, Instant, SystemTime},
7};
8
9/// An independent exit reader. On Unix it shares std's cached reap state;
10/// on Windows it owns a duplicated process handle, never a borrowed handle value.
11/// Keeping this reader alive does not keep an owned process tree alive.
12#[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                // FILETIME epoch precedes Unix by 11,644,473,600 seconds.
48                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    /// Native creation time on Windows/macOS; spawn observation time on other Unix hosts.
76    pub fn creation_time(&self) -> Option<SystemTime> {
77        self.creation_time
78    }
79
80    /// Native Windows FILETIME, in 100 ns ticks since 1601-01-01 UTC.
81    #[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    // libproc writes exactly proc_bsdinfo for this flavor; check size before reading it.
173    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}