pxar: add functionality to pass exclude MatchPatterns on create

This exposes the option to pass a list of exclude MatchPattern via the
'--exclude' option.
The list is encoded as file '.pxarexclude-cli' in the archives root directory.
If such a file is present in the filesystem, it is skipped and not included in
the archive in order to avoid conflicting information.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
Christian Ebner 2019-10-22 15:07:51 +02:00 committed by Dietmar Maurer
parent ba8165c607
commit 62d123e50a
4 changed files with 89 additions and 6 deletions

View File

@ -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()

View File

@ -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());
}

View File

@ -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<MatchPattern>,
) -> 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()));

View File

@ -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")