upstream_rs/utils/platform/
process_id.rs1use std::process;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct ProcessIdentity {
5 pub start_token: String,
6}
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ProcessProbe {
10 pub exists: bool,
11 pub start_token: Option<String>,
12}
13
14pub fn current_process_identity() -> Option<ProcessIdentity> {
15 let probe = probe_process(process::id());
16 probe
17 .exists
18 .then_some(probe.start_token)
19 .flatten()
20 .map(|start_token| ProcessIdentity { start_token })
21}
22
23pub fn probe_process(pid: u32) -> ProcessProbe {
24 if pid == 0 {
25 return ProcessProbe {
26 exists: false,
27 start_token: None,
28 };
29 }
30
31 #[cfg(target_os = "linux")]
32 {
33 return probe_process_linux(pid);
34 }
35
36 #[cfg(all(unix, not(target_os = "linux")))]
37 {
38 return probe_process_unix(pid);
39 }
40
41 #[cfg(windows)]
42 {
43 return probe_process_windows(pid);
44 }
45
46 #[allow(unreachable_code)]
47 ProcessProbe {
48 exists: true,
49 start_token: None,
50 }
51}
52
53#[cfg(target_os = "linux")]
54fn probe_process_linux(pid: u32) -> ProcessProbe {
55 use std::fs;
56 use std::path::Path;
57
58 let proc_dir = Path::new("/proc").join(pid.to_string());
59 if !proc_dir.exists() {
60 return ProcessProbe {
61 exists: false,
62 start_token: None,
63 };
64 }
65
66 let stat_path = proc_dir.join("stat");
67 let start_token = fs::read_to_string(stat_path)
68 .ok()
69 .and_then(|raw| parse_linux_start_time_ticks(&raw))
70 .map(|ticks| ticks.to_string());
71
72 ProcessProbe {
73 exists: true,
74 start_token,
75 }
76}
77
78#[cfg(target_os = "linux")]
79fn parse_linux_start_time_ticks(raw_stat: &str) -> Option<u64> {
80 let close_paren = raw_stat.rfind(") ")?;
83 let rest = &raw_stat[(close_paren + 2)..];
84 let fields: Vec<&str> = rest.split_whitespace().collect();
85 fields.get(19)?.parse::<u64>().ok()
86}
87
88#[cfg(all(unix, not(target_os = "linux")))]
89fn probe_process_unix(pid: u32) -> ProcessProbe {
90 use std::process::Command;
91
92 let exists = kill_zero_exists(pid);
93 if !exists {
94 return ProcessProbe {
95 exists: false,
96 start_token: None,
97 };
98 }
99
100 let start_token = Command::new("ps")
101 .args(["-o", "lstart=", "-p", &pid.to_string()])
102 .output()
103 .ok()
104 .filter(|out| out.status.success())
105 .and_then(|out| String::from_utf8(out.stdout).ok())
106 .map(|stdout| stdout.trim().to_string())
107 .filter(|token| !token.is_empty());
108
109 ProcessProbe {
110 exists: true,
111 start_token,
112 }
113}
114
115#[cfg(all(unix, not(target_os = "linux")))]
116fn kill_zero_exists(pid: u32) -> bool {
117 use std::process::Command;
118
119 match Command::new("kill").arg("-0").arg(pid.to_string()).output() {
120 Ok(output) if output.status.success() => true,
121 Ok(output) => {
122 let stderr = String::from_utf8_lossy(&output.stderr).to_ascii_lowercase();
123 if stderr.contains("no such process") {
124 false
125 } else {
126 true
128 }
129 }
130 Err(_) => true,
131 }
132}
133
134#[cfg(windows)]
135fn probe_process_windows(pid: u32) -> ProcessProbe {
136 use std::mem::MaybeUninit;
137 use winapi::shared::minwindef::{DWORD, FALSE, FILETIME};
138 use winapi::shared::winerror::{ERROR_ACCESS_DENIED, ERROR_INVALID_PARAMETER};
139 use winapi::um::errhandlingapi::GetLastError;
140 use winapi::um::handleapi::CloseHandle;
141 use winapi::um::minwinbase::STILL_ACTIVE;
142 use winapi::um::processthreadsapi::{GetExitCodeProcess, GetProcessTimes, OpenProcess};
143 use winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION;
144
145 unsafe {
146 let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD);
147 if handle.is_null() {
148 let err = GetLastError();
149 return match err {
150 ERROR_INVALID_PARAMETER => ProcessProbe {
151 exists: false,
152 start_token: None,
153 },
154 ERROR_ACCESS_DENIED => ProcessProbe {
155 exists: true,
156 start_token: None,
157 },
158 _ => ProcessProbe {
159 exists: true,
160 start_token: None,
161 },
162 };
163 }
164
165 let mut exit_code: DWORD = 0;
166 if GetExitCodeProcess(handle, &mut exit_code as *mut DWORD) == 0 {
167 CloseHandle(handle);
168 return ProcessProbe {
169 exists: true,
170 start_token: None,
171 };
172 }
173
174 if exit_code != STILL_ACTIVE {
175 CloseHandle(handle);
176 return ProcessProbe {
177 exists: false,
178 start_token: None,
179 };
180 }
181
182 let mut creation = MaybeUninit::<FILETIME>::uninit();
183 let mut exit = MaybeUninit::<FILETIME>::uninit();
184 let mut kernel = MaybeUninit::<FILETIME>::uninit();
185 let mut user = MaybeUninit::<FILETIME>::uninit();
186
187 let start_token = if GetProcessTimes(
188 handle,
189 creation.as_mut_ptr(),
190 exit.as_mut_ptr(),
191 kernel.as_mut_ptr(),
192 user.as_mut_ptr(),
193 ) != 0
194 {
195 let creation = creation.assume_init();
196 let ticks = ((creation.dwHighDateTime as u64) << 32) | (creation.dwLowDateTime as u64);
197 Some(ticks.to_string())
198 } else {
199 None
200 };
201
202 CloseHandle(handle);
203 ProcessProbe {
204 exists: true,
205 start_token,
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::{current_process_identity, probe_process};
213
214 #[test]
215 fn probe_current_process_reports_exists() {
216 let probe = probe_process(std::process::id());
217 assert!(probe.exists);
218 }
219
220 #[test]
221 fn probe_pid_zero_reports_not_exists() {
222 let probe = probe_process(0);
223 assert!(!probe.exists);
224 }
225
226 #[test]
227 fn current_identity_token_is_non_empty_when_available() {
228 if let Some(identity) = current_process_identity() {
229 assert!(!identity.start_token.trim().is_empty());
230 }
231 }
232
233 #[cfg(target_os = "linux")]
234 #[test]
235 fn parse_linux_start_time_from_stat_line() {
236 let raw = "1234 (process name) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 212121 20";
237 let parsed = super::parse_linux_start_time_ticks(raw);
238 assert_eq!(parsed, Some(212121));
239 }
240}