1use std::ffi::OsStr;
2use std::fs::{read_dir, read_link, File, Metadata};
3use std::io::{BufRead, BufReader, Read, Write};
4use std::os::unix::ffi::OsStrExt;
5use std::path::Path;
6use std::str::FromStr;
7
8use lazy_static::lazy_static;
9use nix::sys::time::TimeSpec;
10use nix::time::{clock_gettime, ClockId};
11use nix::unistd::{sysconf, SysconfVar};
12
13use thiserror::Error;
14
15lazy_static! {
16 pub static ref CLK_TCK: u64
18 = sysconf(SysconfVar::CLK_TCK).unwrap().unwrap() as u64;
19}
20
21#[derive(Debug, Error)]
22pub enum ProcFSError {
23 #[error("can't read /proc/{pid}/(obj): {err}")]
24 PidFile {
25 pid: u32,
26 obj: &'static str,
27 err: std::io::Error,
28 },
29 #[error("can't enumerate processes: {0}")]
30 Enum(std::io::Error),
31 #[error("can't get field {0}")]
32 Field(&'static str),
33 #[error("{0}: {1}")]
34 Errno(&'static str, nix::errno::Errno),
35}
36
37pub fn slurp_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, std::io::Error> {
38 let f = File::open(path)?;
39 let mut r = BufReader::with_capacity(1 << 16, f);
40 r.fill_buf()?;
41 let mut buf = Vec::with_capacity(8192);
42 r.read_to_end(&mut buf)?;
43 Ok(buf)
44}
45
46fn slurp_pid_obj(pid: u32, obj: &'static str) -> Result<Vec<u8>, ProcFSError> {
48 let path = format!("/proc/{pid}/{obj}");
49 slurp_file(path).map_err(|err| ProcFSError::PidFile { pid, obj, err })
50}
51
52type Environment = Vec<(Vec<u8>, Vec<u8>)>;
53
54pub fn get_environ<F>(pid: u32, pred: F) -> Result<Environment, ProcFSError>
56where
57 F: Fn(&[u8]) -> bool,
58{
59 let buf = slurp_pid_obj(pid, "environ")?;
60 let mut res = Vec::new();
61
62 for e in buf.split(|c| *c == 0) {
63 let mut kv = e.splitn(2, |c| *c == b'=');
64 let k = kv.next().unwrap_or_default();
65 if pred(k) {
66 let v = kv.next().unwrap_or_default();
67 res.push((k.to_owned(), v.to_owned()));
68 }
69 }
70 Ok(res)
71}
72
73pub fn get_pids() -> Result<Vec<u32>, ProcFSError> {
75 Ok(read_dir("/proc")
76 .map_err(ProcFSError::Enum)?
77 .flatten()
78 .filter_map(|e| u32::from_str(e.file_name().to_string_lossy().as_ref()).ok())
79 .collect::<Vec<u32>>())
80}
81
82pub fn pid_path_metadata(pid: u32, path: &[u8]) -> Result<Metadata, std::io::Error> {
84 if path.is_empty() || path[0] != b'/' {
85 return Err(std::io::ErrorKind::NotFound.into());
86 }
87 let mut proc_path = Vec::with_capacity(20 + path.len());
88 write!(proc_path, "/proc/{pid}/root").unwrap();
90 proc_path.extend(path);
91 std::fs::metadata(OsStr::from_bytes(&proc_path))
92}
93
94pub struct ProcStat<'a> {
95 pub pid: u32,
96 pub ppid: u32,
97 pub comm: &'a [u8],
98 pub starttime: u64,
99 pub utime: u64,
100 pub stime: u64,
101}
102
103pub fn parse_proc_pid_stat(buf: &[u8]) -> Result<ProcStat, ProcFSError> {
104 let pid_end = buf
105 .iter()
106 .enumerate()
107 .find(|(_, c)| **c == b' ')
108 .ok_or(ProcFSError::Field("pid"))?
109 .0;
110 let stat_pid = &buf[..pid_end];
111
112 let comm_end = buf[..32]
114 .iter()
115 .enumerate()
116 .rfind(|(_, c)| **c == b')')
117 .ok_or(ProcFSError::Field("comm"))?
118 .0;
119 let stat = &buf[comm_end + 2..]
120 .split(|c| *c == b' ')
121 .collect::<Vec<_>>();
122
123 let pid = u32::from_str(String::from_utf8_lossy(stat_pid).as_ref())
124 .map_err(|_| ProcFSError::Field("pid"))?;
125 let comm = &buf[pid_end + 2..comm_end];
126 let ppid = u32::from_str(String::from_utf8_lossy(stat[1]).as_ref())
127 .map_err(|_| ProcFSError::Field("ppid"))?;
128 let starttime = u64::from_str(String::from_utf8_lossy(stat[19]).as_ref())
129 .map_err(|_| ProcFSError::Field("starttime"))?;
130 let utime = u64::from_str(String::from_utf8_lossy(stat[11]).as_ref())
131 .map_err(|_| ProcFSError::Field("utime"))?;
132 let stime = u64::from_str(String::from_utf8_lossy(stat[12]).as_ref())
133 .map_err(|_| ProcFSError::Field("stime"))?;
134
135 Ok(ProcStat {
136 pid,
137 ppid,
138 comm,
139 starttime,
140 utime,
141 stime,
142 })
143}
144
145#[derive(Debug)]
146pub(crate) struct ProcPidInfo {
147 pub pid: u32,
149 pub ppid: u32,
151 pub starttime: u64,
153 pub comm: Vec<u8>,
155 pub exe: Option<Vec<u8>>,
157 pub cgroup: Option<Vec<u8>>,
159}
160
161pub(crate) fn parse_proc_pid(pid: u32) -> Result<ProcPidInfo, ProcFSError> {
163 let buf = slurp_pid_obj(pid, "stat")?;
164
165 let ProcStat {
166 pid,
167 ppid,
168 comm,
169 starttime,
170 ..
171 } = parse_proc_pid_stat(&buf)?;
172
173 let exe = read_link(format!("/proc/{pid}/exe"))
174 .map(|p| Vec::from(p.as_os_str().as_bytes()))
175 .ok();
176
177 let proc_boottime = TimeSpec::from(libc::timespec {
180 tv_sec: (starttime / *CLK_TCK) as _,
181 tv_nsec: ((starttime % *CLK_TCK) * (1_000_000_000 / *CLK_TCK)) as _,
182 });
183 #[cfg(not(target_os = "linux"))]
184 let proc_age = TimeSpec::from(std::time::Duration::ZERO);
185 #[cfg(target_os = "linux")]
186 let proc_age = clock_gettime(ClockId::CLOCK_BOOTTIME)
187 .map_err(|e| ProcFSError::Errno("clock_gettime(CLOCK_BOOTTIME)", e))?
188 - proc_boottime;
189 let starttime = {
190 let lt = clock_gettime(ClockId::CLOCK_REALTIME)
191 .map_err(|e| ProcFSError::Errno("clock_gettime(CLOCK_REALTIME)", e))?
192 - proc_age;
193 (lt.tv_sec() as u64) * 1000 + (lt.tv_nsec() as u64) / 1_000_000
194 };
195
196 let cgroup = parse_proc_pid_cgroup(pid)?;
197
198 Ok(ProcPidInfo {
199 pid,
200 ppid,
201 starttime,
202 comm: comm.to_vec(),
203 exe,
204 cgroup,
205 })
206}
207
208pub(crate) fn parse_proc_pid_cgroup(pid: u32) -> Result<Option<Vec<u8>>, ProcFSError> {
210 parse_cgroup_buf(&slurp_pid_obj(pid, "cgroup")?)
211}
212
213fn parse_cgroup_buf(buf: &[u8]) -> Result<Option<Vec<u8>>, ProcFSError> {
214 for line in buf.split(|c| *c == b'\n') {
215 match line.split(|&c| c == b':').nth(2) {
216 None => continue,
217 Some(dir) => return Ok(Some(dir.to_vec())),
218 }
219 }
220 Ok(None)
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 #[test]
227 fn parse_self() {
228 let pid = std::process::id();
229 let proc = parse_proc_pid(pid).unwrap_or_else(|_| panic!("parse entry for {pid}"));
230 println!("{proc:?}");
231 }
232
233 #[test]
234 fn parse_stat() {
235 let ProcStat { pid, ppid, comm, starttime, utime, stime } = parse_proc_pid_stat(
236 br#"925028 (emacs) R 3057 925028 925028 0 -1 4194304 1131624 1579849 3 604 183731 6453 20699 2693 20 0 5 0 22398221 3922935808 191059 18446744073709551615 187652449566720 187652452795816 281474372905056 0 0 0 0 67112960 1535209215 0 0 0 17 2 0 0 0 0 0 187652452866344 187652460555720 187652461281280 281474372910201 281474372910228 281474372910228 281474372911081 0
237"#).expect("parse error");
238 assert_eq!(pid, 925028);
239 assert_eq!(ppid, 3057);
240 assert_eq!(comm, "emacs".as_bytes());
241 assert_eq!(starttime, 22398221);
242 assert_eq!(utime, 183731);
243 assert_eq!(stime, 6453);
244 }
245}