From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 826969F1DC for ; Wed, 7 Jun 2023 18:34:36 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6614E1CF6F for ; Wed, 7 Jun 2023 18:34:36 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 7 Jun 2023 18:34:35 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 0F38C41FA4 for ; Wed, 7 Jun 2023 18:34:35 +0200 (CEST) From: Max Carrara To: pbs-devel@lists.proxmox.com Date: Wed, 7 Jun 2023 18:34:26 +0200 Message-Id: <20230607163428.1154123-4-m.carrara@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230607163428.1154123-1-m.carrara@proxmox.com> References: <20230607163428.1154123-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.012 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [PATCH proxmox-backup 3/5] pbs-client: pxar: add `PxarExtractContext` X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 07 Jun 2023 16:34:36 -0000 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 --- 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 { 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::() { +/// match ctx { +/// PxarExtractContext::ExtractFile => { +/// // Conditionally handle the underlying error by type +/// if let Some(io_err) = err.downcast_ref::() { +/// // ... +/// }; +/// }, +/// 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