linux_loader/cmdline/
mod.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2//
3// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE-BSD-3-Clause file.
6//
7// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
8//
9//! Helper for creating valid kernel command line strings.
10
11use std::ffi::CString;
12use std::fmt;
13use std::result;
14
15use vm_memory::{Address, GuestAddress, GuestUsize};
16
17const INIT_ARGS_SEPARATOR: &str = " -- ";
18
19/// The error type for command line building operations.
20#[derive(Debug, PartialEq, Eq)]
21pub enum Error {
22    /// Null terminator identified in the command line.
23    NullTerminator,
24    /// No boot args inserted into cmdline.
25    NoBootArgsInserted,
26    /// Invalid capacity provided.
27    InvalidCapacity,
28    /// Operation would have resulted in a non-printable ASCII character.
29    InvalidAscii,
30    /// Key/Value Operation would have had a space in it.
31    HasSpace,
32    /// Key/Value Operation would have had an equals sign in it.
33    HasEquals,
34    /// Key/Value Operation was not passed a value.
35    MissingVal(String),
36    /// 0-sized virtio MMIO device passed to the kernel command line builder.
37    MmioSize,
38    /// Operation would have made the command line too large.
39    TooLarge,
40}
41
42impl fmt::Display for Error {
43    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44        match *self {
45            Error::NullTerminator => {
46                write!(f, "Null terminator detected in the command line structure.")
47            }
48            Error::NoBootArgsInserted => write!(f, "Cmdline cannot contain only init args."),
49            Error::InvalidCapacity => write!(f, "Invalid cmdline capacity provided."),
50            Error::InvalidAscii => write!(f, "String contains a non-printable ASCII character."),
51            Error::HasSpace => write!(f, "String contains a space."),
52            Error::HasEquals => write!(f, "String contains an equals sign."),
53            Error::MissingVal(ref k) => write!(f, "Missing value for key {}.", k),
54            Error::MmioSize => write!(
55                f,
56                "0-sized virtio MMIO device passed to the kernel command line builder."
57            ),
58            Error::TooLarge => write!(f, "Inserting string would make command line too long."),
59        }
60    }
61}
62
63impl std::error::Error for Error {}
64
65/// Specialized [`Result`] type for command line operations.
66///
67/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
68pub type Result<T> = result::Result<T, Error>;
69
70fn valid_char(c: char) -> bool {
71    matches!(c, ' '..='~')
72}
73
74fn valid_str(s: &str) -> Result<()> {
75    if s.chars().all(valid_char) {
76        Ok(())
77    } else {
78        Err(Error::InvalidAscii)
79    }
80}
81
82fn valid_element(s: &str) -> Result<()> {
83    if !s.chars().all(valid_char) {
84        Err(Error::InvalidAscii)
85    } else if s.contains(' ') {
86        Err(Error::HasSpace)
87    } else if s.contains('=') {
88        Err(Error::HasEquals)
89    } else {
90        Ok(())
91    }
92}
93
94/// A builder for a kernel command line string that validates the string as it's being built.
95///
96/// # Examples
97///
98/// ```rust
99/// # use linux_loader::cmdline::*;
100/// # use std::ffi::CString;
101/// let mut cl = Cmdline::new(100).unwrap();
102/// cl.insert_str("foobar").unwrap();
103/// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foobar\0");
104/// ```
105#[derive(Clone, Debug)]
106pub struct Cmdline {
107    boot_args: String,
108    init_args: String,
109    capacity: usize,
110}
111
112impl Cmdline {
113    /// Constructs an empty [`Cmdline`] with the given capacity, including the nul terminator.
114    ///
115    /// # Arguments
116    ///
117    /// * `capacity` - Command line capacity. Must be greater than 0.
118    ///
119    /// # Examples
120    ///
121    /// ```rust
122    /// # use linux_loader::cmdline::*;
123    /// let cl = Cmdline::new(100).unwrap();
124    /// ```
125    /// [`Cmdline`]: struct.Cmdline.html
126    pub fn new(capacity: usize) -> Result<Cmdline> {
127        if capacity == 0 {
128            return Err(Error::InvalidCapacity);
129        }
130
131        Ok(Cmdline {
132            boot_args: String::new(),
133            init_args: String::new(),
134            capacity,
135        })
136    }
137
138    /// Validates and inserts a key-value pair representing a boot
139    /// arg of the command line.
140    ///
141    /// # Arguments
142    ///
143    /// * `key` - Key to be inserted in the command line string.
144    /// * `val` - Value corresponding to `key`.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// # use linux_loader::cmdline::*;
150    /// let mut cl = Cmdline::new(100).unwrap();
151    /// cl.insert("foo", "bar");
152    /// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
153    /// ```
154    pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
155        let k = key.as_ref();
156        let v = val.as_ref();
157
158        valid_element(k)?;
159        valid_element(v)?;
160
161        let kv_str = format!("{}={}", k, v);
162
163        self.insert_str(kv_str)
164    }
165
166    /// Validates and inserts a key-value1,...,valueN pair representing a
167    /// boot arg of the command line.
168    ///
169    /// # Arguments
170    ///
171    /// * `key` - Key to be inserted in the command line string.
172    /// * `vals` - Values corresponding to `key`.
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// # use linux_loader::cmdline::*;
178    /// # use std::ffi::CString;
179    /// let mut cl = Cmdline::new(100).unwrap();
180    /// cl.insert_multiple("foo", &["bar", "baz"]);
181    /// assert_eq!(
182    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
183    ///     b"foo=bar,baz\0"
184    /// );
185    /// ```
186    pub fn insert_multiple<T: AsRef<str>>(&mut self, key: T, vals: &[T]) -> Result<()> {
187        let k = key.as_ref();
188
189        valid_element(k)?;
190        if vals.is_empty() {
191            return Err(Error::MissingVal(k.to_string()));
192        }
193
194        let kv_str = format!(
195            "{}={}",
196            k,
197            vals.iter()
198                .map(|v| -> Result<&str> {
199                    valid_element(v.as_ref())?;
200                    Ok(v.as_ref())
201                })
202                .collect::<Result<Vec<&str>>>()?
203                .join(",")
204        );
205
206        self.insert_str(kv_str)
207    }
208
209    /// Inserts a string in the boot args; returns an error if the string
210    /// is invalid.
211    ///
212    /// # Arguments
213    ///
214    /// * `slug` - String to be appended to the command line.
215    ///
216    /// # Examples
217    ///
218    /// ```rust
219    /// # use linux_loader::cmdline::*;
220    /// # use std::ffi::CString;
221    /// let mut cl = Cmdline::new(100).unwrap();
222    /// cl.insert_str("foobar").unwrap();
223    /// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foobar\0");
224    /// ```
225    pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
226        // Step 1: Check if the string provided is a valid boot arg string and remove any
227        // leading or trailing whitespaces.
228        let s = slug.as_ref().trim();
229        valid_str(s)?;
230
231        // Step 2: Check if cmdline capacity is not exceeded when inserting the boot arg
232        // string provided.
233        let mut cmdline_size = self.get_null_terminated_representation_size();
234
235        // Count extra space required if this is not the first boot arg of the cmdline.
236        if !self.boot_args.is_empty() {
237            cmdline_size = cmdline_size.checked_add(1).ok_or(Error::TooLarge)?;
238        }
239
240        // Count extra space required for the insertion of the new boot arg string.
241        cmdline_size = cmdline_size.checked_add(s.len()).ok_or(Error::TooLarge)?;
242
243        if cmdline_size > self.capacity {
244            return Err(Error::TooLarge);
245        }
246
247        // Step 3: Insert the string as boot args to the cmdline.
248        if !self.boot_args.is_empty() {
249            self.boot_args.push(' ');
250        }
251
252        self.boot_args.push_str(s);
253
254        Ok(())
255    }
256
257    /// Inserts a string in the init args; returns an error if the string
258    /// is invalid.
259    ///
260    /// # Arguments
261    ///
262    /// * `slug` - String to be appended to the command line.
263    ///
264    /// # Examples
265    ///
266    /// ```rust
267    /// # use linux_loader::cmdline::*;
268    /// # use std::ffi::CString;
269    /// let mut cl = Cmdline::new(100).unwrap();
270    /// cl.insert_str("foo").unwrap();
271    /// cl.insert_init_args("bar").unwrap();
272    /// assert_eq!(
273    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
274    ///     b"foo -- bar\0"
275    /// );
276    /// ```
277    pub fn insert_init_args<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
278        // Step 1: Check if the string provided is a valid init arg string and remove any
279        // leading or trailing whitespaces.
280        let s = slug.as_ref().trim();
281        valid_str(s)?;
282
283        // Step 2: Check if cmdline capacity is not exceeded when inserting the init arg
284        // string provided.
285        let mut cmdline_size = self.get_null_terminated_representation_size();
286
287        // Count extra space required if this is not the first init arg of the cmdline.
288        cmdline_size = cmdline_size
289            .checked_add(if self.init_args.is_empty() {
290                INIT_ARGS_SEPARATOR.len()
291            } else {
292                1
293            })
294            .ok_or(Error::TooLarge)?;
295
296        // Count extra space required for the insertion of the new init arg string.
297        cmdline_size = cmdline_size.checked_add(s.len()).ok_or(Error::TooLarge)?;
298
299        if cmdline_size > self.capacity {
300            return Err(Error::TooLarge);
301        }
302
303        // Step 3: Insert the string as init args to the cmdline.
304        if !self.init_args.is_empty() {
305            self.init_args.push(' ');
306        }
307
308        self.init_args.push_str(s);
309
310        Ok(())
311    }
312
313    fn get_null_terminated_representation_size(&self) -> usize {
314        // Counting current size of the cmdline (no overflows are possible as long as the cmdline
315        // size is always smaller or equal to the cmdline capacity provided in constructor)
316        let mut cmdline_size = self.boot_args.len() + 1; // for null terminator
317
318        if !self.init_args.is_empty() {
319            cmdline_size += INIT_ARGS_SEPARATOR.len() + self.init_args.len();
320        }
321
322        cmdline_size
323    }
324
325    /// Returns a C compatible representation of the command line
326    /// The Linux kernel expects a null terminated cmdline according to the source:
327    /// <https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179>
328    ///
329    /// To get bytes of the cmdline to be written in guest's memory (including the
330    /// null terminator) from this representation, use CString::as_bytes_with_nul()
331    ///
332    /// # Examples
333    ///
334    /// ```rust
335    /// # use linux_loader::cmdline::*;
336    /// let mut cl = Cmdline::new(20).unwrap();
337    /// cl.insert_str("foo").unwrap();
338    /// cl.insert_init_args("bar").unwrap();
339    /// assert_eq!(
340    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
341    ///     b"foo -- bar\0"
342    /// );
343    /// ```
344    pub fn as_cstring(&self) -> Result<CString> {
345        if self.boot_args.is_empty() && self.init_args.is_empty() {
346            CString::new("".to_string()).map_err(|_| Error::NullTerminator)
347        } else if self.boot_args.is_empty() {
348            Err(Error::NoBootArgsInserted)
349        } else if self.init_args.is_empty() {
350            CString::new(self.boot_args.to_string()).map_err(|_| Error::NullTerminator)
351        } else {
352            CString::new(format!(
353                "{}{}{}",
354                self.boot_args, INIT_ARGS_SEPARATOR, self.init_args
355            ))
356            .map_err(|_| Error::NullTerminator)
357        }
358    }
359
360    /// Adds a virtio MMIO device to the kernel command line.
361    ///
362    /// Multiple devices can be specified, with multiple `virtio_mmio.device=` options. This
363    /// function must be called once per device.
364    /// The function appends a string of the following format to the kernel command line:
365    /// `<size>@<baseaddr>:<irq>[:<id>]`.
366    /// For more details see the [documentation] (section `virtio_mmio.device=`).
367    ///
368    /// # Arguments
369    ///
370    /// * `size` - Size of the slot the device occupies on the MMIO bus.
371    /// * `baseaddr` - Physical base address of the device.
372    /// * `irq` - Interrupt number to be used by the device.
373    /// * `id` - Optional platform device ID.
374    ///
375    /// # Examples
376    ///
377    /// ```rust
378    /// # use linux_loader::cmdline::*;
379    /// # use std::ffi::CString;
380    /// # use vm_memory::{GuestAddress, GuestUsize};
381    /// let mut cl = Cmdline::new(100).unwrap();
382    /// cl.add_virtio_mmio_device(1 << 12, GuestAddress(0x1000), 5, Some(42))
383    ///     .unwrap();
384    /// assert_eq!(
385    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
386    ///     b"virtio_mmio.device=4K@0x1000:5:42\0"
387    /// );
388    /// ```
389    ///
390    /// [documentation]: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
391    pub fn add_virtio_mmio_device(
392        &mut self,
393        size: GuestUsize,
394        baseaddr: GuestAddress,
395        irq: u32,
396        id: Option<u32>,
397    ) -> Result<()> {
398        if size == 0 {
399            return Err(Error::MmioSize);
400        }
401
402        let mut device_str = format!(
403            "virtio_mmio.device={}@0x{:x?}:{}",
404            Self::guestusize_to_str(size),
405            baseaddr.raw_value(),
406            irq
407        );
408        if let Some(id) = id {
409            device_str.push_str(format!(":{}", id).as_str());
410        }
411        self.insert_str(&device_str)
412    }
413
414    // Converts a `GuestUsize` to a concise string representation, with multiplier suffixes.
415    fn guestusize_to_str(size: GuestUsize) -> String {
416        const KB_MULT: u64 = 1 << 10;
417        const MB_MULT: u64 = KB_MULT << 10;
418        const GB_MULT: u64 = MB_MULT << 10;
419
420        if size % GB_MULT == 0 {
421            return format!("{}G", size / GB_MULT);
422        }
423        if size % MB_MULT == 0 {
424            return format!("{}M", size / MB_MULT);
425        }
426        if size % KB_MULT == 0 {
427            return format!("{}K", size / KB_MULT);
428        }
429        size.to_string()
430    }
431
432    fn check_outside_double_quotes(slug: &str) -> bool {
433        slug.matches('\"').count() % 2 == 0
434    }
435
436    /// Tries to build a [`Cmdline`] with a given capacity from a [`str`]. The format of the
437    /// str provided must be one of the following:
438    ///
439    /// * `<boot args> -- <init args>`
440    /// * `<boot args>`
441    ///
442    /// where `<boot args>` and `<init args>` can contain `--` only if double quoted and
443    /// `<boot args>` and `<init args>` contain at least one non-whitespace char each.
444    ///
445    /// Providing a str not following these rules might end up in undefined behaviour of
446    /// the resulting `Cmdline`.
447    ///
448    /// # Arguments
449    ///
450    /// * `cmdline_raw` - Contains boot params and init params of the cmdline.
451    /// * `capacity` - Capacity of the cmdline.
452    ///
453    /// # Examples
454    ///
455    /// ```rust
456    /// # use linux_loader::cmdline::*;
457    /// let cl = Cmdline::try_from("foo -- bar", 100).unwrap();
458    /// assert_eq!(
459    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
460    ///     b"foo -- bar\0"
461    /// );
462    /// ```
463    pub fn try_from(cmdline_raw: &str, capacity: usize) -> Result<Cmdline> {
464        // The cmdline_raw argument should contain no more than one INIT_ARGS_SEPARATOR sequence
465        // that is not double quoted; in case the INIT_ARGS_SEPARATOR is found all chars following
466        // it will be parsed as init args.
467
468        if capacity == 0 {
469            return Err(Error::InvalidCapacity);
470        }
471
472        // Step 1: Extract boot args and init args from input by searching for INIT_ARGS_SEPARATOR.
473
474        // Check first occurrence of the INIT_ARGS_SEPARATOR that is not between double quotes.
475        // All chars following the INIT_ARGS_SEPARATOR will be parsed as init args.
476        let (mut boot_args, mut init_args) = match cmdline_raw
477            .match_indices(INIT_ARGS_SEPARATOR)
478            .find(|&separator_occurrence| {
479                Self::check_outside_double_quotes(&cmdline_raw[..(separator_occurrence.0)])
480            }) {
481            None => (cmdline_raw, ""),
482            Some((delimiter_index, _)) => (
483                &cmdline_raw[..delimiter_index],
484                // This does not overflow as long as `delimiter_index + INIT_ARGS_SEPARATOR.len()`
485                // is pointing to the first char after the INIT_ARGS_SEPARATOR which always exists;
486                // as a result, `delimiter_index + INIT_ARGS_SEPARATOR.len()` is less or equal to the
487                // length of the initial string.
488                &cmdline_raw[(delimiter_index + INIT_ARGS_SEPARATOR.len())..],
489            ),
490        };
491
492        boot_args = boot_args.trim();
493        init_args = init_args.trim();
494
495        // Step 2: Check if capacity provided for the cmdline is not exceeded and create a new `Cmdline`
496        // if size check passes.
497        let mut cmdline_size = boot_args.len().checked_add(1).ok_or(Error::TooLarge)?;
498
499        if !init_args.is_empty() {
500            cmdline_size = cmdline_size
501                .checked_add(INIT_ARGS_SEPARATOR.len())
502                .ok_or(Error::TooLarge)?;
503
504            cmdline_size = cmdline_size
505                .checked_add(init_args.len())
506                .ok_or(Error::TooLarge)?;
507        }
508
509        if cmdline_size > capacity {
510            return Err(Error::InvalidCapacity);
511        }
512
513        Ok(Cmdline {
514            boot_args: boot_args.to_string(),
515            init_args: init_args.to_string(),
516            capacity,
517        })
518    }
519}
520
521impl TryFrom<Cmdline> for Vec<u8> {
522    type Error = Error;
523
524    fn try_from(cmdline: Cmdline) -> result::Result<Self, Self::Error> {
525        cmdline
526            .as_cstring()
527            .map(|cmdline_cstring| cmdline_cstring.into_bytes_with_nul())
528    }
529}
530
531impl PartialEq for Cmdline {
532    fn eq(&self, other: &Self) -> bool {
533        self.as_cstring() == other.as_cstring()
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540    use std::ffi::CString;
541
542    const CMDLINE_MAX_SIZE: usize = 4096;
543
544    #[test]
545    fn test_insert_hello_world() {
546        let mut cl = Cmdline::new(100).unwrap();
547        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
548        assert!(cl.insert("hello", "world").is_ok());
549        assert_eq!(
550            cl.as_cstring().unwrap().as_bytes_with_nul(),
551            b"hello=world\0"
552        );
553    }
554
555    #[test]
556    fn test_insert_multi() {
557        let mut cl = Cmdline::new(100).unwrap();
558        assert!(cl.insert("hello", "world").is_ok());
559        assert!(cl.insert("foo", "bar").is_ok());
560        assert_eq!(
561            cl.as_cstring().unwrap().as_bytes_with_nul(),
562            b"hello=world foo=bar\0"
563        );
564    }
565
566    #[test]
567    fn test_insert_space() {
568        let mut cl = Cmdline::new(100).unwrap();
569        assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
570        assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
571        assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
572        assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
573        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
574    }
575
576    #[test]
577    fn test_insert_equals() {
578        let mut cl = Cmdline::new(100).unwrap();
579        assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
580        assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
581        assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
582        assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
583        assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
584        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
585    }
586
587    #[test]
588    fn test_insert_emoji() {
589        let mut cl = Cmdline::new(100).unwrap();
590        assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
591        assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
592        assert_eq!(cl.insert_str("heart=💖"), Err(Error::InvalidAscii));
593        assert_eq!(
594            cl.insert_multiple("💖", &["heart", "love"]),
595            Err(Error::InvalidAscii)
596        );
597        assert_eq!(
598            cl.insert_multiple("heart", &["💖", "love"]),
599            Err(Error::InvalidAscii)
600        );
601        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
602    }
603
604    #[test]
605    fn test_insert_string() {
606        let mut cl = Cmdline::new(13).unwrap();
607        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
608        assert!(cl.insert_str("noapic").is_ok());
609        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"noapic\0");
610        assert!(cl.insert_str("nopci").is_ok());
611        assert_eq!(
612            cl.as_cstring().unwrap().as_bytes_with_nul(),
613            b"noapic nopci\0"
614        );
615    }
616
617    #[test]
618    fn test_insert_too_large() {
619        let mut cl = Cmdline::new(4).unwrap();
620        assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
621        assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
622        assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
623        assert!(cl.insert("a", "b").is_ok());
624        assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
625        assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
626        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"a=b\0");
627
628        let mut cl = Cmdline::new(10).unwrap();
629        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length; 4 chars available
630        assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
631        assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
632
633        let mut cl = Cmdline::new(11).unwrap();
634        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length; 5 chars available
635        assert_eq!(cl.insert_init_args("da"), Err(Error::TooLarge)); // adds 6 (including INIT_ARGS_SEPARATOR) length
636        assert!(cl.insert_init_args("d").is_ok()); // adds 6 (including INIT_ARGS_SEPARATOR)
637
638        let mut cl = Cmdline::new(20).unwrap();
639        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length; 14 chars available
640        assert!(cl.insert_init_args("da").is_ok()); // 8 chars available
641        assert_eq!(cl.insert_init_args("abcdabcd"), Err(Error::TooLarge)); // adds 9 (including space) length
642        assert!(cl.insert_init_args("abcdabc").is_ok()); // adds 8 (including space) length
643    }
644
645    #[test]
646    fn test_add_virtio_mmio_device() {
647        let mut cl = Cmdline::new(5).unwrap();
648        assert_eq!(
649            cl.add_virtio_mmio_device(0, GuestAddress(0), 0, None),
650            Err(Error::MmioSize)
651        );
652        assert_eq!(
653            cl.add_virtio_mmio_device(1, GuestAddress(0), 0, None),
654            Err(Error::TooLarge)
655        );
656
657        let mut cl = Cmdline::new(150).unwrap();
658        assert!(cl
659            .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
660            .is_ok());
661        let mut expected_str = "virtio_mmio.device=1@0x0:1".to_string();
662        assert_eq!(
663            cl.as_cstring().unwrap(),
664            CString::new(expected_str.as_bytes()).unwrap()
665        );
666
667        assert!(cl
668            .add_virtio_mmio_device(2 << 10, GuestAddress(0x100), 2, None)
669            .is_ok());
670        expected_str.push_str(" virtio_mmio.device=2K@0x100:2");
671        assert_eq!(
672            cl.as_cstring().unwrap(),
673            CString::new(expected_str.as_bytes()).unwrap()
674        );
675
676        assert!(cl
677            .add_virtio_mmio_device(3 << 20, GuestAddress(0x1000), 3, None)
678            .is_ok());
679        expected_str.push_str(" virtio_mmio.device=3M@0x1000:3");
680        assert_eq!(
681            cl.as_cstring().unwrap(),
682            CString::new(expected_str.as_bytes()).unwrap()
683        );
684
685        assert!(cl
686            .add_virtio_mmio_device(4 << 30, GuestAddress(0x0001_0000), 4, Some(42))
687            .is_ok());
688        expected_str.push_str(" virtio_mmio.device=4G@0x10000:4:42");
689        assert_eq!(
690            cl.as_cstring().unwrap(),
691            CString::new(expected_str.as_bytes()).unwrap()
692        );
693    }
694
695    #[test]
696    fn test_insert_kv() {
697        let mut cl = Cmdline::new(10).unwrap();
698
699        let no_vals: Vec<&str> = vec![];
700        assert_eq!(cl.insert_multiple("foo=", &no_vals), Err(Error::HasEquals));
701        assert_eq!(
702            cl.insert_multiple("foo", &no_vals),
703            Err(Error::MissingVal("foo".to_string()))
704        );
705        assert_eq!(cl.insert_multiple("foo", &["bar "]), Err(Error::HasSpace));
706        assert_eq!(
707            cl.insert_multiple("foo", &["bar", "baz"]),
708            Err(Error::TooLarge)
709        );
710
711        let mut cl = Cmdline::new(100).unwrap();
712        assert!(cl.insert_multiple("foo", &["bar"]).is_ok());
713        assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
714
715        let mut cl = Cmdline::new(100).unwrap();
716        assert!(cl.insert_multiple("foo", &["bar", "baz"]).is_ok());
717        assert_eq!(
718            cl.as_cstring().unwrap().as_bytes_with_nul(),
719            b"foo=bar,baz\0"
720        );
721    }
722
723    #[test]
724    fn test_try_from_cmdline_for_vec() {
725        let cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
726        assert_eq!(Vec::try_from(cl).unwrap(), vec![b'\0']);
727
728        let cl = Cmdline::try_from("foo", CMDLINE_MAX_SIZE).unwrap();
729        assert_eq!(Vec::try_from(cl).unwrap(), vec![b'f', b'o', b'o', b'\0']);
730
731        let mut cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
732        cl.insert_init_args("foo--bar").unwrap();
733        assert_eq!(Vec::try_from(cl), Err(Error::NoBootArgsInserted));
734    }
735
736    #[test]
737    fn test_partial_eq() {
738        let mut c1 = Cmdline::new(20).unwrap();
739        let mut c2 = Cmdline::new(30).unwrap();
740
741        c1.insert_str("hello world!").unwrap();
742        c2.insert_str("hello").unwrap();
743        assert_ne!(c1, c2);
744
745        // `insert_str` also adds a whitespace before the string being inserted.
746        c2.insert_str("world!").unwrap();
747        assert_eq!(c1, c2);
748
749        let mut cl1 = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
750        let mut cl2 = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
751
752        assert_eq!(cl1, cl2);
753        assert!(cl1
754            .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
755            .is_ok());
756        assert_ne!(cl1, cl2);
757        assert!(cl2
758            .add_virtio_mmio_device(1, GuestAddress(0), 1, None)
759            .is_ok());
760        assert_eq!(cl1, cl2);
761    }
762
763    #[test]
764    fn test_try_from() {
765        assert_eq!(
766            Cmdline::try_from("foo --  bar", 0),
767            Err(Error::InvalidCapacity)
768        );
769        assert_eq!(
770            Cmdline::try_from("foo --  bar", 10),
771            Err(Error::InvalidCapacity)
772        );
773        assert!(Cmdline::try_from("foo --  bar", 11).is_ok());
774
775        let cl = Cmdline::try_from("hello=world foo=bar", CMDLINE_MAX_SIZE).unwrap();
776
777        assert_eq!(cl.boot_args, "hello=world foo=bar");
778        assert_eq!(cl.init_args, "");
779
780        let cl = Cmdline::try_from("hello=world -- foo=bar", CMDLINE_MAX_SIZE).unwrap();
781
782        assert_eq!(cl.boot_args, "hello=world");
783        assert_eq!(cl.init_args, "foo=bar");
784
785        let cl =
786            Cmdline::try_from("hello=world --foo=bar -- arg1 --arg2", CMDLINE_MAX_SIZE).unwrap();
787
788        assert_eq!(cl.boot_args, "hello=world --foo=bar");
789        assert_eq!(cl.init_args, "arg1 --arg2");
790
791        let cl = Cmdline::try_from("arg1-- arg2 --arg3", CMDLINE_MAX_SIZE).unwrap();
792
793        assert_eq!(cl.boot_args, "arg1-- arg2 --arg3");
794        assert_eq!(cl.init_args, "");
795
796        let cl = Cmdline::try_from("--arg1-- -- arg2 -- --arg3", CMDLINE_MAX_SIZE).unwrap();
797
798        assert_eq!(cl.boot_args, "--arg1--");
799        assert_eq!(cl.init_args, "arg2 -- --arg3");
800
801        let cl = Cmdline::try_from("a=\"b -- c\" d -- e ", CMDLINE_MAX_SIZE).unwrap();
802
803        assert_eq!(cl.boot_args, "a=\"b -- c\" d");
804        assert_eq!(cl.init_args, "e");
805
806        let cl = Cmdline::try_from("foo--bar=baz a=\"b -- c\"", CMDLINE_MAX_SIZE).unwrap();
807
808        assert_eq!(cl.boot_args, "foo--bar=baz a=\"b -- c\"");
809        assert_eq!(cl.init_args, "");
810
811        let cl = Cmdline::try_from("--foo --bar", CMDLINE_MAX_SIZE).unwrap();
812
813        assert_eq!(cl.boot_args, "--foo --bar");
814        assert_eq!(cl.init_args, "");
815
816        let cl = Cmdline::try_from("foo=\"bar--baz\" foo", CMDLINE_MAX_SIZE).unwrap();
817
818        assert_eq!(cl.boot_args, "foo=\"bar--baz\" foo");
819        assert_eq!(cl.init_args, "");
820    }
821
822    #[test]
823    fn test_error_try_from() {
824        assert_eq!(Cmdline::try_from("", 0), Err(Error::InvalidCapacity));
825
826        assert_eq!(
827            Cmdline::try_from(
828                String::from_utf8(vec![b'X'; CMDLINE_MAX_SIZE])
829                    .unwrap()
830                    .as_str(),
831                CMDLINE_MAX_SIZE - 1
832            ),
833            Err(Error::InvalidCapacity)
834        );
835
836        let cl = Cmdline::try_from(
837            "console=ttyS0 nomodules -- /etc/password --param",
838            CMDLINE_MAX_SIZE,
839        )
840        .unwrap();
841        assert_eq!(
842            cl.as_cstring().unwrap().as_bytes_with_nul(),
843            b"console=ttyS0 nomodules -- /etc/password --param\0"
844        );
845    }
846
847    #[test]
848    fn test_as_cstring() {
849        let mut cl = Cmdline::new(CMDLINE_MAX_SIZE).unwrap();
850
851        assert_eq!(cl.as_cstring().unwrap().into_bytes_with_nul(), b"\0");
852        assert!(cl.insert_init_args("/etc/password").is_ok());
853        assert_eq!(cl.as_cstring(), Err(Error::NoBootArgsInserted));
854        assert_eq!(cl.boot_args, "");
855        assert_eq!(cl.init_args, "/etc/password");
856        assert!(cl.insert("console", "ttyS0").is_ok());
857        assert_eq!(
858            cl.as_cstring().unwrap().into_bytes_with_nul(),
859            b"console=ttyS0 -- /etc/password\0"
860        );
861        assert!(cl.insert_str("nomodules").is_ok());
862        assert_eq!(
863            cl.as_cstring().unwrap().into_bytes_with_nul(),
864            b"console=ttyS0 nomodules -- /etc/password\0"
865        );
866        assert!(cl.insert_init_args("--param").is_ok());
867        assert_eq!(
868            cl.as_cstring().unwrap().into_bytes_with_nul(),
869            b"console=ttyS0 nomodules -- /etc/password --param\0"
870        );
871    }
872}