[pbs-devel] [PATCH v2 proxmox-backup 2/2] fix: #4761: introduce overwrite bitflags for fine grained overwrites

Christian Ebner c.ebner at proxmox.com
Tue Aug 1 12:34:12 CEST 2023


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 <c.ebner at proxmox.com>
---
changes since v1:
* rebased and refactored to current master

 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 c1e7b417..6faf0b54 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,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<ErrorHandler>,
 }
 
+
+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<dyn FnMut(Error) -> Result<(), Error> + Send>;
 
 pub fn extract_archive<T, F>(
@@ -141,7 +166,7 @@ where
             dir,
             root.metadata().clone(),
             options.allow_existing_dirs,
-            options.overwrite,
+            options.overwrite_flags,
             feature_flags,
         );
 
@@ -345,7 +370,7 @@ where
                         metadata,
                         *size,
                         &mut contents,
-                        self.extractor.overwrite,
+                        self.extractor.overwrite_flags.contains(OverwriteFlags::FILE),
                     )
                 } else {
                     Err(format_err!(
@@ -438,7 +463,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 +480,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 +576,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
@@ -588,7 +613,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
@@ -1065,7 +1090,7 @@ 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::NONE, Flags::DEFAULT))
 }
 
 pub async fn extract_sub_dir<T, DEST, PATH>(
@@ -1199,7 +1224,7 @@ where
                         .contents()
                         .await
                         .context("found regular file entry without contents in archive")?,
-                    extractor.overwrite,
+                    extractor.overwrite_flags.contains(OverwriteFlags::FILE),
                 )
                 .await?
         }
@@ -1247,7 +1272,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..03610700 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,
+    PxarExtractContext, 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 d9e7b899..db94b65d 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<Value, Error> {
     let repo = extract_repository_from_value(&param)?;
@@ -1388,11 +1406,19 @@ async fn restore(
             None
         };
 
+        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,
         };
 
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<R: std::io::Read>(
                 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<String>,
     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






More information about the pbs-devel mailing list