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 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}