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