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/// 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}