bootc_kernel_cmdline/
bytes.rs

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