diff --git a/Cargo.toml b/Cargo.toml index d8964f09..86c3b746 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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)'] } diff --git a/procfs-core/Cargo.toml b/procfs-core/Cargo.toml index fdb5f061..49dc11db 100644 --- a/procfs-core/Cargo.toml +++ b/procfs-core/Cargo.toml @@ -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 diff --git a/procfs-core/src/lib.rs b/procfs-core/src/lib.rs index 34c77cf3..9d6c1f46 100644 --- a/procfs-core/src/lib.rs +++ b/procfs-core/src/lib.rs @@ -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); @@ -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; #[cfg(feature = "chrono")] fn boot_time(&self) -> ProcResult> { @@ -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 { @@ -293,6 +296,10 @@ impl SystemInfoInterface for ExplicitSystemInfo { fn is_little_endian(&self) -> bool { self.is_little_endian } + + fn kernel_version(&self) -> ProcResult { + Ok(self.kernel_version) + } } /// Values which can provide an output given the [SystemInfo]. diff --git a/procfs-core/src/net.rs b/procfs-core/src/net.rs index 7689d1b6..7fd87d23 100644 --- a/procfs-core/src/net.rs +++ b/procfs-core/src/net.rs @@ -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}; @@ -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 @@ -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())?, @@ -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), }); } @@ -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) + } } diff --git a/procfs-core/src/sys/kernel/mod.rs b/procfs-core/src/sys/kernel/mod.rs index 7d1b2493..88c85a97 100644 --- a/procfs-core/src/sys/kernel/mod.rs +++ b/procfs-core/src/sys/kernel/mod.rs @@ -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, @@ -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 { @@ -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); + } + } } diff --git a/procfs/Cargo.toml b/procfs/Cargo.toml index bdf26301..af76550e 100644 --- a/procfs/Cargo.toml +++ b/procfs/Cargo.toml @@ -39,3 +39,6 @@ rustdoc-args = ["--generate-link-to-definition"] [[bench]] name = "cpuinfo" harness = false + +[lints] +workspace = true diff --git a/procfs/benches/cpuinfo.rs b/procfs/benches/cpuinfo.rs index c1ff02b3..2c094d84 100644 --- a/procfs/benches/cpuinfo.rs +++ b/procfs/benches/cpuinfo.rs @@ -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))); diff --git a/procfs/examples/lslocks.rs b/procfs/examples/lslocks.rs index 622ec06c..ecf43c1c 100644 --- a/procfs/examples/lslocks.rs +++ b/procfs/examples/lslocks.rs @@ -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 diff --git a/procfs/examples/mounts.rs b/procfs/examples/mounts.rs index 71cebccf..2fb31917 100644 --- a/procfs/examples/mounts.rs +++ b/procfs/examples/mounts.rs @@ -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"); diff --git a/procfs/src/crypto.rs b/procfs/src/crypto.rs index d552484d..6086d8fa 100644 --- a/procfs/src/crypto.rs +++ b/procfs/src/crypto.rs @@ -1,6 +1,5 @@ - -use procfs_core::ProcResult; pub use procfs_core::CryptoTable; +use procfs_core::ProcResult; use crate::Current; @@ -12,7 +11,6 @@ pub fn crypto() -> ProcResult { CryptoTable::current() } - #[cfg(test)] mod tests { use super::*; @@ -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"); } -} \ No newline at end of file +} diff --git a/procfs/src/lib.rs b/procfs/src/lib.rs index 05737be1..530a2b1b 100644 --- a/procfs/src/lib.rs +++ b/procfs/src/lib.rs @@ -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 { + KernelVersion::cached().map(Into::into) + } } const LOCAL_SYSTEM_INFO: LocalSystemInfo = LocalSystemInfo; diff --git a/procfs/src/sys/kernel/mod.rs b/procfs/src/sys/kernel/mod.rs index e069d5e6..ccf4f1be 100644 --- a/procfs/src/sys/kernel/mod.rs +++ b/procfs/src/sys/kernel/mod.rs @@ -66,7 +66,7 @@ impl Version { Self { major, minor, - patch: u16::from_ne_bytes([lo, hi]) + patch: u16::from_ne_bytes([lo, hi]), } } @@ -145,6 +145,12 @@ impl cmp::PartialOrd for Version { } } +impl From 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 { diff --git a/procfs/src/sys/kernel/random.rs b/procfs/src/sys/kernel/random.rs index 5c33a382..4f343cb8 100644 --- a/procfs/src/sys/kernel/random.rs +++ b/procfs/src/sys/kernel/random.rs @@ -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