• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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