1use std::ffi::CString;
12use std::fmt;
13use std::result;
14
15use vm_memory::{Address, GuestAddress, GuestUsize};
16
17const INIT_ARGS_SEPARATOR: &str = " -- ";
18
19#[derive(Debug, PartialEq, Eq)]
21pub enum Error {
22 NullTerminator,
24 NoBootArgsInserted,
26 InvalidCapacity,
28 InvalidAscii,
30 HasSpace,
32 HasEquals,
34 MissingVal(String),
36 MmioSize,
38 TooLarge,
40 NoQuoteSpace,
42 InvalidQuote,
44}
45
46impl fmt::Display for Error {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 match *self {
49 Error::NullTerminator => {
50 write!(f, "Null terminator detected in the command line structure.")
51 }
52 Error::NoBootArgsInserted => write!(f, "Cmdline cannot contain only init args."),
53 Error::InvalidCapacity => write!(f, "Invalid cmdline capacity provided."),
54 Error::InvalidAscii => write!(f, "String contains a non-printable ASCII character."),
55 Error::HasSpace => write!(f, "String contains a space."),
56 Error::HasEquals => write!(f, "String contains an equals sign."),
57 Error::MissingVal(ref k) => write!(f, "Missing value for key {}.", k),
58 Error::MmioSize => write!(
59 f,
60 "0-sized virtio MMIO device passed to the kernel command line builder."
61 ),
62 Error::TooLarge => write!(f, "Inserting string would make command line too long."),
63 Error::NoQuoteSpace => write!(
64 f,
65 "Value that contains spaces need to be surrounded by quotes"
66 ),
67 Error::InvalidQuote => write!(f, "Double quote can not be in the middle of the value"),
68 }
69 }
70}
71
72impl std::error::Error for Error {}
73
74pub type Result<T> = result::Result<T, Error>;
78
79fn valid_char(c: char) -> bool {
80 matches!(c, ' '..='~')
81}
82
83fn valid_str(s: &str) -> Result<()> {
84 if s.chars().all(valid_char) {
85 Ok(())
86 } else {
87 Err(Error::InvalidAscii)
88 }
89}
90
91fn is_quoted(s: &str) -> bool {
92 if s.len() < 2 {
93 return false;
94 }
95 if let Some(first_char) = s.chars().next() {
96 if let Some(last_char) = s.chars().last() {
97 return first_char == '"' && last_char == '"';
98 }
99 }
100 false
101}
102
103fn contains_double_quotes(s: &str) -> bool {
104 if s.len() < 3 {
105 return false;
106 }
107 s.chars().skip(1).take(s.len() - 2).any(|c| c == '"')
108}
109
110fn valid_key(s: &str) -> Result<()> {
111 if !s.chars().all(valid_char) {
112 Err(Error::InvalidAscii)
113 } else if s.contains(' ') {
114 Err(Error::HasSpace)
115 } else if s.contains('=') {
116 Err(Error::HasEquals)
117 } else {
118 Ok(())
119 }
120}
121
122fn valid_value(s: &str) -> Result<()> {
123 if !s.chars().all(valid_char) {
124 Err(Error::InvalidAscii)
125 } else if contains_double_quotes(s) {
126 Err(Error::InvalidQuote)
127 } else if s.contains(' ') && !is_quoted(s) {
128 Err(Error::NoQuoteSpace)
129 } else {
130 Ok(())
131 }
132}
133
134#[derive(Clone, Debug)]
146pub struct Cmdline {
147 boot_args: String,
148 init_args: String,
149 capacity: usize,
150}
151
152impl Cmdline {
153 pub fn new(capacity: usize) -> Result<Cmdline> {
167 if capacity == 0 {
168 return Err(Error::InvalidCapacity);
169 }
170
171 Ok(Cmdline {
172 boot_args: String::new(),
173 init_args: String::new(),
174 capacity,
175 })
176 }
177
178 pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
195 let k = key.as_ref();
196 let v = val.as_ref();
197
198 valid_key(k)?;
199 valid_value(v)?;
200
201 let kv_str = format!("{}={}", k, v);
202
203 self.insert_str(kv_str)
204 }
205
206 pub fn insert_multiple<T: AsRef<str>>(&mut self, key: T, vals: &[T]) -> Result<()> {
227 let k = key.as_ref();
228
229 valid_key(k)?;
230 if vals.is_empty() {
231 return Err(Error::MissingVal(k.to_string()));
232 }
233
234 let kv_str = format!(
235 "{}={}",
236 k,
237 vals.iter()
238 .map(|v| -> Result<&str> {
239 valid_value(v.as_ref())?;
240 Ok(v.as_ref())
241 })
242 .collect::<Result<Vec<&str>>>()?
243 .join(",")
244 );
245
246 self.insert_str(kv_str)
247 }
248
249 pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
266 let s = slug.as_ref().trim();
269 valid_str(s)?;
270
271 let mut cmdline_size = self.get_null_terminated_representation_size();
274
275 if !self.boot_args.is_empty() {
277 cmdline_size = cmdline_size.checked_add(1).ok_or(Error::TooLarge)?;
278 }
279
280 cmdline_size = cmdline_size.checked_add(s.len()).ok_or(Error::TooLarge)?;
282
283 if cmdline_size > self.capacity {
284 return Err(Error::TooLarge);
285 }
286
287 if !self.boot_args.is_empty() {
289 self.boot_args.push(' ');
290 }
291
292 self.boot_args.push_str(s);
293
294 Ok(())
295 }
296
297 pub fn insert_init_args<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
318 let s = slug.as_ref().trim();
321 valid_str(s)?;
322
323 let mut cmdline_size = self.get_null_terminated_representation_size();
326
327 cmdline_size = cmdline_size
329 .checked_add(if self.init_args.is_empty() {
330 INIT_ARGS_SEPARATOR.len()
331 } else {
332 1
333 })
334 .ok_or(Error::TooLarge)?;
335
336 cmdline_size = cmdline_size.checked_add(s.len()).ok_or(Error::TooLarge)?;
338
339 if cmdline_size > self.capacity {
340 return Err(Error::TooLarge);
341 }
342
343 if !self.init_args.is_empty() {
345 self.init_args.push(' ');
346 }
347
348 self.init_args.push_str(s);
349
350 Ok(())
351 }
352
353 fn get_null_terminated_representation_size(&self) -> usize {
354 let mut cmdline_size = self.boot_args.len() + 1; if !self.init_args.is_empty() {
359 cmdline_size += INIT_ARGS_SEPARATOR.len() + self.init_args.len();
360 }
361
362 cmdline_size
363 }
364
365 pub fn as_cstring(&self) -> Result<CString> {
385 if self.boot_args.is_empty() && self.init_args.is_empty() {
386 CString::new("".to_string()).map_err(|_| Error::NullTerminator)
387 } else if self.boot_args.is_empty() {
388 Err(Error::NoBootArgsInserted)
389 } else if self.init_args.is_empty() {
390 CString::new(self.boot_args.to_string()).map_err(|_| Error::NullTerminator)
391 } else {
392 CString::new(format!(
393 "{}{}{}",
394 self.boot_args, INIT_ARGS_SEPARATOR, self.init_args
395 ))
396 .map_err(|_| Error::NullTerminator)
397 }
398 }
399
400 pub fn add_virtio_mmio_device(
432 &mut self,
433 size: GuestUsize,
434 baseaddr: GuestAddress,
435 irq: u32,
436 id: Option<u32>,
437 ) -> Result<()> {
438 if size == 0 {
439 return Err(Error::MmioSize);
440 }
441
442 let mut device_str = format!(
443 "virtio_mmio.device={}@0x{:x?}:{}",
444 Self::guestusize_to_str(size),
445 baseaddr.raw_value(),
446 irq
447 );
448 if let Some(id) = id {
449 device_str.push_str(format!(":{}", id).as_str());
450 }
451 self.insert_str(&device_str)
452 }
453
454 fn guestusize_to_str(size: GuestUsize) -> String {
456 const KB_MULT: u64 = 1 << 10;
457 const MB_MULT: u64 = KB_MULT << 10;
458 const GB_MULT: u64 = MB_MULT << 10;
459
460 if size.is_multiple_of(GB_MULT) {
461 return format!("{}G", size / GB_MULT);
462 }
463 if size.is_multiple_of(MB_MULT) {
464 return format!("{}M", size / MB_MULT);
465 }
466 if size.is_multiple_of(KB_MULT) {
467 return format!("{}K", size / KB_MULT);
468 }
469 size.to_string()
470 }
471
472 fn check_outside_double_quotes(slug: &str) -> bool {
473 slug.matches('\"').count().is_multiple_of(2)
474 }
475
476 pub fn try_from(cmdline_raw: &str, capacity: usize) -> Result<Cmdline> {
504 if capacity == 0 {
509 return Err(Error::InvalidCapacity);
510 }
511
512 let (mut boot_args, mut init_args) = match cmdline_raw
517 .match_indices(INIT_ARGS_SEPARATOR)
518 .find(|&separator_occurrence| {
519 Self::check_outside_double_quotes(&cmdline_raw[..(separator_occurrence.0)])
520 }) {
521 None => (cmdline_raw, ""),
522 Some((delimiter_index, _)) => (
523 &cmdline_raw[..delimiter_index],
524 &cmdline_raw[(delimiter_index + INIT_ARGS_SEPARATOR.len())..],
529 ),
530 };
531
532 boot_args = boot_args.trim();
533 init_args = init_args.trim();
534
535 let mut cmdline_size = boot_args.len().checked_add(1).ok_or(Error::TooLarge)?;
538
539 if !init_args.is_empty() {
540 cmdline_size = cmdline_size
541 .checked_add(INIT_ARGS_SEPARATOR.len())
542 .ok_or(Error::TooLarge)?;
543
544 cmdline_size = cmdline_size
545 .checked_add(init_args.len())
546 .ok_or(Error::TooLarge)?;
547 }
548
549 if cmdline_size > capacity {
550 return Err(Error::InvalidCapacity);
551 }
552
553 Ok(Cmdline {
554 boot_args: boot_args.to_string(),
555 init_args: init_args.to_string(),
556 capacity,
557 })
558 }
559}
560
561impl TryFrom<Cmdline> for Vec<u8> {
562 type Error = Error;
563
564 fn try_from(cmdline: Cmdline) -> result::Result<Self, Self::Error> {
565 cmdline
566 .as_cstring()
567 .map(|cmdline_cstring| cmdline_cstring.into_bytes_with_nul())
568 }
569}
570
571impl PartialEq for Cmdline {
572 fn eq(&self, other: &Self) -> bool {
573 self.as_cstring() == other.as_cstring()
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use std::ffi::CString;
581
582 const CMDLINE_MAX_SIZE: usize = 4096;
583
584 #[test]
585 fn test_insert_hello_world() {
586 let mut cl = Cmdline::new(100).unwrap();
587 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
588 assert!(cl.insert("hello", "world").is_ok());
589 assert_eq!(
590 cl.as_cstring().unwrap().as_bytes_with_nul(),
591 b"hello=world\0"
592 );
593 }
594
595 #[test]
596 fn test_insert_multi() {
597 let mut cl = Cmdline::new(100).unwrap();
598 assert!(cl.insert("hello", "world").is_ok());
599 assert!(cl.insert("foo", "bar").is_ok());
600 assert_eq!(
601 cl.as_cstring().unwrap().as_bytes_with_nul(),
602 b"hello=world foo=bar\0"
603 );
604 }
605
606 #[test]
607 fn test_insert_space() {
608 let mut cl = Cmdline::new(100).unwrap();
609 assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
610 assert_eq!(cl.insert("a", "b "), Err(Error::NoQuoteSpace));
611 assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
612 assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
613 assert_eq!(cl.insert("a", "hello \"world"), Err(Error::InvalidQuote));
614 assert_eq!(
615 cl.insert("a", "\"foor bar\" \"foor bar\""),
616 Err(Error::InvalidQuote)
617 );
618 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
619 assert!(cl.insert("a", "\"b b\"").is_ok());
620 assert!(cl.insert("c", "\" d\"").is_ok());
621 assert_eq!(
622 cl.as_cstring().unwrap().as_bytes_with_nul(),
623 b"a=\"b b\" c=\" d\"\0"
624 );
625 }
626
627 #[test]
628 fn test_insert_equals() {
629 let mut cl = Cmdline::new(100).unwrap();
630 assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
631 assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
632 assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
633 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
634 }
635
636 #[test]
637 fn test_insert_emoji() {
638 let mut cl = Cmdline::new(100).unwrap();
639 assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
640 assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
641 assert_eq!(cl.insert_str("heart=💖"), Err(Error::InvalidAscii));
642 assert_eq!(
643 cl.insert_multiple("💖", &["heart", "love"]),
644 Err(Error::InvalidAscii)
645 );
646 assert_eq!(
647 cl.insert_multiple("heart", &["💖", "love"]),
648 Err(Error::InvalidAscii)
649 );
650 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
651 }
652
653 #[test]
654 fn test_insert_string() {
655 let mut cl = Cmdline::new(13).unwrap();
656 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
657 assert!(cl.insert_str("noapic").is_ok());
658 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"noapic\0");
659 assert!(cl.insert_str("nopci").is_ok());
660 assert_eq!(
661 cl.as_cstring().unwrap().as_bytes_with_nul(),
662 b"noapic nopci\0"
663 );
664 }
665
666 #[test]
667 fn test_insert_too_large() {
668 let mut cl = Cmdline::new(4).unwrap();
669 assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
670 assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
671 assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
672 assert!(cl.insert("a", "b").is_ok());
673 assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
674 assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
675 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"a=b\0");
676
677 let mut cl = Cmdline::new(10).unwrap();
678 assert!(cl.insert("ab", "ba").is_ok()); assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); assert!(cl.insert("c", "d").is_ok()); let mut cl = Cmdline::new(11).unwrap();
683 assert!(cl.insert("ab", "ba").is_ok()); assert_eq!(cl.insert_init_args("da"), Err(Error::TooLarge)); assert!(cl.insert_init_args("d").is_ok()); let mut cl = Cmdline::new(20).unwrap();
688 assert!(cl.insert("ab", "ba").is_ok()); assert!(cl.insert_init_args("da").is_ok()); assert_eq!(cl.insert_init_args("abcdabcd"), Err(Error::TooLarge)); assert!(cl.insert_init_args("abcdabc").is_ok()); }
693
694 #[test]
695 fn test_add_virtio_mmio_device() {
696 let mut cl = Cmdline::new(5).unwrap();
697 assert_eq!(
698 cl.add_virtio_mmio_device(0, GuestAddress(0), 0, None),
699 Err(Error::MmioSize)
700 );
701 assert_eq!(
702 cl.add_virtio_mmio_device(1, GuestAddress(0), 0, None),
703 Err(Error::TooLarge)
704 );
705
706 let mut cl = Cmdline::new(150).unwrap();
707 assert!(cl
708 .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
709 .is_ok());
710 let mut expected_str = "virtio_mmio.device=1@0x0:1".to_string();
711 assert_eq!(
712 cl.as_cstring().unwrap(),
713 CString::new(expected_str.as_bytes()).unwrap()
714 );
715
716 assert!(cl
717 .add_virtio_mmio_device(2 << 10, GuestAddress(0x100), 2, None)
718 .is_ok());
719 expected_str.push_str(" virtio_mmio.device=2K@0x100:2");
720 assert_eq!(
721 cl.as_cstring().unwrap(),
722 CString::new(expected_str.as_bytes()).unwrap()
723 );
724
725 assert!(cl
726 .add_virtio_mmio_device(3 << 20, GuestAddress(0x1000), 3, None)
727 .is_ok());
728 expected_str.push_str(" virtio_mmio.device=3M@0x1000:3");
729 assert_eq!(
730 cl.as_cstring().unwrap(),
731 CString::new(expected_str.as_bytes()).unwrap()
732 );
733
734 assert!(cl
735 .add_virtio_mmio_device(4 << 30, GuestAddress(0x0001_0000), 4, Some(42))
736 .is_ok());
737 expected_str.push_str(" virtio_mmio.device=4G@0x10000:4:42");
738 assert_eq!(
739 cl.as_cstring().unwrap(),
740 CString::new(expected_str.as_bytes()).unwrap()
741 );
742 }
743
744 #[test]
745 fn test_insert_kv() {
746 let mut cl = Cmdline::new(10).unwrap();
747
748 let no_vals: Vec<&str> = vec![];
749 assert_eq!(cl.insert_multiple("foo=", &no_vals), Err(Error::HasEquals));
750 assert_eq!(
751 cl.insert_multiple("foo", &no_vals),
752 Err(Error::MissingVal("foo".to_string()))
753 );
754 assert_eq!(
755 cl.insert_multiple("foo", &["bar "]),
756 Err(Error::NoQuoteSpace)
757 );
758 assert_eq!(
759 cl.insert_multiple("foo", &["bar", "baz"]),
760 Err(Error::TooLarge)
761 );
762
763 let mut cl = Cmdline::new(100).unwrap();
764 assert!(cl.insert_multiple("foo", &["bar"]).is_ok());
765 assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
766
767 let mut cl = Cmdline::new(100).unwrap();
768 assert!(cl.insert_multiple("foo", &["bar", "baz"]).is_ok());
769 assert_eq!(
770 cl.as_cstring().unwrap().as_bytes_with_nul(),
771 b"foo=bar,baz\0"
772 );
773 }
774
775 #[test]
776 fn test_try_from_cmdline_for_vec() {
777 let cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
778 assert_eq!(Vec::try_from(cl).unwrap(), vec![b'\0']);
779
780 let cl = Cmdline::try_from("foo", CMDLINE_MAX_SIZE).unwrap();
781 assert_eq!(Vec::try_from(cl).unwrap(), vec![b'f', b'o', b'o', b'\0']);
782
783 let mut cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
784 cl.insert_init_args("foo--bar").unwrap();
785 assert_eq!(Vec::try_from(cl), Err(Error::NoBootArgsInserted));
786 }
787
788 #[test]
789 fn test_partial_eq() {
790 let mut c1 = Cmdline::new(20).unwrap();
791 let mut c2 = Cmdline::new(30).unwrap();
792
793 c1.insert_str("hello world!").unwrap();
794 c2.insert_str("hello").unwrap();
795 assert_ne!(c1, c2);
796
797 c2.insert_str("world!").unwrap();
799 assert_eq!(c1, c2);
800
801 let mut cl1 = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
802 let mut cl2 = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
803
804 assert_eq!(cl1, cl2);
805 assert!(cl1
806 .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
807 .is_ok());
808 assert_ne!(cl1, cl2);
809 assert!(cl2
810 .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
811 .is_ok());
812 assert_eq!(cl1, cl2);
813 }
814
815 #[test]
816 fn test_try_from() {
817 assert_eq!(
818 Cmdline::try_from("foo -- bar", 0),
819 Err(Error::InvalidCapacity)
820 );
821 assert_eq!(
822 Cmdline::try_from("foo -- bar", 10),
823 Err(Error::InvalidCapacity)
824 );
825 assert!(Cmdline::try_from("foo -- bar", 11).is_ok());
826
827 let cl = Cmdline::try_from("hello=world foo=bar", CMDLINE_MAX_SIZE).unwrap();
828
829 assert_eq!(cl.boot_args, "hello=world foo=bar");
830 assert_eq!(cl.init_args, "");
831
832 let cl = Cmdline::try_from("hello=world -- foo=bar", CMDLINE_MAX_SIZE).unwrap();
833
834 assert_eq!(cl.boot_args, "hello=world");
835 assert_eq!(cl.init_args, "foo=bar");
836
837 let cl =
838 Cmdline::try_from("hello=world --foo=bar -- arg1 --arg2", CMDLINE_MAX_SIZE).unwrap();
839
840 assert_eq!(cl.boot_args, "hello=world --foo=bar");
841 assert_eq!(cl.init_args, "arg1 --arg2");
842
843 let cl = Cmdline::try_from("arg1-- arg2 --arg3", CMDLINE_MAX_SIZE).unwrap();
844
845 assert_eq!(cl.boot_args, "arg1-- arg2 --arg3");
846 assert_eq!(cl.init_args, "");
847
848 let cl = Cmdline::try_from("--arg1-- -- arg2 -- --arg3", CMDLINE_MAX_SIZE).unwrap();
849
850 assert_eq!(cl.boot_args, "--arg1--");
851 assert_eq!(cl.init_args, "arg2 -- --arg3");
852
853 let cl = Cmdline::try_from("a=\"b -- c\" d -- e ", CMDLINE_MAX_SIZE).unwrap();
854
855 assert_eq!(cl.boot_args, "a=\"b -- c\" d");
856 assert_eq!(cl.init_args, "e");
857
858 let cl = Cmdline::try_from("foo--bar=baz a=\"b -- c\"", CMDLINE_MAX_SIZE).unwrap();
859
860 assert_eq!(cl.boot_args, "foo--bar=baz a=\"b -- c\"");
861 assert_eq!(cl.init_args, "");
862
863 let cl = Cmdline::try_from("--foo --bar", CMDLINE_MAX_SIZE).unwrap();
864
865 assert_eq!(cl.boot_args, "--foo --bar");
866 assert_eq!(cl.init_args, "");
867
868 let cl = Cmdline::try_from("foo=\"bar--baz\" foo", CMDLINE_MAX_SIZE).unwrap();
869
870 assert_eq!(cl.boot_args, "foo=\"bar--baz\" foo");
871 assert_eq!(cl.init_args, "");
872 }
873
874 #[test]
875 fn test_error_try_from() {
876 assert_eq!(Cmdline::try_from("", 0), Err(Error::InvalidCapacity));
877
878 assert_eq!(
879 Cmdline::try_from(
880 String::from_utf8(vec![b'X'; CMDLINE_MAX_SIZE])
881 .unwrap()
882 .as_str(),
883 CMDLINE_MAX_SIZE - 1
884 ),
885 Err(Error::InvalidCapacity)
886 );
887
888 let cl = Cmdline::try_from(
889 "console=ttyS0 nomodules -- /etc/password --param",
890 CMDLINE_MAX_SIZE,
891 )
892 .unwrap();
893 assert_eq!(
894 cl.as_cstring().unwrap().as_bytes_with_nul(),
895 b"console=ttyS0 nomodules -- /etc/password --param\0"
896 );
897 }
898
899 #[test]
900 fn test_as_cstring() {
901 let mut cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
902
903 assert_eq!(cl.as_cstring().unwrap().into_bytes_with_nul(), b"\0");
904 assert!(cl.insert_init_args("/etc/password").is_ok());
905 assert_eq!(cl.as_cstring(), Err(Error::NoBootArgsInserted));
906 assert_eq!(cl.boot_args, "");
907 assert_eq!(cl.init_args, "/etc/password");
908 assert!(cl.insert("console", "ttyS0").is_ok());
909 assert_eq!(
910 cl.as_cstring().unwrap().into_bytes_with_nul(),
911 b"console=ttyS0 -- /etc/password\0"
912 );
913 assert!(cl.insert_str("nomodules").is_ok());
914 assert_eq!(
915 cl.as_cstring().unwrap().into_bytes_with_nul(),
916 b"console=ttyS0 nomodules -- /etc/password\0"
917 );
918 assert!(cl.insert_init_args("--param").is_ok());
919 assert_eq!(
920 cl.as_cstring().unwrap().into_bytes_with_nul(),
921 b"console=ttyS0 nomodules -- /etc/password --param\0"
922 );
923 }
924}