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
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#[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}