2020-03-23 14:03:18 +00:00
|
|
|
use std::ffi::{CStr, CString};
|
|
|
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|
|
|
|
|
|
|
use anyhow::{bail, format_err, Error};
|
|
|
|
use nix::errno::Errno;
|
|
|
|
use nix::fcntl::OFlag;
|
|
|
|
use nix::sys::stat::Mode;
|
|
|
|
|
|
|
|
use pxar::Metadata;
|
|
|
|
|
2020-06-12 08:51:36 +00:00
|
|
|
use proxmox::c_result;
|
2020-03-23 14:03:18 +00:00
|
|
|
use proxmox::sys::error::SysError;
|
|
|
|
use proxmox::tools::fd::RawFdNum;
|
|
|
|
|
|
|
|
use crate::pxar::tools::perms_from_metadata;
|
2020-06-12 08:51:36 +00:00
|
|
|
use crate::pxar::Flags;
|
2020-03-23 14:03:18 +00:00
|
|
|
use crate::tools::{acl, fs, xattr};
|
|
|
|
|
|
|
|
//
|
|
|
|
// utility functions
|
|
|
|
//
|
|
|
|
|
|
|
|
fn allow_notsupp<E: SysError>(err: E) -> Result<(), E> {
|
|
|
|
if err.is_errno(Errno::EOPNOTSUPP) {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn allow_notsupp_remember<E: SysError>(err: E, not_supp: &mut bool) -> Result<(), E> {
|
|
|
|
if err.is_errno(Errno::EOPNOTSUPP) {
|
|
|
|
*not_supp = true;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-28 10:33:16 +00:00
|
|
|
fn timestamp_to_update_timespec(mtime: &pxar::format::StatxTimestamp) -> [libc::timespec; 2] {
|
2020-03-23 14:03:18 +00:00
|
|
|
// restore mtime
|
|
|
|
const UTIME_OMIT: i64 = (1 << 30) - 2;
|
|
|
|
|
2020-07-28 10:33:16 +00:00
|
|
|
[
|
2020-03-23 14:03:18 +00:00
|
|
|
libc::timespec {
|
|
|
|
tv_sec: 0,
|
|
|
|
tv_nsec: UTIME_OMIT,
|
|
|
|
},
|
|
|
|
libc::timespec {
|
2020-07-28 10:33:16 +00:00
|
|
|
tv_sec: mtime.secs,
|
|
|
|
tv_nsec: mtime.nanos as _,
|
2020-03-23 14:03:18 +00:00
|
|
|
},
|
2020-07-28 10:33:16 +00:00
|
|
|
]
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// metadata application:
|
|
|
|
//
|
|
|
|
|
|
|
|
pub fn apply_at(
|
2020-06-10 09:03:42 +00:00
|
|
|
flags: Flags,
|
2020-03-23 14:03:18 +00:00
|
|
|
metadata: &Metadata,
|
|
|
|
parent: RawFd,
|
|
|
|
file_name: &CStr,
|
2020-07-31 12:08:02 +00:00
|
|
|
on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
|
2020-03-23 14:03:18 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
let fd = proxmox::tools::fd::Fd::openat(
|
|
|
|
&unsafe { RawFdNum::from_raw_fd(parent) },
|
|
|
|
file_name,
|
|
|
|
OFlag::O_PATH | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW,
|
|
|
|
Mode::empty(),
|
|
|
|
)?;
|
|
|
|
|
2020-07-31 12:08:02 +00:00
|
|
|
apply(flags, metadata, fd.as_raw_fd(), file_name, on_error)
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
2020-07-14 07:16:16 +00:00
|
|
|
pub fn apply_initial_flags(
|
|
|
|
flags: Flags,
|
|
|
|
metadata: &Metadata,
|
|
|
|
fd: RawFd,
|
2020-07-31 12:08:02 +00:00
|
|
|
on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
|
2020-07-14 07:16:16 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
let entry_flags = Flags::from_bits_truncate(metadata.stat.flags);
|
2020-07-31 12:08:02 +00:00
|
|
|
apply_chattr(
|
|
|
|
fd,
|
|
|
|
entry_flags.to_initial_chattr(),
|
|
|
|
flags.to_initial_chattr(),
|
|
|
|
)
|
|
|
|
.or_else(on_error)?;
|
2020-07-14 07:16:16 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-07-31 12:08:02 +00:00
|
|
|
pub fn apply(
|
|
|
|
flags: Flags,
|
|
|
|
metadata: &Metadata,
|
|
|
|
fd: RawFd,
|
|
|
|
file_name: &CStr,
|
|
|
|
on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
|
|
|
|
) -> Result<(), Error> {
|
2020-03-23 14:03:18 +00:00
|
|
|
let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap();
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
// UID and GID first, as this fails if we lose access anyway.
|
|
|
|
c_result!(libc::chown(
|
2020-06-12 08:51:36 +00:00
|
|
|
c_proc_path.as_ptr(),
|
2020-03-23 14:03:18 +00:00
|
|
|
metadata.stat.uid,
|
|
|
|
metadata.stat.gid
|
|
|
|
))
|
|
|
|
.map(drop)
|
2020-07-14 07:16:16 +00:00
|
|
|
.or_else(allow_notsupp)
|
2020-07-31 12:08:02 +00:00
|
|
|
.map_err(|err| format_err!("failed to set ownership: {}", err))
|
|
|
|
.or_else(&mut *on_error)?;
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut skip_xattrs = false;
|
2020-07-31 12:08:02 +00:00
|
|
|
apply_xattrs(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)?;
|
2020-07-14 07:16:16 +00:00
|
|
|
apply_acls(flags, &c_proc_path, metadata)
|
2020-07-31 12:08:02 +00:00
|
|
|
.map_err(|err| format_err!("failed to apply acls: {}", err))
|
|
|
|
.or_else(&mut *on_error)?;
|
|
|
|
apply_quota_project_id(flags, fd, metadata).or_else(&mut *on_error)?;
|
2020-03-23 14:03:18 +00:00
|
|
|
|
|
|
|
// Finally mode and time. We may lose access with mode, but the changing the mode also
|
|
|
|
// affects times.
|
|
|
|
if !metadata.is_symlink() {
|
2020-06-12 08:51:36 +00:00
|
|
|
c_result!(unsafe {
|
|
|
|
libc::chmod(c_proc_path.as_ptr(), perms_from_metadata(metadata)?.bits())
|
|
|
|
})
|
|
|
|
.map(drop)
|
2020-07-14 07:16:16 +00:00
|
|
|
.or_else(allow_notsupp)
|
2020-07-31 12:08:02 +00:00
|
|
|
.map_err(|err| format_err!("failed to change file mode: {}", err))
|
|
|
|
.or_else(&mut *on_error)?;
|
2020-07-14 07:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.stat.flags != 0 {
|
2020-07-31 12:08:02 +00:00
|
|
|
apply_flags(flags, fd, metadata.stat.flags).or_else(&mut *on_error)?;
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let res = c_result!(unsafe {
|
|
|
|
libc::utimensat(
|
|
|
|
libc::AT_FDCWD,
|
2020-06-12 08:51:36 +00:00
|
|
|
c_proc_path.as_ptr(),
|
2020-07-28 10:33:16 +00:00
|
|
|
timestamp_to_update_timespec(&metadata.stat.mtime).as_ptr(),
|
2020-03-23 14:03:18 +00:00
|
|
|
0,
|
|
|
|
)
|
|
|
|
});
|
|
|
|
match res {
|
|
|
|
Ok(_) => (),
|
|
|
|
Err(ref err) if err.is_errno(Errno::EOPNOTSUPP) => (),
|
2020-07-31 12:08:02 +00:00
|
|
|
Err(err) => {
|
|
|
|
on_error(format_err!(
|
2020-03-23 14:03:18 +00:00
|
|
|
"failed to restore mtime attribute on {:?}: {}",
|
2020-07-31 12:08:02 +00:00
|
|
|
file_name,
|
|
|
|
err
|
|
|
|
))?;
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_fcaps(
|
2020-06-10 09:03:42 +00:00
|
|
|
flags: Flags,
|
2020-03-23 14:03:18 +00:00
|
|
|
c_proc_path: *const libc::c_char,
|
|
|
|
metadata: &Metadata,
|
|
|
|
skip_xattrs: &mut bool,
|
|
|
|
) -> Result<(), Error> {
|
2020-06-10 09:03:42 +00:00
|
|
|
if *skip_xattrs || !flags.contains(Flags::WITH_FCAPS) {
|
2020-03-23 14:03:18 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let fcaps = match metadata.fcaps.as_ref() {
|
|
|
|
Some(fcaps) => fcaps,
|
|
|
|
None => return Ok(()),
|
|
|
|
};
|
|
|
|
|
|
|
|
c_result!(unsafe {
|
|
|
|
libc::setxattr(
|
|
|
|
c_proc_path,
|
|
|
|
xattr::xattr_name_fcaps().as_ptr(),
|
|
|
|
fcaps.data.as_ptr() as *const libc::c_void,
|
|
|
|
fcaps.data.len(),
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.map(drop)
|
2020-07-14 07:16:16 +00:00
|
|
|
.or_else(|err| allow_notsupp_remember(err, skip_xattrs))
|
|
|
|
.map_err(|err| format_err!("failed to apply file capabilities: {}", err))?;
|
2020-03-23 14:03:18 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn apply_xattrs(
|
2020-06-10 09:03:42 +00:00
|
|
|
flags: Flags,
|
2020-03-23 14:03:18 +00:00
|
|
|
c_proc_path: *const libc::c_char,
|
|
|
|
metadata: &Metadata,
|
|
|
|
skip_xattrs: &mut bool,
|
|
|
|
) -> Result<(), Error> {
|
2020-06-10 09:03:42 +00:00
|
|
|
if *skip_xattrs || !flags.contains(Flags::WITH_XATTRS) {
|
2020-03-23 14:03:18 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
for xattr in &metadata.xattrs {
|
|
|
|
if *skip_xattrs {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if !xattr::is_valid_xattr_name(xattr.name()) {
|
2020-07-31 12:08:02 +00:00
|
|
|
eprintln!("skipping invalid xattr named {:?}", xattr.name());
|
2020-03-23 14:03:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
c_result!(unsafe {
|
|
|
|
libc::setxattr(
|
|
|
|
c_proc_path,
|
|
|
|
xattr.name().as_ptr() as *const libc::c_char,
|
|
|
|
xattr.value().as_ptr() as *const libc::c_void,
|
|
|
|
xattr.value().len(),
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.map(drop)
|
2020-07-14 07:16:16 +00:00
|
|
|
.or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))
|
|
|
|
.map_err(|err| format_err!("failed to apply extended attributes: {}", err))?;
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-12 08:51:36 +00:00
|
|
|
fn apply_acls(flags: Flags, c_proc_path: &CStr, metadata: &Metadata) -> Result<(), Error> {
|
2020-06-10 09:03:42 +00:00
|
|
|
if !flags.contains(Flags::WITH_ACL) || metadata.acl.is_empty() {
|
2020-03-23 14:03:18 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut acl = acl::ACL::init(5)?;
|
|
|
|
|
|
|
|
// acl type access:
|
|
|
|
acl.add_entry_full(
|
|
|
|
acl::ACL_USER_OBJ,
|
|
|
|
None,
|
|
|
|
acl::mode_user_to_acl_permissions(metadata.stat.mode),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
acl.add_entry_full(
|
|
|
|
acl::ACL_OTHER,
|
|
|
|
None,
|
|
|
|
acl::mode_other_to_acl_permissions(metadata.stat.mode),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
match metadata.acl.group_obj.as_ref() {
|
|
|
|
Some(group_obj) => {
|
|
|
|
acl.add_entry_full(
|
|
|
|
acl::ACL_MASK,
|
|
|
|
None,
|
|
|
|
acl::mode_group_to_acl_permissions(metadata.stat.mode),
|
|
|
|
)?;
|
|
|
|
acl.add_entry_full(acl::ACL_GROUP_OBJ, None, group_obj.permissions.0)?;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
acl.add_entry_full(
|
|
|
|
acl::ACL_GROUP_OBJ,
|
|
|
|
None,
|
|
|
|
acl::mode_group_to_acl_permissions(metadata.stat.mode),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for user in &metadata.acl.users {
|
|
|
|
acl.add_entry_full(acl::ACL_USER, Some(user.uid), user.permissions.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for group in &metadata.acl.groups {
|
|
|
|
acl.add_entry_full(acl::ACL_GROUP, Some(group.gid), group.permissions.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !acl.is_valid() {
|
|
|
|
bail!("Error while restoring ACL - ACL invalid");
|
|
|
|
}
|
|
|
|
|
2020-06-12 08:51:36 +00:00
|
|
|
acl.set_file(c_proc_path, acl::ACL_TYPE_ACCESS)?;
|
2020-03-23 14:03:18 +00:00
|
|
|
drop(acl);
|
|
|
|
|
|
|
|
// acl type default:
|
|
|
|
if let Some(default) = metadata.acl.default.as_ref() {
|
|
|
|
let mut acl = acl::ACL::init(5)?;
|
|
|
|
|
|
|
|
acl.add_entry_full(acl::ACL_USER_OBJ, None, default.user_obj_permissions.0)?;
|
|
|
|
|
|
|
|
acl.add_entry_full(acl::ACL_GROUP_OBJ, None, default.group_obj_permissions.0)?;
|
|
|
|
|
|
|
|
acl.add_entry_full(acl::ACL_OTHER, None, default.other_permissions.0)?;
|
|
|
|
|
|
|
|
if default.mask_permissions != pxar::format::acl::Permissions::NO_MASK {
|
|
|
|
acl.add_entry_full(acl::ACL_MASK, None, default.mask_permissions.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for user in &metadata.acl.default_users {
|
|
|
|
acl.add_entry_full(acl::ACL_USER, Some(user.uid), user.permissions.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for group in &metadata.acl.default_groups {
|
|
|
|
acl.add_entry_full(acl::ACL_GROUP, Some(group.gid), group.permissions.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !acl.is_valid() {
|
|
|
|
bail!("Error while restoring ACL - ACL invalid");
|
|
|
|
}
|
|
|
|
|
2020-06-12 08:51:36 +00:00
|
|
|
acl.set_file(c_proc_path, acl::ACL_TYPE_DEFAULT)?;
|
2020-03-23 14:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-10 09:03:42 +00:00
|
|
|
fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Result<(), Error> {
|
|
|
|
if !flags.contains(Flags::WITH_QUOTA_PROJID) {
|
2020-03-23 14:03:18 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let projid = match metadata.quota_project_id {
|
|
|
|
Some(projid) => projid,
|
|
|
|
None => return Ok(()),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut fsxattr = fs::FSXAttr::default();
|
|
|
|
unsafe {
|
|
|
|
fs::fs_ioc_fsgetxattr(fd, &mut fsxattr).map_err(|err| {
|
|
|
|
format_err!(
|
|
|
|
"error while getting fsxattr to restore quota project id - {}",
|
|
|
|
err
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
fsxattr.fsx_projid = projid.projid as u32;
|
|
|
|
|
|
|
|
fs::fs_ioc_fssetxattr(fd, &fsxattr).map_err(|err| {
|
|
|
|
format_err!(
|
|
|
|
"error while setting fsxattr to restore quota project id - {}",
|
|
|
|
err
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-07-14 07:16:16 +00:00
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|