use crate::compat::ABI;
use crate::{uapi, BitFlags};
use enumflags2::bitflags;
#[bitflags]
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Erratum {
TcpSocketIdentification = 1 << 0,
ScopedSignalHandling = 1 << 1,
DisconnectedDirectoryHandling = 1 << 2,
}
impl Erratum {
pub fn current() -> BitFlags<Self> {
let ret = unsafe {
uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_ERRATA)
};
if ret >= 0 {
unsafe { BitFlags::from_bits_unchecked(ret as u32) }
} else {
BitFlags::empty()
}
}
}
impl From<ABI> for BitFlags<Erratum> {
fn from(abi: ABI) -> Self {
match abi {
ABI::Unsupported => BitFlags::empty(),
ABI::V1 | ABI::V2 | ABI::V3 => Erratum::DisconnectedDirectoryHandling.into(),
ABI::V4 | ABI::V5 => Self::from(ABI::V3) | Erratum::TcpSocketIdentification,
ABI::V6 | ABI::V7 => Self::from(ABI::V5) | Erratum::ScopedSignalHandling,
}
}
}
#[cfg(test)]
fn parse_kernel_version(proc_version: &str) -> Option<(u32, u32, u32, &str)> {
let after_prefix = proc_version.strip_prefix("Linux version ")?;
let token = after_prefix.split_whitespace().next()?;
let first_dot = token.find('.')?;
let major: u32 = token[..first_dot].parse().ok()?;
let rest = &token[first_dot + 1..];
let minor_end = rest
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(rest.len());
let minor: u32 = rest[..minor_end].parse().ok()?;
let after_minor = &rest[minor_end..];
let (patch, suffix) = match after_minor.strip_prefix('.') {
Some(after_dot) => {
let patch_end = after_dot
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(after_dot.len());
let patch: u32 = after_dot[..patch_end].parse().unwrap_or(0);
(patch, &after_dot[patch_end..])
}
None => (0, after_minor),
};
Some((major, minor, patch, suffix))
}
#[test]
fn parse_kernel_version_cases() {
assert_eq!(
parse_kernel_version("Linux version 6.15.0-29-generic (build@host) ..."),
Some((6, 15, 0, "-29-generic")),
);
assert_eq!(
parse_kernel_version("Linux version 5.10.234-1-amd64 (debian) ..."),
Some((5, 10, 234, "-1-amd64")),
);
assert_eq!(
parse_kernel_version("Linux version 6.15-rc1 (...)"),
Some((6, 15, 0, "-rc1")),
);
assert_eq!(
parse_kernel_version("Linux version 6.15"),
Some((6, 15, 0, "")),
);
assert_eq!(
parse_kernel_version("Linux version 6.12.5"),
Some((6, 12, 5, "")),
);
assert_eq!(parse_kernel_version(""), None);
assert_eq!(parse_kernel_version("Some other text"), None);
assert_eq!(parse_kernel_version("Linux version garbage"), None);
}
#[cfg(test)]
fn not_backported_yet(version: (u32, u32, u32, &str)) -> BitFlags<Erratum> {
match version {
(5, 15, _, _) | (6, 1, _, _) => Erratum::DisconnectedDirectoryHandling.into(),
(6, 15, _, _) => Erratum::DisconnectedDirectoryHandling.into(),
_ => BitFlags::empty(),
}
}
#[test]
fn errata_query() {
let _errata = Erratum::current();
}
#[test]
fn errata_up_to_date() {
use crate::compat::{ABI, TEST_ABI, TEST_ABI_ENV_NAME};
let proc_version = std::fs::read_to_string("/proc/version").unwrap_or_default();
eprintln!("/proc/version: {}", proc_version.trim());
if std::env::var(TEST_ABI_ENV_NAME).is_err() {
eprintln!("Skipping errata_up_to_date: {} not set", TEST_ABI_ENV_NAME,);
return;
}
let kernel_version =
parse_kernel_version(&proc_version).expect("Failed to parse /proc/version");
eprintln!("Parsed kernel version: {:?}", kernel_version);
let current = Erratum::current();
let applicable: BitFlags<Erratum> = (*TEST_ABI).into();
let expected = applicable & !not_backported_yet(kernel_version);
assert!(
current & !applicable == BitFlags::empty(),
"kernel reported errata not applicable to ABI {:?}: {:?}",
*TEST_ABI,
current & !applicable,
);
match *TEST_ABI {
ABI::Unsupported => assert!(current.is_empty()),
ABI::V1 | ABI::V2 => assert_eq!(current, expected),
ABI::V3 | ABI::V4 | ABI::V5 => {}
ABI::V6 | ABI::V7 => assert_eq!(current, expected),
}
}