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