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",
] }
parking_lot = { version = "0.12.0", features = ["send_guard"] }
text_io = "0.1.12"
thiserror = "1"
tokio = { version = "1.24.0", features = [
"macros",

@ -1,12 +1,10 @@
//! Utility functions.
use std::{
collections::BTreeMap,
ffi::CString,
ffi::{CStr, CString},
fs::{self, File},
io::{self, BufRead, BufReader},
mem,
num::ParseIntError,
slice,
mem, slice,
str::FromStr,
};
@ -15,7 +13,7 @@ use crate::{
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.
// 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.
///
/// This is taken from `/proc/sys/kernel/osrelease`;
pub fn current() -> Result<Self, String> {
let s =
fs::read_to_string("/proc/sys/kernel/osrelease").map_err(|err| format!("{err:?}"))?;
let s = s.as_str();
let pos = s.find(|c: char| c != '.' && !c.is_ascii_digit());
let kernel = if let Some(pos) = pos {
let (s, _) = s.split_at(pos);
s
} else {
s
let kernel_version = Self::get_kernel_version();
// The kernel version is clamped to 4.19.255 on kernels 4.19.222 and above.
//
// See https://github.com/torvalds/linux/commit/a256aac.
const CLAMPED_KERNEL_MAJOR: u8 = 4;
const CLAMPED_KERNEL_MINOR: u8 = 19;
if let Ok(Self {
major: CLAMPED_KERNEL_MAJOR,
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 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)))
})().map_err(|e: text_io::Error| format!("failed to parse {:?}: {}", s, e))
}
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 mut kernel_split = kernel.split('.');
let major = kernel_split
.next()
.ok_or("Missing major version component")?;
let minor = kernel_split
.next()
.ok_or("Missing minor version component")?;
let patch = kernel_split
.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")?;
Ok(Self::new(major, minor, patch))
(|| {
let major: u8;
let minor: u8;
let patch: u16;
text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch);
Ok(Self::new(major, minor, patch))
})()
.map_err(|e: text_io::Error| format!("failed to parse {}: {}", p, e))
}
}

Loading…
Cancel
Save