use std::fmt; pub const MINIMUM_KERNEL_MAJOR: u64 = 6; pub const MINIMUM_KERNEL_MINOR: u64 = 9; #[derive(Clone, Debug, Eq, PartialEq)] pub struct PlatformObservation<'a> { pub operating_system: &'a str, pub architecture: &'a str, pub kernel_release: &'a str, } #[derive(Clone, Debug, Eq, PartialEq)] pub enum PlatformRefusal { OperatingSystem(String), Architecture(String), KernelRelease(String), KernelFloor { major: u64, minor: u64 }, } impl fmt::Display for PlatformRefusal { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::OperatingSystem(value) => { write!(formatter, "unsupported operating system {value}") } Self::Architecture(value) => write!(formatter, "unsupported architecture {value}"), Self::KernelRelease(value) => write!(formatter, "invalid Linux kernel release {value}"), Self::KernelFloor { major, minor } => write!( formatter, "Linux kernel {major}.{minor} is below the required 6.9 floor" ), } } } impl std::error::Error for PlatformRefusal {} /// # Errors /// Refuses unsupported operating systems, architectures, and kernel releases. pub fn check_platform(observation: &PlatformObservation<'_>) -> Result<(), PlatformRefusal> { if observation.operating_system != "linux" { return Err(PlatformRefusal::OperatingSystem( observation.operating_system.into(), )); } if observation.architecture != "x86_64" { return Err(PlatformRefusal::Architecture( observation.architecture.into(), )); } let (major, minor) = parse_kernel_release(observation.kernel_release)?; if (major, minor) < (MINIMUM_KERNEL_MAJOR, MINIMUM_KERNEL_MINOR) { return Err(PlatformRefusal::KernelFloor { major, minor }); } Ok(()) } /// # Errors /// Refuses releases without numeric major and minor components. pub fn parse_kernel_release(release: &str) -> Result<(u64, u64), PlatformRefusal> { let mut components = release.split('.'); let major = components .next() .and_then(|value| value.parse().ok()) .ok_or_else(|| PlatformRefusal::KernelRelease(release.into()))?; let minor = components .next() .and_then(|value| { value .split_once('-') .map_or(value, |(number, _)| number) .parse() .ok() }) .ok_or_else(|| PlatformRefusal::KernelRelease(release.into()))?; Ok((major, minor)) } #[cfg(test)] mod tests { use super::{PlatformObservation, PlatformRefusal, check_platform}; #[test] fn accepts_floor_and_newer_linux_x86_64() { for release in ["6.9.0", "6.9.12-custom", "6.10.0", "7.0"] { check_platform(&PlatformObservation { operating_system: "linux", architecture: "x86_64", kernel_release: release, }) .unwrap(); } } #[test] fn rejects_older_kernel_and_other_platforms() { let old = check_platform(&PlatformObservation { operating_system: "linux", architecture: "x86_64", kernel_release: "6.8.14", }); assert_eq!( old, Err(PlatformRefusal::KernelFloor { major: 6, minor: 8 }) ); for observation in [ PlatformObservation { operating_system: "freebsd", architecture: "x86_64", kernel_release: "14.0", }, PlatformObservation { operating_system: "linux", architecture: "aarch64", kernel_release: "6.9.0", }, ] { assert!(check_platform(&observation).is_err()); } } }