pbs-client: pxar: add PxarExtractContext

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@proxmox.com>
This commit is contained in:
Max Carrara 2023-07-17 10:04:08 +02:00 committed by Wolfgang Bumiller
parent c66f2579d5
commit 68478bb122
2 changed files with 90 additions and 10 deletions

View File

@ -185,6 +185,8 @@ where
/// [`ErrorHandler`] provided by the [`PxarExtractOptions`] used to
/// initialize the iterator.
///
/// Extraction errors will have a corresponding [`PxarExtractContext`] attached.
///
/// [E]: pxar::Entry
/// [D]: pxar::decoder::Decoder
fn next(&mut self) -> Option<Self::Item> {
@ -252,11 +254,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
@ -281,7 +282,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
@ -296,16 +298,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(())
}
@ -313,7 +319,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(())
}
@ -321,7 +329,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(())
}
@ -342,6 +352,7 @@ where
"found regular file entry without contents in archive"
))
}
.context(PxarExtractContext::ExtractFile)
}
(false, _) => Ok(()), // skip this
};
@ -354,6 +365,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,

View File

@ -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