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 get(&self, key: &[u8]) -> Option<&FlyByteStr> {
39 self.options.get(&key.into())
40 }
41
42 pub fn get_as<T: std::str::FromStr>(&self, key: &[u8]) -> Result<Option<T>, Errno>
43 where
44 <T as std::str::FromStr>::Err: std::fmt::Debug,
45 {
46 self.get(key).map(|v| parse::<T>(v.as_ref())).transpose()
47 }
48
49 pub fn get_with<T, E: std::fmt::Debug>(
50 &self,
51 key: &[u8],
52 parser: impl FnOnce(&str) -> Result<T, E>,
53 ) -> Result<Option<T>, Errno> {
54 self.get(key).map(|v| parse_with(v.as_ref(), parser)).transpose()
55 }
56
57 pub fn remove(&mut self, key: &[u8]) -> Option<FlyByteStr> {
58 self.options.remove(&key.into())
59 }
60
61 pub fn is_empty(&self) -> bool {
62 self.options.is_empty()
63 }
64
65 pub fn remove_mount_flags(&mut self) -> MountFlags {
66 let mut flags = MountFlags::empty();
67 if self.remove(b"ro").is_some() {
68 flags |= MountFlags::RDONLY;
69 }
70 if self.remove(b"nosuid").is_some() {
71 flags |= MountFlags::NOSUID;
72 }
73 if self.remove(b"nodev").is_some() {
74 flags |= MountFlags::NODEV;
75 }
76 if self.remove(b"noexec").is_some() {
77 flags |= MountFlags::NOEXEC;
78 }
79 if self.remove(b"noatime").is_some() {
80 flags |= MountFlags::NOATIME;
81 }
82 if self.remove(b"nodiratime").is_some() {
83 flags |= MountFlags::NODIRATIME;
84 }
85 if self.remove(b"relatime").is_some() {
86 flags |= MountFlags::RELATIME;
87 }
88 if self.remove(b"strictatime").is_some() {
89 flags |= MountFlags::STRICTATIME;
90 }
91 flags
92 }
93}
94
95impl Display for MountParams {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 write!(f, "{}", itertools::join(self.options.iter().map(|(k, v)| format!("{k}={v}")), ","))
98 }
99}
100
101pub fn parse<F: std::str::FromStr>(data: &FsStr) -> Result<F, Errno>
105where
106 <F as std::str::FromStr>::Err: std::fmt::Debug,
107{
108 parse_with(data, F::from_str)
109}
110
111pub fn parse_with<F, E: std::fmt::Debug>(
115 data: &FsStr,
116 parser: impl FnOnce(&str) -> Result<F, E>,
117) -> Result<F, Errno> {
118 parser(std::str::from_utf8(data.as_ref()).map_err(|e| errno!(EINVAL, e))?.trim())
119 .map_err(|e| errno!(EINVAL, format!("{:?}: {:?}", data, e)))
120}
121
122mod parse_mount_options {
123 use crate::vfs::FsStr;
124 use flyweights::FlyByteStr;
125 use nom::branch::alt;
126 use nom::bytes::complete::{is_not, tag};
127 use nom::combinator::opt;
128 use nom::multi::separated_list0;
129 use nom::sequence::{delimited, separated_pair, terminated};
130 use nom::{IResult, Parser};
131 use starnix_uapi::errors::{Errno, errno, error};
132 use std::collections::HashMap;
133
134 fn unquoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
135 is_not(",=").parse(input)
136 }
137
138 fn quoted(input: &[u8]) -> IResult<&[u8], &[u8]> {
139 delimited(tag("\""), is_not("\""), tag("\"")).parse(input)
140 }
141
142 fn value(input: &[u8]) -> IResult<&[u8], &[u8]> {
143 alt((quoted, unquoted)).parse(input)
144 }
145
146 fn key_value(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
147 separated_pair(unquoted, tag("="), value).parse(input)
148 }
149
150 fn key_only(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
151 let (input, key) = unquoted(input)?;
152 Ok((input, (key, b"")))
153 }
154
155 fn option(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
156 alt((key_value, key_only)).parse(input)
157 }
158
159 pub(super) fn parse_mount_options(
160 input: &FsStr,
161 ) -> Result<HashMap<FlyByteStr, FlyByteStr>, Errno> {
162 let (input, options) = terminated(separated_list0(tag(","), option), opt(tag(",")))
163 .parse(input.into())
164 .map_err(|_| errno!(EINVAL))?;
165
166 if input.len() > 0 {
168 return error!(EINVAL);
169 }
170
171 let mut options_map: HashMap<FlyByteStr, FlyByteStr> =
173 HashMap::with_capacity(options.len());
174 for (key, value) in options.into_iter() {
175 options_map.insert(key.into(), value.into());
176 }
177
178 Ok(options_map)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::{MountParams, parse};
185 use flyweights::FlyByteStr;
186 use maplit::hashmap;
187 use starnix_uapi::mount_flags::MountFlags;
188
189 #[::fuchsia::test]
190 fn empty_data() {
191 assert!(MountParams::parse(Default::default()).unwrap().is_empty());
192 }
193
194 #[::fuchsia::test]
195 fn parse_options_with_trailing_comma() {
196 let data = b"key0=value0,";
197 let parsed_data =
198 MountParams::parse(data.into()).expect("mount options parse: key0=value0,");
199 assert_eq!(
200 parsed_data.options,
201 hashmap! {
202 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value0"),
203 }
204 );
205 }
206
207 #[::fuchsia::test]
208 fn parse_options_last_value_wins() {
209 let data = b"key0=value0,key1,key2=value2,key0=value3";
211 let parsed_data = MountParams::parse(data.into())
212 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
213 assert_eq!(
214 parsed_data.options,
215 hashmap! {
216 FlyByteStr::new(b"key1") => FlyByteStr::new(b""),
217 FlyByteStr::new(b"key2") => FlyByteStr::new(b"value2"),
218 FlyByteStr::new(b"key0") => FlyByteStr::new(b"value3"),
220 }
221 );
222 }
223
224 #[::fuchsia::test]
225 fn parse_options_quoted() {
226 let data = b"key0=unqouted,key1=\"quoted,with=punc:tua-tion.\"";
227 let parsed_data = MountParams::parse(data.into())
228 .expect("mount options parse: key0=value0,key1,key2=value2,key0=value3");
229 assert_eq!(
230 parsed_data.options,
231 hashmap! {
232 FlyByteStr::new(b"key0") => FlyByteStr::new(b"unqouted"),
233 FlyByteStr::new(b"key1") => FlyByteStr::new(b"quoted,with=punc:tua-tion."),
234 }
235 );
236 }
237
238 #[::fuchsia::test]
239 fn parse_options_misquoted() {
240 let data = b"key0=\"mis\"quoted,key1=\"quoted\"";
241 let parse_result = MountParams::parse(data.into());
242 assert!(
243 parse_result.is_err(),
244 "expected parse failure: key0=\"mis\"quoted,key1=\"quoted\""
245 );
246 }
247
248 #[::fuchsia::test]
249 fn parse_options_misquoted_tail() {
250 let data = b"key0=\"quoted\",key1=\"mis\"quoted";
251 let parse_result = MountParams::parse(data.into());
252 assert!(
253 parse_result.is_err(),
254 "expected parse failure: key0=\"quoted\",key1=\"mis\"quoted"
255 );
256 }
257
258 #[::fuchsia::test]
259 fn parse_normal_mount_flags() {
260 let data = b"nosuid,nodev,noexec,relatime";
261 let parsed_data = MountParams::parse(data.into())
262 .expect("mount options parse: nosuid,nodev,noexec,relatime");
263 assert_eq!(
264 parsed_data.options,
265 hashmap! {
266 FlyByteStr::new(b"nosuid") => FlyByteStr::default(),
267 FlyByteStr::new(b"nodev") => FlyByteStr::default(),
268 FlyByteStr::new(b"noexec") => FlyByteStr::default(),
269 FlyByteStr::new(b"relatime") => FlyByteStr::default(),
270 }
271 );
272 }
273
274 #[::fuchsia::test]
275 fn parse_and_remove_normal_mount_flags() {
276 let data = b"nosuid,nodev,noexec,relatime";
277 let mut parsed_data = MountParams::parse(data.into())
278 .expect("mount options parse: nosuid,nodev,noexec,relatime");
279 let flags = parsed_data.remove_mount_flags();
280 assert_eq!(
281 flags,
282 MountFlags::NOSUID | MountFlags::NODEV | MountFlags::NOEXEC | MountFlags::RELATIME
283 );
284 }
285
286 #[::fuchsia::test]
287 fn parse_data() {
288 assert_eq!(parse::<usize>("42".into()), Ok(42));
289 }
290}