#![cfg(target_os = "linux")]
use std::ffi::CStr;
use std::os::unix::io::{FromRawFd, OwnedFd, RawFd};
use std::sync::OnceLock;
const SYS_OPENAT2: i64 = 437;
#[repr(C)]
pub(crate) struct OpenHow {
pub flags: u64, pub mode: u64, pub resolve: u64, }
pub(crate) const RESOLVE_BENEATH: u64 = 0x08;
pub(crate) const RESOLVE_NO_SYMLINKS: u64 = 0x04;
pub(crate) const RESOLVE_NO_MAGICLINKS: u64 = 0x02;
pub(crate) const RESOLVE_NO_XDEV: u64 = 0x01;
pub(crate) const O_RDONLY: u64 = 0;
pub(crate) const O_WRONLY: u64 = 1;
#[allow(dead_code)]
pub(crate) const O_RDWR: u64 = 2;
pub(crate) const O_CREAT: u64 = 0o100;
pub(crate) const O_EXCL: u64 = 0o200;
pub(crate) const O_TRUNC: u64 = 0o1000;
pub(crate) const O_APPEND: u64 = 0o2000;
pub(crate) const O_CLOEXEC: u64 = 0o2000000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Errno(pub i32);
impl Errno {
pub const EXDEV: Errno = Errno(18); pub const ELOOP: Errno = Errno(40); pub const ENOSYS: Errno = Errno(38); }
impl std::fmt::Display for Errno {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "errno {}", self.0)
}
}
impl From<Errno> for std::io::Error {
fn from(e: Errno) -> Self {
std::io::Error::from_raw_os_error(e.0)
}
}
pub(crate) fn openat2(dirfd: RawFd, path: &CStr, how: &OpenHow) -> Result<OwnedFd, Errno> {
let fd = unsafe {
syscall4(
SYS_OPENAT2,
dirfd as i64,
path.as_ptr() as i64,
how as *const OpenHow as i64,
std::mem::size_of::<OpenHow>() as i64,
)
};
if fd < 0 {
debug_assert!(fd >= i32::MIN as i64, "syscall errno out of expected range");
Err(Errno(-fd as i32))
} else {
Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
}
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn syscall4(nr: i64, a0: i64, a1: i64, a2: i64, a3: i64) -> i64 {
let ret: i64;
std::arch::asm!(
"syscall",
inlateout("rax") nr => ret,
in("rdi") a0,
in("rsi") a1,
in("rdx") a2,
in("r10") a3,
out("rcx") _,
out("r11") _,
options(nostack),
);
ret
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
unsafe fn syscall4(nr: i64, a0: i64, a1: i64, a2: i64, a3: i64) -> i64 {
let ret: i64;
std::arch::asm!(
"svc #0",
in("x8") nr,
inout("x0") a0 => ret,
in("x1") a1,
in("x2") a2,
in("x3") a3,
options(nostack),
);
ret
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub struct KernelVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl std::fmt::Display for KernelVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
pub(crate) const MIN_OPENAT2_KERNEL: KernelVersion = KernelVersion {
major: 5,
minor: 6,
patch: 0,
};
pub(crate) fn kernel_version() -> Option<KernelVersion> {
static CACHED: OnceLock<Option<KernelVersion>> = OnceLock::new();
*CACHED.get_or_init(|| {
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok()
.and_then(|s| parse_kernel_version(s.trim()))
})
}
fn parse_kernel_version(s: &str) -> Option<KernelVersion> {
let s = s.split(['-', ' ']).next()?;
let mut parts = s.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
let patch = parts.next().and_then(|p| p.parse().ok()).unwrap_or(0);
Some(KernelVersion {
major,
minor,
patch,
})
}
pub(crate) fn probe_openat2() -> Result<(), Errno> {
static CACHED: OnceLock<Result<(), Errno>> = OnceLock::new();
*CACHED.get_or_init(|| {
let how = OpenHow {
flags: O_RDONLY | O_CLOEXEC,
mode: 0,
resolve: RESOLVE_BENEATH,
};
let empty = c"";
match openat2(-100i32 as RawFd, empty, &how) {
Ok(_) => Ok(()),
Err(e) if e == Errno::ENOSYS => Err(e),
Err(_) => Ok(()), }
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_kernel_versions() {
let v = parse_kernel_version("5.15.0-1045-aws").unwrap();
assert_eq!(
v,
KernelVersion {
major: 5,
minor: 15,
patch: 0
}
);
let v = parse_kernel_version("6.1.0").unwrap();
assert_eq!(
v,
KernelVersion {
major: 6,
minor: 1,
patch: 0
}
);
let v = parse_kernel_version("5.6.0-generic").unwrap();
assert_eq!(
v,
KernelVersion {
major: 5,
minor: 6,
patch: 0
}
);
let v = parse_kernel_version("5.15.153.1-microsoft-standard-WSL2").unwrap();
assert_eq!(
v,
KernelVersion {
major: 5,
minor: 15,
patch: 153
}
);
}
#[test]
fn kernel_version_ordering() {
let v56 = KernelVersion {
major: 5,
minor: 6,
patch: 0,
};
let v515 = KernelVersion {
major: 5,
minor: 15,
patch: 0,
};
let v6 = KernelVersion {
major: 6,
minor: 0,
patch: 0,
};
assert!(v56 < v515);
assert!(v515 < v6);
assert!(v56 >= MIN_OPENAT2_KERNEL);
}
#[test]
fn probe_is_cached() {
let r1 = probe_openat2();
let r2 = probe_openat2();
assert_eq!(r1.is_ok(), r2.is_ok());
}
#[test]
fn kernel_version_is_cached() {
let v1 = kernel_version();
let v2 = kernel_version();
assert_eq!(v1, v2);
}
}