kernel_cmdline/
kernel_cmdline.rs

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
7use std::result;
8
9use remain::sorted;
10use thiserror::Error;
11
12/// The error type for command line building operations.
13#[sorted]
14#[derive(Error, PartialEq, Eq, Debug)]
15pub 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("command line length {0} exceeds maximum {1}")]
27    TooLarge(usize, usize),
28}
29
30/// Specialized Result type for command line operations.
31pub type Result<T> = result::Result<T, Error>;
32
33fn valid_char(c: char) -> bool {
34    matches!(c, ' '..='~')
35}
36
37fn valid_str(s: &str) -> Result<()> {
38    if s.chars().all(valid_char) {
39        Ok(())
40    } else {
41        Err(Error::InvalidAscii)
42    }
43}
44
45fn 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 it is built.
58#[derive(Default)]
59pub struct Cmdline {
60    line: String,
61}
62
63impl Cmdline {
64    /// Constructs an empty Cmdline.
65    pub fn new() -> Cmdline {
66        Cmdline::default()
67    }
68
69    fn push_space_if_needed(&mut self) {
70        if !self.line.is_empty() {
71            self.line.push(' ');
72        }
73    }
74
75    /// Validates and inserts a key value pair into this command line
76    pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
77        let k = key.as_ref();
78        let v = val.as_ref();
79
80        valid_element(k)?;
81        valid_element(v)?;
82
83        self.push_space_if_needed();
84        self.line.push_str(k);
85        self.line.push('=');
86        self.line.push_str(v);
87
88        Ok(())
89    }
90
91    /// Validates and inserts a string to the end of the current command line
92    pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
93        let s = slug.as_ref();
94        valid_str(s)?;
95
96        self.push_space_if_needed();
97        self.line.push_str(s);
98
99        Ok(())
100    }
101
102    /// Returns the cmdline in progress without nul termination
103    pub fn as_str(&self) -> &str {
104        self.line.as_str()
105    }
106
107    /// Returns the current command line as a string with a maximum length.
108    ///
109    /// # Arguments
110    ///
111    /// `max_len`: maximum number of bytes (not including NUL terminator)
112    pub fn as_str_with_max_len(&self, max_len: usize) -> Result<&str> {
113        let s = self.line.as_str();
114        if s.len() <= max_len {
115            Ok(s)
116        } else {
117            Err(Error::TooLarge(s.len(), max_len))
118        }
119    }
120
121    /// Converts the command line into a `Vec<u8>` with a maximum length.
122    ///
123    /// # Arguments
124    ///
125    /// `max_len`: maximum number of bytes (not including NUL terminator)
126    pub fn into_bytes_with_max_len(self, max_len: usize) -> Result<Vec<u8>> {
127        let bytes: Vec<u8> = self.line.into_bytes();
128        if bytes.len() <= max_len {
129            Ok(bytes)
130        } else {
131            Err(Error::TooLarge(bytes.len(), max_len))
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn insert_hello_world() {
142        let mut cl = Cmdline::new();
143        assert_eq!(cl.as_str(), "");
144        assert!(cl.insert("hello", "world").is_ok());
145        assert_eq!(cl.as_str(), "hello=world");
146
147        let bytes = cl
148            .into_bytes_with_max_len(100)
149            .expect("failed to convert Cmdline into bytes");
150        assert_eq!(bytes, b"hello=world");
151    }
152
153    #[test]
154    fn insert_multi() {
155        let mut cl = Cmdline::new();
156        assert!(cl.insert("hello", "world").is_ok());
157        assert!(cl.insert("foo", "bar").is_ok());
158        assert_eq!(cl.as_str(), "hello=world foo=bar");
159    }
160
161    #[test]
162    fn insert_space() {
163        let mut cl = Cmdline::new();
164        assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
165        assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
166        assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
167        assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
168        assert_eq!(cl.as_str(), "");
169    }
170
171    #[test]
172    fn insert_equals() {
173        let mut cl = Cmdline::new();
174        assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
175        assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
176        assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
177        assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
178        assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
179        assert_eq!(cl.as_str(), "");
180    }
181
182    #[test]
183    fn insert_emoji() {
184        let mut cl = Cmdline::new();
185        assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
186        assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
187        assert_eq!(cl.as_str(), "");
188    }
189
190    #[test]
191    fn insert_string() {
192        let mut cl = Cmdline::new();
193        assert_eq!(cl.as_str(), "");
194        assert!(cl.insert_str("noapic").is_ok());
195        assert_eq!(cl.as_str(), "noapic");
196        assert!(cl.insert_str("nopci").is_ok());
197        assert_eq!(cl.as_str(), "noapic nopci");
198    }
199
200    #[test]
201    fn as_str_too_large() {
202        let mut cl = Cmdline::new();
203        assert!(cl.insert("a", "b").is_ok()); // start off with 3.
204        assert_eq!(cl.as_str(), "a=b");
205        assert_eq!(cl.as_str_with_max_len(2), Err(Error::TooLarge(3, 2)));
206        assert_eq!(cl.as_str_with_max_len(3), Ok("a=b"));
207
208        let mut cl = Cmdline::new();
209        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
210        assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
211        assert_eq!(cl.as_str(), "ab=ba c=d");
212        assert_eq!(cl.as_str_with_max_len(8), Err(Error::TooLarge(9, 8)));
213        assert_eq!(cl.as_str_with_max_len(9), Ok("ab=ba c=d"));
214
215        let mut cl = Cmdline::new();
216        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
217        assert!(cl.insert_str("123").is_ok()); // adds 4 (including space) length
218        assert_eq!(cl.as_str(), "ab=ba 123");
219        assert_eq!(cl.as_str_with_max_len(8), Err(Error::TooLarge(9, 8)));
220        assert_eq!(cl.as_str_with_max_len(9), Ok("ab=ba 123"));
221    }
222}