]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/pxar.rs
avoid some clippy warnings
[proxmox-backup.git] / src / bin / pxar.rs
index 1ba81d58fc69d36c0c07b08c7740732760fc5676..5e77b69c9f7b8a80d6fcfce35f67adea05e10e4e 100644 (file)
@@ -12,7 +12,11 @@ use serde_json::{Value};
 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;
 
@@ -41,7 +45,7 @@ fn dump_archive(
     let archive = tools::required_string_param(&param, "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();
@@ -57,15 +61,24 @@ fn dump_archive(
     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(())
 }
@@ -77,32 +90,69 @@ fn extract_archive(
 ) -> Result<Value, Error> {
 
     let archive = tools::required_string_param(&param, "archive")?;
-    let target = tools::required_string_param(&param, "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)
@@ -121,6 +171,13 @@ fn create_archive(
     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);
 
@@ -134,24 +191,75 @@ fn create_archive(
         .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(&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::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()
@@ -166,26 +274,61 @@ fn main() {
                     .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(