diff --git a/Cargo.lock b/Cargo.lock index 3bd3c5cb93..adfd4d0742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,6 +2037,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" name = "fuse" version = "0.0.0" dependencies = [ + "bitfield-struct 0.11.0", "libc", "lx", "parking_lot", @@ -3789,7 +3790,10 @@ dependencies = [ name = "lx" version = "0.0.0" dependencies = [ + "bitfield-struct 0.11.0", + "static_assertions", "thiserror 2.0.16", + "zerocopy 0.8.27", ] [[package]] diff --git a/vm/devices/support/fs/fuse/Cargo.toml b/vm/devices/support/fs/fuse/Cargo.toml index a85377c0a6..3704664300 100644 --- a/vm/devices/support/fs/fuse/Cargo.toml +++ b/vm/devices/support/fs/fuse/Cargo.toml @@ -13,6 +13,7 @@ ci = [] [dependencies] lx.workspace = true +bitfield-struct.workspace = true parking_lot.workspace = true thiserror.workspace = true tracing.workspace = true diff --git a/vm/devices/support/fs/fuse/src/lib.rs b/vm/devices/support/fs/fuse/src/lib.rs index cb01dd33f1..16bd97807d 100644 --- a/vm/devices/support/fs/fuse/src/lib.rs +++ b/vm/devices/support/fs/fuse/src/lib.rs @@ -378,6 +378,23 @@ pub trait Fuse { let _ = (request, mapper, moffset, len); Err(lx::Error::ENOSYS) } + + /// Retrieves the statx details of a file. + /// + /// # Note + /// + /// If information is retrieved through an open file descriptor (i.e. using `fstat`), the + /// `fh` parameter will be set to the file handle returned by the `open` call. + fn get_statx( + &self, + _request: &Request, + _fh: u64, + _getattr_flags: u32, + _flags: StatxFlags, + _mask: lx::StatExMask, + ) -> lx::Result { + Err(lx::Error::ENOSYS) + } } #[cfg(windows)] @@ -433,6 +450,19 @@ impl fuse_attr_out { } } +impl fuse_statx_out { + /// Create a new `fuse_statx_out`. + pub fn new(valid: Duration, flags: StatxFlags, statx: fuse_statx) -> Self { + Self { + attr_valid: valid.as_secs(), + attr_valid_nsec: valid.subsec_nanos(), + flags, + statx, + _rsvd: [0; 2], + } + } +} + impl fuse_open_out { /// Create a new `fuse_open_out`. pub fn new(fh: u64, open_flags: u32) -> Self { diff --git a/vm/devices/support/fs/fuse/src/protocol.rs b/vm/devices/support/fs/fuse/src/protocol.rs index 7120500bdd..ba10c79da8 100644 --- a/vm/devices/support/fs/fuse/src/protocol.rs +++ b/vm/devices/support/fs/fuse/src/protocol.rs @@ -10,6 +10,7 @@ #![allow(non_camel_case_types)] #![expect(unused_parens)] +use bitfield_struct::bitfield; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::IntoBytes; @@ -68,6 +69,40 @@ pub struct fuse_attr { pub padding: u32, } +#[repr(C)] +#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct fuse_sx_time { + pub sec: i64, + pub nsec: u32, + pub _rsvd: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct fuse_statx { + pub mask: u32, + pub blksize: u32, + pub attributes: u64, + pub nlink: u32, + pub uid: u32, + pub gid: u32, + pub mode: u16, + pub _rsvd1: u16, + pub ino: u64, + pub size: u64, + pub blocks: u64, + pub attributes_mask: u64, + pub atime: fuse_sx_time, + pub btime: fuse_sx_time, + pub ctime: fuse_sx_time, + pub mtime: fuse_sx_time, + pub rdev_major: u32, + pub rdev_minor: u32, + pub dev_major: u32, + pub dev_minor: u32, + pub _rsvd2: [u64; 14], +} + #[repr(C)] #[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] pub struct fuse_kstatfs { @@ -306,6 +341,8 @@ pub const FUSE_COPY_FILE_RANGE: u32 = 47; pub const FUSE_SETUPMAPPING: u32 = 48; pub const FUSE_REMOVEMAPPING: u32 = 49; pub const FUSE_SYNCFS: u32 = 50; +pub const FUSE_TMPFILE: u32 = 51; +pub const FUSE_STATX: u32 = 52; /* Special Android FUSE operation */ pub const FUSE_CANONICAL_PATH: u32 = 2016; @@ -872,3 +909,34 @@ pub struct fuse_removemapping_one { pub struct fuse_syncfs_in { pub padding: u64, } + +#[bitfield(u32)] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct StatxFlags { + #[bits(13)] + _rsvd1: u32, + pub force_sync: bool, + pub dont_sync: bool, + #[bits(17)] + _rsvd2: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct fuse_statx_in { + pub getattr_flags: u32, + _rsvd: u32, + pub fh: u64, + pub flags: StatxFlags, + pub mask: u32, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct fuse_statx_out { + pub attr_valid: u64, /* Cache timeout for the attributes */ + pub attr_valid_nsec: u32, + pub flags: StatxFlags, + pub _rsvd: [u64; 2], + pub statx: fuse_statx, +} diff --git a/vm/devices/support/fs/fuse/src/request.rs b/vm/devices/support/fs/fuse/src/request.rs index 75bfceecf8..cdeae6b67e 100644 --- a/vm/devices/support/fs/fuse/src/request.rs +++ b/vm/devices/support/fs/fuse/src/request.rs @@ -61,6 +61,7 @@ fuse_operations! { FUSE_SETUPMAPPING SetupMapping arg:fuse_setupmapping_in; FUSE_REMOVEMAPPING RemoveMapping arg:fuse_removemapping_in mappings:[u8]; FUSE_SYNCFS SyncFs _arg:fuse_syncfs_in; + FUSE_STATX StatX arg:fuse_statx_in; FUSE_CANONICAL_PATH CanonicalPath; } @@ -75,7 +76,6 @@ impl Request { pub fn new(mut reader: impl RequestReader) -> lx::Result { let header: fuse_in_header = reader.read_type()?; let operation = Self::read_operation(&header, reader); - Ok(Self { header, operation }) } @@ -136,7 +136,6 @@ impl Request { len = reader.remaining_len() + size_of_val(header), "Invalid message length", ); - return FuseOperation::Invalid; } @@ -149,7 +148,6 @@ impl Request { error = &e as &dyn std::error::Error, "Invalid message payload", ); - FuseOperation::Invalid } } @@ -252,6 +250,34 @@ pub(crate) mod tests { } } + #[test] + fn parse_statx() { + let request = Request::new(FUSE_STATX_REQUEST).unwrap(); + check_header(&request, 2, FUSE_STATX, 1); + if let FuseOperation::StatX { arg } = request.operation { + assert_eq!(arg.fh, 0); + assert_eq!(arg.getattr_flags, 0); + let mask = lx::StatExMask::new() + .with_file_type(true) + .with_mode(true) + .with_nlink(true) + .with_uid(true) + .with_gid(true) + .with_atime(true) + .with_mtime(true) + .with_ctime(true) + .with_ino(true) + .with_size(true) + .with_blocks(true) + .with_btime(true); + assert_eq!(arg.mask, mask.into_bits()); + let flags = StatxFlags::new().with_dont_sync(true); + assert_eq!(arg.flags.into_bits(), flags.into_bits()); + } else { + panic!("Incorrect operation {:?}", request.operation); + } + } + #[test] fn parse_lookup() { let request = Request::new(FUSE_LOOKUP_REQUEST).unwrap(); @@ -429,4 +455,10 @@ pub(crate) mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; + + const FUSE_STATX_REQUEST: &[u8] = &[ + 64, 0, 0, 0, 52, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, + 0, 255, 15, 0, 0, + ]; } diff --git a/vm/devices/support/fs/fuse/src/session.rs b/vm/devices/support/fs/fuse/src/session.rs index 0424a0b64d..f821f3d9a3 100644 --- a/vm/devices/support/fs/fuse/src/session.rs +++ b/vm/devices/support/fs/fuse/src/session.rs @@ -402,6 +402,16 @@ impl Session { // Rely on host file system to sync data sender.send_empty(request.unique())?; } + FuseOperation::StatX { arg } => { + let out = self.fs.get_statx( + &request, + arg.fh, + arg.getattr_flags, + arg.flags, + arg.mask.into(), + )?; + sender.send_arg(request.unique(), out)?; + } FuseOperation::CanonicalPath {} => { // Android-specific opcode used to return a guest accessible // path to the file location being proxied by the fuse diff --git a/vm/devices/support/fs/lx/Cargo.toml b/vm/devices/support/fs/lx/Cargo.toml index e103c23368..d96abfa754 100644 --- a/vm/devices/support/fs/lx/Cargo.toml +++ b/vm/devices/support/fs/lx/Cargo.toml @@ -7,7 +7,10 @@ edition.workspace = true rust-version.workspace = true [dependencies] +bitfield-struct.workspace = true +static_assertions.workspace = true thiserror.workspace = true +zerocopy.workspace = true [lints] workspace = true diff --git a/vm/devices/support/fs/lx/src/lib.rs b/vm/devices/support/fs/lx/src/lib.rs index d1a6ef6e3f..7fa547107c 100644 --- a/vm/devices/support/fs/lx/src/lib.rs +++ b/vm/devices/support/fs/lx/src/lib.rs @@ -9,8 +9,14 @@ mod macros; mod string; +use bitfield_struct::bitfield; +use static_assertions::const_assert_eq; use std::io; use thiserror::Error; +use zerocopy::FromBytes; +use zerocopy::Immutable; +use zerocopy::IntoBytes; +use zerocopy::KnownLayout; pub use string::LxStr; pub use string::LxString; @@ -254,6 +260,116 @@ impl From<&std::time::Duration> for Timespec { } } +#[bitfield(u32)] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct StatExMask { + pub file_type: bool, // STATX_TYPE + pub mode: bool, // STATX_MODE + pub nlink: bool, // STATX_NLINK + pub uid: bool, // STATX_UID + pub gid: bool, // STATX_GID + pub atime: bool, // STATX_ATIME + pub mtime: bool, // STATX_MTIME + pub ctime: bool, // STATX_CTIME + pub ino: bool, // STATX_INO + pub size: bool, // STATX_SIZE + pub blocks: bool, // STATX_BLOCKS + pub btime: bool, // STATX_BTIME + pub mnt_id: bool, // STATX_MNT_ID + pub dio_align: bool, // STATX_DIOALIGN + pub mnt_id_unique: bool, // STATX_MNT_ID_UNIQUE + pub subvol: bool, // STATX_SUBVOL + pub write_atomic: bool, // STATX_WRITE_ATOMIC + #[bits(15)] + pub _rsvd: u32, +} + +#[bitfield(u64)] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct StatExAttributes { + #[bits(2)] + pub _rsvd1: u8, + pub compressed: bool, // STATX_ATTR_COMPRESSED + pub _rsvd2: bool, + pub immutable: bool, // STATX_ATTR_IMMUTABLE + pub append: bool, // STATX_ATTR_APPEND + pub nodump: bool, // STATX_ATTR_NODUMP + #[bits(4)] + pub _rsvd3: u8, + pub encrypted: bool, // STATX_ATTR_ENCRYPTED + pub automount: bool, // STATX_ATTR_AUTOMOUNT + pub mount_root: bool, // STATX_ATTR_MOUNT_ROOT + #[bits(6)] + pub _rsvd4: u8, + pub verity: bool, // STATX_ATTR_VERITY + pub dax: bool, // STATX_ATTR_DAX, + pub write_atomic: bool, // STATX_ATTR_WRITE_ATOMIC + #[bits(41)] + pub _rsvd: u64, +} + +#[repr(C)] +#[derive(Debug, Default, Eq, PartialEq)] +pub struct StatExTimestamp { + pub seconds: i64, + pub nanoseconds: u32, + pub _rsvd: i32, +} + +impl From for Timespec { + fn from(ts: StatExTimestamp) -> Self { + Timespec { + seconds: ts.seconds as usize, + nanoseconds: ts.nanoseconds as usize, + } + } +} +impl From for StatExTimestamp { + fn from(ts: Timespec) -> Self { + StatExTimestamp { + seconds: ts.seconds as i64, + nanoseconds: ts.nanoseconds as u32, + _rsvd: 0, + } + } +} + +#[repr(C)] +#[derive(Debug, Default)] +pub struct StatEx { + pub mask: StatExMask, + pub block_size: u32, + pub attributes: StatExAttributes, + pub link_count: u32, + pub uid: uid_t, + pub gid: gid_t, + pub mode: u16, + pub _rsvd1: u16, + pub inode_id: ino_t, + pub file_size: u64, + pub block_count: u64, + pub attributes_mask: StatExAttributes, + pub access_time: StatExTimestamp, + pub creation_time: StatExTimestamp, + pub change_time: StatExTimestamp, + pub write_time: StatExTimestamp, + pub rdev_major: u32, + pub rdev_minor: u32, + pub dev_major: u32, + pub dev_minor: u32, + pub mount_id: u64, + pub dio_mem_align: u32, + pub dio_offset_align: u32, + pub subvolume_id: u64, + pub atomic_write_unit_min: u32, + pub atomic_write_unit_max: u32, + pub atomic_write_segments_max: u32, + pub _rsvd2: u32, + pub _rsvd3: [u64; 9], +} + +const_assert_eq!(size_of::(), 256); + /// A Linux `stat` structure. #[cfg(target_arch = "x86_64")] // xtask-fmt allow-target-arch sys-crate #[repr(C)] @@ -291,7 +407,7 @@ pub struct Stat { pub pad0: u32, pub file_size: u64, pub block_size: u32, - pub pad2: u32, + pub pad1: u32, pub block_count: u64, pub access_time: Timespec, pub write_time: Timespec, @@ -299,6 +415,33 @@ pub struct Stat { pub unused: [u32; 2], } +impl From for Stat { + fn from(statx: StatEx) -> Self { + Stat { + device_nr: make_dev(statx.dev_major, statx.dev_minor) as _, + inode_nr: statx.inode_id, + link_count: statx.link_count as _, + mode: statx.mode as _, + uid: statx.uid, + gid: statx.gid, + device_nr_special: make_dev(statx.rdev_major, statx.rdev_minor) as _, + file_size: statx.file_size, + block_size: statx.block_size as _, + block_count: statx.block_count, + access_time: statx.access_time.into(), + write_time: statx.write_time.into(), + change_time: statx.change_time.into(), + pad0: 0, + #[cfg(target_arch = "x86_64")] // xtask-fmt allow-target-arch sys-crate + pad1: [0; 3], + #[cfg(target_arch = "aarch64")] // xtask-fmt allow-target-arch sys-crate + pad1: 0, + #[cfg(target_arch = "aarch64")] // xtask-fmt allow-target-arch sys-crate + unused: [0; 2], + } + } +} + #[repr(C)] #[derive(Debug, Eq, PartialEq)] pub struct StatFs { diff --git a/vm/devices/support/fs/lxutil/src/lib.rs b/vm/devices/support/fs/lxutil/src/lib.rs index 2ac65294b5..9a676a4308 100644 --- a/vm/devices/support/fs/lxutil/src/lib.rs +++ b/vm/devices/support/fs/lxutil/src/lib.rs @@ -89,6 +89,11 @@ impl LxVolume { /// Retrieves the attributes of a file. Symlinks are not followed. pub fn lstat(&self, path: impl AsRef) -> lx::Result { + self.inner.lstat(path.as_ref()).map(|x| x.into()) + } + + /// Retrieves the statx details of a file. Symlinks are not followed. + pub fn statx(&self, path: impl AsRef) -> lx::Result { self.inner.lstat(path.as_ref()) } @@ -530,7 +535,7 @@ pub struct LxFile { impl LxFile { /// Retrieves the attributes of the file. - pub fn fstat(&self) -> lx::Result { + pub fn fstat(&self) -> lx::Result { self.inner.fstat() } @@ -1151,7 +1156,7 @@ mod tests { env.create_file("testfile", "test"); let stat = env.volume.lstat("testfile").unwrap(); let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap(); - let fstat = file.fstat().unwrap(); + let fstat = file.fstat().unwrap().into(); println!("{:#?}", fstat); assert_eq!(stat, fstat); @@ -1161,7 +1166,7 @@ mod tests { .open("", lx::O_RDONLY | lx::O_DIRECTORY, None) .unwrap(); - let fstat = file.fstat().unwrap(); + let fstat = file.fstat().unwrap().into(); println!("{:#?}", fstat); assert_eq!(stat, fstat); } @@ -1355,7 +1360,7 @@ mod tests { .unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o640); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o640); // Only Windows uses the uid/gid if cfg!(windows) { assert_eq!(stat.uid, 1000); @@ -1518,7 +1523,8 @@ mod tests { ) .unwrap(); - assert_eq!(file.fstat().unwrap(), env.volume.lstat(&path).unwrap()); + let file_stat: lx::Stat = file.fstat().unwrap().into(); + assert_eq!(file_stat, env.volume.lstat(&path).unwrap()); } #[test] @@ -1685,7 +1691,7 @@ mod tests { .unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); let write_result = if cfg!(windows) || !is_lx_root() { lx::S_IFREG | 0o777 @@ -1696,45 +1702,45 @@ mod tests { // Write clears it (except for root). file.pwrite(b"hello", 0, 1000).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, write_result); + assert_eq!(stat.mode as u32, write_result); if cfg!(windows) { // Write does not clear it for root. file.chmod(lx::S_IFREG | 0o6777).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); file.pwrite(b"hello", 0, 0).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); } file.chmod(lx::S_IFREG | 0o6777).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); // Truncate clears it (except for root). file.truncate(2, 1000).unwrap(); let stat = file.fstat().unwrap(); assert_eq!(stat.file_size, 2); - assert_eq!(stat.mode, write_result); + assert_eq!(stat.mode as u32, write_result); if cfg!(windows) { // Truncate does not clear it as root. file.chmod(lx::S_IFREG | 0o6777).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); file.truncate(2, 0).unwrap(); let stat = file.fstat().unwrap(); assert_eq!(stat.file_size, 2); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); } file.chmod(lx::S_IFREG | 0o6777).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); // Chown no changes does not clear it. let stat = file.fstat().unwrap(); file.chown(None, None).unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777); // Chown clears it. // N.B. Only perform this test if we have permissions to do so. @@ -1743,17 +1749,17 @@ mod tests { let stat = file.fstat().unwrap(); assert_eq!(stat.uid, 1001); assert_eq!(stat.gid, 2001); - assert_eq!(stat.mode, lx::S_IFREG | 0o777); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o777); // Chown doesn't clear setgid if not group executable. file.chmod(lx::S_IFREG | 0o6767).unwrap(); let stat = file.fstat().unwrap(); - assert_eq!(stat.mode, lx::S_IFREG | 0o6767); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6767); file.chown(Some(1001), Some(2001)).unwrap(); let stat = file.fstat().unwrap(); assert_eq!(stat.uid, 1001); assert_eq!(stat.gid, 2001); - assert_eq!(stat.mode, lx::S_IFREG | 0o2767); + assert_eq!(stat.mode as u32, lx::S_IFREG | 0o2767); } } diff --git a/vm/devices/support/fs/lxutil/src/unix/mod.rs b/vm/devices/support/fs/lxutil/src/unix/mod.rs index 1e8ebe8632..d94f66a7be 100644 --- a/vm/devices/support/fs/lxutil/src/unix/mod.rs +++ b/vm/devices/support/fs/lxutil/src/unix/mod.rs @@ -9,11 +9,15 @@ pub(crate) mod path; mod util; use crate::SetAttributes; +use lx::StatEx; use std::ffi; use std::mem; use std::os::unix::prelude::*; use std::path::Path; +const STATX_BASIC_STATS: u32 = 0x000007ff; +const STATX_BTIME: u32 = 0x00000800; + // Unix implementation of LxVolume. // See crate::LxVolume for more detailed comments. pub struct LxVolume { @@ -42,23 +46,24 @@ impl LxVolume { true } - pub fn lstat(&self, path: &Path) -> lx::Result { + pub fn lstat(&self, path: &Path) -> lx::Result { assert!(path.is_relative()); let path = util::path_to_cstr(path)?; - // SAFETY: Calling C API as documented, with no special requirements. - let stat = unsafe { - let mut stat = mem::zeroed(); - util::check_lx_errno(libc::fstatat( + // SAFETY: Calling syscall as documented, with no special requirements. + let statx = unsafe { + let mut statx: StatEx = mem::zeroed(); + util::check_lx_errno(libc::syscall( + libc::SYS_statx as ffi::c_long, self.root.as_raw_fd(), path.as_ptr(), - &mut stat, libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, + &mut statx, ))?; - stat + statx }; - - Ok(util::libc_stat_to_lx_stat(stat)) + Ok(statx) } pub fn set_attr(&self, path: &Path, attr: SetAttributes) -> lx::Result<()> { @@ -67,7 +72,7 @@ impl LxVolume { pub fn set_attr_stat(&self, path: &Path, attr: SetAttributes) -> lx::Result { util::set_attr(&self.root, Some(path), &attr)?; - self.lstat(path) + self.lstat(path).map(|x| x.into()) } pub fn open( @@ -105,7 +110,7 @@ impl LxVolume { pub fn mkdir_stat(&self, path: &Path, options: super::LxCreateOptions) -> lx::Result { self.mkdir(path, options)?; - self.lstat(path) + self.lstat(path).map(|x| x.into()) } // The options are entirely ignored on Unix, because uid/gid are never used, and mode isn't @@ -140,7 +145,7 @@ impl LxVolume { options: super::LxCreateOptions, ) -> lx::Result { self.symlink(path, target, options)?; - self.lstat(path) + self.lstat(path).map(|x| x.into()) } pub fn read_link(&self, path: &Path) -> lx::Result { @@ -206,7 +211,7 @@ impl LxVolume { device_id: lx::dev_t, ) -> lx::Result { self.mknod(path, options, device_id)?; - self.lstat(path) + self.lstat(path).map(|x| x.into()) } pub fn rename(&self, path: &Path, new_path: &Path, flags: u32) -> lx::Result<()> { @@ -254,7 +259,7 @@ impl LxVolume { pub fn link_stat(&self, path: &Path, new_path: &Path) -> lx::Result { self.link(path, new_path)?; - self.lstat(new_path) + self.lstat(new_path).map(|x| x.into()) } pub fn stat_fs(&self, path: &Path) -> lx::Result { @@ -383,15 +388,22 @@ pub struct LxFile { } impl LxFile { - pub fn fstat(&self) -> lx::Result { - // SAFETY: Calling C API as documented, with no special requirements. - let stat = unsafe { - let mut stat = mem::zeroed(); - util::check_lx_errno(libc::fstat(self.fd.as_raw_fd(), &mut stat))?; - stat + pub fn fstat(&self) -> lx::Result { + // SAFETY: Calling syscall as documented, with no special requirements. + let empty_path = util::create_cstr("")?; + let statx = unsafe { + let mut statx: StatEx = mem::zeroed(); + util::check_lx_errno(libc::syscall( + libc::SYS_statx as ffi::c_long, + self.fd.as_raw_fd(), + empty_path.as_ptr(), + libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, + &mut statx, + ))?; + statx }; - - Ok(util::libc_stat_to_lx_stat(stat)) + Ok(statx) } pub fn set_attr(&self, attr: SetAttributes) -> lx::Result<()> { diff --git a/vm/devices/support/fs/lxutil/src/unix/util.rs b/vm/devices/support/fs/lxutil/src/unix/util.rs index a57e48cfa2..546623343e 100644 --- a/vm/devices/support/fs/lxutil/src/unix/util.rs +++ b/vm/devices/support/fs/lxutil/src/unix/util.rs @@ -131,12 +131,6 @@ pub unsafe fn set_errno(error: i32) { } } -pub fn libc_stat_to_lx_stat(stat: libc::stat) -> lx::Stat { - // SAFETY: lx::Stat is identical to libc's version, and padding bytes are not exposed, so just transmute it. - // N.B. This call won't compile if the two aren't the same size. - unsafe { mem::transmute(stat) } -} - pub fn libc_stat_fs_to_lx_stat_fs(stat_fs: libc::statfs) -> lx::StatFs { // SAFETY: lx::StatFs is identical to libc's version, and padding bytes are not exposed, so just transmute it. // N.B. This call won't compile if the two aren't the same size. diff --git a/vm/devices/support/fs/lxutil/src/windows/fs.rs b/vm/devices/support/fs/lxutil/src/windows/fs.rs index 90f92cd43f..e7d5183544 100644 --- a/vm/devices/support/fs/lxutil/src/windows/fs.rs +++ b/vm/devices/support/fs/lxutil/src/windows/fs.rs @@ -565,7 +565,7 @@ pub fn get_lx_attr( umask: u32, fmask: u32, dmask: u32, -) -> lx::Result { +) -> lx::Result { let inode_attr = determine_inode_attributes(fs_context, info, flags, umask, fmask, dmask)?; let mode = inode_attr.mode.unwrap_or(0); let file_size: u64; @@ -579,29 +579,56 @@ pub fn get_lx_attr( block_count = allocation_size_to_block_count(info.AllocationSize, block_size) } - // lx::Stat has different padding members on ARM and x86. As such, don't construct it manually, - // but just fill out the individual fields. - let mut stat: lx::Stat = unsafe { std::mem::zeroed() }; - stat.uid = inode_attr.uid.unwrap_or(default_uid); - stat.gid = inode_attr.gid.unwrap_or(default_gid); - stat.mode = mode; - stat.device_nr_special = inode_attr.device_id.unwrap_or(0) as _; - stat.inode_nr = info.FileId as _; - stat.link_count = info.NumberOfLinks as _; - stat.access_time = util::nt_time_to_timespec(info.LastAccessTime, true); - stat.write_time = util::nt_time_to_timespec(info.LastWriteTime, true); - stat.change_time = if info.ChangeTime == 0 { - // Some file systems do not provide a change time. If this is the case, - // use the write time. - util::nt_time_to_timespec(info.LastWriteTime, true) - } else { - util::nt_time_to_timespec(info.ChangeTime, true) - }; - stat.block_size = block_size as _; - stat.file_size = file_size; - stat.block_count = block_count; - - Ok(stat) + let attributes_mask = lx::StatExAttributes::new() + .with_compressed(true) + .with_encrypted(true) + .with_nodump(true); + let attributes = lx::StatExAttributes::new() + .with_compressed(info.FileAttributes & W32Fs::FILE_ATTRIBUTE_COMPRESSED.0 != 0) + .with_encrypted(info.FileAttributes & W32Fs::FILE_ATTRIBUTE_ENCRYPTED.0 != 0) + .with_nodump(info.FileAttributes & W32Fs::FILE_ATTRIBUTE_ARCHIVE.0 == 0); + let mask = lx::StatExMask::new() + .with_file_type(true) + .with_mode(true) + .with_nlink(true) + .with_uid(true) + .with_gid(true) + .with_atime(true) + .with_btime(true) + .with_ctime(info.ChangeTime != 0) + .with_mtime(true) + .with_ino(true) + .with_size(true) + .with_blocks(true); + + let rdev_id = inode_attr.device_id.unwrap_or(0); + Ok(lx::StatEx { + uid: inode_attr.uid.unwrap_or(default_uid), + gid: inode_attr.gid.unwrap_or(default_gid), + mode: mode as u16, + rdev_major: lx::major32(rdev_id), + rdev_minor: lx::minor(rdev_id), + inode_id: info.FileId as _, + link_count: info.NumberOfLinks as _, + creation_time: util::nt_time_to_timespec(info.CreationTime, true).into(), + access_time: util::nt_time_to_timespec(info.LastAccessTime, true).into(), + write_time: util::nt_time_to_timespec(info.LastWriteTime, true).into(), + change_time: if info.ChangeTime == 0 { + // Some file systems do not provide a change time. If this is the case, + // use the write time. + util::nt_time_to_timespec(info.LastWriteTime, true) + } else { + util::nt_time_to_timespec(info.ChangeTime, true) + } + .into(), + block_size: block_size as _, + file_size, + block_count, + attributes_mask, + attributes, + mask, + ..Default::default() + }) } /// Query the stat information for a handle. If the filesystem does not support FILE_STAT_INFORMATION, diff --git a/vm/devices/support/fs/lxutil/src/windows/mod.rs b/vm/devices/support/fs/lxutil/src/windows/mod.rs index 9bd3c2cb11..05fdb80dda 100644 --- a/vm/devices/support/fs/lxutil/src/windows/mod.rs +++ b/vm/devices/support/fs/lxutil/src/windows/mod.rs @@ -192,7 +192,7 @@ impl LxVolume { Ok(()) } - pub fn lstat(&self, path: &Path) -> lx::Result { + pub fn lstat(&self, path: &Path) -> lx::Result { assert!(path.is_relative()); // Special-case returning attributes of the root itself to just use the existing handle. @@ -1088,6 +1088,7 @@ impl LxVolume { &self.state.options, self.state.block_size, ) + .map(|x| x.into()) } } @@ -1103,7 +1104,7 @@ pub struct LxFile { } impl LxFile { - pub fn fstat(&self) -> lx::Result { + pub fn fstat(&self) -> lx::Result { let mut info = self.state.get_attributes_by_handle(&self.handle)?; *self.is_app_exec_alias.lock() = info.is_app_execution_alias; util::file_info_to_stat( @@ -1199,9 +1200,9 @@ impl LxFile { && self.kill_priv.swap(false, Ordering::AcqRel) { let stat = self.fstat()?; - if stat.mode & (lx::S_ISUID | lx::S_ISGID) != 0 { + if stat.mode as u32 & (lx::S_ISUID | lx::S_ISGID) != 0 { let mut attr = SetAttributes::default(); - attr.mode = Some(stat.mode & !(lx::S_ISUID | lx::S_ISGID)); + attr.mode = Some(stat.mode as u32 & !(lx::S_ISUID | lx::S_ISGID)); self.set_attr(attr)?; } } diff --git a/vm/devices/support/fs/lxutil/src/windows/util.rs b/vm/devices/support/fs/lxutil/src/windows/util.rs index 12b3f44e50..f502096434 100644 --- a/vm/devices/support/fs/lxutil/src/windows/util.rs +++ b/vm/devices/support/fs/lxutil/src/windows/util.rs @@ -553,7 +553,7 @@ pub fn file_info_to_stat( information: &mut LxStatInformation, options: &crate::LxVolumeOptions, block_size: u32, -) -> lx::Result { +) -> lx::Result { let mut stat = fs::get_lx_attr( fs_context, &mut information.stat, @@ -574,7 +574,7 @@ pub fn file_info_to_stat( stat.gid = gid; } if let Some(mode) = options.mode { - stat.mode = override_mode(stat.mode, mode); + stat.mode = override_mode(stat.mode as u32, mode) as u16; } if let Some(symlink_len) = information.symlink_len { stat.file_size = symlink_len as u64; diff --git a/vm/devices/support/fs/plan9/src/fid.rs b/vm/devices/support/fs/plan9/src/fid.rs index 784089ef1c..4ce59a0c21 100644 --- a/vm/devices/support/fs/plan9/src/fid.rs +++ b/vm/devices/support/fs/plan9/src/fid.rs @@ -221,7 +221,7 @@ impl FileState { // Determine file attributes based on the stored path. fn get_attributes(&self) -> lx::Result<(Qid, lx::Stat)> { let stat = if let Some(file) = self.file.as_ref() { - file.fstat()? + file.fstat()?.into() } else { self.root.lstat(&self.path)? }; @@ -230,7 +230,7 @@ impl FileState { } fn get_file_attributes(file: &LxFile) -> lx::Result<(Qid, lx::Stat)> { - let stat = file.fstat()?; + let stat = file.fstat()?.into(); Ok((Self::stat_to_qid(&stat), stat)) } } diff --git a/vm/devices/virtio/virtiofs/src/file.rs b/vm/devices/virtio/virtiofs/src/file.rs index d52bc094a7..19e275fd24 100644 --- a/vm/devices/virtio/virtiofs/src/file.rs +++ b/vm/devices/virtio/virtiofs/src/file.rs @@ -7,6 +7,7 @@ use fuse::DirEntryWriter; use fuse::protocol::fuse_attr; use fuse::protocol::fuse_entry_out; use fuse::protocol::fuse_setattr_in; +use fuse::protocol::fuse_statx; use lxutil::LxFile; use parking_lot::RwLock; use std::sync::Arc; @@ -29,10 +30,16 @@ impl VirtioFsFile { /// Gets the attributes of the open file. pub fn get_attr(&self) -> lx::Result { - let stat = self.file.read().fstat()?; + let stat = self.file.read().fstat()?.into(); Ok(util::stat_to_fuse_attr(&stat)) } + /// Gets the statx details for the open file. + pub fn get_statx(&self) -> lx::Result { + let statx = self.file.read().fstat()?; + Ok(util::statx_to_fuse_statx(&statx)) + } + /// Sets the attributes of the open file. pub fn set_attr(&self, arg: &fuse_setattr_in, request_uid: lx::uid_t) -> lx::Result<()> { let attr = util::fuse_set_attr_to_lxutil(arg, request_uid); @@ -71,7 +78,6 @@ impl VirtioFsFile { let mut file = self.file.write(); file.read_dir(offset as lx::off_t, |entry| { entry_count += 1; - let get_child_fuse_entry = || -> lx::Result> { match fs.lookup_helper(&self.inode, &entry.name) { Ok(e) => Ok(Some(e)), diff --git a/vm/devices/virtio/virtiofs/src/inode.rs b/vm/devices/virtio/virtiofs/src/inode.rs index 6a39460cd1..73dad78eae 100644 --- a/vm/devices/virtio/virtiofs/src/inode.rs +++ b/vm/devices/virtio/virtiofs/src/inode.rs @@ -92,6 +92,12 @@ impl VirtioFsInode { Ok(util::stat_to_fuse_attr(&stat)) } + /// Retrieves the extended attributes of this inode. + pub fn get_statx(&self) -> lx::Result { + let statx = self.volume.statx(&*self.get_path())?; + Ok(util::statx_to_fuse_statx(&statx)) + } + /// Sets the attributes of this inode. pub fn set_attr(&self, arg: &fuse_setattr_in, request_uid: lx::uid_t) -> lx::Result { let attr = util::fuse_set_attr_to_lxutil(arg, request_uid); @@ -123,7 +129,7 @@ impl VirtioFsInode { let options = LxCreateOptions::new(mode, uid, gid); let flags = (flags as i32) | lx::O_CREAT | lx::O_NOFOLLOW; let file = self.volume.open(&path, flags, Some(options))?; - let stat = file.fstat()?; + let stat = file.fstat()?.into(); let inode = Self::with_attr(Arc::clone(&self.volume), path, &stat); let attr = util::stat_to_fuse_attr(&stat); Ok((inode, attr, file)) diff --git a/vm/devices/virtio/virtiofs/src/lib.rs b/vm/devices/virtio/virtiofs/src/lib.rs index 303db4669c..23e06699f9 100644 --- a/vm/devices/virtio/virtiofs/src/lib.rs +++ b/vm/devices/virtio/virtiofs/src/lib.rs @@ -75,6 +75,28 @@ impl Fuse for VirtioFs { Ok(fuse_attr_out::new(ATTRIBUTE_TIMEOUT, attr)) } + fn get_statx( + &self, + request: &Request, + fh: u64, + getattr_flags: u32, + flags: StatxFlags, + _mask: lx::StatExMask, + ) -> lx::Result { + let node_id = request.node_id(); + // If a file handle is specified, get the attributes from the open file. This is faster on + // Windows and works if the file was deleted. + let statx = if getattr_flags & FUSE_GETATTR_FH != 0 { + let file = self.get_file(fh)?; + file.get_statx()? + } else { + let inode = self.get_inode(node_id)?; + inode.get_statx()? + }; + + Ok(fuse_statx_out::new(ATTRIBUTE_TIMEOUT, flags, statx)) + } + fn set_attr(&self, request: &Request, arg: &fuse_setattr_in) -> lx::Result { let node_id = request.node_id(); diff --git a/vm/devices/virtio/virtiofs/src/util.rs b/vm/devices/virtio/virtiofs/src/util.rs index 93a94c7462..e5e86596b5 100644 --- a/vm/devices/virtio/virtiofs/src/util.rs +++ b/vm/devices/virtio/virtiofs/src/util.rs @@ -30,6 +30,41 @@ pub fn stat_to_fuse_attr(stat: &lx::Stat) -> fuse_attr { } } +pub fn statex_timestamp_to_fuse_sx_time(ts: &lx::StatExTimestamp) -> fuse_sx_time { + fuse_sx_time { + sec: ts.seconds, + nsec: ts.nanoseconds, + _rsvd: 0, + } +} + +/// Convert a Linux stat struct to FUSE extended attributes. +pub fn statx_to_fuse_statx(stat: &lx::StatEx) -> fuse_statx { + fuse_statx { + mask: stat.mask.into_bits(), + blksize: stat.block_size, + attributes: stat.attributes.into_bits(), + nlink: stat.link_count, + uid: stat.uid, + gid: stat.gid, + mode: stat.mode, + ino: stat.inode_id, + size: stat.file_size, + blocks: stat.block_count, + attributes_mask: stat.attributes_mask.into_bits(), + atime: statex_timestamp_to_fuse_sx_time(&stat.access_time), + btime: statex_timestamp_to_fuse_sx_time(&stat.creation_time), + mtime: statex_timestamp_to_fuse_sx_time(&stat.write_time), + ctime: statex_timestamp_to_fuse_sx_time(&stat.change_time), + rdev_major: stat.rdev_major, + rdev_minor: stat.rdev_minor, + dev_major: stat.dev_major, + dev_minor: stat.dev_minor, + _rsvd1: 0, + _rsvd2: [0; 14], + } +} + /// Convert a FUSE setattr message to a lxutil `SetAttributes` struct. pub fn fuse_set_attr_to_lxutil( arg: &fuse_setattr_in,