Skip to content

Commit 3758da2

Browse files
authored
Merge pull request #1490 from cgwalters/kernel-cleanups-3
kernel_cmdline: More cleanups and API additions
2 parents e260212 + 17b4f7b commit 3758da2

File tree

2 files changed

+165
-68
lines changed

2 files changed

+165
-68
lines changed

crates/lib/src/install.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,17 +1668,15 @@ fn find_root_args_to_inherit(cmdline: &Cmdline, root_info: &Filesystem) -> Resul
16681668
.context("Parsing root= karg")?;
16691669
// If we have a root= karg, then use that
16701670
let (mount_spec, kargs) = if let Some(root) = root {
1671-
let rootflags = cmdline.find(crate::kernel_cmdline::ROOTFLAGS);
1672-
let inherit_kargs = cmdline.iter().filter(|arg| {
1673-
arg.key
1674-
.starts_with(crate::kernel_cmdline::INITRD_ARG_PREFIX)
1675-
});
1671+
let rootflags = cmdline.find_str(crate::kernel_cmdline::ROOTFLAGS);
1672+
let inherit_kargs =
1673+
cmdline.find_all_starting_with_str(crate::kernel_cmdline::INITRD_ARG_PREFIX);
16761674
(
16771675
root.to_owned(),
16781676
rootflags
16791677
.into_iter()
16801678
.chain(inherit_kargs)
1681-
.map(|p| p.to_string())
1679+
.map(|p| p.as_ref().to_owned())
16821680
.collect(),
16831681
)
16841682
} else {

crates/lib/src/kernel_cmdline.rs

Lines changed: 161 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use std::borrow::Cow;
88
use anyhow::Result;
99

1010
/// This is used by dracut.
11-
pub(crate) const INITRD_ARG_PREFIX: &[u8] = b"rd.";
11+
pub(crate) const INITRD_ARG_PREFIX: &str = "rd.";
1212
/// The kernel argument for configuring the rootfs flags.
13-
pub(crate) const ROOTFLAGS: &[u8] = b"rootflags";
13+
pub(crate) const ROOTFLAGS: &str = "rootflags";
1414

1515
/// A parsed kernel command line.
1616
///
@@ -41,7 +41,7 @@ impl<'a> Cmdline<'a> {
4141
/// Properly handles quoted values containing whitespace and splits on
4242
/// unquoted whitespace characters. Parameters are parsed as either
4343
/// key-only switches or key=value pairs.
44-
pub fn iter(&'a self) -> impl Iterator<Item = Parameter<'a>> {
44+
pub fn iter(&'a self) -> impl Iterator<Item = Parameter<'a>> + 'a {
4545
let mut in_quotes = false;
4646

4747
self.0
@@ -63,6 +63,29 @@ impl<'a> Cmdline<'a> {
6363
self.iter().find(|p| p.key == key)
6464
}
6565

66+
/// Locate a kernel argument with the given key name that must be UTF-8.
67+
///
68+
/// Otherwise the same as [`Self::find`].
69+
pub fn find_str(&'a self, key: &str) -> Option<ParameterStr<'a>> {
70+
let key = ParameterKeyStr(key);
71+
self.iter()
72+
.filter_map(|p| p.to_str())
73+
.find(move |p| p.key == key)
74+
}
75+
76+
/// Find all kernel arguments starting with the given prefix which must be UTF-8.
77+
/// Non-UTF8 values are ignored.
78+
///
79+
/// This is a variant of [`Self::find`].
80+
pub fn find_all_starting_with_str(
81+
&'a self,
82+
prefix: &'a str,
83+
) -> impl Iterator<Item = ParameterStr<'a>> + 'a {
84+
self.iter()
85+
.filter_map(|p| p.to_str())
86+
.filter(move |p| p.key.0.starts_with(prefix))
87+
}
88+
6689
/// Locate the value of the kernel argument with the given key name.
6790
///
6891
/// Returns the first value matching the given key, or `None` if not found.
@@ -121,47 +144,52 @@ impl<'a> From<&'a [u8]> for ParameterKey<'a> {
121144
}
122145
}
123146

147+
/// A single kernel command line parameter key that is known to be UTF-8.
148+
///
149+
/// Otherwise the same as [`ParameterKey`].
150+
#[derive(Debug, Eq)]
151+
pub(crate) struct ParameterKeyStr<'a>(&'a str);
152+
153+
impl<'a> From<&'a str> for ParameterKeyStr<'a> {
154+
fn from(value: &'a str) -> Self {
155+
Self(value)
156+
}
157+
}
158+
124159
/// A single kernel command line parameter.
125-
#[derive(Debug, PartialEq, Eq)]
160+
#[derive(Debug, Eq)]
126161
pub(crate) struct Parameter<'a> {
162+
/// The full original value
163+
pub parameter: &'a [u8],
127164
/// The parameter key as raw bytes
128165
pub key: ParameterKey<'a>,
129166
/// The parameter value as raw bytes, if present
130167
pub value: Option<&'a [u8]>,
131168
}
132169

133-
impl<'a> Parameter<'a> {
134-
/// Create a new parameter with the provided key and value.
135-
#[cfg(test)]
136-
pub fn new_kv<'k: 'a, 'v: 'a>(key: &'k [u8], value: &'v [u8]) -> Self {
137-
Self {
138-
key: ParameterKey(key),
139-
value: Some(value),
140-
}
141-
}
142-
143-
/// Create a new parameter with the provided key.
144-
#[cfg(test)]
145-
pub fn new_key(key: &'a [u8]) -> Self {
146-
Self {
147-
key: ParameterKey(key),
148-
value: None,
149-
}
150-
}
170+
/// A single kernel command line parameter.
171+
#[derive(Debug, PartialEq, Eq)]
172+
pub(crate) struct ParameterStr<'a> {
173+
/// The original value
174+
pub parameter: &'a str,
175+
/// The parameter key
176+
pub key: ParameterKeyStr<'a>,
177+
/// The parameter value, if present
178+
pub value: Option<&'a str>,
179+
}
151180

152-
/// Returns the key as a lossy UTF-8 string.
153-
///
154-
/// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
155-
pub fn key_lossy(&self) -> String {
156-
String::from_utf8_lossy(&self.key).to_string()
181+
impl<'a> Parameter<'a> {
182+
pub fn to_str(&self) -> Option<ParameterStr<'a>> {
183+
let Ok(parameter) = std::str::from_utf8(self.parameter) else {
184+
return None;
185+
};
186+
Some(ParameterStr::from(parameter))
157187
}
188+
}
158189

159-
/// Returns the value as a lossy UTF-8 string.
160-
///
161-
/// Invalid UTF-8 sequences are replaced with the Unicode replacement character.
162-
/// Returns an empty string if no value is present.
163-
pub fn value_lossy(&self) -> String {
164-
String::from_utf8_lossy(self.value.unwrap_or(&[])).to_string()
190+
impl<'a> AsRef<str> for ParameterStr<'a> {
191+
fn as_ref(&self) -> &str {
192+
self.parameter
165193
}
166194
}
167195

@@ -177,6 +205,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Parameter<'a> {
177205

178206
match equals {
179207
None => Self {
208+
parameter: input,
180209
key: ParameterKey(input),
181210
value: None,
182211
},
@@ -196,6 +225,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Parameter<'a> {
196225
.unwrap_or(value);
197226

198227
Self {
228+
parameter: input,
199229
key,
200230
value: Some(value),
201231
}
@@ -228,29 +258,40 @@ impl PartialEq for ParameterKey<'_> {
228258
}
229259
}
230260

231-
impl std::fmt::Display for Parameter<'_> {
232-
/// Formats the parameter for display.
233-
///
234-
/// Key-only parameters are displayed as just the key.
235-
/// Key-value parameters are displayed as `key=value`.
236-
/// Values containing whitespace are automatically quoted.
237-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238-
let key = self.key_lossy();
239-
240-
if self.value.is_some() {
241-
let value = self.value_lossy();
242-
243-
if value.chars().any(|c| c.is_ascii_whitespace()) {
244-
write!(f, "{key}=\"{value}\"")
245-
} else {
246-
write!(f, "{key}={value}")
247-
}
261+
impl<'a> From<&'a str> for ParameterStr<'a> {
262+
fn from(parameter: &'a str) -> Self {
263+
let (key, value) = if let Some((key, value)) = parameter.split_once('=') {
264+
let value = value
265+
.strip_prefix('"')
266+
.unwrap_or(value)
267+
.strip_suffix('"')
268+
.unwrap_or(value);
269+
(key, Some(value))
248270
} else {
249-
write!(f, "{key}")
271+
(parameter, None)
272+
};
273+
let key = ParameterKeyStr(key);
274+
ParameterStr {
275+
parameter,
276+
key,
277+
value,
250278
}
251279
}
252280
}
253281

282+
impl<'a> PartialEq for Parameter<'a> {
283+
fn eq(&self, other: &Self) -> bool {
284+
// Note we don't compare parameter because we want hyphen-dash insensitivity for the key
285+
self.key == other.key && self.value == other.value
286+
}
287+
}
288+
289+
impl<'a> PartialEq for ParameterKeyStr<'a> {
290+
fn eq(&self, other: &Self) -> bool {
291+
ParameterKey(self.0.as_bytes()) == ParameterKey(other.0.as_bytes())
292+
}
293+
}
294+
254295
#[cfg(test)]
255296
mod tests {
256297
use super::*;
@@ -293,9 +334,6 @@ mod tests {
293334
p.push(non_utf8_byte[0]);
294335
let p = Parameter::from(&p);
295336
assert_eq!(p.value, Some(non_utf8_byte.as_slice()));
296-
297-
// lossy replacement sanity check
298-
assert_eq!(p.value_lossy(), char::REPLACEMENT_CHARACTER.to_string());
299337
}
300338

301339
#[test]
@@ -334,14 +372,9 @@ mod tests {
334372
let kargs = Cmdline::from(b"foo=bar,bar2 baz=fuz wiz".as_slice());
335373
let mut iter = kargs.iter();
336374

337-
assert_eq!(iter.next(), Some(Parameter::new_kv(b"foo", b"bar,bar2")));
338-
339-
assert_eq!(
340-
iter.next(),
341-
Some(Parameter::new_kv(b"baz", b"fuz".as_slice()))
342-
);
343-
344-
assert_eq!(iter.next(), Some(Parameter::new_key(b"wiz")));
375+
assert_eq!(iter.next(), Some(Parameter::from(b"foo=bar,bar2")));
376+
assert_eq!(iter.next(), Some(Parameter::from(b"baz=fuz")));
377+
assert_eq!(iter.next(), Some(Parameter::from(b"wiz")));
345378
assert_eq!(iter.next(), None);
346379

347380
// Test the find API
@@ -483,4 +516,70 @@ mod tests {
483516
let kargs = Cmdline::from(&invalid_utf8);
484517
assert!(kargs.require_value_of_utf8("invalid").is_err());
485518
}
519+
520+
#[test]
521+
fn test_find_str() {
522+
let kargs = Cmdline::from(b"foo=bar baz=qux switch rd.break".as_slice());
523+
let p = kargs.find_str("foo").unwrap();
524+
assert_eq!(p, ParameterStr::from("foo=bar"));
525+
assert_eq!(p.as_ref(), "foo=bar");
526+
let p = kargs.find_str("rd.break").unwrap();
527+
assert_eq!(p, ParameterStr::from("rd.break"));
528+
assert!(kargs.find_str("missing").is_none());
529+
}
530+
531+
#[test]
532+
fn test_find_all_str() {
533+
let kargs =
534+
Cmdline::from(b"foo=bar rd.foo=a rd.bar=b rd.baz rd.qux=c notrd.val=d".as_slice());
535+
let mut rd_args: Vec<_> = kargs.find_all_starting_with_str("rd.").collect();
536+
rd_args.sort_by(|a, b| a.key.0.cmp(b.key.0));
537+
assert_eq!(rd_args.len(), 4);
538+
assert_eq!(rd_args[0], ParameterStr::from("rd.bar=b"));
539+
assert_eq!(rd_args[1], ParameterStr::from("rd.baz"));
540+
assert_eq!(rd_args[2], ParameterStr::from("rd.foo=a"));
541+
assert_eq!(rd_args[3], ParameterStr::from("rd.qux=c"));
542+
}
543+
544+
#[test]
545+
fn test_param_to_str() {
546+
let p = Parameter::from("foo=bar");
547+
let p_str = p.to_str().unwrap();
548+
assert_eq!(p_str, ParameterStr::from("foo=bar"));
549+
let non_utf8_byte = b"\xff";
550+
let mut p_u8 = b"foo=".to_vec();
551+
p_u8.push(non_utf8_byte[0]);
552+
let p = Parameter::from(&p_u8);
553+
assert!(p.to_str().is_none());
554+
}
555+
556+
#[test]
557+
fn test_param_key_str_eq() {
558+
let k1 = ParameterKeyStr("a-b");
559+
let k2 = ParameterKeyStr("a_b");
560+
assert_eq!(k1, k2);
561+
let k1 = ParameterKeyStr("a-b");
562+
let k2 = ParameterKeyStr("a-c");
563+
assert_ne!(k1, k2);
564+
}
565+
566+
#[test]
567+
fn test_kargs_non_utf8() {
568+
let non_utf8_val = b"an_invalid_key=\xff";
569+
let mut kargs_bytes = b"foo=bar ".to_vec();
570+
kargs_bytes.extend_from_slice(non_utf8_val);
571+
kargs_bytes.extend_from_slice(b" baz=qux");
572+
let kargs = Cmdline::from(kargs_bytes.as_slice());
573+
574+
// We should be able to find the valid kargs
575+
assert_eq!(kargs.find_str("foo").unwrap().value, Some("bar"));
576+
assert_eq!(kargs.find_str("baz").unwrap().value, Some("qux"));
577+
578+
// But we should not find the invalid one via find_str
579+
assert!(kargs.find("an_invalid_key").unwrap().to_str().is_none());
580+
581+
// And even using the raw find, trying to convert it to_str will fail.
582+
let raw_param = kargs.find("an_invalid_key").unwrap();
583+
assert_eq!(raw_param.value.unwrap(), b"\xff");
584+
}
486585
}

0 commit comments

Comments
 (0)