├── clippy.toml ├── procfs ├── LICENSE-MIT ├── COPYRIGHT.txt ├── LICENSE-APACHE ├── examples │ ├── partitions.rs │ ├── dump.rs │ ├── mounts.rs │ ├── iomem.rs │ ├── mountinfo.rs │ ├── ps.rs │ ├── lsmod.rs │ ├── shm.rs │ ├── crypto.rs │ ├── pressure.rs │ ├── diskstat.rs │ ├── interface_stats.rs │ ├── netstat.rs │ ├── self_memory.rs │ ├── kpagecount.rs │ ├── process_hierarchy.rs │ ├── pfn.rs │ ├── lslocks.rs │ ├── process_kpageflags.rs │ └── README.md ├── src │ ├── iomem.rs │ ├── sys │ │ ├── mod.rs │ │ ├── fs │ │ │ ├── epoll.rs │ │ │ ├── mod.rs │ │ │ └── binfmt_misc.rs │ │ ├── kernel │ │ │ ├── random.rs │ │ │ └── keys.rs │ │ └── vm.rs │ ├── crypto.rs │ ├── keyring.rs │ ├── cgroups.rs │ ├── process │ │ ├── pagemap.rs │ │ ├── namespaces.rs │ │ └── task.rs │ ├── kpagecount.rs │ ├── kpageflags.rs │ └── net.rs ├── build.rs ├── benches │ └── cpuinfo.rs └── Cargo.toml ├── rustfmt.toml ├── procfs-core ├── LICENSE-MIT ├── COPYRIGHT.txt ├── LICENSE-APACHE ├── src │ ├── process │ │ ├── smaps_rollup.rs │ │ ├── namespaces.rs │ │ ├── schedstat.rs │ │ ├── syscall.rs │ │ ├── clear_refs.rs │ │ ├── pagemap.rs │ │ └── limit.rs │ ├── sys │ │ └── mod.rs │ ├── iomem.rs │ ├── uptime.rs │ ├── partitions.rs │ ├── sysvipc_shm.rs │ ├── mounts.rs │ ├── kpageflags.rs │ ├── cgroups.rs │ ├── devices.rs │ ├── diskstats.rs │ ├── pressure.rs │ ├── cpuinfo.rs │ └── locks.rs └── Cargo.toml ├── .gitignore ├── Cargo.toml ├── LICENSE-MIT ├── msrv.md ├── .github └── workflows │ └── rust.yml ├── README.md ├── support.md └── LICENSE-APACHE /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.70" 2 | -------------------------------------------------------------------------------- /procfs/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /procfs-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /procfs/COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | ../COPYRIGHT.txt -------------------------------------------------------------------------------- /procfs/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /procfs-core/COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | ../COPYRIGHT.txt -------------------------------------------------------------------------------- /procfs-core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .idea/ 6 | lcov.info 7 | -------------------------------------------------------------------------------- /procfs/examples/partitions.rs: -------------------------------------------------------------------------------- 1 | // List partitions listed in /proc/partitions 2 | 3 | fn main() { 4 | for part_entry in procfs::partitions().unwrap() { 5 | println!("{part_entry:?}"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /procfs-core/src/process/smaps_rollup.rs: -------------------------------------------------------------------------------- 1 | use super::MemoryMaps; 2 | use crate::ProcResult; 3 | 4 | #[derive(Debug)] 5 | pub struct SmapsRollup { 6 | pub memory_map_rollup: MemoryMaps, 7 | } 8 | 9 | impl crate::FromBufRead for SmapsRollup { 10 | fn from_buf_read(r: R) -> ProcResult { 11 | MemoryMaps::from_buf_read(r).map(|m| SmapsRollup { memory_map_rollup: m }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "procfs", 4 | "procfs-core", 5 | ] 6 | 7 | [workspace.package] 8 | version = "0.18.0" 9 | authors = ["Andrew Chin "] 10 | repository = "https://github.com/eminence/procfs" 11 | keywords = ["procfs", "proc", "linux", "process"] 12 | categories = ["os::unix-apis", "filesystem"] 13 | license = "MIT OR Apache-2.0" 14 | edition = "2018" 15 | rust-version = "1.70" 16 | -------------------------------------------------------------------------------- /procfs/src/iomem.rs: -------------------------------------------------------------------------------- 1 | use super::ProcResult; 2 | use crate::Current; 3 | use procfs_core::{Iomem, PhysicalMemoryMap}; 4 | 5 | impl Current for Iomem { 6 | const PATH: &'static str = "/proc/iomem"; 7 | } 8 | 9 | /// Reads and parses the `/proc/iomem`, returning an error if there are problems. 10 | /// 11 | /// Requires root, otherwise every memory address will be zero. 12 | pub fn iomem() -> ProcResult> { 13 | Iomem::current().map(|v| v.0) 14 | } 15 | -------------------------------------------------------------------------------- /procfs-core/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Sysctl is a means of configuring certain aspects of the kernel at run-time, 2 | //! and the `/proc/sys/` directory is there so that you don't even need special tools to do it! 3 | //! 4 | //! This directory (present since 1.3.57) contains a number of files 5 | //! and subdirectories corresponding to kernel variables. 6 | //! These variables can be read and sometimes modified using the `/proc` filesystem, 7 | //! and the (deprecated) sysctl(2) system call. 8 | 9 | pub mod kernel; 10 | -------------------------------------------------------------------------------- /procfs/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Filters are extracted from `libc` filters 3 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("Missing CARGO_CFG_TARGET_OS envvar"); 4 | if !["android", "linux", "l4re"].contains(&target_os.as_str()) { 5 | eprintln!("Building {} on an for a unsupported platform. Currently only linux and android are supported", env!("CARGO_PKG_NAME")); 6 | eprintln!("(Your current target_os is {})", target_os); 7 | std::process::exit(1) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /procfs/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Sysctl is a means of configuring certain aspects of the kernel at run-time, 2 | //! and the `/proc/sys/` directory is there so that you don't even need special tools to do it! 3 | //! 4 | //! This directory (present since 1.3.57) contains a number of files 5 | //! and subdirectories corresponding to kernel variables. 6 | //! These variables can be read and sometimes modified using the `/proc` filesystem, 7 | //! and the (deprecated) sysctl(2) system call. 8 | 9 | pub mod fs; 10 | pub mod kernel; 11 | pub mod vm; 12 | -------------------------------------------------------------------------------- /procfs/examples/dump.rs: -------------------------------------------------------------------------------- 1 | extern crate procfs; 2 | use procfs::prelude::*; 3 | 4 | fn main() { 5 | let pid = std::env::args().nth(1).and_then(|s| s.parse::().ok()); 6 | 7 | let prc = if let Some(pid) = pid { 8 | println!("Info for pid={}", pid); 9 | procfs::process::Process::new(pid).unwrap() 10 | } else { 11 | procfs::process::Process::myself().unwrap() 12 | }; 13 | println!("{:#?}", prc); 14 | 15 | let stat = prc.stat().unwrap(); 16 | println!("State: {:?}", stat.state()); 17 | println!("RSS: {} bytes", stat.rss_bytes().get()); 18 | } 19 | -------------------------------------------------------------------------------- /procfs/src/crypto.rs: -------------------------------------------------------------------------------- 1 | 2 | use procfs_core::ProcResult; 3 | pub use procfs_core::CryptoTable; 4 | 5 | use crate::Current; 6 | 7 | impl Current for CryptoTable { 8 | const PATH: &'static str = "/proc/crypto"; 9 | } 10 | 11 | pub fn crypto() -> ProcResult { 12 | CryptoTable::current() 13 | } 14 | 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn read_crypto() { 22 | let table = crypto(); 23 | let table = table.expect("CrytoTable should have been read"); 24 | assert!(!table.crypto_blocks.is_empty(), "Crypto table was empty"); 25 | } 26 | } -------------------------------------------------------------------------------- /procfs/examples/mounts.rs: -------------------------------------------------------------------------------- 1 | // List mountpoints listed in /proc/mounts 2 | 3 | fn main() { 4 | let width = 15; 5 | for mount_entry in procfs::mounts().unwrap() { 6 | println!("Device: {}", mount_entry.fs_spec); 7 | println!("{:>width$}: {}", "Mount point", mount_entry.fs_file); 8 | println!("{:>width$}: {}","FS type", mount_entry.fs_vfstype); 9 | println!("{:>width$}: {}", "Dump", mount_entry.fs_freq); 10 | println!("{:>width$}: {}", "Check", mount_entry.fs_passno); 11 | print!("{:>width$}: ", "Options"); 12 | for (name, entry) in mount_entry.fs_mntops { 13 | if let Some(entry) = entry { 14 | print!("{name}: {entry} "); 15 | } 16 | } 17 | println!(""); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /procfs/examples/iomem.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Print physical location of system RAM 3 | // This requires CAP_SYS_ADMIN privilege, or root, otherwise physical memory addresses will be zero 4 | // 5 | 6 | fn main() { 7 | if !rustix::process::geteuid().is_root() { 8 | println!("WARNING: Access to /proc/iomem requires root, re-run with sudo"); 9 | } 10 | 11 | let iomem = procfs::iomem().expect("Can't read /proc/iomem"); 12 | 13 | for (_indent, map) in iomem.iter() { 14 | if map.name == "System RAM" { 15 | println!("Found RAM here: 0x{:x}-0x{:x}", map.address.0, map.address.1); 16 | } 17 | } 18 | 19 | if !rustix::process::geteuid().is_root() { 20 | println!("\n\nWARNING: Access to /proc/iomem requires root, re-run with sudo"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /procfs/benches/cpuinfo.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use procfs::CpuInfo; 3 | 4 | fn bench_cpuinfo(c: &mut Criterion) { 5 | c.bench_function("CpuInfo::new", |b| b.iter(|| black_box(CpuInfo::new().unwrap()))); 6 | 7 | let cpuinfo = black_box(CpuInfo::new().unwrap()); 8 | c.bench_function("CpuInfo::get_info", |b| b.iter(|| black_box(cpuinfo.get_info(0)))); 9 | c.bench_function("CpuInfo::model_name", |b| b.iter(|| cpuinfo.model_name(0))); 10 | c.bench_function("CpuInfo::vendor_id", |b| b.iter(|| cpuinfo.vendor_id(0))); 11 | c.bench_function("CpuInfo::physical_id", |b| b.iter(|| cpuinfo.physical_id(0))); 12 | c.bench_function("CpuInfo::flags", |b| b.iter(|| cpuinfo.flags(0))); 13 | } 14 | 15 | criterion_group!(benches, bench_cpuinfo); 16 | criterion_main!(benches); 17 | -------------------------------------------------------------------------------- /procfs/examples/mountinfo.rs: -------------------------------------------------------------------------------- 1 | use procfs::process::Process; 2 | use std::collections::HashSet; 3 | 4 | fn main() { 5 | for mount in Process::myself().unwrap().mountinfo().unwrap() { 6 | let (a, b): (HashSet<_>, HashSet<_>) = mount 7 | .mount_options 8 | .into_iter() 9 | .chain(mount.super_options) 10 | .partition(|&(_, ref m)| m.is_none()); 11 | 12 | println!( 13 | "{} on {} type {} ({})", 14 | mount.mount_source.unwrap_or_else(|| "None".to_string()), 15 | mount.mount_point.display(), 16 | mount.fs_type, 17 | a.into_iter().map(|(k, _)| k).collect::>().join(",") 18 | ); 19 | 20 | for (opt, val) in b { 21 | if let Some(val) = val { 22 | println!(" {} = {}", opt, val); 23 | } else { 24 | println!(" {}", opt); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /procfs-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procfs-core" 3 | documentation = "https://docs.rs/procfs-core/" 4 | description = "Data structures and parsing for the linux procfs pseudo-filesystem" 5 | readme = "../README.md" 6 | version.workspace = true 7 | authors.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | license.workspace = true 12 | edition.workspace = true 13 | rust-version.workspace = true 14 | 15 | [features] 16 | default = ["chrono"] 17 | serde1 = ["serde", "bitflags/serde"] 18 | 19 | [dependencies] 20 | backtrace = { version = "0.3", optional = true } 21 | bitflags = { version = "2" } 22 | chrono = { version = "0.4.20", optional = true, features = ["clock"], default-features = false } 23 | hex = "0.4" 24 | serde = { version = "1.0", features = ["derive"], optional = true } 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | rustdoc-args = ["--generate-link-to-definition"] 29 | -------------------------------------------------------------------------------- /procfs/examples/ps.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::print_literal)] 2 | 3 | extern crate procfs; 4 | 5 | /// A very basic clone of `ps` on Linux, in the simple no-argument mode. 6 | /// It shows all the processes that share the same tty as our self 7 | 8 | fn main() { 9 | let mestat = procfs::process::Process::myself().unwrap().stat().unwrap(); 10 | let tps = procfs::ticks_per_second(); 11 | 12 | println!("{: >10} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); 13 | 14 | let tty = format!("pty/{}", mestat.tty_nr().1); 15 | for p in procfs::process::all_processes().unwrap() { 16 | let prc = p.unwrap(); 17 | if let Ok(stat) = prc.stat() { 18 | if stat.tty_nr == mestat.tty_nr { 19 | // total_time is in seconds 20 | let total_time = (stat.utime + stat.stime) as f32 / (tps as f32); 21 | println!("{: >10} {: <8} {: >8} {}", stat.pid, tty, total_time, stat.comm); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /procfs/examples/lsmod.rs: -------------------------------------------------------------------------------- 1 | use procfs::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | fn print(name: &str, indent: usize, mods: &HashMap<&str, Vec<&str>>) { 5 | println!("{}{} {}", if indent == 0 { "-" } else { " " }, " ".repeat(indent), name); 6 | 7 | if let Some(uses_list) = mods.get(name) { 8 | for name in uses_list { 9 | print(name, indent + 2, mods); 10 | } 11 | } 12 | } 13 | 14 | fn main() { 15 | let procfs::KernelModules(modules) = Current::current().unwrap(); 16 | 17 | // 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. 18 | // This maps a module name to a list of modules that it uses 19 | let mut map: HashMap<&str, Vec<&str>> = HashMap::new(); 20 | 21 | for module in modules.values() { 22 | for name in &module.used_by { 23 | map.entry(name).or_default().push(&module.name); 24 | } 25 | } 26 | 27 | // println!("{:?}", map["xt_policy"]); 28 | for modname in map.keys() { 29 | print(modname, 0, &map); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The procfs Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /procfs/src/keyring.rs: -------------------------------------------------------------------------------- 1 | use super::{Current, ProcResult}; 2 | use procfs_core::keyring::*; 3 | use std::collections::HashMap; 4 | 5 | impl Current for Keys { 6 | const PATH: &'static str = "/proc/keys"; 7 | } 8 | 9 | /// Returns a list of the keys for which the reading thread has **view** permission, providing 10 | /// various information about each key. 11 | pub fn keys() -> ProcResult> { 12 | Keys::current().map(|k| k.0) 13 | } 14 | 15 | impl Current for KeyUsers { 16 | const PATH: &'static str = "/proc/key-users"; 17 | } 18 | 19 | /// Get various information for each user ID that has at least one key on the system. 20 | pub fn key_users() -> ProcResult> { 21 | KeyUsers::current().map(|k| k.0) 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn test_keys() { 30 | for key in keys().unwrap() { 31 | println!("{:#?}", key); 32 | } 33 | } 34 | 35 | #[test] 36 | fn test_key_users() { 37 | for (_user, data) in key_users().unwrap() { 38 | println!("{:#?}", data); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /procfs/examples/shm.rs: -------------------------------------------------------------------------------- 1 | extern crate procfs; 2 | use procfs::prelude::*; 3 | 4 | /// List processes using posix shared memory segments 5 | 6 | fn main() { 7 | let shared_memory_vec = procfs::SharedMemorySegments::current().unwrap(); 8 | 9 | for shared_memory in &shared_memory_vec.0 { 10 | println!("key: {}, shmid: {}", shared_memory.key, shared_memory.shmid); 11 | println!("============"); 12 | 13 | for prc in procfs::process::all_processes().unwrap() { 14 | let prc = prc.unwrap(); 15 | match prc.smaps() { 16 | Ok(memory_maps) => { 17 | for memory_map in &memory_maps { 18 | if let procfs::process::MMapPath::Vsys(key) = memory_map.pathname { 19 | if key == shared_memory.key && memory_map.inode == shared_memory.shmid { 20 | println!("{}: {:?}", prc.pid, prc.cmdline().unwrap()); 21 | } 22 | } 23 | } 24 | } 25 | Err(_) => continue, 26 | } 27 | } 28 | println!(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /procfs/examples/crypto.rs: -------------------------------------------------------------------------------- 1 | use std::env::args; 2 | 3 | use procfs::crypto; 4 | 5 | pub fn main() { 6 | let crypto = crypto().expect("Was not able to access current crypto"); 7 | let name_arg = args().nth(1); 8 | for (name, entries) in crypto.crypto_blocks { 9 | if let Some(ref name_find) = name_arg { 10 | if !name.contains(name_find) { 11 | continue; 12 | } 13 | } 14 | println!("Type: {name}"); 15 | for block in entries { 16 | println!("{:>14}: {}", "Name", block.name); 17 | println!("{:>14}: {}", "Driver", block.driver); 18 | println!("{:>14}: {}", "Module", block.module); 19 | println!("{:>14}: {}", "Priority", block.priority); 20 | println!("{:>14}: {}", "Ref Count", block.ref_count); 21 | println!("{:>14}: {:?}", "Self Test", block.self_test); 22 | println!("{:>14}: {}", "Internal", block.internal); 23 | println!("{:>14}: {}", "fips enabled", block.fips_enabled); 24 | println!("{:>14}: {:?}", "Type Details", block.crypto_type); 25 | println!(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /procfs-core/src/process/namespaces.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::OsString; 3 | use std::path::PathBuf; 4 | 5 | #[cfg(feature = "serde1")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | /// Information about a namespace 9 | #[derive(Debug, Clone, Eq)] 10 | #[cfg_attr(all(feature = "serde1", not(target_family = "wasm")), derive(Serialize, Deserialize))] 11 | pub struct Namespace { 12 | /// Namespace type 13 | pub ns_type: OsString, 14 | /// Handle to the namespace 15 | pub path: PathBuf, 16 | /// Namespace identifier (inode number) 17 | pub identifier: u64, 18 | /// Device id of the namespace 19 | pub device_id: u64, 20 | } 21 | 22 | impl PartialEq for Namespace { 23 | fn eq(&self, other: &Self) -> bool { 24 | // see https://lore.kernel.org/lkml/87poky5ca9.fsf@xmission.com/ 25 | self.identifier == other.identifier && self.device_id == other.device_id 26 | } 27 | } 28 | 29 | /// All namespaces of a process. 30 | #[derive(Debug, Clone, PartialEq, Eq)] 31 | #[cfg_attr(all(feature = "serde1", not(target_family = "wasm")), derive(Serialize, Deserialize))] 32 | pub struct Namespaces(pub HashMap); 33 | -------------------------------------------------------------------------------- /procfs/examples/pressure.rs: -------------------------------------------------------------------------------- 1 | use procfs::{prelude::*, CpuPressure, IoPressure, MemoryPressure, PressureRecord}; 2 | 3 | /// A basic example of /proc/pressure/ usage. 4 | fn main() { 5 | if let Ok(pressure) = MemoryPressure::current() { 6 | println!("Memory Pressure:"); 7 | println!("{:>10}:", "Some"); 8 | print_pressure(pressure.some, 20); 9 | println!("{:>10}:", "Full"); 10 | print_pressure(pressure.full, 20); 11 | } 12 | if let Ok(pressure) = CpuPressure::current() { 13 | println!("CPU Pressure:"); 14 | print_pressure(pressure.some, 20); 15 | } 16 | if let Ok(pressure) = IoPressure::current() { 17 | println!("IO Pressure:"); 18 | println!("{:>10}:", "Some"); 19 | print_pressure(pressure.some, 20); 20 | println!("{:>10}:", "Full"); 21 | print_pressure(pressure.full, 20); 22 | } 23 | } 24 | 25 | fn print_pressure(pressure: PressureRecord, width: usize) { 26 | println!("{:>width$}: {}", "Average 10", pressure.avg10); 27 | println!("{:>width$}: {}", "Average 60", pressure.avg60); 28 | println!("{:>width$}: {}", "Average 300", pressure.avg300); 29 | println!("{:>width$}: {}", "Total", pressure.total); 30 | } 31 | -------------------------------------------------------------------------------- /procfs/src/sys/fs/epoll.rs: -------------------------------------------------------------------------------- 1 | use crate::{read_value, write_value, ProcResult}; 2 | 3 | /// Get the limit on the total number of file descriptors that a user can register across all epoll instances. 4 | /// 5 | /// The limit is per real user ID. Each registered file descriptor costs roughtly 90 bytes on a 32-bit kernel, 6 | /// and roughly 160 bytes on a 64-bit kernel. Currently, the default value for `max_user_watches` is 1/25 (4%) 7 | /// of the available low memory, divided by the registration cost in bytes. 8 | /// 9 | /// (Since Linux 2.6.28) 10 | pub fn max_user_watches() -> ProcResult { 11 | read_value("/proc/sys/fs/epoll/max_user_watches") 12 | } 13 | 14 | /// Sets the limit on the total number of file descriptors that a user can register across all epoll instances. 15 | pub fn set_max_user_watches(val: u64) -> ProcResult<()> { 16 | write_value("/proc/sys/fs/epoll/max_user_watches", val) 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use crate::KernelVersion; 23 | 24 | #[test] 25 | fn test_max_user_watches() { 26 | if KernelVersion::current().unwrap() >= KernelVersion::new(2, 6, 28) { 27 | println!("{}", max_user_watches().unwrap()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /procfs/src/cgroups.rs: -------------------------------------------------------------------------------- 1 | use super::process::Process; 2 | use crate::{Current, ProcResult}; 3 | use procfs_core::CGroupControllers; 4 | pub use procfs_core::ProcessCGroups; 5 | 6 | impl Current for CGroupControllers { 7 | const PATH: &'static str = "/proc/cgroups"; 8 | } 9 | 10 | /// Information about the cgroup controllers that are compiled into the kernel 11 | /// 12 | /// (since Linux 2.6.24) 13 | pub fn cgroups() -> ProcResult { 14 | CGroupControllers::current() 15 | } 16 | 17 | impl Process { 18 | /// Describes control groups to which the process with the corresponding PID belongs. 19 | /// 20 | /// The displayed information differs for cgroupsversion 1 and version 2 hierarchies. 21 | pub fn cgroups(&self) -> ProcResult { 22 | self.read("cgroup") 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn test_cgroups() { 32 | let groups = cgroups().unwrap(); 33 | println!("{:?}", groups); 34 | } 35 | 36 | #[test] 37 | fn test_process_cgroups() { 38 | let myself = Process::myself().unwrap(); 39 | let groups = myself.cgroups(); 40 | println!("{:?}", groups); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /procfs-core/src/process/schedstat.rs: -------------------------------------------------------------------------------- 1 | use crate::from_iter; 2 | use crate::ProcResult; 3 | use std::io::Read; 4 | 5 | #[cfg(feature = "serde1")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | /// Provides scheduler statistics of the process, based on the `/proc//schedstat` file. 9 | #[derive(Debug, Clone)] 10 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 11 | pub struct Schedstat { 12 | /// Time spent on the cpu. 13 | /// 14 | /// Measured in nanoseconds. 15 | pub sum_exec_runtime: u64, 16 | /// Time spent waiting on a runqueue. 17 | /// 18 | /// Measured in nanoseconds. 19 | pub run_delay: u64, 20 | /// \# of timeslices run on this cpu. 21 | pub pcount: u64, 22 | } 23 | 24 | impl crate::FromRead for Schedstat { 25 | fn from_read(mut r: R) -> ProcResult { 26 | let mut line = String::new(); 27 | r.read_to_string(&mut line)?; 28 | let mut s = line.split_whitespace(); 29 | 30 | let schedstat = Schedstat { 31 | sum_exec_runtime: expect!(from_iter(&mut s)), 32 | run_delay: expect!(from_iter(&mut s)), 33 | pcount: expect!(from_iter(&mut s)), 34 | }; 35 | 36 | if cfg!(test) { 37 | assert!(s.next().is_none()); 38 | } 39 | 40 | Ok(schedstat) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /procfs/examples/diskstat.rs: -------------------------------------------------------------------------------- 1 | use procfs::{diskstats, process::Process, DiskStat}; 2 | use std::collections::HashMap; 3 | use std::iter::FromIterator; 4 | 5 | fn main() { 6 | let me = Process::myself().unwrap(); 7 | let mounts = me.mountinfo().unwrap(); 8 | 9 | // Get a list of all disks that we have IO stat info on 10 | let disk_stats: HashMap<(i32, i32), DiskStat> = 11 | HashMap::from_iter(diskstats().unwrap().into_iter().map(|i| ((i.major, i.minor), i))); 12 | 13 | for mount in mounts { 14 | // parse the majmin string (something like "0:3") into an (i32, i32) tuple 15 | let (maj, min): (i32, i32) = { 16 | let mut s = mount.majmin.split(':'); 17 | (s.next().unwrap().parse().unwrap(), s.next().unwrap().parse().unwrap()) 18 | }; 19 | 20 | if let Some(stat) = disk_stats.get(&(maj, min)) { 21 | println!("{} mounted on {}:", stat.name, mount.mount_point.display()); 22 | println!(" total reads: {} ({} ms)", stat.reads, stat.time_reading); 23 | println!(" total writes: {} ({} ms)", stat.writes, stat.time_writing); 24 | println!( 25 | " total flushes: {} ({} ms)", 26 | stat.flushes.unwrap_or(0), 27 | stat.time_flushing.unwrap_or(0) 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /msrv.md: -------------------------------------------------------------------------------- 1 | # MSRV for the `procfs` crate 2 | 3 | The latest version of the `procfs` crate is only tested against the latest stable compiler. However, it 4 | may work with older compilers. 5 | 6 | If you are using an older compiler and need a `procfs` bug fix on an older `procfs` version, 7 | please open an issue on github, and we'll backport that bugfix. If you need a 8 | feature backported, please also open an issue to ask for a backport, but you may 9 | be asked to help by opening a PR. 10 | 11 | If you have any comments on this MSRV policy, please leave a comment 12 | [on this issue](https://github.com/eminence/procfs/issues/223). 13 | 14 | The table below attempts to list the latest version of `procfs` you can use, for 15 | a given rustc compiler. 16 | 17 | 18 | | Rust Version | `procfs` version | Notes | 19 | |--- | --- |--- | 20 | | Latest | Latest | The latest version of procfs always supports the latest rustc compiler | 21 | | 1.65 to 1.70 | 0.17 | | 22 | | 1.48 to 1.67 | 0.16 | [^1] | 23 | | 1.34 to 1.54 | 0.13 | [^1] [^2] | 24 | 25 | 26 | [^1]: `procfs` will support these older versions of rustc, but you'll need 27 | to pin some of the `procfs` dependencies to older versions. The dependencies that need pinning can change over time, but are likely `hex`, `bitflags`, and `flate2`. 28 | 29 | [^2]: If you use the optional backtrace feature, you'll need rust 1.42 or newer. 30 | -------------------------------------------------------------------------------- /procfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procfs" 3 | documentation = "https://docs.rs/procfs/" 4 | description = "Interface to the linux procfs pseudo-filesystem" 5 | readme = "../README.md" 6 | version.workspace = true 7 | authors.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | license.workspace = true 12 | edition.workspace = true 13 | rust-version.workspace = true 14 | 15 | [features] 16 | backtrace = ["dep:backtrace", "procfs-core/backtrace"] 17 | default = ["chrono", "flate2", "procfs-core/default"] 18 | serde1 = ["serde", "procfs-core/serde1"] 19 | 20 | [dependencies] 21 | procfs-core = { path = "../procfs-core", version = "0.18.0", default-features = false } 22 | rustix = { version = "1.0.1", features = ["fs", "process", "param", "system", "thread"] } 23 | bitflags = { version = "2.0", default-features = false } 24 | chrono = {version = "0.4.20", optional = true, features = ["clock"], default-features = false } 25 | flate2 = { version = "1.0.3", optional = true } 26 | backtrace = { version = "0.3", optional = true } 27 | serde = { version = "1.0", features = ["derive"], optional = true } 28 | 29 | [dev-dependencies] 30 | criterion = "0.5" 31 | procinfo = "0.4.2" 32 | failure = "0.1" 33 | libc = "0.2.139" 34 | 35 | [package.metadata.docs.rs] 36 | all-features = true 37 | rustdoc-args = ["--generate-link-to-definition"] 38 | 39 | [[bench]] 40 | name = "cpuinfo" 41 | harness = false 42 | -------------------------------------------------------------------------------- /procfs/examples/interface_stats.rs: -------------------------------------------------------------------------------- 1 | //! For each interface, display the number of bytes sent and received, along with a data rate 2 | 3 | fn main() { 4 | let delay = std::time::Duration::from_secs(2); 5 | 6 | let mut prev_stats = procfs::net::dev_status().unwrap(); 7 | let mut prev_now = std::time::Instant::now(); 8 | loop { 9 | std::thread::sleep(delay); 10 | let now = std::time::Instant::now(); 11 | let dev_stats = procfs::net::dev_status().unwrap(); 12 | 13 | // calculate diffs from previous 14 | let dt = (now - prev_now).as_millis() as f32 / 1000.0; 15 | 16 | let mut stats: Vec<_> = dev_stats.values().collect(); 17 | stats.sort_by_key(|s| &s.name); 18 | println!(); 19 | println!( 20 | "{:>16}: {:<20} {:<20} ", 21 | "Interface", "bytes recv", "bytes sent" 22 | ); 23 | println!( 24 | "{:>16} {:<20} {:<20}", 25 | "================", "====================", "====================" 26 | ); 27 | for stat in stats { 28 | println!( 29 | "{:>16}: {:<20} {:>6.1} kbps {:<20} {:>6.1} kbps ", 30 | stat.name, 31 | stat.recv_bytes, 32 | (stat.recv_bytes - prev_stats.get(&stat.name).unwrap().recv_bytes) as f32 / dt / 1000.0, 33 | stat.sent_bytes, 34 | (stat.sent_bytes - prev_stats.get(&stat.name).unwrap().sent_bytes) as f32 / dt / 1000.0 35 | ); 36 | } 37 | 38 | prev_stats = dev_stats; 39 | prev_now = now; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /procfs/examples/netstat.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::print_literal)] 2 | 3 | extern crate procfs; 4 | 5 | use procfs::process::{FDTarget, Stat}; 6 | 7 | use std::collections::HashMap; 8 | 9 | fn main() { 10 | // get all processes 11 | let all_procs = procfs::process::all_processes().unwrap(); 12 | 13 | // build up a map between socket inodes and processes: 14 | let mut map: HashMap = HashMap::new(); 15 | for p in all_procs { 16 | let process = p.unwrap(); 17 | if let (Ok(stat), Ok(fds)) = (process.stat(), process.fd()) { 18 | for fd in fds { 19 | if let FDTarget::Socket(inode) = fd.unwrap().target { 20 | map.insert(inode, stat.clone()); 21 | } 22 | } 23 | } 24 | } 25 | 26 | // get the tcp table 27 | let tcp = procfs::net::tcp().unwrap(); 28 | let tcp6 = procfs::net::tcp6().unwrap(); 29 | 30 | println!( 31 | "{:<26} {:<26} {:<15} {:<8} {}", 32 | "Local address", "Remote address", "State", "Inode", "PID/Program name" 33 | ); 34 | 35 | for entry in tcp.into_iter().chain(tcp6) { 36 | // find the process (if any) that has an open FD to this entry's inode 37 | let local_address = format!("{}", entry.local_address); 38 | let remote_addr = format!("{}", entry.remote_address); 39 | let state = format!("{:?}", entry.state); 40 | if let Some(stat) = map.get(&entry.inode) { 41 | println!( 42 | "{:<26} {:<26} {:<15} {:<12} {}/{}", 43 | local_address, remote_addr, state, entry.inode, stat.pid, stat.comm 44 | ); 45 | } else { 46 | // We might not always be able to find the process assocated with this socket 47 | println!( 48 | "{:<26} {:<26} {:<15} {:<12} -", 49 | local_address, remote_addr, state, entry.inode 50 | ); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /procfs/examples/self_memory.rs: -------------------------------------------------------------------------------- 1 | use procfs::process::Process; 2 | 3 | fn main() { 4 | let me = Process::myself().expect("Unable to load myself!"); 5 | println!("PID: {}", me.pid); 6 | 7 | let page_size = procfs::page_size(); 8 | println!("Memory page size: {}", page_size); 9 | 10 | // Note: when comparing the below values to what "top" will display, note that "top" will use 11 | // base-2 units (kibibytes), not base-10 units (kilobytes). 12 | 13 | if let Ok(stat) = me.stat() { 14 | println!("== Data from /proc/self/stat:"); 15 | println!("Total virtual memory used: {} bytes", stat.vsize); 16 | println!( 17 | "Total resident set: {} pages ({} bytes)", 18 | stat.rss, 19 | stat.rss * page_size 20 | ); 21 | println!(); 22 | } 23 | 24 | if let Ok(statm) = me.statm() { 25 | println!("== Data from /proc/self/statm:"); 26 | println!( 27 | "Total virtual memory used: {} pages ({} bytes)", 28 | statm.size, 29 | statm.size * page_size 30 | ); 31 | println!( 32 | "Total resident set: {} pages ({} byte)s", 33 | statm.resident, 34 | statm.resident * page_size 35 | ); 36 | println!( 37 | "Total shared memory: {} pages ({} bytes)", 38 | statm.shared, 39 | statm.shared * page_size 40 | ); 41 | println!(); 42 | } 43 | 44 | if let Ok(status) = me.status() { 45 | println!("== Data from /proc/self/status:"); 46 | println!( 47 | "Total virtual memory used: {} bytes", 48 | status.vmsize.expect("vmsize") * 1024 49 | ); 50 | println!("Total resident set: {} bytes", status.vmrss.expect("vmrss") * 1024); 51 | println!( 52 | "Total shared memory: {} bytes", 53 | status.rssfile.expect("rssfile") * 1024 + status.rssshmem.expect("rssshmem") * 1024 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: 5 16 * * 3 8 | 9 | 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | toolchain: ["stable", "beta", "nightly"] 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Install toolchains 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: ${{ matrix.toolchain }} 26 | 27 | - name: Build 28 | run: cargo +${{ matrix.toolchain }} build --workspace --verbose 29 | - name: Run tests 30 | run: cargo +${{ matrix.toolchain }} test --workspace --all-features --verbose -- --skip _runsinglethread 31 | - name: Run tests (single-threaded tests) 32 | run: cargo +${{ matrix.toolchain }} test --workspace --all-features --verbose -- _runsinglethread --test-threads 1 33 | 34 | - name: Build docs (all features) 35 | run: cargo +${{ matrix.toolchain }} doc --workspace --all-features 36 | 37 | - name: Build docs 38 | run: cargo +${{ matrix.toolchain }} doc --workspace 39 | 40 | 41 | check: 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | toolchain: ["stable", "beta", "nightly"] 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v2 49 | 50 | - name: Install toolchains (aarch64) 51 | uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: ${{ matrix.toolchain }} 54 | target: aarch64-linux-android 55 | - name: Install toolchains (i686) 56 | uses: actions-rs/toolchain@v1 57 | with: 58 | toolchain: ${{ matrix.toolchain }} 59 | target: i686-unknown-linux-gnu 60 | 61 | - name: cargo check (aarch64) 62 | run: cargo +${{ matrix.toolchain }} check --workspace --target aarch64-linux-android --all-features 63 | - name: cargo check (i686) 64 | run: cargo +${{ matrix.toolchain }} check --workspace --target i686-unknown-linux-gnu --all-features 65 | 66 | -------------------------------------------------------------------------------- /procfs-core/src/iomem.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde1")] 2 | use serde::{Deserialize, Serialize}; 3 | use std::io::BufRead; 4 | 5 | use super::ProcResult; 6 | use crate::{process::Pfn, split_into_num}; 7 | 8 | #[derive(Debug, PartialEq, Eq, Clone, Hash)] 9 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 10 | pub struct Iomem(pub Vec<(usize, PhysicalMemoryMap)>); 11 | 12 | impl crate::FromBufRead for Iomem { 13 | fn from_buf_read(r: R) -> ProcResult { 14 | let mut vec = Vec::new(); 15 | 16 | for line in r.lines() { 17 | let line = expect!(line); 18 | 19 | let (indent, map) = PhysicalMemoryMap::from_line(&line)?; 20 | 21 | vec.push((indent, map)); 22 | } 23 | 24 | Ok(Iomem(vec)) 25 | } 26 | } 27 | 28 | #[derive(Debug, PartialEq, Eq, Clone, Hash)] 29 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 30 | pub struct PhysicalMemoryMap { 31 | /// The address space in the process that the mapping occupies. 32 | pub address: (u64, u64), 33 | pub name: String, 34 | } 35 | 36 | impl PhysicalMemoryMap { 37 | fn from_line(line: &str) -> ProcResult<(usize, PhysicalMemoryMap)> { 38 | let indent = line.chars().take_while(|c| *c == ' ').count() / 2; 39 | let line = line.trim(); 40 | let mut s = line.split(" : "); 41 | let address = expect!(s.next()); 42 | let name = expect!(s.next()); 43 | 44 | Ok(( 45 | indent, 46 | PhysicalMemoryMap { 47 | address: split_into_num(address, '-', 16)?, 48 | name: String::from(name), 49 | }, 50 | )) 51 | } 52 | 53 | /// Get the PFN range for the mapping 54 | /// 55 | /// First element of the tuple (start) is included. 56 | /// Second element (end) is excluded 57 | pub fn get_range(&self) -> impl crate::WithSystemInfo { 58 | move |si: &crate::SystemInfo| { 59 | let start = self.address.0 / si.page_size(); 60 | let end = (self.address.1 + 1) / si.page_size(); 61 | 62 | (Pfn(start), Pfn(end)) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /procfs/examples/kpagecount.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Print the physical memory page with the most references 3 | // 4 | // Require CAP_SYS_ADMIN privilege, or root 5 | // 6 | // Sample output: 7 | // 8 | // Found RAM here: 0x1000-0x9fbff 9 | // Lots of references to this locations: addr=0x9d000, pfn=157, refs=0 10 | // Found RAM here: 0x100000-0xdffeffff 11 | // Lots of references to this locations: addr=0x81ba3000, pfn=531363, refs=128 12 | // Found RAM here: 0x100000000-0x11fffffff 13 | // Lots of references to this locations: addr=0x1b575000, pfn=111989, refs=134 14 | // 15 | 16 | use procfs::prelude::*; 17 | 18 | fn main() { 19 | if !rustix::process::geteuid().is_root() { 20 | panic!("ERROR: Access to /proc/iomem requires root, re-run with sudo"); 21 | } 22 | 23 | let page_size = procfs::page_size(); 24 | 25 | // /proc/iomem contain a list of memory mapping, but we're only interested in RAM mapping 26 | let iomem = procfs::iomem().expect("Can't open /proc/iomem"); 27 | 28 | let ram = iomem 29 | .iter() 30 | .filter_map(|(_, map)| if map.name == "System RAM" { Some(map) } else { None }); 31 | let mut kpagecount = procfs::KPageCount::new().expect("Can't open /proc/kpagecount"); 32 | 33 | for map in ram { 34 | println!("Found RAM here: 0x{:x}-0x{:x}", map.address.0, map.address.1); 35 | 36 | // Physical memory is divided into pages of `page_size` bytes (usually 4kiB) 37 | // Each page is referenced by its Page Fram Number (PFN) 38 | let (start_pfn, end_pfn) = map.get_range().get(); 39 | 40 | let page_references = kpagecount 41 | .get_count_in_range(start_pfn, end_pfn) 42 | .expect("Can't read from /proc/kpagecount"); 43 | 44 | // find the page with most references 45 | let (pfn, refs) = page_references 46 | .iter() 47 | .enumerate() 48 | .max_by(|(_, a), (_, b)| a.cmp(b)) 49 | .unwrap(); 50 | 51 | println!( 52 | "Lots of references to this locations: addr=0x{:x}, pfn={}, refs={}", 53 | pfn * page_size as usize, 54 | pfn, 55 | refs 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /procfs-core/src/uptime.rs: -------------------------------------------------------------------------------- 1 | use crate::{expect, ProcResult}; 2 | 3 | use std::io::Read; 4 | use std::str::FromStr; 5 | use std::time::Duration; 6 | 7 | /// The uptime of the system, based on the `/proc/uptime` file. 8 | #[derive(Debug, Clone)] 9 | #[non_exhaustive] 10 | pub struct Uptime { 11 | /// The uptime of the system (including time spent in suspend). 12 | pub uptime: f64, 13 | 14 | /// The sum of how much time each core has spent idle. 15 | pub idle: f64, 16 | } 17 | 18 | impl super::FromRead for Uptime { 19 | fn from_read(mut r: R) -> ProcResult { 20 | let mut buf = Vec::with_capacity(128); 21 | r.read_to_end(&mut buf)?; 22 | 23 | let line = String::from_utf8_lossy(&buf); 24 | let buf = line.trim(); 25 | 26 | let mut s = buf.split(' '); 27 | let uptime = expect!(f64::from_str(expect!(s.next()))); 28 | let idle = expect!(f64::from_str(expect!(s.next()))); 29 | 30 | Ok(Uptime { uptime, idle }) 31 | } 32 | } 33 | 34 | impl Uptime { 35 | /// The uptime of the system (including time spent in suspend). 36 | pub fn uptime_duration(&self) -> Duration { 37 | let secs = self.uptime.trunc() as u64; 38 | let csecs = (self.uptime.fract() * 100.0).round() as u32; 39 | let nsecs = csecs * 10_000_000; 40 | 41 | Duration::new(secs, nsecs) 42 | } 43 | 44 | /// The sum of how much time each core has spent idle. 45 | pub fn idle_duration(&self) -> Duration { 46 | let secs = self.idle.trunc() as u64; 47 | let csecs = (self.idle.fract() * 100.0).round() as u32; 48 | let nsecs = csecs * 10_000_000; 49 | 50 | Duration::new(secs, nsecs) 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | use crate::FromRead; 58 | use std::io::Cursor; 59 | 60 | #[test] 61 | fn test_uptime() { 62 | let reader = Cursor::new(b"2578790.61 1999230.98\n"); 63 | let uptime = Uptime::from_read(reader).unwrap(); 64 | 65 | assert_eq!(uptime.uptime_duration(), Duration::new(2578790, 610_000_000)); 66 | assert_eq!(uptime.idle_duration(), Duration::new(1999230, 980_000_000)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /procfs-core/src/process/syscall.rs: -------------------------------------------------------------------------------- 1 | use crate::{from_iter, from_iter_radix, ProcResult}; 2 | #[cfg(feature = "serde1")] 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::io::Read; 6 | 7 | /// Syscall information about the process, based on the `/proc//syscall` file. 8 | /// 9 | /// New variants to this enum may be added at any time (even without a major or minor semver bump). 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 12 | #[non_exhaustive] 13 | pub enum Syscall { 14 | /// The process is running, and so the values are not present 15 | Running, 16 | Blocked { 17 | /// The syscall this process is blocked on. 18 | /// If the syscall_number is -1, then not blocked on a syscall (blocked for another reason). 19 | /// Note that the rest of the values are still filled in. 20 | syscall_number: i64, 21 | /// The argument registers 22 | argument_registers: [u64; 6], 23 | /// e.g. rsp 24 | stack_pointer: u64, 25 | /// e.g. rip 26 | program_counter: u64, 27 | }, 28 | } 29 | 30 | impl crate::FromRead for Syscall { 31 | fn from_read(mut r: R) -> ProcResult { 32 | // read in entire thing, this is only going to be 1 line 33 | let mut buf = Vec::with_capacity(512); 34 | r.read_to_end(&mut buf)?; 35 | 36 | let line = String::from_utf8_lossy(&buf); 37 | let buf = line.trim(); 38 | 39 | if buf == "running" { 40 | Ok(Self::Running) 41 | } else { 42 | let mut values = buf.split(' '); 43 | 44 | let syscall_number: i64 = expect!(from_iter(&mut values), "failed to read syscall number"); 45 | 46 | let mut argument_registers: [u64; 6] = [0; 6]; 47 | for arg_reg in argument_registers.iter_mut() { 48 | *arg_reg = expect!(from_iter_radix(&mut values, 16), "failed to read argument register"); 49 | } 50 | 51 | let stack_pointer: u64 = expect!(from_iter_radix(&mut values, 16), "failed to read stack pointer"); 52 | let program_counter: u64 = expect!(from_iter_radix(&mut values, 16), "failed to read program counter"); 53 | 54 | Ok(Self::Blocked { 55 | syscall_number, 56 | argument_registers, 57 | stack_pointer, 58 | program_counter, 59 | }) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /procfs/src/process/pagemap.rs: -------------------------------------------------------------------------------- 1 | use crate::{FileWrapper, ProcResult}; 2 | use procfs_core::process::PageInfo; 3 | use std::{ 4 | io::{BufReader, Read, Seek, SeekFrom}, 5 | mem::size_of, 6 | ops::{Bound, RangeBounds}, 7 | }; 8 | 9 | impl super::Process { 10 | /// Returns a struct that can be used to access information in the `/proc/pid/pagemap` file. 11 | pub fn pagemap(&self) -> ProcResult { 12 | let path = self.root.join("pagemap"); 13 | let file = FileWrapper::open(&path)?; 14 | Ok(PageMap::from_file_wrapper(file)) 15 | } 16 | } 17 | 18 | /// Parses page table entries accessing `/proc//pagemap`. 19 | pub struct PageMap { 20 | reader: BufReader, 21 | } 22 | 23 | impl PageMap { 24 | pub(crate) fn from_file_wrapper(file: FileWrapper) -> Self { 25 | Self { 26 | reader: BufReader::new(file), 27 | } 28 | } 29 | 30 | /// Retrieves information in the page table entry for the page at index `page_index`. 31 | /// 32 | /// Some mappings are not accessible, and will return an Err: `vsyscall` 33 | pub fn get_info(&mut self, page_index: usize) -> ProcResult { 34 | self.get_range_info(page_index..page_index + 1) 35 | .map(|mut vec| vec.pop().unwrap()) 36 | } 37 | 38 | /// Retrieves information in the page table entry for the pages with index in range `page_range`. 39 | pub fn get_range_info(&mut self, page_range: impl RangeBounds) -> ProcResult> { 40 | // `start` is always included 41 | let start = match page_range.start_bound() { 42 | Bound::Included(v) => *v, 43 | Bound::Excluded(v) => *v + 1, 44 | Bound::Unbounded => 0, 45 | }; 46 | 47 | // `end` is always excluded 48 | let end = match page_range.end_bound() { 49 | Bound::Included(v) => *v + 1, 50 | Bound::Excluded(v) => *v, 51 | Bound::Unbounded => std::usize::MAX / crate::page_size() as usize, 52 | }; 53 | 54 | let start_position = (start * size_of::()) as u64; 55 | self.reader.seek(SeekFrom::Start(start_position))?; 56 | 57 | let mut page_infos = Vec::with_capacity((end - start) as usize); 58 | for _ in start..end { 59 | let mut info_bytes = [0; size_of::()]; 60 | self.reader.read_exact(&mut info_bytes)?; 61 | page_infos.push(PageInfo::parse_info(u64::from_ne_bytes(info_bytes))); 62 | } 63 | 64 | Ok(page_infos) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /procfs/src/process/namespaces.rs: -------------------------------------------------------------------------------- 1 | use super::Process; 2 | use crate::{build_internal_error, ProcResult}; 3 | use procfs_core::process::{Namespace, Namespaces}; 4 | use rustix::fs::{AtFlags, Mode, OFlags}; 5 | use std::{collections::HashMap, ffi::OsString}; 6 | 7 | impl Process { 8 | /// Describes namespaces to which the process with the corresponding PID belongs. 9 | /// Doc reference: 10 | /// The namespace type is the key for the HashMap, i.e 'net', 'user', etc. 11 | pub fn namespaces(&self) -> ProcResult { 12 | let mut namespaces = HashMap::new(); 13 | let dir_ns = wrap_io_error!( 14 | self.root.join("ns"), 15 | rustix::fs::openat( 16 | &self.fd, 17 | "ns", 18 | OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, 19 | Mode::empty() 20 | ) 21 | )?; 22 | let dir = wrap_io_error!(self.root.join("ns"), rustix::fs::Dir::read_from(&dir_ns))?; 23 | for entry in dir { 24 | let entry = entry.map_err(|_| build_internal_error!(format!("Unable to get ns dir entry")))?; 25 | match entry.file_name().to_bytes() { 26 | b"." | b".." => continue, 27 | _ => {} 28 | }; 29 | 30 | let path = self.root.join("ns").join(entry.file_name().to_string_lossy().as_ref()); 31 | let ns_type = OsString::from(entry.file_name().to_string_lossy().as_ref()); 32 | let stat = rustix::fs::statat(&dir_ns, entry.file_name(), AtFlags::empty()) 33 | .map_err(|_| build_internal_error!(format!("Unable to stat {:?}", path)))?; 34 | 35 | if let Some(n) = namespaces.insert( 36 | ns_type.clone(), 37 | Namespace { 38 | ns_type, 39 | path, 40 | identifier: stat.st_ino, 41 | device_id: stat.st_dev, 42 | }, 43 | ) { 44 | return Err(build_internal_error!(format!( 45 | "NsType appears more than once {:?}", 46 | n.ns_type 47 | ))); 48 | } 49 | } 50 | 51 | Ok(Namespaces(namespaces)) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use crate::process::Process; 58 | 59 | #[test] 60 | fn test_namespaces() { 61 | let myself = Process::myself().unwrap(); 62 | let namespaces = myself.namespaces().unwrap(); 63 | print!("{:?}", namespaces); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /procfs/examples/process_hierarchy.rs: -------------------------------------------------------------------------------- 1 | use procfs::process::{all_processes, Stat}; 2 | 3 | struct ProcessEntry { 4 | stat: Stat, 5 | cmdline: Option>, 6 | } 7 | 8 | /// Print all processes as a tree. 9 | /// The tree reflects the hierarchical relationship between parent and child processes. 10 | fn main() { 11 | // Get all processes 12 | let processes: Vec = match all_processes() { 13 | Err(err) => { 14 | println!("Failed to read all processes: {}", err); 15 | return; 16 | } 17 | Ok(processes) => processes, 18 | } 19 | .filter_map(|v| { 20 | v.and_then(|p| { 21 | let stat = p.stat()?; 22 | let cmdline = p.cmdline().ok(); 23 | Ok(ProcessEntry { stat, cmdline }) 24 | }) 25 | .ok() 26 | }) 27 | .collect(); 28 | // Iterate through all processes and start with top-level processes. 29 | // Those can be identified by checking if their parent PID is zero. 30 | for process in &processes { 31 | if process.stat.ppid == 0 { 32 | print_process(process, &processes, 0); 33 | } 34 | } 35 | } 36 | 37 | /// Take a process, print its command and recursively list all child processes. 38 | /// This function will call itself until no further children can be found. 39 | /// It's a depth-first tree exploration. 40 | /// 41 | /// depth: The hierarchical depth of the process 42 | fn print_process(process: &ProcessEntry, all_processes: &Vec, depth: usize) { 43 | let cmdline = match &process.cmdline { 44 | Some(cmdline) => cmdline.join(" "), 45 | None => "zombie process".into(), 46 | }; 47 | 48 | // Some processes seem to have an empty cmdline. 49 | if cmdline.is_empty() { 50 | return; 51 | } 52 | 53 | // 10 characters width for the pid 54 | let pid_length = 8; 55 | let mut pid = process.stat.pid.to_string(); 56 | pid.push_str(&" ".repeat(pid_length - pid.len())); 57 | 58 | let padding = " ".repeat(4 * depth); 59 | println!("{}{}{}", pid, padding, cmdline); 60 | 61 | let children = get_children(process.stat.pid, all_processes); 62 | for child in &children { 63 | print_process(child, all_processes, depth + 1); 64 | } 65 | } 66 | 67 | /// Get all children of a specific process, by iterating through all processes and 68 | /// checking their parent pid. 69 | fn get_children(pid: i32, all_processes: &[ProcessEntry]) -> Vec<&ProcessEntry> { 70 | all_processes 71 | .iter() 72 | .filter(|process| process.stat.ppid == pid) 73 | .collect() 74 | } 75 | -------------------------------------------------------------------------------- /procfs/examples/pfn.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Print physical memory location for each page for each memory mapping 3 | // This requires CAP_SYS_ADMIN privilege, or root, otherwise physical memory addresses will be zero 4 | // 5 | // Vocabulary 6 | // VA = Virtual Address: memory address from a process point of view 7 | // VPN = Virtual Page Number: page number of the a Virtual Memory address 8 | // PA = Physical Address: memory address in physical memory 9 | // PFN = Page Frame Number: page number of a Physical Address 10 | // 11 | 12 | use procfs::process::MMapPath; 13 | use procfs::process::Process; 14 | 15 | fn main() { 16 | if !rustix::process::geteuid().is_root() { 17 | println!("WARNING: Access to /proc//pagemap requires root, re-run with sudo"); 18 | } 19 | 20 | let page_size = procfs::page_size(); 21 | 22 | let process = Process::myself().expect("Unable to load myself!"); 23 | 24 | let mut pagemap = process.pagemap().unwrap(); 25 | let mem_map = process.maps().unwrap(); 26 | 27 | for memory_map in mem_map { 28 | let va_start = memory_map.address.0; 29 | let va_end = memory_map.address.1; 30 | 31 | let vpn_start = (va_start / page_size) as usize; 32 | let vpn_end = (va_end / page_size) as usize; 33 | 34 | // can't scan Vsyscall, so skip it 35 | if memory_map.pathname == MMapPath::Vsyscall { 36 | continue; 37 | } 38 | 39 | println!("Memory mapping {:?}", memory_map); 40 | 41 | for vpn in vpn_start..vpn_end { 42 | let va = vpn * page_size as usize; 43 | let page_info = pagemap.get_info(vpn).unwrap(); 44 | match page_info { 45 | procfs::process::PageInfo::MemoryPage(memory_page) => { 46 | let pfn = memory_page.get_page_frame_number(); 47 | let pa = pfn.0 * page_size; 48 | println!("virt_mem: 0x{:x}, pfn: 0x{:x}, phys_addr: 0x{:x}", va, pfn, pa); 49 | } 50 | procfs::process::PageInfo::SwapPage(swap_page_flags) => { 51 | let swap_type = swap_page_flags.get_swap_type(); 52 | let swap_offset = swap_page_flags.get_swap_offset(); 53 | println!( 54 | "virt_mem: 0x{:x}, swap: {:}, offset: 0x{:x}", 55 | va, swap_type, swap_offset 56 | ); 57 | } 58 | } 59 | } 60 | } 61 | 62 | if !rustix::process::geteuid().is_root() { 63 | println!("\n\nWARNING: Access to /proc//pagemap requires root, re-run with sudo"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /procfs-core/src/partitions.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | use super::ProcResult; 4 | use std::str::FromStr; 5 | 6 | #[cfg(feature = "serde1")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// A partition entry under `/proc/partitions` 10 | #[derive(Debug, Clone)] 11 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 12 | #[allow(non_snake_case)] 13 | pub struct PartitionEntry { 14 | /// Device major number 15 | pub major: u16, 16 | /// Device minor number 17 | pub minor: u16, 18 | /// Number of 1024 byte blocks 19 | pub blocks: u64, 20 | /// Device name 21 | pub name: String, 22 | } 23 | 24 | impl super::FromBufRead for Vec { 25 | fn from_buf_read(r: R) -> ProcResult { 26 | let mut vec = Vec::new(); 27 | 28 | for line in r.lines().skip(2) { 29 | let line = expect!(line); 30 | 31 | let mut s = line.split_whitespace(); 32 | 33 | let major = expect!(u16::from_str(expect!(s.next()))); 34 | let minor = expect!(u16::from_str(expect!(s.next()))); 35 | let blocks = expect!(u64::from_str(expect!(s.next()))); 36 | let name = expect!(s.next()).to_string(); 37 | 38 | let partition_entry = PartitionEntry { 39 | major, 40 | minor, 41 | blocks, 42 | name, 43 | }; 44 | 45 | vec.push(partition_entry); 46 | } 47 | 48 | Ok(vec) 49 | } 50 | } 51 | 52 | #[test] 53 | fn test_partitions() { 54 | use crate::FromBufRead; 55 | use std::io::Cursor; 56 | 57 | let s = "major minor #blocks name 58 | 59 | 259 0 1000204632 nvme0n1 60 | 259 1 1048576 nvme0n1p1 61 | 259 2 1048576 nvme0n1p2 62 | 259 3 104857600 nvme0n1p3 63 | 259 4 893248512 nvme0n1p4 64 | 253 0 104841216 dm-0 65 | 252 0 8388608 zram0 66 | 253 1 893232128 dm-1 67 | 8 0 3953664 sda 68 | 8 1 2097152 sda1 69 | 8 2 1855488 sda2 70 | 253 2 1853440 dm-2 71 | "; 72 | 73 | let cursor = Cursor::new(s); 74 | let partitions = Vec::::from_buf_read(cursor).unwrap(); 75 | assert_eq!(partitions.len(), 12); 76 | 77 | assert_eq!(partitions[3].major, 259); 78 | assert_eq!(partitions[3].minor, 3); 79 | assert_eq!(partitions[3].blocks, 104857600); 80 | assert_eq!(partitions[3].name, "nvme0n1p3"); 81 | 82 | assert_eq!(partitions[11].major, 253); 83 | assert_eq!(partitions[11].minor, 2); 84 | assert_eq!(partitions[11].blocks, 1853440); 85 | assert_eq!(partitions[11].name, "dm-2"); 86 | } 87 | -------------------------------------------------------------------------------- /procfs/src/kpagecount.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{BufReader, Read, Seek, SeekFrom}, 3 | mem::size_of, 4 | path::Path, 5 | }; 6 | 7 | use crate::{process::Pfn, FileWrapper}; 8 | 9 | use super::ProcResult; 10 | 11 | /// Parse physical memory references accessing `/proc/kpagecount` 12 | /// 13 | /// Require root or CAP_SYS_ADMIN 14 | pub struct KPageCount { 15 | reader: BufReader, 16 | } 17 | 18 | impl KPageCount { 19 | /// Get a parser from default `/proc/kpagecount` 20 | /// 21 | /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN 22 | pub fn new() -> ProcResult { 23 | Self::from_custom_root("/proc") 24 | } 25 | 26 | /// Get a parser from custom `/proc` 27 | /// 28 | /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN 29 | pub fn from_custom_root>(root: P) -> ProcResult { 30 | let mut path = root.as_ref().to_path_buf(); 31 | path.push("kpagecount"); 32 | 33 | let reader = BufReader::new(FileWrapper::open(path)?); 34 | 35 | Ok(Self { reader }) 36 | } 37 | 38 | /// Get the number of references to physical memory at `pfn` 39 | /// 40 | /// Return Err if pfn is not in RAM. See [crate::iomem()] for a list of valid physical RAM addresses 41 | /// 42 | /// See [crate::process::Process::pagemap] and [crate::process::MemoryPageFlags::get_page_frame_number] 43 | pub fn get_count_at_pfn(&mut self, pfn: Pfn) -> ProcResult { 44 | self.get_count_in_range(pfn, Pfn(pfn.0 + 1)) 45 | .map(|mut vec| vec.pop().unwrap()) 46 | } 47 | 48 | /// Get the number of references to physical memory at for PFNs within `start` and `end` PFNs, `end` is excluded 49 | /// 50 | /// Return Err if any pfn is not in RAM. See [crate::iomem()] for a list of valid physical RAM addresses 51 | /// 52 | /// See [crate::process::Process::pagemap] and [crate::process::MemoryPageFlags::get_page_frame_number] 53 | pub fn get_count_in_range(&mut self, start: Pfn, end: Pfn) -> ProcResult> { 54 | let mut result = Vec::with_capacity((end.0 - start.0) as usize); 55 | 56 | let start_position = start.0 * size_of::() as u64; 57 | self.reader.seek(SeekFrom::Start(start_position))?; 58 | 59 | for _pfn in start.0..end.0 { 60 | // Each entry is a 64 bits counter 61 | let mut buf = [0; size_of::()]; 62 | 63 | self.reader.read_exact(&mut buf)?; 64 | let page_references: u64 = u64::from_le_bytes(buf); 65 | 66 | result.push(page_references); 67 | } 68 | 69 | Ok(result) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /procfs/examples/lslocks.rs: -------------------------------------------------------------------------------- 1 | use procfs::process::{FDTarget, Process}; 2 | use rustix::fs::AtFlags; 3 | use std::path::Path; 4 | 5 | fn main() { 6 | let myself = Process::myself().unwrap(); 7 | let mountinfo = myself.mountinfo().unwrap(); 8 | println!("{:18}{:13}{:13}{:13}{:12} Path", "Process", "PID", "Lock Type", "Mode", "Kind"); 9 | println!("{}", "=".repeat(74)); 10 | for lock in procfs::locks().unwrap() { 11 | lock.pid 12 | .and_then(|pid| Process::new(pid).ok()) 13 | .and_then(|proc| proc.cmdline().ok()) 14 | .and_then(|mut cmd| cmd.drain(..).next()) 15 | .map_or_else( 16 | || { 17 | print!("{:18}", "(undefined)"); 18 | }, 19 | |s| { 20 | let p = Path::new(&s); 21 | print!("{:18}", p.file_name().unwrap_or(p.as_os_str()).to_string_lossy()); 22 | }, 23 | ); 24 | 25 | print!("{:<12} ", lock.pid.unwrap_or(-1)); 26 | print!("{:12} ", lock.lock_type.as_str()); 27 | print!("{:12} ", lock.mode.as_str()); 28 | print!("{:12} ", lock.kind.as_str()); 29 | 30 | // try to find the path for this inode 31 | let mut found = false; 32 | if let Some(pid) = lock.pid { 33 | if let Ok(fds) = Process::new(pid).and_then(|p| p.fd()) { 34 | for f in fds { 35 | let fd = f.unwrap(); 36 | if let FDTarget::Path(p) = fd.target { 37 | if let Ok(stat) = rustix::fs::statat(&rustix::fs::CWD, &p, AtFlags::empty()) { 38 | if stat.st_ino as u64 == lock.inode { 39 | print!("{}", p.display()); 40 | found = true; 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | if !found { 50 | // we don't have a PID or we don't have permission to inspect the processes files, but we still have the device and inode 51 | // There's no way to look up a path from an inode, so just bring the device mount point 52 | for mount in &mountinfo { 53 | if format!("{}:{}", lock.devmaj, lock.devmin) == mount.majmin { 54 | print!("{}...", mount.mount_point.display()); 55 | found = true; 56 | break; 57 | } 58 | } 59 | } 60 | 61 | if !found { 62 | // still not found? print the device 63 | print!("{}:{}", lock.devmaj, lock.devmin); 64 | } 65 | 66 | println!(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /procfs/src/kpageflags.rs: -------------------------------------------------------------------------------- 1 | use crate::{process::Pfn, FileWrapper, ProcResult}; 2 | 3 | use std::{ 4 | io::{BufReader, Read, Seek, SeekFrom}, 5 | mem::size_of, 6 | path::Path, 7 | }; 8 | 9 | pub use procfs_core::PhysicalPageFlags; 10 | 11 | /// Parse physical memory flags accessing `/proc/kpageflags`. 12 | /// 13 | /// Require root or CAP_SYS_ADMIN 14 | pub struct KPageFlags { 15 | reader: BufReader, 16 | } 17 | 18 | impl KPageFlags { 19 | /// Get a parser from default `/proc/kpageflags` 20 | /// 21 | /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN 22 | pub fn new() -> ProcResult { 23 | Self::from_custom_root("/proc") 24 | } 25 | 26 | /// Get a parser from custom `/proc` 27 | /// 28 | /// Return `Err` if process is not running as root or don't have CAP_SYS_ADMIN 29 | pub fn from_custom_root>(root: P) -> ProcResult { 30 | let mut path = root.as_ref().to_path_buf(); 31 | path.push("kpageflags"); 32 | 33 | let reader = BufReader::new(FileWrapper::open(path)?); 34 | 35 | Ok(Self { reader }) 36 | } 37 | 38 | /// Retrieve information in the page table entry for the PFN (page frame number) at index `page_index`. 39 | /// If you need to retrieve multiple PFNs, opt for [Self::get_range_info()] instead. 40 | /// 41 | /// Return Err if the PFN is not in RAM (see [crate::iomem()]): 42 | /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) 43 | pub fn get_info(&mut self, pfn: Pfn) -> ProcResult { 44 | self.get_range_info(pfn, Pfn(pfn.0 + 1)) 45 | .map(|mut vec| vec.pop().unwrap()) 46 | } 47 | 48 | /// Retrieve information in the page table entry for the PFNs within range `start` (included) and `end` (excluded) PFNs. 49 | /// 50 | /// Return Err if any PFN is not in RAM (see [crate::iomem()]): 51 | /// Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, None) 52 | pub fn get_range_info(&mut self, start: Pfn, end: Pfn) -> ProcResult> { 53 | let start_position = start.0 * size_of::() as u64; 54 | self.reader.seek(SeekFrom::Start(start_position))?; 55 | 56 | let mut page_infos = Vec::with_capacity((end.0 - start.0) as usize); 57 | for _ in start.0..end.0 { 58 | let mut info_bytes = [0; size_of::()]; 59 | self.reader.read_exact(&mut info_bytes)?; 60 | page_infos.push(PhysicalPageFlags::parse_info(u64::from_ne_bytes(info_bytes))); 61 | } 62 | 63 | Ok(page_infos) 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_kpageflags_parsing() { 73 | let pagemap_entry: u64 = 0b0000000000000000000000000000000000000000000000000000000000000001; 74 | let info = PhysicalPageFlags::parse_info(pagemap_entry); 75 | assert!(info == PhysicalPageFlags::LOCKED); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /procfs/src/sys/fs/mod.rs: -------------------------------------------------------------------------------- 1 | //! This modules contains functions for kernel variables related to filesystems 2 | 3 | use crate::{expect, from_str, read_file, read_value, write_value, ProcResult}; 4 | use std::time::Duration; 5 | 6 | pub mod binfmt_misc; 7 | pub mod epoll; 8 | 9 | /// Information about the status of the directory cache (dcache) 10 | #[derive(Debug, Clone)] 11 | pub struct DEntryState { 12 | /// The number of allocated dentries (dcache entries) 13 | /// 14 | /// Unused in Linux 2.2 15 | pub nr_dentry: u32, 16 | 17 | /// The number of unused dentries. 18 | pub nr_unused: u32, 19 | 20 | /// The age after which dcache entries can be reclaimied when memory is short 21 | pub age_limit: Duration, 22 | 23 | /// Is true when the kernel has called `shrink_dcache_pages()` and the dcache isn't pruned yet. 24 | pub want_pages: bool, 25 | } 26 | 27 | impl DEntryState { 28 | fn from_str(s: &str) -> ProcResult { 29 | let mut s = s.split_whitespace(); 30 | let nr_dentry = from_str!(u32, expect!(s.next())); 31 | let nr_unused = from_str!(u32, expect!(s.next())); 32 | let age_limit_sec = from_str!(u32, expect!(s.next())); 33 | let want_pages = from_str!(u32, expect!(s.next())); 34 | 35 | Ok(DEntryState { 36 | nr_dentry, 37 | nr_unused, 38 | age_limit: Duration::from_secs(age_limit_sec as u64), 39 | want_pages: want_pages != 0, 40 | }) 41 | } 42 | } 43 | 44 | /// Get information about the status of the directory cache (dcache) 45 | /// 46 | /// Linux Linux 2.2 47 | pub fn dentry_state() -> ProcResult { 48 | let s: String = read_file("/proc/sys/fs/dentry-state")?; 49 | 50 | DEntryState::from_str(&s) 51 | } 52 | 53 | /// Get the system-wide limit on the number of open files for all processes. 54 | /// 55 | /// System calls that fail when encoun‐ tering this limit fail with the error `ENFILE`. 56 | pub fn file_max() -> ProcResult { 57 | read_value("/proc/sys/fs/file-max") 58 | } 59 | 60 | /// Set the system-wide limit on the number of open files for all processes. 61 | pub fn set_file_max(max: usize) -> ProcResult<()> { 62 | write_value("/proc/sys/fs/file-max", max) 63 | } 64 | #[derive(Debug, Clone)] 65 | pub struct FileState { 66 | /// The number of allocated file handles. 67 | /// 68 | /// (i.e. the number of files presently opened) 69 | pub allocated: u64, 70 | 71 | /// The number of free file handles. 72 | pub free: u64, 73 | 74 | /// The maximum number of file handles. 75 | /// 76 | /// This may be u64::MAX 77 | pub max: u64, 78 | } 79 | 80 | pub fn file_nr() -> ProcResult { 81 | let s = read_file("/proc/sys/fs/file-nr")?; 82 | let mut s = s.split_whitespace(); 83 | let allocated = from_str!(u64, expect!(s.next())); 84 | let free = from_str!(u64, expect!(s.next())); 85 | let max = from_str!(u64, expect!(s.next())); 86 | 87 | Ok(FileState { allocated, free, max }) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | #[test] 94 | fn dentry() { 95 | let d = dentry_state().unwrap(); 96 | println!("{:?}", d); 97 | } 98 | 99 | #[test] 100 | fn filenr() { 101 | let f = file_nr().unwrap(); 102 | println!("{:?}", f); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /procfs-core/src/process/clear_refs.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Clearing the PG_Referenced and ACCESSED/YOUNG bits 4 | /// provides a method to measure approximately how much memory 5 | /// a process is using. One first inspects the values in the 6 | /// "Referenced" fields for the VMAs shown in 7 | /// `/proc/[pid]/smaps` to get an idea of the memory footprint 8 | /// of the process. One then clears the PG_Referenced and 9 | /// ACCESSED/YOUNG bits and, after some measured time 10 | /// interval, once again inspects the values in the 11 | /// "Referenced" fields to get an idea of the change in memory 12 | /// footprint of the process during the measured interval. If 13 | /// one is interested only in inspecting the selected mapping 14 | /// types, then the value 2 or 3 can be used instead of 1. 15 | /// 16 | /// The `/proc/[pid]/clear_refs` file is present only if the 17 | /// CONFIG_PROC_PAGE_MONITOR kernel configuration option is 18 | /// enabled. 19 | /// 20 | /// Only writable by the owner of the process 21 | /// 22 | /// See `procfs::Process::clear_refs()` and `procfs::Process::pagemap()` 23 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 24 | pub enum ClearRefs { 25 | /// (since Linux 2.6.22) 26 | /// 27 | /// Reset the PG_Referenced and ACCESSED/YOUNG bits for 28 | /// all the pages associated with the process. (Before 29 | /// kernel 2.6.32, writing any nonzero value to this 30 | /// file had this effect.) 31 | PGReferencedAll = 1, 32 | /// (since Linux 2.6.32) 33 | /// 34 | /// Reset the PG_Referenced and ACCESSED/YOUNG bits for 35 | /// all anonymous pages associated with the process. 36 | PGReferencedAnonymous = 2, 37 | /// (since Linux 2.6.32) 38 | /// 39 | /// Reset the PG_Referenced and ACCESSED/YOUNG bits for 40 | /// all file-mapped pages associated with the process. 41 | PGReferencedFile = 3, 42 | /// (since Linux 3.11) 43 | /// 44 | /// Clear the soft-dirty bit for all the pages 45 | /// associated with the process. This is used (in 46 | /// conjunction with `/proc/[pid]/pagemap`) by the check- 47 | /// point restore system to discover which pages of a 48 | /// process have been dirtied since the file 49 | /// `/proc/[pid]/clear_refs` was written to. 50 | SoftDirty = 4, 51 | /// (since Linux 4.0) 52 | /// 53 | /// Reset the peak resident set size ("high water 54 | /// mark") to the process's current resident set size 55 | /// value. 56 | PeakRSS = 5, 57 | } 58 | 59 | impl fmt::Display for ClearRefs { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | write!( 62 | f, 63 | "{}", 64 | match self { 65 | ClearRefs::PGReferencedAll => 1, 66 | ClearRefs::PGReferencedAnonymous => 2, 67 | ClearRefs::PGReferencedFile => 3, 68 | ClearRefs::SoftDirty => 4, 69 | ClearRefs::PeakRSS => 5, 70 | } 71 | ) 72 | } 73 | } 74 | 75 | impl std::str::FromStr for ClearRefs { 76 | type Err = &'static str; 77 | 78 | fn from_str(s: &str) -> Result { 79 | s.parse() 80 | .map_err(|_| "Fail to parse clear refs value") 81 | .and_then(|n| match n { 82 | 1 => Ok(ClearRefs::PGReferencedAll), 83 | 2 => Ok(ClearRefs::PGReferencedAnonymous), 84 | 3 => Ok(ClearRefs::PGReferencedFile), 85 | 4 => Ok(ClearRefs::SoftDirty), 86 | 5 => Ok(ClearRefs::PeakRSS), 87 | _ => Err("Unknown clear refs value"), 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /procfs-core/src/sysvipc_shm.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use super::{expect, ProcResult}; 4 | use std::str::FromStr; 5 | 6 | #[cfg(feature = "serde1")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// A shared memory segment parsed from `/proc/sysvipc/shm` 10 | /// Relation with [crate::process::MMapPath::Vsys] 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 13 | #[allow(non_snake_case)] 14 | pub struct Shm { 15 | /// Segment key 16 | pub key: i32, 17 | /// Segment ID, unique 18 | pub shmid: u64, 19 | /// Access permissions, as octal 20 | pub perms: u16, 21 | /// Size in bytes 22 | pub size: u64, 23 | /// Creator PID 24 | pub cpid: i32, 25 | /// Last operator PID 26 | pub lpid: i32, 27 | /// Number of attached processes 28 | pub nattch: u32, 29 | /// User ID 30 | pub uid: u16, 31 | /// Group ID 32 | pub gid: u16, 33 | /// Creator UID 34 | pub cuid: u16, 35 | /// Creator GID 36 | pub cgid: u16, 37 | /// Time of last `shmat` (attach), epoch 38 | pub atime: u64, 39 | /// Time of last `shmdt` (detach), epoch 40 | pub dtime: u64, 41 | /// Time of last permission change, epoch 42 | pub ctime: u64, 43 | /// Current part of the shared memory resident in memory 44 | pub rss: u64, 45 | /// Current part of the shared memory in SWAP 46 | pub swap: u64, 47 | } 48 | 49 | /// A set of shared memory segments parsed from `/proc/sysvipc/shm` 50 | #[derive(Debug, Clone)] 51 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 52 | pub struct SharedMemorySegments(pub Vec); 53 | 54 | impl super::FromBufRead for SharedMemorySegments { 55 | fn from_buf_read(r: R) -> ProcResult { 56 | let mut vec = Vec::new(); 57 | 58 | // See printing code here: 59 | // https://elixir.bootlin.com/linux/latest/source/ipc/shm.c#L1737 60 | for line in r.lines().skip(1) { 61 | let line = expect!(line); 62 | let mut s = line.split_whitespace(); 63 | 64 | let key = expect!(i32::from_str(expect!(s.next()))); 65 | let shmid = expect!(u64::from_str(expect!(s.next()))); 66 | let perms = expect!(u16::from_str(expect!(s.next()))); 67 | let size = expect!(u64::from_str(expect!(s.next()))); 68 | let cpid = expect!(i32::from_str(expect!(s.next()))); 69 | let lpid = expect!(i32::from_str(expect!(s.next()))); 70 | let nattch = expect!(u32::from_str(expect!(s.next()))); 71 | let uid = expect!(u16::from_str(expect!(s.next()))); 72 | let gid = expect!(u16::from_str(expect!(s.next()))); 73 | let cuid = expect!(u16::from_str(expect!(s.next()))); 74 | let cgid = expect!(u16::from_str(expect!(s.next()))); 75 | let atime = expect!(u64::from_str(expect!(s.next()))); 76 | let dtime = expect!(u64::from_str(expect!(s.next()))); 77 | let ctime = expect!(u64::from_str(expect!(s.next()))); 78 | let rss = expect!(u64::from_str(expect!(s.next()))); 79 | let swap = expect!(u64::from_str(expect!(s.next()))); 80 | 81 | let shm = Shm { 82 | key, 83 | shmid, 84 | perms, 85 | size, 86 | cpid, 87 | lpid, 88 | nattch, 89 | uid, 90 | gid, 91 | cuid, 92 | cgid, 93 | atime, 94 | dtime, 95 | ctime, 96 | rss, 97 | swap, 98 | }; 99 | 100 | vec.push(shm); 101 | } 102 | 103 | Ok(SharedMemorySegments(vec)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /procfs/src/sys/kernel/random.rs: -------------------------------------------------------------------------------- 1 | //! These files provide additional information about the /dev/random device 2 | //! 3 | //! Note that some of these entries are only documented in random(4), while some are also documented under proc(5) 4 | 5 | use crate::{read_value, write_value, ProcError, ProcResult}; 6 | use std::path::Path; 7 | 8 | const RANDOM_ROOT: &str = "/proc/sys/kernel/random"; 9 | 10 | /// This read-only file gives the available entropy, in bits. This will be a number in the range 11 | /// 0 to 4096 12 | pub fn entropy_avail() -> ProcResult { 13 | read_value(Path::new(RANDOM_ROOT).join("entropy_avail")) 14 | } 15 | 16 | /// This file gives the size of the entropy pool 17 | /// 18 | /// The semantics of this file are different on kernel versions older than 2.6, however, since 19 | /// Linux 2.6 it is read-only, and gives the size of the entropy pool in bits, containing the value 4096. 20 | /// 21 | /// See `man random(4)` for more information 22 | pub fn poolsize() -> ProcResult { 23 | read_value(Path::new(RANDOM_ROOT).join("poolsize")) 24 | } 25 | 26 | /// This file contains the number of bits of entropy required for waking up processes that sleep waiting 27 | /// for entropy from /dev/random 28 | /// 29 | /// The default is 64. 30 | /// 31 | /// This will first attempt to read from `/proc/sys/kernel/random/read_wakeup_threshold` but it 32 | /// will fallback to `/proc/sys/kernel/random/write_wakeup_threshold` if the former file is not found. 33 | pub fn read_wakeup_threshold() -> ProcResult { 34 | match read_value(Path::new(RANDOM_ROOT).join("read_wakeup_threshold")) { 35 | Ok(val) => Ok(val), 36 | Err(err) => match err { 37 | ProcError::NotFound(_) => read_value(Path::new(RANDOM_ROOT).join("write_wakeup_threshold")), 38 | err => Err(err), 39 | }, 40 | } 41 | } 42 | 43 | /// This file contains the number of bits of entropy below which we wake up processes that do a 44 | /// select(2) or poll(2) for write access to /dev/random. These values can be changed by writing to the file. 45 | pub fn write_wakeup_threshold(new_value: u32) -> ProcResult<()> { 46 | write_value(Path::new(RANDOM_ROOT).join("write_wakeup_threshold"), new_value) 47 | } 48 | 49 | /// This read-only file randomly generates a fresh 128-bit UUID on each read 50 | pub fn uuid() -> ProcResult { 51 | read_value(Path::new(RANDOM_ROOT).join("uuid")) 52 | } 53 | 54 | /// This is a read-only file containing a 128-bit UUID generated at boot 55 | pub fn boot_id() -> ProcResult { 56 | read_value(Path::new(RANDOM_ROOT).join("boot_id")) 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | fn test_entropy_avail() { 65 | let entropy = entropy_avail().unwrap(); 66 | assert!(entropy <= 4096); 67 | } 68 | 69 | #[test] 70 | fn test_poolsize() { 71 | // The kernel support section in the root lib.rs file says that we only aim to support >= 2.6 kernels, 72 | // so only test that case 73 | let _poolsize = poolsize().unwrap(); 74 | } 75 | 76 | #[test] 77 | fn test_read_wakeup_threshold() { 78 | let threshold = read_wakeup_threshold().unwrap(); 79 | 80 | println!("{}", threshold); 81 | } 82 | 83 | #[test] 84 | fn test_write_wakeup_threshold() { 85 | let old_threshold = read_wakeup_threshold().unwrap(); 86 | 87 | match write_wakeup_threshold(1024) { 88 | Ok(_) => (), 89 | Err(err) => match err { 90 | ProcError::PermissionDenied(_) => { 91 | // This is ok, not everyone wants to run our tests as root 92 | return; 93 | } 94 | err => panic!("test_write_wakeup_threshold error: {:?}", err), 95 | }, 96 | } 97 | 98 | // If we got here, let's restore the old threshold 99 | let _ = write_wakeup_threshold(old_threshold); 100 | } 101 | 102 | #[test] 103 | fn test_uuid_fns() { 104 | let uuid = uuid().unwrap(); 105 | let boot_id = boot_id().unwrap(); 106 | 107 | println!("UUID: {}", uuid); 108 | println!("boot UUID: {}", boot_id); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /procfs-core/src/mounts.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, io::BufRead}; 2 | 3 | use super::ProcResult; 4 | use std::str::FromStr; 5 | 6 | #[cfg(feature = "serde1")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// A mountpoint entry under `/proc/mounts` 10 | #[derive(Debug, Clone)] 11 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 12 | #[allow(non_snake_case)] 13 | pub struct MountEntry { 14 | /// Device 15 | pub fs_spec: String, 16 | /// Mountpoint 17 | pub fs_file: String, 18 | /// FS type 19 | pub fs_vfstype: String, 20 | /// Mount options 21 | pub fs_mntops: HashMap>, 22 | /// Dump 23 | pub fs_freq: u8, 24 | /// Check 25 | pub fs_passno: u8, 26 | } 27 | 28 | impl super::FromBufRead for Vec { 29 | fn from_buf_read(r: R) -> ProcResult { 30 | let mut vec = Vec::new(); 31 | 32 | for line in r.lines() { 33 | let line = expect!(line); 34 | let mut s = line.split(' '); // not using split_whitespace because we might have empty fields 35 | 36 | let fs_spec = unmangle_octal(expect!(s.next())); 37 | let fs_file = unmangle_octal(expect!(s.next())); 38 | let fs_vfstype = unmangle_octal(expect!(s.next())); 39 | let fs_mntops = unmangle_octal(expect!(s.next())); 40 | let fs_mntops: HashMap> = fs_mntops 41 | .split(',') 42 | .map(|s| { 43 | let mut split = s.splitn(2, '='); 44 | let k = split.next().unwrap().to_string(); // can not fail, splitn will always return at least 1 element 45 | let v = split.next().map(|s| s.to_string()); 46 | 47 | (k, v) 48 | }) 49 | .collect(); 50 | let fs_freq = expect!(u8::from_str(expect!(s.next()))); 51 | let fs_passno = expect!(u8::from_str(expect!(s.next()))); 52 | 53 | let mount_entry = MountEntry { 54 | fs_spec, 55 | fs_file, 56 | fs_vfstype, 57 | fs_mntops, 58 | fs_freq, 59 | fs_passno, 60 | }; 61 | 62 | vec.push(mount_entry); 63 | } 64 | 65 | Ok(vec) 66 | } 67 | } 68 | 69 | /// Unmangle spaces ' ', tabs '\t', line breaks '\n', backslashes '\\', and hashes '#' 70 | /// 71 | /// See https://elixir.bootlin.com/linux/v6.2.8/source/fs/proc_namespace.c#L89 72 | pub(crate) fn unmangle_octal(input: &str) -> String { 73 | let mut input = input.to_string(); 74 | 75 | for (octal, c) in [(r"\011", "\t"), (r"\012", "\n"), (r"\134", "\\"), (r"\043", "#")] { 76 | input = input.replace(octal, c); 77 | } 78 | 79 | input 80 | } 81 | 82 | #[test] 83 | fn test_unmangle_octal() { 84 | let tests = [ 85 | (r"a\134b\011c\012d\043e", "a\\b\tc\nd#e"), // all escaped chars with abcde in between 86 | (r"abcd", r"abcd"), // do nothing 87 | ]; 88 | 89 | for (input, expected) in tests { 90 | assert_eq!(unmangle_octal(input), expected); 91 | } 92 | } 93 | 94 | #[test] 95 | fn test_mounts() { 96 | use crate::FromBufRead; 97 | use std::io::Cursor; 98 | 99 | let s = "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 100 | sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 101 | /dev/mapper/ol-root / xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 102 | Downloads /media/sf_downloads vboxsf rw,nodev,relatime,iocharset=utf8,uid=0,gid=977,dmode=0770,fmode=0770,tag=VBoxAutomounter 0 0"; 103 | 104 | let cursor = Cursor::new(s); 105 | let mounts = Vec::::from_buf_read(cursor).unwrap(); 106 | assert_eq!(mounts.len(), 4); 107 | 108 | // https://github.com/eminence/procfs/issues/333 109 | let s = " / tmpfs ro,nosuid,nodev,noexec,relatime,size=0k,nr_inodes=2,uid=1000,gid=1000,inode64 0 0"; 110 | let mounts = Vec::::from_buf_read(Cursor::new(s)).unwrap(); 111 | assert_eq!(mounts.len(), 1); 112 | assert_eq!(mounts[0].fs_spec, ""); 113 | assert_eq!(mounts[0].fs_file, "/"); 114 | assert_eq!(mounts[0].fs_vfstype, "tmpfs"); 115 | assert!(mounts[0].fs_mntops.contains_key("ro")); 116 | } 117 | -------------------------------------------------------------------------------- /procfs/src/sys/kernel/keys.rs: -------------------------------------------------------------------------------- 1 | //! Functions related to the in-kernel key management and retention facility 2 | //! 3 | //! For more details on this facility, see the `keyrings(7)` man page. 4 | //! 5 | //! Additional functions can be found in the [keyring](crate::keyring) module. 6 | use crate::{read_value, write_value, ProcResult}; 7 | 8 | /// GC Delay 9 | /// 10 | /// The value in this file specifies the interval, in seconds, 11 | /// after which revoked and expired keys will be garbage collected. 12 | /// The purpose of having such an interval is so that 13 | /// there is a window of time where user space can see an error 14 | /// (respectively EKEYREVOKED and EKEYEXPIRED) that indicates what 15 | /// happened to the key. 16 | /// 17 | /// The default value in this file is 300 (i.e., 5 minutes). 18 | /// 19 | /// (since Linux 2.6.32) 20 | pub fn gc_delay() -> ProcResult { 21 | read_value("/proc/sys/kernel/keys/gc_delay") 22 | } 23 | 24 | /// Persistent Keyring Expiry 25 | /// 26 | /// This file defines an interval, in seconds, to which the persistent 27 | /// keyring's expiration timer is reset each time the 28 | /// keyring is accessed (via keyctl_get_persistent(3) or the 29 | /// keyctl(2) KEYCTL_GET_PERSISTENT operation.) 30 | /// 31 | /// The default value in this file is 259200 (i.e., 3 days). 32 | /// 33 | /// (Since Linux 3.13) 34 | pub fn persistent_keyring_expiry() -> ProcResult { 35 | read_value("/proc/sys/kernel/keys/persistent_keyring_expiry") 36 | } 37 | 38 | /// Max bytes 39 | /// 40 | /// This is the maximum number of bytes of data that a nonroot 41 | /// user can hold in the payloads of the keys owned by the user. 42 | /// 43 | /// The default value in this file is 20,000. 44 | /// 45 | /// (since linux 2.6.26) 46 | pub fn maxbytes() -> ProcResult { 47 | read_value("/proc/sys/kernel/keys/maxbytes") 48 | } 49 | 50 | /// Set max bytes 51 | pub fn set_maxbytes(bytes: u32) -> ProcResult<()> { 52 | write_value("/proc/sys/kernel/keys/maxbytes", bytes) 53 | } 54 | 55 | /// Max keys 56 | /// 57 | /// This is the maximum number of keys that a nonroot user may own. 58 | /// 59 | /// (since linux 2.6.26) 60 | pub fn maxkeys() -> ProcResult { 61 | read_value("/proc/sys/kernel/keys/maxkeys") 62 | } 63 | 64 | /// Set max keys 65 | pub fn set_maxkeys(keys: u32) -> ProcResult<()> { 66 | write_value("/proc/sys/kernel/keys/maxkeys", keys) 67 | } 68 | 69 | /// Root maxbytes 70 | /// 71 | /// This is the maximum number of bytes of data that the root user 72 | /// (UID 0 in the root user namespace) can hold in the payloads of 73 | /// the keys owned by root. 74 | /// 75 | /// The default value in this file is 25,000,000 (20,000 before Linux 3.17). 76 | /// 77 | /// (since Linux 2.6.26) 78 | pub fn root_maxbytes() -> ProcResult { 79 | read_value("/proc/sys/kernel/keys/root_maxbytes") 80 | } 81 | 82 | /// Set root maxbytes 83 | pub fn set_root_maxbytes(bytes: u32) -> ProcResult<()> { 84 | write_value("/proc/sys/kernel/keys/root_maxbytes", bytes) 85 | } 86 | 87 | /// Root maxkeys 88 | /// 89 | /// This is the maximum number of keys that the root user (UID 0 in the root user namespace) may own. 90 | /// 91 | /// The default value in this file is 1,000,000 (200 before Linux 3.17). 92 | /// (since Linux 2.6.26) 93 | pub fn root_maxkeys() -> ProcResult { 94 | read_value("/proc/sys/kernel/keys/root_maxkeys") 95 | } 96 | 97 | /// Set root maxkeys 98 | pub fn set_root_maxkeys(keys: u32) -> ProcResult<()> { 99 | write_value("/proc/sys/kernel/keys/root_maxkeys", keys) 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use crate::{ProcError, ProcResult}; 105 | 106 | fn check_unwrap(val: ProcResult) { 107 | match val { 108 | Ok(_) => {} 109 | Err(ProcError::NotFound(_)) => { 110 | // ok to ignore 111 | } 112 | Err(e) => { 113 | panic!("Unexpected proc error: {:?}", e); 114 | } 115 | } 116 | } 117 | 118 | #[test] 119 | fn test_keys() { 120 | check_unwrap(super::gc_delay()); 121 | check_unwrap(super::persistent_keyring_expiry()); 122 | check_unwrap(super::maxbytes()); 123 | check_unwrap(super::maxkeys()); 124 | check_unwrap(super::root_maxbytes()); 125 | check_unwrap(super::root_maxkeys()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /procfs/examples/process_kpageflags.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Look for a value in the virtual memory of a process, and physical memory, then prints memory page details 3 | // This shows how to go from virtual address to mapping, and from mapping to physical address. 4 | // 5 | // This requires CAP_SYS_ADMIN privilege, or root 6 | // 7 | // Sample output: 8 | // 9 | // Virtual address of `variable`: 0x7ffd2de4708f 10 | // Found memory mapping 11 | // MemoryMap { address: (140725373272064, 140725373407232), perms: "rw-p", offset: 0, dev: (0, 0), inode: 0, pathname: Stack } 12 | // Found page 13 | // virt_mem: 0x7ffd2de47000, pfn: 0x107b06, phys_addr: 0x107b06000, flags: UPTODATE | LRU | MMAP | ANON | SWAPBACKED 14 | // 15 | 16 | use procfs::process::Process; 17 | use procfs::KPageFlags; 18 | 19 | fn main() { 20 | if !rustix::process::geteuid().is_root() { 21 | // KpageFlags::new().unwrap() will panic either way 22 | panic!("ERROR: Access to /proc/kpageflags requires root, re-run with sudo"); 23 | } 24 | 25 | let page_size = procfs::page_size(); 26 | 27 | // We will inspect this process's own memory 28 | let process = Process::myself().expect("Unable to load myself!"); 29 | let mut kpageflags = KPageFlags::new().expect("Can't open /proc/kpageflags"); 30 | 31 | let mut pagemap = process.pagemap().unwrap(); 32 | 33 | // The memory maps are read now, so the value we look for must already exist in RAM when we it this line 34 | // In this case it works, because the variables already exist in the executable 35 | // You probably want to put this right above the "for memory_map" loop 36 | let mem_map = process.maps().unwrap(); 37 | 38 | // We allocate memory for a value. This is a trick to get a semi random value 39 | // The goal is to find this value in physical memory 40 | let chrono = std::time::Instant::now(); 41 | let variable: u8 = chrono.elapsed().as_nanos() as u8; 42 | 43 | // We could do the same with a constant, the compiler will place this value in a different memory mapping with different properties 44 | //let constant = 42u8; 45 | 46 | // `ptr` is the virtual address we are looking for 47 | let ptr = &variable as *const u8; 48 | println!("Virtual address of `variable`: {:p}", ptr); 49 | 50 | for memory_map in mem_map { 51 | let mem_start = memory_map.address.0; 52 | let mem_end = memory_map.address.1; 53 | 54 | if (ptr as u64) < mem_start || (ptr as u64) >= mem_end { 55 | // pointer is not in this memory mapping 56 | continue; 57 | } 58 | 59 | // found the memory mapping where the value is stored 60 | println!("Found memory mapping\n{:?}", memory_map); 61 | 62 | // memory is split into pages (usually 4 kiB) 63 | let index_start = (mem_start / page_size) as usize; 64 | let index_end = (mem_end / page_size) as usize; 65 | 66 | for index in index_start..index_end { 67 | // we search for the exact page inside the memory mapping 68 | let virt_mem = index * page_size as usize; 69 | 70 | // ptr must be reside between this page and the next one 71 | if (ptr as usize) < virt_mem || (ptr as usize) >= virt_mem + page_size as usize { 72 | continue; 73 | } 74 | 75 | // we found the exact page where the value resides 76 | let page_info = pagemap.get_info(index).unwrap(); 77 | match page_info { 78 | procfs::process::PageInfo::MemoryPage(memory_page) => { 79 | let pfn = memory_page.get_page_frame_number(); 80 | let phys_addr = pfn.0 * page_size; 81 | 82 | let physical_page_info = kpageflags.get_info(pfn).expect("Can't get kpageflags info"); 83 | 84 | println!( 85 | "Found page\nvirt_mem: 0x{:x}, pfn: 0x{:x}, phys_addr: 0x{:x}, flags: {:?}", 86 | virt_mem, pfn, phys_addr, physical_page_info 87 | ); 88 | } 89 | procfs::process::PageInfo::SwapPage(swap_page_flags) => { 90 | let swap_type = swap_page_flags.get_swap_type(); 91 | let swap_offset = swap_page_flags.get_swap_offset(); 92 | 93 | println!( 94 | "Found page\nvirt_mem: 0x{:x}, swap_type: {:}, swap_offset: 0x{:x}, flags: {:?}", 95 | virt_mem, swap_type, swap_offset, swap_page_flags 96 | ); 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | procfs 2 | ====== 3 | 4 | [![Crate](https://img.shields.io/crates/v/procfs.svg)](https://crates.io/crates/procfs) 5 | [![Docs](https://docs.rs/procfs/badge.svg)](https://docs.rs/procfs) 6 | [![Minimum rustc version](https://img.shields.io/badge/rustc-1.70+-lightgray.svg)](https://github.com/eminence/procfs#minimum-rust-version) 7 | 8 | 9 | This crate is an interface to the `proc` pseudo-filesystem on linux, which is normally mounted as `/proc`. 10 | Long-term, this crate aims to be fairly feature complete, but at the moment not all files are exposed. 11 | See the docs for info on what's supported, or view the [support.md](https://github.com/eminence/procfs/blob/master/support.md) 12 | file in the code repository. 13 | 14 | ## Examples 15 | There are several examples in the docs and in the [examples folder](https://github.com/eminence/procfs/tree/master/procfs/examples) 16 | of the code repository. 17 | 18 | Here's a small example that prints out all processes that are running on the same tty as the calling 19 | process. This is very similar to what "ps" does in its default mode: 20 | 21 | ```rust 22 | fn main() { 23 | let me = procfs::process::Process::myself().unwrap(); 24 | let me_stat = me.stat().unwrap(); 25 | let tps = procfs::ticks_per_second(); 26 | 27 | println!("{: >5} {: <8} {: >8} {}", "PID", "TTY", "TIME", "CMD"); 28 | 29 | let tty = format!("pty/{}", me_stat.tty_nr().1); 30 | for prc in procfs::process::all_processes().unwrap() { 31 | let prc = prc.unwrap(); 32 | let stat = prc.stat().unwrap(); 33 | if stat.tty_nr == me_stat.tty_nr { 34 | // total_time is in seconds 35 | let total_time = 36 | (stat.utime + stat.stime) as f32 / (tps as f32); 37 | println!( 38 | "{: >5} {: <8} {: >8} {}", 39 | stat.pid, tty, total_time, stat.comm 40 | ); 41 | } 42 | } 43 | } 44 | 45 | ``` 46 | 47 | Here's another example that shows how to get the current memory usage of the current process: 48 | 49 | ```rust 50 | use procfs::process::Process; 51 | 52 | fn main() { 53 | let me = Process::myself().unwrap(); 54 | let me_stat = me.stat().unwrap(); 55 | println!("PID: {}", me.pid); 56 | 57 | let page_size = procfs::page_size(); 58 | println!("Memory page size: {}", page_size); 59 | 60 | println!("== Data from /proc/self/stat:"); 61 | println!("Total virtual memory used: {} bytes", me_stat.vsize); 62 | println!( 63 | "Total resident set: {} pages ({} bytes)", 64 | me_stat.rss, 65 | me_stat.rss * page_size 66 | ); 67 | } 68 | 69 | ``` 70 | 71 | There are a few ways to get this data, so also checkout the longer 72 | [self_memory](https://github.com/eminence/procfs/blob/master/procfs/examples/self_memory.rs) example for more 73 | details. 74 | 75 | ## Cargo features 76 | 77 | The following cargo features are available: 78 | 79 | * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects. 80 | * `flate2` -- Default. Optional. This feature enables parsing gzip compressed `/proc/config.gz` file via the `procfs::kernel_config` method. 81 | * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised. 82 | * `serde1` -- Optional. This feature allows most structs to be serialized and deserialized using serde 1.0. Note, this 83 | feature requires a version of rust newer than 1.70.0 (which is the MSRV for procfs). The exact version required is not 84 | specified here, since serde does not not have an MSRV policy. 85 | 86 | ## Minimum Rust Version 87 | 88 | This crate is only tested against the latest stable rustc compiler, but may 89 | work with older compilers. See [msrv.md](msrv.md) for more details. 90 | 91 | ## License 92 | 93 | The procfs library is licensed under either of 94 | 95 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 96 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 97 | 98 | at your option. 99 | 100 | For additional copyright information regarding documentation, please also see the COPYRIGHT.txt file. 101 | 102 | ### Contribution 103 | 104 | Contributions are welcome, especially in the areas of documentation and testing on older kernels. 105 | 106 | Unless you explicitly state otherwise, any contribution intentionally 107 | submitted for inclusion in the work by you, as defined in the Apache-2.0 108 | license, shall be dual licensed as above, without any additional terms or 109 | conditions. 110 | -------------------------------------------------------------------------------- /procfs-core/src/kpageflags.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | #[cfg(feature = "serde1")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | //const fn genmask(high: usize, low: usize) -> u64 { 7 | // let mask_bits = size_of::() * 8; 8 | // (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) 9 | //} 10 | 11 | bitflags! { 12 | /// Represents the fields and flags in a page table entry for a memory page. 13 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 14 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 15 | pub struct PhysicalPageFlags: u64 { 16 | /// The page is being locked for exclusive access, e.g. by undergoing read/write IO 17 | const LOCKED = 1 << 0; 18 | /// IO error occurred 19 | const ERROR = 1 << 1; 20 | /// The page has been referenced since last LRU list enqueue/requeue 21 | const REFERENCED = 1 << 2; 22 | /// The page has up-to-date data. ie. for file backed page: (in-memory data revision >= on-disk one) 23 | const UPTODATE = 1 << 3; 24 | /// The page has been written to, hence contains new data. i.e. for file backed page: (in-memory data revision > on-disk one) 25 | const DIRTY = 1 << 4; 26 | /// The page is in one of the LRU lists 27 | const LRU = 1 << 5; 28 | /// The page is in the active LRU list 29 | const ACTIVE = 1 << 6; 30 | /// The page is managed by the SLAB/SLOB/SLUB/SLQB kernel memory allocator. When compound page is used, SLUB/SLQB will only set this flag on the head page; SLOB will not flag it at all 31 | const SLAB = 1 << 7; 32 | /// The page is being synced to disk 33 | const WRITEBACK = 1 << 8; 34 | /// The page will be reclaimed soon after its pageout IO completed 35 | const RECLAIM = 1 << 9; 36 | /// A free memory block managed by the buddy system allocator. The buddy system organizes free memory in blocks of various orders. An order N block has 2^N physically contiguous pages, with the BUDDY flag set for and _only_ for the first page 37 | const BUDDY = 1 << 10; 38 | /// A memory mapped page 39 | const MMAP = 1 << 11; 40 | /// A memory mapped page that is not part of a file 41 | const ANON = 1 << 12; 42 | /// The page is mapped to swap space, i.e. has an associated swap entry 43 | const SWAPCACHE = 1 << 13; 44 | /// The page is backed by swap/RAM 45 | const SWAPBACKED = 1 << 14; 46 | /// A compound page with order N consists of 2^N physically contiguous pages. A compound page with order 2 takes the form of “HTTT”, where H donates its head page and T donates its tail page(s). The major consumers of compound pages are hugeTLB pages (), the SLUB etc. memory allocators and various device drivers. However in this interface, only huge/giga pages are made visible to end users 47 | const COMPOUND_HEAD = 1 << 15; 48 | /// A compound page tail (see description above) 49 | const COMPOUND_TAIL = 1 << 16; 50 | /// This is an integral part of a HugeTLB page 51 | const HUGE = 1 << 17; 52 | /// The page is in the unevictable (non-)LRU list It is somehow pinned and not a candidate for LRU page reclaims, e.g. ramfs pages, shmctl(SHM_LOCK) and mlock() memory segments 53 | const UNEVICTABLE = 1 << 18; 54 | /// Hardware detected memory corruption on this page: don’t touch the data! 55 | const HWPOISON = 1 << 19; 56 | /// No page frame exists at the requested address 57 | const NOPAGE = 1 << 20; 58 | /// Identical memory pages dynamically shared between one or more processes 59 | const KSM = 1 << 21; 60 | /// Contiguous pages which construct transparent hugepages 61 | const THP = 1 << 22; 62 | /// The page is logically offline 63 | const OFFLINE = 1 << 23; 64 | /// Zero page for pfn_zero or huge_zero page 65 | const ZERO_PAGE = 1 << 24; 66 | /// The page has not been accessed since it was marked idle (see ). Note that this flag may be stale in case the page was accessed via a PTE. To make sure the flag is up-to-date one has to read /sys/kernel/mm/page_idle/bitmap first 67 | const IDLE = 1 << 25; 68 | /// The page is in use as a page table 69 | const PGTABLE = 1 << 26; 70 | 71 | } 72 | } 73 | 74 | impl PhysicalPageFlags { 75 | pub fn parse_info(info: u64) -> Self { 76 | PhysicalPageFlags::from_bits_truncate(info) 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | 84 | #[test] 85 | fn test_kpageflags_parsing() { 86 | let pagemap_entry: u64 = 0b0000000000000000000000000000000000000000000000000000000000000001; 87 | let info = PhysicalPageFlags::parse_info(pagemap_entry); 88 | assert!(info == PhysicalPageFlags::LOCKED); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /procfs/src/sys/vm.rs: -------------------------------------------------------------------------------- 1 | //! Memory management tuning buffer and cache management 2 | //! 3 | //! The files in this directory can be used to tune 4 | //! the operation of the virtual memory (VM) subsystem of the Linux kernel 5 | //! and the write out of dirty data to disk. 6 | 7 | use std::fmt; 8 | use std::str; 9 | 10 | use crate::{read_value, write_value, ProcResult}; 11 | 12 | /// The amount of free memory in the system that should be reserved for users with the capability cap_sys_admin. 13 | /// 14 | /// # Example 15 | /// 16 | /// ``` 17 | /// use procfs::sys::vm::admin_reserve_kbytes; 18 | /// 19 | /// assert_ne!(admin_reserve_kbytes().unwrap(), 0); 20 | /// ``` 21 | pub fn admin_reserve_kbytes() -> ProcResult { 22 | read_value("/proc/sys/vm/admin_reserve_kbytes") 23 | } 24 | 25 | /// Set the amount of free memory in the system that should be reserved for users with the capability cap_sys_admin. 26 | pub fn set_admin_reserve_kbytes(kbytes: usize) -> ProcResult<()> { 27 | write_value("/proc/sys/vm/admin_reserve_kbytes", kbytes) 28 | } 29 | 30 | /// Force all zones are compacted such that free memory is available in contiguous blocks where possible. 31 | /// 32 | /// This can be important for example in the allocation of huge pages 33 | /// although processes will also directly compact memory as required. 34 | /// 35 | /// Present only if the kernel was configured with CONFIG_COMPACTION. 36 | pub fn compact_memory() -> ProcResult<()> { 37 | write_value("/proc/sys/vm/compact_memory", 1) 38 | } 39 | 40 | /// drop clean caches, dentries, and inodes from memory, causing that memory to become free. 41 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 42 | pub enum DropCache { 43 | /// default 44 | Default = 0, 45 | /// free pagecache 46 | PageCache = 1, 47 | /// free dentries and inodes 48 | Inodes = 2, 49 | /// free pagecache, dentries and inodes 50 | All = 3, 51 | /// disable 52 | Disable = 4, 53 | } 54 | 55 | impl fmt::Display for DropCache { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | write!( 58 | f, 59 | "{}", 60 | match self { 61 | DropCache::Default => 0, 62 | DropCache::PageCache => 1, 63 | DropCache::Inodes => 2, 64 | DropCache::All => 3, 65 | DropCache::Disable => 4, 66 | } 67 | ) 68 | } 69 | } 70 | 71 | impl str::FromStr for DropCache { 72 | type Err = &'static str; 73 | 74 | fn from_str(s: &str) -> Result { 75 | s.parse().map_err(|_| "Fail to parse drop cache").and_then(|n| match n { 76 | 0 => Ok(DropCache::Default), 77 | 1 => Ok(DropCache::PageCache), 78 | 2 => Ok(DropCache::Inodes), 79 | 3 => Ok(DropCache::All), 80 | 4 => Ok(DropCache::Disable), 81 | _ => Err("Unknown drop cache value"), 82 | }) 83 | } 84 | } 85 | 86 | /// Causes the kernel to drop clean caches, dentries, and inodes from memory, 87 | /// causing that memory to become free. 88 | /// 89 | /// This can be useful for memory management testing and performing reproducible filesystem benchmarks. 90 | /// Because writing to this file causes the benefits of caching to be lost, 91 | /// it can degrade overall system performance. 92 | pub fn drop_caches(drop: DropCache) -> ProcResult<()> { 93 | write_value("/proc/sys/vm/drop_caches", drop) 94 | } 95 | 96 | /// The maximum number of memory map areas a process may have. 97 | /// 98 | /// Memory map areas are used as a side-effect of calling malloc, 99 | /// directly by mmap, mprotect, and madvise, and also when loading shared libraries. 100 | /// 101 | /// # Example 102 | /// 103 | /// ``` 104 | /// use procfs::sys::vm::max_map_count; 105 | /// 106 | /// assert_ne!(max_map_count().unwrap(), 0); 107 | /// ``` 108 | pub fn max_map_count() -> ProcResult { 109 | read_value("/proc/sys/vm/max_map_count") 110 | } 111 | 112 | /// Set the maximum number of memory map areas a process may have. 113 | /// 114 | /// Memory map areas are used as a side-effect of calling malloc, 115 | /// directly by mmap, mprotect, and madvise, and also when loading shared libraries. 116 | pub fn set_max_map_count(count: u64) -> ProcResult<()> { 117 | write_value("/proc/sys/vm/max_map_count", count) 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use super::*; 123 | use std::str::FromStr; 124 | 125 | #[test] 126 | fn test() { 127 | use std::path::Path; 128 | if Path::new("/proc/sys/vm/admin_reserve_kbytes").exists() { 129 | admin_reserve_kbytes().unwrap(); 130 | } 131 | if Path::new("/proc/sys/vm/max_map_count").exists() { 132 | max_map_count().unwrap(); 133 | } 134 | 135 | for v in 0..5 { 136 | let s = format!("{}", v); 137 | let dc = DropCache::from_str(&s).unwrap(); 138 | assert_eq!(format!("{}", dc), s); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /procfs-core/src/cgroups.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcResult; 2 | #[cfg(feature = "serde1")] 3 | use serde::{Deserialize, Serialize}; 4 | use std::io::BufRead; 5 | 6 | #[derive(Debug, Clone)] 7 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 8 | /// Container group controller information. 9 | pub struct CGroupController { 10 | /// The name of the controller. 11 | pub name: String, 12 | /// The unique ID of the cgroup hierarchy on which this controller is mounted. 13 | /// 14 | /// If multiple cgroups v1 controllers are bound to the same hierarchy, then each will show 15 | /// the same hierarchy ID in this field. The value in this field will be 0 if: 16 | /// 17 | /// * the controller is not mounted on a cgroups v1 hierarchy; 18 | /// * the controller is bound to the cgroups v2 single unified hierarchy; or 19 | /// * the controller is disabled (see below). 20 | pub hierarchy: u32, 21 | /// The number of control groups in this hierarchy using this controller. 22 | pub num_cgroups: u32, 23 | /// This field contains the value `true` if this controller is enabled, or `false` if it has been disabled 24 | pub enabled: bool, 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 29 | /// Container group controller information. 30 | // This contains a vector, but if each subsystem name is unique, maybe this can be a 31 | // hashmap instead 32 | pub struct CGroupControllers(pub Vec); 33 | 34 | impl crate::FromBufRead for CGroupControllers { 35 | fn from_buf_read(reader: R) -> ProcResult { 36 | let mut vec = Vec::new(); 37 | 38 | for line in reader.lines() { 39 | let line = line?; 40 | if line.starts_with('#') { 41 | continue; 42 | } 43 | 44 | let mut s = line.split_whitespace(); 45 | let name = expect!(s.next(), "name").to_owned(); 46 | let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); 47 | let num_cgroups = from_str!(u32, expect!(s.next(), "num_cgroups")); 48 | let enabled = expect!(s.next(), "enabled") == "1"; 49 | 50 | vec.push(CGroupController { 51 | name, 52 | hierarchy, 53 | num_cgroups, 54 | enabled, 55 | }); 56 | } 57 | 58 | Ok(CGroupControllers(vec)) 59 | } 60 | } 61 | 62 | /// Information about a process cgroup 63 | #[derive(Debug, Clone)] 64 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 65 | pub struct ProcessCGroup { 66 | /// For cgroups version 1 hierarchies, this field contains a unique hierarchy ID number 67 | /// that can be matched to a hierarchy ID in /proc/cgroups. For the cgroups version 2 68 | /// hierarchy, this field contains the value 0. 69 | pub hierarchy: u32, 70 | /// For cgroups version 1 hierarchies, this field contains a comma-separated list of the 71 | /// controllers bound to the hierarchy. 72 | /// 73 | /// For the cgroups version 2 hierarchy, this field is empty. 74 | pub controllers: Vec, 75 | 76 | /// This field contains the pathname of the control group in the hierarchy to which the process 77 | /// belongs. 78 | /// 79 | /// This pathname is relative to the mount point of the hierarchy. 80 | pub pathname: String, 81 | } 82 | 83 | /// Information about process cgroups. 84 | #[derive(Debug, Clone)] 85 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 86 | pub struct ProcessCGroups(pub Vec); 87 | 88 | impl crate::FromBufRead for ProcessCGroups { 89 | fn from_buf_read(reader: R) -> ProcResult { 90 | let mut vec = Vec::new(); 91 | 92 | for line in reader.lines() { 93 | let line = line?; 94 | if line.starts_with('#') { 95 | continue; 96 | } 97 | 98 | let mut s = line.splitn(3, ':'); 99 | let hierarchy = from_str!(u32, expect!(s.next(), "hierarchy")); 100 | let controllers = expect!(s.next(), "controllers") 101 | .split(',') 102 | .filter(|s| !s.is_empty()) 103 | .map(|s| s.to_owned()) 104 | .collect(); 105 | let pathname = expect!(s.next(), "path").to_owned(); 106 | 107 | vec.push(ProcessCGroup { 108 | hierarchy, 109 | controllers, 110 | pathname, 111 | }); 112 | } 113 | 114 | Ok(ProcessCGroups(vec)) 115 | } 116 | } 117 | 118 | impl IntoIterator for ProcessCGroups { 119 | type IntoIter = std::vec::IntoIter; 120 | type Item = ProcessCGroup; 121 | 122 | fn into_iter(self) -> Self::IntoIter { 123 | self.0.into_iter() 124 | } 125 | } 126 | 127 | impl<'a> IntoIterator for &'a ProcessCGroups { 128 | type IntoIter = std::slice::Iter<'a, ProcessCGroup>; 129 | type Item = &'a ProcessCGroup; 130 | 131 | fn into_iter(self) -> Self::IntoIter { 132 | self.0.iter() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /procfs-core/src/devices.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | use super::ProcResult; 4 | use std::str::FromStr; 5 | 6 | #[cfg(feature = "serde1")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// Device entries under `/proc/devices` 10 | #[derive(Debug, Clone)] 11 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 12 | pub struct Devices { 13 | /// Character devices 14 | pub char_devices: Vec, 15 | /// Block devices, which can be empty if the kernel doesn't support block devices (without `CONFIG_BLOCK`) 16 | pub block_devices: Vec, 17 | } 18 | 19 | /// A charcter device entry under `/proc/devices` 20 | #[derive(Debug, Clone)] 21 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 22 | pub struct CharDeviceEntry { 23 | /// Device major number 24 | pub major: u32, 25 | /// Device name 26 | pub name: String, 27 | } 28 | 29 | /// A block device entry under `/proc/devices` 30 | #[derive(Debug, Clone)] 31 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 32 | pub struct BlockDeviceEntry { 33 | /// Device major number 34 | pub major: i32, 35 | /// Device name 36 | pub name: String, 37 | } 38 | 39 | impl super::FromBufRead for Devices { 40 | fn from_buf_read(r: R) -> ProcResult { 41 | enum State { 42 | Char, 43 | Block, 44 | } 45 | let mut state = State::Char; // Always start with char devices 46 | let mut devices = Devices { 47 | char_devices: vec![], 48 | block_devices: vec![], 49 | }; 50 | 51 | for line in r.lines() { 52 | let line = expect!(line); 53 | 54 | if line.is_empty() { 55 | continue; 56 | } else if line.starts_with("Character devices:") { 57 | state = State::Char; 58 | continue; 59 | } else if line.starts_with("Block devices:") { 60 | state = State::Block; 61 | continue; 62 | } 63 | 64 | let mut s = line.split_whitespace(); 65 | 66 | match state { 67 | State::Char => { 68 | let major = expect!(u32::from_str(expect!(s.next()))); 69 | let name = expect!(s.next()).to_string(); 70 | 71 | let char_device_entry = CharDeviceEntry { major, name }; 72 | 73 | devices.char_devices.push(char_device_entry); 74 | } 75 | State::Block => { 76 | let major = expect!(i32::from_str(expect!(s.next()))); 77 | let name = expect!(s.next()).to_string(); 78 | 79 | let block_device_entry = BlockDeviceEntry { major, name }; 80 | 81 | devices.block_devices.push(block_device_entry); 82 | } 83 | } 84 | } 85 | 86 | Ok(devices) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | #[test] 94 | fn test_devices() { 95 | use crate::FromBufRead; 96 | use std::io::Cursor; 97 | 98 | let s = "Character devices: 99 | 1 mem 100 | 4 /dev/vc/0 101 | 4 tty 102 | 4 ttyS 103 | 5 /dev/tty 104 | 5 /dev/console 105 | 5 /dev/ptmx 106 | 7 vcs 107 | 10 misc 108 | 13 input 109 | 29 fb 110 | 90 mtd 111 | 136 pts 112 | 180 usb 113 | 188 ttyUSB 114 | 189 usb_device 115 | 116 | Block devices: 117 | 7 loop 118 | 8 sd 119 | 65 sd 120 | 71 sd 121 | 128 sd 122 | 135 sd 123 | 254 device-mapper 124 | 259 blkext 125 | "; 126 | 127 | let cursor = Cursor::new(s); 128 | let devices = Devices::from_buf_read(cursor).unwrap(); 129 | let (chrs, blks) = (devices.char_devices, devices.block_devices); 130 | 131 | assert_eq!(chrs.len(), 16); 132 | 133 | assert_eq!(chrs[1].major, 4); 134 | assert_eq!(chrs[1].name, "/dev/vc/0"); 135 | 136 | assert_eq!(chrs[8].major, 10); 137 | assert_eq!(chrs[8].name, "misc"); 138 | 139 | assert_eq!(chrs[15].major, 189); 140 | assert_eq!(chrs[15].name, "usb_device"); 141 | 142 | assert_eq!(blks.len(), 8); 143 | 144 | assert_eq!(blks[0].major, 7); 145 | assert_eq!(blks[0].name, "loop"); 146 | 147 | assert_eq!(blks[7].major, 259); 148 | assert_eq!(blks[7].name, "blkext"); 149 | } 150 | 151 | #[test] 152 | fn test_devices_without_block() { 153 | use crate::FromBufRead; 154 | use std::io::Cursor; 155 | 156 | let s = "Character devices: 157 | 1 mem 158 | 4 /dev/vc/0 159 | 4 tty 160 | 4 ttyS 161 | 5 /dev/tty 162 | 5 /dev/console 163 | 5 /dev/ptmx 164 | 7 vcs 165 | 10 misc 166 | 13 input 167 | 29 fb 168 | 90 mtd 169 | 136 pts 170 | 180 usb 171 | 188 ttyUSB 172 | 189 usb_device 173 | "; 174 | 175 | let cursor = Cursor::new(s); 176 | let devices = Devices::from_buf_read(cursor).unwrap(); 177 | let (chrs, blks) = (devices.char_devices, devices.block_devices); 178 | 179 | assert_eq!(chrs.len(), 16); 180 | 181 | assert_eq!(chrs[1].major, 4); 182 | assert_eq!(chrs[1].name, "/dev/vc/0"); 183 | 184 | assert_eq!(chrs[8].major, 10); 185 | assert_eq!(chrs[8].name, "misc"); 186 | 187 | assert_eq!(chrs[15].major, 189); 188 | assert_eq!(chrs[15].name, "usb_device"); 189 | 190 | assert_eq!(blks.len(), 0); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /procfs-core/src/diskstats.rs: -------------------------------------------------------------------------------- 1 | use crate::{expect, from_str, ProcResult}; 2 | #[cfg(feature = "serde1")] 3 | use serde::{Deserialize, Serialize}; 4 | use std::io::BufRead; 5 | 6 | /// Disk IO stat information 7 | /// 8 | /// To fully understand these fields, please see the [iostats.txt](https://www.kernel.org/doc/Documentation/iostats.txt) 9 | /// kernel documentation. 10 | /// 11 | /// For an example, see the [diskstats.rs](https://github.com/eminence/procfs/tree/master/examples) 12 | /// example in the source repo. 13 | // Doc reference: https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats 14 | // Doc reference: https://www.kernel.org/doc/Documentation/iostats.txt 15 | #[derive(Debug, Clone)] 16 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 17 | pub struct DiskStat { 18 | /// The device major number 19 | pub major: i32, 20 | 21 | /// The device minor number 22 | pub minor: i32, 23 | 24 | /// Device name 25 | pub name: String, 26 | 27 | /// Reads completed successfully 28 | /// 29 | /// This is the total number of reads completed successfully 30 | pub reads: u64, 31 | 32 | /// Reads merged 33 | /// 34 | /// The number of adjacent reads that have been merged for efficiency. 35 | pub merged: u64, 36 | 37 | /// Sectors read successfully 38 | /// 39 | /// This is the total number of sectors read successfully. 40 | pub sectors_read: u64, 41 | 42 | /// Time spent reading (ms) 43 | pub time_reading: u64, 44 | 45 | /// writes completed 46 | pub writes: u64, 47 | 48 | /// writes merged 49 | /// 50 | /// The number of adjacent writes that have been merged for efficiency. 51 | pub writes_merged: u64, 52 | 53 | /// Sectors written successfully 54 | pub sectors_written: u64, 55 | 56 | /// Time spent writing (ms) 57 | pub time_writing: u64, 58 | 59 | /// I/Os currently in progress 60 | pub in_progress: u64, 61 | 62 | /// Time spent doing I/Os (ms) 63 | pub time_in_progress: u64, 64 | 65 | /// Weighted time spent doing I/Os (ms) 66 | pub weighted_time_in_progress: u64, 67 | 68 | /// Discards completed successfully 69 | /// 70 | /// (since kernel 4.18) 71 | pub discards: Option, 72 | 73 | /// Discards merged 74 | pub discards_merged: Option, 75 | 76 | /// Sectors discarded 77 | pub sectors_discarded: Option, 78 | 79 | /// Time spent discarding 80 | pub time_discarding: Option, 81 | 82 | /// Flush requests completed successfully 83 | /// 84 | /// (since kernel 5.5) 85 | pub flushes: Option, 86 | 87 | /// Time spent flushing 88 | pub time_flushing: Option, 89 | } 90 | 91 | /// A list of disk stats. 92 | #[derive(Debug, Clone)] 93 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 94 | pub struct DiskStats(pub Vec); 95 | 96 | impl crate::FromBufRead for DiskStats { 97 | fn from_buf_read(r: R) -> ProcResult { 98 | let mut v = Vec::new(); 99 | 100 | for line in r.lines() { 101 | let line = line?; 102 | v.push(DiskStat::from_line(&line)?); 103 | } 104 | Ok(DiskStats(v)) 105 | } 106 | } 107 | 108 | impl DiskStat { 109 | pub fn from_line(line: &str) -> ProcResult { 110 | let mut s = line.split_whitespace(); 111 | 112 | let major = from_str!(i32, expect!(s.next())); 113 | let minor = from_str!(i32, expect!(s.next())); 114 | let name = expect!(s.next()).to_string(); 115 | let reads = from_str!(u64, expect!(s.next())); 116 | let merged = from_str!(u64, expect!(s.next())); 117 | let sectors_read = from_str!(u64, expect!(s.next())); 118 | let time_reading = from_str!(u64, expect!(s.next())); 119 | let writes = from_str!(u64, expect!(s.next())); 120 | let writes_merged = from_str!(u64, expect!(s.next())); 121 | let sectors_written = from_str!(u64, expect!(s.next())); 122 | let time_writing = from_str!(u64, expect!(s.next())); 123 | let in_progress = from_str!(u64, expect!(s.next())); 124 | let time_in_progress = from_str!(u64, expect!(s.next())); 125 | let weighted_time_in_progress = from_str!(u64, expect!(s.next())); 126 | let discards = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 127 | let discards_merged = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 128 | let sectors_discarded = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 129 | let time_discarding = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 130 | let flushes = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 131 | let time_flushing = s.next().and_then(|s| u64::from_str_radix(s, 10).ok()); 132 | 133 | Ok(DiskStat { 134 | major, 135 | minor, 136 | name, 137 | reads, 138 | merged, 139 | sectors_read, 140 | time_reading, 141 | writes, 142 | writes_merged, 143 | sectors_written, 144 | time_writing, 145 | in_progress, 146 | time_in_progress, 147 | weighted_time_in_progress, 148 | discards, 149 | discards_merged, 150 | sectors_discarded, 151 | time_discarding, 152 | flushes, 153 | time_flushing, 154 | }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /procfs-core/src/process/pagemap.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use std::{fmt, mem::size_of}; 3 | 4 | #[cfg(feature = "serde1")] 5 | use serde::{Deserialize, Serialize}; 6 | 7 | const fn genmask(high: usize, low: usize) -> u64 { 8 | let mask_bits = size_of::() * 8; 9 | (!0 - (1 << low) + 1) & (!0 >> (mask_bits - 1 - high)) 10 | } 11 | 12 | // source: include/linux/swap.h 13 | const MAX_SWAPFILES_SHIFT: usize = 5; 14 | 15 | // source: fs/proc/task_mmu.c 16 | bitflags! { 17 | /// Represents the fields and flags in a page table entry for a swapped page. 18 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 19 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 20 | pub struct SwapPageFlags: u64 { 21 | /// Swap type if swapped 22 | #[doc(hidden)] 23 | const SWAP_TYPE = genmask(MAX_SWAPFILES_SHIFT - 1, 0); 24 | /// Swap offset if swapped 25 | #[doc(hidden)] 26 | const SWAP_OFFSET = genmask(54, MAX_SWAPFILES_SHIFT); 27 | /// PTE is soft-dirty 28 | const SOFT_DIRTY = 1 << 55; 29 | /// Page is exclusively mapped 30 | const MMAP_EXCLUSIVE = 1 << 56; 31 | /// Page is file-page or shared-anon 32 | const FILE = 1 << 61; 33 | /// Page is swapped 34 | #[doc(hidden)] 35 | const SWAP = 1 << 62; 36 | /// Page is present 37 | const PRESENT = 1 << 63; 38 | } 39 | } 40 | 41 | impl SwapPageFlags { 42 | /// Returns the swap type recorded in this entry. 43 | pub fn get_swap_type(&self) -> u64 { 44 | (*self & Self::SWAP_TYPE).bits() 45 | } 46 | 47 | /// Returns the swap offset recorded in this entry. 48 | pub fn get_swap_offset(&self) -> u64 { 49 | (*self & Self::SWAP_OFFSET).bits() >> MAX_SWAPFILES_SHIFT 50 | } 51 | } 52 | 53 | bitflags! { 54 | /// Represents the fields and flags in a page table entry for a memory page. 55 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 56 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 57 | pub struct MemoryPageFlags: u64 { 58 | /// Page frame number if present 59 | #[doc(hidden)] 60 | const PFN = genmask(54, 0); 61 | /// PTE is soft-dirty 62 | const SOFT_DIRTY = 1 << 55; 63 | /// Page is exclusively mapped 64 | const MMAP_EXCLUSIVE = 1 << 56; 65 | /// Page is file-page or shared-anon 66 | const FILE = 1 << 61; 67 | /// Page is swapped 68 | #[doc(hidden)] 69 | const SWAP = 1 << 62; 70 | /// Page is present 71 | const PRESENT = 1 << 63; 72 | } 73 | } 74 | 75 | impl MemoryPageFlags { 76 | /// Returns the page frame number recorded in this entry. 77 | pub fn get_page_frame_number(&self) -> Pfn { 78 | Pfn((*self & Self::PFN).bits()) 79 | } 80 | } 81 | 82 | /// A Page Frame Number, representing a 4 kiB physical memory page 83 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 84 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 85 | pub struct Pfn(pub u64); 86 | 87 | impl fmt::UpperHex for Pfn { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 89 | let val = self.0; 90 | 91 | fmt::UpperHex::fmt(&val, f) 92 | } 93 | } 94 | 95 | impl fmt::LowerHex for Pfn { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | let val = self.0; 98 | 99 | fmt::LowerHex::fmt(&val, f) 100 | } 101 | } 102 | 103 | /// Represents a page table entry in `/proc//pagemap`. 104 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 105 | pub enum PageInfo { 106 | /// Entry referring to a memory page 107 | MemoryPage(MemoryPageFlags), 108 | /// Entry referring to a swapped page 109 | SwapPage(SwapPageFlags), 110 | } 111 | 112 | impl PageInfo { 113 | pub fn parse_info(info: u64) -> Self { 114 | let flags = MemoryPageFlags::from_bits_retain(info); 115 | 116 | if flags.contains(MemoryPageFlags::SWAP) { 117 | Self::SwapPage(SwapPageFlags::from_bits_retain(info)) 118 | } else { 119 | Self::MemoryPage(flags) 120 | } 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | #[test] 129 | fn test_genmask() { 130 | let mask = genmask(3, 1); 131 | assert_eq!(mask, 0b1110); 132 | 133 | let mask = genmask(3, 0); 134 | assert_eq!(mask, 0b1111); 135 | 136 | let mask = genmask(63, 62); 137 | assert_eq!(mask, 0b11 << 62); 138 | } 139 | 140 | #[test] 141 | fn test_page_info() { 142 | let pagemap_entry: u64 = 0b1000000110000000000000000000000000000000000000000000000000000011; 143 | let info = PageInfo::parse_info(pagemap_entry); 144 | if let PageInfo::MemoryPage(memory_flags) = info { 145 | assert!(memory_flags 146 | .contains(MemoryPageFlags::PRESENT | MemoryPageFlags::MMAP_EXCLUSIVE | MemoryPageFlags::SOFT_DIRTY)); 147 | assert_eq!(memory_flags.get_page_frame_number(), Pfn(0b11)); 148 | } else { 149 | panic!("Wrong SWAP decoding"); 150 | } 151 | 152 | let pagemap_entry: u64 = 0b1100000110000000000000000000000000000000000000000000000001100010; 153 | let info = PageInfo::parse_info(pagemap_entry); 154 | if let PageInfo::SwapPage(swap_flags) = info { 155 | assert!( 156 | swap_flags.contains(SwapPageFlags::PRESENT | SwapPageFlags::MMAP_EXCLUSIVE | SwapPageFlags::SOFT_DIRTY) 157 | ); 158 | assert_eq!(swap_flags.get_swap_type(), 0b10); 159 | assert_eq!(swap_flags.get_swap_offset(), 0b11); 160 | } else { 161 | panic!("Wrong SWAP decoding"); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /procfs-core/src/pressure.rs: -------------------------------------------------------------------------------- 1 | //! Pressure stall information retreived from `/proc/pressure/cpu`, 2 | //! `/proc/pressure/memory` and `/proc/pressure/io` 3 | //! may not be available on kernels older than 4.20.0 4 | //! For reference: 5 | //! 6 | //! See also: 7 | 8 | use crate::{ProcError, ProcResult}; 9 | use std::collections::HashMap; 10 | 11 | #[cfg(feature = "serde1")] 12 | use serde::{Deserialize, Serialize}; 13 | 14 | /// Pressure stall information for either CPU, memory, or IO. 15 | /// 16 | /// See also: 17 | #[derive(Debug, Clone)] 18 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 19 | pub struct PressureRecord { 20 | /// 10 second window 21 | /// 22 | /// The percentage of time, over a 10 second window, that either some or all tasks were stalled 23 | /// waiting for a resource. 24 | pub avg10: f32, 25 | /// 60 second window 26 | /// 27 | /// The percentage of time, over a 60 second window, that either some or all tasks were stalled 28 | /// waiting for a resource. 29 | pub avg60: f32, 30 | /// 300 second window 31 | /// 32 | /// The percentage of time, over a 300 second window, that either some or all tasks were stalled 33 | /// waiting for a resource. 34 | pub avg300: f32, 35 | /// Total stall time (in microseconds). 36 | pub total: u64, 37 | } 38 | 39 | /// CPU pressure information 40 | #[derive(Debug, Clone)] 41 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 42 | pub struct CpuPressure { 43 | /// This record indicates the share of time in which at least some tasks are stalled. 44 | pub some: PressureRecord, 45 | /// This record indicates this share of time in which all non-idle tasks are stalled 46 | /// simultaneously. 47 | /// 48 | /// At the system level CPU full is set to zero. 49 | pub full: PressureRecord, 50 | } 51 | 52 | impl super::FromBufRead for CpuPressure { 53 | fn from_buf_read(mut r: R) -> ProcResult { 54 | let (some, full) = get_pressure(r)?; 55 | Ok(CpuPressure { some, full }) 56 | } 57 | } 58 | 59 | /// Memory pressure information 60 | #[derive(Debug, Clone)] 61 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 62 | pub struct MemoryPressure { 63 | /// This record indicates the share of time in which at least some tasks are stalled. 64 | pub some: PressureRecord, 65 | /// This record indicates this share of time in which all non-idle tasks are stalled 66 | /// simultaneously. 67 | pub full: PressureRecord, 68 | } 69 | 70 | impl super::FromBufRead for MemoryPressure { 71 | fn from_buf_read(r: R) -> ProcResult { 72 | let (some, full) = get_pressure(r)?; 73 | Ok(MemoryPressure { some, full }) 74 | } 75 | } 76 | 77 | /// IO pressure information 78 | #[derive(Debug, Clone)] 79 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 80 | pub struct IoPressure { 81 | /// This record indicates the share of time in which at least some tasks are stalled. 82 | pub some: PressureRecord, 83 | /// This record indicates this share of time in which all non-idle tasks are stalled 84 | /// simultaneously. 85 | pub full: PressureRecord, 86 | } 87 | 88 | impl super::FromBufRead for IoPressure { 89 | fn from_buf_read(r: R) -> ProcResult { 90 | let (some, full) = get_pressure(r)?; 91 | Ok(IoPressure { some, full }) 92 | } 93 | } 94 | 95 | fn get_f32(map: &HashMap<&str, &str>, value: &str) -> ProcResult { 96 | map.get(value).map_or_else( 97 | || Err(ProcError::Incomplete(None)), 98 | |v| v.parse::().map_err(|_| ProcError::Incomplete(None)), 99 | ) 100 | } 101 | 102 | fn get_total(map: &HashMap<&str, &str>) -> ProcResult { 103 | map.get("total").map_or_else( 104 | || Err(ProcError::Incomplete(None)), 105 | |v| v.parse::().map_err(|_| ProcError::Incomplete(None)), 106 | ) 107 | } 108 | 109 | /// Parse a single `some`/`full` pressure record in the following format: 110 | /// 111 | /// ```text 112 | /// full avg10=2.10 avg60=0.12 avg300=0.00 total=391926 113 | /// ``` 114 | /// 115 | /// See also [`get_pressure`]. 116 | pub fn parse_pressure_record(line: &str) -> ProcResult { 117 | let mut parsed = HashMap::new(); 118 | 119 | if !line.starts_with("some") && !line.starts_with("full") { 120 | return Err(ProcError::Incomplete(None)); 121 | } 122 | 123 | let values = &line[5..]; 124 | 125 | for kv_str in values.split_whitespace() { 126 | let kv_split = kv_str.split('='); 127 | let vec: Vec<&str> = kv_split.collect(); 128 | if vec.len() == 2 { 129 | parsed.insert(vec[0], vec[1]); 130 | } 131 | } 132 | 133 | Ok(PressureRecord { 134 | avg10: get_f32(&parsed, "avg10")?, 135 | avg60: get_f32(&parsed, "avg60")?, 136 | avg300: get_f32(&parsed, "avg300")?, 137 | total: get_total(&parsed)?, 138 | }) 139 | } 140 | 141 | /// Get the pressure records from a reader. The first line should be a `some` record, and the second line a `full` record. 142 | /// The records are returned in the same order. 143 | /// 144 | /// ```text 145 | /// some avg10=4.50 avg60=0.91 avg300=0.00 total=681245 146 | /// full avg10=2.10 avg60=0.12 avg300=0.00 total=391926 147 | /// ``` 148 | /// 149 | /// See also [`parse_pressure_record`]. 150 | pub fn get_pressure(mut r: R) -> ProcResult<(PressureRecord, PressureRecord)> { 151 | let mut some = String::new(); 152 | r.read_line(&mut some)?; 153 | let mut full = String::new(); 154 | r.read_line(&mut full)?; 155 | Ok((parse_pressure_record(&some)?, parse_pressure_record(&full)?)) 156 | } 157 | 158 | #[cfg(test)] 159 | mod test { 160 | use super::*; 161 | use std::f32::EPSILON; 162 | 163 | #[test] 164 | fn test_parse_pressure_record() { 165 | let record = parse_pressure_record("full avg10=2.10 avg60=0.12 avg300=0.00 total=391926").unwrap(); 166 | 167 | assert!(record.avg10 - 2.10 < EPSILON); 168 | assert!(record.avg60 - 0.12 < EPSILON); 169 | assert!(record.avg300 - 0.00 < EPSILON); 170 | assert_eq!(record.total, 391_926); 171 | } 172 | 173 | #[test] 174 | fn test_parse_pressure_record_errs() { 175 | assert!(parse_pressure_record("avg10=2.10 avg60=0.12 avg300=0.00 total=391926").is_err()); 176 | assert!(parse_pressure_record("some avg10=2.10 avg300=0.00 total=391926").is_err()); 177 | assert!(parse_pressure_record("some avg10=2.10 avg60=0.00 avg300=0.00").is_err()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /procfs-core/src/cpuinfo.rs: -------------------------------------------------------------------------------- 1 | use crate::{expect, ProcResult}; 2 | 3 | #[cfg(feature = "serde1")] 4 | use serde::{Deserialize, Serialize}; 5 | use std::{collections::HashMap, io::BufRead}; 6 | 7 | /// Represents the data from `/proc/cpuinfo`. 8 | /// 9 | /// The `fields` field stores the fields that are common among all CPUs. The `cpus` field stores 10 | /// CPU-specific info. 11 | /// 12 | /// For common fields, there are methods that will return the data, converted to a more appropriate 13 | /// data type. These methods will all return `None` if the field doesn't exist, or is in some 14 | /// unexpected format (in that case, you'll have to access the string data directly). 15 | #[derive(Debug, Clone)] 16 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 17 | pub struct CpuInfo { 18 | /// This stores fields that are common among all CPUs 19 | pub fields: HashMap, 20 | pub cpus: Vec>, 21 | } 22 | 23 | impl crate::FromBufRead for CpuInfo { 24 | fn from_buf_read(r: R) -> ProcResult { 25 | let mut list = Vec::new(); 26 | let mut map = Some(HashMap::new()); 27 | 28 | // the first line of a cpu block must start with "processor" 29 | let mut found_first = false; 30 | 31 | for line in r.lines().flatten() { 32 | if !line.is_empty() { 33 | let mut s = line.split(':'); 34 | let key = expect!(s.next()); 35 | if !found_first && key.trim() == "processor" { 36 | found_first = true; 37 | } 38 | if !found_first { 39 | continue; 40 | } 41 | if let Some(value) = s.next() { 42 | let key = key.trim().to_owned(); 43 | let value = value.trim().to_owned(); 44 | 45 | map.get_or_insert(HashMap::new()).insert(key, value); 46 | } 47 | } else if let Some(map) = map.take() { 48 | list.push(map); 49 | found_first = false; 50 | } 51 | } 52 | if let Some(map) = map.take() { 53 | list.push(map); 54 | } 55 | 56 | // find properties that are the same for all cpus 57 | assert!(!list.is_empty()); 58 | 59 | let common_fields: Vec = list[0] 60 | .iter() 61 | .filter_map(|(key, val)| { 62 | if list.iter().all(|map| map.get(key).map_or(false, |v| v == val)) { 63 | Some(key.clone()) 64 | } else { 65 | None 66 | } 67 | }) 68 | .collect(); 69 | 70 | let mut common_map = HashMap::new(); 71 | for (k, v) in &list[0] { 72 | if common_fields.contains(k) { 73 | common_map.insert(k.clone(), v.clone()); 74 | } 75 | } 76 | 77 | for map in &mut list { 78 | map.retain(|k, _| !common_fields.contains(k)); 79 | } 80 | 81 | Ok(CpuInfo { 82 | fields: common_map, 83 | cpus: list, 84 | }) 85 | } 86 | } 87 | 88 | impl CpuInfo { 89 | /// Get the total number of cpu cores. 90 | /// 91 | /// This is the number of entries in the `/proc/cpuinfo` file. 92 | pub fn num_cores(&self) -> usize { 93 | self.cpus.len() 94 | } 95 | 96 | /// Get info for a specific cpu. 97 | /// 98 | /// This will merge the common fields with the cpu-specific fields. 99 | /// 100 | /// Returns None if the requested cpu index is not found. 101 | pub fn get_info(&self, cpu_num: usize) -> Option> { 102 | self.cpus.get(cpu_num).map(|info| { 103 | self.fields 104 | .iter() 105 | .chain(info.iter()) 106 | .map(|(k, v)| (k.as_ref(), v.as_ref())) 107 | .collect() 108 | }) 109 | } 110 | 111 | /// Get the content of a specific field associated to a CPU 112 | /// 113 | /// If the field is not found in the set of CPU-specific fields, then 114 | /// it is returned from the set of common fields. 115 | /// 116 | /// Returns None if the requested cpu index is not found, or if the field 117 | /// is not found. 118 | pub fn get_field(&self, cpu_num: usize, field_name: &str) -> Option<&str> { 119 | self.cpus.get(cpu_num).and_then(|cpu_fields| { 120 | cpu_fields 121 | .get(field_name) 122 | .or_else(|| self.fields.get(field_name)) 123 | .map(|s| s.as_ref()) 124 | }) 125 | } 126 | 127 | pub fn model_name(&self, cpu_num: usize) -> Option<&str> { 128 | self.get_field(cpu_num, "model name") 129 | } 130 | 131 | pub fn vendor_id(&self, cpu_num: usize) -> Option<&str> { 132 | self.get_field(cpu_num, "vendor_id") 133 | } 134 | 135 | /// May not be available on some older 2.6 kernels 136 | pub fn physical_id(&self, cpu_num: usize) -> Option { 137 | self.get_field(cpu_num, "physical id").and_then(|s| s.parse().ok()) 138 | } 139 | 140 | pub fn flags(&self, cpu_num: usize) -> Option> { 141 | self.get_field(cpu_num, "flags") 142 | .map(|flags| flags.split_whitespace().collect()) 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | 150 | #[test] 151 | fn test_cpuinfo_rpi() { 152 | // My rpi system includes some stuff at the end of /proc/cpuinfo that we shouldn't parse 153 | let data = r#"processor : 0 154 | model name : ARMv7 Processor rev 4 (v7l) 155 | BogoMIPS : 38.40 156 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 157 | CPU implementer : 0x41 158 | CPU architecture: 7 159 | CPU variant : 0x0 160 | CPU part : 0xd03 161 | CPU revision : 4 162 | 163 | processor : 1 164 | model name : ARMv7 Processor rev 4 (v7l) 165 | BogoMIPS : 38.40 166 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 167 | CPU implementer : 0x41 168 | CPU architecture: 7 169 | CPU variant : 0x0 170 | CPU part : 0xd03 171 | CPU revision : 4 172 | 173 | processor : 2 174 | model name : ARMv7 Processor rev 4 (v7l) 175 | BogoMIPS : 38.40 176 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 177 | CPU implementer : 0x41 178 | CPU architecture: 7 179 | CPU variant : 0x0 180 | CPU part : 0xd03 181 | CPU revision : 4 182 | 183 | processor : 3 184 | model name : ARMv7 Processor rev 4 (v7l) 185 | BogoMIPS : 38.40 186 | Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 187 | CPU implementer : 0x41 188 | CPU architecture: 7 189 | CPU variant : 0x0 190 | CPU part : 0xd03 191 | CPU revision : 4 192 | 193 | Hardware : BCM2835 194 | Revision : a020d3 195 | Serial : 0000000012345678 196 | Model : Raspberry Pi 3 Model B Plus Rev 1.3 197 | "#; 198 | 199 | let r = std::io::Cursor::new(data.as_bytes()); 200 | 201 | use crate::FromRead; 202 | 203 | let info = CpuInfo::from_read(r).unwrap(); 204 | assert_eq!(info.num_cores(), 4); 205 | let info = info.get_info(0).unwrap(); 206 | assert!(info.get("model name").is_some()); 207 | assert!(info.get("BogoMIPS").is_some()); 208 | assert!(info.get("Features").is_some()); 209 | assert!(info.get("CPU implementer").is_some()); 210 | assert!(info.get("CPU architecture").is_some()); 211 | assert!(info.get("CPU variant").is_some()); 212 | assert!(info.get("CPU part").is_some()); 213 | assert!(info.get("CPU revision").is_some()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /procfs-core/src/process/limit.rs: -------------------------------------------------------------------------------- 1 | use crate::{ProcError, ProcResult}; 2 | 3 | use std::collections::HashMap; 4 | use std::io::BufRead; 5 | use std::str::FromStr; 6 | 7 | #[cfg(feature = "serde1")] 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// Process limits 11 | /// 12 | /// For more details about each of these limits, see the `getrlimit` man page. 13 | #[derive(Debug, Clone)] 14 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 15 | pub struct Limits { 16 | /// Max Cpu Time 17 | /// 18 | /// This is a limit, in seconds, on the amount of CPU time that the process can consume. 19 | pub max_cpu_time: Limit, 20 | 21 | /// Max file size 22 | /// 23 | /// This is the maximum size in bytes of files that the process may create. 24 | pub max_file_size: Limit, 25 | 26 | /// Max data size 27 | /// 28 | /// This is the maximum size of the process's data segment (initialized data, uninitialized 29 | /// data, and heap). 30 | pub max_data_size: Limit, 31 | 32 | /// Max stack size 33 | /// 34 | /// This is the maximum size of the process stack, in bytes. 35 | pub max_stack_size: Limit, 36 | 37 | /// Max core file size 38 | /// 39 | /// This is the maximum size of a *core* file in bytes that the process may dump. 40 | pub max_core_file_size: Limit, 41 | 42 | /// Max resident set 43 | /// 44 | /// This is a limit (in bytes) on the process's resident set (the number of virtual pages 45 | /// resident in RAM). 46 | pub max_resident_set: Limit, 47 | 48 | /// Max processes 49 | /// 50 | /// This is a limit on the number of extant process (or, more precisely on Linux, threads) for 51 | /// the real user rID of the calling process. 52 | pub max_processes: Limit, 53 | 54 | /// Max open files 55 | /// 56 | /// This specifies a value one greater than the maximum file descriptor number that can be 57 | /// opened by this process. 58 | pub max_open_files: Limit, 59 | 60 | /// Max locked memory 61 | /// 62 | /// This is the maximum number of bytes of memory that may be locked into RAM. 63 | pub max_locked_memory: Limit, 64 | 65 | /// Max address space 66 | /// 67 | /// This is the maximum size of the process's virtual memory (address space). 68 | pub max_address_space: Limit, 69 | 70 | /// Max file locks 71 | /// 72 | /// This is a limit on the combined number of flock locks and fcntl leases that this process 73 | /// may establish. 74 | pub max_file_locks: Limit, 75 | 76 | /// Max pending signals 77 | /// 78 | /// This is a limit on the number of signals that may be queued for the real user rID of the 79 | /// calling process. 80 | pub max_pending_signals: Limit, 81 | 82 | /// Max msgqueue size 83 | /// 84 | /// This is a limit on the number of bytes that can be allocated for POSIX message queues for 85 | /// the real user rID of the calling process. 86 | pub max_msgqueue_size: Limit, 87 | 88 | /// Max nice priority 89 | /// 90 | /// This specifies a ceiling to which the process's nice value can be raised using 91 | /// `setpriority` or `nice`. 92 | pub max_nice_priority: Limit, 93 | 94 | /// Max realtime priority 95 | /// 96 | /// This specifies a ceiling on the real-time priority that may be set for this process using 97 | /// `sched_setscheduler` and `sched_setparam`. 98 | pub max_realtime_priority: Limit, 99 | 100 | /// Max realtime timeout 101 | /// 102 | /// This is a limit (in microseconds) on the amount of CPU time that a process scheduled under 103 | /// a real-time scheduling policy may consume without making a blocking system call. 104 | pub max_realtime_timeout: Limit, 105 | } 106 | 107 | impl crate::FromBufRead for Limits { 108 | fn from_buf_read(r: R) -> ProcResult { 109 | let mut lines = r.lines(); 110 | 111 | let mut map = HashMap::new(); 112 | 113 | while let Some(Ok(line)) = lines.next() { 114 | let line = line.trim(); 115 | if line.starts_with("Limit") { 116 | continue; 117 | } 118 | let s: Vec<_> = line.split_whitespace().collect(); 119 | let l = s.len(); 120 | 121 | let (hard_limit, soft_limit, name) = 122 | if line.starts_with("Max nice priority") || line.starts_with("Max realtime priority") { 123 | // these two limits don't have units, and so need different offsets: 124 | let hard_limit = expect!(s.get(l - 1)).to_owned(); 125 | let soft_limit = expect!(s.get(l - 2)).to_owned(); 126 | let name = s[0..l - 2].join(" "); 127 | (hard_limit, soft_limit, name) 128 | } else { 129 | let hard_limit = expect!(s.get(l - 2)).to_owned(); 130 | let soft_limit = expect!(s.get(l - 3)).to_owned(); 131 | let name = s[0..l - 3].join(" "); 132 | (hard_limit, soft_limit, name) 133 | }; 134 | let _units = expect!(s.get(l - 1)); 135 | 136 | map.insert(name.to_owned(), (soft_limit.to_owned(), hard_limit.to_owned())); 137 | } 138 | 139 | let limits = Limits { 140 | max_cpu_time: Limit::from_pair(expect!(map.remove("Max cpu time")))?, 141 | max_file_size: Limit::from_pair(expect!(map.remove("Max file size")))?, 142 | max_data_size: Limit::from_pair(expect!(map.remove("Max data size")))?, 143 | max_stack_size: Limit::from_pair(expect!(map.remove("Max stack size")))?, 144 | max_core_file_size: Limit::from_pair(expect!(map.remove("Max core file size")))?, 145 | max_resident_set: Limit::from_pair(expect!(map.remove("Max resident set")))?, 146 | max_processes: Limit::from_pair(expect!(map.remove("Max processes")))?, 147 | max_open_files: Limit::from_pair(expect!(map.remove("Max open files")))?, 148 | max_locked_memory: Limit::from_pair(expect!(map.remove("Max locked memory")))?, 149 | max_address_space: Limit::from_pair(expect!(map.remove("Max address space")))?, 150 | max_file_locks: Limit::from_pair(expect!(map.remove("Max file locks")))?, 151 | max_pending_signals: Limit::from_pair(expect!(map.remove("Max pending signals")))?, 152 | max_msgqueue_size: Limit::from_pair(expect!(map.remove("Max msgqueue size")))?, 153 | max_nice_priority: Limit::from_pair(expect!(map.remove("Max nice priority")))?, 154 | max_realtime_priority: Limit::from_pair(expect!(map.remove("Max realtime priority")))?, 155 | max_realtime_timeout: Limit::from_pair(expect!(map.remove("Max realtime timeout")))?, 156 | }; 157 | if cfg!(test) { 158 | assert!(map.is_empty(), "Map isn't empty: {:?}", map); 159 | } 160 | Ok(limits) 161 | } 162 | } 163 | 164 | #[derive(Debug, Copy, Clone)] 165 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 166 | pub struct Limit { 167 | pub soft_limit: LimitValue, 168 | pub hard_limit: LimitValue, 169 | } 170 | 171 | impl Limit { 172 | fn from_pair(l: (String, String)) -> ProcResult { 173 | let (soft, hard) = l; 174 | Ok(Limit { 175 | soft_limit: LimitValue::from_str(&soft)?, 176 | hard_limit: LimitValue::from_str(&hard)?, 177 | }) 178 | } 179 | } 180 | 181 | #[derive(Debug, Copy, Clone)] 182 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 183 | pub enum LimitValue { 184 | Unlimited, 185 | Value(u64), 186 | } 187 | 188 | impl FromStr for LimitValue { 189 | type Err = ProcError; 190 | fn from_str(s: &str) -> Result { 191 | if s == "unlimited" { 192 | Ok(LimitValue::Unlimited) 193 | } else { 194 | Ok(LimitValue::Value(from_str!(u64, s))) 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /procfs/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These examples can be run by running `cargo run --example example_name` 4 | 5 | ## dump.rs 6 | 7 | Prints out details about the current process (the dumper itself), or a process specifed by PID 8 | 9 | ## interface_stats.rs 10 | 11 | Runs continually and prints out how many bytes/packets are sent/received. Press ctrl-c to exit the example: 12 | 13 | ```text 14 | Interface: bytes recv bytes sent 15 | ================ ==================== ==================== 16 | br-883c4c992deb: 823307769 0.2 kbps 1537694158 0.5 kbps 17 | br-d73af6e6d094: 9137600399 0.9 kbps 2334717319 0.4 kbps 18 | docker0: 2938964881 0.6 kbps 19291691656 11.4 kbps 19 | docker_gwbridge: 1172300 0.0 kbps 15649536 0.0 kbps 20 | enp5s0f0: 44643307888420 5599.8 kbps 1509415976135 99.0 kbps 21 | enp5s0f1: 0 0.0 kbps 0 0.0 kbps 22 | lo: 161143108162 0.4 kbps 161143108162 0.4 kbps 23 | veth3154ff3: 3809619534 1.0 kbps 867529906 0.4 kbps 24 | veth487bc9b: 2650532684 0.8 kbps 2992458899 0.9 kbps 25 | veth8cb8ca8: 3234030733 0.7 kbps 16921098378 11.4 kbps 26 | vethbadbe14: 12007615348 3.8 kbps 15583195644 5.0 kbps 27 | vethc152f93: 978828 0.0 kbps 3839134 0.0 kbps 28 | vethe481f30: 1637142 0.0 kbps 15805768 0.0 kbps 29 | vethfac2e83: 19445827683 6.2 kbps 16194181515 5.1 kbps 30 | 31 | ``` 32 | 33 | ## netstat.rs 34 | 35 | Prints out all open and listening TCP/UDP sockets, along with the owning process. The 36 | output format is very similar to the standard `netstat` linux utility: 37 | 38 | ```text 39 | Local address Remote address State Inode PID/Program name 40 | 0.0.0.0:53 0.0.0.0:0 Listen 30883 1409/pdns_server 41 | 0.0.0.0:51413 0.0.0.0:0 Listen 24263 927/transmission-da 42 | 0.0.0.0:35445 0.0.0.0:0 Listen 21777 942/rpc.mountd 43 | 0.0.0.0:22 0.0.0.0:0 Listen 27973 1149/sshd 44 | 0.0.0.0:25 0.0.0.0:0 Listen 28295 1612/master 45 | ``` 46 | 47 | ## pressure.rs 48 | 49 | Prints out CPU/IO/Memory pressure information 50 | 51 | ## ps.rs 52 | 53 | Prints out all processes that share the same tty as the current terminal. This is very similar to the standard 54 | `ps` utility on linux when run with no arguments: 55 | 56 | ```text 57 | PID TTY TIME CMD 58 | 8369 pty/13 4.05 bash 59 | 23124 pty/13 0.23 basic-http-serv 60 | 24206 pty/13 0.11 ps 61 | ``` 62 | 63 | ## self_memory.rs 64 | 65 | Shows several ways to get the current memory usage of the current process 66 | 67 | ```text 68 | PID: 21867 69 | Memory page size: 4096 70 | == Data from /proc/self/stat: 71 | Total virtual memory used: 3436544 bytes 72 | Total resident set: 220 pages (901120 bytes) 73 | 74 | == Data from /proc/self/statm: 75 | Total virtual memory used: 839 pages (3436544 bytes) 76 | Total resident set: 220 pages (901120 byte)s 77 | Total shared memory: 191 pages (782336 bytes) 78 | 79 | == Data from /proc/self/status: 80 | Total virtual memory used: 3436544 bytes 81 | Total resident set: 901120 bytes 82 | Total shared memory: 782336 bytes 83 | ``` 84 | 85 | ## lsmod.rs 86 | 87 | This lists all the loaded kernel modules, in a simple tree format. 88 | 89 | ## diskstat.rs 90 | 91 | Lists IO information for local disks: 92 | 93 | ```text 94 | sda1 mounted on /: 95 | total reads: 7325390 (13640070 ms) 96 | total writes: 124191552 (119109541 ms) 97 | total flushes: 0 (0 ms) 98 | ``` 99 | 100 | Note: only local disks will be shown (not NFS mounts, 101 | and disks used for ZFS will not be shown either). 102 | 103 | ## lslocks.rs 104 | 105 | Shows current file locks in a format that is similiar to the `lslocks` utility. 106 | 107 | ## mountinfo.rs 108 | 109 | Lists all mountpoints, along with their type and options: 110 | 111 | ```text 112 | sysfs on /sys type sysfs (noexec,relatime,nodev,rw,nosuid) 113 | proc on /proc type proc (noexec,rw,nodev,relatime,nosuid) 114 | udev on /dev type devtmpfs (rw,nosuid,relatime) 115 | mode = 755 116 | nr_inodes = 4109298 117 | size = 16437192k 118 | devpts on /dev/pts type devpts (nosuid,rw,noexec,relatime) 119 | gid = 5 120 | ptmxmode = 000 121 | mode = 620 122 | tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime) 123 | size = 3291852k 124 | mode = 755 125 | /dev/sda1 on / type ext4 (rw,relatime) 126 | errors = remount-ro 127 | ``` 128 | 129 | ## process_hierarchy.rs 130 | 131 | Lists all processes as a tree. Sub-processes will be hierarchically ordered beneath their parents. 132 | 133 | ```text 134 | 1 /usr/lib/systemd/systemd --system --deserialize 54 135 | 366 /usr/lib/systemd/systemd-journald 136 | 375 /usr/lib/systemd/systemd-udevd 137 | 383 /usr/bin/lvmetad -f 138 | 525 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only 139 | 529 /usr/bin/syncthing -no-browser -no-restart -logflags=0 140 | 608 /usr/bin/syncthing -no-browser -no-restart -logflags=0 141 | 530 /usr/lib/systemd/systemd-logind 142 | ... 143 | ``` 144 | 145 | ## pfn.rs 146 | 147 | List memory mapping, and physical address for each virtual address. Must be run as root, see [pagemap.txt](https://www.kernel.org/doc/Documentation/vm/pagemap.txt) 148 | 149 | ```text 150 | Memory mapping MemoryMap { address: (140561525968896, 140561525972992), perms: "r--p", offset: 884736, dev: (252, 0), inode: 18221539, pathname: Path("/usr/lib64/libm.so.6") } 151 | virt_mem: 0x7fd707d31000, pfn: 0x1fd37d, phys_addr: 0x1fd37d000 152 | Memory mapping MemoryMap { address: (140561525972992, 140561525977088), perms: "rw-p", offset: 888832, dev: (252, 0), inode: 18221539, pathname: Path("/usr/lib64/libm.so.6") } 153 | virt_mem: 0x7fd707d32000, pfn: 0x1fcb97, phys_addr: 0x1fcb97000 154 | ``` 155 | 156 | ## process_kpageflags.rs 157 | 158 | Search for a pointer (virtual address) in physical memory. Display physical page flags 159 | 160 | Requires root or sudo 161 | 162 | ```text 163 | Virtual address of `variable`: 0x7ffd2de4708f 164 | Found memory mapping 165 | MemoryMap { address: (140725373272064, 140725373407232), perms: "rw-p", offset: 0, dev: (0, 0), inode: 0, pathname: Stack } 166 | Found page 167 | virt_mem: 0x7ffd2de47000, pfn: 0x107b06, phys_addr: 0x107b06000, flags: UPTODATE | LRU | MMAP | ANON | SWAPBACKED 168 | ``` 169 | 170 | ## kpagecount 171 | 172 | List physical memory pages by reading /proc/iomem, and find the page with the most references 173 | 174 | Require root or CAP_SYS_ADMIN 175 | 176 | ```text 177 | Found RAM here: 0x1000-0x9fbff 178 | Lots of references to this locations: addr=0x9d000, pfn=157, refs=0 179 | Found RAM here: 0x100000-0xdffeffff 180 | Lots of references to this locations: addr=0x81ba3000, pfn=531363, refs=128 181 | Found RAM here: 0x100000000-0x11fffffff 182 | Lots of references to this locations: addr=0x1b575000, pfn=111989, refs=134 183 | ``` 184 | 185 | 186 | ## Crypto 187 | 188 | List available crypto algorithms, along with details. Passing an algorithm as an argument will show only that algorithms 189 | 190 | implementations (this can potentially be multiple). Partial arguments (i.e "sha") will return all algorithms that match. 191 | 192 | ```text 193 | Type: sha256 194 | Name: sha256 195 | Driver: sha256-avx2 196 | Module: sha256_ssse3 197 | Priority: 170 198 | Ref Count: 2 199 | Self Test: Passed 200 | Internal: false 201 | fips enabled: false 202 | Type Details: Shash(Shash { block_size: 64, digest_size: 32 }) 203 | ``` 204 | -------------------------------------------------------------------------------- /procfs-core/src/locks.rs: -------------------------------------------------------------------------------- 1 | use crate::{expect, from_str, ProcResult}; 2 | use std::io::BufRead; 3 | 4 | #[cfg(feature = "serde1")] 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// The type of a file lock 8 | #[derive(Debug, Clone, PartialEq, Eq)] 9 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 10 | pub enum LockType { 11 | /// A BSD file lock created using `flock` 12 | FLock, 13 | 14 | /// A POSIX byte-range lock created with `fcntl` 15 | Posix, 16 | 17 | /// An Open File Description (ODF) lock created with `fnctl` 18 | ODF, 19 | 20 | /// Some other unknown lock type 21 | Other(String), 22 | } 23 | 24 | impl LockType { 25 | pub fn as_str(&self) -> &str { 26 | match self { 27 | LockType::FLock => "FLOCK", 28 | LockType::Posix => "POSIX", 29 | LockType::ODF => "ODF", 30 | LockType::Other(s) => s.as_ref(), 31 | } 32 | } 33 | } 34 | 35 | impl From<&str> for LockType { 36 | fn from(s: &str) -> LockType { 37 | match s { 38 | "FLOCK" => LockType::FLock, 39 | "OFDLCK" => LockType::ODF, 40 | "POSIX" => LockType::Posix, 41 | x => LockType::Other(x.to_string()), 42 | } 43 | } 44 | } 45 | 46 | /// The mode of a lock (advisory or mandatory) 47 | #[derive(Debug, Clone)] 48 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 49 | pub enum LockMode { 50 | Advisory, 51 | Mandatory, 52 | 53 | /// Some other unknown lock mode 54 | Other(String), 55 | } 56 | 57 | impl LockMode { 58 | pub fn as_str(&self) -> &str { 59 | match self { 60 | LockMode::Advisory => "ADVISORY", 61 | LockMode::Mandatory => "MANDATORY", 62 | LockMode::Other(s) => s.as_ref(), 63 | } 64 | } 65 | } 66 | 67 | impl From<&str> for LockMode { 68 | fn from(s: &str) -> LockMode { 69 | match s { 70 | "ADVISORY" => LockMode::Advisory, 71 | "MANDATORY" => LockMode::Mandatory, 72 | x => LockMode::Other(x.to_string()), 73 | } 74 | } 75 | } 76 | 77 | /// The kind of a lock (read or write) 78 | #[derive(Debug, Clone)] 79 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 80 | pub enum LockKind { 81 | /// A read lock (or BSD shared lock) 82 | Read, 83 | 84 | /// A write lock (or a BSD exclusive lock) 85 | Write, 86 | 87 | /// Some other unknown lock kind 88 | Other(String), 89 | } 90 | 91 | impl LockKind { 92 | pub fn as_str(&self) -> &str { 93 | match self { 94 | LockKind::Read => "READ", 95 | LockKind::Write => "WRITE", 96 | LockKind::Other(s) => s.as_ref(), 97 | } 98 | } 99 | } 100 | 101 | impl From<&str> for LockKind { 102 | fn from(s: &str) -> LockKind { 103 | match s { 104 | "READ" => LockKind::Read, 105 | "WRITE" => LockKind::Write, 106 | x => LockKind::Other(x.to_string()), 107 | } 108 | } 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 113 | /// Details about an individual file lock 114 | /// 115 | /// For an example, see the [lslocks.rs](https://github.com/eminence/procfs/tree/master/examples) 116 | /// example in the source repo. 117 | pub struct Lock { 118 | /// The type of lock 119 | pub lock_type: LockType, 120 | /// The lock mode (advisory or mandatory) 121 | pub mode: LockMode, 122 | /// The kind of lock (read or write) 123 | pub kind: LockKind, 124 | /// The process that owns the lock 125 | /// 126 | /// Because OFD locks are not owned by a single process (since multiple processes 127 | /// may have file descriptors that refer to the same FD), this field may be `None`. 128 | /// 129 | /// Before kernel 4.14 a bug meant that the PID of of the process that initially 130 | /// acquired the lock was displayed instead of `None`. 131 | pub pid: Option, 132 | /// The major ID of the device containing the FS that contains this lock 133 | pub devmaj: u32, 134 | /// The minor ID of the device containing the FS that contains this lock 135 | pub devmin: u32, 136 | /// The inode of the locked file 137 | pub inode: u64, 138 | /// The offset (in bytes) of the first byte of the lock. 139 | /// 140 | /// For BSD locks, this value is always 0. 141 | pub offset_first: u64, 142 | /// The offset (in bytes) of the last byte of the lock. 143 | /// 144 | /// `None` means the lock extends to the end of the file. For BSD locks, 145 | /// the value is always `None`. 146 | pub offset_last: Option, 147 | } 148 | 149 | impl Lock { 150 | fn from_line(line: &str) -> ProcResult { 151 | let mut s = line.split_whitespace(); 152 | 153 | let _ = expect!(s.next()); 154 | let typ = { 155 | let t = expect!(s.next()); 156 | if t == "->" { 157 | // some locks start a "->" which apparently means they are "blocked" (but i'm not sure what that actually means) 158 | From::from(expect!(s.next())) 159 | } else { 160 | From::from(t) 161 | } 162 | }; 163 | let mode = From::from(expect!(s.next())); 164 | let kind = From::from(expect!(s.next())); 165 | let pid = expect!(s.next()); 166 | let disk_inode = expect!(s.next()); 167 | let offset_first = from_str!(u64, expect!(s.next())); 168 | let offset_last = expect!(s.next()); 169 | 170 | let mut dis = disk_inode.split(':'); 171 | let devmaj = from_str!(u32, expect!(dis.next()), 16); 172 | let devmin = from_str!(u32, expect!(dis.next()), 16); 173 | let inode = from_str!(u64, expect!(dis.next())); 174 | 175 | Ok(Lock { 176 | lock_type: typ, 177 | mode, 178 | kind, 179 | pid: if pid == "-1" { None } else { Some(from_str!(i32, pid)) }, 180 | devmaj, 181 | devmin, 182 | inode, 183 | offset_first, 184 | offset_last: if offset_last == "EOF" { 185 | None 186 | } else { 187 | Some(from_str!(u64, offset_last)) 188 | }, 189 | }) 190 | } 191 | } 192 | 193 | #[derive(Debug, Clone)] 194 | #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] 195 | /// Details about file locks 196 | pub struct Locks(pub Vec); 197 | 198 | impl super::FromBufRead for Locks { 199 | fn from_buf_read(r: R) -> ProcResult { 200 | let mut v = Vec::new(); 201 | 202 | for line in r.lines() { 203 | let line = line?; 204 | v.push(Lock::from_line(&line)?); 205 | } 206 | Ok(Locks(v)) 207 | } 208 | } 209 | 210 | #[cfg(test)] 211 | mod tests { 212 | #[test] 213 | fn test_blocked() { 214 | let data = r#"1: POSIX ADVISORY WRITE 723 00:14:16845 0 EOF 215 | 2: FLOCK ADVISORY WRITE 652 00:14:16763 0 EOF 216 | 3: FLOCK ADVISORY WRITE 1594 fd:00:396528 0 EOF 217 | 4: FLOCK ADVISORY WRITE 1594 fd:00:396527 0 EOF 218 | 5: FLOCK ADVISORY WRITE 2851 fd:00:529372 0 EOF 219 | 6: POSIX ADVISORY WRITE 1280 00:14:16200 0 0 220 | 6: -> POSIX ADVISORY WRITE 1281 00:14:16200 0 0 221 | 6: -> POSIX ADVISORY WRITE 1279 00:14:16200 0 0 222 | 6: -> POSIX ADVISORY WRITE 1282 00:14:16200 0 0 223 | 6: -> POSIX ADVISORY WRITE 1283 00:14:16200 0 0 224 | 7: OFDLCK ADVISORY READ -1 00:06:1028 0 EOF 225 | 8: FLOCK ADVISORY WRITE 6471 fd:00:529426 0 EOF 226 | 9: FLOCK ADVISORY WRITE 6471 fd:00:529424 0 EOF 227 | 10: FLOCK ADVISORY WRITE 6471 fd:00:529420 0 EOF 228 | 11: FLOCK ADVISORY WRITE 6471 fd:00:529418 0 EOF 229 | 12: POSIX ADVISORY WRITE 1279 00:14:23553 0 EOF 230 | 13: FLOCK ADVISORY WRITE 6471 fd:00:393838 0 EOF 231 | 14: POSIX ADVISORY WRITE 655 00:14:16146 0 EOF"#; 232 | 233 | for line in data.lines() { 234 | super::Lock::from_line(line.trim()).unwrap(); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /support.md: -------------------------------------------------------------------------------- 1 | # Supported features 2 | 3 | This is an approximate list of all the files under the `/proc` mount, and an indication if that feature/file is supported by the `procfs` crate. Help is needed to keep this file up-to-date, so please open an issue or pull request if you spot something that's not right. 4 | 5 | * [ ] `/proc/[pid]` 6 | * [ ] `/proc/[pid]/attr` 7 | * [ ] `/proc/[pid]/attr/current` 8 | * [ ] `/proc/[pid]/attr/exec` 9 | * [ ] `/proc/[pid]/attr/fscreate` 10 | * [ ] `/proc/[pid]/attr/keycreate` 11 | * [ ] `/proc/[pid]/attr/prev` 12 | * [ ] `/proc/[pid]/attr/socketcreate` 13 | * [x] `/proc/[pid]/autogroup` 14 | * [x] `/proc/[pid]/auxv` 15 | * [x] `/proc/[pid]/cgroup` 16 | * [x] `/proc/[pid]/clear_refs` 17 | * [x] `/proc/[pid]/cmdline` 18 | * [x] `/proc/[pid]/comm` 19 | * [x] `/proc/[pid]/coredump_filter` 20 | * [ ] `/proc/[pid]/cpuset` 21 | * [x] `/proc/[pid]/cwd` 22 | * [x] `/proc/[pid]/environ` 23 | * [x] `/proc/[pid]/exe` 24 | * [x] `/proc/[pid]/fd/` 25 | * [ ] `/proc/[pid]/fdinfo/` 26 | * [ ] `/proc/[pid]/gid_map` 27 | * [x] `/proc/[pid]/io` 28 | * [x] `/proc/[pid]/limits` 29 | * [ ] `/proc/[pid]/map_files/` 30 | * [x] `/proc/[pid]/maps` 31 | * [x] `/proc/[pid]/mem` 32 | * [x] `/proc/[pid]/mountinfo` 33 | * [ ] `/proc/[pid]/mounts` 34 | * [x] `/proc/[pid]/mountstats` 35 | * [x] `/proc/[pid]/ns/` 36 | * [ ] `/proc/[pid]/numa_maps` 37 | * [ ] `/proc/[pid]/oom_adj` 38 | * [x] `/proc/[pid]/oom_score` 39 | * [x] `/proc/[pid]/oom_score_adj` 40 | * [ ] `/proc/[pid]/pagemap` 41 | * [ ] `/proc/[pid]/personality` 42 | * [x] `/proc/[pid]/root` 43 | * [ ] `/proc/[pid]/seccomp` 44 | * [ ] `/proc/[pid]/setgroups` 45 | * [ ] `/proc/[pid]/sched_autogroup_enabled` 46 | * [x] `/proc/[pid]/smaps` 47 | * [x] `/proc/[pid]/smaps_rollup` 48 | * [ ] `/proc/[pid]/stack` 49 | * [x] `/proc/[pid]/stat` 50 | * [x] `/proc/[pid]/statm` 51 | * [x] `/proc/[pid]/status` 52 | * [x] `/proc/[pid]/syscall` 53 | * [ ] `/proc/[pid]/task` 54 | * [x] `/proc/[pid]/task/[tid]/stat` 55 | * [x] `/proc/[pid]/task/[tid]/status` 56 | * [x] `/proc/[pid]/task/[tid]/syscall` 57 | * [x] `/proc/[pid]/task/[tid]/io` 58 | * [x] `/proc/[pid]/task/[tid]/children` 59 | * [ ] `/proc/[pid]/timers` 60 | * [ ] `/proc/[pid]/timerslack_ns` 61 | * [ ] `/proc/[pid]/uid_map` 62 | * [ ] `/proc/[pid]/gid_map` 63 | * [x] `/proc/[pid]/wchan` 64 | * [ ] `/proc/apm` 65 | * [ ] `/proc/buddyinfo` 66 | * [ ] `/proc/bus` 67 | * [ ] `/proc/bus/pccard` 68 | * [ ] `/proc/bus/pccard/drivers` 69 | * [ ] `/proc/bus/pci` 70 | * [ ] `/proc/bus/pci/devices` 71 | * [x] `/proc/cmdline` 72 | * [ ] `/proc/config.gz` 73 | * [ ] `/proc/crypto` 74 | * [ ] `/proc/cpuinfo` 75 | * [x] `/proc/devices` 76 | * [x] `/proc/diskstats` 77 | * [ ] `/proc/dma` 78 | * [ ] `/proc/driver` 79 | * [ ] `/proc/execdomains` 80 | * [ ] `/proc/fb` 81 | * [ ] `/proc/filesystems` 82 | * [ ] `/proc/fs` 83 | * [ ] `/proc/ide` 84 | * [ ] `/proc/interrupts` 85 | * [x] `/proc/iomem` 86 | * [ ] `/proc/ioports` 87 | * [ ] `/proc/kallsyms` 88 | * [ ] `/proc/kcore` 89 | * [x] `/proc/keys` 90 | * [x] `/proc/key-users` 91 | * [ ] `/proc/kmsg` 92 | * [ ] `/proc/kpagecgroup` 93 | * [ ] `/proc/kpagecgroup` 94 | * [x] `/proc/kpageflags` 95 | * [x] `/proc/kpagecount` 96 | * [ ] `/proc/ksyms` 97 | * [x] `/proc/loadavg` 98 | * [x] `/proc/locks` 99 | * [ ] `/proc/malloc` 100 | * [x] `/proc/meminfo` 101 | * [x] `/proc/modules` 102 | * [x] `/proc/mounts` 103 | * [ ] `/proc/mtrr` 104 | * [ ] `/proc/net` 105 | * [x] `/proc/net/arp` 106 | * [x] `/proc/net/dev` 107 | * [ ] `/proc/net/dev_mcast` 108 | * [ ] `/proc/net/igmp` 109 | * [ ] `/proc/net/ipv6_route` 110 | * [ ] `/proc/net/rarp` 111 | * [ ] `/proc/net/raw` 112 | * [x] `/proc/net/route` 113 | * [x] `/proc/net/snmp` 114 | * [x] `/proc/net/snmp6` 115 | * [x] `/proc/net/tcp` 116 | * [x] `/proc/net/udp` 117 | * [x] `/proc/net/unix` 118 | * [ ] `/proc/net/netfilter/nfnetlink_queue` 119 | * [x] `/proc/partitions` 120 | * [ ] `/proc/pci` 121 | * [x] `/proc/pressure` 122 | * [x] `/proc/pressure/cpu` 123 | * [x] `/proc/pressure/io` 124 | * [x] `/proc/pressure/memory` 125 | * [ ] `/proc/profile` 126 | * [ ] `/proc/scsi` 127 | * [ ] `/proc/scsi/scsi` 128 | * [ ] `/proc/scsi/[drivername]` 129 | * [ ] `/proc/self` 130 | * [ ] `/proc/slabinfo` 131 | * [x] `/proc/stat` 132 | * [ ] `/proc/swaps` 133 | * [ ] `/proc/sys` 134 | * [ ] `/proc/sys/abi` 135 | * [ ] `/proc/sys/debug` 136 | * [ ] `/proc/sys/dev` 137 | * [ ] `/proc/sys/fs` 138 | * [x] `/proc/sys/fs/binfmt_misc` 139 | * [x] `/proc/sys/fs/dentry-state` 140 | * [ ] `/proc/sys/fs/dir-notify-enable` 141 | * [ ] `/proc/sys/fs/dquot-max` 142 | * [ ] `/proc/sys/fs/dquot-nr` 143 | * [x] `/proc/sys/fs/epoll` 144 | * [x] `/proc/sys/fs/file-max` 145 | * [x] `/proc/sys/fs/file-nr` 146 | * [ ] `/proc/sys/fs/inode-max` 147 | * [ ] `/proc/sys/fs/inode-nr` 148 | * [ ] `/proc/sys/fs/inode-state` 149 | * [ ] `/proc/sys/fs/inotify` 150 | * [ ] `/proc/sys/fs/lease-break-time` 151 | * [ ] `/proc/sys/fs/leases-enable` 152 | * [ ] `/proc/sys/fs/mount-max` 153 | * [ ] `/proc/sys/fs/mqueue` 154 | * [ ] `/proc/sys/fs/nr_open` 155 | * [ ] `/proc/sys/fs/overflowgid` 156 | * [ ] `/proc/sys/fs/overflowuid` 157 | * [ ] `/proc/sys/fs/pipe-max-size` 158 | * [ ] `/proc/sys/fs/pipe-user-pages-hard` 159 | * [ ] `/proc/sys/fs/pipe-user-pages-soft` 160 | * [ ] `/proc/sys/fs/protected_hardlinks` 161 | * [ ] `/proc/sys/fs/protected_symlinks` 162 | * [ ] `/proc/sys/fs/suid_dumpable` 163 | * [ ] `/proc/sys/fs/super-max` 164 | * [ ] `/proc/sys/fs/super-nr` 165 | * [ ] `/proc/sys/kernel` 166 | * [ ] `/proc/sys/kernel/acct` 167 | * [ ] `/proc/sys/kernel/auto_msgmni` 168 | * [ ] `/proc/sys/kernel/cap_last_cap` 169 | * [ ] `/proc/sys/kernel/cap-bound` 170 | * [ ] `/proc/sys/kernel/core_pattern` 171 | * [ ] `/proc/sys/kernel/core_pipe_limit` 172 | * [ ] `/proc/sys/kernel/core_uses_pid` 173 | * [ ] `/proc/sys/kernel/ctrl-alt-del` 174 | * [ ] `/proc/sys/kernel/dmesg_restrict` 175 | * [ ] `/proc/sys/kernel/domainname` 176 | * [ ] `/proc/sys/kernel/hostname` 177 | * [ ] `/proc/sys/kernel/hotplug` 178 | * [ ] `/proc/sys/kernel/htab-reclaim` 179 | * [x] `/proc/sys/kernel/keys/\*` 180 | * [ ] `/proc/sys/kernel/kptr_restrict` 181 | * [ ] `/proc/sys/kernel/l2cr` 182 | * [ ] `/proc/sys/kernel/modprobe` 183 | * [ ] `/proc/sys/kernel/modules_disabled` 184 | * [ ] `/proc/sys/kernel/msgmax` 185 | * [ ] `/proc/sys/kernel/msgmni` 186 | * [ ] `/proc/sys/kernel/msgmnb` 187 | * [ ] `/proc/sys/kernel/ngroups_max` 188 | * [ ] `/proc/sys/kernel/ns_last_pid` 189 | * [x] `/proc/sys/kernel/ostype` 190 | * [x] `/proc/sys/kernel/osrelease` 191 | * [ ] `/proc/sys/kernel/overflowgid` 192 | * [ ] `/proc/sys/kernel/overflowuid` 193 | * [ ] `/proc/sys/kernel/panic` 194 | * [ ] `/proc/sys/kernel/panic_on_oops` 195 | * [x] `/proc/sys/kernel/pid_max` 196 | * [ ] `/proc/sys/kernel/powersave-nap` 197 | * [ ] `/proc/sys/kernel/printk` 198 | * [ ] `/proc/sys/kernel/pty` 199 | * [ ] `/proc/sys/kernel/pty/max` 200 | * [ ] `/proc/sys/kernel/pty/nr` 201 | * [x] `/proc/sys/kernel/random` 202 | * [x] `/proc/sys/kernel/random/entropy_avail` 203 | * [x] `/proc/sys/kernel/random/poolsize` 204 | * [x] `/proc/sys/kernel/random/read_wakeup_threshold` 205 | * [x] `/proc/sys/kernel/random/write_wakeup_threshold` 206 | * [x] `/proc/sys/kernel/random/uuid` 207 | * [x] `/proc/sys/kernel/random/boot_id` 208 | * [ ] `/proc/sys/kernel/randomize_va_space` 209 | * [ ] `/proc/sys/kernel/real-root-dev` 210 | * [ ] `/proc/sys/kernel/reboot-cmd` 211 | * [ ] `/proc/sys/kernel/rtsig-max` 212 | * [ ] `/proc/sys/kernel/rtsig-nr` 213 | * [ ] `/proc/sys/kernel/sched_child_runs_first` 214 | * [ ] `/proc/sys/kernel/sched_rr_timeslice_ms` 215 | * [ ] `/proc/sys/kernel/sched_rt_period_us` 216 | * [ ] `/proc/sys/kernel/sched_rt_runtime_us` 217 | * [ ] `/proc/sys/kernel/seccomp` 218 | * [x] `/proc/sys/kernel/sem` 219 | * [ ] `/proc/sys/kernel/sg-big-buff` 220 | * [ ] `/proc/sys/kernel/shm_rmid_forced` 221 | * [x] `/proc/sys/kernel/shmall` 222 | * [x] `/proc/sys/kernel/shmmax` 223 | * [x] `/proc/sys/kernel/shmmni` 224 | * [ ] `/proc/sys/kernel/sysctl_writes_strict` 225 | * [x] `/proc/sys/kernel/sysrq` 226 | * [x] `/proc/sys/kernel/version` 227 | * [x] `/proc/sys/kernel/threads-max` 228 | * [ ] `/proc/sys/kernel/yama/ptrace_scope` 229 | * [ ] `/proc/sys/kernel/zero-paged` 230 | * [ ] `/proc/sys/net` 231 | * [ ] `/proc/sys/net/core/bpf_jit_enable` 232 | * [ ] `/proc/sys/net/core/somaxconn` 233 | * [ ] `/proc/sys/proc` 234 | * [ ] `/proc/sys/sunrpc` 235 | * [ ] `/proc/sys/user` 236 | * [ ] `/proc/sys/vm` 237 | * [x] `/proc/sys/vm/admin_reserve_kbytes` 238 | * [ ] `/proc/sys/vm/compact_memory` 239 | * [x] `/proc/sys/vm/drop_caches` 240 | * [ ] `/proc/sys/vm/legacy_va_layout` 241 | * [ ] `/proc/sys/vm/memory_failure_early_kill` 242 | * [ ] `/proc/sys/vm/memory_failure_recovery` 243 | * [ ] `/proc/sys/vm/oom_dump_tasks` 244 | * [ ] `/proc/sys/vm/oom_kill_allocating_task` 245 | * [ ] `/proc/sys/vm/overcommit_kbytes` 246 | * [x] `/proc/sys/vm/overcommit_memory` 247 | * [ ] `/proc/sys/vm/overcommit_ratio` 248 | * [ ] `/proc/sys/vm/panic_on_oom` 249 | * [ ] `/proc/sys/vm/swappiness` 250 | * [ ] `/proc/sys/vm/user_reserve_kbytes` 251 | * [ ] `/proc/sysrq-trigger` 252 | * [ ] `/proc/sysvipc` 253 | * [ ] `/proc/thread-self` 254 | * [ ] `/proc/timer_list` 255 | * [ ] `/proc/timer_stats` 256 | * [ ] `/proc/tty` 257 | * [x] `/proc/uptime` 258 | * [ ] `/proc/version` 259 | * [x] `/proc/vmstat` 260 | * [ ] `/proc/zoneinfo` 261 | -------------------------------------------------------------------------------- /procfs/src/net.rs: -------------------------------------------------------------------------------- 1 | // Don't throw clippy warnings for manual string stripping. 2 | // The suggested fix with `strip_prefix` removes support for Rust 1.33 and 1.38 3 | #![allow(clippy::manual_strip)] 4 | 5 | //! Information about the networking layer. 6 | //! 7 | //! This module corresponds to the `/proc/net` directory and contains various information about the 8 | //! networking layer. 9 | //! 10 | //! # Example 11 | //! 12 | //! Here's an example that will print out all of the open and listening TCP sockets, and their 13 | //! corresponding processes, if know. This mimics the "netstat" utility, but for TCP only. You 14 | //! can run this example yourself with: 15 | //! 16 | //! > cargo run --example=netstat 17 | //! 18 | //! ```rust 19 | //! # use procfs::process::{FDTarget, Stat}; 20 | //! # use std::collections::HashMap; 21 | //! let all_procs = procfs::process::all_processes().unwrap(); 22 | //! 23 | //! // build up a map between socket inodes and process stat info: 24 | //! let mut map: HashMap = HashMap::new(); 25 | //! for p in all_procs { 26 | //! let Ok(process) = p else { 27 | //! // process vanished 28 | //! continue; 29 | //! }; 30 | //! if let (Ok(stat), Ok(fds)) = (process.stat(), process.fd()) { 31 | //! for fd in fds { 32 | //! if let FDTarget::Socket(inode) = fd.unwrap().target { 33 | //! map.insert(inode, stat.clone()); 34 | //! } 35 | //! } 36 | //! } 37 | //! } 38 | //! 39 | //! // get the tcp table 40 | //! let tcp = procfs::net::tcp().unwrap(); 41 | //! let tcp6 = procfs::net::tcp6().unwrap(); 42 | //! println!("{:<26} {:<26} {:<15} {:<8} {}", "Local address", "Remote address", "State", "Inode", "PID/Program name"); 43 | //! for entry in tcp.into_iter().chain(tcp6) { 44 | //! // find the process (if any) that has an open FD to this entry's inode 45 | //! let local_address = format!("{}", entry.local_address); 46 | //! let remote_addr = format!("{}", entry.remote_address); 47 | //! let state = format!("{:?}", entry.state); 48 | //! if let Some(stat) = map.get(&entry.inode) { 49 | //! println!("{:<26} {:<26} {:<15} {:<12} {}/{}", local_address, remote_addr, state, entry.inode, stat.pid, stat.comm); 50 | //! } else { 51 | //! // We might not always be able to find the process associated with this socket 52 | //! println!("{:<26} {:<26} {:<15} {:<12} -", local_address, remote_addr, state, entry.inode); 53 | //! } 54 | //! } 55 | //! ``` 56 | use crate::ProcResult; 57 | use crate::{current_system_info, Current}; 58 | pub use procfs_core::net::*; 59 | use procfs_core::FromReadSI; 60 | use std::collections::HashMap; 61 | 62 | /// Reads the tcp socket table 63 | /// 64 | /// Note that this is the socket table for the current process. If you want to 65 | /// see the socket table for another process, then see [Process::tcp()](crate::process::Process::tcp()) 66 | pub fn tcp() -> ProcResult> { 67 | TcpNetEntries::from_file("/proc/net/tcp", current_system_info()).map(|e| e.0) 68 | } 69 | 70 | /// Reads the tcp6 socket table 71 | /// 72 | /// Note that this is the socket table for the current process. If you want to 73 | /// see the socket table for another process, then see [Process::tcp6()](crate::process::Process::tcp6()) 74 | pub fn tcp6() -> ProcResult> { 75 | TcpNetEntries::from_file("/proc/net/tcp6", current_system_info()).map(|e| e.0) 76 | } 77 | 78 | /// Reads the udp socket table 79 | /// 80 | /// Note that this is the socket table for the current process. If you want to 81 | /// see the socket table for another process, then see [Process::udp()](crate::process::Process::udp()) 82 | pub fn udp() -> ProcResult> { 83 | UdpNetEntries::from_file("/proc/net/udp", current_system_info()).map(|e| e.0) 84 | } 85 | 86 | /// Reads the udp6 socket table 87 | /// 88 | /// Note that this is the socket table for the current process. If you want to 89 | /// see the socket table for another process, then see [Process::udp6()](crate::process::Process::udp6()) 90 | pub fn udp6() -> ProcResult> { 91 | UdpNetEntries::from_file("/proc/net/udp6", current_system_info()).map(|e| e.0) 92 | } 93 | 94 | impl Current for UnixNetEntries { 95 | const PATH: &'static str = "/proc/net/unix"; 96 | } 97 | 98 | /// Reads the unix socket table 99 | /// 100 | /// Note that this is the socket table for the current process. If you want to 101 | /// see the socket table for another process, then see [Process::unix()](crate::process::Process::unix()) 102 | pub fn unix() -> ProcResult> { 103 | UnixNetEntries::current().map(|e| e.0) 104 | } 105 | 106 | impl super::Current for ArpEntries { 107 | const PATH: &'static str = "/proc/net/arp"; 108 | } 109 | 110 | /// Reads the ARP table 111 | /// 112 | /// Note that this is the ARP table for the current progress. If you want to 113 | /// see the ARP table for another process, then see [Process::arp()](crate::process::Process::arp()) 114 | pub fn arp() -> ProcResult> { 115 | ArpEntries::current().map(|e| e.0) 116 | } 117 | 118 | impl super::Current for InterfaceDeviceStatus { 119 | const PATH: &'static str = "/proc/net/dev"; 120 | } 121 | 122 | /// Returns basic network device statistics for all interfaces 123 | /// 124 | /// This data is from the `/proc/net/dev` file. 125 | /// 126 | /// For an example, see the [interface_stats.rs](https://github.com/eminence/procfs/tree/master/examples) 127 | /// example in the source repo. 128 | /// 129 | /// Note that this returns information from the networking namespace of the 130 | /// current process. If you want information for some otherr process, see 131 | /// [Process::dev_status()](crate::process::Process::dev_status()) 132 | pub fn dev_status() -> ProcResult> { 133 | InterfaceDeviceStatus::current().map(|e| e.0) 134 | } 135 | 136 | impl super::Current for RouteEntries { 137 | const PATH: &'static str = "/proc/net/route"; 138 | } 139 | 140 | /// Reads the ipv4 route table 141 | /// 142 | /// This data is from the `/proc/net/route` file 143 | /// 144 | /// Note that this returns information from the networking namespace of the 145 | /// current process. If you want information for some other process, see 146 | /// [Process::route()](crate::process::Process::route()) 147 | pub fn route() -> ProcResult> { 148 | RouteEntries::current().map(|r| r.0) 149 | } 150 | 151 | impl super::Current for Snmp { 152 | const PATH: &'static str = "/proc/net/snmp"; 153 | } 154 | 155 | /// Reads the network management information by Simple Network Management Protocol 156 | /// 157 | /// This data is from the `/proc/net/snmp` file and for IPv4 Protocol 158 | /// 159 | /// Note that this returns information from the networking namespace of the 160 | /// current process. If you want information for some other process, see 161 | /// [Process::snmp()](crate::process::Process::snmp()) 162 | pub fn snmp() -> ProcResult { 163 | Snmp::current() 164 | } 165 | 166 | impl super::Current for Snmp6 { 167 | const PATH: &'static str = "/proc/net/snmp6"; 168 | } 169 | 170 | /// Reads the network management information of IPv6 by Simple Network Management Protocol 171 | /// 172 | /// This data is from the `/proc/net/snmp6` file and for IPv6 Protocol 173 | /// 174 | /// Note that this returns information from the networking namespace of the 175 | /// current process. If you want information for some other process, see 176 | /// [Process::snmp6()](crate::process::Process::snmp6()) 177 | pub fn snmp6() -> ProcResult { 178 | Snmp6::current() 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use super::*; 184 | 185 | #[test] 186 | fn test_tcp() { 187 | for entry in tcp().unwrap() { 188 | println!("{:?}", entry); 189 | assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap()); 190 | } 191 | } 192 | 193 | #[test] 194 | fn test_tcp6() { 195 | for entry in tcp6().unwrap() { 196 | println!("{:?}", entry); 197 | assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap()); 198 | } 199 | } 200 | 201 | #[test] 202 | fn test_udp() { 203 | for entry in udp().unwrap() { 204 | println!("{:?}", entry); 205 | assert_eq!(entry.state, UdpState::from_u8(entry.state.to_u8()).unwrap()); 206 | } 207 | } 208 | 209 | #[test] 210 | fn test_udp6() { 211 | for entry in udp6().unwrap() { 212 | println!("{:?}", entry); 213 | } 214 | } 215 | 216 | #[test] 217 | fn test_unix() { 218 | for entry in unix().unwrap() { 219 | println!("{:?}", entry); 220 | } 221 | } 222 | 223 | #[test] 224 | fn test_dev_status() { 225 | let status = dev_status().unwrap(); 226 | println!("{:#?}", status); 227 | } 228 | 229 | #[test] 230 | fn test_arp() { 231 | for entry in arp().unwrap() { 232 | println!("{:?}", entry); 233 | } 234 | } 235 | 236 | #[test] 237 | fn test_route() { 238 | for entry in route().unwrap() { 239 | println!("{:?}", entry); 240 | } 241 | } 242 | 243 | #[test] 244 | fn test_snmp() { 245 | let snmp = snmp().unwrap(); 246 | println!("{:?}", snmp); 247 | } 248 | 249 | #[test] 250 | fn test_snmp6() { 251 | let snmp6 = snmp6().unwrap(); 252 | println!("{:?}", snmp6); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /procfs/src/sys/fs/binfmt_misc.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use std::path::Path; 3 | 4 | use crate::{build_internal_error, from_str, read_value, ProcResult}; 5 | 6 | /// Returns true if the miscellaneous Binary Formats system is enabled. 7 | pub fn enabled() -> ProcResult { 8 | let val: String = read_value("/proc/sys/fs/binfmt_misc/status")?; 9 | Ok(val == "enabled") 10 | } 11 | 12 | fn hex_to_vec(hex: &str) -> ProcResult> { 13 | if hex.len() % 2 != 0 { 14 | return Err(build_internal_error!(format!( 15 | "Hex string {:?} has non-even length", 16 | hex 17 | ))); 18 | } 19 | let mut idx = 0; 20 | let mut data = Vec::new(); 21 | while idx < hex.len() { 22 | let byte = from_str!(u8, &hex[idx..idx + 2], 16); 23 | data.push(byte); 24 | idx += 2; 25 | } 26 | 27 | Ok(data) 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub enum BinFmtData { 32 | /// A BinFmt entry based on a file extension (does not include the period) 33 | Extension(String), 34 | /// A BinFmt entry based on magic string matching 35 | Magic { offset: u8, magic: Vec, mask: Vec }, 36 | } 37 | 38 | /// A registered binary format entry 39 | /// 40 | /// For more info, see the kernel doc Documentation/admin-guide/binfmt-misc.rst 41 | #[derive(Debug, Clone)] 42 | pub struct BinFmtEntry { 43 | /// The name of the entry 44 | /// 45 | /// Corresponds to a file in /proc/sys/fs/binfmt_misc/ 46 | pub name: String, 47 | /// Is the entry enabled or not 48 | pub enabled: bool, 49 | /// Full path to the interpreter to run this entry 50 | pub interpreter: String, 51 | /// 52 | pub flags: BinFmtFlags, 53 | pub data: BinFmtData, 54 | } 55 | 56 | impl BinFmtEntry { 57 | pub(crate) fn from_string(name: String, data: &str) -> ProcResult { 58 | let mut enabled = false; 59 | let mut interpreter = String::new(); 60 | 61 | let mut ext = None; 62 | 63 | let mut offset = 0; 64 | let mut magic = Vec::new(); 65 | let mut mask = Vec::new(); 66 | let mut flags = BinFmtFlags::empty(); 67 | 68 | for line in data.lines() { 69 | if line == "enabled" { 70 | enabled = true; 71 | } else if let Some(stripped) = line.strip_prefix("interpreter ") { 72 | interpreter = stripped.to_string(); 73 | } else if let Some(stripped) = line.strip_prefix("flags:") { 74 | flags = BinFmtFlags::from_str(stripped); 75 | } else if let Some(stripped) = line.strip_prefix("extension .") { 76 | ext = Some(stripped.to_string()); 77 | } else if let Some(stripped) = line.strip_prefix("offset ") { 78 | offset = from_str!(u8, stripped); 79 | } else if let Some(stripped) = line.strip_prefix("magic ") { 80 | let hex = stripped; 81 | magic = hex_to_vec(dbg!(hex))?; 82 | } else if let Some(stripped) = line.strip_prefix("mask ") { 83 | let hex = stripped; 84 | mask = hex_to_vec(hex)?; 85 | } 86 | } 87 | 88 | if !magic.is_empty() && mask.is_empty() { 89 | mask.resize(magic.len(), 0xff); 90 | } 91 | 92 | Ok(BinFmtEntry { 93 | name, 94 | enabled, 95 | interpreter, 96 | flags, 97 | data: if let Some(ext) = ext { 98 | BinFmtData::Extension(ext) 99 | } else { 100 | BinFmtData::Magic { magic, mask, offset } 101 | }, 102 | }) 103 | } 104 | } 105 | 106 | bitflags! { 107 | /// Various key flags 108 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] 109 | pub struct BinFmtFlags: u8 { 110 | /// Preserve Argv[0] 111 | /// 112 | /// Legacy behavior of binfmt_misc is to overwrite the original argv[0] with the full path to the binary. When 113 | /// this flag is included, binfmt_misc will add an argument to the argument vector for this purpose, thus 114 | /// preserving the original `argv[0]`. 115 | /// 116 | /// For example, If your interp is set to `/bin/foo` and you run `blah` (which is in `/usr/local/bin`), 117 | /// then the kernel will execute `/bin/foo` with `argv[]` set to `["/bin/foo", "/usr/local/bin/blah", "blah"]`. 118 | /// 119 | /// The interp has to be aware of this so it can execute `/usr/local/bin/blah` with `argv[]` set to `["blah"]`. 120 | const P = 0x01; 121 | 122 | /// Open Binary 123 | /// 124 | /// Legacy behavior of binfmt_misc is to pass the full path 125 | /// of the binary to the interpreter as an argument. When this flag is 126 | /// included, binfmt_misc will open the file for reading and pass its 127 | /// descriptor as an argument, instead of the full path, thus allowing 128 | /// the interpreter to execute non-readable binaries. This feature 129 | /// should be used with care - the interpreter has to be trusted not to 130 | //// emit the contents of the non-readable binary. 131 | const O = 0x02; 132 | 133 | /// Credentials 134 | /// 135 | /// Currently, the behavior of binfmt_misc is to calculate 136 | /// the credentials and security token of the new process according to 137 | /// the interpreter. When this flag is included, these attributes are 138 | /// calculated according to the binary. It also implies the `O` flag. 139 | /// This feature should be used with care as the interpreter 140 | /// will run with root permissions when a setuid binary owned by root 141 | /// is run with binfmt_misc. 142 | const C = 0x04; 143 | 144 | /// Fix binary 145 | /// 146 | /// The usual behaviour of binfmt_misc is to spawn the 147 | /// binary lazily when the misc format file is invoked. However, 148 | /// this doesn't work very well in the face of mount namespaces and 149 | /// changeroots, so the `F` mode opens the binary as soon as the 150 | /// emulation is installed and uses the opened image to spawn the 151 | /// emulator, meaning it is always available once installed, 152 | /// regardless of how the environment changes. 153 | const F = 0x08; 154 | } 155 | } 156 | 157 | impl BinFmtFlags { 158 | pub(crate) fn from_str(s: &str) -> Self { 159 | s.chars() 160 | .filter_map(|c| match c { 161 | 'P' => Some(BinFmtFlags::P), 162 | 'O' => Some(BinFmtFlags::O), 163 | 'C' => Some(BinFmtFlags::C), 164 | 'F' => Some(BinFmtFlags::F), 165 | _ => None, 166 | }) 167 | .fold(BinFmtFlags::empty(), |a, b| a | b) 168 | } 169 | } 170 | 171 | pub fn list() -> ProcResult> { 172 | let path = Path::new("/proc/sys/fs/binfmt_misc/"); 173 | 174 | let mut v = Vec::new(); 175 | 176 | for entry in wrap_io_error!(path, path.read_dir())? { 177 | let entry = entry?; 178 | if entry.file_name() == "status" || entry.file_name() == "register" { 179 | // these entries do not represent real entries 180 | continue; 181 | } 182 | 183 | let name = entry.file_name().to_string_lossy().to_string(); 184 | 185 | let data = std::fs::read_to_string(entry.path())?; 186 | 187 | v.push(BinFmtEntry::from_string(name, &data)?); 188 | } 189 | 190 | Ok(v) 191 | } 192 | 193 | #[cfg(test)] 194 | mod tests { 195 | use super::*; 196 | 197 | #[test] 198 | fn test_enabled() { 199 | match enabled() { 200 | Ok(_) => {} 201 | Err(crate::ProcError::NotFound(_)) => {} 202 | Err(e) => panic!("{}", e), 203 | } 204 | } 205 | 206 | #[test] 207 | fn parse_magic() { 208 | let mask = "7f454c460201010000000000000000000200f300"; 209 | 210 | let data = hex_to_vec(mask).unwrap(); 211 | println!("{:?}", data); 212 | assert_eq!(data.len(), 20); 213 | assert_eq!(data[0], 0x7f); 214 | assert_eq!(data[1], 0x45); 215 | 216 | assert!(hex_to_vec("a").is_err()); 217 | assert!(hex_to_vec("zz").is_err()); 218 | } 219 | 220 | #[test] 221 | fn flags_parsing() { 222 | assert!(BinFmtFlags::from_str("").is_empty()); 223 | 224 | let flags = BinFmtFlags::from_str("F"); 225 | assert_eq!(flags, BinFmtFlags::F); 226 | 227 | let flags = BinFmtFlags::from_str("OCF"); 228 | assert_eq!(flags, BinFmtFlags::F | BinFmtFlags::C | BinFmtFlags::O); 229 | } 230 | 231 | #[test] 232 | fn binfmt() { 233 | let data = r#"enabled 234 | interpreter /usr/bin/qemu-riscv64-static 235 | flags: OCF 236 | offset 12 237 | magic 7f454c460201010000000000000000000200f300 238 | mask ffffffffffffff00fffffffffffffffffeffffff"#; 239 | 240 | let entry = BinFmtEntry::from_string("test".to_owned(), data).unwrap(); 241 | println!("{:#?}", entry); 242 | assert_eq!(entry.flags, BinFmtFlags::F | BinFmtFlags::C | BinFmtFlags::O); 243 | assert!(entry.enabled); 244 | assert_eq!(entry.interpreter, "/usr/bin/qemu-riscv64-static"); 245 | if let BinFmtData::Magic { offset, magic, mask } = entry.data { 246 | assert_eq!(offset, 12); 247 | assert_eq!(magic.len(), mask.len()); 248 | assert_eq!( 249 | magic, 250 | vec![ 251 | 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 252 | 0x02, 0x00, 0xf3, 0x00 253 | ] 254 | ); 255 | } else { 256 | panic!("Unexpected data"); 257 | } 258 | 259 | let data = r#"enabled 260 | interpreter /bin/hello 261 | flags: 262 | extension .hello"#; 263 | let entry = BinFmtEntry::from_string("test".to_owned(), data).unwrap(); 264 | println!("{:#?}", entry); 265 | assert_eq!(entry.flags, BinFmtFlags::empty()); 266 | assert!(entry.enabled); 267 | assert_eq!(entry.interpreter, "/bin/hello"); 268 | if let BinFmtData::Extension(ext) = entry.data { 269 | assert_eq!(ext, "hello"); 270 | } else { 271 | panic!("Unexpected data"); 272 | } 273 | } 274 | 275 | #[test] 276 | fn live() { 277 | for entry in super::list().unwrap() { 278 | println!("{:?}", entry); 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /procfs/src/process/task.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use super::{FileWrapper, Io, Schedstat, Stat, Status, Syscall}; 5 | use crate::{ProcError, ProcResult}; 6 | use procfs_core::FromRead; 7 | use rustix::fd::{BorrowedFd, OwnedFd}; 8 | 9 | /// A task (aka Thread) inside of a [`Process`](crate::process::Process) 10 | /// 11 | /// Created by [`Process::tasks`](crate::process::Process::tasks), tasks in 12 | /// general are similar to Processes and should have mostly the same fields. 13 | #[derive(Debug)] 14 | pub struct Task { 15 | fd: OwnedFd, 16 | /// The ID of the process that this task belongs to 17 | pub pid: i32, 18 | /// The task ID 19 | pub tid: i32, 20 | /// Task root: `/proc//task/` 21 | pub(crate) root: PathBuf, 22 | } 23 | 24 | impl Task { 25 | /// Create a new `Task` inside of the process 26 | /// 27 | /// This API is designed to be ergonomic from inside of [`TasksIter`](super::TasksIter) 28 | pub(crate) fn from_process_at, Q: AsRef>( 29 | base: P, 30 | dirfd: BorrowedFd, 31 | path: Q, 32 | pid: i32, 33 | tid: i32, 34 | ) -> ProcResult { 35 | use rustix::fs::{Mode, OFlags}; 36 | 37 | let p = path.as_ref(); 38 | let root = base.as_ref().join(p); 39 | let fd = wrap_io_error!( 40 | root, 41 | rustix::fs::openat( 42 | dirfd, 43 | p, 44 | OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, 45 | Mode::empty() 46 | ) 47 | )?; 48 | 49 | Ok(Task { fd, pid, tid, root }) 50 | } 51 | 52 | /// Thread info from `/proc//task//stat` 53 | /// 54 | /// Many of the returned fields will be the same as the parent process, but some fields like `utime` and `stime` will be per-task 55 | pub fn stat(&self) -> ProcResult { 56 | self.read("stat") 57 | } 58 | 59 | /// Thread info from `/proc//task//status` 60 | /// 61 | /// Many of the returned fields will be the same as the parent process 62 | pub fn status(&self) -> ProcResult { 63 | self.read("status") 64 | } 65 | 66 | /// Thread IO info from `/proc//task//io` 67 | /// 68 | /// This data will be unique per task. 69 | pub fn io(&self) -> ProcResult { 70 | self.read("io") 71 | } 72 | 73 | /// Thread scheduler info from `/proc//task//schedstat` 74 | /// 75 | /// This data will be unique per task. 76 | pub fn schedstat(&self) -> ProcResult { 77 | self.read("schedstat") 78 | } 79 | 80 | /// Returns the status info from `/proc//task//syscall`. 81 | pub fn syscall(&self) -> ProcResult { 82 | self.read("syscall") 83 | } 84 | 85 | /// Thread children from `/proc//task//children` 86 | /// 87 | /// WARNING: 88 | /// This interface is not reliable unless all the child processes are stoppped or frozen. 89 | /// If a child task exits while the file is being read, non-exiting children may be omitted. 90 | /// See the procfs(5) man page for more information. 91 | /// 92 | /// This data will be unique per task. 93 | pub fn children(&self) -> ProcResult> { 94 | let mut buf = String::new(); 95 | let mut file = FileWrapper::open_at(&self.root, &self.fd, "children")?; 96 | file.read_to_string(&mut buf)?; 97 | buf.split_whitespace() 98 | .map(|child| { 99 | child 100 | .parse() 101 | .map_err(|_| ProcError::Other("Failed to parse task's child PIDs".to_string())) 102 | }) 103 | .collect() 104 | } 105 | 106 | /// Deliberately generate an IO error 107 | #[cfg(test)] 108 | pub(crate) fn generate_error(&self) -> ProcResult<()> { 109 | let _ = FileWrapper::open_at(&self.root, &self.fd, "does_not_exist")?; 110 | Ok(()) 111 | } 112 | 113 | /// Parse a file relative to the task proc structure. 114 | pub fn read(&self, path: P) -> ProcResult 115 | where 116 | P: AsRef, 117 | T: FromRead, 118 | { 119 | FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?) 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use crate::process::{Io, Syscall}; 126 | use crate::ProcError; 127 | use rustix; 128 | use std::process; 129 | use std::sync::{Arc, Barrier}; 130 | 131 | #[test] 132 | #[cfg(not(tarpaulin))] // this test is unstable under tarpaulin, and i'm yet sure why 133 | // When this test runs in CI, run it single-threaded 134 | fn test_task_runsinglethread() { 135 | use std::io::Read; 136 | 137 | let me = crate::process::Process::myself().unwrap(); 138 | 139 | let (work_barrier, w_a, w_b) = { 140 | let b = Arc::new(Barrier::new(3)); 141 | (b.clone(), b.clone(), b) 142 | }; 143 | let (done_barrier, d_a, d_b) = { 144 | let b = Arc::new(Barrier::new(3)); 145 | (b.clone(), b.clone(), b) 146 | }; 147 | 148 | let bytes_to_read = 2_000_000; 149 | 150 | // create a new task to do some work 151 | let join_a = std::thread::Builder::new() 152 | .name("one".to_owned()) 153 | .spawn(move || { 154 | let mut vec = Vec::new(); 155 | let zero = std::fs::File::open("/dev/zero").unwrap(); 156 | 157 | zero.take(bytes_to_read).read_to_end(&mut vec).unwrap(); 158 | assert_eq!(vec.len(), bytes_to_read as usize); 159 | 160 | // spin for about 52 ticks (utime accounting isn't perfectly accurate) 161 | let dur = std::time::Duration::from_millis(52 * (1000 / crate::ticks_per_second()) as u64); 162 | let start = std::time::Instant::now(); 163 | while start.elapsed() <= dur { 164 | // spin 165 | } 166 | 167 | w_a.wait(); 168 | d_a.wait() 169 | }) 170 | .unwrap(); 171 | 172 | // create a new task that does nothing 173 | let join_b = std::thread::Builder::new() 174 | .name("two".to_owned()) 175 | .spawn(move || { 176 | w_b.wait(); 177 | d_b.wait(); 178 | }) 179 | .unwrap(); 180 | 181 | work_barrier.wait(); 182 | 183 | let mut found_one = false; 184 | let mut found_two = false; 185 | let mut summed_io = Io { 186 | rchar: 0, 187 | wchar: 0, 188 | syscr: 0, 189 | syscw: 0, 190 | read_bytes: 0, 191 | write_bytes: 0, 192 | cancelled_write_bytes: 0, 193 | }; 194 | for task in me.tasks().unwrap() { 195 | let task = task.unwrap(); 196 | let stat = task.stat().unwrap(); 197 | let status = task.status().unwrap(); 198 | let io = task.io().unwrap(); 199 | let syscall = task.syscall().unwrap(); 200 | 201 | summed_io.rchar += io.rchar; 202 | summed_io.wchar += io.wchar; 203 | summed_io.syscr += io.syscr; 204 | summed_io.syscw += io.syscw; 205 | summed_io.read_bytes += io.read_bytes; 206 | summed_io.write_bytes += io.write_bytes; 207 | summed_io.cancelled_write_bytes += io.cancelled_write_bytes; 208 | 209 | if stat.comm == "one" && status.name == "one" { 210 | found_one = true; 211 | assert!(io.rchar >= bytes_to_read); 212 | assert!(stat.utime >= 50, "utime({}) too small", stat.utime); 213 | assert!(matches!(syscall, Syscall::Blocked { .. }), "{:?}", syscall); 214 | } 215 | if stat.comm == "two" && status.name == "two" { 216 | found_two = true; 217 | // The process might read miscellaneous things from procfs or 218 | // things like /sys/devices/system/cpu/online; allow some small 219 | // reads. 220 | assert!(io.rchar < bytes_to_read); 221 | assert_eq!(io.wchar, 0); 222 | assert_eq!(stat.utime, 0); 223 | assert!(matches!(syscall, Syscall::Blocked { .. }), "{:?}", syscall); 224 | } 225 | } 226 | 227 | let proc_io = me.io().unwrap(); 228 | // these should be mostly the same (though creating the IO struct in the above line will cause some IO to occur) 229 | println!("{:?}", summed_io); 230 | println!("{:?}", proc_io); 231 | 232 | // signal the threads to exit 233 | done_barrier.wait(); 234 | join_a.join().unwrap(); 235 | join_b.join().unwrap(); 236 | 237 | assert!(found_one); 238 | assert!(found_two); 239 | } 240 | 241 | #[test] 242 | fn test_task_children() { 243 | // Use tail -f /dev/null to create two infinite processes 244 | let mut command = process::Command::new("tail"); 245 | command.arg("-f").arg("/dev/null"); 246 | let (mut child1, mut child2) = (command.spawn().unwrap(), command.spawn().unwrap()); 247 | 248 | let tid = rustix::thread::gettid(); 249 | 250 | let children = crate::process::Process::myself() 251 | .unwrap() 252 | .task_from_tid(tid.as_raw_nonzero().get() as i32) 253 | .unwrap() 254 | .children() 255 | .unwrap(); 256 | 257 | assert!(children.contains(&child1.id())); 258 | assert!(children.contains(&child2.id())); 259 | 260 | child1.kill().unwrap(); 261 | child2.kill().unwrap(); 262 | } 263 | 264 | #[test] 265 | fn test_error_msg() { 266 | let myself = crate::process::Process::myself().unwrap(); 267 | // let mytask = myself.task_main_thread().unwrap(); 268 | for task in myself.tasks().unwrap() { 269 | let task = task.unwrap(); 270 | let err = task.generate_error().unwrap_err(); 271 | // make sure the contained path in the error is correct 272 | if let ProcError::NotFound(Some(p)) = err { 273 | assert!( 274 | p.display() 275 | .to_string() 276 | .ends_with(&format!("/task/{}/does_not_exist", task.tid)), 277 | "NotFound path({:?}) doesn't look right", 278 | p 279 | ); 280 | } else { 281 | panic!("Unexpected error from task.generate_error()"); 282 | } 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------