diff --git a/Cargo.toml b/Cargo.toml index 99b5b330..d63da939 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ pam = "0.7" pam-sys = "0.5" percent-encoding = "2.1" pin-utils = "0.1.0" -pathpatterns = "0.1.0" +pathpatterns = "0.1.1" proxmox = { version = "0.1.39", features = [ "sortable-macro", "api-macro" ] } #proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] } #proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro" ] } diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index e9ae1d95..29ee562b 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -821,10 +821,10 @@ async fn create_backup( let empty = Vec::new(); let exclude_args = param["exclude"].as_array().unwrap_or(&empty); - let mut exclude_list = Vec::with_capacity(exclude_args.len()); + let mut pattern_list = Vec::with_capacity(exclude_args.len()); for entry in exclude_args { let entry = entry.as_str().ok_or_else(|| format_err!("Invalid pattern string slice"))?; - exclude_list.push( + pattern_list.push( MatchEntry::parse_pattern(entry, PatternFlag::PATH_NAME, MatchType::Exclude) .map_err(|err| format_err!("invalid exclude pattern entry: {}", err))? ); @@ -971,7 +971,7 @@ async fn create_backup( skip_lost_and_found, crypt_config.clone(), catalog.clone(), - exclude_list.clone(), + pattern_list.clone(), entries_max as usize, ).await?; manifest.add_file(target, stats.size, stats.csum)?; diff --git a/src/bin/pxar.rs b/src/bin/pxar.rs index e99550f7..fc5b8046 100644 --- a/src/bin/pxar.rs +++ b/src/bin/pxar.rs @@ -275,16 +275,16 @@ fn create_archive( exclude: Option>, entries_max: isize, ) -> Result<(), Error> { - let exclude_list = { + let pattern_list = { let input = exclude.unwrap_or_else(Vec::new); - let mut exclude = Vec::with_capacity(input.len()); + let mut pattern_list = Vec::with_capacity(input.len()); for entry in input { - exclude.push( + pattern_list.push( MatchEntry::parse_pattern(entry, PatternFlag::PATH_NAME, MatchType::Exclude) .map_err(|err| format_err!("error in exclude pattern: {}", err))?, ); } - exclude + pattern_list }; let device_set = if all_file_systems { @@ -332,7 +332,7 @@ fn create_archive( proxmox_backup::pxar::create_archive( dir, writer, - exclude_list, + pattern_list, feature_flags, device_set, true, diff --git a/src/client/pxar_backup_stream.rs b/src/client/pxar_backup_stream.rs index c651cc79..11f5d924 100644 --- a/src/client/pxar_backup_stream.rs +++ b/src/client/pxar_backup_stream.rs @@ -43,7 +43,7 @@ impl PxarBackupStream { _verbose: bool, skip_lost_and_found: bool, catalog: Arc>>, - exclude_pattern: Vec, + patterns: Vec, entries_max: usize, ) -> Result { let (tx, rx) = std::sync::mpsc::sync_channel(10); @@ -66,7 +66,7 @@ impl PxarBackupStream { if let Err(err) = crate::pxar::create_archive( dir, writer, - exclude_pattern, + patterns, crate::pxar::flags::DEFAULT, device_set, skip_lost_and_found, @@ -93,7 +93,7 @@ impl PxarBackupStream { verbose: bool, skip_lost_and_found: bool, catalog: Arc>>, - exclude_pattern: Vec, + patterns: Vec, entries_max: usize, ) -> Result { let dir = nix::dir::Dir::open(dirname, OFlag::O_DIRECTORY, Mode::empty())?; @@ -106,7 +106,7 @@ impl PxarBackupStream { verbose, skip_lost_and_found, catalog, - exclude_pattern, + patterns, entries_max, ) } diff --git a/src/pxar/create.rs b/src/pxar/create.rs index 7fbcae40..9146cede 100644 --- a/src/pxar/create.rs +++ b/src/pxar/create.rs @@ -90,7 +90,7 @@ struct Archiver<'a, 'b> { feature_flags: u64, fs_feature_flags: u64, fs_magic: i64, - excludes: &'a [MatchEntry], + patterns: &'a [MatchEntry], callback: &'a mut dyn FnMut(&Path) -> Result<(), Error>, catalog: Option<&'b mut dyn BackupCatalogWriter>, path: PathBuf, @@ -106,7 +106,7 @@ type Encoder<'a, 'b> = pxar::encoder::Encoder<'a, &'b mut dyn pxar::encoder::Seq pub fn create_archive( source_dir: Dir, mut writer: T, - mut excludes: Vec, + mut patterns: Vec, feature_flags: u64, mut device_set: Option>, skip_lost_and_found: bool, @@ -142,7 +142,7 @@ where let mut encoder = Encoder::new(writer, &metadata)?; if skip_lost_and_found { - excludes.push(MatchEntry::parse_pattern( + patterns.push(MatchEntry::parse_pattern( "**/lost+found", PatternFlag::PATH_NAME, MatchType::Exclude, @@ -154,7 +154,7 @@ where fs_feature_flags, fs_magic, callback: &mut callback, - excludes: &excludes, + patterns: &patterns, catalog, path: PathBuf::new(), entry_counter: 0, @@ -164,6 +164,18 @@ where hardlinks: HashMap::new(), }; + if !patterns.is_empty() { + let content = generate_pxar_excludes_cli(&patterns); + let mut file = encoder.create_file( + &Metadata::default(), + ".pxarexclude-cli", + content.len() as u64, + )?; + + use std::io::Write; + file.write_all(&content)?; + } + archiver.archive_dir_contents(&mut encoder, source_dir)?; encoder.finish()?; Ok(()) @@ -222,8 +234,6 @@ impl<'a, 'b> Archiver<'a, 'b> { continue; } - // FIXME: deal with `.pxarexclude-cli` - if file_name_bytes == b".pxarexclude" { // FIXME: handle this file! continue; @@ -244,7 +254,7 @@ impl<'a, 'b> Archiver<'a, 'b> { }; if self - .excludes + .patterns .matches(full_path.as_os_str().as_bytes(), Some(stat.st_mode as u32)) == Some(MatchType::Exclude) { @@ -294,7 +304,7 @@ impl<'a, 'b> Archiver<'a, 'b> { let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic)?; if self - .excludes + .patterns .matches(self.path.as_os_str().as_bytes(), Some(stat.st_mode as u32)) == Some(MatchType::Exclude) { @@ -767,3 +777,32 @@ fn process_acl( Ok(()) } + +/// Note that our pattern lists are "positive". `MatchType::Include` means the file is included. +/// Since we are generating an *exclude* list, we need to invert this, so includes get a `'!'` +/// prefix. +fn generate_pxar_excludes_cli(patterns: &[MatchEntry]) -> Vec { + use pathpatterns::{MatchFlag, MatchPattern}; + + let mut content = Vec::new(); + + for pattern in patterns { + match pattern.match_type() { + MatchType::Include => content.push(b'!'), + MatchType::Exclude => (), + } + + match pattern.pattern() { + MatchPattern::Literal(lit) => content.extend(lit), + MatchPattern::Pattern(pat) => content.extend(pat.pattern().to_bytes()), + } + + if pattern.match_flags() == MatchFlag::MATCH_DIRECTORIES && content.last() != Some(&b'/') { + content.push(b'/'); + } + + content.push(b'\n'); + } + + content +}