use std::io::Write;
use std::path::{Path, PathBuf};
use std::fs::OpenOptions;
+use std::ffi::OsStr;
+use std::sync::Arc;
use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::AsRawFd;
+use std::collections::HashSet;
use proxmox_backup::pxar;
let archive = tools::required_string_param(¶m, "archive")?;
let verbose = param["verbose"].as_bool().unwrap_or(false);
- let feature_flags = pxar::CA_FORMAT_DEFAULT;
+ let feature_flags = pxar::flags::DEFAULT;
if archive == "-" {
let stdin = std::io::stdin();
Ok(Value::Null)
}
-fn extract_archive_from_reader<R: std::io::Read>(reader: &mut R, target: &str, feature_flags: u64, verbose: bool) -> Result<(), Error> {
+fn extract_archive_from_reader<R: std::io::Read>(
+ reader: &mut R,
+ target: &str,
+ feature_flags: u64,
+ allow_existing_dirs: bool,
+ verbose: bool,
+ pattern: Option<Vec<pxar::MatchPattern>>
+) -> Result<(), Error> {
let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |path| {
if verbose {
println!("{:?}", path);
}
Ok(())
});
+ decoder.set_allow_existing_dirs(allow_existing_dirs);
- decoder.restore(Path::new(target))?;
+ let pattern = pattern.unwrap_or_else(Vec::new);
+ decoder.restore(Path::new(target), &pattern)?;
Ok(())
}
) -> Result<Value, Error> {
let archive = tools::required_string_param(¶m, "archive")?;
- let target = tools::required_string_param(¶m, "target")?;
+ let target = param["target"].as_str().unwrap_or(".");
let verbose = param["verbose"].as_bool().unwrap_or(false);
let no_xattrs = param["no-xattrs"].as_bool().unwrap_or(false);
let no_fcaps = param["no-fcaps"].as_bool().unwrap_or(false);
let no_acls = param["no-acls"].as_bool().unwrap_or(false);
-
- let mut feature_flags = pxar::CA_FORMAT_DEFAULT;
+ 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 allow_existing_dirs = param["allow-existing-dirs"].as_bool().unwrap_or(false);
+ let files_from = param["files-from"].as_str();
+ let empty = Vec::new();
+ let arg_pattern = param["pattern"].as_array().unwrap_or(&empty);
+
+ let mut feature_flags = pxar::flags::DEFAULT;
if no_xattrs {
- feature_flags ^= pxar::CA_FORMAT_WITH_XATTRS;
+ feature_flags ^= pxar::flags::WITH_XATTRS;
}
if no_fcaps {
- feature_flags ^= pxar::CA_FORMAT_WITH_FCAPS;
+ feature_flags ^= pxar::flags::WITH_FCAPS;
}
if no_acls {
- feature_flags ^= pxar::CA_FORMAT_WITH_ACL;
+ feature_flags ^= pxar::flags::WITH_ACL;
+ }
+ if no_device_nodes {
+ feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
+ }
+ if no_fifos {
+ feature_flags ^= pxar::flags::WITH_FIFOS;
+ }
+ if no_sockets {
+ feature_flags ^= pxar::flags::WITH_SOCKETS;
+ }
+
+ let mut pattern_list = Vec::new();
+ if let Some(filename) = files_from {
+ let dir = nix::dir::Dir::open("./", nix::fcntl::OFlag::O_RDONLY, nix::sys::stat::Mode::empty())?;
+ if let Some((mut pattern, _, _)) = pxar::MatchPattern::from_file(dir.as_raw_fd(), filename)? {
+ pattern_list.append(&mut pattern);
+ }
}
+ for s in arg_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 pattern = if pattern_list.is_empty() {
+ None
+ } else {
+ Some(pattern_list)
+ };
+
if archive == "-" {
let stdin = std::io::stdin();
let mut reader = stdin.lock();
- extract_archive_from_reader(&mut reader, target, feature_flags, verbose)?;
+ extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
} else {
- println!("PXAR dump: {}", archive);
+ if verbose { println!("PXAR extract: {}", archive); }
let file = std::fs::File::open(archive)?;
let mut reader = std::io::BufReader::new(file);
- extract_archive_from_reader(&mut reader, target, feature_flags, verbose)?;
+ extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
}
Ok(Value::Null)
let no_xattrs = param["no-xattrs"].as_bool().unwrap_or(false);
let no_fcaps = param["no-fcaps"].as_bool().unwrap_or(false);
let no_acls = param["no-acls"].as_bool().unwrap_or(false);
+ 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()) };
let source = PathBuf::from(source);
.open(archive)?;
let mut writer = std::io::BufWriter::with_capacity(1024*1024, file);
- let mut feature_flags = pxar::CA_FORMAT_DEFAULT;
+ let mut feature_flags = pxar::flags::DEFAULT;
if no_xattrs {
- feature_flags ^= pxar::CA_FORMAT_WITH_XATTRS;
+ feature_flags ^= pxar::flags::WITH_XATTRS;
}
if no_fcaps {
- feature_flags ^= pxar::CA_FORMAT_WITH_FCAPS;
+ feature_flags ^= pxar::flags::WITH_FCAPS;
}
if no_acls {
- feature_flags ^= pxar::CA_FORMAT_WITH_ACL;
+ feature_flags ^= pxar::flags::WITH_ACL;
+ }
+ if no_device_nodes {
+ feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
+ }
+ if no_fifos {
+ feature_flags ^= pxar::flags::WITH_FIFOS;
+ }
+ if no_sockets {
+ 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);
}
- pxar::Encoder::encode(source, &mut dir, &mut writer, all_file_systems, verbose, feature_flags)?;
+ let catalog = None::<&mut pxar::catalog::DummyCatalogWriter>;
+ pxar::Encoder::encode(
+ source,
+ &mut dir,
+ &mut writer,
+ catalog,
+ devices,
+ verbose,
+ false,
+ feature_flags,
+ pattern_list,
+ )?;
writer.flush()?;
Ok(Value::Null)
}
+/// Mount the archive to the provided mountpoint via FUSE.
+fn mount_archive(
+ param: Value,
+ _info: &ApiMethod,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Value, Error> {
+ let archive = tools::required_string_param(¶m, "archive")?;
+ let mountpoint = tools::required_string_param(¶m, "mountpoint")?;
+ let verbose = param["verbose"].as_bool().unwrap_or(false);
+ let no_mt = param["no-mt"].as_bool().unwrap_or(false);
+
+ let archive = Path::new(archive);
+ let mountpoint = Path::new(mountpoint);
+ let options = OsStr::new("ro,default_permissions");
+ let mut session = pxar::fuse::Session::new(&archive, &options, verbose)
+ .map_err(|err| format_err!("pxar mount failed: {}", err))?;
+ // Mount the session and deamonize if verbose is not set
+ session.mount(&mountpoint, !verbose)?;
+ session.run_loop(!no_mt)?;
+
+ Ok(Value::Null)
+}
+
fn main() {
let cmd_def = CliCommandMap::new()
.optional("no-fcaps", BooleanSchema::new("Ignore file capabilities.").default(false))
.optional("no-acls", BooleanSchema::new("Ignore access control list entries.").default(false))
.optional("all-file-systems", BooleanSchema::new("Include mounted sudirs.").default(false))
- ))
- .arg_param(vec!["archive", "source"])
+ .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", "exclude"])
.completion_cb("archive", tools::complete_file_name)
.completion_cb("source", tools::complete_file_name)
- .into()
+ .into()
)
.insert("extract", CliCommand::new(
ApiMethod::new(
extract_archive,
ObjectSchema::new("Extract an archive.")
.required("archive", StringSchema::new("Archive name."))
- .required("target", StringSchema::new("Target directory."))
+ .optional("pattern", Arc::new(
+ ArraySchema::new(
+ "List of paths or pattern matching files to restore",
+ Arc::new(StringSchema::new("Path or pattern matching files to restore.").into())
+ ).into()
+ ))
+ .optional("target", StringSchema::new("Target directory."))
.optional("verbose", BooleanSchema::new("Verbose output.").default(false))
.optional("no-xattrs", BooleanSchema::new("Ignore extended file attributes.").default(false))
.optional("no-fcaps", BooleanSchema::new("Ignore file capabilities.").default(false))
.optional("no-acls", BooleanSchema::new("Ignore access control list entries.").default(false))
- ))
- .arg_param(vec!["archive", "target"])
+ .optional("allow-existing-dirs", BooleanSchema::new("Allows directories to already exist on restore.").default(false))
+ .optional("files-from", StringSchema::new("Match pattern for files to restore."))
+ .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))
+ ))
+ .arg_param(vec!["archive", "pattern"])
.completion_cb("archive", tools::complete_file_name)
.completion_cb("target", tools::complete_file_name)
+ .completion_cb("files-from", tools::complete_file_name)
+ .into()
+ )
+ .insert("mount", CliCommand::new(
+ ApiMethod::new(
+ mount_archive,
+ ObjectSchema::new("Mount the archive as filesystem via FUSE.")
+ .required("archive", StringSchema::new("Archive name."))
+ .required("mountpoint", StringSchema::new("Mountpoint for the filesystem root."))
+ .optional("verbose", BooleanSchema::new("Verbose output, keeps process running in foreground (for debugging).").default(false))
+ .optional("no-mt", BooleanSchema::new("Run in single threaded mode (for debugging).").default(false))
+ ))
+ .arg_param(vec!["archive", "mountpoint"])
+ .completion_cb("archive", tools::complete_file_name)
+ .completion_cb("mountpoint", tools::complete_file_name)
.into()
)
.insert("list", CliCommand::new(