Skip to main content

fuser/mnt/
mount_options.rs

1use std::collections::HashSet;
2use std::io;
3use std::io::ErrorKind;
4
5use crate::SessionACL;
6
7/// Fuser session configuration, including mount options.
8#[derive(Debug, Clone, Default, Eq, PartialEq)]
9#[non_exhaustive]
10pub struct Config {
11    /// Mount options.
12    pub mount_options: Vec<MountOption>,
13    /// Who can access the filesystem.
14    pub acl: SessionACL,
15    /// Number of event loop threads. If unspecified, one thread is used.
16    pub n_threads: Option<usize>,
17    /// Use `FUSE_DEV_IOC_CLONE` to give each worker thread its own fd.
18    /// This enables more efficient request processing
19    /// when multiple threads are used. Requires Linux 4.5+.
20    pub clone_fd: bool,
21}
22
23/// Mount options accepted by the FUSE filesystem type
24/// See 'man mount.fuse' for details
25// TODO: add all options that 'man mount.fuse' documents and libfuse supports
26#[derive(Debug, Eq, PartialEq, Hash, Clone)]
27pub enum MountOption {
28    /// Set the name of the source in mtab
29    FSName(String),
30    /// Set the filesystem subtype in mtab
31    Subtype(String),
32    /// Allows passing an option which is not otherwise supported in these enums
33    #[allow(clippy::upper_case_acronyms)]
34    CUSTOM(String),
35
36    /* Parameterless options */
37    /// Automatically unmount when the mounting process exits
38    ///
39    /// `AutoUnmount` requires `AllowOther` or `AllowRoot`. If `AutoUnmount` is set and neither `Allow...` is set, the FUSE configuration must permit `allow_other`, otherwise mounting will fail.
40    AutoUnmount,
41    /// Enable permission checking in the kernel
42    DefaultPermissions,
43
44    /* Flags */
45    /// Enable special character and block devices
46    Dev,
47    /// Disable special character and block devices
48    NoDev,
49    /// Honor set-user-id and set-groupd-id bits on files
50    Suid,
51    /// Don't honor set-user-id and set-groupd-id bits on files
52    NoSuid,
53    /// Read-only filesystem
54    RO,
55    /// Read-write filesystem
56    RW,
57    /// Allow execution of binaries
58    Exec,
59    /// Don't allow execution of binaries
60    NoExec,
61    /// Support inode access time
62    Atime,
63    /// Don't update inode access time
64    NoAtime,
65    /// All modifications to directories will be done synchronously
66    DirSync,
67    /// All I/O will be done synchronously
68    Sync,
69    /// All I/O will be done asynchronously
70    Async,
71    /* libfuse library options, such as "direct_io", are not included since they are specific
72    to libfuse, and not part of the kernel ABI */
73}
74
75impl MountOption {
76    #[cfg(test)]
77    fn from_str(s: &str) -> MountOption {
78        match s {
79            "auto_unmount" => MountOption::AutoUnmount,
80            "default_permissions" => MountOption::DefaultPermissions,
81            "dev" => MountOption::Dev,
82            "nodev" => MountOption::NoDev,
83            "suid" => MountOption::Suid,
84            "nosuid" => MountOption::NoSuid,
85            "ro" => MountOption::RO,
86            "rw" => MountOption::RW,
87            "exec" => MountOption::Exec,
88            "noexec" => MountOption::NoExec,
89            "atime" => MountOption::Atime,
90            "noatime" => MountOption::NoAtime,
91            "dirsync" => MountOption::DirSync,
92            "sync" => MountOption::Sync,
93            "async" => MountOption::Async,
94            x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()),
95            x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()),
96            x => MountOption::CUSTOM(x.into()),
97        }
98    }
99}
100
101pub(crate) fn check_option_conflicts(options: &Config) -> Result<(), io::Error> {
102    let mut options_set = HashSet::new();
103    options_set.extend(options.mount_options.iter().cloned());
104    let conflicting: HashSet<MountOption> = options
105        .mount_options
106        .iter()
107        .flat_map(conflicts_with)
108        .collect();
109    let intersection: Vec<MountOption> = conflicting.intersection(&options_set).cloned().collect();
110    if intersection.is_empty() {
111        Ok(())
112    } else {
113        Err(io::Error::new(
114            ErrorKind::InvalidInput,
115            format!("Conflicting mount options found: {intersection:?}"),
116        ))
117    }
118}
119
120fn conflicts_with(option: &MountOption) -> Vec<MountOption> {
121    match option {
122        MountOption::FSName(_)
123        | MountOption::Subtype(_)
124        | MountOption::CUSTOM(_)
125        | MountOption::DirSync
126        | MountOption::AutoUnmount
127        | MountOption::DefaultPermissions => vec![],
128        MountOption::Dev => vec![MountOption::NoDev],
129        MountOption::NoDev => vec![MountOption::Dev],
130        MountOption::Suid => vec![MountOption::NoSuid],
131        MountOption::NoSuid => vec![MountOption::Suid],
132        MountOption::RO => vec![MountOption::RW],
133        MountOption::RW => vec![MountOption::RO],
134        MountOption::Exec => vec![MountOption::NoExec],
135        MountOption::NoExec => vec![MountOption::Exec],
136        MountOption::Atime => vec![MountOption::NoAtime],
137        MountOption::NoAtime => vec![MountOption::Atime],
138        MountOption::Sync => vec![MountOption::Async],
139        MountOption::Async => vec![MountOption::Sync],
140    }
141}
142
143// Format option to be passed to libfuse or kernel
144#[allow(dead_code)]
145pub(crate) fn option_to_string(option: &MountOption) -> String {
146    match option {
147        MountOption::FSName(name) => format!("fsname={name}"),
148        MountOption::Subtype(subtype) => format!("subtype={subtype}"),
149        MountOption::CUSTOM(value) => value.to_string(),
150        MountOption::AutoUnmount => "auto_unmount".to_string(),
151        MountOption::DefaultPermissions => "default_permissions".to_string(),
152        MountOption::Dev => "dev".to_string(),
153        MountOption::NoDev => "nodev".to_string(),
154        MountOption::Suid => "suid".to_string(),
155        MountOption::NoSuid => "nosuid".to_string(),
156        MountOption::RO => "ro".to_string(),
157        MountOption::RW => "rw".to_string(),
158        MountOption::Exec => "exec".to_string(),
159        MountOption::NoExec => "noexec".to_string(),
160        MountOption::Atime => "atime".to_string(),
161        MountOption::NoAtime => "noatime".to_string(),
162        MountOption::DirSync => "dirsync".to_string(),
163        MountOption::Sync => "sync".to_string(),
164        MountOption::Async => "async".to_string(),
165    }
166}
167
168#[cfg(test)]
169mod test {
170    use crate::mnt::mount_options::*;
171
172    #[test]
173    fn option_checking() {
174        assert!(
175            check_option_conflicts(&Config {
176                mount_options: vec![MountOption::Suid, MountOption::NoSuid],
177                ..Config::default()
178            })
179            .is_err()
180        );
181        assert!(
182            check_option_conflicts(&Config {
183                mount_options: vec![MountOption::Suid, MountOption::NoExec],
184                ..Config::default()
185            })
186            .is_ok()
187        );
188    }
189    #[test]
190    fn option_round_trip() {
191        use crate::mnt::mount_options::MountOption::*;
192        for x in &[
193            FSName("Blah".to_owned()),
194            Subtype("Bloo".to_owned()),
195            CUSTOM("bongos".to_owned()),
196            AutoUnmount,
197            DefaultPermissions,
198            Dev,
199            NoDev,
200            Suid,
201            NoSuid,
202            RO,
203            RW,
204            Exec,
205            NoExec,
206            Atime,
207            NoAtime,
208            DirSync,
209            Sync,
210            Async,
211        ] {
212            assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref()));
213        }
214    }
215}