Skip to content

Commit 67ba584

Browse files
committed
kernel_cmdline: Consolidate parsing and add iter_bytes/iter_str
This commit refactors the kernel_cmdline parsing implementation to reduce code duplication and provide a clearer separation of concerns. Key changes: 1. Introduced CmdlineIterBytes as a dedicated iterator for splitting the command line into raw parameter byte slices. This centralizes the quote-aware whitespace splitting logic in one place. 2. Refactored CmdlineIter to wrap CmdlineIterBytes instead of duplicating the splitting logic. This eliminates redundant code and ensures consistent behavior. 3. Consolidated Parameter::parse and Parameter::parse_one into a single parse() method. The internal parse_internal() method now assumes the input is already a single parameter (having been split by iter_bytes), while parse() handles arbitrary input by using iter_bytes to find the first parameter boundary. 4. Added iter_bytes() and iter_str() methods to Cmdline for cases where users need raw byte slices or strings without full Parameter parsing overhead. 5. Removed utf8::Parameter::parse_one() in favor of a simplified parse() that wraps the bytes implementation. 6. Added to_str_vec() helper for FFI/C API compatibility. All existing tests pass with the new implementation. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: John Eckersberg <[email protected]>
1 parent 0c8b0f7 commit 67ba584

File tree

2 files changed

+172
-112
lines changed

2 files changed

+172
-112
lines changed

crates/kernel_cmdline/src/bytes.rs

Lines changed: 125 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,46 @@ impl<'a> From<Vec<u8>> for Cmdline<'a> {
3838
///
3939
/// This is created by the `iter` method on `Cmdline`.
4040
#[derive(Debug)]
41-
pub struct CmdlineIter<'a>(&'a [u8]);
41+
pub struct CmdlineIter<'a>(CmdlineIterBytes<'a>);
4242

4343
impl<'a> Iterator for CmdlineIter<'a> {
4444
type Item = Parameter<'a>;
4545

4646
fn next(&mut self) -> Option<Self::Item> {
47-
let (param, rest) = Parameter::parse_one(self.0);
47+
self.0.next().and_then(Parameter::parse_internal)
48+
}
49+
}
50+
51+
/// An iterator over kernel command line parameters as byte slices.
52+
///
53+
/// This is created by the `iter_bytes` method on `Cmdline`.
54+
#[derive(Debug)]
55+
pub struct CmdlineIterBytes<'a>(&'a [u8]);
56+
57+
impl<'a> Iterator for CmdlineIterBytes<'a> {
58+
type Item = &'a [u8];
59+
60+
fn next(&mut self) -> Option<Self::Item> {
61+
let input = self.0.trim_ascii_start();
62+
63+
if input.is_empty() {
64+
self.0 = input;
65+
return None;
66+
}
67+
68+
let mut in_quotes = false;
69+
let end = input.iter().position(move |c| {
70+
if *c == b'"' {
71+
in_quotes = !in_quotes;
72+
}
73+
!in_quotes && c.is_ascii_whitespace()
74+
});
75+
76+
let end = end.unwrap_or(input.len());
77+
let (param, rest) = input.split_at(end);
4878
self.0 = rest;
49-
param
79+
80+
Some(param)
5081
}
5182
}
5283

@@ -71,7 +102,15 @@ impl<'a> Cmdline<'a> {
71102
/// unquoted whitespace characters. Parameters are parsed as either
72103
/// key-only switches or key=value pairs.
73104
pub fn iter(&'a self) -> CmdlineIter<'a> {
74-
CmdlineIter(&self.0)
105+
CmdlineIter(self.iter_bytes())
106+
}
107+
108+
/// Returns an iterator over all parameters in the command line as byte slices.
109+
///
110+
/// This is similar to `iter()` but yields `&[u8]` directly instead of `Parameter`,
111+
/// which can be more convenient when you just need the raw byte representation.
112+
pub fn iter_bytes(&self) -> CmdlineIterBytes<'_> {
113+
CmdlineIterBytes(&self.0)
75114
}
76115

77116
/// Returns an iterator over all parameters in the command line
@@ -369,51 +408,39 @@ impl<'a> Parameter<'a> {
369408
/// be constructed from the input. This occurs when the input is
370409
/// either empty or contains only whitespace.
371410
///
372-
/// Any remaining bytes not consumed from the input are discarded.
411+
/// If the input contains multiple parameters, only the first one
412+
/// is parsed and the rest is discarded.
373413
pub fn parse<T: AsRef<[u8]> + ?Sized>(input: &'a T) -> Option<Self> {
374-
Self::parse_one(input).0
375-
}
376-
377-
/// Attempt to parse a single command line parameter from a slice
378-
/// of bytes.
379-
///
380-
/// The first tuple item contains the parsed parameter, or `None`
381-
/// if a Parameter could not be constructed from the input. This
382-
/// occurs when the input is either empty or contains only
383-
/// whitespace.
384-
///
385-
/// Any remaining bytes not consumed from the input are returned
386-
/// as the second tuple item.
387-
pub fn parse_one<T: AsRef<[u8]> + ?Sized>(input: &'a T) -> (Option<Self>, &'a [u8]) {
388414
let input = input.as_ref().trim_ascii_start();
389415

390-
if input.is_empty() {
391-
return (None, input);
392-
}
416+
// Use iter_bytes to determine the length of the first parameter
417+
let temp_cmdline = Cmdline::from(input);
418+
let param_len = temp_cmdline.iter_bytes().next().map(|p| p.len());
393419

394-
let mut in_quotes = false;
395-
let end = input.iter().position(move |c| {
396-
if *c == b'"' {
397-
in_quotes = !in_quotes;
398-
}
399-
!in_quotes && c.is_ascii_whitespace()
400-
});
420+
// Explicitly drop to ensure no lifetime leakage
421+
drop(temp_cmdline);
401422

402-
let end = match end {
403-
Some(end) => end,
404-
None => input.len(),
405-
};
423+
let end = param_len?;
406424

407-
let (input, rest) = input.split_at(end);
425+
// Now split at the determined length from the trimmed input
426+
let param_bytes = &input[..end];
408427

428+
Self::parse_internal(param_bytes)
429+
}
430+
431+
/// Parse a parameter from a byte slice that contains exactly one parameter.
432+
///
433+
/// This is an internal method that assumes the input has already been
434+
/// split into a single parameter (e.g., by CmdlineIterBytes).
435+
fn parse_internal(input: &'a [u8]) -> Option<Self> {
409436
let equals = input.iter().position(|b| *b == b'=');
410437

411-
let ret = match equals {
412-
None => Self {
438+
match equals {
439+
None => Some(Self {
413440
parameter: input,
414441
key: ParameterKey(input),
415442
value: None,
416-
},
443+
}),
417444
Some(i) => {
418445
let (key, mut value) = input.split_at(i);
419446
let key = ParameterKey(key);
@@ -428,15 +455,13 @@ impl<'a> Parameter<'a> {
428455
v.strip_suffix(b"\"").unwrap_or(v)
429456
};
430457

431-
Self {
458+
Some(Self {
432459
parameter: input,
433460
key,
434461
value: Some(value),
435-
}
462+
})
436463
}
437-
};
438-
439-
(Some(ret), rest)
464+
}
440465
}
441466

442467
/// Returns the key part of the parameter
@@ -479,27 +504,19 @@ mod tests {
479504
}
480505

481506
#[test]
482-
fn test_parameter_parse_one() {
483-
let (p, rest) = Parameter::parse_one(b"foo");
484-
let p = p.unwrap();
507+
fn test_parameter_parse() {
508+
let p = Parameter::parse(b"foo").unwrap();
485509
assert_eq!(p.key.0, b"foo");
486510
assert_eq!(p.value, None);
487-
assert_eq!(rest, "".as_bytes());
488511

489-
// should consume one parameter and return the rest of the input
490-
let (p, rest) = Parameter::parse_one(b"foo=bar baz");
491-
let p = p.unwrap();
512+
// should parse only the first parameter and discard the rest of the input
513+
let p = Parameter::parse(b"foo=bar baz").unwrap();
492514
assert_eq!(p.key.0, b"foo");
493515
assert_eq!(p.value, Some(b"bar".as_slice()));
494-
assert_eq!(rest, " baz".as_bytes());
495516

496517
// should return None on empty or whitespace inputs
497-
let (p, rest) = Parameter::parse_one(b"");
498-
assert!(p.is_none());
499-
assert_eq!(rest, b"".as_slice());
500-
let (p, rest) = Parameter::parse_one(b" ");
501-
assert!(p.is_none());
502-
assert_eq!(rest, b"".as_slice());
518+
assert!(Parameter::parse(b"").is_none());
519+
assert!(Parameter::parse(b" ").is_none());
503520
}
504521

505522
#[test]
@@ -534,11 +551,10 @@ mod tests {
534551

535552
#[test]
536553
fn test_parameter_internal_key_whitespace() {
537-
let (p, rest) = Parameter::parse_one("foo bar=baz".as_bytes());
538-
let p = p.unwrap();
554+
// parse should only consume the first parameter
555+
let p = Parameter::parse("foo bar=baz".as_bytes()).unwrap();
539556
assert_eq!(p.key.0, b"foo");
540557
assert_eq!(p.value, None);
541-
assert_eq!(rest, b" bar=baz");
542558
}
543559

544560
#[test]
@@ -923,4 +939,54 @@ mod tests {
923939
assert_eq!(params[1], param("baz=qux"));
924940
assert_eq!(params[2], param("wiz"));
925941
}
942+
943+
#[test]
944+
fn test_iter_bytes_simple() {
945+
let kargs = Cmdline::from(b"foo bar baz");
946+
let params: Vec<_> = kargs.iter_bytes().collect();
947+
948+
assert_eq!(params.len(), 3);
949+
assert_eq!(params[0], b"foo");
950+
assert_eq!(params[1], b"bar");
951+
assert_eq!(params[2], b"baz");
952+
}
953+
954+
#[test]
955+
fn test_iter_bytes_with_values() {
956+
let kargs = Cmdline::from(b"foo=bar baz=qux wiz");
957+
let params: Vec<_> = kargs.iter_bytes().collect();
958+
959+
assert_eq!(params.len(), 3);
960+
assert_eq!(params[0], b"foo=bar");
961+
assert_eq!(params[1], b"baz=qux");
962+
assert_eq!(params[2], b"wiz");
963+
}
964+
965+
#[test]
966+
fn test_iter_bytes_with_quotes() {
967+
let kargs = Cmdline::from(b"foo=\"bar baz\" qux");
968+
let params: Vec<_> = kargs.iter_bytes().collect();
969+
970+
assert_eq!(params.len(), 2);
971+
assert_eq!(params[0], b"foo=\"bar baz\"");
972+
assert_eq!(params[1], b"qux");
973+
}
974+
975+
#[test]
976+
fn test_iter_bytes_extra_whitespace() {
977+
let kargs = Cmdline::from(b" foo bar ");
978+
let params: Vec<_> = kargs.iter_bytes().collect();
979+
980+
assert_eq!(params.len(), 2);
981+
assert_eq!(params[0], b"foo");
982+
assert_eq!(params[1], b"bar");
983+
}
984+
985+
#[test]
986+
fn test_iter_bytes_empty() {
987+
let kargs = Cmdline::from(b"");
988+
let params: Vec<_> = kargs.iter_bytes().collect();
989+
990+
assert_eq!(params.len(), 0);
991+
}
926992
}

0 commit comments

Comments
 (0)