1use std::collections::HashSet;
2use std::io;
3use std::io::ErrorKind;
4
5use crate::SessionACL;
6
7#[derive(Debug, Clone, Default, Eq, PartialEq)]
9#[non_exhaustive]
10pub struct Config {
11 pub mount_options: Vec<MountOption>,
13 pub acl: SessionACL,
15 pub n_threads: Option<usize>,
17 pub clone_fd: bool,
21}
22
23#[derive(Debug, Eq, PartialEq, Hash, Clone)]
27pub enum MountOption {
28 FSName(String),
30 Subtype(String),
32 #[allow(clippy::upper_case_acronyms)]
34 CUSTOM(String),
35
36 AutoUnmount,
41 DefaultPermissions,
43
44 Dev,
47 NoDev,
49 Suid,
51 NoSuid,
53 RO,
55 RW,
57 Exec,
59 NoExec,
61 Atime,
63 NoAtime,
65 DirSync,
67 Sync,
69 Async,
71 }
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
101#[allow(dead_code)]
102#[derive(PartialEq)]
103pub(crate) enum MountOptionGroup {
104 KernelOption,
105 KernelFlag,
106 Fusermount,
107}
108
109pub(crate) fn check_option_conflicts(config: &Config) -> Result<(), io::Error> {
110 let options = &config.mount_options;
111 let mut options_set = HashSet::new();
112 options_set.extend(options.iter().cloned());
113 let conflicting: HashSet<MountOption> = options.iter().flat_map(conflicts_with).collect();
114 let intersection: Vec<MountOption> = conflicting.intersection(&options_set).cloned().collect();
115 if intersection.is_empty() {
116 Ok(())
117 } else {
118 Err(io::Error::new(
119 ErrorKind::InvalidInput,
120 format!("Conflicting mount options found: {intersection:?}"),
121 ))
122 }
123}
124
125fn conflicts_with(option: &MountOption) -> Vec<MountOption> {
126 match option {
127 MountOption::FSName(_)
128 | MountOption::Subtype(_)
129 | MountOption::CUSTOM(_)
130 | MountOption::DirSync
131 | MountOption::AutoUnmount
132 | MountOption::DefaultPermissions => vec![],
133 MountOption::Dev => vec![MountOption::NoDev],
134 MountOption::NoDev => vec![MountOption::Dev],
135 MountOption::Suid => vec![MountOption::NoSuid],
136 MountOption::NoSuid => vec![MountOption::Suid],
137 MountOption::RO => vec![MountOption::RW],
138 MountOption::RW => vec![MountOption::RO],
139 MountOption::Exec => vec![MountOption::NoExec],
140 MountOption::NoExec => vec![MountOption::Exec],
141 MountOption::Atime => vec![MountOption::NoAtime],
142 MountOption::NoAtime => vec![MountOption::Atime],
143 MountOption::Sync => vec![MountOption::Async],
144 MountOption::Async => vec![MountOption::Sync],
145 }
146}
147
148#[allow(dead_code)]
150pub(crate) fn option_to_string(option: &MountOption) -> String {
151 match option {
152 MountOption::FSName(name) => format!("fsname={name}"),
153 MountOption::Subtype(subtype) => format!("subtype={subtype}"),
154 MountOption::CUSTOM(value) => value.to_string(),
155 MountOption::AutoUnmount => "auto_unmount".to_string(),
156 MountOption::DefaultPermissions => "default_permissions".to_string(),
157 MountOption::Dev => "dev".to_string(),
158 MountOption::NoDev => "nodev".to_string(),
159 MountOption::Suid => "suid".to_string(),
160 MountOption::NoSuid => "nosuid".to_string(),
161 MountOption::RO => "ro".to_string(),
162 MountOption::RW => "rw".to_string(),
163 MountOption::Exec => "exec".to_string(),
164 MountOption::NoExec => "noexec".to_string(),
165 MountOption::Atime => "atime".to_string(),
166 MountOption::NoAtime => "noatime".to_string(),
167 MountOption::DirSync => "dirsync".to_string(),
168 MountOption::Sync => "sync".to_string(),
169 MountOption::Async => "async".to_string(),
170 }
171}
172
173#[allow(dead_code)]
174pub(crate) fn option_group(option: &MountOption) -> MountOptionGroup {
175 match option {
176 MountOption::FSName(_) => MountOptionGroup::Fusermount,
177 MountOption::Subtype(_) => MountOptionGroup::Fusermount,
178 MountOption::CUSTOM(_) => MountOptionGroup::KernelOption,
179 MountOption::AutoUnmount => MountOptionGroup::Fusermount,
180 MountOption::Dev => MountOptionGroup::KernelFlag,
181 MountOption::NoDev => MountOptionGroup::KernelFlag,
182 MountOption::Suid => MountOptionGroup::KernelFlag,
183 MountOption::NoSuid => MountOptionGroup::KernelFlag,
184 MountOption::RO => MountOptionGroup::KernelFlag,
185 MountOption::RW => MountOptionGroup::KernelFlag,
186 MountOption::Exec => MountOptionGroup::KernelFlag,
187 MountOption::NoExec => MountOptionGroup::KernelFlag,
188 MountOption::Atime => MountOptionGroup::KernelFlag,
189 MountOption::NoAtime => MountOptionGroup::KernelFlag,
190 MountOption::DirSync => MountOptionGroup::KernelFlag,
191 MountOption::Sync => MountOptionGroup::KernelFlag,
192 MountOption::Async => MountOptionGroup::KernelFlag,
193 MountOption::DefaultPermissions => MountOptionGroup::KernelOption,
194 }
195}
196
197#[cfg(target_os = "linux")]
198#[allow(dead_code)]
199pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MsFlags> {
200 match option {
201 MountOption::Dev => Ok(nix::mount::MsFlags::empty()), MountOption::NoDev => Ok(nix::mount::MsFlags::MS_NODEV),
203 MountOption::Suid => Ok(nix::mount::MsFlags::empty()),
204 MountOption::NoSuid => Ok(nix::mount::MsFlags::MS_NOSUID),
205 MountOption::RW => Ok(nix::mount::MsFlags::empty()),
206 MountOption::RO => Ok(nix::mount::MsFlags::MS_RDONLY),
207 MountOption::Exec => Ok(nix::mount::MsFlags::empty()),
208 MountOption::NoExec => Ok(nix::mount::MsFlags::MS_NOEXEC),
209 MountOption::Atime => Ok(nix::mount::MsFlags::empty()),
210 MountOption::NoAtime => Ok(nix::mount::MsFlags::MS_NOATIME),
211 MountOption::Async => Ok(nix::mount::MsFlags::empty()),
212 MountOption::Sync => Ok(nix::mount::MsFlags::MS_SYNCHRONOUS),
213 MountOption::DirSync => Ok(nix::mount::MsFlags::MS_DIRSYNC),
214 option => Err(io::Error::new(
215 io::ErrorKind::InvalidInput,
216 format!("Invalid mount option for flag conversion: {option:?}"),
217 )),
218 }
219}
220
221#[cfg(target_os = "macos")]
222#[allow(dead_code)]
223pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MntFlags> {
224 match option {
225 MountOption::Dev => Ok(nix::mount::MntFlags::empty()), MountOption::NoDev => Ok(nix::mount::MntFlags::MNT_NODEV),
227 MountOption::Suid => Ok(nix::mount::MntFlags::empty()),
228 MountOption::NoSuid => Ok(nix::mount::MntFlags::MNT_NOSUID),
229 MountOption::RW => Ok(nix::mount::MntFlags::empty()),
230 MountOption::RO => Ok(nix::mount::MntFlags::MNT_RDONLY),
231 MountOption::Exec => Ok(nix::mount::MntFlags::empty()),
232 MountOption::NoExec => Ok(nix::mount::MntFlags::MNT_NOEXEC),
233 MountOption::Atime => Ok(nix::mount::MntFlags::empty()),
234 MountOption::NoAtime => Ok(nix::mount::MntFlags::MNT_NOATIME),
235 MountOption::Async => Ok(nix::mount::MntFlags::empty()),
236 MountOption::Sync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
237 option => Err(io::Error::new(
238 io::ErrorKind::InvalidInput,
239 format!("Invalid mount option for flag conversion: {option:?}"),
240 )),
241 }
242}
243
244#[cfg_attr(
245 any(
246 target_os = "freebsd",
247 target_os = "dragonfly",
248 target_os = "openbsd",
249 target_os = "netbsd"
250 ),
251 allow(dead_code)
252)]
253#[cfg(any(
254 target_os = "freebsd",
255 target_os = "dragonfly",
256 target_os = "openbsd",
257 target_os = "netbsd"
258))]
259pub(crate) fn option_to_flag(option: &MountOption) -> io::Result<nix::mount::MntFlags> {
260 match option {
261 MountOption::Dev => Ok(nix::mount::MntFlags::empty()),
262 #[cfg(target_os = "freebsd")]
263 MountOption::NoDev => Err(io::Error::new(
264 io::ErrorKind::Unsupported,
265 "NoDev option is not supported on FreeBSD",
266 )),
267 #[cfg(not(target_os = "freebsd"))]
268 MountOption::NoDev => Ok(nix::mount::MntFlags::MNT_NODEV),
269 MountOption::Suid => Ok(nix::mount::MntFlags::empty()),
270 MountOption::NoSuid => Ok(nix::mount::MntFlags::MNT_NOSUID),
271 MountOption::RW => Ok(nix::mount::MntFlags::empty()),
272 MountOption::RO => Ok(nix::mount::MntFlags::MNT_RDONLY),
273 MountOption::Exec => Ok(nix::mount::MntFlags::empty()),
274 MountOption::NoExec => Ok(nix::mount::MntFlags::MNT_NOEXEC),
275 MountOption::Atime => Ok(nix::mount::MntFlags::empty()),
276 MountOption::NoAtime => Ok(nix::mount::MntFlags::MNT_NOATIME),
277 MountOption::Async => Ok(nix::mount::MntFlags::MNT_ASYNC),
278 MountOption::Sync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
279 MountOption::DirSync => Ok(nix::mount::MntFlags::MNT_SYNCHRONOUS),
280 option => Err(io::Error::new(
281 io::ErrorKind::InvalidInput,
282 format!("Invalid mount option for flag conversion: {option:?}"),
283 )),
284 }
285}
286
287#[cfg(test)]
288mod test {
289 use crate::mnt::mount_options::*;
290
291 #[test]
292 fn option_checking() {
293 assert!(
294 check_option_conflicts(&Config {
295 mount_options: vec![MountOption::Suid, MountOption::NoSuid],
296 ..Config::default()
297 })
298 .is_err()
299 );
300 assert!(
301 check_option_conflicts(&Config {
302 mount_options: vec![MountOption::Suid, MountOption::NoExec],
303 ..Config::default()
304 })
305 .is_ok()
306 );
307 }
308 #[test]
309 fn option_round_trip() {
310 use crate::mnt::mount_options::MountOption::*;
311 for x in &[
312 FSName("Blah".to_owned()),
313 Subtype("Bloo".to_owned()),
314 CUSTOM("bongos".to_owned()),
315 AutoUnmount,
316 DefaultPermissions,
317 Dev,
318 NoDev,
319 Suid,
320 NoSuid,
321 RO,
322 RW,
323 Exec,
324 NoExec,
325 Atime,
326 NoAtime,
327 DirSync,
328 Sync,
329 Async,
330 ] {
331 assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref()));
332 }
333 }
334}