blob: 284493f6c5bc07bd50c2f92d50e992060bb2c9f3 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use std::arch::x86_64::__cpuid;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use std::arch::x86_64::__cpuid_count;
use std::collections::BTreeMap;
use std::net;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use arch::set_default_serial_parameters;
use arch::CpuSet;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use arch::MsrAction;
use arch::MsrConfig;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use arch::MsrFilter;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use arch::MsrRWType;
use arch::MsrValueFrom;
use arch::Pstore;
use arch::VcpuAffinity;
use base::debug;
use base::pagesize;
use cros_async::ExecutorKind;
use devices::serial_device::SerialHardware;
use devices::serial_device::SerialParameters;
use devices::virtio::block::block::DiskOption;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
use devices::virtio::device_constants::video::VideoDeviceConfig;
#[cfg(feature = "gpu")]
use devices::virtio::gpu::GpuParameters;
#[cfg(feature = "audio")]
use devices::virtio::snd::parameters::Parameters as SndParameters;
#[cfg(all(windows, feature = "gpu"))]
use devices::virtio::vhost::user::device::gpu::sys::windows::GpuBackendConfig;
#[cfg(all(windows, feature = "gpu"))]
use devices::virtio::vhost::user::device::gpu::sys::windows::GpuVmmConfig;
#[cfg(all(windows, feature = "audio"))]
use devices::virtio::vhost::user::device::snd::sys::windows::SndSplitConfig;
use devices::virtio::vsock::VsockConfig;
use devices::virtio::NetParameters;
#[cfg(feature = "audio")]
use devices::Ac97Backend;
#[cfg(feature = "audio")]
use devices::Ac97Parameters;
#[cfg(feature = "direct")]
use devices::BusRange;
use devices::PciAddress;
use devices::PflashParameters;
use devices::StubPciParameters;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use hypervisor::CpuHybridType;
use hypervisor::ProtectionType;
use jail::JailConfig;
use resources::AddressRange;
use serde::Deserialize;
use serde::Serialize;
use serde_keyvalue::FromKeyValues;
use uuid::Uuid;
use vm_control::BatteryType;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use x86_64::check_host_hybrid_support;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use x86_64::set_enable_pnp_data_msr_config;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use x86_64::CpuIdCall;
pub(crate) use super::sys::HypervisorKind;
cfg_if::cfg_if! {
if #[cfg(unix)] {
use base::RawDescriptor;
use devices::virtio::fs::passthrough;
#[cfg(feature = "gpu")]
use crate::crosvm::sys::GpuRenderServerParameters;
use libc::{getegid, geteuid};
static KVM_PATH: &str = "/dev/kvm";
#[cfg(any(target_arch = "aarch64"))]
#[cfg(all(unix, feature = "geniezone"))]
static GENIEZONE_PATH: &str = "/dev/gzvm";
#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))]
static GUNYAH_PATH: &str = "/dev/gunyah";
static VHOST_NET_PATH: &str = "/dev/vhost-net";
} else if #[cfg(windows)] {
use base::{Event, Tube};
use crate::crosvm::sys::windows::config::IrqChipKind;
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const ONE_MB: u64 = 1 << 20;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const MB_ALIGNED: u64 = ONE_MB - 1;
// the max bus number is 256 and each bus occupy 1MB, so the max pcie cfg mmio size = 256M
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const MAX_PCIE_ECAM_SIZE: u64 = ONE_MB * 256;
/// Indicates the location and kind of executable kernel for a VM.
#[allow(dead_code)]
#[derive(Debug, Serialize, Deserialize)]
pub enum Executable {
/// An executable intended to be run as a BIOS directly.
Bios(PathBuf),
/// A elf linux kernel, loaded and executed by crosvm.
Kernel(PathBuf),
/// Path to a plugin executable that is forked by crosvm.
Plugin(PathBuf),
}
/// The core types in hybrid architecture.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct CpuCoreType {
/// Intel Atom.
pub atom: CpuSet,
/// Intel Core.
pub core: CpuSet,
}
#[derive(Debug, Default, PartialEq, Eq, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct CpuOptions {
/// Number of CPU cores.
#[serde(default)]
pub num_cores: Option<usize>,
/// Vector of CPU ids to be grouped into the same cluster.
#[serde(default)]
pub clusters: Vec<CpuSet>,
/// Core Type of CPUs.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub core_types: Option<CpuCoreType>,
}
#[derive(Debug, Default, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct MemOptions {
/// Amount of guest memory in MiB.
#[serde(default)]
pub size: Option<u64>,
}
#[derive(Serialize, Deserialize)]
pub struct VhostUserOption {
pub socket: PathBuf,
}
impl FromStr for VhostUserOption {
type Err = <PathBuf as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self { socket: s.parse()? })
}
}
#[derive(Serialize, Deserialize)]
pub struct VhostUserFsOption {
pub socket: PathBuf,
pub tag: String,
}
impl FromStr for VhostUserFsOption {
type Err = &'static str;
fn from_str(param: &str) -> Result<Self, Self::Err> {
// (socket:tag)
let mut components = param.split(':');
let socket = PathBuf::from(
components
.next()
.ok_or("missing socket path for `vhost-user-fs`")?,
);
let tag = components
.next()
.ok_or("missing tag for `vhost-user-fs`")?
.to_owned();
Ok(Self { socket, tag })
}
}
/// Options for virtio-vhost-user proxy device.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
pub struct VvuOption {
pub socket: PathBuf,
pub addr: Option<PciAddress>,
pub uuid: Option<Uuid>,
}
/// A bind mount for directories in the plugin process.
#[derive(Debug, Serialize, Deserialize)]
pub struct BindMount {
pub src: PathBuf,
pub dst: PathBuf,
pub writable: bool,
}
impl FromStr for BindMount {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let components: Vec<&str> = value.split(':').collect();
if components.is_empty() || components.len() > 3 || components[0].is_empty() {
return Err(invalid_value_err(
value,
"`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
));
}
let src = PathBuf::from(components[0]);
if src.is_relative() {
return Err(invalid_value_err(
components[0],
"the source path for `plugin-mount` must be absolute",
));
}
if !src.exists() {
return Err(invalid_value_err(
components[0],
"the source path for `plugin-mount` does not exist",
));
}
let dst = PathBuf::from(match components.get(1) {
None | Some(&"") => components[0],
Some(path) => path,
});
if dst.is_relative() {
return Err(invalid_value_err(
components[1],
"the destination path for `plugin-mount` must be absolute",
));
}
let writable: bool = match components.get(2) {
None => false,
Some(s) => s.parse().map_err(|_| {
invalid_value_err(
components[2],
"the <writable> component for `plugin-mount` is not valid bool",
)
})?,
};
Ok(BindMount { src, dst, writable })
}
}
/// A mapping of linux group IDs for the plugin process.
#[cfg(feature = "plugin")]
#[derive(Debug, Deserialize, Serialize)]
pub struct GidMap {
pub inner: base::platform::Gid,
pub outer: base::platform::Gid,
pub count: u32,
}
#[cfg(feature = "plugin")]
impl FromStr for GidMap {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let components: Vec<&str> = value.split(':').collect();
if components.is_empty() || components.len() > 3 || components[0].is_empty() {
return Err(invalid_value_err(
value,
"`plugin-gid-map` must have exactly 3 components: <inner>[:[<outer>][:<count>]]",
));
}
let inner: base::platform::Gid = components[0].parse().map_err(|_| {
invalid_value_err(
components[0],
"the <inner> component for `plugin-gid-map` is not valid gid",
)
})?;
let outer: base::platform::Gid = match components.get(1) {
None | Some(&"") => inner,
Some(s) => s.parse().map_err(|_| {
invalid_value_err(
components[1],
"the <outer> component for `plugin-gid-map` is not valid gid",
)
})?,
};
let count: u32 = match components.get(2) {
None => 1,
Some(s) => s.parse().map_err(|_| {
invalid_value_err(
components[2],
"the <count> component for `plugin-gid-map` is not valid number",
)
})?,
};
Ok(GidMap {
inner,
outer,
count,
})
}
}
/// Direct IO forwarding options
#[cfg(feature = "direct")]
#[derive(Debug, Deserialize, Serialize)]
pub struct DirectIoOption {
pub path: PathBuf,
pub ranges: Vec<BusRange>,
}
pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1024;
pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 1280;
#[derive(Serialize, Deserialize)]
pub struct TouchDeviceOption {
path: PathBuf,
width: Option<u32>,
height: Option<u32>,
default_width: u32,
default_height: u32,
}
impl TouchDeviceOption {
pub fn new(path: PathBuf) -> TouchDeviceOption {
TouchDeviceOption {
path,
width: None,
height: None,
default_width: DEFAULT_TOUCH_DEVICE_WIDTH,
default_height: DEFAULT_TOUCH_DEVICE_HEIGHT,
}
}
/// Getter for the path to the input event streams.
#[cfg_attr(windows, allow(unused))]
pub fn get_path(&self) -> &Path {
self.path.as_path()
}
/// When a user specifies the parameters for a touch device, width and height are optional.
/// If the width and height are missing, default values are used. Default values can be set
/// dynamically, for example from the display sizes specified by the gpu argument.
#[cfg(feature = "gpu")]
pub fn set_default_size(&mut self, width: u32, height: u32) {
self.default_width = width;
self.default_height = height;
}
/// Setter for the width specified by the user.
pub fn set_width(&mut self, width: u32) {
self.width.replace(width);
}
/// Setter for the height specified by the user.
pub fn set_height(&mut self, height: u32) {
self.height.replace(height);
}
/// If the user specifies the size, use it. Otherwise, use the default values.
#[cfg(any(unix, feature = "gpu"))]
pub fn get_size(&self) -> (u32, u32) {
(
self.width.unwrap_or(self.default_width),
self.height.unwrap_or(self.default_height),
)
}
}
impl FromStr for TouchDeviceOption {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut it = s.split(':');
let mut touch_spec = TouchDeviceOption::new(PathBuf::from(it.next().unwrap().to_owned()));
if let Some(width) = it.next() {
touch_spec.set_width(width.trim().parse().unwrap());
}
if let Some(height) = it.next() {
touch_spec.set_height(height.trim().parse().unwrap());
}
Ok(touch_spec)
}
}
#[derive(Eq, PartialEq, Serialize, Deserialize)]
pub enum SharedDirKind {
FS,
P9,
}
impl FromStr for SharedDirKind {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use SharedDirKind::*;
match s {
"fs" | "FS" => Ok(FS),
"9p" | "9P" | "p9" | "P9" => Ok(P9),
_ => Err("invalid file system type"),
}
}
}
impl Default for SharedDirKind {
fn default() -> SharedDirKind {
SharedDirKind::P9
}
}
#[cfg(unix)]
pub struct SharedDir {
pub src: PathBuf,
pub tag: String,
pub kind: SharedDirKind,
pub uid_map: String,
pub gid_map: String,
pub fs_cfg: passthrough::Config,
pub p9_cfg: p9::Config,
}
#[cfg(unix)]
impl Default for SharedDir {
fn default() -> SharedDir {
SharedDir {
src: Default::default(),
tag: Default::default(),
kind: Default::default(),
uid_map: format!("0 {} 1", unsafe { geteuid() }),
gid_map: format!("0 {} 1", unsafe { getegid() }),
fs_cfg: Default::default(),
p9_cfg: Default::default(),
}
}
}
#[cfg(unix)]
impl FromStr for SharedDir {
type Err = &'static str;
fn from_str(param: &str) -> Result<Self, Self::Err> {
// This is formatted as multiple fields, each separated by ":". The first 2 fields are
// fixed (src:tag). The rest may appear in any order:
//
// * type=TYPE - must be one of "p9" or "fs" (default: p9)
// * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
// (default: "0 <current euid> 1")
// * gidmap=GIDMAP - a gid map in the same format as uidmap
// (default: "0 <current egid> 1")
// * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
// performing quota-related operations, these UIDs are treated as if they have
// CAP_FOWNER.
// * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
// and directory contents should be considered valid (default: 5)
// * cache=CACHE - one of "never", "always", or "auto" (default: auto)
// * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
let mut components = param.split(':');
let src = PathBuf::from(
components
.next()
.ok_or("missing source path for `shared-dir`")?,
);
let tag = components
.next()
.ok_or("missing tag for `shared-dir`")?
.to_owned();
if !src.is_dir() {
return Err("source path for `shared-dir` must be a directory");
}
let mut shared_dir = SharedDir {
src,
tag,
..Default::default()
};
let mut type_opts = vec![];
for opt in components {
let mut o = opt.splitn(2, '=');
let kind = o.next().ok_or("`shared-dir` options must not be empty")?;
let value = o
.next()
.ok_or("`shared-dir` options must be of the form `kind=value`")?;
match kind {
"type" => {
shared_dir.kind = value
.parse()
.map_err(|_| "`type` must be one of `fs` or `9p`")?
}
"uidmap" => shared_dir.uid_map = value.into(),
"gidmap" => shared_dir.gid_map = value.into(),
_ => type_opts.push(opt),
}
}
match shared_dir.kind {
SharedDirKind::FS => {
shared_dir.fs_cfg = type_opts.join(":").parse()?;
}
SharedDirKind::P9 => {
shared_dir.p9_cfg = type_opts.join(":").parse()?;
}
}
Ok(shared_dir)
}
}
#[derive(Debug, Serialize, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields)]
pub struct FileBackedMappingParameters {
pub path: PathBuf,
#[serde(rename = "addr")]
pub address: u64,
pub size: u64,
#[serde(default)]
pub offset: u64,
#[serde(rename = "rw", default)]
pub writable: bool,
#[serde(default)]
pub sync: bool,
#[serde(default)]
pub align: bool,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct HostPcieRootPortParameters {
pub host_path: PathBuf,
pub hp_gpe: Option<u32>,
}
fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64, String> {
// Parse string starting with 0x as hex and others as numbers.
if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
u64::from_str_radix(hex_string, 16)
} else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
u64::from_str_radix(hex_string, 16)
} else {
u64::from_str(maybe_hex_string)
}
.map_err(|e| format!("invalid numeric value {}: {}", maybe_hex_string, e))
}
pub fn parse_mmio_address_range(s: &str) -> Result<Vec<AddressRange>, String> {
s.split(",")
.map(|s| {
let r: Vec<&str> = s.split("-").collect();
if r.len() != 2 {
return Err(invalid_value_err(s, "invalid range"));
}
let parse = |s: &str| -> Result<u64, String> {
match parse_hex_or_decimal(s) {
Ok(v) => Ok(v),
Err(_) => Err(invalid_value_err(s, "expected u64 value")),
}
};
Ok(AddressRange {
start: parse(r[0])?,
end: parse(r[1])?,
})
})
.collect()
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[derive(Deserialize, Serialize, serde_keyvalue::FromKeyValues)]
#[serde(deny_unknown_fields)]
struct UserspaceMsrOptions {
pub index: u32,
#[serde(rename = "type")]
pub rw_type: MsrRWType,
pub action: MsrAction,
#[serde(default = "default_msr_value_from")]
pub from: MsrValueFrom,
#[serde(default = "default_msr_filter")]
pub filter: MsrFilter,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn default_msr_value_from() -> MsrValueFrom {
MsrValueFrom::RWFromRunningCPU
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn default_msr_filter() -> MsrFilter {
MsrFilter::Default
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn parse_userspace_msr_options(value: &str) -> Result<(u32, MsrConfig), String> {
let options: UserspaceMsrOptions = from_key_values(value)?;
Ok((
options.index,
MsrConfig {
rw_type: options.rw_type,
action: options.action,
from: options.from,
filter: options.filter,
},
))
}
pub fn validate_serial_parameters(params: &SerialParameters) -> Result<(), String> {
if params.stdin && params.input.is_some() {
return Err("Cannot specify both stdin and input options".to_string());
}
if params.num < 1 {
return Err(invalid_value_err(
params.num.to_string(),
"Serial port num must be at least 1",
));
}
if params.hardware == SerialHardware::Serial && params.num > 4 {
return Err(invalid_value_err(
format!("{}", params.num),
"Serial port num must be 4 or less",
));
}
Ok(())
}
pub fn parse_serial_options(s: &str) -> Result<SerialParameters, String> {
let params: SerialParameters = from_key_values(s)?;
validate_serial_parameters(&params)?;
Ok(params)
}
#[cfg(feature = "plugin")]
pub fn parse_plugin_mount_option(value: &str) -> Result<BindMount, String> {
let components: Vec<&str> = value.split(':').collect();
if components.is_empty() || components.len() > 3 || components[0].is_empty() {
return Err(invalid_value_err(
value,
"`plugin-mount` should be in a form of: <src>[:[<dst>][:<writable>]]",
));
}
let src = PathBuf::from(components[0]);
if src.is_relative() {
return Err(invalid_value_err(
components[0],
"the source path for `plugin-mount` must be absolute",
));
}
if !src.exists() {
return Err(invalid_value_err(
components[0],
"the source path for `plugin-mount` does not exist",
));
}
let dst = PathBuf::from(match components.get(1) {
None | Some(&"") => components[0],
Some(path) => path,
});
if dst.is_relative() {
return Err(invalid_value_err(
components[1],
"the destination path for `plugin-mount` must be absolute",
));
}
let writable: bool = match components.get(2) {
None => false,
Some(s) => s.parse().map_err(|_| {
invalid_value_err(
components[2],
"the <writable> component for `plugin-mount` is not valid bool",
)
})?,
};
Ok(BindMount { src, dst, writable })
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn parse_memory_region(value: &str) -> Result<AddressRange, String> {
let paras: Vec<&str> = value.split(',').collect();
if paras.len() != 2 {
return Err(invalid_value_err(
value,
"pcie-ecam must have exactly 2 parameters: ecam_base,ecam_size",
));
}
let base = parse_hex_or_decimal(paras[0]).map_err(|_| {
invalid_value_err(
value,
"pcie-ecam, the first parameter base should be integer",
)
})?;
let mut len = parse_hex_or_decimal(paras[1]).map_err(|_| {
invalid_value_err(
value,
"pcie-ecam, the second parameter size should be integer",
)
})?;
if (base & MB_ALIGNED != 0) || (len & MB_ALIGNED != 0) {
return Err(invalid_value_err(
value,
"pcie-ecam, the base and len should be aligned to 1MB",
));
}
if len > MAX_PCIE_ECAM_SIZE {
len = MAX_PCIE_ECAM_SIZE;
}
if base + len >= 0x1_0000_0000 {
return Err(invalid_value_err(
value,
"pcie-ecam, the end address couldn't beyond 4G",
));
}
if base % len != 0 {
return Err(invalid_value_err(
value,
"pcie-ecam, base should be multiple of len",
));
}
if let Some(range) = AddressRange::from_start_and_size(base, len) {
Ok(range)
} else {
Err(invalid_value_err(
value,
"pcie-ecam must be representable as AddressRange",
))
}
}
#[cfg(feature = "direct")]
pub fn parse_pcie_root_port_params(value: &str) -> Result<HostPcieRootPortParameters, String> {
let opts: Vec<_> = value.split(',').collect();
if opts.len() > 2 {
return Err(invalid_value_err(
value,
"pcie-root-port has maxmimum two arguments",
));
}
let pcie_path = PathBuf::from(opts[0]);
if !pcie_path.exists() {
return Err(invalid_value_err(
value,
"the pcie root port path does not exist",
));
}
if !pcie_path.is_dir() {
return Err(invalid_value_err(
value,
"the pcie root port path should be directory",
));
}
let hp_gpe = if opts.len() == 2 {
let gpes: Vec<&str> = opts[1].split('=').collect();
if gpes.len() != 2 || gpes[0] != "hp_gpe" {
return Err(invalid_value_err(value, "it should be hp_gpe=Num"));
}
match gpes[1].parse::<u32>() {
Ok(gpe) => Some(gpe),
Err(_) => {
return Err(invalid_value_err(
value,
"host hp gpe must be a non-negative integer",
));
}
}
} else {
None
};
Ok(HostPcieRootPortParameters {
host_path: pcie_path,
hp_gpe,
})
}
pub fn parse_bus_id_addr(v: &str) -> Result<(u8, u8, u16, u16), String> {
debug!("parse_bus_id_addr: {}", v);
let mut ids = v.split(':');
let errorre = move |item| move |e| format!("{}: {}", item, e);
match (ids.next(), ids.next(), ids.next(), ids.next()) {
(Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
let bus_id = bus_id.parse::<u8>().map_err(errorre("bus_id"))?;
let addr = addr.parse::<u8>().map_err(errorre("addr"))?;
let vid = u16::from_str_radix(vid, 16).map_err(errorre("vid"))?;
let pid = u16::from_str_radix(pid, 16).map_err(errorre("pid"))?;
Ok((bus_id, addr, vid, pid))
}
_ => Err(String::from("BUS_ID:ADDR:BUS_NUM:DEV_NUM")),
}
}
#[cfg(feature = "audio")]
pub fn parse_ac97_options(s: &str) -> Result<Ac97Parameters, String> {
let mut ac97_params: Ac97Parameters = Default::default();
let opts = s
.split(',')
.map(|frag| frag.split('='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
for (k, v) in opts {
match k {
"backend" => {
ac97_params.backend = v
.parse::<Ac97Backend>()
.map_err(|e| invalid_value_err(v, e))?;
}
"capture" => {
ac97_params.capture = v
.parse::<bool>()
.map_err(|e| format!("invalid capture option: {}", e))?;
}
_ => {
super::sys::config::parse_ac97_options(&mut ac97_params, k, v)?;
}
}
}
Ok(ac97_params)
}
pub fn invalid_value_err<T: AsRef<str>, S: ToString>(value: T, expected: S) -> String {
format!("invalid value {}: {}", value.as_ref(), expected.to_string())
}
#[derive(Debug, Serialize, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct BatteryConfig {
#[serde(rename = "type", default)]
pub type_: BatteryType,
}
pub fn parse_cpu_capacity(s: &str) -> Result<BTreeMap<usize, u32>, String> {
let mut cpu_capacity: BTreeMap<usize, u32> = BTreeMap::default();
for cpu_pair in s.split(',') {
let assignment: Vec<&str> = cpu_pair.split('=').collect();
if assignment.len() != 2 {
return Err(invalid_value_err(cpu_pair, "invalid CPU capacity syntax"));
}
let cpu = assignment[0].parse().map_err(|_| {
invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
})?;
let capacity = assignment[1].parse().map_err(|_| {
invalid_value_err(assignment[1], "CPU capacity must be a non-negative integer")
})?;
if cpu_capacity.insert(cpu, capacity).is_some() {
return Err(invalid_value_err(cpu_pair, "CPU index must be unique"));
}
}
Ok(cpu_capacity)
}
pub fn from_key_values<'a, T: Deserialize<'a>>(value: &'a str) -> Result<T, String> {
serde_keyvalue::from_key_values(value).map_err(|e| e.to_string())
}
/// Parse a list of guest to host CPU mappings.
///
/// Each mapping consists of a single guest CPU index mapped to one or more host CPUs in the form
/// accepted by `CpuSet::from_str`:
///
/// `<GUEST-CPU>=<HOST-CPU-SET>[:<GUEST-CPU>=<HOST-CPU-SET>[:...]]`
pub fn parse_cpu_affinity(s: &str) -> Result<VcpuAffinity, String> {
if s.contains('=') {
let mut affinity_map = BTreeMap::new();
for cpu_pair in s.split(':') {
let assignment: Vec<&str> = cpu_pair.split('=').collect();
if assignment.len() != 2 {
return Err(invalid_value_err(
cpu_pair,
"invalid VCPU assignment syntax",
));
}
let guest_cpu = assignment[0].parse().map_err(|_| {
invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
})?;
let host_cpu_set = CpuSet::from_str(assignment[1])?;
if affinity_map.insert(guest_cpu, host_cpu_set).is_some() {
return Err(invalid_value_err(cpu_pair, "VCPU index must be unique"));
}
}
Ok(VcpuAffinity::PerVcpu(affinity_map))
} else {
Ok(VcpuAffinity::Global(CpuSet::from_str(s)?))
}
}
#[cfg(feature = "direct")]
pub fn parse_direct_io_options(s: &str) -> Result<DirectIoOption, String> {
let parts: Vec<&str> = s.splitn(2, '@').collect();
if parts.len() != 2 {
return Err(invalid_value_err(
s,
"missing port range, use /path@X-Y,Z,.. syntax",
));
}
let path = PathBuf::from(parts[0]);
if !path.exists() {
return Err(invalid_value_err(parts[0], "the path does not exist"));
};
let ranges: Result<Vec<BusRange>, String> = parts[1]
.split(',')
.map(|frag| frag.split('-'))
.map(|mut range| {
let base = range
.next()
.map(parse_hex_or_decimal)
.map_or(Ok(None), |r| r.map(Some));
let last = range
.next()
.map(parse_hex_or_decimal)
.map_or(Ok(None), |r| r.map(Some));
(base, last)
})
.map(|range| match range {
(Ok(Some(base)), Ok(None)) => Ok(BusRange { base, len: 1 }),
(Ok(Some(base)), Ok(Some(last))) => Ok(BusRange {
base,
len: last.saturating_sub(base).saturating_add(1),
}),
(Err(_), _) => Err(invalid_value_err(s, "invalid base range value")),
(_, Err(_)) => Err(invalid_value_err(s, "invalid last range value")),
_ => Err(invalid_value_err(s, "invalid range format")),
})
.collect();
Ok(DirectIoOption {
path,
ranges: ranges?,
})
}
pub fn executable_is_plugin(executable: &Option<Executable>) -> bool {
matches!(executable, Some(Executable::Plugin(_)))
}
pub fn parse_pflash_parameters(s: &str) -> Result<PflashParameters, String> {
let pflash_parameters: PflashParameters = from_key_values(s)?;
Ok(pflash_parameters)
}
// BTreeMaps serialize fine, as long as their keys are trivial types. A tuple does not
// work, hence the need to convert to/from a vector form.
mod serde_serial_params {
use std::iter::FromIterator;
use serde::Deserializer;
use serde::Serializer;
use super::*;
pub fn serialize<S>(
params: &BTreeMap<(SerialHardware, u8), SerialParameters>,
ser: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let v: Vec<(&(SerialHardware, u8), &SerialParameters)> = params.iter().collect();
serde::Serialize::serialize(&v, ser)
}
pub fn deserialize<'a, D>(
de: D,
) -> Result<BTreeMap<(SerialHardware, u8), SerialParameters>, D::Error>
where
D: Deserializer<'a>,
{
let params: Vec<((SerialHardware, u8), SerialParameters)> =
serde::Deserialize::deserialize(de)?;
Ok(BTreeMap::from_iter(params.into_iter()))
}
}
/// Aggregate of all configurable options for a running VM.
#[derive(Serialize, Deserialize)]
#[remain::sorted]
pub struct Config {
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), unix))]
pub ac_adapter: bool,
#[cfg(feature = "audio")]
pub ac97_parameters: Vec<Ac97Parameters>,
pub acpi_tables: Vec<PathBuf>,
pub android_fstab: Option<PathBuf>,
pub async_executor: Option<ExecutorKind>,
pub balloon: bool,
pub balloon_bias: i64,
pub balloon_control: Option<PathBuf>,
pub balloon_page_reporting: bool,
pub battery_config: Option<BatteryConfig>,
#[cfg(windows)]
pub block_control_tube: Vec<Tube>,
#[cfg(windows)]
pub block_vhost_user_tube: Vec<Tube>,
#[cfg(windows)]
pub broker_shutdown_event: Option<Event>,
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), unix))]
pub bus_lock_ratelimit: u64,
#[cfg(unix)]
pub coiommu_param: Option<devices::CoIommuParameters>,
pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
pub cpu_clusters: Vec<CpuSet>,
#[cfg(feature = "crash-report")]
pub crash_pipe_name: Option<String>,
#[cfg(feature = "crash-report")]
pub crash_report_uuid: Option<String>,
pub delay_rt: bool,
#[cfg(feature = "direct")]
pub direct_edge_irq: Vec<u32>,
#[cfg(feature = "direct")]
pub direct_fixed_evts: Vec<devices::ACPIPMFixedEvent>,
#[cfg(feature = "direct")]
pub direct_gpe: Vec<u32>,
#[cfg(feature = "direct")]
pub direct_level_irq: Vec<u32>,
#[cfg(feature = "direct")]
pub direct_mmio: Option<DirectIoOption>,
#[cfg(feature = "direct")]
pub direct_pmio: Option<DirectIoOption>,
pub disable_virtio_intx: bool,
pub disks: Vec<DiskOption>,
pub display_window_keyboard: bool,
pub display_window_mouse: bool,
pub dmi_path: Option<PathBuf>,
pub dump_device_tree_blob: Option<PathBuf>,
pub enable_hwp: bool,
pub enable_pnp_data: bool,
pub executable_path: Option<Executable>,
#[cfg(windows)]
pub exit_stats: bool,
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
pub force_calibrated_tsc_leaf: bool,
pub force_s2idle: bool,
#[cfg(feature = "gdb")]
pub gdb: Option<u32>,
#[cfg(any(target_arch = "aarch64"))]
#[cfg(all(unix, feature = "geniezone"))]
pub geniezone_device_path: PathBuf,
#[cfg(all(windows, feature = "gpu"))]
pub gpu_backend_config: Option<GpuBackendConfig>,
#[cfg(all(unix, feature = "gpu"))]
pub gpu_cgroup_path: Option<PathBuf>,
#[cfg(feature = "gpu")]
pub gpu_parameters: Option<GpuParameters>,
#[cfg(all(unix, feature = "gpu"))]
pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
#[cfg(all(unix, feature = "gpu"))]
pub gpu_server_cgroup_path: Option<PathBuf>,
#[cfg(all(windows, feature = "gpu"))]
pub gpu_vmm_config: Option<GpuVmmConfig>,
#[cfg(all(
unix,
any(target_arch = "arm", target_arch = "aarch64"),
feature = "gunyah"
))]
pub gunyah_device_path: PathBuf,
pub host_cpu_topology: bool,
#[cfg(windows)]
pub host_guid: Option<String>,
pub host_ip: Option<net::Ipv4Addr>,
pub hugepages: bool,
pub hypervisor: Option<HypervisorKind>,
pub init_memory: Option<u64>,
pub initrd_path: Option<PathBuf>,
#[cfg(windows)]
pub irq_chip: Option<IrqChipKind>,
pub itmt: bool,
pub jail_config: Option<JailConfig>,
#[cfg(windows)]
pub kernel_log_file: Option<String>,
#[cfg(unix)]
pub kvm_device_path: PathBuf,
#[cfg(unix)]
pub lock_guest_memory: bool,
#[cfg(windows)]
pub log_file: Option<String>,
#[cfg(windows)]
pub logs_directory: Option<String>,
pub mac_address: Option<net_util::MacAddress>,
pub memory: Option<u64>,
pub memory_file: Option<PathBuf>,
pub mmio_address_ranges: Vec<AddressRange>,
#[cfg(target_arch = "aarch64")]
pub mte: bool,
pub net: Vec<NetParameters>,
#[cfg(windows)]
pub net_vhost_user_tube: Option<Tube>,
pub net_vq_pairs: Option<u16>,
pub netmask: Option<net::Ipv4Addr>,
pub no_i8042: bool,
pub no_rtc: bool,
pub no_smt: bool,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub oem_strings: Vec<String>,
pub params: Vec<String>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub pci_low_start: Option<u64>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub pcie_ecam: Option<AddressRange>,
#[cfg(feature = "direct")]
pub pcie_rp: Vec<HostPcieRootPortParameters>,
pub per_vm_core_scheduling: bool,
pub pflash_parameters: Option<PflashParameters>,
#[cfg(feature = "plugin")]
pub plugin_gid_maps: Vec<GidMap>,
pub plugin_mounts: Vec<BindMount>,
pub plugin_root: Option<PathBuf>,
pub pmem_devices: Vec<DiskOption>,
pub privileged_vm: bool,
#[cfg(feature = "process-invariants")]
pub process_invariants_data_handle: Option<u64>,
#[cfg(feature = "process-invariants")]
pub process_invariants_data_size: Option<usize>,
#[cfg(windows)]
pub product_channel: Option<String>,
#[cfg(windows)]
pub product_name: Option<String>,
#[cfg(windows)]
pub product_version: Option<String>,
pub protection_type: ProtectionType,
pub pstore: Option<Pstore>,
#[cfg(windows)]
pub pvclock: bool,
/// Must be `Some` iff `protection_type == ProtectionType::UnprotectedWithFirmware`.
pub pvm_fw: Option<PathBuf>,
pub restore_path: Option<PathBuf>,
pub rng: bool,
pub rt_cpus: CpuSet,
#[serde(with = "serde_serial_params")]
pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
#[cfg(windows)]
pub service_pipe_name: Option<String>,
#[cfg(unix)]
#[serde(skip)]
pub shared_dirs: Vec<SharedDir>,
#[cfg(feature = "slirp-ring-capture")]
pub slirp_capture_file: Option<String>,
#[cfg(all(windows, feature = "audio"))]
pub snd_split_config: Option<SndSplitConfig>,
pub socket_path: Option<PathBuf>,
#[cfg(feature = "tpm")]
pub software_tpm: bool,
#[cfg(feature = "audio")]
pub sound: Option<PathBuf>,
pub split_irqchip: bool,
pub strict_balloon: bool,
pub stub_pci_devices: Vec<StubPciParameters>,
pub swap_dir: Option<PathBuf>,
pub swiotlb: Option<u64>,
#[cfg(windows)]
pub syslog_tag: Option<String>,
#[cfg(unix)]
pub tap_fd: Vec<RawDescriptor>,
pub tap_name: Vec<String>,
#[cfg(target_os = "android")]
pub task_profiles: Vec<String>,
#[cfg(unix)]
pub unmap_guest_memory_on_fork: bool,
pub usb: bool,
pub userspace_msr: BTreeMap<u32, MsrConfig>,
pub vcpu_affinity: Option<VcpuAffinity>,
pub vcpu_cgroup_path: Option<PathBuf>,
pub vcpu_count: Option<usize>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, // CPU index -> hybrid type
#[cfg(unix)]
pub vfio: Vec<super::sys::config::VfioOption>,
#[cfg(unix)]
pub vfio_isolate_hotplug: bool,
pub vhost_net: bool,
#[cfg(unix)]
pub vhost_net_device_path: PathBuf,
pub vhost_user_blk: Vec<VhostUserOption>,
pub vhost_user_console: Vec<VhostUserOption>,
pub vhost_user_fs: Vec<VhostUserFsOption>,
pub vhost_user_gpu: Vec<VhostUserOption>,
pub vhost_user_mac80211_hwsim: Option<VhostUserOption>,
pub vhost_user_net: Vec<VhostUserOption>,
pub vhost_user_snd: Vec<VhostUserOption>,
pub vhost_user_video_dec: Vec<VhostUserOption>,
pub vhost_user_vsock: Vec<VhostUserOption>,
pub vhost_user_wl: Option<VhostUserOption>,
#[cfg(feature = "video-decoder")]
pub video_dec: Vec<VideoDeviceConfig>,
#[cfg(feature = "video-encoder")]
pub video_enc: Vec<VideoDeviceConfig>,
pub virtio_input_evdevs: Vec<PathBuf>,
pub virtio_keyboard: Vec<PathBuf>,
pub virtio_mice: Vec<PathBuf>,
pub virtio_multi_touch: Vec<TouchDeviceOption>,
pub virtio_single_touch: Vec<TouchDeviceOption>,
#[cfg(feature = "audio")]
#[serde(skip)]
pub virtio_snds: Vec<SndParameters>,
pub virtio_switches: Vec<PathBuf>,
pub virtio_trackpad: Vec<TouchDeviceOption>,
pub vsock: Option<VsockConfig>,
#[cfg(all(feature = "vtpm", target_arch = "x86_64"))]
pub vtpm_proxy: bool,
pub vvu_proxy: Vec<VvuOption>,
pub wayland_socket_paths: BTreeMap<String, PathBuf>,
pub x_display: Option<String>,
}
impl Default for Config {
fn default() -> Config {
Config {
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), unix))]
ac_adapter: false,
#[cfg(feature = "audio")]
ac97_parameters: Vec::new(),
acpi_tables: Vec::new(),
android_fstab: None,
async_executor: None,
balloon: true,
balloon_bias: 0,
balloon_control: None,
balloon_page_reporting: false,
battery_config: None,
#[cfg(windows)]
block_control_tube: Vec::new(),
#[cfg(windows)]
block_vhost_user_tube: Vec::new(),
#[cfg(windows)]
broker_shutdown_event: None,
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), unix))]
bus_lock_ratelimit: 0,
#[cfg(unix)]
coiommu_param: None,
#[cfg(feature = "crash-report")]
crash_pipe_name: None,
#[cfg(feature = "crash-report")]
crash_report_uuid: None,
cpu_capacity: BTreeMap::new(),
cpu_clusters: Vec::new(),
delay_rt: false,
#[cfg(feature = "direct")]
direct_edge_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_fixed_evts: Vec::new(),
#[cfg(feature = "direct")]
direct_gpe: Vec::new(),
#[cfg(feature = "direct")]
direct_level_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_mmio: None,
#[cfg(feature = "direct")]
direct_pmio: None,
disks: Vec::new(),
disable_virtio_intx: false,
display_window_keyboard: false,
display_window_mouse: false,
dmi_path: None,
dump_device_tree_blob: None,
enable_hwp: false,
enable_pnp_data: false,
executable_path: None,
#[cfg(windows)]
exit_stats: false,
file_backed_mappings: Vec::new(),
force_calibrated_tsc_leaf: false,
force_s2idle: false,
#[cfg(feature = "gdb")]
gdb: None,
#[cfg(all(windows, feature = "gpu"))]
gpu_backend_config: None,
#[cfg(feature = "gpu")]
gpu_parameters: None,
#[cfg(all(unix, feature = "gpu"))]
gpu_render_server_parameters: None,
#[cfg(all(unix, feature = "gpu"))]
gpu_cgroup_path: None,
#[cfg(all(unix, feature = "gpu"))]
gpu_server_cgroup_path: None,
#[cfg(all(windows, feature = "gpu"))]
gpu_vmm_config: None,
#[cfg(any(target_arch = "aarch64"))]
#[cfg(all(unix, feature = "geniezone"))]
geniezone_device_path: PathBuf::from(GENIEZONE_PATH),
#[cfg(all(
unix,
any(target_arch = "arm", target_arch = "aarch64"),
feature = "gunyah"
))]
gunyah_device_path: PathBuf::from(GUNYAH_PATH),
host_cpu_topology: false,
#[cfg(windows)]
host_guid: None,
host_ip: None,
#[cfg(windows)]
product_version: None,
#[cfg(windows)]
product_channel: None,
hugepages: false,
hypervisor: None,
init_memory: None,
initrd_path: None,
#[cfg(windows)]
irq_chip: None,
itmt: false,
jail_config: if !cfg!(feature = "default-no-sandbox") {
Some(Default::default())
} else {
None
},
#[cfg(windows)]
kernel_log_file: None,
#[cfg(unix)]
kvm_device_path: PathBuf::from(KVM_PATH),
#[cfg(unix)]
lock_guest_memory: false,
#[cfg(windows)]
log_file: None,
#[cfg(windows)]
logs_directory: None,
mac_address: None,
memory: None,
memory_file: None,
mmio_address_ranges: Vec::new(),
#[cfg(target_arch = "aarch64")]
mte: false,
net: Vec::new(),
#[cfg(windows)]
net_vhost_user_tube: None,
net_vq_pairs: None,
netmask: None,
no_i8042: false,
no_rtc: false,
no_smt: false,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
oem_strings: Vec::new(),
params: Vec::new(),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pci_low_start: None,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pcie_ecam: None,
#[cfg(feature = "direct")]
pcie_rp: Vec::new(),
per_vm_core_scheduling: false,
pflash_parameters: None,
#[cfg(feature = "plugin")]
plugin_gid_maps: Vec::new(),
plugin_mounts: Vec::new(),
plugin_root: None,
pmem_devices: Vec::new(),
privileged_vm: false,
#[cfg(feature = "process-invariants")]
process_invariants_data_handle: None,
#[cfg(feature = "process-invariants")]
process_invariants_data_size: None,
#[cfg(windows)]
product_name: None,
protection_type: ProtectionType::Unprotected,
pstore: None,
#[cfg(windows)]
pvclock: false,
pvm_fw: None,
restore_path: None,
rng: true,
rt_cpus: Default::default(),
serial_parameters: BTreeMap::new(),
#[cfg(windows)]
service_pipe_name: None,
#[cfg(unix)]
shared_dirs: Vec::new(),
#[cfg(feature = "slirp-ring-capture")]
slirp_capture_file: None,
#[cfg(all(windows, feature = "audio"))]
snd_split_config: None,
swap_dir: None,
socket_path: None,
#[cfg(feature = "tpm")]
software_tpm: false,
#[cfg(feature = "audio")]
sound: None,
split_irqchip: false,
strict_balloon: false,
stub_pci_devices: Vec::new(),
swiotlb: None,
#[cfg(windows)]
syslog_tag: None,
#[cfg(unix)]
tap_fd: Vec::new(),
tap_name: Vec::new(),
#[cfg(target_os = "android")]
task_profiles: Vec::new(),
#[cfg(unix)]
unmap_guest_memory_on_fork: false,
usb: true,
userspace_msr: BTreeMap::new(),
vcpu_affinity: None,
vcpu_cgroup_path: None,
vcpu_count: None,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
vcpu_hybrid_type: BTreeMap::new(),
#[cfg(unix)]
vfio: Vec::new(),
#[cfg(unix)]
vfio_isolate_hotplug: false,
vhost_net: false,
#[cfg(unix)]
vhost_net_device_path: PathBuf::from(VHOST_NET_PATH),
vhost_user_blk: Vec::new(),
vhost_user_console: Vec::new(),
vhost_user_video_dec: Vec::new(),
vhost_user_fs: Vec::new(),
vhost_user_gpu: Vec::new(),
vhost_user_mac80211_hwsim: None,
vhost_user_net: Vec::new(),
vhost_user_snd: Vec::new(),
vhost_user_vsock: Vec::new(),
vhost_user_wl: None,
vsock: None,
#[cfg(feature = "video-decoder")]
video_dec: Vec::new(),
#[cfg(feature = "video-encoder")]
video_enc: Vec::new(),
virtio_input_evdevs: Vec::new(),
virtio_keyboard: Vec::new(),
virtio_mice: Vec::new(),
virtio_multi_touch: Vec::new(),
virtio_single_touch: Vec::new(),
#[cfg(feature = "audio")]
virtio_snds: Vec::new(),
virtio_switches: Vec::new(),
virtio_trackpad: Vec::new(),
#[cfg(all(feature = "vtpm", target_arch = "x86_64"))]
vtpm_proxy: false,
vvu_proxy: Vec::new(),
wayland_socket_paths: BTreeMap::new(),
x_display: None,
}
}
}
pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
if cfg.executable_path.is_none() {
return Err("Executable is not specified".to_string());
}
if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) {
return Err("`plugin-root` requires `plugin`".to_string());
}
#[cfg(feature = "gpu")]
{
crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
}
#[cfg(feature = "gdb")]
if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
return Err("`gdb` requires the number of vCPU to be 1".to_string());
}
if cfg.host_cpu_topology {
if cfg.no_smt {
return Err(
"`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
the smt of the Guest is the same as that of the Host when \
`host-cpu-topology` is set."
.to_string(),
);
}
let pcpu_count =
base::number_of_logical_cores().expect("Could not read number of logical cores");
if let Some(vcpu_count) = cfg.vcpu_count {
if pcpu_count != vcpu_count {
return Err(format!(
"`host-cpu-topology` requires the count of vCPUs({}) to equal the \
count of CPUs({}) on host.",
vcpu_count, pcpu_count
));
}
} else {
cfg.vcpu_count = Some(pcpu_count);
}
match &cfg.vcpu_affinity {
None => {
let mut affinity_map = BTreeMap::new();
for cpu_id in 0..cfg.vcpu_count.unwrap() {
affinity_map.insert(cpu_id, CpuSet::new([cpu_id]));
}
cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
}
_ => {
return Err(
"`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
.to_string(),
);
}
}
} else {
// TODO(b/215297064): Support generic cpuaffinity if there's a need.
if !cfg.userspace_msr.is_empty() {
for (_, msr_config) in cfg.userspace_msr.iter() {
if msr_config.from == MsrValueFrom::RWFromRunningCPU {
return Err(
"`userspace-msr` must set `cpu0` if `host-cpu-topology` is not set"
.to_string(),
);
}
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if !cfg.vcpu_hybrid_type.is_empty() {
if cfg.host_cpu_topology {
return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
}
check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
.map_err(|e| format!("the cpu doesn't support `core-types`: {}", e))?;
if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
return Err("`core-types` must be set for all virtual CPUs".to_string());
}
for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
return Err("`core-types` must be set for all virtual CPUs".to_string());
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if cfg.enable_hwp && !cfg.host_cpu_topology {
return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
}
if cfg.enable_pnp_data {
if !cfg.host_cpu_topology {
return Err(
"setting `enable_pnp_data` must require `host-cpu-topology` is set previously."
.to_string(),
);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
set_enable_pnp_data_msr_config(&mut cfg.userspace_msr)
.map_err(|e| format!("MSR can't be passed through {}", e))?;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if cfg.itmt {
use std::collections::BTreeSet;
// ITMT only works on the case each vCPU is 1:1 mapping to a pCPU.
// `host-cpu-topology` has already set this 1:1 mapping. If no
// `host-cpu-topology`, we need check the cpu affinity setting.
if !cfg.host_cpu_topology {
// only VcpuAffinity::PerVcpu supports setting cpu affinity
// for each vCPU.
if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
// ITMT allows more pCPUs than vCPUs.
if v.len() != cfg.vcpu_count.unwrap_or(1) {
return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
}
let mut pcpu_set = BTreeSet::new();
for cpus in v.values() {
if cpus.len() != 1 {
return Err(
"`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
);
}
// Ensure that each vCPU corresponds to a different pCPU to avoid pCPU sharing,
// otherwise it will seriously affect the ITMT scheduling optimization effect.
if !pcpu_set.insert(cpus[0]) {
return Err(
"`cpu_host` requires affinity to be set different pVPU for each vCPU."
.to_owned(),
);
}
}
} else {
return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
}
}
if !cfg.enable_hwp {
return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
}
}
if !cfg.balloon && cfg.balloon_control.is_some() {
return Err("'balloon-control' requires enabled balloon".to_string());
}
if !cfg.balloon && cfg.balloon_page_reporting {
return Err("'balloon_page_reporting' requires enabled balloon".to_string());
}
#[cfg(unix)]
if cfg.lock_guest_memory && cfg.jail_config.is_none() {
return Err("'lock-guest-memory' and 'disable-sandbox' are mutually exclusive".to_string());
}
// TODO(b/253386409): Vmm-swap only support sandboxed devices until vmm-swap use
// `devices::Suspendable` to suspend devices.
#[cfg(feature = "swap")]
if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
}
set_default_serial_parameters(
&mut cfg.serial_parameters,
!cfg.vhost_user_console.is_empty(),
);
for mapping in cfg.file_backed_mappings.iter_mut() {
validate_file_backed_mapping(mapping)?;
}
// Validate platform specific things
super::sys::config::validate_config(cfg)
}
fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
let pagesize_mask = pagesize() as u64 - 1;
let aligned_address = mapping.address & !pagesize_mask;
let aligned_size =
((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
if mapping.align {
mapping.address = aligned_address;
mapping.size = aligned_size;
} else if aligned_address != mapping.address || aligned_size != mapping.size {
return Err(
"--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
);
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::needless_update)]
mod tests {
#[cfg(unix)]
use std::time::Duration;
use argh::FromArgs;
use devices::PciClassCode;
use devices::StubPciParameters;
use super::*;
#[test]
fn parse_cpu_opts() {
let res: CpuOptions = from_key_values("").unwrap();
assert_eq!(res, CpuOptions::default());
// num_cores
let res: CpuOptions = from_key_values("12").unwrap();
assert_eq!(
res,
CpuOptions {
num_cores: Some(12),
..Default::default()
}
);
let res: CpuOptions = from_key_values("num-cores=16").unwrap();
assert_eq!(
res,
CpuOptions {
num_cores: Some(16),
..Default::default()
}
);
// clusters
let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
assert_eq!(
res,
CpuOptions {
clusters: vec![
CpuSet::new([0]),
CpuSet::new([1]),
CpuSet::new([2]),
CpuSet::new([3])
],
..Default::default()
}
);
let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
assert_eq!(
res,
CpuOptions {
clusters: vec![CpuSet::new([0, 1, 2, 3])],
..Default::default()
}
);
let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
assert_eq!(
res,
CpuOptions {
clusters: vec![
CpuSet::new([0, 2]),
CpuSet::new([1, 3]),
CpuSet::new([4, 5, 6, 7, 12])
],
..Default::default()
}
);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
assert_eq!(
res,
CpuOptions {
core_types: Some(CpuCoreType {
atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
core: CpuSet::new([0, 2])
}),
..Default::default()
}
);
}
// All together
let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
assert_eq!(
res,
CpuOptions {
num_cores: Some(16),
clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
..Default::default()
}
);
let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
assert_eq!(
res,
CpuOptions {
num_cores: Some(32),
clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
..Default::default()
}
);
}
#[test]
fn parse_cpu_set_single() {
assert_eq!(
CpuSet::from_str("123").expect("parse failed"),
CpuSet::new([123])
);
}
#[test]
fn parse_cpu_set_list() {
assert_eq!(
CpuSet::from_str("0,1,2,3").expect("parse failed"),
CpuSet::new([0, 1, 2, 3])
);
}
#[test]
fn parse_cpu_set_range() {
assert_eq!(
CpuSet::from_str("0-3").expect("parse failed"),
CpuSet::new([0, 1, 2, 3])
);
}
#[test]
fn parse_cpu_set_list_of_ranges() {
assert_eq!(
CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
CpuSet::new([3, 4, 7, 8, 9, 18])
);
}
#[test]
fn parse_cpu_set_repeated() {
// For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t conversion.
assert_eq!(
CpuSet::from_str("1,1,1").expect("parse failed"),
CpuSet::new([1, 1, 1])
);
}
#[test]
fn parse_cpu_set_negative() {
// Negative CPU numbers are not allowed.
CpuSet::from_str("-3").expect_err("parse should have failed");
}
#[test]
fn parse_cpu_set_reverse_range() {
// Ranges must be from low to high.
CpuSet::from_str("5-2").expect_err("parse should have failed");
}
#[test]
fn parse_cpu_set_open_range() {
CpuSet::from_str("3-").expect_err("parse should have failed");
}
#[test]
fn parse_cpu_set_extra_comma() {
CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
}
#[test]
fn parse_cpu_affinity_global() {
assert_eq!(
parse_cpu_affinity("0,5-7,9").expect("parse failed"),
VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
);
}
#[test]
fn parse_cpu_affinity_per_vcpu_one_to_one() {
let mut expected_map = BTreeMap::new();
expected_map.insert(0, CpuSet::new([0]));
expected_map.insert(1, CpuSet::new([1]));
expected_map.insert(2, CpuSet::new([2]));
expected_map.insert(3, CpuSet::new([3]));
assert_eq!(
parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
VcpuAffinity::PerVcpu(expected_map),
);
}
#[test]
fn parse_cpu_affinity_per_vcpu_sets() {
let mut expected_map = BTreeMap::new();
expected_map.insert(0, CpuSet::new([0, 1, 2]));
expected_map.insert(1, CpuSet::new([3, 4, 5]));
expected_map.insert(2, CpuSet::new([6, 7, 8]));
assert_eq!(
parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
VcpuAffinity::PerVcpu(expected_map),
);
}
#[test]
fn parse_mem_opts() {
let res: MemOptions = from_key_values("").unwrap();
assert_eq!(res.size, None);
let res: MemOptions = from_key_values("1024").unwrap();
assert_eq!(res.size, Some(1024));
let res: MemOptions = from_key_values("size=0x4000").unwrap();
assert_eq!(res.size, Some(16384));
}
#[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_vaild() {
parse_ac97_options("backend=cras").expect("parse should have succeded");
}
#[cfg(feature = "audio")]
#[test]
fn parse_ac97_null_vaild() {
parse_ac97_options("backend=null").expect("parse should have succeded");
}
#[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_capture_vaild() {
parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
}
#[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_client_type() {
parse_ac97_options("backend=cras,capture=true,client_type=crosvm")
.expect("parse should have succeded");
parse_ac97_options("backend=cras,capture=true,client_type=arcvm")
.expect("parse should have succeded");
parse_ac97_options("backend=cras,capture=true,client_type=none")
.expect_err("parse should have failed");
}
#[test]
fn parse_serial_vaild() {
parse_serial_options("type=syslog,num=1,console=true,stdin=true")
.expect("parse should have succeded");
}
#[test]
fn parse_serial_virtio_console_vaild() {
parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
.expect("parse should have succeded");
}
#[test]
fn parse_serial_valid_no_num() {
parse_serial_options("type=syslog").expect("parse should have succeded");
}
#[test]
fn parse_serial_equals_in_value() {
let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
.expect("parse should have succeded");
assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
}
#[test]
fn parse_serial_invalid_type() {
parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
}
#[test]
fn parse_serial_invalid_num_upper() {
parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
}
#[test]
fn parse_serial_invalid_num_lower() {
parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
}
#[test]
fn parse_serial_virtio_console_invalid_num_lower() {
parse_serial_options("type=syslog,hardware=virtio-console,num=0")
.expect_err("parse should have failed");
}
#[test]
fn parse_serial_invalid_num_string() {
parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
}
#[test]
fn parse_serial_invalid_option() {
parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
}
#[test]
fn parse_serial_invalid_two_stdin() {
assert!(TryInto::<Config>::try_into(
crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--serial",
"num=1,type=stdout,stdin=true",
"--serial",
"num=2,type=stdout,stdin=true"
]
)
.unwrap()
)
.is_err())
}
#[test]
fn parse_plugin_mount_invalid() {
"".parse::<BindMount>().expect_err("parse should fail");
"/dev/null:/dev/null:true:false"
.parse::<BindMount>()
.expect_err("parse should fail because too many arguments");
"null:/dev/null:true"
.parse::<BindMount>()
.expect_err("parse should fail because source is not absolute");
"/dev/null:null:true"
.parse::<BindMount>()
.expect_err("parse should fail because source is not absolute");
"/dev/null:null:blah"
.parse::<BindMount>()
.expect_err("parse should fail because flag is not boolean");
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_valid() {
let opt: GidMap = "1:2:3".parse().expect("parse should succeed");
assert_eq!(opt.inner, 1);
assert_eq!(opt.outer, 2);
assert_eq!(opt.count, 3);
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_valid_shorthand() {
let opt: GidMap = "1".parse().expect("parse should succeed");
assert_eq!(opt.inner, 1);
assert_eq!(opt.outer, 1);
assert_eq!(opt.count, 1);
let opt: GidMap = "1:2".parse().expect("parse should succeed");
assert_eq!(opt.inner, 1);
assert_eq!(opt.outer, 2);
assert_eq!(opt.count, 1);
let opt: GidMap = "1::3".parse().expect("parse should succeed");
assert_eq!(opt.inner, 1);
assert_eq!(opt.outer, 1);
assert_eq!(opt.count, 3);
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_invalid() {
"".parse::<GidMap>().expect_err("parse should fail");
"1:2:3:4"
.parse::<GidMap>()
.expect_err("parse should fail because too many arguments");
"blah:2:3"
.parse::<GidMap>()
.expect_err("parse should fail because inner is not a number");
"1:blah:3"
.parse::<GidMap>()
.expect_err("parse should fail because outer is not a number");
"1:2:blah"
.parse::<GidMap>()
.expect_err("parse should fail because count is not a number");
}
#[test]
fn parse_battery_valid() {
let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
assert_eq!(bat_config.type_, BatteryType::Goldfish);
}
#[test]
fn parse_battery_valid_no_type() {
let bat_config: BatteryConfig = from_key_values("").unwrap();
assert_eq!(bat_config.type_, BatteryType::Goldfish);
}
#[test]
fn parse_battery_invalid_parameter() {
from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
}
#[test]
fn parse_battery_invalid_type_value() {
from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
}
#[test]
fn parse_stub_pci() {
let params = from_key_values::<StubPciParameters>("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa").unwrap();
assert_eq!(params.address.bus, 1);
assert_eq!(params.address.dev, 2);
assert_eq!(params.address.func, 3);
assert_eq!(params.vendor, 0xfffe);
assert_eq!(params.device, 0xfffd);
assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
assert_eq!(params.class.subclass, 0xc1);
assert_eq!(params.class.programming_interface, 0xc2);
assert_eq!(params.subsystem_vendor, 0xfffc);
assert_eq!(params.subsystem_device, 0xfffb);
assert_eq!(params.revision, 0xa);
}
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_valid() {
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
let params = parse_direct_io_options("/dev/zero@1,100-110").unwrap();
assert_eq!(params.path.to_str(), Some("/dev/zero"));
assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
assert_eq!(params.ranges[1], BusRange { base: 100, len: 11 });
}
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_hex() {
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
let params = parse_direct_io_options("/dev/zero@1,0x10,100-110,0x10-0x20").unwrap();
assert_eq!(params.path.to_str(), Some("/dev/zero"));
assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
assert_eq!(params.ranges[1], BusRange { base: 0x10, len: 1 });
assert_eq!(params.ranges[2], BusRange { base: 100, len: 11 });
assert_eq!(
params.ranges[3],
BusRange {
base: 0x10,
len: 0x11
}
);
}
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_invalid() {
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
assert!(parse_direct_io_options("/dev/zero@0y10")
.unwrap_err()
.to_string()
.contains("invalid base range value"));
assert!(parse_direct_io_options("/dev/zero@")
.unwrap_err()
.to_string()
.contains("invalid base range value"));
}
#[test]
fn parse_file_backed_mapping_valid() {
let params = from_key_values::<FileBackedMappingParameters>(
"addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
)
.unwrap();
assert_eq!(params.address, 0x1000);
assert_eq!(params.size, 0x2000);
assert_eq!(params.path, PathBuf::from("/dev/mem"));
assert_eq!(params.offset, 0x3000);
assert!(params.writable);
assert!(params.sync);
}
#[test]
fn parse_file_backed_mapping_incomplete() {
assert!(
from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
.unwrap_err()
.contains("missing field `path`")
);
assert!(
from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
.unwrap_err()
.contains("missing field `addr`")
);
assert!(
from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
.unwrap_err()
.contains("missing field `size`")
);
}
#[test]
fn parse_file_backed_mapping_unaligned_addr() {
let mut params =
from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
.unwrap();
assert!(validate_file_backed_mapping(&mut params)
.unwrap_err()
.contains("aligned"));
}
#[test]
fn parse_file_backed_mapping_unaligned_size() {
let mut params =
from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
.unwrap();
assert!(validate_file_backed_mapping(&mut params)
.unwrap_err()
.contains("aligned"));
}
#[test]
fn parse_file_backed_mapping_align() {
let mut params = from_key_values::<FileBackedMappingParameters>(
"addr=0x3042,size=0xff0,path=/dev/mem,align",
)
.unwrap();
assert_eq!(params.address, 0x3042);
assert_eq!(params.size, 0xff0);
validate_file_backed_mapping(&mut params).unwrap();
assert_eq!(params.address, 0x3000);
assert_eq!(params.size, 0x2000);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn parse_userspace_msr_options_test() {
let (pass_cpu0_index, pass_cpu0_cfg) =
parse_userspace_msr_options("0x10,type=w,action=pass,filter=yes").unwrap();
assert_eq!(pass_cpu0_index, 0x10);
assert_eq!(pass_cpu0_cfg.rw_type, MsrRWType::WriteOnly);
assert_eq!(pass_cpu0_cfg.action, MsrAction::MsrPassthrough);
assert_eq!(pass_cpu0_cfg.filter, MsrFilter::Override);
let (pass_cpu0_index, pass_cpu0_cfg) =
parse_userspace_msr_options("0x10,type=r,action=pass,from=cpu0").unwrap();
assert_eq!(pass_cpu0_index, 0x10);
assert_eq!(pass_cpu0_cfg.rw_type, MsrRWType::ReadOnly);
assert_eq!(pass_cpu0_cfg.action, MsrAction::MsrPassthrough);
assert_eq!(pass_cpu0_cfg.from, MsrValueFrom::RWFromCPU0);
let (pass_cpus_index, pass_cpus_cfg) =
parse_userspace_msr_options("0x10,type=rw,action=pass").unwrap();
assert_eq!(pass_cpus_index, 0x10);
assert_eq!(pass_cpus_cfg.rw_type, MsrRWType::ReadWrite);
assert_eq!(pass_cpus_cfg.action, MsrAction::MsrPassthrough);
assert_eq!(pass_cpus_cfg.from, MsrValueFrom::RWFromRunningCPU);
let (pass_cpus_index, pass_cpus_cfg) =
parse_userspace_msr_options("0x10,type=rw,action=emu").unwrap();
assert_eq!(pass_cpus_index, 0x10);
assert_eq!(pass_cpus_cfg.rw_type, MsrRWType::ReadWrite);
assert_eq!(pass_cpus_cfg.action, MsrAction::MsrEmulate);
assert_eq!(pass_cpus_cfg.from, MsrValueFrom::RWFromRunningCPU);
assert!(parse_userspace_msr_options("0x10,action=none").is_err());
assert!(parse_userspace_msr_options("0x10,action=pass").is_err());
assert!(parse_userspace_msr_options("0x10,type=none").is_err());
assert!(parse_userspace_msr_options("0x10,type=rw").is_err());
assert!(parse_userspace_msr_options("0x10,type=w,action=pass,from=f").is_err());
assert!(parse_userspace_msr_options("0x10").is_err());
assert!(parse_userspace_msr_options("hoge").is_err());
}
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
#[test]
fn parse_video() {
use devices::virtio::device_constants::video::VideoBackendType;
#[cfg(feature = "libvda")]
{
let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
assert_eq!(params.backend, VideoBackendType::Libvda);
let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
assert_eq!(params.backend, VideoBackendType::LibvdaVd);
}
#[cfg(feature = "ffmpeg")]
{
let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
assert_eq!(params.backend, VideoBackendType::Ffmpeg);
}
#[cfg(feature = "vaapi")]
{
let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
assert_eq!(params.backend, VideoBackendType::Vaapi);
}
}
#[test]
fn parse_vvu() {
assert_eq!(
from_key_values::<VvuOption>(
"/tmp/vvu-sock,addr=05:2.1,uuid=23546c3d-962d-4ebc-94d9-4acf50996944"
)
.unwrap(),
VvuOption {
socket: PathBuf::from("/tmp/vvu-sock"),
addr: Some(PciAddress::new(0, 0x05, 0x02, 1).unwrap()),
uuid: Some(Uuid::parse_str("23546c3d-962d-4ebc-94d9-4acf50996944").unwrap()),
}
);
}
#[cfg(unix)]
#[test]
fn parse_shared_dir() {
// Although I want to test /usr/local/bin, Use / instead of
// /usr/local/bin, as /usr/local/bin doesn't always exist.
let s = "/:usr_local_bin:type=fs:cache=always:uidmap=0 655360 5000,5000 600 50,5050 660410 1994950:gidmap=0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950:timeout=3600:rewrite-security-xattrs=true:ascii_casefold=false:writeback=true:posix_acl=true";
let shared_dir: SharedDir = s.parse().unwrap();
assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
assert_eq!(shared_dir.tag, "usr_local_bin");
assert!(shared_dir.kind == SharedDirKind::FS);
assert_eq!(
shared_dir.uid_map,
"0 655360 5000,5000 600 50,5050 660410 1994950"
);
assert_eq!(
shared_dir.gid_map,
"0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 1994950"
);
assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
assert_eq!(shared_dir.fs_cfg.attr_timeout, Duration::from_secs(3600));
assert_eq!(shared_dir.fs_cfg.entry_timeout, Duration::from_secs(3600));
assert_eq!(shared_dir.fs_cfg.writeback, true);
assert_eq!(
shared_dir.fs_cfg.cache_policy,
passthrough::CachePolicy::Always
);
assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
assert_eq!(shared_dir.fs_cfg.use_dax, false);
assert_eq!(shared_dir.fs_cfg.posix_acl, true);
}
#[cfg(unix)]
#[test]
fn parse_shared_dir_oem() {
let shared_dir: SharedDir = "/:oem_etc:type=fs:cache=always:uidmap=0 299 1, 5000 600 50:gidmap=0 300 1, 5000 600 50:timeout=3600:rewrite-security-xattrs=true".parse().unwrap();
assert_eq!(shared_dir.src, Path::new("/").to_path_buf());
assert_eq!(shared_dir.tag, "oem_etc");
assert!(shared_dir.kind == SharedDirKind::FS);
assert_eq!(shared_dir.uid_map, "0 299 1, 5000 600 50");
assert_eq!(shared_dir.gid_map, "0 300 1, 5000 600 50");
assert_eq!(shared_dir.fs_cfg.ascii_casefold, false);
assert_eq!(shared_dir.fs_cfg.attr_timeout, Duration::from_secs(3600));
assert_eq!(shared_dir.fs_cfg.entry_timeout, Duration::from_secs(3600));
assert_eq!(shared_dir.fs_cfg.writeback, false);
assert_eq!(
shared_dir.fs_cfg.cache_policy,
passthrough::CachePolicy::Always
);
assert_eq!(shared_dir.fs_cfg.rewrite_security_xattrs, true);
assert_eq!(shared_dir.fs_cfg.use_dax, false);
assert_eq!(shared_dir.fs_cfg.posix_acl, true);
}
}