From 35ed85a87ff467c7091c8749e48b8475cd1af592 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 14 Jul 2023 10:50:03 -0400 Subject: [PATCH] Handle WSL kernel version strings Fixes #654. --- aya/Cargo.toml | 1 - aya/src/util.rs | 84 ++++++++++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 75980470..d2a39a77 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -24,7 +24,6 @@ object = { version = "0.31", default-features = false, features = [ "std", ] } parking_lot = { version = "0.12.0", features = ["send_guard"] } -text_io = "0.1.12" thiserror = "1" tokio = { version = "1.24.0", features = ["rt"], optional = true } diff --git a/aya/src/util.rs b/aya/src/util.rs index fb1026af..2e6e01ec 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -25,18 +25,14 @@ pub struct KernelVersion { pub(crate) patch: u16, } -/// An error encountered while fetching the current kernel version. #[derive(thiserror::Error, Debug)] -pub enum CurrentKernelVersionError { - /// The kernel version string could not be read. +enum CurrentKernelVersionError { #[error("failed to read kernel version")] - IOError(#[from] io::Error), - /// The kernel version string could not be parsed. + IO(#[from] io::Error), #[error("failed to parse kernel version")] - ParseError(#[from] text_io::Error), - /// The kernel version string was not valid UTF-8. + ParseError(String), #[error("kernel version string is not valid UTF-8")] - Utf8Error(#[from] Utf8Error), + Utf8(#[from] Utf8Error), } impl KernelVersion { @@ -74,7 +70,7 @@ impl KernelVersion { fn get_ubuntu_kernel_version() -> Result, CurrentKernelVersionError> { const UBUNTU_KVER_FILE: &str = "/proc/version_signature"; - let s = match fs::read(UBUNTU_KVER_FILE) { + let s = match fs::read_to_string(UBUNTU_KVER_FILE) { Ok(s) => s, Err(e) => { if e.kind() == io::ErrorKind::NotFound { @@ -83,13 +79,16 @@ impl KernelVersion { return Err(e.into()); } }; - let ubuntu: String; - let ubuntu_version: String; - let major: u8; - let minor: u8; - let patch: u16; - text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch); - Ok(Some(Self::new(major, minor, patch))) + let mut parts = s.split_terminator(char::is_whitespace); + let mut next = || { + parts + .next() + .ok_or_else(|| CurrentKernelVersionError::ParseError(s.to_string())) + }; + let _ubuntu: &str = next()?; + let _ubuntu_version: &str = next()?; + let kernel_version_string = next()?; + Self::parse_kernel_version_string(kernel_version_string).map(Some) } fn get_debian_kernel_version( @@ -99,17 +98,13 @@ impl KernelVersion { // // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // terminated by a null byte ('\0'). - let p = unsafe { CStr::from_ptr(info.version.as_ptr()) }; - let p = p.to_str()?; - let p = match p.split_once("Debian ") { + let s = unsafe { CStr::from_ptr(info.version.as_ptr()) }; + let s = s.to_str()?; + let kernel_version_string = match s.split_once("Debian ") { Some((_prefix, suffix)) => suffix, None => return Ok(None), }; - let major: u8; - let minor: u8; - let patch: u16; - text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); - Ok(Some(Self::new(major, minor, patch))) + Self::parse_kernel_version_string(kernel_version_string).map(Some) } fn get_kernel_version() -> Result { @@ -130,17 +125,23 @@ impl KernelVersion { // // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // terminated by a null byte ('\0'). - let p = unsafe { CStr::from_ptr(info.release.as_ptr()) }; - let p = p.to_str()?; - // Unlike sscanf, text_io::try_scan! does not stop at the first non-matching character. - let p = match p.split_once(|c: char| c != '.' && !c.is_ascii_digit()) { - Some((prefix, _suffix)) => prefix, - None => p, - }; - let major: u8; - let minor: u8; - let patch: u16; - text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); + let s = unsafe { CStr::from_ptr(info.release.as_ptr()) }; + let s = s.to_str()?; + Self::parse_kernel_version_string(s) + } + + fn parse_kernel_version_string(s: &str) -> Result { + fn parse>(s: Option<&str>) -> Option { + match s.map(str::parse).transpose() { + Ok(option) => option, + Err(std::num::ParseIntError { .. }) => None, + } + } + let error = || CurrentKernelVersionError::ParseError(s.to_string()); + let mut parts = s.split(|c: char| c == '.' || !c.is_ascii_digit()); + let major = parse(parts.next()).ok_or_else(error)?; + let minor = parse(parts.next()).ok_or_else(error)?; + let patch = parse(parts.next()).ok_or_else(error)?; Ok(Self::new(major, minor, patch)) } } @@ -331,6 +332,19 @@ pub(crate) fn bytes_of_slice(val: &[T]) -> &[u8] { #[cfg(test)] mod tests { use super::*; + use assert_matches::assert_matches; + + #[test] + fn test_parse_kernel_version_string() { + // WSL. + assert_matches!(KernelVersion::parse_kernel_version_string("5.15.90.1-microsoft-standard-WSL2"), Ok(kernel_version) => { + assert_eq!(kernel_version, KernelVersion::new(5, 15, 90)) + }); + // uname -r on Fedora. + assert_matches!(KernelVersion::parse_kernel_version_string("6.3.11-200.fc38.x86_64"), Ok(kernel_version) => { + assert_eq!(kernel_version, KernelVersion::new(6, 3, 11)) + }); + } #[test] fn test_parse_online_cpus() {