Skip to main content

ghostscope_process/pid/
procfs.rs

1use super::types::PidNamespaceId;
2use anyhow::Result;
3use std::fs;
4use std::os::unix::fs::MetadataExt;
5use std::path::Path;
6
7/// Linux initial PID namespace inode (PROC_PID_INIT_INO).
8pub const INITIAL_PID_NAMESPACE_INO: u64 = 4026531836;
9
10pub fn process_exists(pid: u32) -> bool {
11    let proc_path = format!("/proc/{pid}");
12    Path::new(&proc_path).is_dir()
13}
14
15pub fn read_status(pid: u32) -> Result<String> {
16    let path = format!("/proc/{pid}/status");
17    fs::read_to_string(&path)
18        .map_err(|e| anyhow::anyhow!("Failed to read {} while resolving PID mapping: {}", path, e))
19}
20
21pub fn read_nspid_chain(pid: u32) -> Option<Vec<u32>> {
22    let status = fs::read_to_string(format!("/proc/{pid}/status")).ok()?;
23    read_nspid_chain_from_status(&status)
24}
25
26pub fn read_pid_ns_id(pid: u32) -> Option<PidNamespaceId> {
27    if let Ok(metadata) = fs::metadata(format!("/proc/{pid}/ns/pid")) {
28        return Some(PidNamespaceId {
29            dev: Some(metadata.dev()),
30            inode: metadata.ino(),
31        });
32    }
33
34    read_pid_ns_inode_from_link(pid).map(|inode| PidNamespaceId { dev: None, inode })
35}
36
37pub fn read_pid_ns_inode(pid: u32) -> Option<u64> {
38    read_pid_ns_id(pid).map(|pid_ns| pid_ns.inode)
39}
40
41fn parse_status_chain(status: &str, key: &str) -> Option<Vec<u32>> {
42    let line = status.lines().find(|line| line.starts_with(key))?;
43    let payload = line.strip_prefix(key)?.trim();
44    if payload.is_empty() {
45        return None;
46    }
47    let chain: Vec<u32> = payload
48        .split_whitespace()
49        .filter_map(|token| token.parse::<u32>().ok())
50        .collect();
51    if chain.is_empty() {
52        None
53    } else {
54        Some(chain)
55    }
56}
57
58pub(crate) fn read_nspid_chain_from_status(status: &str) -> Option<Vec<u32>> {
59    parse_status_chain(status, "NSpid:")
60}
61
62fn read_pid_ns_inode_from_link(pid: u32) -> Option<u64> {
63    let link = fs::read_link(format!("/proc/{pid}/ns/pid")).ok()?;
64    parse_ns_inode(&link.to_string_lossy())
65}
66
67fn parse_ns_inode(link_target: &str) -> Option<u64> {
68    // expected format: "pid:[4026531836]"
69    let lb = link_target.find('[')?;
70    let rb = link_target[lb + 1..].find(']')? + lb + 1;
71    link_target[lb + 1..rb].parse::<u64>().ok()
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn parse_ns_inode_works() {
80        assert_eq!(
81            parse_ns_inode("pid:[4026531836]"),
82            Some(INITIAL_PID_NAMESPACE_INO)
83        );
84        assert_eq!(parse_ns_inode("pid:[abc]"), None);
85        assert_eq!(parse_ns_inode("unknown"), None);
86    }
87
88    #[test]
89    fn parse_status_chain_works() {
90        let status = "Name:\tcat\nNSpid:\t1234 1\n";
91        assert_eq!(parse_status_chain(status, "NSpid:"), Some(vec![1234, 1]));
92    }
93}