xref: /aosp_15_r20/bootable/libbootloader/gbl/libbootparams/src/commandline.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
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