1+ //! Kernel command line parsing utilities.
2+ //!
3+ //! This module provides functionality for parsing and working with kernel command line
4+ //! arguments, supporting both key-only switches and key-value pairs with proper quote handling.
5+
16use std:: borrow:: Cow ;
27
38use anyhow:: Result ;
@@ -7,19 +12,35 @@ pub(crate) const INITRD_ARG_PREFIX: &[u8] = b"rd.";
712/// The kernel argument for configuring the rootfs flags.
813pub ( crate ) const ROOTFLAGS : & [ u8 ] = b"rootflags" ;
914
15+ /// A parsed kernel command line.
16+ ///
17+ /// Wraps the raw command line bytes and provides methods for parsing and iterating
18+ /// over individual parameters. Uses copy-on-write semantics to avoid unnecessary
19+ /// allocations when working with borrowed data.
1020pub ( crate ) struct Cmdline < ' a > ( Cow < ' a , [ u8 ] > ) ;
1121
1222impl < ' a , T : AsRef < [ u8 ] > + ?Sized > From < & ' a T > for Cmdline < ' a > {
23+ /// Creates a new `Cmdline` from any type that can be referenced as bytes.
24+ ///
25+ /// Uses borrowed data when possible to avoid unnecessary allocations.
1326 fn from ( input : & ' a T ) -> Self {
1427 Self ( Cow :: Borrowed ( input. as_ref ( ) ) )
1528 }
1629}
1730
1831impl < ' a > Cmdline < ' a > {
32+ /// Reads the kernel command line from `/proc/cmdline`.
33+ ///
34+ /// Returns an error if the file cannot be read or if there are I/O issues.
1935 pub fn from_proc ( ) -> Result < Self > {
2036 Ok ( Self ( Cow :: Owned ( std:: fs:: read ( "/proc/cmdline" ) ?) ) )
2137 }
2238
39+ /// Returns an iterator over all parameters in the command line.
40+ ///
41+ /// Properly handles quoted values containing whitespace and splits on
42+ /// unquoted whitespace characters. Parameters are parsed as either
43+ /// key-only switches or key=value pairs.
2344 pub fn iter ( & ' a self ) -> impl Iterator < Item = Parameter < ' a > > {
2445 let mut in_quotes = false ;
2546
@@ -32,36 +53,91 @@ impl<'a> Cmdline<'a> {
3253 } )
3354 . map ( Parameter :: from)
3455 }
56+
57+ /// Locate a kernel argument with the given key name.
58+ ///
59+ /// Returns the first parameter matching the given key, or `None` if not found.
60+ /// Key comparison treats dashes and underscores as equivalent.
61+ pub fn find ( & ' a self , key : impl AsRef < [ u8 ] > ) -> Option < Parameter < ' a > > {
62+ let key = ParameterKey ( key. as_ref ( ) ) ;
63+ self . iter ( ) . find ( |p| p. key == key)
64+ }
3565}
3666
67+ /// A single kernel command line parameter key
68+ ///
69+ /// Handles quoted values and treats dashes and underscores in keys as equivalent.
3770#[ derive( Debug , Eq ) ]
71+ pub ( crate ) struct ParameterKey < ' a > ( pub & ' a [ u8 ] ) ;
72+
73+ impl < ' a > From < & ' a [ u8 ] > for ParameterKey < ' a > {
74+ fn from ( value : & ' a [ u8 ] ) -> Self {
75+ Self ( value)
76+ }
77+ }
78+
79+ /// A single kernel command line parameter.
80+ #[ derive( Debug , PartialEq , Eq ) ]
3881pub ( crate ) struct Parameter < ' a > {
39- pub key : & ' a [ u8 ] ,
82+ /// The parameter key as raw bytes
83+ pub key : ParameterKey < ' a > ,
84+ /// The parameter value as raw bytes, if present
4085 pub value : Option < & ' a [ u8 ] > ,
4186}
4287
4388impl < ' a > Parameter < ' a > {
89+ /// Create a new parameter with the provided key and value.
90+ #[ cfg( test) ]
91+ pub fn new_kv < ' k : ' a , ' v : ' a > ( key : & ' k [ u8 ] , value : & ' v [ u8 ] ) -> Self {
92+ Self {
93+ key : ParameterKey ( key) ,
94+ value : Some ( value) ,
95+ }
96+ }
97+
98+ /// Create a new parameter with the provided key.
99+ #[ cfg( test) ]
100+ pub fn new_key ( key : & ' a [ u8 ] ) -> Self {
101+ Self {
102+ key : ParameterKey ( key) ,
103+ value : None ,
104+ }
105+ }
106+
107+ /// Returns the key as a lossy UTF-8 string.
108+ ///
109+ /// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
44110 pub fn key_lossy ( & self ) -> String {
45- String :: from_utf8_lossy ( self . key ) . to_string ( )
111+ String :: from_utf8_lossy ( self . key . 0 ) . to_string ( )
46112 }
47113
114+ /// Returns the value as a lossy UTF-8 string.
115+ ///
116+ /// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
117+ /// Returns an empty string if no value is present.
48118 pub fn value_lossy ( & self ) -> String {
49119 String :: from_utf8_lossy ( self . value . unwrap_or ( & [ ] ) ) . to_string ( )
50120 }
51121}
52122
53123impl < ' a , T : AsRef < [ u8 ] > + ?Sized > From < & ' a T > for Parameter < ' a > {
124+ /// Parses a parameter from raw bytes.
125+ ///
126+ /// Splits on the first `=` character to separate key and value.
127+ /// Strips only the outermost pair of double quotes from values.
128+ /// If no `=` is found, treats the entire input as a key-only parameter.
54129 fn from ( input : & ' a T ) -> Self {
55130 let input = input. as_ref ( ) ;
56131 let equals = input. iter ( ) . position ( |b| * b == b'=' ) ;
57132
58133 match equals {
59134 None => Self {
60- key : input,
135+ key : ParameterKey ( input) ,
61136 value : None ,
62137 } ,
63138 Some ( i) => {
64139 let ( key, mut value) = input. split_at ( i) ;
140+ let key = ParameterKey ( key) ;
65141
66142 // skip `=`, we know it's the first byte because we
67143 // found it above
@@ -83,7 +159,11 @@ impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Parameter<'a> {
83159 }
84160}
85161
86- impl PartialEq for Parameter < ' _ > {
162+ impl PartialEq for ParameterKey < ' _ > {
163+ /// Compares two parameter keys for equality.
164+ ///
165+ /// Keys are compared with dashes and underscores treated as equivalent.
166+ /// This comparison is case-sensitive.
87167 fn eq ( & self , other : & Self ) -> bool {
88168 let dedashed = |& c: & u8 | {
89169 if c == b'-' {
@@ -97,21 +177,18 @@ impl PartialEq for Parameter<'_> {
97177 //
98178 // For example, "foo" == "foobar" since the zipped iterator
99179 // only compares the first three chars.
100- let our_iter = self . key . iter ( ) . map ( dedashed) ;
101- let other_iter = other. key . iter ( ) . map ( dedashed) ;
102- if !our_iter. eq ( other_iter) {
103- return false ;
104- }
105-
106- match ( self . value , other. value ) {
107- ( Some ( ours) , Some ( other) ) => ours == other,
108- ( None , None ) => true ,
109- _ => false ,
110- }
180+ let our_iter = self . 0 . iter ( ) . map ( dedashed) ;
181+ let other_iter = other. 0 . iter ( ) . map ( dedashed) ;
182+ our_iter. eq ( other_iter)
111183 }
112184}
113185
114186impl std:: fmt:: Display for Parameter < ' _ > {
187+ /// Formats the parameter for display.
188+ ///
189+ /// Key-only parameters are displayed as just the key.
190+ /// Key-value parameters are displayed as `key=value`.
191+ /// Values containing whitespace are automatically quoted.
115192 fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
116193 let key = self . key_lossy ( ) ;
117194
@@ -136,11 +213,11 @@ mod tests {
136213 #[ test]
137214 fn test_parameter_simple ( ) {
138215 let switch = Parameter :: from ( "foo" ) ;
139- assert_eq ! ( switch. key, b"foo" ) ;
216+ assert_eq ! ( switch. key. 0 , b"foo" ) ;
140217 assert_eq ! ( switch. value, None ) ;
141218
142219 let kv = Parameter :: from ( "bar=baz" ) ;
143- assert_eq ! ( kv. key, b"bar" ) ;
220+ assert_eq ! ( kv. key. 0 , b"bar" ) ;
144221 assert_eq ! ( kv. value, Some ( b"baz" . as_slice( ) ) ) ;
145222 }
146223
@@ -156,7 +233,7 @@ mod tests {
156233
157234 // quotes don't get removed from keys
158235 let p = Parameter :: from ( "\" \" \" " ) ;
159- assert_eq ! ( p. key, b"\" \" \" " ) ;
236+ assert_eq ! ( p. key. 0 , b"\" \" \" " ) ;
160237
161238 // quotes only get stripped from the absolute ends of values
162239 let p = Parameter :: from ( "foo=\" internal \" quotes \" are ok\" " ) ;
@@ -212,31 +289,19 @@ mod tests {
212289 let kargs = Cmdline :: from ( b"foo=bar,bar2 baz=fuz wiz" . as_slice ( ) ) ;
213290 let mut iter = kargs. iter ( ) ;
214291
215- assert_eq ! (
216- iter. next( ) ,
217- Some ( Parameter {
218- key: b"foo" ,
219- value: Some ( b"bar,bar2" . as_slice( ) )
220- } )
221- ) ;
292+ assert_eq ! ( iter. next( ) , Some ( Parameter :: new_kv( b"foo" , b"bar,bar2" ) ) ) ;
222293
223294 assert_eq ! (
224295 iter. next( ) ,
225- Some ( Parameter {
226- key: b"baz" ,
227- value: Some ( b"fuz" . as_slice( ) )
228- } )
229- ) ;
230-
231- assert_eq ! (
232- iter. next( ) ,
233- Some ( Parameter {
234- key: b"wiz" ,
235- value: None ,
236- } )
296+ Some ( Parameter :: new_kv( b"baz" , b"fuz" . as_slice( ) ) )
237297 ) ;
238298
299+ assert_eq ! ( iter. next( ) , Some ( Parameter :: new_key( b"wiz" ) ) ) ;
239300 assert_eq ! ( iter. next( ) , None ) ;
301+
302+ // Test the find API
303+ assert_eq ! ( kargs. find( "foo" ) . unwrap( ) . value. unwrap( ) , b"bar,bar2" ) ;
304+ assert ! ( kargs. find( "nothing" ) . is_none( ) ) ;
240305 }
241306
242307 #[ test]
@@ -248,4 +313,25 @@ mod tests {
248313 // tests are running
249314 assert ! ( kargs. iter( ) . count( ) > 0 ) ;
250315 }
316+
317+ #[ test]
318+ fn test_kargs_find_dash_hyphen ( ) {
319+ let kargs = Cmdline :: from ( b"a-b=1 a_b=2" . as_slice ( ) ) ;
320+ // find should find the first one, which is a-b=1
321+ let p = kargs. find ( "a_b" ) . unwrap ( ) ;
322+ assert_eq ! ( p. key. 0 , b"a-b" ) ;
323+ assert_eq ! ( p. value. unwrap( ) , b"1" ) ;
324+ let p = kargs. find ( "a-b" ) . unwrap ( ) ;
325+ assert_eq ! ( p. key. 0 , b"a-b" ) ;
326+ assert_eq ! ( p. value. unwrap( ) , b"1" ) ;
327+
328+ let kargs = Cmdline :: from ( b"a_b=2 a-b=1" . as_slice ( ) ) ;
329+ // find should find the first one, which is a_b=2
330+ let p = kargs. find ( "a_b" ) . unwrap ( ) ;
331+ assert_eq ! ( p. key. 0 , b"a_b" ) ;
332+ assert_eq ! ( p. value. unwrap( ) , b"2" ) ;
333+ let p = kargs. find ( "a-b" ) . unwrap ( ) ;
334+ assert_eq ! ( p. key. 0 , b"a_b" ) ;
335+ assert_eq ! ( p. value. unwrap( ) , b"2" ) ;
336+ }
251337}
0 commit comments