Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
name = "fuse"
version = "0.0.0"
dependencies = [
"bitfield-struct 0.11.0",
"libc",
"lx",
"parking_lot",
Expand Down Expand Up @@ -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]]
Expand Down
1 change: 1 addition & 0 deletions vm/devices/support/fs/fuse/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ci = []
[dependencies]
lx.workspace = true

bitfield-struct.workspace = true
parking_lot.workspace = true
thiserror.workspace = true
tracing.workspace = true
Expand Down
30 changes: 30 additions & 0 deletions vm/devices/support/fs/fuse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<fuse_statx_out> {
Err(lx::Error::ENOSYS)
}
}

#[cfg(windows)]
Expand Down Expand Up @@ -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 {
Expand Down
68 changes: 68 additions & 0 deletions vm/devices/support/fs/fuse/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
}
38 changes: 35 additions & 3 deletions vm/devices/support/fs/fuse/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -75,7 +76,6 @@ impl Request {
pub fn new(mut reader: impl RequestReader) -> lx::Result<Self> {
let header: fuse_in_header = reader.read_type()?;
let operation = Self::read_operation(&header, reader);

Ok(Self { header, operation })
}

Expand Down Expand Up @@ -136,7 +136,6 @@ impl Request {
len = reader.remaining_len() + size_of_val(header),
"Invalid message length",
);

return FuseOperation::Invalid;
}

Expand All @@ -149,7 +148,6 @@ impl Request {
error = &e as &dyn std::error::Error,
"Invalid message payload",
);

FuseOperation::Invalid
}
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
];
}
10 changes: 10 additions & 0 deletions vm/devices/support/fs/fuse/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions vm/devices/support/fs/lx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading