use std::cmp;
use std::collections::HashSet;
use std::str::FromStr;
use bitflags::bitflags;
use crate::{ProcError, ProcResult};
#[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 }
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Self, &'static str> {
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;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<cmp::Ordering> {
Some(self.cmp(other))
}
}
#[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;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Type::new(s.to_string()))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct BuildInfo {
pub version: String,
pub flags: HashSet<String>,
pub extra: String,
}
impl BuildInfo {
pub fn new(version: &str, flags: HashSet<String>, extra: String) -> BuildInfo {
BuildInfo {
version: version.to_string(),
flags,
extra,
}
}
pub fn smp(&self) -> bool {
self.flags.contains("SMP")
}
pub fn preempt(&self) -> bool {
self.flags.contains("PREEMPT")
}
pub fn preemptrt(&self) -> bool {
self.flags.contains("PREEMPTRT")
}
pub fn version_number(&self) -> ProcResult<u32> {
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)
}
#[cfg(feature = "chrono")]
pub fn extra_date(&self) -> ProcResult<chrono::DateTime<chrono::Local>> {
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;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut version = String::new();
let mut flags: HashSet<String> = 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)]
pub struct SemaphoreLimits {
pub semmsl: u64,
pub semmns: u64,
pub semopm: u64,
pub semmni: u64,
}
impl SemaphoreLimits {
fn from_str(s: &str) -> Result<Self, &'static str> {
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<Self, Self::Err> {
SemaphoreLimits::from_str(s)
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct AllowedFunctions : u16 {
const ENABLE_CONTROL_LOG_LEVEL = 2;
const ENABLE_CONTROL_KEYBOARD = 4;
const ENABLE_DEBUGGING_DUMPS = 8;
const ENABLE_SYNC_COMMAND = 16;
const ENABLE_REMOUNT_READ_ONLY = 32;
const ENABLE_SIGNALING_PROCESSES = 64;
const ALLOW_REBOOT_POWEROFF = 128;
const ALLOW_NICING_REAL_TIME_TASKS = 256;
}
}
#[derive(Copy, Clone, Debug)]
pub enum SysRq {
Disable,
Enable,
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<Self> {
match s.parse::<u16>()? {
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<Self, Self::Err> {
SysRq::from_str(s)
}
}
pub const THREADS_MIN: u32 = 20;
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() {
let a = BuildInfo::from_str("#1 SMP PREEMPT Thu Sep 30 15:29:01 UTC 2021").unwrap();
let mut flags: HashSet<String> = 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();
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();
let c = BuildInfo::from_str("#1 SMP Debian 5.10.46-4 (2021-08-03)").unwrap();
let mut flags: HashSet<String> = 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());
}
#[test]
fn test_semaphore_limits() {
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");
}
}