diff --git a/pbs-client/src/catalog_shell.rs b/pbs-client/src/catalog_shell.rs index b11901ed..25e27b37 100644 --- a/pbs-client/src/catalog_shell.rs +++ b/pbs-client/src/catalog_shell.rs @@ -984,7 +984,7 @@ impl Shell { .clone(); let extractor = - crate::pxar::extract::Extractor::new(rootdir, root_meta, true, Flags::DEFAULT); + crate::pxar::extract::Extractor::new(rootdir, root_meta, true, false, Flags::DEFAULT); let mut extractor = ExtractorState::new( &mut self.catalog, @@ -1172,7 +1172,7 @@ impl<'a> ExtractorState<'a> { let file_name = CString::new(entry.file_name().as_bytes())?; let mut contents = entry.contents().await?; self.extractor - .async_extract_file(&file_name, entry.metadata(), *size, &mut contents) + .async_extract_file(&file_name, entry.metadata(), *size, &mut contents, false) .await } _ => { diff --git a/pbs-client/src/pxar/extract.rs b/pbs-client/src/pxar/extract.rs index 161d2cef..206e105e 100644 --- a/pbs-client/src/pxar/extract.rs +++ b/pbs-client/src/pxar/extract.rs @@ -34,6 +34,7 @@ pub struct PxarExtractOptions<'a> { pub match_list: &'a [MatchEntry], pub extract_match_default: bool, pub allow_existing_dirs: bool, + pub overwrite: bool, pub on_error: Option, } @@ -80,6 +81,7 @@ where dir, root.metadata().clone(), options.allow_existing_dirs, + options.overwrite, feature_flags, ); @@ -198,6 +200,7 @@ where &mut decoder.contents().ok_or_else(|| { format_err!("found regular file entry without contents in archive") })?, + extractor.overwrite, ), (false, _) => Ok(()), // skip this } @@ -215,6 +218,7 @@ where pub struct Extractor { feature_flags: Flags, allow_existing_dirs: bool, + overwrite: bool, dir_stack: PxarDirStack, /// For better error output we need to track the current path in the Extractor state. @@ -231,11 +235,13 @@ impl Extractor { root_dir: Dir, metadata: Metadata, allow_existing_dirs: bool, + overwrite: bool, feature_flags: Flags, ) -> Self { Self { dir_stack: PxarDirStack::new(root_dir, metadata), allow_existing_dirs, + overwrite, feature_flags, current_path: Arc::new(Mutex::new(OsString::new())), on_error: Box::new(Err), @@ -392,14 +398,21 @@ impl Extractor { metadata: &Metadata, size: u64, contents: &mut dyn io::Read, + overwrite: bool, ) -> Result<(), Error> { let parent = self.parent_fd()?; + let mut oflags = OFlag::O_CREAT | OFlag::O_WRONLY | OFlag::O_CLOEXEC; + if overwrite { + oflags = oflags | OFlag::O_TRUNC; + } else { + oflags = oflags | OFlag::O_EXCL; + } let mut file = unsafe { std::fs::File::from_raw_fd( nix::fcntl::openat( parent, file_name, - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, + oflags, Mode::from_bits(0o600).unwrap(), ) .map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?, @@ -448,14 +461,21 @@ impl Extractor { metadata: &Metadata, size: u64, contents: &mut T, + overwrite: bool, ) -> Result<(), Error> { let parent = self.parent_fd()?; + let mut oflags = OFlag::O_CREAT | OFlag::O_WRONLY | OFlag::O_CLOEXEC; + if overwrite { + oflags = oflags | OFlag::O_TRUNC; + } else { + oflags = oflags | OFlag::O_EXCL; + } let mut file = tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd( nix::fcntl::openat( parent, file_name, - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, + oflags, Mode::from_bits(0o600).unwrap(), ) .map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?, @@ -818,7 +838,7 @@ where ) })?; - Ok(Extractor::new(dir, metadata, false, Flags::DEFAULT)) + Ok(Extractor::new(dir, metadata, false, false, Flags::DEFAULT)) } pub async fn extract_sub_dir( @@ -951,6 +971,7 @@ where &mut file.contents().await.map_err(|_| { format_err!("found regular file entry without contents in archive") })?, + extractor.overwrite, ) .await? } @@ -998,6 +1019,7 @@ where &mut decoder.contents().ok_or_else(|| { format_err!("found regular file entry without contents in archive") })?, + extractor.overwrite, ) .await? } diff --git a/pxar-bin/src/main.rs b/pxar-bin/src/main.rs index 3714eb03..9d7cf9de 100644 --- a/pxar-bin/src/main.rs +++ b/pxar-bin/src/main.rs @@ -75,6 +75,11 @@ fn extract_archive_from_reader( optional: true, default: false, }, + "overwrite": { + description: "overwrite already existing files", + optional: true, + default: false, + }, "files-from": { description: "File containing match pattern for files to restore.", optional: true, @@ -112,6 +117,7 @@ fn extract_archive( no_fcaps: bool, no_acls: bool, allow_existing_dirs: bool, + overwrite: bool, files_from: Option, no_device_nodes: bool, no_fifos: bool, @@ -179,6 +185,7 @@ fn extract_archive( let options = PxarExtractOptions { match_list: &match_list, allow_existing_dirs, + overwrite, extract_match_default, on_error, };