xref: /aosp_15_r20/external/crosvm/kernel_cmdline/src/kernel_cmdline.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Helper for creating valid kernel command line strings.
6 
7 use std::result;
8 
9 use remain::sorted;
10 use thiserror::Error;
11 
12 /// The error type for command line building operations.
13 #[sorted]
14 #[derive(Error, PartialEq, Eq, Debug)]
15 pub enum Error {
16     /// Key/Value Operation would have had an equals sign in it.
17     #[error("string contains an equals sign")]
18     HasEquals,
19     /// Key/Value Operation would have had a space in it.
20     #[error("string contains a space")]
21     HasSpace,
22     /// Operation would have resulted in a non-printable ASCII character.
23     #[error("string contains non-printable ASCII character")]
24     InvalidAscii,
25     /// Operation would have made the command line too large.
26     #[error("command line length {0} exceeds maximum {1}")]
27     TooLarge(usize, usize),
28 }
29 
30 /// Specialized Result type for command line operations.
31 pub type Result<T> = result::Result<T, Error>;
32 
valid_char(c: char) -> bool33 fn valid_char(c: char) -> bool {
34     matches!(c, ' '..='~')
35 }
36 
valid_str(s: &str) -> Result<()>37 fn valid_str(s: &str) -> Result<()> {
38     if s.chars().all(valid_char) {
39         Ok(())
40     } else {
41         Err(Error::InvalidAscii)
42     }
43 }
44 
valid_element(s: &str) -> Result<()>45 fn valid_element(s: &str) -> Result<()> {
46     if !s.chars().all(valid_char) {
47         Err(Error::InvalidAscii)
48     } else if s.contains(' ') {
49         Err(Error::HasSpace)
50     } else if s.contains('=') {
51         Err(Error::HasEquals)
52     } else {
53         Ok(())
54     }
55 }
56 
57 /// A builder for a kernel command line string that validates the string as it is built.
58 #[derive(Default)]
59 pub struct Cmdline {
60     line: String,
61 }
62 
63 impl Cmdline {
64     /// Constructs an empty Cmdline.
new() -> Cmdline65     pub fn new() -> Cmdline {
66         Cmdline::default()
67     }
68 
push_space_if_needed(&mut self)69     fn push_space_if_needed(&mut self) {
70         if !self.line.is_empty() {
71             self.line.push(' ');
72         }
73     }
74 
75     /// Validates and inserts a key value pair into this command line
insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()>76     pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
77         let k = key.as_ref();
78         let v = val.as_ref();
79 
80         valid_element(k)?;
81         valid_element(v)?;
82 
83         self.push_space_if_needed();
84         self.line.push_str(k);
85         self.line.push('=');
86         self.line.push_str(v);
87 
88         Ok(())
89     }
90 
91     /// Validates and inserts a string to the end of the current command line
insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()>92     pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
93         let s = slug.as_ref();
94         valid_str(s)?;
95 
96         self.push_space_if_needed();
97         self.line.push_str(s);
98 
99         Ok(())
100     }
101 
102     /// Returns the cmdline in progress without nul termination
as_str(&self) -> &str103     pub fn as_str(&self) -> &str {
104         self.line.as_str()
105     }
106 
107     /// Returns the current command line as a string with a maximum length.
108     ///
109     /// # Arguments
110     ///
111     /// `max_len`: maximum number of bytes (not including NUL terminator)
as_str_with_max_len(&self, max_len: usize) -> Result<&str>112     pub fn as_str_with_max_len(&self, max_len: usize) -> Result<&str> {
113         let s = self.line.as_str();
114         if s.len() <= max_len {
115             Ok(s)
116         } else {
117             Err(Error::TooLarge(s.len(), max_len))
118         }
119     }
120 
121     /// Converts the command line into a `Vec<u8>` with a maximum length.
122     ///
123     /// # Arguments
124     ///
125     /// `max_len`: maximum number of bytes (not including NUL terminator)
into_bytes_with_max_len(self, max_len: usize) -> Result<Vec<u8>>126     pub fn into_bytes_with_max_len(self, max_len: usize) -> Result<Vec<u8>> {
127         let bytes: Vec<u8> = self.line.into_bytes();
128         if bytes.len() <= max_len {
129             Ok(bytes)
130         } else {
131             Err(Error::TooLarge(bytes.len(), max_len))
132         }
133     }
134 }
135 
136 #[cfg(test)]
137 mod tests {
138     use super::*;
139 
140     #[test]
insert_hello_world()141     fn insert_hello_world() {
142         let mut cl = Cmdline::new();
143         assert_eq!(cl.as_str(), "");
144         assert!(cl.insert("hello", "world").is_ok());
145         assert_eq!(cl.as_str(), "hello=world");
146 
147         let bytes = cl
148             .into_bytes_with_max_len(100)
149             .expect("failed to convert Cmdline into bytes");
150         assert_eq!(bytes, b"hello=world");
151     }
152 
153     #[test]
insert_multi()154     fn insert_multi() {
155         let mut cl = Cmdline::new();
156         assert!(cl.insert("hello", "world").is_ok());
157         assert!(cl.insert("foo", "bar").is_ok());
158         assert_eq!(cl.as_str(), "hello=world foo=bar");
159     }
160 
161     #[test]
insert_space()162     fn insert_space() {
163         let mut cl = Cmdline::new();
164         assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
165         assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
166         assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
167         assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
168         assert_eq!(cl.as_str(), "");
169     }
170 
171     #[test]
insert_equals()172     fn insert_equals() {
173         let mut cl = Cmdline::new();
174         assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
175         assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
176         assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
177         assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
178         assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
179         assert_eq!(cl.as_str(), "");
180     }
181 
182     #[test]
insert_emoji()183     fn insert_emoji() {
184         let mut cl = Cmdline::new();
185         assert_eq!(cl.insert("heart", "��"), Err(Error::InvalidAscii));
186         assert_eq!(cl.insert("��", "love"), Err(Error::InvalidAscii));
187         assert_eq!(cl.as_str(), "");
188     }
189 
190     #[test]
insert_string()191     fn insert_string() {
192         let mut cl = Cmdline::new();
193         assert_eq!(cl.as_str(), "");
194         assert!(cl.insert_str("noapic").is_ok());
195         assert_eq!(cl.as_str(), "noapic");
196         assert!(cl.insert_str("nopci").is_ok());
197         assert_eq!(cl.as_str(), "noapic nopci");
198     }
199 
200     #[test]
as_str_too_large()201     fn as_str_too_large() {
202         let mut cl = Cmdline::new();
203         assert!(cl.insert("a", "b").is_ok()); // start off with 3.
204         assert_eq!(cl.as_str(), "a=b");
205         assert_eq!(cl.as_str_with_max_len(2), Err(Error::TooLarge(3, 2)));
206         assert_eq!(cl.as_str_with_max_len(3), Ok("a=b"));
207 
208         let mut cl = Cmdline::new();
209         assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
210         assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
211         assert_eq!(cl.as_str(), "ab=ba c=d");
212         assert_eq!(cl.as_str_with_max_len(8), Err(Error::TooLarge(9, 8)));
213         assert_eq!(cl.as_str_with_max_len(9), Ok("ab=ba c=d"));
214 
215         let mut cl = Cmdline::new();
216         assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
217         assert!(cl.insert_str("123").is_ok()); // adds 4 (including space) length
218         assert_eq!(cl.as_str(), "ab=ba 123");
219         assert_eq!(cl.as_str_with_max_len(8), Err(Error::TooLarge(9, 8)));
220         assert_eq!(cl.as_str_with_max_len(9), Ok("ab=ba 123"));
221     }
222 }
223