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 016EFCB77 for ; Wed, 16 Aug 2023 11:58:06 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id DD92E13E14 for ; Wed, 16 Aug 2023 11:58:05 +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, 16 Aug 2023 11:58:04 +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 BA91F40A76 for ; Wed, 16 Aug 2023 11:58:04 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com Date: Wed, 16 Aug 2023 11:57:46 +0200 Message-Id: <20230816095746.153036-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230816095746.153036-1-c.ebner@proxmox.com> References: <20230816095746.153036-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.129 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 Subject: [pbs-devel] [PATCH v3 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, 16 Aug 2023 09:58:06 -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 --- changes since v1: * rebased to current master changes since v2: * use `all()` and `empty()` instead of defining ALL and NONE variants for bitflags. * Derive default instead of explicit impl. * Fix formating issues by running rustfmt 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 | 34 +++++++++++++++++++++- pxar-bin/src/main.rs | 32 +++++++++++++++++++-- 5 files changed, 105 insertions(+), 19 deletions(-) diff --git a/pbs-client/src/catalog_shell.rs b/pbs-client/src/catalog_shell.rs index 98af5699..b8aaf8cb 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::empty(), + 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 9a95faa0..37399f17 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, Context, Error}; +use bitflags::bitflags; use nix::dir::Dir; use nix::fcntl::OFlag; use nix::sys::stat::Mode; @@ -33,10 +34,22 @@ 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! { + #[derive(Default)] + pub struct OverwriteFlags: u8 { + /// Overwrite existing entries file content + const FILE = 0x1; + /// Overwrite existing entry with symlink + const SYMLINK = 0x2; + /// Overwrite existing entry with hardlink + const HARDLINK = 0x4; + } +} + pub type ErrorHandler = Box Result<(), Error> + Send>; pub fn extract_archive( @@ -141,7 +154,7 @@ where dir, root.metadata().clone(), options.allow_existing_dirs, - options.overwrite, + options.overwrite_flags, feature_flags, ); @@ -345,7 +358,9 @@ where metadata, *size, &mut contents, - self.extractor.overwrite, + self.extractor + .overwrite_flags + .contains(OverwriteFlags::FILE), ) } else { Err(format_err!( @@ -438,7 +453,7 @@ impl std::fmt::Display for PxarExtractContext { 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. @@ -455,13 +470,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), @@ -551,7 +566,7 @@ impl Extractor { match nix::unistd::symlinkat(link, Some(parent), file_name) { Ok(()) => {} Err(err @ nix::errno::Errno::EEXIST) => { - if !self.overwrite { + if !self.overwrite_flags.contains(OverwriteFlags::SYMLINK) { return Err(err.into()); } // Never unlink directories @@ -559,7 +574,7 @@ impl Extractor { nix::unistd::unlinkat(Some(parent), file_name, flag)?; nix::unistd::symlinkat(link, Some(parent), file_name)?; } - Err(err) => return Err(err.into()) + Err(err) => return Err(err.into()), } metadata::apply_at( @@ -591,7 +606,7 @@ impl Extractor { match dolink() { Ok(()) => {} Err(err @ nix::errno::Errno::EEXIST) => { - if !self.overwrite { + if !self.overwrite_flags.contains(OverwriteFlags::HARDLINK) { return Err(err.into()); } // Never unlink directories @@ -599,7 +614,7 @@ impl Extractor { nix::unistd::unlinkat(Some(parent), file_name, flag)?; dolink()?; } - Err(err) => return Err(err.into()) + Err(err) => return Err(err.into()), } Ok(()) @@ -1062,7 +1077,13 @@ where ) .with_context(|| format!("unable to open target directory {:?}", destination.as_ref()))?; - Ok(Extractor::new(dir, metadata, false, false, Flags::DEFAULT)) + Ok(Extractor::new( + dir, + metadata, + false, + OverwriteFlags::empty(), + Flags::DEFAULT, + )) } pub async fn extract_sub_dir( @@ -1196,7 +1217,7 @@ where .contents() .await .context("found regular file entry without contents in archive")?, - extractor.overwrite, + extractor.overwrite_flags.contains(OverwriteFlags::FILE), ) .await? } @@ -1244,7 +1265,7 @@ where &mut decoder .contents() .context("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 b042717d..14674b9b 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, - PxarExtractContext, PxarExtractOptions, + OverwriteFlags, PxarExtractContext, PxarExtractOptions, }; /// 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 d9e7b899..1a13291a 100644 --- a/proxmox-backup-client/src/main.rs +++ b/proxmox-backup-client/src/main.rs @@ -1234,6 +1234,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, + }, "ignore-extract-device-errors": { type: Boolean, description: "ignore errors that occur during device node extraction", @@ -1252,6 +1267,9 @@ async fn restore( ignore_ownership: bool, ignore_permissions: bool, overwrite: bool, + overwrite_files: bool, + overwrite_symlinks: bool, + overwrite_hardlinks: bool, ignore_extract_device_errors: bool, ) -> Result { let repo = extract_repository_from_value(¶m)?; @@ -1388,11 +1406,25 @@ async fn restore( None }; + let mut overwrite_flags = pbs_client::pxar::OverwriteFlags::empty(); + 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, }; diff --git a/pxar-bin/src/main.rs b/pxar-bin/src/main.rs index 90887321..bc044035 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, OverwriteFlags, PxarExtractOptions, 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, @@ -142,6 +162,14 @@ fn extract_archive( feature_flags.remove(Flags::WITH_SOCKETS); } + let mut overwrite_flags = OverwriteFlags::empty(); + 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