linux.rs

  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}