Path: blob/main/devices/src/virtio/vhost_user_backend/params.rs
5394 views
// Copyright 2021 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::fmt::Debug;56use argh::FromArgValue;7use serde::Deserialize;8use serde_keyvalue::ErrorKind;9use serde_keyvalue::KeyValueDeserializer;1011/// Extends any device configuration with a mandatory extra "vhost" parameter to specify the socket12/// or PCI device to use in order to communicate with a vhost client.13///14/// The `vhost` argument must come first, followed by all the arguments required by `device`.15#[derive(Debug, Deserialize)]16#[serde(deny_unknown_fields)]17// TODO(b/262345003): This requires a custom `Deserialize` implementation to support configuration18// files properly. Right now the pseudo-flattening is done by the `FromArgValue` implementation,19// which is only used with command-line arguments. A good `Deserialize` implementation would allow20// the same behavior with any deserializer, but requires some serde-fu that is above my current21// skills.22pub struct VhostUserParams<T: Debug> {23pub vhost: String,24pub device: T,25}2627impl<T> FromArgValue for VhostUserParams<T>28where29T: Debug + for<'de> Deserialize<'de>,30{31fn from_arg_value(value: &str) -> std::result::Result<Self, String> {32// `from_arg_value` returns a `String` as error, but our deserializer API defines its own33// error type. Perform parsing from a closure so we can easily map returned errors.34let builder = move || {35let mut deserializer = KeyValueDeserializer::from(value);3637// Parse the "vhost" parameter38let id = deserializer.parse_identifier()?;39if id != "vhost" {40return Err(deserializer41.error_here(ErrorKind::SerdeError("expected \"vhost\" parameter".into())));42}43if deserializer.next_char() != Some('=') {44return Err(deserializer.error_here(ErrorKind::ExpectedEqual));45}46let vhost = deserializer.parse_string()?;47match deserializer.next_char() {48Some(',') | None => (),49_ => return Err(deserializer.error_here(ErrorKind::ExpectedComma)),50}5152// Parse the device-specific parameters and finish53let device = T::deserialize(&mut deserializer)?;54deserializer.finish()?;5556Ok(Self {57vhost: vhost.into(),58device,59})60};6162builder().map_err(|e| e.to_string())63}64}6566#[cfg(test)]67mod tests {68use std::path::PathBuf;6970use argh::FromArgValue;71use serde::Deserialize;72use serde_keyvalue::*;7374use super::VhostUserParams;7576#[derive(Debug, Deserialize, PartialEq, Eq)]77#[serde(deny_unknown_fields, rename_all = "kebab-case")]78struct DummyDevice {79path: PathBuf,80#[serde(default)]81boom_range: u32,82}8384fn from_arg_value(s: &str) -> Result<VhostUserParams<DummyDevice>, String> {85VhostUserParams::<DummyDevice>::from_arg_value(s)86}8788#[test]89fn vhost_user_params() {90let device = from_arg_value("vhost=vhost_sock,path=/path/to/dummy,boom-range=42").unwrap();91assert_eq!(device.vhost.as_str(), "vhost_sock");92assert_eq!(93device.device,94DummyDevice {95path: "/path/to/dummy".into(),96boom_range: 42,97}98);99100// Default parameter of device not specified.101let device = from_arg_value("vhost=vhost_sock,path=/path/to/dummy").unwrap();102assert_eq!(device.vhost.as_str(), "vhost_sock");103assert_eq!(104device.device,105DummyDevice {106path: "/path/to/dummy".into(),107boom_range: Default::default(),108}109);110111// Invalid parameter is rejected.112assert_eq!(113from_arg_value("vhost=vhost_sock,path=/path/to/dummy,boom-range=42,invalid-param=10")114.unwrap_err(),115"unknown field `invalid-param`, expected `path` or `boom-range`".to_string(),116);117118// Device path can be parsed even if specified as a number.119// This ensures that we don't flatten the `device` member, which would result in120// `deserialize_any` being called and the type of `path` to be mistaken for an integer.121let device = from_arg_value("vhost=vhost_sock,path=10").unwrap();122assert_eq!(device.vhost.as_str(), "vhost_sock");123assert_eq!(124device.device,125DummyDevice {126path: "10".into(),127boom_range: Default::default(),128}129);130131// Misplaced `vhost` parameter is rejected132let _ = from_arg_value("path=/path/to/dummy,vhost=vhost_sock,boom-range=42").unwrap_err();133}134}135136137