Skip to main content

ghostscope_process/pid/
resolve.rs

1use super::procfs::{
2    process_exists, read_nspid_chain, read_nspid_chain_from_status, read_pid_ns_id, read_status,
3};
4use super::types::{PidResolveSource, PidViews};
5
6fn push_unique_pid(pids: &mut Vec<u32>, pid: u32) {
7    if !pids.contains(&pid) {
8        pids.push(pid);
9    }
10}
11
12fn runtime_pid_candidates_from_chain(proc_pid: u32, nspid_chain: Option<&[u32]>) -> Vec<u32> {
13    let mut pids = Vec::new();
14    push_unique_pid(&mut pids, proc_pid);
15    if let Some(chain) = nspid_chain {
16        for pid in chain {
17            push_unique_pid(&mut pids, *pid);
18        }
19    }
20    pids
21}
22
23pub fn resolve_input_pid(input_pid: u32) -> anyhow::Result<PidViews> {
24    if !process_exists(input_pid) {
25        return Err(anyhow::anyhow!(
26            "Process with PID {} is not running. Use 'ps -p {}' to verify the process exists.\n\
27             Additional check: -p expects a PID visible in the current PID namespace.",
28            input_pid,
29            input_pid
30        ));
31    }
32
33    resolve_proc_pid(input_pid)
34}
35
36pub fn resolve_proc_pid(proc_pid: u32) -> anyhow::Result<PidViews> {
37    let status = read_status(proc_pid)?;
38    let nspid_chain = read_nspid_chain_from_status(&status);
39    let host_pid = nspid_chain
40        .as_ref()
41        .and_then(|chain| chain.first().copied())
42        .unwrap_or(proc_pid);
43    let container_pid = nspid_chain.as_ref().and_then(|chain| chain.last().copied());
44
45    Ok(PidViews {
46        proc_pid,
47        host_pid,
48        container_pid,
49        pid_ns: read_pid_ns_id(proc_pid),
50        nspid_chain,
51        source: PidResolveSource::DirectProcStatus,
52    })
53}
54
55pub fn host_pid_for_proc_pid(proc_pid: u32) -> u32 {
56    read_nspid_chain(proc_pid)
57        .and_then(|chain| chain.first().copied())
58        .unwrap_or(proc_pid)
59}
60
61/// Resolve a kernel event PID (initial PID namespace) to the `/proc` PID in the
62/// current userspace namespace when possible.
63pub fn resolve_proc_pid_for_event(event_pid: u32) -> u32 {
64    if std::path::Path::new(&format!("/proc/{event_pid}")).exists() {
65        return event_pid;
66    }
67
68    if let Ok(dir) = std::fs::read_dir("/proc") {
69        for ent in dir.flatten() {
70            let file_name = ent.file_name();
71            let Ok(proc_pid) = file_name.to_string_lossy().parse::<u32>() else {
72                continue;
73            };
74            let Some(chain) = read_nspid_chain(proc_pid) else {
75                continue;
76            };
77            if chain.first().copied() == Some(event_pid) {
78                return proc_pid;
79            }
80        }
81    }
82
83    event_pid
84}
85
86/// Resolve a `/proc` PID back to the host-view event PID when possible.
87pub fn resolve_event_pid_for_proc(proc_pid: u32) -> u32 {
88    read_nspid_chain(proc_pid)
89        .and_then(|chain| chain.first().copied())
90        .unwrap_or(proc_pid)
91}
92
93/// Return PID values that eBPF-side runtime lookups may observe for a process.
94///
95/// Userspace stores proc-module offsets under the PID visible in its `/proc`
96/// view, but eBPF helpers and sysmon events may report any PID in the visible
97/// namespace chain for nested containers.
98pub fn runtime_pid_candidates_for_proc(proc_pid: u32) -> Vec<u32> {
99    let chain = read_nspid_chain(proc_pid);
100    runtime_pid_candidates_from_chain(proc_pid, chain.as_deref())
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn runtime_pid_candidates_include_proc_and_full_nspid_chain() {
109        assert_eq!(
110            runtime_pid_candidates_from_chain(445, Some(&[1000, 531, 17])),
111            vec![445, 1000, 531, 17]
112        );
113    }
114
115    #[test]
116    fn runtime_pid_candidates_deduplicate_proc_pid() {
117        assert_eq!(
118            runtime_pid_candidates_from_chain(531, Some(&[1000, 531, 17])),
119            vec![531, 1000, 17]
120        );
121    }
122}