Skip to main content

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