Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cargo-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ jobs:
with:
command: test

- name: Run clippy on example
- name: Run clippy on examples
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path example/Cargo.toml --all-targets -- ${{matrix.deny_warnings}}
args: --package hello --package passthrufs --all-targets -- ${{matrix.deny_warnings}}

- name: Run smoke test
run: ./smoke_test.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
Cargo.lock
.idea/
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
[package]
name = "fuse_mt"
version = "0.6.1"
version = "0.7.0"
authors = ["William R. Fraser <wfraser@codewise.org>"]
repository = "https://github.com/wfraser/fuse-mt"
description = "A higher-level FUSE filesystem library with multi-threading and inode->path translation."
categories = ["filesystem"]
keywords = ["fuse", "filesystem"]
license = "MIT/Apache-2.0"
readme = "README.md"
edition = "2018"
edition = "2021"


[dependencies]
fuser = "0.13"
fuser = "0.14"
libc = "0.2"
log = "0.4"
threadpool = "1.8"

[workspace]
members = [".", "example"]
members = [".", "examples/hello", "examples/passthrough"]
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

This code is a wrapper on top of the Rust FUSE crate with the following additions:
* Dispatch system calls on multiple threads, so that e.g. I/O doesn't block directory listing.
* Translate inodes into paths, to simplify filesystem implementation.
* Optionally translate inodes into paths, to simplify filesystem implementation.

The `fuser` crate provides a minimal, low-level access to the FUSE kernel API, whereas this crate is more high-level, like the FUSE C API.

Expand All @@ -16,7 +16,8 @@ It includes a sample filesystem that uses the crate to pass all system calls thr
This is a work-in-progress. Bug reports, pull requests, and other feedback are welcome!

Some random notes on the implementation:
* The trait that filesystems will implement is called `FilesystemMT`, and instead of the FUSE crate's convention of having methods return void and including a "reply" parameter, the methods return their values. This feels more idiomatic to me. They also take `&Path` arguments instead of inode numbers.
* The trait that filesystems will implement is called `FilesystemMT<'_>`, and instead of the FUSE crate's convention of having methods return void and including a "reply" parameter, the methods return their values. This feels more idiomatic to me. They also take `&Path` arguments instead of inode numbers.
* If you do not wish fuse-mt to translate inode numbers into `&Path` but still retain the comfort of fuse-mt you may implement `FilesystemMT<'_, Inode, RawFileAttr>` and `RawFilesystemMT` instead. All methods will then receive `u64` as their input.
* Currently, only the following calls are dispatched to other threads:
* read
* write
Expand Down
11 changes: 11 additions & 0 deletions examples/hello/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "hello"
version = "0.1.0"
authors = ["not-jan"]
edition = "2021"
workspace = "../.."

[dependencies]
libc = "0.2"
log = "0.4"
fuse_mt = { path = "../.." }
9 changes: 9 additions & 0 deletions examples/hello/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
A simple test filesystem showcasing the unmanaged capabilities of fuse-mt.

It implements a filesystem that serves a single file: `hello.txt`.

Just enough fuse operations are implemented to interact with the file system using `ls`, `cat` and the likes.

To use it and test fuse_mt, run:

cargo run <mount point>
196 changes: 196 additions & 0 deletions examples/hello/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::time::{Duration, SystemTime};

use fuse_mt::{CallbackResult, DirectoryEntry, FileAttr, FilesystemMT, FileType, Inode, RawFileAttr, RawFilesystemMT, RequestInfo, ResultEmpty, ResultEntry, ResultInode, ResultOpen, ResultReaddir, ResultSlice, ResultStatfs, Statfs};

#[derive(Debug)]
struct HelloFS {
ttl: Duration,
bootup: SystemTime
}

impl HelloFS {
const ROOT_INODE: Inode = 1;
const HELLO_INODE: Inode = 2;
const HELLO_NAME: &'static str = "hello.txt";
const HELLO_CONTENT: &'static [u8] = b"Hello World, this is fuse-mt!\x0a";

fn root_attr(&self, uid: u32, gid: u32) -> FileAttr {
FileAttr {
size: 0,
blocks: 0,
atime: self.bootup,
mtime: self.bootup,
ctime: self.bootup,
crtime: self.bootup,
kind: FileType::Directory,
perm: 0o755,
nlink: 2,
uid,
gid,
rdev: 0,
flags: 0,
}
}

fn hello_attr(&self, uid: u32, gid: u32) -> FileAttr {
FileAttr {
size: Self::HELLO_CONTENT.len() as u64,
blocks: 1,
atime: self.bootup,
mtime: self.bootup,
ctime: self.bootup,
crtime: self.bootup,
kind: FileType::RegularFile,
perm: 0o755,
nlink: 1,
uid,
gid,
rdev: 0,
flags: 0,
}
}
}

impl FilesystemMT<'_, Inode, RawFileAttr> for HelloFS {
fn getattr(&self, req: RequestInfo, path: Inode, _fh: Option<u64>) -> ResultEntry<RawFileAttr> {
match path {
Self::ROOT_INODE => Ok((self.ttl, self.root_attr(req.uid, req.gid).as_raw(Self::ROOT_INODE, 0))),
Self::HELLO_INODE => Ok((self.ttl, self.hello_attr(req.uid, req.gid).as_raw(Self::HELLO_INODE, 0))),
_ => Err(libc::ENOENT)
}
}

fn open(&self, _req: RequestInfo, path: Inode, _flags: u32) -> ResultOpen {
match path {
Self::HELLO_INODE => Ok((2, 0)),
_ => Err(libc::ENOENT)
}
}

fn read(&self, _req: RequestInfo, path: Inode, _fh: u64, offset: u64, size: u32, callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult) -> CallbackResult {
let result = if path == Self::HELLO_INODE {
let start = offset as usize;
let end = start + size as usize;

Ok(&Self::HELLO_CONTENT[start..end])
} else {
Err(libc::ENOENT)
};

callback(result)
}

fn opendir(&self, _req: RequestInfo, path: Inode, _flags: u32) -> ResultOpen {
match path {
Self::ROOT_INODE => Ok((1, 0)),
_ => Err(libc::ENOTDIR)
}
}

fn readdir(&self, _req: RequestInfo, path: Inode, _fh: u64) -> ResultReaddir {
if path != 1 {
return Err(libc::ENOENT);
}

Ok(vec![
DirectoryEntry {
name: ".".into(),
kind: FileType::Directory,
},
DirectoryEntry {
name: "..".into(),
kind: FileType::Directory,
},
DirectoryEntry {
name: "hello.txt".into(),
kind: FileType::Directory,
}
])
}

fn releasedir(&self, _req: RequestInfo, _path: Inode, _fh: u64, _flags: u32) -> ResultEmpty {
Ok(())
}

fn statfs(&self, _req: RequestInfo, path: Inode) -> ResultStatfs {
if path != Self::ROOT_INODE {
return Err(libc::ENOENT);
}

Ok(Statfs {
blocks: 1,
bfree: 0,
bavail: 0,
files: 2,
ffree: 0,
bsize: 512,
namelen: 512,
frsize: 512,
})
}
}

impl RawFilesystemMT for HelloFS {
fn lookup(&self, req: RequestInfo, parent: Inode, name: &OsStr) -> ResultEntry<RawFileAttr> {
if parent != Self::ROOT_INODE {
return Err(libc::ENOENT);
}

if let Some(Self::HELLO_NAME) = name.to_str() {
let attr: RawFileAttr = self.hello_attr(req.uid, req.gid).as_raw(Self::HELLO_INODE, 0);

Ok((self.ttl, attr))
} else {
Err(libc::ENOENT)
}
}

fn forget(&self, _req: RequestInfo, _path: Inode, _nlookup: u64) {
}

fn parent(&self, _req: RequestInfo, path: Inode) -> ResultInode {
match path {
Self::ROOT_INODE | Self::HELLO_INODE => Ok(Self::ROOT_INODE),
_ => Err(libc::ENOENT)
}
}
}

struct ConsoleLogger;

impl log::Log for ConsoleLogger {
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
true
}

fn log(&self, record: &log::Record<'_>) {
println!("{}: {}: {}", record.target(), record.level(), record.args());
}

fn flush(&self) {}
}

static LOGGER: ConsoleLogger = ConsoleLogger;

fn main() {
log::set_logger(&LOGGER).unwrap();
log::set_max_level(log::LevelFilter::Debug);

let args: Vec<OsString> = env::args_os().collect();

if args.len() != 2 {
println!("usage: {} <mountpoint>", &env::args().next().unwrap());
std::process::exit(-1);
}

let filesystem = HelloFS {
ttl: Duration::from_secs(1),
bootup: SystemTime::now()
};

let fuse_args = [OsStr::new("-o"), OsStr::new("fsname=hellofs"), OsStr::new("-o"), OsStr::new("ro")];

fuse_mt::mount(fuse_mt::RawFuseMT::new(filesystem, 1), &args[1], &fuse_args[..]).unwrap();
}
4 changes: 2 additions & 2 deletions example/Cargo.toml → examples/passthrough/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name = "passthrufs"
version = "0.1.0"
authors = ["William R. Fraser <wfraser@codewise.org>"]
edition = "2018"
workspace = ".."
workspace = "../.."

[dependencies]
libc = "0.2"
log = "0.4"
fuse_mt = { path = ".." }
fuse_mt = { path = "../.." }
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl PassthroughFS {

const TTL: Duration = Duration::from_secs(1);

impl FilesystemMT for PassthroughFS {
impl FilesystemMT<'_> for PassthroughFS {
fn init(&self, _req: RequestInfo) -> ResultEmpty {
debug!("init");
Ok(())
Expand Down Expand Up @@ -250,7 +250,7 @@ impl FilesystemMT for PassthroughFS {
error!("seek({:?}, {}): {}", path, offset, e);
return callback(Err(e.raw_os_error().unwrap()));
}
match file.read(unsafe { mem::transmute(data.spare_capacity_mut()) }) {
match file.read(unsafe { mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(data.spare_capacity_mut()) }) {
Ok(n) => { unsafe { data.set_len(n) }; },
Err(e) => {
error!("read {:?}, {:#x} @ {:#x}: {}", path, size, offset, e);
Expand Down Expand Up @@ -331,8 +331,8 @@ impl FilesystemMT for PassthroughFS {
}

fn chown(&self, _req: RequestInfo, path: &Path, fh: Option<u64>, uid: Option<u32>, gid: Option<u32>) -> ResultEmpty {
let uid = uid.unwrap_or(::std::u32::MAX); // docs say "-1", but uid_t is unsigned
let gid = gid.unwrap_or(::std::u32::MAX); // ditto for gid_t
let uid = uid.unwrap_or(u32::MAX); // docs say "-1", but uid_t is unsigned
let gid = gid.unwrap_or(u32::MAX); // ditto for gid_t
debug!("chown: {:?} to {}:{}", path, uid, gid);

let result = if let Some(fd) = fh {
Expand Down Expand Up @@ -624,7 +624,7 @@ impl FilesystemMT for PassthroughFS {
if size > 0 {
let mut data = Vec::<u8>::with_capacity(size as usize);
let nread = libc_wrappers::llistxattr(
real, unsafe { mem::transmute(data.spare_capacity_mut()) })?;
real, unsafe { mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(data.spare_capacity_mut()) })?;
unsafe { data.set_len(nread) };
Ok(Xattr::Data(data))
} else {
Expand All @@ -641,7 +641,7 @@ impl FilesystemMT for PassthroughFS {
if size > 0 {
let mut data = Vec::<u8>::with_capacity(size as usize);
let nread = libc_wrappers::lgetxattr(
real, name.to_owned(), unsafe { mem::transmute(data.spare_capacity_mut()) })?;
real, name.to_owned(), unsafe { mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(data.spare_capacity_mut()) })?;
unsafe { data.set_len(nread) };
Ok(Xattr::Data(data))
} else {
Expand Down
Loading