1use crate::vfs::{FsStr, FsString};
6use flyweights::FlyByteStr;
7use starnix_uapi::errno;
8use starnix_uapi::errors::Errno;
9use starnix_uapi::mount_flags::MountFlags;
10use std::collections::HashMap;
11use std::fmt::Display;
12
13#[derive(Debug, Default, Clone)]
28pub struct MountParams {
29 options: HashMap<FlyByteStr, FlyByteStr>,
30}
31
32impl MountParams {
33 pub fn parse(data: &FsStr) -> Result<Self, Errno> {
34 let options = parse_mount_options::parse_mount_options(data).map_err(|_| errno!(EINVAL))?;
35 Ok(MountParams { options })
36 }
37
38 pub fn keys(&self) -> impl Iterator<Item = &FlyByteStr> {
39 self.options.keys()
40 }
41
42 pub fn get(&self, key: &[u8]) -> Option<&FlyByteStr> {
43 self.options.get(&key.into())
44 }
45
46 pub fn get_as<T: std::str::FromStr>(&self, key: &[u8]) -> Result<Option<T>, Errno>
47 where
48 <T as std::str::FromStr>::Err: std::fmt::Debug,
49 {
50 self.get(key).map(|v| parse::<T>(v.as_ref())).transpose()
51 }
52
53 pub fn get_with<T, E: std::fmt::Debug>(
54 &self,
55 key: &[u8],
56 parser: impl FnOnce(&str) -> Result<T, E>,
57 ) -> Result<Option<T>, Errno> {
58 self.get(key).map(|v| parse_with(v.as_ref(), parser)).transpose()
59 }
60
61 pub fn remove(&mut self, key: &[u8]) -> Option<FlyByteStr> {
62 self.options.remove(&key.into())
63 }
64
65 pub fn is_empty(&self) -> bool {
66 self.options.is_empty()
67 }
68
69 pub fn remove_mount_flags(&mut self) -> MountFlags {
70 let mut flags = MountFlags::empty();
71 if self.remove(b"ro").is_some() {
72 flags |= MountFlags::RDONLY;
73 }
74 if self.remove(b"nosuid").is_some() {
75 flags |= MountFlags::NOSUID;
76 }
77 if self.remove(b"nodev").is_some() {
78 flags |= MountFlags::NODEV;
79 }
80 if self.remove(b"noexec").is_some() {
81 flags |= MountFlags::NOEXEC;
82 }
83 if self.remove(b"noatime").is_some() {
84 flags |= MountFlags::NOATIME;
85 }
86 if self.remove(b"nodiratime").is_some() {
87 flags |= MountFlags::NODIRATIME;
88 }
89 if self.remove(b"relatime").is_some() {
90 flags |= MountFlags::RELATIME;
91 }
92 if self.remove(b"strictatime").is_some() {
93 flags |= MountFlags::STRICTATIME;
94 }
95 flags
96 }
97
98 pub fn format_options(&self) -> FsString {
101 let mut result = FsString::default();
102 for (key, value) in &self.options {
103 result.push(b',');
104 result.extend_from_slice(key.as_ref());
105 if !value.is_empty() {
106 result.push(b'=');
107 result.extend_from_slice(value.as_ref());
108 }
109 }
110 result
111 }
112}
113
114impl Display for MountParams {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 write!(f, "{}", itertools::join(self.options.iter().map(|(k, v)| format!("{k}={v}")), ","))
117 }
118}
119
120pub fn parse<F: std::str::FromStr>(data: &FsStr) -> Result<F, Errno>
124where
125 <F as std::str::FromStr>::Err: std::fmt::Debug,
126{
127 parse_with(data, F::from_str)
128}
129
130pub fn parse_with<F, E: std::fmt::Debug>(
134 data: &FsStr,
135 parser: impl FnOnce(&str) -> Result<F, E>,
136) -> Result<F, Errno> {
137 parser(std::str::from_utf8(data.as_ref()).map_err(|e| errno!(EINVAL, e))?.trim())
138 .map_err(|e| errno!(EINVAL, format!("{:?}: {:?}", data, e)))
139}
140
141mod parse_mount_options {
142 use crate::vfs::FsStr;
143 use flyweights::FlyByteStr;
144 use nom::branch::alt;
145 use nom::bytes::complete::{is_not, tag};
146 use nom::combinator::opt;
147 use nom::multi::separated_list0;
148 use nom::sequence::{delimited, separated_pair, terminated};
149 use nom::{IResult, Parser};
150 use starnix_uapi::errors::{Errno, errno, error};
151 use std::collections::HashMap;
152
153 fn unquoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
154 is_not(",=").parse(input)
155 }
156
157 fn quoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
158 delimited(tag("\""), is_not("\""), tag("\"")).parse(input)
159 }
160
161 fn value(input: &[u8]) -> IResult<&[u8], &[u8]> {
162 alt((quoted, unquoted)).parse(input)
163 }
164
165 fn key_value(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
166 separated_pair(unquoted, tag("="), value).parse(input)
167 }
168
169 fn key_only(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
170 let (input, key) = unquoted(input)?;
171 Ok((input, (key, b"")))
172 }
173
174 fn option(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
175 alt((key_value, key_only)).parse(input)
176 }
177
178 pub(super) fn parse_mount_options(
179 input: &FsStr,
180 ) -> Result<HashMap<FlyByteStr, FlyByteStr>, Errno> {
181 let (input, options) = terminated(separated_list0(tag(","), option), opt(tag(",")))
182 .parse(input.into())
183 .map_err(|_| errno!(EINVAL))?;
184
185 if input.len() > 0 {
187 return error!(EINVAL);
188 }
189
190 let mut options_map: HashMap<FlyByteStr, FlyByteStr> =
192 HashMap::with_capacity(options.len());
193 for (key, value) in options.into_iter() {
194 options_map.insert(key.into(), value.into());
195 }
196
197 Ok(options_map)
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::{MountParams, parse};
204 use crate::vfs::FsStr;
205 use flyweights::FlyByteStr;
206 use maplit::hashmap;
207 use starnix_uapi::mount_flags::MountFlags;
208 use std::collections::HashSet;
209
210 #[::fuchsia::test]
211 fn empty_data() {
212 assert!(MountParams::parse(Default::default()).unwrap().is_empty());
213 }
214
215 #[::fuchsia::test]
216 fn parse_options_with_trailing_comma() {
217 let data = b"key0=value0,";
218 let parsed_data =
219 MountParams::parse(data.into()).expect("mount options parse: key0=value0,");
220 assert_eq!(
221 parsed_data.options,
222 hashmap! {
223 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value0"),
224 }
225 );
226 }
227
228 #[::fuchsia::test]
229 fn parse_options_last_value_wins() {
230 let data = b"key0=value0,key1,key2=value2,key0=value3";
232 let parsed_data = MountParams::parse(data.into())
233 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
234 assert_eq!(
235 parsed_data.options,
236 hashmap! {
237 FlyByteStr::new(b"key1") => FlyByteStr::new(b""),
238 FlyByteStr::new(b"key2") => FlyByteStr::new(b"value2"),
239 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value3"),
241 }
242 );
243 }
244
245 #[::fuchsia::test]
246 fn parse_options_quoted() {
247 let data = b"key0=unqouted,key1=\"quoted,with=punc:tua-tion.\"";
248 let parsed_data = MountParams::parse(data.into())
249 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
250 assert_eq!(
251 parsed_data.options,
252 hashmap! {
253 FlyByteStr::new(b"key0") => FlyByteStr::new(b"unqouted"),
254 FlyByteStr::new(b"key1") => FlyByteStr::new(b"quoted,with=punc:tua-tion."),
255 }
256 );
257 }
258
259 #[::fuchsia::test]
260 fn parse_options_misquoted() {
261 let data = b"key0=\"mis\"quoted,key1=\"quoted\"";
262 let parse_result = MountParams::parse(data.into());
263 assert!(
264 parse_result.is_err(),
265 "expected parse failure: key0=\"mis\"quoted,key1=\"quoted\""
266 );
267 }
268
269 #[::fuchsia::test]
270 fn parse_options_misquoted_tail() {
271 let data = b"key0=\"quoted\",key1=\"mis\"quoted";
272 let parse_result = MountParams::parse(data.into());
273 assert!(
274 parse_result.is_err(),
275 "expected parse failure: key0=\"quoted\",key1=\"mis\"quoted"
276 );
277 }
278
279 #[::fuchsia::test]
280 fn parse_normal_mount_flags() {
281 let data = b"nosuid,nodev,noexec,relatime";
282 let parsed_data = MountParams::parse(data.into())
283 .expect("mount options parse: nosuid,nodev,noexec,relatime");
284 assert_eq!(
285 parsed_data.options,
286 hashmap! {
287 FlyByteStr::new(b"nosuid") => FlyByteStr::default(),
288 FlyByteStr::new(b"nodev") => FlyByteStr::default(),
289 FlyByteStr::new(b"noexec") => FlyByteStr::default(),
290 FlyByteStr::new(b"relatime") => FlyByteStr::default(),
291 }
292 );
293 }
294
295 #[::fuchsia::test]
296 fn parse_and_remove_normal_mount_flags() {
297 let data = b"nosuid,nodev,noexec,relatime";
298 let mut parsed_data = MountParams::parse(data.into())
299 .expect("mount options parse: nosuid,nodev,noexec,relatime");
300 let flags = parsed_data.remove_mount_flags();
301 assert_eq!(
302 flags,
303 MountFlags::NOSUID | MountFlags::NODEV | MountFlags::NOEXEC | MountFlags::RELATIME
304 );
305 }
306
307 #[::fuchsia::test]
308 fn parse_data() {
309 assert_eq!(parse::<usize>("42".into()), Ok(42));
310 }
311
312 #[::fuchsia::test]
313 fn test_format_options() {
314 fn split(options: &FsStr) -> HashSet<&[u8]> {
316 let bytes: &[u8] = options;
317 let bytes = bytes.strip_prefix(b",").expect("leading comma");
318 bytes.split(|&b| b == b',').collect()
319 }
320
321 let empty = MountParams::default();
322 assert_eq!(empty.format_options(), "");
323
324 let data = b"hidepid=2,gid=3009";
325 let parsed = MountParams::parse(data.into()).expect("parse options");
326 assert_eq!(
327 split(parsed.format_options().as_ref()),
328 HashSet::from([&b"gid=3009"[..], &b"hidepid=2"[..]])
329 );
330
331 let flags_data = b"nodev,nosuid,custom=opt";
332 let mut parsed = MountParams::parse(flags_data.into()).expect("parse options");
333 let _ = parsed.remove_mount_flags();
334 assert_eq!(parsed.format_options(), ",custom=opt");
335 }
336}