Browse Source

Rewrite kernel version logic

This restores and enhances the logic originally added in #579.
Tamir Duberstein 1 year ago
parent
commit
6e570f0f14
2 changed files with 107 additions and 40 deletions
  1. 1 0
      aya/Cargo.toml
  2. 106 40
      aya/src/util.rs

+ 1 - 0
aya/Cargo.toml

@@ -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",

+ 106 - 40
aya/src/util.rs

@@ -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))
     }
 }