Skip to main content

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/// ### Errors
26/// - `EACCES`: Permission denied.
27/// - `ENOENT`: The process does not exist.
28pub fn read_proc_status(pid: i32) -> Result<ProcStatus, CoreError> {
29    read_proc_status_at("/proc", pid)
30}
31
32/// Read process status from an explicit procfs root.
33///
34/// ### Errors
35/// - `EACCES`: Permission denied.
36/// - `ENOENT`: The process or status file does not exist.
37pub fn read_proc_status_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<ProcStatus, CoreError> {
38    let path = proc_root.as_ref().join(pid.to_string()).join("status");
39    let content = std::fs::read_to_string(path).map_err(|err| io_error(err, "read_proc_status"))?;
40    parse_proc_status(&content)
41}
42
43/// Read process command line from `/proc/<pid>/cmdline`.
44///
45/// NUL separators are converted into spaces so the returned string is easier
46/// to log or inspect.
47///
48/// ### Errors
49/// - `EACCES`: Permission denied.
50/// - `ENOENT`: The process does not exist.
51pub fn read_proc_cmdline(pid: i32) -> Result<String, CoreError> {
52    read_proc_cmdline_at("/proc", pid)
53}
54
55/// Read process command line from an explicit procfs root.
56///
57/// This is useful for tests, alternate proc mounts, or callers that need the
58/// same parsing behavior without being hard-wired to `/proc`.
59///
60/// ### Errors
61/// - `EACCES`: Permission denied.
62/// - `ENOENT`: The process or cmdline file does not exist.
63pub fn read_proc_cmdline_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<String, CoreError> {
64    let path = proc_root.as_ref().join(pid.to_string()).join("cmdline");
65    let bytes = std::fs::read(path).map_err(|err| io_error(err, "read_proc_cmdline"))?;
66    Ok(parse_proc_cmdline_bytes(&bytes))
67}
68
69pub(crate) fn parse_proc_cmdline_bytes(bytes: &[u8]) -> String {
70    String::from_utf8_lossy(bytes)
71        .trim_end_matches('\0')
72        .replace('\0', " ")
73}
74
75/// Parse the contents of a `/proc/<pid>/status` file.
76pub fn parse_proc_status(content: &str) -> Result<ProcStatus, CoreError> {
77    let mut name = None;
78    let mut uid = None;
79
80    for line in content.lines() {
81        if let Some(rest) = line.strip_prefix("Name:") {
82            name = Some(rest.trim().to_string());
83        } else if let Some(rest) = line.strip_prefix("Uid:") {
84            uid = rest
85                .split_whitespace()
86                .next()
87                .and_then(|value| value.parse::<u32>().ok());
88        }
89
90        if name.is_some() && uid.is_some() {
91            break;
92        }
93    }
94
95    match (name, uid) {
96        (Some(name), Some(uid)) => Ok(ProcStatus { name, uid }),
97        _ => Err(CoreError::sys(libc::EINVAL, "parse_proc_status")),
98    }
99}
100
101fn io_error(err: std::io::Error, op: &'static str) -> CoreError {
102    CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), op)
103}
104
105/// Return the number of clock ticks per second for the current system.
106///
107/// ### Errors
108/// - `EINVAL`: `sysconf` failed to retrieve the clock tick rate.
109#[inline(always)]
110pub fn clock_ticks_per_second() -> Result<u64, CoreError> {
111    let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
112    if ticks <= 0 {
113        let code = std::io::Error::last_os_error()
114            .raw_os_error()
115            .unwrap_or(libc::EINVAL);
116        Err(CoreError::sys(code, "sysconf(_SC_CLK_TCK)"))
117    } else {
118        Ok(ticks as u64)
119    }
120}