1use crate::vfs::FsStr;
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
99impl Display for MountParams {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{}", itertools::join(self.options.iter().map(|(k, v)| format!("{k}={v}")), ","))
102 }
103}
104
105pub fn parse<F: std::str::FromStr>(data: &FsStr) -> Result<F, Errno>
109where
110 <F as std::str::FromStr>::Err: std::fmt::Debug,
111{
112 parse_with(data, F::from_str)
113}
114
115pub fn parse_with<F, E: std::fmt::Debug>(
119 data: &FsStr,
120 parser: impl FnOnce(&str) -> Result<F, E>,
121) -> Result<F, Errno> {
122 parser(std::str::from_utf8(data.as_ref()).map_err(|e| errno!(EINVAL, e))?.trim())
123 .map_err(|e| errno!(EINVAL, format!("{:?}: {:?}", data, e)))
124}
125
126mod parse_mount_options {
127 use crate::vfs::FsStr;
128 use flyweights::FlyByteStr;
129 use nom::branch::alt;
130 use nom::bytes::complete::{is_not, tag};
131 use nom::combinator::opt;
132 use nom::multi::separated_list0;
133 use nom::sequence::{delimited, separated_pair, terminated};
134 use nom::{IResult, Parser};
135 use starnix_uapi::errors::{Errno, errno, error};
136 use std::collections::HashMap;
137
138 fn unquoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
139 is_not(",=").parse(input)
140 }
141
142 fn quoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
143 delimited(tag("\""), is_not("\""), tag("\"")).parse(input)
144 }
145
146 fn value(input: &[u8]) -> IResult<&[u8], &[u8]> {
147 alt((quoted, unquoted)).parse(input)
148 }
149
150 fn key_value(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
151 separated_pair(unquoted, tag("="), value).parse(input)
152 }
153
154 fn key_only(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
155 let (input, key) = unquoted(input)?;
156 Ok((input, (key, b"")))
157 }
158
159 fn option(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
160 alt((key_value, key_only)).parse(input)
161 }
162
163 pub(super) fn parse_mount_options(
164 input: &FsStr,
165 ) -> Result<HashMap<FlyByteStr, FlyByteStr>, Errno> {
166 let (input, options) = terminated(separated_list0(tag(","), option), opt(tag(",")))
167 .parse(input.into())
168 .map_err(|_| errno!(EINVAL))?;
169
170 if input.len() > 0 {
172 return error!(EINVAL);
173 }
174
175 let mut options_map: HashMap<FlyByteStr, FlyByteStr> =
177 HashMap::with_capacity(options.len());
178 for (key, value) in options.into_iter() {
179 options_map.insert(key.into(), value.into());
180 }
181
182 Ok(options_map)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::{MountParams, parse};
189 use flyweights::FlyByteStr;
190 use maplit::hashmap;
191 use starnix_uapi::mount_flags::MountFlags;
192
193 #[::fuchsia::test]
194 fn empty_data() {
195 assert!(MountParams::parse(Default::default()).unwrap().is_empty());
196 }
197
198 #[::fuchsia::test]
199 fn parse_options_with_trailing_comma() {
200 let data = b"key0=value0,";
201 let parsed_data =
202 MountParams::parse(data.into()).expect("mount options parse: key0=value0,");
203 assert_eq!(
204 parsed_data.options,
205 hashmap! {
206 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value0"),
207 }
208 );
209 }
210
211 #[::fuchsia::test]
212 fn parse_options_last_value_wins() {
213 let data = b"key0=value0,key1,key2=value2,key0=value3";
215 let parsed_data = MountParams::parse(data.into())
216 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
217 assert_eq!(
218 parsed_data.options,
219 hashmap! {
220 FlyByteStr::new(b"key1") => FlyByteStr::new(b""),
221 FlyByteStr::new(b"key2") => FlyByteStr::new(b"value2"),
222 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value3"),
224 }
225 );
226 }
227
228 #[::fuchsia::test]
229 fn parse_options_quoted() {
230 let data = b"key0=unqouted,key1=\"quoted,with=punc:tua-tion.\"";
231 let parsed_data = MountParams::parse(data.into())
232 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
233 assert_eq!(
234 parsed_data.options,
235 hashmap! {
236 FlyByteStr::new(b"key0") => FlyByteStr::new(b"unqouted"),
237 FlyByteStr::new(b"key1") => FlyByteStr::new(b"quoted,with=punc:tua-tion."),
238 }
239 );
240 }
241
242 #[::fuchsia::test]
243 fn parse_options_misquoted() {
244 let data = b"key0=\"mis\"quoted,key1=\"quoted\"";
245 let parse_result = MountParams::parse(data.into());
246 assert!(
247 parse_result.is_err(),
248 "expected parse failure: key0=\"mis\"quoted,key1=\"quoted\""
249 );
250 }
251
252 #[::fuchsia::test]
253 fn parse_options_misquoted_tail() {
254 let data = b"key0=\"quoted\",key1=\"mis\"quoted";
255 let parse_result = MountParams::parse(data.into());
256 assert!(
257 parse_result.is_err(),
258 "expected parse failure: key0=\"quoted\",key1=\"mis\"quoted"
259 );
260 }
261
262 #[::fuchsia::test]
263 fn parse_normal_mount_flags() {
264 let data = b"nosuid,nodev,noexec,relatime";
265 let parsed_data = MountParams::parse(data.into())
266 .expect("mount options parse: nosuid,nodev,noexec,relatime");
267 assert_eq!(
268 parsed_data.options,
269 hashmap! {
270 FlyByteStr::new(b"nosuid") => FlyByteStr::default(),
271 FlyByteStr::new(b"nodev") => FlyByteStr::default(),
272 FlyByteStr::new(b"noexec") => FlyByteStr::default(),
273 FlyByteStr::new(b"relatime") => FlyByteStr::default(),
274 }
275 );
276 }
277
278 #[::fuchsia::test]
279 fn parse_and_remove_normal_mount_flags() {
280 let data = b"nosuid,nodev,noexec,relatime";
281 let mut parsed_data = MountParams::parse(data.into())
282 .expect("mount options parse: nosuid,nodev,noexec,relatime");
283 let flags = parsed_data.remove_mount_flags();
284 assert_eq!(
285 flags,
286 MountFlags::NOSUID | MountFlags::NODEV | MountFlags::NOEXEC | MountFlags::RELATIME
287 );
288 }
289
290 #[::fuchsia::test]
291 fn parse_data() {
292 assert_eq!(parse::<usize>("42".into()), Ok(42));
293 }
294}