Type-erase KernelVersion::current error

pull/643/head
Tamir Duberstein 1 year ago
parent 6bceb1c3da
commit a1e0130387
No known key found for this signature in database

@ -1,11 +1,12 @@
//! Utility functions. //! Utility functions.
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
error::Error,
ffi::{CStr, CString}, ffi::{CStr, CString},
fs::{self, File}, fs::{self, File},
io::{self, BufRead, BufReader}, io::{self, BufRead, BufReader},
mem, slice, mem, slice,
str::FromStr, str::{FromStr, Utf8Error},
}; };
use crate::{ use crate::{
@ -24,6 +25,20 @@ pub struct KernelVersion {
pub(crate) patch: u16, 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.
#[error("failed to read kernel version")]
IOError(#[from] io::Error),
/// The kernel version string could not be parsed.
#[error("failed to parse kernel version")]
ParseError(#[from] text_io::Error),
/// The kernel version string was not valid UTF-8.
#[error("kernel version string is not valid UTF-8")]
Utf8Error(#[from] Utf8Error),
}
impl KernelVersion { impl KernelVersion {
/// Constructor. /// Constructor.
pub fn new(major: u8, minor: u8, patch: u16) -> Self { pub fn new(major: u8, minor: u8, patch: u16) -> Self {
@ -35,7 +50,7 @@ impl KernelVersion {
} }
/// Returns the kernel version of the currently running kernel. /// Returns the kernel version of the currently running kernel.
pub fn current() -> Result<Self, String> { pub fn current() -> Result<Self, impl Error> {
let kernel_version = Self::get_kernel_version(); let kernel_version = Self::get_kernel_version();
// The kernel version is clamped to 4.19.255 on kernels 4.19.222 and above. // The kernel version is clamped to 4.19.255 on kernels 4.19.222 and above.
@ -57,7 +72,7 @@ impl KernelVersion {
// This is ported from https://github.com/torvalds/linux/blob/3f01e9f/tools/lib/bpf/libbpf_probes.c#L21-L101. // This is ported from https://github.com/torvalds/linux/blob/3f01e9f/tools/lib/bpf/libbpf_probes.c#L21-L101.
fn get_ubuntu_kernel_version() -> Result<Option<Self>, String> { fn get_ubuntu_kernel_version() -> Result<Option<Self>, CurrentKernelVersionError> {
const UBUNTU_KVER_FILE: &str = "/proc/version_signature"; const UBUNTU_KVER_FILE: &str = "/proc/version_signature";
let s = match fs::read(UBUNTU_KVER_FILE) { let s = match fs::read(UBUNTU_KVER_FILE) {
Ok(s) => s, Ok(s) => s,
@ -65,10 +80,9 @@ impl KernelVersion {
if e.kind() == io::ErrorKind::NotFound { if e.kind() == io::ErrorKind::NotFound {
return Ok(None); return Ok(None);
} }
return Err(format!("failed to read {}: {}", UBUNTU_KVER_FILE, e)); return Err(e.into());
} }
}; };
(|| {
let ubuntu: String; let ubuntu: String;
let ubuntu_version: String; let ubuntu_version: String;
let major: u8; let major: u8;
@ -76,43 +90,36 @@ impl KernelVersion {
let patch: u16; let patch: u16;
text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch); text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch);
Ok(Some(Self::new(major, minor, patch))) Ok(Some(Self::new(major, minor, patch)))
})().map_err(|e: text_io::Error| format!("failed to parse {:?}: {}", s, e))
} }
fn get_debian_kernel_version(info: &utsname) -> Result<Option<Self>, String> { fn get_debian_kernel_version(
info: &utsname,
) -> Result<Option<Self>, CurrentKernelVersionError> {
// Safety: man 2 uname: // Safety: man 2 uname:
// //
// The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are
// terminated by a null byte ('\0'). // terminated by a null byte ('\0').
let p = unsafe { CStr::from_ptr(info.version.as_ptr()) }; let p = unsafe { CStr::from_ptr(info.version.as_ptr()) };
let p = p let p = p.to_str()?;
.to_str()
.map_err(|e| format!("failed to parse version: {}", e))?;
let p = match p.split_once("Debian ") { let p = match p.split_once("Debian ") {
Some((_prefix, suffix)) => suffix, Some((_prefix, suffix)) => suffix,
None => return Ok(None), None => return Ok(None),
}; };
(|| {
let major: u8; let major: u8;
let minor: u8; let minor: u8;
let patch: u16; let patch: u16;
text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch);
Ok(Some(Self::new(major, minor, patch))) Ok(Some(Self::new(major, minor, patch)))
})()
.map_err(|e: text_io::Error| format!("failed to parse {}: {}", p, e))
} }
fn get_kernel_version() -> Result<Self, String> { fn get_kernel_version() -> Result<Self, CurrentKernelVersionError> {
if let Some(v) = Self::get_ubuntu_kernel_version()? { if let Some(v) = Self::get_ubuntu_kernel_version()? {
return Ok(v); return Ok(v);
} }
let mut info = unsafe { mem::zeroed::<utsname>() }; let mut info = unsafe { mem::zeroed::<utsname>() };
if unsafe { uname(&mut info) } != 0 { if unsafe { uname(&mut info) } != 0 {
return Err(format!( return Err(io::Error::last_os_error().into());
"failed to get kernel version: {}",
io::Error::last_os_error()
));
} }
if let Some(v) = Self::get_debian_kernel_version(&info)? { if let Some(v) = Self::get_debian_kernel_version(&info)? {
@ -124,22 +131,17 @@ impl KernelVersion {
// The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are
// terminated by a null byte ('\0'). // terminated by a null byte ('\0').
let p = unsafe { CStr::from_ptr(info.release.as_ptr()) }; let p = unsafe { CStr::from_ptr(info.release.as_ptr()) };
let p = p let p = p.to_str()?;
.to_str()
.map_err(|e| format!("failed to parse release: {}", e))?;
// Unlike sscanf, text_io::try_scan! does not stop at the first non-matching character. // 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()) { let p = match p.split_once(|c: char| c != '.' && !c.is_ascii_digit()) {
Some((prefix, _suffix)) => prefix, Some((prefix, _suffix)) => prefix,
None => p, None => p,
}; };
(|| {
let major: u8; let major: u8;
let minor: u8; let minor: u8;
let patch: u16; let patch: u16;
text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch);
Ok(Self::new(major, minor, patch)) Ok(Self::new(major, minor, patch))
})()
.map_err(|e: text_io::Error| format!("failed to parse {}: {}", p, e))
} }
} }

Loading…
Cancel
Save