#![allow(unknown_lints)]
#![allow(clippy::from_str_radix_10)]
#![deny(rustdoc::broken_intra_doc_links, rustdoc::invalid_html_tags)]
pub use procfs_core::*;
use bitflags::bitflags;
use rustix::fd::AsFd;
use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::{self, BufReader, Read, Seek};
use std::path::{Path, PathBuf};
use std::str::FromStr;
#[cfg(feature = "chrono")]
use chrono::DateTime;
const PROC_CONFIG_GZ: &str = "/proc/config.gz";
const BOOT_CONFIG: &str = "/boot/config";
pub trait Current: FromRead {
const PATH: &'static str;
fn current() -> ProcResult<Self> {
Self::from_file(Self::PATH)
}
}
pub struct LocalSystemInfo;
impl SystemInfoInterface for LocalSystemInfo {
fn boot_time_secs(&self) -> ProcResult<u64> {
crate::boot_time_secs()
}
fn ticks_per_second(&self) -> u64 {
crate::ticks_per_second()
}
fn page_size(&self) -> u64 {
crate::page_size()
}
fn is_little_endian(&self) -> bool {
u16::from_ne_bytes([0, 1]).to_le_bytes() == [0, 1]
}
}
const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo;
pub fn current_system_info() -> &'static SystemInfo {
&LOCAL_SYSTEM_INFO
}
pub trait CurrentSI: FromReadSI {
const PATH: &'static str;
fn current() -> ProcResult<Self> {
Self::current_with_system_info(current_system_info())
}
fn current_with_system_info(si: &SystemInfo) -> ProcResult<Self> {
Self::from_file(Self::PATH, si)
}
}
pub trait WithCurrentSystemInfo<'a>: WithSystemInfo<'a> + Sized {
fn get(self) -> Self::Output {
self.with_system_info(current_system_info())
}
}
impl<'a, T: WithSystemInfo<'a>> WithCurrentSystemInfo<'a> for T {}
pub mod prelude {
pub use super::{Current, CurrentSI, WithCurrentSystemInfo};
pub use procfs_core::prelude::*;
}
macro_rules! wrap_io_error {
($path:expr, $expr:expr) => {
match $expr {
Ok(v) => Ok(v),
Err(e) => {
let kind = e.kind();
Err(::std::io::Error::new(
kind,
crate::IoErrorWrapper {
path: $path.to_owned(),
inner: e.into(),
},
))
}
}
};
}
pub(crate) fn read_file<P: AsRef<Path>>(path: P) -> ProcResult<String> {
std::fs::read_to_string(path.as_ref())
.map_err(|e| e.into())
.error_path(path.as_ref())
}
pub(crate) fn write_file<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> {
std::fs::write(path.as_ref(), buf)
.map_err(|e| e.into())
.error_path(path.as_ref())
}
pub(crate) fn read_value<P, T, E>(path: P) -> ProcResult<T>
where
P: AsRef<Path>,
T: FromStr<Err = E>,
ProcError: From<E>,
{
read_file(path).and_then(|s| s.trim().parse().map_err(ProcError::from))
}
pub(crate) fn write_value<P: AsRef<Path>, T: fmt::Display>(path: P, value: T) -> ProcResult<()> {
write_file(path, value.to_string().as_bytes())
}
mod cgroups;
pub use crate::cgroups::*;
mod crypto;
pub use crate::crypto::*;
pub mod keyring;
mod iomem;
pub use iomem::*;
mod kpageflags;
pub use kpageflags::*;
mod kpagecount;
pub use kpagecount::*;
pub mod net;
pub mod process;
pub mod sys;
pub use crate::sys::kernel::BuildInfo as KernelBuildInfo;
pub use crate::sys::kernel::Type as KernelType;
pub use crate::sys::kernel::Version as KernelVersion;
struct FileWrapper {
inner: File,
path: PathBuf,
}
impl FileWrapper {
fn open<P: AsRef<Path>>(path: P) -> Result<FileWrapper, io::Error> {
let p = path.as_ref();
let f = wrap_io_error!(p, File::open(p))?;
Ok(FileWrapper {
inner: f,
path: p.to_owned(),
})
}
fn open_at<P, Q, Fd: AsFd>(root: P, dirfd: Fd, path: Q) -> Result<FileWrapper, io::Error>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
use rustix::fs::{Mode, OFlags};
let p = root.as_ref().join(path.as_ref());
let fd = wrap_io_error!(
p,
rustix::fs::openat(dirfd, path.as_ref(), OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty())
)?;
Ok(FileWrapper {
inner: File::from(fd),
path: p,
})
}
fn inner(self) -> File {
self.inner
}
}
impl Read for FileWrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read(buf))
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read_to_end(buf))
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read_to_string(buf))
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
wrap_io_error!(self.path, self.inner.read_exact(buf))
}
}
impl Seek for FileWrapper {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
wrap_io_error!(self.path, self.inner.seek(pos))
}
}
pub fn ticks_per_second() -> u64 {
rustix::param::clock_ticks_per_second()
}
#[cfg(feature = "chrono")]
pub fn boot_time() -> ProcResult<DateTime<chrono::Local>> {
use chrono::TimeZone;
let secs = boot_time_secs()?;
let date_time = expect!(chrono::Local.timestamp_opt(secs as i64, 0).single());
Ok(date_time)
}
#[cfg_attr(
not(feature = "chrono"),
doc = "If you compile with the optional `chrono` feature, you can use the `boot_time()` method to get the boot time as a `DateTime` object."
)]
#[cfg_attr(
feature = "chrono",
doc = "See also [boot_time()] to get the boot time as a `DateTime`"
)]
pub fn boot_time_secs() -> ProcResult<u64> {
BOOT_TIME.with(|x| {
let mut btime = x.borrow_mut();
if let Some(btime) = *btime {
Ok(btime)
} else {
let stat = KernelStats::current()?;
*btime = Some(stat.btime);
Ok(stat.btime)
}
})
}
thread_local! {
static BOOT_TIME : std::cell::RefCell<Option<u64>> = std::cell::RefCell::new(None);
}
pub fn page_size() -> u64 {
rustix::param::page_size() as u64
}
impl Current for LoadAverage {
const PATH: &'static str = "/proc/loadavg";
}
impl Current for KernelConfig {
const PATH: &'static str = PROC_CONFIG_GZ;
#[cfg_attr(feature = "flate2", doc = "The flate2 feature is currently enabled")]
#[cfg_attr(not(feature = "flate2"), doc = "The flate2 feature is NOT currently enabled")]
fn current() -> ProcResult<Self> {
let reader: Box<dyn Read> = if Path::new(PROC_CONFIG_GZ).exists() && cfg!(feature = "flate2") {
#[cfg(feature = "flate2")]
{
let file = FileWrapper::open(PROC_CONFIG_GZ)?;
let decoder = flate2::read::GzDecoder::new(file);
Box::new(decoder)
}
#[cfg(not(feature = "flate2"))]
{
unreachable!("flate2 feature not enabled")
}
} else {
let kernel = rustix::system::uname();
let filename = format!("{}-{}", BOOT_CONFIG, kernel.release().to_string_lossy());
match FileWrapper::open(filename) {
Ok(file) => Box::new(BufReader::new(file)),
Err(e) => match e.kind() {
io::ErrorKind::NotFound => {
let file = FileWrapper::open(BOOT_CONFIG)?;
Box::new(file)
}
_ => return Err(e.into()),
},
}
};
Self::from_read(reader)
}
}
pub fn kernel_config() -> ProcResult<HashMap<String, ConfigSetting>> {
KernelConfig::current().map(|c| c.0)
}
impl CurrentSI for KernelStats {
const PATH: &'static str = "/proc/stat";
}
impl Current for VmStat {
const PATH: &'static str = "/proc/vmstat";
}
pub fn vmstat() -> ProcResult<HashMap<String, i64>> {
VmStat::current().map(|s| s.0)
}
impl Current for KernelModules {
const PATH: &'static str = "/proc/modules";
}
pub fn modules() -> ProcResult<HashMap<String, KernelModule>> {
KernelModules::current().map(|m| m.0)
}
impl Current for KernelCmdline {
const PATH: &'static str = "/proc/cmdline";
}
pub fn cmdline() -> ProcResult<Vec<String>> {
KernelCmdline::current().map(|c| c.0)
}
impl Current for CpuInfo {
const PATH: &'static str = "/proc/cpuinfo";
}
impl Current for Devices {
const PATH: &'static str = "/proc/devices";
}
impl Current for DiskStats {
const PATH: &'static str = "/proc/diskstats";
}
pub fn diskstats() -> ProcResult<Vec<DiskStat>> {
DiskStats::current().map(|d| d.0)
}
impl Current for Vec<MountEntry> {
const PATH: &'static str = "/proc/mounts";
}
pub fn mounts() -> ProcResult<Vec<MountEntry>> {
Vec::<MountEntry>::current()
}
impl Current for Vec<PartitionEntry> {
const PATH: &'static str = "/proc/partitions";
}
pub fn partitions() -> ProcResult<Vec<PartitionEntry>> {
Vec::<PartitionEntry>::current()
}
impl Current for Locks {
const PATH: &'static str = "/proc/locks";
}
pub fn locks() -> ProcResult<Vec<Lock>> {
Locks::current().map(|l| l.0)
}
impl Current for Meminfo {
const PATH: &'static str = "/proc/meminfo";
}
impl Current for CpuPressure {
const PATH: &'static str = "/proc/pressure/cpu";
}
impl Current for MemoryPressure {
const PATH: &'static str = "/proc/pressure/memory";
}
impl Current for IoPressure {
const PATH: &'static str = "/proc/pressure/io";
}
impl Current for SharedMemorySegments {
const PATH: &'static str = "/proc/sysvipc/shm";
}
impl Current for Uptime {
const PATH: &'static str = "/proc/uptime";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_statics() {
println!("{:?}", crate::sys::kernel::Version::cached());
}
#[test]
fn test_loadavg() {
let load = LoadAverage::current().unwrap();
println!("{:?}", load);
}
#[test]
fn test_kernel_config() {
match std::env::var("TRAVIS") {
Ok(ref s) if s == "true" => return,
_ => {}
}
if !Path::new(PROC_CONFIG_GZ).exists() && !Path::new(BOOT_CONFIG).exists() {
return;
}
let config = KernelConfig::current().unwrap();
println!("{:#?}", config);
}
#[test]
fn test_file_io_errors() {
fn inner<P: AsRef<Path>>(p: P) -> Result<(), ProcError> {
let mut file = FileWrapper::open(p)?;
let mut buf = [0; 128];
file.read_exact(&mut buf[0..128])?;
Ok(())
}
let err = inner("/this_should_not_exist").unwrap_err();
println!("{}", err);
match err {
ProcError::NotFound(Some(p)) => {
assert_eq!(p, Path::new("/this_should_not_exist"));
}
x => panic!("Unexpected return value: {:?}", x),
}
match inner("/proc/loadavg") {
Err(ProcError::Io(_, Some(p))) => {
assert_eq!(p, Path::new("/proc/loadavg"));
}
x => panic!("Unexpected return value: {:?}", x),
}
}
#[test]
fn test_kernel_stat() {
let stat = KernelStats::current().unwrap();
println!("{:#?}", stat);
let boottime = boot_time_secs().unwrap();
let diff = (boottime as i32 - stat.btime as i32).abs();
assert!(diff <= 1);
let cpuinfo = CpuInfo::current().unwrap();
assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len());
let user: u64 = stat.cpu_time.iter().map(|i| i.user).sum();
let nice: u64 = stat.cpu_time.iter().map(|i| i.nice).sum();
let system: u64 = stat.cpu_time.iter().map(|i| i.system).sum();
assert!(
(stat.total.user as i64 - user as i64).abs() < 6000,
"sum:{} total:{} diff:{}",
stat.total.user,
user,
stat.total.user - user
);
assert!(
(stat.total.nice as i64 - nice as i64).abs() < 6000,
"sum:{} total:{} diff:{}",
stat.total.nice,
nice,
stat.total.nice - nice
);
assert!(
(stat.total.system as i64 - system as i64).abs() < 6000,
"sum:{} total:{} diff:{}",
stat.total.system,
system,
stat.total.system - system
);
let diff = stat.total.idle as i64 - (stat.cpu_time.iter().map(|i| i.idle).sum::<u64>() as i64).abs();
assert!(diff < 1000, "idle time difference too high: {}", diff);
}
#[test]
fn test_vmstat() {
let stat = VmStat::current().unwrap();
println!("{:?}", stat);
}
#[test]
fn test_modules() {
let KernelModules(mods) = KernelModules::current().unwrap();
for module in mods.values() {
println!("{:?}", module);
}
}
#[test]
fn tests_tps() {
let tps = ticks_per_second();
println!("{} ticks per second", tps);
}
#[test]
fn test_cmdline() {
let KernelCmdline(cmdline) = KernelCmdline::current().unwrap();
for argument in cmdline {
println!("{}", argument);
}
}
#[test]
fn test_failure() {
fn inner() -> Result<(), failure::Error> {
let _load = crate::LoadAverage::current()?;
Ok(())
}
let _ = inner();
fn inner2() -> Result<(), failure::Error> {
let proc = crate::process::Process::new(1)?;
let _io = proc.maps()?;
Ok(())
}
let _ = inner2();
}
#[test]
fn test_esrch() {
let mut command = std::process::Command::new("sleep")
.arg("10000")
.spawn()
.expect("Failed to start sleep");
let p = crate::process::Process::new(command.id() as i32).expect("Failed to create Process");
command.kill().expect("Failed to kill sleep");
command.wait().expect("Failed to wait for sleep");
let e = p.stat().unwrap_err();
println!("{:?}", e);
assert!(matches!(e, ProcError::NotFound(_)));
}
#[test]
fn test_cpuinfo() {
let info = CpuInfo::current().unwrap();
println!("{:#?}", info.flags(0));
for num in 0..info.num_cores() {
info.model_name(num).unwrap();
info.vendor_id(num).unwrap();
info.physical_id(num);
}
}
#[test]
fn test_devices() {
let devices = Devices::current().unwrap();
println!("{:#?}", devices);
}
#[test]
fn test_diskstats() {
for disk in super::diskstats().unwrap() {
println!("{:?}", disk);
}
}
#[test]
fn test_locks() {
for lock in locks().unwrap() {
println!("{:?}", lock);
if let LockType::Other(s) = lock.lock_type {
panic!("Found an unknown lock type {:?}", s);
}
if let LockKind::Other(s) = lock.kind {
panic!("Found an unknown lock kind {:?}", s);
}
if let LockMode::Other(s) = lock.mode {
panic!("Found an unknown lock mode {:?}", s);
}
}
}
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::blocks_in_if_conditions)]
#[test]
fn test_meminfo() {
match ::std::env::var("TRAVIS") {
Ok(ref s) if s == "true" => return,
_ => {}
}
let kernel = KernelVersion::current().unwrap();
let config = KernelConfig::current().ok();
let meminfo = Meminfo::current().unwrap();
println!("{:#?}", meminfo);
if kernel >= KernelVersion::new(3, 14, 0) {
assert!(meminfo.mem_available.is_some());
}
if kernel >= KernelVersion::new(2, 6, 28) {
assert!(meminfo.active_anon.is_some());
assert!(meminfo.inactive_anon.is_some());
assert!(meminfo.active_file.is_some());
assert!(meminfo.inactive_file.is_some());
} else {
assert!(meminfo.active_anon.is_none());
assert!(meminfo.inactive_anon.is_none());
assert!(meminfo.active_file.is_none());
assert!(meminfo.inactive_file.is_none());
}
if kernel >= KernelVersion::new(2, 6, 28)
&& kernel <= KernelVersion::new(2, 6, 30)
&& meminfo.unevictable.is_some()
{
if let Some(KernelConfig(ref config)) = config {
assert!(config.get("CONFIG_UNEVICTABLE_LRU").is_some());
}
}
if kernel >= KernelVersion::new(2, 6, 19)
&& config
.as_ref()
.map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HIGHMEM"))
{
assert!(meminfo.high_total.is_some());
assert!(meminfo.high_free.is_some());
assert!(meminfo.low_total.is_some());
assert!(meminfo.low_free.is_some());
} else {
assert!(meminfo.high_total.is_none());
assert!(meminfo.high_free.is_none());
assert!(meminfo.low_total.is_none());
assert!(meminfo.low_free.is_none());
}
if kernel >= KernelVersion::new(2, 6, 18) {
assert!(meminfo.anon_pages.is_some());
assert!(meminfo.page_tables.is_some());
assert!(meminfo.nfs_unstable.is_some());
assert!(meminfo.bounce.is_some());
} else {
assert!(meminfo.anon_pages.is_none());
assert!(meminfo.page_tables.is_none());
assert!(meminfo.nfs_unstable.is_none());
assert!(meminfo.bounce.is_none());
}
if kernel >= KernelVersion::new(2, 6, 32) {
assert!(meminfo.shmem.is_some());
assert!(meminfo.kernel_stack.is_some());
} else {
assert!(meminfo.shmem.is_none());
assert!(meminfo.kernel_stack.is_none());
}
if kernel >= KernelVersion::new(2, 6, 19) {
assert!(meminfo.s_reclaimable.is_some());
assert!(meminfo.s_unreclaim.is_some());
} else {
assert!(meminfo.s_reclaimable.is_none());
assert!(meminfo.s_unreclaim.is_none());
}
if kernel >= KernelVersion::new(2, 6, 27)
&& config
.as_ref()
.map_or(false, |KernelConfig(cfg)| cfg.contains_key("CONFIG_QUICKLIST"))
{
assert!(meminfo.quicklists.is_some());
} else {
assert!(meminfo.quicklists.is_none());
}
if kernel >= KernelVersion::new(2, 6, 26) {
assert!(meminfo.writeback_tmp.is_some());
} else {
assert!(meminfo.writeback_tmp.is_none());
}
if kernel >= KernelVersion::new(2, 6, 10) {
assert!(meminfo.commit_limit.is_some());
} else {
assert!(meminfo.commit_limit.is_none());
}
if kernel >= KernelVersion::new(2, 6, 32)
&& config.as_ref().map_or(
std::path::Path::new("/proc/kpagecgroup").exists(),
|KernelConfig(cfg)| cfg.contains_key("CONFIG_MEMORY_FAILURE"),
)
{
assert!(meminfo.hardware_corrupted.is_some());
} else {
assert!(meminfo.hardware_corrupted.is_none());
}
if kernel >= KernelVersion::new(2, 6, 38)
&& config.as_ref().map_or(false, |KernelConfig(cfg)| {
cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")
})
{
assert!(meminfo.anon_hugepages.is_some());
} else {
}
if kernel >= KernelVersion::new(4, 8, 0)
&& config.as_ref().map_or(true, |KernelConfig(cfg)| {
cfg.contains_key("CONFIG_TRANSPARENT_HUGEPAGE")
})
{
assert!(meminfo.shmem_hugepages.is_some());
assert!(meminfo.shmem_pmd_mapped.is_some());
} else {
assert!(meminfo.shmem_hugepages.is_none());
assert!(meminfo.shmem_pmd_mapped.is_none());
}
if kernel >= KernelVersion::new(3, 1, 0)
&& config
.as_ref()
.map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_CMA"))
{
assert!(meminfo.cma_total.is_some());
assert!(meminfo.cma_free.is_some());
} else {
assert!(meminfo.cma_total.is_none());
assert!(meminfo.cma_free.is_none());
}
if config
.as_ref()
.map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE"))
{
assert!(meminfo.hugepages_total.is_some());
assert!(meminfo.hugepages_free.is_some());
assert!(meminfo.hugepagesize.is_some());
} else {
assert!(meminfo.hugepages_total.is_none());
assert!(meminfo.hugepages_free.is_none());
assert!(meminfo.hugepagesize.is_none());
}
if kernel >= KernelVersion::new(2, 6, 17)
&& config
.as_ref()
.map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE"))
{
assert!(meminfo.hugepages_rsvd.is_some());
} else {
assert!(meminfo.hugepages_rsvd.is_none());
}
if kernel >= KernelVersion::new(2, 6, 24)
&& config
.as_ref()
.map_or(true, |KernelConfig(cfg)| cfg.contains_key("CONFIG_HUGETLB_PAGE"))
{
assert!(meminfo.hugepages_surp.is_some());
} else {
assert!(meminfo.hugepages_surp.is_none());
}
}
#[allow(clippy::manual_range_contains)]
fn valid_percentage(value: f32) -> bool {
value >= 0.00 && value < 100.0
}
#[test]
fn test_mem_pressure() {
if !Path::new("/proc/pressure/memory").exists() {
return;
}
let mem_psi = MemoryPressure::current().unwrap();
assert!(valid_percentage(mem_psi.some.avg10));
assert!(valid_percentage(mem_psi.some.avg60));
assert!(valid_percentage(mem_psi.some.avg300));
assert!(valid_percentage(mem_psi.full.avg10));
assert!(valid_percentage(mem_psi.full.avg60));
assert!(valid_percentage(mem_psi.full.avg300));
}
#[test]
fn test_io_pressure() {
if !Path::new("/proc/pressure/io").exists() {
return;
}
let io_psi = IoPressure::current().unwrap();
assert!(valid_percentage(io_psi.some.avg10));
assert!(valid_percentage(io_psi.some.avg60));
assert!(valid_percentage(io_psi.some.avg300));
assert!(valid_percentage(io_psi.full.avg10));
assert!(valid_percentage(io_psi.full.avg60));
assert!(valid_percentage(io_psi.full.avg300));
}
#[test]
fn test_cpu_pressure() {
if !Path::new("/proc/pressure/cpu").exists() {
return;
}
let cpu_psi = CpuPressure::current().unwrap();
assert!(valid_percentage(cpu_psi.some.avg10));
assert!(valid_percentage(cpu_psi.some.avg60));
assert!(valid_percentage(cpu_psi.some.avg300));
}
}