fuse/
mount.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::ffi::CString;
6use std::ffi::OsStr;
7use std::fmt;
8use std::io;
9use std::os::unix::ffi::OsStrExt;
10use std::os::unix::io::RawFd;
11
12/// Mount options to pass to mount(2) for a FUSE filesystem. See the [official document](
13/// https://www.kernel.org/doc/html/latest/filesystems/fuse.html#mount-options) for the
14/// descriptions.
15pub enum MountOption<'a> {
16    FD(RawFd),
17    RootMode(u32),
18    UserId(libc::uid_t),
19    GroupId(libc::gid_t),
20    DefaultPermissions,
21    AllowOther,
22    MaxRead(u32),
23    BlockSize(u32),
24    // General mount options that are not specific to FUSE. Note that the value is not checked
25    // or interpreted by this library, but by kernel.
26    Extra(&'a str),
27}
28
29// Implement Display for ToString to convert to actual mount options.
30impl fmt::Display for MountOption<'_> {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match &self {
33            MountOption::FD(fd) => write!(f, "fd={fd}"),
34            MountOption::RootMode(mode) => write!(f, "rootmode={mode:o}"),
35            MountOption::UserId(uid) => write!(f, "user_id={uid}"),
36            MountOption::GroupId(gid) => write!(f, "group_id={gid}"),
37            MountOption::DefaultPermissions => write!(f, "default_permissions"),
38            MountOption::AllowOther => write!(f, "allow_other"),
39            MountOption::MaxRead(size) => write!(f, "max_read={size}"),
40            MountOption::BlockSize(size) => write!(f, "blksize={size}"),
41            MountOption::Extra(text) => write!(f, "{text}"),
42        }
43    }
44}
45
46fn join_mount_options(options: &[MountOption]) -> String {
47    if !options.is_empty() {
48        let mut concat = options[0].to_string();
49        for opt in &options[1..] {
50            concat.push(',');
51            concat.push_str(&opt.to_string());
52        }
53        concat
54    } else {
55        String::new()
56    }
57}
58
59/// Initiates a FUSE mount at `mountpoint` directory with `flags` and `options` via mount(2). The
60/// caller should provide a file descriptor (backed by /dev/fuse) with `MountOption::FD`. After
61/// this function completes, the FUSE filesystem can start to handle the requests, e.g. via
62/// `fuse::worker::start_message_loop()`.
63///
64/// This operation requires CAP_SYS_ADMIN privilege, but the privilege can be dropped afterward.
65pub fn mount<P: AsRef<OsStr>>(
66    mountpoint: P,
67    name: &str,
68    flags: libc::c_ulong,
69    options: &[MountOption],
70) -> Result<(), io::Error> {
71    let mount_name = CString::new(name.as_bytes())?;
72    let fs_type = CString::new(String::from("fuse.") + name)?;
73    let mountpoint = CString::new(mountpoint.as_ref().as_bytes())?;
74    let mount_options = CString::new(join_mount_options(options))?;
75
76    // SAFETY:
77    // Safe because pointer arguments all points to null-terminiated CStrings.
78    let retval = unsafe {
79        libc::mount(
80            mount_name.as_ptr(),
81            mountpoint.as_ptr(),
82            fs_type.as_ptr(),
83            flags,
84            mount_options.as_ptr() as *const std::ffi::c_void,
85        )
86    };
87    if retval < 0 {
88        Err(io::Error::last_os_error())
89    } else {
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn basic_options_concatenate_in_order() {
100        assert_eq!("".to_string(), join_mount_options(&[]));
101
102        assert_eq!(
103            "fd=42".to_string(),
104            join_mount_options(&[MountOption::FD(42),])
105        );
106
107        assert_eq!(
108            "fd=42,rootmode=40111,allow_other,user_id=12,group_id=34,max_read=4096".to_string(),
109            join_mount_options(&[
110                MountOption::FD(42),
111                MountOption::RootMode(
112                    libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH
113                ),
114                MountOption::AllowOther,
115                MountOption::UserId(12),
116                MountOption::GroupId(34),
117                MountOption::MaxRead(4096),
118            ])
119        );
120
121        assert_eq!(
122            "fd=42,default_permissions,user_id=12,group_id=34,max_read=4096".to_string(),
123            join_mount_options(&[
124                MountOption::FD(42),
125                MountOption::DefaultPermissions,
126                MountOption::UserId(12),
127                MountOption::GroupId(34),
128                MountOption::MaxRead(4096),
129            ])
130        );
131
132        assert_eq!(
133            "option1=a,option2=b".to_string(),
134            join_mount_options(&[MountOption::Extra("option1=a,option2=b"),])
135        );
136    }
137}