xref: /aosp_15_r20/external/crosvm/devices/src/virtio/vhost/user/mod.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 pub mod device;
6 
7 use std::fmt::Debug;
8 
9 use argh::FromArgValue;
10 use serde::Deserialize;
11 use serde_keyvalue::ErrorKind;
12 use serde_keyvalue::KeyValueDeserializer;
13 
14 pub use self::device::*;
15 
16 /// Extends any device configuration with a mandatory extra "vhost" parameter to specify the socket
17 /// or PCI device to use in order to communicate with a vhost client.
18 ///
19 /// The `vhost` argument must come first, followed by all the arguments required by `device`.
20 #[derive(Debug, Deserialize)]
21 #[serde(deny_unknown_fields)]
22 // TODO(b/262345003): This requires a custom `Deserialize` implementation to support configuration
23 // files properly. Right now the pseudo-flattening is done by the `FromArgValue` implementation,
24 // which is only used with command-line arguments. A good `Deserialize` implementation would allow
25 // the same behavior with any deserializer, but requires some serde-fu that is above my current
26 // skills.
27 pub struct VhostUserParams<T: Debug> {
28     pub vhost: String,
29     pub device: T,
30 }
31 
32 impl<T> FromArgValue for VhostUserParams<T>
33 where
34     T: Debug + for<'de> Deserialize<'de>,
35 {
from_arg_value(value: &str) -> std::result::Result<Self, String>36     fn from_arg_value(value: &str) -> std::result::Result<Self, String> {
37         // `from_arg_value` returns a `String` as error, but our deserializer API defines its own
38         // error type. Perform parsing from a closure so we can easily map returned errors.
39         let builder = move || {
40             let mut deserializer = KeyValueDeserializer::from(value);
41 
42             // Parse the "vhost" parameter
43             let id = deserializer.parse_identifier()?;
44             if id != "vhost" {
45                 return Err(deserializer
46                     .error_here(ErrorKind::SerdeError("expected \"vhost\" parameter".into())));
47             }
48             if deserializer.next_char() != Some('=') {
49                 return Err(deserializer.error_here(ErrorKind::ExpectedEqual));
50             }
51             let vhost = deserializer.parse_string()?;
52             match deserializer.next_char() {
53                 Some(',') | None => (),
54                 _ => return Err(deserializer.error_here(ErrorKind::ExpectedComma)),
55             }
56 
57             // Parse the device-specific parameters and finish
58             let device = T::deserialize(&mut deserializer)?;
59             deserializer.finish()?;
60 
61             Ok(Self {
62                 vhost: vhost.into(),
63                 device,
64             })
65         };
66 
67         builder().map_err(|e| e.to_string())
68     }
69 }
70 
71 #[cfg(test)]
72 mod tests {
73     use std::path::PathBuf;
74 
75     use argh::FromArgValue;
76     use serde::Deserialize;
77     use serde_keyvalue::*;
78 
79     use super::VhostUserParams;
80 
81     #[derive(Debug, Deserialize, PartialEq, Eq)]
82     #[serde(deny_unknown_fields, rename_all = "kebab-case")]
83     struct DummyDevice {
84         path: PathBuf,
85         #[serde(default)]
86         boom_range: u32,
87     }
88 
from_arg_value(s: &str) -> Result<VhostUserParams<DummyDevice>, String>89     fn from_arg_value(s: &str) -> Result<VhostUserParams<DummyDevice>, String> {
90         VhostUserParams::<DummyDevice>::from_arg_value(s)
91     }
92 
93     #[test]
vhost_user_params()94     fn vhost_user_params() {
95         let device = from_arg_value("vhost=vhost_sock,path=/path/to/dummy,boom-range=42").unwrap();
96         assert_eq!(device.vhost.as_str(), "vhost_sock");
97         assert_eq!(
98             device.device,
99             DummyDevice {
100                 path: "/path/to/dummy".into(),
101                 boom_range: 42,
102             }
103         );
104 
105         // Default parameter of device not specified.
106         let device = from_arg_value("vhost=vhost_sock,path=/path/to/dummy").unwrap();
107         assert_eq!(device.vhost.as_str(), "vhost_sock");
108         assert_eq!(
109             device.device,
110             DummyDevice {
111                 path: "/path/to/dummy".into(),
112                 boom_range: Default::default(),
113             }
114         );
115 
116         // Invalid parameter is rejected.
117         assert_eq!(
118             from_arg_value("vhost=vhost_sock,path=/path/to/dummy,boom-range=42,invalid-param=10")
119                 .unwrap_err(),
120             "unknown field `invalid-param`, expected `path` or `boom-range`".to_string(),
121         );
122 
123         // Device path can be parsed even if specified as a number.
124         // This ensures that we don't flatten the `device` member, which would result in
125         // `deserialize_any` being called and the type of `path` to be mistaken for an integer.
126         let device = from_arg_value("vhost=vhost_sock,path=10").unwrap();
127         assert_eq!(device.vhost.as_str(), "vhost_sock");
128         assert_eq!(
129             device.device,
130             DummyDevice {
131                 path: "10".into(),
132                 boom_range: Default::default(),
133             }
134         );
135 
136         // Misplaced `vhost` parameter is rejected
137         let _ = from_arg_value("path=/path/to/dummy,vhost=vhost_sock,boom-range=42").unwrap_err();
138     }
139 }
140