1use 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#[derive(Clone, Debug, Default, Serialize, Deserialize)]
21pub struct Cmdline<'a>(Cow<'a, [u8]>);
22
23pub type CmdlineOwned = Cmdline<'static>;
25
26impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a T> for Cmdline<'a> {
27 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 fn from(input: Vec<u8>) -> Self {
56 Self(Cow::Owned(input))
57 }
58}
59
60#[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#[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 pub fn new() -> CmdlineOwned {
112 Cmdline::default()
113 }
114
115 pub fn from_proc() -> Result<Self> {
119 Ok(Self(Cow::Owned(std::fs::read("/proc/cmdline")?)))
120 }
121
122 pub fn iter(&'a self) -> CmdlineIter<'a> {
128 CmdlineIter(self.iter_bytes())
129 }
130
131 pub fn iter_bytes(&self) -> CmdlineIterBytes<'_> {
136 CmdlineIterBytes(&self.0)
137 }
138
139 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 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 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 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 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 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 pub fn add(&mut self, param: &Parameter) -> Action {
218 for p in self.iter() {
220 if p == *param {
221 return Action::Existed;
223 }
224 }
225
226 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 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 if p != *param {
262 modified = true;
263 }
264 new_params.push(param.parameter);
265 } else {
266 modified = true;
269 }
270 seen_key = true;
271 } else {
272 new_params.push(p.parameter);
273 }
274 }
275
276 if !seen_key {
277 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 Action::Existed
295 }
296 }
297
298 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 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 for param in iter {
373 self.add(¶m);
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#[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 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 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#[derive(Clone, Debug)]
457pub struct Parameter<'a> {
458 parameter: &'a [u8],
460 key: ParameterKey<'a>,
462 value: Option<&'a [u8]>,
464}
465
466impl<'a> Parameter<'a> {
467 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 fn parse_internal(input: &'a [u8]) -> Option<Self> {
487 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 value = &value[1..];
506
507 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 pub fn key(&self) -> ParameterKey<'a> {
524 self.key.clone()
525 }
526
527 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 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 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 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 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 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 let p = param("\"foo\"=bar");
651 assert_eq!(p.key.0, b"foo\"");
652 assert_eq!(p.value, Some(b"bar".as_slice()));
653 assert_ne!(p, param("foo=bar"));
655
656 let p = param("\"foo=\"bar");
659 assert_eq!(p.key.0, b"foo");
660 assert_eq!(p.value, Some(b"bar".as_slice()));
661 assert_eq!(p, param("foo=bar"));
663
664 let p = param("foo=\"internal\"quotes\"are\"ok\"");
666 assert_eq!(p.value, Some(b"internal\"quotes\"are\"ok".as_slice()));
667
668 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 let foo = param("foo");
683 let bar = param("foobar");
684 assert_ne!(foo, bar);
685 assert_ne!(bar, foo);
686
687 let dashes = param("a-delimited-param");
689 let underscores = param("a_delimited_param");
690 assert_eq!(dashes, underscores);
691
692 let dashes = param("a-delimited-param=same_values");
694 let underscores = param("a_delimited_param=same_values");
695 assert_eq!(dashes, underscores);
696
697 let dashes = param("a-delimited-param=different_values");
699 let underscores = param("a_delimited_param=DiFfErEnT_valUEZ");
700 assert_ne!(dashes, underscores);
701
702 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 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 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 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 assert_eq!(
758 kargs.find_utf8("foo").unwrap().unwrap().value().unwrap(),
759 "bar,bar2"
760 );
761
762 assert!(kargs.find_utf8("nothing").unwrap().is_none());
764
765 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 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 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 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 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 assert_eq!(kargs.value_of("switch"), None);
825
826 assert_eq!(kargs.value_of("missing"), None);
828
829 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 assert_eq!(kargs.require_value_of("foo").unwrap(), b"bar");
841 assert_eq!(kargs.require_value_of("baz").unwrap(), b"qux");
842
843 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 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 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 assert!(matches!(kargs.add(¶m("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 assert!(matches!(
890 kargs.add(¶m("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 assert!(matches!(kargs.add(¶m("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(¶m("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 assert!(matches!(kargs.add_or_modify(¶m("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 assert!(matches!(
929 kargs.add_or_modify(¶m("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 assert!(matches!(
940 kargs.add_or_modify(¶m("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(¶m("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(¶m("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 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 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 assert!(kargs.remove_exact(¶m("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 assert!(!kargs.remove_exact(¶m("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 drop(other);
1025
1026 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 assert_eq!(
1116 Cmdline::from("foo bar-with-delim=\"with spaces\""),
1117 Cmdline::from("\"bar_with_delim=with spaces\" foo")
1118 );
1119
1120 assert_ne!(Cmdline::from("foo"), Cmdline::from("foo foo"));
1124 assert_ne!(Cmdline::from("foo foo"), Cmdline::from("foo"));
1125
1126 assert_ne!(Cmdline::from("a a b"), Cmdline::from("a b b"));
1128 }
1129}