Rewrite kernel version logic

This restores and enhances the logic originally added in #579.
pull/643/head
Tamir Duberstein 1 year ago
parent cc2bc0acc1
commit 6e570f0f14
No known key found for this signature in database

@ -24,6 +24,7 @@ object = { version = "0.31", default-features = false, features = [
"elf", "elf",
] } ] }
parking_lot = { version = "0.12.0", features = ["send_guard"] } parking_lot = { version = "0.12.0", features = ["send_guard"] }
text_io = "0.1.12"
thiserror = "1" thiserror = "1"
tokio = { version = "1.24.0", features = [ tokio = { version = "1.24.0", features = [
"macros", "macros",

@ -1,12 +1,10 @@
//! Utility functions. //! Utility functions.
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
ffi::CString, ffi::{CStr, CString},
fs::{self, File}, fs::{self, File},
io::{self, BufRead, BufReader}, io::{self, BufRead, BufReader},
mem, mem, slice,
num::ParseIntError,
slice,
str::FromStr, str::FromStr,
}; };
@ -15,7 +13,7 @@ use crate::{
Pod, Pod,
}; };
use libc::{if_nametoindex, sysconf, _SC_PAGESIZE}; use libc::{if_nametoindex, sysconf, uname, utsname, _SC_PAGESIZE};
/// Represents a kernel version, in major.minor.release version. /// Represents a kernel version, in major.minor.release version.
// Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html. // Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html.
@ -37,43 +35,111 @@ impl KernelVersion {
} }
/// Returns the kernel version of the currently running kernel. /// Returns the kernel version of the currently running kernel.
///
/// This is taken from `/proc/sys/kernel/osrelease`;
pub fn current() -> Result<Self, String> { pub fn current() -> Result<Self, String> {
let s = let kernel_version = Self::get_kernel_version();
fs::read_to_string("/proc/sys/kernel/osrelease").map_err(|err| format!("{err:?}"))?;
let s = s.as_str(); // The kernel version is clamped to 4.19.255 on kernels 4.19.222 and above.
//
let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit()); // See https://github.com/torvalds/linux/commit/a256aac.
let kernel = if let Some(pos) = pos { const CLAMPED_KERNEL_MAJOR: u8 = 4;
let (s, _) = s.split_at(pos); const CLAMPED_KERNEL_MINOR: u8 = 19;
s if let Ok(Self {
} else { major: CLAMPED_KERNEL_MAJOR,
s minor: CLAMPED_KERNEL_MINOR,
patch: 222..,
}) = kernel_version
{
return Ok(Self::new(CLAMPED_KERNEL_MAJOR, CLAMPED_KERNEL_MINOR, 255));
}
kernel_version
}
// 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> {
const UBUNTU_KVER_FILE: &str = "/proc/version_signature";
let s = match fs::read(UBUNTU_KVER_FILE) {
Ok(s) => s,
Err(e) => {
if e.kind() == io::ErrorKind::NotFound {
return Ok(None);
}
return Err(format!("failed to read {}: {}", UBUNTU_KVER_FILE, e));
}
}; };
let mut kernel_split = kernel.split('.'); (|| {
let ubuntu: String;
let major = kernel_split let ubuntu_version: String;
.next() let major: u8;
.ok_or("Missing major version component")?; let minor: u8;
let minor = kernel_split let patch: u16;
.next() text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch);
.ok_or("Missing minor version component")?; Ok(Some(Self::new(major, minor, patch)))
let patch = kernel_split })().map_err(|e: text_io::Error| format!("failed to parse {:?}: {}", s, e))
.next() }
.ok_or("Missing patch version component")?;
let major = major
.parse()
.map_err(|ParseIntError { .. }| "Failed to parse major version")?;
let minor = minor
.parse()
.map_err(|ParseIntError { .. }| "Failed to parse minor version")?;
let patch = patch
.parse()
.map_err(|ParseIntError { .. }| "Failed to parse patch version")?;
fn get_debian_kernel_version(info: &utsname) -> Result<Option<Self>, String> {
// Safety: man 2 uname:
//
// 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()
.map_err(|e| format!("failed to parse version: {}", e))?;
let p = match p.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)))
})()
.map_err(|e: text_io::Error| format!("failed to parse {}: {}", p, e))
}
fn get_kernel_version() -> Result<Self, String> {
if let Some(v) = Self::get_ubuntu_kernel_version()? {
return Ok(v);
}
let mut info = unsafe { mem::zeroed::<utsname>() };
if unsafe { uname(&mut info) } != 0 {
return Err(format!(
"failed to get kernel version: {}",
io::Error::last_os_error()
));
}
if let Some(v) = Self::get_debian_kernel_version(&info)? {
return Ok(v);
}
// Safety: man 2 uname:
//
// 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()
.map_err(|e| format!("failed to parse release: {}", e))?;
// 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);
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