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