blob: a089a67b313cfd334c9e0d1d431dd4c3115ae38e [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::*;
Alice Wang81399f52023-05-26 14:23:43 +0000111
Pierre-Clément Tosi180a7c22023-11-07 09:54:39 +0000112 // TODO(b/308694211): Use cstr!() from vmbase
Alice Wang81399f52023-05-26 14:23:43 +0000113 macro_rules! cstr {
114 ($str:literal) => {{
Pierre-Clément Tosi180a7c22023-11-07 09:54:39 +0000115 const S: &str = concat!($str, "\0");
116 const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes())
117 {
118 Ok(v) => v,
119 Err(_) => panic!("string contains interior NUL"),
120 };
121 C
Alice Wang81399f52023-05-26 14:23:43 +0000122 }};
123 }
Jiyong Parkc5d2ef22023-04-11 01:23:46 +0900124
125 fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
126 let actual = BootArgsIterator::new(raw);
127 assert_eq!(actual.is_err(), expected.is_err(), "Unexpected result with {raw:?}");
128 if actual.is_err() {
129 return;
130 }
131 let mut actual = actual.unwrap();
132
133 for (name, value) in expected.unwrap() {
134 let actual = actual.next();
135 assert!(actual.is_some(), "Expected ({}, {:?}) from {raw:?}", name, value);
136 let actual = actual.unwrap();
137 assert_eq!(name, &actual.name(), "Unexpected name from {raw:?}");
138 assert_eq!(value, &actual.value(), "Unexpected value from {raw:?}");
139 }
140 let remaining = actual.next();
141 assert!(
142 remaining.is_none(),
143 "Unexpected extra item from {raw:?}. Got ({}, {:?})",
144 remaining.as_ref().unwrap().name(),
145 remaining.as_ref().unwrap().value()
146 );
147 }
148
149 #[test]
150 fn empty() {
151 check(cstr!(""), Ok(&[]));
152 check(cstr!(" "), Ok(&[]));
153 check(cstr!(" \n "), Ok(&[]));
154 }
155
156 #[test]
157 fn single() {
158 check(cstr!("foo"), Ok(&[("foo", None)]));
159 check(cstr!(" foo"), Ok(&[("foo", None)]));
160 check(cstr!("foo "), Ok(&[("foo", None)]));
161 check(cstr!(" foo "), Ok(&[("foo", None)]));
162 }
163
164 #[test]
165 fn single_with_value() {
166 check(cstr!("foo=bar"), Ok(&[("foo", Some("=bar"))]));
167 check(cstr!(" foo=bar"), Ok(&[("foo", Some("=bar"))]));
168 check(cstr!("foo=bar "), Ok(&[("foo", Some("=bar"))]));
169 check(cstr!(" foo=bar "), Ok(&[("foo", Some("=bar"))]));
170
171 check(cstr!("foo="), Ok(&[("foo", Some("="))]));
172 check(cstr!(" foo="), Ok(&[("foo", Some("="))]));
173 check(cstr!("foo= "), Ok(&[("foo", Some("="))]));
174 check(cstr!(" foo= "), Ok(&[("foo", Some("="))]));
175 }
176
177 #[test]
178 fn single_with_quote() {
179 check(cstr!("foo=hello\" \"world"), Ok(&[("foo", Some("=hello\" \"world"))]));
180 }
181
182 #[test]
183 fn invalid_encoding() {
184 check(CStr::from_bytes_with_nul(&[255, 255, 255, 0]).unwrap(), Err(()));
185 }
186
187 #[test]
188 fn multiple() {
189 check(
190 cstr!(" a=b c=d e= f g "),
191 Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
192 );
193 check(
194 cstr!(" a=b \n c=d e= f g"),
195 Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
196 );
197 }
198
199 #[test]
200 fn incomplete_quote() {
201 check(
202 cstr!("foo=incomplete\" quote bar=y"),
203 Ok(&[("foo", Some("=incomplete\" quote bar=y"))]),
204 );
205 }
206
207 #[test]
208 fn complex() {
209 check(cstr!(" a a1= b=c d=e,f,g x=\"value with quote\" y=val\"ue with \"multiple\" quo\"te "), Ok(&[
210 ("a", None),
211 ("a1", Some("=")),
212 ("b", Some("=c")),
213 ("d", Some("=e,f,g")),
214 ("x", Some("=\"value with quote\"")),
215 ("y", Some("=val\"ue with \"multiple\" quo\"te")),
216 ]));
217 }
218}