vhost_user: add shared object msg

Add SHARED_OBJECT_* vhost-user request
IDs to align with standard. Implement
backend functions to send these
requests.

Add SHARED_OBJECT protocol feature.

Add GET_SHARED_OBJECT to the frontend
requests (not implemented, this is just to
have the placeholder and align with current
standard).

Signed-off-by: Albert Esteve <aesteve@redhat.com>
This commit is contained in:
Albert Esteve 2024-05-16 11:44:33 +02:00 committed by Sergio López
parent 9323902c59
commit 76c347c108
4 changed files with 215 additions and 5 deletions

View file

@ -29,6 +29,7 @@ postcopy = []
[dependencies]
bitflags = "2.4"
libc = "0.2.39"
uuid = { version = "1.8.0", features=["v4", "fast-rng", "macro-diagnostics"] }
vmm-sys-util = "0.12.1"
vm-memory = { version = "0.14.0", features=["backend-mmap"] }

View file

@ -129,6 +129,29 @@ impl Backend {
}
impl VhostUserFrontendReqHandler for Backend {
/// Forward vhost-user shared-object add request to the frontend.
fn shared_object_add(&self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
self.send_message(BackendReq::SHARED_OBJECT_ADD, uuid, None)
}
/// Forward vhost-user shared-object remove request to the frontend.
fn shared_object_remove(&self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
self.send_message(BackendReq::SHARED_OBJECT_REMOVE, uuid, None)
}
/// Forward vhost-user shared-object lookup request to the frontend.
fn shared_object_lookup(
&self,
uuid: &VhostUserSharedMsg,
fd: &dyn AsRawFd,
) -> HandlerResult<u64> {
self.send_message(
BackendReq::SHARED_OBJECT_LOOKUP,
uuid,
Some(&[fd.as_raw_fd()]),
)
}
/// Forward vhost-user-fs map file requests to the backend.
fn fs_backend_map(&self, fs: &VhostUserFSBackendMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
self.send_message(BackendReq::FS_MAP, fs, Some(&[fd.as_raw_fd()]))

View file

@ -33,6 +33,25 @@ pub trait VhostUserFrontendReqHandler {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object add operation
fn shared_object_add(&self, _uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object remove operation
fn shared_object_remove(&self, _uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object lookup operation
fn shared_object_lookup(
&self,
_uuid: &VhostUserSharedMsg,
_fd: &dyn AsRawFd,
) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle virtio-fs map file requests.
fn fs_backend_map(&self, _fs: &VhostUserFSBackendMsg, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
@ -66,6 +85,25 @@ pub trait VhostUserFrontendReqHandlerMut {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object add operation
fn shared_object_add(&mut self, _uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object remove operation
fn shared_object_remove(&mut self, _uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle shared object lookup operation
fn shared_object_lookup(
&mut self,
_uuid: &VhostUserSharedMsg,
_fd: &dyn AsRawFd,
) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
/// Handle virtio-fs map file requests.
fn fs_backend_map(
&mut self,
@ -103,6 +141,25 @@ impl<S: VhostUserFrontendReqHandlerMut> VhostUserFrontendReqHandler for Mutex<S>
self.lock().unwrap().handle_config_change()
}
/// Handle shared object add operation
fn shared_object_add(&self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
self.lock().unwrap().shared_object_add(uuid)
}
/// Handle shared object remove operation
fn shared_object_remove(&self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
self.lock().unwrap().shared_object_remove(uuid)
}
/// Handle shared object lookup operation
fn shared_object_lookup(
&self,
uuid: &VhostUserSharedMsg,
fd: &dyn AsRawFd,
) -> HandlerResult<u64> {
self.lock().unwrap().shared_object_lookup(uuid, fd)
}
fn fs_backend_map(&self, fs: &VhostUserFSBackendMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
self.lock().unwrap().fs_backend_map(fs, fd)
}
@ -230,6 +287,24 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
.handle_config_change()
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::SHARED_OBJECT_ADD) => {
let msg = self.extract_msg_body::<VhostUserSharedMsg>(&hdr, size, &buf)?;
self.backend
.shared_object_add(&msg)
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::SHARED_OBJECT_REMOVE) => {
let msg = self.extract_msg_body::<VhostUserSharedMsg>(&hdr, size, &buf)?;
self.backend
.shared_object_remove(&msg)
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::SHARED_OBJECT_LOOKUP) => {
let msg = self.extract_msg_body::<VhostUserSharedMsg>(&hdr, size, &buf)?;
self.backend
.shared_object_lookup(&msg, &files.unwrap()[0])
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::FS_MAP) => {
let msg = self.extract_msg_body::<VhostUserFSBackendMsg>(&hdr, size, &buf)?;
// check_attached_files() has validated files
@ -293,7 +368,7 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
files: &Option<Vec<File>>,
) -> Result<()> {
match hdr.get_code() {
Ok(BackendReq::FS_MAP | BackendReq::FS_IO) => {
Ok(BackendReq::SHARED_OBJECT_LOOKUP | BackendReq::FS_MAP | BackendReq::FS_IO) => {
// Expect a single file is passed.
match files {
Some(files) if files.len() == 1 => Ok(()),
@ -370,14 +445,46 @@ impl<S: VhostUserFrontendReqHandler> AsRawFd for FrontendReqHandler<S> {
mod tests {
use super::*;
use std::collections::HashSet;
use uuid::Uuid;
#[cfg(feature = "vhost-user-backend")]
use crate::vhost_user::Backend;
#[cfg(feature = "vhost-user-backend")]
use std::os::unix::io::FromRawFd;
struct MockFrontendReqHandler {}
struct MockFrontendReqHandler {
shared_objects: HashSet<Uuid>,
}
impl MockFrontendReqHandler {
fn new() -> Self {
Self {
shared_objects: HashSet::new(),
}
}
}
impl VhostUserFrontendReqHandlerMut for MockFrontendReqHandler {
fn shared_object_add(&mut self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Ok(!self.shared_objects.insert(uuid.uuid) as u64)
}
fn shared_object_remove(&mut self, uuid: &VhostUserSharedMsg) -> HandlerResult<u64> {
Ok(!self.shared_objects.remove(&uuid.uuid) as u64)
}
fn shared_object_lookup(
&mut self,
uuid: &VhostUserSharedMsg,
_fd: &dyn AsRawFd,
) -> HandlerResult<u64> {
if self.shared_objects.get(&uuid.uuid).is_some() {
return Ok(0);
}
Ok(1)
}
/// Handle virtio-fs map file requests from the backend.
fn fs_backend_map(
&mut self,
@ -395,7 +502,7 @@ mod tests {
#[test]
fn test_new_frontend_req_handler() {
let backend = Arc::new(Mutex::new(MockFrontendReqHandler {}));
let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new()));
let mut handler = FrontendReqHandler::new(backend).unwrap();
assert!(handler.get_tx_raw_fd() >= 0);
@ -411,7 +518,7 @@ mod tests {
#[cfg(feature = "vhost-user-backend")]
#[test]
fn test_frontend_backend_req_handler() {
let backend = Arc::new(Mutex::new(MockFrontendReqHandler {}));
let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new()));
let mut handler = FrontendReqHandler::new(backend).unwrap();
// SAFETY: Safe because `handler` contains valid fds, and we are
@ -428,6 +535,13 @@ mod tests {
let res = handler.handle_request().unwrap();
assert_eq!(res, 0);
handler.handle_request().unwrap_err();
// Testing shared object messages.
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
});
backend
@ -438,6 +552,23 @@ mod tests {
backend
.fs_backend_unmap(&VhostUserFSBackendMsg::default())
.unwrap();
let shobj_msg = VhostUserSharedMsg {
uuid: Uuid::new_v4(),
};
assert!(backend.shared_object_add(&shobj_msg).is_ok());
assert!(backend.shared_object_add(&shobj_msg).is_ok());
assert!(backend.shared_object_lookup(&shobj_msg, &fd).is_ok());
assert!(backend
.shared_object_lookup(
&VhostUserSharedMsg {
uuid: Uuid::new_v4(),
},
&fd,
)
.is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());
// Ensure that the handler thread did not panic.
assert!(frontend_handler.join().is_ok());
}
@ -445,7 +576,7 @@ mod tests {
#[cfg(feature = "vhost-user-backend")]
#[test]
fn test_frontend_backend_req_handler_with_ack() {
let backend = Arc::new(Mutex::new(MockFrontendReqHandler {}));
let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new()));
let mut handler = FrontendReqHandler::new(backend).unwrap();
handler.set_reply_ack_flag(true);
@ -463,6 +594,13 @@ mod tests {
let res = handler.handle_request().unwrap();
assert_eq!(res, 0);
handler.handle_request().unwrap_err();
// Testing shared object messages.
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
});
backend.set_reply_ack_flag(true);
@ -472,6 +610,23 @@ mod tests {
backend
.fs_backend_unmap(&VhostUserFSBackendMsg::default())
.unwrap_err();
let shobj_msg = VhostUserSharedMsg {
uuid: Uuid::new_v4(),
};
assert!(backend.shared_object_add(&shobj_msg).is_ok());
assert!(backend.shared_object_add(&shobj_msg).is_err());
assert!(backend.shared_object_lookup(&shobj_msg, &fd).is_ok());
assert!(backend
.shared_object_lookup(
&VhostUserSharedMsg {
uuid: Uuid::new_v4(),
},
&fd,
)
.is_err());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_err());
// Ensure that the handler thread did not panic.
assert!(frontend_handler.join().is_ok());
}

View file

@ -15,6 +15,8 @@ use std::io;
use std::marker::PhantomData;
use std::ops::Deref;
use uuid::Uuid;
use vm_memory::{mmap::NewBitmap, ByteValued, Error as MmapError, FileOffset, MmapRegion};
#[cfg(feature = "xen")]
@ -178,6 +180,8 @@ enum_value! {
/// Query the backend for its device status as defined in the VIRTIO
/// specification.
GET_STATUS = 40,
/// Retrieve a shared object from the device.
GET_SHARED_OBJECT = 41,
/// Begin transfer of internal state to/from the backend for migration
/// purposes.
SET_DEVICE_STATE_FD = 42,
@ -203,6 +207,12 @@ enum_value! {
VRING_CALL = 4,
/// Indicate that an error occurred on the specific vring.
VRING_ERR = 5,
/// Add a virtio shared object.
SHARED_OBJECT_ADD = 6,
/// Remove a virtio shared object.
SHARED_OBJECT_REMOVE = 7,
/// Lookup for a virtio shared object.
SHARED_OBJECT_LOOKUP = 8,
// Non-standard message types.
/// Virtio-fs draft: map file content into the window.
@ -444,6 +454,8 @@ bitflags! {
const STATUS = 0x0001_0000;
/// Support Xen mmap.
const XEN_MMAP = 0x0002_0000;
/// Support shared objects.
const SHARED_OBJECT = 0x0004_0000;
/// Support transferring internal device state.
const DEVICE_STATE = 0x0008_0000;
}
@ -948,6 +960,25 @@ enum_value! {
}
}
/// Contains UUID to interact with associated virtio shared object.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct VhostUserSharedMsg {
/// UUID of the shared object
pub uuid: Uuid,
}
// SAFETY: Safe because VhostUserSharedMsg is a
// fixed-size array internally and there is no
// compiler-inserted padding.
unsafe impl ByteValued for VhostUserSharedMsg {}
impl VhostUserMsgValidator for VhostUserSharedMsg {
fn is_valid(&self) -> bool {
!(self.uuid.is_nil() || self.uuid.is_max())
}
}
/// Query/send virtio-fs migration state
// Note: this struct is not defined as `packed` in the SPEC and although
// it is not necessary, since the struct has no padding, it simplifies