lean_ctx/core/value/
host.rs1pub fn host_pid() -> Option<u32> {
13 #[cfg(unix)]
14 {
15 let ppid = unsafe { libc::getppid() } as u32;
17 (ppid > 1).then_some(ppid)
18 }
19 #[cfg(windows)]
20 {
21 parent_table()
22 .get(&std::process::id())
23 .copied()
24 .filter(|&ppid| ppid > 1)
25 }
26 #[cfg(not(any(unix, windows)))]
27 {
28 None
29 }
30}
31
32const MAX_DEPTH: usize = 8;
35
36pub fn ancestors() -> Vec<u32> {
38 let parents = parent_table();
39 let mut chain = Vec::new();
40 let mut pid = std::process::id();
41 while let Some(&ppid) = parents.get(&pid) {
42 if ppid <= 1 || chain.contains(&ppid) || chain.len() >= MAX_DEPTH {
43 break;
44 }
45 chain.push(ppid);
46 pid = ppid;
47 }
48 chain
49}
50
51#[cfg(target_os = "linux")]
53fn parent_table() -> std::collections::HashMap<u32, u32> {
54 let mut table = std::collections::HashMap::new();
55 let mut pid = std::process::id();
56 for _ in 0..=MAX_DEPTH {
57 let Some(ppid) = std::fs::read_to_string(format!("/proc/{pid}/stat"))
58 .ok()
59 .and_then(|stat| parse_proc_stat_ppid(&stat))
60 else {
61 break;
62 };
63 table.insert(pid, ppid);
64 if ppid <= 1 {
65 break;
66 }
67 pid = ppid;
68 }
69 table
70}
71
72#[cfg(all(unix, not(target_os = "linux")))]
73fn parent_table() -> std::collections::HashMap<u32, u32> {
74 std::process::Command::new("ps")
75 .args(["-A", "-o", "pid=,ppid="])
76 .stderr(std::process::Stdio::null())
77 .output()
78 .ok()
79 .filter(|out| out.status.success())
80 .map(|out| parse_ps_table(&String::from_utf8_lossy(&out.stdout)))
81 .unwrap_or_default()
82}
83
84#[cfg(windows)]
87fn parent_table() -> std::collections::HashMap<u32, u32> {
88 use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
89 use windows_sys::Win32::System::Diagnostics::ToolHelp::{
90 CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
91 TH32CS_SNAPPROCESS,
92 };
93
94 let mut table = std::collections::HashMap::new();
95 let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
97 if snapshot == INVALID_HANDLE_VALUE {
98 return table;
99 }
100 let mut entry = PROCESSENTRY32W {
101 dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
102 ..PROCESSENTRY32W::default()
103 };
104 let mut has_entry = unsafe { Process32FirstW(snapshot, &raw mut entry) } != 0;
106 while has_entry {
107 table.insert(entry.th32ProcessID, entry.th32ParentProcessID);
108 has_entry = unsafe { Process32NextW(snapshot, &raw mut entry) } != 0;
110 }
111 unsafe { CloseHandle(snapshot) };
113 table
114}
115
116#[cfg(not(any(unix, windows)))]
117fn parent_table() -> std::collections::HashMap<u32, u32> {
118 std::collections::HashMap::new()
119}
120
121#[cfg(any(test, all(unix, not(target_os = "linux"))))]
123fn parse_ps_table(text: &str) -> std::collections::HashMap<u32, u32> {
124 text.lines()
125 .filter_map(|line| {
126 let mut cols = line.split_whitespace();
127 Some((cols.next()?.parse().ok()?, cols.next()?.parse().ok()?))
128 })
129 .collect()
130}
131
132#[cfg(any(test, target_os = "linux"))]
135fn parse_proc_stat_ppid(stat: &str) -> Option<u32> {
136 let rest = &stat[stat.rfind(')')? + 1..];
137 rest.split_whitespace().nth(1)?.parse().ok()
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn ps_rows_parse_and_junk_is_skipped() {
146 let table = parse_ps_table(" 101 1\n 202 101\nPID PPID\n\n");
147 assert_eq!(table.get(&202), Some(&101));
148 assert_eq!(table.get(&101), Some(&1));
149 assert_eq!(table.len(), 2);
150 }
151
152 #[test]
153 fn proc_stat_ppid_survives_odd_command_names() {
154 assert_eq!(parse_proc_stat_ppid("42 (a) b (c) S 7 42 42"), Some(7));
155 assert_eq!(parse_proc_stat_ppid("no parens"), None);
156 }
157
158 #[cfg(any(unix, windows))]
159 #[test]
160 fn ancestors_start_at_the_parent() {
161 let chain = ancestors();
162 assert_eq!(chain.first().copied(), host_pid());
163 }
164}