microsandbox_utils/
process.rs1#[cfg(windows)]
4use windows_sys::Win32::Foundation::{
5 CloseHandle, ERROR_ACCESS_DENIED, GetLastError, STILL_ACTIVE,
6};
7#[cfg(windows)]
8use windows_sys::Win32::System::Threading::{
9 GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
10};
11
12pub fn pid_is_alive(pid: i32) -> bool {
23 if pid <= 0 {
24 return false;
25 }
26
27 pid_is_alive_platform(pid)
28}
29
30#[cfg(unix)]
31fn pid_is_alive_platform(pid: i32) -> bool {
32 if !pid_exists(pid) {
33 return false;
34 }
35
36 !pid_is_zombie(pid).unwrap_or(false)
37}
38
39#[cfg(windows)]
40fn pid_is_alive_platform(pid: i32) -> bool {
41 let Ok(pid) = u32::try_from(pid) else {
42 return false;
43 };
44
45 let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
46 if handle.is_null() {
47 let error = unsafe { GetLastError() };
50 return error == ERROR_ACCESS_DENIED;
51 }
52
53 let mut exit_code = 0;
54 let ok = unsafe { GetExitCodeProcess(handle, &mut exit_code) };
55 unsafe { CloseHandle(handle) };
56
57 ok != 0 && exit_code == STILL_ACTIVE as u32
58}
59
60#[cfg(not(any(unix, windows)))]
61fn pid_is_alive_platform(_pid: i32) -> bool {
62 false
63}
64
65#[cfg(unix)]
67pub fn pid_exists(pid: i32) -> bool {
68 if pid <= 0 {
69 return false;
70 }
71
72 let result = unsafe { libc::kill(pid, 0) };
73 if result == 0 {
74 return true;
75 }
76
77 matches!(
78 std::io::Error::last_os_error().raw_os_error(),
79 Some(code) if code == libc::EPERM
80 )
81}
82
83#[cfg(windows)]
85pub fn pid_exists(pid: i32) -> bool {
86 let Ok(pid) = u32::try_from(pid) else {
87 return false;
88 };
89
90 let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
91 if handle.is_null() {
92 let error = unsafe { GetLastError() };
93 return error == ERROR_ACCESS_DENIED;
94 }
95
96 unsafe { CloseHandle(handle) };
97 true
98}
99
100#[cfg(not(any(unix, windows)))]
102pub fn pid_exists(_pid: i32) -> bool {
103 false
104}
105
106pub fn pid_is_zombie(pid: i32) -> Option<bool> {
111 if pid <= 0 {
112 return Some(false);
113 }
114
115 pid_is_zombie_platform(pid)
116}
117
118#[cfg(target_os = "linux")]
119fn pid_is_zombie_platform(pid: i32) -> Option<bool> {
120 let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
121 let close_paren = stat.rfind(')')?;
122 let state = stat
123 .get(close_paren + 1..)?
124 .bytes()
125 .find(|byte| !byte.is_ascii_whitespace())?;
126 Some(state == b'Z')
127}
128
129#[cfg(target_os = "macos")]
130fn pid_is_zombie_platform(pid: i32) -> Option<bool> {
131 const KINFO_PROC_P_STAT_OFFSET: usize = 36;
136
137 let mut mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
138 let mut len: libc::size_t = 0;
139 let size_result = unsafe {
140 libc::sysctl(
141 mib.as_mut_ptr(),
142 mib.len() as libc::c_uint,
143 std::ptr::null_mut(),
144 &mut len,
145 std::ptr::null_mut(),
146 0,
147 )
148 };
149 if size_result != 0 || len <= KINFO_PROC_P_STAT_OFFSET {
150 return None;
151 }
152
153 let mut buf = vec![0u8; len];
154 let read_result = unsafe {
155 libc::sysctl(
156 mib.as_mut_ptr(),
157 mib.len() as libc::c_uint,
158 buf.as_mut_ptr().cast::<libc::c_void>(),
159 &mut len,
160 std::ptr::null_mut(),
161 0,
162 )
163 };
164 if read_result != 0 || len <= KINFO_PROC_P_STAT_OFFSET {
165 return None;
166 }
167
168 Some(buf[KINFO_PROC_P_STAT_OFFSET] == libc::SZOMB as u8)
169}
170
171#[cfg(not(any(target_os = "linux", target_os = "macos")))]
172fn pid_is_zombie_platform(_pid: i32) -> Option<bool> {
173 None
174}
175
176#[cfg(all(test, unix))]
181mod tests {
182 use std::process::Command;
183 use std::time::{Duration, Instant};
184
185 use super::*;
186
187 #[test]
188 fn pid_liveness_treats_zombies_as_dead() {
189 let mut child = Command::new("sh")
190 .arg("-c")
191 .arg("exit 0")
192 .spawn()
193 .expect("spawn short-lived child");
194 let pid = child.id() as i32;
195 let deadline = Instant::now() + Duration::from_secs(5);
196
197 while Instant::now() < deadline {
198 if pid_is_zombie(pid) == Some(true) {
199 assert!(
200 !pid_is_alive(pid),
201 "zombie process should not count as alive"
202 );
203 let _ = child.wait();
204 return;
205 }
206 std::thread::sleep(Duration::from_millis(10));
207 }
208
209 let status = child.try_wait().expect("poll child");
210 let _ = child.wait();
211 panic!("child did not become observable as a zombie; last status: {status:?}");
212 }
213}