use std::convert::TryFrom;
use std::error;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::Path;
use log::warn;
use nix::unistd::Gid;
use nix::unistd::Pid;
use nix::unistd::Uid;
#[cfg(feature = "serializable")]
use serde::Deserialize;
#[cfg(feature = "serializable")]
use serde::Serialize;
use crate::Errno;
use crate::ll::argument::ArgumentIterator;
use crate::ll::fuse_abi as abi;
use crate::ll::fuse_abi::fuse_in_header;
use crate::ll::fuse_abi::fuse_opcode;
#[derive(Debug)]
pub(crate) enum RequestError {
ShortReadHeader(usize),
UnknownOperation(u32),
ShortRead(usize, usize),
InsufficientData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct RequestId(pub u64);
impl From<RequestId> for u64 {
fn from(fh: RequestId) -> Self {
fh.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct INodeNo(pub u64);
impl INodeNo {
pub const ROOT: INodeNo = INodeNo(1);
}
impl From<INodeNo> for u64 {
fn from(fh: INodeNo) -> Self {
fh.0
}
}
impl Display for INodeNo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct FileHandle(pub u64);
impl From<FileHandle> for u64 {
fn from(fh: FileHandle) -> Self {
fh.0
}
}
impl Display for FileHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct LockOwner(pub u64);
impl Display for LockOwner {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Lock {
pub(crate) range: (u64, u64),
pub(crate) typ: i32,
pub(crate) pid: u32,
}
impl Lock {
fn from_abi(x: &abi::fuse_file_lock) -> Lock {
Lock {
range: (x.start, x.end),
typ: x.typ,
pid: x.pid,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct Version(
pub u32,
pub u32,
);
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.0, self.1)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub(crate) struct FilenameInDir<'a> {
pub(crate) dir: INodeNo,
pub(crate) name: &'a Path,
}
impl fmt::Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RequestError::ShortReadHeader(len) => write!(
f,
"Short read of FUSE request header ({len} < {})",
size_of::<fuse_in_header>()
),
RequestError::UnknownOperation(opcode) => write!(f, "Unknown FUSE opcode ({opcode})"),
RequestError::ShortRead(len, total) => {
write!(f, "Short read of FUSE request ({len} < {total})")
}
RequestError::InsufficientData => write!(f, "Insufficient argument data"),
}
}
}
impl error::Error for RequestError {}
fn validate_off_t(name: &'static str, value: u64) -> Result<(), Errno> {
if value > i64::MAX as u64 {
warn!("{name}={value} is out of range");
return Err(Errno::EINVAL);
}
Ok(())
}
mod op {
use std::cmp;
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fmt::Display;
use std::mem::offset_of;
use std::num::NonZeroU32;
use std::path::Path;
use std::time::SystemTime;
use zerocopy::FromZeros;
use zerocopy::IntoBytes;
use crate::AccessFlags;
use crate::CopyFileRangeFlags;
use crate::Errno;
use crate::IoctlFlags;
use crate::OpenFlags;
use crate::PollEvents;
use crate::PollFlags;
use crate::PollHandle;
use crate::WriteFlags;
use crate::bsd_file_flags::BsdFileFlags;
use crate::ll::ResponseData;
use crate::ll::TimeOrNow;
use crate::ll::argument::ArgumentIterator;
use crate::ll::flags::fattr_flags::FattrFlags;
use crate::ll::flags::fsync_flags::FsyncFlags;
use crate::ll::flags::getattr_flags::GetattrFlags;
use crate::ll::flags::init_flags::InitFlags;
use crate::ll::flags::read_flags::ReadFlags;
use crate::ll::flags::release_flags::ReleaseFlags;
use crate::ll::request::FileHandle;
use crate::ll::request::FilenameInDir;
use crate::ll::request::INodeNo;
use crate::ll::request::Lock;
use crate::ll::request::LockOwner;
use crate::ll::request::Operation;
use crate::ll::request::RequestId;
use crate::ll::request::abi::*;
use crate::ll::request::validate_off_t;
use crate::time::system_time_from_time;
#[derive(Debug)]
pub(crate) struct Lookup<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
name: &'a OsStr,
}
impl<'a> Lookup<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name.as_ref()
}
}
#[derive(Debug)]
pub(crate) struct Forget<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_forget_in,
}
impl Forget<'_> {
pub(crate) fn nlookup(&self) -> u64 {
self.arg.nlookup
}
}
#[derive(Debug)]
pub(crate) struct GetAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_getattr_in,
}
impl GetAttr<'_> {
pub(crate) fn getattr_flags(&self) -> GetattrFlags {
GetattrFlags::from_bits_retain(self.arg.getattr_flags)
}
pub(crate) fn file_handle(&self) -> Option<FileHandle> {
if self.getattr_flags().contains(GetattrFlags::FUSE_GETATTR_FH) {
Some(FileHandle(self.arg.fh))
} else {
None
}
}
}
#[derive(Debug)]
pub(crate) struct SetAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_setattr_in,
}
impl SetAttr<'_> {
fn valid(&self) -> FattrFlags {
FattrFlags::from_bits_retain(self.arg.valid)
}
pub(crate) fn mode(&self) -> Option<u32> {
if self.valid().contains(FattrFlags::FATTR_MODE) {
Some(self.arg.mode)
} else {
None
}
}
pub(crate) fn uid(&self) -> Option<u32> {
if self.valid().contains(FattrFlags::FATTR_UID) {
Some(self.arg.uid)
} else {
None
}
}
pub(crate) fn gid(&self) -> Option<u32> {
if self.valid().contains(FattrFlags::FATTR_GID) {
Some(self.arg.gid)
} else {
None
}
}
pub(crate) fn size(&self) -> Option<u64> {
if self.valid().contains(FattrFlags::FATTR_SIZE) {
Some(self.arg.size)
} else {
None
}
}
pub(crate) fn atime(&self) -> Option<TimeOrNow> {
if self.valid().contains(FattrFlags::FATTR_ATIME) {
Some(if self.arg.atime_now() {
TimeOrNow::Now
} else {
TimeOrNow::SpecificTime(system_time_from_time(
self.arg.atime,
self.arg.atimensec,
))
})
} else {
None
}
}
pub(crate) fn mtime(&self) -> Option<TimeOrNow> {
if self.valid().contains(FattrFlags::FATTR_MTIME) {
Some(if self.arg.mtime_now() {
TimeOrNow::Now
} else {
TimeOrNow::SpecificTime(system_time_from_time(
self.arg.mtime,
self.arg.mtimensec,
))
})
} else {
None
}
}
pub(crate) fn ctime(&self) -> Option<SystemTime> {
if self.valid().contains(FattrFlags::FATTR_CTIME) {
Some(system_time_from_time(self.arg.ctime, self.arg.ctimensec))
} else {
None
}
}
pub(crate) fn file_handle(&self) -> Option<FileHandle> {
if self.valid().contains(FattrFlags::FATTR_FH) {
Some(FileHandle(self.arg.fh))
} else {
None
}
}
pub(crate) fn crtime(&self) -> Option<SystemTime> {
#[cfg(target_os = "macos")]
if self.valid().contains(FattrFlags::FATTR_CRTIME) {
if self.arg.crtime == 0xffffffff83da4f80 {
None
} else {
Some(
SystemTime::UNIX_EPOCH
+ std::time::Duration::new(self.arg.crtime, self.arg.crtimensec),
)
}
} else {
None
}
#[cfg(not(target_os = "macos"))]
None
}
pub(crate) fn chgtime(&self) -> Option<SystemTime> {
#[cfg(target_os = "macos")]
if self.valid().contains(FattrFlags::FATTR_CHGTIME) {
Some(
SystemTime::UNIX_EPOCH
+ std::time::Duration::new(self.arg.chgtime, self.arg.chgtimensec),
)
} else {
None
}
#[cfg(not(target_os = "macos"))]
None
}
pub(crate) fn bkuptime(&self) -> Option<SystemTime> {
#[cfg(target_os = "macos")]
if self.valid().contains(FattrFlags::FATTR_BKUPTIME) {
Some(
SystemTime::UNIX_EPOCH
+ std::time::Duration::new(self.arg.bkuptime, self.arg.bkuptimensec),
)
} else {
None
}
#[cfg(not(target_os = "macos"))]
None
}
pub(crate) fn flags(&self) -> Option<BsdFileFlags> {
#[cfg(target_os = "macos")]
if self.valid().contains(FattrFlags::FATTR_FLAGS) {
Some(BsdFileFlags::from_bits_retain(self.arg.flags))
} else {
None
}
#[cfg(not(target_os = "macos"))]
None
}
}
impl Display for SetAttr<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SETATTR mode: {:?}, uid: {:?}, gid: {:?}, size: {:?}, atime: {:?}, \
mtime: {:?}, ctime: {:?}, file_handle: {:?}, crtime: {:?}, chgtime: {:?}, \
bkuptime: {:?}, flags: {:?}",
self.mode(),
self.uid(),
self.gid(),
self.size(),
self.atime(),
self.mtime(),
self.ctime(),
self.file_handle(),
self.crtime(),
self.chgtime(),
self.bkuptime(),
self.flags()
)
}
}
#[derive(Debug)]
pub(crate) struct ReadLink<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
}
#[derive(Debug)]
pub(crate) struct SymLink<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
target: &'a Path,
link_name: &'a Path,
}
impl<'a> SymLink<'a> {
pub(crate) fn target(&self) -> &'a Path {
self.target
}
pub(crate) fn link_name(&self) -> &'a Path {
self.link_name
}
}
#[derive(Debug)]
pub(crate) struct MkNod<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_mknod_in,
name: &'a Path,
}
impl<'a> MkNod<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name
}
pub(crate) fn mode(&self) -> u32 {
self.arg.mode
}
pub(crate) fn umask(&self) -> u32 {
self.arg.umask
}
pub(crate) fn rdev(&self) -> u32 {
self.arg.rdev
}
}
#[derive(Debug)]
pub(crate) struct MkDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_mkdir_in,
name: &'a Path,
}
impl<'a> MkDir<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name
}
pub(crate) fn mode(&self) -> u32 {
self.arg.mode
}
pub(crate) fn umask(&self) -> u32 {
self.arg.umask
}
}
#[derive(Debug)]
pub(crate) struct Unlink<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
name: &'a Path,
}
impl<'a> Unlink<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name
}
}
#[derive(Debug)]
pub(crate) struct RmDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
pub(crate) name: &'a Path,
}
impl<'a> RmDir<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name
}
}
#[derive(Debug)]
pub(crate) struct Rename<'a> {
header: &'a fuse_in_header,
arg: &'a fuse_rename_in,
name: &'a Path,
newname: &'a Path,
}
impl<'a> Rename<'a> {
pub(crate) fn src(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.header.nodeid),
name: self.name,
}
}
pub(crate) fn dest(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.arg.newdir),
name: self.newname,
}
}
}
#[derive(Debug)]
pub(crate) struct Link<'a> {
header: &'a fuse_in_header,
arg: &'a fuse_link_in,
name: &'a Path,
}
impl<'a> Link<'a> {
pub(crate) fn inode_no(&self) -> INodeNo {
INodeNo(self.arg.oldnodeid)
}
pub(crate) fn dest(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.header.nodeid),
name: self.name,
}
}
}
#[derive(Debug)]
pub(crate) struct Open<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_open_in,
}
impl Open<'_> {
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct Read<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_read_in,
}
impl Read<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> Result<u64, Errno> {
validate_off_t("fuse_read_in.arg.offset", self.arg.offset)?;
Ok(self.arg.offset)
}
pub(crate) fn size(&self) -> u32 {
self.arg.size
}
pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
if self.read_flags().contains(ReadFlags::FUSE_READ_LOCKOWNER) {
Some(LockOwner(self.arg.lock_owner))
} else {
None
}
}
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
pub(crate) fn read_flags(&self) -> ReadFlags {
ReadFlags::from_bits_retain(self.arg.read_flags)
}
}
#[derive(Debug)]
pub(crate) struct Write<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_write_in,
data: &'a [u8],
}
impl<'a> Write<'a> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> Result<u64, Errno> {
let offset = self.arg.offset as u64;
validate_off_t("fuse_write_in.arg.offset", offset)?;
Ok(offset)
}
pub(crate) fn data(&self) -> &'a [u8] {
self.data
}
pub(crate) fn write_flags(&self) -> WriteFlags {
WriteFlags::from_bits_retain(self.arg.write_flags)
}
pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
if self
.write_flags()
.contains(WriteFlags::FUSE_WRITE_LOCKOWNER)
{
Some(LockOwner(self.arg.lock_owner))
} else {
None
}
}
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct StatFs<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
}
#[derive(Debug)]
pub(crate) struct Release<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_release_in,
}
impl Release<'_> {
pub(crate) fn release_flags(&self) -> ReleaseFlags {
ReleaseFlags::from_bits_retain(self.arg.release_flags)
}
pub(crate) fn flush(&self) -> bool {
self.release_flags()
.contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
}
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
if self
.release_flags()
.contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
{
Some(LockOwner(self.arg.lock_owner))
} else {
None
}
}
}
#[derive(Debug)]
pub(crate) struct FSync<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_fsync_in,
}
impl FSync<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn fdatasync(&self) -> bool {
FsyncFlags::from_bits_retain(self.arg.fsync_flags)
.contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
}
}
#[derive(Debug)]
pub(crate) struct SetXAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_setxattr_in,
name: &'a OsStr,
value: &'a [u8],
}
impl<'a> SetXAttr<'a> {
pub(crate) fn name(&self) -> &'a OsStr {
self.name
}
pub(crate) fn value(&self) -> &'a [u8] {
self.value
}
pub(crate) fn flags(&self) -> i32 {
self.arg.flags
}
pub(crate) fn position(&self) -> u32 {
#[cfg(target_os = "macos")]
return self.arg.position;
#[cfg(not(target_os = "macos"))]
0
}
}
#[derive(Debug)]
pub(crate) struct GetXAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_getxattr_in,
name: &'a OsStr,
}
#[derive(Debug)]
pub(crate) struct GetXAttrSize();
#[derive(Debug)]
pub(crate) enum GetXAttrSizeEnum {
GetSize(GetXAttrSize),
#[allow(dead_code)]
Size(NonZeroU32),
}
impl<'a> GetXAttr<'a> {
pub(crate) fn name(&self) -> &'a OsStr {
self.name
}
pub(crate) fn size(&self) -> GetXAttrSizeEnum {
let s: Result<NonZeroU32, _> = self.arg.size.try_into();
match s {
Ok(s) => GetXAttrSizeEnum::Size(s),
Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()),
}
}
pub(crate) fn size_u32(&self) -> u32 {
self.arg.size
}
}
#[derive(Debug)]
pub(crate) struct ListXAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_getxattr_in,
}
impl ListXAttr<'_> {
pub(crate) fn size(&self) -> u32 {
self.arg.size
}
}
#[derive(Debug)]
pub(crate) struct RemoveXAttr<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
name: &'a OsStr,
}
impl<'a> RemoveXAttr<'a> {
pub(crate) fn name(&self) -> &'a OsStr {
self.name
}
}
#[derive(Debug)]
pub(crate) struct Flush<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_flush_in,
}
impl Flush<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn lock_owner(&self) -> LockOwner {
LockOwner(self.arg.lock_owner)
}
}
#[derive(Debug)]
pub(crate) struct Init<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: Box<fuse_init_in>,
}
impl Init<'_> {
pub(crate) fn capabilities(&self) -> InitFlags {
let flags = InitFlags::from_bits_retain(u64::from(self.arg.flags));
if flags.contains(InitFlags::FUSE_INIT_EXT) {
return InitFlags::from_bits_retain(
u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32),
);
}
flags
}
pub(crate) fn max_readahead(&self) -> u32 {
self.arg.max_readahead
}
pub(crate) fn version(&self) -> super::Version {
super::Version(self.arg.major, self.arg.minor)
}
pub(crate) fn reply(&self, config: &crate::KernelConfig) -> ResponseData {
let flags = config.requested | InitFlags::FUSE_INIT_EXT;
let flags = flags & self.capabilities();
let mut init = fuse_init_out {
major: FUSE_KERNEL_VERSION,
minor: FUSE_KERNEL_MINOR_VERSION,
max_readahead: config.max_readahead,
flags: flags.pair().0,
max_background: config.max_background,
congestion_threshold: config.congestion_threshold(),
max_write: config.max_write,
time_gran: config.time_gran.as_nanos() as u32,
max_pages: config.max_pages(),
unused2: 0,
flags2: flags.pair().1,
max_stack_depth: 0,
reserved: [0; 6],
};
if flags.contains(InitFlags::FUSE_PASSTHROUGH) {
init.max_stack_depth = config.max_stack_depth;
}
let init = init.as_bytes();
let init = if self.arg.minor < 5 {
&init[..FUSE_COMPAT_INIT_OUT_SIZE]
} else if self.arg.minor < 23 {
&init[..FUSE_COMPAT_22_INIT_OUT_SIZE]
} else {
init
};
ResponseData::new_data(init)
}
pub(crate) fn reply_version_only(&self) -> ResponseData {
let init = fuse_init_out {
major: FUSE_KERNEL_VERSION,
minor: FUSE_KERNEL_MINOR_VERSION,
max_readahead: 0,
flags: 0,
max_background: 0,
congestion_threshold: 0,
max_write: 0,
time_gran: 0,
max_pages: 0,
unused2: 0,
flags2: 0,
max_stack_depth: 0,
reserved: [0; 6],
};
ResponseData::new_data(init.as_bytes())
}
}
#[derive(Debug)]
pub(crate) struct OpenDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_open_in,
}
impl OpenDir<'_> {
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct ReadDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_read_in,
}
impl ReadDir<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> u64 {
self.arg.offset
}
pub(crate) fn size(&self) -> u32 {
self.arg.size
}
}
#[derive(Debug)]
pub(crate) struct ReleaseDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_release_in,
}
impl ReleaseDir<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn release_flags(&self) -> ReleaseFlags {
ReleaseFlags::from_bits_retain(self.arg.release_flags)
}
pub(crate) fn flush(&self) -> bool {
self.release_flags()
.contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
}
pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
if self
.release_flags()
.contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
{
Some(LockOwner(self.arg.lock_owner))
} else {
None
}
}
pub(crate) fn flags(&self) -> OpenFlags {
OpenFlags(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct FSyncDir<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_fsync_in,
}
impl FSyncDir<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn fdatasync(&self) -> bool {
FsyncFlags::from_bits_retain(self.arg.fsync_flags)
.contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
}
}
#[derive(Debug)]
pub(crate) struct GetLk<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_lk_in,
}
impl GetLk<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn lock(&self) -> Lock {
Lock::from_abi(&self.arg.lk)
}
pub(crate) fn lock_owner(&self) -> LockOwner {
LockOwner(self.arg.owner)
}
}
#[derive(Debug)]
pub(crate) struct SetLk<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_lk_in,
wait: bool,
}
impl SetLk<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn lock(&self) -> Lock {
Lock::from_abi(&self.arg.lk)
}
pub(crate) fn lock_owner(&self) -> LockOwner {
LockOwner(self.arg.owner)
}
pub(crate) fn sleep(&self) -> bool {
self.wait
}
}
#[derive(Debug)]
pub(crate) struct Access<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_access_in,
}
impl Access<'_> {
pub(crate) fn mask(&self) -> AccessFlags {
AccessFlags::from_bits_retain(self.arg.mask)
}
}
#[derive(Debug)]
pub(crate) struct Create<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_create_in,
name: &'a Path,
}
impl<'a> Create<'a> {
pub(crate) fn name(&self) -> &'a Path {
self.name
}
pub(crate) fn mode(&self) -> u32 {
self.arg.mode
}
pub(crate) fn flags(&self) -> i32 {
self.arg.flags
}
pub(crate) fn umask(&self) -> u32 {
self.arg.umask
}
}
#[derive(Debug)]
pub(crate) struct Interrupt<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_interrupt_in,
}
impl Interrupt<'_> {
pub(crate) fn unique(&self) -> RequestId {
RequestId(self.arg.unique)
}
}
#[derive(Debug)]
pub(crate) struct BMap<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_bmap_in,
}
impl BMap<'_> {
pub(crate) fn block_size(&self) -> u32 {
self.arg.blocksize
}
pub(crate) fn block(&self) -> u64 {
self.arg.block
}
}
#[derive(Debug)]
pub(crate) struct Destroy<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
}
#[derive(Debug)]
pub(crate) struct IoCtl<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_ioctl_in,
data: &'a [u8],
}
impl IoCtl<'_> {
pub(crate) fn in_data(&self) -> &[u8] {
&self.data[..self.arg.in_size as usize]
}
pub(crate) fn unrestricted(&self) -> bool {
self.flags().contains(IoctlFlags::FUSE_IOCTL_UNRESTRICTED)
}
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn flags(&self) -> IoctlFlags {
IoctlFlags::from_bits_retain(self.arg.flags)
}
pub(crate) fn command(&self) -> u32 {
self.arg.cmd
}
pub(crate) fn out_size(&self) -> u32 {
self.arg.out_size
}
}
#[derive(Debug)]
pub(crate) struct Poll<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_poll_in,
}
impl Poll<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn kernel_handle(&self) -> PollHandle {
PollHandle(self.arg.kh)
}
pub(crate) fn events(&self) -> PollEvents {
PollEvents::from_bits_retain(self.arg.events)
}
pub(crate) fn flags(&self) -> PollFlags {
PollFlags::from_bits_retain(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct NotifyReply<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
#[expect(dead_code)]
arg: &'a [u8],
}
#[derive(Debug)]
pub(crate) struct BatchForget<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
#[expect(dead_code)]
arg: &'a fuse_batch_forget_in,
nodes: &'a [fuse_forget_one],
}
impl<'a> BatchForget<'a> {
pub(crate) fn nodes(&self) -> &'a [fuse_forget_one] {
self.nodes
}
}
#[derive(Debug)]
pub(crate) struct FAllocate<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_fallocate_in,
}
impl FAllocate<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> Result<u64, Errno> {
validate_off_t("fuse_allocate_in.arg.offset", self.arg.offset)?;
Ok(self.arg.offset)
}
pub(crate) fn len(&self) -> Result<u64, Errno> {
validate_off_t("fuse_allocate_in.arg.length", self.arg.length)?;
Ok(self.arg.length)
}
pub(crate) fn mode(&self) -> i32 {
self.arg.mode
}
}
#[derive(Debug)]
pub(crate) struct ReadDirPlus<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_read_in,
}
impl ReadDirPlus<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> u64 {
self.arg.offset
}
pub(crate) fn size(&self) -> u32 {
self.arg.size
}
}
#[derive(Debug)]
pub(crate) struct Rename2<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_rename2_in,
name: &'a Path,
newname: &'a Path,
old_parent: INodeNo,
}
impl<'a> Rename2<'a> {
pub(crate) fn from(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: self.old_parent,
name: self.name,
}
}
pub(crate) fn to(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.arg.newdir),
name: self.newname,
}
}
pub(crate) fn flags(&self) -> crate::RenameFlags {
crate::RenameFlags::from_bits_retain(self.arg.flags)
}
}
#[derive(Debug)]
pub(crate) struct Lseek<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_lseek_in,
}
impl Lseek<'_> {
pub(crate) fn file_handle(&self) -> FileHandle {
FileHandle(self.arg.fh)
}
pub(crate) fn offset(&self) -> i64 {
self.arg.offset
}
pub(crate) fn whence(&self) -> i32 {
self.arg.whence
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CopyFileRangeFile {
pub(crate) inode: INodeNo,
pub(crate) file_handle: FileHandle,
pub(crate) offset: u64,
}
#[derive(Debug)]
pub(crate) struct CopyFileRange<'a> {
header: &'a fuse_in_header,
arg: &'a fuse_copy_file_range_in,
}
impl CopyFileRange<'_> {
pub(crate) fn src(&self) -> Result<CopyFileRangeFile, Errno> {
let offset = self.arg.off_in as u64;
validate_off_t("fuse_copy_file_range_in.arg.off_in", offset)?;
Ok(CopyFileRangeFile {
inode: INodeNo(self.header.nodeid),
file_handle: FileHandle(self.arg.fh_in),
offset,
})
}
pub(crate) fn dest(&self) -> Result<CopyFileRangeFile, Errno> {
let offset = self.arg.off_out as u64;
validate_off_t("fuse_copy_file_range_in.arg.off_out", offset)?;
Ok(CopyFileRangeFile {
inode: INodeNo(self.arg.nodeid_out),
file_handle: FileHandle(self.arg.fh_out),
offset,
})
}
pub(crate) fn len(&self) -> u64 {
self.arg.len
}
pub(crate) fn flags(&self) -> CopyFileRangeFlags {
CopyFileRangeFlags::from_bits_retain(self.arg.flags)
}
}
#[cfg(target_os = "macos")]
#[derive(Debug)]
pub(crate) struct SetVolName<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
name: &'a OsStr,
}
#[cfg(target_os = "macos")]
impl<'a> SetVolName<'a> {
pub(crate) fn name(&self) -> &'a OsStr {
self.name
}
}
#[cfg(target_os = "macos")]
#[derive(Debug)]
pub(crate) struct GetXTimes<'a> {
header: &'a fuse_in_header,
}
#[cfg(target_os = "macos")]
impl GetXTimes<'_> {
pub(crate) fn nodeid(&self) -> INodeNo {
INodeNo(self.header.nodeid)
}
}
#[cfg(target_os = "macos")]
#[derive(Debug)]
pub(crate) struct Exchange<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
arg: &'a fuse_exchange_in,
oldname: &'a Path,
newname: &'a Path,
}
#[cfg(target_os = "macos")]
impl<'a> Exchange<'a> {
pub(crate) fn from(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.arg.olddir),
name: self.oldname,
}
}
pub(crate) fn to(&self) -> FilenameInDir<'a> {
FilenameInDir::<'a> {
dir: INodeNo(self.arg.newdir),
name: self.newname,
}
}
pub(crate) fn options(&self) -> u64 {
self.arg.options
}
}
#[derive(Debug)]
pub(crate) struct CuseInit<'a> {
#[expect(dead_code)]
header: &'a fuse_in_header,
#[expect(dead_code)]
arg: &'a fuse_init_in,
}
pub(crate) fn parse<'a>(
header: &'a fuse_in_header,
opcode: &fuse_opcode,
data: &'a [u8],
) -> Option<Operation<'a>> {
let mut data = ArgumentIterator::new(data);
Some(match opcode {
fuse_opcode::FUSE_LOOKUP => Operation::Lookup(Lookup {
header,
name: data.fetch_str()?,
}),
fuse_opcode::FUSE_FORGET => Operation::Forget(Forget {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_GETATTR => Operation::GetAttr(GetAttr {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_SETATTR => Operation::SetAttr(SetAttr {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_READLINK => Operation::ReadLink(ReadLink { header }),
fuse_opcode::FUSE_SYMLINK => Operation::SymLink(SymLink {
header,
link_name: data.fetch_str()?.as_ref(),
target: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_MKNOD => Operation::MkNod(MkNod {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_MKDIR => Operation::MkDir(MkDir {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_UNLINK => Operation::Unlink(Unlink {
header,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_RMDIR => Operation::RmDir(RmDir {
header,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_RENAME => Operation::Rename(Rename {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
newname: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_LINK => Operation::Link(Link {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_OPEN => Operation::Open(Open {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_READ => Operation::Read(Read {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_WRITE => Operation::Write({
let out = Write {
header,
arg: data.fetch()?,
data: data.fetch_all(),
};
assert!(out.data().len() == out.arg.size as usize);
out
}),
fuse_opcode::FUSE_STATFS => Operation::StatFs(StatFs { header }),
fuse_opcode::FUSE_RELEASE => Operation::Release(Release {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_FSYNC => Operation::FSync(FSync {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_SETXATTR => Operation::SetXAttr({
let out = SetXAttr {
header,
arg: data.fetch()?,
name: data.fetch_str()?,
value: data.fetch_all(),
};
assert!(out.value.len() == out.arg.size as usize);
out
}),
fuse_opcode::FUSE_GETXATTR => Operation::GetXAttr(GetXAttr {
header,
arg: data.fetch()?,
name: data.fetch_str()?,
}),
fuse_opcode::FUSE_LISTXATTR => Operation::ListXAttr(ListXAttr {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_REMOVEXATTR => Operation::RemoveXAttr(RemoveXAttr {
header,
name: data.fetch_str()?,
}),
fuse_opcode::FUSE_FLUSH => Operation::Flush(Flush {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_INIT => Operation::Init(Init {
header,
arg: {
let mut arg = fuse_init_in::new_zeroed();
let data = data.fetch_all();
if data.len() < offset_of!(fuse_init_in, flags2) {
return None;
}
let prefix = cmp::min(data.len(), arg.as_bytes().len());
arg.as_mut_bytes()[..prefix].copy_from_slice(&data[..prefix]);
Box::new(arg)
},
}),
fuse_opcode::FUSE_OPENDIR => Operation::OpenDir(OpenDir {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_READDIR => Operation::ReadDir(ReadDir {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_RELEASEDIR => Operation::ReleaseDir(ReleaseDir {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_FSYNCDIR => Operation::FSyncDir(FSyncDir {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_GETLK => Operation::GetLk(GetLk {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_SETLK => Operation::SetLk(SetLk {
header,
arg: data.fetch()?,
wait: false,
}),
fuse_opcode::FUSE_SETLKW => Operation::SetLk(SetLk {
header,
arg: data.fetch()?,
wait: true,
}),
fuse_opcode::FUSE_ACCESS => Operation::Access(Access {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_CREATE => Operation::Create(Create {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
}),
fuse_opcode::FUSE_INTERRUPT => Operation::Interrupt(Interrupt {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_BMAP => Operation::BMap(BMap {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_DESTROY => Operation::Destroy(Destroy { header }),
fuse_opcode::FUSE_IOCTL => Operation::IoCtl(IoCtl {
header,
arg: data.fetch()?,
data: data.fetch_all(),
}),
fuse_opcode::FUSE_POLL => Operation::Poll(Poll {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply(NotifyReply {
header,
arg: data.fetch_all(),
}),
fuse_opcode::FUSE_BATCH_FORGET => {
let arg = data.fetch()?;
Operation::BatchForget(BatchForget {
header,
arg,
nodes: data.fetch_slice(arg.count as usize)?,
})
}
fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate(FAllocate {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus(ReadDirPlus {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_RENAME2 => Operation::Rename2(Rename2 {
header,
arg: data.fetch()?,
name: data.fetch_str()?.as_ref(),
newname: data.fetch_str()?.as_ref(),
old_parent: INodeNo(header.nodeid),
}),
fuse_opcode::FUSE_LSEEK => Operation::Lseek(Lseek {
header,
arg: data.fetch()?,
}),
fuse_opcode::FUSE_COPY_FILE_RANGE => Operation::CopyFileRange(CopyFileRange {
header,
arg: data.fetch()?,
}),
#[cfg(target_os = "macos")]
fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName(SetVolName {
header,
name: data.fetch_str()?,
}),
#[cfg(target_os = "macos")]
fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes(GetXTimes { header }),
#[cfg(target_os = "macos")]
fuse_opcode::FUSE_EXCHANGE => Operation::Exchange(Exchange {
header,
arg: data.fetch()?,
oldname: data.fetch_str()?.as_ref(),
newname: data.fetch_str()?.as_ref(),
}),
fuse_opcode::CUSE_INIT => Operation::CuseInit(CuseInit {
header,
arg: data.fetch()?,
}),
})
}
}
use op::*;
#[derive(Debug)]
#[allow(missing_docs)]
pub(crate) enum Operation<'a> {
Lookup(Lookup<'a>),
Forget(Forget<'a>),
GetAttr(GetAttr<'a>),
SetAttr(SetAttr<'a>),
ReadLink(#[expect(dead_code)] ReadLink<'a>),
SymLink(SymLink<'a>),
MkNod(MkNod<'a>),
MkDir(MkDir<'a>),
Unlink(Unlink<'a>),
RmDir(RmDir<'a>),
Rename(Rename<'a>),
Link(Link<'a>),
Open(Open<'a>),
Read(Read<'a>),
Write(Write<'a>),
StatFs(#[expect(dead_code)] StatFs<'a>),
Release(Release<'a>),
FSync(FSync<'a>),
SetXAttr(SetXAttr<'a>),
GetXAttr(GetXAttr<'a>),
ListXAttr(ListXAttr<'a>),
RemoveXAttr(RemoveXAttr<'a>),
Flush(Flush<'a>),
Init(Init<'a>),
OpenDir(OpenDir<'a>),
ReadDir(ReadDir<'a>),
ReleaseDir(ReleaseDir<'a>),
FSyncDir(FSyncDir<'a>),
GetLk(GetLk<'a>),
SetLk(SetLk<'a>),
Access(Access<'a>),
Create(Create<'a>),
Interrupt(Interrupt<'a>),
BMap(BMap<'a>),
Destroy(Destroy<'a>),
IoCtl(IoCtl<'a>),
Poll(Poll<'a>),
NotifyReply(#[expect(dead_code)] NotifyReply<'a>),
BatchForget(BatchForget<'a>),
FAllocate(FAllocate<'a>),
ReadDirPlus(ReadDirPlus<'a>),
Rename2(Rename2<'a>),
Lseek(Lseek<'a>),
CopyFileRange(CopyFileRange<'a>),
#[cfg(target_os = "macos")]
SetVolName(SetVolName<'a>),
#[cfg(target_os = "macos")]
GetXTimes(GetXTimes<'a>),
#[cfg(target_os = "macos")]
Exchange(Exchange<'a>),
#[allow(dead_code)]
CuseInit(CuseInit<'a>),
}
impl fmt::Display for Operation<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Operation::Lookup(x) => write!(f, "LOOKUP name {:?}", x.name()),
Operation::Forget(x) => write!(f, "FORGET nlookup {}", x.nlookup()),
Operation::GetAttr(_) => write!(f, "GETATTR"),
Operation::SetAttr(x) => x.fmt(f),
Operation::ReadLink(_) => write!(f, "READLINK"),
Operation::SymLink(x) => {
write!(
f,
"SYMLINK target {:?}, link_name {:?}",
x.target(),
x.link_name()
)
}
Operation::MkNod(x) => write!(
f,
"MKNOD name {:?}, mode {:#05o}, rdev {}",
x.name(),
x.mode(),
x.rdev()
),
Operation::MkDir(x) => write!(f, "MKDIR name {:?}, mode {:#05o}", x.name(), x.mode()),
Operation::Unlink(x) => write!(f, "UNLINK name {:?}", x.name()),
Operation::RmDir(x) => write!(f, "RMDIR name {:?}", x.name),
Operation::Rename(x) => write!(f, "RENAME src {:?}, dest {:?}", x.src(), x.dest()),
Operation::Link(x) => write!(f, "LINK ino {:?}, dest {:?}", x.inode_no(), x.dest()),
Operation::Open(x) => write!(f, "OPEN flags {:#x}", x.flags()),
Operation::Read(x) => write!(
f,
"READ fh {:?}, offset {:?}, size {}",
x.file_handle(),
x.offset(),
x.size()
),
Operation::Write(x) => write!(
f,
"WRITE fh {:?}, offset {:?}, size {}, write flags {:#x}",
x.file_handle(),
x.offset(),
x.data().len(),
x.write_flags()
),
Operation::StatFs(_) => write!(f, "STATFS"),
Operation::Release(x) => write!(
f,
"RELEASE fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
x.file_handle(),
x.flags(),
x.flush(),
x.lock_owner()
),
Operation::FSync(x) => write!(
f,
"FSYNC fh {:?}, fsync fdatasync {}",
x.file_handle(),
x.fdatasync()
),
Operation::SetXAttr(x) => write!(
f,
"SETXATTR name {:?}, size {}, flags {:#x}",
x.name(),
x.value().len(),
x.flags()
),
Operation::GetXAttr(x) => {
write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size())
}
Operation::ListXAttr(x) => write!(f, "LISTXATTR size {}", x.size()),
Operation::RemoveXAttr(x) => write!(f, "REMOVEXATTR name {:?}", x.name()),
Operation::Flush(x) => write!(
f,
"FLUSH fh {:?}, lock owner {:?}",
x.file_handle(),
x.lock_owner()
),
Operation::Init(x) => write!(
f,
"INIT kernel ABI {}, capabilities {:#x}, max readahead {}",
x.version(),
x.capabilities(),
x.max_readahead()
),
Operation::OpenDir(x) => write!(f, "OPENDIR flags {:#x}", x.flags()),
Operation::ReadDir(x) => write!(
f,
"READDIR fh {:?}, offset {}, size {}",
x.file_handle(),
x.offset(),
x.size()
),
Operation::ReleaseDir(x) => write!(
f,
"RELEASEDIR fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
x.file_handle(),
x.flags(),
x.flush(),
x.lock_owner()
),
Operation::FSyncDir(x) => write!(
f,
"FSYNCDIR fh {:?}, fsync fdatasync: {}",
x.file_handle(),
x.fdatasync()
),
Operation::GetLk(x) => write!(
f,
"GETLK fh {:?}, lock owner {:?}",
x.file_handle(),
x.lock_owner()
),
Operation::SetLk(x) => write!(
f,
"{} fh {:?}, lock owner {:?}",
if x.sleep() { "SETLKW" } else { "SETLK" },
x.file_handle(),
x.lock_owner()
),
Operation::Access(x) => write!(f, "ACCESS mask {:#05o}", x.mask()),
Operation::Create(x) => write!(
f,
"CREATE name {:?}, mode {:#05o}, flags {:#x}",
x.name(),
x.mode(),
x.flags()
),
Operation::Interrupt(x) => write!(f, "INTERRUPT unique {:?}", x.unique()),
Operation::BMap(x) => write!(f, "BMAP blocksize {}, ids {}", x.block_size(), x.block()),
Operation::Destroy(_) => write!(f, "DESTROY"),
Operation::IoCtl(x) => write!(
f,
"IOCTL fh {:?}, cmd {}, data size {}, flags {:#x}",
x.file_handle(),
x.command(),
x.in_data().len(),
x.flags()
),
Operation::Poll(x) => write!(f, "POLL fh {:?}", x.file_handle()),
Operation::NotifyReply(_) => write!(f, "NOTIFYREPLY"),
Operation::BatchForget(x) => write!(f, "BATCHFORGET nodes {:?}", x.nodes()),
Operation::FAllocate(_) => write!(f, "FALLOCATE"),
Operation::ReadDirPlus(x) => write!(
f,
"READDIRPLUS fh {:?}, offset {}, size {}",
x.file_handle(),
x.offset(),
x.size()
),
Operation::Rename2(x) => write!(f, "RENAME2 from {:?}, to {:?}", x.from(), x.to()),
Operation::Lseek(x) => write!(
f,
"LSEEK fh {:?}, offset {}, whence {}",
x.file_handle(),
x.offset(),
x.whence()
),
Operation::CopyFileRange(x) => write!(
f,
"COPY_FILE_RANGE src {:?}, dest {:?}, len {}",
x.src(),
x.dest(),
x.len()
),
#[cfg(target_os = "macos")]
Operation::SetVolName(x) => write!(f, "SETVOLNAME name {:?}", x.name()),
#[cfg(target_os = "macos")]
Operation::GetXTimes(_) => write!(f, "GETXTIMES"),
#[cfg(target_os = "macos")]
Operation::Exchange(x) => write!(
f,
"EXCHANGE from {:?}, to {:?}, options {:#x}",
x.from(),
x.to(),
x.options()
),
Operation::CuseInit(_) => write!(f, "CUSE_INIT"),
}
}
}
#[derive(Debug)]
pub(crate) struct AnyRequest<'a> {
header: &'a fuse_in_header,
data: &'a [u8],
}
impl<'a> AnyRequest<'a> {
pub(crate) fn unique(&self) -> RequestId {
RequestId(self.header.unique)
}
pub(crate) fn nodeid(&self) -> INodeNo {
INodeNo(self.header.nodeid)
}
pub(crate) fn uid(&self) -> Uid {
Uid::from_raw(self.header.uid)
}
#[cfg_attr(not(test), expect(dead_code))]
fn gid(&self) -> Gid {
Gid::from_raw(self.header.gid)
}
#[cfg_attr(not(test), expect(dead_code))]
fn pid(&self) -> Pid {
Pid::from_raw(self.header.pid as i32)
}
pub(crate) fn operation(&self) -> Result<Operation<'a>, RequestError> {
let opcode = fuse_opcode::try_from(self.header.opcode).map_err(
|e: num_enum::TryFromPrimitiveError<_>| RequestError::UnknownOperation(e.number),
)?;
op::parse(self.header, &opcode, self.data).ok_or(RequestError::InsufficientData)
}
pub(crate) fn header(&self) -> &fuse_in_header {
self.header
}
}
impl fmt::Display for AnyRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Ok(op) = self.operation() {
write!(
f,
"FUSE({:3}) ino {:#018x} {}",
self.header.unique, self.header.nodeid, op
)
} else {
write!(
f,
"FUSE({:3}) ino {:#018x}",
self.header.unique, self.header.nodeid
)
}
}
}
impl<'a> TryFrom<&'a [u8]> for AnyRequest<'a> {
type Error = RequestError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
let data_len = data.len();
let mut arg_iter = ArgumentIterator::new(data);
let header: &fuse_in_header = arg_iter
.fetch()
.ok_or_else(|| RequestError::ShortReadHeader(arg_iter.len()))?;
if data_len < header.len as usize {
return Err(RequestError::ShortRead(data_len, header.len as usize));
}
Ok(Self {
header,
data: &data[size_of::<fuse_in_header>()..header.len as usize],
})
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use crate::ll::request::*;
use crate::ll::test::AlignedData;
#[cfg(target_endian = "big")]
const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
#[cfg(target_endian = "little")]
const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
#[cfg(target_endian = "big")]
const MKNOD_REQUEST: AlignedData<[u8; 56]> = [
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, ];
#[cfg(target_endian = "little")]
const MKNOD_REQUEST: AlignedData<[u8; 64]> = AlignedData([
0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, ]);
#[test]
fn short_read_header() {
match AnyRequest::try_from(&INIT_REQUEST[..20]) {
Err(RequestError::ShortReadHeader(20)) => (),
_ => panic!("Unexpected request parsing result"),
}
}
#[test]
fn short_read() {
match AnyRequest::try_from(&INIT_REQUEST[..48]) {
Err(RequestError::ShortRead(48, 104)) => (),
_ => panic!("Unexpected request parsing result"),
}
}
#[test]
fn init() {
let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap();
assert_eq!(req.header.len, 104);
assert_eq!(req.header.opcode, 26);
assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
match req.operation().unwrap() {
Operation::Init(x) => {
assert_eq!(x.version(), Version(7, 8));
assert_eq!(x.max_readahead(), 4096);
}
_ => panic!("Unexpected request operation"),
}
}
#[test]
fn mknod() {
let req = AnyRequest::try_from(&MKNOD_REQUEST[..]).unwrap();
assert_eq!(req.header.len, 64);
assert_eq!(req.header.opcode, 8);
assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
match req.operation().unwrap() {
Operation::MkNod(x) => {
assert_eq!(x.mode(), 0o644);
assert_eq!(x.umask(), 0o755);
assert_eq!(x.name(), OsStr::new("foo.txt"));
}
_ => panic!("Unexpected request operation"),
}
}
}