Jiyong Park | c5d2ef2 | 2023-04-11 01:23:46 +0900 | [diff] [blame] | 1 | // 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))] |
| 18 | use alloc::format; |
| 19 | #[cfg(not(test))] |
| 20 | use alloc::string::String; |
| 21 | use core::ffi::CStr; |
| 22 | |
| 23 | /// A single boot argument ex: "panic", "init=", or "foo=1,2,3". |
| 24 | pub struct BootArg<'a> { |
| 25 | arg: &'a str, |
| 26 | equal_sign: Option<usize>, |
| 27 | } |
| 28 | |
| 29 | impl AsRef<str> for BootArg<'_> { |
| 30 | fn as_ref(&self) -> &str { |
| 31 | self.arg |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | impl 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 |
| 52 | pub struct BootArgsIterator<'a> { |
| 53 | arg: &'a str, |
| 54 | } |
| 55 | |
| 56 | impl<'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 | |
| 82 | impl<'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) { |
Charisee | a732371 | 2023-11-03 15:46:21 +0000 | [diff] [blame] | 97 | Some('=') => { |
Jiyong Park | c5d2ef2 | 2023-04-11 01:23:46 +0900 | [diff] [blame] | 98 | 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 Park | c5d2ef2 | 2023-04-11 01:23:46 +0900 | [diff] [blame] | 109 | mod tests { |
Jiyong Park | c5d2ef2 | 2023-04-11 01:23:46 +0900 | [diff] [blame] | 110 | use super::*; |
Alice Wang | 81399f5 | 2023-05-26 14:23:43 +0000 | [diff] [blame] | 111 | |
Pierre-Clément Tosi | 180a7c2 | 2023-11-07 09:54:39 +0000 | [diff] [blame] | 112 | // TODO(b/308694211): Use cstr!() from vmbase |
Alice Wang | 81399f5 | 2023-05-26 14:23:43 +0000 | [diff] [blame] | 113 | macro_rules! cstr { |
| 114 | ($str:literal) => {{ |
Pierre-Clément Tosi | 180a7c2 | 2023-11-07 09:54:39 +0000 | [diff] [blame] | 115 | 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 Wang | 81399f5 | 2023-05-26 14:23:43 +0000 | [diff] [blame] | 122 | }}; |
| 123 | } |
Jiyong Park | c5d2ef2 | 2023-04-11 01:23:46 +0900 | [diff] [blame] | 124 | |
| 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 | } |