[pbs-devel] [PATCH proxmox-backup 3/5] pbs-client: pxar: add `PxarExtractContext`

Max Carrara m.carrara at proxmox.com
Wed Jun 7 18:34:26 CEST 2023


This enum's purpose is to provide context to errors that occur during
the extraction of a pxar archive, making it possible to handle
extraction errors in a more granular manner.

For now, it's only implemented in `ExtractorIter::next()`, but may be
used in other places if necessary or desired.

Signed-off-by: Max Carrara <m.carrara at proxmox.com>
---

Notes:
  I'm intentionally only using `PxarExtractContext` within
  the `ExtractorIter` here, as I'm not certain whether any other
  places could benefit from the same type of context variable as well
  (e.g. the async fns further down in extract.rs).

 pbs-client/src/pxar/extract.rs | 98 ++++++++++++++++++++++++++++++----
 pbs-client/src/pxar/mod.rs     |  2 +-
 2 files changed, 90 insertions(+), 10 deletions(-)

diff --git a/pbs-client/src/pxar/extract.rs b/pbs-client/src/pxar/extract.rs
index fa08bfd7..6b026a02 100644
--- a/pbs-client/src/pxar/extract.rs
+++ b/pbs-client/src/pxar/extract.rs
@@ -235,6 +235,8 @@ where
     /// [`ErrorHandler`] provided by the [`PxarExtractOptions`] used to
     /// initialize the iterator.
     ///
+    /// Extraction errors will have a corresponding [`PxarExtractContext`] attached.
+    ///
     /// [E]: pxar::Entry
     fn next(&mut self) -> Option<Self::Item> {
         let entry = self.next_entry()?;
@@ -273,11 +275,10 @@ where
                 self.callback(entry.path());
 
                 let create = self.state.current_match && match_result != Some(MatchType::Exclude);
-                let res = self.extractor.enter_directory(
-                    file_name_os.to_owned(),
-                    metadata.clone(),
-                    create,
-                );
+                let res = self
+                    .extractor
+                    .enter_directory(file_name_os.to_owned(), metadata.clone(), create)
+                    .context(PxarExtractContext::EnterDirectory);
 
                 if res.is_ok() {
                     // We're starting a new directory, push our old matching state and replace it with
@@ -302,7 +303,8 @@ where
                     .pop()
                     .context("unexpected end of directory")
                     .map(|path| self.extractor.set_path(path))
-                    .and(self.extractor.leave_directory());
+                    .and(self.extractor.leave_directory())
+                    .context(PxarExtractContext::LeaveDirectory);
 
                 if res.is_ok() {
                     // We left a directory, also get back our previous matching state. This is in sync
@@ -317,16 +319,20 @@ where
                 self.callback(entry.path());
                 self.extractor
                     .extract_symlink(&file_name, metadata, link.as_ref())
+                    .context(PxarExtractContext::ExtractSymlink)
             }
             (true, EntryKind::Hardlink(link)) => {
                 self.callback(entry.path());
                 self.extractor
                     .extract_hardlink(&file_name, link.as_os_str())
+                    .context(PxarExtractContext::ExtractHardlink)
             }
             (true, EntryKind::Device(dev)) => {
                 if self.extractor.contains_flags(Flags::WITH_DEVICE_NODES) {
                     self.callback(entry.path());
-                    self.extractor.extract_device(&file_name, metadata, dev)
+                    self.extractor
+                        .extract_device(&file_name, metadata, dev)
+                        .context(PxarExtractContext::ExtractDevice)
                 } else {
                     Ok(())
                 }
@@ -334,7 +340,9 @@ where
             (true, EntryKind::Fifo) => {
                 if self.extractor.contains_flags(Flags::WITH_FIFOS) {
                     self.callback(entry.path());
-                    self.extractor.extract_special(&file_name, metadata, 0)
+                    self.extractor
+                        .extract_special(&file_name, metadata, 0)
+                        .context(PxarExtractContext::ExtractFifo)
                 } else {
                     Ok(())
                 }
@@ -342,7 +350,9 @@ where
             (true, EntryKind::Socket) => {
                 if self.extractor.contains_flags(Flags::WITH_SOCKETS) {
                     self.callback(entry.path());
-                    self.extractor.extract_special(&file_name, metadata, 0)
+                    self.extractor
+                        .extract_special(&file_name, metadata, 0)
+                        .context(PxarExtractContext::ExtractSocket)
                 } else {
                     Ok(())
                 }
@@ -363,6 +373,7 @@ where
                         "found regular file entry without contents in archive"
                     ))
                 }
+                .context(PxarExtractContext::ExtractFile)
             }
             (false, _) => Ok(()), // skip this
         };
@@ -375,6 +386,75 @@ where
     }
 }
 
+/// Provides additional [context][C] for [`anyhow::Error`]s that are returned
+/// while traversing an [`ExtractorIter`]. The [`PxarExtractContext`] can then
+/// be accessed [via `anyhow`'s facilities][A] and may aid during error handling.
+///
+///
+/// # Example
+///
+/// ```
+/// # use anyhow::{anyhow, Error};
+/// # use std::io;
+/// # use pbs_client::pxar::PxarExtractContext;
+///
+/// let err = anyhow!("oh noes!").context(PxarExtractContext::ExtractFile);
+///
+/// if let Some(ctx) = err.downcast_ref::<PxarExtractContext>() {
+///     match ctx {
+///         PxarExtractContext::ExtractFile => {
+///             // Conditionally handle the underlying error by type
+///             if let Some(io_err) = err.downcast_ref::<io::Error>() {
+///                 // ...
+///             };
+///         },
+///         PxarExtractContext::ExtractSocket => {
+///             // ...
+///         },
+///         // ...
+/// #        _ => (),
+///     }
+/// }
+/// ```
+///
+/// [A]: anyhow::Error
+/// [C]: anyhow::Context
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum PxarExtractContext {
+    EnterDirectory,
+    LeaveDirectory,
+    ExtractSymlink,
+    ExtractHardlink,
+    ExtractDevice,
+    ExtractFifo,
+    ExtractSocket,
+    ExtractFile,
+}
+
+impl PxarExtractContext {
+    #[inline]
+    pub fn as_str(&self) -> &'static str {
+        use PxarExtractContext::*;
+
+        match *self {
+            EnterDirectory => "failed to enter directory",
+            LeaveDirectory => "failed to leave directory",
+            ExtractSymlink => "failed to extract symlink",
+            ExtractHardlink => "failed to extract hardlink",
+            ExtractDevice => "failed to extract device",
+            ExtractFifo => "failed to extract named pipe",
+            ExtractSocket => "failed to extract unix socket",
+            ExtractFile => "failed to extract file",
+        }
+    }
+}
+
+impl std::fmt::Display for PxarExtractContext {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
 /// Common state for file extraction.
 pub struct Extractor {
     feature_flags: Flags,
diff --git a/pbs-client/src/pxar/mod.rs b/pbs-client/src/pxar/mod.rs
index a158101d..b042717d 100644
--- a/pbs-client/src/pxar/mod.rs
+++ b/pbs-client/src/pxar/mod.rs
@@ -59,7 +59,7 @@ pub use flags::Flags;
 pub use create::{create_archive, PxarCreateOptions};
 pub use extract::{
     create_tar, create_zip, extract_archive, extract_sub_dir, extract_sub_dir_seq, ErrorHandler,
-    PxarExtractOptions,
+    PxarExtractContext, PxarExtractOptions,
 };
 
 /// The format requires to build sorted directory lookup tables in
-- 
2.30.2






More information about the pbs-devel mailing list