src/pxar/sequentail_decoder.rs: fix issue when restoring with glob pattern.
Partial extraction of an archive with a glob pattern, e.g. '**/*.conf' lead to the unexpected behaviour of restoring all partially matched directories (in this example all of them). This patch fixes this unexpected behaviour by only restoring those directories were the directory or one of its sub-items fully matched the pattern and should therefore be restored. To achive this behavoiur, directory metadata is pushed onto a stack and restored on demand. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
parent
51ac99c314
commit
beffac999f
|
@ -7,6 +7,7 @@ use endian_trait::Endian;
|
||||||
|
|
||||||
use super::format_definition::*;
|
use super::format_definition::*;
|
||||||
use super::exclude_pattern::*;
|
use super::exclude_pattern::*;
|
||||||
|
use super::dir_buffer::*;
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -612,38 +613,27 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_dir_sequential(
|
fn restore_dir(
|
||||||
&mut self,
|
&mut self,
|
||||||
base_path: &Path,
|
base_path: &Path,
|
||||||
relative_path: &mut PathBuf,
|
dirs: &mut PxarDirBuf,
|
||||||
full_path: &PathBuf,
|
entry: CaFormatEntry,
|
||||||
parent_fd: Option<RawFd>,
|
|
||||||
entry: &CaFormatEntry,
|
|
||||||
filename: &OsStr,
|
filename: &OsStr,
|
||||||
|
matched: MatchType,
|
||||||
match_pattern: &Vec<PxarExcludePattern>,
|
match_pattern: &Vec<PxarExcludePattern>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// By passing back Some(dir) we assure the fd stays open and in scope
|
|
||||||
let (fd, _dir) = if let Some(pfd) = parent_fd {
|
|
||||||
let dir = if filename.is_empty() {
|
|
||||||
nix::dir::Dir::openat(pfd, ".", OFlag::O_DIRECTORY, Mode::empty())?
|
|
||||||
} else {
|
|
||||||
dir_mkdirat(pfd, filename, !self.allow_existing_dirs)
|
|
||||||
.map_err(|err| format_err!("unable to open directory {:?} - {}", full_path, err))?
|
|
||||||
};
|
|
||||||
(Some(dir.as_raw_fd()), Some(dir))
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mut head, attr) = self.read_attributes()
|
let (mut head, attr) = self.read_attributes()
|
||||||
.map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?;
|
.map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?;
|
||||||
|
|
||||||
|
let dir = PxarDir::new(filename, entry, attr);
|
||||||
|
dirs.push(dir);
|
||||||
|
if matched == MatchType::Include {
|
||||||
|
dirs.create_all_dirs(!self.allow_existing_dirs)?;
|
||||||
|
}
|
||||||
|
|
||||||
while head.htype == CA_FORMAT_FILENAME {
|
while head.htype == CA_FORMAT_FILENAME {
|
||||||
let name = self.read_filename(head.size)?;
|
let name = self.read_filename(head.size)?;
|
||||||
relative_path.push(&name);
|
self.restore_dir_entry(base_path, dirs, &name, matched, match_pattern)?;
|
||||||
self.restore_sequential(base_path, relative_path, &name, fd, match_pattern)?;
|
|
||||||
relative_path.pop();
|
|
||||||
head = self.read_item()?;
|
head = self.read_item()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,17 +641,18 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
|
||||||
bail!("got unknown header type inside directory entry {:016x}", head.htype);
|
bail!("got unknown header type inside directory entry {:016x}", head.htype);
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("Skip Goodbye");
|
|
||||||
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
|
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
|
||||||
self.skip_bytes((head.size - HEADER_SIZE) as usize)?;
|
self.skip_bytes((head.size - HEADER_SIZE) as usize)?;
|
||||||
|
|
||||||
// Only restore if we want to restore this part of the archive
|
let last = dirs.pop()
|
||||||
if let Some(fd) = fd {
|
.ok_or_else(|| format_err!("Tried to pop beyond dir root - this should not happen!"))?;
|
||||||
self.restore_ugid(&entry, fd)?;
|
if let Some(d) = last.dir {
|
||||||
|
let fd = d.as_raw_fd();
|
||||||
|
self.restore_ugid(&last.entry, fd)?;
|
||||||
// fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350
|
// fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350
|
||||||
self.restore_attributes(fd, &attr, &entry)?;
|
self.restore_attributes(fd, &last.attr, &last.entry)?;
|
||||||
self.restore_mode(&entry, fd)?;
|
self.restore_mode(&last.entry, fd)?;
|
||||||
self.restore_mtime(&entry, fd)?;
|
self.restore_mtime(&last.entry, fd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -670,33 +661,70 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
|
||||||
/// Restore an archive into the specified directory.
|
/// Restore an archive into the specified directory.
|
||||||
///
|
///
|
||||||
/// The directory is created if it does not exist.
|
/// The directory is created if it does not exist.
|
||||||
pub fn restore(&mut self, path: &Path, match_pattern: &Vec<PxarExcludePattern>) -> Result<(), Error> {
|
pub fn restore(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
match_pattern: &Vec<PxarExcludePattern>
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
let _ = std::fs::create_dir(path);
|
let _ = std::fs::create_dir(path);
|
||||||
|
|
||||||
let dir = nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty())
|
let dir = nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty())
|
||||||
.map_err(|err| format_err!("unable to open target directory {:?} - {}", path, err))?;
|
.map_err(|err| format_err!("unable to open target directory {:?} - {}", path, err))?;
|
||||||
let fd = Some(dir.as_raw_fd());
|
let fd = dir.as_raw_fd();
|
||||||
|
let mut dirs = PxarDirBuf::new(fd);
|
||||||
|
// An empty match pattern list indicates to restore the full archive.
|
||||||
|
let matched = if match_pattern.len() == 0 {
|
||||||
|
MatchType::Include
|
||||||
|
} else {
|
||||||
|
MatchType::None
|
||||||
|
};
|
||||||
|
|
||||||
let mut relative_path = PathBuf::new();
|
let header: CaFormatHeader = self.read_item()?;
|
||||||
self.restore_sequential(path, &mut relative_path, &OsString::new(), fd, match_pattern)
|
check_ca_header::<CaFormatEntry>(&header, CA_FORMAT_ENTRY)?;
|
||||||
|
let entry: CaFormatEntry = self.read_item()?;
|
||||||
|
|
||||||
|
let (mut head, attr) = self.read_attributes()
|
||||||
|
.map_err(|err| format_err!("Reading of directory attributes failed - {}", err))?;
|
||||||
|
|
||||||
|
while head.htype == CA_FORMAT_FILENAME {
|
||||||
|
let name = self.read_filename(head.size)?;
|
||||||
|
self.restore_dir_entry(path, &mut dirs, &name, matched, match_pattern)?;
|
||||||
|
head = self.read_item()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if head.htype != CA_FORMAT_GOODBYE {
|
||||||
|
bail!("got unknown header type inside directory entry {:016x}", head.htype);
|
||||||
|
}
|
||||||
|
|
||||||
|
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
|
||||||
|
self.skip_bytes((head.size - HEADER_SIZE) as usize)?;
|
||||||
|
|
||||||
|
self.restore_ugid(&entry, fd)?;
|
||||||
|
// fcaps have to be restored after restore_ugid as chown clears security.capability xattr, see CVE-2015-1350
|
||||||
|
self.restore_attributes(fd, &attr, &entry)?;
|
||||||
|
self.restore_mode(&entry, fd)?;
|
||||||
|
self.restore_mtime(&entry, fd)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_sequential(
|
fn restore_dir_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
base_path: &Path,
|
base_path: &Path,
|
||||||
relative_path: &mut PathBuf,
|
dirs: &mut PxarDirBuf,
|
||||||
filename: &OsStr, // repeats path last relative_path component
|
filename: &OsStr,
|
||||||
parent_fd: Option<RawFd>,
|
parent_matched: MatchType,
|
||||||
match_pattern: &Vec<PxarExcludePattern>,
|
match_pattern: &Vec<PxarExcludePattern>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let full_path = base_path.join(&relative_path);
|
let relative_path = dirs.as_path_buf();
|
||||||
|
let full_path = base_path.join(&relative_path).join(filename);
|
||||||
|
|
||||||
let head: CaFormatHeader = self.read_item()?;
|
let head: CaFormatHeader = self.read_item()?;
|
||||||
if head.htype == PXAR_FORMAT_HARDLINK {
|
if head.htype == PXAR_FORMAT_HARDLINK {
|
||||||
let (target, _offset) = self.read_hardlink(head.size)?;
|
let (target, _offset) = self.read_hardlink(head.size)?;
|
||||||
let target_path = base_path.join(&target);
|
let target_path = base_path.join(&target);
|
||||||
if let Some(_) = parent_fd {
|
if dirs.last_dir_fd().is_some() {
|
||||||
(self.callback)(&full_path)?;
|
(self.callback)(&full_path)?;
|
||||||
hardlink(&target_path, &full_path)?;
|
hardlink(&target_path, &full_path)?;
|
||||||
}
|
}
|
||||||
|
@ -706,26 +734,42 @@ impl <'a, R: Read, F: Fn(&Path) -> Result<(), Error>> SequentialDecoder<'a, R, F
|
||||||
check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
|
check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
|
||||||
let entry: CaFormatEntry = self.read_item()?;
|
let entry: CaFormatEntry = self.read_item()?;
|
||||||
|
|
||||||
let mut fd = parent_fd;
|
|
||||||
let mut child_pattern = Vec::new();
|
let mut child_pattern = Vec::new();
|
||||||
|
// If parent was a match, then children should be assumed to match too
|
||||||
|
// This is especially the case when the full archive is restored and
|
||||||
|
// there are no match pattern.
|
||||||
|
let mut matched = parent_matched;
|
||||||
if match_pattern.len() > 0 {
|
if match_pattern.len() > 0 {
|
||||||
if filename.is_empty() {
|
match match_filename(filename, entry.mode as u32 & libc::S_IFMT == libc::S_IFDIR, match_pattern) {
|
||||||
child_pattern = match_pattern.clone();
|
(MatchType::Include, pattern) => {
|
||||||
} else {
|
matched = MatchType::Include;
|
||||||
match match_filename(filename, entry.mode as u32 & libc::S_IFMT == libc::S_IFDIR, match_pattern) {
|
child_pattern = pattern;
|
||||||
(MatchType::None, _) => fd = None,
|
},
|
||||||
(MatchType::Exclude, _) => fd = None,
|
(MatchType::None, _) => matched = MatchType::None,
|
||||||
(_, pattern) => child_pattern = pattern,
|
(MatchType::Exclude, _) => matched = MatchType::Exclude,
|
||||||
}
|
(MatchType::PartialExclude, pattern) => {
|
||||||
|
matched = MatchType::PartialExclude;
|
||||||
|
child_pattern = pattern;
|
||||||
|
},
|
||||||
|
(MatchType::PartialInclude, pattern) => {
|
||||||
|
matched = MatchType::PartialInclude;
|
||||||
|
child_pattern = pattern;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fd = if matched == MatchType::Include {
|
||||||
|
Some(dirs.create_all_dirs(!self.allow_existing_dirs)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if fd.is_some() {
|
if fd.is_some() {
|
||||||
(self.callback)(&full_path)?;
|
(self.callback)(&full_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
match entry.mode as u32 & libc::S_IFMT {
|
match entry.mode as u32 & libc::S_IFMT {
|
||||||
libc::S_IFDIR => self.restore_dir_sequential(base_path, relative_path, &full_path, fd, &entry, &filename, &child_pattern),
|
libc::S_IFDIR => self.restore_dir(base_path, dirs, entry, &filename, matched, &child_pattern),
|
||||||
libc::S_IFLNK => self.restore_symlink(fd, &full_path, &entry, &filename),
|
libc::S_IFLNK => self.restore_symlink(fd, &full_path, &entry, &filename),
|
||||||
libc::S_IFSOCK => self.restore_socket(fd, &entry, &filename),
|
libc::S_IFSOCK => self.restore_socket(fd, &entry, &filename),
|
||||||
libc::S_IFIFO => self.restore_fifo(fd, &entry, &filename),
|
libc::S_IFIFO => self.restore_fifo(fd, &entry, &filename),
|
||||||
|
|
Loading…
Reference in New Issue