pbs-client: pxar: preserve error context
In order to preserve the source(s) of errors, `anyhow::Context` is used instead of propagating errors via `Result::map_err()` and / or `anyhow::format_err!()`. This makes it possible to access e.g. an underlying `io::Error` or `nix::Errno` etc. that caused an execution path to fail. Certain usages of `anyhow::bail!()` are also changed / replaced in order to preserve context. Signed-off-by: Max Carrara <m.carrara@proxmox.com>
This commit is contained in:
parent
6afda74c33
commit
54ef4f157a
@ -7,7 +7,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, Context, Error};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use nix::dir::Dir;
|
use nix::dir::Dir;
|
||||||
@ -159,7 +159,7 @@ where
|
|||||||
fs_magic,
|
fs_magic,
|
||||||
&mut fs_feature_flags,
|
&mut fs_feature_flags,
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("failed to get metadata for source directory: {}", err))?;
|
.context("failed to get metadata for source directory")?;
|
||||||
|
|
||||||
let mut device_set = options.device_set.clone();
|
let mut device_set = options.device_set.clone();
|
||||||
if let Some(ref mut set) = device_set {
|
if let Some(ref mut set) = device_set {
|
||||||
@ -441,7 +441,7 @@ impl Archiver {
|
|||||||
) {
|
) {
|
||||||
Ok(stat) => stat,
|
Ok(stat) => stat,
|
||||||
Err(ref err) if err.not_found() => continue,
|
Err(ref err) if err.not_found() => continue,
|
||||||
Err(err) => bail!("stat failed on {:?}: {}", full_path, err),
|
Err(err) => return Err(err).context(format!("stat failed on {:?}", full_path)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let match_path = PathBuf::from("/").join(full_path.clone());
|
let match_path = PathBuf::from("/").join(full_path.clone());
|
||||||
@ -796,7 +796,7 @@ fn get_fcaps(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(Errno::EBADF) => Ok(()), // symlinks
|
Err(Errno::EBADF) => Ok(()), // symlinks
|
||||||
Err(err) => bail!("failed to read file capabilities: {}", err),
|
Err(err) => Err(err).context("failed to read file capabilities"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,7 +818,7 @@ fn get_xattr_fcaps_acl(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(Errno::EBADF) => return Ok(()), // symlinks
|
Err(Errno::EBADF) => return Ok(()), // symlinks
|
||||||
Err(err) => bail!("failed to read xattrs: {}", err),
|
Err(err) => return Err(err).context("failed to read xattrs"),
|
||||||
};
|
};
|
||||||
|
|
||||||
for attr in &xattrs {
|
for attr in &xattrs {
|
||||||
@ -843,7 +843,9 @@ fn get_xattr_fcaps_acl(
|
|||||||
Err(Errno::ENODATA) => (), // it got removed while we were iterating...
|
Err(Errno::ENODATA) => (), // it got removed while we were iterating...
|
||||||
Err(Errno::EOPNOTSUPP) => (), // shouldn't be possible so just ignore this
|
Err(Errno::EOPNOTSUPP) => (), // shouldn't be possible so just ignore this
|
||||||
Err(Errno::EBADF) => (), // symlinks, shouldn't be able to reach this either
|
Err(Errno::EBADF) => (), // symlinks, shouldn't be able to reach this either
|
||||||
Err(err) => bail!("error reading extended attribute {:?}: {}", attr, err),
|
Err(err) => {
|
||||||
|
return Err(err).context(format!("error reading extended attribute {attr:?}"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,7 +860,7 @@ fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
|
|||||||
Err(errno) if errno_is_unsupported(errno) => {
|
Err(errno) if errno_is_unsupported(errno) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(err) => bail!("failed to read file attributes: {}", err),
|
Err(err) => return Err(err).context("failed to read file attributes"),
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.stat.flags |= Flags::from_chattr(attr).bits();
|
metadata.stat.flags |= Flags::from_chattr(attr).bits();
|
||||||
@ -880,7 +882,7 @@ fn get_fat_attr(metadata: &mut Metadata, fd: RawFd, fs_magic: i64) -> Result<(),
|
|||||||
Err(errno) if errno_is_unsupported(errno) => {
|
Err(errno) if errno_is_unsupported(errno) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(err) => bail!("failed to read fat attributes: {}", err),
|
Err(err) => return Err(err).context("failed to read fat attributes"),
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.stat.flags |= Flags::from_fat_attr(attr).bits();
|
metadata.stat.flags |= Flags::from_fat_attr(attr).bits();
|
||||||
@ -919,7 +921,7 @@ fn get_quota_project_id(
|
|||||||
if errno_is_unsupported(errno) {
|
if errno_is_unsupported(errno) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
bail!("error while reading quota project id ({})", errno);
|
return Err(errno).context("error while reading quota project id");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,7 +975,7 @@ fn get_acl_do(
|
|||||||
Err(Errno::EBADF) => return Ok(()),
|
Err(Errno::EBADF) => return Ok(()),
|
||||||
// Don't bail if there is no data
|
// Don't bail if there is no data
|
||||||
Err(Errno::ENODATA) => return Ok(()),
|
Err(Errno::ENODATA) => return Ok(()),
|
||||||
Err(err) => bail!("error while reading ACL - {}", err),
|
Err(err) => return Err(err).context("error while reading ACL"),
|
||||||
};
|
};
|
||||||
|
|
||||||
process_acl(metadata, acl, acl_type)
|
process_acl(metadata, acl, acl_type)
|
||||||
|
@ -2,7 +2,7 @@ use std::ffi::OsString;
|
|||||||
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
|
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, Context, Error};
|
||||||
use nix::dir::Dir;
|
use nix::dir::Dir;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat::{mkdirat, Mode};
|
use nix::sys::stat::{mkdirat, Mode};
|
||||||
@ -130,7 +130,7 @@ impl PxarDirStack {
|
|||||||
let dirs_len = self.dirs.len();
|
let dirs_len = self.dirs.len();
|
||||||
let mut fd = self.dirs[self.created - 1]
|
let mut fd = self.dirs[self.created - 1]
|
||||||
.try_as_borrowed_fd()
|
.try_as_borrowed_fd()
|
||||||
.ok_or_else(|| format_err!("lost track of directory file descriptors"))?
|
.context("lost track of directory file descriptors")?
|
||||||
.as_raw_fd();
|
.as_raw_fd();
|
||||||
|
|
||||||
while self.created < dirs_len {
|
while self.created < dirs_len {
|
||||||
@ -142,7 +142,7 @@ impl PxarDirStack {
|
|||||||
|
|
||||||
self.dirs[self.created - 1]
|
self.dirs[self.created - 1]
|
||||||
.try_as_borrowed_fd()
|
.try_as_borrowed_fd()
|
||||||
.ok_or_else(|| format_err!("lost track of directory file descriptors"))
|
.context("lost track of directory file descriptors")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_last_dir(&mut self, allow_existing_dirs: bool) -> Result<(), Error> {
|
pub fn create_last_dir(&mut self, allow_existing_dirs: bool) -> Result<(), Error> {
|
||||||
@ -156,7 +156,7 @@ impl PxarDirStack {
|
|||||||
|
|
||||||
self.dirs[0]
|
self.dirs[0]
|
||||||
.try_as_borrowed_fd()
|
.try_as_borrowed_fd()
|
||||||
.ok_or_else(|| format_err!("lost track of directory file descriptors"))
|
.context("lost track of directory file descriptors")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
pub fn path(&self) -> &Path {
|
||||||
|
@ -8,7 +8,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, Context, Error};
|
||||||
use nix::dir::Dir;
|
use nix::dir::Dir;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
@ -55,8 +55,8 @@ where
|
|||||||
|
|
||||||
let root = decoder
|
let root = decoder
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| format_err!("found empty pxar archive"))?
|
.context("found empty pxar archive")?
|
||||||
.map_err(|err| format_err!("error reading pxar archive: {}", err))?;
|
.context("error reading pxar archive")?;
|
||||||
|
|
||||||
if !root.is_dir() {
|
if !root.is_dir() {
|
||||||
bail!("pxar archive does not start with a directory entry!");
|
bail!("pxar archive does not start with a directory entry!");
|
||||||
@ -67,14 +67,14 @@ where
|
|||||||
None,
|
None,
|
||||||
Some(CreateOptions::new().perm(Mode::from_bits_truncate(0o700))),
|
Some(CreateOptions::new().perm(Mode::from_bits_truncate(0o700))),
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("error creating directory {:?}: {}", destination, err))?;
|
.with_context(|| format!("error creating directory {:?}", destination))?;
|
||||||
|
|
||||||
let dir = Dir::open(
|
let dir = Dir::open(
|
||||||
destination,
|
destination,
|
||||||
OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
|
OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
|
||||||
Mode::empty(),
|
Mode::empty(),
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("unable to open target directory {:?}: {}", destination, err,))?;
|
.with_context(|| format!("unable to open target directory {:?}", destination))?;
|
||||||
|
|
||||||
let mut extractor = Extractor::new(
|
let mut extractor = Extractor::new(
|
||||||
dir,
|
dir,
|
||||||
@ -92,7 +92,7 @@ where
|
|||||||
let mut err_path_stack = vec![OsString::from("/")];
|
let mut err_path_stack = vec![OsString::from("/")];
|
||||||
let mut current_match = options.extract_match_default;
|
let mut current_match = options.extract_match_default;
|
||||||
while let Some(entry) = decoder.next() {
|
while let Some(entry) = decoder.next() {
|
||||||
let entry = entry.map_err(|err| format_err!("error reading pxar archive: {}", err))?;
|
let entry = entry.context("error reading pxar archive")?;
|
||||||
|
|
||||||
let file_name_os = entry.file_name();
|
let file_name_os = entry.file_name();
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_name = CString::new(file_name_os.as_bytes())
|
let file_name = CString::new(file_name_os.as_bytes())
|
||||||
.map_err(|_| format_err!("encountered file name with null-bytes"))?;
|
.context("encountered file name with null-bytes")?;
|
||||||
|
|
||||||
let metadata = entry.metadata();
|
let metadata = entry.metadata();
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ where
|
|||||||
let create = current_match && match_result != Some(MatchType::Exclude);
|
let create = current_match && match_result != Some(MatchType::Exclude);
|
||||||
extractor
|
extractor
|
||||||
.enter_directory(file_name_os.to_owned(), metadata.clone(), create)
|
.enter_directory(file_name_os.to_owned(), metadata.clone(), create)
|
||||||
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
|
.with_context(|| format!("error at entry {:?}", file_name_os))?;
|
||||||
|
|
||||||
// We're starting a new directory, push our old matching state and replace it with
|
// We're starting a new directory, push our old matching state and replace it with
|
||||||
// our new one:
|
// our new one:
|
||||||
@ -142,16 +142,16 @@ where
|
|||||||
(_, EntryKind::GoodbyeTable) => {
|
(_, EntryKind::GoodbyeTable) => {
|
||||||
// go up a directory
|
// go up a directory
|
||||||
|
|
||||||
extractor.set_path(err_path_stack.pop().ok_or_else(|| {
|
extractor.set_path(err_path_stack.pop().with_context(|| {
|
||||||
format_err!(
|
format!(
|
||||||
"error at entry {:?}: unexpected end of directory",
|
"error at entry {:?} - unexpected end of directory",
|
||||||
file_name_os
|
file_name_os
|
||||||
)
|
)
|
||||||
})?);
|
})?);
|
||||||
|
|
||||||
extractor
|
extractor
|
||||||
.leave_directory()
|
.leave_directory()
|
||||||
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
|
.with_context(|| format!("error at entry {:?}", file_name_os))?;
|
||||||
|
|
||||||
// We left a directory, also get back our previous matching state. This is in sync
|
// 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
|
// with `dir_stack` so this should never be empty except for the final goodbye
|
||||||
@ -196,14 +196,14 @@ where
|
|||||||
&file_name,
|
&file_name,
|
||||||
metadata,
|
metadata,
|
||||||
*size,
|
*size,
|
||||||
&mut decoder.contents().ok_or_else(|| {
|
&mut decoder
|
||||||
format_err!("found regular file entry without contents in archive")
|
.contents()
|
||||||
})?,
|
.context("found regular file entry without contents in archive")?,
|
||||||
extractor.overwrite,
|
extractor.overwrite,
|
||||||
),
|
),
|
||||||
(false, _) => Ok(()), // skip this
|
(false, _) => Ok(()), // skip this
|
||||||
}
|
}
|
||||||
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
|
.with_context(|| format!("error at entry {:?}", file_name_os))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !extractor.dir_stack.is_empty() {
|
if !extractor.dir_stack.is_empty() {
|
||||||
@ -254,7 +254,7 @@ impl Extractor {
|
|||||||
pub fn on_error(&mut self, mut on_error: Box<dyn FnMut(Error) -> Result<(), Error> + Send>) {
|
pub fn on_error(&mut self, mut on_error: Box<dyn FnMut(Error) -> Result<(), Error> + Send>) {
|
||||||
let path = Arc::clone(&self.current_path);
|
let path = Arc::clone(&self.current_path);
|
||||||
self.on_error = Box::new(move |err: Error| -> Result<(), Error> {
|
self.on_error = Box::new(move |err: Error| -> Result<(), Error> {
|
||||||
on_error(format_err!("error at {:?}: {}", path.lock().unwrap(), err))
|
on_error(err.context(format!("error at {:?}", path.lock().unwrap())))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,8 +291,8 @@ impl Extractor {
|
|||||||
let dir = self
|
let dir = self
|
||||||
.dir_stack
|
.dir_stack
|
||||||
.pop()
|
.pop()
|
||||||
.map_err(|err| format_err!("unexpected end of directory entry: {}", err))?
|
.context("unexpected end of directory entry")?
|
||||||
.ok_or_else(|| format_err!("broken pxar archive (directory stack underrun)"))?;
|
.context("broken pxar archive (directory stack underrun)")?;
|
||||||
|
|
||||||
if let Some(fd) = dir.try_as_borrowed_fd() {
|
if let Some(fd) = dir.try_as_borrowed_fd() {
|
||||||
metadata::apply(
|
metadata::apply(
|
||||||
@ -302,7 +302,7 @@ impl Extractor {
|
|||||||
&path_info,
|
&path_info,
|
||||||
&mut self.on_error,
|
&mut self.on_error,
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("failed to apply directory metadata: {}", err))?;
|
.context("failed to apply directory metadata")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -316,7 +316,7 @@ impl Extractor {
|
|||||||
self.dir_stack
|
self.dir_stack
|
||||||
.last_dir_fd(self.allow_existing_dirs)
|
.last_dir_fd(self.allow_existing_dirs)
|
||||||
.map(|d| d.as_raw_fd())
|
.map(|d| d.as_raw_fd())
|
||||||
.map_err(|err| format_err!("failed to get parent directory file descriptor: {}", err))
|
.context("failed to get parent directory file descriptor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_symlink(
|
pub fn extract_symlink(
|
||||||
@ -370,16 +370,12 @@ impl Extractor {
|
|||||||
device: libc::dev_t,
|
device: libc::dev_t,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mode = metadata.stat.mode;
|
let mode = metadata.stat.mode;
|
||||||
let mode = u32::try_from(mode).map_err(|_| {
|
let mode = u32::try_from(mode).with_context(|| {
|
||||||
format_err!(
|
format!("device node's mode contains illegal bits: 0x{mode:x} (0o{mode:o})")
|
||||||
"device node's mode contains illegal bits: 0x{:x} (0o{:o})",
|
|
||||||
mode,
|
|
||||||
mode,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let parent = self.parent_fd()?;
|
let parent = self.parent_fd()?;
|
||||||
unsafe { c_result!(libc::mknodat(parent, file_name.as_ptr(), mode, device)) }
|
unsafe { c_result!(libc::mknodat(parent, file_name.as_ptr(), mode, device)) }
|
||||||
.map_err(|err| format_err!("failed to create device node: {}", err))?;
|
.context("failed to create device node")?;
|
||||||
|
|
||||||
metadata::apply_at(
|
metadata::apply_at(
|
||||||
self.feature_flags,
|
self.feature_flags,
|
||||||
@ -409,7 +405,7 @@ impl Extractor {
|
|||||||
let mut file = unsafe {
|
let mut file = unsafe {
|
||||||
std::fs::File::from_raw_fd(
|
std::fs::File::from_raw_fd(
|
||||||
nix::fcntl::openat(parent, file_name, oflags, Mode::from_bits(0o600).unwrap())
|
nix::fcntl::openat(parent, file_name, oflags, Mode::from_bits(0o600).unwrap())
|
||||||
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?,
|
.with_context(|| format!("failed to create file {file_name:?}"))?,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -419,10 +415,10 @@ impl Extractor {
|
|||||||
file.as_raw_fd(),
|
file.as_raw_fd(),
|
||||||
&mut self.on_error,
|
&mut self.on_error,
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("failed to apply initial flags: {}", err))?;
|
.context("failed to apply initial flags")?;
|
||||||
|
|
||||||
let result = sparse_copy(&mut *contents, &mut file)
|
let result =
|
||||||
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
|
sparse_copy(&mut *contents, &mut file).context("failed to copy file contents")?;
|
||||||
|
|
||||||
if size != result.written {
|
if size != result.written {
|
||||||
bail!(
|
bail!(
|
||||||
@ -436,7 +432,7 @@ impl Extractor {
|
|||||||
while match nix::unistd::ftruncate(file.as_raw_fd(), size as i64) {
|
while match nix::unistd::ftruncate(file.as_raw_fd(), size as i64) {
|
||||||
Ok(_) => false,
|
Ok(_) => false,
|
||||||
Err(errno) if errno == nix::errno::Errno::EINTR => true,
|
Err(errno) if errno == nix::errno::Errno::EINTR => true,
|
||||||
Err(err) => bail!("error setting file size: {}", err),
|
Err(err) => return Err(err).context("error setting file size"),
|
||||||
} {}
|
} {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +463,7 @@ impl Extractor {
|
|||||||
let mut file = tokio::fs::File::from_std(unsafe {
|
let mut file = tokio::fs::File::from_std(unsafe {
|
||||||
std::fs::File::from_raw_fd(
|
std::fs::File::from_raw_fd(
|
||||||
nix::fcntl::openat(parent, file_name, oflags, Mode::from_bits(0o600).unwrap())
|
nix::fcntl::openat(parent, file_name, oflags, Mode::from_bits(0o600).unwrap())
|
||||||
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?,
|
.with_context(|| format!("failed to create file {file_name:?}"))?,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -477,11 +473,11 @@ impl Extractor {
|
|||||||
file.as_raw_fd(),
|
file.as_raw_fd(),
|
||||||
&mut self.on_error,
|
&mut self.on_error,
|
||||||
)
|
)
|
||||||
.map_err(|err| format_err!("failed to apply initial flags: {}", err))?;
|
.context("failed to apply initial flags")?;
|
||||||
|
|
||||||
let result = sparse_copy_async(&mut *contents, &mut file)
|
let result = sparse_copy_async(&mut *contents, &mut file)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
|
.context("failed to copy file contents")?;
|
||||||
|
|
||||||
if size != result.written {
|
if size != result.written {
|
||||||
bail!(
|
bail!(
|
||||||
@ -495,7 +491,7 @@ impl Extractor {
|
|||||||
while match nix::unistd::ftruncate(file.as_raw_fd(), size as i64) {
|
while match nix::unistd::ftruncate(file.as_raw_fd(), size as i64) {
|
||||||
Ok(_) => false,
|
Ok(_) => false,
|
||||||
Err(errno) if errno == nix::errno::Errno::EINTR => true,
|
Err(errno) if errno == nix::errno::Errno::EINTR => true,
|
||||||
Err(err) => bail!("error setting file size: {}", err),
|
Err(err) => return Err(err).context("error setting file size"),
|
||||||
} {}
|
} {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,12 +528,12 @@ where
|
|||||||
header.set_size(size);
|
header.set_size(size);
|
||||||
add_metadata_to_header(&mut header, metadata);
|
add_metadata_to_header(&mut header, metadata);
|
||||||
header.set_cksum();
|
header.set_cksum();
|
||||||
|
|
||||||
match contents {
|
match contents {
|
||||||
Some(content) => tar.add_entry(&mut header, path, content).await,
|
Some(content) => tar.add_entry(&mut header, path, content).await,
|
||||||
None => tar.add_entry(&mut header, path, tokio::io::empty()).await,
|
None => tar.add_entry(&mut header, path, tokio::io::empty()).await,
|
||||||
}
|
}
|
||||||
.map_err(|err| format_err!("could not send file entry: {}", err))?;
|
.context("could not send file entry")
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a tar file from `path` and writes it into `output`
|
/// Creates a tar file from `path` and writes it into `output`
|
||||||
@ -551,7 +547,7 @@ where
|
|||||||
let file = root
|
let file = root
|
||||||
.lookup(&path)
|
.lookup(&path)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| format_err!("error opening '{:?}'", path.as_ref()))?;
|
.with_context(|| format!("error opening {:?}", path.as_ref()))?;
|
||||||
|
|
||||||
let mut components = file.entry().path().components();
|
let mut components = file.entry().path().components();
|
||||||
components.next_back(); // discard last
|
components.next_back(); // discard last
|
||||||
@ -574,13 +570,13 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_entry(&mut header, path, tokio::io::empty())
|
.add_entry(&mut header, path, tokio::io::empty())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send dir entry: {}", err))?;
|
.context("could not send dir entry")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut decoder = dir.decode_full().await?;
|
let mut decoder = dir.decode_full().await?;
|
||||||
decoder.enable_goodbye_entries(false);
|
decoder.enable_goodbye_entries(false);
|
||||||
while let Some(entry) = decoder.next().await {
|
while let Some(entry) = decoder.next().await {
|
||||||
let entry = entry.map_err(|err| format_err!("cannot decode entry: {}", err))?;
|
let entry = entry.context("cannot decode entry")?;
|
||||||
|
|
||||||
let metadata = entry.metadata();
|
let metadata = entry.metadata();
|
||||||
let path = entry.path().strip_prefix(prefix)?;
|
let path = entry.path().strip_prefix(prefix)?;
|
||||||
@ -595,7 +591,7 @@ where
|
|||||||
let entry = root
|
let entry = root
|
||||||
.lookup(&path)
|
.lookup(&path)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| format_err!("error looking up '{:?}'", path))?;
|
.with_context(|| format!("error looking up {path:?}"))?;
|
||||||
let realfile = accessor.follow_hardlink(&entry).await?;
|
let realfile = accessor.follow_hardlink(&entry).await?;
|
||||||
let metadata = realfile.entry().metadata();
|
let metadata = realfile.entry().metadata();
|
||||||
let realpath = Path::new(link);
|
let realpath = Path::new(link);
|
||||||
@ -630,7 +626,7 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_link(&mut header, path, stripped_path)
|
.add_link(&mut header, path, stripped_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send hardlink entry: {}", err))?;
|
.context("could not send hardlink entry")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EntryKind::Symlink(link) if !link.data.is_empty() => {
|
EntryKind::Symlink(link) if !link.data.is_empty() => {
|
||||||
@ -643,7 +639,7 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_link(&mut header, path, realpath)
|
.add_link(&mut header, path, realpath)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send symlink entry: {}", err))?;
|
.context("could not send symlink entry")?;
|
||||||
}
|
}
|
||||||
EntryKind::Fifo => {
|
EntryKind::Fifo => {
|
||||||
log::debug!("adding '{}' to tar", path.display());
|
log::debug!("adding '{}' to tar", path.display());
|
||||||
@ -657,7 +653,7 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_entry(&mut header, path, tokio::io::empty())
|
.add_entry(&mut header, path, tokio::io::empty())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send fifo entry: {}", err))?;
|
.context("coult not send fifo entry")?;
|
||||||
}
|
}
|
||||||
EntryKind::Directory => {
|
EntryKind::Directory => {
|
||||||
log::debug!("adding '{}' to tar", path.display());
|
log::debug!("adding '{}' to tar", path.display());
|
||||||
@ -671,7 +667,7 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_entry(&mut header, path, tokio::io::empty())
|
.add_entry(&mut header, path, tokio::io::empty())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send dir entry: {}", err))?;
|
.context("could not send dir entry")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EntryKind::Device(device) => {
|
EntryKind::Device(device) => {
|
||||||
@ -690,7 +686,7 @@ where
|
|||||||
tarencoder
|
tarencoder
|
||||||
.add_entry(&mut header, path, tokio::io::empty())
|
.add_entry(&mut header, path, tokio::io::empty())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send device entry: {}", err))?;
|
.context("could not send device entry")?;
|
||||||
}
|
}
|
||||||
_ => {} // ignore all else
|
_ => {} // ignore all else
|
||||||
}
|
}
|
||||||
@ -714,7 +710,7 @@ where
|
|||||||
let file = root
|
let file = root
|
||||||
.lookup(&path)
|
.lookup(&path)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| format_err!("error opening '{:?}'", path.as_ref()))?;
|
.with_context(|| format!("error opening {:?}", path.as_ref()))?;
|
||||||
|
|
||||||
let prefix = {
|
let prefix = {
|
||||||
let mut components = file.entry().path().components();
|
let mut components = file.entry().path().components();
|
||||||
@ -756,13 +752,13 @@ where
|
|||||||
);
|
);
|
||||||
zip.add_entry(entry, decoder.contents())
|
zip.add_entry(entry, decoder.contents())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send file entry: {}", err))?;
|
.context("could not send file entry")?;
|
||||||
}
|
}
|
||||||
EntryKind::Hardlink(_) => {
|
EntryKind::Hardlink(_) => {
|
||||||
let entry = root
|
let entry = root
|
||||||
.lookup(&path)
|
.lookup(&path)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| format_err!("error looking up '{:?}'", path))?;
|
.with_context(|| format!("error looking up {:?}", path))?;
|
||||||
let realfile = accessor.follow_hardlink(&entry).await?;
|
let realfile = accessor.follow_hardlink(&entry).await?;
|
||||||
let metadata = realfile.entry().metadata();
|
let metadata = realfile.entry().metadata();
|
||||||
log::debug!("adding '{}' to zip", path.display());
|
log::debug!("adding '{}' to zip", path.display());
|
||||||
@ -774,7 +770,7 @@ where
|
|||||||
);
|
);
|
||||||
zip.add_entry(entry, decoder.contents())
|
zip.add_entry(entry, decoder.contents())
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format_err!("could not send file entry: {}", err))?;
|
.context("could not send file entry")?;
|
||||||
}
|
}
|
||||||
EntryKind::Directory => {
|
EntryKind::Directory => {
|
||||||
log::debug!("adding '{}' to zip", path.display());
|
log::debug!("adding '{}' to zip", path.display());
|
||||||
@ -806,26 +802,14 @@ where
|
|||||||
None,
|
None,
|
||||||
Some(CreateOptions::new().perm(Mode::from_bits_truncate(0o700))),
|
Some(CreateOptions::new().perm(Mode::from_bits_truncate(0o700))),
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.with_context(|| format!("error creating directory {:?}", destination.as_ref()))?;
|
||||||
format_err!(
|
|
||||||
"error creating directory {:?}: {}",
|
|
||||||
destination.as_ref(),
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let dir = Dir::open(
|
let dir = Dir::open(
|
||||||
destination.as_ref(),
|
destination.as_ref(),
|
||||||
OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
|
OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
|
||||||
Mode::empty(),
|
Mode::empty(),
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.with_context(|| format!("unable to open target directory {:?}", destination.as_ref()))?;
|
||||||
format_err!(
|
|
||||||
"unable to open target directory {:?}: {}",
|
|
||||||
destination.as_ref(),
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Extractor::new(dir, metadata, false, false, Flags::DEFAULT))
|
Ok(Extractor::new(dir, metadata, false, false, Flags::DEFAULT))
|
||||||
}
|
}
|
||||||
@ -850,7 +834,7 @@ where
|
|||||||
let file = root
|
let file = root
|
||||||
.lookup(&path)
|
.lookup(&path)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| format_err!("error opening '{:?}'", path.as_ref()))?;
|
.with_context(|| format!("error opening {:?}", path.as_ref()))?;
|
||||||
|
|
||||||
recurse_files_extractor(&mut extractor, file).await
|
recurse_files_extractor(&mut extractor, file).await
|
||||||
}
|
}
|
||||||
@ -866,7 +850,7 @@ where
|
|||||||
decoder.enable_goodbye_entries(true);
|
decoder.enable_goodbye_entries(true);
|
||||||
let root = match decoder.next().await {
|
let root = match decoder.next().await {
|
||||||
Some(Ok(root)) => root,
|
Some(Ok(root)) => root,
|
||||||
Some(Err(err)) => bail!("error getting root entry from pxar: {}", err),
|
Some(Err(err)) => return Err(err).context("error getting root entry from pxar"),
|
||||||
None => bail!("cannot extract empty archive"),
|
None => bail!("cannot extract empty archive"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -920,8 +904,8 @@ fn get_filename(entry: &Entry) -> Result<(OsString, CString), Error> {
|
|||||||
bail!("archive file entry contains slashes, which is invalid and a security concern");
|
bail!("archive file entry contains slashes, which is invalid and a security concern");
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_name = CString::new(file_name_os.as_bytes())
|
let file_name =
|
||||||
.map_err(|_| format_err!("encountered file name with null-bytes"))?;
|
CString::new(file_name_os.as_bytes()).context("encountered file name with null-bytes")?;
|
||||||
|
|
||||||
Ok((file_name_os, file_name))
|
Ok((file_name_os, file_name))
|
||||||
}
|
}
|
||||||
@ -943,7 +927,7 @@ where
|
|||||||
EntryKind::Directory => {
|
EntryKind::Directory => {
|
||||||
extractor
|
extractor
|
||||||
.enter_directory(file_name_os.to_owned(), metadata.clone(), true)
|
.enter_directory(file_name_os.to_owned(), metadata.clone(), true)
|
||||||
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
|
.with_context(|| format!("error at entry {file_name_os:?}"))?;
|
||||||
|
|
||||||
let dir = file.enter_directory().await?;
|
let dir = file.enter_directory().await?;
|
||||||
let mut seq_decoder = dir.decode_full().await?;
|
let mut seq_decoder = dir.decode_full().await?;
|
||||||
@ -957,9 +941,10 @@ where
|
|||||||
&file_name,
|
&file_name,
|
||||||
metadata,
|
metadata,
|
||||||
*size,
|
*size,
|
||||||
&mut file.contents().await.map_err(|_| {
|
&mut file
|
||||||
format_err!("found regular file entry without contents in archive")
|
.contents()
|
||||||
})?,
|
.await
|
||||||
|
.context("found regular file entry without contents in archive")?,
|
||||||
extractor.overwrite,
|
extractor.overwrite,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
@ -997,7 +982,7 @@ where
|
|||||||
dir_level += 1;
|
dir_level += 1;
|
||||||
extractor
|
extractor
|
||||||
.enter_directory(file_name_os.to_owned(), metadata.clone(), true)
|
.enter_directory(file_name_os.to_owned(), metadata.clone(), true)
|
||||||
.map_err(|err| format_err!("error at entry {:?}: {}", file_name_os, err))?;
|
.with_context(|| format!("error at entry {file_name_os:?}"))?;
|
||||||
}
|
}
|
||||||
EntryKind::File { size, .. } => {
|
EntryKind::File { size, .. } => {
|
||||||
extractor
|
extractor
|
||||||
@ -1005,9 +990,9 @@ where
|
|||||||
&file_name,
|
&file_name,
|
||||||
metadata,
|
metadata,
|
||||||
*size,
|
*size,
|
||||||
&mut decoder.contents().ok_or_else(|| {
|
&mut decoder
|
||||||
format_err!("found regular file entry without contents in archive")
|
.contents()
|
||||||
})?,
|
.context("found regular file entry without contents in archive")?,
|
||||||
extractor.overwrite,
|
extractor.overwrite,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -2,7 +2,7 @@ use std::ffi::{CStr, CString};
|
|||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
@ -106,7 +106,7 @@ pub fn apply(
|
|||||||
.or_else(&mut *on_error)?;
|
.or_else(&mut *on_error)?;
|
||||||
add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs).or_else(&mut *on_error)?;
|
add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs).or_else(&mut *on_error)?;
|
||||||
apply_acls(flags, &c_proc_path, metadata, path_info)
|
apply_acls(flags, &c_proc_path, metadata, path_info)
|
||||||
.map_err(|err| format_err!("failed to apply acls: {}", err))
|
.context("failed to apply acls")
|
||||||
.or_else(&mut *on_error)?;
|
.or_else(&mut *on_error)?;
|
||||||
apply_quota_project_id(flags, fd, metadata).or_else(&mut *on_error)?;
|
apply_quota_project_id(flags, fd, metadata).or_else(&mut *on_error)?;
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ pub fn apply(
|
|||||||
})
|
})
|
||||||
.map(drop)
|
.map(drop)
|
||||||
.or_else(allow_notsupp)
|
.or_else(allow_notsupp)
|
||||||
.map_err(|err| format_err!("failed to change file mode: {}", err))
|
.context("failed to change file mode")
|
||||||
.or_else(&mut *on_error)?;
|
.or_else(&mut *on_error)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,11 +134,9 @@ pub fn apply(
|
|||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(ref err) if err.is_errno(Errno::EOPNOTSUPP) => (),
|
Err(ref err) if err.is_errno(Errno::EOPNOTSUPP) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
on_error(format_err!(
|
on_error(anyhow!(err).context(format!(
|
||||||
"failed to restore mtime attribute on {:?}: {}",
|
"failed to restore mtime attribute on {path_info:?}"
|
||||||
path_info,
|
)))?;
|
||||||
err
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +165,7 @@ pub fn apply_ownership(
|
|||||||
))
|
))
|
||||||
.map(drop)
|
.map(drop)
|
||||||
.or_else(allow_notsupp)
|
.or_else(allow_notsupp)
|
||||||
.map_err(|err| format_err!("failed to set ownership: {}", err))
|
.context("failed to set ownership")
|
||||||
.or_else(&mut *on_error)?;
|
.or_else(&mut *on_error)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -198,9 +196,7 @@ fn add_fcaps(
|
|||||||
})
|
})
|
||||||
.map(drop)
|
.map(drop)
|
||||||
.or_else(|err| allow_notsupp_remember(err, skip_xattrs))
|
.or_else(|err| allow_notsupp_remember(err, skip_xattrs))
|
||||||
.map_err(|err| format_err!("failed to apply file capabilities: {}", err))?;
|
.context("failed to apply file capabilities")
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_xattrs(
|
fn apply_xattrs(
|
||||||
@ -234,7 +230,7 @@ fn apply_xattrs(
|
|||||||
})
|
})
|
||||||
.map(drop)
|
.map(drop)
|
||||||
.or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))
|
.or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))
|
||||||
.map_err(|err| format_err!("failed to apply extended attributes: {}", err))?;
|
.context("failed to apply extended attributes")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -348,21 +344,13 @@ fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Resul
|
|||||||
|
|
||||||
let mut fsxattr = fs::FSXAttr::default();
|
let mut fsxattr = fs::FSXAttr::default();
|
||||||
unsafe {
|
unsafe {
|
||||||
fs::fs_ioc_fsgetxattr(fd, &mut fsxattr).map_err(|err| {
|
fs::fs_ioc_fsgetxattr(fd, &mut fsxattr)
|
||||||
format_err!(
|
.context("error while getting fsxattr to restore quota project id")?;
|
||||||
"error while getting fsxattr to restore quota project id - {}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
fsxattr.fsx_projid = projid.projid as u32;
|
fsxattr.fsx_projid = projid.projid as u32;
|
||||||
|
|
||||||
fs::fs_ioc_fssetxattr(fd, &fsxattr).map_err(|err| {
|
fs::fs_ioc_fssetxattr(fd, &fsxattr)
|
||||||
format_err!(
|
.context("error while setting fsxattr to restore quota project id")?;
|
||||||
"error while setting fsxattr to restore quota project id - {}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -386,7 +374,7 @@ fn apply_chattr(fd: RawFd, chattr: libc::c_long, mask: libc::c_long) -> Result<(
|
|||||||
Err(errno) if errno_is_unsupported(errno) => {
|
Err(errno) if errno_is_unsupported(errno) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(err) => bail!("failed to read file attributes: {}", err),
|
Err(err) => return Err(err).context("failed to read file attributes"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let attr = (chattr & mask) | (fattr & !mask);
|
let attr = (chattr & mask) | (fattr & !mask);
|
||||||
@ -398,7 +386,7 @@ fn apply_chattr(fd: RawFd, chattr: libc::c_long, mask: libc::c_long) -> Result<(
|
|||||||
match unsafe { fs::write_attr_fd(fd, &attr) } {
|
match unsafe { fs::write_attr_fd(fd, &attr) } {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(errno) if errno_is_unsupported(errno) => Ok(()),
|
Err(errno) if errno_is_unsupported(errno) => Ok(()),
|
||||||
Err(err) => bail!("failed to set file attributes: {}", err),
|
Err(err) => Err(err).context("failed to set file attributes"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +400,7 @@ fn apply_flags(flags: Flags, fd: RawFd, entry_flags: u64) -> Result<(), Error> {
|
|||||||
match unsafe { fs::write_fat_attr_fd(fd, &fatattr) } {
|
match unsafe { fs::write_fat_attr_fd(fd, &fatattr) } {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(errno) if errno_is_unsupported(errno) => (),
|
Err(errno) if errno_is_unsupported(errno) => (),
|
||||||
Err(err) => bail!("failed to set file FAT attributes: {}", err),
|
Err(err) => return Err(err).context("failed to set file FAT attributes"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use std::ffi::OsStr;
|
|||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, Context, Error};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
|
|
||||||
use pxar::{format::StatxTimestamp, mode, Entry, EntryKind, Metadata};
|
use pxar::{format::StatxTimestamp, mode, Entry, EntryKind, Metadata};
|
||||||
@ -12,10 +12,13 @@ use pxar::{format::StatxTimestamp, mode, Entry, EntryKind, Metadata};
|
|||||||
/// Get the file permissions as `nix::Mode`
|
/// Get the file permissions as `nix::Mode`
|
||||||
pub fn perms_from_metadata(meta: &Metadata) -> Result<Mode, Error> {
|
pub fn perms_from_metadata(meta: &Metadata) -> Result<Mode, Error> {
|
||||||
let mode = meta.stat.get_permission_bits();
|
let mode = meta.stat.get_permission_bits();
|
||||||
|
|
||||||
u32::try_from(mode)
|
u32::try_from(mode)
|
||||||
.map_err(drop)
|
.context("couldn't narrow permission bits")
|
||||||
.and_then(|mode| Mode::from_bits(mode).ok_or(()))
|
.and_then(|mode| {
|
||||||
.map_err(|_| format_err!("mode contains illegal bits: 0x{:x} (0o{:o})", mode, mode))
|
Mode::from_bits(mode)
|
||||||
|
.with_context(|| format!("mode contains illegal bits: 0x{:x} (0o{:o})", mode, mode))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure path is relative and not '.' or '..'.
|
/// Make sure path is relative and not '.' or '..'.
|
||||||
|
Loading…
Reference in New Issue
Block a user