use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;
use fuser::TimeOrNow;
use threadpool::ThreadPool;
use crate::directory_cache::*;
use crate::inode_table::*;
use crate::types::*;
trait IntoRequestInfo {
fn info(&self) -> RequestInfo;
}
impl<'a> IntoRequestInfo for fuser::Request<'a> {
fn info(&self) -> RequestInfo {
RequestInfo {
unique: self.unique(),
uid: self.uid(),
gid: self.gid(),
pid: self.pid(),
}
}
}
fn fuse_fileattr(attr: FileAttr, ino: u64) -> fuser::FileAttr {
fuser::FileAttr {
ino,
size: attr.size,
blocks: attr.blocks,
atime: attr.atime,
mtime: attr.mtime,
ctime: attr.ctime,
crtime: attr.crtime,
kind: attr.kind,
perm: attr.perm,
nlink: attr.nlink,
uid: attr.uid,
gid: attr.gid,
rdev: attr.rdev,
blksize: 4096, flags: attr.flags,
}
}
trait TimeOrNowExt {
fn time(self) -> SystemTime;
}
impl TimeOrNowExt for TimeOrNow {
fn time(self) -> SystemTime {
match self {
TimeOrNow::SpecificTime(t) => t,
TimeOrNow::Now => SystemTime::now(),
}
}
}
#[derive(Debug)]
pub struct FuseMT<T> {
target: Arc<T>,
inodes: InodeTable,
threads: Option<ThreadPool>,
num_threads: usize,
directory_cache: DirectoryCache,
}
impl<T: FilesystemMT + Sync + Send + 'static> FuseMT<T> {
pub fn new(target_fs: T, num_threads: usize) -> FuseMT<T> {
FuseMT {
target: Arc::new(target_fs),
inodes: InodeTable::new(),
threads: None,
num_threads,
directory_cache: DirectoryCache::new(),
}
}
fn threadpool_run<F: FnOnce() + Send + 'static>(&mut self, f: F) {
if self.num_threads == 0 {
f()
} else {
if self.threads.is_none() {
debug!("initializing threadpool with {} threads", self.num_threads);
self.threads = Some(ThreadPool::new(self.num_threads));
}
self.threads.as_ref().unwrap().execute(f);
}
}
}
macro_rules! get_path {
($s:expr, $ino:expr, $reply:expr) => {
if let Some(path) = $s.inodes.get_path($ino) {
path
} else {
$reply.error(libc::EINVAL);
return;
}
}
}
impl<T: FilesystemMT + Sync + Send + 'static> fuser::Filesystem for FuseMT<T> {
fn init(
&mut self,
req: &fuser::Request<'_>,
_config: &mut fuser::KernelConfig, ) -> Result<(), libc::c_int> {
debug!("init");
self.target.init(req.info())
}
fn destroy(&mut self) {
debug!("destroy");
self.target.destroy();
}
fn lookup(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
reply: fuser::ReplyEntry,
) {
let parent_path = get_path!(self, parent, reply);
debug!("lookup: {:?}, {:?}", parent_path, name);
let path = Arc::new((*parent_path).clone().join(name));
match self.target.getattr(req.info(), &path, None) {
Ok((ttl, attr)) => {
let (ino, generation) = self.inodes.add_or_get(path.clone());
self.inodes.lookup(ino);
reply.entry(&ttl, &fuse_fileattr(attr, ino), generation);
},
Err(e) => reply.error(e),
}
}
fn forget(
&mut self,
_req: &fuser::Request<'_>,
ino: u64,
nlookup: u64,
) {
let path = self.inodes.get_path(ino).unwrap_or_else(|| {
Arc::new(PathBuf::from("[unknown]"))
});
let lookups = self.inodes.forget(ino, nlookup);
debug!("forget: inode {} ({:?}) now at {} lookups", ino, path, lookups);
}
fn getattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: Option<u64>,
reply: fuser::ReplyAttr,
) {
let path = get_path!(self, ino, reply);
debug!("getattr: {:?}", path);
match self.target.getattr(req.info(), &path, fh) {
Ok((ttl, attr)) => {
reply.attr(&ttl, &fuse_fileattr(attr, ino))
},
Err(e) => reply.error(e),
}
}
fn setattr(
&mut self,
req: &fuser::Request<'_>, ino: u64, mode: Option<u32>, uid: Option<u32>, gid: Option<u32>, size: Option<u64>, atime: Option<TimeOrNow>, mtime: Option<TimeOrNow>, _ctime: Option<SystemTime>, fh: Option<u64>, crtime: Option<SystemTime>, chgtime: Option<SystemTime>, bkuptime: Option<SystemTime>, flags: Option<u32>, reply: fuser::ReplyAttr,
) {
let path = get_path!(self, ino, reply);
debug!("setattr: {:?}", path);
debug!("\tino:\t{:?}", ino);
debug!("\tmode:\t{:?}", mode);
debug!("\tuid:\t{:?}", uid);
debug!("\tgid:\t{:?}", gid);
debug!("\tsize:\t{:?}", size);
debug!("\tatime:\t{:?}", atime);
debug!("\tmtime:\t{:?}", mtime);
debug!("\tfh:\t{:?}", fh);
if let Some(mode) = mode {
if let Err(e) = self.target.chmod(req.info(), &path, fh, mode) {
reply.error(e);
return;
}
}
if uid.is_some() || gid.is_some() {
if let Err(e) = self.target.chown(req.info(), &path, fh, uid, gid) {
reply.error(e);
return;
}
}
if let Some(size) = size {
if let Err(e) = self.target.truncate(req.info(), &path, fh, size) {
reply.error(e);
return;
}
}
if atime.is_some() || mtime.is_some() {
let atime = atime.map(TimeOrNowExt::time);
let mtime = mtime.map(TimeOrNowExt::time);
if let Err(e) = self.target.utimens(req.info(), &path, fh, atime, mtime) {
reply.error(e);
return;
}
}
if crtime.is_some() || chgtime.is_some() || bkuptime.is_some() || flags.is_some() {
if let Err(e) = self.target.utimens_macos(req.info(), &path, fh, crtime, chgtime, bkuptime, flags) {
reply.error(e);
return
}
}
match self.target.getattr(req.info(), &path, fh) {
Ok((ttl, attr)) => reply.attr(&ttl, &fuse_fileattr(attr, ino)),
Err(e) => reply.error(e),
}
}
fn readlink(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
reply: fuser::ReplyData,
) {
let path = get_path!(self, ino, reply);
debug!("readlink: {:?}", path);
match self.target.readlink(req.info(), &path) {
Ok(data) => reply.data(&data),
Err(e) => reply.error(e),
}
}
fn mknod(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
_umask: u32, rdev: u32,
reply: fuser::ReplyEntry,
) {
let parent_path = get_path!(self, parent, reply);
debug!("mknod: {:?}/{:?}", parent_path, name);
match self.target.mknod(req.info(), &parent_path, name, mode, rdev) {
Ok((ttl, attr)) => {
let (ino, generation) = self.inodes.add(Arc::new(parent_path.join(name)));
reply.entry(&ttl, &fuse_fileattr(attr, ino), generation)
},
Err(e) => reply.error(e),
}
}
fn mkdir(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
_umask: u32, reply: fuser::ReplyEntry,
) {
let parent_path = get_path!(self, parent, reply);
debug!("mkdir: {:?}/{:?}", parent_path, name);
match self.target.mkdir(req.info(), &parent_path, name, mode) {
Ok((ttl, attr)) => {
let (ino, generation) = self.inodes.add(Arc::new(parent_path.join(name)));
reply.entry(&ttl, &fuse_fileattr(attr, ino), generation)
},
Err(e) => reply.error(e),
}
}
fn unlink(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
let parent_path = get_path!(self, parent, reply);
debug!("unlink: {:?}/{:?}", parent_path, name);
match self.target.unlink(req.info(), &parent_path, name) {
Ok(()) => {
self.inodes.unlink(&parent_path.join(name));
reply.ok()
},
Err(e) => reply.error(e),
}
}
fn rmdir(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
let parent_path = get_path!(self, parent, reply);
debug!("rmdir: {:?}/{:?}", parent_path, name);
match self.target.rmdir(req.info(), &parent_path, name) {
Ok(()) => {
self.inodes.unlink(&parent_path.join(name));
reply.ok()
},
Err(e) => reply.error(e),
}
}
fn symlink(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
link: &Path,
reply: fuser::ReplyEntry,
) {
let parent_path = get_path!(self, parent, reply);
debug!("symlink: {:?}/{:?} -> {:?}", parent_path, name, link);
match self.target.symlink(req.info(), &parent_path, name, link) {
Ok((ttl, attr)) => {
let (ino, generation) = self.inodes.add(Arc::new(parent_path.join(name)));
reply.entry(&ttl, &fuse_fileattr(attr, ino), generation)
},
Err(e) => reply.error(e),
}
}
fn rename(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
_flags: u32, reply: fuser::ReplyEmpty,
) {
let parent_path = get_path!(self, parent, reply);
let newparent_path = get_path!(self, newparent, reply);
debug!("rename: {:?}/{:?} -> {:?}/{:?}", parent_path, name, newparent_path, newname);
match self.target.rename(req.info(), &parent_path, name, &newparent_path, newname) {
Ok(()) => {
self.inodes.rename(&parent_path.join(name), Arc::new(newparent_path.join(newname)));
reply.ok()
},
Err(e) => reply.error(e),
}
}
fn link(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
newparent: u64,
newname: &OsStr,
reply: fuser::ReplyEntry,
) {
let path = get_path!(self, ino, reply);
let newparent_path = get_path!(self, newparent, reply);
debug!("link: {:?} -> {:?}/{:?}", path, newparent_path, newname);
match self.target.link(req.info(), &path, &newparent_path, newname) {
Ok((ttl, attr)) => {
let (new_ino, generation) = self.inodes.add(Arc::new(newparent_path.join(newname)));
reply.entry(&ttl, &fuse_fileattr(attr, new_ino), generation);
},
Err(e) => reply.error(e),
}
}
fn open(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
flags: i32,
reply: fuser::ReplyOpen,
) {
let path = get_path!(self, ino, reply);
debug!("open: {:?}", path);
match self.target.open(req.info(), &path, flags as u32) { Ok((fh, flags)) => reply.opened(fh, flags),
Err(e) => reply.error(e),
}
}
fn read(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
offset: i64,
size: u32,
_flags: i32, _lock_owner: Option<u64>, reply: fuser::ReplyData,
) {
let path = get_path!(self, ino, reply);
debug!("read: {:?} {:#x} @ {:#x}", path, size, offset);
if offset < 0 {
error!("read called with a negative offset");
reply.error(libc::EINVAL);
return;
}
let target = self.target.clone();
let req_info = req.info();
self.threadpool_run(move || {
target.read(req_info, &path, fh, offset as u64, size, |result| {
match result {
Ok(data) => reply.data(data),
Err(e) => reply.error(e),
}
CallbackResult {
_private: std::marker::PhantomData {},
}
});
});
}
fn write(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32, flags: i32,
_lock_owner: Option<u64>, reply: fuser::ReplyWrite,
) {
let path = get_path!(self, ino, reply);
debug!("write: {:?} {:#x} @ {:#x}", path, data.len(), offset);
if offset < 0 {
error!("write called with a negative offset");
reply.error(libc::EINVAL);
return;
}
let target = self.target.clone();
let req_info = req.info();
let data_buf = Vec::from(data);
self.threadpool_run(move|| {
match target.write(req_info, &path, fh, offset as u64, data_buf, flags as u32) {
Ok(written) => reply.written(written),
Err(e) => reply.error(e),
}
});
}
fn flush(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
lock_owner: u64,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("flush: {:?}", path);
let target = self.target.clone();
let req_info = req.info();
self.threadpool_run(move|| {
match target.flush(req_info, &path, fh, lock_owner) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
});
}
fn release(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
flags: i32,
lock_owner: Option<u64>,
flush: bool,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("release: {:?}", path);
match self.target.release(
req.info(), &path, fh, flags as u32, lock_owner.unwrap_or(0) , flush)
{
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
fn fsync(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
datasync: bool,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("fsync: {:?}", path);
let target = self.target.clone();
let req_info = req.info();
self.threadpool_run(move|| {
match target.fsync(req_info, &path, fh, datasync) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
});
}
fn opendir(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
flags: i32,
reply: fuser::ReplyOpen,
) {
let path = get_path!(self, ino, reply);
debug!("opendir: {:?}", path);
match self.target.opendir(req.info(), &path, flags as u32) {
Ok((fh, flags)) => {
let dcache_key = self.directory_cache.new_entry(fh);
reply.opened(dcache_key, flags);
},
Err(e) => reply.error(e),
}
}
fn readdir(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
offset: i64,
mut reply: fuser::ReplyDirectory,
) {
let path = get_path!(self, ino, reply);
debug!("readdir: {:?} @ {}", path, offset);
if offset < 0 {
error!("readdir called with a negative offset");
reply.error(libc::EINVAL);
return;
}
let entries: &[DirectoryEntry] = {
let dcache_entry = self.directory_cache.get_mut(fh);
if let Some(ref entries) = dcache_entry.entries {
entries
} else {
debug!("entries not yet fetched; requesting with fh {}", dcache_entry.fh);
match self.target.readdir(req.info(), &path, dcache_entry.fh) {
Ok(entries) => {
dcache_entry.entries = Some(entries);
dcache_entry.entries.as_ref().unwrap()
},
Err(e) => {
reply.error(e);
return;
}
}
}
};
let parent_inode = if ino == 1 {
ino
} else {
let parent_path: &Path = path.parent().unwrap();
match self.inodes.get_inode(parent_path) {
Some(inode) => inode,
None => {
error!("readdir: unable to get inode for parent of {:?}", path);
reply.error(libc::EIO);
return;
}
}
};
debug!("directory has {} entries", entries.len());
for (index, entry) in entries.iter().skip(offset as usize).enumerate() {
let entry_inode = if entry.name == Path::new(".") {
ino
} else if entry.name == Path::new("..") {
parent_inode
} else {
!1
};
debug!("readdir: adding entry #{}, {:?}", offset + index as i64, entry.name);
let buffer_full: bool = reply.add(
entry_inode,
offset + index as i64 + 1,
entry.kind,
entry.name.as_os_str());
if buffer_full {
debug!("readdir: reply buffer is full");
break;
}
}
reply.ok();
}
fn releasedir(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
flags: i32,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("releasedir: {:?}", path);
let real_fh = self.directory_cache.real_fh(fh);
match self.target.releasedir(req.info(), &path, real_fh, flags as u32) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
self.directory_cache.delete(fh);
}
fn fsyncdir(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
fh: u64,
datasync: bool,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("fsyncdir: {:?} (datasync: {:?})", path, datasync);
let real_fh = self.directory_cache.real_fh(fh);
match self.target.fsyncdir(req.info(), &path, real_fh, datasync) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
fn statfs(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
reply: fuser::ReplyStatfs,
) {
let path = if ino == 1 {
Arc::new(PathBuf::from("/"))
} else {
get_path!(self, ino, reply)
};
debug!("statfs: {:?}", path);
match self.target.statfs(req.info(), &path) {
Ok(statfs) => reply.statfs(
statfs.blocks,
statfs.bfree,
statfs.bavail,
statfs.files,
statfs.ffree,
statfs.bsize,
statfs.namelen,
statfs.frsize),
Err(e) => reply.error(e),
}
}
fn setxattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
name: &OsStr,
value: &[u8],
flags: i32,
position: u32,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("setxattr: {:?} {:?} ({} bytes, flags={:#x}, pos={:#x}",
path, name, value.len(), flags, position);
match self.target.setxattr(req.info(), &path, name, value, flags as u32, position) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
fn getxattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
name: &OsStr,
size: u32,
reply: fuser::ReplyXattr,
) {
let path = get_path!(self, ino, reply);
debug!("getxattr: {:?} {:?}", path, name);
match self.target.getxattr(req.info(), &path, name, size) {
Ok(Xattr::Size(size)) => {
debug!("getxattr: sending size {}", size);
reply.size(size)
},
Ok(Xattr::Data(vec)) => {
debug!("getxattr: sending {} bytes", vec.len());
reply.data(&vec)
},
Err(e) => {
debug!("getxattr: error {}", e);
reply.error(e)
},
}
}
fn listxattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
size: u32,
reply: fuser::ReplyXattr,
) {
let path = get_path!(self, ino, reply);
debug!("listxattr: {:?}", path);
match self.target.listxattr(req.info(), &path, size) {
Ok(Xattr::Size(size)) => {
debug!("listxattr: sending size {}", size);
reply.size(size)
},
Ok(Xattr::Data(vec)) => {
debug!("listxattr: sending {} bytes", vec.len());
reply.data(&vec)
}
Err(e) => reply.error(e),
}
}
fn removexattr(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("removexattr: {:?}, {:?}", path, name);
match self.target.removexattr(req.info(), &path, name) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
fn access(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
mask: i32,
reply: fuser::ReplyEmpty,
) {
let path = get_path!(self, ino, reply);
debug!("access: {:?}, mask={:#o}", path, mask);
match self.target.access(req.info(), &path, mask as u32) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
fn create(
&mut self,
req: &fuser::Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
_umask: u32, flags: i32,
reply: fuser::ReplyCreate,
) {
let parent_path = get_path!(self, parent, reply);
debug!("create: {:?}/{:?} (mode={:#o}, flags={:#x})", parent_path, name, mode, flags);
match self.target.create(req.info(), &parent_path, name, mode, flags as u32) {
Ok(create) => {
let (ino, generation) = self.inodes.add(Arc::new(parent_path.join(name)));
let attr = fuse_fileattr(create.attr, ino);
reply.created(&create.ttl, &attr, generation, create.fh, create.flags);
},
Err(e) => reply.error(e),
}
}
#[cfg(target_os = "macos")]
fn setvolname(
&mut self,
req: &fuser::Request<'_>,
name: &OsStr,
reply: fuser::ReplyEmpty,
) {
debug!("setvolname: {:?}", name);
match self.target.setvolname(req.info(), name) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e),
}
}
#[cfg(target_os = "macos")]
fn getxtimes(
&mut self,
req: &fuser::Request<'_>,
ino: u64,
reply: fuser::ReplyXTimes,
) {
let path = get_path!(self, ino, reply);
debug!("getxtimes: {:?}", path);
match self.target.getxtimes(req.info(), &path) {
Ok(xtimes) => {
reply.xtimes(xtimes.bkuptime, xtimes.crtime);
}
Err(e) => reply.error(e),
}
}
}