From: Gabriel Goller <g.goller@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH pathpatterns] match_list: added `matches_path()` function, which matches only the path
Date: Wed, 9 Aug 2023 12:19:12 +0200 [thread overview]
Message-ID: <20230809101913.81818-1-g.goller@proxmox.com> (raw)
Added `matches_path()` function, which only matches against the path and returns
an error if a file_mode pattern is found/needed in the matching list. This is
useful when we want to check if a file is excluded before running `stat()` on
the file to get the file_mode (which could fail).
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/match_list.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 158 insertions(+), 1 deletion(-)
diff --git a/src/match_list.rs b/src/match_list.rs
index c5b14e0..acad328 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::fmt;
use crate::PatternFlag;
@@ -39,6 +39,17 @@ impl Default for MatchFlag {
}
}
+#[derive(Debug, PartialEq)]
+pub struct FileModeRequiredForMatching;
+
+impl fmt::Display for FileModeRequiredForMatching {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "File mode is required for matching")
+ }
+}
+
+impl std::error::Error for FileModeRequiredForMatching {}
+
/// 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,32 @@ 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, FileModeRequiredForMatching> {
+ self.matches_path_do(path.as_ref())
+ }
+
+ fn matches_path_do(&self, path: &[u8]) -> Result<bool, FileModeRequiredForMatching> {
+ if !self.flags.contains(MatchFlag::ANY_FILE_TYPE) {
+ return Err(FileModeRequiredForMatching);
+ }
+
+ 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>, FileModeRequiredForMatching>;
}
impl MatchListEntry for &'_ MatchEntry {
@@ -328,6 +359,21 @@ impl MatchListEntry for &'_ MatchEntry {
None
}
}
+
+ fn entry_matches_path(
+ &self,
+ path: &[u8],
+ ) -> Result<Option<MatchType>, FileModeRequiredForMatching> {
+ if let Ok(b) = self.matches_path(path) {
+ if b {
+ Ok(Some(self.match_type()))
+ } else {
+ Ok(None)
+ }
+ } else {
+ Err(FileModeRequiredForMatching)
+ }
+ }
}
impl MatchListEntry for &'_ &'_ MatchEntry {
@@ -346,6 +392,21 @@ impl MatchListEntry for &'_ &'_ MatchEntry {
None
}
}
+
+ fn entry_matches_path(
+ &self,
+ path: &[u8],
+ ) -> Result<Option<MatchType>, FileModeRequiredForMatching> {
+ if let Ok(b) = self.matches_path(path) {
+ if b {
+ Ok(Some(self.match_type()))
+ } else {
+ Ok(None)
+ }
+ } else {
+ Err(FileModeRequiredForMatching)
+ }
+ }
}
/// This provides [`matches`](MatchList::matches) and [`matches_exact`](MatchList::matches_exact)
@@ -374,6 +435,20 @@ 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.
+ fn matches_path<T: AsRef<[u8]>>(
+ &self,
+ path: T,
+ ) -> Result<Option<MatchType>, FileModeRequiredForMatching> {
+ self.matches_path_do(path.as_ref())
+ }
+
+ fn matches_path_do(
+ &self,
+ path: &[u8],
+ ) -> Result<Option<MatchType>, FileModeRequiredForMatching>;
}
impl<'a, T> MatchList for T
@@ -408,6 +483,24 @@ where
None
}
+
+ fn matches_path_do(
+ &self,
+ path: &[u8],
+ ) -> Result<Option<MatchType>, FileModeRequiredForMatching> {
+ // This is an &self method on a `T where T: 'a`.
+ let this: &'a Self = unsafe { std::mem::transmute(self) };
+
+ for m in this.into_iter().rev() {
+ if let Ok(mt) = m.entry_matches_path(path) {
+ if mt.is_some() {
+ return Ok(mt);
+ }
+ }
+ }
+
+ Err(FileModeRequiredForMatching)
+ }
}
#[test]
@@ -530,3 +623,67 @@ fn test_path_relativity() {
assert_eq!(matchlist.matches("foo/slash", None), None);
assert_eq!(matchlist.matches("foo/slash-a", None), None);
}
+
+#[test]
+fn test_matches_path() {
+ let vec = vec![
+ MatchEntry::include(crate::Pattern::path("as*").unwrap()),
+ MatchEntry::include(crate::Pattern::path("a*").unwrap()),
+ ];
+ assert_eq!(vec.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let list: &[MatchEntry] = &vec[..];
+ assert_eq!(list.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let list: Vec<&MatchEntry> = vec.iter().collect();
+ assert_eq!(list.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let list: &[&MatchEntry] = &list[..];
+ assert_eq!(list.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let vec = vec![
+ MatchEntry::include(crate::Pattern::path("a*").unwrap()),
+ MatchEntry::include(crate::Pattern::path("a*").unwrap())
+ .flags(MatchFlag::MATCH_REGULAR_FILES),
+ ];
+ assert_eq!(vec.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let vec = vec![
+ MatchEntry::include(crate::Pattern::path("a*").unwrap()),
+ MatchEntry::include(crate::Pattern::path("asdf").unwrap()),
+ ];
+ assert_eq!(vec.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let list: &[MatchEntry] = &vec[..];
+ assert_eq!(list.matches_path("azdf"), Ok(Some(MatchType::Include)));
+
+ let vec = vec![
+ MatchEntry::include(crate::Pattern::path("a*").unwrap()).flags(MatchFlag::ANY_FILE_TYPE),
+ MatchEntry::include(crate::Pattern::path("asdf").unwrap()).flags(MatchFlag::ANY_FILE_TYPE),
+ ];
+ assert_eq!(vec.matches_path("asdf"), Ok(Some(MatchType::Include)));
+ assert_eq!(vec.matches_path("adbb"), Ok(Some(MatchType::Include)));
+}
+
+#[test]
+fn test_matches_path_error() {
+ let vec = vec![
+ MatchEntry::include(crate::Pattern::path("a*").unwrap())
+ .flags(MatchFlag::MATCH_REGULAR_FILES),
+ MatchEntry::include(crate::Pattern::path("asdf").unwrap()),
+ ];
+ assert_eq!(vec.matches_path("asdf"), Ok(Some(MatchType::Include)));
+
+ let list: &[MatchEntry] = &vec[..];
+ assert_eq!(list.matches_path("azdf"), Err(FileModeRequiredForMatching));
+
+ let list: Vec<&MatchEntry> = vec.iter().collect();
+ assert_eq!(list.matches_path("abdf"), Err(FileModeRequiredForMatching));
+
+ let list: &[&MatchEntry] = &list[..];
+ assert_eq!(list.matches_path("acdf"), Err(FileModeRequiredForMatching));
+
+ let vec = vec![MatchEntry::include(crate::Pattern::path("a*").unwrap())
+ .flags(MatchFlag::MATCH_DIRECTORIES)];
+ assert_eq!(vec.matches_path("asdf"), Err(FileModeRequiredForMatching));
+}
--
2.39.2
next reply other threads:[~2023-08-09 10:19 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-09 10:19 Gabriel Goller [this message]
2023-08-09 10:19 ` [pbs-devel] [PATCH proxmox-backup v3 1/1] fix #4380: check if file is excluded before running `stat()` Gabriel Goller
2023-08-11 8:51 ` Wolfgang Bumiller
2023-08-14 7:41 ` Gabriel Goller
2023-08-11 8:26 ` [pbs-devel] [PATCH pathpatterns] match_list: added `matches_path()` function, which matches only the path Wolfgang Bumiller
2023-08-11 8:32 ` Wolfgang Bumiller
2023-08-11 8:38 ` Wolfgang Bumiller
2023-08-14 9:32 ` Gabriel Goller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230809101913.81818-1-g.goller@proxmox.com \
--to=g.goller@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.