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