mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: generic vhost-user: add support
Add VMM support for generic vhost-user devices. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
This commit is contained in:
committed by
Rob Bradford
parent
8c618ff5e0
commit
085a7a49fa
@@ -998,6 +998,7 @@ mod unit_tests {
|
|||||||
},
|
},
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
|
generic_vhost_user: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Display, Write};
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -240,6 +241,21 @@ impl FromStr for ByteSized {
|
|||||||
|
|
||||||
pub struct IntegerList(pub Vec<u64>);
|
pub struct IntegerList(pub Vec<u64>);
|
||||||
|
|
||||||
|
impl Display for IntegerList {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_char('[')?;
|
||||||
|
let mut iter = self.0.iter();
|
||||||
|
if let Some(first) = iter.next() {
|
||||||
|
first.fmt(f)?;
|
||||||
|
for i in iter {
|
||||||
|
f.write_char(',')?;
|
||||||
|
i.fmt(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char(']')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum IntegerListParseError {
|
pub enum IntegerListParseError {
|
||||||
#[error("invalid value: {0}")]
|
#[error("invalid value: {0}")]
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ use crate::config::RestoreConfig;
|
|||||||
use crate::device_tree::DeviceTree;
|
use crate::device_tree::DeviceTree;
|
||||||
use crate::vm::{Error as VmError, VmState};
|
use crate::vm::{Error as VmError, VmState};
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
|
DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig,
|
||||||
VmConfig, VsockConfig,
|
UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// API errors are sent back from the VMM API server through the ApiResponse.
|
/// API errors are sent back from the VMM API server through the ApiResponse.
|
||||||
@@ -170,6 +170,10 @@ pub enum ApiError {
|
|||||||
#[error("The fs could not be added to the VM")]
|
#[error("The fs could not be added to the VM")]
|
||||||
VmAddFs(#[source] VmError),
|
VmAddFs(#[source] VmError),
|
||||||
|
|
||||||
|
/// The generic vhost-user device could not be added to the VM.
|
||||||
|
#[error("The generic vhost-user device could not be added to the VM")]
|
||||||
|
VmAddGenericVhostUser(#[source] VmError),
|
||||||
|
|
||||||
/// The pmem device could not be added to the VM.
|
/// The pmem device could not be added to the VM.
|
||||||
#[error("The pmem device could not be added to the VM")]
|
#[error("The pmem device could not be added to the VM")]
|
||||||
VmAddPmem(#[source] VmError),
|
VmAddPmem(#[source] VmError),
|
||||||
@@ -340,6 +344,11 @@ pub trait RequestHandler {
|
|||||||
|
|
||||||
fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> Result<Option<Vec<u8>>, VmError>;
|
fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> Result<Option<Vec<u8>>, VmError>;
|
||||||
|
|
||||||
|
fn vm_add_generic_vhost_user(
|
||||||
|
&mut self,
|
||||||
|
fs_cfg: GenericVhostUserConfig,
|
||||||
|
) -> Result<Option<Vec<u8>>, VmError>;
|
||||||
|
|
||||||
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result<Option<Vec<u8>>, VmError>;
|
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result<Option<Vec<u8>>, VmError>;
|
||||||
|
|
||||||
fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result<Option<Vec<u8>>, VmError>;
|
fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result<Option<Vec<u8>>, VmError>;
|
||||||
@@ -539,6 +548,43 @@ impl ApiAction for VmAddFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct VmAddGenericVhostUser;
|
||||||
|
|
||||||
|
impl ApiAction for VmAddGenericVhostUser {
|
||||||
|
type RequestBody = GenericVhostUserConfig;
|
||||||
|
type ResponseBody = Option<Body>;
|
||||||
|
|
||||||
|
fn request(
|
||||||
|
&self,
|
||||||
|
config: Self::RequestBody,
|
||||||
|
response_sender: Sender<ApiResponse>,
|
||||||
|
) -> ApiRequest {
|
||||||
|
Box::new(move |vmm| {
|
||||||
|
info!("API request event: VmAddGenericVhostUser {config:?}");
|
||||||
|
|
||||||
|
let response = vmm
|
||||||
|
.vm_add_generic_vhost_user(config)
|
||||||
|
.map_err(ApiError::VmAddGenericVhostUser)
|
||||||
|
.map(ApiResponsePayload::VmAction);
|
||||||
|
|
||||||
|
response_sender
|
||||||
|
.send(response)
|
||||||
|
.map_err(VmmError::ApiResponseSend)?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(
|
||||||
|
&self,
|
||||||
|
api_evt: EventFd,
|
||||||
|
api_sender: Sender<ApiRequest>,
|
||||||
|
data: Self::RequestBody,
|
||||||
|
) -> ApiResult<Self::ResponseBody> {
|
||||||
|
get_response_body(self, api_evt, api_sender, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct VmAddPmem;
|
pub struct VmAddPmem;
|
||||||
|
|
||||||
impl ApiAction for VmAddPmem {
|
impl ApiAction for VmAddPmem {
|
||||||
|
|||||||
@@ -46,6 +46,24 @@ pub enum Error {
|
|||||||
/// Filesystem socket is missing
|
/// Filesystem socket is missing
|
||||||
#[error("Error parsing --fs: socket missing")]
|
#[error("Error parsing --fs: socket missing")]
|
||||||
ParseFsSockMissing,
|
ParseFsSockMissing,
|
||||||
|
/// Generic vhost-user socket is missing
|
||||||
|
#[error("Error parsing --generic-vhost-user: socket missing")]
|
||||||
|
ParseGenericVhostUserSockMissing,
|
||||||
|
/// Generic vhost-user number of queues is missing
|
||||||
|
#[error("Error parsing --generic-vhost-user: number of queues missing")]
|
||||||
|
ParseGenericVhostUserNumResponseQueuesMissing,
|
||||||
|
/// Generic vhost-user virtio ID is missing
|
||||||
|
#[error("Error parsing --generic-vhost-user: virtio ID missing")]
|
||||||
|
ParseGenericVhostUserVirtioIdMissing,
|
||||||
|
/// Generic vhost-user available features is missing
|
||||||
|
#[error("Error parsing --generic-vhost-user: available features missing")]
|
||||||
|
ParseGenericVhostUserAvailFeaturesMissing,
|
||||||
|
/// Generic vhost-user queue size is too large
|
||||||
|
#[error("Error parsing --generic-vhost-user: queue size {0} is {1}, but limit is 65535")]
|
||||||
|
ParseGenericVhostUserQueueSizeTooLarge(usize, u64),
|
||||||
|
/// Generic vhost-user queue size missing
|
||||||
|
#[error("Error parsing --generic-vhost-user: queue size missing")]
|
||||||
|
ParseGenericVhostUserQueueSizeMissing,
|
||||||
/// Missing persistent memory file parameter.
|
/// Missing persistent memory file parameter.
|
||||||
#[error("Error parsing --pmem: file missing")]
|
#[error("Error parsing --pmem: file missing")]
|
||||||
ParsePmemFileMissing,
|
ParsePmemFileMissing,
|
||||||
@@ -94,6 +112,9 @@ pub enum Error {
|
|||||||
/// Error parsing persistent memory parameters
|
/// Error parsing persistent memory parameters
|
||||||
#[error("Error parsing --pmem")]
|
#[error("Error parsing --pmem")]
|
||||||
ParsePersistentMemory(#[source] OptionParserError),
|
ParsePersistentMemory(#[source] OptionParserError),
|
||||||
|
/// Error parsing generic vhost-user parameters
|
||||||
|
#[error("Error parsing --generic-vhost-user")]
|
||||||
|
ParseGenericVhostUser(#[source] OptionParserError),
|
||||||
/// Failed parsing console
|
/// Failed parsing console
|
||||||
#[error("Error parsing --console")]
|
#[error("Error parsing --console")]
|
||||||
ParseConsole(#[source] OptionParserError),
|
ParseConsole(#[source] OptionParserError),
|
||||||
@@ -394,6 +415,7 @@ pub struct VmParams<'a> {
|
|||||||
pub rng: &'a str,
|
pub rng: &'a str,
|
||||||
pub balloon: Option<&'a str>,
|
pub balloon: Option<&'a str>,
|
||||||
pub fs: Option<Vec<&'a str>>,
|
pub fs: Option<Vec<&'a str>>,
|
||||||
|
pub generic_vhost_user: Option<Vec<&'a str>>,
|
||||||
pub pmem: Option<Vec<&'a str>>,
|
pub pmem: Option<Vec<&'a str>>,
|
||||||
pub serial: &'a str,
|
pub serial: &'a str,
|
||||||
pub console: &'a str,
|
pub console: &'a str,
|
||||||
@@ -455,6 +477,9 @@ impl<'a> VmParams<'a> {
|
|||||||
let fs: Option<Vec<&str>> = args
|
let fs: Option<Vec<&str>> = args
|
||||||
.get_many::<String>("fs")
|
.get_many::<String>("fs")
|
||||||
.map(|x| x.map(|y| y as &str).collect());
|
.map(|x| x.map(|y| y as &str).collect());
|
||||||
|
let generic_vhost_user: Option<Vec<&str>> = args
|
||||||
|
.get_many::<String>("generic-vhost-user")
|
||||||
|
.map(|x| x.map(|y| y as &str).collect());
|
||||||
let pmem: Option<Vec<&str>> = args
|
let pmem: Option<Vec<&str>> = args
|
||||||
.get_many::<String>("pmem")
|
.get_many::<String>("pmem")
|
||||||
.map(|x| x.map(|y| y as &str).collect());
|
.map(|x| x.map(|y| y as &str).collect());
|
||||||
@@ -509,6 +534,7 @@ impl<'a> VmParams<'a> {
|
|||||||
rng,
|
rng,
|
||||||
balloon,
|
balloon,
|
||||||
fs,
|
fs,
|
||||||
|
generic_vhost_user,
|
||||||
pmem,
|
pmem,
|
||||||
serial,
|
serial,
|
||||||
console,
|
console,
|
||||||
@@ -1642,6 +1668,82 @@ impl BalloonConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GenericVhostUserConfig {
|
||||||
|
pub const SYNTAX: &'static str = "generic vhost-user parameters \
|
||||||
|
\"virtio_id=<ID number for virtio device type (FS, block, net, etc)>,\
|
||||||
|
socket=<socket_path>,\
|
||||||
|
queue_sizes=<list of queue sizes>,\
|
||||||
|
id=<device_id>,pci_segment=<segment_id>\"";
|
||||||
|
|
||||||
|
pub fn parse(vhost_user: &str) -> Result<Self> {
|
||||||
|
let mut parser = OptionParser::new();
|
||||||
|
parser
|
||||||
|
.add("virtio_id")
|
||||||
|
.add("queue_sizes")
|
||||||
|
.add("socket")
|
||||||
|
.add("id")
|
||||||
|
.add("pci_segment");
|
||||||
|
parser
|
||||||
|
.parse(vhost_user)
|
||||||
|
.map_err(Error::ParseGenericVhostUser)?;
|
||||||
|
|
||||||
|
let socket = parser
|
||||||
|
.get("socket")
|
||||||
|
.ok_or(Error::ParseGenericVhostUserSockMissing)?;
|
||||||
|
|
||||||
|
let IntegerList(queue_sizes) = parser
|
||||||
|
.convert("queue_sizes")
|
||||||
|
.map_err(Error::ParseGenericVhostUser)?
|
||||||
|
.ok_or(Error::ParseGenericVhostUserQueueSizeMissing)?;
|
||||||
|
let device_type = parser
|
||||||
|
.convert("virtio_id")
|
||||||
|
.map_err(Error::ParseGenericVhostUser)?
|
||||||
|
.ok_or(Error::ParseGenericVhostUserVirtioIdMissing)?;
|
||||||
|
let id = parser.get("id");
|
||||||
|
let pci_segment = parser
|
||||||
|
.convert("pci_segment")
|
||||||
|
.map_err(Error::ParseGenericVhostUser)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut converted_queue_sizes: Vec<u16> = Vec::new();
|
||||||
|
for (offset, &queue_size) in queue_sizes.iter().enumerate() {
|
||||||
|
match queue_size.try_into() {
|
||||||
|
Err(_) => {
|
||||||
|
return Err(Error::ParseGenericVhostUserQueueSizeTooLarge(
|
||||||
|
offset, queue_size,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(queue_size) => converted_queue_sizes.push(queue_size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GenericVhostUserConfig {
|
||||||
|
socket: socket.into(),
|
||||||
|
device_type,
|
||||||
|
id,
|
||||||
|
pci_segment,
|
||||||
|
queue_sizes: converted_queue_sizes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
|
||||||
|
if let Some(platform_config) = vm_config.platform.as_ref() {
|
||||||
|
if self.pci_segment >= platform_config.num_pci_segments {
|
||||||
|
return Err(ValidationError::InvalidPciSegment(self.pci_segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(iommu_segments) = platform_config.iommu_segments.as_ref()
|
||||||
|
&& iommu_segments.contains(&self.pci_segment)
|
||||||
|
{
|
||||||
|
return Err(ValidationError::IommuNotSupportedOnSegment(
|
||||||
|
self.pci_segment,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FsConfig {
|
impl FsConfig {
|
||||||
pub const SYNTAX: &'static str = "virtio-fs parameters \
|
pub const SYNTAX: &'static str = "virtio-fs parameters \
|
||||||
\"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\
|
\"tag=<tag_name>,socket=<socket_path>,num_queues=<number_of_queues>,\
|
||||||
@@ -2738,6 +2840,17 @@ impl VmConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(generic_vhost_user_devices) = &self.generic_vhost_user {
|
||||||
|
if !generic_vhost_user_devices.is_empty() && !self.backed_by_shared_memory() {
|
||||||
|
return Err(ValidationError::VhostUserRequiresSharedMemory);
|
||||||
|
}
|
||||||
|
for generic_vhost_user_device in generic_vhost_user_devices {
|
||||||
|
generic_vhost_user_device.validate(self)?;
|
||||||
|
|
||||||
|
Self::validate_identifier(&mut id_list, &generic_vhost_user_device.id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(pmems) = &self.pmem {
|
if let Some(pmems) = &self.pmem {
|
||||||
for pmem in pmems {
|
for pmem in pmems {
|
||||||
pmem.validate(self)?;
|
pmem.validate(self)?;
|
||||||
@@ -2991,6 +3104,15 @@ impl VmConfig {
|
|||||||
fs = Some(fs_config_list);
|
fs = Some(fs_config_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut generic_vhost_user: Option<Vec<GenericVhostUserConfig>> = None;
|
||||||
|
if let Some(generic_vhost_user_list) = &vm_params.generic_vhost_user {
|
||||||
|
let mut generic_vhost_user_config_list = Vec::new();
|
||||||
|
for item in generic_vhost_user_list.iter() {
|
||||||
|
generic_vhost_user_config_list.push(GenericVhostUserConfig::parse(item)?);
|
||||||
|
}
|
||||||
|
generic_vhost_user = Some(generic_vhost_user_config_list);
|
||||||
|
}
|
||||||
|
|
||||||
let mut pmem: Option<Vec<PmemConfig>> = None;
|
let mut pmem: Option<Vec<PmemConfig>> = None;
|
||||||
if let Some(pmem_list) = &vm_params.pmem {
|
if let Some(pmem_list) = &vm_params.pmem {
|
||||||
let mut pmem_config_list = Vec::new();
|
let mut pmem_config_list = Vec::new();
|
||||||
@@ -3126,6 +3248,7 @@ impl VmConfig {
|
|||||||
net,
|
net,
|
||||||
rng,
|
rng,
|
||||||
balloon,
|
balloon,
|
||||||
|
generic_vhost_user,
|
||||||
fs,
|
fs,
|
||||||
pmem,
|
pmem,
|
||||||
serial,
|
serial,
|
||||||
@@ -3188,6 +3311,13 @@ impl VmConfig {
|
|||||||
removed |= fs.len() != len;
|
removed |= fs.len() != len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove if generic vhost-user device
|
||||||
|
if let Some(generic_vhost_user) = self.generic_vhost_user.as_mut() {
|
||||||
|
let len = generic_vhost_user.len();
|
||||||
|
generic_vhost_user.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
|
||||||
|
removed |= generic_vhost_user.len() != len;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove if net device
|
// Remove if net device
|
||||||
if let Some(net) = self.net.as_mut() {
|
if let Some(net) = self.net.as_mut() {
|
||||||
let len = net.len();
|
let len = net.len();
|
||||||
@@ -3260,6 +3390,7 @@ impl Clone for VmConfig {
|
|||||||
#[cfg(feature = "pvmemcontrol")]
|
#[cfg(feature = "pvmemcontrol")]
|
||||||
pvmemcontrol: self.pvmemcontrol.clone(),
|
pvmemcontrol: self.pvmemcontrol.clone(),
|
||||||
fs: self.fs.clone(),
|
fs: self.fs.clone(),
|
||||||
|
generic_vhost_user: self.generic_vhost_user.clone(),
|
||||||
pmem: self.pmem.clone(),
|
pmem: self.pmem.clone(),
|
||||||
serial: self.serial.clone(),
|
serial: self.serial.clone(),
|
||||||
console: self.console.clone(),
|
console: self.console.clone(),
|
||||||
@@ -3784,6 +3915,90 @@ mod unit_tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn make_vhost_user_config(
|
||||||
|
socket: &str,
|
||||||
|
virtio_id: u64,
|
||||||
|
id: &str,
|
||||||
|
pci_segment: u64,
|
||||||
|
queue_sizes: &IntegerList,
|
||||||
|
) {
|
||||||
|
assert!(!socket.contains(",[]\n\r\0\""));
|
||||||
|
assert!(!id.contains(",[]\n\r\0\""));
|
||||||
|
let config = GenericVhostUserConfig::parse(&format!(
|
||||||
|
"virtio_id={virtio_id},socket=\"{socket}\",\
|
||||||
|
id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
|
||||||
|
));
|
||||||
|
if pci_segment <= u16::MAX.into()
|
||||||
|
&& virtio_id <= u32::MAX.into()
|
||||||
|
&& queue_sizes.0.iter().all(|&f| f <= u16::MAX.into())
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
config.unwrap(),
|
||||||
|
GenericVhostUserConfig {
|
||||||
|
socket: socket.into(),
|
||||||
|
id: Some(id.to_owned()),
|
||||||
|
device_type: u32::try_from(virtio_id).unwrap(),
|
||||||
|
pci_segment: u16::try_from(pci_segment).unwrap(),
|
||||||
|
queue_sizes: queue_sizes
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&f| u16::try_from(f).unwrap())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
config.unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_vhost_user() -> Result<()> {
|
||||||
|
// all parameters must be supplied, except pci_segment
|
||||||
|
GenericVhostUserConfig::parse("").unwrap_err();
|
||||||
|
GenericVhostUserConfig::parse("virtio_id=1").unwrap_err();
|
||||||
|
GenericVhostUserConfig::parse("queue_size=1").unwrap_err();
|
||||||
|
GenericVhostUserConfig::parse("socket=/tmp/sock").unwrap_err();
|
||||||
|
GenericVhostUserConfig::parse("id=1").unwrap_err();
|
||||||
|
make_vhost_user_config(
|
||||||
|
"/dev/null/doesnotexist",
|
||||||
|
100,
|
||||||
|
"Something",
|
||||||
|
10,
|
||||||
|
&IntegerList(vec![u16::MAX.into(), 20u16.into()]),
|
||||||
|
);
|
||||||
|
make_vhost_user_config(
|
||||||
|
"/dev/null/doesnotexist",
|
||||||
|
100,
|
||||||
|
"Something",
|
||||||
|
10,
|
||||||
|
&IntegerList(vec![u16::MAX.into()]),
|
||||||
|
);
|
||||||
|
make_vhost_user_config(
|
||||||
|
"/dev/null/doesnotexist",
|
||||||
|
u64::from(u32::MAX) + 1,
|
||||||
|
"Something",
|
||||||
|
10,
|
||||||
|
&IntegerList(vec![20u64]),
|
||||||
|
);
|
||||||
|
make_vhost_user_config(
|
||||||
|
"/dev/null/doesnotexist",
|
||||||
|
u64::from(u32::MAX) + 1,
|
||||||
|
"Something",
|
||||||
|
10,
|
||||||
|
&IntegerList(vec![20u64]),
|
||||||
|
);
|
||||||
|
make_vhost_user_config(
|
||||||
|
"/dev/null/doesnotexist",
|
||||||
|
u64::from(u32::MAX) + 1,
|
||||||
|
"Something",
|
||||||
|
10,
|
||||||
|
&IntegerList(vec![20u64]),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn pmem_fixture() -> PmemConfig {
|
fn pmem_fixture() -> PmemConfig {
|
||||||
PmemConfig {
|
PmemConfig {
|
||||||
file: PathBuf::from("/tmp/pmem"),
|
file: PathBuf::from("/tmp/pmem"),
|
||||||
@@ -4168,6 +4383,7 @@ mod unit_tests {
|
|||||||
rate_limit_groups: None,
|
rate_limit_groups: None,
|
||||||
disks: None,
|
disks: None,
|
||||||
rng: RngConfig::default(),
|
rng: RngConfig::default(),
|
||||||
|
generic_vhost_user: None,
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
@@ -4373,6 +4589,7 @@ mod unit_tests {
|
|||||||
},
|
},
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
|
generic_vhost_user: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager};
|
|||||||
use crate::vm_config::IvshmemConfig;
|
use crate::vm_config::IvshmemConfig;
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
|
ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
|
||||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
|
DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig,
|
||||||
VhostMode, VmConfig, VsockConfig,
|
UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node};
|
use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node};
|
||||||
|
|
||||||
@@ -158,6 +158,7 @@ const IVSHMEM_DEVICE_NAME: &str = "__ivshmem";
|
|||||||
const DISK_DEVICE_NAME_PREFIX: &str = "_disk";
|
const DISK_DEVICE_NAME_PREFIX: &str = "_disk";
|
||||||
const FS_DEVICE_NAME_PREFIX: &str = "_fs";
|
const FS_DEVICE_NAME_PREFIX: &str = "_fs";
|
||||||
const NET_DEVICE_NAME_PREFIX: &str = "_net";
|
const NET_DEVICE_NAME_PREFIX: &str = "_net";
|
||||||
|
const GENERIC_VHOST_USER_DEVICE_NAME_PREFIX: &str = "_generic_vhost_user";
|
||||||
const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
|
const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
|
||||||
const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
|
const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
|
||||||
const VSOCK_DEVICE_NAME_PREFIX: &str = "_vsock";
|
const VSOCK_DEVICE_NAME_PREFIX: &str = "_vsock";
|
||||||
@@ -197,6 +198,10 @@ pub enum DeviceManagerError {
|
|||||||
#[error("Cannot create virtio-rng device")]
|
#[error("Cannot create virtio-rng device")]
|
||||||
CreateVirtioRng(#[source] io::Error),
|
CreateVirtioRng(#[source] io::Error),
|
||||||
|
|
||||||
|
/// Cannot create generic vhost-user device
|
||||||
|
#[error("Cannot create generic vhost-user device")]
|
||||||
|
CreateGenericVhostUser(#[source] virtio_devices::vhost_user::Error),
|
||||||
|
|
||||||
/// Cannot create virtio-fs device
|
/// Cannot create virtio-fs device
|
||||||
#[error("Cannot create virtio-fs device")]
|
#[error("Cannot create virtio-fs device")]
|
||||||
CreateVirtioFs(#[source] virtio_devices::vhost_user::Error),
|
CreateVirtioFs(#[source] virtio_devices::vhost_user::Error),
|
||||||
@@ -205,6 +210,10 @@ pub enum DeviceManagerError {
|
|||||||
#[error("Virtio-fs device was created without a socket")]
|
#[error("Virtio-fs device was created without a socket")]
|
||||||
NoVirtioFsSock,
|
NoVirtioFsSock,
|
||||||
|
|
||||||
|
/// Generic vhost-user device was created without a socket.
|
||||||
|
#[error("Generic vhost-user device was created without a socket")]
|
||||||
|
NoGenericVhostUserSock,
|
||||||
|
|
||||||
/// Cannot create vhost-user-blk device
|
/// Cannot create vhost-user-blk device
|
||||||
#[error("Cannot create vhost-user-blk device")]
|
#[error("Cannot create vhost-user-blk device")]
|
||||||
CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
|
CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
|
||||||
@@ -2554,6 +2563,9 @@ impl DeviceManager {
|
|||||||
self.make_virtio_net_devices()?;
|
self.make_virtio_net_devices()?;
|
||||||
self.make_virtio_rng_devices()?;
|
self.make_virtio_rng_devices()?;
|
||||||
|
|
||||||
|
// Add generic vhost-user if required
|
||||||
|
self.make_generic_vhost_user_devices()?;
|
||||||
|
|
||||||
// Add virtio-fs if required
|
// Add virtio-fs if required
|
||||||
self.make_virtio_fs_devices()?;
|
self.make_virtio_fs_devices()?;
|
||||||
|
|
||||||
@@ -3122,6 +3134,72 @@ impl DeviceManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_generic_vhost_user_device(
|
||||||
|
&mut self,
|
||||||
|
generic_vhost_user_cfg: &mut GenericVhostUserConfig,
|
||||||
|
) -> DeviceManagerResult<MetaVirtioDevice> {
|
||||||
|
let id = if let Some(id) = &generic_vhost_user_cfg.id {
|
||||||
|
id.clone()
|
||||||
|
} else {
|
||||||
|
let id = self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?;
|
||||||
|
generic_vhost_user_cfg.id = Some(id.clone());
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Creating generic vhost-user device: {generic_vhost_user_cfg:?}");
|
||||||
|
|
||||||
|
let mut node = device_node!(id);
|
||||||
|
|
||||||
|
if let Some(generic_vhost_user_socket) = generic_vhost_user_cfg.socket.to_str() {
|
||||||
|
let generic_vhost_user_device = Arc::new(Mutex::new(
|
||||||
|
virtio_devices::vhost_user::GenericVhostUser::new(
|
||||||
|
id.clone(),
|
||||||
|
generic_vhost_user_socket,
|
||||||
|
generic_vhost_user_cfg.queue_sizes.clone(),
|
||||||
|
generic_vhost_user_cfg.device_type,
|
||||||
|
None,
|
||||||
|
self.seccomp_action.clone(),
|
||||||
|
self.exit_evt
|
||||||
|
.try_clone()
|
||||||
|
.map_err(DeviceManagerError::EventFd)?,
|
||||||
|
self.force_iommu,
|
||||||
|
state_from_id(self.snapshot.as_ref(), id.as_str())
|
||||||
|
.map_err(DeviceManagerError::RestoreGetState)?,
|
||||||
|
)
|
||||||
|
.map_err(DeviceManagerError::CreateGenericVhostUser)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Update the device tree with the migratable device.
|
||||||
|
node.migratable =
|
||||||
|
Some(Arc::clone(&generic_vhost_user_device) as Arc<Mutex<dyn Migratable>>);
|
||||||
|
self.device_tree.lock().unwrap().insert(id.clone(), node);
|
||||||
|
|
||||||
|
Ok(MetaVirtioDevice {
|
||||||
|
virtio_device: Arc::clone(&generic_vhost_user_device)
|
||||||
|
as Arc<Mutex<dyn virtio_devices::VirtioDevice>>,
|
||||||
|
iommu: false,
|
||||||
|
id,
|
||||||
|
pci_segment: generic_vhost_user_cfg.pci_segment,
|
||||||
|
dma_handler: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(DeviceManagerError::NoGenericVhostUserSock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_generic_vhost_user_devices(&mut self) -> DeviceManagerResult<()> {
|
||||||
|
let mut generic_vhost_user_devices = self.config.lock().unwrap().generic_vhost_user.clone();
|
||||||
|
if let Some(generic_vhost_user_list_cfg) = &mut generic_vhost_user_devices {
|
||||||
|
for generic_vhost_user_cfg in generic_vhost_user_list_cfg.iter_mut() {
|
||||||
|
let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?;
|
||||||
|
self.virtio_devices.push(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.config.lock().unwrap().generic_vhost_user = generic_vhost_user_devices;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn make_virtio_fs_device(
|
fn make_virtio_fs_device(
|
||||||
&mut self,
|
&mut self,
|
||||||
fs_cfg: &mut FsConfig,
|
fs_cfg: &mut FsConfig,
|
||||||
@@ -4918,6 +4996,16 @@ impl DeviceManager {
|
|||||||
self.hotplug_virtio_pci_device(device)
|
self.hotplug_virtio_pci_device(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_generic_vhost_user(
|
||||||
|
&mut self,
|
||||||
|
generic_vhost_user_cfg: &mut GenericVhostUserConfig,
|
||||||
|
) -> DeviceManagerResult<PciDeviceInfo> {
|
||||||
|
self.validate_identifier(&generic_vhost_user_cfg.id)?;
|
||||||
|
|
||||||
|
let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?;
|
||||||
|
self.hotplug_virtio_pci_device(device)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult<PciDeviceInfo> {
|
pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult<PciDeviceInfo> {
|
||||||
self.validate_identifier(&pmem_cfg.id)?;
|
self.validate_identifier(&pmem_cfg.id)?;
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ use crate::migration::{recv_vm_config, recv_vm_state};
|
|||||||
use crate::seccomp_filters::{Thread, get_seccomp_filter};
|
use crate::seccomp_filters::{Thread, get_seccomp_filter};
|
||||||
use crate::vm::{Error as VmError, Vm, VmState};
|
use crate::vm::{Error as VmError, Vm, VmState};
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
|
DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig,
|
||||||
VmConfig, VsockConfig,
|
UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod acpi;
|
mod acpi;
|
||||||
@@ -2125,6 +2125,39 @@ impl RequestHandler for Vmm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vm_add_generic_vhost_user(
|
||||||
|
&mut self,
|
||||||
|
generic_vhost_user_cfg: GenericVhostUserConfig,
|
||||||
|
) -> result::Result<Option<Vec<u8>>, VmError> {
|
||||||
|
self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Validate the configuration change in a cloned configuration
|
||||||
|
let mut config = self.vm_config.as_ref().unwrap().lock().unwrap().clone();
|
||||||
|
add_to_config(
|
||||||
|
&mut config.generic_vhost_user,
|
||||||
|
generic_vhost_user_cfg.clone(),
|
||||||
|
);
|
||||||
|
config.validate().map_err(VmError::ConfigValidation)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut vm) = self.vm {
|
||||||
|
let info = vm
|
||||||
|
.add_generic_vhost_user(generic_vhost_user_cfg)
|
||||||
|
.inspect_err(|e| {
|
||||||
|
error!("Error when adding new generic vhost-user device to the VM: {e:?}");
|
||||||
|
})?;
|
||||||
|
serde_json::to_vec(&info)
|
||||||
|
.map(Some)
|
||||||
|
.map_err(VmError::SerializeJson)
|
||||||
|
} else {
|
||||||
|
// Update VmConfig by adding the new device.
|
||||||
|
let mut config = self.vm_config.as_ref().unwrap().lock().unwrap();
|
||||||
|
add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result<Option<Vec<u8>>, VmError> {
|
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result<Option<Vec<u8>>, VmError> {
|
||||||
self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?;
|
self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?;
|
||||||
|
|
||||||
@@ -2443,6 +2476,7 @@ mod unit_tests {
|
|||||||
},
|
},
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
|
generic_vhost_user: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
@@ -2679,6 +2713,59 @@ mod unit_tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vmm_vm_cold_add_generic_vhost_user() {
|
||||||
|
let mut vmm = create_dummy_vmm();
|
||||||
|
let generic_vhost_user_config =
|
||||||
|
GenericVhostUserConfig::parse("virtio_id=26,socket=/tmp/sock,queue_sizes=[1024]")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone()),
|
||||||
|
Err(VmError::VmNotCreated)
|
||||||
|
));
|
||||||
|
|
||||||
|
let _ = vmm.vm_create(create_dummy_vm_config());
|
||||||
|
assert!(
|
||||||
|
vmm.vm_config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.generic_vhost_user
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone())
|
||||||
|
.unwrap()
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
vmm.vm_config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.generic_vhost_user
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
vmm.vm_config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.generic_vhost_user
|
||||||
|
.clone()
|
||||||
|
.unwrap()[0],
|
||||||
|
generic_vhost_user_config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vmm_vm_cold_add_pmem() {
|
fn test_vmm_vm_cold_add_pmem() {
|
||||||
let mut vmm = create_dummy_vmm();
|
let mut vmm = create_dummy_vmm();
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path};
|
|||||||
#[cfg(feature = "fw_cfg")]
|
#[cfg(feature = "fw_cfg")]
|
||||||
use crate::vm_config::FwCfgConfig;
|
use crate::vm_config::FwCfgConfig;
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, NumaConfig, PayloadConfig,
|
DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, HotplugMethod, NetConfig,
|
||||||
PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
|
NumaConfig, PayloadConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap,
|
CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap,
|
||||||
@@ -2136,6 +2136,33 @@ impl Vm {
|
|||||||
Ok(pci_device_info)
|
Ok(pci_device_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_generic_vhost_user(
|
||||||
|
&mut self,
|
||||||
|
mut generic_vhost_user_cfg: GenericVhostUserConfig,
|
||||||
|
) -> Result<PciDeviceInfo> {
|
||||||
|
let pci_device_info = self
|
||||||
|
.device_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.add_generic_vhost_user(&mut generic_vhost_user_cfg)
|
||||||
|
.map_err(Error::DeviceManager)?;
|
||||||
|
|
||||||
|
// Update VmConfig by adding the new device. This is important to
|
||||||
|
// ensure the device would be created in case of a reboot.
|
||||||
|
{
|
||||||
|
let mut config = self.config.lock().unwrap();
|
||||||
|
add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.device_manager
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.notify_hotplug(AcpiNotificationFlags::PCI_DEVICES_CHANGED)
|
||||||
|
.map_err(Error::DeviceManager)?;
|
||||||
|
|
||||||
|
Ok(pci_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result<PciDeviceInfo> {
|
pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result<PciDeviceInfo> {
|
||||||
let pci_device_info = self
|
let pci_device_info = self
|
||||||
.device_manager
|
.device_manager
|
||||||
|
|||||||
@@ -472,6 +472,24 @@ impl ApplyLandlock for FsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub struct GenericVhostUserConfig {
|
||||||
|
pub socket: PathBuf,
|
||||||
|
pub queue_sizes: Vec<u16>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pci_segment: u16,
|
||||||
|
pub device_type: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplyLandlock for GenericVhostUserConfig {
|
||||||
|
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||||
|
landlock.add_rule_with_access(&self.socket, "rw")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct PmemConfig {
|
pub struct PmemConfig {
|
||||||
pub file: PathBuf,
|
pub file: PathBuf,
|
||||||
@@ -924,6 +942,7 @@ pub struct VmConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub rng: RngConfig,
|
pub rng: RngConfig,
|
||||||
pub balloon: Option<BalloonConfig>,
|
pub balloon: Option<BalloonConfig>,
|
||||||
|
pub generic_vhost_user: Option<Vec<GenericVhostUserConfig>>,
|
||||||
pub fs: Option<Vec<FsConfig>>,
|
pub fs: Option<Vec<FsConfig>>,
|
||||||
pub pmem: Option<Vec<PmemConfig>>,
|
pub pmem: Option<Vec<PmemConfig>>,
|
||||||
#[serde(default = "default_serial")]
|
#[serde(default = "default_serial")]
|
||||||
@@ -1000,6 +1019,12 @@ impl VmConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(generic_vhost_user_configs) = &self.generic_vhost_user {
|
||||||
|
for generic_vhost_user_config in generic_vhost_user_configs.iter() {
|
||||||
|
generic_vhost_user_config.apply_landlock(&mut landlock)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(pmem_configs) = &self.pmem {
|
if let Some(pmem_configs) = &self.pmem {
|
||||||
for pmem_config in pmem_configs.iter() {
|
for pmem_config in pmem_configs.iter() {
|
||||||
pmem_config.apply_landlock(&mut landlock)?;
|
pmem_config.apply_landlock(&mut landlock)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user