Skip to main content

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    /// Double-quotes can be used to protect spaces in values
41    NoQuoteSpace,
42    /// A double quote that is in the middle of the value
43    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
74/// Specialized [`Result`] type for command line operations.
75///
76/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
77pub 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/// A builder for a kernel command line string that validates the string as it's being built.
135///
136/// # Examples
137///
138/// ```rust
139/// # use linux_loader::cmdline::*;
140/// # use std::ffi::CString;
141/// let mut cl = Cmdline::new(100).unwrap();
142/// cl.insert_str("foobar").unwrap();
143/// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foobar\0");
144/// ```
145#[derive(Clone, Debug)]
146pub struct Cmdline {
147    boot_args: String,
148    init_args: String,
149    capacity: usize,
150}
151
152impl Cmdline {
153    /// Constructs an empty [`Cmdline`] with the given capacity, including the nul terminator.
154    ///
155    /// # Arguments
156    ///
157    /// * `capacity` - Command line capacity. Must be greater than 0.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// # use linux_loader::cmdline::*;
163    /// let cl = Cmdline::new(100).unwrap();
164    /// ```
165    /// [`Cmdline`]: struct.Cmdline.html
166    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    /// Validates and inserts a key-value pair representing a boot
179    /// arg of the command line.
180    ///
181    /// # Arguments
182    ///
183    /// * `key` - Key to be inserted in the command line string.
184    /// * `val` - Value corresponding to `key`.
185    ///
186    /// # Examples
187    ///
188    /// ```rust
189    /// # use linux_loader::cmdline::*;
190    /// let mut cl = Cmdline::new(100).unwrap();
191    /// cl.insert("foo", "bar");
192    /// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
193    /// ```
194    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    /// Validates and inserts a key-value1,...,valueN pair representing a
207    /// boot arg of the command line.
208    ///
209    /// # Arguments
210    ///
211    /// * `key` - Key to be inserted in the command line string.
212    /// * `vals` - Values corresponding to `key`.
213    ///
214    /// # Examples
215    ///
216    /// ```rust
217    /// # use linux_loader::cmdline::*;
218    /// # use std::ffi::CString;
219    /// let mut cl = Cmdline::new(100).unwrap();
220    /// cl.insert_multiple("foo", &["bar", "baz"]);
221    /// assert_eq!(
222    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
223    ///     b"foo=bar,baz\0"
224    /// );
225    /// ```
226    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    /// Inserts a string in the boot args; returns an error if the string
250    /// is invalid.
251    ///
252    /// # Arguments
253    ///
254    /// * `slug` - String to be appended to the command line.
255    ///
256    /// # Examples
257    ///
258    /// ```rust
259    /// # use linux_loader::cmdline::*;
260    /// # use std::ffi::CString;
261    /// let mut cl = Cmdline::new(100).unwrap();
262    /// cl.insert_str("foobar").unwrap();
263    /// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foobar\0");
264    /// ```
265    pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
266        // Step 1: Check if the string provided is a valid boot arg string and remove any
267        // leading or trailing whitespaces.
268        let s = slug.as_ref().trim();
269        valid_str(s)?;
270
271        // Step 2: Check if cmdline capacity is not exceeded when inserting the boot arg
272        // string provided.
273        let mut cmdline_size = self.get_null_terminated_representation_size();
274
275        // Count extra space required if this is not the first boot arg of the cmdline.
276        if !self.boot_args.is_empty() {
277            cmdline_size = cmdline_size.checked_add(1).ok_or(Error::TooLarge)?;
278        }
279
280        // Count extra space required for the insertion of the new boot arg string.
281        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        // Step 3: Insert the string as boot args to the cmdline.
288        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    /// Inserts a string in the init args; returns an error if the string
298    /// is invalid.
299    ///
300    /// # Arguments
301    ///
302    /// * `slug` - String to be appended to the command line.
303    ///
304    /// # Examples
305    ///
306    /// ```rust
307    /// # use linux_loader::cmdline::*;
308    /// # use std::ffi::CString;
309    /// let mut cl = Cmdline::new(100).unwrap();
310    /// cl.insert_str("foo").unwrap();
311    /// cl.insert_init_args("bar").unwrap();
312    /// assert_eq!(
313    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
314    ///     b"foo -- bar\0"
315    /// );
316    /// ```
317    pub fn insert_init_args<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
318        // Step 1: Check if the string provided is a valid init arg string and remove any
319        // leading or trailing whitespaces.
320        let s = slug.as_ref().trim();
321        valid_str(s)?;
322
323        // Step 2: Check if cmdline capacity is not exceeded when inserting the init arg
324        // string provided.
325        let mut cmdline_size = self.get_null_terminated_representation_size();
326
327        // Count extra space required if this is not the first init arg of the cmdline.
328        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        // Count extra space required for the insertion of the new init arg string.
337        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        // Step 3: Insert the string as init args to the cmdline.
344        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        // Counting current size of the cmdline (no overflows are possible as long as the cmdline
355        // size is always smaller or equal to the cmdline capacity provided in constructor)
356        let mut cmdline_size = self.boot_args.len() + 1; // for null terminator
357
358        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    /// Returns a C compatible representation of the command line
366    /// The Linux kernel expects a null terminated cmdline according to the source:
367    /// <https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179>
368    ///
369    /// To get bytes of the cmdline to be written in guest's memory (including the
370    /// null terminator) from this representation, use CString::as_bytes_with_nul()
371    ///
372    /// # Examples
373    ///
374    /// ```rust
375    /// # use linux_loader::cmdline::*;
376    /// let mut cl = Cmdline::new(20).unwrap();
377    /// cl.insert_str("foo").unwrap();
378    /// cl.insert_init_args("bar").unwrap();
379    /// assert_eq!(
380    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
381    ///     b"foo -- bar\0"
382    /// );
383    /// ```
384    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    /// Adds a virtio MMIO device to the kernel command line.
401    ///
402    /// Multiple devices can be specified, with multiple `virtio_mmio.device=` options. This
403    /// function must be called once per device.
404    /// The function appends a string of the following format to the kernel command line:
405    /// `<size>@<baseaddr>:<irq>[:<id>]`.
406    /// For more details see the [documentation] (section `virtio_mmio.device=`).
407    ///
408    /// # Arguments
409    ///
410    /// * `size` - Size of the slot the device occupies on the MMIO bus.
411    /// * `baseaddr` - Physical base address of the device.
412    /// * `irq` - Interrupt number to be used by the device.
413    /// * `id` - Optional platform device ID.
414    ///
415    /// # Examples
416    ///
417    /// ```rust
418    /// # use linux_loader::cmdline::*;
419    /// # use std::ffi::CString;
420    /// # use vm_memory::{GuestAddress, GuestUsize};
421    /// let mut cl = Cmdline::new(100).unwrap();
422    /// cl.add_virtio_mmio_device(1 << 12, GuestAddress(0x1000), 5, Some(42))
423    ///     .unwrap();
424    /// assert_eq!(
425    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
426    ///     b"virtio_mmio.device=4K@0x1000:5:42\0"
427    /// );
428    /// ```
429    ///
430    /// [documentation]: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
431    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    // Converts a `GuestUsize` to a concise string representation, with multiplier suffixes.
455    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    /// Tries to build a [`Cmdline`] with a given capacity from a [`str`]. The format of the
477    /// str provided must be one of the following:
478    ///
479    /// * `<boot args> -- <init args>`
480    /// * `<boot args>`
481    ///
482    /// where `<boot args>` and `<init args>` can contain `--` only if double quoted and
483    /// `<boot args>` and `<init args>` contain at least one non-whitespace char each.
484    ///
485    /// Providing a str not following these rules might end up in undefined behaviour of
486    /// the resulting `Cmdline`.
487    ///
488    /// # Arguments
489    ///
490    /// * `cmdline_raw` - Contains boot params and init params of the cmdline.
491    /// * `capacity` - Capacity of the cmdline.
492    ///
493    /// # Examples
494    ///
495    /// ```rust
496    /// # use linux_loader::cmdline::*;
497    /// let cl = Cmdline::try_from("foo -- bar", 100).unwrap();
498    /// assert_eq!(
499    ///     cl.as_cstring().unwrap().as_bytes_with_nul(),
500    ///     b"foo -- bar\0"
501    /// );
502    /// ```
503    pub fn try_from(cmdline_raw: &str, capacity: usize) -> Result<Cmdline> {
504        // The cmdline_raw argument should contain no more than one INIT_ARGS_SEPARATOR sequence
505        // that is not double quoted; in case the INIT_ARGS_SEPARATOR is found all chars following
506        // it will be parsed as init args.
507
508        if capacity == 0 {
509            return Err(Error::InvalidCapacity);
510        }
511
512        // Step 1: Extract boot args and init args from input by searching for INIT_ARGS_SEPARATOR.
513
514        // Check first occurrence of the INIT_ARGS_SEPARATOR that is not between double quotes.
515        // All chars following the INIT_ARGS_SEPARATOR will be parsed as init args.
516        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                // This does not overflow as long as `delimiter_index + INIT_ARGS_SEPARATOR.len()`
525                // is pointing to the first char after the INIT_ARGS_SEPARATOR which always exists;
526                // as a result, `delimiter_index + INIT_ARGS_SEPARATOR.len()` is less or equal to the
527                // length of the initial string.
528                &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        // Step 2: Check if capacity provided for the cmdline is not exceeded and create a new `Cmdline`
536        // if size check passes.
537        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()); // adds 5 length; 4 chars available
679        assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
680        assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
681
682        let mut cl = Cmdline::new(11).unwrap();
683        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length; 5 chars available
684        assert_eq!(cl.insert_init_args("da"), Err(Error::TooLarge)); // adds 6 (including INIT_ARGS_SEPARATOR) length
685        assert!(cl.insert_init_args("d").is_ok()); // adds 6 (including INIT_ARGS_SEPARATOR)
686
687        let mut cl = Cmdline::new(20).unwrap();
688        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length; 14 chars available
689        assert!(cl.insert_init_args("da").is_ok()); // 8 chars available
690        assert_eq!(cl.insert_init_args("abcdabcd"), Err(Error::TooLarge)); // adds 9 (including space) length
691        assert!(cl.insert_init_args("abcdabc").is_ok()); // adds 8 (including space) length
692    }
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        // `insert_str` also adds a whitespace before the string being inserted.
798        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}