pxar: support .pxareclude files, error report updates
Report vanished files (instead of erroring out on them), also only warn about files inaccessible due to permissions instead of bailing out. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
26e78a2efb
commit
3d68536fc2
|
@ -2,6 +2,7 @@ use std::collections::{HashSet, HashMap};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::ffi::{CStr, CString, OsStr};
|
use std::ffi::{CStr, CString, OsStr};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::io::{self, Write};
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -16,6 +17,7 @@ use pathpatterns::{MatchEntry, MatchList, MatchType, PatternFlag};
|
||||||
use pxar::Metadata;
|
use pxar::Metadata;
|
||||||
use pxar::encoder::LinkOffset;
|
use pxar::encoder::LinkOffset;
|
||||||
|
|
||||||
|
use proxmox::c_str;
|
||||||
use proxmox::sys::error::SysError;
|
use proxmox::sys::error::SysError;
|
||||||
use proxmox::tools::fd::RawFdNum;
|
use proxmox::tools::fd::RawFdNum;
|
||||||
|
|
||||||
|
@ -85,11 +87,24 @@ struct HardLinkInfo {
|
||||||
st_ino: u64,
|
st_ino: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// In case we want to collect them or redirect them we can just add this here:
|
||||||
|
struct ErrorReporter;
|
||||||
|
|
||||||
|
impl std::io::Write for ErrorReporter {
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
std::io::stderr().write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
std::io::stderr().flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Archiver<'a, 'b> {
|
struct Archiver<'a, 'b> {
|
||||||
feature_flags: Flags,
|
feature_flags: Flags,
|
||||||
fs_feature_flags: Flags,
|
fs_feature_flags: Flags,
|
||||||
fs_magic: i64,
|
fs_magic: i64,
|
||||||
patterns: &'a [MatchEntry],
|
patterns: Vec<MatchEntry>,
|
||||||
callback: &'a mut dyn FnMut(&Path) -> Result<(), Error>,
|
callback: &'a mut dyn FnMut(&Path) -> Result<(), Error>,
|
||||||
catalog: Option<&'b mut dyn BackupCatalogWriter>,
|
catalog: Option<&'b mut dyn BackupCatalogWriter>,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -98,6 +113,7 @@ struct Archiver<'a, 'b> {
|
||||||
current_st_dev: libc::dev_t,
|
current_st_dev: libc::dev_t,
|
||||||
device_set: Option<HashSet<u64>>,
|
device_set: Option<HashSet<u64>>,
|
||||||
hardlinks: HashMap<HardLinkInfo, (PathBuf, LinkOffset)>,
|
hardlinks: HashMap<HardLinkInfo, (PathBuf, LinkOffset)>,
|
||||||
|
errors: ErrorReporter,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Encoder<'a, 'b> = pxar::encoder::Encoder<'a, &'b mut dyn pxar::encoder::SeqWrite>;
|
type Encoder<'a, 'b> = pxar::encoder::Encoder<'a, &'b mut dyn pxar::encoder::SeqWrite>;
|
||||||
|
@ -153,7 +169,7 @@ where
|
||||||
fs_feature_flags,
|
fs_feature_flags,
|
||||||
fs_magic,
|
fs_magic,
|
||||||
callback: &mut callback,
|
callback: &mut callback,
|
||||||
patterns: &patterns,
|
patterns,
|
||||||
catalog,
|
catalog,
|
||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
entry_counter: 0,
|
entry_counter: 0,
|
||||||
|
@ -161,6 +177,7 @@ where
|
||||||
current_st_dev: stat.st_dev,
|
current_st_dev: stat.st_dev,
|
||||||
device_set,
|
device_set,
|
||||||
hardlinks: HashMap::new(),
|
hardlinks: HashMap::new(),
|
||||||
|
errors: ErrorReporter,
|
||||||
};
|
};
|
||||||
|
|
||||||
archiver.archive_dir_contents(&mut encoder, source_dir, true)?;
|
archiver.archive_dir_contents(&mut encoder, source_dir, true)?;
|
||||||
|
@ -197,17 +214,23 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let entry_counter = self.entry_counter;
|
let entry_counter = self.entry_counter;
|
||||||
|
|
||||||
|
let old_patterns_count = self.patterns.len();
|
||||||
|
self.read_pxar_excludes(dir.as_raw_fd())?;
|
||||||
|
|
||||||
let file_list = self.generate_directory_file_list(&mut dir, is_root)?;
|
let file_list = self.generate_directory_file_list(&mut dir, is_root)?;
|
||||||
|
|
||||||
let dir_fd = dir.as_raw_fd();
|
let dir_fd = dir.as_raw_fd();
|
||||||
|
|
||||||
let old_path = std::mem::take(&mut self.path);
|
let old_path = std::mem::take(&mut self.path);
|
||||||
|
|
||||||
for file_entry in file_list {
|
for file_entry in file_list {
|
||||||
let file_name = file_entry.name.to_bytes();
|
let file_name = file_entry.name.to_bytes();
|
||||||
|
|
||||||
if is_root && file_name == b".pxarexclude-cli" {
|
if is_root && file_name == b".pxarexclude-cli" {
|
||||||
self.encode_pxarexclude_cli(encoder, &file_entry.name)?;
|
self.encode_pxarexclude_cli(encoder, &file_entry.name)?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
(self.callback)(Path::new(OsStr::from_bytes(file_name)))?;
|
(self.callback)(Path::new(OsStr::from_bytes(file_name)))?;
|
||||||
self.path = file_entry.path;
|
self.path = file_entry.path;
|
||||||
self.add_entry(encoder, dir_fd, &file_entry.name, &file_entry.stat)
|
self.add_entry(encoder, dir_fd, &file_entry.name, &file_entry.stat)
|
||||||
|
@ -215,6 +238,76 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
}
|
}
|
||||||
self.path = old_path;
|
self.path = old_path;
|
||||||
self.entry_counter = entry_counter;
|
self.entry_counter = entry_counter;
|
||||||
|
self.patterns.truncate(old_patterns_count);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// openat() wrapper which allows but logs `EACCES` and turns `ENOENT` into `None`.
|
||||||
|
fn open_file(
|
||||||
|
&mut self,
|
||||||
|
parent: RawFd,
|
||||||
|
file_name: &CStr,
|
||||||
|
oflags: OFlag,
|
||||||
|
) -> Result<Option<Fd>, Error> {
|
||||||
|
match Fd::openat(
|
||||||
|
&unsafe { RawFdNum::from_raw_fd(parent) },
|
||||||
|
file_name,
|
||||||
|
oflags,
|
||||||
|
Mode::empty(),
|
||||||
|
) {
|
||||||
|
Ok(fd) => Ok(Some(fd)),
|
||||||
|
Err(nix::Error::Sys(Errno::ENOENT)) => Ok(None),
|
||||||
|
Err(nix::Error::Sys(Errno::EACCES)) => {
|
||||||
|
write!(self.errors, "failed to open file: {:?}: access denied", file_name)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Err(other) => Err(Error::from(other)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_pxar_excludes(&mut self, parent: RawFd) -> Result<(), Error> {
|
||||||
|
let fd = self.open_file(
|
||||||
|
parent,
|
||||||
|
c_str!(".pxarexclude"),
|
||||||
|
OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_NOCTTY,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let old_pattern_count = self.patterns.len();
|
||||||
|
|
||||||
|
if let Some(fd) = fd {
|
||||||
|
let file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) };
|
||||||
|
|
||||||
|
use io::BufRead;
|
||||||
|
for line in io::BufReader::new(file).lines() {
|
||||||
|
let line = match line {
|
||||||
|
Ok(line) => line,
|
||||||
|
Err(err) => {
|
||||||
|
let _ = write!(
|
||||||
|
self.errors,
|
||||||
|
"ignoring .pxarexclude after read error in {:?}: {}",
|
||||||
|
self.path,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
self.patterns.truncate(old_pattern_count);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let line = line.trim();
|
||||||
|
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match MatchEntry::parse_pattern(line, PatternFlag::PATH_NAME, MatchType::Exclude) {
|
||||||
|
Ok(pattern) => self.patterns.push(pattern),
|
||||||
|
Err(err) => {
|
||||||
|
let _ = write!(self.errors, "bad pattern in {:?}: {}", self.path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -234,8 +327,6 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
metadata.stat.mode = pxar::format::mode::IFREG | 0o600;
|
metadata.stat.mode = pxar::format::mode::IFREG | 0o600;
|
||||||
|
|
||||||
let mut file = encoder.create_file(&metadata, ".pxarexclude-cli", content.len() as u64)?;
|
let mut file = encoder.create_file(&metadata, ".pxarexclude-cli", content.len() as u64)?;
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
file.write_all(&content)?;
|
file.write_all(&content)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -272,7 +363,6 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if file_name_bytes == b".pxarexclude" {
|
if file_name_bytes == b".pxarexclude" {
|
||||||
// FIXME: handle this file!
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +405,11 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
Ok(file_list)
|
Ok(file_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn report_vanished_file(&mut self) -> Result<(), Error> {
|
||||||
|
write!(self.errors, "warning: file vanished while reading: {:?}", self.path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn add_entry(
|
fn add_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
encoder: &mut Encoder,
|
encoder: &mut Encoder,
|
||||||
|
@ -331,13 +426,20 @@ impl<'a, 'b> Archiver<'a, 'b> {
|
||||||
OFlag::empty()
|
OFlag::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
let fd = Fd::openat(
|
let fd = self.open_file(
|
||||||
&unsafe { RawFdNum::from_raw_fd(parent) },
|
parent,
|
||||||
c_file_name,
|
c_file_name,
|
||||||
open_mode | OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC | OFlag::O_NOCTTY,
|
open_mode | OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC | OFlag::O_NOCTTY,
|
||||||
Mode::empty(),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let fd = match fd {
|
||||||
|
Some(fd) => fd,
|
||||||
|
None => {
|
||||||
|
self.report_vanished_file()?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic)?;
|
let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic)?;
|
||||||
|
|
||||||
if self
|
if self
|
||||||
|
|
Loading…
Reference in New Issue