• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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