Skip to content
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ categories = ["os::unix-apis", "filesystem"]
license = "MIT OR Apache-2.0"
edition = "2018"
rust-version = "1.70"

[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)'] }
10 changes: 8 additions & 2 deletions procfs-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ rust-version.workspace = true

[features]
default = ["chrono"]
serde1 = ["serde", "bitflags/serde"]
serde1 = ["serde", "bitflags/serde", "serde_with"]

[dependencies]
backtrace = { version = "0.3", optional = true }
bitflags = { version = "2" }
chrono = { version = "0.4.20", optional = true, features = ["clock"], default-features = false }
chrono = { version = "0.4.20", optional = true, features = [
"clock",
], default-features = false }
hex = "0.4"
serde = { version = "1.0", features = ["derive"], optional = true }
serde_with = { version = "3.11", optional = true }

[dev-dependencies]
serde_json = { version = "1.0" }

[package.metadata.docs.rs]
all-features = true
Expand Down
7 changes: 7 additions & 0 deletions procfs-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ macro_rules! from_str {
/// ticks_per_second: 100,
/// page_size: 4096,
/// is_little_endian: true,
/// kernel_version: "6.11.0".parse().unwrap(),
/// };
///
/// let rss_bytes = stat.rss_bytes().with_system_info(&system_info);
Expand All @@ -255,6 +256,7 @@ pub trait SystemInfoInterface {
fn page_size(&self) -> u64;
/// Whether the system is little endian (true) or big endian (false).
fn is_little_endian(&self) -> bool;
fn kernel_version(&self) -> ProcResult<KernelVersion>;

#[cfg(feature = "chrono")]
fn boot_time(&self) -> ProcResult<chrono::DateTime<chrono::Local>> {
Expand All @@ -275,6 +277,7 @@ pub struct ExplicitSystemInfo {
pub ticks_per_second: u64,
pub page_size: u64,
pub is_little_endian: bool,
pub kernel_version: KernelVersion,
}

impl SystemInfoInterface for ExplicitSystemInfo {
Expand All @@ -293,6 +296,10 @@ impl SystemInfoInterface for ExplicitSystemInfo {
fn is_little_endian(&self) -> bool {
self.is_little_endian
}

fn kernel_version(&self) -> ProcResult<KernelVersion> {
Ok(self.kernel_version)
}
}

/// Values which can provide an output given the [SystemInfo].
Expand Down
65 changes: 62 additions & 3 deletions procfs-core/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
//!
//! This module corresponds to the `/proc/net` directory and contains various information about the
//! networking layer.
use crate::ProcResult;
use crate::{build_internal_error, expect, from_iter, from_str};
use std::collections::HashMap;

use crate::{KernelVersion, ProcResult};
use bitflags::bitflags;
use std::collections::HashMap;
use std::io::BufRead;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::{path::PathBuf, str::FromStr};
Expand Down Expand Up @@ -152,6 +151,7 @@ pub struct UdpNetEntry {
pub tx_queue: u32,
pub uid: u32,
pub inode: u64,
pub drops: u64,
}

/// An entry in the Unix socket table
Expand Down Expand Up @@ -296,6 +296,15 @@ impl super::FromBufReadSI for UdpNetEntries {
s.next(); // skip timeout
let inode = expect!(s.next(), "udp::inode");

let drops = match system_info.kernel_version() {
Ok(version) if version >= KernelVersion::new(2, 6, 27) => {
s.next(); // skip ref
s.next(); // skip pointer
expect!(s.next(), "udp::drops")
}
_ => "0", // Fallback if the kernel version does not support the drops column or the kernel version could not be read
};

vec.push(UdpNetEntry {
local_address: parse_addressport_str(local_address, system_info.is_little_endian())?,
remote_address: parse_addressport_str(rem_address, system_info.is_little_endian())?,
Expand All @@ -304,6 +313,7 @@ impl super::FromBufReadSI for UdpNetEntries {
state: expect!(UdpState::from_u8(from_str!(u8, state, 16))),
uid,
inode: from_str!(u64, inode),
drops: from_str!(u64, drops),
});
}

Expand Down Expand Up @@ -1728,4 +1738,53 @@ UdpLite: 0 0 0 0 0 0 0 0 0
let res = Snmp::from_read(r).unwrap();
println!("{res:?}");
}

#[test]
fn test_udp_drops_debian_kernel_version_greater_2_6_27() {
let expected_drops = 42;
let data = format!(
r#" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
1270: 00000000:0202 00000000:0000 07 00000000:00000000 00:00000000 00000000 104 0 3719665 2 00000000357eb7c3 {expected_drops}"#
);
let r = std::io::Cursor::new(data.as_bytes());
use crate::FromBufReadSI;
let entries = UdpNetEntries::from_buf_read(
r,
&crate::ExplicitSystemInfo {
boot_time_secs: 0,
ticks_per_second: 0,
page_size: 0,
is_little_endian: true,
kernel_version: "2.6.27".parse().unwrap(),
},
)
.unwrap();
let entry = entries.0.first().unwrap();

assert_eq!(entry.drops, expected_drops)
}

#[test]
fn test_udp_drops_debian_kernel_version_smaller_2_6_27() {
let data = format!(
r#" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
1270: 00000000:0202 00000000:0000 07 00000000:00000000 00:00000000 00000000 104 0 3719665"#
);
let r = std::io::Cursor::new(data.as_bytes());
use crate::FromBufReadSI;
let entries = UdpNetEntries::from_buf_read(
r,
&crate::ExplicitSystemInfo {
boot_time_secs: 0,
ticks_per_second: 0,
page_size: 0,
is_little_endian: true,
kernel_version: "2.6.26".parse().unwrap(),
},
)
.unwrap();
let entry = entries.0.first().unwrap();

assert_eq!(entry.drops, 0)
}
}
43 changes: 41 additions & 2 deletions procfs-core/src/sys/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
//! The files in this directory can be used to tune and monitor miscellaneous
//! and general things in the operation of the Linux kernel.

use std::cmp;
use std::collections::HashSet;
use std::str::FromStr;
use std::{cmp, fmt::Display};

#[cfg(feature = "serde1")]
use serde_with::{DeserializeFromStr, SerializeDisplay};

use bitflags::bitflags;

use crate::{ProcError, ProcResult};

/// Represents a kernel version, in major.minor.release version.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
#[cfg_attr(feature = "serde1", derive(SerializeDisplay, DeserializeFromStr))]
pub struct Version {
pub major: u8,
pub minor: u8,
Expand Down Expand Up @@ -99,6 +103,12 @@ impl cmp::PartialOrd for Version {
}
}

impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

/// Represents a kernel type
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Type {
Expand Down Expand Up @@ -425,4 +435,33 @@ mod tests {
let a = SemaphoreLimits::from_str("1 string 500 3200");
assert!(a.is_err() && a.err().unwrap() == "Failed to parse SEMMNS");
}

#[cfg(feature = "serde1")]
mod serde_kernel_version {
#[test]
fn should_serialize_kernel_version() {
let version = Version {
major: 42,
minor: 0,
patch: 1,
};
let version = serde_json::to_string(&version).unwrap();

// NOTE: The double quote is necessary because of the JSON format.
assert_eq!(r#""42.0.1""#, &version);
}

#[test]
fn should_deserialize_kernel_version() {
let expected = Version {
major: 21,
minor: 0,
patch: 2,
};
// NOTE: The double quote is necessary because of the JSON format.
let version: Version = serde_json::from_str(r#""21.0.2""#).unwrap();

assert_eq!(version, expected);
}
}
}
3 changes: 3 additions & 0 deletions procfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ rustdoc-args = ["--generate-link-to-definition"]
[[bench]]
name = "cpuinfo"
harness = false

[lints]
workspace = true
7 changes: 5 additions & 2 deletions procfs/benches/cpuinfo.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use procfs::CpuInfo;
use procfs::Current;

fn bench_cpuinfo(c: &mut Criterion) {
c.bench_function("CpuInfo::new", |b| b.iter(|| black_box(CpuInfo::new().unwrap())));
c.bench_function("CpuInfo::current", |b| {
b.iter(|| black_box(CpuInfo::current().unwrap()))
});

let cpuinfo = black_box(CpuInfo::new().unwrap());
let cpuinfo = black_box(CpuInfo::current().unwrap());
c.bench_function("CpuInfo::get_info", |b| b.iter(|| black_box(cpuinfo.get_info(0))));
c.bench_function("CpuInfo::model_name", |b| b.iter(|| cpuinfo.model_name(0)));
c.bench_function("CpuInfo::vendor_id", |b| b.iter(|| cpuinfo.vendor_id(0)));
Expand Down
5 changes: 4 additions & 1 deletion procfs/examples/lslocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use std::path::Path;
fn main() {
let myself = Process::myself().unwrap();
let mountinfo = myself.mountinfo().unwrap();
println!("{:18}{:13}{:13}{:13}{:12} Path", "Process", "PID", "Lock Type", "Mode", "Kind");
println!(
"{:18}{:13}{:13}{:13}{:12} Path",
"Process", "PID", "Lock Type", "Mode", "Kind"
);
println!("{}", "=".repeat(74));
for lock in procfs::locks().unwrap() {
lock.pid
Expand Down
4 changes: 2 additions & 2 deletions procfs/examples/mounts.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// List mountpoints listed in /proc/mounts

fn main() {
let width = 15;
let width = 15;
for mount_entry in procfs::mounts().unwrap() {
println!("Device: {}", mount_entry.fs_spec);
println!("{:>width$}: {}", "Mount point", mount_entry.fs_file);
println!("{:>width$}: {}","FS type", mount_entry.fs_vfstype);
println!("{:>width$}: {}", "FS type", mount_entry.fs_vfstype);
println!("{:>width$}: {}", "Dump", mount_entry.fs_freq);
println!("{:>width$}: {}", "Check", mount_entry.fs_passno);
print!("{:>width$}: ", "Options");
Expand Down
6 changes: 2 additions & 4 deletions procfs/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

use procfs_core::ProcResult;
pub use procfs_core::CryptoTable;
use procfs_core::ProcResult;

use crate::Current;

Expand All @@ -12,7 +11,6 @@ pub fn crypto() -> ProcResult<CryptoTable> {
CryptoTable::current()
}


#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -23,4 +21,4 @@ mod tests {
let table = table.expect("CrytoTable should have been read");
assert!(!table.crypto_blocks.is_empty(), "Crypto table was empty");
}
}
}
4 changes: 4 additions & 0 deletions procfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ impl SystemInfoInterface for LocalSystemInfo {
fn is_little_endian(&self) -> bool {
u16::from_ne_bytes([0, 1]).to_le_bytes() == [0, 1]
}

fn kernel_version(&self) -> ProcResult<procfs_core::KernelVersion> {
KernelVersion::cached().map(Into::into)
}
}

const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo;
Expand Down
8 changes: 7 additions & 1 deletion procfs/src/sys/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Version {
Self {
major,
minor,
patch: u16::from_ne_bytes([lo, hi])
patch: u16::from_ne_bytes([lo, hi]),
}
}

Expand Down Expand Up @@ -145,6 +145,12 @@ impl cmp::PartialOrd for Version {
}
}

impl From<Version> for procfs_core::KernelVersion {
fn from(value: Version) -> Self {
Self::new(value.major, value.minor, value.patch)
}
}

/// Represents a kernel type
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Type {
Expand Down
2 changes: 1 addition & 1 deletion procfs/src/sys/kernel/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{read_value, write_value, ProcError, ProcResult};
use std::path::Path;

const RANDOM_ROOT: &str = "/proc/sys/kernel/random";
const RANDOM_ROOT: &str = "/proc/sys/kernel/random";

/// This read-only file gives the available entropy, in bits. This will be a number in the range
/// 0 to 4096
Expand Down
Loading