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,
V3 = 3,
}
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,
2 => ABI::V2,
_ => ABI::V3,
}
}
#[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, partial_support: ABI, full_support: Option<ABI>) -> bool {
mock < partial_support
|| mock <= *TEST_ABI
|| if let Some(full) = full_support {
full <= *TEST_ABI
} else {
partial_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))]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum CompatState {
Init,
Full,
Partial,
No,
Dummy,
}
impl CompatState {
fn update(&mut self, other: Self) {
*self = match (*self, other) {
(CompatState::Init, other) => other,
(CompatState::Dummy, _) => CompatState::Dummy,
(_, CompatState::Dummy) => CompatState::Dummy,
(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::Dummy);
assert_eq!(state, CompatState::Dummy);
state.update(CompatState::Full);
assert_eq!(state, CompatState::Dummy);
}
#[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, PartialEq))]
#[derive(Copy, Clone)]
pub(crate) struct Compatibility {
abi: ABI,
pub(crate) level: Option<CompatLevel>,
pub(crate) state: CompatState,
}
impl From<ABI> for Compatibility {
fn from(abi: ABI) -> Self {
Compatibility {
abi,
level: Default::default(),
state: match abi {
ABI::Unsupported => CompatState::No,
_ => CompatState::Init,
},
}
}
}
impl Compatibility {
#[allow(clippy::new_without_default)]
pub(crate) fn new() -> Self {
ABI::new_current().into()
}
pub(crate) fn update(&mut self, state: CompatState) {
self.state.update(state);
}
pub(crate) fn abi(&self) -> ABI {
self.abi
}
}
pub(crate) mod private {
use crate::CompatLevel;
pub trait OptionCompatLevelMut {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
}
}
pub trait Compatible: Sized + private::OptionCompatLevelMut {
fn set_compatibility(mut self, level: CompatLevel) -> Self {
*self.as_option_compat_level_mut() = Some(level);
self
}
#[deprecated(note = "Use set_compatibility() instead")]
fn set_best_effort(self, best_effort: bool) -> Self
where
Self: Sized,
{
self.set_compatibility(match best_effort {
true => CompatLevel::BestEffort,
false => CompatLevel::HardRequirement,
})
}
}
#[test]
#[allow(deprecated)]
fn deprecated_set_best_effort() {
use crate::{CompatLevel, Compatible, Ruleset};
assert_eq!(
Ruleset::default().set_best_effort(true).compat,
Ruleset::default()
.set_compatibility(CompatLevel::BestEffort)
.compat
);
assert_eq!(
Ruleset::default().set_best_effort(false).compat,
Ruleset::default()
.set_compatibility(CompatLevel::HardRequirement)
.compat
);
}
#[cfg_attr(test, derive(EnumIter))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CompatLevel {
#[default]
BestEffort,
SoftRequirement,
HardRequirement,
}
impl From<Option<CompatLevel>> for CompatLevel {
fn from(opt: Option<CompatLevel>) -> Self {
match opt {
None => CompatLevel::default(),
Some(ref level) => *level,
}
}
}
pub trait TailoredCompatLevel {
fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
where
L: Into<CompatLevel>,
{
parent_level.into()
}
}
impl<T> TailoredCompatLevel for T
where
Self: Compatible,
{
fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
where
L: Into<CompatLevel>,
{
match self.as_option_compat_level_mut() {
None => parent_level.into(),
Some(ref level) => parent_level.into().max(*level),
}
}
}
#[test]
fn tailored_compat_level() {
use crate::{AccessFs, PathBeneath, PathFd};
fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
}
for parent_level in CompatLevel::iter() {
assert_eq!(
new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
parent_level
);
assert_eq!(
new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
CompatLevel::HardRequirement
);
}
assert_eq!(
new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
CompatLevel::SoftRequirement
);
for child_level in CompatLevel::iter() {
assert_eq!(
new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
child_level
);
assert_eq!(
new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
CompatLevel::HardRequirement
);
}
}
pub enum CompatResult<T, A>
where
T: TryCompat<A>,
A: Access,
{
Full(T),
Partial(T, CompatError<A>),
No(CompatError<A>),
}
pub trait TryCompat<A>
where
Self: Sized + TailoredCompatLevel,
A: Access,
{
fn try_compat_inner(self, abi: ABI) -> Result<CompatResult<Self, A>, CompatError<A>>;
fn try_compat_children<L>(
self,
_abi: ABI,
_parent_level: L,
_compat_state: &mut CompatState,
) -> Result<Option<Self>, CompatError<A>>
where
L: Into<CompatLevel>,
{
Ok(Some(self))
}
fn try_compat<L>(
mut self,
abi: ABI,
parent_level: L,
compat_state: &mut CompatState,
) -> Result<Option<Self>, CompatError<A>>
where
L: Into<CompatLevel>,
{
let compat_level = self.tailored_compat_level(parent_level);
let new_self = match self.try_compat_children(abi, compat_level, compat_state)? {
Some(n) => n,
None => return Ok(None),
};
match new_self.try_compat_inner(abi) {
Ok(CompatResult::Full(new_self)) => {
compat_state.update(CompatState::Full);
Ok(Some(new_self))
}
Ok(CompatResult::Partial(new_self, error)) => match compat_level {
CompatLevel::BestEffort => {
compat_state.update(CompatState::Partial);
Ok(Some(new_self))
}
CompatLevel::SoftRequirement => {
compat_state.update(CompatState::Dummy);
Ok(None)
}
CompatLevel::HardRequirement => {
compat_state.update(CompatState::Dummy);
Err(error)
}
},
Ok(CompatResult::No(error)) => match compat_level {
CompatLevel::BestEffort => {
compat_state.update(CompatState::No);
Ok(None)
}
CompatLevel::SoftRequirement => {
compat_state.update(CompatState::Dummy);
Ok(None)
}
CompatLevel::HardRequirement => {
compat_state.update(CompatState::Dummy);
Err(error)
}
},
Err(e) => {
compat_state.update(CompatState::Dummy);
Err(e)
}
}
}
}