Skip to main content

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