]> git.proxmox.com Git - proxmox-backup.git/commitdiff
pxar: make extractor state more reusable
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 16 Jun 2020 08:32:13 +0000 (10:32 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 16 Jun 2020 08:32:13 +0000 (10:32 +0200)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
src/pxar/dir_stack.rs
src/pxar/extract.rs

index 3dba7de5cfd42f971974724eca13be83ea624885..750d6dff15dc3ae4d40346da54a45e0b54cabd9c 100644 (file)
@@ -1,4 +1,4 @@
-use std::ffi::OsString;
+use std::ffi::{OsStr, OsString};
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::PathBuf;
 
@@ -73,6 +73,10 @@ impl PxarDir {
     pub fn metadata(&self) -> &Metadata {
         &self.metadata
     }
+
+    pub fn file_name(&self) -> &OsStr {
+        &self.file_name
+    }
 }
 
 pub struct PxarDirStack {
@@ -130,6 +134,11 @@ impl PxarDirStack {
         Ok(fd)
     }
 
+    pub fn create_last_dir(&mut self, allow_existing_dirs: bool) -> Result<(), Error> {
+        let _: RawFd = self.last_dir_fd(allow_existing_dirs)?;
+        Ok(())
+    }
+
     pub fn root_dir_fd(&self) -> Result<RawFd, Error> {
         // should not be possible given the way we use it:
         assert!(!self.dirs.is_empty(), "PxarDirStack underrun");
index 2905b7de6c3b3d870c8e713a90d31a44acdc1731..715e25ea869b7fced6dcdc97624d8f87b998ef26 100644 (file)
@@ -1,7 +1,7 @@
 //! Code for extraction of pxar contents onto the file system.
 
 use std::convert::TryFrom;
-use std::ffi::{CStr, CString, OsStr};
+use std::ffi::{CStr, CString, OsStr, OsString};
 use std::io;
 use std::os::unix::ffi::OsStrExt;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@@ -23,19 +23,6 @@ use crate::pxar::dir_stack::PxarDirStack;
 use crate::pxar::Flags;
 use crate::pxar::metadata;
 
-struct Extractor<'a> {
-    feature_flags: Flags,
-    allow_existing_dirs: bool,
-    callback: &'a mut dyn FnMut(&Path),
-    dir_stack: PxarDirStack,
-}
-
-impl<'a> Extractor<'a> {
-    fn contains_flags(&self, flag: Flags) -> bool {
-        self.feature_flags.contains(flag)
-    }
-}
-
 pub fn extract_archive<T, F>(
     mut decoder: pxar::decoder::Decoder<T>,
     destination: &Path,
@@ -74,12 +61,12 @@ where
     )
     .map_err(|err| format_err!("unable to open target directory {:?}: {}", destination, err,))?;
 
-    let mut extractor = Extractor {
-        feature_flags,
+    let mut extractor = Extractor::new(
+        dir,
+        root.metadata().clone(),
         allow_existing_dirs,
-        callback: &mut callback,
-        dir_stack: PxarDirStack::new(dir, root.metadata().clone()),
-    };
+        feature_flags,
+    );
 
     let mut match_stack = Vec::new();
     let mut current_match = true;
@@ -112,22 +99,10 @@ where
         };
         match (did_match, entry.kind()) {
             (_, EntryKind::Directory) => {
-                extractor.callback(entry.path());
+                callback(entry.path());
 
-                extractor
-                    .dir_stack
-                    .push(file_name_os.to_owned(), metadata.clone())?;
-
-                if current_match && match_result != Some(MatchType::Exclude) {
-                    // We're currently in a positive match and this directory does not match an
-                    // exclude entry, so make sure it is created:
-                    let _ = extractor
-                        .dir_stack
-                        .last_dir_fd(extractor.allow_existing_dirs)
-                        .map_err(|err| {
-                            format_err!("error creating entry {:?}: {}", file_name_os, err)
-                        })?;
-                }
+                let create = current_match && match_result != Some(MatchType::Exclude);
+                extractor.enter_directory(file_name_os.to_owned(), metadata.clone(), create)?;
 
                 // We're starting a new directory, push our old matching state and replace it with
                 // our new one:
@@ -138,33 +113,28 @@ where
             }
             (_, EntryKind::GoodbyeTable) => {
                 // go up a directory
-                let dir = extractor
-                    .dir_stack
-                    .pop()
-                    .map_err(|err| format_err!("unexpected end of directory entry: {}", err))?
-                    .ok_or_else(|| format_err!("broken pxar archive (directory stack underrun)"))?;
+                extractor
+                    .leave_directory()
+                    .map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
+
                 // We left a directory, also get back our previous matching state. This is in sync
                 // with `dir_stack` so this should never be empty except for the final goodbye
                 // table, in which case we get back to the default of `true`.
                 current_match = match_stack.pop().unwrap_or(true);
 
-                if let Some(fd) = dir.try_as_raw_fd() {
-                    metadata::apply(extractor.feature_flags, dir.metadata(), fd, &file_name)
-                } else {
-                    Ok(())
-                }
+                Ok(())
             }
             (true, EntryKind::Symlink(link)) => {
-                extractor.callback(entry.path());
+                callback(entry.path());
                 extractor.extract_symlink(&file_name, metadata, link.as_ref())
             }
             (true, EntryKind::Hardlink(link)) => {
-                extractor.callback(entry.path());
+                callback(entry.path());
                 extractor.extract_hardlink(&file_name, metadata, link.as_os_str())
             }
             (true, EntryKind::Device(dev)) => {
                 if extractor.contains_flags(Flags::WITH_DEVICE_NODES) {
-                    extractor.callback(entry.path());
+                    callback(entry.path());
                     extractor.extract_device(&file_name, metadata, dev)
                 } else {
                     Ok(())
@@ -172,7 +142,7 @@ where
             }
             (true, EntryKind::Fifo) => {
                 if extractor.contains_flags(Flags::WITH_FIFOS) {
-                    extractor.callback(entry.path());
+                    callback(entry.path());
                     extractor.extract_special(&file_name, metadata, 0)
                 } else {
                     Ok(())
@@ -180,7 +150,7 @@ where
             }
             (true, EntryKind::Socket) => {
                 if extractor.contains_flags(Flags::WITH_SOCKETS) {
-                    extractor.callback(entry.path());
+                    callback(entry.path());
                     extractor.extract_special(&file_name, metadata, 0)
                 } else {
                     Ok(())
@@ -206,13 +176,72 @@ where
     Ok(())
 }
 
-impl<'a> Extractor<'a> {
-    fn parent_fd(&mut self) -> Result<RawFd, Error> {
-        self.dir_stack.last_dir_fd(self.allow_existing_dirs)
+/// Common state for file extraction.
+pub(crate) struct Extractor {
+    feature_flags: Flags,
+    allow_existing_dirs: bool,
+    dir_stack: PxarDirStack,
+}
+
+impl Extractor {
+    /// Create a new extractor state for a target directory.
+    pub fn new(
+        root_dir: Dir,
+        metadata: Metadata,
+        allow_existing_dirs: bool,
+        feature_flags: Flags,
+    ) -> Self {
+        Self {
+            dir_stack: PxarDirStack::new(root_dir, metadata),
+            allow_existing_dirs,
+            feature_flags,
+        }
+    }
+
+    /// When encountering a directory during extraction, this is used to keep track of it. If
+    /// `create` is true it is immediately created and its metadata will be updated once we leave
+    /// it. If `create` is false it will only be created if it is going to have any actual content.
+    pub fn enter_directory(
+        &mut self,
+        file_name: OsString,
+        metadata: Metadata,
+        create: bool,
+    ) -> Result<(), Error> {
+        self.dir_stack.push(file_name, metadata)?;
+
+        if create {
+            self.dir_stack.create_last_dir(self.allow_existing_dirs)?;
+        }
+
+        Ok(())
     }
 
-    fn callback(&mut self, path: &Path) {
-        (self.callback)(path)
+    /// When done with a directory we need to make sure we're
+    pub fn leave_directory(&mut self) -> Result<(), Error> {
+        let dir = self
+            .dir_stack
+            .pop()
+            .map_err(|err| format_err!("unexpected end of directory entry: {}", err))?
+            .ok_or_else(|| format_err!("broken pxar archive (directory stack underrun)"))?;
+
+        if let Some(fd) = dir.try_as_raw_fd() {
+            metadata::apply(
+                self.feature_flags,
+                dir.metadata(),
+                fd,
+                &CString::new(dir.file_name().as_bytes())?,
+            )?;
+        }
+
+        Ok(())
+    }
+
+    fn contains_flags(&self, flag: Flags) -> bool {
+        self.feature_flags.contains(flag)
+    }
+
+    fn parent_fd(&mut self) -> Result<RawFd, Error> {
+        self.dir_stack.last_dir_fd(self.allow_existing_dirs)
     }
 
     fn extract_symlink(