2018-06-30 14:55:33 +02:00
|
|
|
use anyhow::{anyhow, Result};
|
2018-06-30 21:57:20 +02:00
|
|
|
use std::fs;
|
2018-06-30 14:55:33 +02:00
|
|
|
|
2022-11-03 05:39:22 +01:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2018-06-30 14:55:33 +02:00
|
|
|
pub struct OwnerFilter {
|
2018-06-30 23:45:28 +02:00
|
|
|
uid: Check<u32>,
|
|
|
|
gid: Check<u32>,
|
|
|
|
}
|
|
|
|
|
2022-11-03 05:39:22 +01:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2018-06-30 23:45:28 +02:00
|
|
|
enum Check<T> {
|
|
|
|
Equal(T),
|
|
|
|
NotEq(T),
|
|
|
|
Ignore,
|
2018-06-30 14:55:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl OwnerFilter {
|
2022-11-03 05:39:22 +01:00
|
|
|
const IGNORE: Self = OwnerFilter {
|
|
|
|
uid: Check::Ignore,
|
|
|
|
gid: Check::Ignore,
|
|
|
|
};
|
|
|
|
|
2020-05-13 09:10:43 +02:00
|
|
|
/// Parses an owner constraint
|
|
|
|
/// Returns an error if the string is invalid
|
|
|
|
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
|
2022-11-03 05:39:22 +01:00
|
|
|
pub fn from_string(input: &str) -> Result<Self> {
|
2018-06-30 14:55:33 +02:00
|
|
|
let mut it = input.split(':');
|
|
|
|
let (fst, snd) = (it.next(), it.next());
|
|
|
|
|
2020-05-13 09:10:43 +02:00
|
|
|
if it.next().is_some() {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"more than one ':' present in owner string '{}'. See 'fd --help'.",
|
|
|
|
input
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2018-07-01 00:07:04 +02:00
|
|
|
let uid = Check::parse(fst, |s| {
|
|
|
|
s.parse()
|
|
|
|
.ok()
|
|
|
|
.or_else(|| users::get_user_by_name(s).map(|user| user.uid()))
|
|
|
|
.ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
|
|
|
|
})?;
|
|
|
|
let gid = Check::parse(snd, |s| {
|
|
|
|
s.parse()
|
|
|
|
.ok()
|
|
|
|
.or_else(|| users::get_group_by_name(s).map(|group| group.gid()))
|
|
|
|
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
|
|
|
|
})?;
|
2018-06-30 14:55:33 +02:00
|
|
|
|
2022-11-03 05:39:22 +01:00
|
|
|
Ok(OwnerFilter { uid, gid })
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If self is a no-op (ignore both uid and gid) then return `None`, otherwise wrap in a `Some`
|
|
|
|
pub fn filter_ignore(self) -> Option<Self> {
|
|
|
|
if self == Self::IGNORE {
|
|
|
|
None
|
2018-06-30 14:55:33 +02:00
|
|
|
} else {
|
2022-11-03 05:39:22 +01:00
|
|
|
Some(self)
|
2018-06-30 14:55:33 +02:00
|
|
|
}
|
|
|
|
}
|
2018-06-30 21:57:20 +02:00
|
|
|
|
|
|
|
pub fn matches(&self, md: &fs::Metadata) -> bool {
|
|
|
|
use std::os::unix::fs::MetadataExt;
|
|
|
|
|
2018-06-30 23:45:28 +02:00
|
|
|
self.uid.check(md.uid()) && self.gid.check(md.gid())
|
|
|
|
}
|
|
|
|
}
|
2018-06-30 21:57:20 +02:00
|
|
|
|
2018-06-30 23:45:28 +02:00
|
|
|
impl<T: PartialEq> Check<T> {
|
|
|
|
fn check(&self, v: T) -> bool {
|
|
|
|
match self {
|
|
|
|
Check::Equal(x) => v == *x,
|
|
|
|
Check::NotEq(x) => v != *x,
|
|
|
|
Check::Ignore => true,
|
|
|
|
}
|
2018-06-30 21:57:20 +02:00
|
|
|
}
|
2018-07-01 00:07:04 +02:00
|
|
|
|
|
|
|
fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
|
|
|
|
where
|
|
|
|
F: Fn(&str) -> Result<T>,
|
|
|
|
{
|
|
|
|
let (s, equality) = match s {
|
|
|
|
Some("") | None => return Ok(Check::Ignore),
|
|
|
|
Some(s) if s.starts_with('!') => (&s[1..], false),
|
|
|
|
Some(s) => (s, true),
|
|
|
|
};
|
|
|
|
|
|
|
|
f(s).map(|x| {
|
|
|
|
if equality {
|
|
|
|
Check::Equal(x)
|
|
|
|
} else {
|
|
|
|
Check::NotEq(x)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2018-06-30 14:55:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod owner_parsing {
|
|
|
|
use super::OwnerFilter;
|
|
|
|
|
|
|
|
macro_rules! owner_tests {
|
|
|
|
($($name:ident: $value:expr => $result:pat,)*) => {
|
|
|
|
$(
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
|
|
|
let o = OwnerFilter::from_string($value);
|
|
|
|
match o {
|
|
|
|
$result => {},
|
|
|
|
_ => panic!("{:?} does not match {}", o, stringify!($result)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)*
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-06-30 23:45:28 +02:00
|
|
|
use super::Check::*;
|
2018-06-30 14:55:33 +02:00
|
|
|
owner_tests! {
|
2022-11-03 05:39:22 +01:00
|
|
|
empty: "" => Ok(OwnerFilter::IGNORE),
|
|
|
|
uid_only: "5" => Ok(OwnerFilter { uid: Equal(5), gid: Ignore }),
|
|
|
|
uid_gid: "9:3" => Ok(OwnerFilter { uid: Equal(9), gid: Equal(3) }),
|
|
|
|
gid_only: ":8" => Ok(OwnerFilter { uid: Ignore, gid: Equal(8) }),
|
|
|
|
colon_only: ":" => Ok(OwnerFilter::IGNORE),
|
|
|
|
trailing: "5:" => Ok(OwnerFilter { uid: Equal(5), gid: Ignore }),
|
|
|
|
|
|
|
|
uid_negate: "!5" => Ok(OwnerFilter { uid: NotEq(5), gid: Ignore }),
|
|
|
|
both_negate:"!4:!3" => Ok(OwnerFilter { uid: NotEq(4), gid: NotEq(3) }),
|
|
|
|
uid_not_gid:"6:!8" => Ok(OwnerFilter { uid: Equal(6), gid: NotEq(8) }),
|
2020-05-13 09:10:43 +02:00
|
|
|
|
|
|
|
more_colons:"3:5:" => Err(_),
|
|
|
|
only_colons:"::" => Err(_),
|
2018-06-30 14:55:33 +02:00
|
|
|
}
|
|
|
|
}
|