From 8977b05c314e6bab89a727f83c6e470b0c5f6123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Sanchez?= Date: Wed, 23 Jul 2025 23:34:35 +0200 Subject: [PATCH] Allow setting custom mount flags --- src/transport/fusedev/linux_session.rs | 21 ++++++- tests/example/passthroughfs.rs | 13 ++++ tests/smoke.rs | 86 ++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/transport/fusedev/linux_session.rs b/src/transport/fusedev/linux_session.rs index 0d3ba8362..1dd6e5194 100644 --- a/src/transport/fusedev/linux_session.rs +++ b/src/transport/fusedev/linux_session.rs @@ -56,6 +56,7 @@ pub struct FuseSession { target_mntns: Option, // fusermount binary, default to fusermount3 fusermount: String, + mount_flags: Option, } impl FuseSession { @@ -97,6 +98,7 @@ impl FuseSession { target_mntns: None, fusermount: FUSERMOUNT_BIN.to_string(), allow_other: true, + mount_flags: None, }) } @@ -133,6 +135,21 @@ impl FuseSession { self.file = Some(file); } + /// Set custom mount flags for the session. + /// If not set, default flags (MS_NOSUID | MS_NODEV | MS_NOATIME) will be + /// used. MS_RDONLY will be added automatically if the session is readonly. + /// Not setting MS_NOSUID and MS_NODEV will probably get ignored by + /// fusermount3 for security reasons, so it means you need to be root to + /// mount the FS. + pub fn set_mount_flags(&mut self, flags: MsFlags) { + self.mount_flags = Some(flags); + } + + /// Get the currently configured mount flags, or None if using defaults. + pub fn get_mount_flags(&self) -> Option { + self.mount_flags + } + /// Clone fuse file using ioctl FUSE_DEV_IOC_CLONE. pub fn clone_fuse_file(&self) -> Result { let mut old_fd = self @@ -184,7 +201,9 @@ impl FuseSession { /// Mount the fuse mountpoint, building connection with the in kernel fuse driver. pub fn mount(&mut self) -> Result<()> { - let mut flags = MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOATIME; + let mut flags = self.mount_flags.unwrap_or( + MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOATIME + ); if self.readonly { flags |= MsFlags::MS_RDONLY; } diff --git a/tests/example/passthroughfs.rs b/tests/example/passthroughfs.rs index 856c8a261..ccea8b02c 100644 --- a/tests/example/passthroughfs.rs +++ b/tests/example/passthroughfs.rs @@ -11,6 +11,7 @@ use std::thread; use fuse_backend_rs::api::{server::Server, Vfs, VfsOptions}; use fuse_backend_rs::passthrough::{Config, PassthroughFs}; use fuse_backend_rs::transport::{FuseChannel, FuseSession}; +use nix::mount::MsFlags; /// A fusedev daemon example #[allow(dead_code)] @@ -19,6 +20,7 @@ pub struct Daemon { server: Arc>>, thread_cnt: u32, session: Option, + mount_flags: Option, } #[allow(dead_code)] @@ -47,14 +49,25 @@ impl Daemon { server: Arc::new(Server::new(Arc::new(vfs))), thread_cnt, session: None, + mount_flags: None, }) } + /// Set custom mount flags to pass to the session + pub fn set_mount_flags(&mut self, flags: MsFlags) { + self.mount_flags = Some(flags); + } + /// Mounts a fusedev daemon to the mountpoint, then start service threads to handle /// FUSE requests. pub fn mount(&mut self) -> Result<()> { let mut se = FuseSession::new(Path::new(&self.mountpoint), "passthru_example", "", false).unwrap(); + + if let Some(flags) = self.mount_flags { + se.set_mount_flags(flags); + } + se.mount().unwrap(); for _ in 0..self.thread_cnt { let mut server = FuseServer { diff --git a/tests/smoke.rs b/tests/smoke.rs index c3261f692..d28609697 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -15,6 +15,7 @@ mod fusedev_tests { use std::path::Path; use std::process::Command; + use nix::mount::MsFlags; use vmm_sys_util::tempdir::TempDir; use crate::example::passthroughfs; @@ -79,6 +80,55 @@ mod fusedev_tests { return Ok(stdout.to_string()); } + /// Validates that the mounted filesystem has the expected mount flags + fn validate_mount_flags(mountpoint: &str, expected_flags: MsFlags) -> bool { + // Use findmnt to get the mount flags + let cmd = format!("findmnt -no OPTIONS {}", mountpoint); + let output = match exec(&cmd) { + Ok(out) => out, + Err(_) => return false, + }; + + // Convert the expected flags to a string representation + let expected_flags_str = msflags_to_string_set(expected_flags); + + // Check if all expected flags are present in the output + for flag in expected_flags_str { + if !output.contains(&flag) { + error!("Expected flag '{}' not found in mount options: {}", flag, output); + return false; + } + } + + true + } + + /// Converts MsFlags to a set of string representations + fn msflags_to_string_set(flags: MsFlags) -> Vec { + let mut result = Vec::new(); + + if flags.contains(MsFlags::MS_RDONLY) { + result.push("ro".to_string()); + } + if flags.contains(MsFlags::MS_NOSUID) { + result.push("nosuid".to_string()); + } + if flags.contains(MsFlags::MS_NODEV) { + result.push("nodev".to_string()); + } + if flags.contains(MsFlags::MS_NOEXEC) { + result.push("noexec".to_string()); + } + if flags.contains(MsFlags::MS_SYNCHRONOUS) { + result.push("sync".to_string()); + } + if flags.contains(MsFlags::MS_NOATIME) { + result.push("noatime".to_string()); + } + + result + } + #[test] #[ignore] // it depends on privileged mode to pass through /dev/fuse fn integration_test_tree_gitrepo() -> Result<()> { @@ -99,4 +149,40 @@ mod fusedev_tests { daemon.umount().unwrap(); Ok(()) } + + #[test] + #[ignore] + fn integration_test_mount_flags() -> Result<()> { + // Test custom mount flags + let src = Path::new(".").canonicalize().unwrap(); + let src_dir = src.to_str().unwrap(); + let tmp_dir = TempDir::new().unwrap(); + let mnt_dir = tmp_dir.as_path().to_str().unwrap(); + info!( + "test mount flags src {:?} mountpoint {}", + src_dir, mnt_dir + ); + + // Create a set of custom mount flags + let custom_flags = MsFlags::MS_NODEV | MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC; + + let mut daemon = passthroughfs::Daemon::new(src_dir, mnt_dir, 2).unwrap(); + + // Set the custom mount flags + daemon.set_mount_flags(custom_flags); + + // Mount the filesystem + daemon.mount().unwrap(); + + // Wait for the mount to complete + std::thread::sleep(std::time::Duration::from_millis(100)); + + // Validate that the mounted filesystem has the expected flags + assert!(validate_mount_flags(mnt_dir, custom_flags)); + + // Unmount the filesystem + daemon.umount().unwrap(); + + Ok(()) + } }