]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/pxar.rs
Revert previous, commit, use UTC RFC3339 without timezone (Z)
[proxmox-backup.git] / src / bin / pxar.rs
index 1ba81d58fc69d36c0c07b08c7740732760fc5676..360e982d3bd10f0c1962580455d67358b1a6f58c 100644 (file)
@@ -12,7 +12,9 @@ use serde_json::{Value};
 use std::io::Write;
 use std::path::{Path, PathBuf};
 use std::fs::OpenOptions;
+use std::sync::Arc;
 use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::AsRawFd;
 
 use proxmox_backup::pxar;
 
@@ -57,7 +59,13 @@ 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,
+    verbose: bool,
+    pattern: Option<Vec<pxar::PxarExcludePattern>>
+) -> Result<(), Error> {
     let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |path| {
         if verbose {
             println!("{:?}", path);
@@ -65,7 +73,8 @@ fn extract_archive_from_reader<R: std::io::Read>(reader: &mut R, target: &str, f
         Ok(())
     });
 
-    decoder.restore(Path::new(target))?;
+    let pattern = pattern.unwrap_or(Vec::new());
+    decoder.restore(Path::new(target), &pattern)?;
 
     Ok(())
 }
@@ -77,11 +86,14 @@ 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 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::CA_FORMAT_DEFAULT;
     if no_xattrs {
@@ -94,15 +106,36 @@ fn extract_archive(
         feature_flags ^= pxar::CA_FORMAT_WITH_ACL;
     }
 
+    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::PxarExcludePattern::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::PxarExcludePattern::from_line(l.as_bytes())?
+            .ok_or_else(|| format_err!("Invalid match pattern in arguments"))?;
+        pattern_list.push(p);
+    }
+
+    let pattern = if pattern_list.len() > 0 {
+        Some(pattern_list)
+    } else {
+        None
+    };
+
     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, 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, verbose, pattern)?;
     }
 
     Ok(Value::Null)
@@ -166,26 +199,34 @@ 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"])
             .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("files-from", StringSchema::new("Match pattern for files to restore."))
+            ))
+            .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("list", CliCommand::new(