pxar: make extractor state more reusable

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-06-16 10:32:13 +02:00
parent d30c192589
commit 98c54240e6
2 changed files with 93 additions and 55 deletions

View File

@ -1,4 +1,4 @@
use std::ffi::OsString;
use std::ffi::{OsStr, OsString};
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
@ -73,6 +73,10 @@ impl PxarDir {
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn file_name(&self) -> &OsStr {
&self.file_name
}
}
pub struct PxarDirStack {
@ -130,6 +134,11 @@ impl PxarDirStack {
Ok(fd)
}
pub fn create_last_dir(&mut self, allow_existing_dirs: bool) -> Result<(), Error> {
let _: RawFd = self.last_dir_fd(allow_existing_dirs)?;
Ok(())
}
pub fn root_dir_fd(&self) -> Result<RawFd, Error> {
// should not be possible given the way we use it:
assert!(!self.dirs.is_empty(), "PxarDirStack underrun");

View File

@ -1,7 +1,7 @@
//! Code for extraction of pxar contents onto the file system.
use std::convert::TryFrom;
use std::ffi::{CStr, CString, OsStr};
use std::ffi::{CStr, CString, OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@ -23,19 +23,6 @@ use crate::pxar::dir_stack::PxarDirStack;
use crate::pxar::Flags;
use crate::pxar::metadata;
struct Extractor<'a> {
feature_flags: Flags,
allow_existing_dirs: bool,
callback: &'a mut dyn FnMut(&Path),
dir_stack: PxarDirStack,
}
impl<'a> Extractor<'a> {
fn contains_flags(&self, flag: Flags) -> bool {
self.feature_flags.contains(flag)
}
}
pub fn extract_archive<T, F>(
mut decoder: pxar::decoder::Decoder<T>,
destination: &Path,
@ -74,12 +61,12 @@ where
)
.map_err(|err| format_err!("unable to open target directory {:?}: {}", destination, err,))?;
let mut extractor = Extractor {
feature_flags,
let mut extractor = Extractor::new(
dir,
root.metadata().clone(),
allow_existing_dirs,
callback: &mut callback,
dir_stack: PxarDirStack::new(dir, root.metadata().clone()),
};
feature_flags,
);
let mut match_stack = Vec::new();
let mut current_match = true;
@ -112,22 +99,10 @@ where
};
match (did_match, entry.kind()) {
(_, EntryKind::Directory) => {
extractor.callback(entry.path());
callback(entry.path());
extractor
.dir_stack
.push(file_name_os.to_owned(), metadata.clone())?;
if current_match && match_result != Some(MatchType::Exclude) {
// We're currently in a positive match and this directory does not match an
// exclude entry, so make sure it is created:
let _ = extractor
.dir_stack
.last_dir_fd(extractor.allow_existing_dirs)
.map_err(|err| {
format_err!("error creating entry {:?}: {}", file_name_os, err)
})?;
}
let create = current_match && match_result != Some(MatchType::Exclude);
extractor.enter_directory(file_name_os.to_owned(), metadata.clone(), create)?;
// We're starting a new directory, push our old matching state and replace it with
// our new one:
@ -138,33 +113,28 @@ where
}
(_, EntryKind::GoodbyeTable) => {
// go up a directory
let dir = extractor
.dir_stack
.pop()
.map_err(|err| format_err!("unexpected end of directory entry: {}", err))?
.ok_or_else(|| format_err!("broken pxar archive (directory stack underrun)"))?;
extractor
.leave_directory()
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
// We left a directory, also get back our previous matching state. This is in sync
// with `dir_stack` so this should never be empty except for the final goodbye
// table, in which case we get back to the default of `true`.
current_match = match_stack.pop().unwrap_or(true);
if let Some(fd) = dir.try_as_raw_fd() {
metadata::apply(extractor.feature_flags, dir.metadata(), fd, &file_name)
} else {
Ok(())
}
}
(true, EntryKind::Symlink(link)) => {
extractor.callback(entry.path());
callback(entry.path());
extractor.extract_symlink(&file_name, metadata, link.as_ref())
}
(true, EntryKind::Hardlink(link)) => {
extractor.callback(entry.path());
callback(entry.path());
extractor.extract_hardlink(&file_name, metadata, link.as_os_str())
}
(true, EntryKind::Device(dev)) => {
if extractor.contains_flags(Flags::WITH_DEVICE_NODES) {
extractor.callback(entry.path());
callback(entry.path());
extractor.extract_device(&file_name, metadata, dev)
} else {
Ok(())
@ -172,7 +142,7 @@ where
}
(true, EntryKind::Fifo) => {
if extractor.contains_flags(Flags::WITH_FIFOS) {
extractor.callback(entry.path());
callback(entry.path());
extractor.extract_special(&file_name, metadata, 0)
} else {
Ok(())
@ -180,7 +150,7 @@ where
}
(true, EntryKind::Socket) => {
if extractor.contains_flags(Flags::WITH_SOCKETS) {
extractor.callback(entry.path());
callback(entry.path());
extractor.extract_special(&file_name, metadata, 0)
} else {
Ok(())
@ -206,13 +176,72 @@ where
Ok(())
}
impl<'a> Extractor<'a> {
fn parent_fd(&mut self) -> Result<RawFd, Error> {
self.dir_stack.last_dir_fd(self.allow_existing_dirs)
/// Common state for file extraction.
pub(crate) struct Extractor {
feature_flags: Flags,
allow_existing_dirs: bool,
dir_stack: PxarDirStack,
}
fn callback(&mut self, path: &Path) {
(self.callback)(path)
impl Extractor {
/// Create a new extractor state for a target directory.
pub fn new(
root_dir: Dir,
metadata: Metadata,
allow_existing_dirs: bool,
feature_flags: Flags,
) -> Self {
Self {
dir_stack: PxarDirStack::new(root_dir, metadata),
allow_existing_dirs,
feature_flags,
}
}
/// When encountering a directory during extraction, this is used to keep track of it. If
/// `create` is true it is immediately created and its metadata will be updated once we leave
/// it. If `create` is false it will only be created if it is going to have any actual content.
pub fn enter_directory(
&mut self,
file_name: OsString,
metadata: Metadata,
create: bool,
) -> Result<(), Error> {
self.dir_stack.push(file_name, metadata)?;
if create {
self.dir_stack.create_last_dir(self.allow_existing_dirs)?;
}
Ok(())
}
/// When done with a directory we need to make sure we're
pub fn leave_directory(&mut self) -> Result<(), Error> {
let dir = self
.dir_stack
.pop()
.map_err(|err| format_err!("unexpected end of directory entry: {}", err))?
.ok_or_else(|| format_err!("broken pxar archive (directory stack underrun)"))?;
if let Some(fd) = dir.try_as_raw_fd() {
metadata::apply(
self.feature_flags,
dir.metadata(),
fd,
&CString::new(dir.file_name().as_bytes())?,
)?;
}
Ok(())
}
fn contains_flags(&self, flag: Flags) -> bool {
self.feature_flags.contains(flag)
}
fn parent_fd(&mut self) -> Result<RawFd, Error> {
self.dir_stack.last_dir_fd(self.allow_existing_dirs)
}
fn extract_symlink(