Skip to main content

peek_core/proc/
kernel.rs

1use crate::{KernelInfo, NamespaceEntry};
2use kernel_explainer::capabilities::format_caps;
3use peek_proc_reader::cgroup::read_cgroup;
4use peek_proc_reader::security::read_label;
5
6pub fn collect_kernel(pid: i32) -> anyhow::Result<KernelInfo> {
7    let stat_raw = std::fs::read_to_string(format!("/proc/{}/stat", pid))?;
8    let (nice, priority, sched_policy) = parse_sched(&stat_raw);
9
10    let oom_score = std::fs::read_to_string(format!("/proc/{}/oom_score", pid))
11        .ok()
12        .and_then(|s| s.trim().parse::<i32>().ok())
13        .unwrap_or(0);
14
15    let oom_score_adj = std::fs::read_to_string(format!("/proc/{}/oom_score_adj", pid))
16        .ok()
17        .and_then(|s| s.trim().parse::<i32>().ok())
18        .unwrap_or(0);
19
20    let cgroup = read_cgroup(pid).unwrap_or_else(|| "unknown".to_string());
21    let namespaces = read_namespaces(pid);
22
23    let (cap_permitted, cap_effective, seccomp, vol_ctx, nonvol_ctx) = parse_status(pid);
24    let security_label = read_label(pid);
25
26    Ok(KernelInfo {
27        sched_policy,
28        nice,
29        priority,
30        oom_score,
31        oom_score_adj,
32        cgroup,
33        namespaces,
34        cap_permitted,
35        cap_effective,
36        seccomp,
37        voluntary_ctxt_switches: vol_ctx,
38        nonvoluntary_ctxt_switches: nonvol_ctx,
39        security_label,
40    })
41}
42
43fn parse_sched(stat_raw: &str) -> (i32, i32, String) {
44    // After comm (find last ')') fields are 0-indexed from ')':
45    // [0]=state [1]=ppid ... [15]=priority [16]=nice ... [38]=policy
46    let after = match stat_raw.rfind(')') {
47        Some(i) => &stat_raw[i + 2..],
48        None => return (0, 0, "SCHED_OTHER".to_string()),
49    };
50    let fields: Vec<&str> = after.split_whitespace().collect();
51
52    let priority = fields
53        .get(15)
54        .and_then(|s| s.parse::<i32>().ok())
55        .unwrap_or(0);
56    let nice = fields
57        .get(16)
58        .and_then(|s| s.parse::<i32>().ok())
59        .unwrap_or(0);
60    let policy_num = fields
61        .get(38)
62        .and_then(|s| s.parse::<u32>().ok())
63        .unwrap_or(0);
64
65    let policy = match policy_num {
66        0 => "SCHED_OTHER",
67        1 => "SCHED_FIFO",
68        2 => "SCHED_RR",
69        3 => "SCHED_BATCH",
70        5 => "SCHED_IDLE",
71        6 => "SCHED_DEADLINE",
72        _ => "UNKNOWN",
73    };
74
75    (nice, priority, policy.to_string())
76}
77
78fn read_namespaces(pid: i32) -> Vec<NamespaceEntry> {
79    let ns_types = ["cgroup", "ipc", "mnt", "net", "pid", "time", "user", "uts"];
80    let mut entries = Vec::new();
81    for ns_type in &ns_types {
82        let path = format!("/proc/{}/ns/{}", pid, ns_type);
83        if let Ok(target) = std::fs::read_link(&path) {
84            let inode = target
85                .to_string_lossy()
86                .split('[')
87                .nth(1)
88                .and_then(|s| s.split(']').next())
89                .unwrap_or("?")
90                .to_string();
91            entries.push(NamespaceEntry {
92                ns_type: ns_type.to_string(),
93                inode,
94            });
95        }
96    }
97    entries
98}
99
100fn parse_status(pid: i32) -> (String, String, u32, Option<u64>, Option<u64>) {
101    let raw = match std::fs::read_to_string(format!("/proc/{}/status", pid)) {
102        Ok(r) => r,
103        Err(_) => return ("0".to_string(), "0".to_string(), 0, None, None),
104    };
105
106    let mut cap_prm_bits = 0u64;
107    let mut cap_eff_bits = 0u64;
108    let mut seccomp = 0u32;
109    let mut vol_ctx = None;
110    let mut nonvol_ctx = None;
111
112    for line in raw.lines() {
113        if let Some(v) = line.strip_prefix("CapPrm:\t") {
114            cap_prm_bits = u64::from_str_radix(v.trim(), 16).unwrap_or(0);
115        } else if let Some(v) = line.strip_prefix("CapEff:\t") {
116            cap_eff_bits = u64::from_str_radix(v.trim(), 16).unwrap_or(0);
117        } else if let Some(v) = line.strip_prefix("Seccomp:\t") {
118            seccomp = v.trim().parse().unwrap_or(0);
119        } else if let Some(v) = line.strip_prefix("voluntary_ctxt_switches:\t") {
120            vol_ctx = v.trim().parse().ok();
121        } else if let Some(v) = line.strip_prefix("nonvoluntary_ctxt_switches:\t") {
122            nonvol_ctx = v.trim().parse().ok();
123        }
124    }
125
126    let (cap_prm, cap_eff) = format_caps(cap_prm_bits, cap_eff_bits);
127
128    (cap_prm, cap_eff, seccomp, vol_ctx, nonvol_ctx)
129}