From 032cd1b8621e1e3b83a47058f55cfb2f7acb0b98 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Tue, 14 Jul 2020 09:16:16 +0200 Subject: [PATCH] pxar: restore file attributes, improve errors and use the correct integer types for these operations Signed-off-by: Wolfgang Bumiller --- src/pxar/create.rs | 12 ++--- src/pxar/extract.rs | 24 ++++++--- src/pxar/flags.rs | 115 +++++++++++++++++++++++++++++-------------- src/pxar/metadata.rs | 79 +++++++++++++++++++++++++---- src/tools/fs.rs | 4 +- 5 files changed, 172 insertions(+), 62 deletions(-) diff --git a/src/pxar/create.rs b/src/pxar/create.rs index 2de579a3..b13ff403 100644 --- a/src/pxar/create.rs +++ b/src/pxar/create.rs @@ -23,6 +23,7 @@ use proxmox::tools::fd::RawFdNum; use proxmox::tools::vec; use crate::pxar::catalog::BackupCatalogWriter; +use crate::pxar::metadata::errno_is_unsupported; use crate::pxar::Flags; use crate::pxar::tools::assert_single_path_component; 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) } -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> { if flags.contains(Flags::WITH_FCAPS) { return Ok(()); @@ -769,7 +763,7 @@ fn get_xattr_fcaps_acl( } 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) } { Ok(_) => (), @@ -779,7 +773,7 @@ fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> { 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(()) } diff --git a/src/pxar/extract.rs b/src/pxar/extract.rs index 6b77fb21..8581a8c0 100644 --- a/src/pxar/extract.rs +++ b/src/pxar/extract.rs @@ -230,7 +230,8 @@ impl Extractor { dir.metadata(), fd, &CString::new(dir.file_name().as_bytes())?, - )?; + ) + .map_err(|err| format_err!("failed to apply directory metadata: {}", err))?; } Ok(()) @@ -241,7 +242,9 @@ impl Extractor { } fn parent_fd(&mut self) -> Result { - 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( @@ -320,10 +323,14 @@ impl Extractor { file_name, OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, 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 { bail!("extracted {} bytes of a file of {} bytes", extracted, size); } @@ -345,10 +352,15 @@ impl Extractor { file_name, OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_WRONLY | OFlag::O_CLOEXEC, 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 { bail!("extracted {} bytes of a file of {} bytes", extracted, size); } diff --git a/src/pxar/flags.rs b/src/pxar/flags.rs index 2e81d251..1cac596f 100644 --- a/src/pxar/flags.rs +++ b/src/pxar/flags.rs @@ -3,6 +3,8 @@ //! Flags for known supported features for a given filesystem can be derived //! from the superblocks magic number. +use libc::c_long; + use bitflags::bitflags; bitflags! { @@ -149,34 +151,54 @@ impl Default for Flags { } } +// form /usr/include/linux/fs.h +const FS_APPEND_FL: c_long = 0x0000_0020; +const FS_NOATIME_FL: c_long = 0x0000_0080; +const FS_COMPR_FL: c_long = 0x0000_0004; +const FS_NOCOW_FL: c_long = 0x0080_0000; +const FS_NODUMP_FL: c_long = 0x0000_0040; +const FS_DIRSYNC_FL: c_long = 0x0001_0000; +const FS_IMMUTABLE_FL: c_long = 0x0000_0010; +const FS_SYNC_FL: c_long = 0x0000_0008; +const FS_NOCOMP_FL: c_long = 0x0000_0400; +const FS_PROJINHERIT_FL: c_long = 0x2000_0000; + +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_NOATIME, FS_NOATIME_FL ), + ( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ), + ( Flags::WITH_FLAG_NOCOW, FS_NOCOW_FL ), + ( Flags::WITH_FLAG_NODUMP, FS_NODUMP_FL ), + ( Flags::WITH_FLAG_DIRSYNC, FS_DIRSYNC_FL ), + ( Flags::WITH_FLAG_IMMUTABLE, FS_IMMUTABLE_FL ), + ( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ), + ( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_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: u32) -> Flags { - // form /usr/include/linux/fs.h - const FS_APPEND_FL: u32 = 0x0000_0020; - const FS_NOATIME_FL: u32 = 0x0000_0080; - const FS_COMPR_FL: u32 = 0x0000_0004; - const FS_NOCOW_FL: u32 = 0x0080_0000; - const FS_NODUMP_FL: u32 = 0x0000_0040; - const FS_DIRSYNC_FL: u32 = 0x0001_0000; - const FS_IMMUTABLE_FL: u32 = 0x0000_0010; - 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] = [ - ( Flags::WITH_FLAG_APPEND, FS_APPEND_FL ), - ( Flags::WITH_FLAG_NOATIME, FS_NOATIME_FL ), - ( Flags::WITH_FLAG_COMPR, FS_COMPR_FL ), - ( Flags::WITH_FLAG_NOCOW, FS_NOCOW_FL ), - ( Flags::WITH_FLAG_NODUMP, FS_NODUMP_FL ), - ( Flags::WITH_FLAG_DIRSYNC, FS_DIRSYNC_FL ), - ( Flags::WITH_FLAG_IMMUTABLE, FS_IMMUTABLE_FL ), - ( Flags::WITH_FLAG_SYNC, FS_SYNC_FL ), - ( Flags::WITH_FLAG_NOCOMP, FS_NOCOMP_FL ), - ( Flags::WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL ), - ]; - + pub fn from_chattr(attr: c_long) -> Flags { let mut flags = Flags::empty(); for (fe_flag, fs_flag) in &CHATTR_MAP { @@ -188,19 +210,25 @@ impl 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. 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(); for (fe_flag, fs_flag) in &FAT_ATTR_MAP { @@ -212,6 +240,19 @@ impl 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. pub fn from_magic(magic: i64) -> Flags { use proxmox::sys::linux::magic::*; diff --git a/src/pxar/metadata.rs b/src/pxar/metadata.rs index 4bbb352b..7a6c41ac 100644 --- a/src/pxar/metadata.rs +++ b/src/pxar/metadata.rs @@ -79,13 +79,19 @@ pub fn apply_at( 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> { let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap(); - if metadata.stat.flags != 0 { - todo!("apply flags!"); - } - unsafe { // UID and GID first, as this fails if we lose access anyway. c_result!(libc::chown( @@ -94,13 +100,15 @@ pub fn apply(flags: Flags, metadata: &Metadata, fd: RawFd, file_name: &CStr) -> metadata.stat.gid )) .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; apply_xattrs(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)?; // 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()) }) .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 { @@ -160,7 +173,8 @@ fn add_fcaps( ) }) .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(()) } @@ -195,7 +209,8 @@ fn apply_xattrs( ) }) .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(()) @@ -317,3 +332,49 @@ fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Resul 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(()) +} diff --git a/src/tools/fs.rs b/src/tools/fs.rs index 894fdf3e..ff625d30 100644 --- a/src/tools/fs.rs +++ b/src/tools/fs.rs @@ -222,11 +222,13 @@ where // /usr/include/linux/fs.h: #define FS_IOC_GETFLAGS _IOR('f', 1, long) // 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) // read FAT file system attributes 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 // #define FS_IOC_FSGETXATTR _IOR('X', 31, struct fsxattr)