coreshift_core/proc.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Procfs and process-introspection helpers.
6//!
7//! These functions provide a small, Linux-oriented view into `/proc` for
8//! callers that need process names, command lines, UIDs, or clock-tick
9//! information without bringing in a broader process-inspection crate.
10
11use crate::CoreError;
12use std::path::Path;
13
14/// A snapshot of process status information from procfs.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ProcStatus {
17 /// Command name of the process.
18 pub name: String,
19 /// Real UID of the process.
20 pub uid: u32,
21}
22
23/// Read process status from `/proc/<pid>/status`.
24///
25/// Read process status from `/proc/<pid>/status`.
26///
27/// ### Errors
28/// - `EACCES`: Permission denied.
29/// - `ENOENT`: The process does not exist.
30pub fn read_proc_status(pid: i32) -> Result<ProcStatus, CoreError> {
31 read_proc_status_at("/proc", pid)
32}
33
34/// Read process status from an explicit procfs root.
35///
36/// ### Errors
37/// - `EACCES`: Permission denied.
38/// - `ENOENT`: The process or status file does not exist.
39pub fn read_proc_status_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<ProcStatus, CoreError> {
40 let path = proc_root.as_ref().join(pid.to_string()).join("status");
41 let content = std::fs::read_to_string(path).map_err(|err| io_error(err, "read_proc_status"))?;
42 parse_proc_status(&content)
43}
44
45/// Read process command line from `/proc/<pid>/cmdline`.
46///
47/// NUL separators are converted into spaces so the returned string is easier
48/// to log or inspect.
49/// Read process command line from `/proc/<pid>/cmdline`.
50///
51/// NUL separators are converted into spaces so the returned string is easier
52/// to log or inspect.
53///
54/// ### Errors
55/// - `EACCES`: Permission denied.
56/// - `ENOENT`: The process does not exist.
57pub fn read_proc_cmdline(pid: i32) -> Result<String, CoreError> {
58 read_proc_cmdline_at("/proc", pid)
59}
60
61/// Read process command line from an explicit procfs root.
62///
63/// This is useful for tests, alternate proc mounts, or callers that need the
64/// same parsing behavior without being hard-wired to `/proc`.
65///
66/// ### Errors
67/// - `EACCES`: Permission denied.
68/// - `ENOENT`: The process or cmdline file does not exist.
69pub fn read_proc_cmdline_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<String, CoreError> {
70 let path = proc_root.as_ref().join(pid.to_string()).join("cmdline");
71 let bytes = std::fs::read(path).map_err(|err| io_error(err, "read_proc_cmdline"))?;
72 Ok(parse_proc_cmdline_bytes(&bytes))
73}
74
75pub(crate) fn parse_proc_cmdline_bytes(bytes: &[u8]) -> String {
76 String::from_utf8_lossy(bytes)
77 .trim_end_matches('\0')
78 .replace('\0', " ")
79}
80
81/// Parse the contents of a `/proc/<pid>/status` file.
82pub fn parse_proc_status(content: &str) -> Result<ProcStatus, CoreError> {
83 let mut name = None;
84 let mut uid = None;
85
86 for line in content.lines() {
87 if let Some(rest) = line.strip_prefix("Name:") {
88 name = Some(rest.trim().to_string());
89 } else if let Some(rest) = line.strip_prefix("Uid:") {
90 uid = rest
91 .split_whitespace()
92 .next()
93 .and_then(|value| value.parse::<u32>().ok());
94 }
95
96 if name.is_some() && uid.is_some() {
97 break;
98 }
99 }
100
101 match (name, uid) {
102 (Some(name), Some(uid)) => Ok(ProcStatus { name, uid }),
103 _ => Err(CoreError::sys(libc::EINVAL, "parse_proc_status")),
104 }
105}
106
107fn io_error(err: std::io::Error, op: &'static str) -> CoreError {
108 CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), op)
109}
110
111/// Return the number of clock ticks per second for the current system.
112///
113/// ### Errors
114/// - `EINVAL`: `sysconf` failed to retrieve the clock tick rate.
115#[inline(always)]
116pub fn clock_ticks_per_second() -> Result<u64, CoreError> {
117 let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
118 if ticks <= 0 {
119 let code = std::io::Error::last_os_error()
120 .raw_os_error()
121 .unwrap_or(libc::EINVAL);
122 Err(CoreError::sys(code, "sysconf(_SC_CLK_TCK)"))
123 } else {
124 Ok(ticks as u64)
125 }
126}