From 47996808415af4e0b7bd11db103b52d5fdd5fda7 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Mon, 23 Nov 2020 11:54:55 -0800 Subject: [PATCH] fuse: provide a mount API The mount API is useful especially in an environment without typical fusermount setuid program (e.g. Android). BUG=b:173507504 TEST=create a FUSE fs, run with cap_sys_admin, can read from the mount point TEST=build_test.py Change-Id: Ibfc220e8cf59b54d55f5d030d2e4c4375d3654cb Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2556079 Commit-Queue: Victor Hsieh Tested-by: Victor Hsieh Reviewed-by: Chirantan Ekbote --- fuse/src/lib.rs | 2 + fuse/src/mount.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 fuse/src/mount.rs diff --git a/fuse/src/lib.rs b/fuse/src/lib.rs index e1704d77e3..8da2265de6 100644 --- a/fuse/src/lib.rs +++ b/fuse/src/lib.rs @@ -10,11 +10,13 @@ use thiserror::Error as ThisError; pub mod filesystem; #[cfg(fuzzing)] pub mod fuzzing; +pub mod mount; mod server; #[allow(dead_code)] pub mod sys; pub mod worker; +pub use mount::mount; pub use server::{Reader, Server, Writer}; /// Errors that may occur during the creation or operation of an Fs device. diff --git a/fuse/src/mount.rs b/fuse/src/mount.rs new file mode 100644 index 0000000000..66276d4d4e --- /dev/null +++ b/fuse/src/mount.rs @@ -0,0 +1,126 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::ffi::{CString, OsStr}; +use std::fmt; +use std::io; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::RawFd; + +/// Mount options to pass to mount(2) for a FUSE filesystem. See the [official document]( +/// https://www.kernel.org/doc/html/latest/filesystems/fuse.html#mount-options) for the +/// descriptions. +pub enum MountOption { + FD(RawFd), + RootMode(u32), + UserId(libc::uid_t), + GroupId(libc::gid_t), + DefaultPermissions, + AllowOther, + MaxRead(u32), + BlockSize(u32), +} + +// Implement Display for ToString to convert to actual mount options. +impl fmt::Display for MountOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + MountOption::FD(fd) => write!(f, "fd={}", fd), + MountOption::RootMode(mode) => write!(f, "rootmode={:o}", mode), + MountOption::UserId(uid) => write!(f, "user_id={}", uid), + MountOption::GroupId(gid) => write!(f, "group_id={}", gid), + MountOption::DefaultPermissions => write!(f, "default_permissions"), + MountOption::AllowOther => write!(f, "allow_other"), + MountOption::MaxRead(size) => write!(f, "max_read={}", size), + MountOption::BlockSize(size) => write!(f, "blksize={}", size), + } + } +} + +fn join_mount_options(options: &[MountOption]) -> String { + if !options.is_empty() { + let mut concat = options[0].to_string(); + for opt in &options[1..] { + concat.push(','); + concat.push_str(&opt.to_string()); + } + concat + } else { + String::new() + } +} + +/// Initiates a FUSE mount at `mountpoint` directory with `flags` and `options` via mount(2). The +/// caller should provide a file descriptor (backed by /dev/fuse) with `MountOption::FD`. After +/// this function completes, the FUSE filesystem can start to handle the requests, e.g. via +/// `fuse::worker::start_message_loop()`. +/// +/// This operation requires CAP_SYS_ADMIN privilege, but the privilege can be dropped afterward. +pub fn mount>( + mountpoint: P, + name: &str, + flags: libc::c_ulong, + options: &[MountOption], +) -> Result<(), io::Error> { + let mount_name = CString::new(name.as_bytes())?; + let fs_type = CString::new(String::from("fuse.") + name)?; + let mountpoint = CString::new(mountpoint.as_ref().as_bytes())?; + let mount_options = CString::new(join_mount_options(options))?; + + // Safe because pointer arguments all points to null-terminiated CStrings. + let retval = unsafe { + libc::mount( + mount_name.as_ptr(), + mountpoint.as_ptr(), + fs_type.as_ptr(), + flags, + mount_options.as_ptr() as *const std::ffi::c_void, + ) + }; + if retval < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_options_concatenate_in_order() { + assert_eq!("".to_string(), join_mount_options(&[])); + + assert_eq!( + "fd=42".to_string(), + join_mount_options(&[MountOption::FD(42),]) + ); + + assert_eq!( + "fd=42,rootmode=40111,allow_other,user_id=12,group_id=34,max_read=4096".to_string(), + join_mount_options(&[ + MountOption::FD(42), + MountOption::RootMode( + libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH + ), + MountOption::AllowOther, + MountOption::UserId(12), + MountOption::GroupId(34), + MountOption::MaxRead(4096), + ]) + ); + + assert_eq!( + "fd=42,default_permissions,user_id=12,group_id=34,max_read=4096".to_string(), + join_mount_options(&[ + MountOption::FD(42), + MountOption::DefaultPermissions, + MountOption::UserId(12), + MountOption::GroupId(34), + MountOption::MaxRead(4096), + ]) + ); + } +}