extern crate proxmox_backup;
-use failure::*;
+use anyhow::{format_err, Error};
+
+use proxmox::{sortable, identity};
+use proxmox::api::{ApiHandler, ApiMethod, RpcEnvironment};
+use proxmox::api::schema::*;
+use proxmox::api::cli::*;
use proxmox_backup::tools;
-use proxmox_backup::cli::*;
-use proxmox_backup::api_schema::*;
-use proxmox_backup::api_schema::router::*;
use serde_json::{Value};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::fs::OpenOptions;
-use std::sync::Arc;
+use std::ffi::OsStr;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
use std::collections::HashSet;
feature_flags: u64,
verbose: bool,
) -> Result<(), Error> {
- let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |_| Ok(()));
+ let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags);
let stdout = std::io::stdout();
let mut out = stdout.lock();
verbose: bool,
pattern: Option<Vec<pxar::MatchPattern>>
) -> Result<(), Error> {
- let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |path| {
+ let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags);
+ decoder.set_callback(move |path| {
if verbose {
println!("{:?}", path);
}
});
decoder.set_allow_existing_dirs(allow_existing_dirs);
- let pattern = pattern.unwrap_or(Vec::new());
+ let pattern = pattern.unwrap_or_else(Vec::new);
decoder.restore(Path::new(target), &pattern)?;
Ok(())
pattern_list.push(p);
}
- let pattern = if pattern_list.len() > 0 {
- Some(pattern_list)
- } else {
+ let pattern = if pattern_list.is_empty() {
None
+ } else {
+ Some(pattern_list)
};
if 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 entries_max = param["entries-max"].as_u64().unwrap_or(pxar::ENCODER_MAX_ENTRIES as u64);
let devices = if all_file_systems { None } else { Some(HashSet::new()) };
feature_flags ^= pxar::flags::WITH_SOCKETS;
}
- let catalog = None::<&mut pxar::catalog::SimpleCatalog>;
- pxar::Encoder::encode(source, &mut dir, &mut writer, catalog, devices, verbose, false, feature_flags)?;
+ 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,
+ pattern_list,
+ entries_max as usize,
+ )?;
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::from_path(&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)
+}
+
+#[sortable]
+const API_METHOD_CREATE_ARCHIVE: ApiMethod = ApiMethod::new(
+ &ApiHandler::Sync(&create_archive),
+ &ObjectSchema::new(
+ "Create new .pxar archive.",
+ &sorted!([
+ (
+ "archive",
+ false,
+ &StringSchema::new("Archive name").schema()
+ ),
+ (
+ "source",
+ false,
+ &StringSchema::new("Source directory.").schema()
+ ),
+ (
+ "verbose",
+ true,
+ &BooleanSchema::new("Verbose output.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-xattrs",
+ true,
+ &BooleanSchema::new("Ignore extended file attributes.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-fcaps",
+ true,
+ &BooleanSchema::new("Ignore file capabilities.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-acls",
+ true,
+ &BooleanSchema::new("Ignore access control list entries.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "all-file-systems",
+ true,
+ &BooleanSchema::new("Include mounted sudirs.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-device-nodes",
+ true,
+ &BooleanSchema::new("Ignore device nodes.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-fifos",
+ true,
+ &BooleanSchema::new("Ignore fifos.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-sockets",
+ true,
+ &BooleanSchema::new("Ignore sockets.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "exclude",
+ true,
+ &ArraySchema::new(
+ "List of paths or pattern matching files to exclude.",
+ &StringSchema::new("Path or pattern matching files to restore.").schema()
+ ).schema()
+ ),
+ (
+ "entries-max",
+ true,
+ &IntegerSchema::new("Max number of entries loaded at once into memory")
+ .default(pxar::ENCODER_MAX_ENTRIES as isize)
+ .minimum(0)
+ .maximum(std::isize::MAX)
+ .schema()
+ ),
+ ]),
+ )
+);
+
+#[sortable]
+const API_METHOD_EXTRACT_ARCHIVE: ApiMethod = ApiMethod::new(
+ &ApiHandler::Sync(&extract_archive),
+ &ObjectSchema::new(
+ "Extract an archive.",
+ &sorted!([
+ (
+ "archive",
+ false,
+ &StringSchema::new("Archive name.").schema()
+ ),
+ (
+ "pattern",
+ true,
+ &ArraySchema::new(
+ "List of paths or pattern matching files to restore",
+ &StringSchema::new("Path or pattern matching files to restore.").schema()
+ ).schema()
+ ),
+ (
+ "target",
+ true,
+ &StringSchema::new("Target directory.").schema()
+ ),
+ (
+ "verbose",
+ true,
+ &BooleanSchema::new("Verbose output.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-xattrs",
+ true,
+ &BooleanSchema::new("Ignore extended file attributes.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-fcaps",
+ true,
+ &BooleanSchema::new("Ignore file capabilities.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-acls",
+ true,
+ &BooleanSchema::new("Ignore access control list entries.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "allow-existing-dirs",
+ true,
+ &BooleanSchema::new("Allows directories to already exist on restore.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "files-from",
+ true,
+ &StringSchema::new("Match pattern for files to restore.").schema()
+ ),
+ (
+ "no-device-nodes",
+ true,
+ &BooleanSchema::new("Ignore device nodes.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-fifos",
+ true,
+ &BooleanSchema::new("Ignore fifos.")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-sockets",
+ true,
+ &BooleanSchema::new("Ignore sockets.")
+ .default(false)
+ .schema()
+ ),
+ ]),
+ )
+);
+
+#[sortable]
+const API_METHOD_MOUNT_ARCHIVE: ApiMethod = ApiMethod::new(
+ &ApiHandler::Sync(&mount_archive),
+ &ObjectSchema::new(
+ "Mount the archive as filesystem via FUSE.",
+ &sorted!([
+ (
+ "archive",
+ false,
+ &StringSchema::new("Archive name.").schema()
+ ),
+ (
+ "mountpoint",
+ false,
+ &StringSchema::new("Mountpoint for the filesystem root.").schema()
+ ),
+ (
+ "verbose",
+ true,
+ &BooleanSchema::new("Verbose output, keeps process running in foreground (for debugging).")
+ .default(false)
+ .schema()
+ ),
+ (
+ "no-mt",
+ true,
+ &BooleanSchema::new("Run in single threaded mode (for debugging).")
+ .default(false)
+ .schema()
+ ),
+ ]),
+ )
+);
+
+#[sortable]
+const API_METHOD_DUMP_ARCHIVE: ApiMethod = ApiMethod::new(
+ &ApiHandler::Sync(&dump_archive),
+ &ObjectSchema::new(
+ "List the contents of an archive.",
+ &sorted!([
+ ( "archive", false, &StringSchema::new("Archive name.").schema()),
+ ( "verbose", true, &BooleanSchema::new("Verbose output.")
+ .default(false)
+ .schema()
+ ),
+ ])
+ )
+);
+
fn main() {
let cmd_def = CliCommandMap::new()
- .insert("create", CliCommand::new(
- ApiMethod::new(
- create_archive,
- ObjectSchema::new("Create new .pxar archive.")
- .required("archive", StringSchema::new("Archive name"))
- .required("source", StringSchema::new("Source 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))
- .optional("all-file-systems", BooleanSchema::new("Include mounted sudirs.").default(false))
- .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", "source"])
+ .insert("create", CliCommand::new(&API_METHOD_CREATE_ARCHIVE)
+ .arg_param(&["archive", "source"])
.completion_cb("archive", tools::complete_file_name)
.completion_cb("source", tools::complete_file_name)
- .into()
)
- .insert("extract", CliCommand::new(
- ApiMethod::new(
- extract_archive,
- ObjectSchema::new("Extract an archive.")
- .required("archive", StringSchema::new("Archive name."))
- .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))
- .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"])
+ .insert("extract", CliCommand::new(&API_METHOD_EXTRACT_ARCHIVE)
+ .arg_param(&["archive", "target"])
.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(&API_METHOD_MOUNT_ARCHIVE)
+ .arg_param(&["archive", "mountpoint"])
+ .completion_cb("archive", tools::complete_file_name)
+ .completion_cb("mountpoint", tools::complete_file_name)
)
- .insert("list", CliCommand::new(
- ApiMethod::new(
- dump_archive,
- ObjectSchema::new("List the contents of an archive.")
- .required("archive", StringSchema::new("Archive name."))
- .optional("verbose", BooleanSchema::new("Verbose output.").default(false))
- ))
- .arg_param(vec!["archive"])
+ .insert("list", CliCommand::new(&API_METHOD_DUMP_ARCHIVE)
+ .arg_param(&["archive"])
.completion_cb("archive", tools::complete_file_name)
- .into()
);
- run_cli_command(cmd_def.into());
+ let rpcenv = CliEnvironment::new();
+ run_cli_command(cmd_def, rpcenv, None);
}