1 // Copyright 2024, 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 //! Module for constructing kernel commandline. 16 //! 17 //! https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html 18 19 use crate::entry::{CommandlineParser, Entry}; 20 use core::ffi::CStr; 21 use liberror::{Error, Error::BufferTooSmall, Error::InvalidInput, Result}; 22 23 /// A class for constructing commandline section. 24 pub struct CommandlineBuilder<'a> { 25 current_size: usize, 26 buffer: &'a mut [u8], 27 } 28 29 /// Null terminator. 30 const COMMANDLINE_TRAILING_SIZE: usize = 1; 31 32 impl<'a> CommandlineBuilder<'a> { 33 /// Initialize with a given buffer. new(buffer: &'a mut [u8]) -> Result<Self>34 pub fn new(buffer: &'a mut [u8]) -> Result<Self> { 35 if buffer.len() < COMMANDLINE_TRAILING_SIZE { 36 return Err(BufferTooSmall(Some(COMMANDLINE_TRAILING_SIZE))); 37 } 38 let mut ret = Self { current_size: 0, buffer: buffer }; 39 ret.update_null_terminator(); 40 Ok(ret) 41 } 42 43 /// Initialize with a provided buffer that already contains a command line. new_from_prefix(buffer: &'a mut [u8]) -> Result<Self>44 pub fn new_from_prefix(buffer: &'a mut [u8]) -> Result<Self> { 45 let prefix = CStr::from_bytes_until_nul(buffer).map_err(Error::from)?; 46 Ok(Self { current_size: prefix.to_bytes().len(), buffer: buffer }) 47 } 48 49 /// Get the remaining capacity. remaining_capacity(&self) -> usize50 pub fn remaining_capacity(&self) -> usize { 51 self.buffer.len() - self.current_size - COMMANDLINE_TRAILING_SIZE 52 } 53 54 /// Get the current command line. as_str(&self) -> &str55 pub fn as_str(&self) -> &str { 56 // Maintain data null-terminated so not expecting to fail. 57 CStr::from_bytes_with_nul(&self.buffer[..self.current_size + 1]) 58 .unwrap() 59 .to_str() 60 .unwrap() 61 .trim() 62 } 63 64 /// Append a new command line segment via a reader callback. 65 /// 66 /// Callback arguments: 67 /// * `&CStr` - Current null terminated command line data. 68 /// * `&mut [u8]` - Remaining buffer for reading the data into. May be an empty buffer. 69 /// 70 /// Callback return value: 71 /// It must return the total size written or error. Null terminator must not be included in 72 /// the written buffer. Attempting to return a size greater than the input buffer will cause 73 /// it to panic. Empty read is allowed. 74 /// 75 /// It's up to the caller to make sure the read content will eventually form a valid 76 /// command line (space separation is handled by the call). The API is for situations where 77 /// command line is read from sources such as disk and separate buffer allocation is not 78 /// possible or desired. add_with<F>(&mut self, reader: F) -> Result<()> where F: FnOnce(&CStr, &mut [u8]) -> Result<usize>,79 pub fn add_with<F>(&mut self, reader: F) -> Result<()> 80 where 81 F: FnOnce(&CStr, &mut [u8]) -> Result<usize>, 82 { 83 let (current_buffer, mut remains_buffer) = 84 self.buffer.split_at_mut(self.current_size + COMMANDLINE_TRAILING_SIZE); 85 86 let remains_len = remains_buffer.len(); 87 // Don't need to reserve space for null terminator since buffer is already empty. 88 // Not expecting callback to append any data in this case. 89 if remains_len != 0 { 90 // Existing null terminator is gonna be replaced with separator, so need 91 // a space for another null terminator to append. 92 remains_buffer = &mut remains_buffer[..remains_len - 1]; 93 } 94 95 let current_commandline = CStr::from_bytes_with_nul(current_buffer).unwrap(); 96 let size = match reader(current_commandline, &mut remains_buffer[..]) { 97 // Handle buffer too small to make sure we request additional space for null 98 // terminator. 99 Err(BufferTooSmall(Some(requested))) => Err(BufferTooSmall(Some(requested + 1))), 100 other => other, 101 }?; 102 // Empty write, do nothing. 103 if size == 0 { 104 return Ok(()); 105 } 106 // Appended command line data cannot have null terminator. 107 if remains_buffer[..size].contains(&0u8) { 108 return Err(InvalidInput); 109 } 110 111 assert!(size <= remains_buffer.len()); 112 113 // Replace current null terminator with space separator. This logic adding a redundant 114 // leading space in case build is currently empty. Keep it as is for the simplicity. 115 self.buffer[self.current_size] = b' '; 116 // +1 for space separator 117 self.current_size += size + 1; 118 self.update_null_terminator(); 119 120 Ok(()) 121 } 122 123 /// Append a new command line. 124 /// Wrapper over `add_with`, so refer to its documentation for details. add(&mut self, commandline: &str) -> Result<()>125 pub fn add(&mut self, commandline: &str) -> Result<()> { 126 if commandline.is_empty() { 127 return Ok(()); 128 } 129 130 // +1 for space separator 131 let required_capacity = commandline.len() + 1; 132 if self.remaining_capacity() < required_capacity { 133 return Err(Error::BufferTooSmall(Some(required_capacity))); 134 } 135 136 self.add_with(|_, out| { 137 out[..commandline.len()].clone_from_slice(commandline.as_bytes()); 138 Ok(commandline.len()) 139 }) 140 } 141 142 /// Get the parsed kernel command line entries. entries(&'a self) -> impl Iterator<Item = Result<Entry<'a>>>143 pub fn entries(&'a self) -> impl Iterator<Item = Result<Entry<'a>>> { 144 CommandlineParser::new(self.as_str()) 145 } 146 147 /// Update the command line null terminator at the end of the current buffer. update_null_terminator(&mut self)148 fn update_null_terminator(&mut self) { 149 self.buffer[self.current_size] = 0; 150 } 151 } 152 153 impl core::fmt::Display for CommandlineBuilder<'_> { fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result154 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 155 write!(f, "{}", self.as_str()) 156 } 157 } 158 159 impl core::fmt::Write for CommandlineBuilder<'_> { write_str(&mut self, s: &str) -> core::fmt::Result160 fn write_str(&mut self, s: &str) -> core::fmt::Result { 161 self.add(s).map_err(|_| core::fmt::Error) 162 } 163 } 164 165 #[cfg(test)] 166 mod test { 167 use super::*; 168 use core::fmt::Write; 169 170 const TEST_COMMANDLINE: &[u8] = 171 b"video=vfb:640x400,bpp=32,memsize=3072000 console=ttyMSM0,115200n8 earlycon bootconfig\0"; 172 const NODE_TO_ADD: &str = "bootconfig"; 173 174 #[test] test_new_from_prefix()175 fn test_new_from_prefix() { 176 let mut test_commandline = TEST_COMMANDLINE.to_vec(); 177 178 let builder = CommandlineBuilder::new_from_prefix(&mut test_commandline[..]).unwrap(); 179 assert_eq!( 180 builder.as_str(), 181 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap() 182 ); 183 } 184 185 #[test] test_new_from_prefix_without_null_terminator()186 fn test_new_from_prefix_without_null_terminator() { 187 let mut test_commandline = TEST_COMMANDLINE.to_vec(); 188 189 assert!(CommandlineBuilder::new_from_prefix(&mut test_commandline[..1]).is_err()); 190 } 191 192 #[test] test_empty_initial_buffer()193 fn test_empty_initial_buffer() { 194 let mut empty = [0u8; 0]; 195 196 assert!(CommandlineBuilder::new(&mut empty[..]).is_err()); 197 } 198 199 #[test] test_add_incremental()200 fn test_add_incremental() { 201 // 1 extra byte for leading space 202 let mut buffer = [0u8; TEST_COMMANDLINE.len() + 1]; 203 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 204 for element in 205 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap().split(' ') 206 { 207 builder.add(element).unwrap(); 208 } 209 210 assert_eq!( 211 builder.as_str(), 212 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap() 213 ); 214 } 215 216 #[test] test_add_incremental_via_fmt_write()217 fn test_add_incremental_via_fmt_write() { 218 // 1 extra byte for leading space 219 let mut buffer = [0u8; TEST_COMMANDLINE.len() + 1]; 220 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 221 for element in 222 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap().split(' ') 223 { 224 write!(builder, "{}", element).unwrap(); 225 } 226 227 assert_eq!( 228 builder.as_str(), 229 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap() 230 ); 231 } 232 233 #[test] test_add_with_incremental()234 fn test_add_with_incremental() { 235 // 1 extra byte for leading space 236 let mut buffer = [0u8; TEST_COMMANDLINE.len() + 1]; 237 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 238 239 let mut offset = 0; 240 for element in 241 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap().split(' ') 242 { 243 builder 244 .add_with(|current, out| { 245 let current = core::str::from_utf8(current.to_bytes()).unwrap().trim(); 246 let expected = 247 core::str::from_utf8(&TEST_COMMANDLINE[..offset]).unwrap().trim(); 248 assert_eq!(current, expected); 249 250 out[..element.len()].copy_from_slice(element.as_bytes()); 251 Ok(element.len()) 252 }) 253 .unwrap(); 254 255 // +1 for space separator 256 offset += element.len() + 1; 257 } 258 259 assert_eq!( 260 builder.as_str(), 261 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap() 262 ); 263 } 264 265 #[test] test_add_single_node_to_full_buffer()266 fn test_add_single_node_to_full_buffer() { 267 // 1 extra byte for leading space 268 let mut buffer = [0u8; NODE_TO_ADD.len() + COMMANDLINE_TRAILING_SIZE + 1]; 269 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 270 271 builder.add(NODE_TO_ADD).unwrap(); 272 assert_eq!(builder.as_str(), NODE_TO_ADD); 273 assert_eq!(builder.remaining_capacity(), 0); 274 } 275 276 #[test] test_add_with_single_node_to_full_buffer()277 fn test_add_with_single_node_to_full_buffer() { 278 // 1 extra byte for leading space 279 let mut buffer = [0u8; NODE_TO_ADD.len() + COMMANDLINE_TRAILING_SIZE + 1]; 280 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 281 282 assert!(builder 283 .add_with(|current, out| { 284 assert_eq!(current.to_bytes().len(), 0); 285 out[..NODE_TO_ADD.len()].copy_from_slice(NODE_TO_ADD.as_bytes()); 286 Ok(NODE_TO_ADD.len()) 287 }) 288 .is_ok()); 289 assert_eq!(builder.remaining_capacity(), 0); 290 } 291 292 #[test] test_get_entries()293 fn test_get_entries() { 294 let mut test_commandline = TEST_COMMANDLINE.to_vec(); 295 let builder = CommandlineBuilder::new_from_prefix(&mut test_commandline[..]).unwrap(); 296 297 let data_from_builder = builder 298 .entries() 299 .map(|entry| entry.unwrap().to_string()) 300 .collect::<Vec<String>>() 301 .join(" "); 302 303 assert_eq!( 304 data_from_builder, 305 CStr::from_bytes_until_nul(TEST_COMMANDLINE).unwrap().to_str().unwrap() 306 ); 307 } 308 309 #[test] test_add_to_empty_not_enough_space()310 fn test_add_to_empty_not_enough_space() { 311 let mut buffer = [0u8; COMMANDLINE_TRAILING_SIZE]; 312 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 313 314 // + 1 requested for space separator 315 assert_eq!( 316 builder.add(NODE_TO_ADD), 317 Err(Error::BufferTooSmall(Some(NODE_TO_ADD.len() + 1))) 318 ); 319 } 320 321 #[test] test_add_with_to_empty_not_enough_space_requested_space_for_separator()322 fn test_add_with_to_empty_not_enough_space_requested_space_for_separator() { 323 let mut buffer = [0u8; COMMANDLINE_TRAILING_SIZE]; 324 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 325 326 assert_eq!( 327 builder.add_with(|_, _| { Err(Error::BufferTooSmall(Some(NODE_TO_ADD.len()))) }), 328 Err(Error::BufferTooSmall(Some(NODE_TO_ADD.len() + 1))) 329 ); 330 } 331 332 #[test] test_empty_add_with_to_empty_succeed()333 fn test_empty_add_with_to_empty_succeed() { 334 let mut buffer = [0u8; COMMANDLINE_TRAILING_SIZE]; 335 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 336 337 assert!(builder.add_with(|_, _| { Ok(0) }).is_ok()); 338 } 339 340 #[test] test_add_with_null_terminator_invalid_input()341 fn test_add_with_null_terminator_invalid_input() { 342 let mut buffer = TEST_COMMANDLINE.to_vec(); 343 let mut builder = CommandlineBuilder::new(&mut buffer[..]).unwrap(); 344 345 assert_eq!( 346 builder.add_with(|_, out| { 347 let with_null_terminator = b"null\0terminator"; 348 out[..with_null_terminator.len()].copy_from_slice(&with_null_terminator[..]); 349 Ok(with_null_terminator.len()) 350 }), 351 Err(Error::InvalidInput) 352 ); 353 } 354 } 355