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 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 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}