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