]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/pxar.rs
src/config/network.rs: make it compatible with pve
[proxmox-backup.git] / src / bin / pxar.rs
index 34b8a3a04a7c818c43aa3981bbd816a8cdef634a..5d1eb2e63c2274d3b36c262212939f2364999296 100644 (file)
@@ -1,18 +1,20 @@
 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;
@@ -24,7 +26,7 @@ fn dump_archive_from_reader<R: std::io::Read>(
     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();
@@ -68,7 +70,8 @@ fn extract_archive_from_reader<R: std::io::Read>(
     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);
         }
@@ -76,7 +79,7 @@ fn extract_archive_from_reader<R: std::io::Read>(
     });
     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(())
@@ -137,10 +140,10 @@ fn extract_archive(
         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 == "-" {
@@ -173,6 +176,9 @@ 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 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()) };
 
@@ -208,76 +214,311 @@ fn create_archive(
         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(&param, "archive")?;
+    let mountpoint = tools::required_string_param(&param, "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);
 }