Skip to content

Commit e9df3a7

Browse files
committed
Add option to use FUSE_DEV_IOC_CLONE.
This mimics functionality from libfuse. When enabled, it will create a new file descriptor for each fuse thread by opening /dev/fuse again and using the FUSE_DEV_IOC_CLONE ioctl to associate the file handle with the original filesystem. Signed-off-by: Andrew Peace <adpeace@amazon.com>
1 parent 30ed216 commit e9df3a7

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "mountpoint-s3-fuser"
33
description = "A fork of fuser - Filesystem in Userspace (FUSE) for Rust - only for use in Mountpoint for Amazon S3"
44
license = "MIT"
55
homepage = "https://github.com/awslabs/mountpoint-s3/tree/fuser/fork"
6-
version = "0.1.0"
6+
version = "0.1.1"
77
edition = "2021"
88
readme = "README.md"
99
keywords = ["fuse", "filesystem", "system", "bindings"]
@@ -21,7 +21,7 @@ page_size = "0.6.0"
2121
serde = { version = "1.0.102", features = ["std", "derive"], optional = true }
2222
smallvec = "1.6.1"
2323
zerocopy = { version = "0.8", features = ["derive"] }
24-
nix = { version = "0.29.0", features = ["fs", "user"] }
24+
nix = { version = "0.29.0", features = ["fs", "user", "ioctl"] }
2525

2626
[dev-dependencies]
2727
env_logger = "0.11.3"

src/channel.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
fs::File,
2+
fs::{File, OpenOptions},
33
io,
44
os::{
55
fd::{AsFd, BorrowedFd},
@@ -8,9 +8,21 @@ use std::{
88
sync::Arc,
99
};
1010

11+
use crate::reply::ReplySender;
1112
use libc::{c_int, c_void, size_t};
13+
use nix::ioctl_read;
14+
use nix::Result as NixResult;
1215

13-
use crate::reply::ReplySender;
16+
/// FUSE_DEV_IOC_CLONE ioctl constant matching the constant defined by the FUSE kernel module in fuse.h
17+
const IOCTL_FUSE_DEV_IOC_CLONE: u8 = 229;
18+
19+
ioctl_read!(
20+
/// Ioctl to clone a fuse session onto a new file handle
21+
fuse_dev_ioc_clone,
22+
IOCTL_FUSE_DEV_IOC_CLONE,
23+
0,
24+
libc::c_int
25+
);
1426

1527
/// A raw communication channel to the FUSE kernel driver
1628
#[derive(Debug)]
@@ -30,6 +42,31 @@ impl Channel {
3042
Self(device)
3143
}
3244

45+
/// Create a worker channel by opening a new /dev/fuse file descriptor and
46+
/// associating it with the session using FUSE_DEV_IOC_CLONE ioctl.
47+
pub fn clone_channel(&self) -> io::Result<Self> {
48+
let worker_file = OpenOptions::new()
49+
.read(true)
50+
.write(true)
51+
.open("/dev/fuse")?;
52+
let worker_fd = worker_file.as_raw_fd();
53+
let mut session_fd = self.0.as_raw_fd();
54+
55+
// Associate the worker fd with the session fd using FUSE_DEV_IOC_CLONE
56+
// SAFETY: `session_fd` is a valid open file descriptor. The ioctl
57+
// FUSE_DEV_IOC_CLONE expects a pointer to an int containing the fd
58+
// to clone from. The pointer is valid for the duration of the call.
59+
let result: NixResult<libc::c_int> = unsafe {
60+
let val = &mut session_fd as *mut libc::c_int;
61+
fuse_dev_ioc_clone(worker_fd, val)
62+
};
63+
64+
if let Err(err) = result {
65+
return Err(io::Error::new(io::ErrorKind::Other, err));
66+
}
67+
Ok(Self(Arc::new(worker_file)))
68+
}
69+
3370
/// Receives data up to the capacity of the given buffer (can block).
3471
pub fn receive(&self, buffer: &mut [u8]) -> io::Result<usize> {
3572
let rc = unsafe {

src/session.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,47 @@ impl<FS: Filesystem> Session<FS> {
141141
/// Run the session loop that receives kernel requests and dispatches them to method
142142
/// calls into the filesystem.
143143
pub fn run(&self) -> io::Result<()> {
144-
self.run_with_callbacks(|_| {}, |_| {})
144+
self.run_with_callbacks(|_| {}, |_| {}, false)
145145
}
146146

147147
/// Run the session loop that receives kernel requests and dispatches them to method
148148
/// calls into the filesystem.
149149
/// This version also notifies callers of kernel requests before and after they
150150
/// are dispatched to the filesystem.
151-
pub fn run_with_callbacks<FA, FB>(&self, mut before_dispatch: FB, mut after_dispatch: FA) -> io::Result<()>
152-
where
151+
pub fn run_with_callbacks<FA, FB>(
152+
&self,
153+
before_dispatch: FB,
154+
after_dispatch: FA,
155+
clone_fuse_fd: bool,
156+
) -> io::Result<()>
157+
where
158+
FB: FnMut(&Request<'_>),
159+
FA: FnMut(&Request<'_>),
160+
{
161+
info!(
162+
"FUSE channel cloning: {}",
163+
if clone_fuse_fd { "enabled" } else { "disabled" }
164+
);
165+
166+
if clone_fuse_fd {
167+
// Create a worker channel for this thread using FUSE_DEV_IOC_CLONE
168+
// This allows multiple threads to read from the FUSE device without contention
169+
let worker_channel = self.ch.clone_channel()?;
170+
self.process_requests(&worker_channel, before_dispatch, after_dispatch)
171+
} else {
172+
// Use the original channel without cloning
173+
self.process_requests(&self.ch, before_dispatch, after_dispatch)
174+
}
175+
}
176+
177+
/// Process requests using the given channel
178+
fn process_requests<FA, FB>(
179+
&self,
180+
channel: &Channel,
181+
mut before_dispatch: FB,
182+
mut after_dispatch: FA,
183+
) -> io::Result<()>
184+
where
153185
FB: FnMut(&Request<'_>),
154186
FA: FnMut(&Request<'_>),
155187
{
@@ -163,8 +195,8 @@ impl<FS: Filesystem> Session<FS> {
163195
loop {
164196
// Read the next request from the given channel to kernel driver
165197
// The kernel driver makes sure that we get exactly one request per read
166-
match self.ch.receive(buf) {
167-
Ok(size) => match Request::new(self.ch.sender(), &buf[..size]) {
198+
match channel.receive(buf) {
199+
Ok(size) => match Request::new(channel.sender(), &buf[..size]) {
168200
// Dispatch request
169201
Some(req) => {
170202
before_dispatch(&req);

0 commit comments

Comments
 (0)