pxar: restore file attributes, improve errors

and use the correct integer types for these operations

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-07-14 09:16:16 +02:00
parent ec2434fe3c
commit 032cd1b862
5 changed files with 172 additions and 62 deletions

View File

@ -23,6 +23,7 @@ use proxmox::tools::fd::RawFdNum;
use proxmox::tools::vec; use proxmox::tools::vec;
use crate::pxar::catalog::BackupCatalogWriter; use crate::pxar::catalog::BackupCatalogWriter;
use crate::pxar::metadata::errno_is_unsupported;
use crate::pxar::Flags; use crate::pxar::Flags;
use crate::pxar::tools::assert_single_path_component; use crate::pxar::tools::assert_single_path_component;
use crate::tools::{acl, fs, xattr, Fd}; use crate::tools::{acl, fs, xattr, Fd};
@ -698,13 +699,6 @@ fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64) -> Resu
Ok(meta) Ok(meta)
} }
fn errno_is_unsupported(errno: Errno) -> bool {
match errno {
Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true,
_ => false,
}
}
fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags) -> Result<(), Error> { fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags) -> Result<(), Error> {
if flags.contains(Flags::WITH_FCAPS) { if flags.contains(Flags::WITH_FCAPS) {
return Ok(()); return Ok(());
@ -769,7 +763,7 @@ fn get_xattr_fcaps_acl(
} }
fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> { fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
let mut attr: usize = 0; let mut attr: libc::c_long = 0;
match unsafe { fs::read_attr_fd(fd, &mut attr) } { match unsafe { fs::read_attr_fd(fd, &mut attr) } {
Ok(_) => (), Ok(_) => (),
@ -779,7 +773,7 @@ fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
Err(err) => bail!("failed to read file attributes: {}", err), Err(err) => bail!("failed to read file attributes: {}", err),
} }
metadata.stat.flags |= Flags::from_chattr(attr as u32).bits(); metadata.stat.flags |= Flags::from_chattr(attr).bits();
Ok(()) Ok(())
} }

View File

@ -230,7 +230,8 @@ impl Extractor {
dir.metadata(), dir.metadata(),
fd, fd,
&CString::new(dir.file_name().as_bytes())?, &CString::new(dir.file_name().as_bytes())?,
)?; )
.map_err(|err| format_err!("failed to apply directory metadata: {}", err))?;
} }
Ok(()) Ok(())
@ -241,7 +242,9 @@ impl Extractor {
} }
fn parent_fd(&mut self) -> Result<RawFd, Error> { fn parent_fd(&mut self) -> Result<RawFd, Error> {
self.dir_stack.last_dir_fd(self.allow_existing_dirs) self.dir_stack
.last_dir_fd(self.allow_existing_dirs)
.map_err(|err| format_err!("failed to get parent directory file descriptor: {}", err))
} }
pub fn extract_symlink( pub fn extract_symlink(
@ -320,10 +323,14 @@ impl Extractor {
file_name, file_name,
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC,
Mode::from_bits(0o600).unwrap(), Mode::from_bits(0o600).unwrap(),
)?) )
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?)
}; };
let extracted = io::copy(&mut *contents, &mut file)?; metadata::apply_initial_flags(self.feature_flags, metadata, file.as_raw_fd())?;
let extracted = io::copy(&mut *contents, &mut file)
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
if size != extracted { if size != extracted {
bail!("extracted {} bytes of a file of {} bytes", extracted, size); bail!("extracted {} bytes of a file of {} bytes", extracted, size);
} }
@ -345,10 +352,15 @@ impl Extractor {
file_name, file_name,
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC,
Mode::from_bits(0o600).unwrap(), Mode::from_bits(0o600).unwrap(),
)?) )
.map_err(|err| format_err!("failed to create file {:?}: {}", file_name, err))?)
}); });
let extracted = tokio::io::copy(&mut *contents, &mut file).await?; metadata::apply_initial_flags(self.feature_flags, metadata, file.as_raw_fd())?;
let extracted = tokio::io::copy(&mut *contents, &mut file)
.await
.map_err(|err| format_err!("failed to copy file contents: {}", err))?;
if size != extracted { if size != extracted {
bail!("extracted {} bytes of a file of {} bytes", extracted, size); bail!("extracted {} bytes of a file of {} bytes", extracted, size);
} }

View File

@ -3,6 +3,8 @@
//! Flags for known supported features for a given filesystem can be derived //! Flags for known supported features for a given filesystem can be derived
//! from the superblocks magic number. //! from the superblocks magic number.
use libc::c_long;
use bitflags::bitflags; use bitflags::bitflags;
bitflags! { bitflags! {
@ -149,22 +151,27 @@ impl Default for Flags {
} }
} }
impl Flags { // form /usr/include/linux/fs.h
/// Get a set of feature flags from file attributes. const FS_APPEND_FL: c_long = 0x0000_0020;
pub fn from_chattr(attr: u32) -> Flags { const FS_NOATIME_FL: c_long = 0x0000_0080;
// form /usr/include/linux/fs.h const FS_COMPR_FL: c_long = 0x0000_0004;
const FS_APPEND_FL: u32 = 0x0000_0020; const FS_NOCOW_FL: c_long = 0x0080_0000;
const FS_NOATIME_FL: u32 = 0x0000_0080; const FS_NODUMP_FL: c_long = 0x0000_0040;
const FS_COMPR_FL: u32 = 0x0000_0004; const FS_DIRSYNC_FL: c_long = 0x0001_0000;
const FS_NOCOW_FL: u32 = 0x0080_0000; const FS_IMMUTABLE_FL: c_long = 0x0000_0010;
const FS_NODUMP_FL: u32 = 0x0000_0040; const FS_SYNC_FL: c_long = 0x0000_0008;
const FS_DIRSYNC_FL: u32 = 0x0001_0000; const FS_NOCOMP_FL: c_long = 0x0000_0400;
const FS_IMMUTABLE_FL: u32 = 0x0000_0010; const FS_PROJINHERIT_FL: c_long = 0x2000_0000;
const FS_SYNC_FL: u32 = 0x0000_0008;
const FS_NOCOMP_FL: u32 = 0x0000_0400;
const FS_PROJINHERIT_FL: u32 = 0x2000_0000;
const CHATTR_MAP: [(Flags, u32); 10] = [ pub(crate) const INITIAL_FS_FLAGS: c_long =
FS_NOATIME_FL
| FS_COMPR_FL
| FS_NOCOW_FL
| FS_NOCOMP_FL
| FS_PROJINHERIT_FL;
#[rustfmt::skip]
const CHATTR_MAP: [(Flags, c_long); 10] = [
( Flags::WITH_FLAG_APPEND, FS_APPEND_FL ), ( Flags::WITH_FLAG_APPEND, FS_APPEND_FL ),
( Flags::WITH_FLAG_NOATIME, FS_NOATIME_FL ), ( Flags::WITH_FLAG_NOATIME, FS_NOATIME_FL ),
( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ), ( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ),
@ -175,8 +182,23 @@ impl Flags {
( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ), ( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ),
( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_FL ), ( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_FL ),
( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ), ( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ),
]; ];
// from /usr/include/linux/msdos_fs.h
const ATTR_HIDDEN: u32 = 2;
const ATTR_SYS: u32 = 4;
const ATTR_ARCH: u32 = 32;
#[rustfmt::skip]
const FAT_ATTR_MAP: [(Flags, u32); 3] = [
( Flags::WITH_FLAG_HIDDEN, ATTR_HIDDEN ),
( Flags::WITH_FLAG_SYSTEM, ATTR_SYS ),
( Flags::WITH_FLAG_ARCHIVE, ATTR_ARCH ),
];
impl Flags {
/// Get a set of feature flags from file attributes.
pub fn from_chattr(attr: c_long) -> Flags {
let mut flags = Flags::empty(); let mut flags = Flags::empty();
for (fe_flag, fs_flag) in &CHATTR_MAP { for (fe_flag, fs_flag) in &CHATTR_MAP {
@ -188,19 +210,25 @@ impl Flags {
flags flags
} }
/// Get the chattr bit representation of these feature flags.
pub fn to_chattr(self) -> c_long {
let mut flags: c_long = 0;
for (fe_flag, fs_flag) in &CHATTR_MAP {
if self.contains(*fe_flag) {
flags |= *fs_flag;
}
}
flags
}
pub fn to_initial_chattr(self) -> c_long {
self.to_chattr() & INITIAL_FS_FLAGS
}
/// Get a set of feature flags from FAT attributes. /// Get a set of feature flags from FAT attributes.
pub fn from_fat_attr(attr: u32) -> Flags { pub fn from_fat_attr(attr: u32) -> Flags {
// from /usr/include/linux/msdos_fs.h
const ATTR_HIDDEN: u32 = 2;
const ATTR_SYS: u32 = 4;
const ATTR_ARCH: u32 = 32;
const FAT_ATTR_MAP: [(Flags, u32); 3] = [
( Flags::WITH_FLAG_HIDDEN, ATTR_HIDDEN ),
( Flags::WITH_FLAG_SYSTEM, ATTR_SYS ),
( Flags::WITH_FLAG_ARCHIVE, ATTR_ARCH ),
];
let mut flags = Flags::empty(); let mut flags = Flags::empty();
for (fe_flag, fs_flag) in &FAT_ATTR_MAP { for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
@ -212,6 +240,19 @@ impl Flags {
flags flags
} }
/// Get the fat attribute bit representation of these feature flags.
pub fn to_fat_attr(self) -> u32 {
let mut flags = 0u32;
for (fe_flag, fs_flag) in &FAT_ATTR_MAP {
if self.contains(*fe_flag) {
flags |= *fs_flag;
}
}
flags
}
/// Return the supported *pxar* feature flags based on the magic number of the filesystem. /// Return the supported *pxar* feature flags based on the magic number of the filesystem.
pub fn from_magic(magic: i64) -> Flags { pub fn from_magic(magic: i64) -> Flags {
use proxmox::sys::linux::magic::*; use proxmox::sys::linux::magic::*;

View File

@ -79,13 +79,19 @@ pub fn apply_at(
apply(flags, metadata, fd.as_raw_fd(), file_name) apply(flags, metadata, fd.as_raw_fd(), file_name)
} }
pub fn apply_initial_flags(
flags: Flags,
metadata: &Metadata,
fd: RawFd,
) -> Result<(), Error> {
let entry_flags = Flags::from_bits_truncate(metadata.stat.flags);
apply_chattr(fd, entry_flags.to_initial_chattr(), flags.to_initial_chattr());
Ok(())
}
pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) -> Result<(), Error> { pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) -> Result<(), Error> {
let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap(); let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap();
if metadata.stat.flags != 0 {
todo!("apply flags!");
}
unsafe { unsafe {
// UID and GID first, as this fails if we lose access anyway. // UID and GID first, as this fails if we lose access anyway.
c_result!(libc::chown( c_result!(libc::chown(
@ -94,13 +100,15 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) ->
metadata.stat.gid metadata.stat.gid
)) ))
.map(drop) .map(drop)
.or_else(allow_notsupp)?; .or_else(allow_notsupp)
.map_err(|err| format_err!("failed to set ownership: {}", err))?;
} }
let mut skip_xattrs = false; let mut skip_xattrs = false;
apply_xattrs(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?; apply_xattrs(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?;
add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?; add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)?;
apply_acls(flags, &c_proc_path, metadata)?; apply_acls(flags, &c_proc_path, metadata)
.map_err(|err| format_err!("failed to apply acls: {}", err))?;
apply_quota_project_id(flags, fd, metadata)?; apply_quota_project_id(flags, fd, metadata)?;
// Finally mode and time. We may lose access with mode, but the changing the mode also // Finally mode and time. We may lose access with mode, but the changing the mode also
@ -110,7 +118,12 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) ->
libc::chmod(c_proc_path.as_ptr(), perms_from_metadata(metadata)?.bits()) libc::chmod(c_proc_path.as_ptr(), perms_from_metadata(metadata)?.bits())
}) })
.map(drop) .map(drop)
.or_else(allow_notsupp)?; .or_else(allow_notsupp)
.map_err(|err| format_err!("failed to change file mode: {}", err))?;
}
if metadata.stat.flags != 0 {
apply_flags(flags, fd, metadata.stat.flags)?;
} }
let res = c_result!(unsafe { let res = c_result!(unsafe {
@ -160,7 +173,8 @@ 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))?;
Ok(()) Ok(())
} }
@ -195,7 +209,8 @@ 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))?;
} }
Ok(()) Ok(())
@ -317,3 +332,49 @@ fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Resul
Ok(()) Ok(())
} }
pub(crate) fn errno_is_unsupported(errno: Errno) -> bool {
match errno {
Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true,
_ => false,
}
}
fn apply_chattr(fd: RawFd, chattr: libc::c_long, mask: libc::c_long) -> Result<(), Error> {
if chattr == 0 {
return Ok(());
}
let mut fattr: libc::c_long = 0;
match unsafe { fs::read_attr_fd(fd, &mut fattr) } {
Ok(_) => (),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => {
return Ok(());
}
Err(err) => bail!("failed to read file attributes: {}", err),
}
let attr = (chattr & mask) | (fattr & !mask);
match unsafe { fs::write_attr_fd(fd, &attr) } {
Ok(_) => Ok(()),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => Ok(()),
Err(err) => bail!("failed to set file attributes: {}", err),
}
}
fn apply_flags(flags: Flags, fd: RawFd, entry_flags: u64) -> Result<(), Error> {
let entry_flags = Flags::from_bits_truncate(entry_flags);
apply_chattr(fd, entry_flags.to_chattr(), flags.to_chattr())?;
let fatattr = (flags & entry_flags).to_fat_attr();
if fatattr != 0 {
match unsafe { fs::write_fat_attr_fd(fd, &fatattr) } {
Ok(_) => (),
Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => (),
Err(err) => bail!("failed to set file attributes: {}", err),
}
}
Ok(())
}

View File

@ -222,11 +222,13 @@ where
// /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long) // /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long)
// read Linux file system attributes (see man chattr) // read Linux file system attributes (see man chattr)
nix::ioctl_read!(read_attr_fd, b'f', 1, usize); nix::ioctl_read!(read_attr_fd, b'f', 1, libc::c_long);
nix::ioctl_write_ptr!(write_attr_fd, b'f', 2, libc::c_long);
// /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) // /usr/include/linux/msdos_fs.h: #define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32)
// read FAT file system attributes // read FAT file system attributes
nix::ioctl_read!(read_fat_attr_fd, b'r', 0x10, u32); nix::ioctl_read!(read_fat_attr_fd, b'r', 0x10, u32);
nix::ioctl_write_ptr!(write_fat_attr_fd, b'r', 0x11, u32);
// From /usr/include/linux/fs.h // From /usr/include/linux/fs.h
// #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr) // #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr)