#![allow(
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::ptr_as_ptr,
clippy::borrow_as_ptr,
// Test helpers serialize FUSE structs to byte slices using the same
// `&x as *const T as *const u8` and `.add(N) as *const T` patterns as
// the production code. The suppression is intentional for binary compat.
clippy::ref_as_ptr,
clippy::unnecessary_cast,
clippy::significant_drop_tightening,
clippy::needless_pass_by_ref_mut
)]
mod dax;
mod directories;
mod file_ops;
mod lookup_attrs;
mod mapping_xattr;
mod nodes;
#[cfg(test)]
mod tests;
mod types;
use crate::error::{FsError, Result};
use crate::fuse::{
FATTR_ATIME, FATTR_GID, FATTR_MODE, FATTR_MTIME, FATTR_SIZE, FATTR_UID, FUSE_NO_FH,
FuseAccessIn, FuseAttr, FuseAttrOut, FuseCreateIn, FuseDirent, FuseEntryOut, FuseFallocateIn,
FuseFlushIn, FuseForgetIn, FuseFsyncIn, FuseGetxattrIn, FuseGetxattrOut, FuseInHeader,
FuseLinkIn, FuseLseekIn, FuseLseekOut, FuseMkdirIn, FuseMknodIn, FuseOpcode, FuseOpenIn,
FuseOpenOut, FuseOutHeader, FuseReadIn, FuseReleaseIn, FuseRenameIn, FuseSetattrIn,
FuseSetxattrIn, FuseStatfsOut, FuseWriteIn, FuseWriteOut,
};
use crate::passthrough::PassthroughFs;
use std::ffi::OsStr;
use std::mem::size_of;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::sync::Arc;
pub use dax::DaxFsExt;
pub use types::{DispatcherConfig, RequestContext, ResponseBuilder};
pub struct FuseDispatcher {
fs: Arc<PassthroughFs>,
config: DispatcherConfig,
dax_mapper: Option<Arc<dyn crate::DaxMapper>>,
}
impl FuseDispatcher {
#[must_use]
pub fn new(fs: Arc<PassthroughFs>, config: DispatcherConfig) -> Self {
Self {
fs,
config,
dax_mapper: None,
}
}
pub fn set_dax_mapper(&mut self, mapper: Arc<dyn crate::DaxMapper>) {
self.dax_mapper = Some(mapper);
}
pub fn dispatch(&self, request: &[u8]) -> Result<Vec<u8>> {
if request.len() < FuseInHeader::SIZE {
return Err(FsError::Fuse("request too small".to_string()));
}
let header = unsafe { std::ptr::read_unaligned(request.as_ptr() as *const FuseInHeader) };
let body = &request[FuseInHeader::SIZE..];
let opcode = FuseOpcode::from_u32(header.opcode)
.ok_or_else(|| FsError::Fuse(format!("unknown opcode: {}", header.opcode)))?;
let ctx = RequestContext::from(&header);
let mut response = ResponseBuilder::new();
tracing::debug!(
"FUSE: {:?} nodeid={} unique={}",
opcode,
ctx.nodeid,
ctx.unique
);
match opcode {
FuseOpcode::Init => response.write_error(ctx.unique, libc::ENOSYS),
FuseOpcode::Destroy => self.handle_destroy(&ctx, &mut response),
FuseOpcode::Lookup => self.handle_lookup(&ctx, body, &mut response),
FuseOpcode::Forget => self.handle_forget(&ctx, body, &mut response),
FuseOpcode::Getattr => self.handle_getattr(&ctx, body, &mut response),
FuseOpcode::Setattr => self.handle_setattr(&ctx, body, &mut response),
FuseOpcode::Readlink => self.handle_readlink(&ctx, &mut response),
FuseOpcode::Mknod => self.handle_mknod(&ctx, body, &mut response),
FuseOpcode::Mkdir => self.handle_mkdir(&ctx, body, &mut response),
FuseOpcode::Unlink => self.handle_unlink(&ctx, body, &mut response),
FuseOpcode::Rmdir => self.handle_rmdir(&ctx, body, &mut response),
FuseOpcode::Symlink => self.handle_symlink(&ctx, body, &mut response),
FuseOpcode::Rename => self.handle_rename(&ctx, body, &mut response),
FuseOpcode::Link => self.handle_link(&ctx, body, &mut response),
FuseOpcode::Open => self.handle_open(&ctx, body, &mut response),
FuseOpcode::Read => self.handle_read(&ctx, body, &mut response),
FuseOpcode::Write => self.handle_write(&ctx, body, &mut response),
FuseOpcode::Statfs => self.handle_statfs(&ctx, &mut response),
FuseOpcode::Release => self.handle_release(&ctx, body, &mut response),
FuseOpcode::Fsync => self.handle_fsync(&ctx, body, &mut response),
FuseOpcode::Flush => self.handle_flush(&ctx, body, &mut response),
FuseOpcode::Opendir => self.handle_opendir(&ctx, body, &mut response),
FuseOpcode::Readdir => self.handle_readdir(&ctx, body, &mut response),
FuseOpcode::Readdirplus => self.handle_readdirplus(&ctx, body, &mut response),
FuseOpcode::Releasedir => self.handle_releasedir(&ctx, body, &mut response),
FuseOpcode::Fsyncdir => self.handle_fsyncdir(&ctx, body, &mut response),
FuseOpcode::Access => self.handle_access(&ctx, body, &mut response),
FuseOpcode::Create => self.handle_create(&ctx, body, &mut response),
FuseOpcode::Getxattr => self.handle_getxattr(&ctx, body, &mut response),
FuseOpcode::Setxattr => self.handle_setxattr(&ctx, body, &mut response),
FuseOpcode::Removexattr => self.handle_removexattr(&ctx, body, &mut response),
FuseOpcode::Lseek => self.handle_lseek(&ctx, body, &mut response),
FuseOpcode::Fallocate => self.handle_fallocate(&ctx, body, &mut response),
FuseOpcode::SetupMapping => self.handle_setup_mapping(&ctx, body, &mut response),
FuseOpcode::RemoveMapping => self.handle_remove_mapping(&ctx, body, &mut response),
_ => {
tracing::warn!(
"FUSE: unsupported opcode {:?} ({}), returning ENOSYS",
opcode,
header.opcode
);
response.write_error(ctx.unique, libc::ENOSYS);
}
}
Ok(response.finish())
}
fn parse_name<'a>(&self, body: &'a [u8]) -> &'a OsStr {
let name_end = body.iter().position(|&b| b == 0).unwrap_or(body.len());
OsStr::from_bytes(&body[..name_end])
}
fn make_entry_out(&self, inode: u64, attr: &FuseAttr) -> FuseEntryOut {
FuseEntryOut {
nodeid: inode,
generation: 0,
entry_valid: self.config.entry_timeout,
attr_valid: self.config.attr_timeout,
entry_valid_nsec: 0,
attr_valid_nsec: 0,
attr: *attr,
}
}
}
#[allow(clippy::missing_fields_in_debug)]
impl std::fmt::Debug for FuseDispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FuseDispatcher")
.field("config", &self.config)
.finish()
}
}