1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
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::fmt::{self, Display};
8 use std::result;
9
10 /// The error type for command line building operations.
11 #[derive(PartialEq, Debug)]
12 pub enum Error {
13 /// Operation would have resulted in a non-printable ASCII character.
14 InvalidAscii,
15 /// Key/Value Operation would have had a space in it.
16 HasSpace,
17 /// Key/Value Operation would have had an equals sign in it.
18 HasEquals,
19 /// Operation would have made the command line too large.
20 TooLarge,
21 }
22
23 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 use self::Error::*;
26
27 let description = match self {
28 InvalidAscii => "string contains non-printable ASCII character",
29 HasSpace => "string contains a space",
30 HasEquals => "string contains an equals sign",
31 TooLarge => "inserting string would make command line too long",
32 };
33
34 write!(f, "{}", description)
35 }
36 }
37
38 /// Specialized Result type for command line operations.
39 pub type Result<T> = result::Result<T, Error>;
40
valid_char(c: char) -> bool41 fn valid_char(c: char) -> bool {
42 matches!(c, ' '..='~')
43 }
44
valid_str(s: &str) -> Result<()>45 fn valid_str(s: &str) -> Result<()> {
46 if s.chars().all(valid_char) {
47 Ok(())
48 } else {
49 Err(Error::InvalidAscii)
50 }
51 }
52
valid_element(s: &str) -> Result<()>53 fn valid_element(s: &str) -> Result<()> {
54 if !s.chars().all(valid_char) {
55 Err(Error::InvalidAscii)
56 } else if s.contains(' ') {
57 Err(Error::HasSpace)
58 } else if s.contains('=') {
59 Err(Error::HasEquals)
60 } else {
61 Ok(())
62 }
63 }
64
65 /// A builder for a kernel command line string that validates the string as its being built. A
66 /// `CString` can be constructed from this directly using `CString::new`.
67 pub struct Cmdline {
68 line: String,
69 capacity: usize,
70 }
71
72 impl Cmdline {
73 /// Constructs an empty Cmdline with the given capacity, which includes the nul terminator.
74 /// Capacity must be greater than 0.
new(capacity: usize) -> Cmdline75 pub fn new(capacity: usize) -> Cmdline {
76 assert_ne!(capacity, 0);
77 Cmdline {
78 line: String::new(),
79 capacity,
80 }
81 }
82
has_capacity(&self, more: usize) -> Result<()>83 fn has_capacity(&self, more: usize) -> Result<()> {
84 let needs_space = if self.line.is_empty() { 0 } else { 1 };
85 if self.line.len() + more + needs_space < self.capacity {
86 Ok(())
87 } else {
88 Err(Error::TooLarge)
89 }
90 }
91
start_push(&mut self)92 fn start_push(&mut self) {
93 if !self.line.is_empty() {
94 self.line.push(' ');
95 }
96 }
97
end_push(&mut self)98 fn end_push(&mut self) {
99 // This assert is always true because of the `has_capacity` check that each insert method
100 // uses.
101 assert!(self.line.len() < self.capacity);
102 }
103
104 /// Validates and inserts a key value pair into this command line
insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()>105 pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
106 let k = key.as_ref();
107 let v = val.as_ref();
108
109 valid_element(k)?;
110 valid_element(v)?;
111 self.has_capacity(k.len() + v.len() + 1)?;
112
113 self.start_push();
114 self.line.push_str(k);
115 self.line.push('=');
116 self.line.push_str(v);
117 self.end_push();
118
119 Ok(())
120 }
121
122 /// Validates and inserts a string to the end of the current command line
insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()>123 pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
124 let s = slug.as_ref();
125 valid_str(s)?;
126
127 self.has_capacity(s.len())?;
128
129 self.start_push();
130 self.line.push_str(s);
131 self.end_push();
132
133 Ok(())
134 }
135
136 /// Returns the cmdline in progress without nul termination
as_str(&self) -> &str137 pub fn as_str(&self) -> &str {
138 self.line.as_str()
139 }
140 }
141
142 impl Into<Vec<u8>> for Cmdline {
into(self) -> Vec<u8>143 fn into(self) -> Vec<u8> {
144 self.line.into_bytes()
145 }
146 }
147
148 #[cfg(test)]
149 mod tests {
150 use super::*;
151 use std::ffi::CString;
152
153 #[test]
insert_hello_world()154 fn insert_hello_world() {
155 let mut cl = Cmdline::new(100);
156 assert_eq!(cl.as_str(), "");
157 assert!(cl.insert("hello", "world").is_ok());
158 assert_eq!(cl.as_str(), "hello=world");
159
160 let s = CString::new(cl).expect("failed to create CString from Cmdline");
161 assert_eq!(s, CString::new("hello=world").unwrap());
162 }
163
164 #[test]
insert_multi()165 fn insert_multi() {
166 let mut cl = Cmdline::new(100);
167 assert!(cl.insert("hello", "world").is_ok());
168 assert!(cl.insert("foo", "bar").is_ok());
169 assert_eq!(cl.as_str(), "hello=world foo=bar");
170 }
171
172 #[test]
insert_space()173 fn insert_space() {
174 let mut cl = Cmdline::new(100);
175 assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
176 assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
177 assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
178 assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
179 assert_eq!(cl.as_str(), "");
180 }
181
182 #[test]
insert_equals()183 fn insert_equals() {
184 let mut cl = Cmdline::new(100);
185 assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
186 assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
187 assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
188 assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
189 assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
190 assert_eq!(cl.as_str(), "");
191 }
192
193 #[test]
insert_emoji()194 fn insert_emoji() {
195 let mut cl = Cmdline::new(100);
196 assert_eq!(cl.insert("heart", ""), Err(Error::InvalidAscii));
197 assert_eq!(cl.insert("", "love"), Err(Error::InvalidAscii));
198 assert_eq!(cl.as_str(), "");
199 }
200
201 #[test]
insert_string()202 fn insert_string() {
203 let mut cl = Cmdline::new(13);
204 assert_eq!(cl.as_str(), "");
205 assert!(cl.insert_str("noapic").is_ok());
206 assert_eq!(cl.as_str(), "noapic");
207 assert!(cl.insert_str("nopci").is_ok());
208 assert_eq!(cl.as_str(), "noapic nopci");
209 }
210
211 #[test]
insert_too_large()212 fn insert_too_large() {
213 let mut cl = Cmdline::new(4);
214 assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
215 assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
216 assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
217 assert!(cl.insert("a", "b").is_ok());
218 assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
219 assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
220 assert_eq!(cl.as_str(), "a=b");
221
222 let mut cl = Cmdline::new(10);
223 assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
224 assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
225 assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
226 }
227 }
228