Skip to content

Commit 33d1c8c

Browse files
committed
core: let a server take over an initialized connection
A daemon can hand its FUSE device fds to a new process, which then serves the connection without a FUSE_INIT. The new Server has no way to learn what the old one negotiated, so it parses requests as if no option were enabled, for example the FUSE_EXT_GROUPS extension of create requests. Add Server::init_state(), which returns the protocol version and the options enabled at INIT, and Server::restore_init(), which sets them on the new server. Add abi::fuse_abi::UNIQUE_RESEND, the bit the kernel sets in requests that notify_resend() makes it send again. The server already replies with the unique id unchanged, and a test covers it. Document that notify_inval_entry() waits for the parent's lock, which a lookup in it holds until its reply, so a thread that serves requests must not call it. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
1 parent 8359c5d commit 33d1c8c

3 files changed

Lines changed: 102 additions & 3 deletions

File tree

‎crates/fuse-backend-core/src/abi/fuse_abi_linux.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,10 @@ impl From<u32> for Opcode {
821821
}
822822
}
823823

824+
/// Bit that the kernel sets in the unique id of a request it sends again after a
825+
/// `NotifyOpcode::Resend` notification.
826+
pub const UNIQUE_RESEND: u64 = 1 << 63;
827+
824828
#[repr(u32)]
825829
#[derive(Debug, Copy, Clone)]
826830
pub enum NotifyOpcode {

‎crates/fuse-backend-core/src/api/server/mod.rs‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ use std::ffi::CStr;
2020
use std::io::{self, Read};
2121
use std::marker::PhantomData;
2222
use std::mem::size_of;
23-
use std::sync::atomic::AtomicU64;
23+
use std::sync::atomic::{AtomicU64, Ordering};
2424

2525
use crate::abi::fuse_abi::*;
2626
use crate::api::filesystem::{Context, FileSystem, ZeroCopyReader, ZeroCopyWriter};
2727
use crate::buffer::{Reader, Writer};
2828
use crate::file_traits::FileReadWriteVolatile;
2929
use crate::{bytes_to_cstr, BitmapSlice, Error, Result};
30-
#[cfg(all(target_os = "linux", feature = "fusedev-uring"))]
31-
use std::sync::atomic::Ordering;
3230

3331
#[cfg(feature = "async-io")]
3432
mod async_io;
@@ -81,6 +79,37 @@ impl<F: FileSystem + Sync> Server<F> {
8179
}
8280
}
8381

82+
/// Get the protocol version and the options enabled at INIT, for `restore_init()`. Before
83+
/// INIT, the options are empty.
84+
pub fn init_state(&self) -> (ServerVersion, FsOptions) {
85+
let version = decode_version(self.vers.load(Ordering::Acquire));
86+
let options = FsOptions::from_bits_truncate(self.options.load(Ordering::Acquire));
87+
(version, options)
88+
}
89+
90+
/// Restore the protocol version and options from another server's `init_state()`.
91+
///
92+
/// A server that takes over a FUSE connection never sees INIT, so call this before it
93+
/// serves requests. It doesn't call `FileSystem::init()`, so the file system has to get its
94+
/// INIT state back on its own. `Vfs::restore_from_bytes()` does that for a `Vfs`, and
95+
/// `Vfs::mount()` then initializes each file system mounted on it with the restored
96+
/// options. A file system served without a `Vfs` can have `init()` called with the options
97+
/// from `init_state()`.
98+
///
99+
/// Then send `notify_resend()` to get the requests that the old server didn't reply to.
100+
/// The kernel ends the requests read from a FUSE device file description once it is closed,
101+
/// so the new server needs the file descriptions that the old one read from.
102+
pub fn restore_init(&self, version: ServerVersion, options: FsOptions) {
103+
self.vers.store(
104+
encode_version(version.major, version.minor),
105+
Ordering::Release,
106+
);
107+
self.options.store(options.bits(), Ordering::Release);
108+
#[cfg(all(target_os = "linux", feature = "fusedev-uring"))]
109+
self.negotiated_init_flags
110+
.store(options.bits(), Ordering::Release);
111+
}
112+
84113
/// Request serving FUSE requests over io_uring (experimental).
85114
///
86115
/// Must be called before the session is mounted: the request is carried

‎crates/fuse-backend-core/src/api/server/sync_io.rs‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub(super) fn parse_create_extensions(_options: &FsOptions, _tail: &[u8]) -> Res
113113
impl<F: FileSystem + Sync> Server<F> {
114114
#[cfg(feature = "fusedev")]
115115
/// Use to send notify msg to kernel fuse
116+
///
117+
/// The write waits for the lock of `parent`, which a lookup in it holds until its reply, so
118+
/// don't call this from a thread that serves requests. Any fd of the connection works.
116119
pub fn notify_inval_entry<W: Writer>(
117120
&self,
118121
mut w: W,
@@ -178,6 +181,8 @@ impl<F: FileSystem + Sync> Server<F> {
178181
/// the crash recovery routine. Given that FUSE initialization does not occur again during recovery,
179182
/// the capability to support resend notifications may not be automatically detected. It is the responsibility
180183
/// of the upper layers to verify and persist the kernel's support for this feature upon the initial FUSE setup.
184+
///
185+
/// Resent requests have `UNIQUE_RESEND` set in their unique id, which the reply keeps.
181186
pub fn notify_resend<W: Writer>(&self, mut w: W) -> Result<()> {
182187
let mut buffer_writer = w.split_at(0).map_err(Error::FailedToSplitWriter)?;
183188
let header = {
@@ -2019,6 +2024,67 @@ mod tests {
20192024
);
20202025
}
20212026

2027+
#[test]
2028+
fn test_server_restore_init() {
2029+
struct SuppGroupFs;
2030+
impl FileSystem for SuppGroupFs {
2031+
type Inode = u64;
2032+
type Handle = u64;
2033+
2034+
fn init(&self, capable: FsOptions) -> io::Result<FsOptions> {
2035+
Ok(capable & FsOptions::CREATE_SUPP_GROUP)
2036+
}
2037+
}
2038+
2039+
let init_in = InitIn {
2040+
major: KERNEL_VERSION,
2041+
minor: 40,
2042+
max_readahead: 0,
2043+
flags: FsOptions::INIT_EXT.bits() as u32,
2044+
};
2045+
let init_in2 = InitIn2 {
2046+
flags2: ((FsOptions::CREATE_SUPP_GROUP | FsOptions::HAS_RESEND).bits() >> 32) as u32,
2047+
unused: [0; 11],
2048+
};
2049+
let mut read_buf = [init_in.as_slice(), init_in2.as_slice()].concat();
2050+
let mut write_buf = [0u8; 4096];
2051+
let ctx = {
2052+
let reader = Reader::<()>::from_slice(&mut read_buf);
2053+
let writer = TestWriter::new(&mut write_buf);
2054+
SrvContext::new(InHeader::default(), reader, writer)
2055+
};
2056+
let old = Server::new(SuppGroupFs);
2057+
old.init(ctx, |_| {}).unwrap();
2058+
let (version, options) = old.init_state();
2059+
assert_eq!(version.minor, 40);
2060+
assert!(options.contains(FsOptions::CREATE_SUPP_GROUP | FsOptions::INIT_EXT));
2061+
2062+
let new = Server::new(SuppGroupFs);
2063+
assert!(new.init_state().1.is_empty());
2064+
new.restore_init(version.clone(), options);
2065+
assert_eq!(new.init_state(), (version, options));
2066+
}
2067+
2068+
#[test]
2069+
fn test_server_reply_keeps_unique_resend() {
2070+
let server = Server::new(MockFS);
2071+
let unique = UNIQUE_RESEND | 42;
2072+
let header = InHeader {
2073+
len: (size_of::<InHeader>() + size_of::<GetattrIn>()) as u32,
2074+
opcode: Opcode::Getattr as u32,
2075+
unique,
2076+
nodeid: ROOT_ID,
2077+
..Default::default()
2078+
};
2079+
let mut read_buf = [header.as_slice(), GetattrIn::default().as_slice()].concat();
2080+
let mut write_buf = [0u8; 4096];
2081+
let reader = Reader::<()>::from_slice(&mut read_buf);
2082+
let writer = TestWriter::new(&mut write_buf);
2083+
server.handle_message(reader, writer, None, None).unwrap();
2084+
let out = OutHeader::from_slice(&write_buf[..size_of::<OutHeader>()]).unwrap();
2085+
assert_eq!(out.unique, unique);
2086+
}
2087+
20222088
#[cfg(feature = "fusedev-uring")]
20232089
#[test]
20242090
fn test_server_init_uring_negotiation() {

0 commit comments

Comments
 (0)