landlock/
compat.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::{uapi, Access, CompatError};
4use std::fmt::{self, Display, Formatter};
5use std::io::Error;
6
7#[cfg(test)]
8use std::convert::TryInto;
9#[cfg(test)]
10use strum::{EnumCount, IntoEnumIterator};
11#[cfg(test)]
12use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
13
14/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).
15///
16/// `ABI` enables getting the features supported by a specific Landlock ABI
17/// (without relying on the kernel version which may not be accessible or patched).
18/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)
19/// gets all the file system access rights defined by the first version.
20///
21/// Without `ABI`, it would be hazardous to rely on the the full set of access flags
22/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),
23/// a moving target that would change the semantics of your Landlock rule
24/// when migrating to a newer version of this crate.
25/// Indeed, a simple `cargo update` or `cargo install` run by any developer
26/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
27/// This crate cannot give any guarantee concerning the new restrictions resulting from
28/// these unknown bits (i.e. access rights) that would not be controlled by your application but by
29/// a future version of this crate instead.
30/// Because we cannot know what the effect on your application of an unknown restriction would be
31/// when handling an untested Landlock access right (i.e. denied-by-default access),
32/// it could trigger bugs in your application.
33///
34/// This crate provides a set of tools to sandbox as much as possible
35/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.
36/// You should also test with different relevant kernel versions,
37/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and
38/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).
39///
40/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as
41/// expected because features brought by newer Landlock ABI will never be enabled by default
42/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).
43///
44/// In a nutshell, test the access rights you request on a kernel that support them and
45/// on a kernel that doesn't support them.
46///
47/// Derived `Debug` formats are [not stable](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability).
48#[cfg_attr(test, derive(EnumIter, EnumCountMacro))]
49#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
50#[non_exhaustive]
51pub enum ABI {
52    /// Kernel not supporting Landlock, either because it is not built with Landlock
53    /// or Landlock is not enabled at boot.
54    Unsupported = 0,
55    /// First Landlock ABI, introduced with
56    /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).
57    V1 = 1,
58    /// Second Landlock ABI, introduced with
59    /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).
60    V2 = 2,
61    /// Third Landlock ABI, introduced with
62    /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).
63    V3 = 3,
64    /// Fourth Landlock ABI, introduced with
65    /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).
66    V4 = 4,
67    /// Fifth Landlock ABI, introduced with
68    /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).
69    V5 = 5,
70    /// Sixth Landlock ABI, introduced with
71    /// [Linux 6.12](https://git.kernel.org/stable/c/e1b061b444fb01c237838f0d8238653afe6a8094).
72    V6 = 6,
73}
74
75// ABI should not be dynamically created (in other crates) according to the running kernel
76// to avoid inconsistent behaviors and non-determinism. Creating ABIs based on runtime detection
77// can lead to unreliable sandboxing where rules might differ between executions.
78impl ABI {
79    #[cfg(test)]
80    fn is_known(value: i32) -> bool {
81        value > 0 && value < ABI::COUNT as i32
82    }
83}
84
85/// Converting from an integer to an ABI should only be used for testing.
86/// Indeed, manually setting the ABI can lead to inconsistent and unexpected behaviors.
87/// Instead, just use the appropriate access rights, this library will handle the rest.
88impl From<i32> for ABI {
89    fn from(value: i32) -> ABI {
90        match value {
91            n if n <= 0 => ABI::Unsupported,
92            1 => ABI::V1,
93            2 => ABI::V2,
94            3 => ABI::V3,
95            4 => ABI::V4,
96            5 => ABI::V5,
97            // Returns the greatest known ABI.
98            _ => ABI::V6,
99        }
100    }
101}
102
103#[test]
104fn abi_from() {
105    // EOPNOTSUPP (-95), ENOSYS (-38)
106    for n in [-95, -38, -1, 0] {
107        assert_eq!(ABI::from(n), ABI::Unsupported);
108    }
109
110    let mut last_i = 1;
111    let mut last_abi = ABI::Unsupported;
112    for (i, abi) in ABI::iter().enumerate() {
113        last_i = i.try_into().unwrap();
114        last_abi = abi;
115        assert_eq!(ABI::from(last_i), last_abi);
116    }
117
118    assert_eq!(ABI::from(last_i + 1), last_abi);
119    assert_eq!(ABI::from(999), last_abi);
120}
121
122#[test]
123fn known_abi() {
124    assert!(!ABI::is_known(-1));
125    assert!(!ABI::is_known(0));
126    assert!(!ABI::is_known(999));
127
128    let mut last_i = -1;
129    for (i, _) in ABI::iter().enumerate().skip(1) {
130        last_i = i as i32;
131        assert!(ABI::is_known(last_i));
132    }
133    assert!(!ABI::is_known(last_i + 1));
134}
135
136impl Display for ABI {
137    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
138        match self {
139            ABI::Unsupported => write!(f, "unsupported"),
140            v => (*v as u32).fmt(f),
141        }
142    }
143}
144
145/// Status of Landlock support for the running system.
146///
147/// This enum is used to represent the status of the Landlock support for the system where the code
148/// is executed. It can indicate whether Landlock is available or not.
149///
150/// # Warning
151///
152/// Sandboxed programs should only use this data to log or provide information to users,
153/// not to change their behavior according to this status.  Indeed, the `Ruleset` and the other
154/// types are designed to handle the compatibility in a simple and safe way.
155#[derive(Copy, Clone, Debug, PartialEq, Eq)]
156pub enum LandlockStatus {
157    /// Landlock is supported but not enabled (`EOPNOTSUPP`).
158    NotEnabled,
159    /// Landlock is not implemented (i.e. not built into the running kernel: `ENOSYS`).
160    NotImplemented,
161    /// Landlock is available and supported up to the given ABI.
162    ///
163    /// `Option<i32>` contains the raw ABI value if it's greater than the greatest known ABI,
164    /// which would mean that the running kernel is newer than the Landlock crate.
165    Available(ABI, Option<i32>),
166}
167
168impl LandlockStatus {
169    // Must remain private to avoid inconsistent behavior using such unknown-at-build-time ABI
170    // e.g., AccessFs::from_all(ABI::new_current())
171    //
172    // This should not be Default::default() because the returned value would may not be the same
173    // for all users.
174    fn current() -> Self {
175        // Landlock ABI version starts at 1 but errno is only set for negative values.
176        let v = unsafe {
177            uapi::landlock_create_ruleset(
178                std::ptr::null(),
179                0,
180                uapi::LANDLOCK_CREATE_RULESET_VERSION,
181            )
182        };
183        if v < 0 {
184            // The only possible error values should be EOPNOTSUPP and ENOSYS.
185            match Error::last_os_error().raw_os_error() {
186                Some(libc::EOPNOTSUPP) => Self::NotEnabled,
187                _ => Self::NotImplemented,
188            }
189        } else {
190            let abi = ABI::from(v);
191            Self::Available(abi, (v != abi as i32).then_some(v))
192        }
193    }
194}
195
196// Test against the running kernel.
197#[test]
198fn test_current_landlock_status() {
199    let status = LandlockStatus::current();
200    if *TEST_ABI == ABI::Unsupported {
201        assert_eq!(status, LandlockStatus::NotImplemented);
202    } else {
203        assert!(matches!(status, LandlockStatus::Available(abi, _) if abi == *TEST_ABI));
204        if std::env::var(TEST_ABI_ENV_NAME).is_ok() {
205            // We cannot reliably check for unknown kernel.
206            assert!(matches!(status, LandlockStatus::Available(_, None)));
207        }
208    }
209}
210
211impl From<LandlockStatus> for ABI {
212    fn from(status: LandlockStatus) -> Self {
213        match status {
214            // The only possible error values should be EOPNOTSUPP and ENOSYS,
215            // but let's convert all kind of errors as unsupported.
216            LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,
217            LandlockStatus::Available(abi, _) => abi,
218        }
219    }
220}
221
222// This is only useful to tests and should not be exposed publicly because
223// the mapping can only be partial.
224#[cfg(test)]
225impl From<ABI> for LandlockStatus {
226    fn from(abi: ABI) -> Self {
227        match abi {
228            // Convert to ENOSYS because of check_ruleset_support() and ruleset_unsupported() tests.
229            ABI::Unsupported => Self::NotImplemented,
230            _ => Self::Available(abi, None),
231        }
232    }
233}
234
235#[cfg(test)]
236static TEST_ABI_ENV_NAME: &str = "LANDLOCK_CRATE_TEST_ABI";
237
238#[cfg(test)]
239lazy_static! {
240    static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
241        Ok(s) => {
242            let n = s.parse::<i32>().unwrap();
243            if ABI::is_known(n) || n == 0 {
244                ABI::from(n)
245            } else {
246                panic!("Unknown ABI: {n}");
247            }
248        }
249        Err(std::env::VarError::NotPresent) => LandlockStatus::current().into(),
250        Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
251    };
252}
253
254#[cfg(test)]
255pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {
256    mock < partial_support
257        || mock <= *TEST_ABI
258        || if let Some(full) = full_support {
259            full <= *TEST_ABI
260        } else {
261            partial_support <= *TEST_ABI
262        }
263}
264
265#[cfg(test)]
266pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
267    match LandlockStatus::current() {
268        LandlockStatus::NotImplemented | LandlockStatus::NotEnabled => {
269            match Error::last_os_error().raw_os_error() {
270                // Returns ENOSYS when the kernel is not built with Landlock support,
271                // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
272                ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
273                // Other values can only come from bogus seccomp filters or debugging tampering.
274                ret => {
275                    eprintln!("Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI");
276                    eprintln!("Unexpected result: {ret:?}");
277                    unreachable!();
278                }
279            }
280        }
281        LandlockStatus::Available(_, _) => None,
282    }
283}
284
285#[test]
286fn current_kernel_abi() {
287    // Ensures that the tested Landlock ABI is the latest known version supported by the running
288    // kernel.  If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable
289    // to the Landlock ABI version supported by your kernel.  With a missing variable, the latest
290    // Landlock ABI version known by this crate is automatically set.
291    // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
292    let test_abi = *TEST_ABI;
293    let current_abi = LandlockStatus::current().into();
294    println!(
295        "Current kernel version: {}",
296        std::fs::read_to_string("/proc/version")
297            .unwrap_or_else(|_| "unknown".into())
298            .trim()
299    );
300    println!("Expected Landlock ABI {test_abi:?} whereas the current ABI is {current_abi:#?}");
301    assert_eq!(test_abi, current_abi);
302}
303
304// CompatState is not public outside this crate.
305/// Returned by ruleset builder.
306#[derive(Copy, Clone, Debug, PartialEq, Eq)]
307pub enum CompatState {
308    /// Initial undefined state.
309    Init,
310    /// All requested restrictions are enforced.
311    Full,
312    /// Some requested restrictions are enforced, following a best-effort approach.
313    Partial,
314    /// The running system doesn't support Landlock.
315    No,
316    /// Final unsupported state.
317    Dummy,
318}
319
320impl CompatState {
321    fn update(&mut self, other: Self) {
322        *self = match (*self, other) {
323            (CompatState::Init, other) => other,
324            (CompatState::Dummy, _) => CompatState::Dummy,
325            (_, CompatState::Dummy) => CompatState::Dummy,
326            (CompatState::No, CompatState::No) => CompatState::No,
327            (CompatState::Full, CompatState::Full) => CompatState::Full,
328            (_, _) => CompatState::Partial,
329        }
330    }
331}
332
333#[test]
334fn compat_state_update_1() {
335    let mut state = CompatState::Full;
336
337    state.update(CompatState::Full);
338    assert_eq!(state, CompatState::Full);
339
340    state.update(CompatState::No);
341    assert_eq!(state, CompatState::Partial);
342
343    state.update(CompatState::Full);
344    assert_eq!(state, CompatState::Partial);
345
346    state.update(CompatState::Full);
347    assert_eq!(state, CompatState::Partial);
348
349    state.update(CompatState::No);
350    assert_eq!(state, CompatState::Partial);
351
352    state.update(CompatState::Dummy);
353    assert_eq!(state, CompatState::Dummy);
354
355    state.update(CompatState::Full);
356    assert_eq!(state, CompatState::Dummy);
357}
358
359#[test]
360fn compat_state_update_2() {
361    let mut state = CompatState::Full;
362
363    state.update(CompatState::Full);
364    assert_eq!(state, CompatState::Full);
365
366    state.update(CompatState::No);
367    assert_eq!(state, CompatState::Partial);
368
369    state.update(CompatState::Full);
370    assert_eq!(state, CompatState::Partial);
371}
372
373#[cfg_attr(test, derive(PartialEq))]
374#[derive(Copy, Clone, Debug)]
375pub(crate) struct Compatibility {
376    status: LandlockStatus,
377    pub(crate) level: Option<CompatLevel>,
378    pub(crate) state: CompatState,
379}
380
381impl From<LandlockStatus> for Compatibility {
382    fn from(status: LandlockStatus) -> Self {
383        Compatibility {
384            status,
385            level: Default::default(),
386            state: CompatState::Init,
387        }
388    }
389}
390
391#[cfg(test)]
392impl From<ABI> for Compatibility {
393    fn from(abi: ABI) -> Self {
394        Self::from(LandlockStatus::from(abi))
395    }
396}
397
398impl Compatibility {
399    // Compatibility is a semi-opaque struct.
400    #[allow(clippy::new_without_default)]
401    pub(crate) fn new() -> Self {
402        LandlockStatus::current().into()
403    }
404
405    pub(crate) fn update(&mut self, state: CompatState) {
406        self.state.update(state);
407    }
408
409    pub(crate) fn abi(&self) -> ABI {
410        self.status.into()
411    }
412
413    pub(crate) fn status(&self) -> LandlockStatus {
414        self.status
415    }
416}
417
418pub(crate) mod private {
419    use crate::CompatLevel;
420
421    pub trait OptionCompatLevelMut {
422        fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
423    }
424}
425
426/// Properly handles runtime unsupported features.
427///
428/// This guarantees consistent behaviors across crate users
429/// and runtime kernels even if this crate get new features.
430/// It eases backward compatibility and enables future-proofness.
431///
432/// Landlock is a security feature designed to help improve security of a running system
433/// thanks to application developers.
434/// To protect users as much as possible,
435/// compatibility with the running system should then be handled in a best-effort way,
436/// contrary to common system features.
437/// In some circumstances
438/// (e.g. applications carefully designed to only be run with a specific set of kernel features),
439/// it may be required to error out if some of these features are not available
440/// and will then not be enforced.
441pub trait Compatible: Sized + private::OptionCompatLevelMut {
442    /// To enable a best-effort security approach,
443    /// Landlock features that are not supported by the running system
444    /// are silently ignored by default,
445    /// which is a sane choice for most use cases.
446    /// However, on some rare circumstances,
447    /// developers may want to have some guarantees that their applications
448    /// will not run if a certain level of sandboxing is not possible.
449    /// If we really want to error out when not all our requested requirements are met,
450    /// then we can configure it with `set_compatibility()`.
451    ///
452    /// The `Compatible` trait is implemented for all object builders
453    /// (e.g. [`Ruleset`](crate::Ruleset)).
454    /// Such builders have a set of methods to incrementally build an object.
455    /// These build methods rely on kernel features that may not be available at runtime.
456    /// The `set_compatibility()` method enables to control the effect of
457    /// the following build method calls starting after the `set_compatibility()` call.
458    /// Such effect can be:
459    /// * to silently ignore unsupported features
460    ///   and continue building ([`CompatLevel::BestEffort`]);
461    /// * to silently ignore unsupported features
462    ///   and ignore the whole build ([`CompatLevel::SoftRequirement`]);
463    /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).
464    ///
465    /// Taking [`Ruleset`](crate::Ruleset) as an example,
466    /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method
467    /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)
468    /// with a nested [`CompatError`].
469    /// Such error can only occur with a running Linux kernel not supporting the requested
470    /// Landlock accesses *and* if the current compatibility level is
471    /// [`CompatLevel::HardRequirement`].
472    /// However, such error is not possible with [`CompatLevel::BestEffort`]
473    /// nor [`CompatLevel::SoftRequirement`].
474    ///
475    /// The order of this call is important because
476    /// it defines the behavior of the following build method calls that return a [`Result`].
477    /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,
478    /// then a [`CompatError`] may be returned for the next method calls,
479    /// until the next call to `set_compatibility()`.
480    /// This enables to change the behavior of a set of build method calls,
481    /// for instance to be sure that the sandbox will at least restrict some access rights.
482    ///
483    /// New objects inherit the compatibility configuration of their parents, if any.
484    /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns
485    /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the
486    /// `Ruleset`'s compatibility configuration.
487    ///
488    /// # Example with `SoftRequirement`
489    ///
490    /// Let's say an application legitimately needs to rename files between directories.
491    /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),
492    /// this was forbidden with the [first version of Landlock](ABI::V1),
493    /// but it is now handled starting with the [second version](ABI::V2).
494    /// For this use case, we only want the application to be sandboxed
495    /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
496    /// We then create a ruleset which will either support file renaming
497    /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.
498    ///
499    /// ```
500    /// use landlock::*;
501    ///
502    /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
503    ///     Ok(Ruleset::default()
504    ///         // This ruleset must either handle the AccessFs::Refer right,
505    ///         // or it must silently ignore the whole sandboxing.
506    ///         .set_compatibility(CompatLevel::SoftRequirement)
507    ///         .handle_access(AccessFs::Refer)?
508    ///         // However, this ruleset may also handle other (future) access rights
509    ///         // if they are supported by the running kernel.
510    ///         .set_compatibility(CompatLevel::BestEffort)
511    ///         .handle_access(AccessFs::from_all(ABI::V6))?
512    ///         .create()?)
513    /// }
514    /// ```
515    ///
516    /// # Example with `HardRequirement`
517    ///
518    /// Security-dedicated applications may want to ensure that
519    /// an untrusted software component is subject to a minimum of restrictions before launching it.
520    /// In this case, we want to create a ruleset which will at least support
521    /// all restrictions provided by the [first version of Landlock](ABI::V1),
522    /// and opportunistically handle restrictions supported by newer kernels.
523    ///
524    /// ```
525    /// use landlock::*;
526    ///
527    /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
528    ///     Ok(Ruleset::default()
529    ///         // This ruleset must either handle at least all accesses defined by
530    ///         // the first Landlock version (e.g. AccessFs::WriteFile),
531    ///         // or the following handle_access() call must return a wrapped
532    ///         // AccessError<AccessFs>::Incompatible error.
533    ///         .set_compatibility(CompatLevel::HardRequirement)
534    ///         .handle_access(AccessFs::from_all(ABI::V1))?
535    ///         // However, this ruleset may also handle new access rights
536    ///         // (e.g. AccessFs::Refer defined by the second version of Landlock)
537    ///         // if they are supported by the running kernel,
538    ///         // but without returning any error otherwise.
539    ///         .set_compatibility(CompatLevel::BestEffort)
540    ///         .handle_access(AccessFs::from_all(ABI::V6))?
541    ///         .create()?)
542    /// }
543    /// ```
544    fn set_compatibility(mut self, level: CompatLevel) -> Self {
545        *self.as_option_compat_level_mut() = Some(level);
546        self
547    }
548
549    /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):
550    ///
551    /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.
552    ///
553    /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.
554    #[deprecated(note = "Use set_compatibility() instead")]
555    fn set_best_effort(self, best_effort: bool) -> Self
556    where
557        Self: Sized,
558    {
559        self.set_compatibility(match best_effort {
560            true => CompatLevel::BestEffort,
561            false => CompatLevel::HardRequirement,
562        })
563    }
564}
565
566#[test]
567#[allow(deprecated)]
568fn deprecated_set_best_effort() {
569    use crate::{CompatLevel, Compatible, Ruleset};
570
571    assert_eq!(
572        Ruleset::default().set_best_effort(true).compat,
573        Ruleset::default()
574            .set_compatibility(CompatLevel::BestEffort)
575            .compat
576    );
577    assert_eq!(
578        Ruleset::default().set_best_effort(false).compat,
579        Ruleset::default()
580            .set_compatibility(CompatLevel::HardRequirement)
581            .compat
582    );
583}
584
585/// See the [`Compatible`] documentation.
586#[cfg_attr(test, derive(EnumIter))]
587#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
588pub enum CompatLevel {
589    /// Takes into account the build requests if they are supported by the running system,
590    /// or silently ignores them otherwise.
591    /// Never returns a compatibility error.
592    #[default]
593    BestEffort,
594    /// Takes into account the build requests if they are supported by the running system,
595    /// or silently ignores the whole build object otherwise.
596    /// Never returns a compatibility error.
597    /// If not supported,
598    /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())
599    /// will return a
600    /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).
601    SoftRequirement,
602    /// Takes into account the build requests if they are supported by the running system,
603    /// or returns a compatibility error otherwise ([`CompatError`]).
604    HardRequirement,
605}
606
607impl From<Option<CompatLevel>> for CompatLevel {
608    fn from(opt: Option<CompatLevel>) -> Self {
609        match opt {
610            None => CompatLevel::default(),
611            Some(ref level) => *level,
612        }
613    }
614}
615
616// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined
617// in the current crate can be implemented for types defined outside of the crate.  Furthermore it
618// provides a default implementation which is handy for types such as BitFlags.
619pub trait TailoredCompatLevel {
620    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
621    where
622        L: Into<CompatLevel>,
623    {
624        parent_level.into()
625    }
626}
627
628impl<T> TailoredCompatLevel for T
629where
630    Self: Compatible,
631{
632    // Every Compatible trait implementation returns its own compatibility level, if set.
633    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
634    where
635        L: Into<CompatLevel>,
636    {
637        // Using a mutable reference is not required but it makes the code simpler (no double AsRef
638        // implementations for each Compatible types), and more importantly it guarantees
639        // consistency with Compatible::set_compatibility().
640        match self.as_option_compat_level_mut() {
641            None => parent_level.into(),
642            // Returns the most constrained compatibility level.
643            Some(ref level) => parent_level.into().max(*level),
644        }
645    }
646}
647
648#[test]
649fn tailored_compat_level() {
650    use crate::{AccessFs, PathBeneath, PathFd};
651
652    fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
653        PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
654    }
655
656    for parent_level in CompatLevel::iter() {
657        assert_eq!(
658            new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
659            parent_level
660        );
661        assert_eq!(
662            new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
663            CompatLevel::HardRequirement
664        );
665    }
666
667    assert_eq!(
668        new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
669        CompatLevel::SoftRequirement
670    );
671
672    for child_level in CompatLevel::iter() {
673        assert_eq!(
674            new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
675            child_level
676        );
677        assert_eq!(
678            new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
679            CompatLevel::HardRequirement
680        );
681    }
682}
683
684// CompatResult is not public outside this crate.
685pub enum CompatResult<A>
686where
687    A: Access,
688{
689    // Fully matches the request.
690    Full,
691    // Partially matches the request.
692    Partial(CompatError<A>),
693    // Doesn't matches the request.
694    No(CompatError<A>),
695}
696
697// TryCompat is not public outside this crate.
698pub trait TryCompat<A>
699where
700    Self: Sized + TailoredCompatLevel,
701    A: Access,
702{
703    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;
704
705    // Default implementation for objects without children.
706    //
707    // If returning something other than Ok(Some(self)), the implementation must use its own
708    // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass
709    // it with the abi and compat_state to each child.try_compat().  See PathBeneath implementation
710    // and the self.allowed_access.try_compat() call.
711    //
712    // # Warning
713    //
714    // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all
715    // children.
716    fn try_compat_children<L>(
717        self,
718        _abi: ABI,
719        _parent_level: L,
720        _compat_state: &mut CompatState,
721    ) -> Result<Option<Self>, CompatError<A>>
722    where
723        L: Into<CompatLevel>,
724    {
725        Ok(Some(self))
726    }
727
728    // Update compat_state and return an error according to try_compat_*() error, or to the
729    // compatibility level, i.e. either route compatible object or error.
730    fn try_compat<L>(
731        mut self,
732        abi: ABI,
733        parent_level: L,
734        compat_state: &mut CompatState,
735    ) -> Result<Option<Self>, CompatError<A>>
736    where
737        L: Into<CompatLevel>,
738    {
739        let compat_level = self.tailored_compat_level(parent_level);
740        let some_inner = match self.try_compat_inner(abi) {
741            Ok(CompatResult::Full) => {
742                compat_state.update(CompatState::Full);
743                true
744            }
745            Ok(CompatResult::Partial(error)) => match compat_level {
746                CompatLevel::BestEffort => {
747                    compat_state.update(CompatState::Partial);
748                    true
749                }
750                CompatLevel::SoftRequirement => {
751                    compat_state.update(CompatState::Dummy);
752                    false
753                }
754                CompatLevel::HardRequirement => {
755                    compat_state.update(CompatState::Dummy);
756                    return Err(error);
757                }
758            },
759            Ok(CompatResult::No(error)) => match compat_level {
760                CompatLevel::BestEffort => {
761                    compat_state.update(CompatState::No);
762                    false
763                }
764                CompatLevel::SoftRequirement => {
765                    compat_state.update(CompatState::Dummy);
766                    false
767                }
768                CompatLevel::HardRequirement => {
769                    compat_state.update(CompatState::Dummy);
770                    return Err(error);
771                }
772            },
773            Err(error) => {
774                // Safeguard to help for test consistency.
775                compat_state.update(CompatState::Dummy);
776                return Err(error);
777            }
778        };
779
780        // At this point, any inner error have been returned, so we can proceed with
781        // try_compat_children()?.
782        match self.try_compat_children(abi, compat_level, compat_state)? {
783            Some(n) if some_inner => Ok(Some(n)),
784            _ => Ok(None),
785        }
786    }
787}