fd_lib/filter/
owner.rs

1use anyhow::{anyhow, Result};
2use nix::unistd::{Group, User};
3use std::fs;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct OwnerFilter {
7    uid: Check<u32>,
8    gid: Check<u32>,
9}
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12enum Check<T> {
13    Equal(T),
14    NotEq(T),
15    Ignore,
16}
17
18impl OwnerFilter {
19    const IGNORE: Self = OwnerFilter {
20        uid: Check::Ignore,
21        gid: Check::Ignore,
22    };
23
24    /// Parses an owner constraint
25    /// Returns an error if the string is invalid
26    /// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
27    pub fn from_string(input: &str) -> Result<Self> {
28        let mut it = input.split(':');
29        let (fst, snd) = (it.next(), it.next());
30
31        if it.next().is_some() {
32            return Err(anyhow!(
33                "more than one ':' present in owner string '{}'. See 'fd --help'.",
34                input
35            ));
36        }
37
38        let uid = Check::parse(fst, |s| {
39            if let Ok(uid) = s.parse() {
40                Ok(uid)
41            } else {
42                User::from_name(s)?
43                    .map(|user| user.uid.as_raw())
44                    .ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
45            }
46        })?;
47        let gid = Check::parse(snd, |s| {
48            if let Ok(gid) = s.parse() {
49                Ok(gid)
50            } else {
51                Group::from_name(s)?
52                    .map(|group| group.gid.as_raw())
53                    .ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
54            }
55        })?;
56
57        Ok(OwnerFilter { uid, gid })
58    }
59
60    /// If self is a no-op (ignore both uid and gid) then return `None`, otherwise wrap in a `Some`
61    pub fn filter_ignore(self) -> Option<Self> {
62        if self == Self::IGNORE {
63            None
64        } else {
65            Some(self)
66        }
67    }
68
69    pub fn matches(&self, md: &fs::Metadata) -> bool {
70        use std::os::unix::fs::MetadataExt;
71
72        self.uid.check(md.uid()) && self.gid.check(md.gid())
73    }
74}
75
76impl<T: PartialEq> Check<T> {
77    fn check(&self, v: T) -> bool {
78        match self {
79            Check::Equal(x) => v == *x,
80            Check::NotEq(x) => v != *x,
81            Check::Ignore => true,
82        }
83    }
84
85    fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
86    where
87        F: Fn(&str) -> Result<T>,
88    {
89        let (s, equality) = match s {
90            Some("") | None => return Ok(Check::Ignore),
91            Some(s) if s.starts_with('!') => (&s[1..], false),
92            Some(s) => (s, true),
93        };
94
95        f(s).map(|x| {
96            if equality {
97                Check::Equal(x)
98            } else {
99                Check::NotEq(x)
100            }
101        })
102    }
103}
104
105#[cfg(test)]
106mod owner_parsing {
107    use super::OwnerFilter;
108
109    macro_rules! owner_tests {
110        ($($name:ident: $value:expr => $result:pat,)*) => {
111            $(
112                #[test]
113                fn $name() {
114                    let o = OwnerFilter::from_string($value);
115                    match o {
116                        $result => {},
117                        _ => panic!("{:?} does not match {}", o, stringify!($result)),
118                    }
119                }
120            )*
121        };
122    }
123
124    use super::Check::*;
125    owner_tests! {
126        empty:      ""      => Ok(OwnerFilter::IGNORE),
127        uid_only:   "5"     => Ok(OwnerFilter { uid: Equal(5), gid: Ignore     }),
128        uid_gid:    "9:3"   => Ok(OwnerFilter { uid: Equal(9), gid: Equal(3)   }),
129        gid_only:   ":8"    => Ok(OwnerFilter { uid: Ignore,   gid: Equal(8)   }),
130        colon_only: ":"     => Ok(OwnerFilter::IGNORE),
131        trailing:   "5:"    => Ok(OwnerFilter { uid: Equal(5), gid: Ignore     }),
132
133        uid_negate: "!5"    => Ok(OwnerFilter { uid: NotEq(5), gid: Ignore     }),
134        both_negate:"!4:!3" => Ok(OwnerFilter { uid: NotEq(4), gid: NotEq(3)   }),
135        uid_not_gid:"6:!8"  => Ok(OwnerFilter { uid: Equal(6), gid: NotEq(8)   }),
136
137        more_colons:"3:5:"  => Err(_),
138        only_colons:"::"    => Err(_),
139    }
140}