Skip to main content

fuser/
session.rs

1//! Filesystem session
2//!
3//! A session runs a filesystem implementation while it is being mounted to a specific mount
4//! point. A session begins by mounting the filesystem and ends by unmounting it. While the
5//! filesystem is mounted, the session loop receives, dispatches and replies to kernel requests
6//! for filesystem operations under its mount point.
7
8use std::borrow::Cow;
9use std::fs::File;
10use std::io;
11use std::os::fd::AsFd;
12use std::os::fd::BorrowedFd;
13use std::os::fd::OwnedFd;
14use std::path::Path;
15use std::sync::Arc;
16use std::thread::JoinHandle;
17use std::thread::{self};
18
19use log::debug;
20use log::error;
21use log::info;
22use log::warn;
23use nix::unistd::Uid;
24use nix::unistd::geteuid;
25use parking_lot::Mutex;
26
27use crate::Errno;
28use crate::Filesystem;
29use crate::KernelConfig;
30use crate::MountOption;
31use crate::ReplyEmpty;
32use crate::Request;
33use crate::channel::Channel;
34use crate::channel::ChannelSender;
35use crate::dev_fuse::DevFuse;
36use crate::ll;
37use crate::ll::Operation;
38use crate::ll::ResponseErrno;
39use crate::ll::Version;
40use crate::ll::flags::init_flags::InitFlags;
41use crate::ll::fuse_abi as abi;
42use crate::mnt::Mount;
43use crate::mnt::mount_options::Config;
44use crate::notify::Notifier;
45use crate::read_buf::FuseReadBuf;
46use crate::reply::Reply;
47use crate::reply::ReplyRaw;
48use crate::reply::ReplySender;
49use crate::request::RequestWithSender;
50
51/// The max size of write requests from the kernel. The absolute minimum is 4k,
52/// FUSE recommends at least 128k, max 16M. The FUSE default is 16M on macOS
53/// and 128k on other systems.
54pub(crate) const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024;
55
56#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
57/// How requests should be filtered based on the calling UID.
58pub enum SessionACL {
59    /// Allow requests from any user. Corresponds to the `allow_other` mount option.
60    All,
61    /// Allow requests from root. Corresponds to the `allow_root` mount option.
62    RootAndOwner,
63    /// Allow requests from the owning UID. This is FUSE's default mode of operation.
64    #[default]
65    Owner,
66}
67
68impl SessionACL {
69    /// Returns the mount option string for kernel/fusermount/libfuse paths.
70    /// Both `All` and `RootAndOwner` map to `allow_other` - the kernel only
71    /// understands `allow_other`, and fuser enforces the root-only restriction internally.
72    #[allow(dead_code)]
73    pub(crate) fn to_mount_option(self) -> Option<&'static str> {
74        match self {
75            SessionACL::All | SessionACL::RootAndOwner => Some("allow_other"),
76            SessionACL::Owner => None,
77        }
78    }
79}
80
81/// Calls `destroy` on drop.
82#[derive(Debug)]
83pub(crate) struct FilesystemHolder<FS: Filesystem> {
84    pub(crate) fs: Option<FS>,
85}
86
87impl<FS: Filesystem> FilesystemHolder<FS> {
88    fn destroy(&mut self) {
89        if let Some(mut fs) = self.fs.take() {
90            fs.destroy();
91        }
92    }
93}
94
95impl<FS: Filesystem> Drop for FilesystemHolder<FS> {
96    fn drop(&mut self) {
97        self.destroy();
98    }
99}
100
101#[derive(Debug)]
102struct UmountOnDrop {
103    mount: Arc<Mutex<Option<Mount>>>,
104}
105
106impl UmountOnDrop {
107    fn umount(&self) -> io::Result<()> {
108        if let Some(mount) = self.mount.lock().take() {
109            mount.umount()?;
110        }
111        Ok(())
112    }
113}
114
115impl Drop for UmountOnDrop {
116    fn drop(&mut self) {
117        if let Err(e) = self.umount() {
118            warn!("Failed to umount filesystem: {}", e);
119        }
120    }
121}
122
123/// The session data structure
124#[derive(Debug)]
125pub struct Session<FS: Filesystem> {
126    /// Filesystem operation implementations. None after `destroy` called.
127    pub(crate) filesystem: FilesystemHolder<FS>,
128    /// Communication channel to the kernel driver
129    pub(crate) ch: Channel,
130    /// Handle to the mount.  Dropping this unmounts.
131    mount: UmountOnDrop,
132    /// Whether to restrict access to owner, root + owner, or unrestricted
133    /// Used to implement `allow_root` and `auto_unmount`
134    pub(crate) allowed: SessionACL,
135    /// User that launched the fuser process
136    pub(crate) session_owner: Uid,
137    /// FUSE protocol version, as reported by the kernel.
138    /// The field is set to `Some` when the init message is received.
139    pub(crate) proto_version: Option<Version>,
140    pub(crate) config: Config,
141}
142
143impl<FS: Filesystem> AsFd for Session<FS> {
144    fn as_fd(&self) -> BorrowedFd<'_> {
145        self.ch.as_fd()
146    }
147}
148
149impl<FS: Filesystem> Session<FS> {
150    /// Create a new session by mounting the given filesystem to the given mountpoint
151    /// # Errors
152    /// Returns an error if the options are incorrect, or if the fuse device can't be mounted.
153    pub fn new<P: AsRef<Path>>(
154        filesystem: FS,
155        mountpoint: P,
156        options: &Config,
157    ) -> io::Result<Session<FS>> {
158        let mountpoint = mountpoint.as_ref();
159        info!("Mounting {}", mountpoint.display());
160        // If AutoUnmount is requested, but not AllowRoot or AllowOther, return an error
161        // because fusermount needs allow_root or allow_other to handle the auto_unmount option
162        if options.mount_options.contains(&MountOption::AutoUnmount)
163            && options.acl == SessionACL::Owner
164        {
165            return Err(io::Error::new(
166                io::ErrorKind::InvalidInput,
167                format!("auto_unmount requires acl != Owner, got: {:?}", options.acl),
168            ));
169        }
170        let (file, mount) = Mount::new(mountpoint, &options.mount_options, options.acl)?;
171
172        let ch = Channel::new(file);
173
174        let mut session = Session {
175            filesystem: FilesystemHolder {
176                fs: Some(filesystem),
177            },
178            ch,
179            mount: UmountOnDrop {
180                mount: Arc::new(Mutex::new(Some(mount))),
181            },
182            allowed: options.acl,
183            session_owner: geteuid(),
184            proto_version: None,
185            config: options.clone(),
186        };
187
188        session.handshake()?;
189
190        Ok(session)
191    }
192
193    /// Wrap an existing /dev/fuse file descriptor. This doesn't mount the
194    /// filesystem anywhere; that must be done separately.
195    pub fn from_fd(
196        filesystem: FS,
197        fd: OwnedFd,
198        acl: SessionACL,
199        config: Config,
200    ) -> io::Result<Self> {
201        let ch = Channel::new(Arc::new(DevFuse(File::from(fd))));
202        let mut session = Session {
203            filesystem: FilesystemHolder {
204                fs: Some(filesystem),
205            },
206            ch,
207            mount: UmountOnDrop {
208                mount: Arc::new(Mutex::new(None)),
209            },
210            allowed: acl,
211            session_owner: geteuid(),
212            proto_version: None,
213            config,
214        };
215
216        session.handshake()?;
217
218        Ok(session)
219    }
220
221    /// Run the session loop in a background thread. If the returned handle is dropped,
222    /// the filesystem is unmounted and the given session ends.
223    pub fn spawn(self) -> io::Result<BackgroundSession> {
224        let sender = self.ch.sender();
225        // Take the fuse_session, so that we can unmount it
226        let mount = std::mem::take(&mut *self.mount.mount.lock());
227        let guard = thread::Builder::new()
228            .name("fuser-bg".to_string())
229            .spawn(move || self.run())?;
230        Ok(BackgroundSession {
231            guard,
232            sender,
233            mount,
234        })
235    }
236
237    /// Run the session loop that receives kernel requests and dispatches them to method
238    /// calls into the filesystem. This read-dispatch-loop is non-concurrent to prevent
239    /// having multiple buffers (which take up much memory), but the filesystem methods
240    /// may run concurrent by spawning threads.
241    /// # Errors
242    /// Returns any final error when the session comes to an end.
243    pub(crate) fn run(self) -> io::Result<()> {
244        let Session {
245            filesystem,
246            ch,
247            mount: _do_not_umount_yet,
248            allowed,
249            session_owner,
250            proto_version: _,
251            config,
252        } = self;
253
254        let n_threads = config.n_threads.unwrap_or(1);
255
256        if !cfg!(target_os = "linux") && n_threads != 1 {
257            // TODO: check whether it works on macOS/FreeBSD and enable if it works.
258            return Err(io::Error::other(
259                "n_threads != 1 is only supported on Linux",
260            ));
261        }
262
263        let Some(n_threads_minus_one) = n_threads.checked_sub(1) else {
264            return Err(io::Error::other("n_threads"));
265        };
266
267        let mut filesystem = Arc::new(filesystem);
268
269        let mut channels = Vec::with_capacity(n_threads);
270
271        for _ in 0..n_threads_minus_one {
272            if config.clone_fd {
273                #[cfg(target_os = "linux")]
274                {
275                    channels.push(ch.clone_fd()?);
276                    continue;
277                }
278                #[cfg(not(target_os = "linux"))]
279                {
280                    return Err(io::Error::other("clone_fd is only supported on Linux"));
281                }
282            } else {
283                channels.push(ch.clone());
284            }
285        }
286        channels.push(ch);
287
288        let mut threads = Vec::with_capacity(n_threads);
289
290        for (i, ch) in channels.into_iter().enumerate() {
291            let thread_name = format!("fuser-{i}");
292            let event_loop = SessionEventLoop {
293                thread_name: thread_name.clone(),
294                filesystem: filesystem.clone(),
295                ch,
296                allowed,
297                session_owner,
298            };
299            threads.push(
300                thread::Builder::new()
301                    .name(thread_name)
302                    .spawn(move || event_loop.event_loop())?,
303            );
304        }
305
306        let mut reply: io::Result<()> = Ok(());
307        for thread in threads {
308            let res = match thread.join() {
309                Ok(res) => res,
310                Err(_) => {
311                    return Err(io::Error::other("event loop thread panicked"));
312                }
313            };
314            if let Err(e) = res {
315                if reply.is_ok() {
316                    reply = Err(e);
317                }
318            }
319        }
320
321        let Some(filesystem) = Arc::get_mut(&mut filesystem) else {
322            return Err(io::Error::other(
323                "BUG: must have one refcount for filesystem",
324            ));
325        };
326
327        filesystem.destroy();
328
329        reply
330    }
331
332    fn handshake(&mut self) -> io::Result<()> {
333        let mut buf = FuseReadBuf::new();
334        let buf = buf.as_mut();
335
336        loop {
337            // Read the init request from the kernel
338            let size = match self.ch.receive_retrying(buf) {
339                Ok(size) => size,
340                Err(nix::errno::Errno::ENODEV) => {
341                    return Err(io::Error::new(
342                        io::ErrorKind::NotConnected,
343                        "FUSE device disconnected during handshake",
344                    ));
345                }
346                Err(err) => return Err(err.into()),
347            };
348
349            // Parse the request
350            let request = match ll::AnyRequest::try_from(&buf[..size]) {
351                Ok(request) => request,
352                Err(err) => {
353                    error!("{err}");
354                    return Err(io::Error::new(io::ErrorKind::InvalidData, err.to_string()));
355                }
356            };
357
358            // Extract the init operation
359            let op = match request.operation() {
360                Ok(op) => op,
361                Err(_) => {
362                    return Err(io::Error::new(
363                        io::ErrorKind::InvalidData,
364                        "Failed to parse FUSE operation",
365                    ));
366                }
367            };
368
369            let init = match op {
370                ll::Operation::Init(init) => init,
371                _ => {
372                    error!("Received non-init FUSE operation before init: {}", request);
373                    // Send error response and return error - non-init during handshake is invalid
374                    <ReplyRaw as Reply>::new(
375                        request.unique(),
376                        ReplySender::Channel(self.ch.sender()),
377                    )
378                    .send_ll(&ResponseErrno(ll::Errno::EIO));
379                    return Err(io::Error::new(
380                        io::ErrorKind::InvalidData,
381                        "Received non-init FUSE operation during handshake",
382                    ));
383                }
384            };
385
386            let v = init.version();
387            if v.0 > abi::FUSE_KERNEL_VERSION {
388                // Kernel has a newer major version than we support.
389                // Send our version and wait for a second INIT request with a compatible version.
390                debug!(
391                    "INIT: Kernel version {} > our version {}, sending our version and waiting for next init",
392                    v.0,
393                    abi::FUSE_KERNEL_VERSION
394                );
395                let response = init.reply_version_only();
396                <ReplyRaw as Reply>::new(request.unique(), ReplySender::Channel(self.ch.sender()))
397                    .send_ll(&response);
398                continue;
399            }
400
401            // We don't support ABI versions before 7.6
402            if v < Version(7, 6) {
403                error!("Unsupported FUSE ABI version {v}");
404                <ReplyRaw as Reply>::new(request.unique(), ReplySender::Channel(self.ch.sender()))
405                    .send_ll(&ResponseErrno(ll::Errno::EPROTO));
406                return Err(io::Error::new(
407                    io::ErrorKind::Unsupported,
408                    format!("Unsupported FUSE ABI version {v}"),
409                ));
410            }
411
412            let mut config = KernelConfig::new(init.capabilities(), init.max_readahead(), v);
413
414            // Call filesystem init method and give it a chance to return an error
415            let Some(filesystem) = &mut self.filesystem.fs else {
416                return Err(io::Error::new(
417                    io::ErrorKind::InvalidInput,
418                    "Bug: filesystem must be initialized during handshake",
419                ));
420            };
421            let res = filesystem.init(Request::ref_cast(request.header()), &mut config);
422            if let Err(error) = res {
423                let errno = Errno::from_i32(error.raw_os_error().unwrap_or(0));
424                <ReplyRaw as Reply>::new(request.unique(), ReplySender::Channel(self.ch.sender()))
425                    .send_ll(&ResponseErrno(errno));
426                return Err(error);
427            }
428
429            // Remember the ABI version supported by kernel and mark the session initialized.
430            self.proto_version = Some(v);
431
432            // Log capability status for debugging
433            for bit in 0..64 {
434                let bitflags = InitFlags::from_bits_retain(1 << bit);
435                if bitflags == InitFlags::FUSE_INIT_EXT {
436                    continue;
437                }
438                let bitflag_is_known = InitFlags::all().contains(bitflags);
439                let kernel_supports = init.capabilities().contains(bitflags);
440                let we_requested = config.requested.contains(bitflags);
441                // On macOS, there's a clash between linux and macOS constants,
442                // so we pick macOS ones (last).
443                let name = if let Some((name, _)) = bitflags.iter_names().last() {
444                    Cow::Borrowed(name)
445                } else {
446                    Cow::Owned(format!("(1 << {bit})"))
447                };
448                if we_requested && kernel_supports {
449                    debug!("capability {name} enabled")
450                } else if we_requested {
451                    debug!("capability {name} not supported by kernel")
452                } else if kernel_supports {
453                    debug!("capability {name} not requested by client")
454                } else if bitflag_is_known {
455                    debug!("capability {name} not supported nor requested")
456                }
457            }
458
459            // Reply with our desired version and settings.
460            debug!(
461                "INIT response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}",
462                abi::FUSE_KERNEL_VERSION,
463                abi::FUSE_KERNEL_MINOR_VERSION,
464                init.capabilities() & config.requested,
465                config.max_readahead,
466                config.max_write
467            );
468
469            let response = init.reply(&config);
470            <ReplyRaw as Reply>::new(request.unique(), ReplySender::Channel(self.ch.sender()))
471                .send_ll(&response);
472
473            return Ok(());
474        }
475    }
476
477    /// Unmount the filesystem
478    pub fn unmount(&mut self) -> io::Result<()> {
479        self.mount.umount()
480    }
481
482    /// Returns a thread-safe object that can be used to unmount the Filesystem
483    pub fn unmount_callable(&mut self) -> SessionUnmounter {
484        SessionUnmounter {
485            mount: self.mount.mount.clone(),
486        }
487    }
488
489    /// Returns an object that can be used to send notifications to the kernel
490    pub fn notifier(&self) -> Notifier {
491        Notifier::new(self.ch.sender())
492    }
493}
494
495#[derive(Debug)]
496/// A thread-safe object that can be used to unmount a Filesystem
497pub struct SessionUnmounter {
498    mount: Arc<Mutex<Option<Mount>>>,
499}
500
501impl SessionUnmounter {
502    /// Unmount the filesystem
503    pub fn unmount(&mut self) -> io::Result<()> {
504        if let Some(mount) = std::mem::take(&mut *self.mount.lock()) {
505            mount.umount()?;
506        }
507        Ok(())
508    }
509}
510
511pub(crate) struct SessionEventLoop<FS: Filesystem> {
512    /// Cache thread name for faster `debug!`.
513    pub(crate) thread_name: String,
514    pub(crate) ch: Channel,
515    pub(crate) filesystem: Arc<FilesystemHolder<FS>>,
516    pub(crate) allowed: SessionACL,
517    pub(crate) session_owner: Uid,
518}
519
520impl<FS: Filesystem> SessionEventLoop<FS> {
521    fn event_loop(&self) -> io::Result<()> {
522        // Buffer for receiving requests from the kernel. Only one is allocated and
523        // it is reused immediately after dispatching to conserve memory and allocations.
524        let mut buf = FuseReadBuf::new();
525        let buf = buf.as_mut();
526        loop {
527            // Read the next request from the given channel to kernel driver
528            // The kernel driver makes sure that we get exactly one request per read
529            match self.ch.receive_retrying(buf) {
530                Ok(size) => match RequestWithSender::new(self.ch.sender(), &buf[..size]) {
531                    // Dispatch request
532                    Some(req) => {
533                        if let Ok(Operation::Destroy(_)) = req.request.operation() {
534                            req.reply::<ReplyEmpty>().ok();
535                            return Ok(());
536                        } else {
537                            req.dispatch(self)
538                        }
539                    }
540                    // Quit loop on illegal request
541                    None => {
542                        return Err(io::Error::new(
543                            io::ErrorKind::InvalidData,
544                            "Invalid request",
545                        ));
546                    }
547                },
548                Err(nix::errno::Errno::ENODEV) => return Ok(()),
549                Err(err) => return Err(err.into()),
550            }
551        }
552    }
553}
554
555/// The background session data structure
556#[derive(Debug)]
557pub struct BackgroundSession {
558    /// Thread guard of the background session
559    pub guard: JoinHandle<io::Result<()>>,
560    /// Object for creating Notifiers for client use
561    sender: ChannelSender,
562    /// Ensures the filesystem is unmounted when the session ends
563    mount: Option<Mount>,
564}
565
566impl BackgroundSession {
567    /// Unmount the filesystem and join the background thread.
568    pub fn umount_and_join(mut self) -> io::Result<()> {
569        if let Some(mount) = self.mount.take() {
570            mount.umount()?;
571        }
572        self.join()
573    }
574
575    /// Returns an object that can be used to send notifications to the kernel
576    pub fn notifier(&self) -> Notifier {
577        Notifier::new(self.sender.clone())
578    }
579
580    /// Join the filesystem thread.
581    pub fn join(self) -> io::Result<()> {
582        self.guard
583            .join()
584            .map_err(|_panic: Box<dyn std::any::Any + Send>| {
585                io::Error::new(
586                    io::ErrorKind::Other,
587                    "filesystem background thread panicked",
588                )
589            })?
590    }
591}