• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Entities to parse and iterate over both kernel command line and bootconfig.
16 
17 use core::fmt::{Display, Formatter};
18 use liberror::{Error, Result};
19 
20 /// A struct representing a key-value entry inside kernel command line or bootconfig.
21 #[derive(Debug, PartialEq, Eq)]
22 pub struct Entry<'a> {
23     /// Boot parameters entry key.
24     pub key: &'a str,
25     /// Boot parameters entry value (may be not presented).
26     pub value: Option<&'a str>,
27 }
28 
29 /// Convert Entry into kernel command line / bootconfig compatible string.
30 impl<'a> Display for Entry<'a> {
fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result31     fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
32         match self.value {
33             Some(value) => write!(f, "{}={}", self.key, value),
34             None => write!(f, "{}", self.key),
35         }
36     }
37 }
38 
39 /// To iterate over kernel command line entries.
40 pub struct CommandlineParser<'a> {
41     data: &'a str,
42     pos: usize,
43 }
44 
45 impl<'a> CommandlineParser<'a> {
46     /// Creates a new iterator from raw command line.
new(data: &'a str) -> Self47     pub fn new(data: &'a str) -> Self {
48         CommandlineParser { data, pos: 0 }
49     }
50 
remains(&self) -> &'a str51     fn remains(&self) -> &'a str {
52         &self.data.get(self.pos..).unwrap_or("")
53     }
54 
peek(&self) -> Option<char>55     fn peek(&self) -> Option<char> {
56         self.remains().chars().next()
57     }
58 
skip(&mut self, n: usize)59     fn skip(&mut self, n: usize) {
60         self.pos += n;
61     }
62 
take_while<F>(&mut self, predicate: F) -> &'a str where F: Fn(char) -> bool,63     fn take_while<F>(&mut self, predicate: F) -> &'a str
64     where
65         F: Fn(char) -> bool,
66     {
67         let remains = self.remains();
68         let n = match remains.find(|c: char| !predicate(c)) {
69             Some(end) => end,
70             // Take everything if we cannot find.
71             None => remains.len(),
72         };
73 
74         self.pos += n;
75         &remains[..n]
76     }
77 
skip_whitespaces(&mut self)78     fn skip_whitespaces(&mut self) {
79         self.pos += self.remains().len() - self.remains().trim_start().len();
80     }
81 
parse_key(&mut self) -> Option<&'a str>82     fn parse_key(&mut self) -> Option<&'a str> {
83         self.skip_whitespaces();
84 
85         let key = self.take_while(|c| !c.is_whitespace() && c != '=');
86 
87         match key.is_empty() {
88             true => None,
89             false => Some(key),
90         }
91     }
92 
parse_value(&mut self) -> Result<Option<&'a str>>93     fn parse_value(&mut self) -> Result<Option<&'a str>> {
94         match self.peek() {
95             // Skip the '=' character.
96             Some('=') => self.skip(1),
97             // No value.
98             Some(c) if c.is_whitespace() => return Ok(None),
99             // End of input.
100             None => return Ok(None),
101             // Invalid input
102             _ => {
103                 self.skip(self.remains().len());
104                 return Err(Error::InvalidInput);
105             }
106         }
107 
108         let value = match self.peek() {
109             // Check for the open quote.
110             Some('"') => {
111                 // Skip it.
112                 self.skip(1);
113                 let value = self.take_while(|c| c != '"');
114 
115                 // Check for the close quote.
116                 match self.peek() {
117                     Some('"') => {
118                         // Skip it.
119                         self.skip(1);
120                         value
121                     }
122                     _ => {
123                         self.skip(self.remains().len());
124                         return Err(Error::InvalidInput);
125                     }
126                 }
127             }
128             _ => self.take_while(|c| !c.is_whitespace()),
129         };
130 
131         Ok(Some(value))
132     }
133 }
134 
135 /// Parse kernel command line format, so we can iterate over key-value entries.
136 /// https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html
137 impl<'a> Iterator for CommandlineParser<'a> {
138     type Item = Result<Entry<'a>>;
139 
next(&mut self) -> Option<Self::Item>140     fn next(&mut self) -> Option<Self::Item> {
141         match self.parse_key() {
142             Some(key) => match self.parse_value() {
143                 Ok(value) => Some(Ok(Entry { key, value })),
144                 Err(e) => Some(Err(e)),
145             },
146             None => None,
147         }
148     }
149 }
150 
151 #[cfg(test)]
152 mod tests {
153     use super::*;
154 
155     #[test]
test_kernel_command_line_valid_key_value()156     fn test_kernel_command_line_valid_key_value() {
157         let mut iterator = CommandlineParser::new(
158             "video=vfb:640x400,bpp=32,memsize=3072000 console=ttyMSM0,115200n8 earlycon bootconfig",
159         );
160 
161         assert_eq!(
162             iterator.next(),
163             Some(Ok(Entry { key: "video", value: Some("vfb:640x400,bpp=32,memsize=3072000") }))
164         );
165         assert_eq!(
166             iterator.next(),
167             Some(Ok(Entry { key: "console", value: Some("ttyMSM0,115200n8") }))
168         );
169         assert_eq!(iterator.next(), Some(Ok(Entry { key: "earlycon", value: None })));
170         assert_eq!(iterator.next(), Some(Ok(Entry { key: "bootconfig", value: None })));
171         assert_eq!(iterator.next(), None);
172     }
173 
174     #[test]
test_kernel_command_line_multiple_spaces_between_entries()175     fn test_kernel_command_line_multiple_spaces_between_entries() {
176         let mut iterator = CommandlineParser::new("key1=val1   key2    key3=val3   key4=val4   ");
177         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: Some("val1") })));
178         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: None })));
179         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: Some("val3") })));
180         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key4", value: Some("val4") })));
181         assert_eq!(iterator.next(), None);
182     }
183 
184     #[test]
test_kernel_command_line_no_values()185     fn test_kernel_command_line_no_values() {
186         let mut iterator = CommandlineParser::new("key1 key2 key3");
187         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: None })));
188         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: None })));
189         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: None })));
190         assert_eq!(iterator.next(), None);
191     }
192 
193     #[test]
test_kernel_command_line_empty_values()194     fn test_kernel_command_line_empty_values() {
195         let mut iterator = CommandlineParser::new(r#"key1="" key2="" key3="""#);
196         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: Some("") })));
197         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: Some("") })));
198         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: Some("") })));
199         assert_eq!(iterator.next(), None);
200     }
201 
202     #[test]
test_kernel_command_line_quoted_values()203     fn test_kernel_command_line_quoted_values() {
204         let mut iterator = CommandlineParser::new(r#"key1="value with spaces" key2="value""#);
205         assert_eq!(
206             iterator.next(),
207             Some(Ok(Entry { key: "key1", value: Some("value with spaces") }))
208         );
209         assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: Some("value") })));
210         assert_eq!(iterator.next(), None);
211     }
212 
213     #[test]
test_kernel_command_line_value_with_new_line()214     fn test_kernel_command_line_value_with_new_line() {
215         let mut iterator = CommandlineParser::new("key1=\"value with \n new line\"");
216         assert_eq!(
217             iterator.next(),
218             Some(Ok(Entry { key: "key1", value: Some("value with \n new line") }))
219         );
220         assert_eq!(iterator.next(), None);
221     }
222 
223     #[test]
test_invalid_missing_closing_quote()224     fn test_invalid_missing_closing_quote() {
225         let mut iterator = CommandlineParser::new(r#"key="value without closing quote key2=val2"#);
226         assert_eq!(iterator.next(), Some(Err(Error::InvalidInput)));
227         // After encountering invalid input, the iterator may not produce more entries.
228         assert_eq!(iterator.next(), None);
229     }
230 
231     #[test]
test_kernel_command_line_empty()232     fn test_kernel_command_line_empty() {
233         let mut iterator = CommandlineParser::new("");
234         assert_eq!(iterator.next(), None);
235     }
236 
237     #[test]
test_kernel_command_line_whitespace_only()238     fn test_kernel_command_line_whitespace_only() {
239         let mut iterator = CommandlineParser::new("    \t   \n    ");
240         assert_eq!(iterator.next(), None);
241     }
242 }
243