//! Global kernel info / tuning miscellaneous stuff //! //! The files in this directory can be used to tune and monitor miscellaneous //! and general things in the operation of the Linux kernel. use std::cmp; use std::collections::HashSet; use std::str::FromStr; use bitflags::bitflags; use crate::{ProcError, ProcResult}; /// Represents a kernel version, in major.minor.release version. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Version { pub major: u8, pub minor: u8, pub patch: u16, } impl Version { pub fn new(major: u8, minor: u8, patch: u16) -> Version { Version { major, minor, patch } } /// Parses a kernel version string, in major.minor.release syntax. /// /// Note that any extra information (stuff after a dash) is ignored. /// /// # Example /// /// ``` /// # use procfs_core::KernelVersion; /// let a = KernelVersion::from_str("3.16.0-6-amd64").unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); /// /// ``` #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Result { let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit()); let kernel = if let Some(pos) = pos { let (s, _) = s.split_at(pos); s } else { s }; let mut kernel_split = kernel.split('.'); let major = kernel_split.next().ok_or("Missing major version component")?; let minor = kernel_split.next().ok_or("Missing minor version component")?; let patch = kernel_split.next().ok_or("Missing patch version component")?; let major = major.parse().map_err(|_| "Failed to parse major version")?; let minor = minor.parse().map_err(|_| "Failed to parse minor version")?; let patch = patch.parse().map_err(|_| "Failed to parse patch version")?; Ok(Version { major, minor, patch }) } } impl FromStr for Version { type Err = &'static str; /// Parses a kernel version string, in major.minor.release syntax. /// /// Note that any extra information (stuff after a dash) is ignored. /// /// # Example /// /// ``` /// # use procfs_core::KernelVersion; /// let a: KernelVersion = "3.16.0-6-amd64".parse().unwrap(); /// let b = KernelVersion::new(3, 16, 0); /// assert_eq!(a, b); /// /// ``` fn from_str(s: &str) -> Result { Version::from_str(s) } } impl cmp::Ord for Version { fn cmp(&self, other: &Self) -> cmp::Ordering { match self.major.cmp(&other.major) { cmp::Ordering::Equal => match self.minor.cmp(&other.minor) { cmp::Ordering::Equal => self.patch.cmp(&other.patch), x => x, }, x => x, } } } impl cmp::PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Represents a kernel type #[derive(Debug, Clone, Eq, PartialEq)] pub struct Type { pub sysname: String, } impl Type { pub fn new(sysname: String) -> Type { Type { sysname } } } impl FromStr for Type { type Err = &'static str; /// Parse a kernel type string /// /// Notice that in Linux source code, it is defined as a single string. fn from_str(s: &str) -> Result { Ok(Type::new(s.to_string())) } } /// Represents a kernel build information #[derive(Debug, Clone, Eq, PartialEq)] pub struct BuildInfo { pub version: String, pub flags: HashSet, /// This field contains any extra data from the /proc/sys/kernel/version file. It generally contains the build date of the kernel, but the format of the date can vary. /// /// A method named `extra_date` is provided which would try to parse some date formats. When the date format is not supported, an error will be returned. It depends on chrono feature. pub extra: String, } impl BuildInfo { pub fn new(version: &str, flags: HashSet, extra: String) -> BuildInfo { BuildInfo { version: version.to_string(), flags, extra, } } /// Check if SMP is ON pub fn smp(&self) -> bool { self.flags.contains("SMP") } /// Check if PREEMPT is ON pub fn preempt(&self) -> bool { self.flags.contains("PREEMPT") } /// Check if PREEMPTRT is ON pub fn preemptrt(&self) -> bool { self.flags.contains("PREEMPTRT") } /// Return version number /// /// This would parse number from first digits of version string. For example, #21~1 to 21. pub fn version_number(&self) -> ProcResult { let mut version_str = String::new(); for c in self.version.chars() { if c.is_ascii_digit() { version_str.push(c); } else { break; } } let version_number: u32 = version_str.parse().map_err(|_| "Failed to parse version number")?; Ok(version_number) } /// Parse extra field to `DateTime` object /// /// This function may fail as TIMESTAMP can be various formats. #[cfg(feature = "chrono")] pub fn extra_date(&self) -> ProcResult> { if let Ok(dt) = chrono::DateTime::parse_from_str(&format!("{} +0000", &self.extra), "%a %b %d %H:%M:%S UTC %Y %z") { return Ok(dt.with_timezone(&chrono::Local)); } if let Ok(dt) = chrono::DateTime::parse_from_str(&self.extra, "%a, %d %b %Y %H:%M:%S %z") { return Ok(dt.with_timezone(&chrono::Local)); } Err(ProcError::Other("Failed to parse extra field to date".to_string())) } } impl FromStr for BuildInfo { type Err = &'static str; /// Parse a kernel build information string fn from_str(s: &str) -> Result { let mut version = String::new(); let mut flags: HashSet = HashSet::new(); let mut extra: String = String::new(); let mut splited = s.split(' '); let version_str = splited.next(); if let Some(version_str) = version_str { if let Some(stripped) = version_str.strip_prefix('#') { version.push_str(stripped); } else { return Err("Failed to parse kernel build version"); } } else { return Err("Failed to parse kernel build version"); } for s in &mut splited { if s.chars().all(char::is_uppercase) { flags.insert(s.to_string()); } else { extra.push_str(s); extra.push(' '); break; } } let remains: Vec<&str> = splited.collect(); extra.push_str(&remains.join(" ")); Ok(BuildInfo { version, flags, extra }) } } #[derive(Debug, PartialEq, Eq, Copy, Clone)] /// Represents the data from `/proc/sys/kernel/sem` pub struct SemaphoreLimits { /// The maximum semaphores per semaphore set pub semmsl: u64, /// A system-wide limit on the number of semaphores in all semaphore sets pub semmns: u64, /// The maximum number of operations that may be specified in a semop(2) call pub semopm: u64, /// A system-wide limit on the maximum number of semaphore identifiers pub semmni: u64, } impl SemaphoreLimits { fn from_str(s: &str) -> Result { let mut s = s.split_ascii_whitespace(); let semmsl = s.next().ok_or("Missing SEMMSL")?; let semmns = s.next().ok_or("Missing SEMMNS")?; let semopm = s.next().ok_or("Missing SEMOPM")?; let semmni = s.next().ok_or("Missing SEMMNI")?; let semmsl = semmsl.parse().map_err(|_| "Failed to parse SEMMSL")?; let semmns = semmns.parse().map_err(|_| "Failed to parse SEMMNS")?; let semopm = semopm.parse().map_err(|_| "Failed to parse SEMOPM")?; let semmni = semmni.parse().map_err(|_| "Failed to parse SEMMNI")?; Ok(SemaphoreLimits { semmsl, semmns, semopm, semmni, }) } } impl FromStr for SemaphoreLimits { type Err = &'static str; fn from_str(s: &str) -> Result { SemaphoreLimits::from_str(s) } } bitflags! { /// Flags representing allowed sysrq functions #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct AllowedFunctions : u16 { /// Enable control of console log level const ENABLE_CONTROL_LOG_LEVEL = 2; /// Enable control of keyboard (SAK, unraw) const ENABLE_CONTROL_KEYBOARD = 4; /// Enable debugging dumps of processes etc const ENABLE_DEBUGGING_DUMPS = 8; /// Enable sync command const ENABLE_SYNC_COMMAND = 16; /// Enable remound read-only const ENABLE_REMOUNT_READ_ONLY = 32; /// Enable signaling of processes (term, kill, oom-kill) const ENABLE_SIGNALING_PROCESSES = 64; /// Allow reboot/poweroff const ALLOW_REBOOT_POWEROFF = 128; /// Allow nicing of all real-time tasks const ALLOW_NICING_REAL_TIME_TASKS = 256; } } /// Values controlling functions allowed to be invoked by the SysRq key #[derive(Copy, Clone, Debug)] pub enum SysRq { /// Disable sysrq completely Disable, /// Enable all functions of sysrq Enable, /// Bitmask of allowed sysrq functions AllowedFunctions(AllowedFunctions), } impl SysRq { pub fn to_number(self) -> u16 { match self { SysRq::Disable => 0, SysRq::Enable => 1, SysRq::AllowedFunctions(allowed) => allowed.bits(), } } fn from_str(s: &str) -> ProcResult { match s.parse::()? { 0 => Ok(SysRq::Disable), 1 => Ok(SysRq::Enable), x => match AllowedFunctions::from_bits(x) { Some(allowed) => Ok(SysRq::AllowedFunctions(allowed)), None => Err("Invalid value".into()), }, } } } impl FromStr for SysRq { type Err = ProcError; fn from_str(s: &str) -> Result { SysRq::from_str(s) } } /// The minimum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later pub const THREADS_MIN: u32 = 20; /// The maximum value that can be written to `/proc/sys/kernel/threads-max` on Linux 4.1 or later pub const THREADS_MAX: u32 = 0x3fff_ffff; #[cfg(test)] mod tests { use super::*; #[test] fn test_version() { let a = Version::from_str("3.16.0-6-amd64").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); let a = Version::from_str("3.16.0").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); let a = Version::from_str("3.16.0_1").unwrap(); let b = Version::new(3, 16, 0); assert_eq!(a, b); } #[test] fn test_type() { let a = Type::from_str("Linux").unwrap(); assert_eq!(a.sysname, "Linux"); } #[test] fn test_build_info() { // For Ubuntu, Manjaro, CentOS and others: let a = BuildInfo::from_str("#1 SMP PREEMPT Thu Sep 30 15:29:01 UTC 2021").unwrap(); let mut flags: HashSet = HashSet::new(); flags.insert("SMP".to_string()); flags.insert("PREEMPT".to_string()); assert_eq!(a.version, "1"); assert_eq!(a.version_number().unwrap(), 1); assert_eq!(a.flags, flags); assert!(a.smp()); assert!(a.preempt()); assert!(!a.preemptrt()); assert_eq!(a.extra, "Thu Sep 30 15:29:01 UTC 2021"); #[cfg(feature = "chrono")] let _ = a.extra_date().unwrap(); // For Arch and others: let b = BuildInfo::from_str("#1 SMP PREEMPT Fri, 12 Nov 2021 19:22:10 +0000").unwrap(); assert_eq!(b.version, "1"); assert_eq!(b.version_number().unwrap(), 1); assert_eq!(b.flags, flags); assert_eq!(b.extra, "Fri, 12 Nov 2021 19:22:10 +0000"); assert!(b.smp()); assert!(b.preempt()); assert!(!b.preemptrt()); #[cfg(feature = "chrono")] let _ = b.extra_date().unwrap(); // For Debian and others: let c = BuildInfo::from_str("#1 SMP Debian 5.10.46-4 (2021-08-03)").unwrap(); let mut flags: HashSet = HashSet::new(); flags.insert("SMP".to_string()); assert_eq!(c.version, "1"); assert_eq!(c.version_number().unwrap(), 1); assert_eq!(c.flags, flags); assert_eq!(c.extra, "Debian 5.10.46-4 (2021-08-03)"); assert!(c.smp()); assert!(!c.preempt()); assert!(!c.preemptrt()); // Skip the date parsing for now } #[test] fn test_semaphore_limits() { // Note that the below string has tab characters in it. Make sure to not remove them. let a = SemaphoreLimits::from_str("32000 1024000000 500 32000").unwrap(); let b = SemaphoreLimits { semmsl: 32_000, semmns: 1_024_000_000, semopm: 500, semmni: 32_000, }; assert_eq!(a, b); let a = SemaphoreLimits::from_str("1"); assert!(a.is_err() && a.err().unwrap() == "Missing SEMMNS"); let a = SemaphoreLimits::from_str("1 string 500 3200"); assert!(a.is_err() && a.err().unwrap() == "Failed to parse SEMMNS"); } }