[pbs-devel] [PATCH pathpatterns v2] match_list: added `matches_mode_lazy()`, which only retrieves file mode if necessary

Gabriel Goller g.goller at proxmox.com
Wed Aug 16 16:03:29 CEST 2023


Added `matches_mode_lazy()` function, which takes a closure that should return the
`file_mode`. The function will go through the patterns and match by path only
until it finds a pattern which does not have `MatchFlag::ANY_FILE_TYPE`, in case
it will call the closure, which will return a `file_mode`. This will ensure that
the closure (which in our case executes `stat()`) will only be run once(at most),
every pattern will only be processed once and that the order of the patterns
will be respected.

Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---

changes v2:
 - updated the `matches_path()` function to take a closure and renamed 
   it to `matches_mode_lazy()`. This allows us to only match once, so we 
   don't need to match before and after `stat()` (without and with 
   `file_mode`) anymore.

changes v1:
 - added `matches_path()` function, which matches by path only and returns an
   error if the `file_mode` is required.

 src/match_list.rs | 203 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 202 insertions(+), 1 deletion(-)

diff --git a/src/match_list.rs b/src/match_list.rs
index c5b14e0..9914130 100644
--- a/src/match_list.rs
+++ b/src/match_list.rs
@@ -1,6 +1,6 @@
 //! Helpers for include/exclude lists.
-
 use bitflags::bitflags;
+use std::{error::Error, fmt};
 
 use crate::PatternFlag;
 
@@ -39,6 +39,17 @@ impl Default for MatchFlag {
     }
 }
 
+#[derive(Debug, PartialEq)]
+pub struct FileModeRequired;
+
+impl fmt::Display for FileModeRequired {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "File mode is required for matching")
+    }
+}
+
+impl Error for FileModeRequired {}
+
 /// A pattern entry. (Glob patterns or literal patterns.)
 // Note:
 // For regex we'd likely use the POSIX extended REs via `regexec(3)`, since we're targetting
@@ -304,12 +315,26 @@ impl MatchEntry {
 
         self.matches_path_exact(path)
     }
+
+    /// Check whether the path contains a matching suffix. Returns an error if a file mode is required.
+    pub fn matches_path<T: AsRef<[u8]>>(&self, path: T) -> Result<bool, FileModeRequired> {
+        self.matches_path_do(path.as_ref())
+    }
+
+    fn matches_path_do(&self, path: &[u8]) -> Result<bool, FileModeRequired> {
+        if !self.flags.contains(MatchFlag::ANY_FILE_TYPE) {
+            return Err(FileModeRequired);
+        }
+
+        Ok(self.matches_path_suffix_do(path))
+    }
 }
 
 #[doc(hidden)]
 pub trait MatchListEntry {
     fn entry_matches(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
     fn entry_matches_exact(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
+    fn entry_matches_path(&self, path: &[u8]) -> Result<Option<MatchType>, FileModeRequired>;
 }
 
 impl MatchListEntry for &'_ MatchEntry {
@@ -328,6 +353,15 @@ impl MatchListEntry for &'_ MatchEntry {
             None
         }
     }
+
+    fn entry_matches_path(&self, path: &[u8]) -> Result<Option<MatchType>, FileModeRequired> {
+        let matches = self.matches_path(path)?;
+        if matches {
+            Ok(Some(self.match_type()))
+        } else {
+            Ok(None)
+        }
+    }
 }
 
 impl MatchListEntry for &'_ &'_ MatchEntry {
@@ -346,6 +380,15 @@ impl MatchListEntry for &'_ &'_ MatchEntry {
             None
         }
     }
+
+    fn entry_matches_path(&self, path: &[u8]) -> Result<Option<MatchType>, FileModeRequired> {
+        let matches = self.matches_path(path)?;
+        if matches {
+            Ok(Some(self.match_type()))
+        } else {
+            Ok(None)
+        }
+    }
 }
 
 /// This provides [`matches`](MatchList::matches) and [`matches_exact`](MatchList::matches_exact)
@@ -374,6 +417,22 @@ pub trait MatchList {
     }
 
     fn matches_exact_do(&self, path: &[u8], file_mode: Option<u32>) -> Option<MatchType>;
+
+    /// Check whether this list contains anything exactly matching the path, returns error if
+    /// `file_mode` is required for exact matching and cannot be retrieved using closure.
+    fn matches_mode_lazy<T: AsRef<[u8]>, E: Error, U: FnMut() -> Result<u32, E>>(
+        &self,
+        path: T,
+        get_file_mode: U,
+    ) -> Result<Option<MatchType>, E> {
+        self.matches_mode_lazy_do(path.as_ref(), get_file_mode)
+    }
+
+    fn matches_mode_lazy_do<E: Error, T: FnMut() -> Result<u32, E>>(
+        &self,
+        path: &[u8],
+        get_file_mode: T,
+    ) -> Result<Option<MatchType>, E>;
 }
 
 impl<'a, T> MatchList for T
@@ -408,6 +467,39 @@ where
 
         None
     }
+
+    fn matches_mode_lazy_do<E: Error, U: FnMut() -> Result<u32, E>>(
+        &self,
+        path: &[u8],
+        mut get_file_mode: U,
+    ) -> Result<Option<MatchType>, E> {
+        // This is an &self method on a `T where T: 'a`.
+        let this: &'a Self = unsafe { std::mem::transmute(self) };
+
+        let mut file_mode: Option<u32> = None;
+        for m in this.into_iter().rev() {
+            if file_mode.is_none() {
+                match m.entry_matches_path(path) {
+                    Ok(mt) => {
+                        if mt.is_some() {
+                            return Ok(mt);
+                        }
+                    }
+                    Err(_) => match get_file_mode() {
+                        Ok(x) => file_mode = Some(x),
+                        Err(e) => return Err(e),
+                    },
+                }
+            }
+            if file_mode.is_some() {
+                let full_match = m.entry_matches(path, file_mode);
+                if full_match.is_some() {
+                    return Ok(full_match);
+                }
+            }
+        }
+        Ok(None)
+    }
 }
 
 #[test]
@@ -530,3 +622,112 @@ fn test_path_relativity() {
     assert_eq!(matchlist.matches("foo/slash", None), None);
     assert_eq!(matchlist.matches("foo/slash-a", None), None);
 }
+
+#[test]
+fn matches_path() {
+    use crate::Pattern;
+
+    #[derive(fmt::Debug, PartialEq)]
+    struct ExampleErr {}
+    impl fmt::Display for ExampleErr {
+        fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
+            todo!()
+        }
+    }
+    impl Error for ExampleErr {}
+
+    let matchlist = vec![
+        MatchEntry::new(Pattern::path("a*").unwrap(), MatchType::Exclude),
+        MatchEntry::new(Pattern::path("b*").unwrap(), MatchType::Exclude),
+    ];
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Err(ExampleErr {})),
+        Ok(Some(MatchType::Exclude))
+    );
+    let mut _test = MatchFlag::ANY_FILE_TYPE;
+    assert_eq!(
+        matchlist.matches_mode_lazy("bhshdf", || {
+            _test = MatchFlag::MATCH_DIRECTORIES;
+            Ok::<u32, ExampleErr>(libc::S_IFDIR)
+        }),
+        Ok(Some(MatchType::Exclude))
+    );
+
+    let matchlist = vec![
+        MatchEntry::new(Pattern::path("a*").unwrap(), MatchType::Exclude)
+            .flags(MatchFlag::MATCH_DIRECTORIES),
+        MatchEntry::new(Pattern::path("b*").unwrap(), MatchType::Exclude),
+    ];
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Err(ExampleErr {})),
+        Err(ExampleErr {})
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("bhshdf", || Err(ExampleErr {})),
+        Ok(Some(MatchType::Exclude))
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Ok::<u32, ExampleErr>(libc::S_IFDIR)),
+        Ok(Some(MatchType::Exclude))
+    );
+
+    let matchlist = vec![
+        MatchEntry::new(Pattern::path("b*").unwrap(), MatchType::Include),
+        MatchEntry::new(Pattern::path("a*").unwrap(), MatchType::Exclude)
+            .flags(MatchFlag::MATCH_DIRECTORIES),
+    ];
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Err(ExampleErr {})),
+        Err(ExampleErr {})
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("bhshdf", || Err(ExampleErr {})),
+        Err(ExampleErr {})
+    );
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Ok::<u32, ExampleErr>(libc::S_IFDIR)),
+        Ok(Some(MatchType::Exclude))
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("bhshdf", || Err(ExampleErr {})),
+        Err(ExampleErr {})
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("bbb", || Ok::<u32, ExampleErr>(libc::S_IFDIR)),
+        Ok(Some(MatchType::Include))
+    );
+
+    let matchlist = vec![
+        MatchEntry::new(Pattern::path("a*").unwrap(), MatchType::Exclude)
+            .flags(MatchFlag::MATCH_DIRECTORIES),
+    ];
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("bbb", || Ok::<u32, ExampleErr>(libc::S_IFDIR)),
+        Ok(None)
+    );
+
+    let matchlist = vec![
+        MatchEntry::new(Pattern::path("a*").unwrap(), MatchType::Exclude)
+            .flags(MatchFlag::MATCH_DIRECTORIES),
+        MatchEntry::new(Pattern::path("b*").unwrap(), MatchType::Exclude)
+            .flags(MatchFlag::MATCH_REGULAR_FILES),
+    ];
+
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Ok::<u32, ExampleErr>(libc::S_IFDIR)),
+        Ok(Some(MatchType::Exclude))
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("ahsjdj", || Ok::<u32, ExampleErr>(libc::S_IFREG)),
+        Ok(None)
+    );
+    assert_eq!(
+        matchlist.matches_mode_lazy("bhsjdj", || Ok::<u32, ExampleErr>(libc::S_IFREG)),
+        Ok(Some(MatchType::Exclude))
+    );
+}
-- 
2.39.2






More information about the pbs-devel mailing list