diff --git a/src/bin/pxar.rs b/src/bin/pxar.rs index 9e177729a..8e04adb80 100644 --- a/src/bin/pxar.rs +++ b/src/bin/pxar.rs @@ -174,6 +174,8 @@ fn create_archive( let no_device_nodes = param["no-device-nodes"].as_bool().unwrap_or(false); let no_fifos = param["no-fifos"].as_bool().unwrap_or(false); let no_sockets = param["no-sockets"].as_bool().unwrap_or(false); + let empty = Vec::new(); + let exclude_pattern = param["exclude"].as_array().unwrap_or(&empty); let devices = if all_file_systems { None } else { Some(HashSet::new()) }; @@ -209,8 +211,26 @@ fn create_archive( feature_flags ^= pxar::flags::WITH_SOCKETS; } + let mut pattern_list = Vec::new(); + for s in exclude_pattern { + let l = s.as_str().ok_or_else(|| format_err!("Invalid pattern string slice"))?; + let p = pxar::MatchPattern::from_line(l.as_bytes())? + .ok_or_else(|| format_err!("Invalid match pattern in arguments"))?; + pattern_list.push(p); + } + let catalog = None::<&mut pxar::catalog::DummyCatalogWriter>; - pxar::Encoder::encode(source, &mut dir, &mut writer, catalog, devices, verbose, false, feature_flags)?; + pxar::Encoder::encode( + source, + &mut dir, + &mut writer, + catalog, + devices, + verbose, + false, + feature_flags, + pattern_list, + )?; writer.flush()?; @@ -257,8 +277,14 @@ fn main() { .optional("no-device-nodes", BooleanSchema::new("Ignore device nodes.").default(false)) .optional("no-fifos", BooleanSchema::new("Ignore fifos.").default(false)) .optional("no-sockets", BooleanSchema::new("Ignore sockets.").default(false)) + .optional("exclude", Arc::new( + ArraySchema::new( + "List of paths or pattern matching files to exclude.", + Arc::new(StringSchema::new("Path or pattern matching files to restore.").into()) + ).into() + )) )) - .arg_param(vec!["archive", "source"]) + .arg_param(vec!["archive", "source", "exclude"]) .completion_cb("archive", tools::complete_file_name) .completion_cb("source", tools::complete_file_name) .into() diff --git a/src/client/pxar_backup_stream.rs b/src/client/pxar_backup_stream.rs index 884407b5a..629943661 100644 --- a/src/client/pxar_backup_stream.rs +++ b/src/client/pxar_backup_stream.rs @@ -59,10 +59,21 @@ impl PxarBackupStream { let error2 = error.clone(); let catalog = catalog.clone(); + let exclude_pattern = Vec::new(); let child = thread::spawn(move || { let mut guard = catalog.lock().unwrap(); let mut writer = unsafe { std::fs::File::from_raw_fd(tx) }; - if let Err(err) = pxar::Encoder::encode(path, &mut dir, &mut writer, Some(&mut *guard), device_set, verbose, skip_lost_and_found, pxar::flags::DEFAULT) { + if let Err(err) = pxar::Encoder::encode( + path, + &mut dir, + &mut writer, + Some(&mut *guard), + device_set, + verbose, + skip_lost_and_found, + pxar::flags::DEFAULT, + exclude_pattern, + ) { let mut error = error2.lock().unwrap(); *error = Some(err.to_string()); } diff --git a/src/pxar/encoder.rs b/src/pxar/encoder.rs index d427e7a3d..15fff411f 100644 --- a/src/pxar/encoder.rs +++ b/src/pxar/encoder.rs @@ -2,7 +2,7 @@ //! //! This module contain the code to generate *pxar* archive files. use std::collections::{HashMap, HashSet}; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; use std::io::Write; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; @@ -81,6 +81,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { verbose: bool, skip_lost_and_found: bool, // fixme: should be a feature flag ?? feature_flags: u64, + mut excludes: Vec, ) -> Result<(), Error> { const FILE_COPY_BUFFER_SIZE: usize = 1024 * 1024; @@ -131,10 +132,10 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { println!("{:?}", me.full_path()); } - let mut excludes = Vec::new(); if skip_lost_and_found { excludes.push(MatchPattern::from_line(b"**/lost+found").unwrap().unwrap()); } + me.encode_dir(dir, &stat, magic, excludes)?; Ok(()) @@ -631,6 +632,8 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { let dir_start_pos = self.writer_pos; + let is_root = dir_start_pos == 0; + let mut dir_entry = self.create_entry(&dir_stat)?; self.read_chattr(rawfd, &mut dir_entry)?; @@ -706,6 +709,13 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { if name == b".\0" || name == b"..\0" { continue; } + // Do not store a ".pxarexclude-cli" file found in the archive root, + // as this would confilict with new cli passed exclude patterns, + // if present. + if is_root && name == b".pxarexclude-cli\0" { + eprintln!("skip existing '.pxarexclude-cli' in archive root."); + continue; + } let stat = match nix::sys::stat::fstatat( rawfd, @@ -740,6 +750,20 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { ); } } + + // Exclude patterns passed via the CLI are stored as '.pxarexclude-cli' + // in the root directory of the archive. + if is_root && match_pattern.len() > 0 { + let filename = CString::new(".pxarexclude-cli")?; + name_list.push((filename, dir_stat.clone(), match_pattern.clone())); + if name_list.len() > MAX_DIRECTORY_ENTRIES { + bail!( + "too many directory items in {:?} (> {})", + self.full_path(), + MAX_DIRECTORY_ENTRIES + ); + } + } } else { eprintln!("skip mount point: {:?}", self.full_path()); } @@ -789,6 +813,18 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> { } } + if is_root && filename.as_bytes() == b".pxarexclude-cli" { + // '.pxarexclude-cli' is used to store the exclude MatchPatterns + // passed via the cli in the root directory of the archive. + self.write_filename(&filename)?; + let content = MatchPattern::to_bytes(&exclude_list); + if let Some(ref mut catalog) = self.catalog { + catalog.add_file(&filename, content.len() as u64, 0)?; + } + self.encode_pxar_exclude_cli(stat.st_uid, stat.st_gid, 0, &content)?; + continue; + } + self.relative_path .push(std::ffi::OsStr::from_bytes(filename.as_bytes())); diff --git a/tests/catar.rs b/tests/catar.rs index fdbb7135a..d45b63b2c 100644 --- a/tests/catar.rs +++ b/tests/catar.rs @@ -27,7 +27,17 @@ fn run_test(dir_name: &str) -> Result<(), Error> { let path = std::path::PathBuf::from(dir_name); let catalog = None::<&mut catalog::DummyCatalogWriter>; - Encoder::encode(path, &mut dir, &mut writer, catalog, None, false, false, flags::DEFAULT)?; + Encoder::encode( + path, + &mut dir, + &mut writer, + catalog, + None, + false, + false, + flags::DEFAULT, + Vec::new(), + )?; Command::new("cmp") .arg("--verbose")