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