blob: 0a5697f40c8becd8e51b06804d05ec53bc189bb2 [file] [log] [blame]
Jiyong Parkc5d2ef22023-04-11 01:23:46 +09001// Copyright 2023, 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//! Routines for parsing bootargs
16
17#[cfg(not(test))]
18use alloc::format;
19#[cfg(not(test))]
20use alloc::string::String;
21use core::ffi::CStr;
22
23/// A single boot argument ex: "panic", "init=", or "foo=1,2,3".
24pub struct BootArg<'a> {
25 arg: &'a str,
26 equal_sign: Option<usize>,
27}
28
29impl AsRef<str> for BootArg<'_> {
30 fn as_ref(&self) -> &str {
31 self.arg
32 }
33}
34
35impl BootArg<'_> {
36 /// Name of the boot argument
37 pub fn name(&self) -> &str {
38 if let Some(n) = self.equal_sign {
39 &self.arg[..n]
40 } else {
41 self.arg
42 }
43 }
44
45 /// Optional value of the boot aragument. This includes the '=' prefix.
46 pub fn value(&self) -> Option<&str> {
47 Some(&self.arg[self.equal_sign?..])
48 }
49}
50
51/// Iterator that iteratos over bootargs
52pub struct BootArgsIterator<'a> {
53 arg: &'a str,
54}
55
56impl<'a> BootArgsIterator<'a> {
57 /// Creates a new iterator from the raw boot args. The input has to be encoded in ASCII
58 pub fn new(bootargs: &'a CStr) -> Result<Self, String> {
59 let arg = bootargs.to_str().map_err(|e| format!("{e}"))?;
60 if !arg.is_ascii() {
61 return Err(format!("{arg:?} is not ASCII"));
62 }
63
64 Ok(Self { arg })
65 }
66
67 // Finds the end of a value in the given string `s`, and returns the index of the end. A value
68 // can have spaces if quoted. The quote character can't be escaped.
69 fn find_value_end(s: &str) -> usize {
70 let mut in_quote = false;
71 for (i, c) in s.char_indices() {
72 if c == '"' {
73 in_quote = !in_quote;
74 } else if c.is_whitespace() && !in_quote {
75 return i;
76 }
77 }
78 s.len()
79 }
80}
81
82impl<'a> Iterator for BootArgsIterator<'a> {
83 type Item = BootArg<'a>;
84
85 fn next(&mut self) -> Option<Self::Item> {
86 // Skip spaces to find the start of a name. If there's nothing left, that's the end of the
87 // iterator.
88 let arg = self.arg.trim_start();
89 self.arg = arg; // advance before returning
90 if arg.is_empty() {
91 return None;
92 }
93 // Name ends with either whitespace or =. If it ends with =, the value comes immediately
94 // after.
95 let name_end = arg.find(|c: char| c.is_whitespace() || c == '=').unwrap_or(arg.len());
96 let (arg, equal_sign) = match arg.chars().nth(name_end) {
Chariseea7323712023-11-03 15:46:21 +000097 Some('=') => {
Jiyong Parkc5d2ef22023-04-11 01:23:46 +090098 let value_end = name_end + Self::find_value_end(&arg[name_end..]);
99 (&arg[..value_end], Some(name_end))
100 }
101 _ => (&arg[..name_end], None),
102 };
103 self.arg = &self.arg[arg.len()..]; // advance before returning
104 Some(BootArg { arg, equal_sign })
105 }
106}
107
108#[cfg(test)]
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900109mod tests {
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900110 use super::*;
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900111
112 fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
113 let actual = BootArgsIterator::new(raw);
114 assert_eq!(actual.is_err(), expected.is_err(), "Unexpected result with {raw:?}");
115 if actual.is_err() {
116 return;
117 }
118 let mut actual = actual.unwrap();
119
120 for (name, value) in expected.unwrap() {
121 let actual = actual.next();
122 assert!(actual.is_some(), "Expected ({}, {:?}) from {raw:?}", name, value);
123 let actual = actual.unwrap();
124 assert_eq!(name, &actual.name(), "Unexpected name from {raw:?}");
125 assert_eq!(value, &actual.value(), "Unexpected value from {raw:?}");
126 }
127 let remaining = actual.next();
128 assert!(
129 remaining.is_none(),
130 "Unexpected extra item from {raw:?}. Got ({}, {:?})",
131 remaining.as_ref().unwrap().name(),
132 remaining.as_ref().unwrap().value()
133 );
134 }
135
136 #[test]
137 fn empty() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000138 check(c"", Ok(&[]));
139 check(c" ", Ok(&[]));
140 check(c" \n ", Ok(&[]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900141 }
142
143 #[test]
144 fn single() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000145 check(c"foo", Ok(&[("foo", None)]));
146 check(c" foo", Ok(&[("foo", None)]));
147 check(c"foo ", Ok(&[("foo", None)]));
148 check(c" foo ", Ok(&[("foo", None)]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900149 }
150
151 #[test]
152 fn single_with_value() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000153 check(c"foo=bar", Ok(&[("foo", Some("=bar"))]));
154 check(c" foo=bar", Ok(&[("foo", Some("=bar"))]));
155 check(c"foo=bar ", Ok(&[("foo", Some("=bar"))]));
156 check(c" foo=bar ", Ok(&[("foo", Some("=bar"))]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900157
Alan Stokesf46a17c2025-01-05 15:50:18 +0000158 check(c"foo=", Ok(&[("foo", Some("="))]));
159 check(c" foo=", Ok(&[("foo", Some("="))]));
160 check(c"foo= ", Ok(&[("foo", Some("="))]));
161 check(c" foo= ", Ok(&[("foo", Some("="))]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900162 }
163
164 #[test]
165 fn single_with_quote() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000166 check(c"foo=hello\" \"world", Ok(&[("foo", Some("=hello\" \"world"))]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900167 }
168
169 #[test]
170 fn invalid_encoding() {
171 check(CStr::from_bytes_with_nul(&[255, 255, 255, 0]).unwrap(), Err(()));
172 }
173
174 #[test]
175 fn multiple() {
176 check(
Alan Stokesf46a17c2025-01-05 15:50:18 +0000177 c" a=b c=d e= f g ",
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900178 Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
179 );
180 check(
Alan Stokesf46a17c2025-01-05 15:50:18 +0000181 c" a=b \n c=d e= f g",
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900182 Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
183 );
184 }
185
186 #[test]
187 fn incomplete_quote() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000188 check(c"foo=incomplete\" quote bar=y", Ok(&[("foo", Some("=incomplete\" quote bar=y"))]));
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900189 }
190
191 #[test]
192 fn complex() {
Alan Stokesf46a17c2025-01-05 15:50:18 +0000193 check(
194 c" a a1= b=c d=e,f,g x=\"value with quote\" y=val\"ue with \"multiple\" quo\"te ",
195 Ok(&[
196 ("a", None),
197 ("a1", Some("=")),
198 ("b", Some("=c")),
199 ("d", Some("=e,f,g")),
200 ("x", Some("=\"value with quote\"")),
201 ("y", Some("=val\"ue with \"multiple\" quo\"te")),
202 ]),
203 );
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900204 }
205}