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 D046B9EEFE for ; Wed, 7 Jun 2023 15:31:28 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A8E5E1A3A2 for ; Wed, 7 Jun 2023 15:30:58 +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 15:30:57 +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 61B3741E9C for ; Wed, 7 Jun 2023 15:30:57 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Date: Wed, 7 Jun 2023 15:30:27 +0200 Message-Id: <20230607133027.202575-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230607133027.202575-1-c.ebner@proxmox.com> References: <20230607133027.202575-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.016 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 2/2] fix: #4761: introduce overwrite bitflags for fine grained overwrites 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 13:31:28 -0000 Adds OverwriteFlags for granular control of which entry types should overwrite entries present on the filesystem during a restore. The original overwrite flag is refactored in order to cover all of the other cases. Signed-off-by: Christian Ebner --- pbs-client/src/catalog_shell.rs | 9 ++++-- pbs-client/src/pxar/extract.rs | 47 +++++++++++++++++++++++-------- pbs-client/src/pxar/mod.rs | 2 +- proxmox-backup-client/src/main.rs | 28 +++++++++++++++++- pxar-bin/src/main.rs | 32 +++++++++++++++++++-- 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/pbs-client/src/catalog_shell.rs b/pbs-client/src/catalog_shell.rs index 98af5699..b0be50f1 100644 --- a/pbs-client/src/catalog_shell.rs +++ b/pbs-client/src/catalog_shell.rs @@ -987,8 +987,13 @@ impl Shell { .metadata() .clone(); - let extractor = - crate::pxar::extract::Extractor::new(rootdir, root_meta, true, false, Flags::DEFAULT); + let extractor = crate::pxar::extract::Extractor::new( + rootdir, + root_meta, + true, + crate::pxar::extract::OverwriteFlags::NONE, + Flags::DEFAULT + ); let mut extractor = ExtractorState::new( &mut self.catalog, diff --git a/pbs-client/src/pxar/extract.rs b/pbs-client/src/pxar/extract.rs index 14f9b43d..60029dbd 100644 --- a/pbs-client/src/pxar/extract.rs +++ b/pbs-client/src/pxar/extract.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use anyhow::{bail, format_err, Error}; +use bitflags::bitflags; use nix::dir::Dir; use nix::fcntl::OFlag; use nix::sys::stat::Mode; @@ -33,10 +34,34 @@ pub struct PxarExtractOptions<'a> { pub match_list: &'a [MatchEntry], pub extract_match_default: bool, pub allow_existing_dirs: bool, - pub overwrite: bool, + pub overwrite_flags: OverwriteFlags, pub on_error: Option, } + +bitflags! { + pub struct OverwriteFlags: u8 { + /// Disable all + const NONE = 0x0; + /// Overwrite existing entries file content + const FILE = 0x1; + /// Overwrite existing entry with symlink + const SYMLINK = 0x2; + /// Overwrite existing entry with hardlink + const HARDLINK = 0x4; + /// Enable all + const ALL = OverwriteFlags::FILE.bits() + | OverwriteFlags::SYMLINK.bits() + | OverwriteFlags::HARDLINK.bits(); + } +} + +impl Default for OverwriteFlags { + fn default() -> Self { + OverwriteFlags::NONE + } +} + pub type ErrorHandler = Box Result<(), Error> + Send>; pub fn extract_archive( @@ -80,7 +105,7 @@ where dir, root.metadata().clone(), options.allow_existing_dirs, - options.overwrite, + options.overwrite_flags, feature_flags, ); @@ -199,7 +224,7 @@ where &mut decoder.contents().ok_or_else(|| { format_err!("found regular file entry without contents in archive") })?, - extractor.overwrite, + extractor.overwrite_flags.contains(OverwriteFlags::FILE), ), (false, _) => Ok(()), // skip this } @@ -217,7 +242,7 @@ where pub struct Extractor { feature_flags: Flags, allow_existing_dirs: bool, - overwrite: bool, + overwrite_flags: OverwriteFlags, dir_stack: PxarDirStack, /// For better error output we need to track the current path in the Extractor state. @@ -234,13 +259,13 @@ impl Extractor { root_dir: Dir, metadata: Metadata, allow_existing_dirs: bool, - overwrite: bool, + overwrite_flags: OverwriteFlags, feature_flags: Flags, ) -> Self { Self { dir_stack: PxarDirStack::new(root_dir, metadata), allow_existing_dirs, - overwrite, + overwrite_flags, feature_flags, current_path: Arc::new(Mutex::new(OsString::new())), on_error: Box::new(Err), @@ -330,7 +355,7 @@ impl Extractor { match nix::unistd::symlinkat(link, Some(parent), file_name) { Ok(()) => {} Err(nix::errno::Errno::EEXIST) => { - if !self.overwrite { + if !self.overwrite_flags.contains(OverwriteFlags::SYMLINK) { return Err(nix::errno::Errno::EEXIST.into()); } // Never unlink directories @@ -367,7 +392,7 @@ impl Extractor { ) { Ok(()) => {} Err(nix::errno::Errno::EEXIST) => { - if !self.overwrite { + if !self.overwrite_flags.contains(OverwriteFlags::HARDLINK) { return Err(nix::errno::Errno::EEXIST.into()); } // Never unlink directories @@ -860,7 +885,7 @@ where ) })?; - Ok(Extractor::new(dir, metadata, false, false, Flags::DEFAULT)) + Ok(Extractor::new(dir, metadata, false, OverwriteFlags::NONE, Flags::DEFAULT)) } pub async fn extract_sub_dir( @@ -993,7 +1018,7 @@ where &mut file.contents().await.map_err(|_| { format_err!("found regular file entry without contents in archive") })?, - extractor.overwrite, + extractor.overwrite_flags.contains(OverwriteFlags::FILE), ) .await? } @@ -1041,7 +1066,7 @@ where &mut decoder.contents().ok_or_else(|| { format_err!("found regular file entry without contents in archive") })?, - extractor.overwrite, + extractor.overwrite_flags.contains(OverwriteFlags::FILE), ) .await? } diff --git a/pbs-client/src/pxar/mod.rs b/pbs-client/src/pxar/mod.rs index a158101d..81c535c7 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, + PxarExtractOptions, OverwriteFlags, }; /// The format requires to build sorted directory lookup tables in diff --git a/proxmox-backup-client/src/main.rs b/proxmox-backup-client/src/main.rs index 55198108..b56f8c78 100644 --- a/proxmox-backup-client/src/main.rs +++ b/proxmox-backup-client/src/main.rs @@ -1232,6 +1232,21 @@ We do not extract '.pxar' archives when writing to standard output. optional: true, default: false, }, + "overwrite-files": { + description: "overwrite already existing files", + optional: true, + default: false, + }, + "overwrite-symlinks": { + description: "overwrite already existing entries by archives symlink", + optional: true, + default: false, + }, + "overwrite-hardlinks": { + description: "overwrite already existing entries by archives hardlink", + optional: true, + default: false, + }, } } )] @@ -1244,6 +1259,9 @@ async fn restore( ignore_ownership: bool, ignore_permissions: bool, overwrite: bool, + overwrite_files: bool, + overwrite_symlinks: bool, + overwrite_hardlinks: bool, ) -> Result { let repo = extract_repository_from_value(¶m)?; @@ -1364,11 +1382,19 @@ async fn restore( let mut reader = BufferedDynamicReader::new(index, chunk_reader); + let mut overwrite_flags = pbs_client::pxar::OverwriteFlags::NONE; + overwrite_flags.set(pbs_client::pxar::OverwriteFlags::FILE, overwrite_files); + overwrite_flags.set(pbs_client::pxar::OverwriteFlags::SYMLINK, overwrite_symlinks); + overwrite_flags.set(pbs_client::pxar::OverwriteFlags::HARDLINK, overwrite_hardlinks); + if overwrite { + overwrite_flags.insert(pbs_client::pxar::OverwriteFlags::ALL); + } + let options = pbs_client::pxar::PxarExtractOptions { match_list: &[], extract_match_default: true, allow_existing_dirs, - overwrite, + overwrite_flags, on_error: None, }; diff --git a/pxar-bin/src/main.rs b/pxar-bin/src/main.rs index 90887321..fcfa1435 100644 --- a/pxar-bin/src/main.rs +++ b/pxar-bin/src/main.rs @@ -12,7 +12,9 @@ use futures::select; use tokio::signal::unix::{signal, SignalKind}; use pathpatterns::{MatchEntry, MatchType, PatternFlag}; -use pbs_client::pxar::{format_single_line_entry, Flags, PxarExtractOptions, ENCODER_MAX_ENTRIES}; +use pbs_client::pxar::{ + format_single_line_entry, Flags, PxarExtractOptions, OverwriteFlags, ENCODER_MAX_ENTRIES, +}; use proxmox_router::cli::*; use proxmox_schema::api; @@ -74,10 +76,25 @@ fn extract_archive_from_reader( default: false, }, "overwrite": { + description: "overwrite already existing files, symlinks and hardlinks", + optional: true, + default: false, + }, + "overwrite-files": { description: "overwrite already existing files", optional: true, default: false, }, + "overwrite-symlinks": { + description: "overwrite already existing entries by archives symlink", + optional: true, + default: false, + }, + "overwrite-hardlinks": { + description: "overwrite already existing entries by archives hardlink", + optional: true, + default: false, + }, "files-from": { description: "File containing match pattern for files to restore.", optional: true, @@ -116,6 +133,9 @@ fn extract_archive( no_acls: bool, allow_existing_dirs: bool, overwrite: bool, + overwrite_files: bool, + overwrite_symlinks: bool, + overwrite_hardlinks: bool, files_from: Option, no_device_nodes: bool, no_fifos: bool, @@ -141,6 +161,14 @@ fn extract_archive( if no_sockets { feature_flags.remove(Flags::WITH_SOCKETS); } + + let mut overwrite_flags = OverwriteFlags::NONE; + overwrite_flags.set(OverwriteFlags::FILE, overwrite_files); + overwrite_flags.set(OverwriteFlags::SYMLINK, overwrite_symlinks); + overwrite_flags.set(OverwriteFlags::HARDLINK, overwrite_hardlinks); + if overwrite { + overwrite_flags.insert(OverwriteFlags::ALL); + } let pattern = pattern.unwrap_or_default(); let target = target.as_ref().map_or_else(|| ".", String::as_str); @@ -183,7 +211,7 @@ fn extract_archive( let options = PxarExtractOptions { match_list: &match_list, allow_existing_dirs, - overwrite, + overwrite_flags, extract_match_default, on_error, }; -- 2.39.2