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,99 @@ 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 > ( & ' a [ u8 ] ) ;
72+
73+ impl < ' a > std:: ops:: Deref for ParameterKey < ' a > {
74+ type Target = [ u8 ] ;
75+
76+ fn deref ( & self ) -> & ' a Self :: Target {
77+ self . 0
78+ }
79+ }
80+
81+ impl < ' a > From < & ' a [ u8 ] > for ParameterKey < ' a > {
82+ fn from ( value : & ' a [ u8 ] ) -> Self {
83+ Self ( value)
84+ }
85+ }
86+
87+ /// A single kernel command line parameter.
88+ #[ derive( Debug , PartialEq , Eq ) ]
3889pub ( crate ) struct Parameter < ' a > {
39- pub key : & ' a [ u8 ] ,
90+ /// The parameter key as raw bytes
91+ pub key : ParameterKey < ' a > ,
92+ /// The parameter value as raw bytes, if present
4093 pub value : Option < & ' a [ u8 ] > ,
4194}
4295
4396impl < ' a > Parameter < ' a > {
97+ /// Create a new parameter with the provided key and value.
98+ #[ cfg( test) ]
99+ pub fn new_kv < ' k : ' a , ' v : ' a > ( key : & ' k [ u8 ] , value : & ' v [ u8 ] ) -> Self {
100+ Self {
101+ key : ParameterKey ( key) ,
102+ value : Some ( value) ,
103+ }
104+ }
105+
106+ /// Create a new parameter with the provided key.
107+ #[ cfg( test) ]
108+ pub fn new_key ( key : & ' a [ u8 ] ) -> Self {
109+ Self {
110+ key : ParameterKey ( key) ,
111+ value : None ,
112+ }
113+ }
114+
115+ /// Returns the key as a lossy UTF-8 string.
116+ ///
117+ /// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
44118 pub fn key_lossy ( & self ) -> String {
45- String :: from_utf8_lossy ( self . key ) . to_string ( )
119+ String :: from_utf8_lossy ( & self . key ) . to_string ( )
46120 }
47121
122+ /// Returns the value as a lossy UTF-8 string.
123+ ///
124+ /// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
125+ /// Returns an empty string if no value is present.
48126 pub fn value_lossy ( & self ) -> String {
49127 String :: from_utf8_lossy ( self . value . unwrap_or ( & [ ] ) ) . to_string ( )
50128 }
51129}
52130
53131impl < ' a , T : AsRef < [ u8 ] > + ?Sized > From < & ' a T > for Parameter < ' a > {
132+ /// Parses a parameter from raw bytes.
133+ ///
134+ /// Splits on the first `=` character to separate key and value.
135+ /// Strips only the outermost pair of double quotes from values.
136+ /// If no `=` is found, treats the entire input as a key-only parameter.
54137 fn from ( input : & ' a T ) -> Self {
55138 let input = input. as_ref ( ) ;
56139 let equals = input. iter ( ) . position ( |b| * b == b'=' ) ;
57140
58141 match equals {
59142 None => Self {
60- key : input,
143+ key : ParameterKey ( input) ,
61144 value : None ,
62145 } ,
63146 Some ( i) => {
64147 let ( key, mut value) = input. split_at ( i) ;
148+ let key = ParameterKey ( key) ;
65149
66150 // skip `=`, we know it's the first byte because we
67151 // found it above
@@ -83,7 +167,11 @@ impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Parameter<'a> {
83167 }
84168}
85169
86- impl PartialEq for Parameter < ' _ > {
170+ impl PartialEq for ParameterKey < ' _ > {
171+ /// Compares two parameter keys for equality.
172+ ///
173+ /// Keys are compared with dashes and underscores treated as equivalent.
174+ /// This comparison is case-sensitive.
87175 fn eq ( & self , other : & Self ) -> bool {
88176 let dedashed = |& c: & u8 | {
89177 if c == b'-' {
@@ -97,21 +185,18 @@ impl PartialEq for Parameter<'_> {
97185 //
98186 // For example, "foo" == "foobar" since the zipped iterator
99187 // 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- }
188+ let our_iter = self . 0 . iter ( ) . map ( dedashed) ;
189+ let other_iter = other. 0 . iter ( ) . map ( dedashed) ;
190+ our_iter. eq ( other_iter)
111191 }
112192}
113193
114194impl std:: fmt:: Display for Parameter < ' _ > {
195+ /// Formats the parameter for display.
196+ ///
197+ /// Key-only parameters are displayed as just the key.
198+ /// Key-value parameters are displayed as `key=value`.
199+ /// Values containing whitespace are automatically quoted.
115200 fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
116201 let key = self . key_lossy ( ) ;
117202
@@ -136,11 +221,11 @@ mod tests {
136221 #[ test]
137222 fn test_parameter_simple ( ) {
138223 let switch = Parameter :: from ( "foo" ) ;
139- assert_eq ! ( switch. key, b"foo" ) ;
224+ assert_eq ! ( switch. key. 0 , b"foo" ) ;
140225 assert_eq ! ( switch. value, None ) ;
141226
142227 let kv = Parameter :: from ( "bar=baz" ) ;
143- assert_eq ! ( kv. key, b"bar" ) ;
228+ assert_eq ! ( kv. key. 0 , b"bar" ) ;
144229 assert_eq ! ( kv. value, Some ( b"baz" . as_slice( ) ) ) ;
145230 }
146231
@@ -156,7 +241,7 @@ mod tests {
156241
157242 // quotes don't get removed from keys
158243 let p = Parameter :: from ( "\" \" \" " ) ;
159- assert_eq ! ( p. key, b"\" \" \" " ) ;
244+ assert_eq ! ( p. key. 0 , b"\" \" \" " ) ;
160245
161246 // quotes only get stripped from the absolute ends of values
162247 let p = Parameter :: from ( "foo=\" internal \" quotes \" are ok\" " ) ;
@@ -212,31 +297,19 @@ mod tests {
212297 let kargs = Cmdline :: from ( b"foo=bar,bar2 baz=fuz wiz" . as_slice ( ) ) ;
213298 let mut iter = kargs. iter ( ) ;
214299
215- assert_eq ! (
216- iter. next( ) ,
217- Some ( Parameter {
218- key: b"foo" ,
219- value: Some ( b"bar,bar2" . as_slice( ) )
220- } )
221- ) ;
222-
223- assert_eq ! (
224- iter. next( ) ,
225- Some ( Parameter {
226- key: b"baz" ,
227- value: Some ( b"fuz" . as_slice( ) )
228- } )
229- ) ;
300+ assert_eq ! ( iter. next( ) , Some ( Parameter :: new_kv( b"foo" , b"bar,bar2" ) ) ) ;
230301
231302 assert_eq ! (
232303 iter. next( ) ,
233- Some ( Parameter {
234- key: b"wiz" ,
235- value: None ,
236- } )
304+ Some ( Parameter :: new_kv( b"baz" , b"fuz" . as_slice( ) ) )
237305 ) ;
238306
307+ assert_eq ! ( iter. next( ) , Some ( Parameter :: new_key( b"wiz" ) ) ) ;
239308 assert_eq ! ( iter. next( ) , None ) ;
309+
310+ // Test the find API
311+ assert_eq ! ( kargs. find( "foo" ) . unwrap( ) . value. unwrap( ) , b"bar,bar2" ) ;
312+ assert ! ( kargs. find( "nothing" ) . is_none( ) ) ;
240313 }
241314
242315 #[ test]
@@ -248,4 +321,25 @@ mod tests {
248321 // tests are running
249322 assert ! ( kargs. iter( ) . count( ) > 0 ) ;
250323 }
324+
325+ #[ test]
326+ fn test_kargs_find_dash_hyphen ( ) {
327+ let kargs = Cmdline :: from ( b"a-b=1 a_b=2" . as_slice ( ) ) ;
328+ // find should find the first one, which is a-b=1
329+ let p = kargs. find ( "a_b" ) . unwrap ( ) ;
330+ assert_eq ! ( p. key. 0 , b"a-b" ) ;
331+ assert_eq ! ( p. value. unwrap( ) , b"1" ) ;
332+ let p = kargs. find ( "a-b" ) . unwrap ( ) ;
333+ assert_eq ! ( p. key. 0 , b"a-b" ) ;
334+ assert_eq ! ( p. value. unwrap( ) , b"1" ) ;
335+
336+ let kargs = Cmdline :: from ( b"a_b=2 a-b=1" . as_slice ( ) ) ;
337+ // find should find the first one, which is a_b=2
338+ let p = kargs. find ( "a_b" ) . unwrap ( ) ;
339+ assert_eq ! ( p. key. 0 , b"a_b" ) ;
340+ assert_eq ! ( p. value. unwrap( ) , b"2" ) ;
341+ let p = kargs. find ( "a-b" ) . unwrap ( ) ;
342+ assert_eq ! ( p. key. 0 , b"a_b" ) ;
343+ assert_eq ! ( p. value. unwrap( ) , b"2" ) ;
344+ }
251345}
0 commit comments