From c55b1b39a48a60420a8e284b5ccfa4955db33c96 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Fri, 3 Mar 2023 16:54:03 -0500 Subject: [PATCH 1/7] Partial implementation. --- Cargo.toml | 40 +- procfs-core/Cargo.toml | 26 + {src => procfs-core/src}/cgroups.rs | 90 +- {src => procfs-core/src}/lib.rs | 524 +--------- procfs-core/src/process/limit.rs | 215 +++++ procfs-core/src/process/mod.rs | 891 ++++++++++++++++++ procfs-core/src/process/mount.rs | 611 ++++++++++++ procfs-core/src/process/namespaces.rs | 27 + procfs-core/src/process/pagemap.rs | 163 ++++ {src => procfs-core/src}/process/schedstat.rs | 0 .../src}/process/smaps_rollup.rs | 0 procfs-core/src/process/stat.rs | 420 +++++++++ procfs-core/src/process/status.rs | 345 +++++++ procfs-core/src/sys/kernel/mod.rs | 429 +++++++++ procfs-core/src/sys/mod.rs | 9 + procfs/Cargo.toml | 42 + {benches => procfs/benches}/cpuinfo.rs | 0 build.rs => procfs/build.rs | 0 {examples => procfs/examples}/README.md | 0 {examples => procfs/examples}/diskstat.rs | 0 {examples => procfs/examples}/dump.rs | 0 .../examples}/interface_stats.rs | 0 {examples => procfs/examples}/iomem.rs | 0 {examples => procfs/examples}/kpagecount.rs | 0 {examples => procfs/examples}/lslocks.rs | 0 {examples => procfs/examples}/lsmod.rs | 0 {examples => procfs/examples}/mountinfo.rs | 0 {examples => procfs/examples}/netstat.rs | 0 {examples => procfs/examples}/pfn.rs | 0 {examples => procfs/examples}/pressure.rs | 0 .../examples}/process_hierarchy.rs | 0 .../examples}/process_kpageflags.rs | 0 {examples => procfs/examples}/ps.rs | 0 {examples => procfs/examples}/self_memory.rs | 0 {examples => procfs/examples}/shm.rs | 0 procfs/src/cgroups.rs | 37 + {src => procfs/src}/cpuinfo.rs | 0 {src => procfs/src}/diskstats.rs | 0 {src => procfs/src}/iomem.rs | 0 {src => procfs/src}/keyring.rs | 0 {src => procfs/src}/kpagecount.rs | 0 {src => procfs/src}/kpageflags.rs | 0 procfs/src/lib.rs | 659 +++++++++++++ {src => procfs/src}/locks.rs | 0 {src => procfs/src}/meminfo.rs | 0 {src => procfs/src}/net.rs | 0 {src => procfs/src}/pressure.rs | 0 {src => procfs/src}/process/limit.rs | 0 {src => procfs/src}/process/mod.rs | 0 {src => procfs/src}/process/mount.rs | 0 {src => procfs/src}/process/namespaces.rs | 0 {src => procfs/src}/process/pagemap.rs | 0 procfs/src/process/schedstat.rs | 42 + procfs/src/process/smaps_rollup.rs | 14 + {src => procfs/src}/process/stat.rs | 0 {src => procfs/src}/process/status.rs | 0 {src => procfs/src}/process/task.rs | 0 {src => procfs/src}/process/tests.rs | 0 {src => procfs/src}/sys/fs/binfmt_misc.rs | 0 {src => procfs/src}/sys/fs/epoll.rs | 0 {src => procfs/src}/sys/fs/mod.rs | 0 {src => procfs/src}/sys/kernel/keys.rs | 0 {src => procfs/src}/sys/kernel/mod.rs | 0 {src => procfs/src}/sys/kernel/random.rs | 0 {src => procfs/src}/sys/mod.rs | 0 {src => procfs/src}/sys/vm.rs | 0 {src => procfs/src}/sysvipc_shm.rs | 0 {src => procfs/src}/uptime.rs | 0 68 files changed, 4000 insertions(+), 584 deletions(-) create mode 100644 procfs-core/Cargo.toml rename {src => procfs-core/src}/cgroups.rs (59%) rename {src => procfs-core/src}/lib.rs (66%) create mode 100644 procfs-core/src/process/limit.rs create mode 100644 procfs-core/src/process/mod.rs create mode 100644 procfs-core/src/process/mount.rs create mode 100644 procfs-core/src/process/namespaces.rs create mode 100644 procfs-core/src/process/pagemap.rs rename {src => procfs-core/src}/process/schedstat.rs (100%) rename {src => procfs-core/src}/process/smaps_rollup.rs (100%) create mode 100644 procfs-core/src/process/stat.rs create mode 100644 procfs-core/src/process/status.rs create mode 100644 procfs-core/src/sys/kernel/mod.rs create mode 100644 procfs-core/src/sys/mod.rs create mode 100644 procfs/Cargo.toml rename {benches => procfs/benches}/cpuinfo.rs (100%) rename build.rs => procfs/build.rs (100%) rename {examples => procfs/examples}/README.md (100%) rename {examples => procfs/examples}/diskstat.rs (100%) rename {examples => procfs/examples}/dump.rs (100%) rename {examples => procfs/examples}/interface_stats.rs (100%) rename {examples => procfs/examples}/iomem.rs (100%) rename {examples => procfs/examples}/kpagecount.rs (100%) rename {examples => procfs/examples}/lslocks.rs (100%) rename {examples => procfs/examples}/lsmod.rs (100%) rename {examples => procfs/examples}/mountinfo.rs (100%) rename {examples => procfs/examples}/netstat.rs (100%) rename {examples => procfs/examples}/pfn.rs (100%) rename {examples => procfs/examples}/pressure.rs (100%) rename {examples => procfs/examples}/process_hierarchy.rs (100%) rename {examples => procfs/examples}/process_kpageflags.rs (100%) rename {examples => procfs/examples}/ps.rs (100%) rename {examples => procfs/examples}/self_memory.rs (100%) rename {examples => procfs/examples}/shm.rs (100%) create mode 100644 procfs/src/cgroups.rs rename {src => procfs/src}/cpuinfo.rs (100%) rename {src => procfs/src}/diskstats.rs (100%) rename {src => procfs/src}/iomem.rs (100%) rename {src => procfs/src}/keyring.rs (100%) rename {src => procfs/src}/kpagecount.rs (100%) rename {src => procfs/src}/kpageflags.rs (100%) create mode 100644 procfs/src/lib.rs rename {src => procfs/src}/locks.rs (100%) rename {src => procfs/src}/meminfo.rs (100%) rename {src => procfs/src}/net.rs (100%) rename {src => procfs/src}/pressure.rs (100%) rename {src => procfs/src}/process/limit.rs (100%) rename {src => procfs/src}/process/mod.rs (100%) rename {src => procfs/src}/process/mount.rs (100%) rename {src => procfs/src}/process/namespaces.rs (100%) rename {src => procfs/src}/process/pagemap.rs (100%) create mode 100644 procfs/src/process/schedstat.rs create mode 100644 procfs/src/process/smaps_rollup.rs rename {src => procfs/src}/process/stat.rs (100%) rename {src => procfs/src}/process/status.rs (100%) rename {src => procfs/src}/process/task.rs (100%) rename {src => procfs/src}/process/tests.rs (100%) rename {src => procfs/src}/sys/fs/binfmt_misc.rs (100%) rename {src => procfs/src}/sys/fs/epoll.rs (100%) rename {src => procfs/src}/sys/fs/mod.rs (100%) rename {src => procfs/src}/sys/kernel/keys.rs (100%) rename {src => procfs/src}/sys/kernel/mod.rs (100%) rename {src => procfs/src}/sys/kernel/random.rs (100%) rename {src => procfs/src}/sys/mod.rs (100%) rename {src => procfs/src}/sys/vm.rs (100%) rename {src => procfs/src}/sysvipc_shm.rs (100%) rename {src => procfs/src}/uptime.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 293943ae..85f9d777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,15 @@ -[package] -name = "procfs" +[workspace] +members = [ + "procfs", + "procfs-core", +] + +[workspace.package] version = "0.15.1" authors = ["Andrew Chin "] repository = "/~https://github.com/eminence/procfs" -documentation = "https://docs.rs/procfs/" -description = "Interface to the linux procfs pseudo-filesystem" -readme = "README.md" keywords = ["procfs", "proc", "linux", "process"] categories = ["os::unix-apis", "filesystem"] license = "MIT OR Apache-2.0" edition = "2018" rust-version = "1.48" - -[features] -default = ["chrono", "flate2"] -serde1 = ["serde"] - -[dependencies] -rustix = { version = "0.37.0", features = ["fs", "process", "param", "thread"] } -bitflags = "1.2" -lazy_static = "1.0.2" -chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } -byteorder = {version="1.2.3", features=["i128"]} -hex = "0.4" -flate2 = { version = "1.0.3", optional = true } -backtrace = { version = "0.3", optional = true } -serde = { version = "1.0", features = ["derive"], optional = true } - -[dev-dependencies] -criterion = "0.4" -procinfo = "0.4.2" -failure = "0.1" -libc = "0.2.139" - -[package.metadata.docs.rs] -all-features = true - -[[bench]] -name = "cpuinfo" -harness = false diff --git a/procfs-core/Cargo.toml b/procfs-core/Cargo.toml new file mode 100644 index 00000000..5718a195 --- /dev/null +++ b/procfs-core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "procfs-core" +documentation = "https://docs.rs/procfs-core/" +description = "Data structures and parsing for the linux procfs pseudo-filesystem" +readme = "../README.md" +version.workspace = true +authors.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[features] +default = ["chrono"] +serde1 = ["serde"] + +[dependencies] +backtrace = { version = "0.3", optional = true } +bitflags = "1.2" +chrono = { version = "0.4.20", optional = true, features = ["clock"], default-features = false } +serde = { version = "1.0", features = ["derive"], optional = true } + +[package.metadata.docs.rs] +all-features = true diff --git a/src/cgroups.rs b/procfs-core/src/cgroups.rs similarity index 59% rename from src/cgroups.rs rename to procfs-core/src/cgroups.rs index 323800d8..fbf1be80 100644 --- a/src/cgroups.rs +++ b/procfs-core/src/cgroups.rs @@ -1,15 +1,11 @@ use crate::ProcResult; - -use super::process::Process; - #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +use std::io::{BufRead, BufReader, Read}; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] /// Container group controller information. -/// -/// See also the [cgroups()] method. pub struct CGroupController { /// The name of the controller. pub name: String, @@ -28,46 +24,40 @@ pub struct CGroupController { pub enabled: bool, } -/// Information about the cgroup controllers that are compiled into the kernel -/// -/// (since Linux 2.6.24) -// This is returning a vector, but if each subsystem name is unique, maybe this can be a hashmap -// instead -pub fn cgroups() -> ProcResult> { - use std::fs::File; - use std::io::{BufRead, BufReader}; +impl CGroupController { + /// Parse input into a vector of cgroup controllers. + // This is returning a vector, but if each subsystem name is unique, maybe this can be a + // hashmap instead + pub fn cgroup_controllers_from_reader(reader: R) -> ProcResult> { + let reader = BufReader::new(reader); - let file = File::open("/proc/cgroups")?; - let reader = BufReader::new(file); + let mut vec = Vec::new(); + + for line in reader.lines() { + let line = line?; + if line.starts_with('#') { + continue; + } - let mut vec = Vec::new(); + let mut s = line.split_whitespace(); + let name = expect!(s.next(), "name").to_owned(); + let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); + let num_cgroups = from_str!(u32, expect!(s.next(), "num_cgroups")); + let enabled = expect!(s.next(), "enabled") == "1"; - for line in reader.lines() { - let line = line?; - if line.starts_with('#') { - continue; + vec.push(CGroupController { + name, + hierarchy, + num_cgroups, + enabled, + }); } - let mut s = line.split_whitespace(); - let name = expect!(s.next(), "name").to_owned(); - let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); - let num_cgroups = from_str!(u32, expect!(s.next(), "num_cgroups")); - let enabled = expect!(s.next(), "enabled") == "1"; - - vec.push(CGroupController { - name, - hierarchy, - num_cgroups, - enabled, - }); + Ok(vec) } - - Ok(vec) } /// Information about a process cgroup -/// -/// See also the [Process::cgroups()] method. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ProcessCgroup { @@ -88,15 +78,10 @@ pub struct ProcessCgroup { pub pathname: String, } -impl Process { - /// Describes control groups to which the process with the corresponding PID belongs. - /// - /// The displayed information differs for cgroupsversion 1 and version 2 hierarchies. - pub fn cgroups(&self) -> ProcResult> { - use std::io::{BufRead, BufReader}; - - let file = self.open_relative("cgroup")?; - let reader = BufReader::new(file); +impl ProcessCgroup { + /// Parse input into a vector of cgroups. + pub fn cgroups_from_reader(reader: R) -> ProcResult> { + let reader = BufReader::new(reader); let mut vec = Vec::new(); @@ -127,18 +112,5 @@ impl Process { #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_cgroups() { - let groups = cgroups().unwrap(); - println!("{:?}", groups); - } - - #[test] - fn test_process_cgroups() { - let myself = Process::myself().unwrap(); - let groups = myself.cgroups(); - println!("{:?}", groups); - } + // TODO } diff --git a/src/lib.rs b/procfs-core/src/lib.rs similarity index 66% rename from src/lib.rs rename to procfs-core/src/lib.rs index 244f5920..f20dbcb6 100644 --- a/src/lib.rs +++ b/procfs-core/src/lib.rs @@ -37,7 +37,6 @@ //! The following cargo features are available: //! //! * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects. -//! * `flate2` -- Default. Optional. This feature enables parsing gzip compressed `/proc/config.gz` file via the `procfs::kernel_config` method. //! * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised. //! //! # Examples @@ -47,24 +46,16 @@ //! use bitflags::bitflags; -use lazy_static::lazy_static; -use rustix::fd::AsFd; use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io::{self, BufRead, BufReader, Read, Seek, Write}; +use std::io::{self, BufRead, BufReader, Read}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{collections::HashMap, time::Duration}; -#[cfg(feature = "chrono")] -use chrono::{DateTime, Local}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -const PROC_CONFIG_GZ: &str = "/proc/config.gz"; -const BOOT_CONFIG: &str = "/boot/config"; - trait IntoOption { fn into_option(t: Self) -> Option; } @@ -81,7 +72,7 @@ impl IntoOption for Result { } } -pub(crate) trait IntoResult { +pub trait IntoResult { fn into(t: Self) -> Result; } @@ -194,53 +185,8 @@ macro_rules! from_str { }}; } -macro_rules! wrap_io_error { - ($path:expr, $expr:expr) => { - match $expr { - Ok(v) => Ok(v), - Err(e) => { - let kind = e.kind(); - Err(::std::io::Error::new( - kind, - crate::IoErrorWrapper { - path: $path.to_owned(), - inner: e.into(), - }, - )) - } - } - }; -} - -pub(crate) fn read_file>(path: P) -> ProcResult { - let mut f = FileWrapper::open(path)?; - let mut buf = String::new(); - f.read_to_string(&mut buf)?; - Ok(buf) -} - -pub(crate) fn write_file, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> { - let mut f = OpenOptions::new().read(false).write(true).open(path)?; - f.write_all(buf.as_ref())?; - Ok(()) -} - -pub(crate) fn read_value(path: P) -> ProcResult -where - P: AsRef, - T: FromStr, - ProcError: From, -{ - let val = read_file(path)?; - Ok(::from_str(val.trim())?) - //Ok(val.trim().parse()?) -} - -pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> ProcResult<()> { - write_file(path, value.to_string().as_bytes()) -} - -pub(crate) fn from_iter<'a, I, U>(i: I) -> ProcResult +// TODO temporary, only for procfs +pub fn from_iter<'a, I, U>(i: I) -> ProcResult where I: IntoIterator, U: FromStr, @@ -255,81 +201,13 @@ where pub mod process; -mod meminfo; -pub use crate::meminfo::*; - -mod sysvipc_shm; -pub use crate::sysvipc_shm::*; - -pub mod net; - -mod cpuinfo; -pub use crate::cpuinfo::*; - mod cgroups; pub use crate::cgroups::*; - pub mod sys; -pub use crate::sys::kernel::BuildInfo as KernelBuildInfo; -pub use crate::sys::kernel::Type as KernelType; pub use crate::sys::kernel::Version as KernelVersion; -mod pressure; -pub use crate::pressure::*; - -mod diskstats; -pub use diskstats::*; - -mod locks; -pub use locks::*; - -pub mod keyring; - -mod uptime; -pub use uptime::*; - -mod iomem; -pub use iomem::*; - -mod kpageflags; -pub use kpageflags::*; - -mod kpagecount; -pub use kpagecount::*; - -lazy_static! { - /// The number of clock ticks per second. - /// - /// This is calculated from `sysconf(_SC_CLK_TCK)`. - static ref TICKS_PER_SECOND: u64 = { - ticks_per_second() - }; - /// The version of the currently running kernel. - /// - /// This is a lazily constructed static. You can also get this information via - /// [KernelVersion::new()]. - static ref KERNEL: ProcResult = { - KernelVersion::current() - }; - /// Memory page size, in bytes. - /// - /// This is calculated from `sysconf(_SC_PAGESIZE)`. - static ref PAGESIZE: u64 = { - page_size() - }; -} - -fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult { - match unit { - "B" => Ok(num), - "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024), - "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024), - "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024), - unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))), - } -} - -trait FromStrRadix: Sized { +// TODO temporary, only for procfs +pub trait FromStrRadix: Sized { fn from_str_radix(t: &str, radix: u32) -> Result; } @@ -353,7 +231,8 @@ fn split_into_num(s: &str, sep: char, radix: u32) -> ProcResult /// This is used to hold both an IO error as well as the path of the file that originated the error #[derive(Debug)] -struct IoErrorWrapper { +// TODO only for procfs +pub struct IoErrorWrapper { path: PathBuf, inner: std::io::Error, } @@ -365,66 +244,6 @@ impl fmt::Display for IoErrorWrapper { } } -/// A wrapper around a `File` that remembers the name of the path -struct FileWrapper { - inner: File, - path: PathBuf, -} - -impl FileWrapper { - fn open>(path: P) -> Result { - let p = path.as_ref(); - let f = wrap_io_error!(p, File::open(p))?; - Ok(FileWrapper { - inner: f, - path: p.to_owned(), - }) - } - fn open_at(root: P, dirfd: Fd, path: Q) -> Result - where - P: AsRef, - Q: AsRef, - { - use rustix::fs::{Mode, OFlags}; - - let p = root.as_ref().join(path.as_ref()); - let fd = wrap_io_error!( - p, - rustix::fs::openat(dirfd, path.as_ref(), OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty()) - )?; - Ok(FileWrapper { - inner: File::from(fd), - path: p, - }) - } - - /// Returns the inner file - fn inner(self) -> File { - self.inner - } -} - -impl Read for FileWrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - wrap_io_error!(self.path, self.inner.read(buf)) - } - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - wrap_io_error!(self.path, self.inner.read_to_end(buf)) - } - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - wrap_io_error!(self.path, self.inner.read_to_string(buf)) - } - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - wrap_io_error!(self.path, self.inner.read_exact(buf)) - } -} - -impl Seek for FileWrapper { - fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - wrap_io_error!(self.path, self.inner.seek(pos)) - } -} - /// The main error type for the procfs crate. /// /// For more info, see the [ProcError] type. @@ -514,10 +333,12 @@ impl From for ProcError { ErrorKind::PermissionDenied => ProcError::PermissionDenied(Some(path)), ErrorKind::NotFound => ProcError::NotFound(Some(path)), _other => { - use rustix::io::Errno; - if matches!(wrapper.inner.raw_os_error(), Some(raw) if raw == Errno::SRCH.raw_os_error()) { + // All platforms happen to have ESRCH=3, and windows actually + // translates it to a `NotFound` anyway. + const ESRCH: i32 = 3; + if matches!(wrapper.inner.raw_os_error(), Some(raw) if raw == ESRCH) { // This "No such process" error gets mapped into a NotFound error - ProcError::NotFound(Some(path)) + return ProcError::NotFound(Some(path)); } else { ProcError::Io(wrapper.inner, Some(path)) } @@ -603,12 +424,7 @@ pub struct LoadAverage { } impl LoadAverage { - /// Reads load average info from `/proc/loadavg` - pub fn new() -> ProcResult { - LoadAverage::from_reader(FileWrapper::open("/proc/loadavg")?) - } - - /// Get LoadAverage from a custom Read instead of the default `/proc/loadavg`. + /// Get LoadAverage from a Read stream. pub fn from_reader(r: R) -> ProcResult { let mut reader = BufReader::new(r); let mut line = String::new(); @@ -637,65 +453,6 @@ impl LoadAverage { } } -/// Return the number of ticks per second. -/// -/// This isn't part of the proc file system, but it's a useful thing to have, since several fields -/// count in ticks. This is calculated from `sysconf(_SC_CLK_TCK)`. -pub fn ticks_per_second() -> u64 { - rustix::param::clock_ticks_per_second() -} - -/// The boot time of the system, as a `DateTime` object. -/// -/// This is calculated from `/proc/stat`. -/// -/// This function requires the "chrono" features to be enabled (which it is by default). -#[cfg(feature = "chrono")] -pub fn boot_time() -> ProcResult> { - use chrono::TimeZone; - let secs = boot_time_secs()?; - - let date_time = expect!(chrono::Local.timestamp_opt(secs as i64, 0).single()); - - Ok(date_time) -} - -/// The boottime of the system, in seconds since the epoch -/// -/// This is calculated from `/proc/stat`. -/// -#[cfg_attr( - not(feature = "chrono"), - doc = "If you compile with the optional `chrono` feature, you can use the `boot_time()` method to get the boot time as a `DateTime` object." -)] -#[cfg_attr( - feature = "chrono", - doc = "See also [boot_time()] to get the boot time as a `DateTime`" -)] -pub fn boot_time_secs() -> ProcResult { - BOOT_TIME.with(|x| { - let mut btime = x.borrow_mut(); - if let Some(btime) = *btime { - Ok(btime) - } else { - let stat = KernelStats::new()?; - *btime = Some(stat.btime); - Ok(stat.btime) - } - }) -} - -thread_local! { - static BOOT_TIME : std::cell::RefCell> = std::cell::RefCell::new(None); -} - -/// Memory page size, in bytes. -/// -/// This is calculated from `sysconf(_SC_PAGESIZE)`. -pub fn page_size() -> u64 { - rustix::param::page_size() as u64 -} - /// Possible values for a kernel config option #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConfigSetting { @@ -703,48 +460,13 @@ pub enum ConfigSetting { Module, Value(String), } -/// Returns a configuration options used to build the currently running kernel -/// -/// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. -/// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). -/// -/// # Notes -/// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled -/// (which it is by default). -#[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")] -#[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")] -pub fn kernel_config() -> ProcResult> { - let reader: Box = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") { - #[cfg(feature = "flate2")] - { - let file = FileWrapper::open(PROC_CONFIG_GZ)?; - let decoder = flate2::read::GzDecoder::new(file); - Box::new(BufReader::new(decoder)) - } - #[cfg(not(feature = "flate2"))] - { - unreachable!("flate2 feature not enabled") - } - } else { - let kernel = rustix::process::uname(); - - let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy()); - - match FileWrapper::open(filename) { - Ok(file) => Box::new(BufReader::new(file)), - Err(e) => match e.kind() { - io::ErrorKind::NotFound => { - let file = FileWrapper::open(BOOT_CONFIG)?; - Box::new(BufReader::new(file)) - } - _ => return Err(e.into()), - }, - } - }; +/// Reads the configuration options used to build a kernel. +pub fn kernel_config_from_read(read: R) -> ProcResult> { + let read = BufReader::new(read); let mut map = HashMap::new(); - for line in reader.lines() { + for line in read.lines() { let line = line?; if line.starts_with('#') { continue; @@ -827,12 +549,12 @@ pub struct CpuTime { } impl CpuTime { - fn from_str(s: &str) -> ProcResult { + fn from_str(s: &str, ticks_per_second: u64) -> ProcResult { let mut s = s.split_whitespace(); // Store this field in the struct so we don't have to attempt to unwrap ticks_per_second() when we convert // from ticks into other time units - let tps = crate::ticks_per_second(); + let tps = ticks_per_second; s.next(); let user = from_str!(u64, expect!(s.next())); @@ -1003,11 +725,8 @@ pub struct KernelStats { } impl KernelStats { - pub fn new() -> ProcResult { - KernelStats::from_reader(FileWrapper::open("/proc/stat")?) - } - /// Get KernelStatus from a custom Read instead of the default `/proc/stat`. - pub fn from_reader(r: R) -> ProcResult { + /// Get KernelStatus from a Read stream. + pub fn from_reader(r: R, ticks_per_second: u64) -> ProcResult { let bufread = BufReader::new(r); let lines = bufread.lines(); @@ -1022,9 +741,9 @@ impl KernelStats { for line in lines { let line = line?; if line.starts_with("cpu ") { - total_cpu = Some(CpuTime::from_str(&line)?); + total_cpu = Some(CpuTime::from_str(&line, ticks_per_second)?); } else if line.starts_with("cpu") { - cpus.push(CpuTime::from_str(&line)?); + cpus.push(CpuTime::from_str(&line, ticks_per_second)?); } else if let Some(stripped) = line.strip_prefix("ctxt ") { ctxt = Some(from_str!(u64, stripped)); } else if let Some(stripped) = line.strip_prefix("btime ") { @@ -1056,13 +775,8 @@ impl KernelStats { /// and because most of them are not well documented, this function /// returns a HashMap instead of a struct. Consult the kernel source /// code for more details of this data. -/// -/// This data is taken from the `/proc/vmstat` file. -/// -/// (since Linux 2.6.0) -pub fn vmstat() -> ProcResult> { - let file = FileWrapper::open("/proc/vmstat")?; - let reader = BufReader::new(file); +pub fn vmstat_from_read(r: R) -> ProcResult> { + let reader = BufReader::new(r); let mut map = HashMap::new(); for line in reader.lines() { let line = line?; @@ -1102,13 +816,11 @@ pub struct KernelModule { /// Get a list of loaded kernel modules /// -/// This corresponds to the data in `/proc/modules`. -pub fn modules() -> ProcResult> { +/// `r` should correspond to the data in `/proc/modules`. +pub fn modules_from_read(r: R) -> ProcResult> { // kernel reference: kernel/module.c m_show() - let mut map = HashMap::new(); - let file = FileWrapper::open("/proc/modules")?; - let reader = BufReader::new(file); + let reader = BufReader::new(r); for line in reader.lines() { let line: String = line?; let mut s = line.split_whitespace(); @@ -1143,11 +855,10 @@ pub fn modules() -> ProcResult> { /// Get a list of the arguments passed to the Linux kernel at boot time /// -/// This corresponds to the data in `/proc/cmdline` -pub fn cmdline() -> ProcResult> { +/// `r` should correspond to the data in `/proc/cmdline` +pub fn cmdline_from_read(mut r: R) -> ProcResult> { let mut buf = String::new(); - let mut f = FileWrapper::open("/proc/cmdline")?; - f.read_to_string(&mut buf)?; + r.read_to_string(&mut buf)?; Ok(buf .split(' ') .filter_map(|s| if !s.is_empty() { Some(s.to_string()) } else { None }) @@ -1158,13 +869,6 @@ pub fn cmdline() -> ProcResult> { mod tests { use super::*; - #[test] - fn test_statics() { - println!("{:?}", *TICKS_PER_SECOND); - println!("{:?}", *KERNEL); - println!("{:?}", *PAGESIZE); - } - #[test] fn test_kernel_from_str() { let k = KernelVersion::from_str("1.2.3").unwrap(); @@ -1200,12 +904,6 @@ mod tests { assert!(e > b); } - #[test] - fn test_loadavg() { - let load = LoadAverage::new().unwrap(); - println!("{:?}", load); - } - #[test] fn test_loadavg_from_reader() -> ProcResult<()> { let load_average = LoadAverage::from_reader("2.63 1.00 1.42 3/4280 2496732".as_bytes())?; @@ -1237,51 +935,6 @@ mod tests { assert!(inner().is_err()) } - #[test] - fn test_kernel_config() { - // TRAVIS - // we don't have access to the kernel_config on travis, so skip that test there - match std::env::var("TRAVIS") { - Ok(ref s) if s == "true" => return, - _ => {} - } - if !Path::new(PROC_CONFIG_GZ).exists() && !Path::new(BOOT_CONFIG).exists() { - return; - } - - let config = kernel_config().unwrap(); - println!("{:#?}", config); - } - - #[test] - fn test_file_io_errors() { - fn inner>(p: P) -> Result<(), ProcError> { - let mut file = FileWrapper::open(p)?; - - let mut buf = [0; 128]; - file.read_exact(&mut buf[0..128])?; - - Ok(()) - } - - let err = inner("/this_should_not_exist").unwrap_err(); - println!("{}", err); - - match err { - ProcError::NotFound(Some(p)) => { - assert_eq!(p, Path::new("/this_should_not_exist")); - } - x => panic!("Unexpected return value: {:?}", x), - } - - match inner("/proc/loadavg") { - Err(ProcError::Io(_, Some(p))) => { - assert_eq!(p, Path::new("/proc/loadavg")); - } - x => panic!("Unexpected return value: {:?}", x), - } - } - #[test] fn test_nopanic() { fn _inner() -> ProcResult { @@ -1315,117 +968,4 @@ mod tests { let r = _inner(); println!("{:?}", r); } - - #[test] - fn test_kernel_stat() { - let stat = KernelStats::new().unwrap(); - println!("{:#?}", stat); - - // the boottime from KernelStats should match the boottime from /proc/uptime - let boottime = boot_time_secs().unwrap(); - - let diff = (boottime as i32 - stat.btime as i32).abs(); - assert!(diff <= 1); - - let cpuinfo = CpuInfo::new().unwrap(); - assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len()); - - // the sum of each individual CPU should be equal to the total cpu entry - // note: on big machines with 128 cores, it seems that the differences can be rather high, - // especially when heavily loaded. So this test tolerates a 6000-tick discrepancy - // (60 seconds in a 100-tick-per-second kernel) - - let user: u64 = stat.cpu_time.iter().map(|i| i.user).sum(); - let nice: u64 = stat.cpu_time.iter().map(|i| i.nice).sum(); - let system: u64 = stat.cpu_time.iter().map(|i| i.system).sum(); - assert!( - (stat.total.user as i64 - user as i64).abs() < 6000, - "sum:{} total:{} diff:{}", - stat.total.user, - user, - stat.total.user - user - ); - assert!( - (stat.total.nice as i64 - nice as i64).abs() < 6000, - "sum:{} total:{} diff:{}", - stat.total.nice, - nice, - stat.total.nice - nice - ); - assert!( - (stat.total.system as i64 - system as i64).abs() < 6000, - "sum:{} total:{} diff:{}", - stat.total.system, - system, - stat.total.system - system - ); - - let diff = stat.total.idle as i64 - (stat.cpu_time.iter().map(|i| i.idle).sum::() as i64).abs(); - assert!(diff < 1000, "idle time difference too high: {}", diff); - } - - #[test] - fn test_vmstat() { - let stat = vmstat().unwrap(); - println!("{:?}", stat); - } - - #[test] - fn test_modules() { - let mods = modules().unwrap(); - for module in mods.values() { - println!("{:?}", module); - } - } - - #[test] - fn tests_tps() { - let tps = ticks_per_second(); - println!("{} ticks per second", tps); - } - - #[test] - fn test_cmdline() { - let cmdline = cmdline().unwrap(); - - for argument in cmdline { - println!("{}", argument); - } - } - - /// Test that our error type can be easily used with the `failure` crate - #[test] - fn test_failure() { - fn inner() -> Result<(), failure::Error> { - let _load = crate::LoadAverage::new()?; - Ok(()) - } - let _ = inner(); - - fn inner2() -> Result<(), failure::Error> { - let proc = crate::process::Process::new(1)?; - let _io = proc.maps()?; - Ok(()) - } - - let _ = inner2(); - // Unwrapping this failure should produce a message that looks like: - // thread 'tests::test_failure' panicked at 'called `Result::unwrap()` on an `Err` value: PermissionDenied(Some("/proc/1/maps"))', src/libcore/result.rs:997:5 - } - - /// Test that an ESRCH error gets mapped into a ProcError::NotFound - #[test] - fn test_esrch() { - let mut command = std::process::Command::new("sleep") - .arg("10000") - .spawn() - .expect("Failed to start sleep"); - let p = crate::process::Process::new(command.id() as i32).expect("Failed to create Process"); - command.kill().expect("Failed to kill sleep"); - command.wait().expect("Failed to wait for sleep"); - let e = p.stat().unwrap_err(); - println!("{:?}", e); - - assert!(matches!(e, ProcError::NotFound(_))); - } } diff --git a/procfs-core/src/process/limit.rs b/procfs-core/src/process/limit.rs new file mode 100644 index 00000000..27779992 --- /dev/null +++ b/procfs-core/src/process/limit.rs @@ -0,0 +1,215 @@ +use crate::{ProcError, ProcResult}; + +use std::collections::HashMap; +use std::io::{BufRead, BufReader, Read}; +use std::str::FromStr; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// Process limits +/// +/// For more details about each of these limits, see the `getrlimit` man page. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Limits { + /// Max Cpu Time + /// + /// This is a limit, in seconds, on the amount of CPU time that the process can consume. + pub max_cpu_time: Limit, + + /// Max file size + /// + /// This is the maximum size in bytes of files that the process may create. + pub max_file_size: Limit, + + /// Max data size + /// + /// This is the maximum size of the process's data segment (initialized data, uninitialized + /// data, and heap). + pub max_data_size: Limit, + + /// Max stack size + /// + /// This is the maximum size of the process stack, in bytes. + pub max_stack_size: Limit, + + /// Max core file size + /// + /// This is the maximum size of a *core* file in bytes that the process may dump. + pub max_core_file_size: Limit, + + /// Max resident set + /// + /// This is a limit (in bytes) on the process's resident set (the number of virtual pages + /// resident in RAM). + pub max_resident_set: Limit, + + /// Max processes + /// + /// This is a limit on the number of extant process (or, more precisely on Linux, threads) for + /// the real user rID of the calling process. + pub max_processes: Limit, + + /// Max open files + /// + /// This specifies a value one greater than the maximum file descriptor number that can be + /// opened by this process. + pub max_open_files: Limit, + + /// Max locked memory + /// + /// This is the maximum number of bytes of memory that may be locked into RAM. + pub max_locked_memory: Limit, + + /// Max address space + /// + /// This is the maximum size of the process's virtual memory (address space). + pub max_address_space: Limit, + + /// Max file locks + /// + /// This is a limit on the combined number of flock locks and fcntl leases that this process + /// may establish. + pub max_file_locks: Limit, + + /// Max pending signals + /// + /// This is a limit on the number of signals that may be queued for the real user rID of the + /// calling process. + pub max_pending_signals: Limit, + + /// Max msgqueue size + /// + /// This is a limit on the number of bytes that can be allocated for POSIX message queues for + /// the real user rID of the calling process. + pub max_msgqueue_size: Limit, + + /// Max nice priority + /// + /// This specifies a ceiling to which the process's nice value can be raised using + /// `setpriority` or `nice`. + pub max_nice_priority: Limit, + + /// Max realtime priority + /// + /// This specifies a ceiling on the real-time priority that may be set for this process using + /// `sched_setscheduler` and `sched_setparam`. + pub max_realtime_priority: Limit, + + /// Max realtime timeout + /// + /// This is a limit (in microseconds) on the amount of CPU time that a process scheduled under + /// a real-time scheduling policy may consume without making a blocking system call. + pub max_realtime_timeout: Limit, +} + +impl Limits { + pub fn from_reader(r: R) -> ProcResult { + let bufread = BufReader::new(r); + let mut lines = bufread.lines(); + + let mut map = HashMap::new(); + + while let Some(Ok(line)) = lines.next() { + let line = line.trim(); + if line.starts_with("Limit") { + continue; + } + let s: Vec<_> = line.split_whitespace().collect(); + let l = s.len(); + + let (hard_limit, soft_limit, name) = + if line.starts_with("Max nice priority") || line.starts_with("Max realtime priority") { + // these two limits don't have units, and so need different offsets: + let hard_limit = expect!(s.get(l - 1)).to_owned(); + let soft_limit = expect!(s.get(l - 2)).to_owned(); + let name = s[0..l - 2].join(" "); + (hard_limit, soft_limit, name) + } else { + let hard_limit = expect!(s.get(l - 2)).to_owned(); + let soft_limit = expect!(s.get(l - 3)).to_owned(); + let name = s[0..l - 3].join(" "); + (hard_limit, soft_limit, name) + }; + let _units = expect!(s.get(l - 1)); + + map.insert(name.to_owned(), (soft_limit.to_owned(), hard_limit.to_owned())); + } + + let limits = Limits { + max_cpu_time: Limit::from_pair(expect!(map.remove("Max cpu time")))?, + max_file_size: Limit::from_pair(expect!(map.remove("Max file size")))?, + max_data_size: Limit::from_pair(expect!(map.remove("Max data size")))?, + max_stack_size: Limit::from_pair(expect!(map.remove("Max stack size")))?, + max_core_file_size: Limit::from_pair(expect!(map.remove("Max core file size")))?, + max_resident_set: Limit::from_pair(expect!(map.remove("Max resident set")))?, + max_processes: Limit::from_pair(expect!(map.remove("Max processes")))?, + max_open_files: Limit::from_pair(expect!(map.remove("Max open files")))?, + max_locked_memory: Limit::from_pair(expect!(map.remove("Max locked memory")))?, + max_address_space: Limit::from_pair(expect!(map.remove("Max address space")))?, + max_file_locks: Limit::from_pair(expect!(map.remove("Max file locks")))?, + max_pending_signals: Limit::from_pair(expect!(map.remove("Max pending signals")))?, + max_msgqueue_size: Limit::from_pair(expect!(map.remove("Max msgqueue size")))?, + max_nice_priority: Limit::from_pair(expect!(map.remove("Max nice priority")))?, + max_realtime_priority: Limit::from_pair(expect!(map.remove("Max realtime priority")))?, + max_realtime_timeout: Limit::from_pair(expect!(map.remove("Max realtime timeout")))?, + }; + if cfg!(test) { + assert!(map.is_empty(), "Map isn't empty: {:?}", map); + } + Ok(limits) + } +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Limit { + pub soft_limit: LimitValue, + pub hard_limit: LimitValue, +} + +impl Limit { + fn from_pair(l: (String, String)) -> ProcResult { + let (soft, hard) = l; + Ok(Limit { + soft_limit: LimitValue::from_str(&soft)?, + hard_limit: LimitValue::from_str(&hard)?, + }) + } +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum LimitValue { + Unlimited, + Value(u64), +} + +impl LimitValue { + #[cfg(test)] + pub(crate) fn as_limit(&self) -> Option { + match self { + LimitValue::Unlimited => None, + LimitValue::Value(v) => Some(*v), + } + } +} + +impl FromStr for LimitValue { + type Err = ProcError; + fn from_str(s: &str) -> Result { + if s == "unlimited" { + Ok(LimitValue::Unlimited) + } else { + Ok(LimitValue::Value(from_str!(u64, s))) + } + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + // TODO +} diff --git a/procfs-core/src/process/mod.rs b/procfs-core/src/process/mod.rs new file mode 100644 index 00000000..30f0bae0 --- /dev/null +++ b/procfs-core/src/process/mod.rs @@ -0,0 +1,891 @@ +//! Functions and structs related to process information +//! +//! The primary source of data for functions in this module is the files in a `/proc//` +//! directory. If you have a process ID, you can use +//! [`Process::new(pid)`](struct.Process.html#method.new), otherwise you can get a +//! list of all running processes using [`all_processes()`](fn.all_processes.html). +//! +//! In case you have procfs filesystem mounted to a location other than `/proc`, +//! use [`Process::new_with_root()`](struct.Process.html#method.new_with_root). +//! +//! # Examples +//! +//! Here's a small example that prints out all processes that are running on the same tty as the calling +//! process. This is very similar to what "ps" does in its default mode. You can run this example +//! yourself with: +//! +//! > cargo run --example=ps +//! +//! ```rust +//! let me = procfs::process::Process::myself().unwrap(); +//! let me_stat = me.stat().unwrap(); +//! let tps = procfs::ticks_per_second(); +//! +//! println!("{: >10} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); +//! +//! let tty = format!("pty/{}", me_stat.tty_nr().1); +//! for prc in procfs::process::all_processes().unwrap() { +//! if let Ok(stat) = prc.unwrap().stat() { +//! if stat.tty_nr == me_stat.tty_nr { +//! // total_time is in seconds +//! let total_time = +//! (stat.utime + stat.stime) as f32 / (tps as f32); +//! println!( +//! "{: >10} {: <8} {: >8} {}", +//! stat.pid, tty, total_time, stat.comm +//! ); +//! } +//! } +//! } +//! ``` +//! +//! Here's a simple example of how you could get the total memory used by the current process. +//! There are several ways to do this. For a longer example, see the `examples/self_memory.rs` +//! file in the git repository. You can run this example with: +//! +//! > cargo run --example=self_memory +//! +//! ```rust +//! # use procfs::process::Process; +//! let me = Process::myself().unwrap(); +//! let me_stat = me.stat().unwrap(); +//! let page_size = procfs::page_size(); +//! +//! println!("== Data from /proc/self/stat:"); +//! println!("Total virtual memory used: {} bytes", me_stat.vsize); +//! println!("Total resident set: {} pages ({} bytes)", me_stat.rss, me_stat.rss as u64 * page_size); +//! ``` + +use super::*; +use crate::from_iter; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; +use std::io::Read; +use std::path::PathBuf; +use std::str::FromStr; + +mod limit; +pub use limit::*; + +mod stat; +pub use stat::*; + +mod mount; +pub use mount::*; + +mod namespaces; +pub use namespaces::*; + +mod status; +pub use status::*; + +mod schedstat; +pub use schedstat::*; + +mod smaps_rollup; +pub use smaps_rollup::*; + +mod pagemap; +pub use pagemap::*; + +bitflags! { + /// Kernel flags for a process + /// + /// See also the [Stat::flags()] method. + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct StatFlags: u32 { + /// I am an IDLE thread + const PF_IDLE = 0x0000_0002; + /// Getting shut down + const PF_EXITING = 0x0000_0004; + /// PI exit done on shut down + const PF_EXITPIDONE = 0x0000_0008; + /// I'm a virtual CPU + const PF_VCPU = 0x0000_0010; + /// I'm a workqueue worker + const PF_WQ_WORKER = 0x0000_0020; + /// Forked but didn't exec + const PF_FORKNOEXEC = 0x0000_0040; + /// Process policy on mce errors; + const PF_MCE_PROCESS = 0x0000_0080; + /// Used super-user privileges + const PF_SUPERPRIV = 0x0000_0100; + /// Dumped core + const PF_DUMPCORE = 0x0000_0200; + /// Killed by a signal + const PF_SIGNALED = 0x0000_0400; + ///Allocating memory + const PF_MEMALLOC = 0x0000_0800; + /// set_user() noticed that RLIMIT_NPROC was exceeded + const PF_NPROC_EXCEEDED = 0x0000_1000; + /// If unset the fpu must be initialized before use + const PF_USED_MATH = 0x0000_2000; + /// Used async_schedule*(), used by module init + const PF_USED_ASYNC = 0x0000_4000; + /// This thread should not be frozen + const PF_NOFREEZE = 0x0000_8000; + /// Frozen for system suspend + const PF_FROZEN = 0x0001_0000; + /// I am kswapd + const PF_KSWAPD = 0x0002_0000; + /// All allocation requests will inherit GFP_NOFS + const PF_MEMALLOC_NOFS = 0x0004_0000; + /// All allocation requests will inherit GFP_NOIO + const PF_MEMALLOC_NOIO = 0x0008_0000; + /// Throttle me less: I clean memory + const PF_LESS_THROTTLE = 0x0010_0000; + /// I am a kernel thread + const PF_KTHREAD = 0x0020_0000; + /// Randomize virtual address space + const PF_RANDOMIZE = 0x0040_0000; + /// Allowed to write to swap + const PF_SWAPWRITE = 0x0080_0000; + /// Stalled due to lack of memory + const PF_MEMSTALL = 0x0100_0000; + /// I'm an Usermodehelper process + const PF_UMH = 0x0200_0000; + /// Userland is not allowed to meddle with cpus_allowed + const PF_NO_SETAFFINITY = 0x0400_0000; + /// Early kill for mce process policy + const PF_MCE_EARLY = 0x0800_0000; + /// All allocation request will have _GFP_MOVABLE cleared + const PF_MEMALLOC_NOCMA = 0x1000_0000; + /// Thread belongs to the rt mutex tester + const PF_MUTEX_TESTER = 0x2000_0000; + /// Freezer should not count it as freezable + const PF_FREEZER_SKIP = 0x4000_0000; + /// This thread called freeze_processes() and should not be frozen + const PF_SUSPEND_TASK = 0x8000_0000; + + } +} + +bitflags! { + /// See the [coredump_filter()](struct.Process.html#method.coredump_filter) method. + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct CoredumpFlags: u32 { + const ANONYMOUS_PRIVATE_MAPPINGS = 0x01; + const ANONYMOUS_SHARED_MAPPINGS = 0x02; + const FILEBACKED_PRIVATE_MAPPINGS = 0x04; + const FILEBACKED_SHARED_MAPPINGS = 0x08; + const ELF_HEADERS = 0x10; + const PROVATE_HUGEPAGES = 0x20; + const SHARED_HUGEPAGES = 0x40; + const PRIVATE_DAX_PAGES = 0x80; + const SHARED_DAX_PAGES = 0x100; + } +} + +bitflags! { + /// The permissions a process has on memory map entries. + /// + /// Note that the `SHARED` and `PRIVATE` are mutually exclusive, so while you can + /// use `MMPermissions::all()` to construct an instance that has all bits set, + /// this particular value would never been seen in procfs. + #[derive(Default)] + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct MMPermissions: u8 { + /// No permissions + const NONE = 0; + /// Read permission + const READ = 1 << 0; + /// Write permission + const WRITE = 1 << 1; + /// Execute permission + const EXECUTE = 1 << 2; + /// Memory is shared with another process. + /// + /// Mutually exclusive with PRIVATE. + const SHARED = 1 << 3; + /// Memory is private (and copy-on-write) + /// + /// Mutually exclusive with SHARED. + const PRIVATE = 1 << 4; + } +} + +impl MMPermissions { + fn from_ascii_char(b: u8) -> Self { + match b { + b'r' => Self::READ, + b'w' => Self::WRITE, + b'x' => Self::EXECUTE, + b's' => Self::SHARED, + b'p' => Self::PRIVATE, + _ => Self::NONE, + } + } + /// Returns this permission map as a 4-character string, similar to what you + /// might see in `/proc/\/maps`. + /// + /// Note that the SHARED and PRIVATE bits are mutually exclusive, so this + /// string is 4 characters long, not 5. + pub fn as_str(&self) -> String { + let mut s = String::with_capacity(4); + s.push(if self.contains(Self::READ) { 'r' } else { '-' }); + s.push(if self.contains(Self::WRITE) { 'w' } else { '-' }); + s.push(if self.contains(Self::EXECUTE) { 'x' } else { '-' }); + s.push(if self.contains(Self::SHARED) { + 's' + } else if self.contains(Self::PRIVATE) { + 'p' + } else { + '-' + }); + + s + } +} + +impl FromStr for MMPermissions { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result { + // Only operate on ASCII (byte) values + Ok(s.bytes() + .map(Self::from_ascii_char) + .fold(Self::default(), std::ops::BitOr::bitor)) + } +} + +bitflags! { + /// Represents the kernel flags associated with the virtual memory area. + /// The names of these flags are just those you'll find in the man page, but in upper case. + #[derive(Default)] + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct VmFlags: u32 { + /// No flags + const NONE = 0; + /// Readable + const RD = 1 << 0; + /// Writable + const WR = 1 << 1; + /// Executable + const EX = 1 << 2; + /// Shared + const SH = 1 << 3; + /// May read + const MR = 1 << 4; + /// May write + const MW = 1 << 5; + /// May execute + const ME = 1 << 6; + /// May share + const MS = 1 << 7; + /// Stack segment grows down + const GD = 1 << 8; + /// Pure PFN range + const PF = 1 << 9; + /// Disable write to the mapped file + const DW = 1 << 10; + /// Pages are locked in memory + const LO = 1 << 11; + /// Memory mapped I/O area + const IO = 1 << 12; + /// Sequential read advise provided + const SR = 1 << 13; + /// Random read provided + const RR = 1 << 14; + /// Do not copy area on fork + const DC = 1 << 15; + /// Do not expand area on remapping + const DE = 1 << 16; + /// Area is accountable + const AC = 1 << 17; + /// Swap space is not reserved for the area + const NR = 1 << 18; + /// Area uses huge TLB pages + const HT = 1 << 19; + /// Perform synchronous page faults (since Linux 4.15) + const SF = 1 << 20; + /// Non-linear mapping (removed in Linux 4.0) + const NL = 1 << 21; + /// Architecture specific flag + const AR = 1 << 22; + /// Wipe on fork (since Linux 4.14) + const WF = 1 << 23; + /// Do not include area into core dump + const DD = 1 << 24; + /// Soft-dirty flag (since Linux 3.13) + const SD = 1 << 25; + /// Mixed map area + const MM = 1 << 26; + /// Huge page advise flag + const HG = 1 << 27; + /// No-huge page advise flag + const NH = 1 << 28; + /// Mergeable advise flag + const MG = 1 << 29; + /// Userfaultfd missing pages tracking (since Linux 4.3) + const UM = 1 << 30; + /// Userfaultfd wprotect pages tracking (since Linux 4.3) + const UW = 1 << 31; + } +} + +impl VmFlags { + fn from_str(flag: &str) -> Self { + if flag.len() != 2 { + return VmFlags::NONE; + } + + match flag { + "rd" => VmFlags::RD, + "wr" => VmFlags::WR, + "ex" => VmFlags::EX, + "sh" => VmFlags::SH, + "mr" => VmFlags::MR, + "mw" => VmFlags::MW, + "me" => VmFlags::ME, + "ms" => VmFlags::MS, + "gd" => VmFlags::GD, + "pf" => VmFlags::PF, + "dw" => VmFlags::DW, + "lo" => VmFlags::LO, + "io" => VmFlags::IO, + "sr" => VmFlags::SR, + "rr" => VmFlags::RR, + "dc" => VmFlags::DC, + "de" => VmFlags::DE, + "ac" => VmFlags::AC, + "nr" => VmFlags::NR, + "ht" => VmFlags::HT, + "sf" => VmFlags::SF, + "nl" => VmFlags::NL, + "ar" => VmFlags::AR, + "wf" => VmFlags::WF, + "dd" => VmFlags::DD, + "sd" => VmFlags::SD, + "mm" => VmFlags::MM, + "hg" => VmFlags::HG, + "nh" => VmFlags::NH, + "mg" => VmFlags::MG, + "um" => VmFlags::UM, + "uw" => VmFlags::UW, + _ => VmFlags::NONE, + } + } +} + +/// Represents the state of a process. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ProcState { + /// Running (R) + Running, + /// Sleeping in an interruptible wait (S) + Sleeping, + /// Waiting in uninterruptible disk sleep (D) + Waiting, + /// Zombie (Z) + Zombie, + /// Stopped (on a signal) (T) + /// + /// Or before Linux 2.6.33, trace stopped + Stopped, + /// Tracing stop (t) (Linux 2.6.33 onward) + Tracing, + /// Dead (X) + Dead, + /// Wakekill (K) (Linux 2.6.33 to 3.13 only) + Wakekill, + /// Waking (W) (Linux 2.6.33 to 3.13 only) + Waking, + /// Parked (P) (Linux 3.9 to 3.13 only) + Parked, + /// Idle (I) + Idle, +} + +impl ProcState { + pub fn from_char(c: char) -> Option { + match c { + 'R' => Some(ProcState::Running), + 'S' => Some(ProcState::Sleeping), + 'D' => Some(ProcState::Waiting), + 'Z' => Some(ProcState::Zombie), + 'T' => Some(ProcState::Stopped), + 't' => Some(ProcState::Tracing), + 'X' | 'x' => Some(ProcState::Dead), + 'K' => Some(ProcState::Wakekill), + 'W' => Some(ProcState::Waking), + 'P' => Some(ProcState::Parked), + 'I' => Some(ProcState::Idle), + _ => None, + } + } +} + +impl FromStr for ProcState { + type Err = ProcError; + fn from_str(s: &str) -> Result { + ProcState::from_char(expect!(s.chars().next(), "empty string")) + .ok_or_else(|| build_internal_error!("failed to convert")) + } +} + +/// This struct contains I/O statistics for the process, built from `/proc//io` +/// +/// # Note +/// +/// In the current implementation, things are a bit racy on 32-bit systems: if process A +/// reads process B's `/proc//io` while process B is updating one of these 64-bit +/// counters, process A could see an intermediate result. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Io { + /// Characters read + /// + /// The number of bytes which this task has caused to be read from storage. This is simply the + /// sum of bytes which this process passed to read(2) and similar system calls. It includes + /// things such as terminal I/O and is unaffected by whether or not actual physical disk I/O + /// was required (the read might have been satisfied from pagecache). + pub rchar: u64, + + /// characters written + /// + /// The number of bytes which this task has caused, or shall cause to be written to disk. + /// Similar caveats apply here as with rchar. + pub wchar: u64, + /// read syscalls + /// + /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) + /// and pwrite(2). + pub syscr: u64, + /// write syscalls + /// + /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) + /// and pwrite(2). + pub syscw: u64, + /// bytes read + /// + /// Attempt to count the number of bytes which this process really did cause to be fetched from + /// the storage layer. This is accurate for block-backed filesystems. + pub read_bytes: u64, + /// bytes written + /// + /// Attempt to count the number of bytes which this process caused to be sent to the storage layer. + pub write_bytes: u64, + /// Cancelled write bytes. + /// + /// The big inaccuracy here is truncate. If a process writes 1MB to a file and then deletes + /// the file, it will in fact perform no write‐ out. But it will have been accounted as having + /// caused 1MB of write. In other words: this field represents the number of bytes which this + /// process caused to not happen, by truncating pagecache. A task can cause "negative" I/O too. + /// If this task truncates some dirty pagecache, some I/O which another task has been accounted + /// for (in its write_bytes) will not be happening. + pub cancelled_write_bytes: u64, +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum MMapPath { + /// The file that is backing the mapping. + Path(PathBuf), + /// The process's heap. + Heap, + /// The initial process's (also known as the main thread's) stack. + Stack, + /// A thread's stack (where the `` is a thread ID). It corresponds to the + /// `/proc//task//` path. + /// + /// (since Linux 3.4) + TStack(u32), + /// The virtual dynamically linked shared object. + Vdso, + /// Shared kernel variables + Vvar, + /// obsolete virtual syscalls, succeeded by vdso + Vsyscall, + /// rollup memory mappings, from `/proc//smaps_rollup` + Rollup, + /// An anonymous mapping as obtained via mmap(2). + Anonymous, + /// Shared memory segment + Vsys(i32), + /// Some other pseudo-path + Other(String), +} + +impl MMapPath { + fn from(path: &str) -> ProcResult { + Ok(match path.trim() { + "" => MMapPath::Anonymous, + "[heap]" => MMapPath::Heap, + "[stack]" => MMapPath::Stack, + "[vdso]" => MMapPath::Vdso, + "[vvar]" => MMapPath::Vvar, + "[vsyscall]" => MMapPath::Vsyscall, + "[rollup]" => MMapPath::Rollup, + x if x.starts_with("[stack:") => { + let mut s = x[1..x.len() - 1].split(':'); + let tid = from_str!(u32, expect!(s.nth(1))); + MMapPath::TStack(tid) + } + x if x.starts_with('[') && x.ends_with(']') => MMapPath::Other(x[1..x.len() - 1].to_string()), + x if x.starts_with("/SYSV") => MMapPath::Vsys(u32::from_str_radix(&x[5..13], 16)? as i32), // 32bits signed hex. /SYSVaabbccdd (deleted) + x => MMapPath::Path(PathBuf::from(x)), + }) + } +} + +/// Represents all entries in a `/proc//maps` or `/proc//smaps` file. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct MemoryMaps { + pub memory_maps: Vec, +} + +impl MemoryMaps { + /// Read a [MemoryMaps] from the given byte source. + /// + /// The data should be formatted according to procfs /proc/pid/{maps,smaps,smaps_rollup}. + pub fn from_reader(r: R) -> ProcResult { + Self::read(r, None) + } + + /// Return an iterator over [MemoryMap]. + pub fn iter(&self) -> std::slice::Iter { + self.memory_maps.iter() + } + + fn read(r: R, path: Option<&Path>) -> ProcResult { + let reader = BufReader::new(r); + + let mut memory_maps = Vec::new(); + + let mut line_iter = reader + .lines() + .map(|r| r.map_err(|_| ProcError::Incomplete(path.map(ToOwned::to_owned)))); + let mut current_memory_map: Option = None; + while let Some(line) = line_iter.next().transpose()? { + // Assumes all extension fields (in `/proc//smaps`) start with a capital letter, + // which seems to be the case. + if line.starts_with(|c: char| c.is_ascii_uppercase()) { + match current_memory_map.as_mut() { + None => return Err(ProcError::Incomplete(path.map(ToOwned::to_owned))), + Some(mm) => { + // This is probably an attribute + if line.starts_with("VmFlags") { + let flags = line.split_ascii_whitespace(); + let flags = flags.skip(1); // Skips the `VmFlags:` part since we don't need it. + + let flags = flags + .map(VmFlags::from_str) + // FUTURE: use `Iterator::reduce` + .fold(VmFlags::NONE, std::ops::BitOr::bitor); + + mm.extension.vm_flags = flags; + } else { + let mut parts = line.split_ascii_whitespace(); + + let key = parts.next(); + let value = parts.next(); + + if let (Some(k), Some(v)) = (key, value) { + // While most entries do have one, not all of them do. + let size_suffix = parts.next(); + + // Limited poking at /proc//smaps and then checking if "MB", "GB", and "TB" appear in the C file that is + // supposedly responsible for creating smaps, has lead me to believe that the only size suffixes we'll ever encounter + // "kB", which is most likely kibibytes. Actually checking if the size suffix is any of the above is a way to + // future-proof the code, but I am not sure it is worth doing so. + let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 }; + + let v = v.parse::().map_err(|_| { + ProcError::Other("Value in `Key: Value` pair was not actually a number".into()) + })?; + + // This ignores the case when our Key: Value pairs are really Key Value pairs. Is this a good idea? + let k = k.trim_end_matches(':'); + + mm.extension.map.insert(k.into(), v * size_multiplier); + } + } + } + } + } else { + if let Some(mm) = current_memory_map.take() { + memory_maps.push(mm); + } + current_memory_map = Some(MemoryMap::from_line(&line)?); + } + } + if let Some(mm) = current_memory_map.take() { + memory_maps.push(mm); + } + + Ok(MemoryMaps { memory_maps }) + } +} + +impl<'a> IntoIterator for &'a MemoryMaps { + type IntoIter = std::slice::Iter<'a, MemoryMap>; + type Item = &'a MemoryMap; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for MemoryMaps { + type IntoIter = std::vec::IntoIter; + type Item = MemoryMap; + + fn into_iter(self) -> Self::IntoIter { + self.memory_maps.into_iter() + } +} + +/// Represents an entry in a `/proc//maps` or `/proc//smaps` file. +/// +/// To construct this structure for the current process, see [Process::maps()] and +/// [Process::smaps()]. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MemoryMap { + /// The address space in the process that the mapping occupies. + pub address: (u64, u64), + pub perms: MMPermissions, + /// The offset into the file/whatever + pub offset: u64, + /// The device (major, minor) + pub dev: (i32, i32), + /// The inode on that device + /// + /// 0 indicates that no inode is associated with the memory region, as would be the case with + /// BSS (uninitialized data). + pub inode: u64, + pub pathname: MMapPath, + /// Memory mapping extension information, populated when parsing `/proc//smaps`. + /// + /// The members will be `Default::default()` (empty/none) when the information isn't available. + pub extension: MMapExtension, +} + +impl MemoryMap { + fn from_line(line: &str) -> ProcResult { + let mut s = line.splitn(6, ' '); + let address = expect!(s.next()); + let perms = expect!(s.next()); + let offset = expect!(s.next()); + let dev = expect!(s.next()); + let inode = expect!(s.next()); + let path = expect!(s.next()); + + Ok(MemoryMap { + address: split_into_num(address, '-', 16)?, + perms: perms.parse()?, + offset: from_str!(u64, offset, 16), + dev: split_into_num(dev, ':', 16)?, + inode: from_str!(u64, inode), + pathname: MMapPath::from(path)?, + extension: Default::default(), + }) + } +} + +/// Represents the information about a specific mapping as presented in /proc/\/smaps +/// +/// To construct this structure, see [Process::smaps()] +#[derive(Default, Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MMapExtension { + /// Key-value pairs that may represent statistics about memory usage, or other interesting things, + /// such a "ProtectionKey" (if you're on X86 and that kernel config option was specified). + /// + /// Note that should a key-value pair represent a memory usage statistic, it will be in bytes. + /// + /// Check your manpage for more information + pub map: HashMap, + /// Kernel flags associated with the virtual memory area + /// + /// (since Linux 3.8) + pub vm_flags: VmFlags, +} + +impl MMapExtension { + /// Return whether the extension information is empty. + pub fn is_empty(&self) -> bool { + self.map.is_empty() && self.vm_flags == VmFlags::NONE + } +} + +impl Io { + pub fn from_reader(r: R) -> ProcResult { + let mut map = HashMap::new(); + let reader = BufReader::new(r); + + for line in reader.lines() { + let line = line?; + if line.is_empty() || !line.contains(' ') { + continue; + } + let mut s = line.split_whitespace(); + let field = expect!(s.next()); + let value = expect!(s.next()); + + let value = from_str!(u64, value); + + map.insert(field[..field.len() - 1].to_string(), value); + } + let io = Io { + rchar: expect!(map.remove("rchar")), + wchar: expect!(map.remove("wchar")), + syscr: expect!(map.remove("syscr")), + syscw: expect!(map.remove("syscw")), + read_bytes: expect!(map.remove("read_bytes")), + write_bytes: expect!(map.remove("write_bytes")), + cancelled_write_bytes: expect!(map.remove("cancelled_write_bytes")), + }; + + assert!(!cfg!(test) || map.is_empty(), "io map is not empty: {:#?}", map); + + Ok(io) + } +} + +/// Describes a file descriptor opened by a process. +/// +/// See also the [Process::fd()] method. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum FDTarget { + /// A file or device + Path(PathBuf), + /// A socket type, with an inode + Socket(u64), + Net(u64), + Pipe(u64), + /// A file descriptor that have no corresponding inode. + AnonInode(String), + /// A memfd file descriptor with a name. + MemFD(String), + /// Some other file descriptor type, with an inode. + Other(String, u64), +} + +impl FromStr for FDTarget { + type Err = ProcError; + fn from_str(s: &str) -> Result { + // helper function that removes the first and last character + fn strip_first_last(s: &str) -> ProcResult<&str> { + if s.len() > 2 { + let mut c = s.chars(); + // remove the first and last characters + let _ = c.next(); + let _ = c.next_back(); + Ok(c.as_str()) + } else { + Err(ProcError::Incomplete(None)) + } + } + + if !s.starts_with('/') && s.contains(':') { + let mut s = s.split(':'); + let fd_type = expect!(s.next()); + match fd_type { + "socket" => { + let inode = expect!(s.next(), "socket inode"); + let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); + Ok(FDTarget::Socket(inode)) + } + "net" => { + let inode = expect!(s.next(), "net inode"); + let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); + Ok(FDTarget::Net(inode)) + } + "pipe" => { + let inode = expect!(s.next(), "pipe inode"); + let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); + Ok(FDTarget::Pipe(inode)) + } + "anon_inode" => Ok(FDTarget::AnonInode(expect!(s.next(), "anon inode").to_string())), + "" => Err(ProcError::Incomplete(None)), + x => { + let inode = expect!(s.next(), "other inode"); + let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); + Ok(FDTarget::Other(x.to_string(), inode)) + } + } + } else if let Some(s) = s.strip_prefix("/memfd:") { + Ok(FDTarget::MemFD(s.to_string())) + } else { + Ok(FDTarget::Path(PathBuf::from(s))) + } + } +} + +/// Provides information about memory usage, measured in pages. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct StatM { + /// Total program size, measured in pages + /// + /// (same as VmSize in /proc/\/status) + pub size: u64, + /// Resident set size, measured in pages + /// + /// (same as VmRSS in /proc/\/status) + pub resident: u64, + /// number of resident shared pages (i.e., backed by a file) + /// + /// (same as RssFile+RssShmem in /proc/\/status) + pub shared: u64, + /// Text (code) + pub text: u64, + /// library (unused since Linux 2.6; always 0) + pub lib: u64, + /// data + stack + pub data: u64, + /// dirty pages (unused since Linux 2.6; always 0) + pub dt: u64, +} + +impl StatM { + pub fn from_reader(mut r: R) -> ProcResult { + let mut line = String::new(); + r.read_to_string(&mut line)?; + let mut s = line.split_whitespace(); + + let size = expect!(from_iter(&mut s)); + let resident = expect!(from_iter(&mut s)); + let shared = expect!(from_iter(&mut s)); + let text = expect!(from_iter(&mut s)); + let lib = expect!(from_iter(&mut s)); + let data = expect!(from_iter(&mut s)); + let dt = expect!(from_iter(&mut s)); + + if cfg!(test) { + assert!(s.next().is_none()); + } + + Ok(StatM { + size, + resident, + shared, + text, + lib, + data, + dt, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_memory_map_permissions() { + use MMPermissions as P; + assert_eq!("rw-p".parse(), Ok(P::READ | P::WRITE | P::PRIVATE)); + assert_eq!("r-xs".parse(), Ok(P::READ | P::EXECUTE | P::SHARED)); + assert_eq!("----".parse(), Ok(P::NONE)); + + assert_eq!((P::READ | P::WRITE | P::PRIVATE).as_str(), "rw-p"); + assert_eq!((P::READ | P::EXECUTE | P::SHARED).as_str(), "r-xs"); + assert_eq!(P::NONE.as_str(), "----"); + } +} diff --git a/procfs-core/src/process/mount.rs b/procfs-core/src/process/mount.rs new file mode 100644 index 00000000..071de894 --- /dev/null +++ b/procfs-core/src/process/mount.rs @@ -0,0 +1,611 @@ +use bitflags::bitflags; + +use crate::{from_iter, ProcResult}; + +use std::collections::HashMap; +use std::io::{BufRead, BufReader, Lines, Read}; +use std::path::PathBuf; +use std::time::Duration; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +bitflags! { + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct NFSServerCaps: u32 { + + const NFS_CAP_READDIRPLUS = 1; + const NFS_CAP_HARDLINKS = (1 << 1); + const NFS_CAP_SYMLINKS = (1 << 2); + const NFS_CAP_ACLS = (1 << 3); + const NFS_CAP_ATOMIC_OPEN = (1 << 4); + const NFS_CAP_LGOPEN = (1 << 5); + const NFS_CAP_FILEID = (1 << 6); + const NFS_CAP_MODE = (1 << 7); + const NFS_CAP_NLINK = (1 << 8); + const NFS_CAP_OWNER = (1 << 9); + const NFS_CAP_OWNER_GROUP = (1 << 10); + const NFS_CAP_ATIME = (1 << 11); + const NFS_CAP_CTIME = (1 << 12); + const NFS_CAP_MTIME = (1 << 13); + const NFS_CAP_POSIX_LOCK = (1 << 14); + const NFS_CAP_UIDGID_NOMAP = (1 << 15); + const NFS_CAP_STATEID_NFSV41 = (1 << 16); + const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17); + const NFS_CAP_SECURITY_LABEL = (1 << 18); + const NFS_CAP_SEEK = (1 << 19); + const NFS_CAP_ALLOCATE = (1 << 20); + const NFS_CAP_DEALLOCATE = (1 << 21); + const NFS_CAP_LAYOUTSTATS = (1 << 22); + const NFS_CAP_CLONE = (1 << 23); + const NFS_CAP_COPY = (1 << 24); + const NFS_CAP_OFFLOAD_CANCEL = (1 << 25); + } +} + +/// Information about a specific mount in a process's mount namespace. +/// +/// This data is taken from the `/proc/[pid]/mountinfo` file. +/// +/// For an example, see the +/// [mountinfo.rs](/~https://github.com/eminence/procfs/tree/master/procfs/examples) example in the +/// source repo. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MountInfo { + /// Mount ID. A unique ID for the mount (but may be reused after `unmount`) + pub mnt_id: i32, + /// Parent mount ID. The ID of the parent mount (or of self for the root of the mount + /// namespace's mount tree). + /// + /// If the parent mount point lies outside the process's root directory, the ID shown here + /// won't have a corresponding record in mountinfo whose mount ID matches this parent mount + /// ID (because mount points that lie outside the process's root directory are not shown in + /// mountinfo). As a special case of this point, the process's root mount point may have a + /// parent mount (for the initramfs filesystem) that lies outside the process's root + /// directory, and an entry for that mount point will not appear in mountinfo. + pub pid: i32, + /// The value of `st_dev` for files on this filesystem + pub majmin: String, + /// The pathname of the directory in the filesystem which forms the root of this mount. + pub root: String, + /// The pathname of the mount point relative to the process's root directory. + pub mount_point: PathBuf, + /// Per-mount options + pub mount_options: HashMap>, + /// Optional fields + pub opt_fields: Vec, + /// Filesystem type + pub fs_type: String, + /// Mount source + pub mount_source: Option, + /// Per-superblock options. + pub super_options: HashMap>, +} + +impl MountInfo { + pub fn from_line(line: &str) -> ProcResult { + let mut split = line.split_whitespace(); + + let mnt_id = expect!(from_iter(&mut split)); + let pid = expect!(from_iter(&mut split)); + let majmin: String = expect!(from_iter(&mut split)); + let root = expect!(from_iter(&mut split)); + let mount_point = expect!(from_iter(&mut split)); + let mount_options = { + let mut map = HashMap::new(); + let all_opts = expect!(split.next()); + for opt in all_opts.split(',') { + let mut s = opt.splitn(2, '='); + let opt_name = expect!(s.next()); + map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); + } + map + }; + + let mut opt_fields = Vec::new(); + loop { + let f = expect!(split.next()); + if f == "-" { + break; + } + let mut s = f.split(':'); + let opt = match expect!(s.next()) { + "shared" => { + let val = expect!(from_iter(&mut s)); + MountOptFields::Shared(val) + } + "master" => { + let val = expect!(from_iter(&mut s)); + MountOptFields::Master(val) + } + "propagate_from" => { + let val = expect!(from_iter(&mut s)); + MountOptFields::PropagateFrom(val) + } + "unbindable" => MountOptFields::Unbindable, + _ => continue, + }; + opt_fields.push(opt); + } + let fs_type: String = expect!(from_iter(&mut split)); + let mount_source = match expect!(split.next()) { + "none" => None, + x => Some(x.to_owned()), + }; + let super_options = { + let mut map = HashMap::new(); + let all_opts = expect!(split.next()); + for opt in all_opts.split(',') { + let mut s = opt.splitn(2, '='); + let opt_name = expect!(s.next()); + map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); + } + map + }; + + Ok(MountInfo { + mnt_id, + pid, + majmin, + root, + mount_point, + mount_options, + opt_fields, + fs_type, + mount_source, + super_options, + }) + } +} + +/// Optional fields used in [MountInfo] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum MountOptFields { + /// This mount point is shared in peer group. Each peer group has a unique ID that is + /// automatically generated by the kernel, and all mount points in the same peer group will + /// show the same ID + Shared(u32), + /// THis mount is a slave to the specified shared peer group. + Master(u32), + /// This mount is a slave and receives propagation from the shared peer group + PropagateFrom(u32), + /// This is an unbindable mount + Unbindable, +} + +/// Mount information from `/proc//mountstats`. +/// +/// # Example: +/// +/// ``` +/// # use procfs::process::Process; +/// let stats = Process::myself().unwrap().mountstats().unwrap(); +/// +/// for mount in stats { +/// println!("{} mounted on {} wth type {}", +/// mount.device.unwrap_or("??".to_owned()), +/// mount.mount_point.display(), +/// mount.fs +/// ); +/// } +/// ``` +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MountStat { + /// The name of the mounted device + pub device: Option, + /// The mountpoint within the filesystem tree + pub mount_point: PathBuf, + /// The filesystem type + pub fs: String, + /// If the mount is NFS, this will contain various NFS statistics + pub statistics: Option, +} + +impl MountStat { + pub fn from_reader(r: R) -> ProcResult> { + let mut v = Vec::new(); + let bufread = BufReader::new(r); + let mut lines = bufread.lines(); + while let Some(Ok(line)) = lines.next() { + if line.starts_with("device ") { + // line will be of the format: + // device proc mounted on /proc with fstype proc + let mut s = line.split_whitespace(); + + let device = Some(expect!(s.nth(1)).to_owned()); + let mount_point = PathBuf::from(expect!(s.nth(2))); + let fs = expect!(s.nth(2)).to_owned(); + let statistics = match s.next() { + Some(stats) if stats.starts_with("statvers=") => { + Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..])?) + } + _ => None, + }; + + v.push(MountStat { + device, + mount_point, + fs, + statistics, + }); + } + } + + Ok(v) + } +} + +/// Only NFS mounts provide additional statistics in `MountStat` entries. +// +// Thank you to Chris Siebenmann for their helpful work in documenting these structures: +// https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MountNFSStatistics { + /// The version of the NFS statistics block. Either "1.0" or "1.1". + pub version: String, + /// The mount options. + /// + /// The meaning of these can be found in the manual pages for mount(5) and nfs(5) + pub opts: Vec, + /// Duration the NFS mount has been in existence. + pub age: Duration, + // * fsc (?) + // * impl_id (NFSv4): Option> + /// NFS Capabilities. + /// + /// See `include/linux/nfs_fs_sb.h` + /// + /// Some known values: + /// * caps: server capabilities. See [NFSServerCaps]. + /// * wtmult: server disk block size + /// * dtsize: readdir size + /// * bsize: server block size + pub caps: Vec, + // * nfsv4 (NFSv4): Option> + pub sec: Vec, + pub events: NFSEventCounter, + pub bytes: NFSByteCounter, + // * RPC iostats version: + // * xprt + // * per-op statistics + pub per_op_stats: NFSPerOpStats, +} + +impl MountNFSStatistics { + // Keep reading lines until we get to a blank line + fn from_lines(r: &mut Lines, statsver: &str) -> ProcResult { + let mut parsing_per_op = false; + + let mut opts: Option> = None; + let mut age = None; + let mut caps = None; + let mut sec = None; + let mut bytes = None; + let mut events = None; + let mut per_op = HashMap::new(); + + while let Some(Ok(line)) = r.next() { + let line = line.trim(); + if line.trim() == "" { + break; + } + if !parsing_per_op { + if let Some(stripped) = line.strip_prefix("opts:") { + opts = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); + } else if let Some(stripped) = line.strip_prefix("age:") { + age = Some(Duration::from_secs(from_str!(u64, stripped.trim()))); + } else if let Some(stripped) = line.strip_prefix("caps:") { + caps = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); + } else if let Some(stripped) = line.strip_prefix("sec:") { + sec = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); + } else if let Some(stripped) = line.strip_prefix("bytes:") { + bytes = Some(NFSByteCounter::from_str(stripped.trim())?); + } else if let Some(stripped) = line.strip_prefix("events:") { + events = Some(NFSEventCounter::from_str(stripped.trim())?); + } + if line == "per-op statistics" { + parsing_per_op = true; + } + } else { + let mut split = line.split(':'); + let name = expect!(split.next()).to_string(); + let stats = NFSOperationStat::from_str(expect!(split.next()))?; + per_op.insert(name, stats); + } + } + + Ok(MountNFSStatistics { + version: statsver.to_string(), + opts: expect!(opts, "Failed to find opts field in nfs stats"), + age: expect!(age, "Failed to find age field in nfs stats"), + caps: expect!(caps, "Failed to find caps field in nfs stats"), + sec: expect!(sec, "Failed to find sec field in nfs stats"), + events: expect!(events, "Failed to find events section in nfs stats"), + bytes: expect!(bytes, "Failed to find bytes section in nfs stats"), + per_op_stats: per_op, + }) + } + + /// Attempts to parse the caps= value from the [caps](struct.MountNFSStatistics.html#structfield.caps) field. + pub fn server_caps(&self) -> ProcResult> { + for data in &self.caps { + if let Some(stripped) = data.strip_prefix("caps=0x") { + let val = from_str!(u32, stripped, 16); + return Ok(NFSServerCaps::from_bits(val)); + } + } + Ok(None) + } +} + +/// Represents NFS data from `/proc//mountstats` under the section `events`. +/// +/// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. +/// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum +/// nfs_stat_eventcounters`. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct NFSEventCounter { + pub inode_revalidate: u64, + pub deny_try_revalidate: u64, + pub data_invalidate: u64, + pub attr_invalidate: u64, + pub vfs_open: u64, + pub vfs_lookup: u64, + pub vfs_access: u64, + pub vfs_update_page: u64, + pub vfs_read_page: u64, + pub vfs_read_pages: u64, + pub vfs_write_page: u64, + pub vfs_write_pages: u64, + pub vfs_get_dents: u64, + pub vfs_set_attr: u64, + pub vfs_flush: u64, + pub vfs_fs_sync: u64, + pub vfs_lock: u64, + pub vfs_release: u64, + pub congestion_wait: u64, + pub set_attr_trunc: u64, + pub extend_write: u64, + pub silly_rename: u64, + pub short_read: u64, + pub short_write: u64, + pub delay: u64, + pub pnfs_read: u64, + pub pnfs_write: u64, +} + +impl NFSEventCounter { + fn from_str(s: &str) -> ProcResult { + let mut s = s.split_whitespace(); + Ok(NFSEventCounter { + inode_revalidate: from_str!(u64, expect!(s.next())), + deny_try_revalidate: from_str!(u64, expect!(s.next())), + data_invalidate: from_str!(u64, expect!(s.next())), + attr_invalidate: from_str!(u64, expect!(s.next())), + vfs_open: from_str!(u64, expect!(s.next())), + vfs_lookup: from_str!(u64, expect!(s.next())), + vfs_access: from_str!(u64, expect!(s.next())), + vfs_update_page: from_str!(u64, expect!(s.next())), + vfs_read_page: from_str!(u64, expect!(s.next())), + vfs_read_pages: from_str!(u64, expect!(s.next())), + vfs_write_page: from_str!(u64, expect!(s.next())), + vfs_write_pages: from_str!(u64, expect!(s.next())), + vfs_get_dents: from_str!(u64, expect!(s.next())), + vfs_set_attr: from_str!(u64, expect!(s.next())), + vfs_flush: from_str!(u64, expect!(s.next())), + vfs_fs_sync: from_str!(u64, expect!(s.next())), + vfs_lock: from_str!(u64, expect!(s.next())), + vfs_release: from_str!(u64, expect!(s.next())), + congestion_wait: from_str!(u64, expect!(s.next())), + set_attr_trunc: from_str!(u64, expect!(s.next())), + extend_write: from_str!(u64, expect!(s.next())), + silly_rename: from_str!(u64, expect!(s.next())), + short_read: from_str!(u64, expect!(s.next())), + short_write: from_str!(u64, expect!(s.next())), + delay: from_str!(u64, expect!(s.next())), + pnfs_read: from_str!(u64, expect!(s.next())), + pnfs_write: from_str!(u64, expect!(s.next())), + }) + } +} + +/// Represents NFS data from `/proc//mountstats` under the section `bytes`. +/// +/// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. +/// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum +/// nfs_stat_bytecounters` +#[derive(Debug, Copy, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct NFSByteCounter { + pub normal_read: u64, + pub normal_write: u64, + pub direct_read: u64, + pub direct_write: u64, + pub server_read: u64, + pub server_write: u64, + pub pages_read: u64, + pub pages_write: u64, +} + +impl NFSByteCounter { + fn from_str(s: &str) -> ProcResult { + let mut s = s.split_whitespace(); + Ok(NFSByteCounter { + normal_read: from_str!(u64, expect!(s.next())), + normal_write: from_str!(u64, expect!(s.next())), + direct_read: from_str!(u64, expect!(s.next())), + direct_write: from_str!(u64, expect!(s.next())), + server_read: from_str!(u64, expect!(s.next())), + server_write: from_str!(u64, expect!(s.next())), + pages_read: from_str!(u64, expect!(s.next())), + pages_write: from_str!(u64, expect!(s.next())), + }) + } +} + +/// Represents NFS data from `/proc//mountstats` under the section of `per-op statistics`. +/// +/// Here is what the Kernel says about the attributes: +/// +/// Regarding `operations`, `transmissions` and `major_timeouts`: +/// +/// > These counters give an idea about how many request +/// > transmissions are required, on average, to complete that +/// > particular procedure. Some procedures may require more +/// > than one transmission because the server is unresponsive, +/// > the client is retransmitting too aggressively, or the +/// > requests are large and the network is congested. +/// +/// Regarding `bytes_sent` and `bytes_recv`: +/// +/// > These count how many bytes are sent and received for a +/// > given RPC procedure type. This indicates how much load a +/// > particular procedure is putting on the network. These +/// > counts include the RPC and ULP headers, and the request +/// > payload. +/// +/// Regarding `cum_queue_time`, `cum_resp_time` and `cum_total_req_time`: +/// +/// > The length of time an RPC request waits in queue before +/// > transmission, the network + server latency of the request, +/// > and the total time the request spent from init to release +/// > are measured. +/// +/// (source: *include/linux/sunrpc/metrics.h* `struct rpc_iostats`) +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct NFSOperationStat { + /// Count of rpc operations. + pub operations: u64, + /// Count of rpc transmissions + pub transmissions: u64, + /// Count of rpc major timeouts + pub major_timeouts: u64, + /// Count of bytes send. Does not only include the RPC payload but the RPC headers as well. + pub bytes_sent: u64, + /// Count of bytes received as `bytes_sent`. + pub bytes_recv: u64, + /// How long all requests have spend in the queue before being send. + pub cum_queue_time: Duration, + /// How long it took to get a response back. + pub cum_resp_time: Duration, + /// How long all requests have taken from beeing queued to the point they where completely + /// handled. + pub cum_total_req_time: Duration, +} + +impl NFSOperationStat { + fn from_str(s: &str) -> ProcResult { + let mut s = s.split_whitespace(); + + let operations = from_str!(u64, expect!(s.next())); + let transmissions = from_str!(u64, expect!(s.next())); + let major_timeouts = from_str!(u64, expect!(s.next())); + let bytes_sent = from_str!(u64, expect!(s.next())); + let bytes_recv = from_str!(u64, expect!(s.next())); + let cum_queue_time_ms = from_str!(u64, expect!(s.next())); + let cum_resp_time_ms = from_str!(u64, expect!(s.next())); + let cum_total_req_time_ms = from_str!(u64, expect!(s.next())); + + Ok(NFSOperationStat { + operations, + transmissions, + major_timeouts, + bytes_sent, + bytes_recv, + cum_queue_time: Duration::from_millis(cum_queue_time_ms), + cum_resp_time: Duration::from_millis(cum_resp_time_ms), + cum_total_req_time: Duration::from_millis(cum_total_req_time_ms), + }) + } +} + +pub type NFSPerOpStats = HashMap; + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[test] + fn test_mountinfo() { + let s = "25 0 8:1 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro"; + + let stat = MountInfo::from_line(s).unwrap(); + println!("{:?}", stat); + } + + #[test] + fn test_proc_mountstats() { + let simple = MountStat::from_reader( + "device /dev/md127 mounted on /boot with fstype ext2 +device /dev/md124 mounted on /home with fstype ext4 +device tmpfs mounted on /run/user/0 with fstype tmpfs +" + .as_bytes(), + ) + .unwrap(); + let simple_parsed = vec![ + MountStat { + device: Some("/dev/md127".to_string()), + mount_point: PathBuf::from("/boot"), + fs: "ext2".to_string(), + statistics: None, + }, + MountStat { + device: Some("/dev/md124".to_string()), + mount_point: PathBuf::from("/home"), + fs: "ext4".to_string(), + statistics: None, + }, + MountStat { + device: Some("tmpfs".to_string()), + mount_point: PathBuf::from("/run/user/0"), + fs: "tmpfs".to_string(), + statistics: None, + }, + ]; + assert_eq!(simple, simple_parsed); + let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1 + opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none + age: 3542 + impl_id: name='',domain='',date='0,0' + caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255 + nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured + sec: flavor=6,pseudoflavor=390003 + events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1 + bytes: 1 2 3 4 5 6 7 8 + RPC iostats version: 1.0 p/v: 100003/4 (nfs) + xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0 + per-op statistics + NULL: 0 0 0 0 0 0 0 0 + READ: 1 2 3 4 5 6 7 8 + WRITE: 0 0 0 0 0 0 0 0 + COMMIT: 0 0 0 0 0 0 0 0 + OPEN: 1 1 0 320 420 0 124 124 + ".as_bytes()).unwrap(); + let nfs_v4 = &mountstats[0]; + match &nfs_v4.statistics { + Some(stats) => { + assert_eq!("1.1".to_string(), stats.version, "mountstats version wrongly parsed."); + assert_eq!(Duration::from_secs(3542), stats.age); + assert_eq!(1, stats.bytes.normal_read); + assert_eq!(114, stats.events.inode_revalidate); + assert!(stats.server_caps().unwrap().is_some()); + } + None => { + panic!("Failed to retrieve nfs statistics"); + } + } + } +} diff --git a/procfs-core/src/process/namespaces.rs b/procfs-core/src/process/namespaces.rs new file mode 100644 index 00000000..1d7e2fc7 --- /dev/null +++ b/procfs-core/src/process/namespaces.rs @@ -0,0 +1,27 @@ +use std::{ffi::OsString, path::PathBuf}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// Information about a namespace +/// +/// See also the [Process::namespaces()] method +#[derive(Debug, Clone, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Namespace { + /// Namespace type + pub ns_type: OsString, + /// Handle to the namespace + pub path: PathBuf, + /// Namespace identifier (inode number) + pub identifier: u64, + /// Device id of the namespace + pub device_id: u64, +} + +impl PartialEq for Namespace { + fn eq(&self, other: &Self) -> bool { + // see https://lore.kernel.org/lkml/87poky5ca9.fsf@xmission.com/ + self.identifier == other.identifier && self.device_id == other.device_id + } +} diff --git a/procfs-core/src/process/pagemap.rs b/procfs-core/src/process/pagemap.rs new file mode 100644 index 00000000..dcef6c0b --- /dev/null +++ b/procfs-core/src/process/pagemap.rs @@ -0,0 +1,163 @@ +use bitflags::bitflags; +use std::{fmt, mem::size_of}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +const fn genmask(high: usize, low: usize) -> u64 { + let mask_bits = size_of::() * 8; + (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) +} + +// source: include/linux/swap.h +const MAX_SWAPFILES_SHIFT: usize = 5; + +// source: fs/proc/task_mmu.c +bitflags! { + /// Represents the fields and flags in a page table entry for a swapped page. + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct SwapPageFlags: u64 { + /// Swap type if swapped + #[doc(hidden)] + const SWAP_TYPE = genmask(MAX_SWAPFILES_SHIFT - 1, 0); + /// Swap offset if swapped + #[doc(hidden)] + const SWAP_OFFSET = genmask(54, MAX_SWAPFILES_SHIFT); + /// PTE is soft-dirty + const SOFT_DIRTY = 1 << 55; + /// Page is exclusively mapped + const MMAP_EXCLUSIVE = 1 << 56; + /// Page is file-page or shared-anon + const FILE = 1 << 61; + /// Page is swapped + #[doc(hidden)] + const SWAP = 1 << 62; + /// Page is present + const PRESENT = 1 << 63; + } +} + +impl SwapPageFlags { + /// Returns the swap type recorded in this entry. + pub fn get_swap_type(&self) -> u64 { + (*self & Self::SWAP_TYPE).bits() + } + + /// Returns the swap offset recorded in this entry. + pub fn get_swap_offset(&self) -> u64 { + (*self & Self::SWAP_OFFSET).bits() >> MAX_SWAPFILES_SHIFT + } +} + +bitflags! { + /// Represents the fields and flags in a page table entry for a memory page. + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct MemoryPageFlags: u64 { + /// Page frame number if present + #[doc(hidden)] + const PFN = genmask(54, 0); + /// PTE is soft-dirty + const SOFT_DIRTY = 1 << 55; + /// Page is exclusively mapped + const MMAP_EXCLUSIVE = 1 << 56; + /// Page is file-page or shared-anon + const FILE = 1 << 61; + /// Page is swapped + #[doc(hidden)] + const SWAP = 1 << 62; + /// Page is present + const PRESENT = 1 << 63; + } +} + +impl MemoryPageFlags { + /// Returns the page frame number recorded in this entry. + pub fn get_page_frame_number(&self) -> Pfn { + Pfn((*self & Self::PFN).bits()) + } +} + +/// A Page Frame Number, representing a 4 kiB physical memory page +/// +/// See also [crate::iomem()] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Pfn(pub u64); + +impl fmt::UpperHex for Pfn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = self.0; + + fmt::UpperHex::fmt(&val, f) + } +} + +impl fmt::LowerHex for Pfn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = self.0; + + fmt::LowerHex::fmt(&val, f) + } +} + +/// Represents a page table entry in `/proc//pagemap`. +#[derive(Debug)] +pub enum PageInfo { + /// Entry referring to a memory page + MemoryPage(MemoryPageFlags), + /// Entry referring to a swapped page + SwapPage(SwapPageFlags), +} + +impl PageInfo { + pub fn parse_info(info: u64) -> Self { + let flags = MemoryPageFlags::from_bits_truncate(info); + + if flags.contains(MemoryPageFlags::SWAP) { + Self::SwapPage(SwapPageFlags::from_bits_truncate(info)) + } else { + Self::MemoryPage(flags) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_genmask() { + let mask = genmask(3, 1); + assert_eq!(mask, 0b1110); + + let mask = genmask(3, 0); + assert_eq!(mask, 0b1111); + + let mask = genmask(63, 62); + assert_eq!(mask, 0b11 << 62); + } + + #[test] + fn test_page_info() { + let pagemap_entry: u64 = 0b1000000110000000000000000000000000000000000000000000000000000011; + let info = PageInfo::parse_info(pagemap_entry); + if let PageInfo::MemoryPage(memory_flags) = info { + assert!(memory_flags + .contains(MemoryPageFlags::PRESENT | MemoryPageFlags::MMAP_EXCLUSIVE | MemoryPageFlags::SOFT_DIRTY)); + assert_eq!(memory_flags.get_page_frame_number(), Pfn(0b11)); + } else { + panic!("Wrong SWAP decoding"); + } + + let pagemap_entry: u64 = 0b1100000110000000000000000000000000000000000000000000000001100010; + let info = PageInfo::parse_info(pagemap_entry); + if let PageInfo::SwapPage(swap_flags) = info { + assert!( + swap_flags.contains(SwapPageFlags::PRESENT | SwapPageFlags::MMAP_EXCLUSIVE | SwapPageFlags::SOFT_DIRTY) + ); + assert_eq!(swap_flags.get_swap_type(), 0b10); + assert_eq!(swap_flags.get_swap_offset(), 0b11); + } else { + panic!("Wrong SWAP decoding"); + } + } +} diff --git a/src/process/schedstat.rs b/procfs-core/src/process/schedstat.rs similarity index 100% rename from src/process/schedstat.rs rename to procfs-core/src/process/schedstat.rs diff --git a/src/process/smaps_rollup.rs b/procfs-core/src/process/smaps_rollup.rs similarity index 100% rename from src/process/smaps_rollup.rs rename to procfs-core/src/process/smaps_rollup.rs diff --git a/procfs-core/src/process/stat.rs b/procfs-core/src/process/stat.rs new file mode 100644 index 00000000..20f687e0 --- /dev/null +++ b/procfs-core/src/process/stat.rs @@ -0,0 +1,420 @@ +use super::ProcState; +use super::StatFlags; +use crate::{from_iter, KernelVersion, ProcResult}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +use std::io::Read; +use std::str::FromStr; + +/// Status information about the process, based on the `/proc//stat` file. +/// +/// To construct one of these structures, you have to first create a [Process](crate::process::Process). +/// +/// Not all fields are available in every kernel. These fields have `Option` types. +/// +/// New fields to this struct may be added at any time (even without a major or minor semver bump). +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Stat { + /// The process ID. + pub pid: i32, + /// The filename of the executable, without the parentheses. + /// + /// This is visible whether or not the executable is swapped out. + /// + /// Note that if the actual comm field contains invalid UTF-8 characters, they will be replaced + /// here by the U+FFFD replacement character. + pub comm: String, + /// Process State. + /// + /// See [state()](#method.state) to get the process state as an enum. + pub state: char, + /// The PID of the parent of this process. + pub ppid: i32, + /// The process group ID of the process. + pub pgrp: i32, + /// The session ID of the process. + pub session: i32, + /// The controlling terminal of the process. + /// + /// The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; + /// the major device number is in bits 15 to 8. + /// + /// See [tty_nr()](#method.tty_nr) to get this value decoded into a (major, minor) tuple + pub tty_nr: i32, + /// The ID of the foreground process group of the controlling terminal of the process. + pub tpgid: i32, + /// The kernel flags word of the process. + /// + /// For bit meanings, see the PF_* defines in the Linux kernel source file + /// [`include/linux/sched.h`](/~https://github.com/torvalds/linux/blob/master/include/linux/sched.h). + /// + /// See [flags()](#method.flags) to get a [`StatFlags`](struct.StatFlags.html) bitfield object. + pub flags: u32, + /// The number of minor faults the process has made which have not required loading a memory + /// page from disk. + pub minflt: u64, + /// The number of minor faults that the process's waited-for children have made. + pub cminflt: u64, + /// The number of major faults the process has made which have required loading a memory page + /// from disk. + pub majflt: u64, + /// The number of major faults that the process's waited-for children have made. + pub cmajflt: u64, + /// Amount of time that this process has been scheduled in user mode, measured in clock ticks + /// (divide by [`ticks_per_second()`](crate::ticks_per_second). + /// + /// This includes guest time, guest_time (time spent running a virtual CPU, see below), so that + /// applications that are not aware of the guest time field do not lose that time from their + /// calculations. + pub utime: u64, + /// Amount of time that this process has been scheduled in kernel mode, measured in clock ticks + /// (divide by [`ticks_per_second()`](crate::ticks_per_second)). + pub stime: u64, + + /// Amount of time that this process's waited-for children have been scheduled in + /// user mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). + /// + /// This includes guest time, cguest_time (time spent running a virtual CPU, see below). + pub cutime: i64, + + /// Amount of time that this process's waited-for children have been scheduled in kernel + /// mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). + pub cstime: i64, + /// For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)), + /// this is the negated scheduling priority, minus one; + /// + /// That is, a number in the range -2 to -100, + /// corresponding to real-time priority 1 to 99. For processes running under a non-real-time + /// scheduling policy, this is the raw nice value (setpriority(2)) as represented in the kernel. + /// The kernel stores nice values as numbers in the range 0 (high) to 39 (low), corresponding + /// to the user-visible nice range of -20 to 19. + /// (This explanation is for Linux 2.6) + /// + /// Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this process. + pub priority: i64, + /// The nice value (see `setpriority(2)`), a value in the range 19 (low priority) to -20 (high priority). + pub nice: i64, + /// Number of threads in this process (since Linux 2.6). Before kernel 2.6, this field was + /// hard coded to 0 as a placeholder for an earlier removed field. + pub num_threads: i64, + /// The time in jiffies before the next SIGALRM is sent to the process due to an interval + /// timer. + /// + /// Since kernel 2.6.17, this field is no longer maintained, and is hard coded as 0. + pub itrealvalue: i64, + /// The time the process started after system boot. + /// + /// In kernels before Linux 2.6, this value was expressed in jiffies. Since Linux 2.6, the + /// value is expressed in clock ticks (divide by `sysconf(_SC_CLK_TCK)`). + /// + #[cfg_attr( + feature = "chrono", + doc = "See also the [Stat::starttime()] method to get the starttime as a `DateTime` object" + )] + #[cfg_attr( + not(feature = "chrono"), + doc = "If you compile with the optional `chrono` feature, you can use the `starttime()` method to get the starttime as a `DateTime` object" + )] + pub starttime: u64, + /// Virtual memory size in bytes. + pub vsize: u64, + /// Resident Set Size: number of pages the process has in real memory. + /// + /// This is just the pages which count toward text, data, or stack space. + /// This does not include pages which have not been demand-loaded in, or which are swapped out. + pub rss: u64, + /// Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in + /// getrlimit(2). + pub rsslim: u64, + /// The address above which program text can run. + pub startcode: u64, + /// The address below which program text can run. + pub endcode: u64, + /// The address of the start (i.e., bottom) of the stack. + pub startstack: u64, + /// The current value of ESP (stack pointer), as found in the kernel stack page for the + /// process. + pub kstkesp: u64, + /// The current EIP (instruction pointer). + pub kstkeip: u64, + /// The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does + /// not provide information on real-time signals; use `/proc//status` instead. + pub signal: u64, + /// The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does + /// not provide information on real-time signals; use `/proc//status` instead. + pub blocked: u64, + /// The bitmap of ignored signals, displayed as a decimal number. Obsolete, because it does + /// not provide information on real-time signals; use `/proc//status` instead. + pub sigignore: u64, + /// The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not + /// provide information on real-time signals; use `/proc//status` instead. + pub sigcatch: u64, + /// This is the "channel" in which the process is waiting. It is the address of a location + /// in the kernel where the process is sleeping. The corresponding symbolic name can be found in + /// `/proc//wchan`. + pub wchan: u64, + /// Number of pages swapped **(not maintained)**. + pub nswap: u64, + /// Cumulative nswap for child processes **(not maintained)**. + pub cnswap: u64, + /// Signal to be sent to parent when we die. + /// + /// (since Linux 2.1.22) + pub exit_signal: Option, + /// CPU number last executed on. + /// + /// (since Linux 2.2.8) + pub processor: Option, + /// Real-time scheduling priority + /// + /// Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time processes + /// + /// (since Linux 2.5.19) + pub rt_priority: Option, + /// Scheduling policy (see sched_setscheduler(2)). + /// + /// Decode using the `SCHED_*` constants in `linux/sched.h`. + /// + /// (since Linux 2.5.19) + pub policy: Option, + /// Aggregated block I/O delays, measured in clock ticks (centiseconds). + /// + /// (since Linux 2.6.18) + pub delayacct_blkio_ticks: Option, + /// Guest time of the process (time spent running a virtual CPU for a guest operating system), + /// measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)) + /// + /// (since Linux 2.6.24) + pub guest_time: Option, + /// Guest time of the process's children, measured in clock ticks (divide by + /// [`ticks_per_second()`](crate::ticks_per_second)). + /// + /// (since Linux 2.6.24) + pub cguest_time: Option, + /// Address above which program initialized and uninitialized (BSS) data are placed. + /// + /// (since Linux 3.3) + pub start_data: Option, + /// Address below which program initialized and uninitialized (BSS) data are placed. + /// + /// (since Linux 3.3) + pub end_data: Option, + /// Address above which program heap can be expanded with brk(2). + /// + /// (since Linux 3.3) + pub start_brk: Option, + /// Address above which program command-line arguments (argv) are placed. + /// + /// (since Linux 3.5) + pub arg_start: Option, + /// Address below program command-line arguments (argv) are placed. + /// + /// (since Linux 3.5) + pub arg_end: Option, + /// Address above which program environment is placed. + /// + /// (since Linux 3.5) + pub env_start: Option, + /// Address below which program environment is placed. + /// + /// (since Linux 3.5) + pub env_end: Option, + /// The thread's exit status in the form reported by waitpid(2). + /// + /// (since Linux 3.5) + pub exit_code: Option, +} + +impl Stat { + #[allow(clippy::cognitive_complexity)] + pub fn from_reader(kernel_version: Option, mut r: R) -> ProcResult { + // read in entire thing, this is only going to be 1 line + let mut buf = Vec::with_capacity(512); + r.read_to_end(&mut buf)?; + + let line = String::from_utf8_lossy(&buf); + let buf = line.trim(); + + // find the first opening paren, and split off the first part (pid) + let start_paren = expect!(buf.find('(')); + let end_paren = expect!(buf.rfind(')')); + let pid_s = &buf[..start_paren - 1]; + let comm = buf[start_paren + 1..end_paren].to_string(); + let rest = &buf[end_paren + 2..]; + + let pid = expect!(FromStr::from_str(pid_s)); + + let mut rest = rest.split(' '); + let state = expect!(expect!(rest.next()).chars().next()); + + let ppid = expect!(from_iter(&mut rest)); + let pgrp = expect!(from_iter(&mut rest)); + let session = expect!(from_iter(&mut rest)); + let tty_nr = expect!(from_iter(&mut rest)); + let tpgid = expect!(from_iter(&mut rest)); + let flags = expect!(from_iter(&mut rest)); + let minflt = expect!(from_iter(&mut rest)); + let cminflt = expect!(from_iter(&mut rest)); + let majflt = expect!(from_iter(&mut rest)); + let cmajflt = expect!(from_iter(&mut rest)); + let utime = expect!(from_iter(&mut rest)); + let stime = expect!(from_iter(&mut rest)); + let cutime = expect!(from_iter(&mut rest)); + let cstime = expect!(from_iter(&mut rest)); + let priority = expect!(from_iter(&mut rest)); + let nice = expect!(from_iter(&mut rest)); + let num_threads = expect!(from_iter(&mut rest)); + let itrealvalue = expect!(from_iter(&mut rest)); + let starttime = expect!(from_iter(&mut rest)); + let vsize = expect!(from_iter(&mut rest)); + let rss = expect!(from_iter(&mut rest)); + let rsslim = expect!(from_iter(&mut rest)); + let startcode = expect!(from_iter(&mut rest)); + let endcode = expect!(from_iter(&mut rest)); + let startstack = expect!(from_iter(&mut rest)); + let kstkesp = expect!(from_iter(&mut rest)); + let kstkeip = expect!(from_iter(&mut rest)); + let signal = expect!(from_iter(&mut rest)); + let blocked = expect!(from_iter(&mut rest)); + let sigignore = expect!(from_iter(&mut rest)); + let sigcatch = expect!(from_iter(&mut rest)); + let wchan = expect!(from_iter(&mut rest)); + let nswap = expect!(from_iter(&mut rest)); + let cnswap = expect!(from_iter(&mut rest)); + + macro_rules! since_kernel { + ($a:tt, $b:tt, $c:tt, $e:expr) => { + if let Some(kernel) = &kernel_version { + if kernel >= &KernelVersion::new($a, $b, $c) { + Some($e) + } else { + None + } + } else { + None + } + }; + } + + let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest))); + let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest))); + let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); + let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); + let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest))); + let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); + let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); + let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); + let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); + let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); + let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + + Ok(Stat { + pid, + comm, + state, + ppid, + pgrp, + session, + tty_nr, + tpgid, + flags, + minflt, + cminflt, + majflt, + cmajflt, + utime, + stime, + cutime, + cstime, + priority, + nice, + num_threads, + itrealvalue, + starttime, + vsize, + rss, + rsslim, + startcode, + endcode, + startstack, + kstkesp, + kstkeip, + signal, + blocked, + sigignore, + sigcatch, + wchan, + nswap, + cnswap, + exit_signal, + processor, + rt_priority, + policy, + delayacct_blkio_ticks, + guest_time, + cguest_time, + start_data, + end_data, + start_brk, + arg_start, + arg_end, + env_start, + env_end, + exit_code, + }) + } + + pub fn state(&self) -> ProcResult { + ProcState::from_char(self.state) + .ok_or_else(|| build_internal_error!(format!("{:?} is not a recognized process state", self.state))) + } + + pub fn tty_nr(&self) -> (i32, i32) { + // minor is bits 31-20 and 7-0 + // major is 15-8 + + // mmmmmmmmmmmm____MMMMMMMMmmmmmmmm + // 11111111111100000000000000000000 + let major = (self.tty_nr & 0xfff00) >> 8; + let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00); + (major, minor) + } + + /// The kernel flags word of the process, as a bitfield + /// + /// See also the [Stat::flags](struct.Stat.html#structfield.flags) field. + pub fn flags(&self) -> ProcResult { + StatFlags::from_bits(self.flags) + .ok_or_else(|| build_internal_error!(format!("Can't construct flags bitfield from {:?}", self.flags))) + } + + /// Get the starttime of the process as a `DateTime` object. + /// + /// See also the [`starttime`](struct.Stat.html#structfield.starttime) field. + /// + /// This function requires the "chrono" features to be enabled (which it is by default). + #[cfg(feature = "chrono")] + pub fn starttime( + &self, + boot_time: chrono::DateTime, + ticks_per_second: u64, + ) -> ProcResult> { + let seconds_since_boot = self.starttime as f32 / ticks_per_second as f32; + + Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)) + } + + /// Gets the Resident Set Size (in bytes) + /// + /// The `rss` field will return the same value in pages + pub fn rss_bytes(&self, page_size: u64) -> u64 { + self.rss * page_size + } +} diff --git a/procfs-core/src/process/status.rs b/procfs-core/src/process/status.rs new file mode 100644 index 00000000..eb394414 --- /dev/null +++ b/procfs-core/src/process/status.rs @@ -0,0 +1,345 @@ +use crate::{FromStrRadix, ProcResult}; +use std::collections::HashMap; +use std::io::{BufRead, BufReader, Read}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// Status information about the process, based on the `/proc//status` file. +/// +/// To construct this structure, see [Process::status()](crate::process::Process::status). +/// +/// Not all fields are available in every kernel. These fields have `Option` types. +/// In general, the current kernel version will tell you what fields you can expect, but this +/// isn't totally reliable, since some kernels might backport certain fields, or fields might +/// only be present if certain kernel configuration options are enabled. Be prepared to +/// handle `None` values. +/// +/// New fields to this struct may be added at any time (even without a major or minor semver bump). +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Status { + /// Command run by this process. + pub name: String, + /// Process umask, expressed in octal with a leading zero; see umask(2). (Since Linux 4.7.) + pub umask: Option, + /// Current state of the process. + pub state: String, + /// Thread group ID (i.e., Process ID). + pub tgid: i32, + /// NUMA group ID (0 if none; since Linux 3.13). + pub ngid: Option, + /// Thread ID (see gettid(2)). + pub pid: i32, + /// PID of parent process. + pub ppid: i32, + /// PID of process tracing this process (0 if not being traced). + pub tracerpid: i32, + /// Real UID. + pub ruid: u32, + /// Effective UID. + pub euid: u32, + /// Saved set UID. + pub suid: u32, + /// Filesystem UID. + pub fuid: u32, + /// Real GID. + pub rgid: u32, + /// Effective GID. + pub egid: u32, + /// Saved set GID. + pub sgid: u32, + /// Filesystem GID. + pub fgid: u32, + /// Number of file descriptor slots currently allocated. + pub fdsize: u32, + /// Supplementary group list. + pub groups: Vec, + /// Thread group ID (i.e., PID) in each of the PID + /// namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. The leftmost entry + /// shows the value with respect to the PID namespace of the + /// reading process, followed by the value in successively + /// nested inner namespaces. (Since Linux 4.1.) + pub nstgid: Option>, + /// Thread ID in each of the PID namespaces of which + /// (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. + /// (Since Linux 4.1.) + pub nspid: Option>, + /// Process group ID in each of the PID namespaces of + /// which (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. (Since Linux 4.1.) + pub nspgid: Option>, + /// NSsid: descendant namespace session ID hierarchy Session ID + /// in each of the PID namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. + /// The fields are ordered as for NStgid. (Since Linux 4.1.) + pub nssid: Option>, + /// Peak virtual memory size by kibibytes. + pub vmpeak: Option, + /// Virtual memory size by kibibytes. + pub vmsize: Option, + /// Locked memory size by kibibytes (see mlock(3)). + pub vmlck: Option, + /// Pinned memory size by kibibytes (since Linux 3.2). These are + /// pages that can't be moved because something needs to + /// directly access physical memory. + pub vmpin: Option, + /// Peak resident set size by kibibytes ("high water mark"). + pub vmhwm: Option, + /// Resident set size by kibibytes. Note that the value here is the + /// sum of RssAnon, RssFile, and RssShmem. + pub vmrss: Option, + /// Size of resident anonymous memory by kibibytes. (since Linux 4.5). + pub rssanon: Option, + /// Size of resident file mappings by kibibytes. (since Linux 4.5). + pub rssfile: Option, + /// Size of resident shared memory by kibibytes (includes System V + /// shared memory, mappings from tmpfs(5), and shared anonymous + /// mappings). (since Linux 4.5). + pub rssshmem: Option, + /// Size of data by kibibytes. + pub vmdata: Option, + /// Size of stack by kibibytes. + pub vmstk: Option, + /// Size of text seg‐ments by kibibytes. + pub vmexe: Option, + /// Shared library code size by kibibytes. + pub vmlib: Option, + /// Page table entries size by kibibytes (since Linux 2.6.10). + pub vmpte: Option, + /// Swapped-out virtual memory size by anonymous private + /// pages by kibibytes; shmem swap usage is not included (since Linux 2.6.34). + pub vmswap: Option, + /// Size of hugetlb memory portions by kB. (since Linux 4.4). + pub hugetlbpages: Option, + /// Number of threads in process containing this thread. + pub threads: u64, + /// This field contains two slash-separated numbers that + /// relate to queued signals for the real user ID of this + /// process. The first of these is the number of currently + /// queued signals for this real user ID, and the second is the + /// resource limit on the number of queued signals for this + /// process (see the description of RLIMIT_SIGPENDING in + /// getrlimit(2)). + pub sigq: (u64, u64), + /// Number of signals pending for thread (see pthreads(7) and signal(7)). + pub sigpnd: u64, + /// Number of signals pending for process as a whole (see pthreads(7) and signal(7)). + pub shdpnd: u64, + /// Masks indicating signals being blocked (see signal(7)). + pub sigblk: u64, + /// Masks indicating signals being ignored (see signal(7)). + pub sigign: u64, + /// Masks indicating signals being caught (see signal(7)). + pub sigcgt: u64, + /// Masks of capabilities enabled in inheritable sets (see capabilities(7)). + pub capinh: u64, + /// Masks of capabilities enabled in permitted sets (see capabilities(7)). + pub capprm: u64, + /// Masks of capabilities enabled in effective sets (see capabilities(7)). + pub capeff: u64, + /// Capability Bounding set (since Linux 2.6.26, see capabilities(7)). + pub capbnd: Option, + /// Ambient capability set (since Linux 4.3, see capabilities(7)). + pub capamb: Option, + /// Value of the no_new_privs bit (since Linux 4.10, see prctl(2)). + pub nonewprivs: Option, + /// Seccomp mode of the process (since Linux 3.8, see + /// seccomp(2)). 0 means SECCOMP_MODE_DISABLED; 1 means SEC‐ + /// COMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field + /// is provided only if the kernel was built with the CON‐ + /// FIG_SECCOMP kernel configuration option enabled. + pub seccomp: Option, + /// Speculative store bypass mitigation status. + pub speculation_store_bypass: Option, + /// Mask of CPUs on which this process may run (since Linux 2.6.24, see cpuset(7)). + pub cpus_allowed: Option>, + /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). + pub cpus_allowed_list: Option>, + /// Mask of memory nodes allowed to this process (since Linux 2.6.24, see cpuset(7)). + pub mems_allowed: Option>, + /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). + pub mems_allowed_list: Option>, + /// Number of voluntary context switches (since Linux 2.6.23). + pub voluntary_ctxt_switches: Option, + /// Number of involuntary context switches (since Linux 2.6.23). + pub nonvoluntary_ctxt_switches: Option, + + /// Contains true if the process is currently dumping core. + /// + /// This information can be used by a monitoring process to avoid killing a processing that is + /// currently dumping core, which could result in a corrupted core dump file. + /// + /// (Since Linux 4.15) + pub core_dumping: Option, + + /// Contains true if the process is allowed to use THP + /// + /// (Since Linux 5.0) + pub thp_enabled: Option, +} + +impl Status { + pub fn from_reader(r: R) -> ProcResult { + let mut map = HashMap::new(); + let reader = BufReader::new(r); + + for line in reader.lines() { + let line = line?; + if line.is_empty() { + continue; + } + let mut s = line.split(':'); + let field = expect!(s.next()); + let value = expect!(s.next()).trim(); + + map.insert(field.to_string(), value.to_string()); + } + + let status = Status { + name: expect!(map.remove("Name")), + umask: map.remove("Umask").map(|x| Ok(from_str!(u32, &x, 8))).transpose()?, + state: expect!(map.remove("State")), + tgid: from_str!(i32, &expect!(map.remove("Tgid"))), + ngid: map.remove("Ngid").map(|x| Ok(from_str!(i32, &x))).transpose()?, + pid: from_str!(i32, &expect!(map.remove("Pid"))), + ppid: from_str!(i32, &expect!(map.remove("PPid"))), + tracerpid: from_str!(i32, &expect!(map.remove("TracerPid"))), + ruid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 0)), + euid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 1)), + suid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 2)), + fuid: expect!(Status::parse_uid_gid(&expect!(map.remove("Uid")), 3)), + rgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 0)), + egid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 1)), + sgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 2)), + fgid: expect!(Status::parse_uid_gid(&expect!(map.remove("Gid")), 3)), + fdsize: from_str!(u32, &expect!(map.remove("FDSize"))), + groups: Status::parse_list(&expect!(map.remove("Groups")))?, + nstgid: map.remove("NStgid").map(|x| Status::parse_list(&x)).transpose()?, + nspid: map.remove("NSpid").map(|x| Status::parse_list(&x)).transpose()?, + nspgid: map.remove("NSpgid").map(|x| Status::parse_list(&x)).transpose()?, + nssid: map.remove("NSsid").map(|x| Status::parse_list(&x)).transpose()?, + vmpeak: Status::parse_with_kb(map.remove("VmPeak"))?, + vmsize: Status::parse_with_kb(map.remove("VmSize"))?, + vmlck: Status::parse_with_kb(map.remove("VmLck"))?, + vmpin: Status::parse_with_kb(map.remove("VmPin"))?, + vmhwm: Status::parse_with_kb(map.remove("VmHWM"))?, + vmrss: Status::parse_with_kb(map.remove("VmRSS"))?, + rssanon: Status::parse_with_kb(map.remove("RssAnon"))?, + rssfile: Status::parse_with_kb(map.remove("RssFile"))?, + rssshmem: Status::parse_with_kb(map.remove("RssShmem"))?, + vmdata: Status::parse_with_kb(map.remove("VmData"))?, + vmstk: Status::parse_with_kb(map.remove("VmStk"))?, + vmexe: Status::parse_with_kb(map.remove("VmExe"))?, + vmlib: Status::parse_with_kb(map.remove("VmLib"))?, + vmpte: Status::parse_with_kb(map.remove("VmPTE"))?, + vmswap: Status::parse_with_kb(map.remove("VmSwap"))?, + hugetlbpages: Status::parse_with_kb(map.remove("HugetlbPages"))?, + threads: from_str!(u64, &expect!(map.remove("Threads"))), + sigq: expect!(Status::parse_sigq(&expect!(map.remove("SigQ")))), + sigpnd: from_str!(u64, &expect!(map.remove("SigPnd")), 16), + shdpnd: from_str!(u64, &expect!(map.remove("ShdPnd")), 16), + sigblk: from_str!(u64, &expect!(map.remove("SigBlk")), 16), + sigign: from_str!(u64, &expect!(map.remove("SigIgn")), 16), + sigcgt: from_str!(u64, &expect!(map.remove("SigCgt")), 16), + capinh: from_str!(u64, &expect!(map.remove("CapInh")), 16), + capprm: from_str!(u64, &expect!(map.remove("CapPrm")), 16), + capeff: from_str!(u64, &expect!(map.remove("CapEff")), 16), + capbnd: map.remove("CapBnd").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, + capamb: map.remove("CapAmb").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, + nonewprivs: map.remove("NoNewPrivs").map(|x| Ok(from_str!(u64, &x))).transpose()?, + seccomp: map.remove("Seccomp").map(|x| Ok(from_str!(u32, &x))).transpose()?, + speculation_store_bypass: map.remove("Speculation_Store_Bypass"), + cpus_allowed: map + .remove("Cpus_allowed") + .map(|x| Status::parse_allowed(&x)) + .transpose()?, + cpus_allowed_list: map + .remove("Cpus_allowed_list") + .and_then(|x| Status::parse_allowed_list(&x).ok()), + mems_allowed: map + .remove("Mems_allowed") + .map(|x| Status::parse_allowed(&x)) + .transpose()?, + mems_allowed_list: map + .remove("Mems_allowed_list") + .and_then(|x| Status::parse_allowed_list(&x).ok()), + voluntary_ctxt_switches: map + .remove("voluntary_ctxt_switches") + .map(|x| Ok(from_str!(u64, &x))) + .transpose()?, + nonvoluntary_ctxt_switches: map + .remove("nonvoluntary_ctxt_switches") + .map(|x| Ok(from_str!(u64, &x))) + .transpose()?, + core_dumping: map.remove("CoreDumping").map(|x| x == "1"), + thp_enabled: map.remove("THP_enabled").map(|x| x == "1"), + }; + + if cfg!(test) && !map.is_empty() { + // This isn't an error because different kernels may put different data here, and distros + // may backport these changes into older kernels. Too hard to keep track of + eprintln!("Warning: status map is not empty: {:#?}", map); + } + + Ok(status) + } + + fn parse_with_kb(s: Option) -> ProcResult> { + if let Some(s) = s { + Ok(Some(from_str!(T, &s.replace(" kB", "")))) + } else { + Ok(None) + } + } + + pub(crate) fn parse_uid_gid(s: &str, i: usize) -> ProcResult { + Ok(from_str!(u32, expect!(s.split_whitespace().nth(i)))) + } + + fn parse_sigq(s: &str) -> ProcResult<(u64, u64)> { + let mut iter = s.split('/'); + let first = from_str!(u64, expect!(iter.next())); + let second = from_str!(u64, expect!(iter.next())); + Ok((first, second)) + } + + fn parse_list(s: &str) -> ProcResult> { + let mut ret = Vec::new(); + for i in s.split_whitespace() { + ret.push(from_str!(T, i)); + } + Ok(ret) + } + + fn parse_allowed(s: &str) -> ProcResult> { + let mut ret = Vec::new(); + for i in s.split(',') { + ret.push(from_str!(u32, i, 16)); + } + Ok(ret) + } + + fn parse_allowed_list(s: &str) -> ProcResult> { + let mut ret = Vec::new(); + for s in s.split(',') { + if s.contains('-') { + let mut s = s.split('-'); + let beg = from_str!(u32, expect!(s.next())); + if let Some(x) = s.next() { + let end = from_str!(u32, x); + ret.push((beg, end)); + } + } else { + let beg = from_str!(u32, s); + let end = from_str!(u32, s); + ret.push((beg, end)); + } + } + Ok(ret) + } +} + +#[cfg(test)] +mod tests { + // TODO +} diff --git a/procfs-core/src/sys/kernel/mod.rs b/procfs-core/src/sys/kernel/mod.rs new file mode 100644 index 00000000..19a58a22 --- /dev/null +++ b/procfs-core/src/sys/kernel/mod.rs @@ -0,0 +1,429 @@ +//! Global kernel info / tuning miscellaneous stuff +//! +//! The files in this directory can be used to tune and monitor miscellaneous +//! and general things in the operation of the Linux kernel. + +use std::cmp; +use std::collections::HashSet; +use std::str::FromStr; + +use bitflags::bitflags; + +use crate::{ProcError, ProcResult}; + +/// Represents a kernel version, in major.minor.release version. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Version { + pub major: u8, + pub minor: u8, + pub patch: u16, +} + +impl Version { + pub fn new(major: u8, minor: u8, patch: u16) -> Version { + Version { major, minor, patch } + } + + /// Parses a kernel version string, in major.minor.release syntax. + /// + /// Note that any extra information (stuff after a dash) is ignored. + /// + /// # Example + /// + /// ``` + /// # use procfs::KernelVersion; + /// let a = KernelVersion::from_str("3.16.0-6-amd64").unwrap(); + /// let b = KernelVersion::new(3, 16, 0); + /// assert_eq!(a, b); + /// + /// ``` + #[allow(clippy::should_implement_trait)] + pub fn from_str(s: &str) -> Result { + let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit()); + let kernel = if let Some(pos) = pos { + let (s, _) = s.split_at(pos); + s + } else { + s + }; + let mut kernel_split = kernel.split('.'); + + let major = kernel_split.next().ok_or("Missing major version component")?; + let minor = kernel_split.next().ok_or("Missing minor version component")?; + let patch = kernel_split.next().ok_or("Missing patch version component")?; + + let major = major.parse().map_err(|_| "Failed to parse major version")?; + let minor = minor.parse().map_err(|_| "Failed to parse minor version")?; + let patch = patch.parse().map_err(|_| "Failed to parse patch version")?; + + Ok(Version { major, minor, patch }) + } +} + +impl FromStr for Version { + type Err = &'static str; + + /// Parses a kernel version string, in major.minor.release syntax. + /// + /// Note that any extra information (stuff after a dash) is ignored. + /// + /// # Example + /// + /// ``` + /// # use procfs::KernelVersion; + /// let a: KernelVersion = "3.16.0-6-amd64".parse().unwrap(); + /// let b = KernelVersion::new(3, 16, 0); + /// assert_eq!(a, b); + /// + /// ``` + fn from_str(s: &str) -> Result { + Version::from_str(s) + } +} + +impl cmp::Ord for Version { + fn cmp(&self, other: &Self) -> cmp::Ordering { + match self.major.cmp(&other.major) { + cmp::Ordering::Equal => match self.minor.cmp(&other.minor) { + cmp::Ordering::Equal => self.patch.cmp(&other.patch), + x => x, + }, + x => x, + } + } +} + +impl cmp::PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Represents a kernel type +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Type { + pub sysname: String, +} + +impl Type { + pub fn new(sysname: String) -> Type { + Type { sysname } + } +} + +impl FromStr for Type { + type Err = &'static str; + + /// Parse a kernel type string + /// + /// Notice that in Linux source code, it is defined as a single string. + fn from_str(s: &str) -> Result { + Ok(Type::new(s.to_string())) + } +} + +/// Represents a kernel build information +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BuildInfo { + pub version: String, + pub flags: HashSet, + /// This field contains any extra data from the /proc/sys/kernel/version file. It generally contains the build date of the kernel, but the format of the date can vary. + /// + /// A method named `extra_date` is provided which would try to parse some date formats. When the date format is not supported, an error will be returned. It depends on chrono feature. + pub extra: String, +} + +impl BuildInfo { + pub fn new(version: &str, flags: HashSet, extra: String) -> BuildInfo { + BuildInfo { + version: version.to_string(), + flags, + extra, + } + } + + /// Check if SMP is ON + pub fn smp(&self) -> bool { + self.flags.contains("SMP") + } + + /// Check if PREEMPT is ON + pub fn preempt(&self) -> bool { + self.flags.contains("PREEMPT") + } + + /// Check if PREEMPTRT is ON + pub fn preemptrt(&self) -> bool { + self.flags.contains("PREEMPTRT") + } + + /// Return version number + /// + /// This would parse number from first digits of version string. For example, #21~1 to 21. + pub fn version_number(&self) -> ProcResult { + let mut version_str = String::new(); + for c in self.version.chars() { + if c.is_ascii_digit() { + version_str.push(c); + } else { + break; + } + } + let version_number: u32 = version_str.parse().map_err(|_| "Failed to parse version number")?; + Ok(version_number) + } + + /// Parse extra field to `DateTime` object + /// + /// This function may fail as TIMESTAMP can be various formats. + #[cfg(feature = "chrono")] + pub fn extra_date(&self) -> ProcResult> { + if let Ok(dt) = + chrono::DateTime::parse_from_str(&format!("{} +0000", &self.extra), "%a %b %d %H:%M:%S UTC %Y %z") + { + return Ok(dt.with_timezone(&chrono::Local)); + } + if let Ok(dt) = chrono::DateTime::parse_from_str(&self.extra, "%a, %d %b %Y %H:%M:%S %z") { + return Ok(dt.with_timezone(&chrono::Local)); + } + Err(ProcError::Other("Failed to parse extra field to date".to_string())) + } +} + +impl FromStr for BuildInfo { + type Err = &'static str; + + /// Parse a kernel build information string + fn from_str(s: &str) -> Result { + let mut version = String::new(); + let mut flags: HashSet = HashSet::new(); + let mut extra: String = String::new(); + + let mut splited = s.split(' '); + let version_str = splited.next(); + if let Some(version_str) = version_str { + if let Some(stripped) = version_str.strip_prefix('#') { + version.push_str(stripped); + } else { + return Err("Failed to parse kernel build version"); + } + } else { + return Err("Failed to parse kernel build version"); + } + + for s in &mut splited { + if s.chars().all(char::is_uppercase) { + flags.insert(s.to_string()); + } else { + extra.push_str(s); + extra.push(' '); + break; + } + } + let remains: Vec<&str> = splited.collect(); + extra.push_str(&remains.join(" ")); + + Ok(BuildInfo { version, flags, extra }) + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +/// Represents the data from `/proc/sys/kernel/sem` +pub struct SemaphoreLimits { + /// The maximum semaphores per semaphore set + pub semmsl: u64, + /// A system-wide limit on the number of semaphores in all semaphore sets + pub semmns: u64, + /// The maximum number of operations that may be specified in a semop(2) call + pub semopm: u64, + /// A system-wide limit on the maximum number of semaphore identifiers + pub semmni: u64, +} + +impl SemaphoreLimits { + fn from_str(s: &str) -> Result { + let mut s = s.split_ascii_whitespace(); + + let semmsl = s.next().ok_or("Missing SEMMSL")?; + let semmns = s.next().ok_or("Missing SEMMNS")?; + let semopm = s.next().ok_or("Missing SEMOPM")?; + let semmni = s.next().ok_or("Missing SEMMNI")?; + + let semmsl = semmsl.parse().map_err(|_| "Failed to parse SEMMSL")?; + let semmns = semmns.parse().map_err(|_| "Failed to parse SEMMNS")?; + let semopm = semopm.parse().map_err(|_| "Failed to parse SEMOPM")?; + let semmni = semmni.parse().map_err(|_| "Failed to parse SEMMNI")?; + + Ok(SemaphoreLimits { + semmsl, + semmns, + semopm, + semmni, + }) + } +} + +impl FromStr for SemaphoreLimits { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + SemaphoreLimits::from_str(s) + } +} + +bitflags! { + /// Flags representing allowed sysrq functions + pub struct AllowedFunctions : u16 { + /// Enable control of console log level + const ENABLE_CONTROL_LOG_LEVEL = 2; + /// Enable control of keyboard (SAK, unraw) + const ENABLE_CONTROL_KEYBOARD = 4; + /// Enable debugging dumps of processes etc + const ENABLE_DEBUGGING_DUMPS = 8; + /// Enable sync command + const ENABLE_SYNC_COMMAND = 16; + /// Enable remound read-only + const ENABLE_REMOUNT_READ_ONLY = 32; + /// Enable signaling of processes (term, kill, oom-kill) + const ENABLE_SIGNALING_PROCESSES = 64; + /// Allow reboot/poweroff + const ALLOW_REBOOT_POWEROFF = 128; + /// Allow nicing of all real-time tasks + const ALLOW_NICING_REAL_TIME_TASKS = 256; + } +} + +/// Values controlling functions allowed to be invoked by the SysRq key +/// +/// To construct this enum, see [sysrq](crate::sys::kernel::sysrq) +#[derive(Copy, Clone, Debug)] +pub enum SysRq { + /// Disable sysrq completely + Disable, + /// Enable all functions of sysrq + Enable, + /// Bitmask of allowed sysrq functions + AllowedFunctions(AllowedFunctions), +} + +impl SysRq { + pub fn to_number(self) -> u16 { + match self { + SysRq::Disable => 0, + SysRq::Enable => 1, + SysRq::AllowedFunctions(allowed) => allowed.bits, + } + } + + fn from_str(s: &str) -> ProcResult { + match s.parse::()? { + 0 => Ok(SysRq::Disable), + 1 => Ok(SysRq::Enable), + x => match AllowedFunctions::from_bits(x) { + Some(allowed) => Ok(SysRq::AllowedFunctions(allowed)), + None => Err("Invalid value".into()), + }, + } + } +} + +impl FromStr for SysRq { + type Err = ProcError; + + fn from_str(s: &str) -> Result { + SysRq::from_str(s) + } +} + +/// The minimum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later +pub const THREADS_MIN: u32 = 20; +/// The maximum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later +pub const THREADS_MAX: u32 = 0x3fff_ffff; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_version() { + let a = Version::from_str("3.16.0-6-amd64").unwrap(); + let b = Version::new(3, 16, 0); + assert_eq!(a, b); + + let a = Version::from_str("3.16.0").unwrap(); + let b = Version::new(3, 16, 0); + assert_eq!(a, b); + + let a = Version::from_str("3.16.0_1").unwrap(); + let b = Version::new(3, 16, 0); + assert_eq!(a, b); + } + + #[test] + fn test_type() { + let a = Type::from_str("Linux").unwrap(); + assert_eq!(a.sysname, "Linux"); + } + + #[test] + fn test_build_info() { + // For Ubuntu, Manjaro, CentOS and others: + let a = BuildInfo::from_str("#1 SMP PREEMPT Thu Sep 30 15:29:01 UTC 2021").unwrap(); + let mut flags: HashSet = HashSet::new(); + flags.insert("SMP".to_string()); + flags.insert("PREEMPT".to_string()); + assert_eq!(a.version, "1"); + assert_eq!(a.version_number().unwrap(), 1); + assert_eq!(a.flags, flags); + assert!(a.smp()); + assert!(a.preempt()); + assert!(!a.preemptrt()); + assert_eq!(a.extra, "Thu Sep 30 15:29:01 UTC 2021"); + #[cfg(feature = "chrono")] + let _ = a.extra_date().unwrap(); + + // For Arch and others: + let b = BuildInfo::from_str("#1 SMP PREEMPT Fri, 12 Nov 2021 19:22:10 +0000").unwrap(); + assert_eq!(b.version, "1"); + assert_eq!(b.version_number().unwrap(), 1); + assert_eq!(b.flags, flags); + assert_eq!(b.extra, "Fri, 12 Nov 2021 19:22:10 +0000"); + assert!(b.smp()); + assert!(b.preempt()); + assert!(!b.preemptrt()); + #[cfg(feature = "chrono")] + let _ = b.extra_date().unwrap(); + + // For Debian and others: + let c = BuildInfo::from_str("#1 SMP Debian 5.10.46-4 (2021-08-03)").unwrap(); + let mut flags: HashSet = HashSet::new(); + flags.insert("SMP".to_string()); + assert_eq!(c.version, "1"); + assert_eq!(c.version_number().unwrap(), 1); + assert_eq!(c.flags, flags); + assert_eq!(c.extra, "Debian 5.10.46-4 (2021-08-03)"); + assert!(c.smp()); + assert!(!c.preempt()); + assert!(!c.preemptrt()); + // Skip the date parsing for now + } + + #[test] + fn test_semaphore_limits() { + // Note that the below string has tab characters in it. Make sure to not remove them. + let a = SemaphoreLimits::from_str("32000 1024000000 500 32000").unwrap(); + let b = SemaphoreLimits { + semmsl: 32_000, + semmns: 1_024_000_000, + semopm: 500, + semmni: 32_000, + }; + assert_eq!(a, b); + + let a = SemaphoreLimits::from_str("1"); + assert!(a.is_err() && a.err().unwrap() == "Missing SEMMNS"); + + let a = SemaphoreLimits::from_str("1 string 500 3200"); + assert!(a.is_err() && a.err().unwrap() == "Failed to parse SEMMNS"); + } +} diff --git a/procfs-core/src/sys/mod.rs b/procfs-core/src/sys/mod.rs new file mode 100644 index 00000000..3dbb3fbe --- /dev/null +++ b/procfs-core/src/sys/mod.rs @@ -0,0 +1,9 @@ +//! Sysctl is a means of configuring certain aspects of the kernel at run-time, +//! and the `/proc/sys/` directory is there so that you don't even need special tools to do it! +//! +//! This directory (present since 1.3.57) contains a number of files +//! and subdirectories corresponding to kernel variables. +//! These variables can be read and sometimes modified using the `/proc` filesystem, +//! and the (deprecated) sysctl(2) system call. + +pub mod kernel; diff --git a/procfs/Cargo.toml b/procfs/Cargo.toml new file mode 100644 index 00000000..9e8f44ae --- /dev/null +++ b/procfs/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "procfs" +documentation = "https://docs.rs/procfs/" +description = "Interface to the linux procfs pseudo-filesystem" +readme = "../README.md" +version.workspace = true +authors.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[features] +default = ["chrono", "flate2"] +serde1 = ["serde"] + +[dependencies] +procfs-core = { path = "../procfs-core" } +rustix = { version = "0.37.0", features = ["fs", "process", "param", "thread"] } +bitflags = "1.2" +lazy_static = "1.0.2" +chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } +byteorder = {version="1.2.3", features=["i128"]} +hex = "0.4" +flate2 = { version = "1.0.3", optional = true } +backtrace = { version = "0.3", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } + +[dev-dependencies] +criterion = "0.4" +procinfo = "0.4.2" +failure = "0.1" +libc = "0.2.139" + +[package.metadata.docs.rs] +all-features = true + +[[bench]] +name = "cpuinfo" +harness = false diff --git a/benches/cpuinfo.rs b/procfs/benches/cpuinfo.rs similarity index 100% rename from benches/cpuinfo.rs rename to procfs/benches/cpuinfo.rs diff --git a/build.rs b/procfs/build.rs similarity index 100% rename from build.rs rename to procfs/build.rs diff --git a/examples/README.md b/procfs/examples/README.md similarity index 100% rename from examples/README.md rename to procfs/examples/README.md diff --git a/examples/diskstat.rs b/procfs/examples/diskstat.rs similarity index 100% rename from examples/diskstat.rs rename to procfs/examples/diskstat.rs diff --git a/examples/dump.rs b/procfs/examples/dump.rs similarity index 100% rename from examples/dump.rs rename to procfs/examples/dump.rs diff --git a/examples/interface_stats.rs b/procfs/examples/interface_stats.rs similarity index 100% rename from examples/interface_stats.rs rename to procfs/examples/interface_stats.rs diff --git a/examples/iomem.rs b/procfs/examples/iomem.rs similarity index 100% rename from examples/iomem.rs rename to procfs/examples/iomem.rs diff --git a/examples/kpagecount.rs b/procfs/examples/kpagecount.rs similarity index 100% rename from examples/kpagecount.rs rename to procfs/examples/kpagecount.rs diff --git a/examples/lslocks.rs b/procfs/examples/lslocks.rs similarity index 100% rename from examples/lslocks.rs rename to procfs/examples/lslocks.rs diff --git a/examples/lsmod.rs b/procfs/examples/lsmod.rs similarity index 100% rename from examples/lsmod.rs rename to procfs/examples/lsmod.rs diff --git a/examples/mountinfo.rs b/procfs/examples/mountinfo.rs similarity index 100% rename from examples/mountinfo.rs rename to procfs/examples/mountinfo.rs diff --git a/examples/netstat.rs b/procfs/examples/netstat.rs similarity index 100% rename from examples/netstat.rs rename to procfs/examples/netstat.rs diff --git a/examples/pfn.rs b/procfs/examples/pfn.rs similarity index 100% rename from examples/pfn.rs rename to procfs/examples/pfn.rs diff --git a/examples/pressure.rs b/procfs/examples/pressure.rs similarity index 100% rename from examples/pressure.rs rename to procfs/examples/pressure.rs diff --git a/examples/process_hierarchy.rs b/procfs/examples/process_hierarchy.rs similarity index 100% rename from examples/process_hierarchy.rs rename to procfs/examples/process_hierarchy.rs diff --git a/examples/process_kpageflags.rs b/procfs/examples/process_kpageflags.rs similarity index 100% rename from examples/process_kpageflags.rs rename to procfs/examples/process_kpageflags.rs diff --git a/examples/ps.rs b/procfs/examples/ps.rs similarity index 100% rename from examples/ps.rs rename to procfs/examples/ps.rs diff --git a/examples/self_memory.rs b/procfs/examples/self_memory.rs similarity index 100% rename from examples/self_memory.rs rename to procfs/examples/self_memory.rs diff --git a/examples/shm.rs b/procfs/examples/shm.rs similarity index 100% rename from examples/shm.rs rename to procfs/examples/shm.rs diff --git a/procfs/src/cgroups.rs b/procfs/src/cgroups.rs new file mode 100644 index 00000000..1efe8fcf --- /dev/null +++ b/procfs/src/cgroups.rs @@ -0,0 +1,37 @@ +use super::process::Process; +use crate::ProcResult; +pub use procfs_core::{CGroupController, ProcessCgroup}; + +/// Information about the cgroup controllers that are compiled into the kernel +/// +/// (since Linux 2.6.24) +pub fn cgroups() -> ProcResult> { + CGroupController::cgroup_controllers_from_reader(std::fs::File::open("/proc/cgroups")?) +} + +impl Process { + /// Describes control groups to which the process with the corresponding PID belongs. + /// + /// The displayed information differs for cgroupsversion 1 and version 2 hierarchies. + pub fn cgroups(&self) -> ProcResult> { + ProcessCgroup::cgroups_from_reader(self.open_relative("cgroup")?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cgroups() { + let groups = cgroups().unwrap(); + println!("{:?}", groups); + } + + #[test] + fn test_process_cgroups() { + let myself = Process::myself().unwrap(); + let groups = myself.cgroups(); + println!("{:?}", groups); + } +} diff --git a/src/cpuinfo.rs b/procfs/src/cpuinfo.rs similarity index 100% rename from src/cpuinfo.rs rename to procfs/src/cpuinfo.rs diff --git a/src/diskstats.rs b/procfs/src/diskstats.rs similarity index 100% rename from src/diskstats.rs rename to procfs/src/diskstats.rs diff --git a/src/iomem.rs b/procfs/src/iomem.rs similarity index 100% rename from src/iomem.rs rename to procfs/src/iomem.rs diff --git a/src/keyring.rs b/procfs/src/keyring.rs similarity index 100% rename from src/keyring.rs rename to procfs/src/keyring.rs diff --git a/src/kpagecount.rs b/procfs/src/kpagecount.rs similarity index 100% rename from src/kpagecount.rs rename to procfs/src/kpagecount.rs diff --git a/src/kpageflags.rs b/procfs/src/kpageflags.rs similarity index 100% rename from src/kpageflags.rs rename to procfs/src/kpageflags.rs diff --git a/procfs/src/lib.rs b/procfs/src/lib.rs new file mode 100644 index 00000000..cfc38dcd --- /dev/null +++ b/procfs/src/lib.rs @@ -0,0 +1,659 @@ +#![allow(unknown_lints)] +// The suggested fix with `str::parse` removes support for Rust 1.48 +#![allow(clippy::from_str_radix_10)] +#![deny(broken_intra_doc_links, invalid_html_tags)] +//! This crate provides to an interface into the linux `procfs` filesystem, usually mounted at +//! `/proc`. +//! +//! This is a pseudo-filesystem which is available on most every linux system and provides an +//! interface to kernel data structures. +//! +//! +//! # Kernel support +//! +//! Not all fields/data are available in each kernel. Some fields were added in specific kernel +//! releases, and other fields are only present in certain kernel configuration options are +//! enabled. These are represented as `Option` fields in this crate. +//! +//! This crate aims to support all 2.6 kernels (and newer). WSL2 is also supported. +//! +//! # Documentation +//! +//! In almost all cases, the documentation is taken from the +//! [`proc.5`](http://man7.org/linux/man-pages/man5/proc.5.html) manual page. This means that +//! sometimes the style of writing is not very "rusty", or may do things like reference related files +//! (instead of referencing related structs). Contributions to improve this are welcome. +//! +//! # Panicing +//! +//! While previous versions of the library could panic, this current version aims to be panic-free +//! in a many situations as possible. Whenever the procfs crate encounters a bug in its own +//! parsing code, it will return an [`InternalError`](enum.ProcError.html#variant.InternalError) error. This should be considered a +//! bug and should be [reported](/~https://github.com/eminence/procfs). If you encounter a panic, +//! please report that as well. +//! +//! # Cargo features +//! +//! The following cargo features are available: +//! +//! * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects. +//! * `flate2` -- Default. Optional. This feature enables parsing gzip compressed `/proc/config.gz` file via the `procfs::kernel_config` method. +//! * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised. +//! +//! # Examples +//! +//! Examples can be found in the various modules shown below, or in the +//! [examples](/~https://github.com/eminence/procfs/tree/master/examples) folder of the code repository. +//! + +pub use procfs_core::*; + +use bitflags::bitflags; +use lazy_static::lazy_static; + +use rustix::fd::AsFd; +use std::fmt; +use std::fs::{File, OpenOptions}; +use std::io::{self, BufRead, BufReader, Read, Seek, Write}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::{collections::HashMap, time::Duration}; + +#[cfg(feature = "chrono")] +use chrono::{DateTime, Local}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +const PROC_CONFIG_GZ: &str = "/proc/config.gz"; +const BOOT_CONFIG: &str = "/boot/config"; + +macro_rules! build_internal_error { + ($err: expr) => { + crate::ProcError::InternalError(crate::InternalError { + msg: format!("Internal Unwrap Error: {}", $err), + file: file!(), + line: line!(), + #[cfg(feature = "backtrace")] + backtrace: backtrace::Backtrace::new(), + }) + }; + ($err: expr, $msg: expr) => { + crate::ProcError::InternalError(crate::InternalError { + msg: format!("Internal Unwrap Error: {}: {}", $msg, $err), + file: file!(), + line: line!(), + #[cfg(feature = "backtrace")] + backtrace: backtrace::Backtrace::new(), + }) + }; +} + +#[allow(unused_macros)] +macro_rules! proc_panic { + ($e:expr) => { + crate::IntoOption::into_option($e).unwrap_or_else(|| { + panic!( + "Failed to unwrap {}. Please report this as a procfs bug.", + stringify!($e) + ) + }) + }; + ($e:expr, $msg:expr) => { + crate::IntoOption::into_option($e).unwrap_or_else(|| { + panic!( + "Failed to unwrap {} ({}). Please report this as a procfs bug.", + stringify!($e), + $msg + ) + }) + }; +} + +macro_rules! expect { + ($e:expr) => { + match crate::IntoResult::into($e) { + Ok(v) => v, + Err(e) => return Err(build_internal_error!(e)), + } + }; + ($e:expr, $msg:expr) => { + match crate::IntoResult::into($e) { + Ok(v) => v, + Err(e) => return Err(build_internal_error!(e, $msg)), + } + }; +} + +macro_rules! from_str { + ($t:tt, $e:expr) => {{ + let e = $e; + expect!( + $t::from_str_radix(e, 10), + format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t),) + ) + }}; + ($t:tt, $e:expr, $radix:expr) => {{ + let e = $e; + expect!( + $t::from_str_radix(e, $radix), + format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t)) + ) + }}; + ($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{ + let e = $e; + expect!( + $t::from_str_radix(e, $radix), + format!( + "Failed to parse {} ({:?}) as a {} (pid {})", + stringify!($e), + e, + stringify!($t), + $pid + ) + ) + }}; +} + +macro_rules! wrap_io_error { + ($path:expr, $expr:expr) => { + match $expr { + Ok(v) => Ok(v), + Err(e) => { + let kind = e.kind(); + Err(::std::io::Error::new( + kind, + crate::IoErrorWrapper { + path: $path.to_owned(), + inner: e.into(), + }, + )) + } + } + }; +} + +pub(crate) fn read_file>(path: P) -> ProcResult { + let mut f = FileWrapper::open(path)?; + let mut buf = String::new(); + f.read_to_string(&mut buf)?; + Ok(buf) +} + +pub(crate) fn write_file, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> { + let mut f = OpenOptions::new().read(false).write(true).open(path)?; + f.write_all(buf.as_ref())?; + Ok(()) +} + +pub(crate) fn read_value(path: P) -> ProcResult +where + P: AsRef, + T: FromStr, + ProcError: From, +{ + let val = read_file(path)?; + Ok(::from_str(val.trim())?) + //Ok(val.trim().parse()?) +} + +pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> ProcResult<()> { + write_file(path, value.to_string().as_bytes()) +} + +pub mod process; + +mod meminfo; +pub use crate::meminfo::*; + +mod sysvipc_shm; +pub use crate::sysvipc_shm::*; + +pub mod net; + +mod cpuinfo; +pub use crate::cpuinfo::*; + +mod cgroups; +pub use crate::cgroups::*; + +pub mod sys; +pub use crate::sys::kernel::BuildInfo as KernelBuildInfo; +pub use crate::sys::kernel::Type as KernelType; +pub use crate::sys::kernel::Version as KernelVersion; + +mod pressure; +pub use crate::pressure::*; + +mod diskstats; +pub use diskstats::*; + +mod locks; +pub use locks::*; + +pub mod keyring; + +mod uptime; +pub use uptime::*; + +mod iomem; +pub use iomem::*; + +mod kpageflags; +pub use kpageflags::*; + +mod kpagecount; +pub use kpagecount::*; + +lazy_static! { + /// The number of clock ticks per second. + /// + /// This is calculated from `sysconf(_SC_CLK_TCK)`. + static ref TICKS_PER_SECOND: u64 = { + ticks_per_second() + }; + /// The version of the currently running kernel. + /// + /// This is a lazily constructed static. You can also get this information via + /// [KernelVersion::new()]. + static ref KERNEL: ProcResult = { + KernelVersion::current() + }; + /// Memory page size, in bytes. + /// + /// This is calculated from `sysconf(_SC_PAGESIZE)`. + static ref PAGESIZE: u64 = { + page_size() + }; +} + +fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult { + match unit { + "B" => Ok(num), + "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024), + "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024), + "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024), + unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))), + } +} + +/// A wrapper around a `File` that remembers the name of the path +struct FileWrapper { + inner: File, + path: PathBuf, +} + +impl FileWrapper { + fn open>(path: P) -> Result { + let p = path.as_ref(); + let f = wrap_io_error!(p, File::open(p))?; + Ok(FileWrapper { + inner: f, + path: p.to_owned(), + }) + } + fn open_at(root: P, dirfd: Fd, path: Q) -> Result + where + P: AsRef, + Q: AsRef, + { + use rustix::fs::{Mode, OFlags}; + + let p = root.as_ref().join(path.as_ref()); + let fd = wrap_io_error!( + p, + rustix::fs::openat(dirfd, path.as_ref(), OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty()) + )?; + Ok(FileWrapper { + inner: File::from(fd), + path: p, + }) + } + + /// Returns the inner file + fn inner(self) -> File { + self.inner + } +} + +impl Read for FileWrapper { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + wrap_io_error!(self.path, self.inner.read(buf)) + } + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + wrap_io_error!(self.path, self.inner.read_to_end(buf)) + } + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + wrap_io_error!(self.path, self.inner.read_to_string(buf)) + } + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + wrap_io_error!(self.path, self.inner.read_exact(buf)) + } +} + +impl Seek for FileWrapper { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + wrap_io_error!(self.path, self.inner.seek(pos)) + } +} + +pub trait ProcfsLocal: Sized { + fn new() -> ProcResult; +} + +impl ProcfsLocal for LoadAverage { + /// Reads load average info from `/proc/loadavg` + fn new() -> ProcResult { + LoadAverage::from_reader(FileWrapper::open("/proc/loadavg")?) + } +} + +/// Return the number of ticks per second. +/// +/// This isn't part of the proc file system, but it's a useful thing to have, since several fields +/// count in ticks. This is calculated from `sysconf(_SC_CLK_TCK)`. +pub fn ticks_per_second() -> u64 { + rustix::param::clock_ticks_per_second() +} + +/// The boot time of the system, as a `DateTime` object. +/// +/// This is calculated from `/proc/stat`. +/// +/// This function requires the "chrono" features to be enabled (which it is by default). +#[cfg(feature = "chrono")] +pub fn boot_time() -> ProcResult> { + use chrono::TimeZone; + let secs = boot_time_secs()?; + + let date_time = expect!(chrono::Local.timestamp_opt(secs as i64, 0).single()); + + Ok(date_time) +} + +/// The boottime of the system, in seconds since the epoch +/// +/// This is calculated from `/proc/stat`. +/// +#[cfg_attr( + not(feature = "chrono"), + doc = "If you compile with the optional `chrono` feature, you can use the `boot_time()` method to get the boot time as a `DateTime` object." +)] +#[cfg_attr( + feature = "chrono", + doc = "See also [boot_time()] to get the boot time as a `DateTime`" +)] +pub fn boot_time_secs() -> ProcResult { + BOOT_TIME.with(|x| { + let mut btime = x.borrow_mut(); + if let Some(btime) = *btime { + Ok(btime) + } else { + let stat = KernelStats::new()?; + *btime = Some(stat.btime); + Ok(stat.btime) + } + }) +} + +thread_local! { + static BOOT_TIME : std::cell::RefCell> = std::cell::RefCell::new(None); +} + +/// Memory page size, in bytes. +/// +/// This is calculated from `sysconf(_SC_PAGESIZE)`. +pub fn page_size() -> u64 { + rustix::param::page_size() as u64 +} + +/// Returns a configuration options used to build the currently running kernel +/// +/// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. +/// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). +/// +/// # Notes +/// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled +/// (which it is by default). +#[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")] +#[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")] +pub fn kernel_config() -> ProcResult> { + let reader: Box = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") { + #[cfg(feature = "flate2")] + { + let file = FileWrapper::open(PROC_CONFIG_GZ)?; + let decoder = flate2::read::GzDecoder::new(file); + Box::new(decoder) + } + #[cfg(not(feature = "flate2"))] + { + unreachable!("flate2 feature not enabled") + } + } else { + let kernel = rustix::process::uname(); + + let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy()); + + match FileWrapper::open(filename) { + Ok(file) => Box::new(BufReader::new(file)), + Err(e) => match e.kind() { + io::ErrorKind::NotFound => { + let file = FileWrapper::open(BOOT_CONFIG)?; + Box::new(file) + } + _ => return Err(e.into()), + }, + } + }; + + kernel_config_from_read(reader) +} + +impl ProcfsLocal for KernelStats { + fn new() -> ProcResult { + KernelStats::from_reader(FileWrapper::open("/proc/stat")?, ticks_per_second()) + } +} + +/// Get various virtual memory statistics +/// +/// Since the exact set of statistics will vary from kernel to kernel, +/// and because most of them are not well documented, this function +/// returns a HashMap instead of a struct. Consult the kernel source +/// code for more details of this data. +/// +/// This data is taken from the `/proc/vmstat` file. +/// +/// (since Linux 2.6.0) +pub fn vmstat() -> ProcResult> { + vmstat_from_read(FileWrapper::open("/proc/vmstat")?) +} + +/// Get a list of loaded kernel modules +/// +/// This corresponds to the data in `/proc/modules`. +pub fn modules() -> ProcResult> { + modules_from_read(FileWrapper::open("/proc/modules")?) +} + +/// Get a list of the arguments passed to the Linux kernel at boot time +/// +/// This corresponds to the data in `/proc/cmdline` +pub fn cmdline() -> ProcResult> { + cmdline_from_read(FileWrapper::open("/proc/cmdline")?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_statics() { + println!("{:?}", *TICKS_PER_SECOND); + println!("{:?}", *KERNEL); + println!("{:?}", *PAGESIZE); + } + + #[test] + fn test_loadavg() { + let load = LoadAverage::new().unwrap(); + println!("{:?}", load); + } + + #[test] + fn test_kernel_config() { + // TRAVIS + // we don't have access to the kernel_config on travis, so skip that test there + match std::env::var("TRAVIS") { + Ok(ref s) if s == "true" => return, + _ => {} + } + if !Path::new(PROC_CONFIG_GZ).exists() && !Path::new(BOOT_CONFIG).exists() { + return; + } + + let config = kernel_config().unwrap(); + println!("{:#?}", config); + } + + #[test] + fn test_file_io_errors() { + fn inner>(p: P) -> Result<(), ProcError> { + let mut file = FileWrapper::open(p)?; + + let mut buf = [0; 128]; + file.read_exact(&mut buf[0..128])?; + + Ok(()) + } + + let err = inner("/this_should_not_exist").unwrap_err(); + println!("{}", err); + + match err { + ProcError::NotFound(Some(p)) => { + assert_eq!(p, Path::new("/this_should_not_exist")); + } + x => panic!("Unexpected return value: {:?}", x), + } + + match inner("/proc/loadavg") { + Err(ProcError::Io(_, Some(p))) => { + assert_eq!(p, Path::new("/proc/loadavg")); + } + x => panic!("Unexpected return value: {:?}", x), + } + } + + #[test] + fn test_kernel_stat() { + let stat = KernelStats::new().unwrap(); + println!("{:#?}", stat); + + // the boottime from KernelStats should match the boottime from /proc/uptime + let boottime = boot_time_secs().unwrap(); + + let diff = (boottime as i32 - stat.btime as i32).abs(); + assert!(diff <= 1); + + let cpuinfo = CpuInfo::new().unwrap(); + assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len()); + + // the sum of each individual CPU should be equal to the total cpu entry + // note: on big machines with 128 cores, it seems that the differences can be rather high, + // especially when heavily loaded. So this test tolerates a 6000-tick discrepancy + // (60 seconds in a 100-tick-per-second kernel) + + let user: u64 = stat.cpu_time.iter().map(|i| i.user).sum(); + let nice: u64 = stat.cpu_time.iter().map(|i| i.nice).sum(); + let system: u64 = stat.cpu_time.iter().map(|i| i.system).sum(); + assert!( + (stat.total.user as i64 - user as i64).abs() < 6000, + "sum:{} total:{} diff:{}", + stat.total.user, + user, + stat.total.user - user + ); + assert!( + (stat.total.nice as i64 - nice as i64).abs() < 6000, + "sum:{} total:{} diff:{}", + stat.total.nice, + nice, + stat.total.nice - nice + ); + assert!( + (stat.total.system as i64 - system as i64).abs() < 6000, + "sum:{} total:{} diff:{}", + stat.total.system, + system, + stat.total.system - system + ); + + let diff = stat.total.idle as i64 - (stat.cpu_time.iter().map(|i| i.idle).sum::() as i64).abs(); + assert!(diff < 1000, "idle time difference too high: {}", diff); + } + + #[test] + fn test_vmstat() { + let stat = vmstat().unwrap(); + println!("{:?}", stat); + } + + #[test] + fn test_modules() { + let mods = modules().unwrap(); + for module in mods.values() { + println!("{:?}", module); + } + } + + #[test] + fn tests_tps() { + let tps = ticks_per_second(); + println!("{} ticks per second", tps); + } + + #[test] + fn test_cmdline() { + let cmdline = cmdline().unwrap(); + + for argument in cmdline { + println!("{}", argument); + } + } + + /// Test that our error type can be easily used with the `failure` crate + #[test] + fn test_failure() { + fn inner() -> Result<(), failure::Error> { + let _load = crate::LoadAverage::new()?; + Ok(()) + } + let _ = inner(); + + fn inner2() -> Result<(), failure::Error> { + let proc = crate::process::Process::new(1)?; + let _io = proc.maps()?; + Ok(()) + } + + let _ = inner2(); + // Unwrapping this failure should produce a message that looks like: + // thread 'tests::test_failure' panicked at 'called `Result::unwrap()` on an `Err` value: PermissionDenied(Some("/proc/1/maps"))', src/libcore/result.rs:997:5 + } + + /// Test that an ESRCH error gets mapped into a ProcError::NotFound + #[test] + fn test_esrch() { + let mut command = std::process::Command::new("sleep") + .arg("10000") + .spawn() + .expect("Failed to start sleep"); + let p = crate::process::Process::new(command.id() as i32).expect("Failed to create Process"); + command.kill().expect("Failed to kill sleep"); + command.wait().expect("Failed to wait for sleep"); + let e = p.stat().unwrap_err(); + println!("{:?}", e); + + assert!(matches!(e, ProcError::NotFound(_))); + } +} diff --git a/src/locks.rs b/procfs/src/locks.rs similarity index 100% rename from src/locks.rs rename to procfs/src/locks.rs diff --git a/src/meminfo.rs b/procfs/src/meminfo.rs similarity index 100% rename from src/meminfo.rs rename to procfs/src/meminfo.rs diff --git a/src/net.rs b/procfs/src/net.rs similarity index 100% rename from src/net.rs rename to procfs/src/net.rs diff --git a/src/pressure.rs b/procfs/src/pressure.rs similarity index 100% rename from src/pressure.rs rename to procfs/src/pressure.rs diff --git a/src/process/limit.rs b/procfs/src/process/limit.rs similarity index 100% rename from src/process/limit.rs rename to procfs/src/process/limit.rs diff --git a/src/process/mod.rs b/procfs/src/process/mod.rs similarity index 100% rename from src/process/mod.rs rename to procfs/src/process/mod.rs diff --git a/src/process/mount.rs b/procfs/src/process/mount.rs similarity index 100% rename from src/process/mount.rs rename to procfs/src/process/mount.rs diff --git a/src/process/namespaces.rs b/procfs/src/process/namespaces.rs similarity index 100% rename from src/process/namespaces.rs rename to procfs/src/process/namespaces.rs diff --git a/src/process/pagemap.rs b/procfs/src/process/pagemap.rs similarity index 100% rename from src/process/pagemap.rs rename to procfs/src/process/pagemap.rs diff --git a/procfs/src/process/schedstat.rs b/procfs/src/process/schedstat.rs new file mode 100644 index 00000000..bc3a1cd3 --- /dev/null +++ b/procfs/src/process/schedstat.rs @@ -0,0 +1,42 @@ +use crate::from_iter; +use crate::ProcResult; +use std::io::Read; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// Provides scheduler statistics of the process, based on the `/proc//schedstat` file. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Schedstat { + /// Time spent on the cpu. + /// + /// Measured in nanoseconds. + pub sum_exec_runtime: u64, + /// Time spent waiting on a runqueue. + /// + /// Measured in nanoseconds. + pub run_delay: u64, + /// \# of timeslices run on this cpu. + pub pcount: u64, +} + +impl Schedstat { + pub fn from_reader(mut r: R) -> ProcResult { + let mut line = String::new(); + r.read_to_string(&mut line)?; + let mut s = line.split_whitespace(); + + let schedstat = Schedstat { + sum_exec_runtime: expect!(from_iter(&mut s)), + run_delay: expect!(from_iter(&mut s)), + pcount: expect!(from_iter(&mut s)), + }; + + if cfg!(test) { + assert!(s.next().is_none()); + } + + Ok(schedstat) + } +} diff --git a/procfs/src/process/smaps_rollup.rs b/procfs/src/process/smaps_rollup.rs new file mode 100644 index 00000000..fa494f5a --- /dev/null +++ b/procfs/src/process/smaps_rollup.rs @@ -0,0 +1,14 @@ +use super::MemoryMaps; +use crate::ProcResult; +use std::io::Read; + +#[derive(Debug)] +pub struct SmapsRollup { + pub memory_map_rollup: MemoryMaps, +} + +impl SmapsRollup { + pub fn from_reader(r: R) -> ProcResult { + MemoryMaps::from_reader(r).map(|m| SmapsRollup { memory_map_rollup: m }) + } +} diff --git a/src/process/stat.rs b/procfs/src/process/stat.rs similarity index 100% rename from src/process/stat.rs rename to procfs/src/process/stat.rs diff --git a/src/process/status.rs b/procfs/src/process/status.rs similarity index 100% rename from src/process/status.rs rename to procfs/src/process/status.rs diff --git a/src/process/task.rs b/procfs/src/process/task.rs similarity index 100% rename from src/process/task.rs rename to procfs/src/process/task.rs diff --git a/src/process/tests.rs b/procfs/src/process/tests.rs similarity index 100% rename from src/process/tests.rs rename to procfs/src/process/tests.rs diff --git a/src/sys/fs/binfmt_misc.rs b/procfs/src/sys/fs/binfmt_misc.rs similarity index 100% rename from src/sys/fs/binfmt_misc.rs rename to procfs/src/sys/fs/binfmt_misc.rs diff --git a/src/sys/fs/epoll.rs b/procfs/src/sys/fs/epoll.rs similarity index 100% rename from src/sys/fs/epoll.rs rename to procfs/src/sys/fs/epoll.rs diff --git a/src/sys/fs/mod.rs b/procfs/src/sys/fs/mod.rs similarity index 100% rename from src/sys/fs/mod.rs rename to procfs/src/sys/fs/mod.rs diff --git a/src/sys/kernel/keys.rs b/procfs/src/sys/kernel/keys.rs similarity index 100% rename from src/sys/kernel/keys.rs rename to procfs/src/sys/kernel/keys.rs diff --git a/src/sys/kernel/mod.rs b/procfs/src/sys/kernel/mod.rs similarity index 100% rename from src/sys/kernel/mod.rs rename to procfs/src/sys/kernel/mod.rs diff --git a/src/sys/kernel/random.rs b/procfs/src/sys/kernel/random.rs similarity index 100% rename from src/sys/kernel/random.rs rename to procfs/src/sys/kernel/random.rs diff --git a/src/sys/mod.rs b/procfs/src/sys/mod.rs similarity index 100% rename from src/sys/mod.rs rename to procfs/src/sys/mod.rs diff --git a/src/sys/vm.rs b/procfs/src/sys/vm.rs similarity index 100% rename from src/sys/vm.rs rename to procfs/src/sys/vm.rs diff --git a/src/sysvipc_shm.rs b/procfs/src/sysvipc_shm.rs similarity index 100% rename from src/sysvipc_shm.rs rename to procfs/src/sysvipc_shm.rs diff --git a/src/uptime.rs b/procfs/src/uptime.rs similarity index 100% rename from src/uptime.rs rename to procfs/src/uptime.rs From c128e303fa3d48d2c1a68db1768a3764011f0f64 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Thu, 9 Mar 2023 11:20:01 -0500 Subject: [PATCH 2/7] Further progress converting things, and cleaning up interfaces using some traits. The `procfs` crate and tests now build (and pass). --- procfs-core/src/cgroups.rs | 39 +- procfs-core/src/iomem.rs | 65 ++ procfs-core/src/lib.rs | 403 ++++++++--- procfs-core/src/process/limit.rs | 26 +- procfs-core/src/process/mod.rs | 104 +-- procfs-core/src/process/mount.rs | 87 ++- procfs-core/src/process/namespaces.rs | 11 +- procfs-core/src/process/schedstat.rs | 4 +- procfs-core/src/process/smaps_rollup.rs | 7 +- procfs-core/src/process/stat.rs | 75 +- procfs-core/src/process/status.rs | 17 +- procfs/examples/dump.rs | 3 +- procfs/examples/iomem.rs | 2 +- procfs/examples/kpagecount.rs | 4 +- procfs/examples/lsmod.rs | 3 +- procfs/src/cgroups.rs | 17 +- procfs/src/cpuinfo.rs | 2 +- procfs/src/diskstats.rs | 2 +- procfs/src/iomem.rs | 66 +- procfs/src/keyring.rs | 2 +- procfs/src/lib.rs | 313 ++++---- procfs/src/locks.rs | 2 +- procfs/src/meminfo.rs | 47 +- procfs/src/net.rs | 2 +- procfs/src/process/limit.rs | 309 -------- procfs/src/process/mod.rs | 907 +----------------------- procfs/src/process/mount.rs | 656 ----------------- procfs/src/process/namespaces.rs | 41 +- procfs/src/process/pagemap.rs | 168 +---- procfs/src/process/schedstat.rs | 42 -- procfs/src/process/smaps_rollup.rs | 14 - procfs/src/process/stat.rs | 420 ----------- procfs/src/process/status.rs | 391 ---------- procfs/src/process/task.rs | 14 +- procfs/src/process/tests.rs | 177 ++++- procfs/src/sys/fs/binfmt_misc.rs | 2 +- procfs/src/sys/fs/mod.rs | 2 +- procfs/src/sysvipc_shm.rs | 2 +- procfs/src/uptime.rs | 2 +- 39 files changed, 948 insertions(+), 3502 deletions(-) create mode 100644 procfs-core/src/iomem.rs delete mode 100644 procfs/src/process/limit.rs delete mode 100644 procfs/src/process/mount.rs delete mode 100644 procfs/src/process/schedstat.rs delete mode 100644 procfs/src/process/smaps_rollup.rs delete mode 100644 procfs/src/process/stat.rs delete mode 100644 procfs/src/process/status.rs diff --git a/procfs-core/src/cgroups.rs b/procfs-core/src/cgroups.rs index fbf1be80..05ad4864 100644 --- a/procfs-core/src/cgroups.rs +++ b/procfs-core/src/cgroups.rs @@ -1,7 +1,7 @@ use crate::ProcResult; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader, Read}; +use std::io::BufRead; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] @@ -24,13 +24,15 @@ pub struct CGroupController { pub enabled: bool, } -impl CGroupController { - /// Parse input into a vector of cgroup controllers. - // This is returning a vector, but if each subsystem name is unique, maybe this can be a - // hashmap instead - pub fn cgroup_controllers_from_reader(reader: R) -> ProcResult> { - let reader = BufReader::new(reader); +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +/// Container group controller information. +// This contains a vector, but if each subsystem name is unique, maybe this can be a +// hashmap instead +pub struct CGroupControllers(pub Vec); +impl crate::FromBufRead for CGroupControllers { + fn from_buf_read(reader: R) -> ProcResult { let mut vec = Vec::new(); for line in reader.lines() { @@ -53,14 +55,14 @@ impl CGroupController { }); } - Ok(vec) + Ok(CGroupControllers(vec)) } } /// Information about a process cgroup #[derive(Debug, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct ProcessCgroup { +pub struct ProcessCGroup { /// For cgroups version 1 hierarchies, this field contains a unique hierarchy ID number /// that can be matched to a hierarchy ID in /proc/cgroups. For the cgroups version 2 /// hierarchy, this field contains the value 0. @@ -78,11 +80,13 @@ pub struct ProcessCgroup { pub pathname: String, } -impl ProcessCgroup { - /// Parse input into a vector of cgroups. - pub fn cgroups_from_reader(reader: R) -> ProcResult> { - let reader = BufReader::new(reader); +/// Information about process cgroups. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct ProcessCGroups(pub Vec); +impl crate::FromBufRead for ProcessCGroups { + fn from_buf_read(reader: R) -> ProcResult { let mut vec = Vec::new(); for line in reader.lines() { @@ -99,18 +103,13 @@ impl ProcessCgroup { .collect(); let pathname = expect!(s.next(), "path").to_owned(); - vec.push(ProcessCgroup { + vec.push(ProcessCGroup { hierarchy, controllers, pathname, }); } - Ok(vec) + Ok(ProcessCGroups(vec)) } } - -#[cfg(test)] -mod tests { - // TODO -} diff --git a/procfs-core/src/iomem.rs b/procfs-core/src/iomem.rs new file mode 100644 index 00000000..0c1be1a7 --- /dev/null +++ b/procfs-core/src/iomem.rs @@ -0,0 +1,65 @@ +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; +use std::io::BufRead; + +use super::ProcResult; +use crate::{process::Pfn, split_into_num}; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Iomem(pub Vec<(usize, PhysicalMemoryMap)>); + +impl crate::FromBufRead for Iomem { + fn from_buf_read(r: R) -> ProcResult { + let mut vec = Vec::new(); + + for line in r.lines() { + let line = expect!(line); + + let (indent, map) = PhysicalMemoryMap::from_line(&line)?; + + vec.push((indent, map)); + } + + Ok(Iomem(vec)) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct PhysicalMemoryMap { + /// The address space in the process that the mapping occupies. + pub address: (u64, u64), + pub name: String, +} + +impl PhysicalMemoryMap { + fn from_line(line: &str) -> ProcResult<(usize, PhysicalMemoryMap)> { + let indent = line.chars().take_while(|c| *c == ' ').count() / 2; + let line = line.trim(); + let mut s = line.split(" : "); + let address = expect!(s.next()); + let name = expect!(s.next()); + + Ok(( + indent, + PhysicalMemoryMap { + address: split_into_num(address, '-', 16)?, + name: String::from(name), + }, + )) + } + + /// Get the PFN range for the mapping + /// + /// First element of the tuple (start) is included. + /// Second element (end) is excluded + pub fn get_range(&self) -> impl crate::WithSystemInfo { + move |si: &crate::SystemInfo| { + let start = self.address.0 / si.page_size(); + let end = (self.address.1 + 1) / si.page_size(); + + (Pfn(start), Pfn(end)) + } + } +} diff --git a/procfs-core/src/lib.rs b/procfs-core/src/lib.rs index f20dbcb6..a385edf4 100644 --- a/procfs-core/src/lib.rs +++ b/procfs-core/src/lib.rs @@ -48,7 +48,7 @@ use bitflags::bitflags; use std::fmt; -use std::io::{self, BufRead, BufReader, Read}; +use std::io::{BufRead, BufReader, Read}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{collections::HashMap, time::Duration}; @@ -56,7 +56,63 @@ use std::{collections::HashMap, time::Duration}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -trait IntoOption { +/// Types which can be parsed from a Read implementation. +pub trait FromRead: Sized { + /// Read the type from a Read. + fn from_read(r: R) -> ProcResult; + + /// Read the type from a file. + fn from_file>(path: P) -> ProcResult { + std::fs::File::open(path.as_ref()) + .map_err(|e| e.into()) + .and_then(|f| Self::from_read(f)) + .map_err(|e| e.error_path(path.as_ref())) + } +} + +/// Types which can be parsed from a BufRead implementation. +pub trait FromBufRead: Sized { + fn from_buf_read(r: R) -> ProcResult; +} + +impl FromRead for T { + fn from_read(r: R) -> ProcResult { + Self::from_buf_read(BufReader::new(r)) + } +} + +/// Types which can be parsed from a Read implementation and system info. +pub trait FromReadSI: Sized { + /// Parse the type from a Read and system info. + fn from_read(r: R, system_info: &SystemInfo) -> ProcResult; + + /// Parse the type from a file. + fn from_file>(path: P, system_info: &SystemInfo) -> ProcResult { + std::fs::File::open(path.as_ref()) + .map_err(|e| e.into()) + .and_then(|f| Self::from_read(f, system_info)) + .map_err(|e| e.error_path(path.as_ref())) + } +} + +/// Types which can be parsed from a BufRead implementation and system info. +pub trait FromBufReadSI: Sized { + fn from_buf_read(r: R, system_info: &SystemInfo) -> ProcResult; +} + +impl FromReadSI for T { + fn from_read(r: R, system_info: &SystemInfo) -> ProcResult { + Self::from_buf_read(BufReader::new(r), system_info) + } +} + +/// Extension traits useful for importing wholesale. +pub mod prelude { + pub use super::{FromBufRead, FromBufReadSI, FromRead, FromReadSI}; +} + +#[doc(hidden)] +pub trait IntoOption { fn into_option(t: Self) -> Option; } @@ -72,10 +128,13 @@ impl IntoOption for Result { } } +#[doc(hidden)] pub trait IntoResult { fn into(t: Self) -> Result; } +#[macro_export] +#[doc(hidden)] macro_rules! build_internal_error { ($err: expr) => { crate::ProcError::InternalError(crate::InternalError { @@ -99,7 +158,8 @@ macro_rules! build_internal_error { // custom NoneError, since std::option::NoneError is nightly-only // See /~https://github.com/rust-lang/rust/issues/42327 -struct NoneError; +#[doc(hidden)] +pub struct NoneError; impl std::fmt::Display for NoneError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -120,6 +180,8 @@ impl IntoResult for Result { } #[allow(unused_macros)] +#[macro_export] +#[doc(hidden)] macro_rules! proc_panic { ($e:expr) => { crate::IntoOption::into_option($e).unwrap_or_else(|| { @@ -140,39 +202,43 @@ macro_rules! proc_panic { }; } +#[macro_export] +#[doc(hidden)] macro_rules! expect { ($e:expr) => { match crate::IntoResult::into($e) { Ok(v) => v, - Err(e) => return Err(build_internal_error!(e)), + Err(e) => return Err(crate::build_internal_error!(e)), } }; ($e:expr, $msg:expr) => { match crate::IntoResult::into($e) { Ok(v) => v, - Err(e) => return Err(build_internal_error!(e, $msg)), + Err(e) => return Err(crate::build_internal_error!(e, $msg)), } }; } +#[macro_export] +#[doc(hidden)] macro_rules! from_str { ($t:tt, $e:expr) => {{ let e = $e; - expect!( + crate::expect!( $t::from_str_radix(e, 10), format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t),) ) }}; ($t:tt, $e:expr, $radix:expr) => {{ let e = $e; - expect!( + crate::expect!( $t::from_str_radix(e, $radix), format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t)) ) }}; ($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{ let e = $e; - expect!( + crate::expect!( $t::from_str_radix(e, $radix), format!( "Failed to parse {} ({:?}) as a {} (pid {})", @@ -185,7 +251,66 @@ macro_rules! from_str { }}; } -// TODO temporary, only for procfs +/// Auxiliary system information interface. +pub trait SystemInfoInterface { + fn boot_time_secs(&self) -> ProcResult; + fn ticks_per_second(&self) -> u64; + fn page_size(&self) -> u64; + + #[cfg(feature = "chrono")] + fn boot_time(&self) -> ProcResult> { + use chrono::TimeZone; + let date_time = expect!(chrono::Local.timestamp_opt(self.boot_time_secs()? as i64, 0).single()); + Ok(date_time) + } +} + +/// Auxiliary system information. +pub type SystemInfo = dyn SystemInfoInterface; + +/// A convenience stuct implementing [SystemInfoInterface] with explicitly-specified values. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct ExplicitSystemInfo { + pub boot_time_secs: u64, + pub ticks_per_second: u64, + pub page_size: u64, +} + +impl SystemInfoInterface for ExplicitSystemInfo { + fn boot_time_secs(&self) -> ProcResult { + Ok(self.boot_time_secs) + } + + fn ticks_per_second(&self) -> u64 { + self.ticks_per_second + } + + fn page_size(&self) -> u64 { + self.page_size + } +} + +/// Values which can provide an output given the [SystemInfo]. +pub trait WithSystemInfo<'a>: 'a { + type Output: 'a; + + /// Get the output derived from the given [SystemInfo]. + fn with_system_info(self, info: &SystemInfo) -> Self::Output; +} + +impl<'a, F: 'a, R: 'a> WithSystemInfo<'a> for F +where + F: FnOnce(&SystemInfo) -> R, +{ + type Output = R; + + fn with_system_info(self, info: &SystemInfo) -> Self::Output { + self(info) + } +} + +#[doc(hidden)] pub fn from_iter<'a, I, U>(i: I) -> ProcResult where I: IntoIterator, @@ -199,12 +324,30 @@ where } } +fn from_iter_optional<'a, I, U>(i: I) -> ProcResult> +where + I: IntoIterator, + U: FromStr, +{ + let mut iter = i.into_iter(); + let Some(val) = iter.next() else { + return Ok(None); + }; + match FromStr::from_str(val) { + Ok(u) => Ok(Some(u)), + Err(..) => Err(build_internal_error!("Failed to convert")), + } +} + pub mod process; mod cgroups; -pub use crate::cgroups::*; +pub use cgroups::*; pub mod sys; -pub use crate::sys::kernel::Version as KernelVersion; +pub use sys::kernel::Version as KernelVersion; + +mod iomem; +pub use iomem::*; // TODO temporary, only for procfs pub trait FromStrRadix: Sized { @@ -231,10 +374,10 @@ fn split_into_num(s: &str, sep: char, radix: u32) -> ProcResult /// This is used to hold both an IO error as well as the path of the file that originated the error #[derive(Debug)] -// TODO only for procfs +#[doc(hidden)] pub struct IoErrorWrapper { - path: PathBuf, - inner: std::io::Error, + pub path: PathBuf, + pub inner: std::io::Error, } impl std::error::Error for IoErrorWrapper {} @@ -279,6 +422,31 @@ pub enum ProcError { InternalError(InternalError), } +/// Extensions for dealing with ProcErrors. +pub trait ProcErrorExt { + /// Add path information to the error. + fn error_path(self, path: &Path) -> Self; +} + +impl ProcErrorExt for ProcError { + fn error_path(mut self, path: &Path) -> Self { + use ProcError::*; + match &mut self { + PermissionDenied(p) | NotFound(p) | Incomplete(p) | Io(_, p) if p.is_none() => { + *p = Some(path.to_owned()); + } + _ => (), + } + self + } +} + +impl ProcErrorExt for ProcResult { + fn error_path(self, path: &Path) -> Self { + self.map_err(|e| e.error_path(path)) + } +} + /// An internal error in the procfs crate /// /// If you encounter this error, consider it a bug and please report it on @@ -423,10 +591,8 @@ pub struct LoadAverage { pub latest_pid: u32, } -impl LoadAverage { - /// Get LoadAverage from a Read stream. - pub fn from_reader(r: R) -> ProcResult { - let mut reader = BufReader::new(r); +impl FromRead for LoadAverage { + fn from_read(mut reader: R) -> ProcResult { let mut line = String::new(); reader.read_to_string(&mut line)?; @@ -455,43 +621,48 @@ impl LoadAverage { /// Possible values for a kernel config option #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum ConfigSetting { Yes, Module, Value(String), } -/// Reads the configuration options used to build a kernel. -pub fn kernel_config_from_read(read: R) -> ProcResult> { - let read = BufReader::new(read); - let mut map = HashMap::new(); +/// The kernel configuration. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct KernelConfig(pub HashMap); - for line in read.lines() { - let line = line?; - if line.starts_with('#') { - continue; - } - if line.contains('=') { - let mut s = line.splitn(2, '='); - let name = expect!(s.next()).to_owned(); - let value = match expect!(s.next()) { - "y" => ConfigSetting::Yes, - "m" => ConfigSetting::Module, - s => ConfigSetting::Value(s.to_owned()), - }; - map.insert(name, value); +impl FromBufRead for KernelConfig { + fn from_buf_read(r: R) -> ProcResult { + let mut map = HashMap::new(); + + for line in r.lines() { + let line = line?; + if line.starts_with('#') { + continue; + } + if line.contains('=') { + let mut s = line.splitn(2, '='); + let name = expect!(s.next()).to_owned(); + let value = match expect!(s.next()) { + "y" => ConfigSetting::Yes, + "m" => ConfigSetting::Module, + s => ConfigSetting::Value(s.to_owned()), + }; + map.insert(name, value); + } } - } - Ok(map) + Ok(KernelConfig(map)) + } } /// The amount of time, measured in ticks, the CPU has been in specific states /// /// These fields are measured in ticks because the underlying data from the kernel is measured in ticks. -/// The number of ticks per second can be returned by [`ticks_per_second()`](crate::ticks_per_second) -/// and is generally 100 on most systems. - +/// The number of ticks per second is generally 100 on most systems. +/// /// To convert this value to seconds, you can divide by the tps. There are also convenience methods /// that you can use too. #[derive(Debug, Clone)] @@ -724,11 +895,9 @@ pub struct KernelStats { pub procs_blocked: Option, } -impl KernelStats { - /// Get KernelStatus from a Read stream. - pub fn from_reader(r: R, ticks_per_second: u64) -> ProcResult { - let bufread = BufReader::new(r); - let lines = bufread.lines(); +impl FromBufReadSI for KernelStats { + fn from_buf_read(r: R, system_info: &SystemInfo) -> ProcResult { + let lines = r.lines(); let mut total_cpu = None; let mut cpus = Vec::new(); @@ -741,9 +910,9 @@ impl KernelStats { for line in lines { let line = line?; if line.starts_with("cpu ") { - total_cpu = Some(CpuTime::from_str(&line, ticks_per_second)?); + total_cpu = Some(CpuTime::from_str(&line, system_info.ticks_per_second())?); } else if line.starts_with("cpu") { - cpus.push(CpuTime::from_str(&line, ticks_per_second)?); + cpus.push(CpuTime::from_str(&line, system_info.ticks_per_second())?); } else if let Some(stripped) = line.strip_prefix("ctxt ") { ctxt = Some(from_str!(u64, stripped)); } else if let Some(stripped) = line.strip_prefix("btime ") { @@ -769,24 +938,28 @@ impl KernelStats { } } -/// Get various virtual memory statistics +/// Various virtual memory statistics /// -/// Since the exact set of statistics will vary from kernel to kernel, -/// and because most of them are not well documented, this function -/// returns a HashMap instead of a struct. Consult the kernel source -/// code for more details of this data. -pub fn vmstat_from_read(r: R) -> ProcResult> { - let reader = BufReader::new(r); - let mut map = HashMap::new(); - for line in reader.lines() { - let line = line?; - let mut split = line.split_whitespace(); - let name = expect!(split.next()); - let val = from_str!(i64, expect!(split.next())); - map.insert(name.to_owned(), val); - } +/// Since the exact set of statistics will vary from kernel to kernel, and because most of them are +/// not well documented, this struct contains a HashMap instead of specific members. Consult the +/// kernel source code for more details of this data. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct VmStat(pub HashMap); + +impl FromBufRead for VmStat { + fn from_buf_read(r: R) -> ProcResult { + let mut map = HashMap::new(); + for line in r.lines() { + let line = line?; + let mut split = line.split_whitespace(); + let name = expect!(split.next()); + let val = from_str!(i64, expect!(split.next())); + map.insert(name.to_owned(), val); + } - Ok(map) + Ok(VmStat(map)) + } } /// Details about a loaded kernel module @@ -814,55 +987,65 @@ pub struct KernelModule { pub state: String, } -/// Get a list of loaded kernel modules -/// -/// `r` should correspond to the data in `/proc/modules`. -pub fn modules_from_read(r: R) -> ProcResult> { - // kernel reference: kernel/module.c m_show() - let mut map = HashMap::new(); - let reader = BufReader::new(r); - for line in reader.lines() { - let line: String = line?; - let mut s = line.split_whitespace(); - let name = expect!(s.next()); - let size = from_str!(u32, expect!(s.next())); - let refcount = from_str!(i32, expect!(s.next())); - let used_by: &str = expect!(s.next()); - let state = expect!(s.next()); - - map.insert( - name.to_string(), - KernelModule { - name: name.to_string(), - size, - refcount, - used_by: if used_by == "-" { - Vec::new() - } else { - used_by - .split(',') - .filter(|s| !s.is_empty()) - .map(|s| s.to_string()) - .collect() +/// A set of loaded kernel modules +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct KernelModules(pub HashMap); + +impl FromBufRead for KernelModules { + /// This should correspond to the data in `/proc/modules`. + fn from_buf_read(r: R) -> ProcResult { + // kernel reference: kernel/module.c m_show() + let mut map = HashMap::new(); + for line in r.lines() { + let line: String = line?; + let mut s = line.split_whitespace(); + let name = expect!(s.next()); + let size = from_str!(u32, expect!(s.next())); + let refcount = from_str!(i32, expect!(s.next())); + let used_by: &str = expect!(s.next()); + let state = expect!(s.next()); + + map.insert( + name.to_string(), + KernelModule { + name: name.to_string(), + size, + refcount, + used_by: if used_by == "-" { + Vec::new() + } else { + used_by + .split(',') + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect() + }, + state: state.to_string(), }, - state: state.to_string(), - }, - ); - } + ); + } - Ok(map) + Ok(KernelModules(map)) + } } -/// Get a list of the arguments passed to the Linux kernel at boot time -/// -/// `r` should correspond to the data in `/proc/cmdline` -pub fn cmdline_from_read(mut r: R) -> ProcResult> { - let mut buf = String::new(); - r.read_to_string(&mut buf)?; - Ok(buf - .split(' ') - .filter_map(|s| if !s.is_empty() { Some(s.to_string()) } else { None }) - .collect()) +/// A list of the arguments passed to the Linux kernel at boot time. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct KernelCmdline(pub Vec); + +impl FromRead for KernelCmdline { + /// This should correspond to the data in `/proc/cmdline`. + fn from_read(mut r: R) -> ProcResult { + let mut buf = String::new(); + r.read_to_string(&mut buf)?; + Ok(KernelCmdline( + buf.split(' ') + .filter_map(|s| if !s.is_empty() { Some(s.to_string()) } else { None }) + .collect(), + )) + } } #[cfg(test)] @@ -906,7 +1089,7 @@ mod tests { #[test] fn test_loadavg_from_reader() -> ProcResult<()> { - let load_average = LoadAverage::from_reader("2.63 1.00 1.42 3/4280 2496732".as_bytes())?; + let load_average = LoadAverage::from_read("2.63 1.00 1.42 3/4280 2496732".as_bytes())?; assert_eq!(load_average.one, 2.63); assert_eq!(load_average.five, 1.00); diff --git a/procfs-core/src/process/limit.rs b/procfs-core/src/process/limit.rs index 27779992..163497d7 100644 --- a/procfs-core/src/process/limit.rs +++ b/procfs-core/src/process/limit.rs @@ -1,7 +1,7 @@ use crate::{ProcError, ProcResult}; use std::collections::HashMap; -use std::io::{BufRead, BufReader, Read}; +use std::io::BufRead; use std::str::FromStr; #[cfg(feature = "serde1")] @@ -104,10 +104,9 @@ pub struct Limits { pub max_realtime_timeout: Limit, } -impl Limits { - pub fn from_reader(r: R) -> ProcResult { - let bufread = BufReader::new(r); - let mut lines = bufread.lines(); +impl crate::FromBufRead for Limits { + fn from_buf_read(r: R) -> ProcResult { + let mut lines = r.lines(); let mut map = HashMap::new(); @@ -186,16 +185,6 @@ pub enum LimitValue { Value(u64), } -impl LimitValue { - #[cfg(test)] - pub(crate) fn as_limit(&self) -> Option { - match self { - LimitValue::Unlimited => None, - LimitValue::Value(v) => Some(*v), - } - } -} - impl FromStr for LimitValue { type Err = ProcError; fn from_str(s: &str) -> Result { @@ -206,10 +195,3 @@ impl FromStr for LimitValue { } } } - -#[cfg(test)] -mod tests { - use crate::*; - - // TODO -} diff --git a/procfs-core/src/process/mod.rs b/procfs-core/src/process/mod.rs index 30f0bae0..23045b23 100644 --- a/procfs-core/src/process/mod.rs +++ b/procfs-core/src/process/mod.rs @@ -1,60 +1,7 @@ //! Functions and structs related to process information //! //! The primary source of data for functions in this module is the files in a `/proc//` -//! directory. If you have a process ID, you can use -//! [`Process::new(pid)`](struct.Process.html#method.new), otherwise you can get a -//! list of all running processes using [`all_processes()`](fn.all_processes.html). -//! -//! In case you have procfs filesystem mounted to a location other than `/proc`, -//! use [`Process::new_with_root()`](struct.Process.html#method.new_with_root). -//! -//! # Examples -//! -//! Here's a small example that prints out all processes that are running on the same tty as the calling -//! process. This is very similar to what "ps" does in its default mode. You can run this example -//! yourself with: -//! -//! > cargo run --example=ps -//! -//! ```rust -//! let me = procfs::process::Process::myself().unwrap(); -//! let me_stat = me.stat().unwrap(); -//! let tps = procfs::ticks_per_second(); -//! -//! println!("{: >10} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); -//! -//! let tty = format!("pty/{}", me_stat.tty_nr().1); -//! for prc in procfs::process::all_processes().unwrap() { -//! if let Ok(stat) = prc.unwrap().stat() { -//! if stat.tty_nr == me_stat.tty_nr { -//! // total_time is in seconds -//! let total_time = -//! (stat.utime + stat.stime) as f32 / (tps as f32); -//! println!( -//! "{: >10} {: <8} {: >8} {}", -//! stat.pid, tty, total_time, stat.comm -//! ); -//! } -//! } -//! } -//! ``` -//! -//! Here's a simple example of how you could get the total memory used by the current process. -//! There are several ways to do this. For a longer example, see the `examples/self_memory.rs` -//! file in the git repository. You can run this example with: -//! -//! > cargo run --example=self_memory -//! -//! ```rust -//! # use procfs::process::Process; -//! let me = Process::myself().unwrap(); -//! let me_stat = me.stat().unwrap(); -//! let page_size = procfs::page_size(); -//! -//! println!("== Data from /proc/self/stat:"); -//! println!("Total virtual memory used: {} bytes", me_stat.vsize); -//! println!("Total resident set: {} pages ({} bytes)", me_stat.rss, me_stat.rss as u64 * page_size); -//! ``` +//! directory. use super::*; use crate::from_iter; @@ -508,7 +455,7 @@ pub enum MMapPath { } impl MMapPath { - fn from(path: &str) -> ProcResult { + pub fn from(path: &str) -> ProcResult { Ok(match path.trim() { "" => MMapPath::Anonymous, "[heap]" => MMapPath::Heap, @@ -533,38 +480,21 @@ impl MMapPath { #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[non_exhaustive] -pub struct MemoryMaps { - pub memory_maps: Vec, -} +pub struct MemoryMaps(pub Vec); -impl MemoryMaps { - /// Read a [MemoryMaps] from the given byte source. - /// +impl crate::FromBufRead for MemoryMaps { /// The data should be formatted according to procfs /proc/pid/{maps,smaps,smaps_rollup}. - pub fn from_reader(r: R) -> ProcResult { - Self::read(r, None) - } - - /// Return an iterator over [MemoryMap]. - pub fn iter(&self) -> std::slice::Iter { - self.memory_maps.iter() - } - - fn read(r: R, path: Option<&Path>) -> ProcResult { - let reader = BufReader::new(r); - + fn from_buf_read(reader: R) -> ProcResult { let mut memory_maps = Vec::new(); - let mut line_iter = reader - .lines() - .map(|r| r.map_err(|_| ProcError::Incomplete(path.map(ToOwned::to_owned)))); + let mut line_iter = reader.lines().map(|r| r.map_err(|_| ProcError::Incomplete(None))); let mut current_memory_map: Option = None; while let Some(line) = line_iter.next().transpose()? { // Assumes all extension fields (in `/proc//smaps`) start with a capital letter, // which seems to be the case. if line.starts_with(|c: char| c.is_ascii_uppercase()) { match current_memory_map.as_mut() { - None => return Err(ProcError::Incomplete(path.map(ToOwned::to_owned))), + None => return Err(ProcError::Incomplete(None)), Some(mm) => { // This is probably an attribute if line.starts_with("VmFlags") { @@ -616,7 +546,14 @@ impl MemoryMaps { memory_maps.push(mm); } - Ok(MemoryMaps { memory_maps }) + Ok(MemoryMaps(memory_maps)) + } +} + +impl MemoryMaps { + /// Return an iterator over [MemoryMap]. + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() } } @@ -634,7 +571,7 @@ impl IntoIterator for MemoryMaps { type Item = MemoryMap; fn into_iter(self) -> Self::IntoIter { - self.memory_maps.into_iter() + self.0.into_iter() } } @@ -712,10 +649,9 @@ impl MMapExtension { } } -impl Io { - pub fn from_reader(r: R) -> ProcResult { +impl crate::FromBufRead for Io { + fn from_buf_read(reader: R) -> ProcResult { let mut map = HashMap::new(); - let reader = BufReader::new(r); for line in reader.lines() { let line = line?; @@ -843,8 +779,8 @@ pub struct StatM { pub dt: u64, } -impl StatM { - pub fn from_reader(mut r: R) -> ProcResult { +impl crate::FromRead for StatM { + fn from_read(mut r: R) -> ProcResult { let mut line = String::new(); r.read_to_string(&mut line)?; let mut s = line.split_whitespace(); diff --git a/procfs-core/src/process/mount.rs b/procfs-core/src/process/mount.rs index 071de894..d58330b1 100644 --- a/procfs-core/src/process/mount.rs +++ b/procfs-core/src/process/mount.rs @@ -3,7 +3,7 @@ use bitflags::bitflags; use crate::{from_iter, ProcResult}; use std::collections::HashMap; -use std::io::{BufRead, BufReader, Lines, Read}; +use std::io::{BufRead, Lines}; use std::path::PathBuf; use std::time::Duration; @@ -43,6 +43,41 @@ bitflags! { } } +/// Information about a all mounts in a process's mount namespace. +/// +/// This data is taken from the `/proc/[pid]/mountinfo` file. +pub struct MountInfos(pub Vec); + +impl crate::FromBufRead for MountInfos { + fn from_buf_read(r: R) -> ProcResult { + let lines = r.lines(); + let mut vec = Vec::new(); + for line in lines { + vec.push(MountInfo::from_line(&line?)?); + } + + Ok(MountInfos(vec)) + } +} + +impl IntoIterator for MountInfos { + type IntoIter = std::vec::IntoIter; + type Item = MountInfo; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a MountInfos { + type IntoIter = std::slice::Iter<'a, MountInfo>; + type Item = &'a MountInfo; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + /// Information about a specific mount in a process's mount namespace. /// /// This data is taken from the `/proc/[pid]/mountinfo` file. @@ -175,6 +210,21 @@ pub enum MountOptFields { Unbindable, } +/// A single entry in [MountStats]. +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct MountStat { + /// The name of the mounted device + pub device: Option, + /// The mountpoint within the filesystem tree + pub mount_point: PathBuf, + /// The filesystem type + pub fs: String, + /// If the mount is NFS, this will contain various NFS statistics + pub statistics: Option, +} + /// Mount information from `/proc//mountstats`. /// /// # Example: @@ -194,22 +244,13 @@ pub enum MountOptFields { #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MountStat { - /// The name of the mounted device - pub device: Option, - /// The mountpoint within the filesystem tree - pub mount_point: PathBuf, - /// The filesystem type - pub fs: String, - /// If the mount is NFS, this will contain various NFS statistics - pub statistics: Option, -} +pub struct MountStats(pub Vec); -impl MountStat { - pub fn from_reader(r: R) -> ProcResult> { +impl crate::FromBufRead for MountStats { + /// This should correspond to data in `/proc//mountstats`. + fn from_buf_read(r: R) -> ProcResult { let mut v = Vec::new(); - let bufread = BufReader::new(r); - let mut lines = bufread.lines(); + let mut lines = r.lines(); while let Some(Ok(line)) = lines.next() { if line.starts_with("device ") { // line will be of the format: @@ -235,7 +276,16 @@ impl MountStat { } } - Ok(v) + Ok(MountStats(v)) + } +} + +impl IntoIterator for MountStats { + type IntoIter = std::vec::IntoIter; + type Item = MountStat; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } @@ -535,6 +585,7 @@ pub type NFSPerOpStats = HashMap; #[cfg(test)] mod tests { use super::*; + use crate::FromRead; use std::time::Duration; #[test] @@ -547,7 +598,7 @@ mod tests { #[test] fn test_proc_mountstats() { - let simple = MountStat::from_reader( + let MountStats(simple) = FromRead::from_read( "device /dev/md127 mounted on /boot with fstype ext2 device /dev/md124 mounted on /home with fstype ext4 device tmpfs mounted on /run/user/0 with fstype tmpfs @@ -576,7 +627,7 @@ device tmpfs mounted on /run/user/0 with fstype tmpfs }, ]; assert_eq!(simple, simple_parsed); - let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1 + let MountStats(mountstats) = FromRead::from_read("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1 opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none age: 3542 impl_id: name='',domain='',date='0,0' diff --git a/procfs-core/src/process/namespaces.rs b/procfs-core/src/process/namespaces.rs index 1d7e2fc7..869b84da 100644 --- a/procfs-core/src/process/namespaces.rs +++ b/procfs-core/src/process/namespaces.rs @@ -1,11 +1,11 @@ -use std::{ffi::OsString, path::PathBuf}; +use std::collections::HashMap; +use std::ffi::OsString; +use std::path::PathBuf; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// Information about a namespace -/// -/// See also the [Process::namespaces()] method #[derive(Debug, Clone, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Namespace { @@ -25,3 +25,8 @@ impl PartialEq for Namespace { self.identifier == other.identifier && self.device_id == other.device_id } } + +/// All namespaces of a process. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Namespaces(pub HashMap); diff --git a/procfs-core/src/process/schedstat.rs b/procfs-core/src/process/schedstat.rs index bc3a1cd3..138def7e 100644 --- a/procfs-core/src/process/schedstat.rs +++ b/procfs-core/src/process/schedstat.rs @@ -21,8 +21,8 @@ pub struct Schedstat { pub pcount: u64, } -impl Schedstat { - pub fn from_reader(mut r: R) -> ProcResult { +impl crate::FromRead for Schedstat { + fn from_read(mut r: R) -> ProcResult { let mut line = String::new(); r.read_to_string(&mut line)?; let mut s = line.split_whitespace(); diff --git a/procfs-core/src/process/smaps_rollup.rs b/procfs-core/src/process/smaps_rollup.rs index fa494f5a..f428deab 100644 --- a/procfs-core/src/process/smaps_rollup.rs +++ b/procfs-core/src/process/smaps_rollup.rs @@ -1,14 +1,13 @@ use super::MemoryMaps; use crate::ProcResult; -use std::io::Read; #[derive(Debug)] pub struct SmapsRollup { pub memory_map_rollup: MemoryMaps, } -impl SmapsRollup { - pub fn from_reader(r: R) -> ProcResult { - MemoryMaps::from_reader(r).map(|m| SmapsRollup { memory_map_rollup: m }) +impl crate::FromBufRead for SmapsRollup { + fn from_buf_read(r: R) -> ProcResult { + MemoryMaps::from_buf_read(r).map(|m| SmapsRollup { memory_map_rollup: m }) } } diff --git a/procfs-core/src/process/stat.rs b/procfs-core/src/process/stat.rs index 20f687e0..40ec289f 100644 --- a/procfs-core/src/process/stat.rs +++ b/procfs-core/src/process/stat.rs @@ -1,6 +1,6 @@ use super::ProcState; use super::StatFlags; -use crate::{from_iter, KernelVersion, ProcResult}; +use crate::{from_iter, from_iter_optional, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -228,9 +228,9 @@ pub struct Stat { pub exit_code: Option, } -impl Stat { +impl crate::FromRead for Stat { #[allow(clippy::cognitive_complexity)] - pub fn from_reader(kernel_version: Option, mut r: R) -> ProcResult { + fn from_read(mut r: R) -> ProcResult { // read in entire thing, this is only going to be 1 line let mut buf = Vec::with_capacity(512); r.read_to_end(&mut buf)?; @@ -285,35 +285,28 @@ impl Stat { let nswap = expect!(from_iter(&mut rest)); let cnswap = expect!(from_iter(&mut rest)); - macro_rules! since_kernel { - ($a:tt, $b:tt, $c:tt, $e:expr) => { - if let Some(kernel) = &kernel_version { - if kernel >= &KernelVersion::new($a, $b, $c) { - Some($e) - } else { - None - } - } else { - None - } - }; - } - - let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest))); - let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest))); - let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); - let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); - let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest))); - let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); - let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); - let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); + // Since 2.1.22 + let exit_signal = expect!(from_iter_optional(&mut rest)); + // Since 2.2.8 + let processor = expect!(from_iter_optional(&mut rest)); + // Since 2.5.19 + let rt_priority = expect!(from_iter_optional(&mut rest)); + let policy = expect!(from_iter_optional(&mut rest)); + // Since 2.6.18 + let delayacct_blkio_ticks = expect!(from_iter_optional(&mut rest)); + // Since 2.6.24 + let guest_time = expect!(from_iter_optional(&mut rest)); + let cguest_time = expect!(from_iter_optional(&mut rest)); + // Since 3.3.0 + let start_data = expect!(from_iter_optional(&mut rest)); + let end_data = expect!(from_iter_optional(&mut rest)); + let start_brk = expect!(from_iter_optional(&mut rest)); + // Since 3.5.0 + let arg_start = expect!(from_iter_optional(&mut rest)); + let arg_end = expect!(from_iter_optional(&mut rest)); + let env_start = expect!(from_iter_optional(&mut rest)); + let env_end = expect!(from_iter_optional(&mut rest)); + let exit_code = expect!(from_iter_optional(&mut rest)); Ok(Stat { pid, @@ -370,7 +363,9 @@ impl Stat { exit_code, }) } +} +impl Stat { pub fn state(&self) -> ProcResult { ProcState::from_char(self.state) .ok_or_else(|| build_internal_error!(format!("{:?} is not a recognized process state", self.state))) @@ -401,20 +396,18 @@ impl Stat { /// /// This function requires the "chrono" features to be enabled (which it is by default). #[cfg(feature = "chrono")] - pub fn starttime( - &self, - boot_time: chrono::DateTime, - ticks_per_second: u64, - ) -> ProcResult> { - let seconds_since_boot = self.starttime as f32 / ticks_per_second as f32; + pub fn starttime(&self) -> impl crate::WithSystemInfo>> { + move |si: &crate::SystemInfo| { + let seconds_since_boot = self.starttime as f32 / si.ticks_per_second() as f32; - Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)) + Ok(si.boot_time()? + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)) + } } /// Gets the Resident Set Size (in bytes) /// /// The `rss` field will return the same value in pages - pub fn rss_bytes(&self, page_size: u64) -> u64 { - self.rss * page_size + pub fn rss_bytes(&self) -> impl crate::WithSystemInfo { + move |si: &crate::SystemInfo| self.rss * si.page_size() } } diff --git a/procfs-core/src/process/status.rs b/procfs-core/src/process/status.rs index eb394414..a189face 100644 --- a/procfs-core/src/process/status.rs +++ b/procfs-core/src/process/status.rs @@ -1,6 +1,6 @@ use crate::{FromStrRadix, ProcResult}; use std::collections::HashMap; -use std::io::{BufRead, BufReader, Read}; +use std::io::BufRead; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -178,10 +178,9 @@ pub struct Status { pub thp_enabled: Option, } -impl Status { - pub fn from_reader(r: R) -> ProcResult { +impl crate::FromBufRead for Status { + fn from_buf_read(reader: R) -> ProcResult { let mut map = HashMap::new(); - let reader = BufReader::new(r); for line in reader.lines() { let line = line?; @@ -283,7 +282,9 @@ impl Status { Ok(status) } +} +impl Status { fn parse_with_kb(s: Option) -> ProcResult> { if let Some(s) = s { Ok(Some(from_str!(T, &s.replace(" kB", "")))) @@ -292,7 +293,8 @@ impl Status { } } - pub(crate) fn parse_uid_gid(s: &str, i: usize) -> ProcResult { + #[doc(hidden)] + pub fn parse_uid_gid(s: &str, i: usize) -> ProcResult { Ok(from_str!(u32, expect!(s.split_whitespace().nth(i)))) } @@ -338,8 +340,3 @@ impl Status { Ok(ret) } } - -#[cfg(test)] -mod tests { - // TODO -} diff --git a/procfs/examples/dump.rs b/procfs/examples/dump.rs index 56e7b21c..73a084b7 100644 --- a/procfs/examples/dump.rs +++ b/procfs/examples/dump.rs @@ -1,4 +1,5 @@ extern crate procfs; +use procfs::prelude::*; fn main() { let pid = std::env::args().nth(1).and_then(|s| s.parse::().ok()); @@ -13,5 +14,5 @@ fn main() { let stat = prc.stat().unwrap(); println!("State: {:?}", stat.state()); - println!("RSS: {} bytes", stat.rss_bytes()); + println!("RSS: {} bytes", stat.rss_bytes().get()); } diff --git a/procfs/examples/iomem.rs b/procfs/examples/iomem.rs index 8f3f56f2..28873c20 100644 --- a/procfs/examples/iomem.rs +++ b/procfs/examples/iomem.rs @@ -10,7 +10,7 @@ fn main() { let iomem = procfs::iomem().expect("Can't read /proc/iomem"); - for (indent, map) in iomem.iter() { + for (_indent, map) in iomem.iter() { if map.name == "System RAM" { println!("Found RAM here: 0x{:x}-0x{:x}", map.address.0, map.address.1); } diff --git a/procfs/examples/kpagecount.rs b/procfs/examples/kpagecount.rs index 096bf5fc..11b6b917 100644 --- a/procfs/examples/kpagecount.rs +++ b/procfs/examples/kpagecount.rs @@ -13,6 +13,8 @@ // Lots of references to this locations: addr=0x1b575000, pfn=111989, refs=134 // +use procfs::prelude::*; + fn main() { if !rustix::process::geteuid().is_root() { panic!("ERROR: Access to /proc/iomem requires root, re-run with sudo"); @@ -33,7 +35,7 @@ fn main() { // Physical memory is divided into pages of `page_size` bytes (usually 4kiB) // Each page is referenced by its Page Fram Number (PFN) - let (start_pfn, end_pfn) = map.get_range(); + let (start_pfn, end_pfn) = map.get_range().get(); let page_references = kpagecount .get_count_in_range(start_pfn, end_pfn) diff --git a/procfs/examples/lsmod.rs b/procfs/examples/lsmod.rs index 0c967194..113d61ef 100644 --- a/procfs/examples/lsmod.rs +++ b/procfs/examples/lsmod.rs @@ -1,3 +1,4 @@ +use procfs::prelude::*; use std::collections::HashMap; fn print(name: &str, indent: usize, mods: &HashMap<&str, Vec<&str>>) { @@ -11,7 +12,7 @@ fn print(name: &str, indent: usize, mods: &HashMap<&str, Vec<&str>>) { } fn main() { - let modules = procfs::modules().unwrap(); + let procfs::KernelModules(modules) = Current::current().unwrap(); // each module has a list of what other modules use it. Let's invert this and create a list of the modules used by each module. // This maps a module name to a list of modules that it uses diff --git a/procfs/src/cgroups.rs b/procfs/src/cgroups.rs index 1efe8fcf..fb545557 100644 --- a/procfs/src/cgroups.rs +++ b/procfs/src/cgroups.rs @@ -1,20 +1,25 @@ use super::process::Process; -use crate::ProcResult; -pub use procfs_core::{CGroupController, ProcessCgroup}; +use crate::{Current, ProcResult}; +use procfs_core::CGroupControllers; +pub use procfs_core::{CGroupController, ProcessCGroups}; + +impl Current for CGroupControllers { + const PATH: &'static str = "/proc/cgroups"; +} /// Information about the cgroup controllers that are compiled into the kernel /// /// (since Linux 2.6.24) -pub fn cgroups() -> ProcResult> { - CGroupController::cgroup_controllers_from_reader(std::fs::File::open("/proc/cgroups")?) +pub fn cgroups() -> ProcResult { + CGroupControllers::current() } impl Process { /// Describes control groups to which the process with the corresponding PID belongs. /// /// The displayed information differs for cgroupsversion 1 and version 2 hierarchies. - pub fn cgroups(&self) -> ProcResult> { - ProcessCgroup::cgroups_from_reader(self.open_relative("cgroup")?) + pub fn cgroups(&self) -> ProcResult { + self.read("cgroup") } } diff --git a/procfs/src/cpuinfo.rs b/procfs/src/cpuinfo.rs index ccea7c88..cdb670e3 100644 --- a/procfs/src/cpuinfo.rs +++ b/procfs/src/cpuinfo.rs @@ -1,4 +1,4 @@ -use crate::{FileWrapper, ProcResult}; +use crate::{expect, FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; diff --git a/procfs/src/diskstats.rs b/procfs/src/diskstats.rs index 47e138c8..6937458e 100644 --- a/procfs/src/diskstats.rs +++ b/procfs/src/diskstats.rs @@ -1,4 +1,4 @@ -use crate::{FileWrapper, ProcResult}; +use crate::{expect, from_str, FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; use std::io::{BufRead, BufReader}; diff --git a/procfs/src/iomem.rs b/procfs/src/iomem.rs index 5106d751..422d4964 100644 --- a/procfs/src/iomem.rs +++ b/procfs/src/iomem.rs @@ -1,64 +1,14 @@ -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader}; +use super::ProcResult; +use crate::Current; +use procfs_core::{Iomem, PhysicalMemoryMap}; -use super::{FileWrapper, ProcResult}; -use crate::{process::Pfn, split_into_num}; +impl Current for Iomem { + const PATH: &'static str = "/proc/iomem"; +} /// Reads and parses the `/proc/iomem`, returning an error if there are problems. /// -/// Requires root, otherwise every memory address will be zero +/// Requires root, otherwise every memory address will be zero. pub fn iomem() -> ProcResult> { - let f = FileWrapper::open("/proc/iomem")?; - - let reader = BufReader::new(f); - let mut vec = Vec::new(); - - for line in reader.lines() { - let line = expect!(line); - - let (indent, map) = PhysicalMemoryMap::from_line(&line)?; - - vec.push((indent, map)); - } - - Ok(vec) -} - -/// To construct this structure, see [crate::iomem()]. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct PhysicalMemoryMap { - /// The address space in the process that the mapping occupies. - pub address: (u64, u64), - pub name: String, -} - -impl PhysicalMemoryMap { - fn from_line(line: &str) -> ProcResult<(usize, PhysicalMemoryMap)> { - let indent = line.chars().take_while(|c| *c == ' ').count() / 2; - let line = line.trim(); - let mut s = line.split(" : "); - let address = expect!(s.next()); - let name = expect!(s.next()); - - Ok(( - indent, - PhysicalMemoryMap { - address: split_into_num(address, '-', 16)?, - name: String::from(name), - }, - )) - } - - /// Get the PFN range for the mapping - /// - /// First element of the tuple (start) is included. - /// Second element (end) is excluded - pub fn get_range(&self) -> (Pfn, Pfn) { - let start = self.address.0 / crate::page_size(); - let end = (self.address.1 + 1) / crate::page_size(); - - (Pfn(start), Pfn(end)) - } + Iomem::current().map(|v| v.0) } diff --git a/procfs/src/keyring.rs b/procfs/src/keyring.rs index 6e1c5df6..50575fd2 100644 --- a/procfs/src/keyring.rs +++ b/procfs/src/keyring.rs @@ -3,7 +3,7 @@ //! For more details on this facility, see the `keyrings(7)` man page. //! //! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module. -use crate::{FileWrapper, ProcResult}; +use crate::{build_internal_error, expect, from_str, FileWrapper, ProcResult}; use bitflags::bitflags; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; diff --git a/procfs/src/lib.rs b/procfs/src/lib.rs index cfc38dcd..2084d86c 100644 --- a/procfs/src/lib.rs +++ b/procfs/src/lib.rs @@ -52,106 +52,81 @@ use bitflags::bitflags; use lazy_static::lazy_static; use rustix::fd::AsFd; +use std::collections::HashMap; use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io::{self, BufRead, BufReader, Read, Seek, Write}; +use std::fs::File; +use std::io::{self, BufReader, Read, Seek}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::{collections::HashMap, time::Duration}; #[cfg(feature = "chrono")] -use chrono::{DateTime, Local}; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; +use chrono::DateTime; const PROC_CONFIG_GZ: &str = "/proc/config.gz"; const BOOT_CONFIG: &str = "/boot/config"; -macro_rules! build_internal_error { - ($err: expr) => { - crate::ProcError::InternalError(crate::InternalError { - msg: format!("Internal Unwrap Error: {}", $err), - file: file!(), - line: line!(), - #[cfg(feature = "backtrace")] - backtrace: backtrace::Backtrace::new(), - }) - }; - ($err: expr, $msg: expr) => { - crate::ProcError::InternalError(crate::InternalError { - msg: format!("Internal Unwrap Error: {}: {}", $msg, $err), - file: file!(), - line: line!(), - #[cfg(feature = "backtrace")] - backtrace: backtrace::Backtrace::new(), - }) - }; +/// Allows associating a specific file to parse. +pub trait Current: FromRead { + const PATH: &'static str; + + /// Parse the current value using the system file. + fn current() -> ProcResult { + Self::from_file(Self::PATH) + } } -#[allow(unused_macros)] -macro_rules! proc_panic { - ($e:expr) => { - crate::IntoOption::into_option($e).unwrap_or_else(|| { - panic!( - "Failed to unwrap {}. Please report this as a procfs bug.", - stringify!($e) - ) - }) - }; - ($e:expr, $msg:expr) => { - crate::IntoOption::into_option($e).unwrap_or_else(|| { - panic!( - "Failed to unwrap {} ({}). Please report this as a procfs bug.", - stringify!($e), - $msg - ) - }) - }; +pub struct LocalSystemInfo; + +impl SystemInfoInterface for LocalSystemInfo { + fn boot_time_secs(&self) -> ProcResult { + crate::boot_time_secs() + } + + fn ticks_per_second(&self) -> u64 { + crate::ticks_per_second() + } + + fn page_size(&self) -> u64 { + crate::page_size() + } } -macro_rules! expect { - ($e:expr) => { - match crate::IntoResult::into($e) { - Ok(v) => v, - Err(e) => return Err(build_internal_error!(e)), - } - }; - ($e:expr, $msg:expr) => { - match crate::IntoResult::into($e) { - Ok(v) => v, - Err(e) => return Err(build_internal_error!(e, $msg)), - } - }; +const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo; + +/// The current [SystemInfo]. +pub fn current_system_info() -> &'static SystemInfo { + &LOCAL_SYSTEM_INFO +} + +/// Allows associating a specific file to parse with system information. +pub trait CurrentSI: FromReadSI { + const PATH: &'static str; + + /// Parse the current value using the system file and the current system info. + fn current() -> ProcResult { + Self::current_with_system_info(current_system_info()) + } + + /// Parse the current value using the system file and the provided system info. + fn current_with_system_info(si: &SystemInfo) -> ProcResult { + Self::from_file(Self::PATH, si) + } +} + +/// Allows `impl WithSystemInfo` to use the current system info. +pub trait WithCurrentSystemInfo<'a>: WithSystemInfo<'a> + Sized { + /// Get the value using the current system info. + fn get(self) -> Self::Output { + self.with_system_info(current_system_info()) + } } -macro_rules! from_str { - ($t:tt, $e:expr) => {{ - let e = $e; - expect!( - $t::from_str_radix(e, 10), - format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t),) - ) - }}; - ($t:tt, $e:expr, $radix:expr) => {{ - let e = $e; - expect!( - $t::from_str_radix(e, $radix), - format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t)) - ) - }}; - ($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{ - let e = $e; - expect!( - $t::from_str_radix(e, $radix), - format!( - "Failed to parse {} ({:?}) as a {} (pid {})", - stringify!($e), - e, - stringify!($t), - $pid - ) - ) - }}; +impl<'a, T: WithSystemInfo<'a>> WithCurrentSystemInfo<'a> for T {} + +/// Extension traits useful for importing wholesale. +pub mod prelude { + pub use super::{Current, CurrentSI, WithCurrentSystemInfo}; + pub use procfs_core::prelude::*; } macro_rules! wrap_io_error { @@ -173,16 +148,15 @@ macro_rules! wrap_io_error { } pub(crate) fn read_file>(path: P) -> ProcResult { - let mut f = FileWrapper::open(path)?; - let mut buf = String::new(); - f.read_to_string(&mut buf)?; - Ok(buf) + std::fs::read_to_string(path.as_ref()) + .map_err(|e| e.into()) + .error_path(path.as_ref()) } pub(crate) fn write_file, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> { - let mut f = OpenOptions::new().read(false).write(true).open(path)?; - f.write_all(buf.as_ref())?; - Ok(()) + std::fs::write(path.as_ref(), buf) + .map_err(|e| e.into()) + .error_path(path.as_ref()) } pub(crate) fn read_value(path: P) -> ProcResult @@ -191,9 +165,7 @@ where T: FromStr, ProcError: From, { - let val = read_file(path)?; - Ok(::from_str(val.trim())?) - //Ok(val.trim().parse()?) + read_file(path).and_then(|s| s.trim().parse().map_err(ProcError::from)) } pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> ProcResult<()> { @@ -336,17 +308,6 @@ impl Seek for FileWrapper { } } -pub trait ProcfsLocal: Sized { - fn new() -> ProcResult; -} - -impl ProcfsLocal for LoadAverage { - /// Reads load average info from `/proc/loadavg` - fn new() -> ProcResult { - LoadAverage::from_reader(FileWrapper::open("/proc/loadavg")?) - } -} - /// Return the number of ticks per second. /// /// This isn't part of the proc file system, but it's a useful thing to have, since several fields @@ -361,7 +322,7 @@ pub fn ticks_per_second() -> u64 { /// /// This function requires the "chrono" features to be enabled (which it is by default). #[cfg(feature = "chrono")] -pub fn boot_time() -> ProcResult> { +pub fn boot_time() -> ProcResult> { use chrono::TimeZone; let secs = boot_time_secs()?; @@ -388,7 +349,9 @@ pub fn boot_time_secs() -> ProcResult { if let Some(btime) = *btime { Ok(btime) } else { - let stat = KernelStats::new()?; + // KernelStats doesn't call `boot_time_secs()`, so it's safe to call + // KernelStats::current() (which uses the local system info). + let stat = KernelStats::current()?; *btime = Some(stat.btime); Ok(stat.btime) } @@ -406,80 +369,70 @@ pub fn page_size() -> u64 { rustix::param::page_size() as u64 } -/// Returns a configuration options used to build the currently running kernel -/// -/// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. -/// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). -/// -/// # Notes -/// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled -/// (which it is by default). -#[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")] -#[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")] -pub fn kernel_config() -> ProcResult> { - let reader: Box = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") { - #[cfg(feature = "flate2")] - { - let file = FileWrapper::open(PROC_CONFIG_GZ)?; - let decoder = flate2::read::GzDecoder::new(file); - Box::new(decoder) - } - #[cfg(not(feature = "flate2"))] - { - unreachable!("flate2 feature not enabled") - } - } else { - let kernel = rustix::process::uname(); - - let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy()); - - match FileWrapper::open(filename) { - Ok(file) => Box::new(BufReader::new(file)), - Err(e) => match e.kind() { - io::ErrorKind::NotFound => { - let file = FileWrapper::open(BOOT_CONFIG)?; - Box::new(file) - } - _ => return Err(e.into()), - }, - } - }; - - kernel_config_from_read(reader) +impl Current for LoadAverage { + const PATH: &'static str = "/proc/loadavg"; } -impl ProcfsLocal for KernelStats { - fn new() -> ProcResult { - KernelStats::from_reader(FileWrapper::open("/proc/stat")?, ticks_per_second()) +impl Current for KernelConfig { + const PATH: &'static str = PROC_CONFIG_GZ; + + /// Returns a configuration options used to build the currently running kernel + /// + /// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. + /// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). + /// + /// # Notes + /// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled + /// (which it is by default). + #[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")] + #[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")] + fn current() -> ProcResult { + let reader: Box = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") { + #[cfg(feature = "flate2")] + { + let file = FileWrapper::open(PROC_CONFIG_GZ)?; + let decoder = flate2::read::GzDecoder::new(file); + Box::new(decoder) + } + #[cfg(not(feature = "flate2"))] + { + unreachable!("flate2 feature not enabled") + } + } else { + let kernel = rustix::process::uname(); + + let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy()); + + match FileWrapper::open(filename) { + Ok(file) => Box::new(BufReader::new(file)), + Err(e) => match e.kind() { + io::ErrorKind::NotFound => { + let file = FileWrapper::open(BOOT_CONFIG)?; + Box::new(file) + } + _ => return Err(e.into()), + }, + } + }; + + Self::from_read(reader) } } -/// Get various virtual memory statistics -/// -/// Since the exact set of statistics will vary from kernel to kernel, -/// and because most of them are not well documented, this function -/// returns a HashMap instead of a struct. Consult the kernel source -/// code for more details of this data. -/// -/// This data is taken from the `/proc/vmstat` file. -/// -/// (since Linux 2.6.0) -pub fn vmstat() -> ProcResult> { - vmstat_from_read(FileWrapper::open("/proc/vmstat")?) +impl CurrentSI for KernelStats { + const PATH: &'static str = "/proc/stat"; } -/// Get a list of loaded kernel modules -/// -/// This corresponds to the data in `/proc/modules`. -pub fn modules() -> ProcResult> { - modules_from_read(FileWrapper::open("/proc/modules")?) +impl Current for VmStat { + const PATH: &'static str = "/proc/vmstat"; } -/// Get a list of the arguments passed to the Linux kernel at boot time -/// -/// This corresponds to the data in `/proc/cmdline` -pub fn cmdline() -> ProcResult> { - cmdline_from_read(FileWrapper::open("/proc/cmdline")?) +impl Current for KernelModules { + const PATH: &'static str = "/proc/modules"; +} + +impl Current for KernelCmdline { + const PATH: &'static str = "/proc/cmdline"; } #[cfg(test)] @@ -495,7 +448,7 @@ mod tests { #[test] fn test_loadavg() { - let load = LoadAverage::new().unwrap(); + let load = LoadAverage::current().unwrap(); println!("{:?}", load); } @@ -511,7 +464,7 @@ mod tests { return; } - let config = kernel_config().unwrap(); + let config = KernelConfig::current().unwrap(); println!("{:#?}", config); } @@ -546,7 +499,7 @@ mod tests { #[test] fn test_kernel_stat() { - let stat = KernelStats::new().unwrap(); + let stat = KernelStats::current().unwrap(); println!("{:#?}", stat); // the boottime from KernelStats should match the boottime from /proc/uptime @@ -594,13 +547,13 @@ mod tests { #[test] fn test_vmstat() { - let stat = vmstat().unwrap(); + let stat = VmStat::current().unwrap(); println!("{:?}", stat); } #[test] fn test_modules() { - let mods = modules().unwrap(); + let KernelModules(mods) = KernelModules::current().unwrap(); for module in mods.values() { println!("{:?}", module); } @@ -614,7 +567,7 @@ mod tests { #[test] fn test_cmdline() { - let cmdline = cmdline().unwrap(); + let KernelCmdline(cmdline) = KernelCmdline::current().unwrap(); for argument in cmdline { println!("{}", argument); @@ -625,7 +578,7 @@ mod tests { #[test] fn test_failure() { fn inner() -> Result<(), failure::Error> { - let _load = crate::LoadAverage::new()?; + let _load = crate::LoadAverage::current()?; Ok(()) } let _ = inner(); diff --git a/procfs/src/locks.rs b/procfs/src/locks.rs index 4adbe9b8..a80919f1 100644 --- a/procfs/src/locks.rs +++ b/procfs/src/locks.rs @@ -1,4 +1,4 @@ -use crate::{FileWrapper, ProcResult}; +use crate::{expect, from_str, FileWrapper, ProcResult}; use std::io::{BufRead, BufReader}; #[cfg(feature = "serde1")] diff --git a/procfs/src/meminfo.rs b/procfs/src/meminfo.rs index 501eeecc..f675ec6a 100644 --- a/procfs/src/meminfo.rs +++ b/procfs/src/meminfo.rs @@ -1,6 +1,6 @@ use std::io; -use super::{convert_to_kibibytes, FileWrapper, ProcResult}; +use super::{convert_to_kibibytes, expect, from_str, FileWrapper, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -411,7 +411,7 @@ impl Meminfo { #[cfg(test)] mod test { use super::*; - use crate::{kernel_config, KernelVersion}; + use crate::{prelude::*, KernelConfig, KernelVersion}; #[allow(clippy::cognitive_complexity)] #[allow(clippy::blocks_in_if_conditions)] @@ -425,7 +425,7 @@ mod test { } let kernel = KernelVersion::current().unwrap(); - let config = kernel_config().ok(); + let config = KernelConfig::current().ok(); let meminfo = Meminfo::new().unwrap(); println!("{:#?}", meminfo); @@ -453,13 +453,15 @@ mod test { && kernel <= KernelVersion::new(2, 6, 30) && meminfo.unevictable.is_some() { - if let Some(ref config) = config { + if let Some(KernelConfig(ref config)) = config { assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some()); } } if kernel >= KernelVersion::new(2, 6, 19) - && config.as_ref().map_or(false, |cfg| cfg.contains_key("CONFIG_HIGHMEM")) + && config + .as_ref() + .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HIGHMEM")) { assert!(meminfo.high_total.is_some()); assert!(meminfo.high_free.is_some()); @@ -513,7 +515,7 @@ mod test { if kernel >= KernelVersion::new(2, 6, 27) && config .as_ref() - .map_or(false, |cfg| cfg.contains_key("CONFIG_QUICKLIST")) + .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_QUICKLIST")) { assert!(meminfo.quicklists.is_some()); } else { @@ -533,11 +535,10 @@ mod test { } if kernel >= KernelVersion::new(2, 6, 32) - && config - .as_ref() - .map_or(std::path::Path::new("/proc/kpagecgroup").exists(), |cfg| { - cfg.contains_key("CONFIG_MEMORY_FAILURE") - }) + && config.as_ref().map_or( + std::path::Path::new("/proc/kpagecgroup").exists(), + |KernelConfig(cfg)| cfg.contains_key("CONFIG_MEMORY_FAILURE"), + ) { assert!(meminfo.hardware_corrupted.is_some()); } else { @@ -545,9 +546,9 @@ mod test { } if kernel >= KernelVersion::new(2, 6, 38) - && config - .as_ref() - .map_or(false, |cfg| cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")) + && config.as_ref().map_or(false, |KernelConfig(cfg)| { + cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") + }) { assert!(meminfo.anon_hugepages.is_some()); } else { @@ -556,9 +557,9 @@ mod test { } if kernel >= KernelVersion::new(4, 8, 0) - && config - .as_ref() - .map_or(true, |cfg| cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")) + && config.as_ref().map_or(true, |KernelConfig(cfg)| { + cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") + }) { assert!(meminfo.shmem_hugepages.is_some()); assert!(meminfo.shmem_pmd_mapped.is_some()); @@ -567,7 +568,11 @@ mod test { assert!(meminfo.shmem_pmd_mapped.is_none()); } - if kernel >= KernelVersion::new(3, 1, 0) && config.as_ref().map_or(true, |cfg| cfg.contains_key("CONFIG_CMA")) { + if kernel >= KernelVersion::new(3, 1, 0) + && config + .as_ref() + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_CMA")) + { assert!(meminfo.cma_total.is_some()); assert!(meminfo.cma_free.is_some()); } else { @@ -577,7 +582,7 @@ mod test { if config .as_ref() - .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_total.is_some()); assert!(meminfo.hugepages_free.is_some()); @@ -591,7 +596,7 @@ mod test { if kernel >= KernelVersion::new(2, 6, 17) && config .as_ref() - .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_rsvd.is_some()); } else { @@ -601,7 +606,7 @@ mod test { if kernel >= KernelVersion::new(2, 6, 24) && config .as_ref() - .map_or(true, |cfg| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) { assert!(meminfo.hugepages_surp.is_some()); } else { diff --git a/procfs/src/net.rs b/procfs/src/net.rs index d632d29d..14d8362b 100644 --- a/procfs/src/net.rs +++ b/procfs/src/net.rs @@ -49,8 +49,8 @@ //! println!("{:<26} {:<26} {:<15} {:<12} -", local_address, remote_addr, state, entry.inode); //! } //! } -use crate::from_iter; use crate::ProcResult; +use crate::{build_internal_error, expect, from_iter, from_str}; use std::collections::HashMap; use crate::FileWrapper; diff --git a/procfs/src/process/limit.rs b/procfs/src/process/limit.rs deleted file mode 100644 index a548b25c..00000000 --- a/procfs/src/process/limit.rs +++ /dev/null @@ -1,309 +0,0 @@ -use crate::{FileWrapper, ProcError, ProcResult}; - -use std::collections::HashMap; -use std::io::{BufRead, BufReader, Read}; -use std::str::FromStr; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -impl crate::process::Process { - /// Return the limits for this process - pub fn limits(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "limits")?; - Limits::from_reader(file) - } -} - -/// Process limits -/// -/// For more details about each of these limits, see the `getrlimit` man page. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Limits { - /// Max Cpu Time - /// - /// This is a limit, in seconds, on the amount of CPU time that the process can consume. - pub max_cpu_time: Limit, - - /// Max file size - /// - /// This is the maximum size in bytes of files that the process may create. - pub max_file_size: Limit, - - /// Max data size - /// - /// This is the maximum size of the process's data segment (initialized data, uninitialized - /// data, and heap). - pub max_data_size: Limit, - - /// Max stack size - /// - /// This is the maximum size of the process stack, in bytes. - pub max_stack_size: Limit, - - /// Max core file size - /// - /// This is the maximum size of a *core* file in bytes that the process may dump. - pub max_core_file_size: Limit, - - /// Max resident set - /// - /// This is a limit (in bytes) on the process's resident set (the number of virtual pages - /// resident in RAM). - pub max_resident_set: Limit, - - /// Max processes - /// - /// This is a limit on the number of extant process (or, more precisely on Linux, threads) for - /// the real user rID of the calling process. - pub max_processes: Limit, - - /// Max open files - /// - /// This specifies a value one greater than the maximum file descriptor number that can be - /// opened by this process. - pub max_open_files: Limit, - - /// Max locked memory - /// - /// This is the maximum number of bytes of memory that may be locked into RAM. - pub max_locked_memory: Limit, - - /// Max address space - /// - /// This is the maximum size of the process's virtual memory (address space). - pub max_address_space: Limit, - - /// Max file locks - /// - /// This is a limit on the combined number of flock locks and fcntl leases that this process - /// may establish. - pub max_file_locks: Limit, - - /// Max pending signals - /// - /// This is a limit on the number of signals that may be queued for the real user rID of the - /// calling process. - pub max_pending_signals: Limit, - - /// Max msgqueue size - /// - /// This is a limit on the number of bytes that can be allocated for POSIX message queues for - /// the real user rID of the calling process. - pub max_msgqueue_size: Limit, - - /// Max nice priority - /// - /// This specifies a ceiling to which the process's nice value can be raised using - /// `setpriority` or `nice`. - pub max_nice_priority: Limit, - - /// Max realtime priority - /// - /// This specifies a ceiling on the real-time priority that may be set for this process using - /// `sched_setscheduler` and `sched_setparam`. - pub max_realtime_priority: Limit, - - /// Max realtime timeout - /// - /// This is a limit (in microseconds) on the amount of CPU time that a process scheduled under - /// a real-time scheduling policy may consume without making a blocking system call. - pub max_realtime_timeout: Limit, -} - -impl Limits { - fn from_reader(r: R) -> ProcResult { - let bufread = BufReader::new(r); - let mut lines = bufread.lines(); - - let mut map = HashMap::new(); - - while let Some(Ok(line)) = lines.next() { - let line = line.trim(); - if line.starts_with("Limit") { - continue; - } - let s: Vec<_> = line.split_whitespace().collect(); - let l = s.len(); - - let (hard_limit, soft_limit, name) = - if line.starts_with("Max nice priority") || line.starts_with("Max realtime priority") { - // these two limits don't have units, and so need different offsets: - let hard_limit = expect!(s.get(l - 1)).to_owned(); - let soft_limit = expect!(s.get(l - 2)).to_owned(); - let name = s[0..l - 2].join(" "); - (hard_limit, soft_limit, name) - } else { - let hard_limit = expect!(s.get(l - 2)).to_owned(); - let soft_limit = expect!(s.get(l - 3)).to_owned(); - let name = s[0..l - 3].join(" "); - (hard_limit, soft_limit, name) - }; - let _units = expect!(s.get(l - 1)); - - map.insert(name.to_owned(), (soft_limit.to_owned(), hard_limit.to_owned())); - } - - let limits = Limits { - max_cpu_time: Limit::from_pair(expect!(map.remove("Max cpu time")))?, - max_file_size: Limit::from_pair(expect!(map.remove("Max file size")))?, - max_data_size: Limit::from_pair(expect!(map.remove("Max data size")))?, - max_stack_size: Limit::from_pair(expect!(map.remove("Max stack size")))?, - max_core_file_size: Limit::from_pair(expect!(map.remove("Max core file size")))?, - max_resident_set: Limit::from_pair(expect!(map.remove("Max resident set")))?, - max_processes: Limit::from_pair(expect!(map.remove("Max processes")))?, - max_open_files: Limit::from_pair(expect!(map.remove("Max open files")))?, - max_locked_memory: Limit::from_pair(expect!(map.remove("Max locked memory")))?, - max_address_space: Limit::from_pair(expect!(map.remove("Max address space")))?, - max_file_locks: Limit::from_pair(expect!(map.remove("Max file locks")))?, - max_pending_signals: Limit::from_pair(expect!(map.remove("Max pending signals")))?, - max_msgqueue_size: Limit::from_pair(expect!(map.remove("Max msgqueue size")))?, - max_nice_priority: Limit::from_pair(expect!(map.remove("Max nice priority")))?, - max_realtime_priority: Limit::from_pair(expect!(map.remove("Max realtime priority")))?, - max_realtime_timeout: Limit::from_pair(expect!(map.remove("Max realtime timeout")))?, - }; - if cfg!(test) { - assert!(map.is_empty(), "Map isn't empty: {:?}", map); - } - Ok(limits) - } -} - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Limit { - pub soft_limit: LimitValue, - pub hard_limit: LimitValue, -} - -impl Limit { - fn from_pair(l: (String, String)) -> ProcResult { - let (soft, hard) = l; - Ok(Limit { - soft_limit: LimitValue::from_str(&soft)?, - hard_limit: LimitValue::from_str(&hard)?, - }) - } -} - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum LimitValue { - Unlimited, - Value(u64), -} - -impl LimitValue { - #[cfg(test)] - pub(crate) fn as_limit(&self) -> Option { - match self { - LimitValue::Unlimited => None, - LimitValue::Value(v) => Some(*v), - } - } -} - -impl FromStr for LimitValue { - type Err = ProcError; - fn from_str(s: &str) -> Result { - if s == "unlimited" { - Ok(LimitValue::Unlimited) - } else { - Ok(LimitValue::Value(from_str!(u64, s))) - } - } -} - -#[cfg(test)] -mod tests { - use crate::*; - use rustix::process::Resource; - - #[test] - fn test_limits() { - let me = process::Process::myself().unwrap(); - let limits = me.limits().unwrap(); - println!("{:#?}", limits); - - // Max cpu time - let lim = rustix::process::getrlimit(Resource::Cpu); - assert_eq!(lim.current, limits.max_cpu_time.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_cpu_time.hard_limit.as_limit()); - - // Max file size - let lim = rustix::process::getrlimit(Resource::Fsize); - assert_eq!(lim.current, limits.max_file_size.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_file_size.hard_limit.as_limit()); - - // Max data size - let lim = rustix::process::getrlimit(Resource::Data); - assert_eq!(lim.current, limits.max_data_size.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_data_size.hard_limit.as_limit()); - - // Max stack size - let lim = rustix::process::getrlimit(Resource::Stack); - assert_eq!(lim.current, limits.max_stack_size.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_stack_size.hard_limit.as_limit()); - - // Max core file size - let lim = rustix::process::getrlimit(Resource::Core); - assert_eq!(lim.current, limits.max_core_file_size.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_core_file_size.hard_limit.as_limit()); - - // Max resident set - let lim = rustix::process::getrlimit(Resource::Rss); - assert_eq!(lim.current, limits.max_resident_set.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_resident_set.hard_limit.as_limit()); - - // Max processes - let lim = rustix::process::getrlimit(Resource::Nproc); - assert_eq!(lim.current, limits.max_processes.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_processes.hard_limit.as_limit()); - - // Max open files - let lim = rustix::process::getrlimit(Resource::Nofile); - assert_eq!(lim.current, limits.max_open_files.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_open_files.hard_limit.as_limit()); - - // Max locked memory - let lim = rustix::process::getrlimit(Resource::Memlock); - assert_eq!(lim.current, limits.max_locked_memory.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_locked_memory.hard_limit.as_limit()); - - // Max address space - let lim = rustix::process::getrlimit(Resource::As); - assert_eq!(lim.current, limits.max_address_space.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_address_space.hard_limit.as_limit()); - - // Max file locks - let lim = rustix::process::getrlimit(Resource::Locks); - assert_eq!(lim.current, limits.max_file_locks.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_file_locks.hard_limit.as_limit()); - - // Max pending signals - let lim = rustix::process::getrlimit(Resource::Sigpending); - assert_eq!(lim.current, limits.max_pending_signals.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_pending_signals.hard_limit.as_limit()); - - // Max msgqueue size - let lim = rustix::process::getrlimit(Resource::Msgqueue); - assert_eq!(lim.current, limits.max_msgqueue_size.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_msgqueue_size.hard_limit.as_limit()); - - // Max nice priority - let lim = rustix::process::getrlimit(Resource::Nice); - assert_eq!(lim.current, limits.max_nice_priority.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_nice_priority.hard_limit.as_limit()); - - // Max realtime priority - let lim = rustix::process::getrlimit(Resource::Rtprio); - assert_eq!(lim.current, limits.max_realtime_priority.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_realtime_priority.hard_limit.as_limit()); - - // Max realtime timeout - let lim = rustix::process::getrlimit(Resource::Rttime); - assert_eq!(lim.current, limits.max_realtime_timeout.soft_limit.as_limit()); - assert_eq!(lim.maximum, limits.max_realtime_timeout.hard_limit.as_limit()); - } -} diff --git a/procfs/src/process/mod.rs b/procfs/src/process/mod.rs index 8d1dbf63..2e60eae2 100644 --- a/procfs/src/process/mod.rs +++ b/procfs/src/process/mod.rs @@ -57,9 +57,9 @@ //! ``` use super::*; -use crate::from_iter; use crate::net::{read_tcp_table, read_udp_table, TcpNetEntry, UdpNetEntry}; +pub use procfs_core::process::*; use rustix::fd::{AsFd, BorrowedFd, OwnedFd, RawFd}; use rustix::fs::{AtFlags, Mode, OFlags, RawMode}; #[cfg(feature = "serde1")] @@ -74,120 +74,17 @@ use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use std::str::FromStr; -mod limit; -pub use limit::*; - -mod stat; -pub use stat::*; - -mod mount; -pub use mount::*; - mod namespaces; pub use namespaces::*; -mod status; -pub use status::*; - -mod schedstat; -pub use schedstat::*; - -mod smaps_rollup; -pub use smaps_rollup::*; - mod task; pub use task::*; mod pagemap; pub use pagemap::*; -bitflags! { - /// Kernel flags for a process - /// - /// See also the [Stat::flags()] method. - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct StatFlags: u32 { - /// I am an IDLE thread - const PF_IDLE = 0x0000_0002; - /// Getting shut down - const PF_EXITING = 0x0000_0004; - /// PI exit done on shut down - const PF_EXITPIDONE = 0x0000_0008; - /// I'm a virtual CPU - const PF_VCPU = 0x0000_0010; - /// I'm a workqueue worker - const PF_WQ_WORKER = 0x0000_0020; - /// Forked but didn't exec - const PF_FORKNOEXEC = 0x0000_0040; - /// Process policy on mce errors; - const PF_MCE_PROCESS = 0x0000_0080; - /// Used super-user privileges - const PF_SUPERPRIV = 0x0000_0100; - /// Dumped core - const PF_DUMPCORE = 0x0000_0200; - /// Killed by a signal - const PF_SIGNALED = 0x0000_0400; - ///Allocating memory - const PF_MEMALLOC = 0x0000_0800; - /// set_user() noticed that RLIMIT_NPROC was exceeded - const PF_NPROC_EXCEEDED = 0x0000_1000; - /// If unset the fpu must be initialized before use - const PF_USED_MATH = 0x0000_2000; - /// Used async_schedule*(), used by module init - const PF_USED_ASYNC = 0x0000_4000; - /// This thread should not be frozen - const PF_NOFREEZE = 0x0000_8000; - /// Frozen for system suspend - const PF_FROZEN = 0x0001_0000; - /// I am kswapd - const PF_KSWAPD = 0x0002_0000; - /// All allocation requests will inherit GFP_NOFS - const PF_MEMALLOC_NOFS = 0x0004_0000; - /// All allocation requests will inherit GFP_NOIO - const PF_MEMALLOC_NOIO = 0x0008_0000; - /// Throttle me less: I clean memory - const PF_LESS_THROTTLE = 0x0010_0000; - /// I am a kernel thread - const PF_KTHREAD = 0x0020_0000; - /// Randomize virtual address space - const PF_RANDOMIZE = 0x0040_0000; - /// Allowed to write to swap - const PF_SWAPWRITE = 0x0080_0000; - /// Stalled due to lack of memory - const PF_MEMSTALL = 0x0100_0000; - /// I'm an Usermodehelper process - const PF_UMH = 0x0200_0000; - /// Userland is not allowed to meddle with cpus_allowed - const PF_NO_SETAFFINITY = 0x0400_0000; - /// Early kill for mce process policy - const PF_MCE_EARLY = 0x0800_0000; - /// All allocation request will have _GFP_MOVABLE cleared - const PF_MEMALLOC_NOCMA = 0x1000_0000; - /// Thread belongs to the rt mutex tester - const PF_MUTEX_TESTER = 0x2000_0000; - /// Freezer should not count it as freezable - const PF_FREEZER_SKIP = 0x4000_0000; - /// This thread called freeze_processes() and should not be frozen - const PF_SUSPEND_TASK = 0x8000_0000; - - } -} -bitflags! { - - /// See the [coredump_filter()](struct.Process.html#method.coredump_filter) method. - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct CoredumpFlags: u32 { - const ANONYMOUS_PRIVATE_MAPPINGS = 0x01; - const ANONYMOUS_SHARED_MAPPINGS = 0x02; - const FILEBACKED_PRIVATE_MAPPINGS = 0x04; - const FILEBACKED_SHARED_MAPPINGS = 0x08; - const ELF_HEADERS = 0x10; - const PROVATE_HUGEPAGES = 0x20; - const SHARED_HUGEPAGES = 0x40; - const PRIVATE_DAX_PAGES = 0x80; - const SHARED_DAX_PAGES = 0x100; - } -} +#[cfg(test)] +mod tests; bitflags! { /// The mode (read/write permissions) for an open file descriptor @@ -204,680 +101,6 @@ bitflags! { } } -bitflags! { - /// The permissions a process has on memory map entries. - /// - /// Note that the `SHARED` and `PRIVATE` are mutually exclusive, so while you can - /// use `MMPermissions::all()` to construct an instance that has all bits set, - /// this particular value would never been seen in procfs. - #[derive(Default)] - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct MMPermissions: u8 { - /// No permissions - const NONE = 0; - /// Read permission - const READ = 1 << 0; - /// Write permission - const WRITE = 1 << 1; - /// Execute permission - const EXECUTE = 1 << 2; - /// Memory is shared with another process. - /// - /// Mutually exclusive with PRIVATE. - const SHARED = 1 << 3; - /// Memory is private (and copy-on-write) - /// - /// Mutually exclusive with SHARED. - const PRIVATE = 1 << 4; - } -} - -impl MMPermissions { - fn from_ascii_char(b: u8) -> Self { - match b { - b'r' => Self::READ, - b'w' => Self::WRITE, - b'x' => Self::EXECUTE, - b's' => Self::SHARED, - b'p' => Self::PRIVATE, - _ => Self::NONE, - } - } - /// Returns this permission map as a 4-character string, similar to what you - /// might see in `/proc/\/maps`. - /// - /// Note that the SHARED and PRIVATE bits are mutually exclusive, so this - /// string is 4 characters long, not 5. - pub fn as_str(&self) -> String { - let mut s = String::with_capacity(4); - s.push(if self.contains(Self::READ) { 'r' } else { '-' }); - s.push(if self.contains(Self::WRITE) { 'w' } else { '-' }); - s.push(if self.contains(Self::EXECUTE) { 'x' } else { '-' }); - s.push(if self.contains(Self::SHARED) { - 's' - } else if self.contains(Self::PRIVATE) { - 'p' - } else { - '-' - }); - - s - } -} - -impl FromStr for MMPermissions { - type Err = std::convert::Infallible; - - fn from_str(s: &str) -> Result { - // Only operate on ASCII (byte) values - Ok(s.bytes() - .map(Self::from_ascii_char) - .fold(Self::default(), std::ops::BitOr::bitor)) - } -} - -bitflags! { - /// Represents the kernel flags associated with the virtual memory area. - /// The names of these flags are just those you'll find in the man page, but in upper case. - #[derive(Default)] - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct VmFlags: u32 { - /// No flags - const NONE = 0; - /// Readable - const RD = 1 << 0; - /// Writable - const WR = 1 << 1; - /// Executable - const EX = 1 << 2; - /// Shared - const SH = 1 << 3; - /// May read - const MR = 1 << 4; - /// May write - const MW = 1 << 5; - /// May execute - const ME = 1 << 6; - /// May share - const MS = 1 << 7; - /// Stack segment grows down - const GD = 1 << 8; - /// Pure PFN range - const PF = 1 << 9; - /// Disable write to the mapped file - const DW = 1 << 10; - /// Pages are locked in memory - const LO = 1 << 11; - /// Memory mapped I/O area - const IO = 1 << 12; - /// Sequential read advise provided - const SR = 1 << 13; - /// Random read provided - const RR = 1 << 14; - /// Do not copy area on fork - const DC = 1 << 15; - /// Do not expand area on remapping - const DE = 1 << 16; - /// Area is accountable - const AC = 1 << 17; - /// Swap space is not reserved for the area - const NR = 1 << 18; - /// Area uses huge TLB pages - const HT = 1 << 19; - /// Perform synchronous page faults (since Linux 4.15) - const SF = 1 << 20; - /// Non-linear mapping (removed in Linux 4.0) - const NL = 1 << 21; - /// Architecture specific flag - const AR = 1 << 22; - /// Wipe on fork (since Linux 4.14) - const WF = 1 << 23; - /// Do not include area into core dump - const DD = 1 << 24; - /// Soft-dirty flag (since Linux 3.13) - const SD = 1 << 25; - /// Mixed map area - const MM = 1 << 26; - /// Huge page advise flag - const HG = 1 << 27; - /// No-huge page advise flag - const NH = 1 << 28; - /// Mergeable advise flag - const MG = 1 << 29; - /// Userfaultfd missing pages tracking (since Linux 4.3) - const UM = 1 << 30; - /// Userfaultfd wprotect pages tracking (since Linux 4.3) - const UW = 1 << 31; - } -} - -impl VmFlags { - fn from_str(flag: &str) -> Self { - if flag.len() != 2 { - return VmFlags::NONE; - } - - match flag { - "rd" => VmFlags::RD, - "wr" => VmFlags::WR, - "ex" => VmFlags::EX, - "sh" => VmFlags::SH, - "mr" => VmFlags::MR, - "mw" => VmFlags::MW, - "me" => VmFlags::ME, - "ms" => VmFlags::MS, - "gd" => VmFlags::GD, - "pf" => VmFlags::PF, - "dw" => VmFlags::DW, - "lo" => VmFlags::LO, - "io" => VmFlags::IO, - "sr" => VmFlags::SR, - "rr" => VmFlags::RR, - "dc" => VmFlags::DC, - "de" => VmFlags::DE, - "ac" => VmFlags::AC, - "nr" => VmFlags::NR, - "ht" => VmFlags::HT, - "sf" => VmFlags::SF, - "nl" => VmFlags::NL, - "ar" => VmFlags::AR, - "wf" => VmFlags::WF, - "dd" => VmFlags::DD, - "sd" => VmFlags::SD, - "mm" => VmFlags::MM, - "hg" => VmFlags::HG, - "nh" => VmFlags::NH, - "mg" => VmFlags::MG, - "um" => VmFlags::UM, - "uw" => VmFlags::UW, - _ => VmFlags::NONE, - } - } -} - -//impl<'a, 'b, T> ProcFrom<&'b mut T> for u32 where T: Iterator + Sized, 'a: 'b { -// fn from(i: &'b mut T) -> u32 { -// let s = i.next().unwrap(); -// u32::from_str_radix(s, 10).unwrap() -// } -//} - -//impl<'a> ProcFrom<&'a str> for u32 { -// fn from(s: &str) -> Self { -// u32::from_str_radix(s, 10).unwrap() -// } -//} - -//fn from_iter<'a, I: Iterator>(i: &mut I) -> u32 { -// u32::from_str_radix(i.next().unwrap(), 10).unwrap() -//} - -/// Represents the state of a process. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum ProcState { - /// Running (R) - Running, - /// Sleeping in an interruptible wait (S) - Sleeping, - /// Waiting in uninterruptible disk sleep (D) - Waiting, - /// Zombie (Z) - Zombie, - /// Stopped (on a signal) (T) - /// - /// Or before Linux 2.6.33, trace stopped - Stopped, - /// Tracing stop (t) (Linux 2.6.33 onward) - Tracing, - /// Dead (X) - Dead, - /// Wakekill (K) (Linux 2.6.33 to 3.13 only) - Wakekill, - /// Waking (W) (Linux 2.6.33 to 3.13 only) - Waking, - /// Parked (P) (Linux 3.9 to 3.13 only) - Parked, - /// Idle (I) - Idle, -} - -impl ProcState { - pub fn from_char(c: char) -> Option { - match c { - 'R' => Some(ProcState::Running), - 'S' => Some(ProcState::Sleeping), - 'D' => Some(ProcState::Waiting), - 'Z' => Some(ProcState::Zombie), - 'T' => Some(ProcState::Stopped), - 't' => Some(ProcState::Tracing), - 'X' | 'x' => Some(ProcState::Dead), - 'K' => Some(ProcState::Wakekill), - 'W' => Some(ProcState::Waking), - 'P' => Some(ProcState::Parked), - 'I' => Some(ProcState::Idle), - _ => None, - } - } -} - -impl FromStr for ProcState { - type Err = ProcError; - fn from_str(s: &str) -> Result { - ProcState::from_char(expect!(s.chars().next(), "empty string")) - .ok_or_else(|| build_internal_error!("failed to convert")) - } -} - -//impl<'a, 'b, T> ProcFrom<&'b mut T> for ProcState where T: Iterator, 'a: 'b { -// fn from(s: &'b mut T) -> ProcState { -// ProcState::from_str(s.next().unwrap()).unwrap() -// } -//} - -/// This struct contains I/O statistics for the process, built from `/proc//io` -/// -/// To construct this structure, see [Process::io()]. -/// -/// # Note -/// -/// In the current implementation, things are a bit racy on 32-bit systems: if process A -/// reads process B's `/proc//io` while process B is updating one of these 64-bit -/// counters, process A could see an intermediate result. -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Io { - /// Characters read - /// - /// The number of bytes which this task has caused to be read from storage. This is simply the - /// sum of bytes which this process passed to read(2) and similar system calls. It includes - /// things such as terminal I/O and is unaffected by whether or not actual physical disk I/O - /// was required (the read might have been satisfied from pagecache). - pub rchar: u64, - - /// characters written - /// - /// The number of bytes which this task has caused, or shall cause to be written to disk. - /// Similar caveats apply here as with rchar. - pub wchar: u64, - /// read syscalls - /// - /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) - /// and pwrite(2). - pub syscr: u64, - /// write syscalls - /// - /// Attempt to count the number of write I/O operations—that is, system calls such as write(2) - /// and pwrite(2). - pub syscw: u64, - /// bytes read - /// - /// Attempt to count the number of bytes which this process really did cause to be fetched from - /// the storage layer. This is accurate for block-backed filesystems. - pub read_bytes: u64, - /// bytes written - /// - /// Attempt to count the number of bytes which this process caused to be sent to the storage layer. - pub write_bytes: u64, - /// Cancelled write bytes. - /// - /// The big inaccuracy here is truncate. If a process writes 1MB to a file and then deletes - /// the file, it will in fact perform no write‐ out. But it will have been accounted as having - /// caused 1MB of write. In other words: this field represents the number of bytes which this - /// process caused to not happen, by truncating pagecache. A task can cause "negative" I/O too. - /// If this task truncates some dirty pagecache, some I/O which another task has been accounted - /// for (in its write_bytes) will not be happening. - pub cancelled_write_bytes: u64, -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum MMapPath { - /// The file that is backing the mapping. - Path(PathBuf), - /// The process's heap. - Heap, - /// The initial process's (also known as the main thread's) stack. - Stack, - /// A thread's stack (where the `` is a thread ID). It corresponds to the - /// `/proc//task//` path. - /// - /// (since Linux 3.4) - TStack(u32), - /// The virtual dynamically linked shared object. - Vdso, - /// Shared kernel variables - Vvar, - /// obsolete virtual syscalls, succeeded by vdso - Vsyscall, - /// rollup memory mappings, from `/proc//smaps_rollup` - Rollup, - /// An anonymous mapping as obtained via mmap(2). - Anonymous, - /// Shared memory segment - Vsys(i32), - /// Some other pseudo-path - Other(String), -} - -impl MMapPath { - fn from(path: &str) -> ProcResult { - Ok(match path.trim() { - "" => MMapPath::Anonymous, - "[heap]" => MMapPath::Heap, - "[stack]" => MMapPath::Stack, - "[vdso]" => MMapPath::Vdso, - "[vvar]" => MMapPath::Vvar, - "[vsyscall]" => MMapPath::Vsyscall, - "[rollup]" => MMapPath::Rollup, - x if x.starts_with("[stack:") => { - let mut s = x[1..x.len() - 1].split(':'); - let tid = from_str!(u32, expect!(s.nth(1))); - MMapPath::TStack(tid) - } - x if x.starts_with('[') && x.ends_with(']') => MMapPath::Other(x[1..x.len() - 1].to_string()), - x if x.starts_with("/SYSV") => MMapPath::Vsys(u32::from_str_radix(&x[5..13], 16)? as i32), // 32bits signed hex. /SYSVaabbccdd (deleted) - x => MMapPath::Path(PathBuf::from(x)), - }) - } -} - -/// Represents all entries in a `/proc//maps` or `/proc//smaps` file. -#[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub struct MemoryMaps { - pub memory_maps: Vec, -} - -impl MemoryMaps { - /// Read a [MemoryMaps] from the given byte source. - /// - /// The data should be formatted according to procfs /proc/pid/{maps,smaps,smaps_rollup}. - pub fn from_reader(r: R) -> ProcResult { - Self::read(r, None) - } - - /// Read a [MemoryMaps] from the given path. - /// - /// The file data should be formatted according to procfs - /// /proc/pid/{maps,smaps,smaps_rollup}. - pub fn from_path>(path: P) -> ProcResult { - let file = FileWrapper::open(path.as_ref())?; - Self::read(file, Some(path.as_ref())) - } - - /// Return an iterator over [MemoryMap]. - pub fn iter(&self) -> std::slice::Iter { - self.memory_maps.iter() - } - - fn read(r: R, path: Option<&Path>) -> ProcResult { - let reader = BufReader::new(r); - - let mut memory_maps = Vec::new(); - - let mut line_iter = reader - .lines() - .map(|r| r.map_err(|_| ProcError::Incomplete(path.map(ToOwned::to_owned)))); - let mut current_memory_map: Option = None; - while let Some(line) = line_iter.next().transpose()? { - // Assumes all extension fields (in `/proc//smaps`) start with a capital letter, - // which seems to be the case. - if line.starts_with(|c: char| c.is_ascii_uppercase()) { - match current_memory_map.as_mut() { - None => return Err(ProcError::Incomplete(path.map(ToOwned::to_owned))), - Some(mm) => { - // This is probably an attribute - if line.starts_with("VmFlags") { - let flags = line.split_ascii_whitespace(); - let flags = flags.skip(1); // Skips the `VmFlags:` part since we don't need it. - - let flags = flags - .map(VmFlags::from_str) - // FUTURE: use `Iterator::reduce` - .fold(VmFlags::NONE, std::ops::BitOr::bitor); - - mm.extension.vm_flags = flags; - } else { - let mut parts = line.split_ascii_whitespace(); - - let key = parts.next(); - let value = parts.next(); - - if let (Some(k), Some(v)) = (key, value) { - // While most entries do have one, not all of them do. - let size_suffix = parts.next(); - - // Limited poking at /proc//smaps and then checking if "MB", "GB", and "TB" appear in the C file that is - // supposedly responsible for creating smaps, has lead me to believe that the only size suffixes we'll ever encounter - // "kB", which is most likely kibibytes. Actually checking if the size suffix is any of the above is a way to - // future-proof the code, but I am not sure it is worth doing so. - let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 }; - - let v = v.parse::().map_err(|_| { - ProcError::Other("Value in `Key: Value` pair was not actually a number".into()) - })?; - - // This ignores the case when our Key: Value pairs are really Key Value pairs. Is this a good idea? - let k = k.trim_end_matches(':'); - - mm.extension.map.insert(k.into(), v * size_multiplier); - } - } - } - } - } else { - if let Some(mm) = current_memory_map.take() { - memory_maps.push(mm); - } - current_memory_map = Some(MemoryMap::from_line(&line)?); - } - } - if let Some(mm) = current_memory_map.take() { - memory_maps.push(mm); - } - - Ok(MemoryMaps { memory_maps }) - } -} - -impl<'a> IntoIterator for &'a MemoryMaps { - type IntoIter = std::slice::Iter<'a, MemoryMap>; - type Item = &'a MemoryMap; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl IntoIterator for MemoryMaps { - type IntoIter = std::vec::IntoIter; - type Item = MemoryMap; - - fn into_iter(self) -> Self::IntoIter { - self.memory_maps.into_iter() - } -} - -/// Represents an entry in a `/proc//maps` or `/proc//smaps` file. -/// -/// To construct this structure for the current process, see [Process::maps()] and -/// [Process::smaps()]. -#[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MemoryMap { - /// The address space in the process that the mapping occupies. - pub address: (u64, u64), - pub perms: MMPermissions, - /// The offset into the file/whatever - pub offset: u64, - /// The device (major, minor) - pub dev: (i32, i32), - /// The inode on that device - /// - /// 0 indicates that no inode is associated with the memory region, as would be the case with - /// BSS (uninitialized data). - pub inode: u64, - pub pathname: MMapPath, - /// Memory mapping extension information, populated when parsing `/proc//smaps`. - /// - /// The members will be `Default::default()` (empty/none) when the information isn't available. - pub extension: MMapExtension, -} - -impl MemoryMap { - fn from_line(line: &str) -> ProcResult { - let mut s = line.splitn(6, ' '); - let address = expect!(s.next()); - let perms = expect!(s.next()); - let offset = expect!(s.next()); - let dev = expect!(s.next()); - let inode = expect!(s.next()); - let path = expect!(s.next()); - - Ok(MemoryMap { - address: split_into_num(address, '-', 16)?, - perms: perms.parse()?, - offset: from_str!(u64, offset, 16), - dev: split_into_num(dev, ':', 16)?, - inode: from_str!(u64, inode), - pathname: MMapPath::from(path)?, - extension: Default::default(), - }) - } -} - -/// Represents the information about a specific mapping as presented in /proc/\/smaps -/// -/// To construct this structure, see [Process::smaps()] -#[derive(Default, Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MMapExtension { - /// Key-value pairs that may represent statistics about memory usage, or other interesting things, - /// such a "ProtectionKey" (if you're on X86 and that kernel config option was specified). - /// - /// Note that should a key-value pair represent a memory usage statistic, it will be in bytes. - /// - /// Check your manpage for more information - pub map: HashMap, - /// Kernel flags associated with the virtual memory area - /// - /// (since Linux 3.8) - pub vm_flags: VmFlags, -} - -impl MMapExtension { - /// Return whether the extension information is empty. - pub fn is_empty(&self) -> bool { - self.map.is_empty() && self.vm_flags == VmFlags::NONE - } -} - -impl Io { - pub fn from_reader(r: R) -> ProcResult { - let mut map = HashMap::new(); - let reader = BufReader::new(r); - - for line in reader.lines() { - let line = line?; - if line.is_empty() || !line.contains(' ') { - continue; - } - let mut s = line.split_whitespace(); - let field = expect!(s.next()); - let value = expect!(s.next()); - - let value = from_str!(u64, value); - - map.insert(field[..field.len() - 1].to_string(), value); - } - let io = Io { - rchar: expect!(map.remove("rchar")), - wchar: expect!(map.remove("wchar")), - syscr: expect!(map.remove("syscr")), - syscw: expect!(map.remove("syscw")), - read_bytes: expect!(map.remove("read_bytes")), - write_bytes: expect!(map.remove("write_bytes")), - cancelled_write_bytes: expect!(map.remove("cancelled_write_bytes")), - }; - - assert!(!cfg!(test) || map.is_empty(), "io map is not empty: {:#?}", map); - - Ok(io) - } -} - -/// Describes a file descriptor opened by a process. -/// -/// See also the [Process::fd()] method. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum FDTarget { - /// A file or device - Path(PathBuf), - /// A socket type, with an inode - Socket(u64), - Net(u64), - Pipe(u64), - /// A file descriptor that have no corresponding inode. - AnonInode(String), - /// A memfd file descriptor with a name. - MemFD(String), - /// Some other file descriptor type, with an inode. - Other(String, u64), -} - -impl FromStr for FDTarget { - type Err = ProcError; - fn from_str(s: &str) -> Result { - // helper function that removes the first and last character - fn strip_first_last(s: &str) -> ProcResult<&str> { - if s.len() > 2 { - let mut c = s.chars(); - // remove the first and last characters - let _ = c.next(); - let _ = c.next_back(); - Ok(c.as_str()) - } else { - Err(ProcError::Incomplete(None)) - } - } - - if !s.starts_with('/') && s.contains(':') { - let mut s = s.split(':'); - let fd_type = expect!(s.next()); - match fd_type { - "socket" => { - let inode = expect!(s.next(), "socket inode"); - let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); - Ok(FDTarget::Socket(inode)) - } - "net" => { - let inode = expect!(s.next(), "net inode"); - let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); - Ok(FDTarget::Net(inode)) - } - "pipe" => { - let inode = expect!(s.next(), "pipe inode"); - let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); - Ok(FDTarget::Pipe(inode)) - } - "anon_inode" => Ok(FDTarget::AnonInode(expect!(s.next(), "anon inode").to_string())), - "" => Err(ProcError::Incomplete(None)), - x => { - let inode = expect!(s.next(), "other inode"); - let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10)); - Ok(FDTarget::Other(x.to_string(), inode)) - } - } - } else if let Some(s) = s.strip_prefix("/memfd:") { - Ok(FDTarget::MemFD(s.to_string())) - } else { - Ok(FDTarget::Path(PathBuf::from(s))) - } - } -} - /// See the [Process::fd()] method #[derive(Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] @@ -1037,8 +260,6 @@ impl Process { impl Process { /// Returns the complete command line for the process, unless the process is a zombie. - /// - /// pub fn cmdline(&self) -> ProcResult> { let mut buf = String::new(); let mut f = FileWrapper::open_at(&self.root, &self.fd, "cmdline")?; @@ -1168,14 +389,13 @@ impl Process { /// /// (since kernel 2.6.20) pub fn io(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "io")?; - Io::from_reader(file) + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "io")?) } /// Return a list of the currently mapped memory regions and their access permissions, based on /// the `/proc/pid/maps` file. pub fn maps(&self) -> ProcResult { - MemoryMaps::from_reader(FileWrapper::open_at(&self.root, &self.fd, "maps")?) + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "maps")?) } /// Returns a list of currently mapped memory regions and verbose information about them, @@ -1183,21 +403,28 @@ impl Process { /// /// (since Linux 2.6.14 and requires CONFIG_PROG_PAGE_MONITOR) pub fn smaps(&self) -> ProcResult { - MemoryMaps::from_reader(FileWrapper::open_at(&self.root, &self.fd, "smaps")?) + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps")?) } /// This is the sum of all the smaps data but it is much more performant to get it this way. /// /// Since 4.14 and requires CONFIG_PROC_PAGE_MONITOR. pub fn smaps_rollup(&self) -> ProcResult { - SmapsRollup::from_reader(FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?) + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?) + } + + /// Returns the [MountStat] data for this process's mount namespace. + pub fn mountstats(&self) -> ProcResult { + self.read("mountstats") } - /// Returns a struct that can be used to access information in the `/proc/pid/pagemap` file. - pub fn pagemap(&self) -> ProcResult { - let path = self.root.join("pagemap"); - let file = FileWrapper::open(&path)?; - Ok(PageMap::from_file_wrapper(file)) + /// Returns info about the mountpoints in this this process's mount namespace. + /// + /// This data is taken from the `/proc/[pid]/mountinfo` file + /// + /// (Since Linux 2.6.26) + pub fn mountinfo(&self) -> ProcResult { + self.read("mountinfo") } /// Gets the number of open file descriptors for a process @@ -1321,15 +548,17 @@ impl Process { /// Return the `Status` for this process, based on the `/proc/[pid]/status` file. pub fn status(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "status")?; - Status::from_reader(file) + self.read("status") } /// Returns the status info from `/proc/[pid]/stat`. pub fn stat(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "stat")?; - let stat = Stat::from_reader(file)?; - Ok(stat) + self.read("stat") + } + + /// Return the limits for this process + pub fn limits(&self) -> ProcResult { + self.read("limits") } /// Gets the process' login uid. May not be available. @@ -1358,8 +587,7 @@ impl Process { /// /// Much of this data is the same as the data from `stat()` and `status()` pub fn statm(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "statm")?; - StatM::from_reader(file) + self.read("statm") } /// Return a task for the main thread of this process @@ -1377,8 +605,7 @@ impl Process { /// /// (Requires CONFIG_SCHED_INFO) pub fn schedstat(&self) -> ProcResult { - let file = FileWrapper::open_at(&self.root, &self.fd, "schedstat")?; - Schedstat::from_reader(file) + self.read("schedstat") } /// Iterate over all the [`Task`]s (aka Threads) in this process @@ -1600,6 +827,11 @@ impl Process { let file = FileWrapper::open_at(&self.root, &self.fd, path)?; Ok(file.inner()) } + + /// Parse a file relative to the process proc structure. + pub fn read(&self, path: &str) -> ProcResult { + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) + } } /// The result of [`Process::fd`], iterates over all fds in a process @@ -1724,76 +956,3 @@ impl std::iter::Iterator for ProcessesIter { } } } - -/// Provides information about memory usage, measured in pages. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct StatM { - /// Total program size, measured in pages - /// - /// (same as VmSize in /proc/\/status) - pub size: u64, - /// Resident set size, measured in pages - /// - /// (same as VmRSS in /proc/\/status) - pub resident: u64, - /// number of resident shared pages (i.e., backed by a file) - /// - /// (same as RssFile+RssShmem in /proc/\/status) - pub shared: u64, - /// Text (code) - pub text: u64, - /// library (unused since Linux 2.6; always 0) - pub lib: u64, - /// data + stack - pub data: u64, - /// dirty pages (unused since Linux 2.6; always 0) - pub dt: u64, -} - -impl StatM { - fn from_reader(mut r: R) -> ProcResult { - let mut line = String::new(); - r.read_to_string(&mut line)?; - let mut s = line.split_whitespace(); - - let size = expect!(from_iter(&mut s)); - let resident = expect!(from_iter(&mut s)); - let shared = expect!(from_iter(&mut s)); - let text = expect!(from_iter(&mut s)); - let lib = expect!(from_iter(&mut s)); - let data = expect!(from_iter(&mut s)); - let dt = expect!(from_iter(&mut s)); - - if cfg!(test) { - assert!(s.next().is_none()); - } - - Ok(StatM { - size, - resident, - shared, - text, - lib, - data, - dt, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_memory_map_permissions() { - use MMPermissions as P; - assert_eq!("rw-p".parse(), Ok(P::READ | P::WRITE | P::PRIVATE)); - assert_eq!("r-xs".parse(), Ok(P::READ | P::EXECUTE | P::SHARED)); - assert_eq!("----".parse(), Ok(P::NONE)); - - assert_eq!((P::READ | P::WRITE | P::PRIVATE).as_str(), "rw-p"); - assert_eq!((P::READ | P::EXECUTE | P::SHARED).as_str(), "r-xs"); - assert_eq!(P::NONE.as_str(), "----"); - } -} diff --git a/procfs/src/process/mount.rs b/procfs/src/process/mount.rs deleted file mode 100644 index 4176e5aa..00000000 --- a/procfs/src/process/mount.rs +++ /dev/null @@ -1,656 +0,0 @@ -use bitflags::bitflags; - -use crate::{from_iter, FileWrapper, ProcResult}; - -use std::collections::HashMap; -use std::io::{BufRead, BufReader, Lines, Read}; -use std::path::PathBuf; -use std::time::Duration; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -bitflags! { - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct NFSServerCaps: u32 { - - const NFS_CAP_READDIRPLUS = 1; - const NFS_CAP_HARDLINKS = (1 << 1); - const NFS_CAP_SYMLINKS = (1 << 2); - const NFS_CAP_ACLS = (1 << 3); - const NFS_CAP_ATOMIC_OPEN = (1 << 4); - const NFS_CAP_LGOPEN = (1 << 5); - const NFS_CAP_FILEID = (1 << 6); - const NFS_CAP_MODE = (1 << 7); - const NFS_CAP_NLINK = (1 << 8); - const NFS_CAP_OWNER = (1 << 9); - const NFS_CAP_OWNER_GROUP = (1 << 10); - const NFS_CAP_ATIME = (1 << 11); - const NFS_CAP_CTIME = (1 << 12); - const NFS_CAP_MTIME = (1 << 13); - const NFS_CAP_POSIX_LOCK = (1 << 14); - const NFS_CAP_UIDGID_NOMAP = (1 << 15); - const NFS_CAP_STATEID_NFSV41 = (1 << 16); - const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17); - const NFS_CAP_SECURITY_LABEL = (1 << 18); - const NFS_CAP_SEEK = (1 << 19); - const NFS_CAP_ALLOCATE = (1 << 20); - const NFS_CAP_DEALLOCATE = (1 << 21); - const NFS_CAP_LAYOUTSTATS = (1 << 22); - const NFS_CAP_CLONE = (1 << 23); - const NFS_CAP_COPY = (1 << 24); - const NFS_CAP_OFFLOAD_CANCEL = (1 << 25); - } -} - -impl super::Process { - /// Returns the [MountStat] data for this processes mount namespace. - pub fn mountstats(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "mountstats")?; - MountStat::from_reader(file) - } - - /// Returns info about the mountpoints in this this process's mount namespace - /// - /// This data is taken from the `/proc/[pid]/mountinfo` file - /// - /// (Since Linux 2.6.26) - pub fn mountinfo(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "mountinfo")?; - let bufread = BufReader::new(file); - let lines = bufread.lines(); - let mut vec = Vec::new(); - for line in lines { - vec.push(MountInfo::from_line(&line?)?); - } - - Ok(vec) - } -} - -/// Information about a specific mount in a process's mount namespace. -/// -/// This data is taken from the `/proc/[pid]/mountinfo` file. -/// -/// For an example, see the [mountinfo.rs](/~https://github.com/eminence/procfs/tree/master/examples) -/// example in the source repo. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MountInfo { - /// Mount ID. A unique ID for the mount (but may be reused after `unmount`) - pub mnt_id: i32, - /// Parent mount ID. The ID of the parent mount (or of self for the root of the mount - /// namespace's mount tree). - /// - /// If the parent mount point lies outside the process's root directory, the ID shown here - /// won't have a corresponding record in mountinfo whose mount ID matches this parent mount - /// ID (because mount points that lie outside the process's root directory are not shown in - /// mountinfo). As a special case of this point, the process's root mount point may have a - /// parent mount (for the initramfs filesystem) that lies outside the process's root - /// directory, and an entry for that mount point will not appear in mountinfo. - pub pid: i32, - /// The value of `st_dev` for files on this filesystem - pub majmin: String, - /// The pathname of the directory in the filesystem which forms the root of this mount. - pub root: String, - /// The pathname of the mount point relative to the process's root directory. - pub mount_point: PathBuf, - /// Per-mount options - pub mount_options: HashMap>, - /// Optional fields - pub opt_fields: Vec, - /// Filesystem type - pub fs_type: String, - /// Mount source - pub mount_source: Option, - /// Per-superblock options. - pub super_options: HashMap>, -} - -impl MountInfo { - pub(crate) fn from_line(line: &str) -> ProcResult { - let mut split = line.split_whitespace(); - - let mnt_id = expect!(from_iter(&mut split)); - let pid = expect!(from_iter(&mut split)); - let majmin: String = expect!(from_iter(&mut split)); - let root = expect!(from_iter(&mut split)); - let mount_point = expect!(from_iter(&mut split)); - let mount_options = { - let mut map = HashMap::new(); - let all_opts = expect!(split.next()); - for opt in all_opts.split(',') { - let mut s = opt.splitn(2, '='); - let opt_name = expect!(s.next()); - map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); - } - map - }; - - let mut opt_fields = Vec::new(); - loop { - let f = expect!(split.next()); - if f == "-" { - break; - } - let mut s = f.split(':'); - let opt = match expect!(s.next()) { - "shared" => { - let val = expect!(from_iter(&mut s)); - MountOptFields::Shared(val) - } - "master" => { - let val = expect!(from_iter(&mut s)); - MountOptFields::Master(val) - } - "propagate_from" => { - let val = expect!(from_iter(&mut s)); - MountOptFields::PropagateFrom(val) - } - "unbindable" => MountOptFields::Unbindable, - _ => continue, - }; - opt_fields.push(opt); - } - let fs_type: String = expect!(from_iter(&mut split)); - let mount_source = match expect!(split.next()) { - "none" => None, - x => Some(x.to_owned()), - }; - let super_options = { - let mut map = HashMap::new(); - let all_opts = expect!(split.next()); - for opt in all_opts.split(',') { - let mut s = opt.splitn(2, '='); - let opt_name = expect!(s.next()); - map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned())); - } - map - }; - - Ok(MountInfo { - mnt_id, - pid, - majmin, - root, - mount_point, - mount_options, - opt_fields, - fs_type, - mount_source, - super_options, - }) - } -} - -/// Optional fields used in [MountInfo] -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum MountOptFields { - /// This mount point is shared in peer group. Each peer group has a unique ID that is - /// automatically generated by the kernel, and all mount points in the same peer group will - /// show the same ID - Shared(u32), - /// THis mount is a slave to the specified shared peer group. - Master(u32), - /// This mount is a slave and receives propagation from the shared peer group - PropagateFrom(u32), - /// This is an unbindable mount - Unbindable, -} - -/// Mount information from `/proc//mountstats`. -/// -/// # Example: -/// -/// ``` -/// # use procfs::process::Process; -/// let stats = Process::myself().unwrap().mountstats().unwrap(); -/// -/// for mount in stats { -/// println!("{} mounted on {} wth type {}", -/// mount.device.unwrap_or("??".to_owned()), -/// mount.mount_point.display(), -/// mount.fs -/// ); -/// } -/// ``` -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MountStat { - /// The name of the mounted device - pub device: Option, - /// The mountpoint within the filesystem tree - pub mount_point: PathBuf, - /// The filesystem type - pub fs: String, - /// If the mount is NFS, this will contain various NFS statistics - pub statistics: Option, -} - -impl MountStat { - pub fn from_reader(r: R) -> ProcResult> { - let mut v = Vec::new(); - let bufread = BufReader::new(r); - let mut lines = bufread.lines(); - while let Some(Ok(line)) = lines.next() { - if line.starts_with("device ") { - // line will be of the format: - // device proc mounted on /proc with fstype proc - let mut s = line.split_whitespace(); - - let device = Some(expect!(s.nth(1)).to_owned()); - let mount_point = PathBuf::from(expect!(s.nth(2))); - let fs = expect!(s.nth(2)).to_owned(); - let statistics = match s.next() { - Some(stats) if stats.starts_with("statvers=") => { - Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..])?) - } - _ => None, - }; - - v.push(MountStat { - device, - mount_point, - fs, - statistics, - }); - } - } - - Ok(v) - } -} - -/// Only NFS mounts provide additional statistics in `MountStat` entries. -// -// Thank you to Chris Siebenmann for their helpful work in documenting these structures: -// https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct MountNFSStatistics { - /// The version of the NFS statistics block. Either "1.0" or "1.1". - pub version: String, - /// The mount options. - /// - /// The meaning of these can be found in the manual pages for mount(5) and nfs(5) - pub opts: Vec, - /// Duration the NFS mount has been in existence. - pub age: Duration, - // * fsc (?) - // * impl_id (NFSv4): Option> - /// NFS Capabilities. - /// - /// See `include/linux/nfs_fs_sb.h` - /// - /// Some known values: - /// * caps: server capabilities. See [NFSServerCaps]. - /// * wtmult: server disk block size - /// * dtsize: readdir size - /// * bsize: server block size - pub caps: Vec, - // * nfsv4 (NFSv4): Option> - pub sec: Vec, - pub events: NFSEventCounter, - pub bytes: NFSByteCounter, - // * RPC iostats version: - // * xprt - // * per-op statistics - pub per_op_stats: NFSPerOpStats, -} - -impl MountNFSStatistics { - // Keep reading lines until we get to a blank line - fn from_lines(r: &mut Lines, statsver: &str) -> ProcResult { - let mut parsing_per_op = false; - - let mut opts: Option> = None; - let mut age = None; - let mut caps = None; - let mut sec = None; - let mut bytes = None; - let mut events = None; - let mut per_op = HashMap::new(); - - while let Some(Ok(line)) = r.next() { - let line = line.trim(); - if line.trim() == "" { - break; - } - if !parsing_per_op { - if let Some(stripped) = line.strip_prefix("opts:") { - opts = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); - } else if let Some(stripped) = line.strip_prefix("age:") { - age = Some(Duration::from_secs(from_str!(u64, stripped.trim()))); - } else if let Some(stripped) = line.strip_prefix("caps:") { - caps = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); - } else if let Some(stripped) = line.strip_prefix("sec:") { - sec = Some(stripped.trim().split(',').map(|s| s.to_string()).collect()); - } else if let Some(stripped) = line.strip_prefix("bytes:") { - bytes = Some(NFSByteCounter::from_str(stripped.trim())?); - } else if let Some(stripped) = line.strip_prefix("events:") { - events = Some(NFSEventCounter::from_str(stripped.trim())?); - } - if line == "per-op statistics" { - parsing_per_op = true; - } - } else { - let mut split = line.split(':'); - let name = expect!(split.next()).to_string(); - let stats = NFSOperationStat::from_str(expect!(split.next()))?; - per_op.insert(name, stats); - } - } - - Ok(MountNFSStatistics { - version: statsver.to_string(), - opts: expect!(opts, "Failed to find opts field in nfs stats"), - age: expect!(age, "Failed to find age field in nfs stats"), - caps: expect!(caps, "Failed to find caps field in nfs stats"), - sec: expect!(sec, "Failed to find sec field in nfs stats"), - events: expect!(events, "Failed to find events section in nfs stats"), - bytes: expect!(bytes, "Failed to find bytes section in nfs stats"), - per_op_stats: per_op, - }) - } - - /// Attempts to parse the caps= value from the [caps](struct.MountNFSStatistics.html#structfield.caps) field. - pub fn server_caps(&self) -> ProcResult> { - for data in &self.caps { - if let Some(stripped) = data.strip_prefix("caps=0x") { - let val = from_str!(u32, stripped, 16); - return Ok(NFSServerCaps::from_bits(val)); - } - } - Ok(None) - } -} - -/// Represents NFS data from `/proc//mountstats` under the section `events`. -/// -/// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. -/// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum -/// nfs_stat_eventcounters`. -#[derive(Debug, Copy, Clone)] -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct NFSEventCounter { - pub inode_revalidate: u64, - pub deny_try_revalidate: u64, - pub data_invalidate: u64, - pub attr_invalidate: u64, - pub vfs_open: u64, - pub vfs_lookup: u64, - pub vfs_access: u64, - pub vfs_update_page: u64, - pub vfs_read_page: u64, - pub vfs_read_pages: u64, - pub vfs_write_page: u64, - pub vfs_write_pages: u64, - pub vfs_get_dents: u64, - pub vfs_set_attr: u64, - pub vfs_flush: u64, - pub vfs_fs_sync: u64, - pub vfs_lock: u64, - pub vfs_release: u64, - pub congestion_wait: u64, - pub set_attr_trunc: u64, - pub extend_write: u64, - pub silly_rename: u64, - pub short_read: u64, - pub short_write: u64, - pub delay: u64, - pub pnfs_read: u64, - pub pnfs_write: u64, -} - -impl NFSEventCounter { - fn from_str(s: &str) -> ProcResult { - let mut s = s.split_whitespace(); - Ok(NFSEventCounter { - inode_revalidate: from_str!(u64, expect!(s.next())), - deny_try_revalidate: from_str!(u64, expect!(s.next())), - data_invalidate: from_str!(u64, expect!(s.next())), - attr_invalidate: from_str!(u64, expect!(s.next())), - vfs_open: from_str!(u64, expect!(s.next())), - vfs_lookup: from_str!(u64, expect!(s.next())), - vfs_access: from_str!(u64, expect!(s.next())), - vfs_update_page: from_str!(u64, expect!(s.next())), - vfs_read_page: from_str!(u64, expect!(s.next())), - vfs_read_pages: from_str!(u64, expect!(s.next())), - vfs_write_page: from_str!(u64, expect!(s.next())), - vfs_write_pages: from_str!(u64, expect!(s.next())), - vfs_get_dents: from_str!(u64, expect!(s.next())), - vfs_set_attr: from_str!(u64, expect!(s.next())), - vfs_flush: from_str!(u64, expect!(s.next())), - vfs_fs_sync: from_str!(u64, expect!(s.next())), - vfs_lock: from_str!(u64, expect!(s.next())), - vfs_release: from_str!(u64, expect!(s.next())), - congestion_wait: from_str!(u64, expect!(s.next())), - set_attr_trunc: from_str!(u64, expect!(s.next())), - extend_write: from_str!(u64, expect!(s.next())), - silly_rename: from_str!(u64, expect!(s.next())), - short_read: from_str!(u64, expect!(s.next())), - short_write: from_str!(u64, expect!(s.next())), - delay: from_str!(u64, expect!(s.next())), - pnfs_read: from_str!(u64, expect!(s.next())), - pnfs_write: from_str!(u64, expect!(s.next())), - }) - } -} - -/// Represents NFS data from `/proc//mountstats` under the section `bytes`. -/// -/// The underlying data structure in the kernel can be found under *fs/nfs/iostat.h* `nfs_iostat`. -/// The fields are documented in the kernel source only under *include/linux/nfs_iostat.h* `enum -/// nfs_stat_bytecounters` -#[derive(Debug, Copy, Clone)] -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct NFSByteCounter { - pub normal_read: u64, - pub normal_write: u64, - pub direct_read: u64, - pub direct_write: u64, - pub server_read: u64, - pub server_write: u64, - pub pages_read: u64, - pub pages_write: u64, -} - -impl NFSByteCounter { - fn from_str(s: &str) -> ProcResult { - let mut s = s.split_whitespace(); - Ok(NFSByteCounter { - normal_read: from_str!(u64, expect!(s.next())), - normal_write: from_str!(u64, expect!(s.next())), - direct_read: from_str!(u64, expect!(s.next())), - direct_write: from_str!(u64, expect!(s.next())), - server_read: from_str!(u64, expect!(s.next())), - server_write: from_str!(u64, expect!(s.next())), - pages_read: from_str!(u64, expect!(s.next())), - pages_write: from_str!(u64, expect!(s.next())), - }) - } -} - -/// Represents NFS data from `/proc//mountstats` under the section of `per-op statistics`. -/// -/// Here is what the Kernel says about the attributes: -/// -/// Regarding `operations`, `transmissions` and `major_timeouts`: -/// -/// > These counters give an idea about how many request -/// > transmissions are required, on average, to complete that -/// > particular procedure. Some procedures may require more -/// > than one transmission because the server is unresponsive, -/// > the client is retransmitting too aggressively, or the -/// > requests are large and the network is congested. -/// -/// Regarding `bytes_sent` and `bytes_recv`: -/// -/// > These count how many bytes are sent and received for a -/// > given RPC procedure type. This indicates how much load a -/// > particular procedure is putting on the network. These -/// > counts include the RPC and ULP headers, and the request -/// > payload. -/// -/// Regarding `cum_queue_time`, `cum_resp_time` and `cum_total_req_time`: -/// -/// > The length of time an RPC request waits in queue before -/// > transmission, the network + server latency of the request, -/// > and the total time the request spent from init to release -/// > are measured. -/// -/// (source: *include/linux/sunrpc/metrics.h* `struct rpc_iostats`) -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct NFSOperationStat { - /// Count of rpc operations. - pub operations: u64, - /// Count of rpc transmissions - pub transmissions: u64, - /// Count of rpc major timeouts - pub major_timeouts: u64, - /// Count of bytes send. Does not only include the RPC payload but the RPC headers as well. - pub bytes_sent: u64, - /// Count of bytes received as `bytes_sent`. - pub bytes_recv: u64, - /// How long all requests have spend in the queue before being send. - pub cum_queue_time: Duration, - /// How long it took to get a response back. - pub cum_resp_time: Duration, - /// How long all requests have taken from beeing queued to the point they where completely - /// handled. - pub cum_total_req_time: Duration, -} - -impl NFSOperationStat { - fn from_str(s: &str) -> ProcResult { - let mut s = s.split_whitespace(); - - let operations = from_str!(u64, expect!(s.next())); - let transmissions = from_str!(u64, expect!(s.next())); - let major_timeouts = from_str!(u64, expect!(s.next())); - let bytes_sent = from_str!(u64, expect!(s.next())); - let bytes_recv = from_str!(u64, expect!(s.next())); - let cum_queue_time_ms = from_str!(u64, expect!(s.next())); - let cum_resp_time_ms = from_str!(u64, expect!(s.next())); - let cum_total_req_time_ms = from_str!(u64, expect!(s.next())); - - Ok(NFSOperationStat { - operations, - transmissions, - major_timeouts, - bytes_sent, - bytes_recv, - cum_queue_time: Duration::from_millis(cum_queue_time_ms), - cum_resp_time: Duration::from_millis(cum_resp_time_ms), - cum_total_req_time: Duration::from_millis(cum_total_req_time_ms), - }) - } -} - -pub type NFSPerOpStats = HashMap; - -#[cfg(test)] -mod tests { - use crate::process::*; - use std::time::Duration; - - #[test] - fn test_mountinfo() { - let s = "25 0 8:1 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro"; - - let stat = MountInfo::from_line(s).unwrap(); - println!("{:?}", stat); - } - - #[test] - fn test_mountinfo_live() { - let me = Process::myself().unwrap(); - let mounts = me.mountinfo().unwrap(); - println!("{:#?}", mounts); - } - - #[test] - fn test_proc_mountstats() { - let simple = MountStat::from_reader( - "device /dev/md127 mounted on /boot with fstype ext2 -device /dev/md124 mounted on /home with fstype ext4 -device tmpfs mounted on /run/user/0 with fstype tmpfs -" - .as_bytes(), - ) - .unwrap(); - let simple_parsed = vec![ - MountStat { - device: Some("/dev/md127".to_string()), - mount_point: PathBuf::from("/boot"), - fs: "ext2".to_string(), - statistics: None, - }, - MountStat { - device: Some("/dev/md124".to_string()), - mount_point: PathBuf::from("/home"), - fs: "ext4".to_string(), - statistics: None, - }, - MountStat { - device: Some("tmpfs".to_string()), - mount_point: PathBuf::from("/run/user/0"), - fs: "tmpfs".to_string(), - statistics: None, - }, - ]; - assert_eq!(simple, simple_parsed); - let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1 - opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none - age: 3542 - impl_id: name='',domain='',date='0,0' - caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255 - nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured - sec: flavor=6,pseudoflavor=390003 - events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1 - bytes: 1 2 3 4 5 6 7 8 - RPC iostats version: 1.0 p/v: 100003/4 (nfs) - xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0 - per-op statistics - NULL: 0 0 0 0 0 0 0 0 - READ: 1 2 3 4 5 6 7 8 - WRITE: 0 0 0 0 0 0 0 0 - COMMIT: 0 0 0 0 0 0 0 0 - OPEN: 1 1 0 320 420 0 124 124 - ".as_bytes()).unwrap(); - let nfs_v4 = &mountstats[0]; - match &nfs_v4.statistics { - Some(stats) => { - assert_eq!("1.1".to_string(), stats.version, "mountstats version wrongly parsed."); - assert_eq!(Duration::from_secs(3542), stats.age); - assert_eq!(1, stats.bytes.normal_read); - assert_eq!(114, stats.events.inode_revalidate); - assert!(stats.server_caps().unwrap().is_some()); - } - None => { - panic!("Failed to retrieve nfs statistics"); - } - } - } - #[test] - fn test_proc_mountstats_live() { - // this tries to parse a live mountstats file - // there are no assertions, but we still want to check for parsing errors (which can - // cause panics) - - let stats = MountStat::from_reader(FileWrapper::open("/proc/self/mountstats").unwrap()).unwrap(); - for stat in stats { - println!("{:#?}", stat); - if let Some(nfs) = stat.statistics { - println!(" {:?}", nfs.server_caps().unwrap()); - } - } - } -} diff --git a/procfs/src/process/namespaces.rs b/procfs/src/process/namespaces.rs index 770dc75b..39c484a1 100644 --- a/procfs/src/process/namespaces.rs +++ b/procfs/src/process/namespaces.rs @@ -1,18 +1,14 @@ -use rustix::fs::{AtFlags, Mode, OFlags}; -use std::{collections::HashMap, ffi::OsString, path::PathBuf}; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -use crate::ProcResult; - use super::Process; +use crate::{build_internal_error, ProcResult}; +use procfs_core::process::{Namespace, Namespaces}; +use rustix::fs::{AtFlags, Mode, OFlags}; +use std::{collections::HashMap, ffi::OsString}; impl Process { /// Describes namespaces to which the process with the corresponding PID belongs. /// Doc reference: /// The namespace type is the key for the HashMap, i.e 'net', 'user', etc. - pub fn namespaces(&self) -> ProcResult> { + pub fn namespaces(&self) -> ProcResult { let mut namespaces = HashMap::new(); let dir_ns = wrap_io_error!( self.root.join("ns"), @@ -52,35 +48,10 @@ impl Process { } } - Ok(namespaces) + Ok(Namespaces(namespaces)) } } -/// Information about a namespace -/// -/// See also the [Process::namespaces()] method -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Namespace { - /// Namespace type - pub ns_type: OsString, - /// Handle to the namespace - pub path: PathBuf, - /// Namespace identifier (inode number) - pub identifier: u64, - /// Device id of the namespace - pub device_id: u64, -} - -impl PartialEq for Namespace { - fn eq(&self, other: &Self) -> bool { - // see https://lore.kernel.org/lkml/87poky5ca9.fsf@xmission.com/ - self.identifier == other.identifier && self.device_id == other.device_id - } -} - -impl Eq for Namespace {} - #[cfg(test)] mod tests { use crate::process::Process; diff --git a/procfs/src/process/pagemap.rs b/procfs/src/process/pagemap.rs index b2f14b94..f74db71a 100644 --- a/procfs/src/process/pagemap.rs +++ b/procfs/src/process/pagemap.rs @@ -1,129 +1,17 @@ use crate::{FileWrapper, ProcResult}; - -use bitflags::bitflags; +use procfs_core::process::PageInfo; use std::{ - fmt, io::{BufReader, Read, Seek, SeekFrom}, mem::size_of, ops::{Bound, RangeBounds}, }; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -const fn genmask(high: usize, low: usize) -> u64 { - let mask_bits = size_of::() * 8; - (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) -} - -// source: include/linux/swap.h -const MAX_SWAPFILES_SHIFT: usize = 5; - -// source: fs/proc/task_mmu.c -bitflags! { - /// Represents the fields and flags in a page table entry for a swapped page. - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct SwapPageFlags: u64 { - /// Swap type if swapped - #[doc(hidden)] - const SWAP_TYPE = genmask(MAX_SWAPFILES_SHIFT - 1, 0); - /// Swap offset if swapped - #[doc(hidden)] - const SWAP_OFFSET = genmask(54, MAX_SWAPFILES_SHIFT); - /// PTE is soft-dirty - const SOFT_DIRTY = 1 << 55; - /// Page is exclusively mapped - const MMAP_EXCLUSIVE = 1 << 56; - /// Page is file-page or shared-anon - const FILE = 1 << 61; - /// Page is swapped - #[doc(hidden)] - const SWAP = 1 << 62; - /// Page is present - const PRESENT = 1 << 63; - } -} - -impl SwapPageFlags { - /// Returns the swap type recorded in this entry. - pub fn get_swap_type(&self) -> u64 { - (*self & Self::SWAP_TYPE).bits() - } - - /// Returns the swap offset recorded in this entry. - pub fn get_swap_offset(&self) -> u64 { - (*self & Self::SWAP_OFFSET).bits() >> MAX_SWAPFILES_SHIFT - } -} - -bitflags! { - /// Represents the fields and flags in a page table entry for a memory page. - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct MemoryPageFlags: u64 { - /// Page frame number if present - #[doc(hidden)] - const PFN = genmask(54, 0); - /// PTE is soft-dirty - const SOFT_DIRTY = 1 << 55; - /// Page is exclusively mapped - const MMAP_EXCLUSIVE = 1 << 56; - /// Page is file-page or shared-anon - const FILE = 1 << 61; - /// Page is swapped - #[doc(hidden)] - const SWAP = 1 << 62; - /// Page is present - const PRESENT = 1 << 63; - } -} - -impl MemoryPageFlags { - /// Returns the page frame number recorded in this entry. - pub fn get_page_frame_number(&self) -> Pfn { - Pfn((*self & Self::PFN).bits()) - } -} - -/// A Page Frame Number, representing a 4 kiB physical memory page -/// -/// See also [crate::iomem()] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Pfn(pub u64); - -impl fmt::UpperHex for Pfn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let val = self.0; - - fmt::UpperHex::fmt(&val, f) - } -} - -impl fmt::LowerHex for Pfn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let val = self.0; - - fmt::LowerHex::fmt(&val, f) - } -} - -/// Represents a page table entry in `/proc//pagemap`. -#[derive(Debug)] -pub enum PageInfo { - /// Entry referring to a memory page - MemoryPage(MemoryPageFlags), - /// Entry referring to a swapped page - SwapPage(SwapPageFlags), -} - -impl PageInfo { - pub(crate) fn parse_info(info: u64) -> Self { - let flags = MemoryPageFlags::from_bits_truncate(info); - - if flags.contains(MemoryPageFlags::SWAP) { - Self::SwapPage(SwapPageFlags::from_bits_truncate(info)) - } else { - Self::MemoryPage(flags) - } +impl super::Process { + /// Returns a struct that can be used to access information in the `/proc/pid/pagemap` file. + pub fn pagemap(&self) -> ProcResult { + let path = self.root.join("pagemap"); + let file = FileWrapper::open(&path)?; + Ok(PageMap::from_file_wrapper(file)) } } @@ -176,45 +64,3 @@ impl PageMap { Ok(page_infos) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_genmask() { - let mask = genmask(3, 1); - assert_eq!(mask, 0b1110); - - let mask = genmask(3, 0); - assert_eq!(mask, 0b1111); - - let mask = genmask(63, 62); - assert_eq!(mask, 0b11 << 62); - } - - #[test] - fn test_page_info() { - let pagemap_entry: u64 = 0b1000000110000000000000000000000000000000000000000000000000000011; - let info = PageInfo::parse_info(pagemap_entry); - if let PageInfo::MemoryPage(memory_flags) = info { - assert!(memory_flags - .contains(MemoryPageFlags::PRESENT | MemoryPageFlags::MMAP_EXCLUSIVE | MemoryPageFlags::SOFT_DIRTY)); - assert_eq!(memory_flags.get_page_frame_number(), Pfn(0b11)); - } else { - panic!("Wrong SWAP decoding"); - } - - let pagemap_entry: u64 = 0b1100000110000000000000000000000000000000000000000000000001100010; - let info = PageInfo::parse_info(pagemap_entry); - if let PageInfo::SwapPage(swap_flags) = info { - assert!( - swap_flags.contains(SwapPageFlags::PRESENT | SwapPageFlags::MMAP_EXCLUSIVE | SwapPageFlags::SOFT_DIRTY) - ); - assert_eq!(swap_flags.get_swap_type(), 0b10); - assert_eq!(swap_flags.get_swap_offset(), 0b11); - } else { - panic!("Wrong SWAP decoding"); - } - } -} diff --git a/procfs/src/process/schedstat.rs b/procfs/src/process/schedstat.rs deleted file mode 100644 index bc3a1cd3..00000000 --- a/procfs/src/process/schedstat.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::from_iter; -use crate::ProcResult; -use std::io::Read; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -/// Provides scheduler statistics of the process, based on the `/proc//schedstat` file. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Schedstat { - /// Time spent on the cpu. - /// - /// Measured in nanoseconds. - pub sum_exec_runtime: u64, - /// Time spent waiting on a runqueue. - /// - /// Measured in nanoseconds. - pub run_delay: u64, - /// \# of timeslices run on this cpu. - pub pcount: u64, -} - -impl Schedstat { - pub fn from_reader(mut r: R) -> ProcResult { - let mut line = String::new(); - r.read_to_string(&mut line)?; - let mut s = line.split_whitespace(); - - let schedstat = Schedstat { - sum_exec_runtime: expect!(from_iter(&mut s)), - run_delay: expect!(from_iter(&mut s)), - pcount: expect!(from_iter(&mut s)), - }; - - if cfg!(test) { - assert!(s.next().is_none()); - } - - Ok(schedstat) - } -} diff --git a/procfs/src/process/smaps_rollup.rs b/procfs/src/process/smaps_rollup.rs deleted file mode 100644 index fa494f5a..00000000 --- a/procfs/src/process/smaps_rollup.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::MemoryMaps; -use crate::ProcResult; -use std::io::Read; - -#[derive(Debug)] -pub struct SmapsRollup { - pub memory_map_rollup: MemoryMaps, -} - -impl SmapsRollup { - pub fn from_reader(r: R) -> ProcResult { - MemoryMaps::from_reader(r).map(|m| SmapsRollup { memory_map_rollup: m }) - } -} diff --git a/procfs/src/process/stat.rs b/procfs/src/process/stat.rs deleted file mode 100644 index 2d36d700..00000000 --- a/procfs/src/process/stat.rs +++ /dev/null @@ -1,420 +0,0 @@ -use super::ProcState; -use super::StatFlags; -#[cfg(feature = "chrono")] -use crate::TICKS_PER_SECOND; -use crate::{from_iter, KernelVersion, ProcResult}; -use crate::{KERNEL, PAGESIZE}; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -use std::io::Read; -use std::str::FromStr; - -macro_rules! since_kernel { - ($a:tt, $b:tt, $c:tt, $e:expr) => { - if let Ok(kernel) = *KERNEL { - if kernel >= KernelVersion::new($a, $b, $c) { - Some($e) - } else { - None - } - } else { - None - } - }; -} - -/// Status information about the process, based on the `/proc//stat` file. -/// -/// To construct one of these structures, you have to first create a [Process](crate::process::Process). -/// -/// Not all fields are available in every kernel. These fields have `Option` types. -/// -/// New fields to this struct may be added at any time (even without a major or minor semver bump). -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub struct Stat { - /// The process ID. - pub pid: i32, - /// The filename of the executable, without the parentheses. - /// - /// This is visible whether or not the executable is swapped out. - /// - /// Note that if the actual comm field contains invalid UTF-8 characters, they will be replaced - /// here by the U+FFFD replacement character. - pub comm: String, - /// Process State. - /// - /// See [state()](#method.state) to get the process state as an enum. - pub state: char, - /// The PID of the parent of this process. - pub ppid: i32, - /// The process group ID of the process. - pub pgrp: i32, - /// The session ID of the process. - pub session: i32, - /// The controlling terminal of the process. - /// - /// The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; - /// the major device number is in bits 15 to 8. - /// - /// See [tty_nr()](#method.tty_nr) to get this value decoded into a (major, minor) tuple - pub tty_nr: i32, - /// The ID of the foreground process group of the controlling terminal of the process. - pub tpgid: i32, - /// The kernel flags word of the process. - /// - /// For bit meanings, see the PF_* defines in the Linux kernel source file - /// [`include/linux/sched.h`](/~https://github.com/torvalds/linux/blob/master/include/linux/sched.h). - /// - /// See [flags()](#method.flags) to get a [`StatFlags`](struct.StatFlags.html) bitfield object. - pub flags: u32, - /// The number of minor faults the process has made which have not required loading a memory - /// page from disk. - pub minflt: u64, - /// The number of minor faults that the process's waited-for children have made. - pub cminflt: u64, - /// The number of major faults the process has made which have required loading a memory page - /// from disk. - pub majflt: u64, - /// The number of major faults that the process's waited-for children have made. - pub cmajflt: u64, - /// Amount of time that this process has been scheduled in user mode, measured in clock ticks - /// (divide by [`ticks_per_second()`](crate::ticks_per_second). - /// - /// This includes guest time, guest_time (time spent running a virtual CPU, see below), so that - /// applications that are not aware of the guest time field do not lose that time from their - /// calculations. - pub utime: u64, - /// Amount of time that this process has been scheduled in kernel mode, measured in clock ticks - /// (divide by [`ticks_per_second()`](crate::ticks_per_second)). - pub stime: u64, - - /// Amount of time that this process's waited-for children have been scheduled in - /// user mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). - /// - /// This includes guest time, cguest_time (time spent running a virtual CPU, see below). - pub cutime: i64, - - /// Amount of time that this process's waited-for children have been scheduled in kernel - /// mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). - pub cstime: i64, - /// For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)), - /// this is the negated scheduling priority, minus one; - /// - /// That is, a number in the range -2 to -100, - /// corresponding to real-time priority 1 to 99. For processes running under a non-real-time - /// scheduling policy, this is the raw nice value (setpriority(2)) as represented in the kernel. - /// The kernel stores nice values as numbers in the range 0 (high) to 39 (low), corresponding - /// to the user-visible nice range of -20 to 19. - /// (This explanation is for Linux 2.6) - /// - /// Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this process. - pub priority: i64, - /// The nice value (see `setpriority(2)`), a value in the range 19 (low priority) to -20 (high priority). - pub nice: i64, - /// Number of threads in this process (since Linux 2.6). Before kernel 2.6, this field was - /// hard coded to 0 as a placeholder for an earlier removed field. - pub num_threads: i64, - /// The time in jiffies before the next SIGALRM is sent to the process due to an interval - /// timer. - /// - /// Since kernel 2.6.17, this field is no longer maintained, and is hard coded as 0. - pub itrealvalue: i64, - /// The time the process started after system boot. - /// - /// In kernels before Linux 2.6, this value was expressed in jiffies. Since Linux 2.6, the - /// value is expressed in clock ticks (divide by `sysconf(_SC_CLK_TCK)`). - /// - #[cfg_attr( - feature = "chrono", - doc = "See also the [Stat::starttime()] method to get the starttime as a `DateTime` object" - )] - #[cfg_attr( - not(feature = "chrono"), - doc = "If you compile with the optional `chrono` feature, you can use the `starttime()` method to get the starttime as a `DateTime` object" - )] - pub starttime: u64, - /// Virtual memory size in bytes. - pub vsize: u64, - /// Resident Set Size: number of pages the process has in real memory. - /// - /// This is just the pages which count toward text, data, or stack space. - /// This does not include pages which have not been demand-loaded in, or which are swapped out. - pub rss: u64, - /// Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in - /// getrlimit(2). - pub rsslim: u64, - /// The address above which program text can run. - pub startcode: u64, - /// The address below which program text can run. - pub endcode: u64, - /// The address of the start (i.e., bottom) of the stack. - pub startstack: u64, - /// The current value of ESP (stack pointer), as found in the kernel stack page for the - /// process. - pub kstkesp: u64, - /// The current EIP (instruction pointer). - pub kstkeip: u64, - /// The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does - /// not provide information on real-time signals; use `/proc//status` instead. - pub signal: u64, - /// The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does - /// not provide information on real-time signals; use `/proc//status` instead. - pub blocked: u64, - /// The bitmap of ignored signals, displayed as a decimal number. Obsolete, because it does - /// not provide information on real-time signals; use `/proc//status` instead. - pub sigignore: u64, - /// The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not - /// provide information on real-time signals; use `/proc//status` instead. - pub sigcatch: u64, - /// This is the "channel" in which the process is waiting. It is the address of a location - /// in the kernel where the process is sleeping. The corresponding symbolic name can be found in - /// `/proc//wchan`. - pub wchan: u64, - /// Number of pages swapped **(not maintained)**. - pub nswap: u64, - /// Cumulative nswap for child processes **(not maintained)**. - pub cnswap: u64, - /// Signal to be sent to parent when we die. - /// - /// (since Linux 2.1.22) - pub exit_signal: Option, - /// CPU number last executed on. - /// - /// (since Linux 2.2.8) - pub processor: Option, - /// Real-time scheduling priority - /// - /// Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time processes - /// - /// (since Linux 2.5.19) - pub rt_priority: Option, - /// Scheduling policy (see sched_setscheduler(2)). - /// - /// Decode using the `SCHED_*` constants in `linux/sched.h`. - /// - /// (since Linux 2.5.19) - pub policy: Option, - /// Aggregated block I/O delays, measured in clock ticks (centiseconds). - /// - /// (since Linux 2.6.18) - pub delayacct_blkio_ticks: Option, - /// Guest time of the process (time spent running a virtual CPU for a guest operating system), - /// measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)) - /// - /// (since Linux 2.6.24) - pub guest_time: Option, - /// Guest time of the process's children, measured in clock ticks (divide by - /// [`ticks_per_second()`](crate::ticks_per_second)). - /// - /// (since Linux 2.6.24) - pub cguest_time: Option, - /// Address above which program initialized and uninitialized (BSS) data are placed. - /// - /// (since Linux 3.3) - pub start_data: Option, - /// Address below which program initialized and uninitialized (BSS) data are placed. - /// - /// (since Linux 3.3) - pub end_data: Option, - /// Address above which program heap can be expanded with brk(2). - /// - /// (since Linux 3.3) - pub start_brk: Option, - /// Address above which program command-line arguments (argv) are placed. - /// - /// (since Linux 3.5) - pub arg_start: Option, - /// Address below program command-line arguments (argv) are placed. - /// - /// (since Linux 3.5) - pub arg_end: Option, - /// Address above which program environment is placed. - /// - /// (since Linux 3.5) - pub env_start: Option, - /// Address below which program environment is placed. - /// - /// (since Linux 3.5) - pub env_end: Option, - /// The thread's exit status in the form reported by waitpid(2). - /// - /// (since Linux 3.5) - pub exit_code: Option, -} - -impl Stat { - #[allow(clippy::cognitive_complexity)] - pub fn from_reader(mut r: R) -> ProcResult { - // read in entire thing, this is only going to be 1 line - let mut buf = Vec::with_capacity(512); - r.read_to_end(&mut buf)?; - - let line = String::from_utf8_lossy(&buf); - let buf = line.trim(); - - // find the first opening paren, and split off the first part (pid) - let start_paren = expect!(buf.find('(')); - let end_paren = expect!(buf.rfind(')')); - let pid_s = &buf[..start_paren - 1]; - let comm = buf[start_paren + 1..end_paren].to_string(); - let rest = &buf[end_paren + 2..]; - - let pid = expect!(FromStr::from_str(pid_s)); - - let mut rest = rest.split(' '); - let state = expect!(expect!(rest.next()).chars().next()); - - let ppid = expect!(from_iter(&mut rest)); - let pgrp = expect!(from_iter(&mut rest)); - let session = expect!(from_iter(&mut rest)); - let tty_nr = expect!(from_iter(&mut rest)); - let tpgid = expect!(from_iter(&mut rest)); - let flags = expect!(from_iter(&mut rest)); - let minflt = expect!(from_iter(&mut rest)); - let cminflt = expect!(from_iter(&mut rest)); - let majflt = expect!(from_iter(&mut rest)); - let cmajflt = expect!(from_iter(&mut rest)); - let utime = expect!(from_iter(&mut rest)); - let stime = expect!(from_iter(&mut rest)); - let cutime = expect!(from_iter(&mut rest)); - let cstime = expect!(from_iter(&mut rest)); - let priority = expect!(from_iter(&mut rest)); - let nice = expect!(from_iter(&mut rest)); - let num_threads = expect!(from_iter(&mut rest)); - let itrealvalue = expect!(from_iter(&mut rest)); - let starttime = expect!(from_iter(&mut rest)); - let vsize = expect!(from_iter(&mut rest)); - let rss = expect!(from_iter(&mut rest)); - let rsslim = expect!(from_iter(&mut rest)); - let startcode = expect!(from_iter(&mut rest)); - let endcode = expect!(from_iter(&mut rest)); - let startstack = expect!(from_iter(&mut rest)); - let kstkesp = expect!(from_iter(&mut rest)); - let kstkeip = expect!(from_iter(&mut rest)); - let signal = expect!(from_iter(&mut rest)); - let blocked = expect!(from_iter(&mut rest)); - let sigignore = expect!(from_iter(&mut rest)); - let sigcatch = expect!(from_iter(&mut rest)); - let wchan = expect!(from_iter(&mut rest)); - let nswap = expect!(from_iter(&mut rest)); - let cnswap = expect!(from_iter(&mut rest)); - - let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest))); - let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest))); - let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); - let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest))); - let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest))); - let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); - let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest))); - let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest))); - let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest))); - - Ok(Stat { - pid, - comm, - state, - ppid, - pgrp, - session, - tty_nr, - tpgid, - flags, - minflt, - cminflt, - majflt, - cmajflt, - utime, - stime, - cutime, - cstime, - priority, - nice, - num_threads, - itrealvalue, - starttime, - vsize, - rss, - rsslim, - startcode, - endcode, - startstack, - kstkesp, - kstkeip, - signal, - blocked, - sigignore, - sigcatch, - wchan, - nswap, - cnswap, - exit_signal, - processor, - rt_priority, - policy, - delayacct_blkio_ticks, - guest_time, - cguest_time, - start_data, - end_data, - start_brk, - arg_start, - arg_end, - env_start, - env_end, - exit_code, - }) - } - - pub fn state(&self) -> ProcResult { - ProcState::from_char(self.state) - .ok_or_else(|| build_internal_error!(format!("{:?} is not a recognized process state", self.state))) - } - - pub fn tty_nr(&self) -> (i32, i32) { - // minor is bits 31-20 and 7-0 - // major is 15-8 - - // mmmmmmmmmmmm____MMMMMMMMmmmmmmmm - // 11111111111100000000000000000000 - let major = (self.tty_nr & 0xfff00) >> 8; - let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00); - (major, minor) - } - - /// The kernel flags word of the process, as a bitfield - /// - /// See also the [Stat::flags](struct.Stat.html#structfield.flags) field. - pub fn flags(&self) -> ProcResult { - StatFlags::from_bits(self.flags) - .ok_or_else(|| build_internal_error!(format!("Can't construct flags bitfield from {:?}", self.flags))) - } - - /// Get the starttime of the process as a `DateTime` object. - /// - /// See also the [`starttime`](struct.Stat.html#structfield.starttime) field. - /// - /// This function requires the "chrono" features to be enabled (which it is by default). - #[cfg(feature = "chrono")] - pub fn starttime(&self) -> ProcResult> { - let seconds_since_boot = self.starttime as f32 / *TICKS_PER_SECOND as f32; - let boot_time = crate::boot_time()?; - - Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64)) - } - - /// Gets the Resident Set Size (in bytes) - /// - /// The `rss` field will return the same value in pages - pub fn rss_bytes(&self) -> u64 { - self.rss * *PAGESIZE - } -} diff --git a/procfs/src/process/status.rs b/procfs/src/process/status.rs deleted file mode 100644 index a758edef..00000000 --- a/procfs/src/process/status.rs +++ /dev/null @@ -1,391 +0,0 @@ -use crate::{FromStrRadix, ProcResult}; -use std::collections::HashMap; -use std::io::{BufRead, BufReader, Read}; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -/// Status information about the process, based on the `/proc//status` file. -/// -/// To construct this structure, see [Process::status()](crate::process::Process::status). -/// -/// Not all fields are available in every kernel. These fields have `Option` types. -/// In general, the current kernel version will tell you what fields you can expect, but this -/// isn't totally reliable, since some kernels might backport certain fields, or fields might -/// only be present if certain kernel configuration options are enabled. Be prepared to -/// handle `None` values. -/// -/// New fields to this struct may be added at any time (even without a major or minor semver bump). -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub struct Status { - /// Command run by this process. - pub name: String, - /// Process umask, expressed in octal with a leading zero; see umask(2). (Since Linux 4.7.) - pub umask: Option, - /// Current state of the process. - pub state: String, - /// Thread group ID (i.e., Process ID). - pub tgid: i32, - /// NUMA group ID (0 if none; since Linux 3.13). - pub ngid: Option, - /// Thread ID (see gettid(2)). - pub pid: i32, - /// PID of parent process. - pub ppid: i32, - /// PID of process tracing this process (0 if not being traced). - pub tracerpid: i32, - /// Real UID. - pub ruid: u32, - /// Effective UID. - pub euid: u32, - /// Saved set UID. - pub suid: u32, - /// Filesystem UID. - pub fuid: u32, - /// Real GID. - pub rgid: u32, - /// Effective GID. - pub egid: u32, - /// Saved set GID. - pub sgid: u32, - /// Filesystem GID. - pub fgid: u32, - /// Number of file descriptor slots currently allocated. - pub fdsize: u32, - /// Supplementary group list. - pub groups: Vec, - /// Thread group ID (i.e., PID) in each of the PID - /// namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. The leftmost entry - /// shows the value with respect to the PID namespace of the - /// reading process, followed by the value in successively - /// nested inner namespaces. (Since Linux 4.1.) - pub nstgid: Option>, - /// Thread ID in each of the PID namespaces of which - /// (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. - /// (Since Linux 4.1.) - pub nspid: Option>, - /// Process group ID in each of the PID namespaces of - /// which (pid)[struct.Status.html#structfield.pid] is a member. The fields are ordered as for NStgid. (Since Linux 4.1.) - pub nspgid: Option>, - /// NSsid: descendant namespace session ID hierarchy Session ID - /// in each of the PID namespaces of which (pid)[struct.Status.html#structfield.pid] is a member. - /// The fields are ordered as for NStgid. (Since Linux 4.1.) - pub nssid: Option>, - /// Peak virtual memory size by kibibytes. - pub vmpeak: Option, - /// Virtual memory size by kibibytes. - pub vmsize: Option, - /// Locked memory size by kibibytes (see mlock(3)). - pub vmlck: Option, - /// Pinned memory size by kibibytes (since Linux 3.2). These are - /// pages that can't be moved because something needs to - /// directly access physical memory. - pub vmpin: Option, - /// Peak resident set size by kibibytes ("high water mark"). - pub vmhwm: Option, - /// Resident set size by kibibytes. Note that the value here is the - /// sum of RssAnon, RssFile, and RssShmem. - pub vmrss: Option, - /// Size of resident anonymous memory by kibibytes. (since Linux 4.5). - pub rssanon: Option, - /// Size of resident file mappings by kibibytes. (since Linux 4.5). - pub rssfile: Option, - /// Size of resident shared memory by kibibytes (includes System V - /// shared memory, mappings from tmpfs(5), and shared anonymous - /// mappings). (since Linux 4.5). - pub rssshmem: Option, - /// Size of data by kibibytes. - pub vmdata: Option, - /// Size of stack by kibibytes. - pub vmstk: Option, - /// Size of text seg‐ments by kibibytes. - pub vmexe: Option, - /// Shared library code size by kibibytes. - pub vmlib: Option, - /// Page table entries size by kibibytes (since Linux 2.6.10). - pub vmpte: Option, - /// Swapped-out virtual memory size by anonymous private - /// pages by kibibytes; shmem swap usage is not included (since Linux 2.6.34). - pub vmswap: Option, - /// Size of hugetlb memory portions by kB. (since Linux 4.4). - pub hugetlbpages: Option, - /// Number of threads in process containing this thread. - pub threads: u64, - /// This field contains two slash-separated numbers that - /// relate to queued signals for the real user ID of this - /// process. The first of these is the number of currently - /// queued signals for this real user ID, and the second is the - /// resource limit on the number of queued signals for this - /// process (see the description of RLIMIT_SIGPENDING in - /// getrlimit(2)). - pub sigq: (u64, u64), - /// Number of signals pending for thread (see pthreads(7) and signal(7)). - pub sigpnd: u64, - /// Number of signals pending for process as a whole (see pthreads(7) and signal(7)). - pub shdpnd: u64, - /// Masks indicating signals being blocked (see signal(7)). - pub sigblk: u64, - /// Masks indicating signals being ignored (see signal(7)). - pub sigign: u64, - /// Masks indicating signals being caught (see signal(7)). - pub sigcgt: u64, - /// Masks of capabilities enabled in inheritable sets (see capabilities(7)). - pub capinh: u64, - /// Masks of capabilities enabled in permitted sets (see capabilities(7)). - pub capprm: u64, - /// Masks of capabilities enabled in effective sets (see capabilities(7)). - pub capeff: u64, - /// Capability Bounding set (since Linux 2.6.26, see capabilities(7)). - pub capbnd: Option, - /// Ambient capability set (since Linux 4.3, see capabilities(7)). - pub capamb: Option, - /// Value of the no_new_privs bit (since Linux 4.10, see prctl(2)). - pub nonewprivs: Option, - /// Seccomp mode of the process (since Linux 3.8, see - /// seccomp(2)). 0 means SECCOMP_MODE_DISABLED; 1 means SEC‐ - /// COMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field - /// is provided only if the kernel was built with the CON‐ - /// FIG_SECCOMP kernel configuration option enabled. - pub seccomp: Option, - /// Speculative store bypass mitigation status. - pub speculation_store_bypass: Option, - /// Mask of CPUs on which this process may run (since Linux 2.6.24, see cpuset(7)). - pub cpus_allowed: Option>, - /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). - pub cpus_allowed_list: Option>, - /// Mask of memory nodes allowed to this process (since Linux 2.6.24, see cpuset(7)). - pub mems_allowed: Option>, - /// Same as previous, but in "list format" (since Linux 2.6.26, see cpuset(7)). - pub mems_allowed_list: Option>, - /// Number of voluntary context switches (since Linux 2.6.23). - pub voluntary_ctxt_switches: Option, - /// Number of involuntary context switches (since Linux 2.6.23). - pub nonvoluntary_ctxt_switches: Option, - - /// Contains true if the process is currently dumping core. - /// - /// This information can be used by a monitoring process to avoid killing a processing that is - /// currently dumping core, which could result in a corrupted core dump file. - /// - /// (Since Linux 4.15) - pub core_dumping: Option, - - /// Contains true if the process is allowed to use THP - /// - /// (Since Linux 5.0) - pub thp_enabled: Option, -} - -impl Status { - pub fn from_reader(r: R) -> ProcResult { - let mut map = HashMap::new(); - let reader = BufReader::new(r); - - for line in reader.lines() { - let line = line?; - if line.is_empty() { - continue; - } - let mut s = line.split(':'); - let field = expect!(s.next()); - let value = expect!(s.next()).trim(); - - map.insert(field.to_string(), value.to_string()); - } - - let status = Status { - name: expect!(map.remove("Name")), - umask: map.remove("Umask").map(|x| Ok(from_str!(u32, &x, 8))).transpose()?, - state: expect!(map.remove("State")), - tgid: from_str!(i32, &expect!(map.remove("Tgid"))), - ngid: map.remove("Ngid").map(|x| Ok(from_str!(i32, &x))).transpose()?, - pid: from_str!(i32, &expect!(map.remove("Pid"))), - ppid: from_str!(i32, &expect!(map.remove("PPid"))), - tracerpid: from_str!(i32, &expect!(map.remove("TracerPid"))), - ruid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 0)), - euid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 1)), - suid: expect!(Status::parse_uid_gid(expect!(map.get("Uid")), 2)), - fuid: expect!(Status::parse_uid_gid(&expect!(map.remove("Uid")), 3)), - rgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 0)), - egid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 1)), - sgid: expect!(Status::parse_uid_gid(expect!(map.get("Gid")), 2)), - fgid: expect!(Status::parse_uid_gid(&expect!(map.remove("Gid")), 3)), - fdsize: from_str!(u32, &expect!(map.remove("FDSize"))), - groups: Status::parse_list(&expect!(map.remove("Groups")))?, - nstgid: map.remove("NStgid").map(|x| Status::parse_list(&x)).transpose()?, - nspid: map.remove("NSpid").map(|x| Status::parse_list(&x)).transpose()?, - nspgid: map.remove("NSpgid").map(|x| Status::parse_list(&x)).transpose()?, - nssid: map.remove("NSsid").map(|x| Status::parse_list(&x)).transpose()?, - vmpeak: Status::parse_with_kb(map.remove("VmPeak"))?, - vmsize: Status::parse_with_kb(map.remove("VmSize"))?, - vmlck: Status::parse_with_kb(map.remove("VmLck"))?, - vmpin: Status::parse_with_kb(map.remove("VmPin"))?, - vmhwm: Status::parse_with_kb(map.remove("VmHWM"))?, - vmrss: Status::parse_with_kb(map.remove("VmRSS"))?, - rssanon: Status::parse_with_kb(map.remove("RssAnon"))?, - rssfile: Status::parse_with_kb(map.remove("RssFile"))?, - rssshmem: Status::parse_with_kb(map.remove("RssShmem"))?, - vmdata: Status::parse_with_kb(map.remove("VmData"))?, - vmstk: Status::parse_with_kb(map.remove("VmStk"))?, - vmexe: Status::parse_with_kb(map.remove("VmExe"))?, - vmlib: Status::parse_with_kb(map.remove("VmLib"))?, - vmpte: Status::parse_with_kb(map.remove("VmPTE"))?, - vmswap: Status::parse_with_kb(map.remove("VmSwap"))?, - hugetlbpages: Status::parse_with_kb(map.remove("HugetlbPages"))?, - threads: from_str!(u64, &expect!(map.remove("Threads"))), - sigq: expect!(Status::parse_sigq(&expect!(map.remove("SigQ")))), - sigpnd: from_str!(u64, &expect!(map.remove("SigPnd")), 16), - shdpnd: from_str!(u64, &expect!(map.remove("ShdPnd")), 16), - sigblk: from_str!(u64, &expect!(map.remove("SigBlk")), 16), - sigign: from_str!(u64, &expect!(map.remove("SigIgn")), 16), - sigcgt: from_str!(u64, &expect!(map.remove("SigCgt")), 16), - capinh: from_str!(u64, &expect!(map.remove("CapInh")), 16), - capprm: from_str!(u64, &expect!(map.remove("CapPrm")), 16), - capeff: from_str!(u64, &expect!(map.remove("CapEff")), 16), - capbnd: map.remove("CapBnd").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, - capamb: map.remove("CapAmb").map(|x| Ok(from_str!(u64, &x, 16))).transpose()?, - nonewprivs: map.remove("NoNewPrivs").map(|x| Ok(from_str!(u64, &x))).transpose()?, - seccomp: map.remove("Seccomp").map(|x| Ok(from_str!(u32, &x))).transpose()?, - speculation_store_bypass: map.remove("Speculation_Store_Bypass"), - cpus_allowed: map - .remove("Cpus_allowed") - .map(|x| Status::parse_allowed(&x)) - .transpose()?, - cpus_allowed_list: map - .remove("Cpus_allowed_list") - .and_then(|x| Status::parse_allowed_list(&x).ok()), - mems_allowed: map - .remove("Mems_allowed") - .map(|x| Status::parse_allowed(&x)) - .transpose()?, - mems_allowed_list: map - .remove("Mems_allowed_list") - .and_then(|x| Status::parse_allowed_list(&x).ok()), - voluntary_ctxt_switches: map - .remove("voluntary_ctxt_switches") - .map(|x| Ok(from_str!(u64, &x))) - .transpose()?, - nonvoluntary_ctxt_switches: map - .remove("nonvoluntary_ctxt_switches") - .map(|x| Ok(from_str!(u64, &x))) - .transpose()?, - core_dumping: map.remove("CoreDumping").map(|x| x == "1"), - thp_enabled: map.remove("THP_enabled").map(|x| x == "1"), - }; - - if cfg!(test) && !map.is_empty() { - // This isn't an error because different kernels may put different data here, and distros - // may backport these changes into older kernels. Too hard to keep track of - eprintln!("Warning: status map is not empty: {:#?}", map); - } - - Ok(status) - } - - fn parse_with_kb(s: Option) -> ProcResult> { - if let Some(s) = s { - Ok(Some(from_str!(T, &s.replace(" kB", "")))) - } else { - Ok(None) - } - } - - pub(crate) fn parse_uid_gid(s: &str, i: usize) -> ProcResult { - Ok(from_str!(u32, expect!(s.split_whitespace().nth(i)))) - } - - fn parse_sigq(s: &str) -> ProcResult<(u64, u64)> { - let mut iter = s.split('/'); - let first = from_str!(u64, expect!(iter.next())); - let second = from_str!(u64, expect!(iter.next())); - Ok((first, second)) - } - - fn parse_list(s: &str) -> ProcResult> { - let mut ret = Vec::new(); - for i in s.split_whitespace() { - ret.push(from_str!(T, i)); - } - Ok(ret) - } - - fn parse_allowed(s: &str) -> ProcResult> { - let mut ret = Vec::new(); - for i in s.split(',') { - ret.push(from_str!(u32, i, 16)); - } - Ok(ret) - } - - fn parse_allowed_list(s: &str) -> ProcResult> { - let mut ret = Vec::new(); - for s in s.split(',') { - if s.contains('-') { - let mut s = s.split('-'); - let beg = from_str!(u32, expect!(s.next())); - if let Some(x) = s.next() { - let end = from_str!(u32, x); - ret.push((beg, end)); - } - } else { - let beg = from_str!(u32, s); - let end = from_str!(u32, s); - ret.push((beg, end)); - } - } - Ok(ret) - } -} - -#[cfg(test)] -mod tests { - use crate::process::*; - - #[test] - fn test_proc_status() { - let myself = Process::myself().unwrap(); - let stat = myself.stat().unwrap(); - let status = myself.status().unwrap(); - println!("{:?}", status); - - assert_eq!(status.name, stat.comm); - assert_eq!(status.pid, stat.pid); - assert_eq!(status.ppid, stat.ppid); - } - - #[test] - fn test_proc_status_for_kthreadd() { - // when running in a container, pid2 probably isn't kthreadd, so check - let kthreadd = match process::Process::new(2) { - Ok(p) => p, - Err(ProcError::NotFound(_)) => { - return; // ok we can ignore - } - Err(e) => { - panic!("{}", e); - } - }; - let status = kthreadd.status().unwrap(); - println!("{:?}", status); - - assert_eq!(status.pid, 2); - assert_eq!(status.vmpeak, None); - assert_eq!(status.vmsize, None); - assert_eq!(status.vmlck, None); - assert_eq!(status.vmpin, None); - assert_eq!(status.vmhwm, None); - assert_eq!(status.vmrss, None); - assert_eq!(status.rssanon, None); - assert_eq!(status.rssfile, None); - assert_eq!(status.rssshmem, None); - assert_eq!(status.vmdata, None); - assert_eq!(status.vmstk, None); - assert_eq!(status.vmexe, None); - assert_eq!(status.vmlib, None); - assert_eq!(status.vmpte, None); - assert_eq!(status.vmswap, None); - assert_eq!(status.hugetlbpages, None); - } -} diff --git a/procfs/src/process/task.rs b/procfs/src/process/task.rs index 7886ce1c..3bc6c2a1 100644 --- a/procfs/src/process/task.rs +++ b/procfs/src/process/task.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use super::{FileWrapper, Io, Schedstat, Stat, Status}; use crate::{ProcError, ProcResult}; +use procfs_core::FromRead; use rustix::fd::{BorrowedFd, OwnedFd}; /// A task (aka Thread) inside of a [`Process`](crate::process::Process) @@ -52,28 +53,28 @@ impl Task { /// /// Many of the returned fields will be the same as the parent process, but some fields like `utime` and `stime` will be per-task pub fn stat(&self) -> ProcResult { - Stat::from_reader(FileWrapper::open_at(&self.root, &self.fd, "stat")?) + self.read("stat") } /// Thread info from `/proc//task//status` /// /// Many of the returned fields will be the same as the parent process pub fn status(&self) -> ProcResult { - Status::from_reader(FileWrapper::open_at(&self.root, &self.fd, "status")?) + self.read("status") } /// Thread IO info from `/proc//task//io` /// /// This data will be unique per task. pub fn io(&self) -> ProcResult { - Io::from_reader(FileWrapper::open_at(&self.root, &self.fd, "io")?) + self.read("io") } /// Thread scheduler info from `/proc//task//schedstat` /// /// This data will be unique per task. pub fn schedstat(&self) -> ProcResult { - Schedstat::from_reader(FileWrapper::open_at(&self.root, &self.fd, "schedstat")?) + self.read("schedstat") } /// Thread children from `/proc//task//children` @@ -103,6 +104,11 @@ impl Task { let _ = FileWrapper::open_at(&self.root, &self.fd, "does_not_exist")?; Ok(()) } + + /// Parse a file relative to the task proc structure. + pub fn read(&self, path: &str) -> ProcResult { + FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) + } } #[cfg(test)] diff --git a/procfs/src/process/tests.rs b/procfs/src/process/tests.rs index 27d2f989..c31181b5 100644 --- a/procfs/src/process/tests.rs +++ b/procfs/src/process/tests.rs @@ -1,4 +1,5 @@ use super::*; +use rustix::process::Resource; fn check_unwrap(prc: &Process, val: ProcResult) -> Option { match val { @@ -51,7 +52,7 @@ fn test_self_proc() { println!("flags: {:?}", myself.flags()); #[cfg(feature = "chrono")] - println!("starttime: {:#?}", myself.starttime()); + println!("starttime: {:#?}", myself.starttime().get()); let kernel = KernelVersion::current().unwrap(); @@ -122,9 +123,9 @@ fn test_self_proc() { #[test] fn test_all() { - let is_wsl2 = kernel_config() + let is_wsl2 = KernelConfig::current() .ok() - .and_then(|cfg| { + .and_then(|KernelConfig(cfg)| { cfg.get("CONFIG_LOCALVERSION").and_then(|ver| { if let ConfigSetting::Value(s) = ver { Some(s == "\"-microsoft-standard\"") @@ -145,7 +146,7 @@ fn test_all() { stat.flags().unwrap(); stat.state().unwrap(); #[cfg(feature = "chrono")] - stat.starttime().unwrap(); + stat.starttime().get().unwrap(); // if this process is defunct/zombie, don't try to read any of the below data // (some might be successful, but not all) @@ -554,3 +555,171 @@ fn test_network_stuff() { let _dev = myself.dev_status().unwrap(); let _unix = myself.unix().unwrap(); } + +trait LimitValueAsLimit { + fn as_limit(&self) -> Option; +} + +impl LimitValueAsLimit for LimitValue { + fn as_limit(&self) -> Option { + match self { + LimitValue::Unlimited => None, + LimitValue::Value(v) => Some(*v), + } + } +} + +#[test] +fn test_limits() { + let me = process::Process::myself().unwrap(); + let limits = me.limits().unwrap(); + println!("{:#?}", limits); + + // Max cpu time + let lim = rustix::process::getrlimit(Resource::Cpu); + assert_eq!(lim.current, limits.max_cpu_time.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_cpu_time.hard_limit.as_limit()); + + // Max file size + let lim = rustix::process::getrlimit(Resource::Fsize); + assert_eq!(lim.current, limits.max_file_size.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_file_size.hard_limit.as_limit()); + + // Max data size + let lim = rustix::process::getrlimit(Resource::Data); + assert_eq!(lim.current, limits.max_data_size.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_data_size.hard_limit.as_limit()); + + // Max stack size + let lim = rustix::process::getrlimit(Resource::Stack); + assert_eq!(lim.current, limits.max_stack_size.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_stack_size.hard_limit.as_limit()); + + // Max core file size + let lim = rustix::process::getrlimit(Resource::Core); + assert_eq!(lim.current, limits.max_core_file_size.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_core_file_size.hard_limit.as_limit()); + + // Max resident set + let lim = rustix::process::getrlimit(Resource::Rss); + assert_eq!(lim.current, limits.max_resident_set.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_resident_set.hard_limit.as_limit()); + + // Max processes + let lim = rustix::process::getrlimit(Resource::Nproc); + assert_eq!(lim.current, limits.max_processes.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_processes.hard_limit.as_limit()); + + // Max open files + let lim = rustix::process::getrlimit(Resource::Nofile); + assert_eq!(lim.current, limits.max_open_files.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_open_files.hard_limit.as_limit()); + + // Max locked memory + let lim = rustix::process::getrlimit(Resource::Memlock); + assert_eq!(lim.current, limits.max_locked_memory.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_locked_memory.hard_limit.as_limit()); + + // Max address space + let lim = rustix::process::getrlimit(Resource::As); + assert_eq!(lim.current, limits.max_address_space.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_address_space.hard_limit.as_limit()); + + // Max file locks + let lim = rustix::process::getrlimit(Resource::Locks); + assert_eq!(lim.current, limits.max_file_locks.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_file_locks.hard_limit.as_limit()); + + // Max pending signals + let lim = rustix::process::getrlimit(Resource::Sigpending); + assert_eq!(lim.current, limits.max_pending_signals.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_pending_signals.hard_limit.as_limit()); + + // Max msgqueue size + let lim = rustix::process::getrlimit(Resource::Msgqueue); + assert_eq!(lim.current, limits.max_msgqueue_size.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_msgqueue_size.hard_limit.as_limit()); + + // Max nice priority + let lim = rustix::process::getrlimit(Resource::Nice); + assert_eq!(lim.current, limits.max_nice_priority.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_nice_priority.hard_limit.as_limit()); + + // Max realtime priority + let lim = rustix::process::getrlimit(Resource::Rtprio); + assert_eq!(lim.current, limits.max_realtime_priority.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_realtime_priority.hard_limit.as_limit()); + + // Max realtime timeout + let lim = rustix::process::getrlimit(Resource::Rttime); + assert_eq!(lim.current, limits.max_realtime_timeout.soft_limit.as_limit()); + assert_eq!(lim.maximum, limits.max_realtime_timeout.hard_limit.as_limit()); +} + +#[test] +fn test_mountinfo_live() { + let me = Process::myself().unwrap(); + let MountInfos(mounts) = me.mountinfo().unwrap(); + println!("{:#?}", mounts); +} + +#[test] +fn test_proc_mountstats_live() { + // this tries to parse a live mountstats file + // there are no assertions, but we still want to check for parsing errors (which can + // cause panics) + + let MountStats(stats) = FromRead::from_file("/proc/self/mountstats").unwrap(); + for stat in stats { + println!("{:#?}", stat); + if let Some(nfs) = stat.statistics { + println!(" {:?}", nfs.server_caps().unwrap()); + } + } +} + +#[test] +fn test_proc_status() { + let myself = Process::myself().unwrap(); + let stat = myself.stat().unwrap(); + let status = myself.status().unwrap(); + println!("{:?}", status); + + assert_eq!(status.name, stat.comm); + assert_eq!(status.pid, stat.pid); + assert_eq!(status.ppid, stat.ppid); +} + +#[test] +fn test_proc_status_for_kthreadd() { + // when running in a container, pid2 probably isn't kthreadd, so check + let kthreadd = match process::Process::new(2) { + Ok(p) => p, + Err(ProcError::NotFound(_)) => { + return; // ok we can ignore + } + Err(e) => { + panic!("{}", e); + } + }; + let status = kthreadd.status().unwrap(); + println!("{:?}", status); + + assert_eq!(status.pid, 2); + assert_eq!(status.vmpeak, None); + assert_eq!(status.vmsize, None); + assert_eq!(status.vmlck, None); + assert_eq!(status.vmpin, None); + assert_eq!(status.vmhwm, None); + assert_eq!(status.vmrss, None); + assert_eq!(status.rssanon, None); + assert_eq!(status.rssfile, None); + assert_eq!(status.rssshmem, None); + assert_eq!(status.vmdata, None); + assert_eq!(status.vmstk, None); + assert_eq!(status.vmexe, None); + assert_eq!(status.vmlib, None); + assert_eq!(status.vmpte, None); + assert_eq!(status.vmswap, None); + assert_eq!(status.hugetlbpages, None); +} diff --git a/procfs/src/sys/fs/binfmt_misc.rs b/procfs/src/sys/fs/binfmt_misc.rs index bd770926..3f7ae5df 100644 --- a/procfs/src/sys/fs/binfmt_misc.rs +++ b/procfs/src/sys/fs/binfmt_misc.rs @@ -1,7 +1,7 @@ use bitflags::bitflags; use std::path::Path; -use crate::{read_value, ProcResult}; +use crate::{build_internal_error, from_str, read_value, ProcResult}; /// Returns true if the miscellaneous Binary Formats system is enabled. pub fn enabled() -> ProcResult { diff --git a/procfs/src/sys/fs/mod.rs b/procfs/src/sys/fs/mod.rs index edcc92a6..f9130483 100644 --- a/procfs/src/sys/fs/mod.rs +++ b/procfs/src/sys/fs/mod.rs @@ -1,6 +1,6 @@ //! This modules contains functions for kernel variables related to filesystems -use crate::{read_file, read_value, write_value, ProcResult}; +use crate::{expect, from_str, read_file, read_value, write_value, ProcResult}; use std::time::Duration; pub mod binfmt_misc; diff --git a/procfs/src/sysvipc_shm.rs b/procfs/src/sysvipc_shm.rs index 5996f10d..e395306c 100644 --- a/procfs/src/sysvipc_shm.rs +++ b/procfs/src/sysvipc_shm.rs @@ -1,6 +1,6 @@ use std::io; -use super::{FileWrapper, ProcResult}; +use super::{expect, FileWrapper, ProcResult}; use std::str::FromStr; #[cfg(feature = "serde1")] diff --git a/procfs/src/uptime.rs b/procfs/src/uptime.rs index 3be2948a..bc487cb2 100644 --- a/procfs/src/uptime.rs +++ b/procfs/src/uptime.rs @@ -1,4 +1,4 @@ -use crate::{FileWrapper, ProcResult}; +use crate::{expect, FileWrapper, ProcResult}; use std::io::Read; use std::str::FromStr; From 432358377aa192645724a377b55f4e3ce6f50486 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 25 Mar 2023 14:53:40 -0400 Subject: [PATCH 3/7] Add --workspace to all github CI steps --- .github/workflows/rust.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 064831b7..969be6d1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -25,17 +25,17 @@ jobs: toolchain: ${{ matrix.toolchain }} - name: Build - run: cargo +${{ matrix.toolchain }} build --verbose + run: cargo +${{ matrix.toolchain }} build --workspace --verbose - name: Run tests - run: cargo +${{ matrix.toolchain }} test --all-features --verbose -- --skip _runsinglethread + run: cargo +${{ matrix.toolchain }} test --workspace --all-features --verbose -- --skip _runsinglethread - name: Run tests (single-threaded tests) - run: cargo +${{ matrix.toolchain }} test --all-features --verbose -- _runsinglethread --test-threads 1 + run: cargo +${{ matrix.toolchain }} test --workspace --all-features --verbose -- _runsinglethread --test-threads 1 - name: Build docs (all features) - run: cargo +${{ matrix.toolchain }} doc --all-features + run: cargo +${{ matrix.toolchain }} doc --workspace --all-features - name: Build docs - run: cargo +${{ matrix.toolchain }} doc + run: cargo +${{ matrix.toolchain }} doc --workspace check: @@ -59,7 +59,7 @@ jobs: target: i686-unknown-linux-gnu - name: cargo check (aarch64) - run: cargo +${{ matrix.toolchain }} check --target aarch64-linux-android --all-features + run: cargo +${{ matrix.toolchain }} check --workspace --target aarch64-linux-android --all-features - name: cargo check (i686) - run: cargo +${{ matrix.toolchain }} check --target i686-unknown-linux-gnu --all-features + run: cargo +${{ matrix.toolchain }} check --workspace --target i686-unknown-linux-gnu --all-features From 6ae5db5632fa50fa28d2ad109fe019da2c761813 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 25 Mar 2023 14:55:23 -0400 Subject: [PATCH 4/7] Remove some cross-crate links in doc comments --- procfs-core/src/process/mod.rs | 7 ------- procfs-core/src/process/mount.rs | 15 --------------- procfs-core/src/process/pagemap.rs | 2 -- procfs-core/src/process/stat.rs | 14 ++++++-------- procfs-core/src/process/status.rs | 2 -- procfs-core/src/sys/kernel/mod.rs | 6 ++---- procfs/src/process/mod.rs | 15 +++++++++++++++ 7 files changed, 23 insertions(+), 38 deletions(-) diff --git a/procfs-core/src/process/mod.rs b/procfs-core/src/process/mod.rs index 23045b23..0716a766 100644 --- a/procfs-core/src/process/mod.rs +++ b/procfs-core/src/process/mod.rs @@ -576,9 +576,6 @@ impl IntoIterator for MemoryMaps { } /// Represents an entry in a `/proc//maps` or `/proc//smaps` file. -/// -/// To construct this structure for the current process, see [Process::maps()] and -/// [Process::smaps()]. #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MemoryMap { @@ -624,8 +621,6 @@ impl MemoryMap { } /// Represents the information about a specific mapping as presented in /proc/\/smaps -/// -/// To construct this structure, see [Process::smaps()] #[derive(Default, Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct MMapExtension { @@ -683,8 +678,6 @@ impl crate::FromBufRead for Io { } /// Describes a file descriptor opened by a process. -/// -/// See also the [Process::fd()] method. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum FDTarget { diff --git a/procfs-core/src/process/mount.rs b/procfs-core/src/process/mount.rs index d58330b1..41531dc4 100644 --- a/procfs-core/src/process/mount.rs +++ b/procfs-core/src/process/mount.rs @@ -226,21 +226,6 @@ pub struct MountStat { } /// Mount information from `/proc//mountstats`. -/// -/// # Example: -/// -/// ``` -/// # use procfs::process::Process; -/// let stats = Process::myself().unwrap().mountstats().unwrap(); -/// -/// for mount in stats { -/// println!("{} mounted on {} wth type {}", -/// mount.device.unwrap_or("??".to_owned()), -/// mount.mount_point.display(), -/// mount.fs -/// ); -/// } -/// ``` #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] diff --git a/procfs-core/src/process/pagemap.rs b/procfs-core/src/process/pagemap.rs index dcef6c0b..047ef7ee 100644 --- a/procfs-core/src/process/pagemap.rs +++ b/procfs-core/src/process/pagemap.rs @@ -78,8 +78,6 @@ impl MemoryPageFlags { } /// A Page Frame Number, representing a 4 kiB physical memory page -/// -/// See also [crate::iomem()] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Pfn(pub u64); diff --git a/procfs-core/src/process/stat.rs b/procfs-core/src/process/stat.rs index 40ec289f..79179220 100644 --- a/procfs-core/src/process/stat.rs +++ b/procfs-core/src/process/stat.rs @@ -9,8 +9,6 @@ use std::str::FromStr; /// Status information about the process, based on the `/proc//stat` file. /// -/// To construct one of these structures, you have to first create a [Process](crate::process::Process). -/// /// Not all fields are available in every kernel. These fields have `Option` types. /// /// New fields to this struct may be added at any time (even without a major or minor semver bump). @@ -64,24 +62,24 @@ pub struct Stat { /// The number of major faults that the process's waited-for children have made. pub cmajflt: u64, /// Amount of time that this process has been scheduled in user mode, measured in clock ticks - /// (divide by [`ticks_per_second()`](crate::ticks_per_second). + /// (divide by `ticks_per_second()`). /// /// This includes guest time, guest_time (time spent running a virtual CPU, see below), so that /// applications that are not aware of the guest time field do not lose that time from their /// calculations. pub utime: u64, /// Amount of time that this process has been scheduled in kernel mode, measured in clock ticks - /// (divide by [`ticks_per_second()`](crate::ticks_per_second)). + /// (divide by `ticks_per_second()`). pub stime: u64, /// Amount of time that this process's waited-for children have been scheduled in - /// user mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). + /// user mode, measured in clock ticks (divide by `ticks_per_second()`). /// /// This includes guest time, cguest_time (time spent running a virtual CPU, see below). pub cutime: i64, /// Amount of time that this process's waited-for children have been scheduled in kernel - /// mode, measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)). + /// mode, measured in clock ticks (divide by `ticks_per_second()`). pub cstime: i64, /// For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)), /// this is the negated scheduling priority, minus one; @@ -185,12 +183,12 @@ pub struct Stat { /// (since Linux 2.6.18) pub delayacct_blkio_ticks: Option, /// Guest time of the process (time spent running a virtual CPU for a guest operating system), - /// measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)) + /// measured in clock ticks (divide by `ticks_per_second()`) /// /// (since Linux 2.6.24) pub guest_time: Option, /// Guest time of the process's children, measured in clock ticks (divide by - /// [`ticks_per_second()`](crate::ticks_per_second)). + /// `ticks_per_second()`). /// /// (since Linux 2.6.24) pub cguest_time: Option, diff --git a/procfs-core/src/process/status.rs b/procfs-core/src/process/status.rs index a189face..43db5a69 100644 --- a/procfs-core/src/process/status.rs +++ b/procfs-core/src/process/status.rs @@ -7,8 +7,6 @@ use serde::{Deserialize, Serialize}; /// Status information about the process, based on the `/proc//status` file. /// -/// To construct this structure, see [Process::status()](crate::process::Process::status). -/// /// Not all fields are available in every kernel. These fields have `Option` types. /// In general, the current kernel version will tell you what fields you can expect, but this /// isn't totally reliable, since some kernels might backport certain fields, or fields might diff --git a/procfs-core/src/sys/kernel/mod.rs b/procfs-core/src/sys/kernel/mod.rs index 19a58a22..4c63c08a 100644 --- a/procfs-core/src/sys/kernel/mod.rs +++ b/procfs-core/src/sys/kernel/mod.rs @@ -31,7 +31,7 @@ impl Version { /// # Example /// /// ``` - /// # use procfs::KernelVersion; + /// # use procfs_core::KernelVersion; /// let a = KernelVersion::from_str("3.16.0-6-amd64").unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); @@ -70,7 +70,7 @@ impl FromStr for Version { /// # Example /// /// ``` - /// # use procfs::KernelVersion; + /// # use procfs_core::KernelVersion; /// let a: KernelVersion = "3.16.0-6-amd64".parse().unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); @@ -294,8 +294,6 @@ bitflags! { } /// Values controlling functions allowed to be invoked by the SysRq key -/// -/// To construct this enum, see [sysrq](crate::sys::kernel::sysrq) #[derive(Copy, Clone, Debug)] pub enum SysRq { /// Disable sysrq completely diff --git a/procfs/src/process/mod.rs b/procfs/src/process/mod.rs index 2e60eae2..34c08de1 100644 --- a/procfs/src/process/mod.rs +++ b/procfs/src/process/mod.rs @@ -422,6 +422,21 @@ impl Process { /// /// This data is taken from the `/proc/[pid]/mountinfo` file /// + /// # Example: + /// + /// ``` + /// # use procfs::process::Process; + /// let stats = Process::myself().unwrap().mountstats().unwrap(); + /// + /// for mount in stats { + /// println!("{} mounted on {} wth type {}", + /// mount.device.unwrap_or("??".to_owned()), + /// mount.mount_point.display(), + /// mount.fs + /// ); + /// } + /// ``` + /// /// (Since Linux 2.6.26) pub fn mountinfo(&self) -> ProcResult { self.read("mountinfo") From dbc77e2d8a8340f8c8882c900d4a175980308938 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Mon, 10 Apr 2023 10:46:39 -0400 Subject: [PATCH 5/7] Restore removed functions to keep better API compatibility. --- procfs/src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/procfs/src/lib.rs b/procfs/src/lib.rs index 2084d86c..ef0c9a13 100644 --- a/procfs/src/lib.rs +++ b/procfs/src/lib.rs @@ -419,6 +419,18 @@ impl Current for KernelConfig { } } +/// Returns a configuration options used to build the currently running kernel +/// +/// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`. +/// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order). +/// +/// # Notes +/// Reading the compress `/proc/config.gz` is only supported if the `flate2` feature is enabled +/// (which it is by default). +pub fn kernel_config() -> ProcResult> { + KernelConfig::current().map(|c| c.0) +} + impl CurrentSI for KernelStats { const PATH: &'static str = "/proc/stat"; } @@ -427,14 +439,41 @@ impl Current for VmStat { const PATH: &'static str = "/proc/vmstat"; } +/// Get various virtual memory statistics +/// +/// Since the exact set of statistics will vary from kernel to kernel, and because most of them are +/// not well documented, this function returns a HashMap instead of a struct. Consult the kernel +/// source code for more details of this data. +/// +/// This data is taken from the /proc/vmstat file. +/// +/// (since Linux 2.6.0) +pub fn vmstat() -> ProcResult> { + VmStat::current().map(|s| s.0) +} + impl Current for KernelModules { const PATH: &'static str = "/proc/modules"; } +/// Get a list of loaded kernel modules +/// +/// This corresponds to the data in `/proc/modules`. +pub fn modules() -> ProcResult> { + KernelModules::current().map(|m| m.0) +} + impl Current for KernelCmdline { const PATH: &'static str = "/proc/cmdline"; } +/// Get a list of the arguments passed to the Linux kernel at boot time +/// +/// This corresponds to the data in `/proc/cmdline`. +pub fn cmdline() -> ProcResult> { + KernelCmdline::current().map(|c| c.0) +} + #[cfg(test)] mod tests { use super::*; From 19c17ac51c49080a7b12e78eed523f23883c24ae Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Mon, 10 Apr 2023 16:31:29 -0400 Subject: [PATCH 6/7] Finish out everything except `sys` (though there's not much to do there?). --- procfs-core/Cargo.toml | 1 + {procfs => procfs-core}/src/cpuinfo.rs | 44 +- {procfs => procfs-core}/src/diskstats.rs | 36 +- procfs-core/src/keyring.rs | 423 ++++++++++++ procfs-core/src/lib.rs | 41 +- {procfs => procfs-core}/src/locks.rs | 48 +- {procfs => procfs-core}/src/meminfo.rs | 241 +------ procfs-core/src/net.rs | 710 +++++++++++++++++++++ {procfs => procfs-core}/src/pressure.rs | 92 +-- {procfs => procfs-core}/src/sysvipc_shm.rs | 24 +- {procfs => procfs-core}/src/uptime.rs | 16 +- procfs/Cargo.toml | 1 - procfs/examples/pressure.rs | 9 +- procfs/examples/shm.rs | 5 +- procfs/src/keyring.rs | 418 +----------- procfs/src/lib.rs | 392 ++++++++++-- procfs/src/net.rs | 703 +------------------- procfs/src/process/mod.rs | 49 +- 18 files changed, 1678 insertions(+), 1575 deletions(-) rename {procfs => procfs-core}/src/cpuinfo.rs (87%) rename {procfs => procfs-core}/src/diskstats.rs (88%) create mode 100644 procfs-core/src/keyring.rs rename {procfs => procfs-core}/src/locks.rs (85%) rename {procfs => procfs-core}/src/meminfo.rs (67%) create mode 100644 procfs-core/src/net.rs rename {procfs => procfs-core}/src/pressure.rs (64%) rename {procfs => procfs-core}/src/sysvipc_shm.rs (83%) rename {procfs => procfs-core}/src/uptime.rs (84%) diff --git a/procfs-core/Cargo.toml b/procfs-core/Cargo.toml index 5718a195..6ac06181 100644 --- a/procfs-core/Cargo.toml +++ b/procfs-core/Cargo.toml @@ -20,6 +20,7 @@ serde1 = ["serde"] backtrace = { version = "0.3", optional = true } bitflags = "1.2" chrono = { version = "0.4.20", optional = true, features = ["clock"], default-features = false } +hex = "0.4" serde = { version = "1.0", features = ["derive"], optional = true } [package.metadata.docs.rs] diff --git a/procfs/src/cpuinfo.rs b/procfs-core/src/cpuinfo.rs similarity index 87% rename from procfs/src/cpuinfo.rs rename to procfs-core/src/cpuinfo.rs index cdb670e3..07c8d93f 100644 --- a/procfs/src/cpuinfo.rs +++ b/procfs-core/src/cpuinfo.rs @@ -1,8 +1,8 @@ -use crate::{expect, FileWrapper, ProcResult}; +use crate::{expect, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, io::Read}; +use std::{collections::HashMap, io::BufRead}; /// Represents the data from `/proc/cpuinfo`. /// @@ -20,20 +20,15 @@ pub struct CpuInfo { pub cpus: Vec>, } -impl CpuInfo { - /// Get CpuInfo from a custom Read instead of the default `/proc/cpuinfo`. - pub fn from_reader(r: R) -> ProcResult { - use std::io::{BufRead, BufReader}; - - let reader = BufReader::new(r); - +impl crate::FromBufRead for CpuInfo { + fn from_buf_read(r: R) -> ProcResult { let mut list = Vec::new(); let mut map = Some(HashMap::new()); // the first line of a cpu block must start with "processor" let mut found_first = false; - for line in reader.lines().flatten() { + for line in r.lines().flatten() { if !line.is_empty() { let mut s = line.split(':'); let key = expect!(s.next()); @@ -88,12 +83,9 @@ impl CpuInfo { cpus: list, }) } - pub fn new() -> ProcResult { - let file = FileWrapper::open("/proc/cpuinfo")?; - - CpuInfo::from_reader(file) - } +} +impl CpuInfo { /// Get the total number of cpu cores. /// /// This is the number of entries in the `/proc/cpuinfo` file. @@ -115,6 +107,7 @@ impl CpuInfo { .collect() }) } + /// Get the content of a specific field associated to a CPU /// /// Returns None if the requested cpu index is not found. @@ -130,13 +123,16 @@ impl CpuInfo { pub fn model_name(&self, cpu_num: usize) -> Option<&str> { self.get_field(cpu_num, "model name") } + pub fn vendor_id(&self, cpu_num: usize) -> Option<&str> { self.get_field(cpu_num, "vendor_id") } + /// May not be available on some older 2.6 kernels pub fn physical_id(&self, cpu_num: usize) -> Option { self.get_field(cpu_num, "physical id").and_then(|s| s.parse().ok()) } + pub fn flags(&self, cpu_num: usize) -> Option> { self.get_field(cpu_num, "flags") .map(|flags| flags.split_whitespace().collect()) @@ -147,20 +143,6 @@ impl CpuInfo { mod tests { use super::*; - #[test] - fn test_cpuinfo() { - let info = CpuInfo::new().unwrap(); - println!("{:#?}", info.flags(0)); - for num in 0..info.num_cores() { - info.model_name(num).unwrap(); - info.vendor_id(num).unwrap(); - // May not be available on some old kernels: - info.physical_id(num); - } - - //assert_eq!(info.num_cores(), 8); - } - #[test] fn test_cpuinfo_rpi() { // My rpi system includes some stuff at the end of /proc/cpuinfo that we shouldn't parse @@ -212,7 +194,9 @@ Model : Raspberry Pi 3 Model B Plus Rev 1.3 let r = std::io::Cursor::new(data.as_bytes()); - let info = CpuInfo::from_reader(r).unwrap(); + use crate::FromRead; + + let info = CpuInfo::from_read(r).unwrap(); assert_eq!(info.num_cores(), 4); let info = info.get_info(0).unwrap(); assert!(info.get("model name").is_some()); diff --git a/procfs/src/diskstats.rs b/procfs-core/src/diskstats.rs similarity index 88% rename from procfs/src/diskstats.rs rename to procfs-core/src/diskstats.rs index 6937458e..0655c300 100644 --- a/procfs/src/diskstats.rs +++ b/procfs-core/src/diskstats.rs @@ -1,7 +1,7 @@ -use crate::{expect, from_str, FileWrapper, ProcResult}; +use crate::{expect, from_str, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader}; +use std::io::BufRead; /// Disk IO stat information /// @@ -88,17 +88,21 @@ pub struct DiskStat { pub time_flushing: Option, } -/// Get disk IO stat info from /proc/diskstats -pub fn diskstats() -> ProcResult> { - let file = FileWrapper::open("/proc/diskstats")?; - let reader = BufReader::new(file); - let mut v = Vec::new(); +/// A list of disk stats. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct DiskStats(pub Vec); - for line in reader.lines() { - let line = line?; - v.push(DiskStat::from_line(&line)?); +impl crate::FromBufRead for DiskStats { + fn from_buf_read(r: R) -> ProcResult { + let mut v = Vec::new(); + + for line in r.lines() { + let line = line?; + v.push(DiskStat::from_line(&line)?); + } + Ok(DiskStats(v)) } - Ok(v) } impl DiskStat { @@ -150,13 +154,3 @@ impl DiskStat { }) } } - -#[cfg(test)] -mod tests { - #[test] - fn diskstat() { - for disk in super::diskstats().unwrap() { - println!("{:?}", disk); - } - } -} diff --git a/procfs-core/src/keyring.rs b/procfs-core/src/keyring.rs new file mode 100644 index 00000000..3d504f3e --- /dev/null +++ b/procfs-core/src/keyring.rs @@ -0,0 +1,423 @@ +//! Functions related to the in-kernel key management and retention facility +//! +//! For more details on this facility, see the `keyrings(7)` man page. +//! +//! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module. +use crate::{build_internal_error, expect, from_str, ProcResult}; +use bitflags::bitflags; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, io::BufRead, time::Duration}; + +bitflags! { + /// Various key flags + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct KeyFlags: u32 { + /// The key has been instantiated + const INSTANTIATED = 0x01; + /// THe key has been revoked + const REVOKED = 0x02; + /// The key is dead + /// + /// I.e. the key type has been unregistered. A key may be briefly in this state during garbage collection. + const DEAD = 0x04; + /// The key contributes to the user's quota + const QUOTA = 0x08; + /// The key is under construction via a callback to user space + const UNDER_CONSTRUCTION = 0x10; + /// The key is negatively instantiated + const NEGATIVE = 0x20; + /// The key has been invalidated + const INVALID = 0x40; + } +} + +bitflags! { + /// Bitflags that represent the permissions for a key + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct PermissionFlags: u32 { + /// The attributes of the key may be read + /// + /// This includes the type, description, and access rights (excluding the security label) + const VIEW = 0x01; + /// For a key: the payload of the key may be read. For a keyring: the list of serial numbers (keys) to which the keyring has links may be read. + const READ = 0x02; + /// The payload of the key may be updated and the key may be revoked. + /// + /// For a keyring, links may be added to or removed from the keyring, and the keyring + /// may be cleared completely (all links are removed). + const WRITE = 0x04; + /// The key may be found by a search. + /// + /// For keyrings: keys and keyrings that are linked to by the keyring may be searched. + const SEARCH = 0x08; + /// Links may be created from keyrings to the key. + /// + /// The initial link to a key that is established when the key is created doesn't require this permission. + const LINK = 0x10; + /// The ownership details and security label of the key may be changed, the key's expiration + /// time may be set, and the key may be revoked. + const SETATTR = 0x20; + const ALL = Self::VIEW.bits | Self::READ.bits | Self::WRITE.bits | Self::SEARCH.bits | Self::LINK.bits | Self::SETATTR.bits; + } +} + +impl KeyFlags { + fn from_str(s: &str) -> KeyFlags { + let mut me = KeyFlags::empty(); + + let mut chars = s.chars(); + match chars.next() { + Some(c) if c == 'I' => me.insert(KeyFlags::INSTANTIATED), + _ => {} + } + match chars.next() { + Some(c) if c == 'R' => me.insert(KeyFlags::REVOKED), + _ => {} + } + match chars.next() { + Some(c) if c == 'D' => me.insert(KeyFlags::DEAD), + _ => {} + } + match chars.next() { + Some(c) if c == 'Q' => me.insert(KeyFlags::QUOTA), + _ => {} + } + match chars.next() { + Some(c) if c == 'U' => me.insert(KeyFlags::UNDER_CONSTRUCTION), + _ => {} + } + match chars.next() { + Some(c) if c == 'N' => me.insert(KeyFlags::NEGATIVE), + _ => {} + } + match chars.next() { + Some(c) if c == 'i' => me.insert(KeyFlags::INVALID), + _ => {} + } + + me + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Permissions { + pub possessor: PermissionFlags, + pub user: PermissionFlags, + pub group: PermissionFlags, + pub other: PermissionFlags, +} +impl Permissions { + fn from_str(s: &str) -> ProcResult { + let possessor = PermissionFlags::from_bits(from_str!(u32, &s[0..2], 16)) + .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; + + let user = PermissionFlags::from_bits(from_str!(u32, &s[2..4], 16)) + .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; + + let group = PermissionFlags::from_bits(from_str!(u32, &s[4..6], 16)) + .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; + + let other = PermissionFlags::from_bits(from_str!(u32, &s[6..8], 16)) + .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; + + Ok(Permissions { + possessor, + user, + group, + other, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum KeyTimeout { + Permanent, + Expired, + Timeout(Duration), +} + +impl KeyTimeout { + fn from_str(s: &str) -> ProcResult { + if s == "perm" { + Ok(KeyTimeout::Permanent) + } else if s == "expd" { + Ok(KeyTimeout::Expired) + } else { + let (val, unit) = s.split_at(s.len() - 1); + let val = from_str!(u64, val); + match unit { + "s" => Ok(KeyTimeout::Timeout(Duration::from_secs(val))), + "m" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60))), + "h" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60))), + "d" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24))), + "w" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24 * 7))), + _ => Err(build_internal_error!(format!("Unable to parse keytimeout of {:?}", s))), + } + } + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum KeyType { + /// This is a general-purpose key type. + /// + /// The key is kept entirely within kernel memory. The payload may be read and updated by + /// user-space applications. The payload for keys of this type is a blob of arbitrary + /// data of up to 32,767 bytes. + + /// The description may be any valid string, though it is preferred that it start + /// with a colon-delimited prefix representing the service to which the key is of + /// interest (for instance "afs:mykey"). + User, + + /// Keyrings are special keys which store a set of links to other keys (including + /// other keyrings), analogous to a directory holding links to files. The main + /// purpose of a keyring is to prevent other keys from being garbage collected + /// because nothing refers to them. + /// + /// Keyrings with descriptions (names) that begin with a period ('.') are re‐ + /// served to the implementation. + Keyring, + + /// This key type is essentially the same as "user", but it does not provide + /// reading (i.e., the keyctl(2) KEYCTL_READ operation), meaning that the key + /// payload is never visible from user space. This is suitable for storing user‐ + /// name-password pairs that should not be readable from user space. + /// + /// The description of a "logon" key must start with a non-empty colon-delimited + /// prefix whose purpose is to identify the service to which the key belongs. + /// (Note that this differs from keys of the "user" type, where the inclusion of + /// a prefix is recommended but is not enforced.) + Logon, + + /// This key type is similar to the "user" key type, but it may hold a payload of + /// up to 1 MiB in size. This key type is useful for purposes such as holding + /// Kerberos ticket caches. + /// + /// The payload data may be stored in a tmpfs filesystem, rather than in kernel + /// memory, if the data size exceeds the overhead of storing the data in the + /// filesystem. (Storing the data in a filesystem requires filesystem structures + /// to be allocated in the kernel. The size of these structures determines the + /// size threshold above which the tmpfs storage method is used.) Since Linux + /// 4.8, the payload data is encrypted when stored in tmpfs, thereby preventing + /// it from being written unencrypted into swap space. + BigKey, + + /// Other specialized, but rare keys types + Other(String), +} + +impl KeyType { + fn from_str(s: &str) -> KeyType { + match s { + "keyring" => KeyType::Keyring, + "user" => KeyType::User, + "logon" => KeyType::Logon, + "big_key" => KeyType::BigKey, + other => KeyType::Other(other.to_string()), + } + } +} + +/// A key +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Key { + /// The ID (serial number) of the key + pub id: u64, + + /// A set of flags describing the state of the key + pub flags: KeyFlags, + + /// Count of the number of kernel credential structures that are + /// pinning the key (approximately: the number of threads and open file + /// references that refer to this key). + pub usage: u32, + + /// Key timeout + pub timeout: KeyTimeout, + + /// Key permissions + pub permissions: Permissions, + + /// The user ID of the key owner + pub uid: u32, + + /// The group ID of the key. + /// + /// The value of `None` here means that the key has no group ID; this can occur in certain circumstances for + /// keys created by the kernel. + pub gid: Option, + + /// The type of key + pub key_type: KeyType, + + /// The key description + pub description: String, +} + +impl Key { + fn from_line(s: &str) -> ProcResult { + let mut s = s.split_whitespace(); + + let id = from_str!(u64, expect!(s.next()), 16); + let s_flags = expect!(s.next()); + let usage = from_str!(u32, expect!(s.next())); + let s_timeout = expect!(s.next()); + let s_perms = expect!(s.next()); + let uid = from_str!(u32, expect!(s.next())); + let s_gid = expect!(s.next()); + let s_type = expect!(s.next()); + let desc: Vec<_> = s.collect(); + + Ok(Key { + id, + flags: KeyFlags::from_str(s_flags), + usage, + timeout: KeyTimeout::from_str(s_timeout)?, + permissions: Permissions::from_str(s_perms)?, + uid, + gid: if s_gid == "-1" { + None + } else { + Some(from_str!(u32, s_gid)) + }, + key_type: KeyType::from_str(s_type), + description: desc.join(" "), + }) + } +} + +/// A set of keys. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Keys(pub Vec); + +impl crate::FromBufRead for Keys { + fn from_buf_read(r: R) -> ProcResult { + let mut v = Vec::new(); + + for line in r.lines() { + let line = line?; + v.push(Key::from_line(&line)?); + } + Ok(Keys(v)) + } +} + +/// Information about a user with at least one key +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct KeyUser { + /// The user that owns the key + pub uid: u32, + /// The kernel-internal usage count for the kernel structure used to record key users + pub usage: u32, + /// The total number of keys owned by the user + pub nkeys: u32, + /// THe number of keys that have been instantiated + pub nikeys: u32, + /// The number of keys owned by the user + pub qnkeys: u32, + /// The maximum number of keys that the user may own + pub maxkeys: u32, + /// The number of bytes consumed in playloads of the keys owned by this user + pub qnbytes: u32, + /// The upper limit on the number of bytes in key payloads for this user + pub maxbytes: u32, +} + +impl KeyUser { + fn from_str(s: &str) -> ProcResult { + let mut s = s.split_whitespace(); + let uid = expect!(s.next()); + let usage = from_str!(u32, expect!(s.next())); + let keys = expect!(s.next()); + let qkeys = expect!(s.next()); + let qbytes = expect!(s.next()); + + let (nkeys, nikeys) = { + let mut s = keys.split('/'); + (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) + }; + let (qnkeys, maxkeys) = { + let mut s = qkeys.split('/'); + (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) + }; + let (qnbytes, maxbytes) = { + let mut s = qbytes.split('/'); + (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) + }; + + Ok(KeyUser { + uid: from_str!(u32, &uid[0..uid.len() - 1]), + usage, + nkeys, + nikeys, + qnkeys, + maxkeys, + qnbytes, + maxbytes, + }) + } +} + +/// Information about a set of users with at least one key. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct KeyUsers(pub HashMap); + +impl crate::FromBufRead for KeyUsers { + fn from_buf_read(r: R) -> ProcResult { + let mut map = HashMap::new(); + + for line in r.lines() { + let line = line?; + let user = KeyUser::from_str(&line)?; + map.insert(user.uid, user); + } + Ok(KeyUsers(map)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn key_flags() { + assert_eq!(KeyFlags::from_str("I------"), KeyFlags::INSTANTIATED); + assert_eq!(KeyFlags::from_str("IR"), KeyFlags::INSTANTIATED | KeyFlags::REVOKED); + assert_eq!(KeyFlags::from_str("IRDQUNi"), KeyFlags::all()); + } + + #[test] + fn timeout() { + assert_eq!(KeyTimeout::from_str("perm").unwrap(), KeyTimeout::Permanent); + assert_eq!(KeyTimeout::from_str("expd").unwrap(), KeyTimeout::Expired); + assert_eq!( + KeyTimeout::from_str("2w").unwrap(), + KeyTimeout::Timeout(Duration::from_secs(1209600)) + ); + assert_eq!( + KeyTimeout::from_str("14d").unwrap(), + KeyTimeout::Timeout(Duration::from_secs(1209600)) + ); + assert_eq!( + KeyTimeout::from_str("336h").unwrap(), + KeyTimeout::Timeout(Duration::from_secs(1209600)) + ); + assert_eq!( + KeyTimeout::from_str("20160m").unwrap(), + KeyTimeout::Timeout(Duration::from_secs(1209600)) + ); + assert_eq!( + KeyTimeout::from_str("1209600s").unwrap(), + KeyTimeout::Timeout(Duration::from_secs(1209600)) + ); + } +} diff --git a/procfs-core/src/lib.rs b/procfs-core/src/lib.rs index a385edf4..6e07ccca 100644 --- a/procfs-core/src/lib.rs +++ b/procfs-core/src/lib.rs @@ -256,6 +256,8 @@ pub trait SystemInfoInterface { fn boot_time_secs(&self) -> ProcResult; fn ticks_per_second(&self) -> u64; fn page_size(&self) -> u64; + /// Whether the system is little endian (true) or big endian (false). + fn is_little_endian(&self) -> bool; #[cfg(feature = "chrono")] fn boot_time(&self) -> ProcResult> { @@ -275,6 +277,7 @@ pub struct ExplicitSystemInfo { pub boot_time_secs: u64, pub ticks_per_second: u64, pub page_size: u64, + pub is_little_endian: bool, } impl SystemInfoInterface for ExplicitSystemInfo { @@ -289,6 +292,10 @@ impl SystemInfoInterface for ExplicitSystemInfo { fn page_size(&self) -> u64 { self.page_size } + + fn is_little_endian(&self) -> bool { + self.is_little_endian + } } /// Values which can provide an output given the [SystemInfo]. @@ -339,16 +346,42 @@ where } } -pub mod process; - mod cgroups; pub use cgroups::*; -pub mod sys; -pub use sys::kernel::Version as KernelVersion; + +mod cpuinfo; +pub use cpuinfo::*; + +mod diskstats; +pub use diskstats::*; mod iomem; pub use iomem::*; +pub mod keyring; + +mod locks; +pub use locks::*; + +mod meminfo; +pub use meminfo::*; + +pub mod net; + +mod pressure; +pub use pressure::*; + +pub mod process; + +pub mod sys; +pub use sys::kernel::Version as KernelVersion; + +mod sysvipc_shm; +pub use sysvipc_shm::*; + +mod uptime; +pub use uptime::*; + // TODO temporary, only for procfs pub trait FromStrRadix: Sized { fn from_str_radix(t: &str, radix: u32) -> Result; diff --git a/procfs/src/locks.rs b/procfs-core/src/locks.rs similarity index 85% rename from procfs/src/locks.rs rename to procfs-core/src/locks.rs index a80919f1..d10cbedc 100644 --- a/procfs/src/locks.rs +++ b/procfs-core/src/locks.rs @@ -1,5 +1,5 @@ -use crate::{expect, from_str, FileWrapper, ProcResult}; -use std::io::{BufRead, BufReader}; +use crate::{expect, from_str, ProcResult}; +use std::io::BufRead; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -192,43 +192,25 @@ impl Lock { } } -/// Get a list of current file locks and leases -/// -/// Since Linux 4.9, the list of locks is filtered to show just the locks -/// for the processes in the PID namespace for which the `/proc` filesystem -/// was mounted. -pub fn locks() -> ProcResult> { - let file = FileWrapper::open("/proc/locks")?; - let reader = BufReader::new(file); - let mut v = Vec::new(); +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +/// Details about file locks +pub struct Locks(pub Vec); + +impl super::FromBufRead for Locks { + fn from_buf_read(r: R) -> ProcResult { + let mut v = Vec::new(); - for line in reader.lines() { - let line = line?; - v.push(Lock::from_line(&line)?); + for line in r.lines() { + let line = line?; + v.push(Lock::from_line(&line)?); + } + Ok(Locks(v)) } - Ok(v) } #[cfg(test)] mod tests { - use crate::{locks, LockKind, LockMode, LockType}; - - #[test] - fn live() { - for lock in locks().unwrap() { - println!("{:?}", lock); - if let LockType::Other(s) = lock.lock_type { - panic!("Found an unknown lock type {:?}", s); - } - if let LockKind::Other(s) = lock.kind { - panic!("Found an unknown lock kind {:?}", s); - } - if let LockMode::Other(s) = lock.mode { - panic!("Found an unknown lock mode {:?}", s); - } - } - } - #[test] fn test_blocked() { let data = r#"1: POSIX ADVISORY WRITE 723 00:14:16845 0 EOF diff --git a/procfs/src/meminfo.rs b/procfs-core/src/meminfo.rs similarity index 67% rename from procfs/src/meminfo.rs rename to procfs-core/src/meminfo.rs index f675ec6a..4a17fa7c 100644 --- a/procfs/src/meminfo.rs +++ b/procfs-core/src/meminfo.rs @@ -1,9 +1,17 @@ -use std::io; - -use super::{convert_to_kibibytes, expect, from_str, FileWrapper, ProcResult}; - +use super::{expect, from_str, ProcResult}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, io}; + +fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult { + match unit { + "B" => Ok(num), + "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024), + "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024), + "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024), + unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))), + } +} /// This struct reports statistics about memory usage on the system, based on /// the `/proc/meminfo` file. @@ -294,23 +302,11 @@ pub struct Meminfo { pub z_swapped: Option, } -impl Meminfo { - /// Reads and parses the `/proc/meminfo`, returning an error if there are problems. - pub fn new() -> ProcResult { - let f = FileWrapper::open("/proc/meminfo")?; - - Meminfo::from_reader(f) - } - - /// Get Meminfo from a custom Read instead of the default `/proc/meminfo`. - pub fn from_reader(r: R) -> ProcResult { - use std::collections::HashMap; - use std::io::{BufRead, BufReader}; - - let reader = BufReader::new(r); +impl super::FromBufRead for Meminfo { + fn from_buf_read(r: R) -> ProcResult { let mut map = HashMap::new(); - for line in reader.lines() { + for line in r.lines() { let line = expect!(line); if line.is_empty() { continue; @@ -407,210 +403,3 @@ impl Meminfo { Ok(meminfo) } } - -#[cfg(test)] -mod test { - use super::*; - use crate::{prelude::*, KernelConfig, KernelVersion}; - - #[allow(clippy::cognitive_complexity)] - #[allow(clippy::blocks_in_if_conditions)] - #[test] - fn test_meminfo() { - // TRAVIS - // we don't have access to the kernel_config on travis, so skip that test there - match ::std::env::var("TRAVIS") { - Ok(ref s) if s == "true" => return, - _ => {} - } - - let kernel = KernelVersion::current().unwrap(); - let config = KernelConfig::current().ok(); - - let meminfo = Meminfo::new().unwrap(); - println!("{:#?}", meminfo); - - // for the fields that are only present in some kernel versions, make sure our - // actual kernel agrees - - if kernel >= KernelVersion::new(3, 14, 0) { - assert!(meminfo.mem_available.is_some()); - } - - if kernel >= KernelVersion::new(2, 6, 28) { - assert!(meminfo.active_anon.is_some()); - assert!(meminfo.inactive_anon.is_some()); - assert!(meminfo.active_file.is_some()); - assert!(meminfo.inactive_file.is_some()); - } else { - assert!(meminfo.active_anon.is_none()); - assert!(meminfo.inactive_anon.is_none()); - assert!(meminfo.active_file.is_none()); - assert!(meminfo.inactive_file.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 28) - && kernel <= KernelVersion::new(2, 6, 30) - && meminfo.unevictable.is_some() - { - if let Some(KernelConfig(ref config)) = config { - assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some()); - } - } - - if kernel >= KernelVersion::new(2, 6, 19) - && config - .as_ref() - .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HIGHMEM")) - { - assert!(meminfo.high_total.is_some()); - assert!(meminfo.high_free.is_some()); - assert!(meminfo.low_total.is_some()); - assert!(meminfo.low_free.is_some()); - } else { - assert!(meminfo.high_total.is_none()); - assert!(meminfo.high_free.is_none()); - assert!(meminfo.low_total.is_none()); - assert!(meminfo.low_free.is_none()); - } - - // Possible bug in procfs documentation: - // The man page says that MmapCopy requires CONFIG_MMU, but if you look at the - // source, MmapCopy is only included if CONFIG_MMU is *missing*: - // /~https://github.com/torvalds/linux/blob/v4.17/fs/proc/meminfo.c#L80 - //if kernel >= KernelVersion::new(2, 6, 29) && config.contains_key("CONFIG_MMU") { - // assert!(meminfo.mmap_copy.is_some()); - //} else { - // assert!(meminfo.mmap_copy.is_none()); - //} - - if kernel >= KernelVersion::new(2, 6, 18) { - assert!(meminfo.anon_pages.is_some()); - assert!(meminfo.page_tables.is_some()); - assert!(meminfo.nfs_unstable.is_some()); - assert!(meminfo.bounce.is_some()); - } else { - assert!(meminfo.anon_pages.is_none()); - assert!(meminfo.page_tables.is_none()); - assert!(meminfo.nfs_unstable.is_none()); - assert!(meminfo.bounce.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 32) { - assert!(meminfo.shmem.is_some()); - assert!(meminfo.kernel_stack.is_some()); - } else { - assert!(meminfo.shmem.is_none()); - assert!(meminfo.kernel_stack.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 19) { - assert!(meminfo.s_reclaimable.is_some()); - assert!(meminfo.s_unreclaim.is_some()); - } else { - assert!(meminfo.s_reclaimable.is_none()); - assert!(meminfo.s_unreclaim.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 27) - && config - .as_ref() - .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_QUICKLIST")) - { - assert!(meminfo.quicklists.is_some()); - } else { - assert!(meminfo.quicklists.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 26) { - assert!(meminfo.writeback_tmp.is_some()); - } else { - assert!(meminfo.writeback_tmp.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 10) { - assert!(meminfo.commit_limit.is_some()); - } else { - assert!(meminfo.commit_limit.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 32) - && config.as_ref().map_or( - std::path::Path::new("/proc/kpagecgroup").exists(), - |KernelConfig(cfg)| cfg.contains_key("CONFIG_MEMORY_FAILURE"), - ) - { - assert!(meminfo.hardware_corrupted.is_some()); - } else { - assert!(meminfo.hardware_corrupted.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 38) - && config.as_ref().map_or(false, |KernelConfig(cfg)| { - cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") - }) - { - assert!(meminfo.anon_hugepages.is_some()); - } else { - // SOme distributions may backport this option into older kernels - // assert!(meminfo.anon_hugepages.is_none()); - } - - if kernel >= KernelVersion::new(4, 8, 0) - && config.as_ref().map_or(true, |KernelConfig(cfg)| { - cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") - }) - { - assert!(meminfo.shmem_hugepages.is_some()); - assert!(meminfo.shmem_pmd_mapped.is_some()); - } else { - assert!(meminfo.shmem_hugepages.is_none()); - assert!(meminfo.shmem_pmd_mapped.is_none()); - } - - if kernel >= KernelVersion::new(3, 1, 0) - && config - .as_ref() - .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_CMA")) - { - assert!(meminfo.cma_total.is_some()); - assert!(meminfo.cma_free.is_some()); - } else { - assert!(meminfo.cma_total.is_none()); - assert!(meminfo.cma_free.is_none()); - } - - if config - .as_ref() - .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) - { - assert!(meminfo.hugepages_total.is_some()); - assert!(meminfo.hugepages_free.is_some()); - assert!(meminfo.hugepagesize.is_some()); - } else { - assert!(meminfo.hugepages_total.is_none()); - assert!(meminfo.hugepages_free.is_none()); - assert!(meminfo.hugepagesize.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 17) - && config - .as_ref() - .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) - { - assert!(meminfo.hugepages_rsvd.is_some()); - } else { - assert!(meminfo.hugepages_rsvd.is_none()); - } - - if kernel >= KernelVersion::new(2, 6, 24) - && config - .as_ref() - .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) - { - assert!(meminfo.hugepages_surp.is_some()); - } else { - assert!(meminfo.hugepages_surp.is_none()); - } - } -} diff --git a/procfs-core/src/net.rs b/procfs-core/src/net.rs new file mode 100644 index 00000000..1da96128 --- /dev/null +++ b/procfs-core/src/net.rs @@ -0,0 +1,710 @@ +// Don't throw clippy warnings for manual string stripping. +// The suggested fix with `strip_prefix` removes support for Rust 1.33 and 1.38 +#![allow(clippy::manual_strip)] + +//! Information about the networking layer. +//! +//! This module corresponds to the `/proc/net` directory and contains various information about the +//! networking layer. +use crate::ProcResult; +use crate::{build_internal_error, expect, from_iter, from_str}; +use std::collections::HashMap; + +use bitflags::bitflags; +use std::io::BufRead; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::{path::PathBuf, str::FromStr}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum TcpState { + Established = 1, + SynSent, + SynRecv, + FinWait1, + FinWait2, + TimeWait, + Close, + CloseWait, + LastAck, + Listen, + Closing, + NewSynRecv, +} + +impl TcpState { + pub fn from_u8(num: u8) -> Option { + match num { + 0x01 => Some(TcpState::Established), + 0x02 => Some(TcpState::SynSent), + 0x03 => Some(TcpState::SynRecv), + 0x04 => Some(TcpState::FinWait1), + 0x05 => Some(TcpState::FinWait2), + 0x06 => Some(TcpState::TimeWait), + 0x07 => Some(TcpState::Close), + 0x08 => Some(TcpState::CloseWait), + 0x09 => Some(TcpState::LastAck), + 0x0A => Some(TcpState::Listen), + 0x0B => Some(TcpState::Closing), + 0x0C => Some(TcpState::NewSynRecv), + _ => None, + } + } + + pub fn to_u8(&self) -> u8 { + match self { + TcpState::Established => 0x01, + TcpState::SynSent => 0x02, + TcpState::SynRecv => 0x03, + TcpState::FinWait1 => 0x04, + TcpState::FinWait2 => 0x05, + TcpState::TimeWait => 0x06, + TcpState::Close => 0x07, + TcpState::CloseWait => 0x08, + TcpState::LastAck => 0x09, + TcpState::Listen => 0x0A, + TcpState::Closing => 0x0B, + TcpState::NewSynRecv => 0x0C, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum UdpState { + Established = 1, + Close = 7, +} + +impl UdpState { + pub fn from_u8(num: u8) -> Option { + match num { + 0x01 => Some(UdpState::Established), + 0x07 => Some(UdpState::Close), + _ => None, + } + } + + pub fn to_u8(&self) -> u8 { + match self { + UdpState::Established => 0x01, + UdpState::Close => 0x07, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum UnixState { + UNCONNECTED = 1, + CONNECTING = 2, + CONNECTED = 3, + DISCONNECTING = 4, +} + +impl UnixState { + pub fn from_u8(num: u8) -> Option { + match num { + 0x01 => Some(UnixState::UNCONNECTED), + 0x02 => Some(UnixState::CONNECTING), + 0x03 => Some(UnixState::CONNECTED), + 0x04 => Some(UnixState::DISCONNECTING), + _ => None, + } + } + + pub fn to_u8(&self) -> u8 { + match self { + UnixState::UNCONNECTED => 0x01, + UnixState::CONNECTING => 0x02, + UnixState::CONNECTED => 0x03, + UnixState::DISCONNECTING => 0x04, + } + } +} + +/// An entry in the TCP socket table +#[derive(Debug, Clone)] +#[non_exhaustive] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct TcpNetEntry { + pub local_address: SocketAddr, + pub remote_address: SocketAddr, + pub state: TcpState, + pub rx_queue: u32, + pub tx_queue: u32, + pub uid: u32, + pub inode: u64, +} + +/// An entry in the UDP socket table +#[derive(Debug, Clone)] +#[non_exhaustive] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UdpNetEntry { + pub local_address: SocketAddr, + pub remote_address: SocketAddr, + pub state: UdpState, + pub rx_queue: u32, + pub tx_queue: u32, + pub uid: u32, + pub inode: u64, +} + +/// An entry in the Unix socket table +#[derive(Debug, Clone)] +#[non_exhaustive] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UnixNetEntry { + /// The number of users of the socket + pub ref_count: u32, + /// The socket type. + /// + /// Possible values are `SOCK_STREAM`, `SOCK_DGRAM`, or `SOCK_SEQPACKET`. These constants can + /// be found in the libc crate. + pub socket_type: u16, + /// The state of the socket + pub state: UnixState, + /// The inode number of the socket + pub inode: u64, + /// The bound pathname (if any) of the socket. + /// + /// Sockets in the abstract namespace are included, and are shown with a path that commences + /// with the '@' character. + pub path: Option, +} + +/// Parses an address in the form 00010203:1234 +/// +/// Also supports IPv6 +fn parse_addressport_str(s: &str, little_endian: bool) -> ProcResult { + let mut las = s.split(':'); + let ip_part = expect!(las.next(), "ip_part"); + let port = expect!(las.next(), "port"); + let port = from_str!(u16, port, 16); + + use std::convert::TryInto; + + let read_u32 = if little_endian { + u32::from_le_bytes + } else { + u32::from_be_bytes + }; + + if ip_part.len() == 8 { + let bytes = expect!(hex::decode(ip_part)); + let ip_u32 = read_u32(bytes[..4].try_into().unwrap()); + + let ip = Ipv4Addr::from(ip_u32); + + Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) + } else if ip_part.len() == 32 { + let bytes = expect!(hex::decode(ip_part)); + + let ip_a = read_u32(bytes[0..4].try_into().unwrap()); + let ip_b = read_u32(bytes[4..8].try_into().unwrap()); + let ip_c = read_u32(bytes[8..12].try_into().unwrap()); + let ip_d = read_u32(bytes[12..16].try_into().unwrap()); + + let ip = Ipv6Addr::new( + ((ip_a >> 16) & 0xffff) as u16, + (ip_a & 0xffff) as u16, + ((ip_b >> 16) & 0xffff) as u16, + (ip_b & 0xffff) as u16, + ((ip_c >> 16) & 0xffff) as u16, + (ip_c & 0xffff) as u16, + ((ip_d >> 16) & 0xffff) as u16, + (ip_d & 0xffff) as u16, + ); + + Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0))) + } else { + Err(build_internal_error!(format!( + "Unable to parse {:?} as an address:port", + s + ))) + } +} + +/// TCP socket entries. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct TcpNetEntries(pub Vec); + +impl super::FromBufReadSI for TcpNetEntries { + fn from_buf_read(r: R, system_info: &crate::SystemInfo) -> ProcResult { + let mut vec = Vec::new(); + + // first line is a header we need to skip + for line in r.lines().skip(1) { + let line = line?; + let mut s = line.split_whitespace(); + s.next(); + let local_address = expect!(s.next(), "tcp::local_address"); + let rem_address = expect!(s.next(), "tcp::rem_address"); + let state = expect!(s.next(), "tcp::st"); + let mut tx_rx_queue = expect!(s.next(), "tcp::tx_queue:rx_queue").splitn(2, ':'); + let tx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::tx_queue"), 16); + let rx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::rx_queue"), 16); + s.next(); // skip tr and tm->when + s.next(); // skip retrnsmt + let uid = from_str!(u32, expect!(s.next(), "tcp::uid")); + s.next(); // skip timeout + let inode = expect!(s.next(), "tcp::inode"); + + vec.push(TcpNetEntry { + local_address: parse_addressport_str(local_address, system_info.is_little_endian())?, + remote_address: parse_addressport_str(rem_address, system_info.is_little_endian())?, + rx_queue, + tx_queue, + state: expect!(TcpState::from_u8(from_str!(u8, state, 16))), + uid, + inode: from_str!(u64, inode), + }); + } + + Ok(TcpNetEntries(vec)) + } +} + +/// UDP socket entries. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UdpNetEntries(pub Vec); + +impl super::FromBufReadSI for UdpNetEntries { + fn from_buf_read(r: R, system_info: &crate::SystemInfo) -> ProcResult { + let mut vec = Vec::new(); + + // first line is a header we need to skip + for line in r.lines().skip(1) { + let line = line?; + let mut s = line.split_whitespace(); + s.next(); + let local_address = expect!(s.next(), "udp::local_address"); + let rem_address = expect!(s.next(), "udp::rem_address"); + let state = expect!(s.next(), "udp::st"); + let mut tx_rx_queue = expect!(s.next(), "udp::tx_queue:rx_queue").splitn(2, ':'); + let tx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::tx_queue"), 16); + let rx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::rx_queue"), 16); + s.next(); // skip tr and tm->when + s.next(); // skip retrnsmt + let uid = from_str!(u32, expect!(s.next(), "udp::uid")); + s.next(); // skip timeout + let inode = expect!(s.next(), "udp::inode"); + + vec.push(UdpNetEntry { + local_address: parse_addressport_str(local_address, system_info.is_little_endian())?, + remote_address: parse_addressport_str(rem_address, system_info.is_little_endian())?, + rx_queue, + tx_queue, + state: expect!(UdpState::from_u8(from_str!(u8, state, 16))), + uid, + inode: from_str!(u64, inode), + }); + } + + Ok(UdpNetEntries(vec)) + } +} + +/// Unix socket entries. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UnixNetEntries(pub Vec); + +impl super::FromBufRead for UnixNetEntries { + fn from_buf_read(r: R) -> ProcResult { + let mut vec = Vec::new(); + + // first line is a header we need to skip + for line in r.lines().skip(1) { + let line = line?; + let mut s = line.split_whitespace(); + s.next(); // skip table slot number + let ref_count = from_str!(u32, expect!(s.next()), 16); + s.next(); // skip protocol, always zero + s.next(); // skip internal kernel flags + let socket_type = from_str!(u16, expect!(s.next()), 16); + let state = from_str!(u8, expect!(s.next()), 16); + let inode = from_str!(u64, expect!(s.next())); + let path = s.next().map(PathBuf::from); + + vec.push(UnixNetEntry { + ref_count, + socket_type, + inode, + state: expect!(UnixState::from_u8(state)), + path, + }); + } + + Ok(UnixNetEntries(vec)) + } +} + +/// An entry in the ARP table +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct ARPEntry { + /// IPv4 address + pub ip_address: Ipv4Addr, + /// Hardware type + /// + /// This will almost always be ETHER (or maybe INFINIBAND) + pub hw_type: ARPHardware, + /// Internal kernel flags + pub flags: ARPFlags, + /// MAC Address + pub hw_address: Option<[u8; 6]>, + /// Device name + pub device: String, +} + +bitflags! { + /// Hardware type for an ARP table entry. + // source: include/uapi/linux/if_arp.h + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct ARPHardware: u32 { + /// NET/ROM pseudo + const NETROM = 0; + /// Ethernet + const ETHER = 1; + /// Experimental ethernet + const EETHER = 2; + /// AX.25 Level 2 + const AX25 = 3; + /// PROnet token ring + const PRONET = 4; + /// Chaosnet + const CHAOS = 5; + /// IEEE 802.2 Ethernet/TR/TB + const IEEE802 = 6; + /// Arcnet + const ARCNET = 7; + /// APPLEtalk + const APPLETLK = 8; + /// Frame Relay DLCI + const DLCI = 15; + /// ATM + const ATM = 19; + /// Metricom STRIP + const METRICOM = 23; + //// IEEE 1394 IPv4 - RFC 2734 + const IEEE1394 = 24; + /// EUI-64 + const EUI64 = 27; + /// InfiniBand + const INFINIBAND = 32; + } +} + +bitflags! { + /// Flags for ARP entries + // source: include/uapi/linux/if_arp.h + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + pub struct ARPFlags: u32 { + /// Completed entry + const COM = 0x02; + /// Permanent entry + const PERM = 0x04; + /// Publish entry + const PUBL = 0x08; + /// Has requested trailers + const USETRAILERS = 0x10; + /// Want to use a netmask (only for proxy entries) + const NETMASK = 0x20; + // Don't answer this address + const DONTPUB = 0x40; + } +} + +/// ARP table entries. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct ArpEntries(pub Vec); + +impl super::FromBufRead for ArpEntries { + fn from_buf_read(r: R) -> ProcResult { + let mut vec = Vec::new(); + + // First line is a header we need to skip + for line in r.lines().skip(1) { + // Check if there might have been an IO error. + let line = line?; + let mut line = line.split_whitespace(); + let ip_address = expect!(Ipv4Addr::from_str(expect!(line.next()))); + let hw = from_str!(u32, &expect!(line.next())[2..], 16); + let hw = ARPHardware::from_bits_truncate(hw); + let flags = from_str!(u32, &expect!(line.next())[2..], 16); + let flags = ARPFlags::from_bits_truncate(flags); + + let mac = expect!(line.next()); + let mut mac: Vec> = mac.split(':').map(|s| Ok(from_str!(u8, s, 16))).collect(); + + let mac = if mac.len() == 6 { + let mac_block_f = mac.pop().unwrap()?; + let mac_block_e = mac.pop().unwrap()?; + let mac_block_d = mac.pop().unwrap()?; + let mac_block_c = mac.pop().unwrap()?; + let mac_block_b = mac.pop().unwrap()?; + let mac_block_a = mac.pop().unwrap()?; + if mac_block_a == 0 + && mac_block_b == 0 + && mac_block_c == 0 + && mac_block_d == 0 + && mac_block_e == 0 + && mac_block_f == 0 + { + None + } else { + Some([ + mac_block_a, + mac_block_b, + mac_block_c, + mac_block_d, + mac_block_e, + mac_block_f, + ]) + } + } else { + None + }; + + // mask is always "*" + let _mask = expect!(line.next()); + let dev = expect!(line.next()); + + vec.push(ARPEntry { + ip_address, + hw_type: hw, + flags, + hw_address: mac, + device: dev.to_string(), + }) + } + + Ok(ArpEntries(vec)) + } +} + +/// General statistics for a network interface/device +/// +/// For an example, see the [interface_stats.rs](/~https://github.com/eminence/procfs/tree/master/examples) +/// example in the source repo. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct DeviceStatus { + /// Name of the interface + pub name: String, + /// Total bytes received + pub recv_bytes: u64, + /// Total packets received + pub recv_packets: u64, + /// Bad packets received + pub recv_errs: u64, + /// Packets dropped + pub recv_drop: u64, + /// Fifo overrun + pub recv_fifo: u64, + /// Frame alignment errors + pub recv_frame: u64, + /// Number of compressed packets received + pub recv_compressed: u64, + /// Number of multicast packets received + pub recv_multicast: u64, + + /// Total bytes transmitted + pub sent_bytes: u64, + /// Total packets transmitted + pub sent_packets: u64, + /// Number of transmission errors + pub sent_errs: u64, + /// Number of packets dropped during transmission + pub sent_drop: u64, + pub sent_fifo: u64, + /// Number of collisions + pub sent_colls: u64, + /// Number of packets not sent due to carrier errors + pub sent_carrier: u64, + /// Number of compressed packets transmitted + pub sent_compressed: u64, +} + +impl DeviceStatus { + fn from_str(s: &str) -> ProcResult { + let mut split = s.split_whitespace(); + let name: String = expect!(from_iter(&mut split)); + let recv_bytes = expect!(from_iter(&mut split)); + let recv_packets = expect!(from_iter(&mut split)); + let recv_errs = expect!(from_iter(&mut split)); + let recv_drop = expect!(from_iter(&mut split)); + let recv_fifo = expect!(from_iter(&mut split)); + let recv_frame = expect!(from_iter(&mut split)); + let recv_compressed = expect!(from_iter(&mut split)); + let recv_multicast = expect!(from_iter(&mut split)); + let sent_bytes = expect!(from_iter(&mut split)); + let sent_packets = expect!(from_iter(&mut split)); + let sent_errs = expect!(from_iter(&mut split)); + let sent_drop = expect!(from_iter(&mut split)); + let sent_fifo = expect!(from_iter(&mut split)); + let sent_colls = expect!(from_iter(&mut split)); + let sent_carrier = expect!(from_iter(&mut split)); + let sent_compressed = expect!(from_iter(&mut split)); + + Ok(DeviceStatus { + name: name.trim_end_matches(':').to_owned(), + recv_bytes, + recv_packets, + recv_errs, + recv_drop, + recv_fifo, + recv_frame, + recv_compressed, + recv_multicast, + sent_bytes, + sent_packets, + sent_errs, + sent_drop, + sent_fifo, + sent_colls, + sent_carrier, + sent_compressed, + }) + } +} + +/// Device status information for all network interfaces. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct InterfaceDeviceStatus(pub HashMap); + +impl super::FromBufRead for InterfaceDeviceStatus { + fn from_buf_read(r: R) -> ProcResult { + let mut map = HashMap::new(); + // the first two lines are headers, so skip them + for line in r.lines().skip(2) { + let dev = DeviceStatus::from_str(&line?)?; + map.insert(dev.name.clone(), dev); + } + + Ok(InterfaceDeviceStatus(map)) + } +} + +/// An entry in the ipv4 route table +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct RouteEntry { + /// Interface to which packets for this route will be sent + pub iface: String, + /// The destination network or destination host + pub destination: Ipv4Addr, + pub gateway: Ipv4Addr, + pub flags: u16, + /// Number of references to this route + pub refcnt: u16, + /// Count of lookups for the route + pub in_use: u16, + /// The 'distance' to the target (usually counted in hops) + pub metrics: u32, + pub mask: Ipv4Addr, + /// Default maximum transmission unit for TCP connections over this route + pub mtu: u32, + /// Default window size for TCP connections over this route + pub window: u32, + /// Initial RTT (Round Trip Time) + pub irtt: u32, +} + +/// A set of ipv4 routes. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct RouteEntries(pub Vec); + +impl super::FromBufRead for RouteEntries { + fn from_buf_read(r: R) -> ProcResult { + let mut vec = Vec::new(); + + // First line is a header we need to skip + for line in r.lines().skip(1) { + // Check if there might have been an IO error. + let line = line?; + let mut line = line.split_whitespace(); + // network interface name, e.g. eth0 + let iface = expect!(line.next()); + let destination = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); + let gateway = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); + let flags = from_str!(u16, expect!(line.next()), 16); + let refcnt = from_str!(u16, expect!(line.next()), 10); + let in_use = from_str!(u16, expect!(line.next()), 10); + let metrics = from_str!(u32, expect!(line.next()), 10); + let mask = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); + let mtu = from_str!(u32, expect!(line.next()), 10); + let window = from_str!(u32, expect!(line.next()), 10); + let irtt = from_str!(u32, expect!(line.next()), 10); + vec.push(RouteEntry { + iface: iface.to_string(), + destination, + gateway, + flags, + refcnt, + in_use, + metrics, + mask, + mtu, + window, + irtt, + }); + } + + Ok(RouteEntries(vec)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::IpAddr; + + #[test] + fn test_parse_ipaddr() { + use std::str::FromStr; + + let addr = parse_addressport_str("0100007F:1234", true).unwrap(); + assert_eq!(addr.port(), 0x1234); + match addr.ip() { + IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1)), + _ => panic!("Not IPv4"), + } + + // When you connect to [2a00:1450:4001:814::200e]:80 (ipv6.google.com) the entry with + // 5014002A14080140000000000E200000:0050 remote endpoint is created in /proc/net/tcp6 + // on Linux 4.19. + let addr = parse_addressport_str("5014002A14080140000000000E200000:0050", true).unwrap(); + assert_eq!(addr.port(), 80); + match addr.ip() { + IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2a00:1450:4001:814::200e").unwrap()), + _ => panic!("Not IPv6"), + } + + // IPv6 test case from https://stackoverflow.com/questions/41940483/parse-ipv6-addresses-from-proc-net-tcp6-python-2-7/41948004#41948004 + let addr = parse_addressport_str("B80D01200000000067452301EFCDAB89:0", true).unwrap(); + assert_eq!(addr.port(), 0); + match addr.ip() { + IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2001:db8::123:4567:89ab:cdef").unwrap()), + _ => panic!("Not IPv6"), + } + + let addr = parse_addressport_str("1234:1234", true); + assert!(addr.is_err()); + } + + #[test] + fn test_tcpstate_from() { + assert_eq!(TcpState::from_u8(0xA).unwrap(), TcpState::Listen); + } +} diff --git a/procfs/src/pressure.rs b/procfs-core/src/pressure.rs similarity index 64% rename from procfs/src/pressure.rs rename to procfs-core/src/pressure.rs index 52c39cb8..7bf3caf0 100644 --- a/procfs/src/pressure.rs +++ b/procfs-core/src/pressure.rs @@ -43,17 +43,10 @@ pub struct CpuPressure { pub some: PressureRecord, } -impl CpuPressure { - /// Get CPU pressure information - pub fn new() -> ProcResult { - use std::fs::File; - use std::io::{BufRead, BufReader}; - - let file = File::open("/proc/pressure/cpu")?; - let mut reader = BufReader::new(file); - +impl super::FromBufRead for CpuPressure { + fn from_buf_read(mut r: R) -> ProcResult { let mut some = String::new(); - reader.read_line(&mut some)?; + r.read_line(&mut some)?; Ok(CpuPressure { some: parse_pressure_record(&some)?, @@ -72,11 +65,9 @@ pub struct MemoryPressure { pub full: PressureRecord, } -impl MemoryPressure { - /// Get memory pressure information - pub fn new() -> ProcResult { - let (some, full) = get_pressure("memory")?; - +impl super::FromBufRead for MemoryPressure { + fn from_buf_read(r: R) -> ProcResult { + let (some, full) = get_pressure(r)?; Ok(MemoryPressure { some, full }) } } @@ -92,11 +83,9 @@ pub struct IoPressure { pub full: PressureRecord, } -impl IoPressure { - /// Get IO pressure information - pub fn new() -> ProcResult { - let (some, full) = get_pressure("io")?; - +impl super::FromBufRead for IoPressure { + fn from_buf_read(r: R) -> ProcResult { + let (some, full) = get_pressure(r)?; Ok(IoPressure { some, full }) } } @@ -140,18 +129,11 @@ fn parse_pressure_record(line: &str) -> ProcResult { }) } -fn get_pressure(pressure_file: &str) -> ProcResult<(PressureRecord, PressureRecord)> { - use std::fs::File; - use std::io::{BufRead, BufReader}; - - let file = File::open(format!("/proc/pressure/{}", pressure_file))?; - let mut reader = BufReader::new(file); - +fn get_pressure(mut r: R) -> ProcResult<(PressureRecord, PressureRecord)> { let mut some = String::new(); - reader.read_line(&mut some)?; + r.read_line(&mut some)?; let mut full = String::new(); - reader.read_line(&mut full)?; - + r.read_line(&mut full)?; Ok((parse_pressure_record(&some)?, parse_pressure_record(&full)?)) } @@ -159,12 +141,6 @@ fn get_pressure(pressure_file: &str) -> ProcResult<(PressureRecord, PressureReco mod test { use super::*; use std::f32::EPSILON; - use std::path::Path; - - #[allow(clippy::manual_range_contains)] - fn valid_percentage(value: f32) -> bool { - value >= 0.00 && value < 100.0 - } #[test] fn test_parse_pressure_record() { @@ -182,48 +158,4 @@ mod test { assert!(parse_pressure_record("some avg10=2.10 avg300=0.00 total=391926").is_err()); assert!(parse_pressure_record("some avg10=2.10 avg60=0.00 avg300=0.00").is_err()); } - - #[test] - fn test_mem_pressure() { - if !Path::new("/proc/pressure/memory").exists() { - return; - } - - let mem_psi = MemoryPressure::new().unwrap(); - assert!(valid_percentage(mem_psi.some.avg10)); - assert!(valid_percentage(mem_psi.some.avg60)); - assert!(valid_percentage(mem_psi.some.avg300)); - - assert!(valid_percentage(mem_psi.full.avg10)); - assert!(valid_percentage(mem_psi.full.avg60)); - assert!(valid_percentage(mem_psi.full.avg300)); - } - - #[test] - fn test_io_pressure() { - if !Path::new("/proc/pressure/io").exists() { - return; - } - - let io_psi = IoPressure::new().unwrap(); - assert!(valid_percentage(io_psi.some.avg10)); - assert!(valid_percentage(io_psi.some.avg60)); - assert!(valid_percentage(io_psi.some.avg300)); - - assert!(valid_percentage(io_psi.full.avg10)); - assert!(valid_percentage(io_psi.full.avg60)); - assert!(valid_percentage(io_psi.full.avg300)); - } - - #[test] - fn test_cpu_pressure() { - if !Path::new("/proc/pressure/cpu").exists() { - return; - } - - let cpu_psi = CpuPressure::new().unwrap(); - assert!(valid_percentage(cpu_psi.some.avg10)); - assert!(valid_percentage(cpu_psi.some.avg60)); - assert!(valid_percentage(cpu_psi.some.avg300)); - } } diff --git a/procfs/src/sysvipc_shm.rs b/procfs-core/src/sysvipc_shm.rs similarity index 83% rename from procfs/src/sysvipc_shm.rs rename to procfs-core/src/sysvipc_shm.rs index e395306c..7c6ede33 100644 --- a/procfs/src/sysvipc_shm.rs +++ b/procfs-core/src/sysvipc_shm.rs @@ -1,6 +1,6 @@ use std::io; -use super::{expect, FileWrapper, ProcResult}; +use super::{expect, ProcResult}; use std::str::FromStr; #[cfg(feature = "serde1")] @@ -46,24 +46,18 @@ pub struct Shm { pub swap: u64, } -impl Shm { - /// Reads and parses the `/proc/sysvipc/shm`, returning an error if there are problems. - pub fn new() -> ProcResult> { - let f = FileWrapper::open("/proc/sysvipc/shm")?; - - Shm::from_reader(f) - } - - /// Get Meminfo from a custom Read instead of the default `/proc/sysvipc/shm`. - pub fn from_reader(r: R) -> ProcResult> { - use std::io::{BufRead, BufReader}; +/// A set of shared memory segments parsed from `/proc/sysvipc/shm` +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct SharedMemorySegments(pub Vec); - let reader = BufReader::new(r); +impl super::FromBufRead for SharedMemorySegments { + fn from_buf_read(r: R) -> ProcResult { let mut vec = Vec::new(); // See printing code here: // https://elixir.bootlin.com/linux/latest/source/ipc/shm.c#L1737 - for line in reader.lines().skip(1) { + for line in r.lines().skip(1) { let line = expect!(line); let mut s = line.split_whitespace(); @@ -106,6 +100,6 @@ impl Shm { vec.push(shm); } - Ok(vec) + Ok(SharedMemorySegments(vec)) } } diff --git a/procfs/src/uptime.rs b/procfs-core/src/uptime.rs similarity index 84% rename from procfs/src/uptime.rs rename to procfs-core/src/uptime.rs index bc487cb2..d520d312 100644 --- a/procfs/src/uptime.rs +++ b/procfs-core/src/uptime.rs @@ -1,4 +1,4 @@ -use crate::{expect, FileWrapper, ProcResult}; +use crate::{expect, ProcResult}; use std::io::Read; use std::str::FromStr; @@ -15,13 +15,8 @@ pub struct Uptime { pub idle: f64, } -impl Uptime { - pub fn new() -> ProcResult { - let file = FileWrapper::open("/proc/uptime")?; - Uptime::from_reader(file) - } - - pub fn from_reader(mut r: R) -> ProcResult { +impl super::FromRead for Uptime { + fn from_read(mut r: R) -> ProcResult { let mut buf = Vec::with_capacity(128); r.read_to_end(&mut buf)?; @@ -34,7 +29,9 @@ impl Uptime { Ok(Uptime { uptime, idle }) } +} +impl Uptime { /// The uptime of the system (including time spent in suspend). pub fn uptime_duration(&self) -> Duration { let secs = self.uptime.trunc() as u64; @@ -57,12 +54,13 @@ impl Uptime { #[cfg(test)] mod tests { use super::*; + use crate::FromRead; use std::io::Cursor; #[test] fn test_uptime() { let reader = Cursor::new(b"2578790.61 1999230.98\n"); - let uptime = Uptime::from_reader(reader).unwrap(); + let uptime = Uptime::from_read(reader).unwrap(); assert_eq!(uptime.uptime_duration(), Duration::new(2578790, 610_000_000)); assert_eq!(uptime.idle_duration(), Duration::new(1999230, 980_000_000)); diff --git a/procfs/Cargo.toml b/procfs/Cargo.toml index 9e8f44ae..39596965 100644 --- a/procfs/Cargo.toml +++ b/procfs/Cargo.toml @@ -22,7 +22,6 @@ rustix = { version = "0.37.0", features = ["fs", "process", "param", "thread"] } bitflags = "1.2" lazy_static = "1.0.2" chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } -byteorder = {version="1.2.3", features=["i128"]} hex = "0.4" flate2 = { version = "1.0.3", optional = true } backtrace = { version = "0.3", optional = true } diff --git a/procfs/examples/pressure.rs b/procfs/examples/pressure.rs index 87b60ff3..1665d6a2 100644 --- a/procfs/examples/pressure.rs +++ b/procfs/examples/pressure.rs @@ -1,7 +1,8 @@ -/// A basic example of /proc/pressure/ usage. +use procfs::prelude::*; +/// A basic example of /proc/pressure/ usage. fn main() { - println!("memory pressure: {:#?}", procfs::MemoryPressure::new()); - println!("cpu pressure: {:#?}", procfs::CpuPressure::new()); - println!("io pressure: {:#?}", procfs::IoPressure::new()); + println!("memory pressure: {:#?}", procfs::MemoryPressure::current()); + println!("cpu pressure: {:#?}", procfs::CpuPressure::current()); + println!("io pressure: {:#?}", procfs::IoPressure::current()); } diff --git a/procfs/examples/shm.rs b/procfs/examples/shm.rs index 8acbee41..32c3daa2 100644 --- a/procfs/examples/shm.rs +++ b/procfs/examples/shm.rs @@ -1,11 +1,12 @@ extern crate procfs; +use procfs::prelude::*; /// List processes using posix shared memory segments fn main() { - let shared_memory_vec = procfs::Shm::new().unwrap(); + let shared_memory_vec = procfs::SharedMemorySegments::current().unwrap(); - for shared_memory in &shared_memory_vec { + for shared_memory in &shared_memory_vec.0 { println!("key: {}, shmid: {}", shared_memory.key, shared_memory.shmid); println!("============"); diff --git a/procfs/src/keyring.rs b/procfs/src/keyring.rs index 50575fd2..5f1238d8 100644 --- a/procfs/src/keyring.rs +++ b/procfs/src/keyring.rs @@ -1,383 +1,24 @@ -//! Functions related to the in-kernel key management and retention facility -//! -//! For more details on this facility, see the `keyrings(7)` man page. -//! -//! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module. -use crate::{build_internal_error, expect, from_str, FileWrapper, ProcResult}; -use bitflags::bitflags; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - io::{BufRead, BufReader}, - time::Duration, -}; +use super::{Current, ProcResult}; +use procfs_core::keyring::*; +use std::collections::HashMap; -bitflags! { - /// Various key flags - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct KeyFlags: u32 { - /// The key has been instantiated - const INSTANTIATED = 0x01; - /// THe key has been revoked - const REVOKED = 0x02; - /// The key is dead - /// - /// I.e. the key type has been unregistered. A key may be briefly in this state during garbage collection. - const DEAD = 0x04; - /// The key contributes to the user's quota - const QUOTA = 0x08; - /// The key is under construction via a callback to user space - const UNDER_CONSTRUCTION = 0x10; - /// The key is negatively instantiated - const NEGATIVE = 0x20; - /// The key has been invalidated - const INVALID = 0x40; - } -} - -bitflags! { - /// Bitflags that represent the permissions for a key - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct PermissionFlags: u32 { - /// The attributes of the key may be read - /// - /// This includes the type, description, and access rights (excluding the security label) - const VIEW = 0x01; - /// For a key: the payload of the key may be read. For a keyring: the list of serial numbers (keys) to which the keyring has links may be read. - const READ = 0x02; - /// The payload of the key may be updated and the key may be revoked. - /// - /// For a keyring, links may be added to or removed from the keyring, and the keyring - /// may be cleared completely (all links are removed). - const WRITE = 0x04; - /// The key may be found by a search. - /// - /// For keyrings: keys and keyrings that are linked to by the keyring may be searched. - const SEARCH = 0x08; - /// Links may be created from keyrings to the key. - /// - /// The initial link to a key that is established when the key is created doesn't require this permission. - const LINK = 0x10; - /// The ownership details and security label of the key may be changed, the key's expiration - /// time may be set, and the key may be revoked. - const SETATTR = 0x20; - const ALL = Self::VIEW.bits | Self::READ.bits | Self::WRITE.bits | Self::SEARCH.bits | Self::LINK.bits | Self::SETATTR.bits; - } -} - -impl KeyFlags { - fn from_str(s: &str) -> KeyFlags { - let mut me = KeyFlags::empty(); - - let mut chars = s.chars(); - match chars.next() { - Some(c) if c == 'I' => me.insert(KeyFlags::INSTANTIATED), - _ => {} - } - match chars.next() { - Some(c) if c == 'R' => me.insert(KeyFlags::REVOKED), - _ => {} - } - match chars.next() { - Some(c) if c == 'D' => me.insert(KeyFlags::DEAD), - _ => {} - } - match chars.next() { - Some(c) if c == 'Q' => me.insert(KeyFlags::QUOTA), - _ => {} - } - match chars.next() { - Some(c) if c == 'U' => me.insert(KeyFlags::UNDER_CONSTRUCTION), - _ => {} - } - match chars.next() { - Some(c) if c == 'N' => me.insert(KeyFlags::NEGATIVE), - _ => {} - } - match chars.next() { - Some(c) if c == 'i' => me.insert(KeyFlags::INVALID), - _ => {} - } - - me - } -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Permissions { - pub possessor: PermissionFlags, - pub user: PermissionFlags, - pub group: PermissionFlags, - pub other: PermissionFlags, +impl Current for Keys { + const PATH: &'static str = "/proc/keys"; } -impl Permissions { - fn from_str(s: &str) -> ProcResult { - let possessor = PermissionFlags::from_bits(from_str!(u32, &s[0..2], 16)) - .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; - let user = PermissionFlags::from_bits(from_str!(u32, &s[2..4], 16)) - .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; - - let group = PermissionFlags::from_bits(from_str!(u32, &s[4..6], 16)) - .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; - - let other = PermissionFlags::from_bits(from_str!(u32, &s[6..8], 16)) - .ok_or_else(|| build_internal_error!(format!("Unable to parse {:?} as PermissionFlags", s)))?; - - Ok(Permissions { - possessor, - user, - group, - other, - }) - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum KeyTimeout { - Permanent, - Expired, - Timeout(Duration), -} - -impl KeyTimeout { - fn from_str(s: &str) -> ProcResult { - if s == "perm" { - Ok(KeyTimeout::Permanent) - } else if s == "expd" { - Ok(KeyTimeout::Expired) - } else { - let (val, unit) = s.split_at(s.len() - 1); - let val = from_str!(u64, val); - match unit { - "s" => Ok(KeyTimeout::Timeout(Duration::from_secs(val))), - "m" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60))), - "h" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60))), - "d" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24))), - "w" => Ok(KeyTimeout::Timeout(Duration::from_secs(val * 60 * 60 * 24 * 7))), - _ => Err(build_internal_error!(format!("Unable to parse keytimeout of {:?}", s))), - } - } - } -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum KeyType { - /// This is a general-purpose key type. - /// - /// The key is kept entirely within kernel memory. The payload may be read and updated by - /// user-space applications. The payload for keys of this type is a blob of arbitrary - /// data of up to 32,767 bytes. - - /// The description may be any valid string, though it is preferred that it start - /// with a colon-delimited prefix representing the service to which the key is of - /// interest (for instance "afs:mykey"). - User, - - /// Keyrings are special keys which store a set of links to other keys (including - /// other keyrings), analogous to a directory holding links to files. The main - /// purpose of a keyring is to prevent other keys from being garbage collected - /// because nothing refers to them. - /// - /// Keyrings with descriptions (names) that begin with a period ('.') are re‐ - /// served to the implementation. - Keyring, - - /// This key type is essentially the same as "user", but it does not provide - /// reading (i.e., the keyctl(2) KEYCTL_READ operation), meaning that the key - /// payload is never visible from user space. This is suitable for storing user‐ - /// name-password pairs that should not be readable from user space. - /// - /// The description of a "logon" key must start with a non-empty colon-delimited - /// prefix whose purpose is to identify the service to which the key belongs. - /// (Note that this differs from keys of the "user" type, where the inclusion of - /// a prefix is recommended but is not enforced.) - Logon, - - /// This key type is similar to the "user" key type, but it may hold a payload of - /// up to 1 MiB in size. This key type is useful for purposes such as holding - /// Kerberos ticket caches. - /// - /// The payload data may be stored in a tmpfs filesystem, rather than in kernel - /// memory, if the data size exceeds the overhead of storing the data in the - /// filesystem. (Storing the data in a filesystem requires filesystem structures - /// to be allocated in the kernel. The size of these structures determines the - /// size threshold above which the tmpfs storage method is used.) Since Linux - /// 4.8, the payload data is encrypted when stored in tmpfs, thereby preventing - /// it from being written unencrypted into swap space. - BigKey, - - /// Other specialized, but rare keys types - Other(String), -} - -impl KeyType { - fn from_str(s: &str) -> KeyType { - match s { - "keyring" => KeyType::Keyring, - "user" => KeyType::User, - "logon" => KeyType::Logon, - "big_key" => KeyType::BigKey, - other => KeyType::Other(other.to_string()), - } - } -} - -/// A key -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Key { - /// The ID (serial number) of the key - pub id: u64, - - /// A set of flags describing the state of the key - pub flags: KeyFlags, - - /// Count of the number of kernel credential structures that are - /// pinning the key (approximately: the number of threads and open file - /// references that refer to this key). - pub usage: u32, - - /// Key timeout - pub timeout: KeyTimeout, - - /// Key permissions - pub permissions: Permissions, - - /// The user ID of the key owner - pub uid: u32, - - /// The group ID of the key. - /// - /// The value of `None` here means that the key has no group ID; this can occur in certain circumstances for - /// keys created by the kernel. - pub gid: Option, - - /// The type of key - pub key_type: KeyType, - - /// The key description - pub description: String, -} - -impl Key { - fn from_line(s: &str) -> ProcResult { - let mut s = s.split_whitespace(); - - let id = from_str!(u64, expect!(s.next()), 16); - let s_flags = expect!(s.next()); - let usage = from_str!(u32, expect!(s.next())); - let s_timeout = expect!(s.next()); - let s_perms = expect!(s.next()); - let uid = from_str!(u32, expect!(s.next())); - let s_gid = expect!(s.next()); - let s_type = expect!(s.next()); - let desc: Vec<_> = s.collect(); - - Ok(Key { - id, - flags: KeyFlags::from_str(s_flags), - usage, - timeout: KeyTimeout::from_str(s_timeout)?, - permissions: Permissions::from_str(s_perms)?, - uid, - gid: if s_gid == "-1" { - None - } else { - Some(from_str!(u32, s_gid)) - }, - key_type: KeyType::from_str(s_type), - description: desc.join(" "), - }) - } -} - -/// Returns a list of the keys for which the reading thread has **view** permission, providing various information about each key. +/// Returns a list of the keys for which the reading thread has **view** permission, providing +/// various information about each key. pub fn keys() -> ProcResult> { - let file = FileWrapper::open("/proc/keys")?; - let reader = BufReader::new(file); - let mut v = Vec::new(); - - for line in reader.lines() { - let line = line?; - v.push(Key::from_line(&line)?); - } - Ok(v) + Keys::current().map(|k| k.0) } -/// Information about a user with at least one key -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct KeyUser { - /// The user that owns the key - pub uid: u32, - /// The kernel-internal usage count for the kernel structure used to record key users - pub usage: u32, - /// The total number of keys owned by the user - pub nkeys: u32, - /// THe number of keys that have been instantiated - pub nikeys: u32, - /// The number of keys owned by the user - pub qnkeys: u32, - /// The maximum number of keys that the user may own - pub maxkeys: u32, - /// The number of bytes consumed in playloads of the keys owned by this user - pub qnbytes: u32, - /// The upper limit on the number of bytes in key payloads for this user - pub maxbytes: u32, -} - -impl KeyUser { - fn from_str(s: &str) -> ProcResult { - let mut s = s.split_whitespace(); - let uid = expect!(s.next()); - let usage = from_str!(u32, expect!(s.next())); - let keys = expect!(s.next()); - let qkeys = expect!(s.next()); - let qbytes = expect!(s.next()); - - let (nkeys, nikeys) = { - let mut s = keys.split('/'); - (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) - }; - let (qnkeys, maxkeys) = { - let mut s = qkeys.split('/'); - (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) - }; - let (qnbytes, maxbytes) = { - let mut s = qbytes.split('/'); - (from_str!(u32, expect!(s.next())), from_str!(u32, expect!(s.next()))) - }; - - Ok(KeyUser { - uid: from_str!(u32, &uid[0..uid.len() - 1]), - usage, - nkeys, - nikeys, - qnkeys, - maxkeys, - qnbytes, - maxbytes, - }) - } +impl Current for KeyUsers { + const PATH: &'static str = "/proc/key-users"; } /// Get various information for each user ID that has at least one key on the system. pub fn key_users() -> ProcResult> { - let file = FileWrapper::open("/proc/key-users")?; - let reader = BufReader::new(file); - let mut map = HashMap::new(); - - for line in reader.lines() { - let line = line?; - let user = KeyUser::from_str(&line)?; - map.insert(user.uid, user); - } - Ok(map) + KeyUsers::current().map(|k| k.0) } #[cfg(test)] @@ -385,47 +26,14 @@ mod tests { use super::*; #[test] - fn key_flags() { - assert_eq!(KeyFlags::from_str("I------"), KeyFlags::INSTANTIATED); - assert_eq!(KeyFlags::from_str("IR"), KeyFlags::INSTANTIATED | KeyFlags::REVOKED); - assert_eq!(KeyFlags::from_str("IRDQUNi"), KeyFlags::all()); - } - - #[test] - fn timeout() { - assert_eq!(KeyTimeout::from_str("perm").unwrap(), KeyTimeout::Permanent); - assert_eq!(KeyTimeout::from_str("expd").unwrap(), KeyTimeout::Expired); - assert_eq!( - KeyTimeout::from_str("2w").unwrap(), - KeyTimeout::Timeout(Duration::from_secs(1209600)) - ); - assert_eq!( - KeyTimeout::from_str("14d").unwrap(), - KeyTimeout::Timeout(Duration::from_secs(1209600)) - ); - assert_eq!( - KeyTimeout::from_str("336h").unwrap(), - KeyTimeout::Timeout(Duration::from_secs(1209600)) - ); - assert_eq!( - KeyTimeout::from_str("20160m").unwrap(), - KeyTimeout::Timeout(Duration::from_secs(1209600)) - ); - assert_eq!( - KeyTimeout::from_str("1209600s").unwrap(), - KeyTimeout::Timeout(Duration::from_secs(1209600)) - ); - } - - #[test] - fn live_keys() { + fn test_keys() { for key in keys().unwrap() { println!("{:#?}", key); } } #[test] - fn live_key_users() { + fn test_key_users() { for (_user, data) in key_users().unwrap() { println!("{:#?}", data); } diff --git a/procfs/src/lib.rs b/procfs/src/lib.rs index ef0c9a13..0de9fad2 100644 --- a/procfs/src/lib.rs +++ b/procfs/src/lib.rs @@ -89,6 +89,10 @@ impl SystemInfoInterface for LocalSystemInfo { fn page_size(&self) -> u64 { crate::page_size() } + + fn is_little_endian(&self) -> bool { + u16::from_ne_bytes([0, 1]).to_le_bytes() == [0, 1] + } } const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo; @@ -172,41 +176,11 @@ pub(crate) fn write_value, T: fmt::Display>(path: P, value: T) -> write_file(path, value.to_string().as_bytes()) } -pub mod process; - -mod meminfo; -pub use crate::meminfo::*; - -mod sysvipc_shm; -pub use crate::sysvipc_shm::*; - -pub mod net; - -mod cpuinfo; -pub use crate::cpuinfo::*; - mod cgroups; pub use crate::cgroups::*; -pub mod sys; -pub use crate::sys::kernel::BuildInfo as KernelBuildInfo; -pub use crate::sys::kernel::Type as KernelType; -pub use crate::sys::kernel::Version as KernelVersion; - -mod pressure; -pub use crate::pressure::*; - -mod diskstats; -pub use diskstats::*; - -mod locks; -pub use locks::*; - pub mod keyring; -mod uptime; -pub use uptime::*; - mod iomem; pub use iomem::*; @@ -216,6 +190,15 @@ pub use kpageflags::*; mod kpagecount; pub use kpagecount::*; +pub mod net; + +pub mod process; + +pub mod sys; +pub use crate::sys::kernel::BuildInfo as KernelBuildInfo; +pub use crate::sys::kernel::Type as KernelType; +pub use crate::sys::kernel::Version as KernelVersion; + lazy_static! { /// The number of clock ticks per second. /// @@ -238,16 +221,6 @@ lazy_static! { }; } -fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult { - match unit { - "B" => Ok(num), - "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024), - "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024), - "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024), - unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))), - } -} - /// A wrapper around a `File` that remembers the name of the path struct FileWrapper { inner: File, @@ -474,6 +447,56 @@ pub fn cmdline() -> ProcResult> { KernelCmdline::current().map(|c| c.0) } +impl Current for CpuInfo { + const PATH: &'static str = "/proc/cpuinfo"; +} + +impl Current for DiskStats { + const PATH: &'static str = "/proc/diskstats"; +} + +/// Get disk IO stat info from /proc/diskstats +pub fn diskstats() -> ProcResult> { + DiskStats::current().map(|d| d.0) +} + +impl Current for Locks { + const PATH: &'static str = "/proc/locks"; +} + +/// Get a list of current file locks and leases +/// +/// Since Linux 4.9, the list of locks is filtered to show just the locks +/// for the processes in the PID namespace for which the `/proc` filesystem +/// was mounted. +pub fn locks() -> ProcResult> { + Locks::current().map(|l| l.0) +} + +impl Current for Meminfo { + const PATH: &'static str = "/proc/meminfo"; +} + +impl Current for CpuPressure { + const PATH: &'static str = "/proc/pressure/cpu"; +} + +impl Current for MemoryPressure { + const PATH: &'static str = "/proc/pressure/memory"; +} + +impl Current for IoPressure { + const PATH: &'static str = "/proc/pressure/io"; +} + +impl Current for SharedMemorySegments { + const PATH: &'static str = "/proc/sysvipc/shm"; +} + +impl Current for Uptime { + const PATH: &'static str = "/proc/uptime"; +} + #[cfg(test)] mod tests { use super::*; @@ -547,7 +570,7 @@ mod tests { let diff = (boottime as i32 - stat.btime as i32).abs(); assert!(diff <= 1); - let cpuinfo = CpuInfo::new().unwrap(); + let cpuinfo = CpuInfo::current().unwrap(); assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len()); // the sum of each individual CPU should be equal to the total cpu entry @@ -648,4 +671,291 @@ mod tests { assert!(matches!(e, ProcError::NotFound(_))); } + + #[test] + fn test_cpuinfo() { + let info = CpuInfo::current().unwrap(); + println!("{:#?}", info.flags(0)); + for num in 0..info.num_cores() { + info.model_name(num).unwrap(); + info.vendor_id(num).unwrap(); + // May not be available on some old kernels: + info.physical_id(num); + } + + //assert_eq!(info.num_cores(), 8); + } + + #[test] + fn test_diskstats() { + for disk in super::diskstats().unwrap() { + println!("{:?}", disk); + } + } + + #[test] + fn test_locks() { + for lock in locks().unwrap() { + println!("{:?}", lock); + if let LockType::Other(s) = lock.lock_type { + panic!("Found an unknown lock type {:?}", s); + } + if let LockKind::Other(s) = lock.kind { + panic!("Found an unknown lock kind {:?}", s); + } + if let LockMode::Other(s) = lock.mode { + panic!("Found an unknown lock mode {:?}", s); + } + } + } + + #[allow(clippy::cognitive_complexity)] + #[allow(clippy::blocks_in_if_conditions)] + #[test] + fn test_meminfo() { + // TRAVIS + // we don't have access to the kernel_config on travis, so skip that test there + match ::std::env::var("TRAVIS") { + Ok(ref s) if s == "true" => return, + _ => {} + } + + let kernel = KernelVersion::current().unwrap(); + let config = KernelConfig::current().ok(); + + let meminfo = Meminfo::current().unwrap(); + println!("{:#?}", meminfo); + + // for the fields that are only present in some kernel versions, make sure our + // actual kernel agrees + + if kernel >= KernelVersion::new(3, 14, 0) { + assert!(meminfo.mem_available.is_some()); + } + + if kernel >= KernelVersion::new(2, 6, 28) { + assert!(meminfo.active_anon.is_some()); + assert!(meminfo.inactive_anon.is_some()); + assert!(meminfo.active_file.is_some()); + assert!(meminfo.inactive_file.is_some()); + } else { + assert!(meminfo.active_anon.is_none()); + assert!(meminfo.inactive_anon.is_none()); + assert!(meminfo.active_file.is_none()); + assert!(meminfo.inactive_file.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 28) + && kernel <= KernelVersion::new(2, 6, 30) + && meminfo.unevictable.is_some() + { + if let Some(KernelConfig(ref config)) = config { + assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some()); + } + } + + if kernel >= KernelVersion::new(2, 6, 19) + && config + .as_ref() + .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HIGHMEM")) + { + assert!(meminfo.high_total.is_some()); + assert!(meminfo.high_free.is_some()); + assert!(meminfo.low_total.is_some()); + assert!(meminfo.low_free.is_some()); + } else { + assert!(meminfo.high_total.is_none()); + assert!(meminfo.high_free.is_none()); + assert!(meminfo.low_total.is_none()); + assert!(meminfo.low_free.is_none()); + } + + // Possible bug in procfs documentation: + // The man page says that MmapCopy requires CONFIG_MMU, but if you look at the + // source, MmapCopy is only included if CONFIG_MMU is *missing*: + // /~https://github.com/torvalds/linux/blob/v4.17/fs/proc/meminfo.c#L80 + //if kernel >= KernelVersion::new(2, 6, 29) && config.contains_key("CONFIG_MMU") { + // assert!(meminfo.mmap_copy.is_some()); + //} else { + // assert!(meminfo.mmap_copy.is_none()); + //} + + if kernel >= KernelVersion::new(2, 6, 18) { + assert!(meminfo.anon_pages.is_some()); + assert!(meminfo.page_tables.is_some()); + assert!(meminfo.nfs_unstable.is_some()); + assert!(meminfo.bounce.is_some()); + } else { + assert!(meminfo.anon_pages.is_none()); + assert!(meminfo.page_tables.is_none()); + assert!(meminfo.nfs_unstable.is_none()); + assert!(meminfo.bounce.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 32) { + assert!(meminfo.shmem.is_some()); + assert!(meminfo.kernel_stack.is_some()); + } else { + assert!(meminfo.shmem.is_none()); + assert!(meminfo.kernel_stack.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 19) { + assert!(meminfo.s_reclaimable.is_some()); + assert!(meminfo.s_unreclaim.is_some()); + } else { + assert!(meminfo.s_reclaimable.is_none()); + assert!(meminfo.s_unreclaim.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 27) + && config + .as_ref() + .map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_QUICKLIST")) + { + assert!(meminfo.quicklists.is_some()); + } else { + assert!(meminfo.quicklists.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 26) { + assert!(meminfo.writeback_tmp.is_some()); + } else { + assert!(meminfo.writeback_tmp.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 10) { + assert!(meminfo.commit_limit.is_some()); + } else { + assert!(meminfo.commit_limit.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 32) + && config.as_ref().map_or( + std::path::Path::new("/proc/kpagecgroup").exists(), + |KernelConfig(cfg)| cfg.contains_key("CONFIG_MEMORY_FAILURE"), + ) + { + assert!(meminfo.hardware_corrupted.is_some()); + } else { + assert!(meminfo.hardware_corrupted.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 38) + && config.as_ref().map_or(false, |KernelConfig(cfg)| { + cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") + }) + { + assert!(meminfo.anon_hugepages.is_some()); + } else { + // SOme distributions may backport this option into older kernels + // assert!(meminfo.anon_hugepages.is_none()); + } + + if kernel >= KernelVersion::new(4, 8, 0) + && config.as_ref().map_or(true, |KernelConfig(cfg)| { + cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE") + }) + { + assert!(meminfo.shmem_hugepages.is_some()); + assert!(meminfo.shmem_pmd_mapped.is_some()); + } else { + assert!(meminfo.shmem_hugepages.is_none()); + assert!(meminfo.shmem_pmd_mapped.is_none()); + } + + if kernel >= KernelVersion::new(3, 1, 0) + && config + .as_ref() + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_CMA")) + { + assert!(meminfo.cma_total.is_some()); + assert!(meminfo.cma_free.is_some()); + } else { + assert!(meminfo.cma_total.is_none()); + assert!(meminfo.cma_free.is_none()); + } + + if config + .as_ref() + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + { + assert!(meminfo.hugepages_total.is_some()); + assert!(meminfo.hugepages_free.is_some()); + assert!(meminfo.hugepagesize.is_some()); + } else { + assert!(meminfo.hugepages_total.is_none()); + assert!(meminfo.hugepages_free.is_none()); + assert!(meminfo.hugepagesize.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 17) + && config + .as_ref() + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + { + assert!(meminfo.hugepages_rsvd.is_some()); + } else { + assert!(meminfo.hugepages_rsvd.is_none()); + } + + if kernel >= KernelVersion::new(2, 6, 24) + && config + .as_ref() + .map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE")) + { + assert!(meminfo.hugepages_surp.is_some()); + } else { + assert!(meminfo.hugepages_surp.is_none()); + } + } + + #[allow(clippy::manual_range_contains)] + fn valid_percentage(value: f32) -> bool { + value >= 0.00 && value < 100.0 + } + + #[test] + fn test_mem_pressure() { + if !Path::new("/proc/pressure/memory").exists() { + return; + } + + let mem_psi = MemoryPressure::current().unwrap(); + assert!(valid_percentage(mem_psi.some.avg10)); + assert!(valid_percentage(mem_psi.some.avg60)); + assert!(valid_percentage(mem_psi.some.avg300)); + + assert!(valid_percentage(mem_psi.full.avg10)); + assert!(valid_percentage(mem_psi.full.avg60)); + assert!(valid_percentage(mem_psi.full.avg300)); + } + + #[test] + fn test_io_pressure() { + if !Path::new("/proc/pressure/io").exists() { + return; + } + + let io_psi = IoPressure::current().unwrap(); + assert!(valid_percentage(io_psi.some.avg10)); + assert!(valid_percentage(io_psi.some.avg60)); + assert!(valid_percentage(io_psi.some.avg300)); + + assert!(valid_percentage(io_psi.full.avg10)); + assert!(valid_percentage(io_psi.full.avg60)); + assert!(valid_percentage(io_psi.full.avg300)); + } + + #[test] + fn test_cpu_pressure() { + if !Path::new("/proc/pressure/cpu").exists() { + return; + } + + let cpu_psi = CpuPressure::current().unwrap(); + assert!(valid_percentage(cpu_psi.some.avg10)); + assert!(valid_percentage(cpu_psi.some.avg60)); + assert!(valid_percentage(cpu_psi.some.avg300)); + } } diff --git a/procfs/src/net.rs b/procfs/src/net.rs index 14d8362b..d21bf9b1 100644 --- a/procfs/src/net.rs +++ b/procfs/src/net.rs @@ -50,450 +50,57 @@ //! } //! } use crate::ProcResult; -use crate::{build_internal_error, expect, from_iter, from_str}; +use crate::{current_system_info, Current}; +pub use procfs_core::net::*; +use procfs_core::FromReadSI; use std::collections::HashMap; -use crate::FileWrapper; -use bitflags::bitflags; -use byteorder::{ByteOrder, NativeEndian, NetworkEndian}; -use std::io::{BufRead, BufReader, Read}; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; -use std::{path::PathBuf, str::FromStr}; - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum TcpState { - Established = 1, - SynSent, - SynRecv, - FinWait1, - FinWait2, - TimeWait, - Close, - CloseWait, - LastAck, - Listen, - Closing, - NewSynRecv, -} - -impl TcpState { - pub fn from_u8(num: u8) -> Option { - match num { - 0x01 => Some(TcpState::Established), - 0x02 => Some(TcpState::SynSent), - 0x03 => Some(TcpState::SynRecv), - 0x04 => Some(TcpState::FinWait1), - 0x05 => Some(TcpState::FinWait2), - 0x06 => Some(TcpState::TimeWait), - 0x07 => Some(TcpState::Close), - 0x08 => Some(TcpState::CloseWait), - 0x09 => Some(TcpState::LastAck), - 0x0A => Some(TcpState::Listen), - 0x0B => Some(TcpState::Closing), - 0x0C => Some(TcpState::NewSynRecv), - _ => None, - } - } - - pub fn to_u8(&self) -> u8 { - match self { - TcpState::Established => 0x01, - TcpState::SynSent => 0x02, - TcpState::SynRecv => 0x03, - TcpState::FinWait1 => 0x04, - TcpState::FinWait2 => 0x05, - TcpState::TimeWait => 0x06, - TcpState::Close => 0x07, - TcpState::CloseWait => 0x08, - TcpState::LastAck => 0x09, - TcpState::Listen => 0x0A, - TcpState::Closing => 0x0B, - TcpState::NewSynRecv => 0x0C, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum UdpState { - Established = 1, - Close = 7, -} - -impl UdpState { - pub fn from_u8(num: u8) -> Option { - match num { - 0x01 => Some(UdpState::Established), - 0x07 => Some(UdpState::Close), - _ => None, - } - } - - pub fn to_u8(&self) -> u8 { - match self { - UdpState::Established => 0x01, - UdpState::Close => 0x07, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum UnixState { - UNCONNECTED = 1, - CONNECTING = 2, - CONNECTED = 3, - DISCONNECTING = 4, -} - -impl UnixState { - pub fn from_u8(num: u8) -> Option { - match num { - 0x01 => Some(UnixState::UNCONNECTED), - 0x02 => Some(UnixState::CONNECTING), - 0x03 => Some(UnixState::CONNECTED), - 0x04 => Some(UnixState::DISCONNECTING), - _ => None, - } - } - - pub fn to_u8(&self) -> u8 { - match self { - UnixState::UNCONNECTED => 0x01, - UnixState::CONNECTING => 0x02, - UnixState::CONNECTED => 0x03, - UnixState::DISCONNECTING => 0x04, - } - } -} - -/// An entry in the TCP socket table -#[derive(Debug, Clone)] -#[non_exhaustive] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct TcpNetEntry { - pub local_address: SocketAddr, - pub remote_address: SocketAddr, - pub state: TcpState, - pub rx_queue: u32, - pub tx_queue: u32, - pub uid: u32, - pub inode: u64, -} - -/// An entry in the UDP socket table -#[derive(Debug, Clone)] -#[non_exhaustive] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UdpNetEntry { - pub local_address: SocketAddr, - pub remote_address: SocketAddr, - pub state: UdpState, - pub rx_queue: u32, - pub tx_queue: u32, - pub uid: u32, - pub inode: u64, -} - -/// An entry in the Unix socket table -#[derive(Debug, Clone)] -#[non_exhaustive] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UnixNetEntry { - /// The number of users of the socket - pub ref_count: u32, - /// The socket type. - /// - /// Possible values are `SOCK_STREAM`, `SOCK_DGRAM`, or `SOCK_SEQPACKET`. These constants can - /// be found in the libc crate. - pub socket_type: u16, - /// The state of the socket - pub state: UnixState, - /// The inode number of the socket - pub inode: u64, - /// The bound pathname (if any) of the socket. - /// - /// Sockets in the abstract namespace are included, and are shown with a path that commences - /// with the '@' character. - pub path: Option, -} - -/// Parses an address in the form 00010203:1234 -/// -/// Also supports IPv6 -fn parse_addressport_str(s: &str) -> ProcResult { - let mut las = s.split(':'); - let ip_part = expect!(las.next(), "ip_part"); - let port = expect!(las.next(), "port"); - let port = from_str!(u16, port, 16); - - if ip_part.len() == 8 { - let bytes = expect!(hex::decode(ip_part)); - let ip_u32 = NetworkEndian::read_u32(&bytes); - - let ip = Ipv4Addr::new( - (ip_u32 & 0xff) as u8, - ((ip_u32 & 0xff << 8) >> 8) as u8, - ((ip_u32 & 0xff << 16) >> 16) as u8, - ((ip_u32 & 0xff << 24) >> 24) as u8, - ); - - Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) - } else if ip_part.len() == 32 { - let bytes = expect!(hex::decode(ip_part)); - - let ip_a = NativeEndian::read_u32(&bytes[0..]); - let ip_b = NativeEndian::read_u32(&bytes[4..]); - let ip_c = NativeEndian::read_u32(&bytes[8..]); - let ip_d = NativeEndian::read_u32(&bytes[12..]); - - let ip = Ipv6Addr::new( - ((ip_a >> 16) & 0xffff) as u16, - (ip_a & 0xffff) as u16, - ((ip_b >> 16) & 0xffff) as u16, - (ip_b & 0xffff) as u16, - ((ip_c >> 16) & 0xffff) as u16, - (ip_c & 0xffff) as u16, - ((ip_d >> 16) & 0xffff) as u16, - (ip_d & 0xffff) as u16, - ); - - Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0))) - } else { - Err(build_internal_error!(format!( - "Unable to parse {:?} as an address:port", - s - ))) - } -} - -/// Reads TCP socket table from the provided `reader`. -pub fn read_tcp_table(reader: BufReader) -> ProcResult> { - let mut vec = Vec::new(); - - // first line is a header we need to skip - for line in reader.lines().skip(1) { - let line = line?; - let mut s = line.split_whitespace(); - s.next(); - let local_address = expect!(s.next(), "tcp::local_address"); - let rem_address = expect!(s.next(), "tcp::rem_address"); - let state = expect!(s.next(), "tcp::st"); - let mut tx_rx_queue = expect!(s.next(), "tcp::tx_queue:rx_queue").splitn(2, ':'); - let tx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::tx_queue"), 16); - let rx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::rx_queue"), 16); - s.next(); // skip tr and tm->when - s.next(); // skip retrnsmt - let uid = from_str!(u32, expect!(s.next(), "tcp::uid")); - s.next(); // skip timeout - let inode = expect!(s.next(), "tcp::inode"); - - vec.push(TcpNetEntry { - local_address: parse_addressport_str(local_address)?, - remote_address: parse_addressport_str(rem_address)?, - rx_queue, - tx_queue, - state: expect!(TcpState::from_u8(from_str!(u8, state, 16))), - uid, - inode: from_str!(u64, inode), - }); - } - - Ok(vec) -} - -/// Reads UDP socket table from the provided `reader`. -pub fn read_udp_table(reader: BufReader) -> ProcResult> { - let mut vec = Vec::new(); - - // first line is a header we need to skip - for line in reader.lines().skip(1) { - let line = line?; - let mut s = line.split_whitespace(); - s.next(); - let local_address = expect!(s.next(), "udp::local_address"); - let rem_address = expect!(s.next(), "udp::rem_address"); - let state = expect!(s.next(), "udp::st"); - let mut tx_rx_queue = expect!(s.next(), "udp::tx_queue:rx_queue").splitn(2, ':'); - let tx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::tx_queue"), 16); - let rx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::rx_queue"), 16); - s.next(); // skip tr and tm->when - s.next(); // skip retrnsmt - let uid = from_str!(u32, expect!(s.next(), "udp::uid")); - s.next(); // skip timeout - let inode = expect!(s.next(), "udp::inode"); - - vec.push(UdpNetEntry { - local_address: parse_addressport_str(local_address)?, - remote_address: parse_addressport_str(rem_address)?, - rx_queue, - tx_queue, - state: expect!(UdpState::from_u8(from_str!(u8, state, 16))), - uid, - inode: from_str!(u64, inode), - }); - } - - Ok(vec) -} - /// Reads the tcp socket table /// -/// Note that this is the socket table for the current progress. If you want to +/// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::tcp()](crate::process::Process::tcp()) pub fn tcp() -> ProcResult> { - let file = FileWrapper::open("/proc/net/tcp")?; - - read_tcp_table(BufReader::new(file)) + TcpNetEntries::from_file("/proc/net/tcp", current_system_info()).map(|e| e.0) } /// Reads the tcp6 socket table /// -/// Note that this is the socket table for the current progress. If you want to +/// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::tcp6()](crate::process::Process::tcp6()) pub fn tcp6() -> ProcResult> { - let file = FileWrapper::open("/proc/net/tcp6")?; - - read_tcp_table(BufReader::new(file)) + TcpNetEntries::from_file("/proc/net/tcp6", current_system_info()).map(|e| e.0) } /// Reads the udp socket table /// -/// Note that this is the socket table for the current progress. If you want to +/// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::udp()](crate::process::Process::udp()) pub fn udp() -> ProcResult> { - let file = FileWrapper::open("/proc/net/udp")?; - - read_udp_table(BufReader::new(file)) + UdpNetEntries::from_file("/proc/net/udp", current_system_info()).map(|e| e.0) } /// Reads the udp6 socket table /// -/// Note that this is the socket table for the current progress. If you want to +/// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::udp6()](crate::process::Process::udp6()) pub fn udp6() -> ProcResult> { - let file = FileWrapper::open("/proc/net/udp6")?; + UdpNetEntries::from_file("/proc/net/udp6", current_system_info()).map(|e| e.0) +} - read_udp_table(BufReader::new(file)) +impl Current for UnixNetEntries { + const PATH: &'static str = "/proc/net/unix"; } /// Reads the unix socket table /// -/// Note that this is the socket table for the current progress. If you want to +/// Note that this is the socket table for the current process. If you want to /// see the socket table for another process, then see [Process::unix()](crate::process::Process::unix()) pub fn unix() -> ProcResult> { - let file = FileWrapper::open("/proc/net/unix")?; - unix_from_reader(file) + UnixNetEntries::current().map(|e| e.0) } -pub(crate) fn unix_from_reader(file: impl Read) -> ProcResult> { - let reader = BufReader::new(file); - - let mut vec = Vec::new(); - - // first line is a header we need to skip - for line in reader.lines().skip(1) { - let line = line?; - let mut s = line.split_whitespace(); - s.next(); // skip table slot number - let ref_count = from_str!(u32, expect!(s.next()), 16); - s.next(); // skip protocol, always zero - s.next(); // skip internal kernel flags - let socket_type = from_str!(u16, expect!(s.next()), 16); - let state = from_str!(u8, expect!(s.next()), 16); - let inode = from_str!(u64, expect!(s.next())); - let path = s.next().map(PathBuf::from); - - vec.push(UnixNetEntry { - ref_count, - socket_type, - inode, - state: expect!(UnixState::from_u8(state)), - path, - }); - } - - Ok(vec) -} - -/// An entry in the ARP table -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct ARPEntry { - /// IPv4 address - pub ip_address: Ipv4Addr, - /// Hardware type - /// - /// This will almost always be ETHER (or maybe INFINIBAND) - pub hw_type: ARPHardware, - /// Internal kernel flags - pub flags: ARPFlags, - /// MAC Address - pub hw_address: Option<[u8; 6]>, - /// Device name - pub device: String, -} - -bitflags! { - /// Hardware type for an ARP table entry. - // source: include/uapi/linux/if_arp.h - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct ARPHardware: u32 { - /// NET/ROM pseudo - const NETROM = 0; - /// Ethernet - const ETHER = 1; - /// Experimental ethernet - const EETHER = 2; - /// AX.25 Level 2 - const AX25 = 3; - /// PROnet token ring - const PRONET = 4; - /// Chaosnet - const CHAOS = 5; - /// IEEE 802.2 Ethernet/TR/TB - const IEEE802 = 6; - /// Arcnet - const ARCNET = 7; - /// APPLEtalk - const APPLETLK = 8; - /// Frame Relay DLCI - const DLCI = 15; - /// ATM - const ATM = 19; - /// Metricom STRIP - const METRICOM = 23; - //// IEEE 1394 IPv4 - RFC 2734 - const IEEE1394 = 24; - /// EUI-64 - const EUI64 = 27; - /// InfiniBand - const INFINIBAND = 32; - } -} - -bitflags! { - /// Flags for ARP entries - // source: include/uapi/linux/if_arp.h - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] - pub struct ARPFlags: u32 { - /// Completed entry - const COM = 0x02; - /// Permanent entry - const PERM = 0x04; - /// Publish entry - const PUBL = 0x08; - /// Has requested trailers - const USETRAILERS = 0x10; - /// Want to use a netmask (only for proxy entries) - const NETMASK = 0x20; - // Don't answer this address - const DONTPUB = 0x40; - } +impl super::Current for ArpEntries { + const PATH: &'static str = "/proc/net/arp"; } /// Reads the ARP table @@ -501,157 +108,11 @@ bitflags! { /// Note that this is the ARP table for the current progress. If you want to /// see the ARP table for another process, then see [Process::arp()](crate::process::Process::arp()) pub fn arp() -> ProcResult> { - let file = FileWrapper::open("/proc/net/arp")?; - arp_from_reader(file) + ArpEntries::current().map(|e| e.0) } -pub(crate) fn arp_from_reader(file: impl Read) -> ProcResult> { - let reader = BufReader::new(file); - - let mut vec = Vec::new(); - - // First line is a header we need to skip - for line in reader.lines().skip(1) { - // Check if there might have been an IO error. - let line = line?; - let mut line = line.split_whitespace(); - let ip_address = expect!(Ipv4Addr::from_str(expect!(line.next()))); - let hw = from_str!(u32, &expect!(line.next())[2..], 16); - let hw = ARPHardware::from_bits_truncate(hw); - let flags = from_str!(u32, &expect!(line.next())[2..], 16); - let flags = ARPFlags::from_bits_truncate(flags); - - let mac = expect!(line.next()); - let mut mac: Vec> = mac.split(':').map(|s| Ok(from_str!(u8, s, 16))).collect(); - - let mac = if mac.len() == 6 { - let mac_block_f = mac.pop().unwrap()?; - let mac_block_e = mac.pop().unwrap()?; - let mac_block_d = mac.pop().unwrap()?; - let mac_block_c = mac.pop().unwrap()?; - let mac_block_b = mac.pop().unwrap()?; - let mac_block_a = mac.pop().unwrap()?; - if mac_block_a == 0 - && mac_block_b == 0 - && mac_block_c == 0 - && mac_block_d == 0 - && mac_block_e == 0 - && mac_block_f == 0 - { - None - } else { - Some([ - mac_block_a, - mac_block_b, - mac_block_c, - mac_block_d, - mac_block_e, - mac_block_f, - ]) - } - } else { - None - }; - - // mask is always "*" - let _mask = expect!(line.next()); - let dev = expect!(line.next()); - vec.push(ARPEntry { - ip_address, - hw_type: hw, - flags, - hw_address: mac, - device: dev.to_string(), - }) - } - - Ok(vec) -} - -/// General statistics for a network interface/device -/// -/// For an example, see the [interface_stats.rs](/~https://github.com/eminence/procfs/tree/master/examples) -/// example in the source repo. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct DeviceStatus { - /// Name of the interface - pub name: String, - /// Total bytes received - pub recv_bytes: u64, - /// Total packets received - pub recv_packets: u64, - /// Bad packets received - pub recv_errs: u64, - /// Packets dropped - pub recv_drop: u64, - /// Fifo overrun - pub recv_fifo: u64, - /// Frame alignment errors - pub recv_frame: u64, - /// Number of compressed packets received - pub recv_compressed: u64, - /// Number of multicast packets received - pub recv_multicast: u64, - - /// Total bytes transmitted - pub sent_bytes: u64, - /// Total packets transmitted - pub sent_packets: u64, - /// Number of transmission errors - pub sent_errs: u64, - /// Number of packets dropped during transmission - pub sent_drop: u64, - pub sent_fifo: u64, - /// Number of collisions - pub sent_colls: u64, - /// Number of packets not sent due to carrier errors - pub sent_carrier: u64, - /// Number of compressed packets transmitted - pub sent_compressed: u64, -} - -impl DeviceStatus { - fn from_str(s: &str) -> ProcResult { - let mut split = s.split_whitespace(); - let name: String = expect!(from_iter(&mut split)); - let recv_bytes = expect!(from_iter(&mut split)); - let recv_packets = expect!(from_iter(&mut split)); - let recv_errs = expect!(from_iter(&mut split)); - let recv_drop = expect!(from_iter(&mut split)); - let recv_fifo = expect!(from_iter(&mut split)); - let recv_frame = expect!(from_iter(&mut split)); - let recv_compressed = expect!(from_iter(&mut split)); - let recv_multicast = expect!(from_iter(&mut split)); - let sent_bytes = expect!(from_iter(&mut split)); - let sent_packets = expect!(from_iter(&mut split)); - let sent_errs = expect!(from_iter(&mut split)); - let sent_drop = expect!(from_iter(&mut split)); - let sent_fifo = expect!(from_iter(&mut split)); - let sent_colls = expect!(from_iter(&mut split)); - let sent_carrier = expect!(from_iter(&mut split)); - let sent_compressed = expect!(from_iter(&mut split)); - - Ok(DeviceStatus { - name: name.trim_end_matches(':').to_owned(), - recv_bytes, - recv_packets, - recv_errs, - recv_drop, - recv_fifo, - recv_frame, - recv_compressed, - recv_multicast, - sent_bytes, - sent_packets, - sent_errs, - sent_drop, - sent_fifo, - sent_colls, - sent_carrier, - sent_compressed, - }) - } +impl super::Current for InterfaceDeviceStatus { + const PATH: &'static str = "/proc/net/dev"; } /// Returns basic network device statistics for all interfaces @@ -665,45 +126,11 @@ impl DeviceStatus { /// current process. If you want information for some otherr process, see /// [Process::dev_status()](crate::process::Process::dev_status()) pub fn dev_status() -> ProcResult> { - let file = FileWrapper::open("/proc/net/dev")?; - - dev_status_from_reader(file) -} -pub(crate) fn dev_status_from_reader(file: impl Read) -> ProcResult> { - let buf = BufReader::new(file); - let mut map = HashMap::new(); - // the first two lines are headers, so skip them - for line in buf.lines().skip(2) { - let dev = DeviceStatus::from_str(&line?)?; - map.insert(dev.name.clone(), dev); - } - - Ok(map) + InterfaceDeviceStatus::current().map(|e| e.0) } -/// An entry in the ipv4 route table -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct RouteEntry { - /// Interface to which packets for this route will be sent - pub iface: String, - /// The destination network or destination host - pub destination: Ipv4Addr, - pub gateway: Ipv4Addr, - pub flags: u16, - /// Number of references to this route - pub refcnt: u16, - /// Count of lookups for the route - pub in_use: u16, - /// The 'distance' to the target (usually counted in hops) - pub metrics: u32, - pub mask: Ipv4Addr, - /// Default maximum transmission unit for TCP connections over this route - pub mtu: u32, - /// Default window size for TCP connections over this route - pub window: u32, - /// Initial RTT (Round Trip Time) - pub irtt: u32, +impl super::Current for RouteEntries { + const PATH: &'static str = "/proc/net/route"; } /// Reads the ipv4 route table @@ -714,92 +141,12 @@ pub struct RouteEntry { /// current process. If you want information for some other process, see /// [Process::route()](crate::process::Process::route()) pub fn route() -> ProcResult> { - let file = FileWrapper::open("/proc/net/route")?; - route_from_reader(file) -} - -pub(crate) fn route_from_reader(file: impl Read) -> ProcResult> { - let reader = BufReader::new(file); - - let mut vec = Vec::new(); - - // First line is a header we need to skip - for line in reader.lines().skip(1) { - // Check if there might have been an IO error. - let line = line?; - let mut line = line.split_whitespace(); - // network interface name, e.g. eth0 - let iface = expect!(line.next()); - let destination = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); - let gateway = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); - let flags = from_str!(u16, expect!(line.next()), 16); - let refcnt = from_str!(u16, expect!(line.next()), 10); - let in_use = from_str!(u16, expect!(line.next()), 10); - let metrics = from_str!(u32, expect!(line.next()), 10); - let mask = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into(); - let mtu = from_str!(u32, expect!(line.next()), 10); - let window = from_str!(u32, expect!(line.next()), 10); - let irtt = from_str!(u32, expect!(line.next()), 10); - vec.push(RouteEntry { - iface: iface.to_string(), - destination, - gateway, - flags, - refcnt, - in_use, - metrics, - mask, - mtu, - window, - irtt, - }); - } - - Ok(vec) + RouteEntries::current().map(|r| r.0) } #[cfg(test)] mod tests { use super::*; - use std::net::IpAddr; - - #[test] - fn test_parse_ipaddr() { - use std::str::FromStr; - - let addr = parse_addressport_str("0100007F:1234").unwrap(); - assert_eq!(addr.port(), 0x1234); - match addr.ip() { - IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1)), - _ => panic!("Not IPv4"), - } - - // When you connect to [2a00:1450:4001:814::200e]:80 (ipv6.google.com) the entry with - // 5014002A14080140000000000E200000:0050 remote endpoint is created in /proc/net/tcp6 - // on Linux 4.19. - let addr = parse_addressport_str("5014002A14080140000000000E200000:0050").unwrap(); - assert_eq!(addr.port(), 80); - match addr.ip() { - IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2a00:1450:4001:814::200e").unwrap()), - _ => panic!("Not IPv6"), - } - - // IPv6 test case from https://stackoverflow.com/questions/41940483/parse-ipv6-addresses-from-proc-net-tcp6-python-2-7/41948004#41948004 - let addr = parse_addressport_str("B80D01200000000067452301EFCDAB89:0").unwrap(); - assert_eq!(addr.port(), 0); - match addr.ip() { - IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2001:db8::123:4567:89ab:cdef").unwrap()), - _ => panic!("Not IPv6"), - } - - let addr = parse_addressport_str("1234:1234"); - assert!(addr.is_err()); - } - - #[test] - fn test_tcpstate_from() { - assert_eq!(TcpState::from_u8(0xA).unwrap(), TcpState::Listen); - } #[test] fn test_tcp() { diff --git a/procfs/src/process/mod.rs b/procfs/src/process/mod.rs index 34c08de1..cf2555d8 100644 --- a/procfs/src/process/mod.rs +++ b/procfs/src/process/mod.rs @@ -57,7 +57,7 @@ //! ``` use super::*; -use crate::net::{read_tcp_table, read_udp_table, TcpNetEntry, UdpNetEntry}; +use crate::net::{TcpNetEntry, UdpNetEntry}; pub use procfs_core::process::*; use rustix::fd::{AsFd, BorrowedFd, OwnedFd, RawFd}; @@ -68,7 +68,6 @@ use std::ffi::OsStr; use std::ffi::OsString; use std::fs::read_link; use std::io::{self, Read}; -use std::mem; use std::os::unix::ffi::OsStringExt; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; @@ -436,7 +435,7 @@ impl Process { /// ); /// } /// ``` - /// + /// /// (Since Linux 2.6.26) pub fn mountinfo(&self) -> ProcResult { self.read("mountinfo") @@ -525,8 +524,6 @@ impl Process { /// /// (since 2.6.0-test7) pub fn auxv(&self) -> ProcResult> { - use byteorder::{NativeEndian, ReadBytesExt}; - let mut file = FileWrapper::open_at(&self.root, &self.fd, "auxv")?; let mut map = HashMap::new(); @@ -539,9 +536,12 @@ impl Process { buf.truncate(bytes_read); let mut file = std::io::Cursor::new(buf); + let mut buf = 0usize.to_ne_bytes(); loop { - let key = file.read_uint::(mem::size_of::())? as u64; - let value = file.read_uint::(mem::size_of::())? as u64; + file.read_exact(&mut buf)?; + let key = usize::from_ne_bytes(buf) as u64; + file.read_exact(&mut buf)?; + let value = usize::from_ne_bytes(buf) as u64; if key == 0 && value == 0 { break; } @@ -750,55 +750,44 @@ impl Process { /// Reads the tcp socket table from the process net namespace pub fn tcp(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp")?; - read_tcp_table(BufReader::new(file)) + self.read_si("net/tcp").map(|net::TcpNetEntries(e)| e) } /// Reads the tcp6 socket table from the process net namespace pub fn tcp6(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp6")?; - read_tcp_table(BufReader::new(file)) + self.read_si("net/tcp6").map(|net::TcpNetEntries(e)| e) } /// Reads the udp socket table from the process net namespace pub fn udp(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp")?; - read_udp_table(BufReader::new(file)) + self.read_si("net/udp").map(|net::UdpNetEntries(e)| e) } /// Reads the udp6 socket table from the process net namespace pub fn udp6(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp6")?; - read_udp_table(BufReader::new(file)) + self.read_si("net/udp6").map(|net::UdpNetEntries(e)| e) } /// Returns basic network device statistics for all interfaces in the process net namespace /// /// See also the [dev_status()](crate::net::dev_status()) function. pub fn dev_status(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/dev")?; - - net::dev_status_from_reader(file) + self.read("net/dev").map(|net::InterfaceDeviceStatus(e)| e) } /// Reads the unix socket table pub fn unix(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/unix")?; - - net::unix_from_reader(file) + self.read("net/unix").map(|net::UnixNetEntries(e)| e) } /// Reads the ARP table from the process net namespace pub fn arp(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/arp")?; - - net::arp_from_reader(file) + self.read("net/arp").map(|net::ArpEntries(e)| e) } /// Reads the ipv4 route table from the process net namespace pub fn route(&self) -> ProcResult> { - let file = FileWrapper::open_at(&self.root, &self.fd, "net/route")?; - net::route_from_reader(file) + self.read("net/route").map(|net::RouteEntries(e)| e) } /// Opens a file to the process's memory (`/proc//mem`). @@ -847,6 +836,14 @@ impl Process { pub fn read(&self, path: &str) -> ProcResult { FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) } + + /// Parse a file relative to the process proc structure. + pub fn read_si(&self, path: &str) -> ProcResult { + FromReadSI::from_read( + FileWrapper::open_at(&self.root, &self.fd, path)?, + crate::current_system_info(), + ) + } } /// The result of [`Process::fd`], iterates over all fds in a process From 367131e1c118c72e2add5a3a93b200d3423e0690 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Mon, 10 Apr 2023 16:40:48 -0400 Subject: [PATCH 7/7] Remove broken doc links. --- procfs-core/src/keyring.rs | 2 -- procfs-core/src/locks.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/procfs-core/src/keyring.rs b/procfs-core/src/keyring.rs index 3d504f3e..225256dd 100644 --- a/procfs-core/src/keyring.rs +++ b/procfs-core/src/keyring.rs @@ -1,8 +1,6 @@ //! Functions related to the in-kernel key management and retention facility //! //! For more details on this facility, see the `keyrings(7)` man page. -//! -//! Additional functions can be found in the [kernel::keys](crate::sys::kernel::keys) module. use crate::{build_internal_error, expect, from_str, ProcResult}; use bitflags::bitflags; #[cfg(feature = "serde1")] diff --git a/procfs-core/src/locks.rs b/procfs-core/src/locks.rs index d10cbedc..5021fd1e 100644 --- a/procfs-core/src/locks.rs +++ b/procfs-core/src/locks.rs @@ -112,8 +112,6 @@ impl From<&str> for LockKind { #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] /// Details about an individual file lock /// -/// See the [`locks`] function. -/// /// For an example, see the [lslocks.rs](/~https://github.com/eminence/procfs/tree/master/examples) /// example in the source repo. pub struct Lock {