use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::sync::Mutex;
use super::protocol::{Attr, S_IFDIR, S_IFREG};
pub type Errno = i32;
pub const ENOENT: Errno = -2;
pub const EIO: Errno = -5;
pub const EBADF: Errno = -9;
pub const EACCES: Errno = -13;
pub const EEXIST: Errno = -17;
pub const ENOTDIR: Errno = -20;
pub const EISDIR: Errno = -21;
pub const EINVAL: Errno = -22;
pub const ENOSPC: Errno = -28;
pub const EROFS: Errno = -30;
pub const ENAMETOOLONG: Errno = -36;
pub const ENOSYS: Errno = -38;
pub const ENOTSUP: Errno = -45;
#[derive(Clone, Debug)]
pub struct Entry {
pub nodeid: u64,
pub generation: u64,
pub attr: Attr,
pub entry_valid: u64,
pub attr_valid: u64,
}
#[derive(Clone, Debug)]
pub struct DirEntry {
pub ino: u64,
pub name: Vec<u8>,
pub typ: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct StatFs {
pub blocks: u64,
pub bfree: u64,
pub bavail: u64,
pub files: u64,
pub ffree: u64,
pub bsize: u32,
pub namelen: u32,
pub frsize: u32,
}
pub trait FsBackend: Send + Sync {
fn lookup(&self, parent: u64, name: &OsStr) -> Result<Entry, Errno>;
fn forget(&self, nodeid: u64, nlookup: u64);
fn getattr(&self, nodeid: u64, fh: Option<u64>) -> Result<Attr, Errno>;
fn open(&self, nodeid: u64, flags: u32) -> Result<u64, Errno>;
fn read(&self, nodeid: u64, fh: u64, offset: u64, size: u32) -> Result<Vec<u8>, Errno>;
fn write(
&self,
_nodeid: u64,
_fh: u64,
_offset: u64,
_data: &[u8],
) -> Result<u32, Errno> {
Err(EROFS)
}
fn fsync(&self, _nodeid: u64, _fh: u64, _datasync: bool) -> Result<(), Errno> {
Ok(())
}
fn release(&self, nodeid: u64, fh: u64) -> Result<(), Errno>;
fn opendir(&self, nodeid: u64, flags: u32) -> Result<u64, Errno>;
fn readdir(
&self,
nodeid: u64,
fh: u64,
offset: u64,
size: u32,
) -> Result<Vec<DirEntry>, Errno>;
fn releasedir(&self, nodeid: u64, fh: u64) -> Result<(), Errno>;
fn statfs(&self, nodeid: u64) -> Result<StatFs, Errno>;
fn create(
&self,
_parent: u64,
_name: &OsStr,
_mode: u32,
_flags: u32,
) -> Result<(Entry, u64), Errno> {
Err(EROFS)
}
fn mkdir(
&self,
_parent: u64,
_name: &OsStr,
_mode: u32,
) -> Result<Entry, Errno> {
Err(EROFS)
}
fn unlink(&self, _parent: u64, _name: &OsStr) -> Result<(), Errno> {
Err(EROFS)
}
fn rmdir(&self, _parent: u64, _name: &OsStr) -> Result<(), Errno> {
Err(EROFS)
}
fn dax_map(
&self,
_nodeid: u64,
_fh: u64,
_foffset: u64,
_len: u64,
_prot: u32,
) -> Result<*mut u8, Errno> {
Err(ENOTSUP)
}
fn dax_unmap(&self, _nodeid: u64, _host_va: *mut u8, _len: u64) -> Result<(), Errno> {
Err(ENOTSUP)
}
}
#[derive(Clone, Debug)]
enum Node {
Dir(Vec<(Vec<u8>, u64)>),
File(Vec<u8>),
}
struct State {
inodes: std::collections::BTreeMap<u64, (Node, Attr, u64)>,
handles: std::collections::BTreeMap<u64, (u64, bool)>,
next_nodeid: u64,
next_fh: u64,
}
pub struct MemoryFs {
st: Mutex<State>,
}
impl Default for MemoryFs {
fn default() -> Self {
Self::new()
}
}
impl MemoryFs {
pub fn new() -> Self {
let mut inodes = std::collections::BTreeMap::new();
let mut root_attr = Attr::default();
root_attr.ino = super::protocol::FUSE_ROOT_ID;
root_attr.mode = S_IFDIR | 0o755;
root_attr.nlink = 2;
root_attr.size = 0;
root_attr.blksize = 4096;
inodes.insert(
super::protocol::FUSE_ROOT_ID,
(Node::Dir(Vec::new()), root_attr, 0),
);
Self {
st: Mutex::new(State {
inodes,
handles: std::collections::BTreeMap::new(),
next_nodeid: super::protocol::FUSE_ROOT_ID + 1,
next_fh: 1,
}),
}
}
pub fn add_file(&self, parent: u64, name: &[u8], contents: Vec<u8>) -> u64 {
let mut st = self.st.lock().unwrap();
let nodeid = st.next_nodeid;
st.next_nodeid += 1;
let mut attr = Attr::default();
attr.ino = nodeid;
attr.mode = S_IFREG | 0o644;
attr.nlink = 1;
attr.size = contents.len() as u64;
attr.blocks = contents.len() as u64 / 512 + 1;
attr.blksize = 4096;
st.inodes.insert(nodeid, (Node::File(contents), attr, 0));
if let Some((Node::Dir(entries), _, _)) = st.inodes.get_mut(&parent) {
entries.push((name.to_vec(), nodeid));
} else {
panic!("parent {parent} is not a directory");
}
nodeid
}
pub fn add_dir(&self, parent: u64, name: &[u8]) -> u64 {
let mut st = self.st.lock().unwrap();
let nodeid = st.next_nodeid;
st.next_nodeid += 1;
let mut attr = Attr::default();
attr.ino = nodeid;
attr.mode = S_IFDIR | 0o755;
attr.nlink = 2;
attr.blksize = 4096;
st.inodes.insert(nodeid, (Node::Dir(Vec::new()), attr, 0));
if let Some((Node::Dir(entries), _, _)) = st.inodes.get_mut(&parent) {
entries.push((name.to_vec(), nodeid));
} else {
panic!("parent {parent} is not a directory");
}
nodeid
}
}
impl FsBackend for MemoryFs {
fn lookup(&self, parent: u64, name: &OsStr) -> Result<Entry, Errno> {
let mut st = self.st.lock().unwrap();
let entries = match st.inodes.get(&parent) {
Some((Node::Dir(es), _, _)) => es.clone(),
Some(_) => return Err(ENOTDIR),
None => return Err(ENOENT),
};
let name_bytes = name.as_bytes();
let target = entries
.iter()
.find(|(n, _)| n == name_bytes)
.map(|(_, id)| *id)
.ok_or(ENOENT)?;
let (_, attr, lookup_count) = st.inodes.get_mut(&target).ok_or(ENOENT)?;
*lookup_count += 1;
Ok(Entry {
nodeid: target,
generation: 0,
attr: *attr,
entry_valid: 1,
attr_valid: 1,
})
}
fn forget(&self, nodeid: u64, nlookup: u64) {
let mut st = self.st.lock().unwrap();
if let Some((_, _, count)) = st.inodes.get_mut(&nodeid) {
*count = count.saturating_sub(nlookup);
}
}
fn getattr(&self, nodeid: u64, _fh: Option<u64>) -> Result<Attr, Errno> {
let st = self.st.lock().unwrap();
st.inodes.get(&nodeid).map(|(_, a, _)| *a).ok_or(ENOENT)
}
fn open(&self, nodeid: u64, _flags: u32) -> Result<u64, Errno> {
let mut st = self.st.lock().unwrap();
match st.inodes.get(&nodeid) {
Some((Node::File(_), _, _)) => {}
Some(_) => return Err(EISDIR),
None => return Err(ENOENT),
}
let fh = st.next_fh;
st.next_fh += 1;
st.handles.insert(fh, (nodeid, false));
Ok(fh)
}
fn read(&self, nodeid: u64, fh: u64, offset: u64, size: u32) -> Result<Vec<u8>, Errno> {
let st = self.st.lock().unwrap();
let (h_node, is_dir) = *st.handles.get(&fh).ok_or(EBADF)?;
if is_dir || h_node != nodeid {
return Err(EBADF);
}
let data = match st.inodes.get(&nodeid) {
Some((Node::File(d), _, _)) => d.clone(),
Some(_) => return Err(EISDIR),
None => return Err(ENOENT),
};
let off = offset as usize;
if off >= data.len() {
return Ok(Vec::new());
}
let end = (off + size as usize).min(data.len());
Ok(data[off..end].to_vec())
}
fn release(&self, nodeid: u64, fh: u64) -> Result<(), Errno> {
let mut st = self.st.lock().unwrap();
let (h_node, is_dir) = st.handles.remove(&fh).ok_or(EBADF)?;
if is_dir || h_node != nodeid {
st.handles.insert(fh, (h_node, is_dir));
return Err(EBADF);
}
Ok(())
}
fn write(&self, nodeid: u64, fh: u64, offset: u64, data: &[u8]) -> Result<u32, Errno> {
let mut st = self.st.lock().unwrap();
let (h_node, is_dir) = *st.handles.get(&fh).ok_or(EBADF)?;
if is_dir || h_node != nodeid {
return Err(EBADF);
}
let bytes = match st.inodes.get_mut(&nodeid) {
Some((Node::File(d), attr, _)) => {
let off = offset as usize;
let end = off + data.len();
if d.len() < end {
d.resize(end, 0);
}
d[off..end].copy_from_slice(data);
attr.size = d.len() as u64;
data.len() as u32
}
Some(_) => return Err(EISDIR),
None => return Err(ENOENT),
};
Ok(bytes)
}
fn opendir(&self, nodeid: u64, _flags: u32) -> Result<u64, Errno> {
let mut st = self.st.lock().unwrap();
match st.inodes.get(&nodeid) {
Some((Node::Dir(_), _, _)) => {}
Some(_) => return Err(ENOTDIR),
None => return Err(ENOENT),
}
let fh = st.next_fh;
st.next_fh += 1;
st.handles.insert(fh, (nodeid, true));
Ok(fh)
}
fn readdir(
&self,
nodeid: u64,
fh: u64,
offset: u64,
_size: u32,
) -> Result<Vec<DirEntry>, Errno> {
let st = self.st.lock().unwrap();
let (h_node, is_dir) = *st.handles.get(&fh).ok_or(EBADF)?;
if !is_dir || h_node != nodeid {
return Err(EBADF);
}
let entries = match st.inodes.get(&nodeid) {
Some((Node::Dir(es), _, _)) => es.clone(),
_ => return Err(ENOTDIR),
};
let mut out = Vec::new();
for (i, (name, child_id)) in entries.iter().enumerate().skip(offset as usize) {
let typ = match st.inodes.get(child_id) {
Some((Node::Dir(_), _, _)) => super::protocol::DT_DIR,
Some((Node::File(_), _, _)) => super::protocol::DT_REG,
None => super::protocol::DT_UNKNOWN,
};
out.push(DirEntry {
ino: *child_id,
name: name.clone(),
typ,
});
let _ = i;
}
Ok(out)
}
fn releasedir(&self, nodeid: u64, fh: u64) -> Result<(), Errno> {
let mut st = self.st.lock().unwrap();
let (h_node, is_dir) = st.handles.remove(&fh).ok_or(EBADF)?;
if !is_dir || h_node != nodeid {
st.handles.insert(fh, (h_node, is_dir));
return Err(EBADF);
}
Ok(())
}
fn statfs(&self, _nodeid: u64) -> Result<StatFs, Errno> {
Ok(StatFs {
blocks: 1 << 20,
bfree: 1 << 19,
bavail: 1 << 19,
files: 1 << 16,
ffree: 1 << 15,
bsize: 4096,
namelen: 255,
frsize: 4096,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memfs_lookup_and_read_round_trip() {
let fs = MemoryFs::new();
let f = fs.add_file(super::super::protocol::FUSE_ROOT_ID, b"hello.txt", b"hi!".to_vec());
let e = fs.lookup(super::super::protocol::FUSE_ROOT_ID, OsStr::new("hello.txt")).unwrap();
assert_eq!(e.nodeid, f);
assert_eq!(e.attr.size, 3);
let fh = fs.open(f, 0).unwrap();
let data = fs.read(f, fh, 0, 1024).unwrap();
assert_eq!(&data, b"hi!");
fs.release(f, fh).unwrap();
}
#[test]
fn memfs_readdir_lists_children_with_types() {
let fs = MemoryFs::new();
fs.add_file(super::super::protocol::FUSE_ROOT_ID, b"a.txt", b"a".to_vec());
fs.add_dir(super::super::protocol::FUSE_ROOT_ID, b"sub");
let dh = fs.opendir(super::super::protocol::FUSE_ROOT_ID, 0).unwrap();
let entries = fs.readdir(super::super::protocol::FUSE_ROOT_ID, dh, 0, 4096).unwrap();
assert_eq!(entries.len(), 2);
let by_name: std::collections::HashMap<&[u8], u32> =
entries.iter().map(|e| (e.name.as_slice(), e.typ)).collect();
assert_eq!(by_name[&b"a.txt"[..]], super::super::protocol::DT_REG);
assert_eq!(by_name[&b"sub"[..]], super::super::protocol::DT_DIR);
fs.releasedir(super::super::protocol::FUSE_ROOT_ID, dh).unwrap();
}
#[test]
fn memfs_read_offset_and_eof() {
let fs = MemoryFs::new();
let f = fs.add_file(super::super::protocol::FUSE_ROOT_ID, b"x", (0u8..100).collect());
let fh = fs.open(f, 0).unwrap();
let chunk = fs.read(f, fh, 50, 10).unwrap();
assert_eq!(chunk, (50u8..60).collect::<Vec<_>>());
let eof = fs.read(f, fh, 200, 10).unwrap();
assert!(eof.is_empty());
}
#[test]
fn memfs_open_directory_errors() {
let fs = MemoryFs::new();
let d = fs.add_dir(super::super::protocol::FUSE_ROOT_ID, b"sub");
let err = fs.open(d, 0).unwrap_err();
assert_eq!(err, EISDIR);
}
#[test]
fn memfs_lookup_miss_returns_enoent() {
let fs = MemoryFs::new();
let err = fs.lookup(super::super::protocol::FUSE_ROOT_ID, OsStr::new("nope")).unwrap_err();
assert_eq!(err, ENOENT);
}
}