Skip to content

Customizable mount flags for Linux #209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 20 additions & 1 deletion src/transport/fusedev/linux_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub struct FuseSession {
target_mntns: Option<libc::pid_t>,
// fusermount binary, default to fusermount3
fusermount: String,
mount_flags: Option<MsFlags>,
}

impl FuseSession {
Expand Down Expand Up @@ -97,6 +98,7 @@ impl FuseSession {
target_mntns: None,
fusermount: FUSERMOUNT_BIN.to_string(),
allow_other: true,
mount_flags: None,
})
}

Expand Down Expand Up @@ -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<MsFlags> {
self.mount_flags
}

/// Clone fuse file using ioctl FUSE_DEV_IOC_CLONE.
pub fn clone_fuse_file(&self) -> Result<File> {
let mut old_fd = self
Expand Down Expand Up @@ -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;
}
Expand Down
13 changes: 13 additions & 0 deletions tests/example/passthroughfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -19,6 +20,7 @@ pub struct Daemon {
server: Arc<Server<Arc<Vfs>>>,
thread_cnt: u32,
session: Option<FuseSession>,
mount_flags: Option<MsFlags>,
}

#[allow(dead_code)]
Expand Down Expand Up @@ -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 {
Expand Down
86 changes: 86 additions & 0 deletions tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> {
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<()> {
Expand All @@ -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(())
}
}