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::{bytes, Action};
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!(err
701            .to_string()
702            .contains("Failed to find kernel argument 'switch'"));
703
704        // Test non-existent key should fail
705        let err = kargs.require_value_of("missing").unwrap_err();
706        assert!(err
707            .to_string()
708            .contains("Failed to find kernel argument 'missing'"));
709
710        // Test dash/underscore equivalence
711        let kargs = Cmdline::from("dash-key=value1 under_key=value2");
712        assert_eq!(kargs.require_value_of("dash_key").unwrap(), "value1");
713        assert_eq!(kargs.require_value_of("under-key").unwrap(), "value2");
714    }
715
716    #[test]
717    fn test_find_str() {
718        let kargs = Cmdline::from("foo=bar baz=qux switch rd.break");
719        let p = kargs.find("foo").unwrap();
720        assert_eq!(p, param("foo=bar"));
721        let p = kargs.find("rd.break").unwrap();
722        assert_eq!(p, param("rd.break"));
723        assert!(kargs.find("missing").is_none());
724    }
725
726    #[test]
727    fn test_find_all_str() {
728        let kargs = Cmdline::from("foo=bar rd.foo=a rd.bar=b rd.baz rd.qux=c notrd.val=d");
729        let mut rd_args: Vec<_> = kargs.find_all_starting_with("rd.").collect();
730        rd_args.sort_by(|a, b| a.key().cmp(&b.key()));
731        assert_eq!(rd_args.len(), 4);
732        assert_eq!(rd_args[0], param("rd.bar=b"));
733        assert_eq!(rd_args[1], param("rd.baz"));
734        assert_eq!(rd_args[2], param("rd.foo=a"));
735        assert_eq!(rd_args[3], param("rd.qux=c"));
736    }
737
738    #[test]
739    fn test_param_key_eq() {
740        let k1 = ParameterKey::from("a-b");
741        let k2 = ParameterKey::from("a_b");
742        assert_eq!(k1, k2);
743        let k1 = ParameterKey::from("a-b");
744        let k2 = ParameterKey::from("a-c");
745        assert_ne!(k1, k2);
746    }
747
748    #[test]
749    fn test_add() {
750        let mut kargs = Cmdline::from("console=tty0 console=ttyS1");
751
752        // add new parameter with duplicate key but different value
753        assert!(matches!(kargs.add(&param("console=ttyS2")), Action::Added));
754        let mut iter = kargs.iter();
755        assert_eq!(iter.next(), Some(param("console=tty0")));
756        assert_eq!(iter.next(), Some(param("console=ttyS1")));
757        assert_eq!(iter.next(), Some(param("console=ttyS2")));
758        assert_eq!(iter.next(), None);
759
760        // try to add exact duplicate - should return Existed
761        assert!(matches!(
762            kargs.add(&param("console=ttyS1")),
763            Action::Existed
764        ));
765        iter = kargs.iter();
766        assert_eq!(iter.next(), Some(param("console=tty0")));
767        assert_eq!(iter.next(), Some(param("console=ttyS1")));
768        assert_eq!(iter.next(), Some(param("console=ttyS2")));
769        assert_eq!(iter.next(), None);
770
771        // add completely new parameter
772        assert!(matches!(kargs.add(&param("quiet")), Action::Added));
773        iter = kargs.iter();
774        assert_eq!(iter.next(), Some(param("console=tty0")));
775        assert_eq!(iter.next(), Some(param("console=ttyS1")));
776        assert_eq!(iter.next(), Some(param("console=ttyS2")));
777        assert_eq!(iter.next(), Some(param("quiet")));
778        assert_eq!(iter.next(), None);
779    }
780
781    #[test]
782    fn test_add_empty_cmdline() {
783        let mut kargs = Cmdline::from("");
784        assert!(matches!(kargs.add(&param("foo")), Action::Added));
785        assert_eq!(&*kargs, "foo");
786    }
787
788    #[test]
789    fn test_add_or_modify() {
790        let mut kargs = Cmdline::from("foo=bar");
791
792        // add new
793        assert!(matches!(kargs.add_or_modify(&param("baz")), Action::Added));
794        let mut iter = kargs.iter();
795        assert_eq!(iter.next(), Some(param("foo=bar")));
796        assert_eq!(iter.next(), Some(param("baz")));
797        assert_eq!(iter.next(), None);
798
799        // modify existing
800        assert!(matches!(
801            kargs.add_or_modify(&param("foo=fuz")),
802            Action::Modified
803        ));
804        iter = kargs.iter();
805        assert_eq!(iter.next(), Some(param("foo=fuz")));
806        assert_eq!(iter.next(), Some(param("baz")));
807        assert_eq!(iter.next(), None);
808
809        // already exists with same value returns false and doesn't
810        // modify anything
811        assert!(matches!(
812            kargs.add_or_modify(&param("foo=fuz")),
813            Action::Existed
814        ));
815        iter = kargs.iter();
816        assert_eq!(iter.next(), Some(param("foo=fuz")));
817        assert_eq!(iter.next(), Some(param("baz")));
818        assert_eq!(iter.next(), None);
819    }
820
821    #[test]
822    fn test_add_or_modify_empty_cmdline() {
823        let mut kargs = Cmdline::from("");
824        assert!(matches!(kargs.add_or_modify(&param("foo")), Action::Added));
825        assert_eq!(&*kargs, "foo");
826    }
827
828    #[test]
829    fn test_add_or_modify_duplicate_parameters() {
830        let mut kargs = Cmdline::from("a=1 a=2");
831        assert!(matches!(
832            kargs.add_or_modify(&param("a=3")),
833            Action::Modified
834        ));
835        let mut iter = kargs.iter();
836        assert_eq!(iter.next(), Some(param("a=3")));
837        assert_eq!(iter.next(), None);
838    }
839
840    #[test]
841    fn test_remove() {
842        let mut kargs = Cmdline::from("foo bar baz");
843
844        // remove existing
845        assert!(kargs.remove(&"bar".into()));
846        let mut iter = kargs.iter();
847        assert_eq!(iter.next(), Some(param("foo")));
848        assert_eq!(iter.next(), Some(param("baz")));
849        assert_eq!(iter.next(), None);
850
851        // doesn't exist? returns false and doesn't modify anything
852        assert!(!kargs.remove(&"missing".into()));
853        iter = kargs.iter();
854        assert_eq!(iter.next(), Some(param("foo")));
855        assert_eq!(iter.next(), Some(param("baz")));
856        assert_eq!(iter.next(), None);
857    }
858
859    #[test]
860    fn test_remove_duplicates() {
861        let mut kargs = Cmdline::from("a=1 b=2 a=3");
862        assert!(kargs.remove(&"a".into()));
863        let mut iter = kargs.iter();
864        assert_eq!(iter.next(), Some(param("b=2")));
865        assert_eq!(iter.next(), None);
866    }
867
868    #[test]
869    fn test_remove_exact() {
870        let mut kargs = Cmdline::from("foo foo=bar foo=baz");
871
872        // remove existing
873        assert!(kargs.remove_exact(&param("foo=bar")));
874        let mut iter = kargs.iter();
875        assert_eq!(iter.next(), Some(param("foo")));
876        assert_eq!(iter.next(), Some(param("foo=baz")));
877        assert_eq!(iter.next(), None);
878
879        // doesn't exist? returns false and doesn't modify anything
880        assert!(!kargs.remove_exact(&param("foo=wuz")));
881        iter = kargs.iter();
882        assert_eq!(iter.next(), Some(param("foo")));
883        assert_eq!(iter.next(), Some(param("foo=baz")));
884        assert_eq!(iter.next(), None);
885    }
886
887    #[test]
888    fn test_extend() {
889        let mut kargs = Cmdline::from("foo=bar baz");
890        let other = Cmdline::from("qux=quux foo=updated");
891
892        kargs.extend(&other);
893
894        // Sanity check that the lifetimes of the two Cmdlines are not
895        // tied to each other.
896        drop(other);
897
898        // Should have preserved the original foo, added qux, baz
899        // unchanged, and added the second (duplicate key) foo
900        let mut iter = kargs.iter();
901        assert_eq!(iter.next(), Some(param("foo=bar")));
902        assert_eq!(iter.next(), Some(param("baz")));
903        assert_eq!(iter.next(), Some(param("qux=quux")));
904        assert_eq!(iter.next(), Some(param("foo=updated")));
905        assert_eq!(iter.next(), None);
906    }
907
908    #[test]
909    fn test_extend_empty() {
910        let mut kargs = Cmdline::from("");
911        let other = Cmdline::from("foo=bar baz");
912
913        kargs.extend(&other);
914
915        let mut iter = kargs.iter();
916        assert_eq!(iter.next(), Some(param("foo=bar")));
917        assert_eq!(iter.next(), Some(param("baz")));
918        assert_eq!(iter.next(), None);
919    }
920
921    #[test]
922    fn test_into_iterator() {
923        let kargs = Cmdline::from("foo=bar baz=qux wiz");
924        let params: Vec<_> = (&kargs).into_iter().collect();
925
926        assert_eq!(params.len(), 3);
927        assert_eq!(params[0], param("foo=bar"));
928        assert_eq!(params[1], param("baz=qux"));
929        assert_eq!(params[2], param("wiz"));
930    }
931
932    #[test]
933    fn test_cmdline_eq() {
934        // Ordering, quoting, and the whole dash-underscore
935        // equivalence thing shouldn't affect whether these are
936        // semantically equal
937        assert_eq!(
938            Cmdline::from("foo bar-with-delim=\"with spaces\""),
939            Cmdline::from("\"bar_with_delim=with spaces\" foo")
940        );
941
942        // Uneven lengths are not equal even if the parameters are. Or
943        // to put it another way, duplicate parameters break equality.
944        // Check with both orderings.
945        assert_ne!(Cmdline::from("foo"), Cmdline::from("foo foo"));
946        assert_ne!(Cmdline::from("foo foo"), Cmdline::from("foo"));
947
948        // Equal lengths but differing duplicates are also not equal
949        assert_ne!(Cmdline::from("a a b"), Cmdline::from("a b b"));
950    }
951}