use crate::versioned_embrfs::VersionedEmbrFS;
use crate::{DirEntry, FileAttr, FileKind, Ino, ROOT_INO};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};
#[cfg(feature = "fuse")]
use crate::EmbrFSError;
#[cfg(feature = "fuse")]
use fuser::{
Filesystem, KernelConfig, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
ReplyEntry, ReplyOpen, ReplyWrite, ReplyXattr, Request,
};
#[cfg(feature = "fuse")]
use std::ffi::OsStr;
type XattrStorage = Arc<RwLock<HashMap<Ino, HashMap<String, Vec<u8>>>>>;
#[allow(dead_code)]
pub struct VersionedFUSE {
pub(crate) fs: Arc<VersionedEmbrFS>,
inode_paths: Arc<RwLock<HashMap<Ino, String>>>,
path_inodes: Arc<RwLock<HashMap<String, Ino>>>,
directories: Arc<RwLock<HashMap<Ino, Vec<DirEntry>>>>,
parent_inodes: Arc<RwLock<HashMap<Ino, Ino>>>,
symlinks: Arc<RwLock<HashMap<Ino, String>>>,
next_ino: Arc<RwLock<Ino>>,
next_fh: Arc<RwLock<u64>>,
open_files: Arc<RwLock<HashMap<u64, (String, u64)>>>,
xattrs: XattrStorage,
attr_ttl: Duration,
entry_ttl: Duration,
}
#[allow(dead_code)]
impl VersionedFUSE {
pub fn new(fs: VersionedEmbrFS) -> Self {
let mut adapter = Self {
fs: Arc::new(fs),
inode_paths: Arc::new(RwLock::new(HashMap::new())),
path_inodes: Arc::new(RwLock::new(HashMap::new())),
directories: Arc::new(RwLock::new(HashMap::new())),
parent_inodes: Arc::new(RwLock::new(HashMap::new())),
symlinks: Arc::new(RwLock::new(HashMap::new())),
next_ino: Arc::new(RwLock::new(ROOT_INO + 1)),
next_fh: Arc::new(RwLock::new(1)),
open_files: Arc::new(RwLock::new(HashMap::new())),
xattrs: Arc::new(RwLock::new(HashMap::new())),
attr_ttl: Duration::from_secs(1),
entry_ttl: Duration::from_secs(1),
};
adapter.init_root();
adapter.rebuild_directory_structure();
adapter
}
fn init_root(&mut self) {
let mut inode_paths = self.inode_paths.write().unwrap();
let mut path_inodes = self.path_inodes.write().unwrap();
let mut directories = self.directories.write().unwrap();
let mut parent_inodes = self.parent_inodes.write().unwrap();
inode_paths.insert(ROOT_INO, "/".to_string());
path_inodes.insert("/".to_string(), ROOT_INO);
directories.insert(ROOT_INO, Vec::new());
parent_inodes.insert(ROOT_INO, ROOT_INO); }
fn rebuild_directory_structure(&mut self) {
let files = self.fs.list_files();
for path in files {
self.register_file(&path);
}
}
fn register_file(&self, path: &str) {
let parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
let mut current_path = String::new();
for (i, part) in parts.iter().enumerate() {
let parent_path = current_path.clone();
current_path = if current_path.is_empty() {
format!("/{}", part)
} else {
format!("{}/{}", current_path, part)
};
let is_file = i == parts.len() - 1;
let ino = self.get_or_create_inode(¤t_path);
let parent_ino = if parent_path.is_empty() {
ROOT_INO
} else {
self.get_or_create_inode(&parent_path)
};
let mut parent_map = self.parent_inodes.write().unwrap();
parent_map.insert(ino, parent_ino);
drop(parent_map);
let mut directories = self.directories.write().unwrap();
let parent_entries = directories.entry(parent_ino).or_default();
if !parent_entries.iter().any(|e| e.name == *part) {
parent_entries.push(DirEntry {
ino,
name: part.to_string(),
kind: if is_file {
FileKind::RegularFile
} else {
FileKind::Directory
},
});
}
if !is_file {
directories.entry(ino).or_default();
}
}
}
fn get_parent_ino(&self, ino: Ino) -> Ino {
self.parent_inodes
.read()
.unwrap()
.get(&ino)
.copied()
.unwrap_or(ROOT_INO)
}
fn get_or_create_inode(&self, path: &str) -> Ino {
let path_inodes = self.path_inodes.read().unwrap();
if let Some(&ino) = path_inodes.get(path) {
return ino;
}
drop(path_inodes);
let mut next_ino = self.next_ino.write().unwrap();
let ino = *next_ino;
*next_ino += 1;
drop(next_ino);
let mut path_inodes = self.path_inodes.write().unwrap();
let mut inode_paths = self.inode_paths.write().unwrap();
path_inodes.insert(path.to_string(), ino);
inode_paths.insert(ino, path.to_string());
ino
}
fn get_path(&self, ino: Ino) -> Option<String> {
let inode_paths = self.inode_paths.read().unwrap();
inode_paths.get(&ino).cloned()
}
fn get_inode(&self, path: &str) -> Option<Ino> {
let path_inodes = self.path_inodes.read().unwrap();
path_inodes.get(path).copied()
}
fn make_file_attr(&self, ino: Ino, path: &str) -> FileAttr {
let now = SystemTime::now();
let directories = self.directories.read().unwrap();
if directories.contains_key(&ino) {
return FileAttr {
ino,
size: 0,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
crtime: now,
kind: FileKind::Directory,
perm: 0o755,
nlink: 2,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
flags: 0,
};
}
let size = if let Ok((data, _version)) = self.fs.read_file(path) {
data.len() as u64
} else {
0
};
FileAttr {
ino,
size,
blocks: size.div_ceil(4096),
atime: now,
mtime: now,
ctime: now,
crtime: now,
kind: FileKind::RegularFile,
perm: 0o644,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
flags: 0,
}
}
fn allocate_fh(&self, path: String, version: u64) -> u64 {
let mut next_fh = self.next_fh.write().unwrap();
let fh = *next_fh;
*next_fh += 1;
drop(next_fh);
let mut open_files = self.open_files.write().unwrap();
open_files.insert(fh, (path, version));
fh
}
fn release_fh(&self, fh: u64) -> Option<(String, u64)> {
let mut open_files = self.open_files.write().unwrap();
open_files.remove(&fh)
}
}
#[cfg(feature = "fuse")]
impl Filesystem for VersionedFUSE {
fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), libc::c_int> {
Ok(())
}
fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::ENOENT);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let child_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
let ino = match self.get_inode(&child_path) {
Some(i) => i,
None => {
reply.error(libc::ENOENT);
return;
}
};
let attr = self.make_file_attr(ino, &child_path);
reply.entry(&self.entry_ttl, &attr.into(), 0);
}
fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option<u64>, reply: ReplyAttr) {
let path = match self.get_path(ino) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let attr = self.make_file_attr(ino, &path);
reply.attr(&self.attr_ttl, &attr.into());
}
fn read(
&mut self,
_req: &Request,
ino: u64,
_fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyData,
) {
let path = match self.get_path(ino) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let offset = offset as usize;
let size = size as usize;
match self.fs.read_range(&path, offset, size) {
Ok((data, _version)) => {
reply.data(&data);
}
Err(EmbrFSError::FileNotFound(_)) => {
reply.error(libc::ENOENT);
}
Err(_) => {
reply.error(libc::EIO);
}
}
}
fn readdir(
&mut self,
_req: &Request,
ino: u64,
_fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
let directories = self.directories.read().unwrap();
let entries = match directories.get(&ino) {
Some(e) => e,
None => {
reply.error(libc::ENOTDIR);
return;
}
};
let parent_ino = self.get_parent_ino(ino);
let mut all_entries = vec![
DirEntry {
ino,
name: ".".to_string(),
kind: FileKind::Directory,
},
DirEntry {
ino: parent_ino,
name: "..".to_string(),
kind: FileKind::Directory,
},
];
all_entries.extend_from_slice(entries);
for (i, entry) in all_entries.iter().enumerate().skip(offset as usize) {
let full = reply.add(entry.ino, (i + 1) as i64, entry.kind.into(), &entry.name);
if full {
break;
}
}
reply.ok();
}
fn open(&mut self, _req: &Request, ino: u64, flags: i32, reply: ReplyOpen) {
let path = match self.get_path(ino) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let version = match self.fs.read_file(&path) {
Ok((_data, version)) => version,
Err(EmbrFSError::FileNotFound(_)) => 0, Err(_) => {
reply.error(libc::EIO);
return;
}
};
let fh = self.allocate_fh(path, version);
reply.opened(fh, flags as u32);
}
fn release(
&mut self,
_req: &Request,
_ino: u64,
fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
self.release_fh(fh);
reply.ok();
}
fn write(
&mut self,
_req: &Request,
_ino: u64,
fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
let (path, file_version) = match self.open_files.read().unwrap().get(&fh) {
Some(info) => info.clone(),
None => {
reply.error(libc::EBADF);
return;
}
};
let mut file_data = match self.fs.read_file(&path) {
Ok((data, _ver)) => data,
Err(EmbrFSError::FileNotFound(_)) => Vec::new(), Err(_) => {
reply.error(libc::EIO);
return;
}
};
let offset = offset as usize;
if offset + data.len() > file_data.len() {
file_data.resize(offset + data.len(), 0);
}
file_data[offset..offset + data.len()].copy_from_slice(data);
let expected_version = if file_data.len() == data.len() {
None } else {
Some(file_version)
};
match self.fs.write_file(&path, &file_data, expected_version) {
Ok(new_version) => {
let mut open_files = self.open_files.write().unwrap();
if let Some(info) = open_files.get_mut(&fh) {
info.1 = new_version;
}
reply.written(data.len() as u32);
}
Err(EmbrFSError::VersionMismatch { .. }) => {
reply.error(libc::EAGAIN);
}
Err(_) => {
reply.error(libc::EIO);
}
}
}
fn create(
&mut self,
_req: &Request,
parent: u64,
name: &OsStr,
_mode: u32,
_umask: u32,
flags: i32,
reply: ReplyCreate,
) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let file_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
match self.fs.write_file(&file_path, &[], None) {
Ok(version) => {
self.register_file(&file_path);
let ino = self.get_inode(&file_path).unwrap();
let attr = self.make_file_attr(ino, &file_path);
let fh = self.allocate_fh(file_path, version);
reply.created(&self.entry_ttl, &attr.into(), 0, fh, flags as u32);
}
Err(_) => {
reply.error(libc::EIO);
}
}
}
fn unlink(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let file_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
let file_version = match self.fs.read_file(&file_path) {
Ok((_data, version)) => version,
Err(_) => {
reply.error(libc::ENOENT);
return;
}
};
match self.fs.delete_file(&file_path, file_version) {
Ok(_) => {
let mut directories = self.directories.write().unwrap();
if let Some(entries) = directories.get_mut(&parent) {
entries.retain(|e| e.name != name_str);
}
reply.ok();
}
Err(EmbrFSError::VersionMismatch { .. }) => {
reply.error(libc::EAGAIN);
}
Err(_) => {
reply.error(libc::EIO);
}
}
}
fn setattr(
&mut self,
_req: &Request,
ino: u64,
_mode: Option<u32>,
_uid: Option<u32>,
_gid: Option<u32>,
size: Option<u64>,
_atime: Option<fuser::TimeOrNow>,
_mtime: Option<fuser::TimeOrNow>,
_ctime: Option<SystemTime>,
_fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_flags: Option<u32>,
reply: ReplyAttr,
) {
if let Some(new_size) = size {
let path = match self.get_path(ino) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let (mut data, version) = match self.fs.read_file(&path) {
Ok(result) => result,
Err(_) => {
reply.error(libc::EIO);
return;
}
};
data.resize(new_size as usize, 0);
match self.fs.write_file(&path, &data, Some(version)) {
Ok(_) => {
let attr = self.make_file_attr(ino, &path);
reply.attr(&self.attr_ttl, &attr.into());
}
Err(_) => {
reply.error(libc::EIO);
}
}
} else {
let path = match self.get_path(ino) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let attr = self.make_file_attr(ino, &path);
reply.attr(&self.attr_ttl, &attr.into());
}
}
fn readlink(&mut self, _req: &Request, ino: u64, reply: ReplyData) {
let symlinks = self.symlinks.read().unwrap();
match symlinks.get(&ino) {
Some(target) => {
reply.data(target.as_bytes());
}
None => {
reply.error(libc::EINVAL); }
}
}
fn symlink(
&mut self,
_req: &Request,
parent: u64,
link_name: &OsStr,
target: &std::path::Path,
reply: ReplyEntry,
) {
let link_name_str = match link_name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let target_str = match target.to_str() {
Some(s) => s.to_string(),
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let symlink_path = if parent_path == "/" {
format!("/{}", link_name_str)
} else {
format!("{}/{}", parent_path, link_name_str)
};
let mut next_ino = self.next_ino.write().unwrap();
let ino = *next_ino;
*next_ino += 1;
drop(next_ino);
let mut path_inodes = self.path_inodes.write().unwrap();
let mut inode_paths = self.inode_paths.write().unwrap();
let mut symlinks = self.symlinks.write().unwrap();
let mut directories = self.directories.write().unwrap();
path_inodes.insert(symlink_path.clone(), ino);
inode_paths.insert(ino, symlink_path);
symlinks.insert(ino, target_str.clone());
if let Some(parent_entries) = directories.get_mut(&parent) {
parent_entries.push(DirEntry {
ino,
name: link_name_str.to_string(),
kind: FileKind::Symlink,
});
}
let now = SystemTime::now();
let attr = FileAttr {
ino,
size: target_str.len() as u64,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
crtime: now,
kind: FileKind::Symlink,
perm: 0o777,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
flags: 0,
};
reply.entry(&self.entry_ttl, &attr.into(), 0);
}
fn mkdir(
&mut self,
_req: &Request,
parent: u64,
name: &OsStr,
_mode: u32,
_umask: u32,
reply: ReplyEntry,
) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let dir_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
if self.get_inode(&dir_path).is_some() {
reply.error(libc::EEXIST);
return;
}
let mut next_ino = self.next_ino.write().unwrap();
let ino = *next_ino;
*next_ino += 1;
drop(next_ino);
let mut path_inodes = self.path_inodes.write().unwrap();
let mut inode_paths = self.inode_paths.write().unwrap();
let mut directories = self.directories.write().unwrap();
let mut parent_map = self.parent_inodes.write().unwrap();
path_inodes.insert(dir_path.clone(), ino);
inode_paths.insert(ino, dir_path);
directories.insert(ino, Vec::new());
parent_map.insert(ino, parent);
if let Some(parent_entries) = directories.get_mut(&parent) {
parent_entries.push(DirEntry {
ino,
name: name_str.to_string(),
kind: FileKind::Directory,
});
}
let now = SystemTime::now();
let attr = FileAttr {
ino,
size: 0,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
crtime: now,
kind: FileKind::Directory,
perm: 0o755,
nlink: 2,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
flags: 0,
};
reply.entry(&self.entry_ttl, &attr.into(), 0);
}
fn rmdir(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let dir_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
let ino = match self.get_inode(&dir_path) {
Some(i) => i,
None => {
reply.error(libc::ENOENT);
return;
}
};
{
let directories = self.directories.read().unwrap();
match directories.get(&ino) {
Some(entries) if !entries.is_empty() => {
reply.error(libc::ENOTEMPTY);
return;
}
None => {
reply.error(libc::ENOTDIR);
return;
}
_ => {}
}
}
let mut path_inodes = self.path_inodes.write().unwrap();
let mut inode_paths = self.inode_paths.write().unwrap();
let mut directories = self.directories.write().unwrap();
let mut parent_map = self.parent_inodes.write().unwrap();
path_inodes.remove(&dir_path);
inode_paths.remove(&ino);
directories.remove(&ino);
parent_map.remove(&ino);
if let Some(parent_entries) = directories.get_mut(&parent) {
parent_entries.retain(|e| e.name != name_str);
}
reply.ok();
}
fn rename(
&mut self,
_req: &Request,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
_flags: u32,
reply: ReplyEmpty,
) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let newname_str = match newname.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
let parent_path = match self.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let newparent_path = match self.get_path(newparent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};
let old_path = if parent_path == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_path, name_str)
};
let new_path = if newparent_path == "/" {
format!("/{}", newname_str)
} else {
format!("{}/{}", newparent_path, newname_str)
};
let ino = match self.get_inode(&old_path) {
Some(i) => i,
None => {
reply.error(libc::ENOENT);
return;
}
};
let kind = {
let directories = self.directories.read().unwrap();
if directories.contains_key(&ino) {
FileKind::Directory
} else {
FileKind::RegularFile
}
};
{
let mut directories = self.directories.write().unwrap();
if let Some(parent_entries) = directories.get_mut(&parent) {
parent_entries.retain(|e| e.name != name_str);
}
}
{
let mut path_inodes = self.path_inodes.write().unwrap();
let mut inode_paths = self.inode_paths.write().unwrap();
let mut parent_map = self.parent_inodes.write().unwrap();
path_inodes.remove(&old_path);
path_inodes.insert(new_path.clone(), ino);
inode_paths.insert(ino, new_path.clone());
parent_map.insert(ino, newparent);
}
{
let mut directories = self.directories.write().unwrap();
let new_parent_entries = directories.entry(newparent).or_default();
new_parent_entries.retain(|e| e.name != newname_str);
new_parent_entries.push(DirEntry {
ino,
name: newname_str.to_string(),
kind,
});
}
if kind == FileKind::RegularFile {
if let Ok((data, version)) = self.fs.read_file(&old_path) {
let _ = self.fs.write_file(&new_path, &data, None);
let _ = self.fs.delete_file(&old_path, version);
}
}
reply.ok();
}
fn getxattr(
&mut self,
_req: &Request<'_>,
ino: u64,
name: &OsStr,
size: u32,
reply: ReplyXattr,
) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::ENODATA);
return;
}
};
let value = match name_str {
"user.engram.version" => {
let version = self.fs.version();
Some(version.to_string().into_bytes())
}
"user.engram.file_count" => {
let stats = self.fs.stats();
Some(stats.active_files.to_string().into_bytes())
}
"user.engram.total_size" => {
let stats = self.fs.stats();
Some(stats.total_size_bytes.to_string().into_bytes())
}
"user.engram.chunk_count" => {
let stats = self.fs.stats();
Some(stats.total_chunks.to_string().into_bytes())
}
"user.engram.correction_overhead" => {
let stats = self.fs.stats();
Some(stats.correction_overhead_bytes.to_string().into_bytes())
}
_ => {
let xattrs = self.xattrs.read().unwrap();
xattrs
.get(&ino)
.and_then(|attrs| attrs.get(name_str).cloned())
}
};
match value {
Some(data) => {
if size == 0 {
reply.size(data.len() as u32);
} else if size >= data.len() as u32 {
reply.data(&data);
} else {
reply.error(libc::ERANGE);
}
}
None => {
reply.error(libc::ENODATA);
}
}
}
fn setxattr(
&mut self,
_req: &Request<'_>,
ino: u64,
name: &OsStr,
value: &[u8],
_flags: i32,
_position: u32,
reply: ReplyEmpty,
) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
if name_str.starts_with("user.engram.") {
reply.error(libc::EPERM);
return;
}
let inode_paths = self.inode_paths.read().unwrap();
if !inode_paths.contains_key(&ino) {
reply.error(libc::ENOENT);
return;
}
drop(inode_paths);
let mut xattrs = self.xattrs.write().unwrap();
let inode_xattrs = xattrs.entry(ino).or_default();
inode_xattrs.insert(name_str.to_string(), value.to_vec());
reply.ok();
}
fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: ReplyXattr) {
let inode_paths = self.inode_paths.read().unwrap();
if !inode_paths.contains_key(&ino) {
reply.error(libc::ENOENT);
return;
}
drop(inode_paths);
let mut names: Vec<String> = vec![
"user.engram.version".to_string(),
"user.engram.file_count".to_string(),
"user.engram.total_size".to_string(),
"user.engram.chunk_count".to_string(),
"user.engram.correction_overhead".to_string(),
];
let xattrs = self.xattrs.read().unwrap();
if let Some(inode_xattrs) = xattrs.get(&ino) {
for name in inode_xattrs.keys() {
names.push(name.clone());
}
}
let mut data = Vec::new();
for name in &names {
data.extend(name.as_bytes());
data.push(0); }
if size == 0 {
reply.size(data.len() as u32);
} else if size >= data.len() as u32 {
reply.data(&data);
} else {
reply.error(libc::ERANGE);
}
}
fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) {
let name_str = match name.to_str() {
Some(s) => s,
None => {
reply.error(libc::EINVAL);
return;
}
};
if name_str.starts_with("user.engram.") {
reply.error(libc::EPERM);
return;
}
let mut xattrs = self.xattrs.write().unwrap();
if let Some(inode_xattrs) = xattrs.get_mut(&ino) {
if inode_xattrs.remove(name_str).is_some() {
reply.ok();
return;
}
}
reply.error(libc::ENODATA);
}
}
#[cfg(feature = "fuse")]
pub fn mount_versioned_fs(
fs: VersionedEmbrFS,
mountpoint: &std::path::Path,
options: &[fuser::MountOption],
) -> std::io::Result<()> {
let fuse_fs = VersionedFUSE::new(fs);
fuser::mount2(fuse_fs, mountpoint, options)
}
#[cfg(feature = "fuse")]
#[derive(Clone, Debug)]
pub struct VersionedMountOptions {
pub fsname: String,
pub allow_other: bool,
pub allow_root: bool,
pub auto_unmount: bool,
}
#[cfg(feature = "fuse")]
impl Default for VersionedMountOptions {
fn default() -> Self {
Self {
fsname: "versioned-engram".to_string(),
allow_other: false,
allow_root: true,
auto_unmount: true,
}
}
}
#[cfg(feature = "fuse")]
pub fn mount_versioned_fs_with_signals(
fs: VersionedEmbrFS,
mountpoint: &std::path::Path,
options: VersionedMountOptions,
) -> std::io::Result<()> {
use crate::fs::signal::{install_signal_handlers, ShutdownSignal};
use fuser::MountOption;
use std::sync::Arc;
let shutdown = Arc::new(ShutdownSignal::new());
install_signal_handlers(shutdown.clone())?;
let mut mount_options = vec![
MountOption::FSName(options.fsname),
MountOption::DefaultPermissions,
];
if options.auto_unmount {
mount_options.push(MountOption::AutoUnmount);
}
if options.allow_other {
mount_options.push(MountOption::AllowOther);
} else if options.allow_root {
mount_options.push(MountOption::AllowRoot);
}
let fuse_fs = VersionedFUSE::new(fs);
let underlying_fs = fuse_fs.fs.clone();
let session = fuser::spawn_mount2(fuse_fs, mountpoint, &mount_options)?;
eprintln!("VersionedEmbrFS mounted at {}", mountpoint.display());
eprintln!("Write operations are supported. Press Ctrl+C to unmount gracefully.");
eprintln!(
"Use 'fusermount -u {}' to unmount manually.",
mountpoint.display()
);
loop {
if shutdown.is_signaled() {
eprintln!(
"\nReceived {} - initiating graceful shutdown...",
shutdown.signal_name()
);
std::thread::sleep(std::time::Duration::from_millis(100));
let stats = underlying_fs.stats();
eprintln!(
"Filesystem stats: {} files, {} bytes total",
stats.active_files, stats.total_size_bytes
);
drop(session);
eprintln!("VersionedEmbrFS unmounted cleanly.");
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
#[cfg(feature = "fuse")]
pub fn mount_versioned_foreground(
fs: VersionedEmbrFS,
mountpoint: &std::path::Path,
options: VersionedMountOptions,
handle_signals: bool,
) -> std::io::Result<()> {
if handle_signals {
mount_versioned_fs_with_signals(fs, mountpoint, options)
} else {
use fuser::MountOption;
let mut mount_options = vec![
MountOption::FSName(options.fsname),
MountOption::DefaultPermissions,
];
if options.auto_unmount {
mount_options.push(MountOption::AutoUnmount);
}
if options.allow_other {
mount_options.push(MountOption::AllowOther);
} else if options.allow_root {
mount_options.push(MountOption::AllowRoot);
}
mount_versioned_fs(fs, mountpoint, &mount_options)
}
}