use std::collections::HashMap;
use std::ffi::{CStr, OsStr};
use std::fmt::Debug;
use std::fs::{
File, FileType, Metadata, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
};
use std::io::{IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::iter::{Enumerate, Peekable};
use std::marker::PhantomData;
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{DirEntryExt, FileTypeExt, MetadataExt, OpenOptionsExt};
use std::path::Path;
use zerocopy::{FromBytes, IntoBytes};
use crate::align_up_ty;
use crate::fuse::bindings::{
FUSE_KERNEL_MINOR_VERSION, FUSE_KERNEL_VERSION, FUSE_ROOT_ID, FuseAttr, FuseAttrOut,
FuseCreateIn, FuseCreateOut, FuseDirent, FuseDirentType, FuseEntryOut, FuseFlushIn,
FuseForgetIn, FuseGetattrFlag, FuseGetattrIn, FuseInHeader, FuseInitIn, FuseInitOut,
FuseOpcode, FuseOpenIn, FuseOpenOut, FuseReadIn, FuseReleaseIn, FuseRemovemappingIn,
FuseRemovemappingOne, FuseRename2In, FuseRenameIn, FuseSetupmappingFlag, FuseSetupmappingIn,
FuseSyncfsIn, FuseWriteIn, FuseWriteOut, RenameFlag,
};
use crate::fuse::{DaxRegion, Fuse, Result, error};
const MAX_BUFFER_SIZE: u32 = 1 << 20;
fn fuse_dir_type(e: FileType) -> FuseDirentType {
if e.is_dir() {
FuseDirentType::DIR
} else if e.is_file() {
FuseDirentType::REG
} else if e.is_symlink() {
FuseDirentType::LNK
} else if e.is_socket() {
FuseDirentType::SOCK
} else if e.is_file() {
FuseDirentType::FIFO
} else if e.is_char_device() {
FuseDirentType::CHR
} else if e.is_block_device() {
FuseDirentType::BLK
} else {
FuseDirentType::UNKNOWN
}
}
fn convert_o_flags(flags: i32) -> Result<OpenOptions> {
let mut opts = OpenOptions::new();
match flags & libc::O_ACCMODE {
libc::O_RDONLY => opts.read(true),
libc::O_WRONLY => opts.write(true),
libc::O_RDWR => opts.read(true).write(true),
mode => return error::InvalidAccMode { mode }.fail(),
};
opts.append(flags & libc::O_APPEND == libc::O_APPEND);
opts.truncate(flags & libc::O_TRUNC == libc::O_TRUNC);
opts.create(flags & libc::O_CREAT == libc::O_CREAT);
opts.create_new(flags & (libc::O_CREAT | libc::O_EXCL) == libc::O_CREAT | libc::O_EXCL);
let all = libc::O_ACCMODE | libc::O_APPEND | libc::O_TRUNC | libc::O_CREAT | libc::O_EXCL;
opts.custom_flags(flags & !all);
Ok(opts)
}
#[derive(Debug)]
enum Handle {
ReadDir(Box<Peekable<Enumerate<ReadDir>>>),
File(File),
}
impl Handle {
fn fh(&self) -> u64 {
match self {
Handle::File(f) => f.as_raw_fd() as u64,
Handle::ReadDir(rd) => rd.as_ref() as *const Peekable<_> as u64,
}
}
}
#[derive(Debug)]
struct Node {
lookup_count: u64,
path: Box<Path>,
handle: Option<Handle>,
}
#[derive(Debug)]
pub struct Passthrough {
nodes: HashMap<u64, Node>,
dax_region: Option<Box<dyn DaxRegion>>,
}
impl Passthrough {
pub fn new(path: Box<Path>) -> Result<Self> {
let node = Node {
lookup_count: 1,
path,
handle: None,
};
let nodes = HashMap::from([(FUSE_ROOT_ID, node)]);
Ok(Passthrough {
nodes,
dax_region: None,
})
}
fn get_node(&self, id: u64) -> Result<&Node> {
match self.nodes.get(&id) {
Some(node) => Ok(node),
None => error::NodeId { id }.fail(),
}
}
fn get_node_mut(&mut self, id: u64) -> Result<&mut Node> {
match self.nodes.get_mut(&id) {
Some(node) => Ok(node),
None => error::NodeId { id }.fail(),
}
}
fn join_path(&self, parent: u64, name: &[u8]) -> Result<Box<Path>> {
let parent = self.get_node(parent)?;
let p = OsStr::from_bytes(CStr::from_bytes_until_nul(name)?.to_bytes());
Ok(parent.path.join(p).into_boxed_path())
}
fn convert_meta(&self, meta: &Metadata) -> FuseAttr {
FuseAttr {
ino: meta.ino(),
size: meta.size(),
blocks: meta.blocks(),
atime: meta.atime() as _,
mtime: meta.mtime() as _,
ctime: meta.ctime() as _,
atimensec: meta.atime_nsec() as _,
mtimensec: meta.mtime_nsec() as _,
ctimensec: meta.ctime_nsec() as _,
mode: meta.mode(),
nlink: meta.nlink() as _,
uid: meta.uid(),
gid: meta.gid(),
rdev: meta.rdev() as _,
blksize: meta.blksize() as _,
flags: 0,
}
}
}
impl Fuse for Passthrough {
fn init(&mut self, _hdr: &FuseInHeader, in_: &FuseInitIn) -> Result<FuseInitOut> {
Ok(FuseInitOut {
major: FUSE_KERNEL_VERSION,
minor: FUSE_KERNEL_MINOR_VERSION,
max_readahead: in_.max_readahead,
flags: 0,
max_background: u16::MAX,
congestion_threshold: (u16::MAX / 4) * 3,
max_write: MAX_BUFFER_SIZE,
time_gran: 1,
max_pages: 256,
map_alignment: 0,
flags2: 0,
..Default::default()
})
}
fn set_dax_region(&mut self, dax_window: Box<dyn DaxRegion>) {
self.dax_region = Some(dax_window)
}
fn get_attr(&mut self, hdr: &FuseInHeader, in_: &FuseGetattrIn) -> Result<FuseAttrOut> {
let node = self.get_node(hdr.nodeid)?;
log::trace!("get_attr: {in_:?} {:?}", node.path);
let flag = FuseGetattrFlag::from_bits_retain(in_.getattr_flags);
if flag.contains(FuseGetattrFlag::FH) {
let Some(handle) = &node.handle else {
return error::InvalidFileHandle.fail();
};
let fh = handle.fh();
if in_.fh != fh {
return error::InvalidFileHandle.fail();
}
}
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(&node.path)?;
let meta = file.metadata()?;
Ok(FuseAttrOut {
attr_valid: 1,
attr_valid_nsec: 0,
attr: self.convert_meta(&meta),
dummy: 0,
})
}
fn open_dir(&mut self, hdr: &FuseInHeader, in_: &FuseOpenIn) -> Result<FuseOpenOut> {
let node = self.get_node_mut(hdr.nodeid)?;
log::trace!("open_dir: {in_:?} {:?}", node.path);
let handle = Handle::ReadDir(Box::new(read_dir(&node.path)?.enumerate().peekable()));
let fh = handle.fh();
node.handle = Some(handle);
Ok(FuseOpenOut {
fh,
open_flags: 0,
backing_id: 0,
})
}
fn read_dir(
&mut self,
hdr: &FuseInHeader,
in_: &FuseReadIn,
mut buf: &mut [u8],
) -> Result<usize> {
let node = self.get_node_mut(hdr.nodeid)?;
log::trace!("read_dir: {:?}", node.path);
let Some(Handle::ReadDir(read_dir)) = &mut node.handle else {
return error::DirNotOpened.fail();
};
let Some((index, _)) = read_dir.peek() else {
return Ok(0);
};
if *index as u64 != in_.offset {
todo!("in_offset = {}, != {}", in_.offset, *index);
}
let mut total_len = 0;
while let Some((index, entry)) = read_dir.peek() {
let e = entry.as_ref()?;
let name = e.file_name();
let namelen = name.len();
let dir_entry = FuseDirent {
ino: e.ino(),
off: *index as u64 + 1,
namelen: namelen as _,
type_: fuse_dir_type(e.file_type()?),
name: PhantomData,
};
let aligned_namelen = align_up_ty!(namelen, FuseDirent);
let len = size_of_val(&dir_entry) + aligned_namelen;
let Some((p1, p2)) = buf.split_at_mut_checked(len) else {
break;
};
let (b_entry, b_name) = p1.split_at_mut(size_of_val(&dir_entry));
log::trace!("read_dir: {dir_entry:?} {name:?}");
b_entry.copy_from_slice(dir_entry.as_bytes());
b_name[..namelen].copy_from_slice(name.as_encoded_bytes());
buf = p2;
total_len += len;
read_dir.next();
}
Ok(total_len)
}
fn release_dir(&mut self, hdr: &FuseInHeader, in_: &FuseReleaseIn) -> Result<()> {
let node = self.get_node_mut(hdr.nodeid)?;
node.handle = None;
log::trace!("release_dir: {in_:?} {:?}", node.path);
Ok(())
}
fn release(&mut self, hdr: &FuseInHeader, in_: &FuseReleaseIn) -> Result<()> {
let node = self.get_node_mut(hdr.nodeid)?;
node.handle = None;
log::trace!("release: {in_:?} {:?}", node.path);
Ok(())
}
fn lookup(&mut self, hdr: &FuseInHeader, in_: &[u8]) -> Result<FuseEntryOut> {
let path = self.join_path(hdr.nodeid, in_)?;
log::trace!("lookup: {path:?}");
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(&path)?;
let meta = file.metadata()?;
let nodeid =
if let Some((nodeid, node)) = self.nodes.iter_mut().find(|(_, n)| n.path == path) {
node.lookup_count += 1;
*nodeid
} else {
let nodeid = path.as_os_str().as_bytes().as_ptr() as u64;
let node = Node {
lookup_count: 1,
path,
handle: None,
};
self.nodes.insert(nodeid, node);
nodeid
};
Ok(FuseEntryOut {
nodeid,
generation: 0,
entry_valid: 0,
attr_valid: 0,
entry_valid_nsec: 0,
attr_valid_nsec: 0,
attr: self.convert_meta(&meta),
})
}
fn forget(&mut self, hdr: &FuseInHeader, in_: &FuseForgetIn) -> Result<()> {
let node = self.get_node_mut(hdr.nodeid)?;
log::trace!(
"forget: {:?}, ref_count {}, remove {}",
node.path,
node.lookup_count,
in_.nlookup
);
node.lookup_count -= in_.nlookup;
if node.lookup_count == 0 {
self.nodes.remove(&hdr.nodeid);
}
Ok(())
}
fn open(&mut self, hdr: &FuseInHeader, in_: &FuseOpenIn) -> Result<FuseOpenOut> {
let node = self.get_node_mut(hdr.nodeid)?;
log::trace!("open: {:?} {in_:?}", node.path);
let opts = convert_o_flags(in_.flags as i32)?;
let handle = Handle::File(opts.open(&node.path)?);
let fh = handle.fh();
node.handle = Some(handle);
Ok(FuseOpenOut {
fh,
open_flags: 0,
backing_id: 0,
})
}
fn read(
&mut self,
hdr: &FuseInHeader,
in_: &FuseReadIn,
iov: &mut [IoSliceMut],
) -> Result<usize> {
let node = self.get_node(hdr.nodeid)?;
log::trace!("read: {hdr:?} {in_:?} {:?}", node.path);
let Some(Handle::File(f)) = &node.handle else {
return error::FileNotOpened.fail();
};
let mut file = f;
file.seek(SeekFrom::Start(in_.offset))?;
let size = file.read_vectored(iov)?;
Ok(size)
}
fn flush(&mut self, hdr: &FuseInHeader, in_: &FuseFlushIn) -> Result<()> {
log::error!("flush: {hdr:?} {in_:?}");
Ok(())
}
fn syncfs(&mut self, hdr: &FuseInHeader, in_: &FuseSyncfsIn) -> Result<()> {
log::error!("syncfs: {hdr:?} {in_:?}");
Ok(())
}
fn create(
&mut self,
hdr: &FuseInHeader,
in_: &FuseCreateIn,
buf: &[u8],
) -> Result<FuseCreateOut> {
let opts = convert_o_flags(in_.flags as i32)?;
let path = self.join_path(hdr.nodeid, buf)?;
let nodeid = path.as_os_str().as_bytes().as_ptr() as u64;
let f = opts.open(&path)?;
let meta = f.metadata()?;
let handle = Handle::File(f);
let fh = handle.fh();
let node = Node {
lookup_count: 1,
path,
handle: Some(handle),
};
self.nodes.insert(nodeid, node);
Ok(FuseCreateOut {
entry: FuseEntryOut {
nodeid,
generation: 0,
entry_valid: 0,
attr_valid: 0,
entry_valid_nsec: 0,
attr_valid_nsec: 0,
attr: self.convert_meta(&meta),
},
open: FuseOpenOut {
fh,
open_flags: 0,
backing_id: 0,
},
})
}
fn write(
&mut self,
hdr: &FuseInHeader,
in_: &FuseWriteIn,
buf: &[IoSlice],
) -> Result<FuseWriteOut> {
let node = self.get_node(hdr.nodeid)?;
let Some(Handle::File(f)) = &node.handle else {
return error::FileNotOpened.fail();
};
let mut file = f;
file.seek(SeekFrom::Start(in_.offset))?;
let size = file.write_vectored(buf)? as u32;
Ok(FuseWriteOut { size, padding: 0 })
}
fn unlink(&mut self, hdr: &FuseInHeader, in_: &[u8]) -> Result<()> {
let path = self.join_path(hdr.nodeid, in_)?;
remove_file(&path)?;
log::trace!("unlink: {path:?}");
Ok(())
}
fn rmdir(&mut self, hdr: &FuseInHeader, in_: &[u8]) -> Result<()> {
let path = self.join_path(hdr.nodeid, in_)?;
remove_dir(path)?;
Ok(())
}
fn rename(&mut self, hdr: &FuseInHeader, in_: &FuseRenameIn, buf: &[u8]) -> Result<()> {
let in2 = FuseRename2In {
newdir: in_.newdir,
flags: 0,
padding: 0,
};
self.rename2(hdr, &in2, buf)
}
fn rename2(&mut self, hdr: &FuseInHeader, in_: &FuseRename2In, buf: &[u8]) -> Result<()> {
let mut paths = buf.split_inclusive(|b| *b == b'\0');
let Some(p1) = paths.next() else {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL))?;
};
let Some(p2) = paths.next() else {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL))?;
};
let flag = RenameFlag::from_bits_retain(in_.flags);
if !flag.is_empty() {
return error::Unsupported {
op: FuseOpcode::RENAME2,
flag: flag.bits(),
}
.fail();
}
let src = self.join_path(hdr.nodeid, p1)?;
let dst = self.join_path(in_.newdir, p2)?;
rename(src, dst)?;
Ok(())
}
fn setup_mapping(&mut self, hdr: &FuseInHeader, in_: &FuseSetupmappingIn) -> Result<()> {
let Some(dax_region) = &self.dax_region else {
return Err(std::io::Error::from_raw_os_error(libc::ENOSYS))?;
};
let node = self.get_node(hdr.nodeid)?;
let Some(Handle::File(fd)) = &node.handle else {
return error::FileNotOpened.fail();
};
let flag = FuseSetupmappingFlag::from_bits_retain(in_.flags);
dax_region.map(in_.moffset, fd, in_.foffset, in_.len, flag)?;
log::trace!(
"setup_mapping: offset = {:#x}, file = {:?}",
in_.moffset,
node.path
);
Ok(())
}
fn remove_mapping(&mut self, _hdr: &FuseInHeader, in_: &[u8]) -> Result<()> {
let Some(dax_region) = &self.dax_region else {
return Err(std::io::Error::from_raw_os_error(libc::ENOSYS))?;
};
let Ok((h, mut remain)) = FuseRemovemappingIn::ref_from_prefix(in_) else {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL))?;
};
for _ in 0..h.count {
let Ok((one, r)) = FuseRemovemappingOne::read_from_prefix(remain) else {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL))?;
};
dax_region.unmap(one.moffset, one.len)?;
log::trace!(
"remove_mapping: offset = {:#x}, size = {:#x}",
one.moffset,
one.len
);
remain = r;
}
Ok(())
}
}