diff --git a/src/pxar/encoder.rs b/src/pxar/encoder.rs index c916b67d..b40a7a6b 100644 --- a/src/pxar/encoder.rs +++ b/src/pxar/encoder.rs @@ -736,7 +736,11 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { Err(err) => bail!("fstat {:?} failed - {}", self.full_path(), err), }; - match match_filename(&filename, &stat, &local_match_pattern)? { + match MatchPattern::match_filename_exclude( + &filename, + is_directory(&stat), + &local_match_pattern, + )? { (MatchType::Positive, _) => { let filename_osstr = std::ffi::OsStr::from_bytes(filename.to_bytes()); eprintln!( @@ -1227,32 +1231,6 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { } } -// If there is a match, an updated MatchPattern list to pass to the matched child is returned. -fn match_filename( - filename: &CStr, - stat: &FileStat, - match_pattern: &[MatchPattern], -) -> Result<(MatchType, Vec), Error> { - let mut child_pattern = Vec::new(); - let mut match_state = MatchType::None; - - for pattern in match_pattern { - match pattern.matches_filename(filename, is_directory(&stat))? { - MatchType::None => {} - MatchType::Positive => match_state = MatchType::Positive, - MatchType::Negative => match_state = MatchType::Negative, - match_type => { - if match_state != MatchType::Positive && match_state != MatchType::Negative { - match_state = match_type; - } - child_pattern.push(pattern.get_rest_pattern()); - } - } - } - - Ok((match_state, child_pattern)) -} - fn errno_is_unsupported(errno: Errno) -> bool { match errno { Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true, diff --git a/src/pxar/match_pattern.rs b/src/pxar/match_pattern.rs index a6c22d1c..487b6cad 100644 --- a/src/pxar/match_pattern.rs +++ b/src/pxar/match_pattern.rs @@ -290,6 +290,142 @@ impl MatchPattern { Ok(res) } + + /// Match the given filename against the set of match patterns. + /// + /// A positive match is intended to includes the full subtree (unless another + /// negative match excludes entries later). + /// The `MatchType` together with an updated `MatchPattern` list for passing + /// to the matched child is returned. + /// ``` + /// # use std::ffi::CString; + /// # use self::proxmox_backup::pxar::{MatchPattern, MatchType}; + /// # fn main() -> Result<(), failure::Error> { + /// let patterns = vec![ + /// MatchPattern::from_line(b"some/match/pattern/")?.unwrap(), + /// MatchPattern::from_line(b"to_match/")?.unwrap() + /// ]; + /// let filename = CString::new("some")?; + /// let is_dir = true; + /// let (match_type, child_pattern) = MatchPattern::match_filename_include( + /// &filename, + /// is_dir, + /// &patterns + /// )?; + /// assert_eq!(match_type, MatchType::PartialPositive); + /// /// child pattern will be the same as ... + /// let pattern = MatchPattern::from_line(b"match/pattern/")?.unwrap(); + /// + /// let filename = CString::new("to_match")?; + /// let is_dir = true; + /// let (match_type, child_pattern) = MatchPattern::match_filename_include( + /// &filename, + /// is_dir, + /// &patterns + /// )?; + /// assert_eq!(match_type, MatchType::Positive); + /// /// child pattern will be the same as ... + /// let pattern = MatchPattern::from_line(b"**/*")?.unwrap(); + /// # Ok(()) + /// # } + /// ``` + pub fn match_filename_include( + filename: &CStr, + is_dir: bool, + match_pattern: &[MatchPattern], + ) -> Result<(MatchType, Vec), Error> { + let mut child_pattern = Vec::new(); + let mut match_state = MatchType::None; + + for pattern in match_pattern { + match pattern.matches_filename(filename, is_dir)? { + MatchType::None => continue, + MatchType::Positive => { + match_state = MatchType::Positive; + // Full match so lets include everything below this node + let incl_pattern = MatchPattern::from_line(b"**/*").unwrap().unwrap(); + child_pattern.push(incl_pattern); + } + MatchType::Negative => match_state = MatchType::Negative, + MatchType::PartialPositive => { + if match_state != MatchType::Negative && match_state != MatchType::Positive { + match_state = MatchType::PartialPositive; + } + child_pattern.push(pattern.get_rest_pattern()); + } + MatchType::PartialNegative => { + if match_state == MatchType::PartialPositive { + match_state = MatchType::PartialNegative; + } + child_pattern.push(pattern.get_rest_pattern()); + } + } + } + + Ok((match_state, child_pattern)) + } + + /// Match the given filename against the set of match patterns. + /// + /// A positive match is intended to exclude the full subtree, independent of + /// matches deeper down the tree. + /// The `MatchType` together with an updated `MatchPattern` list for passing + /// to the matched child is returned. + /// ``` + /// # use std::ffi::CString; + /// # use self::proxmox_backup::pxar::{MatchPattern, MatchType}; + /// # fn main() -> Result<(), failure::Error> { + /// let patterns = vec![ + /// MatchPattern::from_line(b"some/match/pattern/")?.unwrap(), + /// MatchPattern::from_line(b"to_match/")?.unwrap() + /// ]; + /// let filename = CString::new("some")?; + /// let is_dir = true; + /// let (match_type, child_pattern) = MatchPattern::match_filename_exclude( + /// &filename, + /// is_dir, + /// &patterns + /// )?; + /// assert_eq!(match_type, MatchType::PartialPositive); + /// /// child pattern will be the same as ... + /// let pattern = MatchPattern::from_line(b"match/pattern/")?.unwrap(); + /// + /// let filename = CString::new("to_match")?; + /// let is_dir = true; + /// let (match_type, child_pattern) = MatchPattern::match_filename_exclude( + /// &filename, + /// is_dir, + /// &patterns + /// )?; + /// assert_eq!(match_type, MatchType::Positive); + /// /// child pattern will be empty + /// # Ok(()) + /// # } + /// ``` + pub fn match_filename_exclude( + filename: &CStr, + is_dir: bool, + match_pattern: &[MatchPattern], + ) -> Result<(MatchType, Vec), Error> { + let mut child_pattern = Vec::new(); + let mut match_state = MatchType::None; + + for pattern in match_pattern { + match pattern.matches_filename(filename, is_dir)? { + MatchType::None => {} + MatchType::Positive => match_state = MatchType::Positive, + MatchType::Negative => match_state = MatchType::Negative, + match_type => { + if match_state != MatchType::Positive && match_state != MatchType::Negative { + match_state = match_type; + } + child_pattern.push(pattern.get_rest_pattern()); + } + } + } + + Ok((match_state, child_pattern)) + } } // Splits the `CStr` slice at the first slash encountered and returns the diff --git a/src/pxar/sequential_decoder.rs b/src/pxar/sequential_decoder.rs index 98644af7..6364fe5a 100644 --- a/src/pxar/sequential_decoder.rs +++ b/src/pxar/sequential_decoder.rs @@ -819,7 +819,11 @@ impl SequentialDecoder { // there are no match pattern. let mut matched = parent_matched; if !match_pattern.is_empty() { - match match_filename(filename, ifmt == libc::S_IFDIR, match_pattern)? { + match MatchPattern::match_filename_include( + &CString::new(filename.as_bytes())?, + ifmt == libc::S_IFDIR, + match_pattern, + )? { (MatchType::None, _) => matched = MatchType::None, (MatchType::Negative, _) => matched = MatchType::Negative, (match_type, pattern) => { @@ -1111,43 +1115,6 @@ impl SequentialDecoder { } } -fn match_filename( - filename: &OsStr, - is_dir: bool, - match_pattern: &[MatchPattern], -) -> Result<(MatchType, Vec), Error> { - let mut child_pattern = Vec::new(); - let mut match_state = MatchType::None; - // read_filename() checks for nul bytes, so it is save to unwrap here - let name = CString::new(filename.as_bytes()).unwrap(); - - for pattern in match_pattern { - match pattern.matches_filename(&name, is_dir)? { - MatchType::None => {} - MatchType::Positive => { - match_state = MatchType::Positive; - let incl_pattern = MatchPattern::from_line(b"**/*").unwrap().unwrap(); - child_pattern.push(incl_pattern.get_rest_pattern()); - } - MatchType::Negative => match_state = MatchType::Negative, - MatchType::PartialPositive => { - if match_state != MatchType::Negative && match_state != MatchType::Positive { - match_state = MatchType::PartialPositive; - } - child_pattern.push(pattern.get_rest_pattern()); - } - MatchType::PartialNegative => { - if match_state == MatchType::PartialPositive { - match_state = MatchType::PartialNegative; - } - child_pattern.push(pattern.get_rest_pattern()); - } - } - } - - Ok((match_state, child_pattern)) -} - fn file_openat( parent: RawFd, filename: &OsStr,