devices/virtio/vsock/sys/
linux.rs

1// Copyright 2023 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
5use std::path::Path;
6use std::path::PathBuf;
7
8use serde::Deserialize;
9use serde::Serialize;
10use serde_keyvalue::FromKeyValues;
11
12static VHOST_VSOCK_DEFAULT_PATH: &str = "/dev/vhost-vsock";
13
14fn default_vsock_path() -> PathBuf {
15    PathBuf::from(VHOST_VSOCK_DEFAULT_PATH)
16}
17
18#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, FromKeyValues)]
19#[serde(deny_unknown_fields, rename_all = "kebab-case")]
20/// Configuration for a Vsock device.
21pub struct VsockConfig {
22    /// CID to be used for this vsock device.
23    pub cid: u64,
24    /// Path to the vhost-vsock device.
25    #[serde(default = "default_vsock_path", rename = "device")]
26    pub vhost_device: PathBuf,
27    #[serde(default)]
28    pub max_queue_sizes: Option<[u16; 3]>,
29}
30
31impl VsockConfig {
32    /// Create a new vsock configuration. If `vhost_device` is `None`, the default vhost-vsock
33    /// device path will be used.
34    pub fn new<P: AsRef<Path>>(cid: u64, vhost_device: Option<P>) -> Self {
35        Self {
36            cid,
37            #[cfg(any(target_os = "android", target_os = "linux"))]
38            vhost_device: vhost_device
39                .map(|p| PathBuf::from(p.as_ref()))
40                .unwrap_or_else(|| PathBuf::from(VHOST_VSOCK_DEFAULT_PATH)),
41            max_queue_sizes: None,
42        }
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use serde_keyvalue::from_key_values;
49    use serde_keyvalue::ErrorKind;
50    use serde_keyvalue::ParseError;
51
52    use super::*;
53
54    fn from_vsock_arg(options: &str) -> Result<VsockConfig, ParseError> {
55        from_key_values(options)
56    }
57
58    #[test]
59    fn params_from_key_values() {
60        // Default device
61        assert_eq!(
62            from_vsock_arg("cid=56").unwrap(),
63            VsockConfig {
64                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
65                cid: 56,
66                max_queue_sizes: None,
67            }
68        );
69
70        // No argument
71        assert_eq!(
72            from_vsock_arg("").unwrap_err(),
73            ParseError {
74                kind: ErrorKind::SerdeError("missing field `cid`".into()),
75                pos: 0
76            }
77        );
78
79        // CID passed without key
80        assert_eq!(
81            from_vsock_arg("78").unwrap(),
82            VsockConfig {
83                #[cfg(any(target_os = "android", target_os = "linux"))]
84                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
85                cid: 78,
86                max_queue_sizes: None,
87            }
88        );
89
90        // CID passed twice
91        assert_eq!(
92            from_vsock_arg("cid=42,cid=56").unwrap_err(),
93            ParseError {
94                kind: ErrorKind::SerdeError("duplicate field `cid`".into()),
95                pos: 0,
96            }
97        );
98
99        // Invalid argument
100        assert_eq!(
101            from_vsock_arg("invalid=foo").unwrap_err(),
102            ParseError {
103                kind: ErrorKind::SerdeError(
104                    "unknown field `invalid`, expected one of `cid`, `device`, `max-queue-sizes`"
105                        .into()
106                ),
107                pos: 0,
108            }
109        );
110
111        // Path device
112        assert_eq!(
113            from_vsock_arg("device=/some/path,cid=56").unwrap(),
114            VsockConfig {
115                vhost_device: "/some/path".into(),
116                cid: 56,
117                max_queue_sizes: None,
118            }
119        );
120
121        // CID passed without key
122        assert_eq!(
123            from_vsock_arg("56,device=/some/path").unwrap(),
124            VsockConfig {
125                vhost_device: "/some/path".into(),
126                cid: 56,
127                max_queue_sizes: None,
128            }
129        );
130
131        // Missing cid
132        assert_eq!(
133            from_vsock_arg("device=42").unwrap_err(),
134            ParseError {
135                kind: ErrorKind::SerdeError("missing field `cid`".into()),
136                pos: 0,
137            }
138        );
139
140        // Device passed twice
141        assert_eq!(
142            from_vsock_arg("cid=56,device=42,device=/some/path").unwrap_err(),
143            ParseError {
144                kind: ErrorKind::SerdeError("duplicate field `device`".into()),
145                pos: 0,
146            }
147        );
148
149        // Queue sizes
150        assert_eq!(
151            from_vsock_arg("cid=56,max-queue-sizes=[1,2,4]").unwrap(),
152            VsockConfig {
153                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
154                cid: 56,
155                max_queue_sizes: Some([1, 2, 4]),
156            }
157        );
158    }
159}