Skip to main content

ghostscope_process/pid/
types.rs

1use super::procfs::INITIAL_PID_NAMESPACE_INO;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct PidAttachRequest {
6    /// Original user input from `ghostscope -p <PID>`.
7    ///
8    /// This is kept for CLI diagnostics and `$input_pid`, not as part of the
9    /// runtime PID view model.
10    pub input_pid: u32,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PidResolveSource {
15    /// `/proc/<proc_pid>/status` already provided a usable NSpid chain.
16    DirectProcStatus,
17}
18
19impl fmt::Display for PidResolveSource {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            PidResolveSource::DirectProcStatus => write!(f, "direct-proc-status"),
23        }
24    }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct PidNamespaceId {
29    /// Device id is only present when GhostScope resolved a helper-usable namespace handle.
30    ///
31    /// Some procfs-derived paths only expose the inode, so `dev` remains `None` until
32    /// we collect the extra information needed by `bpf_get_ns_current_pid_tgid`.
33    pub dev: Option<u64>,
34    pub inode: u64,
35}
36
37impl PidNamespaceId {
38    pub fn helper_dev_inode(self) -> Option<(u64, u64)> {
39        self.dev.map(|dev| (dev, self.inode))
40    }
41}
42
43#[derive(Debug, Clone)]
44pub struct PidViews {
45    /// PID used for `/proc/<pid>/...` access in GhostScope's current userspace view.
46    ///
47    /// We intentionally do not store `input_pid` here. In GhostScope's supported
48    /// `-p` contract, users enter the PID visible in the current environment, so
49    /// `input_pid` and `proc_pid` are normally the same value. Keeping only
50    /// `proc_pid` avoids duplicating the same concept inside the runtime PID model.
51    pub proc_pid: u32,
52    /// PID used by host-view runtime events and host-TGID filtering.
53    pub host_pid: u32,
54    /// Innermost PID namespace view when it differs from `proc_pid`.
55    pub container_pid: Option<u32>,
56    /// PID namespace identifier for `proc_pid` when it can be resolved.
57    pub pid_ns: Option<PidNamespaceId>,
58    /// Raw NSpid chain as reported by `/proc/<proc_pid>/status`.
59    pub nspid_chain: Option<Vec<u32>>,
60    /// Resolution source for diagnostics.
61    pub source: PidResolveSource,
62}
63
64impl PidViews {
65    pub fn compact_display(&self) -> String {
66        let container = self
67            .container_pid
68            .map(|value| value.to_string())
69            .unwrap_or_else(|| "n/a".to_string());
70        let ns_inode = self
71            .pid_ns_inode()
72            .map(|value| value.to_string())
73            .unwrap_or_else(|| "n/a".to_string());
74        let ns_dev = self
75            .pid_ns_dev()
76            .map(|value| value.to_string())
77            .unwrap_or_else(|| "n/a".to_string());
78        let chain = self
79            .nspid_chain
80            .as_ref()
81            .map(|values| {
82                values
83                    .iter()
84                    .map(|value| value.to_string())
85                    .collect::<Vec<_>>()
86                    .join("->")
87            })
88            .unwrap_or_else(|| "n/a".to_string());
89
90        format!(
91            "proc_pid={} host_pid={} container_pid={} ns_dev={} ns_inode={} nspid_chain={} source={}",
92            self.proc_pid, self.host_pid, container, ns_dev, ns_inode, chain, self.source
93        )
94    }
95
96    pub fn has_explicit_host_mapping(&self) -> bool {
97        self.nspid_chain
98            .as_ref()
99            .map(|chain| chain.len() >= 2)
100            .unwrap_or(false)
101    }
102
103    pub fn is_initial_pid_namespace(&self) -> bool {
104        self.pid_ns_inode() == Some(INITIAL_PID_NAMESPACE_INO)
105    }
106
107    pub fn pid_ns_dev(&self) -> Option<u64> {
108        self.pid_ns.and_then(|pid_ns| pid_ns.dev)
109    }
110
111    pub fn pid_ns_inode(&self) -> Option<u64> {
112        self.pid_ns.map(|pid_ns| pid_ns.inode)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn initial_pid_namespace_detection_works() {
122        let mut pid_views = PidViews {
123            proc_pid: 123,
124            host_pid: 123,
125            container_pid: None,
126            pid_ns: Some(PidNamespaceId {
127                dev: Some(1),
128                inode: INITIAL_PID_NAMESPACE_INO,
129            }),
130            nspid_chain: None,
131            source: PidResolveSource::DirectProcStatus,
132        };
133        assert!(pid_views.is_initial_pid_namespace());
134
135        pid_views.pid_ns = Some(PidNamespaceId {
136            dev: Some(1),
137            inode: INITIAL_PID_NAMESPACE_INO + 1,
138        });
139        assert!(!pid_views.is_initial_pid_namespace());
140    }
141}