use crate::{uapi, Access, CompatError};
#[cfg(test)]
use std::convert::TryInto;
#[cfg(test)]
use strum::{EnumCount, IntoEnumIterator};
#[cfg(test)]
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
#[cfg_attr(
test,
derive(Debug, PartialEq, Eq, PartialOrd, EnumIter, EnumCountMacro)
)]
#[derive(Copy, Clone)]
#[non_exhaustive]
pub enum ABI {
Unsupported = 0,
V1 = 1,
V2 = 2,
}
impl ABI {
fn new_current() -> Self {
ABI::from(unsafe {
uapi::landlock_create_ruleset(
std::ptr::null(),
0,
uapi::LANDLOCK_CREATE_RULESET_VERSION,
)
})
}
fn from(value: i32) -> ABI {
match value {
n if n <= 0 => ABI::Unsupported,
1 => ABI::V1,
_ => ABI::V2,
}
}
#[cfg(test)]
fn is_known(value: i32) -> bool {
value > 0 && value < ABI::COUNT as i32
}
}
#[test]
fn abi_from() {
for n in [-95, -38, -1, 0] {
assert_eq!(ABI::from(n), ABI::Unsupported);
}
let mut last_i = 1;
let mut last_abi = ABI::Unsupported;
for (i, abi) in ABI::iter().enumerate() {
last_i = i.try_into().unwrap();
last_abi = abi;
assert_eq!(ABI::from(last_i), last_abi);
}
assert_eq!(ABI::from(last_i + 1), last_abi);
assert_eq!(ABI::from(9), last_abi);
}
#[test]
fn known_abi() {
assert!(!ABI::is_known(-1));
assert!(!ABI::is_known(0));
assert!(!ABI::is_known(99));
let mut last_i = -1;
for (i, _) in ABI::iter().enumerate().skip(1) {
last_i = i as i32;
assert!(ABI::is_known(last_i));
}
assert!(!ABI::is_known(last_i + 1));
}
#[cfg(test)]
lazy_static! {
static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
Ok(s) => {
let n = s.parse::<i32>().unwrap();
if ABI::is_known(n) || n == 0 {
ABI::from(n)
} else {
panic!("Unknown ABI: {n}");
}
}
Err(std::env::VarError::NotPresent) => ABI::iter().last().unwrap(),
Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
};
}
#[cfg(test)]
pub(crate) fn can_emulate(mock: ABI, full_support: ABI) -> bool {
mock <= *TEST_ABI || full_support <= *TEST_ABI
}
#[cfg(test)]
pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
use std::io::Error;
if unsafe {
uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_VERSION)
} < 0
{
match Error::last_os_error().raw_os_error() {
ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
_ => unreachable!(),
}
} else {
None
}
}
#[test]
fn current_kernel_abi() {
assert_eq!(*TEST_ABI, ABI::new_current());
}
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Copy, Clone)]
pub(crate) enum CompatState {
Full,
Partial,
No,
Final,
}
impl CompatState {
pub(crate) fn update(&mut self, other: Self) {
*self = match (*self, other) {
(CompatState::Final, _) => CompatState::Final,
(_, CompatState::Final) => CompatState::Final,
(CompatState::No, CompatState::No) => CompatState::No,
(CompatState::Full, CompatState::Full) => CompatState::Full,
(_, _) => CompatState::Partial,
}
}
}
#[test]
fn compat_state_update_1() {
let mut state = CompatState::Full;
state.update(CompatState::Full);
assert_eq!(state, CompatState::Full);
state.update(CompatState::No);
assert_eq!(state, CompatState::Partial);
state.update(CompatState::Full);
assert_eq!(state, CompatState::Partial);
state.update(CompatState::Full);
assert_eq!(state, CompatState::Partial);
state.update(CompatState::No);
assert_eq!(state, CompatState::Partial);
state.update(CompatState::Final);
assert_eq!(state, CompatState::Final);
state.update(CompatState::Full);
assert_eq!(state, CompatState::Final);
}
#[test]
fn compat_state_update_2() {
let mut state = CompatState::Full;
state.update(CompatState::Full);
assert_eq!(state, CompatState::Full);
state.update(CompatState::No);
assert_eq!(state, CompatState::Partial);
state.update(CompatState::Full);
assert_eq!(state, CompatState::Partial);
}
#[cfg_attr(test, derive(Debug))]
#[derive(Clone)]
pub struct Compatibility {
pub(crate) abi: ABI,
pub(crate) is_best_effort: bool,
pub(crate) state: CompatState,
}
impl From<ABI> for Compatibility {
fn from(abi: ABI) -> Self {
Compatibility {
abi,
is_best_effort: true,
state: match abi {
ABI::Unsupported => CompatState::Final,
_ => CompatState::Full,
},
}
}
}
impl Compatibility {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
ABI::new_current().into()
}
}
pub trait Compatible {
fn set_best_effort(self, best_effort: bool) -> Self;
}
pub trait TryCompat<T> {
fn try_compat(self, compat: &mut Compatibility) -> Result<Self, CompatError<T>>
where
Self: Sized,
T: Access;
}