Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions crates/kernel_cmdline/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,38 @@ impl<'a> Cmdline<'a> {
removed
}

/// Returns the canonicalized version of the `Cmdline`.
///
/// This:
///
/// 1. Sorts the parameter list
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm but I think in some corner cases this could actually change semantics. Some kargs probably have last-one-wins behavior e.g.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good point. I don't think it's at all consistent either, it varies arg to arg.

/// 2. Canonicalizes each `Parameter`
/// 3. Joins each parameter together with a single space ' '
///
/// # Examples
///
/// ```
/// use bootc_kernel_cmdline::bytes::Cmdline;
///
/// let cmdline = Cmdline::from(b"z a=\"b c\"");
/// assert_eq!(&cmdline.canonicalized(), b"\"a=b c\" z");
/// ```
pub fn canonicalized(&self) -> Vec<u8> {
let mut params = self.iter().collect::<Vec<_>>();
params.sort();

let mut res = Vec::new();

for (i, p) in params.iter().enumerate() {
if i > 0 {
res.push(b' ');
}
res.extend(p.canonicalized());
}

res
}

#[cfg(test)]
pub(crate) fn is_owned(&self) -> bool {
matches!(self.0, Cow::Owned(_))
Expand Down Expand Up @@ -426,6 +458,21 @@ impl ParameterKey<'_> {
.iter()
.map(|&c: &u8| if c == b'-' { b'_' } else { c })
}

/// Returns the canonicalized version of the key. This replaces
/// all dashes '-' with underscores '_'.
///
/// # Example
///
/// ```
/// use bootc_kernel_cmdline::bytes::ParameterKey;
///
/// assert_eq!(&ParameterKey::from("key-with-dashes").canonicalized(),
/// "key_with_dashes".as_bytes());
/// ```
pub fn canonicalized(&self) -> Vec<u8> {
self.iter().collect()
}
}

impl PartialEq for ParameterKey<'_> {
Expand Down Expand Up @@ -528,6 +575,57 @@ impl<'a> Parameter<'a> {
pub fn value(&self) -> Option<&'a [u8]> {
self.value
}

/// Returns the canonical representation of the parameter.
///
/// The canonical representation:
///
/// 1. Will use the canonicalized form of the key via
/// `ParameterKey::canonicalized`
///
/// 2. Will be "externally" quoted if either the key or
/// (optional) value contains ascii whitespace.
///
/// 3. Unnecessary quoting will be removed.
///
/// # Examples
///
/// ```
/// use bootc_kernel_cmdline::bytes::Parameter;
///
/// // key is canonicalized
/// assert_eq!(Parameter::parse("a-dashed-key").unwrap().canonicalized(),
/// "a_dashed_key".as_bytes());
///
/// // quotes are externally added if needed
/// assert_eq!(Parameter::parse("foo=\"has some spaces\"").unwrap().canonicalized(),
/// "\"foo=has some spaces\"".as_bytes());
///
/// // unnecessary quotes are removed
/// assert_eq!(Parameter::parse("foo=\"bar\"").unwrap().canonicalized(),
/// "foo=bar".as_bytes());
/// ```
pub fn canonicalized(&self) -> Vec<u8> {
let spaces = self.key.iter().any(|b| b.is_ascii_whitespace())
|| self
.value
.map_or(false, |val| val.iter().any(|b| b.is_ascii_whitespace()));

let mut res = if spaces { vec![b'"'] } else { vec![] };

res.extend(self.key.iter());

if let Some(val) = self.value {
res.push(b'=');
res.extend(val);
}

if spaces {
res.push(b'"');
}

res
}
}

impl PartialEq for Parameter<'_> {
Expand Down
77 changes: 77 additions & 0 deletions crates/kernel_cmdline/src/utf8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,29 @@ impl<'a> Cmdline<'a> {
self.0.remove_exact(&param.0)
}

/// Returns the canonicalized version of the `Cmdline`.
///
/// This:
///
/// 1. Sorts the parameter list
/// 2. Canonicalizes each `Parameter`
/// 3. Joins each parameter together with a single space ' '
///
/// # Examples
///
/// ```
/// use bootc_kernel_cmdline::utf8::Cmdline;
///
/// let cmdline = Cmdline::from("z a=\"b c\"");
/// assert_eq!(&cmdline.canonicalized(), "\"a=b c\" z");
/// ```
pub fn canonicalized(&self) -> String {
self.0
.canonicalized()
.try_into()
.expect("We only construct the underlying bytes from valid UTF-8")
}

#[cfg(test)]
pub(crate) fn is_owned(&self) -> bool {
self.0.is_owned()
Expand Down Expand Up @@ -298,6 +321,24 @@ impl<'a> ParameterKey<'a> {
fn from_bytes(input: bytes::ParameterKey<'a>) -> Self {
Self(input)
}

/// Returns the canonicalized version of the key. This replaces
/// all dashes '-' with underscores '_'.
///
/// # Example
///
/// ```
/// use bootc_kernel_cmdline::utf8::ParameterKey;
///
/// assert_eq!(ParameterKey::from("key-with-dashes").canonicalized(),
/// "key_with_dashes".to_string());
/// ```
pub fn canonicalized(&self) -> String {
self.0
.canonicalized()
.try_into()
.expect("We only construct the underlying bytes from valid UTF-8")
}
}

impl<'a, T: AsRef<str> + ?Sized> From<&'a T> for ParameterKey<'a> {
Expand Down Expand Up @@ -358,6 +399,42 @@ impl<'a> Parameter<'a> {
str::from_utf8(p).expect("We only construct the underlying bytes from valid UTF-8")
})
}

/// Returns the canonical representation of the parameter.
///
/// The canonical representation:
///
/// 1. Will use the canonicalized form of the key via
/// `ParameterKey::canonicalized`
///
/// 2. Will be "externally" quoted if either the key or
/// (optional) value contains ascii whitespace.
///
/// 3. Unnecessary quoting will be removed.
///
/// # Examples
///
/// ```
/// use bootc_kernel_cmdline::utf8::Parameter;
///
/// // key is canonicalized
/// assert_eq!(Parameter::parse("a-dashed-key").unwrap().canonicalized(),
/// "a_dashed_key".to_string());
///
/// // quotes are externally added if needed
/// assert_eq!(Parameter::parse("foo=\"has some spaces\"").unwrap().canonicalized(),
/// "\"foo=has some spaces\"".to_string());
///
/// // unnecessary quotes are removed
/// assert_eq!(Parameter::parse("foo=\"bar\"").unwrap().canonicalized(),
/// "foo=bar".to_string());
/// ```
pub fn canonicalized(&self) -> String {
self.0
.canonicalized()
.try_into()
.expect("We only construct the underlying bytes from valid UTF-8")
}
}

impl<'a> TryFrom<bytes::Parameter<'a>> for Parameter<'a> {
Expand Down