]>
git.proxmox.com Git - pathpatterns.git/blob - src/match_list.rs
1 //! Helpers for include/exclude lists.
3 use bitflags
::bitflags
;
5 use crate::PatternFlag
;
9 /// These flags influence what kind of paths should be matched.
10 pub struct MatchFlag
: u16 {
11 /// Match only a complete entry. The pattern `bar` will not match `/foo/bar`.
12 const ANCHORED
= 0x00_01;
14 const MATCH_DIRECTORIES
= 0x01_00;
15 const MATCH_REGULAR_FILES
= 0x02_00;
16 const MATCH_SYMLINKS
= 0x04_00;
17 const MATCH_SOCKETS
= 0x08_00;
18 const MATCH_FIFOS
= 0x10_00;
19 const MATCH_CHARDEVS
= 0x20_00;
20 const MATCH_BLOCKDEVS
= 0x40_00;
22 MatchFlag
::MATCH_CHARDEVS
.bits() | MatchFlag
::MATCH_BLOCKDEVS
.bits();
24 /// This is the default.
26 MatchFlag
::MATCH_DIRECTORIES
.bits()
27 | MatchFlag
::MATCH_REGULAR_FILES
.bits()
28 | MatchFlag
::MATCH_SYMLINKS
.bits()
29 | MatchFlag
::MATCH_SOCKETS
.bits()
30 | MatchFlag
::MATCH_FIFOS
.bits()
31 | MatchFlag
::MATCH_CHARDEVS
.bits()
32 | MatchFlag
::MATCH_BLOCKDEVS
.bits();
36 impl Default
for MatchFlag
{
37 fn default() -> Self {
42 /// A pattern entry. For now this only contains glob patterns, but we may want to add regex
43 /// patterns or user defined callback functions later on as well.
45 /// For regex we'd likely use the POSIX extended REs via `regexec(3)`, since we're targetting
46 /// command line interfaces and want something command line users are used to.
47 #[derive(Clone, Debug)]
48 pub enum MatchPattern
{
50 Pattern(crate::Pattern
),
56 impl From
<crate::Pattern
> for MatchPattern
{
57 fn from(pattern
: crate::Pattern
) -> Self {
58 MatchPattern
::Pattern(pattern
)
63 pub fn literal(literal
: impl Into
<Vec
<u8>>) -> Self {
64 MatchPattern
::Literal(literal
.into())
68 /// A pattern can be used as an include or an exclude pattern. In a list of `MatchEntry`s, later
69 /// patterns take precedence over earlier patterns and the order of includes vs excludes makes a
71 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
77 /// Convenience helpers
79 pub fn is_include(self) -> bool
{
80 self == MatchType
::Include
83 pub fn is_exclude(self) -> bool
{
84 self == MatchType
::Exclude
88 impl std
::ops
::Not
for MatchType
{
89 type Output
= MatchType
;
91 fn not(self) -> Self::Output
{
93 MatchType
::Include
=> MatchType
::Exclude
,
94 MatchType
::Exclude
=> MatchType
::Include
,
99 /// A single entry in a `MatchList`.
100 #[derive(Clone, Debug)]
101 pub struct MatchEntry
{
102 pattern
: MatchPattern
,
108 /// Create a new match entry.
109 pub fn new
<T
: Into
<MatchPattern
>>(pattern
: T
, ty
: MatchType
) -> Self {
111 pattern
: pattern
.into(),
113 flags
: MatchFlag
::default(),
117 /// Create a new include-type match entry with default flags.
118 pub fn include
<T
: Into
<MatchPattern
>>(pattern
: T
) -> Self {
119 Self::new(pattern
.into(), MatchType
::Include
)
122 /// Create a new exclude-type match entry with default flags.
123 pub fn exclude
<T
: Into
<MatchPattern
>>(pattern
: T
) -> Self {
124 Self::new(pattern
.into(), MatchType
::Exclude
)
127 /// Builder method to set the match flags to a specific value.
128 pub fn flags(mut self, flags
: MatchFlag
) -> Self {
133 /// Builder method to add flag bits to the already present ones.
134 pub fn add_flags(mut self, flags
: MatchFlag
) -> Self {
135 self.flags
.insert(flags
);
139 /// Builder method to remove match flag bits.
140 pub fn remove_flags(mut self, flags
: MatchFlag
) -> Self {
141 self.flags
.remove(flags
);
145 /// Builder method to toggle flag bits.
146 pub fn toggle_flags(mut self, flags
: MatchFlag
) -> Self {
147 self.flags
.toggle(flags
);
152 pub fn match_type(&self) -> MatchType
{
156 /// Non-Builder method to change the match type.
157 pub fn match_type_mut(&mut self) -> &mut MatchType
{
161 /// Directly access the pattern.
162 pub fn pattern(&self) -> &MatchPattern
{
166 /// Non-Builder method to change the pattern.
167 pub fn pattern_mut(&mut self) -> &mut MatchPattern
{
171 /// Directly access the match flags.
172 pub fn match_flags(&mut self) -> MatchFlag
{
176 /// Non-Builder method to change the flags.
177 pub fn match_flags_mut(&mut self) -> &mut MatchFlag
{
181 /// Parse a pattern into a `MatchEntry` while interpreting a leading exclamation mark as
182 /// inversion and trailing slashes to match only directories.
183 pub fn parse_pattern
<T
: AsRef
<[u8]>>(
185 pattern_flags
: PatternFlag
,
187 ) -> Result
<Self, crate::ParseError
> {
188 Self::parse_pattern_do(pattern
.as_ref(), pattern_flags
, ty
)
193 pattern_flags
: PatternFlag
,
195 ) -> Result
<Self, crate::ParseError
> {
196 let (pattern
, ty
) = if pattern
.get(0).copied() == Some(b'
!'
) {
202 let (pattern
, flags
) = match pattern
.iter().rposition(|&b
| b
!= b'
/'
) {
203 Some(pos
) if (pos
+ 1) == pattern
.len() => (pattern
, MatchFlag
::default()),
204 Some(pos
) => (&pattern
[..=pos
], MatchFlag
::MATCH_DIRECTORIES
),
205 None
=> (b
"/".as_ref(), MatchFlag
::MATCH_DIRECTORIES
),
208 Ok(Self::new(crate::Pattern
::new(pattern
, pattern_flags
)?
, ty
).flags(flags
))
211 /// Test this entry's file type restrictions against a file mode retrieved from `stat()`.
212 pub fn matches_mode(&self, file_mode
: u32) -> bool
{
213 // bitflags' `.contains` means ALL bits must be set, if they are all set we don't
214 // need to check the mode...
215 if self.flags
.contains(MatchFlag
::ANY_FILE_TYPE
) {
219 let flag
= match file_mode
& libc
::S_IFMT
{
220 libc
::S_IFDIR
=> MatchFlag
::MATCH_DIRECTORIES
,
221 libc
::S_IFREG
=> MatchFlag
::MATCH_REGULAR_FILES
,
222 libc
::S_IFLNK
=> MatchFlag
::MATCH_SYMLINKS
,
223 libc
::S_IFSOCK
=> MatchFlag
::MATCH_SOCKETS
,
224 libc
::S_IFIFO
=> MatchFlag
::MATCH_FIFOS
,
225 libc
::S_IFCHR
=> MatchFlag
::MATCH_CHARDEVS
,
226 libc
::S_IFBLK
=> MatchFlag
::MATCH_BLOCKDEVS
,
227 _unknown
=> return false,
229 self.flags
.intersects(flag
)
232 /// Test whether this entry's pattern matches any complete suffix of a path.
234 /// For the path `/foo/bar/baz`, this tests whether `baz`, `bar/baz` or `foo/bar/baz` is
236 pub fn matches_path_suffix
<T
: AsRef
<[u8]>>(&self, path
: T
) -> bool
{
237 self.matches_path_suffix_do(path
.as_ref())
240 fn matches_path_suffix_do(&self, path
: &[u8]) -> bool
{
241 if self.flags
.intersects(MatchFlag
::ANCHORED
) {
242 return self.matches_path_exact(path
);
249 for start
in (0..path
.len()).rev() {
250 if path
[start
] == b'
/'
&& self.matches_path_exact(&path
[(start
+ 1)..]) {
256 // we had "foo/bar", so we haven't yet tried to match the whole string:
257 self.matches_path_exact(path
)
263 /// Test whether this entry's pattern matches a path exactly.
264 pub fn matches_path_exact
<T
: AsRef
<[u8]>>(&self, path
: T
) -> bool
{
265 self.matches_path_exact_do(path
.as_ref())
268 fn matches_path_exact_do(&self, path
: &[u8]) -> bool
{
269 match &self.pattern
{
270 MatchPattern
::Pattern(pattern
) => pattern
.matches(path
),
271 MatchPattern
::Literal(literal
) => path
== &literal
[..],
275 /// Check whether the path contains a matching suffix and the file mode match the expected file modes.
276 /// This is a combination of using `.matches_mode()` and `.matches_path_suffix()`.
277 pub fn matches
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> bool
{
278 self.matches_do(path
.as_ref(), file_mode
)
281 fn matches_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> bool
{
282 if let Some(mode
) = file_mode
{
283 if !self.matches_mode(mode
) {
288 self.matches_path_suffix(path
)
291 /// Check whether the path contains a matching suffix and the file mode match the expected file modes.
292 /// This is a combination of using `.matches_mode()` and `.matches_path_exact()`.
293 pub fn matches_exact
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> bool
{
294 self.matches_exact_do(path
.as_ref(), file_mode
)
297 fn matches_exact_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> bool
{
298 if let Some(mode
) = file_mode
{
299 if !self.matches_mode(mode
) {
304 self.matches_path_exact(path
)
309 pub trait MatchListEntry
{
310 fn entry_matches(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
311 fn entry_matches_exact(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
314 impl MatchListEntry
for &'_ MatchEntry
{
315 fn entry_matches(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
316 if self.matches(path
, file_mode
) {
317 Some(self.match_type())
323 fn entry_matches_exact(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
324 if self.matches_exact(path
, file_mode
) {
325 Some(self.match_type())
332 pub trait MatchList
: Sized
{
333 /// Check whether this list contains anything matching a prefix of the specified path, and the
334 /// specified file mode.
335 fn matches
<T
: AsRef
<[u8]>>(self, path
: T
, file_mode
: Option
<u32>) -> Option
<MatchType
> {
336 self.matches_do(path
.as_ref(), file_mode
)
339 fn matches_do(self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
341 /// Check whether this list contains anything exactly matching the path and mode.
342 fn matches_exact
<T
: AsRef
<[u8]>>(
345 file_mode
: Option
<u32>,
346 ) -> Option
<MatchType
> {
347 self.matches_exact_do(path
.as_ref(), file_mode
)
350 fn matches_exact_do(self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
353 impl<T
> MatchList
for T
356 <T
as IntoIterator
>::IntoIter
: DoubleEndedIterator
,
357 <T
as IntoIterator
>::Item
: MatchListEntry
,
359 fn matches_do(self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
360 for m
in self.into_iter().rev() {
361 if let Some(mt
) = m
.entry_matches(path
, file_mode
) {
369 fn matches_exact_do(self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
370 for m
in self.into_iter().rev() {
371 if let Some(mt
) = m
.entry_matches_exact(path
, file_mode
) {
381 fn assert_containers_implement_match_list() {
382 use std
::iter
::FromIterator
;
384 let vec
= vec
![MatchEntry
::include(crate::Pattern
::path("a*").unwrap())];
385 assert_eq
!(vec
.matches("asdf", None
), Some(MatchType
::Include
));
387 // FIXME: ideally we can make this work as well!
388 let vd
= std
::collections
::VecDeque
::<MatchEntry
>::from_iter(vec
.clone());
389 assert_eq
!(vd
.matches("asdf", None
), Some(MatchType
::Include
));
391 let list
: &[MatchEntry
] = &vec
[..];
392 assert_eq
!(list
.matches("asdf", None
), Some(MatchType
::Include
));
394 let list
: Vec
<&MatchEntry
> = vec
.iter().collect();
395 assert_eq
!(list
.matches("asdf", None
), Some(MatchType
::Include
));
399 fn test_file_type_matches() {
400 let matchlist
= vec
![
401 MatchEntry
::parse_pattern("a_dir/", PatternFlag
::PATH_NAME
, MatchType
::Include
)
403 MatchEntry
::parse_pattern("!a_file", PatternFlag
::PATH_NAME
, MatchType
::Include
)
405 .flags(MatchFlag
::MATCH_REGULAR_FILES
),
406 MatchEntry
::parse_pattern("!another_dir//", PatternFlag
::PATH_NAME
, MatchType
::Include
)
410 matchlist
.matches("a_dir", Some(libc
::S_IFDIR
)),
411 Some(MatchType
::Include
)
414 matchlist
.matches("/a_dir", Some(libc
::S_IFDIR
)),
415 Some(MatchType
::Include
)
417 assert_eq
!(matchlist
.matches("/a_dir", Some(libc
::S_IFREG
)), None
);
420 matchlist
.matches("/a_file", Some(libc
::S_IFREG
)),
421 Some(MatchType
::Exclude
)
423 assert_eq
!(matchlist
.matches("/a_file", Some(libc
::S_IFDIR
)), None
);
426 matchlist
.matches("/another_dir", Some(libc
::S_IFDIR
)),
427 Some(MatchType
::Exclude
)
429 assert_eq
!(matchlist
.matches("/another_dir", Some(libc
::S_IFREG
)), None
);
433 fn test_anchored_matches() {
436 let matchlist
= vec
![
437 MatchEntry
::new(Pattern
::path("file-a").unwrap(), MatchType
::Include
),
438 MatchEntry
::new(Pattern
::path("some/path").unwrap(), MatchType
::Include
)
439 .flags(MatchFlag
::ANCHORED
),
442 assert_eq
!(matchlist
.matches("file-a", None
), Some(MatchType
::Include
));
444 matchlist
.matches("another/file-a", None
),
445 Some(MatchType
::Include
)
448 assert_eq
!(matchlist
.matches("some", None
), None
);
449 assert_eq
!(matchlist
.matches("path", None
), None
);
451 matchlist
.matches("some/path", None
),
452 Some(MatchType
::Include
)
454 assert_eq
!(matchlist
.matches("another/some/path", None
), None
);