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
+
1
6
use std:: borrow:: Cow ;
2
7
3
8
use anyhow:: Result ;
@@ -7,19 +12,35 @@ pub(crate) const INITRD_ARG_PREFIX: &[u8] = b"rd.";
7
12
/// The kernel argument for configuring the rootfs flags.
8
13
pub ( crate ) const ROOTFLAGS : & [ u8 ] = b"rootflags" ;
9
14
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.
10
20
pub ( crate ) struct Cmdline < ' a > ( Cow < ' a , [ u8 ] > ) ;
11
21
12
22
impl < ' 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.
13
26
fn from ( input : & ' a T ) -> Self {
14
27
Self ( Cow :: Borrowed ( input. as_ref ( ) ) )
15
28
}
16
29
}
17
30
18
31
impl < ' 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.
19
35
pub fn from_proc ( ) -> Result < Self > {
20
36
Ok ( Self ( Cow :: Owned ( std:: fs:: read ( "/proc/cmdline" ) ?) ) )
21
37
}
22
38
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.
23
44
pub fn iter ( & ' a self ) -> impl Iterator < Item = Parameter < ' a > > {
24
45
let mut in_quotes = false ;
25
46
@@ -32,36 +53,99 @@ impl<'a> Cmdline<'a> {
32
53
} )
33
54
. map ( Parameter :: from)
34
55
}
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
+ }
35
65
}
36
66
67
+ /// A single kernel command line parameter key
68
+ ///
69
+ /// Handles quoted values and treats dashes and underscores in keys as equivalent.
37
70
#[ 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 ) ]
38
89
pub ( 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
40
93
pub value : Option < & ' a [ u8 ] > ,
41
94
}
42
95
43
96
impl < ' 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.
44
118
pub fn key_lossy ( & self ) -> String {
45
- String :: from_utf8_lossy ( self . key ) . to_string ( )
119
+ String :: from_utf8_lossy ( & self . key ) . to_string ( )
46
120
}
47
121
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.
48
126
pub fn value_lossy ( & self ) -> String {
49
127
String :: from_utf8_lossy ( self . value . unwrap_or ( & [ ] ) ) . to_string ( )
50
128
}
51
129
}
52
130
53
131
impl < ' 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.
54
137
fn from ( input : & ' a T ) -> Self {
55
138
let input = input. as_ref ( ) ;
56
139
let equals = input. iter ( ) . position ( |b| * b == b'=' ) ;
57
140
58
141
match equals {
59
142
None => Self {
60
- key : input,
143
+ key : ParameterKey ( input) ,
61
144
value : None ,
62
145
} ,
63
146
Some ( i) => {
64
147
let ( key, mut value) = input. split_at ( i) ;
148
+ let key = ParameterKey ( key) ;
65
149
66
150
// skip `=`, we know it's the first byte because we
67
151
// found it above
@@ -83,7 +167,11 @@ impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Parameter<'a> {
83
167
}
84
168
}
85
169
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.
87
175
fn eq ( & self , other : & Self ) -> bool {
88
176
let dedashed = |& c: & u8 | {
89
177
if c == b'-' {
@@ -97,21 +185,18 @@ impl PartialEq for Parameter<'_> {
97
185
//
98
186
// For example, "foo" == "foobar" since the zipped iterator
99
187
// 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)
111
191
}
112
192
}
113
193
114
194
impl 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.
115
200
fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
116
201
let key = self . key_lossy ( ) ;
117
202
@@ -136,11 +221,11 @@ mod tests {
136
221
#[ test]
137
222
fn test_parameter_simple ( ) {
138
223
let switch = Parameter :: from ( "foo" ) ;
139
- assert_eq ! ( switch. key, b"foo" ) ;
224
+ assert_eq ! ( switch. key. 0 , b"foo" ) ;
140
225
assert_eq ! ( switch. value, None ) ;
141
226
142
227
let kv = Parameter :: from ( "bar=baz" ) ;
143
- assert_eq ! ( kv. key, b"bar" ) ;
228
+ assert_eq ! ( kv. key. 0 , b"bar" ) ;
144
229
assert_eq ! ( kv. value, Some ( b"baz" . as_slice( ) ) ) ;
145
230
}
146
231
@@ -156,7 +241,7 @@ mod tests {
156
241
157
242
// quotes don't get removed from keys
158
243
let p = Parameter :: from ( "\" \" \" " ) ;
159
- assert_eq ! ( p. key, b"\" \" \" " ) ;
244
+ assert_eq ! ( p. key. 0 , b"\" \" \" " ) ;
160
245
161
246
// quotes only get stripped from the absolute ends of values
162
247
let p = Parameter :: from ( "foo=\" internal \" quotes \" are ok\" " ) ;
@@ -212,31 +297,19 @@ mod tests {
212
297
let kargs = Cmdline :: from ( b"foo=bar,bar2 baz=fuz wiz" . as_slice ( ) ) ;
213
298
let mut iter = kargs. iter ( ) ;
214
299
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" ) ) ) ;
230
301
231
302
assert_eq ! (
232
303
iter. next( ) ,
233
- Some ( Parameter {
234
- key: b"wiz" ,
235
- value: None ,
236
- } )
304
+ Some ( Parameter :: new_kv( b"baz" , b"fuz" . as_slice( ) ) )
237
305
) ;
238
306
307
+ assert_eq ! ( iter. next( ) , Some ( Parameter :: new_key( b"wiz" ) ) ) ;
239
308
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( ) ) ;
240
313
}
241
314
242
315
#[ test]
@@ -248,4 +321,25 @@ mod tests {
248
321
// tests are running
249
322
assert ! ( kargs. iter( ) . count( ) > 0 ) ;
250
323
}
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
+ }
251
345
}
0 commit comments