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("inserting string would make command line too long")]
27 TooLarge,
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 its being built. A
58 /// `CString` can be constructed from this directly using `CString::new`.
59 pub struct Cmdline {
60 line: String,
61 capacity: usize,
62 }
63
64 impl Cmdline {
65 /// Constructs an empty Cmdline with the given capacity, which includes the nul terminator.
66 /// Capacity must be greater than 0.
new(capacity: usize) -> Cmdline67 pub fn new(capacity: usize) -> Cmdline {
68 assert_ne!(capacity, 0);
69 Cmdline {
70 line: String::new(),
71 capacity,
72 }
73 }
74
has_capacity(&self, more: usize) -> Result<()>75 fn has_capacity(&self, more: usize) -> Result<()> {
76 let needs_space = if self.line.is_empty() { 0 } else { 1 };
77 if self.line.len() + more + needs_space < self.capacity {
78 Ok(())
79 } else {
80 Err(Error::TooLarge)
81 }
82 }
83
start_push(&mut self)84 fn start_push(&mut self) {
85 if !self.line.is_empty() {
86 self.line.push(' ');
87 }
88 }
89
end_push(&mut self)90 fn end_push(&mut self) {
91 // This assert is always true because of the `has_capacity` check that each insert method
92 // uses.
93 assert!(self.line.len() < self.capacity);
94 }
95
96 /// Validates and inserts a key value pair into this command line
insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()>97 pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
98 let k = key.as_ref();
99 let v = val.as_ref();
100
101 valid_element(k)?;
102 valid_element(v)?;
103 self.has_capacity(k.len() + v.len() + 1)?;
104
105 self.start_push();
106 self.line.push_str(k);
107 self.line.push('=');
108 self.line.push_str(v);
109 self.end_push();
110
111 Ok(())
112 }
113
114 /// Validates and inserts a string to the end of the current command line
insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()>115 pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
116 let s = slug.as_ref();
117 valid_str(s)?;
118
119 self.has_capacity(s.len())?;
120
121 self.start_push();
122 self.line.push_str(s);
123 self.end_push();
124
125 Ok(())
126 }
127
128 /// Returns the cmdline in progress without nul termination
as_str(&self) -> &str129 pub fn as_str(&self) -> &str {
130 self.line.as_str()
131 }
132 }
133
134 impl From<Cmdline> for Vec<u8> {
from(c: Cmdline) -> Vec<u8>135 fn from(c: Cmdline) -> Vec<u8> {
136 c.line.into_bytes()
137 }
138 }
139
140 #[cfg(test)]
141 mod tests {
142 use std::ffi::CString;
143
144 use super::*;
145
146 #[test]
insert_hello_world()147 fn insert_hello_world() {
148 let mut cl = Cmdline::new(100);
149 assert_eq!(cl.as_str(), "");
150 assert!(cl.insert("hello", "world").is_ok());
151 assert_eq!(cl.as_str(), "hello=world");
152
153 let s = CString::new(cl).expect("failed to create CString from Cmdline");
154 assert_eq!(s, CString::new("hello=world").unwrap());
155 }
156
157 #[test]
insert_multi()158 fn insert_multi() {
159 let mut cl = Cmdline::new(100);
160 assert!(cl.insert("hello", "world").is_ok());
161 assert!(cl.insert("foo", "bar").is_ok());
162 assert_eq!(cl.as_str(), "hello=world foo=bar");
163 }
164
165 #[test]
insert_space()166 fn insert_space() {
167 let mut cl = Cmdline::new(100);
168 assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
169 assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
170 assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
171 assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
172 assert_eq!(cl.as_str(), "");
173 }
174
175 #[test]
insert_equals()176 fn insert_equals() {
177 let mut cl = Cmdline::new(100);
178 assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
179 assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
180 assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
181 assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
182 assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
183 assert_eq!(cl.as_str(), "");
184 }
185
186 #[test]
insert_emoji()187 fn insert_emoji() {
188 let mut cl = Cmdline::new(100);
189 assert_eq!(cl.insert("heart", ""), Err(Error::InvalidAscii));
190 assert_eq!(cl.insert("", "love"), Err(Error::InvalidAscii));
191 assert_eq!(cl.as_str(), "");
192 }
193
194 #[test]
insert_string()195 fn insert_string() {
196 let mut cl = Cmdline::new(13);
197 assert_eq!(cl.as_str(), "");
198 assert!(cl.insert_str("noapic").is_ok());
199 assert_eq!(cl.as_str(), "noapic");
200 assert!(cl.insert_str("nopci").is_ok());
201 assert_eq!(cl.as_str(), "noapic nopci");
202 }
203
204 #[test]
insert_too_large()205 fn insert_too_large() {
206 let mut cl = Cmdline::new(4);
207 assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
208 assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
209 assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
210 assert!(cl.insert("a", "b").is_ok());
211 assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
212 assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
213 assert_eq!(cl.as_str(), "a=b");
214
215 let mut cl = Cmdline::new(10);
216 assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
217 assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
218 assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
219 }
220 }
221