Skip to main content

fuse/
session.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use super::Fuse;
5use super::Mapper;
6use super::protocol::*;
7use super::reply::ReplySender;
8use super::request::FuseOperation;
9use super::request::Request;
10use super::request::RequestReader;
11use parking_lot::RwLock;
12use std::io;
13use std::sync::atomic;
14use thiserror::Error;
15use zerocopy::FromZeros;
16use zerocopy::Immutable;
17use zerocopy::KnownLayout;
18
19// These are the flags that libfuse enables by default when calling init.
20const DEFAULT_FLAGS: u32 = FUSE_ASYNC_READ
21    | FUSE_PARALLEL_DIROPS
22    | FUSE_AUTO_INVAL_DATA
23    | FUSE_HANDLE_KILLPRIV
24    | FUSE_ASYNC_DIO
25    | FUSE_ATOMIC_O_TRUNC
26    | FUSE_BIG_WRITES
27    | FUSE_MAX_PAGES
28    | FUSE_INIT_EXT;
29
30// Default flags2 to negotiate when FUSE_INIT_EXT is supported.
31// Individual filesystem implementations can set additional flags2 in their
32// init callback (e.g. FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 for virtiofs).
33const DEFAULT_FLAGS2: u32 = 0;
34
35const DEFAULT_MAX_PAGES: u32 = 256;
36
37// Page size is currently hardcoded. While it could be determined from the OS, in the case of
38// virtio-fs it's not clear whether the host's or guest's page size should be used, if there's
39// a difference.
40const PAGE_SIZE: u32 = 4096;
41
42/// A FUSE session for a file system.
43///
44/// Handles negotiation and dispatching requests to the file system.
45pub struct Session {
46    fs: Box<dyn Fuse + Send + Sync>,
47    // Initialized provides a quick way to check if FUSE_INIT is expected without having to take
48    // a lock, since operations mostly don't need to access the SessionInfo.
49    initialized: atomic::AtomicBool,
50    info: RwLock<SessionInfo>,
51}
52
53impl Session {
54    /// Create a new `Session`.
55    pub fn new<T>(fs: T) -> Self
56    where
57        T: 'static + Fuse + Send + Sync,
58    {
59        Self {
60            fs: Box::new(fs),
61            initialized: atomic::AtomicBool::new(false),
62            info: RwLock::new(SessionInfo::default()),
63        }
64    }
65
66    /// Indicates whether the session has received an init request.
67    ///
68    /// Also returns `false` after the session received a destroy request.
69    pub fn is_initialized(&self) -> bool {
70        self.initialized.load(atomic::Ordering::Acquire)
71    }
72
73    /// Dispatch a FUSE request to the file system.
74    pub fn dispatch(
75        &self,
76        request: Request,
77        sender: &mut impl ReplySender,
78        mapper: Option<&dyn Mapper>,
79    ) {
80        let unique = request.unique();
81        let result = if self.is_initialized() {
82            self.dispatch_helper(request, sender, mapper)
83        } else {
84            self.dispatch_init(request, sender)
85        };
86
87        match result {
88            Err(OperationError::FsError(e)) => {
89                if let Err(e) = sender.send_error(unique, e.value()) {
90                    tracing::error!(
91                        unique,
92                        error = &e as &dyn std::error::Error,
93                        "Failed to send reply",
94                    );
95                }
96            }
97            Err(OperationError::SendError(e)) => {
98                if e.kind() == io::ErrorKind::NotFound {
99                    tracing::trace!(unique, "Request was interrupted.");
100                } else {
101                    tracing::error!(
102                        unique,
103                        error = &e as &dyn std::error::Error,
104                        "Failed to send reply",
105                    );
106                }
107            }
108            Ok(_) => (),
109        }
110    }
111
112    /// End the session.
113    ///
114    /// This puts the session in a state where it can accept another FUSE_INIT message. This allows
115    /// a virtiofs file system to be remounted after unmount.
116    ///
117    /// This invokes the file system's destroy callback if it hadn't been called already.
118    pub fn destroy(&self) {
119        if self.initialized.swap(false, atomic::Ordering::AcqRel) {
120            self.fs.destroy();
121        }
122    }
123
124    /// Perform the actual dispatch. This allows the caller to send an error reply if any operation
125    /// encounters an error.
126    fn dispatch_helper(
127        &self,
128        request: Request,
129        sender: &mut impl ReplySender,
130        mapper: Option<&dyn Mapper>,
131    ) -> Result<(), OperationError> {
132        request.log();
133
134        match request.operation() {
135            FuseOperation::Invalid => {
136                // This indicates the header could be parsed but the rest of the request could not,
137                // so send an error reply.
138                return Err(lx::Error::EIO.into());
139            }
140            FuseOperation::Error(e) => {
141                // This indicates the request was parsed but contained invalid data (e.g., a name
142                // that was too long). Return the specific error.
143                return Err((*e).into());
144            }
145            FuseOperation::Lookup { name } => {
146                let out = self.fs.lookup(&request, name)?;
147                sender.send_arg(request.unique(), out)?;
148            }
149            FuseOperation::Forget { arg } => {
150                self.fs.forget(request.node_id(), arg.nlookup);
151            }
152            FuseOperation::GetAttr { arg } => {
153                let out = self.fs.get_attr(&request, arg.getattr_flags, arg.fh)?;
154                sender.send_arg(request.unique(), out)?;
155            }
156            FuseOperation::SetAttr { arg } => {
157                let out = self.fs.set_attr(&request, arg)?;
158                sender.send_arg(request.unique(), out)?;
159            }
160            FuseOperation::ReadLink {} => {
161                let out = self.fs.read_link(&request)?;
162                sender.send_string(request.unique(), out)?;
163            }
164            FuseOperation::Symlink { name, target } => {
165                let out = self.fs.symlink(&request, name, target)?;
166                sender.send_arg(request.unique(), out)?;
167            }
168            FuseOperation::MkNod { arg, name } => {
169                let out = self.fs.mknod(&request, name, arg)?;
170                sender.send_arg(request.unique(), out)?;
171            }
172            FuseOperation::MkDir { arg, name } => {
173                let out = self.fs.mkdir(&request, name, arg)?;
174                sender.send_arg(request.unique(), out)?;
175            }
176            FuseOperation::Unlink { name } => {
177                self.fs.unlink(&request, name)?;
178                sender.send_empty(request.unique())?;
179            }
180            FuseOperation::RmDir { name } => {
181                self.fs.rmdir(&request, name)?;
182                sender.send_empty(request.unique())?;
183            }
184            FuseOperation::Rename {
185                arg,
186                name,
187                new_name,
188            } => {
189                self.fs.rename(&request, name, arg.newdir, new_name, 0)?;
190
191                sender.send_empty(request.unique())?;
192            }
193            FuseOperation::Link { arg, name } => {
194                let out = self.fs.link(&request, name, arg.oldnodeid)?;
195                sender.send_arg(request.unique(), out)?;
196            }
197            FuseOperation::Open { arg } => {
198                let out = self.fs.open(&request, arg.flags)?;
199                self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, false)?;
200            }
201            FuseOperation::Read { arg } => {
202                let out = self.fs.read(&request, arg)?;
203                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
204            }
205            FuseOperation::Write { arg, data } => {
206                let out = self.fs.write(&request, arg, data)?;
207                sender.send_arg(
208                    request.unique(),
209                    fuse_write_out {
210                        size: out.try_into().unwrap(),
211                        padding: 0,
212                    },
213                )?;
214            }
215            FuseOperation::StatFs {} => {
216                let out = self.fs.statfs(&request)?;
217                sender.send_arg(request.unique(), fuse_statfs_out { st: out })?;
218            }
219            FuseOperation::Release { arg } => {
220                self.fs.release(&request, arg)?;
221                sender.send_empty(request.unique())?;
222            }
223            FuseOperation::FSync { arg } => {
224                self.fs.fsync(&request, arg.fh, arg.fsync_flags)?;
225                sender.send_empty(request.unique())?;
226            }
227            FuseOperation::SetXAttr { arg, name, value } => {
228                self.fs.set_xattr(&request, name, value, arg.flags)?;
229                sender.send_empty(request.unique())?;
230            }
231            FuseOperation::GetXAttr { arg, name } => {
232                if arg.size == 0 {
233                    let out = self.fs.get_xattr_size(&request, name)?;
234                    sender.send_arg(
235                        request.unique(),
236                        fuse_getxattr_out {
237                            size: out,
238                            padding: 0,
239                        },
240                    )?;
241                } else {
242                    let out = self.fs.get_xattr(&request, name, arg.size)?;
243                    Self::send_max_size(sender, request.unique(), &out, arg.size)?;
244                }
245            }
246            FuseOperation::ListXAttr { arg } => {
247                if arg.size == 0 {
248                    let out = self.fs.list_xattr_size(&request)?;
249                    sender.send_arg(
250                        request.unique(),
251                        fuse_getxattr_out {
252                            size: out,
253                            padding: 0,
254                        },
255                    )?;
256                } else {
257                    let out = self.fs.list_xattr(&request, arg.size)?;
258                    Self::send_max_size(sender, request.unique(), &out, arg.size)?;
259                }
260            }
261            FuseOperation::RemoveXAttr { name } => {
262                self.fs.remove_xattr(&request, name)?;
263                sender.send_empty(request.unique())?;
264            }
265            FuseOperation::Flush { arg } => {
266                self.fs.flush(&request, arg)?;
267                sender.send_empty(request.unique())?;
268            }
269            FuseOperation::Init { arg: _ } => {
270                tracing::warn!("Duplicate init message.");
271                return Err(lx::Error::EIO.into());
272            }
273            FuseOperation::OpenDir { arg } => {
274                let out = self.fs.open_dir(&request, arg.flags)?;
275                self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, true)?;
276            }
277            FuseOperation::ReadDir { arg } => {
278                let out = self.fs.read_dir(&request, arg)?;
279                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
280            }
281            FuseOperation::ReleaseDir { arg } => {
282                self.fs.release_dir(&request, arg)?;
283                sender.send_empty(request.unique())?;
284            }
285            FuseOperation::FSyncDir { arg } => {
286                self.fs.fsync_dir(&request, arg.fh, arg.fsync_flags)?;
287                sender.send_empty(request.unique())?;
288            }
289            FuseOperation::GetLock { arg } => {
290                let out = self.fs.get_lock(&request, arg)?;
291                sender.send_arg(request.unique(), out)?;
292            }
293            FuseOperation::SetLock { arg } => {
294                self.fs.set_lock(&request, arg, false)?;
295                sender.send_empty(request.unique())?;
296            }
297            FuseOperation::SetLockSleep { arg } => {
298                self.fs.set_lock(&request, arg, true)?;
299                sender.send_empty(request.unique())?;
300            }
301            FuseOperation::Access { arg } => {
302                self.fs.access(&request, arg.mask)?;
303                sender.send_empty(request.unique())?;
304            }
305            FuseOperation::Create { arg, name } => {
306                let out = self.fs.create(&request, name, arg)?;
307                self.send_release_if_interrupted(
308                    &request,
309                    sender,
310                    out.open.fh,
311                    arg.flags,
312                    out,
313                    false,
314                )?;
315            }
316            FuseOperation::Interrupt { arg: _ } => {
317                // Interrupt is potentially complicated, and none of the sample file systems seem
318                // to use it, so it's left as TODO for now.
319                tracing::warn!("FUSE_INTERRUPT not supported.");
320                return Err(lx::Error::ENOSYS.into());
321            }
322            FuseOperation::BMap { arg } => {
323                let out = self.fs.block_map(&request, arg.block, arg.blocksize)?;
324                sender.send_arg(request.unique(), fuse_bmap_out { block: out })?;
325            }
326            FuseOperation::Destroy {} => {
327                if let Some(mapper) = mapper {
328                    mapper.clear();
329                }
330                self.destroy();
331                sender.send_empty(request.unique())?;
332            }
333            FuseOperation::Ioctl { arg, data } => {
334                let out = self.fs.ioctl(&request, arg, data)?;
335                if out.1.len() > arg.out_size as usize {
336                    return Err(lx::Error::EINVAL.into());
337                }
338
339                // As far as I can tell, the fields other than result are only used for CUSE.
340                sender.send_arg_data(
341                    request.unique(),
342                    fuse_ioctl_out {
343                        result: out.0,
344                        flags: 0,
345                        in_iovs: 0,
346                        out_iovs: 0,
347                    },
348                    data,
349                )?;
350            }
351            FuseOperation::Poll { arg: _ } => {
352                // Poll is not currently needed, and complicated to support. It appears to have some
353                // way of registering for later notifications, but I can't figure out how that
354                // works without libfuse source.
355                tracing::warn!("FUSE_POLL not supported.");
356                return Err(lx::Error::ENOSYS.into());
357            }
358            FuseOperation::NotifyReply { arg: _, data: _ } => {
359                // Not sure what this is. It has something to do with poll, I think.
360                tracing::warn!("FUSE_NOTIFY_REPLY not supported.");
361                return Err(lx::Error::ENOSYS.into());
362            }
363            FuseOperation::BatchForget { arg, nodes } => {
364                self.batch_forget(arg.count, nodes);
365            }
366            FuseOperation::FAllocate { arg } => {
367                self.fs.fallocate(&request, arg)?;
368                sender.send_empty(request.unique())?;
369            }
370            FuseOperation::ReadDirPlus { arg } => {
371                let out = self.fs.read_dir_plus(&request, arg)?;
372                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
373            }
374            FuseOperation::Rename2 {
375                arg,
376                name,
377                new_name,
378            } => {
379                self.fs
380                    .rename(&request, name, arg.newdir, new_name, arg.flags)?;
381
382                sender.send_empty(request.unique())?;
383            }
384            FuseOperation::LSeek { arg } => {
385                let out = self.fs.lseek(&request, arg.fh, arg.offset, arg.whence)?;
386                sender.send_arg(request.unique(), fuse_lseek_out { offset: out })?;
387            }
388            FuseOperation::CopyFileRange { arg } => {
389                let out = self.fs.copy_file_range(&request, arg)?;
390                sender.send_arg(
391                    request.unique(),
392                    fuse_write_out {
393                        size: out.try_into().unwrap(),
394                        padding: 0,
395                    },
396                )?;
397            }
398            FuseOperation::SetupMapping { arg } => {
399                if let Some(mapper) = mapper {
400                    self.fs.setup_mapping(&request, mapper, arg)?;
401                    sender.send_empty(request.unique())?;
402                } else {
403                    return Err(lx::Error::ENOSYS.into());
404                }
405            }
406            FuseOperation::RemoveMapping { arg, mappings } => {
407                if let Some(mapper) = mapper {
408                    self.remove_mapping(&request, mapper, arg.count, mappings)?;
409                    sender.send_empty(request.unique())?;
410                } else {
411                    return Err(lx::Error::ENOSYS.into());
412                }
413            }
414            FuseOperation::SyncFs { _arg } => {
415                // Rely on host file system to sync data
416                sender.send_empty(request.unique())?;
417            }
418            FuseOperation::StatX { arg } => {
419                let out = self.fs.get_statx(
420                    &request,
421                    arg.fh,
422                    arg.getattr_flags,
423                    arg.flags,
424                    arg.mask.into(),
425                )?;
426                sender.send_arg(request.unique(), out)?;
427            }
428            FuseOperation::CanonicalPath {} => {
429                // Android-specific opcode used to return a guest accessible
430                // path to the file location being proxied by the fuse
431                // implementation.
432                tracing::trace!("Unsupported opcode FUSE_CANONICAL_PATH");
433                sender.send_error(request.unique(), lx::Error::ENOSYS.value())?;
434            }
435        }
436
437        Ok(())
438    }
439
440    /// Dispatch the init message.
441    fn dispatch_init(
442        &self,
443        request: Request,
444        sender: &mut impl ReplySender,
445    ) -> Result<(), OperationError> {
446        request.log();
447        let init: &fuse_init_in = if let FuseOperation::Init { arg } = request.operation() {
448            arg
449        } else {
450            tracing::error!(opcode = request.opcode(), "Expected FUSE_INIT");
451            return Err(lx::Error::EIO.into());
452        };
453
454        let mut info = self.info.write();
455        if self.is_initialized() {
456            tracing::error!("Racy FUSE_INIT requests.");
457            return Err(lx::Error::EIO.into());
458        }
459
460        let mut out = fuse_init_out::new_zeroed();
461        out.major = FUSE_KERNEL_VERSION;
462        out.minor = FUSE_KERNEL_MINOR_VERSION;
463
464        // According to the docs, if the kernel reports a higher version, the response should have
465        // only the desired version set and the kernel will resend FUSE_INIT with that version.
466        if init.major > FUSE_KERNEL_VERSION {
467            sender.send_arg(request.unique(), out)?;
468            return Ok(());
469        }
470
471        // Don't bother supporting old versions. Version 7.27 is what kernel 4.19 uses, and can
472        // be supported without needing to change the daemon's behavior for compatibility.
473        if init.major < FUSE_KERNEL_VERSION || init.minor < 27 {
474            tracing::error!(
475                major = init.major,
476                minor = init.minor,
477                "Got unsupported kernel version",
478            );
479            return Err(lx::Error::EIO.into());
480        }
481
482        // Prepare the session info and call the file system to negotiate.
483        info.major = init.major;
484        info.minor = init.minor;
485        info.max_readahead = init.max_readahead;
486        info.capable = init.flags;
487        info.want = DEFAULT_FLAGS & init.flags;
488        info.want2 = 0;
489        info.capable2 = 0;
490        // Negotiate flags2 when the kernel supports extended init.
491        if init.flags & FUSE_INIT_EXT != 0 {
492            info.capable2 = init.flags2;
493            info.want2 = DEFAULT_FLAGS2 & init.flags2;
494        }
495        info.time_gran = 1;
496        info.max_write = DEFAULT_MAX_PAGES * PAGE_SIZE;
497        self.fs.init(&mut info);
498
499        assert!(info.want & !info.capable == 0);
500        // If the filesystem cleared FUSE_INIT_EXT from want, force want2 to
501        // zero so we never reply with flags2 the kernel won't expect.
502        if info.want & FUSE_INIT_EXT == 0 {
503            info.want2 = 0;
504        }
505        assert!(info.want2 & !info.capable2 == 0);
506
507        // Report the negotiated values back to the client.
508        // TODO: Set map_alignment for DAX.
509        out.max_readahead = info.max_readahead;
510        out.flags = info.want;
511        out.max_background = info.max_background;
512        out.congestion_threshold = info.congestion_threshold;
513        out.max_write = info.max_write;
514        out.time_gran = info.time_gran;
515        out.max_pages = info.max_write.div_ceil(PAGE_SIZE).min(u16::MAX as u32) as u16;
516        // Only report flags2 when extended init was negotiated.
517        if info.want & FUSE_INIT_EXT != 0 {
518            out.flags2 = info.want2;
519        }
520
521        sender.send_arg(request.unique(), out)?;
522
523        // Indicate other requests can be received now.
524        self.initialized.store(true, atomic::Ordering::Release);
525        Ok(())
526    }
527
528    /// Send a reply and call the release method if the reply was interrupted.
529    fn send_release_if_interrupted<
530        TArg: zerocopy::IntoBytes + std::fmt::Debug + Immutable + KnownLayout,
531    >(
532        &self,
533        request: &Request,
534        sender: &mut impl ReplySender,
535        fh: u64,
536        flags: u32,
537        arg: TArg,
538        dir: bool,
539    ) -> lx::Result<()> {
540        if let Err(e) = sender.send_arg(request.unique(), arg) {
541            // ENOENT means the request was interrupted, and the kernel will not call
542            // release, so do it now.
543            if e.kind() == io::ErrorKind::NotFound {
544                let arg = fuse_release_in {
545                    fh,
546                    flags,
547                    release_flags: 0,
548                    lock_owner: 0,
549                };
550
551                if dir {
552                    self.fs.release_dir(request, &arg)?;
553                } else {
554                    self.fs.release(request, &arg)?;
555                }
556            } else {
557                return Err(e.into());
558            }
559        }
560
561        Ok(())
562    }
563
564    /// Send a reply, validating it doesn't exceed the requested size.
565    ///
566    /// If it exceeds the maximum size, this causes a panic because that's a bug in the file system.
567    fn send_max_size(
568        sender: &mut impl ReplySender,
569        unique: u64,
570        data: &[u8],
571        max_size: u32,
572    ) -> Result<(), OperationError> {
573        assert!(data.len() <= max_size as usize);
574        sender.send_data(unique, data)?;
575        Ok(())
576    }
577
578    /// Process `FUSE_BATCH_FORGET` by repeatedly calling `forget`.
579    fn batch_forget(&self, count: u32, mut nodes: &[u8]) {
580        for _ in 0..count {
581            let forget: fuse_forget_one = match nodes.read_type() {
582                Ok(f) => f,
583                Err(_) => break,
584            };
585
586            self.fs.forget(forget.nodeid, forget.nlookup);
587        }
588    }
589
590    /// Remove multiple DAX mappings.
591    fn remove_mapping(
592        &self,
593        request: &Request,
594        mapper: &dyn Mapper,
595        count: u32,
596        mut mappings: &[u8],
597    ) -> lx::Result<()> {
598        for _ in 0..count {
599            let mapping: fuse_removemapping_one = mappings.read_type()?;
600            self.fs
601                .remove_mapping(request, mapper, mapping.moffset, mapping.len)?;
602        }
603
604        Ok(())
605    }
606}
607
608/// Provides information about a session. Public fields may be modified during `init`.
609#[derive(Default)]
610pub struct SessionInfo {
611    major: u32,
612    minor: u32,
613    pub max_readahead: u32,
614    capable: u32,
615    capable2: u32,
616    pub want: u32,
617    /// Extended flags (flags2) to negotiate when FUSE_INIT_EXT is active.
618    pub want2: u32,
619    pub max_background: u16,
620    pub congestion_threshold: u16,
621    pub max_write: u32,
622    pub time_gran: u32,
623}
624
625impl SessionInfo {
626    pub fn major(&self) -> u32 {
627        self.major
628    }
629
630    pub fn minor(&self) -> u32 {
631        self.minor
632    }
633
634    pub fn capable(&self) -> u32 {
635        self.capable
636    }
637
638    pub fn capable2(&self) -> u32 {
639        self.capable2
640    }
641}
642
643#[derive(Debug, Error)]
644enum OperationError {
645    #[error("File system error")]
646    FsError(#[from] lx::Error),
647    #[error("Send error")]
648    SendError(#[from] io::Error),
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::request::tests::*;
655    use parking_lot::Mutex;
656    use std::sync::Arc;
657    use zerocopy::FromBytes;
658    use zerocopy::IntoBytes;
659
660    #[test]
661    fn dispatch_error_name_too_long() {
662        let fs = TestFs::default();
663        let session = Session::new(fs);
664
665        // Initialize the session first
666        let mut init_sender = MockSender::default();
667        session.dispatch(
668            Request::new(FUSE_INIT_REQUEST).unwrap(),
669            &mut init_sender,
670            None,
671        );
672        assert!(session.is_initialized());
673
674        // Create a LOOKUP request with a name that's too long (256 bytes, exceeds NAME_MAX of 255)
675        let mut error_sender = ErrorCheckingSender::default();
676        let lookup_data = make_lookup_name_too_long();
677        let request = Request::new(lookup_data.as_slice()).unwrap();
678
679        // Verify the operation is Error(ENAMETOOLONG)
680        assert!(
681            matches!(request.operation(), FuseOperation::Error(e) if *e == lx::Error::ENAMETOOLONG)
682        );
683
684        session.dispatch(request, &mut error_sender, None);
685
686        // Verify that an error reply was sent with ENAMETOOLONG (36)
687        assert_eq!(
688            error_sender.last_error,
689            Some(lx::Error::ENAMETOOLONG.value())
690        );
691    }
692
693    #[test]
694    fn dispatch() {
695        let mut sender = MockSender::default();
696        let fs = TestFs::default();
697        let state = fs.state.clone();
698        let session = Session::new(fs);
699        assert!(!session.is_initialized());
700        let request = Request::new(FUSE_INIT_REQUEST).unwrap();
701        session.dispatch(request, &mut sender, None);
702        assert_eq!(state.lock().called, INIT_CALLED);
703        assert!(session.is_initialized());
704        session.dispatch(
705            Request::new(FUSE_GETATTR_REQUEST).unwrap(),
706            &mut sender,
707            None,
708        );
709        assert_eq!(state.lock().called, INIT_CALLED | GETATTR_CALLED);
710
711        session.dispatch(
712            Request::new(FUSE_LOOKUP_REQUEST).unwrap(),
713            &mut sender,
714            None,
715        );
716        assert_eq!(
717            state.lock().called,
718            INIT_CALLED | GETATTR_CALLED | LOOKUP_CALLED
719        );
720    }
721
722    #[derive(Default)]
723    struct State {
724        called: u32,
725    }
726
727    #[derive(Default)]
728    struct TestFs {
729        state: Arc<Mutex<State>>,
730    }
731
732    impl Fuse for TestFs {
733        fn init(&self, info: &mut SessionInfo) {
734            assert_eq!(self.state.lock().called & INIT_CALLED, 0);
735            assert_eq!(info.major(), 7);
736            assert_eq!(info.minor(), 27);
737            assert_eq!(info.capable(), 0x3FFFFB);
738            assert_eq!(info.want, 0xC9029);
739            assert_eq!(info.max_readahead, 131072);
740            assert_eq!(info.max_background, 0);
741            assert_eq!(info.max_write, 1048576);
742            assert_eq!(info.congestion_threshold, 0);
743            assert_eq!(info.time_gran, 1);
744            self.state.lock().called |= INIT_CALLED;
745        }
746
747        fn get_attr(&self, request: &Request, flags: u32, fh: u64) -> lx::Result<fuse_attr_out> {
748            assert_eq!(self.state.lock().called & GETATTR_CALLED, 0);
749            assert_eq!(request.node_id(), 1);
750            assert_eq!(flags, 0);
751            assert_eq!(fh, 0);
752            let mut attr = fuse_attr_out::new_zeroed();
753            attr.attr.ino = 1;
754            attr.attr.mode = lx::S_IFDIR | 0o755;
755            attr.attr.nlink = 2;
756            attr.attr_valid = 1;
757            self.state.lock().called |= GETATTR_CALLED;
758            Ok(attr)
759        }
760
761        fn lookup(&self, request: &Request, name: &lx::LxStr) -> lx::Result<fuse_entry_out> {
762            assert_eq!(self.state.lock().called & LOOKUP_CALLED, 0);
763            assert_eq!(request.node_id(), 1);
764            assert_eq!(name, "hello");
765            self.state.lock().called |= LOOKUP_CALLED;
766            let mut attr = fuse_attr::new_zeroed();
767            attr.ino = 2;
768            attr.mode = lx::S_IFREG | 0o644;
769            attr.nlink = 1;
770            attr.size = 13;
771            Ok(fuse_entry_out {
772                nodeid: 2,
773                generation: 0,
774                entry_valid: 1,
775                entry_valid_nsec: 0,
776                attr_valid: 1,
777                attr_valid_nsec: 0,
778                attr,
779            })
780        }
781    }
782
783    #[derive(Default)]
784    struct MockSender {
785        state: u32,
786    }
787
788    impl ReplySender for MockSender {
789        fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
790            let flat: Vec<u8> = bufs.iter().flat_map(|s| s.iter()).copied().collect();
791            match self.state {
792                0 => assert_eq!(flat, INIT_REPLY),
793                1 => assert_eq!(flat, GETATTR_REPLY),
794                2 => assert_eq!(flat, LOOKUP_REPLY),
795                _ => panic!("Unexpected send."),
796            }
797
798            self.state += 1;
799            Ok(())
800        }
801    }
802
803    const INIT_CALLED: u32 = 0x1;
804    const GETATTR_CALLED: u32 = 0x2;
805    const LOOKUP_CALLED: u32 = 0x4;
806
807    const INIT_REPLY: &[u8] = &[
808        80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 39, 0, 0, 0, 0, 0, 2, 0, 41,
809        144, 12, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
810        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
811    ];
812
813    const GETATTR_REPLY: &[u8] = &[
814        120, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
815        0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
816        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
817        0, 0, 237, 65, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
818        0,
819    ];
820
821    const LOOKUP_REPLY: &[u8] = &[
822        144, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
823        0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
824        0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
825        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0,
826        0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
827    ];
828
829    /// A ReplySender that tracks error responses for testing
830    #[derive(Default)]
831    struct ErrorCheckingSender {
832        last_error: Option<i32>,
833    }
834
835    impl ReplySender for ErrorCheckingSender {
836        fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
837            // Parse the fuse_out_header to check for errors
838            let flat: Vec<u8> = bufs.iter().flat_map(|s| s.iter()).copied().collect();
839            if flat.len() >= 16 {
840                // fuse_out_header: len (4), error (4), unique (8)
841                let error = i32::from_ne_bytes([flat[4], flat[5], flat[6], flat[7]]);
842                if error != 0 {
843                    self.last_error = Some(-error); // Error is stored as negative in header
844                }
845            }
846            Ok(())
847        }
848    }
849
850    /// A ReplySender that captures the raw response bytes for inspection.
851    #[derive(Default)]
852    struct CapturingSender {
853        data: Vec<u8>,
854    }
855
856    impl ReplySender for CapturingSender {
857        fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
858            self.data = bufs.iter().flat_map(|s| s.iter()).copied().collect();
859            Ok(())
860        }
861    }
862
863    impl CapturingSender {
864        /// Parse the captured reply as a fuse_out_header + fuse_init_out.
865        fn parse_init_reply(&self) -> (fuse_out_header, fuse_init_out) {
866            let hdr = fuse_out_header::read_from_prefix(&self.data).unwrap().0;
867            let body = fuse_init_out::read_from_prefix(&self.data[size_of::<fuse_out_header>()..])
868                .unwrap()
869                .0;
870            (hdr, body)
871        }
872    }
873
874    /// Build a FUSE_INIT request with the given version and flags.
875    fn make_init_request(
876        major: u32,
877        minor: u32,
878        max_readahead: u32,
879        flags: u32,
880        flags2: u32,
881    ) -> Vec<u8> {
882        let header = fuse_in_header {
883            len: (size_of::<fuse_in_header>() + size_of::<fuse_init_in>()) as u32,
884            opcode: FUSE_INIT,
885            unique: 1,
886            nodeid: 0,
887            uid: 0,
888            gid: 0,
889            pid: 0,
890            padding: 0,
891        };
892        let init = fuse_init_in {
893            major,
894            minor,
895            max_readahead,
896            flags,
897            flags2,
898            unused: [0; 11],
899        };
900        let mut data = Vec::new();
901        data.extend_from_slice(header.as_bytes());
902        data.extend_from_slice(init.as_bytes());
903        data
904    }
905
906    /// A minimal Fuse implementation that records the SessionInfo seen during init
907    /// and optionally requests FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 or overrides max_write.
908    #[derive(Default)]
909    struct InitCapturingFs {
910        info: Arc<Mutex<Option<(u32, u32, u32)>>>, // (want, want2, capable)
911        request_direct_io_mmap: bool,
912        max_write_override: Option<u32>,
913    }
914
915    impl Fuse for InitCapturingFs {
916        fn init(&self, info: &mut SessionInfo) {
917            if self.request_direct_io_mmap && info.capable2() & FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 != 0
918            {
919                info.want2 |= FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2;
920            }
921            if let Some(max_write) = self.max_write_override {
922                info.max_write = max_write;
923            }
924            *self.info.lock() = Some((info.want, info.want2, info.capable()));
925        }
926    }
927
928    #[test]
929    fn init_with_ext_negotiates_flags2_and_direct_io_allow_mmap() {
930        // Kernel advertises FUSE_INIT_EXT among its capabilities.
931        let flags = 0x003FFFFB | FUSE_INIT_EXT;
932        let request_data = make_init_request(7, 39, 131072, flags, FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2);
933
934        let fs = InitCapturingFs {
935            request_direct_io_mmap: true,
936            ..Default::default()
937        };
938        let info_ref = fs.info.clone();
939        let session = Session::new(fs);
940
941        let mut sender = CapturingSender::default();
942        session.dispatch(
943            Request::new(request_data.as_slice()).unwrap(),
944            &mut sender,
945            None,
946        );
947
948        assert!(session.is_initialized());
949
950        // The filesystem should see FUSE_INIT_EXT in the negotiated flags and
951        // FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 in want2.
952        let info = info_ref.lock();
953        let &(want, want2, _capable) = info
954            .as_ref()
955            .expect("filesystem init info should be captured after initialization");
956        assert_ne!(
957            want & FUSE_INIT_EXT,
958            0,
959            "FUSE_INIT_EXT should be negotiated"
960        );
961        assert_ne!(
962            want2 & FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2,
963            0,
964            "FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 should be in want2"
965        );
966
967        // The reply must carry both flags and flags2.
968        let (_hdr, init_out) = sender.parse_init_reply();
969        assert_ne!(
970            init_out.flags & FUSE_INIT_EXT,
971            0,
972            "Reply flags must include FUSE_INIT_EXT"
973        );
974        assert_ne!(
975            init_out.flags2 & FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2,
976            0,
977            "Reply flags2 must include FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2"
978        );
979    }
980
981    #[test]
982    fn init_without_ext_does_not_negotiate_flags2() {
983        // Kernel does NOT advertise FUSE_INIT_EXT.
984        let flags = 0x003FFFFB; // same as FUSE_INIT_REQUEST, no FUSE_INIT_EXT
985        let request_data = make_init_request(7, 27, 131072, flags, 0);
986
987        let fs = InitCapturingFs::default();
988        let info_ref = fs.info.clone();
989        let session = Session::new(fs);
990
991        let mut sender = CapturingSender::default();
992        session.dispatch(
993            Request::new(request_data.as_slice()).unwrap(),
994            &mut sender,
995            None,
996        );
997
998        assert!(session.is_initialized());
999
1000        // Without FUSE_INIT_EXT the daemon must not request any flags2.
1001        let info = info_ref.lock();
1002        let &(_want, want2, _capable) = info
1003            .as_ref()
1004            .expect("filesystem init info should be captured after initialization");
1005        assert_eq!(want2, 0, "want2 must be zero without FUSE_INIT_EXT");
1006
1007        let (_hdr, init_out) = sender.parse_init_reply();
1008        assert_eq!(
1009            init_out.flags & FUSE_INIT_EXT,
1010            0,
1011            "Reply flags must NOT include FUSE_INIT_EXT"
1012        );
1013        assert_eq!(init_out.flags2, 0, "Reply flags2 must be zero");
1014    }
1015
1016    #[test]
1017    fn init_ext_without_direct_io_mmap_results_in_zero_flags2() {
1018        // Kernel supports FUSE_INIT_EXT but does NOT advertise
1019        // FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2 in flags2.
1020        let flags = 0x003FFFFB | FUSE_INIT_EXT;
1021        let request_data = make_init_request(7, 39, 131072, flags, 0);
1022
1023        let fs = InitCapturingFs::default();
1024        let info_ref = fs.info.clone();
1025        let session = Session::new(fs);
1026
1027        let mut sender = CapturingSender::default();
1028        session.dispatch(
1029            Request::new(request_data.as_slice()).unwrap(),
1030            &mut sender,
1031            None,
1032        );
1033
1034        assert!(session.is_initialized());
1035
1036        let info = info_ref.lock();
1037        let &(_want, want2, _capable) = info
1038            .as_ref()
1039            .expect("filesystem init info should be captured after initialization");
1040        assert_eq!(
1041            want2, 0,
1042            "want2 must be zero when kernel flags2 lacks FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2"
1043        );
1044
1045        let (_hdr, init_out) = sender.parse_init_reply();
1046        assert_eq!(init_out.flags2, 0, "Reply flags2 must be zero");
1047    }
1048
1049    #[test]
1050    fn init_higher_major_version_replies_with_supported_version() {
1051        // Kernel reports major version 8, higher than FUSE_KERNEL_VERSION (7).
1052        let request_data = make_init_request(8, 0, 131072, 0, 0);
1053
1054        let fs = InitCapturingFs::default();
1055        let info_ref = fs.info.clone();
1056        let session = Session::new(fs);
1057
1058        let mut sender = CapturingSender::default();
1059        session.dispatch(
1060            Request::new(request_data.as_slice()).unwrap(),
1061            &mut sender,
1062            None,
1063        );
1064
1065        // Session should NOT be marked initialized — the kernel will resend INIT.
1066        assert!(!session.is_initialized());
1067
1068        // The filesystem's init callback should NOT have been called.
1069        assert!(info_ref.lock().is_none());
1070
1071        // Reply should carry the supported version.
1072        let (hdr, init_out) = sender.parse_init_reply();
1073        assert_eq!(hdr.error, 0);
1074        assert_eq!(init_out.major, FUSE_KERNEL_VERSION);
1075        assert_eq!(init_out.minor, FUSE_KERNEL_MINOR_VERSION);
1076    }
1077
1078    #[test]
1079    fn init_old_unsupported_version_returns_error() {
1080        // Kernel reports version 7.26, below the minimum (7.27).
1081        let request_data = make_init_request(7, 26, 131072, 0, 0);
1082
1083        let fs = InitCapturingFs::default();
1084        let session = Session::new(fs);
1085
1086        let mut sender = ErrorCheckingSender::default();
1087        session.dispatch(
1088            Request::new(request_data.as_slice()).unwrap(),
1089            &mut sender,
1090            None,
1091        );
1092
1093        // Session should not be initialized after an unsupported version.
1094        assert!(!session.is_initialized());
1095
1096        // An error reply should have been sent.
1097        assert!(sender.last_error.is_some());
1098    }
1099
1100    #[test]
1101    fn init_negotiates_fuse_max_pages_when_kernel_supports_it() {
1102        // Kernel advertises FUSE_MAX_PAGES.
1103        let flags = 0x003FFFFB | FUSE_MAX_PAGES;
1104        let request_data = make_init_request(7, 39, 131072, flags, 0);
1105
1106        let fs = InitCapturingFs::default();
1107        let info_ref = fs.info.clone();
1108        let session = Session::new(fs);
1109
1110        let mut sender = CapturingSender::default();
1111        session.dispatch(
1112            Request::new(request_data.as_slice()).unwrap(),
1113            &mut sender,
1114            None,
1115        );
1116
1117        assert!(session.is_initialized());
1118
1119        // The filesystem should see FUSE_MAX_PAGES in the negotiated flags.
1120        let info = info_ref.lock();
1121        let &(want, _want2, _capable) = info
1122            .as_ref()
1123            .expect("filesystem init info should be captured after initialization");
1124        assert_ne!(
1125            want & FUSE_MAX_PAGES,
1126            0,
1127            "FUSE_MAX_PAGES should be negotiated"
1128        );
1129
1130        // The reply must advertise FUSE_MAX_PAGES, the default max_write of
1131        // 256 pages * 4096 bytes, and the matching max_pages.
1132        let (_hdr, init_out) = sender.parse_init_reply();
1133        assert_ne!(
1134            init_out.flags & FUSE_MAX_PAGES,
1135            0,
1136            "Reply flags must include FUSE_MAX_PAGES"
1137        );
1138        assert_eq!(init_out.max_write, 256 * 4096);
1139        assert_eq!(init_out.max_pages, 256);
1140    }
1141
1142    #[test]
1143    fn init_without_max_pages_does_not_advertise_it() {
1144        // Kernel does NOT advertise FUSE_MAX_PAGES.
1145        let flags = 0x003FFFFB; // matches FUSE_INIT_REQUEST
1146        let request_data = make_init_request(7, 27, 131072, flags, 0);
1147
1148        let fs = InitCapturingFs::default();
1149        let session = Session::new(fs);
1150
1151        let mut sender = CapturingSender::default();
1152        session.dispatch(
1153            Request::new(request_data.as_slice()).unwrap(),
1154            &mut sender,
1155            None,
1156        );
1157
1158        assert!(session.is_initialized());
1159
1160        let (_hdr, init_out) = sender.parse_init_reply();
1161        assert_eq!(
1162            init_out.flags & FUSE_MAX_PAGES,
1163            0,
1164            "Reply flags must NOT include FUSE_MAX_PAGES when kernel lacks it"
1165        );
1166    }
1167
1168    #[test]
1169    fn init_max_pages_uses_ceiling_division() {
1170        // The fix replaced integer division with div_ceil so that a max_write
1171        // that is not a whole number of pages still reports enough max_pages
1172        // to cover the largest possible request.
1173        //
1174        // 4097 bytes spans 2 pages; the old `max_write / PAGE_SIZE` produced 1.
1175        let flags = 0x003FFFFB | FUSE_MAX_PAGES;
1176        let request_data = make_init_request(7, 39, 131072, flags, 0);
1177
1178        let fs = InitCapturingFs {
1179            max_write_override: Some(4097),
1180            ..Default::default()
1181        };
1182        let session = Session::new(fs);
1183
1184        let mut sender = CapturingSender::default();
1185        session.dispatch(
1186            Request::new(request_data.as_slice()).unwrap(),
1187            &mut sender,
1188            None,
1189        );
1190
1191        assert!(session.is_initialized());
1192
1193        let (_hdr, init_out) = sender.parse_init_reply();
1194        assert_eq!(init_out.max_write, 4097);
1195        assert_eq!(
1196            init_out.max_pages, 2,
1197            "max_pages must round up to cover max_write"
1198        );
1199    }
1200
1201    /// Creates a FUSE_LOOKUP request with a name that's too long (256 bytes, exceeds NAME_MAX of 255)
1202    fn make_lookup_name_too_long() -> Vec<u8> {
1203        let mut data = vec![0u8; 297]; // 40 byte header + 256 byte name + 1 null terminator
1204
1205        // fuse_in_header (40 bytes):
1206        // len: u32 = 297 (0x129)
1207        data[0] = 0x29;
1208        data[1] = 0x01;
1209        data[2] = 0x00;
1210        data[3] = 0x00;
1211
1212        // opcode: u32 = 1 (FUSE_LOOKUP)
1213        data[4] = 0x01;
1214        data[5] = 0x00;
1215        data[6] = 0x00;
1216        data[7] = 0x00;
1217
1218        // unique: u64 = 99
1219        data[8] = 99;
1220        data[9] = 0x00;
1221        data[10] = 0x00;
1222        data[11] = 0x00;
1223        data[12] = 0x00;
1224        data[13] = 0x00;
1225        data[14] = 0x00;
1226        data[15] = 0x00;
1227
1228        // nodeid: u64 = 1
1229        data[16] = 0x01;
1230        data[17] = 0x00;
1231        data[18] = 0x00;
1232        data[19] = 0x00;
1233        data[20] = 0x00;
1234        data[21] = 0x00;
1235        data[22] = 0x00;
1236        data[23] = 0x00;
1237
1238        // uid: u32 = 0
1239        // gid: u32 = 0
1240        // pid: u32 = 971 (0x3CB)
1241        data[32] = 0xCB;
1242        data[33] = 0x03;
1243        data[34] = 0x00;
1244        data[35] = 0x00;
1245
1246        // padding: u32 = 0
1247
1248        // Name: 256 'a' characters (0x61) starting at byte 40
1249        for item in data.iter_mut().take(296).skip(40) {
1250            *item = 0x61; // 'a'
1251        }
1252        // Null terminator at byte 296
1253        data[296] = 0x00;
1254
1255        data
1256    }
1257}