Skip to content
Open
100 changes: 98 additions & 2 deletions crates/osutils/src/uname.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,107 @@ pub fn kernel_release() -> Result<String, Error> {
.context("Failed to run uname -r")
}

/// Parsed kernel version with major and minor components.
///
/// Implements `Ord` so callers can compare against feature thresholds
/// (e.g., `kv >= KernelVersion { major: 6, minor: 7 }`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct KernelVersion {
pub major: u32,
pub minor: u32,
}

impl KernelVersion {
/// Parse a kernel version from a `uname -r` string.
///
/// Extracts the leading `major.minor` from strings like:
/// - `6.6.78.2-1.cm2`
/// - `6.7.0-1.cm2`
Comment thread
bfjelds marked this conversation as resolved.
/// - `7.0.0`
///
/// Returns `None` if the string cannot be parsed.
pub fn parse(release: &str) -> Option<Self> {
// Trim surrounding whitespace/newlines (uname output is not trimmed),
// strip everything after the first '-' (e.g. "-1.cm2"), then split on '.'.
let numeric_part = release.trim().split('-').next()?;
let mut parts = numeric_part.split('.');
let major = parts.next()?.parse::<u32>().ok()?;
let minor = parts.next()?.parse::<u32>().ok()?;
Some(KernelVersion { major, minor })
}

/// Returns the kernel version of the running system.
///
/// Returns `Err` if the `uname` command fails to execute, or `Ok(None)`
/// if the output cannot be parsed into a major.minor version.
pub fn running() -> Result<Option<Self>, Error> {
let release = kernel_release()?;
Ok(Self::parse(&release))
}
}

#[cfg(test)]
mod tests {
use crate::uname;
use super::*;

#[test]
fn test_kernel_release() {
uname::kernel_release().unwrap();
kernel_release().unwrap();
}

#[test]
fn test_parse_azl_kernel() {
let v = KernelVersion::parse("6.6.78.2-1.cm2").unwrap();
assert_eq!(v, KernelVersion { major: 6, minor: 6 });
assert!(v < KernelVersion { major: 6, minor: 7 });
}

#[test]
fn test_parse_67_kernel() {
let v = KernelVersion::parse("6.7.0-1.cm2").unwrap();
assert_eq!(v, KernelVersion { major: 6, minor: 7 });
assert!(v >= KernelVersion { major: 6, minor: 7 });
}

#[test]
fn test_parse_major_7() {
let v = KernelVersion::parse("7.0.0").unwrap();
assert_eq!(v, KernelVersion { major: 7, minor: 0 });
assert!(v >= KernelVersion { major: 6, minor: 7 });
}

#[test]
fn test_parse_simple() {
let v = KernelVersion::parse("5.15").unwrap();
assert_eq!(
v,
KernelVersion {
major: 5,
minor: 15
}
);
assert!(v < KernelVersion { major: 6, minor: 7 });
}

#[test]
fn test_parse_garbage() {
assert!(KernelVersion::parse("not-a-version").is_none());
assert!(KernelVersion::parse("").is_none());
assert!(KernelVersion::parse("6").is_none());
}

#[test]
fn test_parse_trailing_newline() {
// uname output is not trimmed, so parse must tolerate trailing whitespace.
let v = KernelVersion::parse("5.15\n").unwrap();
assert_eq!(
v,
KernelVersion {
major: 5,
minor: 15
}
);
let v = KernelVersion::parse("6.7.0-1.cm2\n").unwrap();
assert_eq!(v, KernelVersion { major: 6, minor: 7 });
}
}
Loading
Loading