use std::fmt;
use std::result;
use vm_memory::{Address, GuestAddress, GuestUsize};
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidAscii,
InvalidDevice(String),
HasSpace,
HasEquals,
MissingVal(String),
MmioSize,
TooLarge,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::InvalidAscii => write!(f, "String contains a non-printable ASCII character."),
Error::InvalidDevice(ref dev) => write!(
f,
"Invalid device passed to the kernel command line builder: {}.",
dev
),
Error::HasSpace => write!(f, "String contains a space."),
Error::HasEquals => write!(f, "String contains an equals sign."),
Error::MissingVal(ref k) => write!(f, "Missing value for key {}.", k),
Error::MmioSize => write!(
f,
"0-sized virtio MMIO device passed to the kernel command line builder."
),
Error::TooLarge => write!(f, "Inserting string would make command line too long."),
}
}
}
impl std::error::Error for Error {}
pub type Result<T> = result::Result<T, Error>;
fn valid_char(c: char) -> bool {
matches!(c, ' '..='~')
}
fn valid_str(s: &str) -> Result<()> {
if s.chars().all(valid_char) {
Ok(())
} else {
Err(Error::InvalidAscii)
}
}
fn valid_element(s: &str) -> Result<()> {
if !s.chars().all(valid_char) {
Err(Error::InvalidAscii)
} else if s.contains(' ') {
Err(Error::HasSpace)
} else if s.contains('=') {
Err(Error::HasEquals)
} else {
Ok(())
}
}
pub struct Cmdline {
line: String,
capacity: usize,
}
impl Cmdline {
pub fn new(capacity: usize) -> Cmdline {
assert_ne!(capacity, 0);
Cmdline {
line: String::with_capacity(capacity),
capacity,
}
}
fn has_capacity(&self, more: usize) -> Result<()> {
let needs_space = if self.line.is_empty() { 0 } else { 1 };
if self.line.len() + more + needs_space < self.capacity {
Ok(())
} else {
Err(Error::TooLarge)
}
}
fn start_push(&mut self) {
if !self.line.is_empty() {
self.line.push(' ');
}
}
fn end_push(&mut self) {
assert!(self.line.len() < self.capacity);
}
pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
let k = key.as_ref();
let v = val.as_ref();
valid_element(k)?;
valid_element(v)?;
self.has_capacity(k.len() + v.len() + 1)?;
self.start_push();
self.line.push_str(k);
self.line.push('=');
self.line.push_str(v);
self.end_push();
Ok(())
}
pub fn insert_multiple<T: AsRef<str>>(&mut self, key: T, vals: &[T]) -> Result<()> {
let k = key.as_ref();
valid_element(k)?;
if vals.is_empty() {
return Err(Error::MissingVal(k.to_string()));
}
let kv_str = format!(
"{}={}",
k,
vals.iter()
.map(|v| -> Result<&str> {
valid_element(v.as_ref())?;
Ok(v.as_ref())
})
.collect::<Result<Vec<&str>>>()?
.join(",")
);
self.insert_str(kv_str)
}
pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
let s = slug.as_ref();
valid_str(s)?;
self.has_capacity(s.len())?;
self.start_push();
self.line.push_str(s);
self.end_push();
Ok(())
}
pub fn as_str(&self) -> &str {
self.line.as_str()
}
pub fn add_virtio_mmio_device(
&mut self,
size: GuestUsize,
baseaddr: GuestAddress,
irq: u32,
id: Option<u32>,
) -> Result<()> {
if size == 0 {
return Err(Error::MmioSize);
}
let mut device_str = format!(
"virtio_mmio.device={}@0x{:x?}:{}",
Self::guestusize_to_str(size),
baseaddr.raw_value(),
irq
);
if let Some(id) = id {
device_str.push_str(format!(":{}", id).as_str());
}
self.insert_str(&device_str)
}
fn guestusize_to_str(size: GuestUsize) -> String {
const KB_MULT: u64 = 1 << 10;
const MB_MULT: u64 = KB_MULT << 10;
const GB_MULT: u64 = MB_MULT << 10;
if size % GB_MULT == 0 {
return format!("{}G", size / GB_MULT);
}
if size % MB_MULT == 0 {
return format!("{}M", size / MB_MULT);
}
if size % KB_MULT == 0 {
return format!("{}K", size / KB_MULT);
}
size.to_string()
}
}
impl Into<Vec<u8>> for Cmdline {
fn into(self) -> Vec<u8> {
self.line.into_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_insert_hello_world() {
let mut cl = Cmdline::new(100);
assert_eq!(cl.as_str(), "");
assert!(cl.insert("hello", "world").is_ok());
assert_eq!(cl.as_str(), "hello=world");
let s = CString::new(cl).expect("failed to create CString from Cmdline");
assert_eq!(s, CString::new("hello=world").unwrap());
}
#[test]
fn test_insert_multi() {
let mut cl = Cmdline::new(100);
assert!(cl.insert("hello", "world").is_ok());
assert!(cl.insert("foo", "bar").is_ok());
assert_eq!(cl.as_str(), "hello=world foo=bar");
}
#[test]
fn test_insert_space() {
let mut cl = Cmdline::new(100);
assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
assert_eq!(cl.as_str(), "");
}
#[test]
fn test_insert_equals() {
let mut cl = Cmdline::new(100);
assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
assert_eq!(cl.as_str(), "");
}
#[test]
fn test_insert_emoji() {
let mut cl = Cmdline::new(100);
assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
assert_eq!(cl.as_str(), "");
}
#[test]
fn test_insert_string() {
let mut cl = Cmdline::new(13);
assert_eq!(cl.as_str(), "");
assert!(cl.insert_str("noapic").is_ok());
assert_eq!(cl.as_str(), "noapic");
assert!(cl.insert_str("nopci").is_ok());
assert_eq!(cl.as_str(), "noapic nopci");
}
#[test]
fn test_insert_too_large() {
let mut cl = Cmdline::new(4);
assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
assert!(cl.insert("a", "b").is_ok());
assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
assert_eq!(cl.as_str(), "a=b");
let mut cl = Cmdline::new(10);
assert!(cl.insert("ab", "ba").is_ok());
assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge));
assert!(cl.insert("c", "d").is_ok());
}
#[test]
fn test_add_virtio_mmio_device() {
let mut cl = Cmdline::new(5);
assert_eq!(
cl.add_virtio_mmio_device(0, GuestAddress(0), 0, None),
Err(Error::MmioSize)
);
assert_eq!(
cl.add_virtio_mmio_device(1, GuestAddress(0), 0, None),
Err(Error::TooLarge)
);
let mut cl = Cmdline::new(150);
assert!(cl
.add_virtio_mmio_device(1, GuestAddress(0), 1, None)
.is_ok());
let mut expected_str = "virtio_mmio.device=1@0x0:1".to_string();
assert_eq!(cl.as_str(), &expected_str);
assert!(cl
.add_virtio_mmio_device(2 << 10, GuestAddress(0x100), 2, None)
.is_ok());
expected_str.push_str(" virtio_mmio.device=2K@0x100:2");
assert_eq!(cl.as_str(), &expected_str);
assert!(cl
.add_virtio_mmio_device(3 << 20, GuestAddress(0x1000), 3, None)
.is_ok());
expected_str.push_str(" virtio_mmio.device=3M@0x1000:3");
assert_eq!(cl.as_str(), &expected_str);
assert!(cl
.add_virtio_mmio_device(4 << 30, GuestAddress(0x0001_0000), 4, Some(42))
.is_ok());
expected_str.push_str(" virtio_mmio.device=4G@0x10000:4:42");
assert_eq!(cl.as_str(), &expected_str);
}
#[test]
fn test_insert_kv() {
let mut cl = Cmdline::new(10);
let no_vals: Vec<&str> = vec![];
assert_eq!(cl.insert_multiple("foo=", &no_vals), Err(Error::HasEquals));
assert_eq!(
cl.insert_multiple("foo", &no_vals),
Err(Error::MissingVal("foo".to_string()))
);
assert_eq!(
cl.insert_multiple("foo", &vec!["bar "]),
Err(Error::HasSpace)
);
assert_eq!(
cl.insert_multiple("foo", &vec!["bar", "baz"]),
Err(Error::TooLarge)
);
let mut cl = Cmdline::new(100);
assert!(cl.insert_multiple("foo", &vec!["bar"]).is_ok());
assert_eq!(cl.as_str(), "foo=bar");
let mut cl = Cmdline::new(100);
assert!(cl.insert_multiple("foo", &vec!["bar", "baz"]).is_ok());
assert_eq!(cl.as_str(), "foo=bar,baz");
}
}