Skip to content

Commit

Permalink
feat: add FuseFs
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Jun 16, 2024
1 parent c47ba6c commit ad7b125
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ default = []
cli = ["merge", "clap"]
merge = ["dep:merge"]
clap = ["dep:clap"]
fuse = ["dep:fuse_mt"]
webdav = ["dep:dav-server", "dep:futures"]

[package.metadata.docs.rs]
Expand Down Expand Up @@ -97,6 +98,7 @@ merge = { version = "0.1.0", optional = true }

# vfs support
dav-server = { version = "0.5.8", default-features = false, optional = true }
fuse_mt = { version = "0.6", optional = true }
futures = { version = "0.3", optional = true }
runtime-format = "0.1.3"

Expand Down
9 changes: 9 additions & 0 deletions crates/core/src/vfs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod format;
#[cfg(feature = "fuse")]
mod fusefs;
#[cfg(feature = "webdav")]
mod webdavfs;

Expand All @@ -12,6 +14,8 @@ use bytes::{Bytes, BytesMut};
use runtime_format::FormatArgs;
use strum::EnumString;

#[cfg(feature = "fuse")]
pub use crate::vfs::fusefs::FuseFS;
#[cfg(feature = "webdav")]
/// A struct which enables `WebDAV` access to a [`Vfs`] using [`dav-server`]
pub use crate::vfs::webdavfs::WebDavFS;
Expand Down Expand Up @@ -390,6 +394,11 @@ impl Vfs {
Ok(result)
}

#[cfg(feature = "fuse")]
pub fn into_fuse_fs<P, S: IndexedFull>(self, repo: Repository<P, S>) -> FuseFS<P, S> {
FuseFS::new(repo, self)
}

#[cfg(feature = "webdav")]
/// Turn the [`Vfs`] into a [`WebDavFS`]
///
Expand Down
239 changes: 239 additions & 0 deletions crates/core/src/vfs/fusefs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
use super::Vfs;

#[cfg(not(windows))]
use std::os::unix::prelude::OsStrExt;
use std::{
collections::BTreeMap,
ffi::{CString, OsStr},
path::Path,
sync::RwLock,
time::{Duration, SystemTime},
};

use crate::{
repofile::{Node, NodeType},
IndexedFull, Repository,
};

use fuse_mt::{
CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultData,
ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice, ResultXattr, Xattr,
};
use itertools::Itertools;
use nix::libc;

use super::OpenFile;

pub struct FuseFS<P, S> {
repo: Repository<P, S>,
vfs: Vfs,
open_files: RwLock<BTreeMap<u64, OpenFile>>,
now: SystemTime,
}

impl<P, S: IndexedFull> FuseFS<P, S> {
pub(crate) fn new(repo: Repository<P, S>, vfs: Vfs) -> Self {
let open_files = RwLock::new(BTreeMap::new());

Self {
repo,
vfs,
open_files,
now: SystemTime::now(),
}
}

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 in bytes
size: node.meta.size,
/// Size in blocks
blocks: 0,
// Time of last access
atime: node.meta.atime.map(SystemTime::from).unwrap_or(now),
/// Time of last modification
mtime: node.meta.mtime.map(SystemTime::from).unwrap_or(now),
/// Time of last metadata change
ctime: node.meta.ctime.map(SystemTime::from).unwrap_or(now),
/// Time of creation (macOS only)
crtime: now,
/// Kind of file (directory, file, pipe, etc.)
kind: node_to_filetype(node),
/// Permissions
perm: node.meta.mode.unwrap_or(0o755) as u16,
/// Number of hard links
nlink: node.meta.links.try_into().unwrap_or(1),
/// User ID
uid: node.meta.uid.unwrap_or(0),
/// Group ID
gid: node.meta.gid.unwrap_or(0),
/// Device ID (if special file)
rdev: node_type_to_rdev(&node.node_type),
/// Flags (macOS only; see chflags(2))
flags: 0,
}
}

impl<P, S: IndexedFull> FilesystemMT for FuseFS<P, S> {
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 {
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(|(fh, _)| *fh + 1)
.unwrap_or(0);
_ = 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) {
if 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(),
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()
// convert into null-terminated [u8]
.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) => {
if size == 0 {
Ok(Xattr::Size(u32::try_from(attr.value.len()).unwrap()))
} else {
Ok(Xattr::Data(attr.value))
}
}
}
}
}

0 comments on commit ad7b125

Please sign in to comment.