// Copyright 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::io; use std::io::ErrorKind; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use event_monitor::event; use log::{error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use vhost::Error as VhostError; use vhost::vhost_user::message::{ VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; use vhost::vhost_user::{Error as VhostUserError, FrontendReqHandler, VhostUserFrontendReqHandler}; use virtio_queue::{Error as QueueError, Queue}; use vm_memory::guest_memory::Error as MmapError; use vm_memory::mmap::MmapRegionError; use vm_memory::{Address, GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{MigratableError, Pausable, Snapshot}; use vmm_sys_util::eventfd::EventFd; use vu_common_ctrl::VhostUserHandle; use crate::seccomp_filters::Thread; use crate::{ ActivateError, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IN_ORDER, VIRTIO_F_NOTIFICATION_DATA, VIRTIO_F_ORDER_PLATFORM, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_VERSION_1, VirtioCommon, VirtioInterrupt, }; pub mod blk; pub mod fs; pub mod generic_vhost_user; pub mod net; pub mod vu_common_ctrl; pub use self::blk::Blk; pub use self::fs::*; pub use self::generic_vhost_user::GenericVhostUser; pub use self::net::Net; pub use self::vu_common_ctrl::VhostUserConfig; #[derive(Error, Debug)] pub enum Error { #[error("Failed accepting connection")] AcceptConnection(#[source] io::Error), #[error("Invalid available address")] AvailAddress, #[error("Queue number is not correct")] BadQueueNum, #[error("Failed binding vhost-user socket")] BindSocket(#[source] io::Error), #[error("Creating kill eventfd failed")] CreateKillEventFd(#[source] io::Error), #[error("Cloning kill eventfd failed")] CloneKillEventFd(#[source] io::Error), #[error("Invalid descriptor table address")] DescriptorTableAddress, #[error("Signal used queue failed")] FailedSignalingUsedQueue(#[source] io::Error), #[error("Failed to read vhost eventfd")] MemoryRegions(#[source] MmapError), #[error("Failed removing socket path")] RemoveSocketPath(#[source] io::Error), #[error("Failed to create frontend")] VhostUserCreateFrontend(#[source] VhostError), #[error("Failed to open vhost device")] VhostUserOpen(#[source] VhostError), #[error("Connection to socket failed")] VhostUserConnect(#[source] VhostError), #[error("Backend disconnected for socket {0}")] BackendDisconnected(String), #[error("Get features failed")] VhostUserGetFeatures(#[source] VhostError), #[error("Get queue max number failed")] VhostUserGetQueueMaxNum(#[source] VhostError), #[error("Get protocol features failed")] VhostUserGetProtocolFeatures(#[source] VhostError), #[error("Get vring base failed")] VhostUserGetVringBase(#[source] VhostError), #[error("Vhost-user Backend not support vhost-user protocol")] VhostUserProtocolNotSupport, #[error("Set owner failed")] VhostUserSetOwner(#[source] VhostError), #[error("Reset owner failed")] VhostUserResetOwner(#[source] VhostError), #[error("Set features failed")] VhostUserSetFeatures(#[source] VhostError), #[error("Set protocol features failed")] VhostUserSetProtocolFeatures(#[source] VhostError), #[error("Set mem table failed")] VhostUserSetMemTable(#[source] VhostError), #[error("Set vring num failed")] VhostUserSetVringNum(#[source] VhostError), #[error("Set vring addr failed")] VhostUserSetVringAddr(#[source] VhostError), #[error("Set vring base failed")] VhostUserSetVringBase(#[source] VhostError), #[error("Set vring call failed")] VhostUserSetVringCall(#[source] VhostError), #[error("Set vring kick failed")] VhostUserSetVringKick(#[source] VhostError), #[error("Set vring enable failed")] VhostUserSetVringEnable(#[source] VhostError), #[error("Failed to create vhost eventfd")] VhostIrqCreate(#[source] io::Error), #[error("Failed to read vhost eventfd")] VhostIrqRead(#[source] io::Error), #[error("Failed to read vhost eventfd")] VhostUserMemoryRegion(#[source] MmapError), #[error("Failed to create the frontend request handler from backend")] FrontendReqHandlerCreation(#[source] vhost::vhost_user::Error), #[error("Set backend request fd failed")] VhostUserSetBackendRequestFd(#[source] vhost::Error), #[error("Add memory region failed")] VhostUserAddMemReg(#[source] VhostError), #[error("Failed getting the configuration")] VhostUserGetConfig(#[source] VhostError), #[error("Failed setting the configuration")] VhostUserSetConfig(#[source] VhostError), #[error("Failed getting inflight shm log")] VhostUserGetInflight(#[source] VhostError), #[error("Failed setting inflight shm log")] VhostUserSetInflight(#[source] VhostError), #[error("Failed setting the log base")] VhostUserSetLogBase(#[source] VhostError), #[error("Invalid used address")] UsedAddress, #[error("Invalid features provided from vhost-user backend")] InvalidFeatures, #[error("Missing file descriptor for the region")] MissingRegionFd, #[error("Missing IrqFd")] MissingIrqFd, #[error("Failed getting the available index")] GetAvailableIndex(#[source] QueueError), #[error("Migration is not supported by this vhost-user device")] MigrationNotSupported, #[error("Failed creating memfd")] MemfdCreate(#[source] io::Error), #[error("Failed truncating the file size to the expected size")] SetFileSize(#[source] io::Error), #[error("Failed to set the seals on the file")] SetSeals(#[source] io::Error), #[error("Failed creating new mmap region")] NewMmapRegion(#[source] MmapRegionError), #[error("Could not find the shm log region")] MissingShmLogRegion, #[error("Failed setting device state fd")] VhostUserSetDeviceStateFd(#[source] VhostError), #[error("Failed checking device state")] VhostUserCheckDeviceState(#[source] VhostError), #[error("Failed saving/restoring backend state")] SaveRestoreBackendState(#[source] io::Error), #[error("Vring bases count ({0}) does not match queue count ({1})")] VringBasesCountMismatch(usize, usize), #[error("Backend state and vring bases must both be present or both be absent")] InconsistentBackendState, #[error("Failed to create timerfd")] TimerFdCreate(#[source] io::Error), #[error("Failed to arm timerfd")] TimerFdArm(#[source] io::Error), #[error("Failed waiting on timerfd")] TimerFdWait(#[source] io::Error), #[error("Failed to create epoll instance")] EpollCreate(#[source] io::Error), #[error("Failed to add fd to epoll")] EpollCtl(#[source] io::Error), #[error("Failed waiting on epoll")] EpollWait(#[source] io::Error), #[error("Aborted vhost-user connect: kill event received")] ConnectKilled, } type Result = std::result::Result; fn io_error_is_connection_lost(error: &io::Error) -> bool { matches!( error.kind(), ErrorKind::BrokenPipe | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset | ErrorKind::NotConnected | ErrorKind::UnexpectedEof ) } fn vhost_user_error_is_transport_lost(error: &VhostUserError) -> bool { match error { VhostUserError::Disconnected | VhostUserError::PartialMessage | VhostUserError::SocketBroken(_) | VhostUserError::SocketConnect(_) => true, VhostUserError::SocketError(e) => io_error_is_connection_lost(e), _ => false, } } fn vhost_error_is_transport_lost(error: &VhostError) -> bool { match error { VhostError::VhostUserProtocol(e) => vhost_user_error_is_transport_lost(e), VhostError::IOError(e) => io_error_is_connection_lost(e), _ => false, } } impl Error { fn is_transport_lost(&self) -> bool { match self { Error::VhostUserConnect(_) | Error::BackendDisconnected(_) => true, Error::VhostUserCreateFrontend(e) | Error::VhostUserOpen(e) | Error::VhostUserGetFeatures(e) | Error::VhostUserGetQueueMaxNum(e) | Error::VhostUserGetProtocolFeatures(e) | Error::VhostUserGetVringBase(e) | Error::VhostUserSetOwner(e) | Error::VhostUserResetOwner(e) | Error::VhostUserSetFeatures(e) | Error::VhostUserSetProtocolFeatures(e) | Error::VhostUserSetMemTable(e) | Error::VhostUserSetVringNum(e) | Error::VhostUserSetVringAddr(e) | Error::VhostUserSetVringBase(e) | Error::VhostUserSetVringCall(e) | Error::VhostUserSetVringKick(e) | Error::VhostUserSetVringEnable(e) | Error::VhostUserSetBackendRequestFd(e) | Error::VhostUserAddMemReg(e) | Error::VhostUserGetConfig(e) | Error::VhostUserSetConfig(e) | Error::VhostUserGetInflight(e) | Error::VhostUserSetInflight(e) | Error::VhostUserSetLogBase(e) | Error::VhostUserSetDeviceStateFd(e) | Error::VhostUserCheckDeviceState(e) => vhost_error_is_transport_lost(e), Error::FrontendReqHandlerCreation(e) => vhost_user_error_is_transport_lost(e), _ => false, } } } pub const DEFAULT_VIRTIO_FEATURES: u64 = (1 << VIRTIO_F_RING_INDIRECT_DESC) | (1 << VIRTIO_F_RING_EVENT_IDX) | (1 << VIRTIO_F_VERSION_1) | (1 << VIRTIO_F_IN_ORDER) | (1 << VIRTIO_F_ORDER_PLATFORM) | (1 << VIRTIO_F_NOTIFICATION_DATA) | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() | VhostUserVirtioFeatures::LOG_ALL.bits(); const HUP_CONNECTION_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; const BACKEND_REQ_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2; #[derive(Default)] pub struct Inflight { pub info: VhostUserInflight, pub fd: Option, } pub struct VhostUserEpollHandler { pub vu: Arc>, pub mem: GuestMemoryAtomic, pub kill_evt: EventFd, pub pause_evt: EventFd, pub queues: Vec<(usize, Queue, EventFd)>, pub virtio_interrupt: Arc, pub acked_features: u64, pub acked_protocol_features: u64, pub socket_path: String, pub server: bool, pub backend_req_handler: Option>, pub inflight: Option, /// Flag set by the worker when the vhost-user backend is no longer reachable. pub disconnected: Arc, } impl VhostUserEpollHandler { pub fn run( &mut self, paused: &AtomicBool, paused_sync: &Barrier, ) -> std::result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event_custom( self.vu.lock().unwrap().socket_handle().as_raw_fd(), HUP_CONNECTION_EVENT, epoll::Events::EPOLLHUP, )?; if let Some(backend_req_handler) = &self.backend_req_handler { helper.add_event(backend_req_handler.as_raw_fd(), BACKEND_REQ_EVENT)?; } helper.run(paused, paused_sync, self)?; Ok(()) } fn reconnect(&mut self, helper: &mut EpollHelper) -> std::result::Result<(), EpollHelperError> { let result = self.reconnect_inner(helper); if result.is_err() { // If reconnect fails, mark disconnected to avoid repeated failed socket calls. self.disconnected.store(true, Ordering::Relaxed); } result } fn reconnect_inner( &mut self, helper: &mut EpollHelper, ) -> std::result::Result<(), EpollHelperError> { helper.del_event_custom( self.vu.lock().unwrap().socket_handle().as_raw_fd(), HUP_CONNECTION_EVENT, epoll::Events::EPOLLHUP, )?; let mut vhost_user = match VhostUserHandle::connect_vhost_user( self.server, &self.socket_path, self.queues.len() as u64, true, Some(&self.kill_evt), ) { Ok(vu) => vu, // Kill event fired during the connect retry loop; abandon the // reconnect attempt. The EpollHelper observes the same kill // event and will tear down on its next iteration. Err(Error::ConnectKilled) => return Ok(()), Err(e) => { return Err(EpollHelperError::IoError(std::io::Error::other(format!( "failed connecting vhost-user backend for socket {}: {e:?}", self.socket_path )))); } }; let queues = self .queues .iter() .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) .collect::>(); // Initialize the backend vhost_user .reinitialize_vhost_user( self.mem.memory().deref(), &queues, self.virtio_interrupt.as_ref(), self.acked_features, self.acked_protocol_features, &self.backend_req_handler, self.inflight.as_mut(), ) .map_err(|e| { EpollHelperError::IoError(std::io::Error::other(format!( "failed reconnecting vhost-user backend: {e:?}" ))) })?; helper.add_event_custom( vhost_user.socket_handle().as_raw_fd(), HUP_CONNECTION_EVENT, epoll::Events::EPOLLHUP, )?; // Update vhost-user reference let mut vu = self.vu.lock().unwrap(); *vu = vhost_user; Ok(()) } } impl EpollHelperHandler for VhostUserEpollHandler { fn handle_event( &mut self, helper: &mut EpollHelper, event: &epoll::Event, ) -> std::result::Result<(), EpollHelperError> { let ev_type = event.data as u16; let result = match ev_type { HUP_CONNECTION_EVENT => { info!( "vhost-user backend for socket {} disconnected, attempting reconnection", self.socket_path ); self.reconnect(helper).map_err(|e| { EpollHelperError::HandleEvent(anyhow!( "failed to reconnect vhost-user backend for socket {}: {e:?}", self.socket_path )) }) } BACKEND_REQ_EVENT => { if let Some(backend_req_handler) = self.backend_req_handler.as_mut() { backend_req_handler .handle_request() .map(|_| ()) .map_err(|e| { EpollHelperError::HandleEvent(anyhow!( "Failed to handle request from vhost-user backend: {e:?}" )) }) } else { Ok(()) } } _ => Err(EpollHelperError::HandleEvent(anyhow!( "Unknown event for vhost-user thread" ))), }; // If the backend hits and error it is unusable from this point on. if result.is_err() { self.disconnected.store(true, Ordering::Relaxed); } result } } /// Common snapshot state for all vhost-user device types. /// /// Generic over `C` which is the device-specific config type /// (e.g. VirtioBlockConfig, VirtioFsConfig, VirtioNetConfig). /// Devices without a config type use `()`. #[derive(Default, Serialize, Deserialize)] pub struct VhostUserState { pub avail_features: u64, pub acked_features: u64, pub config: C, pub acked_protocol_features: u64, pub vu_num_queues: usize, #[serde(default)] pub backend_req_support: bool, #[serde(default)] pub vring_bases: Option>, #[serde(default)] pub backend_state: Option>, } impl VhostUserState { pub fn validate(&self) -> Result<()> { if self.backend_state.is_some() != self.vring_bases.is_some() { return Err(Error::InconsistentBackendState); } Ok(()) } } #[derive(Default)] pub struct VhostUserCommon { pub virtio_common: VirtioCommon, pub vu: Option>>, pub guest_memory: Option>, pub acked_protocol_features: u64, pub socket_path: String, pub vu_num_queues: usize, pub migration_started: bool, pub server: bool, pub vring_bases: Option>, /// Indicates that the backend is no longer reachable. Shared with EPollHandler. pub disconnected: Arc, } impl VhostUserCommon { #[allow(clippy::too_many_arguments)] pub fn activate( &mut self, mem: GuestMemoryAtomic, queues: &[(usize, Queue, EventFd)], interrupt_cb: Arc, acked_features: u64, backend_req_handler: Option>, kill_evt: EventFd, pause_evt: EventFd, ) -> std::result::Result, ActivateError> { self.guest_memory = Some(mem.clone()); if self.disconnected.load(Ordering::Relaxed) { warn!( "Not activating disconnected vhost-user device for socket {}", self.socket_path ); return Err(ActivateError::BadActivate); } let mut inflight: Option = if self.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits() != 0 { Some(Inflight::default()) } else { None }; if self.vu.is_none() { error!("Missing vhost-user handle for socket {}", self.socket_path); return Err(ActivateError::BadActivate); } let vu = self.vu.as_ref().unwrap(); let queues = queues .iter() .map(|(i, q, e)| (*i, vm_virtio::clone_queue(q), e.try_clone().unwrap())) .collect::>(); let vring_bases = self.vring_bases.take(); vu.lock() .unwrap() .setup_vhost_user( &mem.memory(), &queues, interrupt_cb.as_ref(), acked_features, &backend_req_handler, inflight.as_mut(), vring_bases.as_deref(), ) .map_err(ActivateError::VhostUserSetup)?; Ok(VhostUserEpollHandler { vu: vu.clone(), mem, kill_evt, pause_evt, queues, virtio_interrupt: interrupt_cb, acked_features, acked_protocol_features: self.acked_protocol_features, socket_path: self.socket_path.clone(), server: self.server, backend_req_handler, inflight, disconnected: self.disconnected.clone(), }) } /// Like `VirtioCommon::spawn_worker`, but on failure also runs /// `self.reset(id)` to tear down the vhost-user backend. #[expect(clippy::too_many_arguments)] pub fn spawn_worker( &mut self, id: &str, seccomp_action: &SeccompAction, thread_type: Thread, exit_evt: &EventFd, device_status: Arc, interrupt_cb: Arc, f: F, ) -> std::result::Result<(), ActivateError> where F: FnOnce() -> std::result::Result<(), EpollHelperError> + Send + 'static, { if let Err(e) = self.virtio_common.spawn_worker( id, seccomp_action, thread_type, exit_evt, device_status, interrupt_cb, f, ) { self.reset(id); return Err(e); } Ok(()) } pub fn reset(&mut self, id: &str) { // Resume the virtio thread if it was paused. Reset must always // converge to fresh state, so backend resume / reset failures are // logged but don't skip the rest of the teardown. if self.virtio_common.pause_evt.take().is_some() && let Err(e) = self.virtio_common.resume() { error!("Failed to resume paused device during reset: {e:?}"); } // Skip reset_vhost_user if the backend is not connected. if !self.disconnected.load(Ordering::Relaxed) && let Some(vu) = &self.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { if e.is_transport_lost() { warn!( "Failed to reset vhost-user daemon for socket {}: {e:?}; \ marking device as disconnected", self.socket_path ); self.disconnected.store(true, Ordering::Relaxed); } else { warn!( "Failed to reset vhost-user daemon for socket {}: {e:?}", self.socket_path ); } } self.virtio_common.reset(); event!("virtio-device", "reset", "id", id); } fn memory_update_error(&self, source: Error) -> crate::Error { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() != 0 { crate::Error::VhostUserAddMemoryRegion(source) } else { crate::Error::VhostUserUpdateMemory(source) } } pub fn shutdown(&mut self) { // Join the workers so they drop their Arc and the // socket closes, letting the migration destination reconnect. self.virtio_common.wait_for_epoll_threads(); // Remove socket path if needed if self.server { let _ = std::fs::remove_file(&self.socket_path); } // Drop the vhost-user handle self.vu = None; } fn add_memory_region_internal(&self, region: &Arc) -> Result<()> { if let Some(vu) = &self.vu { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() != 0 { return vu.lock().unwrap().add_memory_region(region); } else if let Some(guest_memory) = &self.guest_memory { return vu .lock() .unwrap() .update_mem_table(guest_memory.memory().deref()); } } Ok(()) } pub fn add_memory_region( &mut self, region: &Arc, ) -> std::result::Result<(), crate::Error> { if self.disconnected.load(Ordering::Relaxed) { warn!( "Skipping add memory region on disconnected dev with socket: {}", self.socket_path ); } if let Err(e) = self.add_memory_region_internal(region) { if e.is_transport_lost() { warn!( "Failed updating memory on vhost-user backend for socket {}: {e:?}; \ marking device as disconnected", self.socket_path ); self.disconnected.store(true, Ordering::Relaxed); } else { warn!( "Failed updating memory on vhost-user backend for socket {}: {e:?}", self.socket_path ); } return Err(self.memory_update_error(e)); } Ok(()) } pub fn pause(&mut self) -> std::result::Result<(), MigratableError> { if self.disconnected.load(Ordering::Relaxed) { return Err(MigratableError::DeviceDisconnected( self.socket_path.clone(), )); } if let Some(vu) = &self.vu && let Err(e) = vu.lock().unwrap().pause_vhost_user() { if e.is_transport_lost() { self.disconnected.store(true, Ordering::Relaxed); return Err(MigratableError::DeviceDisconnected( self.socket_path.clone(), )); } return Err(MigratableError::Pause(anyhow!( "Error pausing vhost-user backend for socket {}: {e:?}", self.socket_path ))); } Ok(()) } fn resume_internal(&mut self) -> std::result::Result<(), MigratableError> { // Skip the resume_vhost_user call if the backend is disconnected. Process the queue // interrupts to kick any paused workers. if self.disconnected.load(Ordering::Relaxed) { return Err(MigratableError::DeviceDisconnected( self.socket_path.clone(), )); } if let Some(vu) = &self.vu && let Err(e) = vu.lock().unwrap().resume_vhost_user() { if e.is_transport_lost() { self.disconnected.store(true, Ordering::Relaxed); return Err(MigratableError::DeviceDisconnected( self.socket_path.clone(), )); } return Err(MigratableError::Resume(anyhow!( "Error resuming vhost-user backend for socket {}: {e:?}", self.socket_path ))); } Ok(()) } pub fn resume(&mut self) -> std::result::Result<(), MigratableError> { let ret = self.resume_internal(); // Always run the interrupt loop so workers don't get stuck. for i in 0..self.vu_num_queues { self.virtio_common .trigger_interrupt(crate::VirtioInterruptType::Queue(i as u16)) .ok(); } ret } pub fn state( &self, config: C, ) -> std::result::Result, MigratableError> { let mut state = VhostUserState { avail_features: self.virtio_common.avail_features, acked_features: self.virtio_common.acked_features, config, acked_protocol_features: self.acked_protocol_features, vu_num_queues: self.vu_num_queues, ..Default::default() }; if let Some(vu) = &self.vu { let mut vu_locked = vu.lock().unwrap(); if vu_locked.supports_device_state() { let (backend_state, vring_bases) = vu_locked.save_backend_state().map_err(|e| { MigratableError::Snapshot(anyhow!("Failed saving backend state: {e:?}")) })?; state.backend_state = Some(backend_state); state.vring_bases = Some(vring_bases); } } Ok(state) } pub fn snapshot(&mut self, state: &T) -> std::result::Result where T: Serialize, { let snapshot = Snapshot::new_from_state(state)?; if self.migration_started { self.shutdown(); } Ok(snapshot) } pub fn start_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { if let Some(vu) = &self.vu { if let Some(guest_memory) = &self.guest_memory { let last_ram_addr = guest_memory.memory().last_addr().raw_value(); vu.lock() .unwrap() .start_dirty_log(last_ram_addr) .map_err(|e| { MigratableError::StartDirtyLog(anyhow!( "Error starting migration for vhost-user backend: {e:?}" )) }) } else { Err(MigratableError::StartDirtyLog(anyhow!( "Missing guest memory" ))) } } else { Ok(()) } } pub fn stop_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { if let Some(vu) = &self.vu { vu.lock().unwrap().stop_dirty_log().map_err(|e| { MigratableError::StopDirtyLog(anyhow!( "Error stopping migration for vhost-user backend: {e:?}" )) }) } else { Ok(()) } } pub fn dirty_log(&mut self) -> std::result::Result { if let Some(vu) = &self.vu { if let Some(guest_memory) = &self.guest_memory { let last_ram_addr = guest_memory.memory().last_addr().raw_value(); vu.lock().unwrap().dirty_log(last_ram_addr).map_err(|e| { MigratableError::DirtyLog(anyhow!( "Error retrieving dirty ranges from vhost-user backend: {e:?}" )) }) } else { Err(MigratableError::DirtyLog(anyhow!("Missing guest memory"))) } } else { Ok(MemoryRangeTable::default()) } } pub fn start_migration(&mut self) -> std::result::Result<(), MigratableError> { self.migration_started = true; Ok(()) } pub fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { self.migration_started = false; // Make sure the device thread is killed in order to prevent from // reconnections to the socket. if let Some(workers) = self.virtio_common.workers.as_ref() { workers.signal_exit().map_err(|e| { MigratableError::CompleteMigration(anyhow!( "Error killing vhost-user thread: {e:?}" )) })?; } // Drop the vhost-user handler to avoid further calls to fail because // the connection with the backend has been closed. self.vu = None; Ok(()) } }