// Copyright 2024, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Entities to parse and iterate over both kernel command line and bootconfig. use core::fmt::{Display, Formatter}; use liberror::{Error, Result}; /// A struct representing a key-value entry inside kernel command line or bootconfig. #[derive(Debug, PartialEq, Eq)] pub struct Entry<'a> { /// Boot parameters entry key. pub key: &'a str, /// Boot parameters entry value (may be not presented). pub value: Option<&'a str>, } /// Convert Entry into kernel command line / bootconfig compatible string. impl<'a> Display for Entry<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.value { Some(value) => write!(f, "{}={}", self.key, value), None => write!(f, "{}", self.key), } } } /// To iterate over kernel command line entries. pub struct CommandlineParser<'a> { data: &'a str, pos: usize, } impl<'a> CommandlineParser<'a> { /// Creates a new iterator from raw command line. pub fn new(data: &'a str) -> Self { CommandlineParser { data, pos: 0 } } fn remains(&self) -> &'a str { &self.data.get(self.pos..).unwrap_or("") } fn peek(&self) -> Option { self.remains().chars().next() } fn skip(&mut self, n: usize) { self.pos += n; } fn take_while(&mut self, predicate: F) -> &'a str where F: Fn(char) -> bool, { let remains = self.remains(); let n = match remains.find(|c: char| !predicate(c)) { Some(end) => end, // Take everything if we cannot find. None => remains.len(), }; self.pos += n; &remains[..n] } fn skip_whitespaces(&mut self) { self.pos += self.remains().len() - self.remains().trim_start().len(); } fn parse_key(&mut self) -> Option<&'a str> { self.skip_whitespaces(); let key = self.take_while(|c| !c.is_whitespace() && c != '='); match key.is_empty() { true => None, false => Some(key), } } fn parse_value(&mut self) -> Result> { match self.peek() { // Skip the '=' character. Some('=') => self.skip(1), // No value. Some(c) if c.is_whitespace() => return Ok(None), // End of input. None => return Ok(None), // Invalid input _ => { self.skip(self.remains().len()); return Err(Error::InvalidInput); } } let value = match self.peek() { // Check for the open quote. Some('"') => { // Skip it. self.skip(1); let value = self.take_while(|c| c != '"'); // Check for the close quote. match self.peek() { Some('"') => { // Skip it. self.skip(1); value } _ => { self.skip(self.remains().len()); return Err(Error::InvalidInput); } } } _ => self.take_while(|c| !c.is_whitespace()), }; Ok(Some(value)) } } /// Parse kernel command line format, so we can iterate over key-value entries. /// https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html impl<'a> Iterator for CommandlineParser<'a> { type Item = Result>; fn next(&mut self) -> Option { match self.parse_key() { Some(key) => match self.parse_value() { Ok(value) => Some(Ok(Entry { key, value })), Err(e) => Some(Err(e)), }, None => None, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_kernel_command_line_valid_key_value() { let mut iterator = CommandlineParser::new( "video=vfb:640x400,bpp=32,memsize=3072000 console=ttyMSM0,115200n8 earlycon bootconfig", ); assert_eq!( iterator.next(), Some(Ok(Entry { key: "video", value: Some("vfb:640x400,bpp=32,memsize=3072000") })) ); assert_eq!( iterator.next(), Some(Ok(Entry { key: "console", value: Some("ttyMSM0,115200n8") })) ); assert_eq!(iterator.next(), Some(Ok(Entry { key: "earlycon", value: None }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "bootconfig", value: None }))); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_multiple_spaces_between_entries() { let mut iterator = CommandlineParser::new("key1=val1 key2 key3=val3 key4=val4 "); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: Some("val1") }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: None }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: Some("val3") }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key4", value: Some("val4") }))); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_no_values() { let mut iterator = CommandlineParser::new("key1 key2 key3"); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: None }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: None }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: None }))); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_empty_values() { let mut iterator = CommandlineParser::new(r#"key1="" key2="" key3="""#); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key1", value: Some("") }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: Some("") }))); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key3", value: Some("") }))); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_quoted_values() { let mut iterator = CommandlineParser::new(r#"key1="value with spaces" key2="value""#); assert_eq!( iterator.next(), Some(Ok(Entry { key: "key1", value: Some("value with spaces") })) ); assert_eq!(iterator.next(), Some(Ok(Entry { key: "key2", value: Some("value") }))); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_value_with_new_line() { let mut iterator = CommandlineParser::new("key1=\"value with \n new line\""); assert_eq!( iterator.next(), Some(Ok(Entry { key: "key1", value: Some("value with \n new line") })) ); assert_eq!(iterator.next(), None); } #[test] fn test_invalid_missing_closing_quote() { let mut iterator = CommandlineParser::new(r#"key="value without closing quote key2=val2"#); assert_eq!(iterator.next(), Some(Err(Error::InvalidInput))); // After encountering invalid input, the iterator may not produce more entries. assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_empty() { let mut iterator = CommandlineParser::new(""); assert_eq!(iterator.next(), None); } #[test] fn test_kernel_command_line_whitespace_only() { let mut iterator = CommandlineParser::new(" \t \n "); assert_eq!(iterator.next(), None); } }