1#![cfg(target_os = "linux")]
2use super::*;
3
4impl From<&str> for LocalProcessStatus {
5 fn from(s: &str) -> Self {
6 match s {
7 "R" => Self::Run,
8 "S" => Self::Sleep,
9 "D" => Self::Idle,
10 "Z" => Self::Zombie,
11 "T" => Self::Stop,
12 "t" => Self::Tracing,
13 "X" | "x" => Self::Dead,
14 "K" => Self::Wakekill,
15 "W" => Self::Waking,
16 "P" => Self::Parked,
17 _ => Self::Unknown,
18 }
19 }
20}
21
22impl LocalProcessInfo {
23 pub fn current_working_dir(pid: u32) -> Option<PathBuf> {
24 std::fs::read_link(format!("/proc/{}/cwd", pid)).ok()
25 }
26
27 pub fn executable_path(pid: u32) -> Option<PathBuf> {
28 std::fs::read_link(format!("/proc/{}/exe", pid)).ok()
29 }
30
31 pub fn with_root_pid(pid: u32) -> Option<Self> {
32 use libc::pid_t;
33
34 let pid = pid as pid_t;
35
36 fn all_pids() -> Vec<pid_t> {
37 let mut pids = vec![];
38 if let Ok(dir) = std::fs::read_dir("/proc") {
39 for entry in dir {
40 if let Ok(entry) = entry {
41 if let Ok(file_type) = entry.file_type() {
42 if file_type.is_dir() {
43 if let Some(name) = entry.file_name().to_str() {
44 if let Ok(pid) = name.parse::<pid_t>() {
45 pids.push(pid);
46 }
47 }
48 }
49 }
50 }
51 }
52 }
53 pids
54 }
55
56 struct LinuxStat {
57 pid: pid_t,
58 name: String,
59 status: String,
60 ppid: pid_t,
61 // Time process started after boot, measured in ticks
62 starttime: u64,
63 }
64
65 fn info_for_pid(pid: pid_t) -> Option<LinuxStat> {
66 let data = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
67 let (_pid_space, name) = data.split_once('(')?;
68 let (name, fields) = name.rsplit_once(')')?;
69 let fields = fields.split_whitespace().collect::<Vec<_>>();
70
71 Some(LinuxStat {
72 pid,
73 name: name.to_string(),
74 status: fields.get(0)?.to_string(),
75 ppid: fields.get(1)?.parse().ok()?,
76 starttime: fields.get(20)?.parse().ok()?,
77 })
78 }
79
80 fn exe_for_pid(pid: pid_t) -> PathBuf {
81 std::fs::read_link(format!("/proc/{}/exe", pid)).unwrap_or_else(|_| PathBuf::new())
82 }
83
84 fn cwd_for_pid(pid: pid_t) -> PathBuf {
85 LocalProcessInfo::current_working_dir(pid as u32).unwrap_or_else(|| PathBuf::new())
86 }
87
88 fn parse_cmdline(pid: pid_t) -> Vec<String> {
89 let data = match std::fs::read(format!("/proc/{}/cmdline", pid)) {
90 Ok(data) => data,
91 Err(_) => return vec![],
92 };
93
94 let mut args = vec![];
95
96 let data = data.strip_suffix(&[0]).unwrap_or(&data);
97
98 for arg in data.split(|&c| c == 0) {
99 args.push(String::from_utf8_lossy(arg).to_owned().to_string());
100 }
101
102 args
103 }
104
105 let procs: Vec<_> = all_pids().into_iter().filter_map(info_for_pid).collect();
106
107 fn build_proc(info: &LinuxStat, procs: &[LinuxStat]) -> LocalProcessInfo {
108 let mut children = HashMap::new();
109
110 for kid in procs {
111 if kid.ppid == info.pid {
112 children.insert(kid.pid as u32, build_proc(kid, procs));
113 }
114 }
115
116 let executable = exe_for_pid(info.pid);
117 let name = info.name.clone();
118 let argv = parse_cmdline(info.pid);
119
120 LocalProcessInfo {
121 pid: info.pid as _,
122 ppid: info.ppid as _,
123 name,
124 executable,
125 cwd: cwd_for_pid(info.pid),
126 argv,
127 start_time: info.starttime,
128 status: info.status.as_str().into(),
129 children,
130 }
131 }
132
133 if let Some(info) = procs.iter().find(|info| info.pid == pid) {
134 Some(build_proc(info, &procs))
135 } else {
136 None
137 }
138 }
139}