blob: aacd8e042408e8c2e8c119c177517cc378484b60 [file] [log] [blame]
// Copyright 2023, 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.
//! Routines for parsing bootargs
#[cfg(not(test))]
use alloc::format;
#[cfg(not(test))]
use alloc::string::String;
use core::ffi::CStr;
/// A single boot argument ex: "panic", "init=", or "foo=1,2,3".
pub struct BootArg<'a> {
arg: &'a str,
equal_sign: Option<usize>,
}
impl AsRef<str> for BootArg<'_> {
fn as_ref(&self) -> &str {
self.arg
}
}
impl BootArg<'_> {
/// Name of the boot argument
pub fn name(&self) -> &str {
if let Some(n) = self.equal_sign {
&self.arg[..n]
} else {
self.arg
}
}
/// Optional value of the boot aragument. This includes the '=' prefix.
pub fn value(&self) -> Option<&str> {
Some(&self.arg[self.equal_sign?..])
}
}
/// Iterator that iteratos over bootargs
pub struct BootArgsIterator<'a> {
arg: &'a str,
}
impl<'a> BootArgsIterator<'a> {
/// Creates a new iterator from the raw boot args. The input has to be encoded in ASCII
pub fn new(bootargs: &'a CStr) -> Result<Self, String> {
let arg = bootargs.to_str().map_err(|e| format!("{e}"))?;
if !arg.is_ascii() {
return Err(format!("{arg:?} is not ASCII"));
}
Ok(Self { arg })
}
// Finds the end of a value in the given string `s`, and returns the index of the end. A value
// can have spaces if quoted. The quote character can't be escaped.
fn find_value_end(s: &str) -> usize {
let mut in_quote = false;
for (i, c) in s.char_indices() {
if c == '"' {
in_quote = !in_quote;
} else if c.is_whitespace() && !in_quote {
return i;
}
}
s.len()
}
}
impl<'a> Iterator for BootArgsIterator<'a> {
type Item = BootArg<'a>;
fn next(&mut self) -> Option<Self::Item> {
// Skip spaces to find the start of a name. If there's nothing left, that's the end of the
// iterator.
let arg = self.arg.trim_start();
self.arg = arg; // advance before returning
if arg.is_empty() {
return None;
}
// Name ends with either whitespace or =. If it ends with =, the value comes immediately
// after.
let name_end = arg.find(|c: char| c.is_whitespace() || c == '=').unwrap_or(arg.len());
let (arg, equal_sign) = match arg.chars().nth(name_end) {
Some('=') => {
let value_end = name_end + Self::find_value_end(&arg[name_end..]);
(&arg[..value_end], Some(name_end))
}
_ => (&arg[..name_end], None),
};
self.arg = &self.arg[arg.len()..]; // advance before returning
Some(BootArg { arg, equal_sign })
}
}
#[cfg(test)]
mod tests {
use super::*;
use cstr::cstr;
fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
let actual = BootArgsIterator::new(raw);
assert_eq!(actual.is_err(), expected.is_err(), "Unexpected result with {raw:?}");
if actual.is_err() {
return;
}
let mut actual = actual.unwrap();
for (name, value) in expected.unwrap() {
let actual = actual.next();
assert!(actual.is_some(), "Expected ({}, {:?}) from {raw:?}", name, value);
let actual = actual.unwrap();
assert_eq!(name, &actual.name(), "Unexpected name from {raw:?}");
assert_eq!(value, &actual.value(), "Unexpected value from {raw:?}");
}
let remaining = actual.next();
assert!(
remaining.is_none(),
"Unexpected extra item from {raw:?}. Got ({}, {:?})",
remaining.as_ref().unwrap().name(),
remaining.as_ref().unwrap().value()
);
}
#[test]
fn empty() {
check(cstr!(""), Ok(&[]));
check(cstr!(" "), Ok(&[]));
check(cstr!(" \n "), Ok(&[]));
}
#[test]
fn single() {
check(cstr!("foo"), Ok(&[("foo", None)]));
check(cstr!(" foo"), Ok(&[("foo", None)]));
check(cstr!("foo "), Ok(&[("foo", None)]));
check(cstr!(" foo "), Ok(&[("foo", None)]));
}
#[test]
fn single_with_value() {
check(cstr!("foo=bar"), Ok(&[("foo", Some("=bar"))]));
check(cstr!(" foo=bar"), Ok(&[("foo", Some("=bar"))]));
check(cstr!("foo=bar "), Ok(&[("foo", Some("=bar"))]));
check(cstr!(" foo=bar "), Ok(&[("foo", Some("=bar"))]));
check(cstr!("foo="), Ok(&[("foo", Some("="))]));
check(cstr!(" foo="), Ok(&[("foo", Some("="))]));
check(cstr!("foo= "), Ok(&[("foo", Some("="))]));
check(cstr!(" foo= "), Ok(&[("foo", Some("="))]));
}
#[test]
fn single_with_quote() {
check(cstr!("foo=hello\" \"world"), Ok(&[("foo", Some("=hello\" \"world"))]));
}
#[test]
fn invalid_encoding() {
check(CStr::from_bytes_with_nul(&[255, 255, 255, 0]).unwrap(), Err(()));
}
#[test]
fn multiple() {
check(
cstr!(" a=b c=d e= f g "),
Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
);
check(
cstr!(" a=b \n c=d e= f g"),
Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
);
}
#[test]
fn incomplete_quote() {
check(
cstr!("foo=incomplete\" quote bar=y"),
Ok(&[("foo", Some("=incomplete\" quote bar=y"))]),
);
}
#[test]
fn complex() {
check(cstr!(" a a1= b=c d=e,f,g x=\"value with quote\" y=val\"ue with \"multiple\" quo\"te "), Ok(&[
("a", None),
("a1", Some("=")),
("b", Some("=c")),
("d", Some("=e,f,g")),
("x", Some("=\"value with quote\"")),
("y", Some("=val\"ue with \"multiple\" quo\"te")),
]));
}
}