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