Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a0c9369
core/async-io: let the application select the runtime type
Kranzes Oct 10, 2026
57e9828
fusedev: wait for requests instead of spinning on the tokio runtime
Kranzes Oct 10, 2026
c88efc2
core: let a server take over an initialized connection
Kranzes Oct 10, 2026
92dc2e7
fusedev: test async lookups, notifications and takeover on tokio
Kranzes Oct 10, 2026
a51ee32
fusedev: stop an async worker on a read error it can't retry
Kranzes Oct 19, 2026
c48258b
fusedev: support Linux kernel FUSE passthrough
Kranzes Oct 10, 2026
370cc32
core: return a backing id from async_open and async_create
Kranzes Oct 10, 2026
7eeca70
tests: cover kernel passthrough through the async server
Kranzes Oct 10, 2026
075d5b7
passthrough: support kernel FUSE passthrough
Kranzes Oct 13, 2026
74ae043
passthrough: let a shared file system use kernel passthrough
Kranzes Oct 14, 2026
bda7c12
benchmark: add --kernel-passthrough and --no-writeback to the daemon
Kranzes Oct 13, 2026
bdcd37c
tests: skip the kernel passthrough tests without CAP_SYS_ADMIN
Kranzes Oct 14, 2026
b6233e3
passthrough: don't hold the backing id lock across the ioctl
Kranzes Oct 14, 2026
0bd2b4f
docs: record kernel passthrough in the changelog
Kranzes Oct 14, 2026
4cafae2
passthrough: correct the takeover note of set_backing_files()
Kranzes Oct 14, 2026
a54cea1
passthrough: give an O_DIRECT create DIRECT_IO like an open
Kranzes Oct 14, 2026
26da2d4
passthrough: drop DIRECT_IO from passthrough opens
Kranzes Oct 14, 2026
cf4c814
passthrough: open without passthrough when the kernel refuses a backi…
Kranzes Oct 19, 2026
dadbae4
passthrough: turn kernel passthrough off when init() doesn't enable it
Kranzes Oct 19, 2026
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
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@
FUSE-over-io_uring transport (kernel 6.14+). Both join the `FuseServing` interface, so a daemon
can pick its transport at runtime behind `Box<dyn FuseServing>` and fall back to the classic
transport on `Error::UringNotSupported`.
- `async_runtime::set_runtime_type()` selects the asynchronous runtime type in code, because
setting `FUSE_BACKEND_RS_ASYNC_RUNTIME` from a running program isn't safe.
`async_runtime::runtime_type()` returns the type in effect, and `RuntimeType` is now public.
- `Server::init_state()` and `Server::restore_init()` let a new server take over a connection
that another server initialized. `abi::fuse_abi::UNIQUE_RESEND` is the bit that the kernel
sets in requests it sends again after `notify_resend()`.
- Linux kernel FUSE passthrough, from Linux 6.9 on. `FsOptions::PASSTHROUGH` and
`OpenOptions::PASSTHROUGH` enable it, and the fusedev transport's `fuse_backing_open()` and
`fuse_backing_close()` register and release backing files. `Server::set_max_stack_depth()` sets
the depth sent in the INIT reply, which a backing file on overlayfs needs. In the ABI,
`BackingMap` is the argument of the backing ioctl.
- `PassthroughFs::set_backing_files()` and the `BackingFiles` trait make `PassthroughFs` use
kernel passthrough. `init` leaves it off when the kernel doesn't offer it. It also stays off
with the writeback cache, `no_open`, `seal_size`, `allow_direct_io = false`, and the `Never`
and `Metadata` cache policies. With the `async-io` feature, `PassthroughFs::into_shared()` shares a file system
created with `new()`, so the blocking thread pool can use passthrough too.
- [269](https://github.com/cloud-hypervisor/fuse-backend-rs/pull/269): `AsyncFileSystem` methods default to calling their `FileSystem`
counterparts, so a file system only implements the handlers it serves asynchronously.
- [269](https://github.com/cloud-hypervisor/fuse-backend-rs/pull/269): `FuseSession::detach()` lazily unmounts a Linux session through `&self`,
Expand All @@ -49,6 +65,18 @@
- The FUSE-over-io_uring transport answers opcodes it doesn't know with ENOSYS instead of EIO.
Kernels that send `FUSE_STATX` only fall back to `FUSE_GETATTR` after ENOSYS, so `statx()`
calls that ask for more than the basic fields, like the birth time `stat(1)` shows, failed.
- `AsyncFileSystem::async_open()` and `AsyncFileSystem::async_create()` return the backing id of
kernel passthrough as the last element of their tuple, like `FileSystem::open()` and
`FileSystem::create()`. The default methods forward the id from the sync handlers.
Implementations that override them need to add it, and `None` keeps the old behavior.
- `InitOut` has a `max_stack_depth` field, which takes one of its `unused` words, so `unused` is
now `[u32; 6]`.
- With the tokio runtime, `FuseDevTask` no longer retries reads in a loop. It waits for requests
through the IO driver of the tokio runtime that polls it, and checks its state flag every 100ms
while idle. Polling it on a tokio runtime without IO enabled panics. `fuse-backend-fusedev`
needs tokio 1.53.3.
- The async fusedev server retries a read that fails with `ENOENT` and stops the task on read
errors it doesn't know, like the synchronous server. It used to log them and read again.
- [254](https://github.com/cloud-hypervisor/fuse-backend-rs/pull/254): `fuse-backend-rs` is now a thin facade re-exporting the sub-crates. Every
historical `fuse_backend_rs::{abi, api, buffer, common, transport, passthrough,
overlayfs}` import path and cargo feature name (`fusedev`, `virtiofs`,
Expand Down
49 changes: 48 additions & 1 deletion crates/fuse-backend-core/src/abi/fuse_abi_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ const FOPEN_CACHE_DIR: u32 = 8;
/// the file is stream-like (no file position at all)
const FOPEN_STREAM: u32 = 16;

/// Pass reads, writes and mmap through to the backing file.
const FOPEN_PASSTHROUGH: u32 = 1 << 7;

bitflags! {
/// Options controlling the behavior of files opened by the server in response
/// to an open or create request.
Expand All @@ -98,6 +101,9 @@ bitflags! {
const CACHE_DIR = FOPEN_CACHE_DIR;
/// the file is stream-like (no file position at all)
const STREAM = FOPEN_STREAM;
/// Pass reads, writes and mmap through to the backing file whose id is returned along
/// with this flag. Requires `FsOptions::PASSTHROUGH`.
const PASSTHROUGH = FOPEN_PASSTHROUGH;
}
}

Expand Down Expand Up @@ -197,6 +203,9 @@ const INIT_EXT: u64 = 0x4000_0000;
// This flag indicates whether the guest kernel enable per-file dax
const PERFILE_DAX: u64 = 0x2_0000_0000;

// Passthrough of reads, writes and mmap to backing files
const PASSTHROUGH: u64 = 1_u64 << 37;

// this flag indicates whether the guest kernel enable resend
const HAS_RESEND: u64 = 1_u64 << 39;

Expand Down Expand Up @@ -474,6 +483,15 @@ bitflags! {
/// indicates whether the kernel support resend inflight request
const HAS_RESEND = HAS_RESEND;

/// Indicates support for passthrough of reads, writes and mmap to backing files, as
/// added in Linux 6.9 (not the Anolis `FD_PASSTHROUGH`).
///
/// The kernel enables it only if the reply doesn't also set `WRITEBACK_CACHE` and the
/// max stack depth from `Server::set_max_stack_depth()` is 1 or 2. Register a backing
/// file with `fuse_backing_open()` and return its id from `open` along with
/// `OpenOptions::PASSTHROUGH`.
const PASSTHROUGH = PASSTHROUGH;

/// Daemon supports serving FUSE requests over io_uring (experimental).
#[cfg(feature = "fusedev-uring")]
const OVER_IO_URING = OVER_IO_URING;
Expand Down Expand Up @@ -821,6 +839,10 @@ impl From<u32> for Opcode {
}
}

/// Bit that the kernel sets in the unique id of a request it sends again after a
/// `NotifyOpcode::Resend` notification.
pub const UNIQUE_RESEND: u64 = 1 << 63;

#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum NotifyOpcode {
Expand Down Expand Up @@ -1151,7 +1173,8 @@ pub struct InitOut {
pub max_pages: u16,
pub map_alignment: u16,
pub flags2: u32,
pub unused: [u32; 7],
pub max_stack_depth: u32,
pub unused: [u32; 6],
}
unsafe impl ByteValued for InitOut {}

Expand Down Expand Up @@ -1417,6 +1440,16 @@ pub struct CopyFileRangeIn {
}
unsafe impl ByteValued for CopyFileRangeIn {}

/// Argument of the FUSE_DEV_IOC_BACKING_OPEN ioctl.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct BackingMap {
pub fd: i32,
pub flags: u32,
pub padding: u64,
}
unsafe impl ByteValued for BackingMap {}

#[cfg(test)]
mod tests {

Expand All @@ -1437,6 +1470,20 @@ mod tests {
assert_eq!(std::mem::size_of::<MkdirIn>(), 8);
assert_eq!(std::mem::size_of::<InHeader>(), 40);
assert_eq!(std::mem::size_of::<OutHeader>(), 16);
assert_eq!(std::mem::size_of::<OpenOut>(), 16);
assert_eq!(std::mem::size_of::<InitOut>(), 64);
assert_eq!(std::mem::size_of::<BackingMap>(), 16);
}

#[test]
fn test_passthrough_abi() {
assert_eq!(std::mem::offset_of!(OpenOut, passthrough), 12);
assert_eq!(std::mem::offset_of!(InitOut, flags2), 32);
assert_eq!(std::mem::offset_of!(InitOut, max_stack_depth), 36);
assert_eq!(std::mem::offset_of!(BackingMap, flags), 4);
assert_eq!(std::mem::offset_of!(BackingMap, padding), 8);
assert_eq!(FsOptions::PASSTHROUGH.bits(), 1 << 37);
assert_eq!(OpenOptions::PASSTHROUGH.bits(), 1 << 7);
}

#[test]
Expand Down
47 changes: 30 additions & 17 deletions crates/fuse-backend-core/src/api/filesystem/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ pub trait AsyncFileSystem: FileSystem {
/// The returned `OpenOptions` allow the file system to change the way the opened file is
/// handled by the kernel. See the documentation of `OpenOptions` for more information.
///
/// With `OpenOptions::PASSTHROUGH`, the returned `u32` is the id of the backing file to pass
/// reads, writes and mmap through to, as returned by `fuse_backing_open()`.
///
/// If the `FsOptions::ZERO_MESSAGE_OPEN` feature is enabled by both the file system
/// implementation and the kernel, then the file system may return an error of `ENOSYS`. This
/// will be interpreted by the kernel as success and future calls to `open` and `release` will
Expand All @@ -356,8 +359,10 @@ pub trait AsyncFileSystem: FileSystem {
{
let result = self
.open(ctx, inode, flags, fuse_flags)
.map(|(handle, opts, _backing_id)| (handle.map(Into::<u64>::into), opts));
Box::pin(async move { result.map(|(handle, opts)| (handle.map(From::from), opts)) })
.map(|(handle, opts, backing_id)| (handle.map(Into::<u64>::into), opts, backing_id));
Box::pin(async move {
result.map(|(handle, opts, backing_id)| (handle.map(From::from), opts, backing_id))
})
}

/// Create and open a file.
Expand All @@ -370,10 +375,10 @@ pub trait AsyncFileSystem: FileSystem {
/// unimplemented and all future calls to `create` will be handled by calling the `mknod` and
/// `open` methods instead.
///
/// See the documentation for the `open` method for more information about opening the file. In
/// addition to the optional `Handle` and the `OpenOptions`, the file system must also return an
/// `Entry` for the file. This increases the lookup count for the `Inode` associated with the
/// file by 1.
/// See the documentation for the `open` method for more information about opening the file,
/// including the optional backing id. In addition to the optional `Handle`, the `OpenOptions`
/// and the optional backing id, the file system must also return an `Entry` for the file.
/// This increases the lookup count for the `Inode` associated with the file by 1.
fn async_create<'a, 'b, 'c, 'async_trait>(
&'a self,
ctx: &'b Context,
Expand All @@ -387,11 +392,15 @@ pub trait AsyncFileSystem: FileSystem {
'c: 'async_trait,
Self: 'async_trait,
{
let result = self
.create(ctx, parent, name, args)
.map(|(entry, handle, opts, _backing_id)| (entry, handle.map(Into::<u64>::into), opts));
let result =
self.create(ctx, parent, name, args)
.map(|(entry, handle, opts, backing_id)| {
(entry, handle.map(Into::<u64>::into), opts, backing_id)
});
Box::pin(async move {
result.map(|(entry, handle, opts)| (entry, handle.map(From::from), opts))
result.map(|(entry, handle, opts, backing_id)| {
(entry, handle.map(From::from), opts, backing_id)
})
})
}

Expand Down Expand Up @@ -975,10 +984,14 @@ pub trait AsyncFileSystem: FileSystem {

type AttrFuture<'async_trait> =
Box<dyn Future<Output = io::Result<(stat64, Duration)>> + Send + 'async_trait>;
type OpenFuture<'async_trait, H> =
Box<dyn Future<Output = io::Result<(Option<H>, OpenOptions)>> + Send + 'async_trait>;
type CreateFuture<'async_trait, H> =
Box<dyn Future<Output = io::Result<(Entry, Option<H>, OpenOptions)>> + Send + 'async_trait>;
type OpenFuture<'async_trait, H> = Box<
dyn Future<Output = io::Result<(Option<H>, OpenOptions, Option<u32>)>> + Send + 'async_trait,
>;
type CreateFuture<'async_trait, H> = Box<
dyn Future<Output = io::Result<(Entry, Option<H>, OpenOptions, Option<u32>)>>
+ Send
+ 'async_trait,
>;

impl<FS: AsyncFileSystem> AsyncFileSystem for Arc<FS> {
fn async_lookup<'a, 'b, 'c, 'async_trait>(
Expand Down Expand Up @@ -1186,7 +1199,7 @@ mod tests {
_flags: u32,
_fuse_flags: u32,
) -> io::Result<(Option<u64>, OpenOptions, Option<u32>)> {
Ok((Some(inode * 10), OpenOptions::KEEP_CACHE, None))
Ok((Some(inode * 10), OpenOptions::KEEP_CACHE, Some(7)))
}

fn read(
Expand Down Expand Up @@ -1254,8 +1267,8 @@ mod tests {
let entry = fs.async_lookup(&ctx, 1, name).await.unwrap();
assert_eq!(entry.inode, 2);

let (handle, opts) = fs.async_open(&ctx, 2, 0, 0).await.unwrap();
assert_eq!((handle, opts), (Some(20), OpenOptions::KEEP_CACHE));
let opened = fs.async_open(&ctx, 2, 0, 0).await.unwrap();
assert_eq!(opened, (Some(20), OpenOptions::KEEP_CACHE, Some(7)));

let mut w = VecWriter(Vec::new());
let n = fs
Expand Down
3 changes: 3 additions & 0 deletions crates/fuse-backend-core/src/api/filesystem/sync_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ pub trait FileSystem {
/// The returned `OpenOptions` allow the file system to change the way the opened file is
/// handled by the kernel. See the documentation of `OpenOptions` for more information.
///
/// With `OpenOptions::PASSTHROUGH`, the returned `u32` is the id of the backing file to pass
/// reads, writes and mmap through to, as returned by `fuse_backing_open()`.
///
/// If the `FsOptions::ZERO_MESSAGE_OPEN` feature is enabled by both the file system
/// implementation and the kernel, then the file system may return an error of `ENOSYS`. This
/// will be interpreted by the kernel as success and future calls to `open` and `release` will
Expand Down
Loading
Loading