1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use core::fmt;
use once_cell::sync::Lazy;

/// Retain reference to value array.
pub static FSACTION_VALUES: Lazy<Vec<FsAction>> = Lazy::new(FsAction::values);

/// File system actions, e.g. read, write, etc.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FsAction {
    // POSIX style
    None,
    Execute,
    Write,
    WriteExecute,
    Read,
    ReadExecute,
    ReadWrite,
    All,
}

impl FsAction {
    fn values() -> Vec<Self> {
        vec![
            Self::None,
            Self::Execute,
            Self::Write,
            Self::WriteExecute,
            Self::Read,
            Self::ReadExecute,
            Self::ReadWrite,
            Self::All,
        ]
    }

    pub fn ordinal(&self) -> usize {
        *self as usize
    }

    /// Symbolic representation
    pub fn symbol(&self) -> String {
        self.to_string()
    }

    /// Return true if this action implies that action.
    pub fn implies(&self, that: Option<&FsAction>) -> bool {
        match that {
            Some(that) => self.ordinal() & that.ordinal() == that.ordinal(),
            None => false,
        }
    }

    /// AND operation.
    pub fn and(&self, that: &FsAction) -> FsAction {
        FSACTION_VALUES[self.ordinal() & that.ordinal()]
    }

    /// OR operation.
    pub fn or(&self, that: &FsAction) -> FsAction {
        FSACTION_VALUES[self.ordinal() | that.ordinal()]
    }

    /// NOT operation.
    pub fn not(&self) -> FsAction {
        FSACTION_VALUES[7 - self.ordinal()]
    }

    /// Get the FsAction enum for String representation of permissions
    pub fn get_fs_action(permission: &str) -> Option<FsAction> {
        FSACTION_VALUES
            .iter()
            .filter(|a| a.symbol() == permission)
            .next()
            .cloned()
    }
}

impl fmt::Display for FsAction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::None => write!(f, "---"),
            Self::Execute => write!(f, "--x"),
            Self::Write => write!(f, "-w-"),
            Self::WriteExecute => write!(f, "-wx"),
            Self::Read => write!(f, "r--"),
            Self::ReadExecute => write!(f, "r-x"),
            Self::ReadWrite => write!(f, "rw-"),
            Self::All => write!(f, "rwx"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fs_action() {
        // implies
        FsAction::values()
            .iter()
            .for_each(|a| assert!(FsAction::All.implies(Some(a))));
        FsAction::values().iter().for_each(|a| {
            assert!(if *a == FsAction::None {
                FsAction::None.implies(Some(a))
            } else {
                !FsAction::None.implies(Some(a))
            });
        });
        FsAction::values().iter().for_each(|a| {
            assert!(if *a == FsAction::ReadExecute
                || *a == FsAction::Read
                || *a == FsAction::Execute
                || *a == FsAction::None
            {
                FsAction::ReadExecute.implies(Some(a))
            } else {
                !FsAction::ReadExecute.implies(Some(a))
            });
        });

        // masks
        assert!(FsAction::Execute == FsAction::Execute.and(&FsAction::ReadExecute));
        assert!(FsAction::Read == FsAction::Read.and(&FsAction::ReadExecute));
        assert!(FsAction::None == FsAction::Write.and(&FsAction::ReadExecute));

        assert!(FsAction::Read == FsAction::ReadExecute.and(&FsAction::ReadWrite));
        assert!(FsAction::None == FsAction::ReadExecute.and(&FsAction::Write));
        assert!(FsAction::WriteExecute == FsAction::All.and(&FsAction::WriteExecute));
    }
}