#[cfg(not(windows))]
use std::os::unix::prelude::OsStrExt;
use std::{
collections::BTreeMap,
ffi::{CString, OsStr},
path::Path,
sync::RwLock,
time::{Duration, SystemTime},
};
use rustic_core::{
repofile::{Node, NodeType},
vfs::{FilePolicy, OpenFile, Vfs},
};
use fuse_mt::{
CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultData,
ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice, ResultXattr, Xattr,
};
use itertools::Itertools;
use crate::repository::IndexedRepo;
pub struct FuseFS {
repo: IndexedRepo,
vfs: Vfs,
open_files: RwLock<BTreeMap<u64, OpenFile>>,
now: SystemTime,
file_policy: FilePolicy,
}
impl FuseFS {
pub(crate) fn new(repo: IndexedRepo, vfs: Vfs, file_policy: FilePolicy) -> Self {
let open_files = RwLock::new(BTreeMap::new());
Self {
repo,
vfs,
open_files,
now: SystemTime::now(),
file_policy,
}
}
fn node_from_path(&self, path: &Path) -> Result<Node, i32> {
self.vfs
.node_from_path(&self.repo, path)
.map_err(|_| libc::ENOENT)
}
fn dir_entries_from_path(&self, path: &Path) -> Result<Vec<Node>, i32> {
self.vfs
.dir_entries_from_path(&self.repo, path)
.map_err(|_| libc::ENOENT)
}
}
fn node_to_filetype(node: &Node) -> FileType {
match node.node_type {
NodeType::File => FileType::RegularFile,
NodeType::Dir => FileType::Directory,
NodeType::Symlink { .. } => FileType::Symlink,
NodeType::Chardev { .. } => FileType::CharDevice,
NodeType::Dev { .. } => FileType::BlockDevice,
NodeType::Fifo => FileType::NamedPipe,
NodeType::Socket => FileType::Socket,
}
}
fn node_type_to_rdev(tpe: &NodeType) -> u32 {
u32::try_from(match tpe {
NodeType::Dev { device } | NodeType::Chardev { device } => *device,
_ => 0,
})
.unwrap()
}
fn node_to_linktarget(node: &Node) -> Option<&OsStr> {
if node.is_symlink() {
Some(node.node_type.to_link().as_os_str())
} else {
None
}
}
fn node_to_file_attr(node: &Node, now: SystemTime) -> FileAttr {
FileAttr {
size: node.meta.size,
blocks: 0,
atime: node.meta.atime.map(SystemTime::from).unwrap_or(now),
mtime: node.meta.mtime.map(SystemTime::from).unwrap_or(now),
ctime: node.meta.ctime.map(SystemTime::from).unwrap_or(now),
crtime: now,
kind: node_to_filetype(node),
perm: node.meta.mode.unwrap_or(0o755) as u16,
nlink: node.meta.links.try_into().unwrap_or(1),
uid: node.meta.uid.unwrap_or(0),
gid: node.meta.gid.unwrap_or(0),
rdev: node_type_to_rdev(&node.node_type),
flags: 0,
}
}
impl FilesystemMT for FuseFS {
fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option<u64>) -> ResultEntry {
let node = self.node_from_path(path)?;
Ok((Duration::from_secs(1), node_to_file_attr(&node, self.now)))
}
#[cfg(not(windows))]
fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData {
let target = node_to_linktarget(&self.node_from_path(path)?)
.ok_or(libc::ENOSYS)?
.as_bytes()
.to_vec();
Ok(target)
}
fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
if matches!(self.file_policy, FilePolicy::Forbidden) {
return Err(libc::ENOTSUP);
}
let node = self.node_from_path(path)?;
let open = self.repo.open_file(&node).map_err(|_| libc::ENOSYS)?;
let fh = {
let mut open_files = self.open_files.write().unwrap();
let fh = open_files.last_key_value().map_or(0, |(fh, _)| *fh + 1);
_ = open_files.insert(fh, open);
fh
};
Ok((fh, 0))
}
fn release(
&self,
_req: RequestInfo,
_path: &Path,
fh: u64,
_flags: u32,
_lock_owner: u64,
_flush: bool,
) -> ResultEmpty {
_ = self.open_files.write().unwrap().remove(&fh);
Ok(())
}
fn read(
&self,
_req: RequestInfo,
_path: &Path,
fh: u64,
offset: u64,
size: u32,
callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult,
) -> CallbackResult {
if let Some(open_file) = self.open_files.read().unwrap().get(&fh)
&& let Ok(data) =
self.repo
.read_file_at(open_file, offset.try_into().unwrap(), size as usize)
{
return callback(Ok(&data));
}
callback(Err(libc::ENOSYS))
}
fn opendir(&self, _req: RequestInfo, _path: &Path, _flags: u32) -> ResultOpen {
Ok((0, 0))
}
fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir {
let nodes = self.dir_entries_from_path(path)?;
let result = nodes
.into_iter()
.map(|node| DirectoryEntry {
name: node.name().to_os_string(),
kind: node_to_filetype(&node),
})
.collect();
Ok(result)
}
fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty {
Ok(())
}
fn listxattr(&self, _req: RequestInfo, path: &Path, size: u32) -> ResultXattr {
let node = self.node_from_path(path)?;
let xattrs = node
.meta
.extended_attributes
.into_iter()
.map(|a| CString::new(a.name).unwrap().into_bytes_with_nul())
.concat();
if size == 0 {
Ok(Xattr::Size(u32::try_from(xattrs.len()).unwrap()))
} else {
Ok(Xattr::Data(xattrs))
}
}
fn getxattr(&self, _req: RequestInfo, path: &Path, name: &OsStr, size: u32) -> ResultXattr {
let node = self.node_from_path(path)?;
match node
.meta
.extended_attributes
.into_iter()
.find(|a| name == OsStr::new(&a.name))
{
None => Err(libc::ENOSYS),
Some(attr) => {
let value = attr.value.unwrap_or_default();
if size == 0 {
Ok(Xattr::Size(u32::try_from(value.len()).unwrap()))
} else {
Ok(Xattr::Data(value))
}
}
}
}
}