1use bitflags::bitflags;
4
5use crate::PatternFlag;
6
7#[rustfmt::skip]
8bitflags! {
9 pub struct MatchFlag: u16 {
11 const ANCHORED = 0x00_01;
13
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;
21 const MATCH_DEVICES =
22 MatchFlag::MATCH_CHARDEVS.bits() | MatchFlag::MATCH_BLOCKDEVS.bits();
23
24 const ANY_FILE_TYPE =
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();
33 }
34}
35
36impl Default for MatchFlag {
37 fn default() -> Self {
38 Self::ANY_FILE_TYPE
39 }
40}
41
42#[derive(Clone, Debug)]
48pub enum MatchPattern {
49 Pattern(crate::Pattern),
51
52 Literal(Vec<u8>),
54}
55
56impl From<crate::Pattern> for MatchPattern {
57 fn from(pattern: crate::Pattern) -> Self {
58 MatchPattern::Pattern(pattern)
59 }
60}
61
62impl MatchPattern {
63 pub fn literal(literal: impl Into<Vec<u8>>) -> Self {
64 MatchPattern::Literal(literal.into())
65 }
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum MatchType {
73 Include,
74 Exclude,
75}
76
77impl MatchType {
79 pub fn is_include(self) -> bool {
80 self == MatchType::Include
81 }
82
83 pub fn is_exclude(self) -> bool {
84 self == MatchType::Exclude
85 }
86}
87
88impl std::ops::Not for MatchType {
89 type Output = MatchType;
90
91 fn not(self) -> Self::Output {
92 match self {
93 MatchType::Include => MatchType::Exclude,
94 MatchType::Exclude => MatchType::Include,
95 }
96 }
97}
98
99#[derive(Clone, Debug)]
101pub struct MatchEntry {
102 pattern: MatchPattern,
103 ty: MatchType,
104 flags: MatchFlag,
105}
106
107impl MatchEntry {
108 pub fn new<T: Into<MatchPattern>>(pattern: T, ty: MatchType) -> Self {
110 Self {
111 pattern: pattern.into(),
112 ty,
113 flags: MatchFlag::default(),
114 }
115 }
116
117 pub fn include<T: Into<MatchPattern>>(pattern: T) -> Self {
119 Self::new(pattern.into(), MatchType::Include)
120 }
121
122 pub fn exclude<T: Into<MatchPattern>>(pattern: T) -> Self {
124 Self::new(pattern.into(), MatchType::Exclude)
125 }
126
127 pub fn flags(mut self, flags: MatchFlag) -> Self {
129 self.flags = flags;
130 self
131 }
132
133 pub fn add_flags(mut self, flags: MatchFlag) -> Self {
135 self.flags.insert(flags);
136 self
137 }
138
139 pub fn remove_flags(mut self, flags: MatchFlag) -> Self {
141 self.flags.remove(flags);
142 self
143 }
144
145 pub fn toggle_flags(mut self, flags: MatchFlag) -> Self {
147 self.flags.toggle(flags);
148 self
149 }
150
151 #[inline]
152 pub fn match_type(&self) -> MatchType {
153 self.ty
154 }
155
156 pub fn match_type_mut(&mut self) -> &mut MatchType {
158 &mut self.ty
159 }
160
161 pub fn pattern(&self) -> &MatchPattern {
163 &self.pattern
164 }
165
166 pub fn pattern_mut(&mut self) -> &mut MatchPattern {
168 &mut self.pattern
169 }
170
171 pub fn match_flags(&self) -> MatchFlag {
173 self.flags
174 }
175
176 pub fn match_flags_mut(&mut self) -> &mut MatchFlag {
178 &mut self.flags
179 }
180
181 pub fn parse_pattern<T: AsRef<[u8]>>(
184 pattern: T,
185 pattern_flags: PatternFlag,
186 ty: MatchType,
187 ) -> Result<Self, crate::ParseError> {
188 Self::parse_pattern_do(pattern.as_ref(), pattern_flags, ty)
189 }
190
191 fn parse_pattern_do(
192 pattern: &[u8],
193 pattern_flags: PatternFlag,
194 ty: MatchType,
195 ) -> Result<Self, crate::ParseError> {
196 let (pattern, ty) = if pattern.get(0).copied() == Some(b'!') {
197 (&pattern[1..], !ty)
198 } else {
199 (pattern, ty)
200 };
201
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),
206 };
207
208 Ok(Self::new(crate::Pattern::new(pattern, pattern_flags)?, ty).flags(flags))
209 }
210
211 pub fn matches_mode(&self, file_mode: u32) -> bool {
213 if self.flags.contains(MatchFlag::ANY_FILE_TYPE) {
216 return true;
217 }
218
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,
228 };
229 self.flags.intersects(flag)
230 }
231
232 pub fn matches_path_suffix<T: AsRef<[u8]>>(&self, path: T) -> bool {
237 self.matches_path_suffix_do(path.as_ref())
238 }
239
240 fn matches_path_suffix_do(&self, path: &[u8]) -> bool {
241 if self.flags.intersects(MatchFlag::ANCHORED) {
242 return self.matches_path_exact(path);
243 }
244
245 if path.is_empty() {
246 return false;
247 }
248
249 for start in (0..path.len()).rev() {
250 if path[start] == b'/' && self.matches_path_exact(&path[(start + 1)..]) {
251 return true;
252 }
253 }
254
255 self.matches_path_exact(path)
257 }
258
259 pub fn matches_path_exact<T: AsRef<[u8]>>(&self, path: T) -> bool {
261 self.matches_path_exact_do(path.as_ref())
262 }
263
264 fn matches_path_exact_do(&self, path: &[u8]) -> bool {
265 match &self.pattern {
266 MatchPattern::Pattern(pattern) => pattern.matches(path),
267 MatchPattern::Literal(literal) => path == &literal[..],
268 }
269 }
270
271 pub fn matches<T: AsRef<[u8]>>(&self, path: T, file_mode: Option<u32>) -> bool {
274 self.matches_do(path.as_ref(), file_mode)
275 }
276
277 fn matches_do(&self, path: &[u8], file_mode: Option<u32>) -> bool {
278 if let Some(mode) = file_mode {
279 if !self.matches_mode(mode) {
280 return false;
281 }
282 }
283
284 self.matches_path_suffix(path)
285 }
286
287 pub fn matches_exact<T: AsRef<[u8]>>(&self, path: T, file_mode: Option<u32>) -> bool {
290 self.matches_exact_do(path.as_ref(), file_mode)
291 }
292
293 fn matches_exact_do(&self, path: &[u8], file_mode: Option<u32>) -> bool {
294 if let Some(mode) = file_mode {
295 if !self.matches_mode(mode) {
296 return false;
297 }
298 }
299
300 self.matches_path_exact(path)
301 }
302}
303
304#[doc(hidden)]
305pub trait MatchListEntry {
306 fn entry_matches(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
307 fn entry_matches_exact(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
308}
309
310impl MatchListEntry for &'_ MatchEntry {
311 fn entry_matches(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
312 if self.matches(path, file_mode) {
313 Some(self.match_type())
314 } else {
315 None
316 }
317 }
318
319 fn entry_matches_exact(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
320 if self.matches_exact(path, file_mode) {
321 Some(self.match_type())
322 } else {
323 None
324 }
325 }
326}
327
328impl MatchListEntry for &'_ &'_ MatchEntry {
329 fn entry_matches(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
330 if self.matches(path, file_mode) {
331 Some(self.match_type())
332 } else {
333 None
334 }
335 }
336
337 fn entry_matches_exact(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
338 if self.matches_exact(path, file_mode) {
339 Some(self.match_type())
340 } else {
341 None
342 }
343 }
344}
345
346pub trait MatchList {
354 fn matches<T: AsRef<[u8]>>(&self, path: T, file_mode: Option<u32>) -> Option<MatchType> {
357 self.matches_do(path.as_ref(), file_mode)
358 }
359
360 fn matches_do(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
361
362 fn matches_exact<T: AsRef<[u8]>>(
364 &self,
365 path: T,
366 file_mode: Option<u32>,
367 ) -> Option<MatchType> {
368 self.matches_exact_do(path.as_ref(), file_mode)
369 }
370
371 fn matches_exact_do(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
372}
373
374impl<'a, T> MatchList for T
375where
376 T: 'a + ?Sized,
377 &'a T: IntoIterator,
378 <&'a T as IntoIterator>::IntoIter: DoubleEndedIterator,
379 <&'a T as IntoIterator>::Item: MatchListEntry,
380{
381 fn matches_do(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
382 let this: &'a Self = unsafe { std::mem::transmute(self) };
384
385 for m in this.into_iter().rev() {
386 if let Some(mt) = m.entry_matches(path, file_mode) {
387 return Some(mt);
388 }
389 }
390
391 None
392 }
393
394 fn matches_exact_do(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType> {
395 let this: &'a Self = unsafe { std::mem::transmute(self) };
397
398 for m in this.into_iter().rev() {
399 if let Some(mt) = m.entry_matches_exact(path, file_mode) {
400 return Some(mt);
401 }
402 }
403
404 None
405 }
406}
407
408#[test]
409fn assert_containers_implement_match_list() {
410 use std::iter::FromIterator;
411
412 let vec = vec![MatchEntry::include(crate::Pattern::path("a*").unwrap())];
413 assert_eq!(vec.matches("asdf", None), Some(MatchType::Include));
414
415 let vd = std::collections::VecDeque::<MatchEntry>::from_iter(vec.clone());
417 assert_eq!(vd.matches("asdf", None), Some(MatchType::Include));
418
419 let list: &[MatchEntry] = &vec[..];
420 assert_eq!(list.matches("asdf", None), Some(MatchType::Include));
421
422 let list: Vec<&MatchEntry> = vec.iter().collect();
423 assert_eq!(list.matches("asdf", None), Some(MatchType::Include));
424
425 let list: &[&MatchEntry] = &list[..];
426 assert_eq!(list.matches("asdf", None), Some(MatchType::Include));
427}
428
429#[test]
430fn test_file_type_matches() {
431 let matchlist = vec![
432 MatchEntry::parse_pattern("a_dir/", PatternFlag::PATH_NAME, MatchType::Include)
433 .unwrap(),
434 MatchEntry::parse_pattern("!a_file", PatternFlag::PATH_NAME, MatchType::Include)
435 .unwrap()
436 .flags(MatchFlag::MATCH_REGULAR_FILES),
437 MatchEntry::parse_pattern("!another_dir//", PatternFlag::PATH_NAME, MatchType::Include)
438 .unwrap(),
439 ];
440 assert_eq!(
441 matchlist.matches("a_dir", Some(libc::S_IFDIR)),
442 Some(MatchType::Include)
443 );
444 assert_eq!(
445 matchlist.matches("/a_dir", Some(libc::S_IFDIR)),
446 Some(MatchType::Include)
447 );
448 assert_eq!(matchlist.matches("/a_dir", Some(libc::S_IFREG)), None);
449
450 assert_eq!(
451 matchlist.matches("/a_file", Some(libc::S_IFREG)),
452 Some(MatchType::Exclude)
453 );
454 assert_eq!(matchlist.matches("/a_file", Some(libc::S_IFDIR)), None);
455
456 assert_eq!(
457 matchlist.matches("/another_dir", Some(libc::S_IFDIR)),
458 Some(MatchType::Exclude)
459 );
460 assert_eq!(matchlist.matches("/another_dir", Some(libc::S_IFREG)), None);
461}
462
463#[test]
464fn test_anchored_matches() {
465 use crate::Pattern;
466
467 let matchlist = vec![
468 MatchEntry::new(Pattern::path("file-a").unwrap(), MatchType::Include),
469 MatchEntry::new(Pattern::path("some/path").unwrap(), MatchType::Include)
470 .flags(MatchFlag::ANCHORED),
471 ];
472
473 assert_eq!(matchlist.matches("file-a", None), Some(MatchType::Include));
474 assert_eq!(
475 matchlist.matches("another/file-a", None),
476 Some(MatchType::Include)
477 );
478
479 assert_eq!(matchlist.matches("some", None), None);
480 assert_eq!(matchlist.matches("path", None), None);
481 assert_eq!(
482 matchlist.matches("some/path", None),
483 Some(MatchType::Include)
484 );
485 assert_eq!(matchlist.matches("another/some/path", None), None);
486}
487
488#[test]
489fn test_literal_matches() {
490 let matchlist = vec![
491 MatchEntry::new(MatchPattern::Literal(b"/bin/mv".to_vec()), MatchType::Include),
492 ];
493 assert_eq!(matchlist.matches("/bin/mv", None), Some(MatchType::Include));
494}