bootc_kernel_cmdline/
utf8.rs

1//! UTF-8-based 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
6use std::ops::Deref;
7
8use crate::{Action, bytes};
9
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12
13/// A parsed UTF-8 kernel command line.
14///
15/// Wraps the raw command line bytes and provides methods for parsing and iterating
16/// over individual parameters. Uses copy-on-write semantics to avoid unnecessary
17/// allocations when working with borrowed data.
18#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Cmdline<'a>(bytes::Cmdline<'a>);
20
21/// An owned `Cmdline`.  Alias for `Cmdline<'static>`.
22pub type CmdlineOwned = Cmdline<'static>;
23
24impl<'a, T: AsRef<str> + ?Sized> From<&'a T> for Cmdline<'a> {
25    /// Creates a new `Cmdline` from any type that can be referenced as `str`.
26    ///
27    /// Uses borrowed data when possible to avoid unnecessary allocations.
28    fn from(input: &'a T) -> Self {
29        Self(bytes::Cmdline::from(input.as_ref().as_bytes()))
30    }
31}
32
33impl From<String> for CmdlineOwned {
34    /// Creates a new `Cmdline` from a `String`.
35    ///
36    /// Takes ownership of input and maintains it for internal owned data.
37    fn from(input: String) -> Self {
38        Self(bytes::Cmdline::from(input.into_bytes()))
39    }
40}
41
42/// An iterator over UTF-8 kernel command line parameters.
43///
44/// This is created by the `iter` method on `CmdlineUTF8`.
45#[derive(Debug)]
46pub struct CmdlineIter<'a>(bytes::CmdlineIter<'a>);
47
48impl<'a> Iterator for CmdlineIter<'a> {
49    type Item = Parameter<'a>;
50
51    fn next(&mut self) -> Option<Self::Item> {
52        self.0.next().map(Parameter::from_bytes)
53    }
54}
55
56/// An iterator over UTF-8 kernel command line parameters as string slices.
57///
58/// This is created by the `iter_str` method on `Cmdline`.
59#[derive(Debug)]
60pub struct CmdlineIterStr<'a>(bytes::CmdlineIterBytes<'a>);
61
62impl<'a> Iterator for CmdlineIterStr<'a> {
63    type Item = &'a str;
64
65    fn next(&mut self) -> Option<Self::Item> {
66        // Get the next byte slice from the underlying iterator
67        let bytes = self.0.next()?;
68
69        // Convert to UTF-8 string slice
70        // SAFETY: We know this is valid UTF-8 since the Cmdline was constructed from valid UTF-8
71        Some(str::from_utf8(bytes).expect("Parameter bytes come from valid UTF-8 cmdline"))
72    }
73}
74
75impl<'a> Cmdline<'a> {
76    /// Creates a new empty owned `Cmdline`.
77    ///
78    /// This is equivalent to `Cmdline::default()` but makes ownership explicit.
79    pub fn new() -> CmdlineOwned {
80        Cmdline::default()
81    }
82
83    /// Reads the kernel command line from `/proc/cmdline`.
84    ///
85    /// Returns an error if:
86    ///   - The file cannot be read
87    ///   - There are I/O issues
88    ///   - The cmdline from proc is not valid UTF-8
89    pub fn from_proc() -> Result<Self> {
90        let cmdline = std::fs::read("/proc/cmdline")?;
91
92        // SAFETY: validate the value from proc is valid UTF-8.  We
93        // don't need to save this, but checking now will ensure we
94        // can safely convert from the underlying bytes back to UTF-8
95        // later.
96        str::from_utf8(&cmdline)?;
97
98        Ok(Self(bytes::Cmdline::from(cmdline)))
99    }
100
101    /// Returns an iterator over all parameters in the command line.
102    ///
103    /// Properly handles quoted values containing whitespace and splits on
104    /// unquoted whitespace characters. Parameters are parsed as either
105    /// key-only switches or key=value pairs.
106    pub fn iter(&'a self) -> CmdlineIter<'a> {
107        CmdlineIter(self.0.iter())
108    }
109
110    /// Returns an iterator over all parameters in the command line as string slices.
111    ///
112    /// This is similar to `iter()` but yields `&str` directly instead of `Parameter`,
113    /// which can be more convenient when you just need the string representation.
114    pub fn iter_str(&self) -> CmdlineIterStr<'_> {
115        CmdlineIterStr(self.0.iter_bytes())
116    }
117
118    /// Locate a kernel argument with the given key name.
119    ///
120    /// Returns the first parameter matching the given key, or `None` if not found.
121    /// Key comparison treats dashes and underscores as equivalent.
122    pub fn find<T: AsRef<str> + ?Sized>(&'a self, key: &T) -> Option<Parameter<'a>> {
123        let key = ParameterKey::from(key.as_ref());
124        self.iter().find(|p| p.key() == key)
125    }
126
127    /// Find all kernel arguments starting with the given UTF-8 prefix.
128    ///
129    /// This is a variant of [`Self::find`].
130    pub fn find_all_starting_with<T: AsRef<str> + ?Sized>(
131        &'a self,
132        prefix: &'a T,
133    ) -> impl Iterator<Item = Parameter<'a>> + 'a {
134        self.iter()
135            .filter(move |p| p.key().starts_with(prefix.as_ref()))
136    }
137
138    /// Locate the value of the kernel argument with the given key name.
139    ///
140    /// Returns the first value matching the given key, or `None` if not found.
141    /// Key comparison treats dashes and underscores as equivalent.
142    pub fn value_of<T: AsRef<str> + ?Sized>(&'a self, key: &T) -> Option<&'a str> {
143        self.0.value_of(key.as_ref().as_bytes()).map(|v| {
144            // SAFETY: We know this is valid UTF-8 since we only
145            // construct the underlying `bytes` from valid UTF-8
146            str::from_utf8(v).expect("We only construct the underlying bytes from valid UTF-8")
147        })
148    }
149
150    /// Find the value of the kernel argument with the provided name, which must be present.
151    ///
152    /// Otherwise the same as [`Self::value_of`].
153    pub fn require_value_of<T: AsRef<str> + ?Sized>(&'a self, key: &T) -> Result<&'a str> {
154        let key = key.as_ref();
155        self.value_of(key)
156            .ok_or_else(|| anyhow::anyhow!("Failed to find kernel argument '{key}'"))
157    }
158
159    /// Add a parameter to the command line if it doesn't already exist
160    ///
161    /// Returns `Action::Added` if the parameter did not already exist
162    /// and was added.
163    ///
164    /// Returns `Action::Existed` if the exact parameter (same key and value)
165    /// already exists. No modification was made.
166    ///
167    /// Unlike `add_or_modify`, this method will not modify existing
168    /// parameters. If a parameter with the same key exists but has a
169    /// different value, the new parameter is still added, allowing
170    /// duplicate keys (e.g., multiple `console=` parameters).
171    pub fn add(&mut self, param: &Parameter) -> Action {
172        self.0.add(&param.0)
173    }
174
175    /// Add or modify a parameter to the command line
176    ///
177    /// Returns `Action::Added` if the parameter did not exist before
178    /// and was added.
179    ///
180    /// Returns `Action::Modified` if the parameter existed before,
181    /// but contained a different value.  The value was updated to the
182    /// newly-requested value.
183    ///
184    /// Returns `Action::Existed` if the parameter existed before, and
185    /// contained the same value as the newly-requested value.  No
186    /// modification was made.
187    pub fn add_or_modify(&mut self, param: &Parameter) -> Action {
188        self.0.add_or_modify(&param.0)
189    }
190
191    /// Remove parameter(s) with the given key from the command line
192    ///
193    /// Returns `true` if parameter(s) were removed.
194    pub fn remove(&mut self, key: &ParameterKey) -> bool {
195        self.0.remove(&key.0)
196    }
197
198    /// Remove all parameters that exactly match the given parameter
199    /// from the command line
200    ///
201    /// Returns `true` if parameter(s) were removed.
202    pub fn remove_exact(&mut self, param: &Parameter) -> bool {
203        self.0.remove_exact(&param.0)
204    }
205
206    #[cfg(test)]
207    pub(crate) fn is_owned(&self) -> bool {
208        self.0.is_owned()
209    }
210
211    #[cfg(test)]
212    pub(crate) fn is_borrowed(&self) -> bool {
213        self.0.is_borrowed()
214    }
215}
216
217impl Deref for Cmdline<'_> {
218    type Target = str;
219
220    fn deref(&self) -> &Self::Target {
221        // SAFETY: We know this is valid UTF-8 since we only
222        // construct the underlying `bytes` from valid UTF-8
223        str::from_utf8(&self.0).expect("We only construct the underlying bytes from valid UTF-8")
224    }
225}
226
227impl<'a, T> AsRef<T> for Cmdline<'a>
228where
229    T: ?Sized,
230    <Cmdline<'a> as Deref>::Target: AsRef<T>,
231{
232    fn as_ref(&self) -> &T {
233        self.deref().as_ref()
234    }
235}
236
237impl<'a> std::fmt::Display for Cmdline<'a> {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
239        f.write_str(self)
240    }
241}
242
243impl<'a> IntoIterator for &'a Cmdline<'a> {
244    type Item = Parameter<'a>;
245    type IntoIter = CmdlineIter<'a>;
246
247    fn into_iter(self) -> Self::IntoIter {
248        self.iter()
249    }
250}
251
252impl<'a, 'other> Extend<Parameter<'other>> for Cmdline<'a> {
253    // Note this is O(N*M), but in practice this doesn't matter
254    // because kernel cmdlines are typically quite small (limited
255    // to at most 4k depending on arch).  Using a hash-based
256    // structure to reduce this to O(N)+C would likely raise the C
257    // portion so much as to erase any benefit from removing the
258    // combinatorial complexity.  Plus CPUs are good at
259    // caching/pipelining through contiguous memory.
260    fn extend<T: IntoIterator<Item = Parameter<'other>>>(&mut self, iter: T) {
261        for param in iter {
262            self.add(&param);
263        }
264    }
265}
266
267/// A single kernel command line parameter key
268///
269/// Handles quoted values and treats dashes and underscores in keys as equivalent.
270#[derive(Clone, Debug, Eq)]
271pub struct ParameterKey<'a>(bytes::ParameterKey<'a>);
272
273impl Deref for ParameterKey<'_> {
274    type Target = str;
275
276    fn deref(&self) -> &Self::Target {
277        // SAFETY: We know this is valid UTF-8 since we only
278        // construct the underlying `bytes` from valid UTF-8
279        str::from_utf8(&self.0).expect("We only construct the underlying bytes from valid UTF-8")
280    }
281}
282
283impl<'a, T> AsRef<T> for ParameterKey<'a>
284where
285    T: ?Sized,
286    <ParameterKey<'a> as Deref>::Target: AsRef<T>,
287{
288    fn as_ref(&self) -> &T {
289        self.deref().as_ref()
290    }
291}
292
293impl<'a> ParameterKey<'a> {
294    /// Construct a utf8::ParameterKey from a bytes::ParameterKey
295    ///
296    /// This is non-public and should only be used when the underlying
297    /// bytes are known to be valid UTF-8.
298    fn from_bytes(input: bytes::ParameterKey<'a>) -> Self {
299        Self(input)
300    }
301}
302
303impl<'a, T: AsRef<str> + ?Sized> From<&'a T> for ParameterKey<'a> {
304    fn from(input: &'a T) -> Self {
305        Self(bytes::ParameterKey(input.as_ref().as_bytes()))
306    }
307}
308
309impl<'a> std::fmt::Display for ParameterKey<'a> {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
311        f.write_str(self)
312    }
313}
314
315impl PartialEq for ParameterKey<'_> {
316    /// Compares two parameter keys for equality.
317    ///
318    /// Keys are compared with dashes and underscores treated as equivalent.
319    /// This comparison is case-sensitive.
320    fn eq(&self, other: &Self) -> bool {
321        self.0 == other.0
322    }
323}
324
325/// A single kernel command line parameter.
326#[derive(Clone, Debug, Eq)]
327pub struct Parameter<'a>(bytes::Parameter<'a>);
328
329impl<'a> Parameter<'a> {
330    /// Attempt to parse a single command line parameter from a UTF-8
331    /// string.
332    ///
333    /// Returns `Some(Parameter)`, or `None` if a Parameter could not
334    /// be constructed from the input.  This occurs when the input is
335    /// either empty or contains only whitespace.
336    pub fn parse<T: AsRef<str> + ?Sized>(input: &'a T) -> Option<Self> {
337        bytes::Parameter::parse(input.as_ref().as_bytes()).map(Self)
338    }
339
340    /// Construct a utf8::Parameter from a bytes::Parameter
341    ///
342    /// This is non-public and should only be used when the underlying
343    /// bytes are known to be valid UTF-8.
344    fn from_bytes(bytes: bytes::Parameter<'a>) -> Self {
345        Self(bytes)
346    }
347
348    /// Returns the key part of the parameter
349    pub fn key(&'a self) -> ParameterKey<'a> {
350        ParameterKey::from_bytes(self.0.key())
351    }
352
353    /// Returns the optional value part of the parameter
354    pub fn value(&'a self) -> Option<&'a str> {
355        self.0.value().map(|p| {
356            // SAFETY: We know this is valid UTF-8 since we only
357            // construct the underlying `bytes` from valid UTF-8
358            str::from_utf8(p).expect("We only construct the underlying bytes from valid UTF-8")
359        })
360    }
361}
362
363impl<'a> TryFrom<bytes::Parameter<'a>> for Parameter<'a> {
364    type Error = anyhow::Error;
365
366    fn try_from(bytes: bytes::Parameter<'a>) -> Result<Self, Self::Error> {
367        if str::from_utf8(bytes.key().deref()).is_err() {
368            anyhow::bail!("Parameter key is not valid UTF-8");
369        }
370
371        if let Some(value) = bytes.value() {
372            if str::from_utf8(value).is_err() {
373                anyhow::bail!("Parameter value is not valid UTF-8");
374            }
375        }
376
377        Ok(Self(bytes))
378    }
379}
380
381impl<'a> std::fmt::Display for Parameter<'a> {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
383        f.write_str(self)
384    }
385}
386
387impl Deref for Parameter<'_> {
388    type Target = str;
389
390    fn deref(&self) -> &Self::Target {
391        // SAFETY: We know this is valid UTF-8 since we only
392        // construct the underlying `bytes` from valid UTF-8
393        str::from_utf8(&self.0).expect("We only construct the underlying bytes from valid UTF-8")
394    }
395}
396
397impl<'a, T> AsRef<T> for Parameter<'a>
398where
399    T: ?Sized,
400    <Parameter<'a> as Deref>::Target: AsRef<T>,
401{
402    fn as_ref(&self) -> &T {
403        self.deref().as_ref()
404    }
405}
406
407impl<'a> PartialEq for Parameter<'a> {
408    fn eq(&self, other: &Self) -> bool {
409        self.0 == other.0
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416
417    // convenience method for tests
418    fn param(s: &str) -> Parameter<'_> {
419        Parameter::parse(s).unwrap()
420    }
421
422    #[test]
423    fn test_parameter_parse() {
424        let p = Parameter::parse("foo").unwrap();
425        assert_eq!(p.key(), "foo".into());
426        assert_eq!(p.value(), None);
427
428        // should parse only the first parameter and discard the rest of the input
429        let p = Parameter::parse("foo=bar baz").unwrap();
430        assert_eq!(p.key(), "foo".into());
431        assert_eq!(p.value(), Some("bar"));
432
433        // should return None on empty or whitespace inputs
434        assert!(Parameter::parse("").is_none());
435        assert!(Parameter::parse("   ").is_none());
436    }
437
438    #[test]
439    fn test_parameter_simple() {
440        let switch = param("foo");
441        assert_eq!(switch.key(), "foo".into());
442        assert_eq!(switch.value(), None);
443
444        let kv = param("bar=baz");
445        assert_eq!(kv.key(), "bar".into());
446        assert_eq!(kv.value(), Some("baz"));
447    }
448
449    #[test]
450    fn test_parameter_quoted() {
451        let p = param("foo=\"quoted value\"");
452        assert_eq!(p.value(), Some("quoted value"));
453
454        let p = param("foo=\"unclosed quotes");
455        assert_eq!(p.value(), Some("unclosed quotes"));
456
457        let p = param("foo=trailing_quotes\"");
458        assert_eq!(p.value(), Some("trailing_quotes"));
459
460        let outside_quoted = param("\"foo=quoted value\"");
461        let value_quoted = param("foo=\"quoted value\"");
462        assert_eq!(outside_quoted, value_quoted);
463    }
464
465    #[test]
466    fn test_parameter_display() {
467        // Basically this should always return the original data
468        // without modification.
469
470        // unquoted stays unquoted
471        assert_eq!(param("foo").to_string(), "foo");
472
473        // quoted stays quoted
474        assert_eq!(param("\"foo\"").to_string(), "\"foo\"");
475    }
476
477    #[test]
478    fn test_parameter_extra_whitespace() {
479        let p = param("  foo=bar  ");
480        assert_eq!(p.key(), "foo".into());
481        assert_eq!(p.value(), Some("bar"));
482    }
483
484    #[test]
485    fn test_parameter_internal_key_whitespace() {
486        // parse should only consume the first parameter
487        let p = Parameter::parse("foo bar=baz").unwrap();
488        assert_eq!(p.key(), "foo".into());
489        assert_eq!(p.value(), None);
490    }
491
492    #[test]
493    fn test_parameter_pathological() {
494        // valid things that certified insane people would do
495
496        // you can quote just the key part in a key-value param, but
497        // the end quote is actually part of the key as far as the
498        // kernel is concerned...
499        let p = param("\"foo\"=bar");
500        assert_eq!(p.key(), ParameterKey::from("foo\""));
501        assert_eq!(p.value(), Some("bar"));
502        // and it is definitely not equal to an unquoted foo ...
503        assert_ne!(p, param("foo=bar"));
504
505        // ... but if you close the quote immediately after the
506        // equals sign, it does get removed.
507        let p = param("\"foo=\"bar");
508        assert_eq!(p.key(), ParameterKey::from("foo"));
509        assert_eq!(p.value(), Some("bar"));
510        // ... so of course this makes sense ...
511        assert_eq!(p, param("foo=bar"));
512
513        // quotes only get stripped from the absolute ends of values
514        let p = param("foo=\"internal\"quotes\"are\"ok\"");
515        assert_eq!(p.value(), Some("internal\"quotes\"are\"ok"));
516    }
517
518    #[test]
519    fn test_parameter_equality() {
520        // substrings are not equal
521        let foo = param("foo");
522        let bar = param("foobar");
523        assert_ne!(foo, bar);
524        assert_ne!(bar, foo);
525
526        // dashes and underscores are treated equally
527        let dashes = param("a-delimited-param");
528        let underscores = param("a_delimited_param");
529        assert_eq!(dashes, underscores);
530
531        // same key, same values is equal
532        let dashes = param("a-delimited-param=same_values");
533        let underscores = param("a_delimited_param=same_values");
534        assert_eq!(dashes, underscores);
535
536        // same key, different values is not equal
537        let dashes = param("a-delimited-param=different_values");
538        let underscores = param("a_delimited_param=DiFfErEnT_valUEZ");
539        assert_ne!(dashes, underscores);
540
541        // mixed variants are never equal
542        let switch = param("same_key");
543        let keyvalue = param("same_key=but_with_a_value");
544        assert_ne!(switch, keyvalue);
545    }
546
547    #[test]
548    fn test_parameter_tryfrom() {
549        // ok switch
550        let p = bytes::Parameter::parse(b"foo").unwrap();
551        let utf = Parameter::try_from(p).unwrap();
552        assert_eq!(utf.key(), "foo".into());
553        assert_eq!(utf.value(), None);
554
555        // ok key/value
556        let p = bytes::Parameter::parse(b"foo=bar").unwrap();
557        let utf = Parameter::try_from(p).unwrap();
558        assert_eq!(utf.key(), "foo".into());
559        assert_eq!(utf.value(), Some("bar".into()));
560
561        // bad switch
562        let p = bytes::Parameter::parse(b"f\xffoo").unwrap();
563        let e = Parameter::try_from(p);
564        assert_eq!(
565            e.unwrap_err().to_string(),
566            "Parameter key is not valid UTF-8"
567        );
568
569        // bad key/value
570        let p = bytes::Parameter::parse(b"foo=b\xffar").unwrap();
571        let e = Parameter::try_from(p);
572        assert_eq!(
573            e.unwrap_err().to_string(),
574            "Parameter value is not valid UTF-8"
575        );
576    }
577
578    #[test]
579    fn test_kargs_simple() {
580        // example taken lovingly from:
581        // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/params.c?id=89748acdf226fd1a8775ff6fa2703f8412b286c8#n160
582        let kargs = Cmdline::from("foo=bar,bar2 baz=fuz wiz");
583        assert!(kargs.is_borrowed());
584        let mut iter = kargs.iter();
585
586        assert_eq!(iter.next(), Some(param("foo=bar,bar2")));
587        assert_eq!(iter.next(), Some(param("baz=fuz")));
588        assert_eq!(iter.next(), Some(param("wiz")));
589        assert_eq!(iter.next(), None);
590
591        // Test the find API
592        assert_eq!(kargs.find("foo").unwrap().value().unwrap(), "bar,bar2");
593        assert!(kargs.find("nothing").is_none());
594    }
595
596    #[test]
597    fn test_cmdline_default() {
598        let kargs: Cmdline = Default::default();
599        assert_eq!(kargs.iter().next(), None);
600    }
601
602    #[test]
603    fn test_cmdline_new() {
604        let kargs = Cmdline::new();
605        assert_eq!(kargs.iter().next(), None);
606        assert!(kargs.is_owned());
607
608        // Verify we can store it in an owned ('static) context
609        let _static_kargs: CmdlineOwned = Cmdline::new();
610    }
611
612    #[test]
613    fn test_kargs_simple_from_string() {
614        let kargs = Cmdline::from("foo=bar,bar2 baz=fuz wiz".to_string());
615        assert!(kargs.is_owned());
616        let mut iter = kargs.iter();
617
618        assert_eq!(iter.next(), Some(param("foo=bar,bar2")));
619        assert_eq!(iter.next(), Some(param("baz=fuz")));
620        assert_eq!(iter.next(), Some(param("wiz")));
621        assert_eq!(iter.next(), None);
622
623        // Test the find API
624        assert_eq!(kargs.find("foo").unwrap().value().unwrap(), "bar,bar2");
625        assert!(kargs.find("nothing").is_none());
626    }
627
628    #[test]
629    fn test_kargs_from_proc() {
630        let kargs = Cmdline::from_proc().unwrap();
631
632        // Not really a good way to test this other than assume
633        // there's at least one argument in /proc/cmdline wherever the
634        // tests are running
635        assert!(kargs.iter().count() > 0);
636    }
637
638    #[test]
639    fn test_kargs_find_dash_hyphen() {
640        let kargs = Cmdline::from("a-b=1 a_b=2");
641        // find should find the first one, which is a-b=1
642        let p = kargs.find("a_b").unwrap();
643        assert_eq!(p.key(), "a-b".into());
644        assert_eq!(p.value().unwrap(), "1");
645        let p = kargs.find("a-b").unwrap();
646        assert_eq!(p.key(), "a-b".into());
647        assert_eq!(p.value().unwrap(), "1");
648
649        let kargs = Cmdline::from("a_b=2 a-b=1");
650        // find should find the first one, which is a_b=2
651        let p = kargs.find("a_b").unwrap();
652        assert_eq!(p.key(), "a_b".into());
653        assert_eq!(p.value().unwrap(), "2");
654        let p = kargs.find("a-b").unwrap();
655        assert_eq!(p.key(), "a_b".into());
656        assert_eq!(p.value().unwrap(), "2");
657    }
658
659    #[test]
660    fn test_kargs_extra_whitespace() {
661        let kargs = Cmdline::from("  foo=bar    baz=fuz  wiz   ");
662        let mut iter = kargs.iter();
663
664        assert_eq!(iter.next(), Some(param("foo=bar")));
665        assert_eq!(iter.next(), Some(param("baz=fuz")));
666        assert_eq!(iter.next(), Some(param("wiz")));
667        assert_eq!(iter.next(), None);
668    }
669
670    #[test]
671    fn test_value_of() {
672        let kargs = Cmdline::from("foo=bar baz=qux switch");
673
674        // Test existing key with value
675        assert_eq!(kargs.value_of("foo"), Some("bar"));
676        assert_eq!(kargs.value_of("baz"), Some("qux"));
677
678        // Test key without value
679        assert_eq!(kargs.value_of("switch"), None);
680
681        // Test non-existent key
682        assert_eq!(kargs.value_of("missing"), None);
683
684        // Test dash/underscore equivalence
685        let kargs = Cmdline::from("dash-key=value1 under_key=value2");
686        assert_eq!(kargs.value_of("dash_key"), Some("value1"));
687        assert_eq!(kargs.value_of("under-key"), Some("value2"));
688    }
689
690    #[test]
691    fn test_require_value_of() {
692        let kargs = Cmdline::from("foo=bar baz=qux switch");
693
694        // Test existing key with value
695        assert_eq!(kargs.require_value_of("foo").unwrap(), "bar");
696        assert_eq!(kargs.require_value_of("baz").unwrap(), "qux");
697
698        // Test key without value should fail
699        let err = kargs.require_value_of("switch").unwrap_err();
700        assert!(
701            err.to_string()
702                .contains("Failed to find kernel argument 'switch'")
703        );
704
705        // Test non-existent key should fail
706        let err = kargs.require_value_of("missing").unwrap_err();
707        assert!(
708            err.to_string()
709                .contains("Failed to find kernel argument 'missing'")
710        );
711
712        // Test dash/underscore equivalence
713        let kargs = Cmdline::from("dash-key=value1 under_key=value2");
714        assert_eq!(kargs.require_value_of("dash_key").unwrap(), "value1");
715        assert_eq!(kargs.require_value_of("under-key").unwrap(), "value2");
716    }
717
718    #[test]
719    fn test_find_str() {
720        let kargs = Cmdline::from("foo=bar baz=qux switch rd.break");
721        let p = kargs.find("foo").unwrap();
722        assert_eq!(p, param("foo=bar"));
723        let p = kargs.find("rd.break").unwrap();
724        assert_eq!(p, param("rd.break"));
725        assert!(kargs.find("missing").is_none());
726    }
727
728    #[test]
729    fn test_find_all_str() {
730        let kargs = Cmdline::from("foo=bar rd.foo=a rd.bar=b rd.baz rd.qux=c notrd.val=d");
731        let mut rd_args: Vec<_> = kargs.find_all_starting_with("rd.").collect();
732        rd_args.sort_by(|a, b| a.key().cmp(&b.key()));
733        assert_eq!(rd_args.len(), 4);
734        assert_eq!(rd_args[0], param("rd.bar=b"));
735        assert_eq!(rd_args[1], param("rd.baz"));
736        assert_eq!(rd_args[2], param("rd.foo=a"));
737        assert_eq!(rd_args[3], param("rd.qux=c"));
738    }
739
740    #[test]
741    fn test_param_key_eq() {
742        let k1 = ParameterKey::from("a-b");
743        let k2 = ParameterKey::from("a_b");
744        assert_eq!(k1, k2);
745        let k1 = ParameterKey::from("a-b");
746        let k2 = ParameterKey::from("a-c");
747        assert_ne!(k1, k2);
748    }
749
750    #[test]
751    fn test_add() {
752        let mut kargs = Cmdline::from("console=tty0 console=ttyS1");
753
754        // add new parameter with duplicate key but different value
755        assert!(matches!(kargs.add(&param("console=ttyS2")), Action::Added));
756        let mut iter = kargs.iter();
757        assert_eq!(iter.next(), Some(param("console=tty0")));
758        assert_eq!(iter.next(), Some(param("console=ttyS1")));
759        assert_eq!(iter.next(), Some(param("console=ttyS2")));
760        assert_eq!(iter.next(), None);
761
762        // try to add exact duplicate - should return Existed
763        assert!(matches!(
764            kargs.add(&param("console=ttyS1")),
765            Action::Existed
766        ));
767        iter = kargs.iter();
768        assert_eq!(iter.next(), Some(param("console=tty0")));
769        assert_eq!(iter.next(), Some(param("console=ttyS1")));
770        assert_eq!(iter.next(), Some(param("console=ttyS2")));
771        assert_eq!(iter.next(), None);
772
773        // add completely new parameter
774        assert!(matches!(kargs.add(&param("quiet")), Action::Added));
775        iter = kargs.iter();
776        assert_eq!(iter.next(), Some(param("console=tty0")));
777        assert_eq!(iter.next(), Some(param("console=ttyS1")));
778        assert_eq!(iter.next(), Some(param("console=ttyS2")));
779        assert_eq!(iter.next(), Some(param("quiet")));
780        assert_eq!(iter.next(), None);
781    }
782
783    #[test]
784    fn test_add_empty_cmdline() {
785        let mut kargs = Cmdline::from("");
786        assert!(matches!(kargs.add(&param("foo")), Action::Added));
787        assert_eq!(&*kargs, "foo");
788    }
789
790    #[test]
791    fn test_add_or_modify() {
792        let mut kargs = Cmdline::from("foo=bar");
793
794        // add new
795        assert!(matches!(kargs.add_or_modify(&param("baz")), Action::Added));
796        let mut iter = kargs.iter();
797        assert_eq!(iter.next(), Some(param("foo=bar")));
798        assert_eq!(iter.next(), Some(param("baz")));
799        assert_eq!(iter.next(), None);
800
801        // modify existing
802        assert!(matches!(
803            kargs.add_or_modify(&param("foo=fuz")),
804            Action::Modified
805        ));
806        iter = kargs.iter();
807        assert_eq!(iter.next(), Some(param("foo=fuz")));
808        assert_eq!(iter.next(), Some(param("baz")));
809        assert_eq!(iter.next(), None);
810
811        // already exists with same value returns false and doesn't
812        // modify anything
813        assert!(matches!(
814            kargs.add_or_modify(&param("foo=fuz")),
815            Action::Existed
816        ));
817        iter = kargs.iter();
818        assert_eq!(iter.next(), Some(param("foo=fuz")));
819        assert_eq!(iter.next(), Some(param("baz")));
820        assert_eq!(iter.next(), None);
821    }
822
823    #[test]
824    fn test_add_or_modify_empty_cmdline() {
825        let mut kargs = Cmdline::from("");
826        assert!(matches!(kargs.add_or_modify(&param("foo")), Action::Added));
827        assert_eq!(&*kargs, "foo");
828    }
829
830    #[test]
831    fn test_add_or_modify_duplicate_parameters() {
832        let mut kargs = Cmdline::from("a=1 a=2");
833        assert!(matches!(
834            kargs.add_or_modify(&param("a=3")),
835            Action::Modified
836        ));
837        let mut iter = kargs.iter();
838        assert_eq!(iter.next(), Some(param("a=3")));
839        assert_eq!(iter.next(), None);
840    }
841
842    #[test]
843    fn test_remove() {
844        let mut kargs = Cmdline::from("foo bar baz");
845
846        // remove existing
847        assert!(kargs.remove(&"bar".into()));
848        let mut iter = kargs.iter();
849        assert_eq!(iter.next(), Some(param("foo")));
850        assert_eq!(iter.next(), Some(param("baz")));
851        assert_eq!(iter.next(), None);
852
853        // doesn't exist? returns false and doesn't modify anything
854        assert!(!kargs.remove(&"missing".into()));
855        iter = kargs.iter();
856        assert_eq!(iter.next(), Some(param("foo")));
857        assert_eq!(iter.next(), Some(param("baz")));
858        assert_eq!(iter.next(), None);
859    }
860
861    #[test]
862    fn test_remove_duplicates() {
863        let mut kargs = Cmdline::from("a=1 b=2 a=3");
864        assert!(kargs.remove(&"a".into()));
865        let mut iter = kargs.iter();
866        assert_eq!(iter.next(), Some(param("b=2")));
867        assert_eq!(iter.next(), None);
868    }
869
870    #[test]
871    fn test_remove_exact() {
872        let mut kargs = Cmdline::from("foo foo=bar foo=baz");
873
874        // remove existing
875        assert!(kargs.remove_exact(&param("foo=bar")));
876        let mut iter = kargs.iter();
877        assert_eq!(iter.next(), Some(param("foo")));
878        assert_eq!(iter.next(), Some(param("foo=baz")));
879        assert_eq!(iter.next(), None);
880
881        // doesn't exist? returns false and doesn't modify anything
882        assert!(!kargs.remove_exact(&param("foo=wuz")));
883        iter = kargs.iter();
884        assert_eq!(iter.next(), Some(param("foo")));
885        assert_eq!(iter.next(), Some(param("foo=baz")));
886        assert_eq!(iter.next(), None);
887    }
888
889    #[test]
890    fn test_extend() {
891        let mut kargs = Cmdline::from("foo=bar baz");
892        let other = Cmdline::from("qux=quux foo=updated");
893
894        kargs.extend(&other);
895
896        // Sanity check that the lifetimes of the two Cmdlines are not
897        // tied to each other.
898        drop(other);
899
900        // Should have preserved the original foo, added qux, baz
901        // unchanged, and added the second (duplicate key) foo
902        let mut iter = kargs.iter();
903        assert_eq!(iter.next(), Some(param("foo=bar")));
904        assert_eq!(iter.next(), Some(param("baz")));
905        assert_eq!(iter.next(), Some(param("qux=quux")));
906        assert_eq!(iter.next(), Some(param("foo=updated")));
907        assert_eq!(iter.next(), None);
908    }
909
910    #[test]
911    fn test_extend_empty() {
912        let mut kargs = Cmdline::from("");
913        let other = Cmdline::from("foo=bar baz");
914
915        kargs.extend(&other);
916
917        let mut iter = kargs.iter();
918        assert_eq!(iter.next(), Some(param("foo=bar")));
919        assert_eq!(iter.next(), Some(param("baz")));
920        assert_eq!(iter.next(), None);
921    }
922
923    #[test]
924    fn test_into_iterator() {
925        let kargs = Cmdline::from("foo=bar baz=qux wiz");
926        let params: Vec<_> = (&kargs).into_iter().collect();
927
928        assert_eq!(params.len(), 3);
929        assert_eq!(params[0], param("foo=bar"));
930        assert_eq!(params[1], param("baz=qux"));
931        assert_eq!(params[2], param("wiz"));
932    }
933
934    #[test]
935    fn test_cmdline_eq() {
936        // Ordering, quoting, and the whole dash-underscore
937        // equivalence thing shouldn't affect whether these are
938        // semantically equal
939        assert_eq!(
940            Cmdline::from("foo bar-with-delim=\"with spaces\""),
941            Cmdline::from("\"bar_with_delim=with spaces\" foo")
942        );
943
944        // Uneven lengths are not equal even if the parameters are. Or
945        // to put it another way, duplicate parameters break equality.
946        // Check with both orderings.
947        assert_ne!(Cmdline::from("foo"), Cmdline::from("foo foo"));
948        assert_ne!(Cmdline::from("foo foo"), Cmdline::from("foo"));
949
950        // Equal lengths but differing duplicates are also not equal
951        assert_ne!(Cmdline::from("a a b"), Cmdline::from("a b b"));
952    }
953}