use crate::{uapi, Access, CompatError};
use std::fmt::{self, Display, Formatter};
use std::io::Error;
#[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(EnumIter, EnumCountMacro))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ABI {
Unsupported = 0,
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
V6 = 6,
}
impl ABI {
#[cfg(test)]
fn is_known(value: i32) -> bool {
value > 0 && value < ABI::COUNT as i32
}
}
impl From<i32> for ABI {
fn from(value: i32) -> ABI {
match value {
n if n <= 0 => ABI::Unsupported,
1 => ABI::V1,
2 => ABI::V2,
3 => ABI::V3,
4 => ABI::V4,
5 => ABI::V5,
_ => ABI::V6,
}
}
}
#[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(999), last_abi);
}
#[test]
fn known_abi() {
assert!(!ABI::is_known(-1));
assert!(!ABI::is_known(0));
assert!(!ABI::is_known(999));
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));
}
impl Display for ABI {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ABI::Unsupported => write!(f, "unsupported"),
v => (*v as u32).fmt(f),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LandlockStatus {
NotEnabled,
NotImplemented,
Available {
effective_abi: ABI,
kernel_abi: Option<i32>,
},
}
impl LandlockStatus {
fn current() -> Self {
let v = unsafe {
uapi::landlock_create_ruleset(
std::ptr::null(),
0,
uapi::LANDLOCK_CREATE_RULESET_VERSION,
)
};
if v < 0 {
match Error::last_os_error().raw_os_error() {
Some(libc::EOPNOTSUPP) => Self::NotEnabled,
_ => Self::NotImplemented,
}
} else {
let abi = ABI::from(v);
Self::Available {
effective_abi: abi,
kernel_abi: (v != abi as i32).then_some(v),
}
}
}
}
#[test]
fn test_current_landlock_status() {
let status = LandlockStatus::current();
if *TEST_ABI == ABI::Unsupported {
assert_eq!(status, LandlockStatus::NotImplemented);
} else {
assert!(
matches!(status, LandlockStatus::Available { effective_abi, .. } if effective_abi == *TEST_ABI)
);
if std::env::var(TEST_ABI_ENV_NAME).is_ok() {
assert!(matches!(
status,
LandlockStatus::Available {
kernel_abi: None,
..
}
));
}
}
}
impl From<LandlockStatus> for ABI {
fn from(status: LandlockStatus) -> Self {
match status {
LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,
LandlockStatus::Available { effective_abi, .. } => effective_abi,
}
}
}
#[cfg(test)]
impl From<ABI> for LandlockStatus {
fn from(abi: ABI) -> Self {
match abi {
ABI::Unsupported => Self::NotImplemented,
_ => Self::Available {
effective_abi: abi,
kernel_abi: None,
},
}
}
}
#[cfg(test)]
static TEST_ABI_ENV_NAME: &str = "LANDLOCK_CRATE_TEST_ABI";
#[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) => LandlockStatus::current().into(),
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> {
match LandlockStatus::current() {
LandlockStatus::NotImplemented | LandlockStatus::NotEnabled => {
match Error::last_os_error().raw_os_error() {
ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
ret => {
eprintln!("Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI");
eprintln!("Unexpected result: {ret:?}");
unreachable!();
}
}
}
LandlockStatus::Available { .. } => None,
}
}
#[test]
fn current_kernel_abi() {
let test_abi = *TEST_ABI;
let current_abi = LandlockStatus::current().into();
println!(
"Current kernel version: {}",
std::fs::read_to_string("/proc/version")
.unwrap_or_else(|_| "unknown".into())
.trim()
);
println!("Expected Landlock ABI {test_abi:?} whereas the current ABI is {current_abi:#?}");
assert_eq!(test_abi, current_abi);
}
#[derive(Copy, Clone, Debug, 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(PartialEq))]
#[derive(Copy, Clone, Debug)]
pub(crate) struct Compatibility {
status: LandlockStatus,
pub(crate) level: Option<CompatLevel>,
pub(crate) state: CompatState,
}
impl From<LandlockStatus> for Compatibility {
fn from(status: LandlockStatus) -> Self {
Compatibility {
status,
level: Default::default(),
state: CompatState::Init,
}
}
}
#[cfg(test)]
impl From<ABI> for Compatibility {
fn from(abi: ABI) -> Self {
Self::from(LandlockStatus::from(abi))
}
}
impl Compatibility {
#[allow(clippy::new_without_default)]
pub(crate) fn new() -> Self {
LandlockStatus::current().into()
}
pub(crate) fn update(&mut self, state: CompatState) {
self.state.update(state);
}
pub(crate) fn abi(&self) -> ABI {
self.status.into()
}
pub(crate) fn status(&self) -> LandlockStatus {
self.status
}
}
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<A>
where
A: Access,
{
Full,
Partial(CompatError<A>),
No(CompatError<A>),
}
pub trait TryCompat<A>
where
Self: Sized + TailoredCompatLevel,
A: Access,
{
fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<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 some_inner = match self.try_compat_inner(abi) {
Ok(CompatResult::Full) => {
compat_state.update(CompatState::Full);
true
}
Ok(CompatResult::Partial(error)) => match compat_level {
CompatLevel::BestEffort => {
compat_state.update(CompatState::Partial);
true
}
CompatLevel::SoftRequirement => {
compat_state.update(CompatState::Dummy);
false
}
CompatLevel::HardRequirement => {
compat_state.update(CompatState::Dummy);
return Err(error);
}
},
Ok(CompatResult::No(error)) => match compat_level {
CompatLevel::BestEffort => {
compat_state.update(CompatState::No);
false
}
CompatLevel::SoftRequirement => {
compat_state.update(CompatState::Dummy);
false
}
CompatLevel::HardRequirement => {
compat_state.update(CompatState::Dummy);
return Err(error);
}
},
Err(error) => {
compat_state.update(CompatState::Dummy);
return Err(error);
}
};
match self.try_compat_children(abi, compat_level, compat_state)? {
Some(n) if some_inner => Ok(Some(n)),
_ => Ok(None),
}
}
}