Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fd7f2af
Clippy says it's better to inline the arguments of format strings: [h…
rarensu Sep 6, 2025
063b1b5
Clippy says it's better to use backticks in doc strings: [https://rus…
rarensu Sep 6, 2025
aae61c2
Clippy says these `else` blocks are redundant: [https://rust-lang.gi…
rarensu Sep 6, 2025
3d35b03
Clippy says the logic reads better the other way around: [https://rus…
rarensu Sep 6, 2025
94251f0
Clippy says it's better to borrow with the `&` when possible: [https:…
rarensu Sep 6, 2025
cfb5ebc
Clippy says it's better to use a semicolon to avoid giving the impres…
rarensu Sep 6, 2025
394313d
Consts go at the top, but I can't remember why.
rarensu Sep 6, 2025
10327ab
Clippy says it's better to use methods directly, rather than construc…
rarensu Sep 6, 2025
580bc9f
Clippy says it's better to combine match arms that lead to the same o…
rarensu Sep 6, 2025
dfc1251
Clippy says `from` is preferred to casts, when available: [https://ru…
rarensu Sep 6, 2025
d9f8da6
It's safer to define the block size as u32 and cast up to u64 as needed.
rarensu Sep 6, 2025
0fecd68
These `check_*` helper functions don't borrow anything from self, so …
rarensu Sep 6, 2025
4832757
This `write_directory_content` function copies but doesn't move the e…
rarensu Sep 6, 2025
41254b4
Clippy says that functions with the potential to error or panic shoul…
rarensu Sep 6, 2025
be6f033
Repaired incorrect doc strings, likely the result of copy and paste e…
rarensu Sep 6, 2025
21588e9
Additional documentation and comments where I felt they were appropri…
rarensu Sep 6, 2025
80cba01
Removed the underscores in the main Filesystem trait methods. The def…
rarensu Sep 6, 2025
e1ba845
Underscore is better than an attribute for just one variable.
rarensu Sep 6, 2025
39b1b98
Cleanup a few lines that are no longer relevant since abi-7-11 and fr…
rarensu Sep 6, 2025
18afd73
Inserted some missing feature gates on abi-specific blocks (`DirPlus`…
rarensu Sep 6, 2025
39ac066
Renamed `foobar` because I don't think it's very professional.
rarensu Sep 6, 2025
297f493
The log import clippy attribute is better expressed as `allow_unused`…
rarensu Sep 6, 2025
cbf46c4
Removed the underscore from some variables that weren't actually unused.
rarensu Sep 6, 2025
618c398
Corrected a bug in `simple.fs` where it would let you try to use dire…
rarensu Sep 6, 2025
d1876c1
Clippy says it's better to use a semicolon to avoid giving the impres…
rarensu Sep 6, 2025
a6baf7e
Clippy says it's better to use backticks in doc strings: [https://rus…
rarensu Sep 6, 2025
5710791
Clippy says `from` is preferred to casts, when available: [https://ru…
rarensu Sep 6, 2025
ab1c67d
Fixed a bug where the unit test for Init would fail for certain abi v…
rarensu Sep 6, 2025
b45c751
Clippy says `map` and `unwrap_or` can be combined as `map_or`.
rarensu Sep 6, 2025
cc3f322
Fixed a bug where the documentation was missing from the `NO_XATTR` c…
rarensu Sep 6, 2025
0d96a55
Clippy says it's better to write long constants with underscore separ…
rarensu Sep 6, 2025
001d73e
Promoted some important structs to `public(crate)` for easier testing…
rarensu Sep 6, 2025
690068c
Fixed a bug where the passthrough test would fail on macOS simply bec…
rarensu Sep 6, 2025
ab428ea
Fixed a bug where the application would panic while exiting normally …
rarensu Sep 6, 2025
51370d8
Clippy says it's better to apply the `#[must_use]` attribute when a f…
rarensu Sep 6, 2025
befdc6d
Updated the README to reflect that the fact that we actually have don…
rarensu Sep 6, 2025
364a636
Standarized the derived attributes to improve readability and make fu…
rarensu Sep 6, 2025
4cdd9f3
Relocated comment to improve readability.
rarensu Sep 6, 2025
6f61e5e
Fixed a bug where an application naively implementing the Poll functi…
rarensu Sep 6, 2025
a9e591f
This import is better expressed more specifically, to make future reo…
rarensu Sep 6, 2025
93b386e
New example `ioctl_client.rs` which performs a useful test of the ioc…
rarensu Sep 6, 2025
bcc3433
Reorganized reply test data for better readability and code duplicati…
rarensu Sep 6, 2025
8a8a142
Added a reply test for directory plus.
rarensu Sep 6, 2025
3d02edc
Renamed an incorrectly named reply test. This test includes a thread,…
rarensu Sep 6, 2025
74b1c20
Clippy says it's more efficient to copy a `u32` than to borrow it: [h…
rarensu Sep 6, 2025
3887b18
Added clippy allow attributes on a variety of blocks. I have done my …
rarensu Sep 6, 2025
646257d
Clippy says MacOS belongs in backticks. I don't really agree, but I g…
rarensu Sep 6, 2025
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ name = "notify_inval_inode"
[[example]]
name = "ioctl"

[[example]]
name = "ioctl_client"

[[example]]
name = "passthrough"
required-features = ["abi-7-40"]
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ of continuing development. In particular adding features from ABIs after 7.19

A working FUSE filesystem consists of three parts:

1. The **kernel driver** that registers as a filesystem and forwards operations into a communication channel to a userspace process that handles them.
1. The **userspace library** (libfuse) that helps the userspace process to establish and run communication with the kernel driver.
1. The **userspace implementation** that actually processes the filesystem operations.
1. The **kernel driver** (part of the operating system) that registers as a filesystem and forwards operations into a communication channel to a userspace process that handles them.
1. The **userspace library** (e.g., `fuser` and/or `libfuse`) that helps the userspace process to establish and run communication with the kernel driver.
1. The **userspace implementation** (your code here) that actually processes the filesystem operations.

The kernel driver is provided by the FUSE project, the userspace implementation needs to be provided by the developer. FUSE-Rust provides a replacement for the libfuse userspace library between these two. This way, a developer can fully take advantage of the Rust type interface and runtime features when building a FUSE filesystem in Rust.

Expand Down Expand Up @@ -61,10 +61,11 @@ sudo apt-get install libfuse-dev pkg-config
sudo yum install fuse-devel pkgconfig
```

### macOS (untested)
### macOS

Install [FUSE for macOS], which can be obtained from their website or installed using the Homebrew or Nix package managers. macOS version 10.9 or later is required. If you are using a Mac with Apple Silicon, you must also [enable support for third party kernel extensions][enable kext].

Note: Testing on macOS is only done infrequently. If you experience difficulties, please create an issue.

#### To install using Homebrew

Expand Down Expand Up @@ -107,22 +108,45 @@ fuser = "0.15"

To create a new filesystem, implement the trait `fuser::Filesystem`. See the [documentation] for details or the `examples` directory for some basic examples.

### Feature Gates

The crate uses feature gates to manage optional functionality and dependencies. Some key features include:
* **`abi-7-x`**: A set of features to select the FUSE protocol version. Recommended to select the highest version.
* **`libfuse`**: Use libfuse bindings for some very low-level operations. An older alternative to the newer Rust-native implementations.
* **`serializable`**: Enable conversion between `fuser` data structures and raw bytes, for saving to disk or transmission over a network.

## To Do

Most features of libfuse up to 3.10.3 are implemented. Feel free to contribute. See the [list of issues][issues] on GitHub and search the source files for comments containing "`TODO`" or "`FIXME`" to see what's still missing.

## Compatibility

Developed and tested on Linux. Tested under [Linux][FUSE for Linux] and [FreeBSD][FUSE for FreeBSD] using stable [Rust] (see CI for details).
Developed and automatically tested on Linux. Tested under [Linux][FUSE for Linux] and [FreeBSD][FUSE for FreeBSD] using stable [Rust] (see CI for details). Infrequently, manually tested for MacFUSE on MacOS.

## License

Licensed under [MIT License](LICENSE.md), except for those files in `examples/` that explicitly contain a different license.

## Contribution
## Contributing

Fork, hack, submit pull request. Make sure to make it useful for the target audience, keep the project's philosophy and Rust coding standards in mind. For larger or essential changes, you may want to open an issue for discussion first. Also remember to update the [Changelog] if your changes are relevant to the users.

### Concepts

A brief overview of Fuser concepts for new contributors.

* **`Session`**: The core struct which saves configuration options. Its provides methods to start and end event handling loops.
* **`Request`** and **`Reply`**: These structures represents one FUSE operation initiated by the Kernel. The Request methods handle unpacks this message, and directs it to the filesystem. The Reply methods packege the response and pass it back to the kernel.
* **`Notification`**: This structure represents a message for the Kernel initiated by the User application (i.e., not in response to a `Request`).
* **`Filesystem`**: User application code.

### Subdirectories

A bried overview of repository organization for new contributors.

* **`src/mnt/`**: Code for establishing communication with the fuse device, which is called mounting.
* **`src/ll/`**: The low-level FUSE message interface. This module contains the raw FUSE ABI definitions and is responsible for the translating between Rust-based data structures and byte-based fuse kernel messages. It is not recommended for applications to use this code directly.

[Rust]: https://rust-lang.org
[Homebrew]: https://brew.sh
[Changelog]: https://keepachangelog.com/en/1.0.0/
Expand All @@ -134,4 +158,4 @@ Fork, hack, submit pull request. Make sure to make it useful for the target audi
[FUSE for Linux]: https://github.com/libfuse/libfuse/
[FUSE for macOS]: https://macfuse.github.io
[enable kext]: https://github.com/macfuse/macfuse/wiki/Getting-Started#enabling-support-for-third-party-kernel-extensions-apple-silicon-macs
[FUSE for FreeBSD]: https://wiki.freebsd.org/FUSEFS
[FUSE for FreeBSD]: https://wiki.freebsd.org/FUSEFS
4 changes: 2 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn main() {
if pkg_config::Config::new()
.atleast_version("2.6.0")
.probe("fuse") // for macFUSE 4.x
.map_err(|e| eprintln!("{}", e))
.map_err(|e| eprintln!("{e}"))
.is_ok()
{
println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\"");
Expand All @@ -27,7 +27,7 @@ fn main() {
pkg_config::Config::new()
.atleast_version("2.6.0")
.probe("osxfuse") // for osxfuse 3.x
.map_err(|e| eprintln!("{}", e))
.map_err(|e| eprintln!("{e}"))
.unwrap();
println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\"");
}
Expand Down
8 changes: 8 additions & 0 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ impl Filesystem for HelloFS {
}
}

// cast is safe because max read < max usize
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn read(
&mut self,
_req: &Request,
Expand All @@ -84,6 +86,12 @@ impl Filesystem for HelloFS {
}
}

// cast is safe because the sign of an offset has no meaning
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn readdir(
&mut self,
_req: &Request,
Expand Down
20 changes: 14 additions & 6 deletions examples/ioctl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This example requires fuse 7.11 or later. Run with:
//
// cargo run --example ioctl --features abi-7-11 /tmp/foobar
// cargo run --example ioctl DIR

use clap::{Arg, ArgAction, Command, crate_version};
use fuser::{
Expand All @@ -14,6 +14,9 @@ use std::time::{Duration, UNIX_EPOCH};

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

const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::<usize>());
const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::<usize>());

struct FiocFS {
content: Vec<u8>,
root_attr: FileAttr,
Expand Down Expand Up @@ -86,6 +89,8 @@ impl Filesystem for FiocFS {
}
}

// Max read < max i64
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn read(
&mut self,
_req: &Request,
Expand All @@ -98,12 +103,18 @@ impl Filesystem for FiocFS {
reply: ReplyData,
) {
if ino == 2 {
reply.data(&self.content[offset as usize..])
reply.data(&self.content[offset as usize..]);
} else {
reply.error(ENOENT);
}
}

// the sign of an offset has no meaning
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn readdir(
&mut self,
_req: &Request,
Expand Down Expand Up @@ -148,9 +159,6 @@ impl Filesystem for FiocFS {
return;
}

const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::<usize>());
const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::<usize>());

match cmd.into() {
FIOC_GET_SIZE => {
let size_bytes = self.content.len().to_ne_bytes();
Expand All @@ -162,7 +170,7 @@ impl Filesystem for FiocFS {
reply.ioctl(0, &[]);
}
_ => {
debug!("unknown ioctl: {}", cmd);
debug!("unknown ioctl: {cmd}");
reply.error(EINVAL);
}
}
Expand Down
151 changes: 151 additions & 0 deletions examples/ioctl_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// A small client to exercise the ioctl example filesystem.
//
// Usage:
// 1) In one shell, mount the example:
// cargo run --example ioctl DIR
// 2) In another shell, run this client to test setting/getting size:
// cargo run --example ioctl_client DIR
//
// Ioctl actions:
// - GET size
// - SET size to 4096
// - GET size (expect 4096)
// - SET size to 0
// - GET size (expect 0)

#![allow(clippy::cast_possible_truncation)]

use clap::{Arg, Command, crate_version};
use std::fs::OpenOptions;
use std::io::{self, Write};
use std::os::fd::AsRawFd;
use std::path::PathBuf;

// Generate wrappers matching examples/ioctl.rs
nix::ioctl_read!(ioctl_get_size, 'E', 0, usize);
nix::ioctl_write_ptr!(ioctl_set_size, 'E', 1, usize);

// Helper to GET size
fn get_size(fd: i32) -> nix::Result<usize> {
let mut out: usize = 0;
unsafe {
ioctl_get_size(fd, &raw mut out)?;
}
Ok(out)
}

// Helper to SET size
fn set_size(fd: i32, sz: usize) -> nix::Result<()> {
let mut val = sz;
unsafe {
ioctl_set_size(fd, &raw mut val)?;
}
Ok(())
}

fn main() -> io::Result<()> {
let matches = Command::new("ioctl_client")
.version(crate_version!())
.author("Richard Lawrence")
.arg(
Arg::new("IOCTL_MOUNT_POINT")
.required(true)
.index(1)
.help("Test an example Ioctl filesystem mounted at the given path"),
)
.get_matches();
env_logger::init();
let mountpoint = matches.get_one::<String>("MOUNT_POINT").unwrap();
let dir = PathBuf::from(mountpoint);
assert!(
dir.is_dir(),
"MOUNT_POINT does not look like a valid directory"
);
// Open the example file in the current directory mount
let f = OpenOptions::new()
.read(true)
.write(true)
.open(dir.join("fioc"))
.expect("File `fioc` not opened. (Are you sure Ioctl is mounted correctly?)");
let fd = f.as_raw_fd();

// 1) Read current size
match get_size(fd) {
Ok(sz) => println!("Initial size: {sz} bytes"),
Err(e) => {
eprintln!("FIOC_GET_SIZE failed: {e}");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("ioctl get failed: {e}"),
));
}
}

// 2) Set size to 4096
if let Err(e) = set_size(fd, 4096) {
eprintln!("FIOC_SET_SIZE(4096) failed: {e}");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("ioctl set failed: {e}"),
));
}
println!("Set size to 4096 bytes");

// 3) Get size and expect 4096
match get_size(fd) {
Ok(sz) => {
println!("After set(4096), size: {sz} bytes");
if sz != 4096 {
eprintln!("Unexpected size after set(4096): got {sz}, expected 4096");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("size mismatch after set(4096): {sz}"),
));
}
}
Err(e) => {
eprintln!("FIOC_GET_SIZE failed after set(4096): {e}");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("ioctl get failed: {e}"),
));
}
}

// 4) Set size to 0
if let Err(e) = set_size(fd, 0) {
eprintln!("FIOC_SET_SIZE(0) failed: {e}");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("ioctl set failed: {e}"),
));
}
println!("Set size to 0 bytes");

// 5) Get size and expect 0
match get_size(fd) {
Ok(sz) => {
println!("After set(0), size: {sz} bytes");
if sz != 0 {
eprintln!("Unexpected size after set(0): got {sz}, expected 0");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("size mismatch after set(0): {sz}"),
));
}
}
Err(e) => {
eprintln!("FIOC_GET_SIZE failed after set(0): {e}");
return Err(io::Error::new(
io::ErrorKind::Other,
format!("ioctl get failed: {e}"),
));
}
}

// Write a newline to stdout to flush buffered output (looks better in some environments)
io::stdout().flush().ok();

println!("ioctl_client completed successfully.");
Ok(())
}
4 changes: 2 additions & 2 deletions examples/notify_inval_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ fn now_filename() -> String {
format!("Time_is_{}", d.as_secs())
}

#[derive(Parser)]
#[derive(Parser, Debug)]
struct Options {
/// Mount demo filesystem at given path
mount_point: String,
Expand Down Expand Up @@ -174,7 +174,7 @@ fn main() {
if opts.only_expire {
// fuser::notify_expire_entry(_SOME_HANDLE_, FUSE_ROOT_ID, &oldname);
} else if let Err(e) = notifier.inval_entry(FUSE_ROOT_ID, oldname.as_ref()) {
eprintln!("Warning: failed to invalidate entry '{}': {}", oldname, e);
eprintln!("Warning: failed to invalidate entry '{oldname}': {e}");
}
}
thread::sleep(Duration::from_secs_f32(opts.update_interval));
Expand Down
Loading