Skip to main content

whyno_core/state/
mod.rs

1//! Gathered filesystem state types.
2//!
3//! Complete state snapshot from OS. Check pipeline operates purely
4//! On these types with zero I/O.
5
6pub mod acl;
7pub mod fsflags;
8pub mod mac;
9pub mod mount;
10pub mod path;
11pub mod subject;
12
13use mac::MacState;
14use mount::MountTable;
15use path::PathComponent;
16use serde::Serialize;
17use subject::ResolvedSubject;
18
19use crate::operation::Operation;
20
21/// Tri-state for gathered data that may be unknown or inaccessible.
22///
23/// Distinguishes successfully gathered data, general failures,
24/// And explicit permission denials during gathering. Check functions
25/// Treat `Unknown` and `Inaccessible` as degraded -- never false-green.
26#[must_use = "probed values must be inspected -- ignoring them risks false-green results"]
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28#[serde(tag = "status", content = "value")]
29#[non_exhaustive]
30pub enum Probe<T> {
31    /// Successfully gathered data.
32    Known(T),
33    /// Could not read -- general failure (e.g., syscall error, missing /proc).
34    Unknown,
35    /// Could not read -- explicit EACCES/EPERM on the probe itself.
36    Inaccessible,
37}
38
39impl<T> Probe<T> {
40    /// Transforms inner value, preserving `Unknown` and `Inaccessible`.
41    #[must_use = "map returns a new Probe without modifying the original"]
42    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Probe<U> {
43        match self {
44            Self::Known(v) => Probe::Known(f(v)),
45            Self::Unknown => Probe::Unknown,
46            Self::Inaccessible => Probe::Inaccessible,
47        }
48    }
49
50    /// Reference to inner value if `Known`, `None` otherwise.
51    #[must_use]
52    pub fn known(&self) -> Option<&T> {
53        match self {
54            Self::Known(v) => Some(v),
55            Self::Unknown | Self::Inaccessible => None,
56        }
57    }
58
59    /// Returns `true` if this probe holds a `Known` value.
60    #[must_use]
61    pub fn is_known(&self) -> bool {
62        matches!(self, Self::Known(_))
63    }
64
65    /// Converts `&Probe<T>` to `Probe<&T>`.
66    #[must_use = "as_ref returns a new Probe without modifying the original"]
67    pub fn as_ref(&self) -> Probe<&T> {
68        match self {
69            Self::Known(v) => Probe::Known(v),
70            Self::Unknown => Probe::Unknown,
71            Self::Inaccessible => Probe::Inaccessible,
72        }
73    }
74}
75
76/// Complete gathered state for a single permission query.
77///
78/// Built by gathering layer, consumed by check pipeline.
79/// Contains resolved subject, path walk, mount table, and
80/// Requested operation.
81#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
82pub struct SystemState {
83    /// Resolved identity performing the operation.
84    pub subject: ResolvedSubject,
85    /// Path components from `/` to target, each with own permissions.
86    pub walk: Vec<PathComponent>,
87    /// Mount entries (metadata from mountinfo, flags from `statvfs()`).
88    pub mounts: MountTable,
89    /// Operation being checked.
90    pub operation: Operation,
91    /// MAC layer state (`SELinux` and `AppArmor`), pre-gathered before pipeline runs.
92    pub mac_state: MacState,
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn probe_map_transforms_known_value() {
101        let probe = Probe::Known(42);
102        let mapped = probe.map(|v| v * 2);
103        assert_eq!(mapped, Probe::Known(84));
104    }
105
106    #[test]
107    fn probe_map_preserves_unknown() {
108        let probe: Probe<i32> = Probe::Unknown;
109        let mapped = probe.map(|v| v * 2);
110        assert_eq!(mapped, Probe::<i32>::Unknown);
111    }
112
113    #[test]
114    fn probe_map_preserves_inaccessible() {
115        let probe: Probe<i32> = Probe::Inaccessible;
116        let mapped = probe.map(|v| v * 2);
117        assert_eq!(mapped, Probe::<i32>::Inaccessible);
118    }
119
120    #[test]
121    fn probe_known_returns_some_for_known() {
122        let probe = Probe::Known(42);
123        assert_eq!(probe.known(), Some(&42));
124    }
125
126    #[test]
127    fn probe_known_returns_none_for_unknown() {
128        let probe: Probe<i32> = Probe::Unknown;
129        assert_eq!(probe.known(), None);
130    }
131
132    #[test]
133    fn probe_known_returns_none_for_inaccessible() {
134        let probe: Probe<i32> = Probe::Inaccessible;
135        assert_eq!(probe.known(), None);
136    }
137
138    #[test]
139    fn probe_is_known_true_for_known() {
140        let probe = Probe::Known(42);
141        assert!(probe.is_known());
142    }
143
144    #[test]
145    fn probe_is_known_false_for_unknown() {
146        let probe: Probe<i32> = Probe::Unknown;
147        assert!(!probe.is_known());
148    }
149
150    #[test]
151    fn probe_is_known_false_for_inaccessible() {
152        let probe: Probe<i32> = Probe::Inaccessible;
153        assert!(!probe.is_known());
154    }
155
156    #[test]
157    fn probe_as_ref_known_returns_reference() {
158        let probe = Probe::Known(42);
159        assert_eq!(probe.as_ref(), Probe::Known(&42));
160    }
161
162    #[test]
163    fn probe_as_ref_unknown_stays_unknown() {
164        let probe: Probe<i32> = Probe::Unknown;
165        assert_eq!(probe.as_ref(), Probe::<&i32>::Unknown);
166    }
167
168    #[test]
169    fn probe_as_ref_inaccessible_stays_inaccessible() {
170        let probe: Probe<i32> = Probe::Inaccessible;
171        assert_eq!(probe.as_ref(), Probe::<&i32>::Inaccessible);
172    }
173}