diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 22da8df7f..2283ff8be 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -14,6 +14,38 @@ mod bitpos_iterator; mod context; pub mod protocol; +#[derive(Error, Debug)] +pub enum UffdError { + #[error("Snapshot ranges are not page-aligned")] + UnalignedRanges, + + #[error("Failed to create userfaultfd")] + Create(#[source] std::io::Error), + + #[error("Cannot translate GPA {gpa:#x} to host address")] + GpaTranslation { gpa: u64 }, + + #[error("Failed to register region at {addr:#x}+{len:#x}")] + Register { + addr: u64, + len: u64, + #[source] + source: std::io::Error, + }, + + #[error("Region at {addr:#x}+{len:#x} missing COPY/WAKE support")] + MissingIoctlSupport { addr: u64, len: u64 }, + + #[error("Failed to spawn handler thread")] + SpawnThread(#[source] std::io::Error), + + #[error("Handler terminated before startup completed")] + HandlerStartup, + + #[error("Handler failed after startup")] + HandlerFailed(#[source] std::io::Error), +} + #[derive(Error, Debug)] pub enum MigratableError { #[error("Failed to pause migratable component")] @@ -34,6 +66,9 @@ pub enum MigratableError { #[error("Failed to receive migratable component snapshot")] MigrateReceive(#[source] anyhow::Error), + #[error("On-demand restore failed")] + OnDemandRestore(#[source] UffdError), + #[error("Socket error")] MigrateSocket(#[source] std::io::Error), diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f03927ce3..0b82be49e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -51,7 +51,7 @@ use crate::api::{ ApiRequest, ApiResponse, RequestHandler, VmInfoResponse, VmReceiveMigrationData, VmSendMigrationData, VmmPingResponse, }; -use crate::config::{RestoreConfig, add_to_config}; +use crate::config::{MemoryRestoreMode, RestoreConfig, add_to_config}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::GuestDebuggable; use crate::landlock::Landlock; @@ -88,6 +88,8 @@ mod pci_segment; pub mod seccomp_filters; mod serial_manager; mod sigwinch_listener; +mod uffd; +mod userfaultfd; pub mod vm; pub mod vm_config; @@ -1506,6 +1508,7 @@ impl Vmm { source_url: &str, vm_config: Arc>, prefault: bool, + memory_restore_mode: MemoryRestoreMode, ) -> std::result::Result<(), VmError> { let snapshot = recv_vm_state(source_url).map_err(VmError::Restore)?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] @@ -1548,6 +1551,7 @@ impl Vmm { Some(&snapshot), Some(source_url), Some(prefault), + Some(memory_restore_mode), )?; self.vm = Some(vm); @@ -1754,6 +1758,7 @@ impl RequestHandler for Vmm { None, None, None, + None, )?; self.vm = Some(vm); @@ -1838,17 +1843,22 @@ impl RequestHandler for Vmm { } } - self.vm_restore(source_url, vm_config, restore_cfg.prefault) - .map_err(|vm_restore_err| { - error!("VM Restore failed: {vm_restore_err:?}"); + self.vm_restore( + source_url, + vm_config, + restore_cfg.prefault, + restore_cfg.memory_restore_mode, + ) + .map_err(|vm_restore_err| { + error!("VM Restore failed: {vm_restore_err:?}"); - // Cleanup the VM being created while vm restore - if let Err(e) = self.vm_delete() { - return e; - } + // Cleanup the VM being created while vm restore + if let Err(e) = self.vm_delete() { + return e; + } - vm_restore_err - }) + vm_restore_err + }) } #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -1930,6 +1940,7 @@ impl RequestHandler for Vmm { None, None, None, + None, )?; // And we boot it diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index 2cb735ecd..ba0313b29 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -7,13 +7,13 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::fs::{File, OpenOptions}; -use std::io::{self}; +use std::io::{self, Read as _, Seek, SeekFrom}; use std::ops::{BitAnd, Not, Sub}; -#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] -use std::os::fd::AsFd; +use std::os::fd::{AsFd, OwnedFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::mpsc::{self, Receiver, SyncSender}; use std::sync::{Arc, Barrier, Mutex}; use std::{ffi, result, thread}; @@ -44,15 +44,31 @@ use vm_memory::{ use vm_migration::protocol::{MemoryRange, MemoryRangeTable}; use vm_migration::{ Migratable, MigratableError, Pausable, Snapshot, SnapshotData, Snapshottable, Transportable, + UffdError, }; +use vmm_sys_util::eventfd::EventFd; +use crate::config::MemoryRestoreMode; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ CoredumpMemoryRegion, CoredumpMemoryRegions, DumpState, GuestDebuggableError, }; use crate::migration::url_to_path; use crate::vm_config::{HotplugMethod, MemoryConfig, MemoryZoneConfig}; -use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID}; +use crate::{GuestMemoryMmap, GuestRegionMmap, MEMORY_MANAGER_SNAPSHOT_ID, uffd}; + +struct UffdHandler { + stop_event: EventFd, + result_rx: Receiver>, + handle: thread::JoinHandle<()>, +} + +struct UffdRange { + host_addr: u64, + length: u64, + file_offset: u64, + page_size: u64, +} pub const MEMORY_MANAGER_ACPI_SIZE: usize = 0x18; @@ -116,13 +132,25 @@ impl VirtioMemZone { } } -#[derive(Default)] pub struct MemoryZone { regions: Vec>, virtio_mem_zone: Option, + shared: bool, + hugepages: bool, + backing_page_size: u64, } impl MemoryZone { + fn new(shared: bool, hugepages: bool, backing_page_size: u64) -> Self { + Self { + regions: Vec::new(), + virtio_mem_zone: None, + shared, + hugepages, + backing_page_size, + } + } + pub fn regions(&self) -> &Vec> { &self.regions } @@ -132,6 +160,21 @@ impl MemoryZone { pub fn virtio_mem_zone_mut(&mut self) -> Option<&mut VirtioMemZone> { self.virtio_mem_zone.as_mut() } + + fn backing_page_size_for_gpa(&self, gpa: u64) -> Option { + if self.regions.iter().any(|region| { + let start = region.start_addr().raw_value(); + gpa >= start && gpa < start + region.len() + }) { + return Some(self.backing_page_size); + } + + self.virtio_mem_zone.as_ref().and_then(|virtio_mem_zone| { + let start = virtio_mem_zone.region.start_addr().raw_value(); + (gpa >= start && gpa < start + virtio_mem_zone.region.len()) + .then_some(self.backing_page_size) + }) + } } pub type MemoryZones = HashMap; @@ -187,6 +230,7 @@ pub struct MemoryManager { // This is useful for getting the dirty pages as we need to know the // slots that the mapping is created in. guest_ram_mappings: Vec, + uffd_handler: Option, pub acpi_address: Option, #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] @@ -350,6 +394,12 @@ pub enum Error { MisalignedMemorySize, } +impl From for Error { + fn from(e: UffdError) -> Self { + Error::Restore(MigratableError::OnDemandRestore(e)) + } +} + const ENABLE_FLAG: usize = 0; const INSERTING_FLAG: usize = 1; const REMOVING_FLAG: usize = 2; @@ -551,7 +601,10 @@ impl MemoryManager { } // Add zone id to the list of memory zones. - memory_zones.insert(zone.id.clone(), MemoryZone::default()); + memory_zones.insert( + zone.id.clone(), + MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + ); for ram_region in ram_regions.iter() { let mut ram_region_offset = 0; @@ -642,7 +695,10 @@ impl MemoryManager { ); return Err(Error::DuplicateZoneId); } - memory_zones.insert(zone.id.clone(), MemoryZone::default()); + memory_zones.insert( + zone.id.clone(), + MemoryZone::new(zone.shared, zone.hugepages, zone_align_size), + ); } if ram_region_consumed { @@ -670,7 +726,11 @@ impl MemoryManager { let mut memory_zones = HashMap::new(); for zone_config in zones_config { - memory_zones.insert(zone_config.id.clone(), MemoryZone::default()); + let zone_page_size = memory_zone_get_align_size(zone_config)?; + memory_zones.insert( + zone_config.id.clone(), + MemoryZone::new(zone_config.shared, zone_config.hugepages, zone_page_size), + ); } for guest_ram_mapping in guest_ram_mappings { @@ -760,6 +820,360 @@ impl MemoryManager { Ok(()) } + /// Restore guest memory using userfaultfd for lazy demand paging. + /// + /// Instead of reading the entire snapshot into guest RAM upfront (which + /// blocks restore for hundreds of milliseconds at multi-GB sizes), this + /// registers the guest memory regions with a userfaultfd. A background + /// thread handles page faults by reading the corresponding page from the + /// snapshot file and copying it into guest memory via `UFFDIO_COPY`. + /// + /// This preserves the original memory mapping type (anonymous or shared), + /// making it compatible with VFIO device passthrough and shared-memory + /// guest RAM. + /// + /// Fails the restore if UFFD setup cannot be completed successfully. + /// + /// The handler thread keeps the snapshot file open while lazy restore + /// is active. The file must remain available until the VM is shut down or + /// all faulted pages have been served. + fn restore_by_uffd( + &mut self, + file_path: &Path, + saved_regions: &MemoryRangeTable, + exit_evt: &EventFd, + ) -> Result<(), Error> { + if saved_regions.is_empty() { + return Ok(()); + } + + let guest_memory = self.guest_memory.memory(); + let required_uffd_features = self.required_uffd_features(); + + // SAFETY: FFI call. Trivially safe. + let base_page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64; + + info!( + "UFFD restore: attempting demand-paged restore for {} region(s)", + saved_regions.regions().len() + ); + + if saved_regions + .regions() + .iter() + .any(|range| range.gpa % base_page_size != 0 || range.length % base_page_size != 0) + { + return Err(UffdError::UnalignedRanges.into()); + } + + let snapshot_file = File::open(file_path).map_err(Error::SnapshotOpen)?; + + let uffd_fd = uffd::create(required_uffd_features).map_err(UffdError::Create)?; + + let mut handler_ranges: Vec = Vec::new(); + let mut file_offset: u64 = 0; + + for range in saved_regions.regions() { + let host_addr = guest_memory + .get_host_address(GuestAddress(range.gpa)) + .map_err(|_| UffdError::GpaTranslation { gpa: range.gpa })? + as u64; + + let ioctls = uffd::register(uffd_fd.as_fd(), host_addr, range.length).map_err(|e| { + UffdError::Register { + addr: host_addr, + len: range.length, + source: e, + } + })?; + + if ioctls & crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC + != crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC + { + return Err(UffdError::MissingIoctlSupport { + addr: host_addr, + len: range.length, + } + .into()); + } + + let range_page_size = self + .memory_zones + .values() + .find_map(|zone| zone.backing_page_size_for_gpa(range.gpa)) + .unwrap_or(base_page_size); + + handler_ranges.push(UffdRange { + host_addr, + length: range.length, + file_offset, + page_size: range_page_size, + }); + + file_offset += range.length; + } + + info!( + "UFFD restore: registered {} region(s), {} total bytes, spawning handler", + handler_ranges.len(), + file_offset + ); + + let stop_event = EventFd::new(libc::EFD_NONBLOCK).map_err(Error::EventFdFail)?; + let thread_stop_event = stop_event.try_clone().map_err(Error::EventFdFail)?; + let thread_exit_evt = exit_evt.try_clone().map_err(Error::EventFdFail)?; + let (ready_tx, ready_rx) = mpsc::sync_channel(1); + let (result_tx, result_rx) = mpsc::sync_channel(1); + let handle = thread::Builder::new() + .name("uffd-handler".to_string()) + .spawn(move || { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || { + let max_page_size = handler_ranges + .iter() + .map(|r| r.page_size) + .max() + .unwrap_or(base_page_size); + let result = Self::uffd_handler_loop( + uffd_fd, + thread_stop_event, + snapshot_file, + &handler_ranges, + max_page_size, + &ready_tx, + ); + + if let Err(e) = &result { + error!("UFFD handler exited with error: {e}"); + } + + result_tx.send(result).ok(); + })) + .map_err(|_| { + error!("uffd-handler thread panicked"); + thread_exit_evt.write(1).ok(); + }) + .ok(); + }) + .map_err(UffdError::SpawnThread)?; + + if ready_rx.recv().is_err() { + handle.join().ok(); + return Err(UffdError::HandlerStartup.into()); + } + + if let Ok(Err(e)) = result_rx.try_recv() { + handle.join().ok(); + return Err(UffdError::HandlerFailed(e).into()); + } + + self.uffd_handler = Some(UffdHandler { + stop_event, + result_rx, + handle, + }); + + info!("UFFD restore: demand-paged restore enabled"); + + Ok(()) + } + + fn required_uffd_features(&self) -> u64 { + let mut features = 0u64; + if self.memory_zones.values().any(|z| z.shared && !z.hugepages) { + features |= crate::userfaultfd::UFFD_FEATURE_MISSING_SHMEM; + } + if self.memory_zones.values().any(|z| z.hugepages) { + features |= crate::userfaultfd::UFFD_FEATURE_MISSING_HUGETLBFS; + } + features + } + + fn stop_uffd_handler(&mut self) { + if let Some(uffd_handler) = self.uffd_handler.take() { + uffd_handler.stop_event.write(1).ok(); + uffd_handler.handle.join().ok(); + + match uffd_handler.result_rx.try_recv() { + Ok(Err(e)) => error!("UFFD handler terminated with error: {e}"), + Err(mpsc::TryRecvError::Disconnected) => { + warn!("UFFD handler terminated unexpectedly (possible panic)"); + } + _ => {} + } + } + } + + /// Poll the UFFD fd and serve page faults from the snapshot file. + /// + /// Runs until the fd is closed (EPOLLHUP) or an unrecoverable error occurs. + /// Each fault triggers a seek + read from the snapshot file followed by a + /// `UFFDIO_COPY` to resolve the fault and wake the faulting thread. + #[allow(clippy::needless_pass_by_value)] + fn uffd_handler_loop( + uffd_fd: OwnedFd, + stop_event: EventFd, + mut snapshot_file: File, + ranges: &[UffdRange], + page_size: u64, + ready_tx: &SyncSender<()>, + ) -> Result<(), io::Error> { + let uffd_raw_fd = uffd_fd.as_raw_fd(); + let mut page_buf = vec![0u8; page_size as usize]; + + let total_pages: u64 = ranges.iter().map(|r| r.length.div_ceil(r.page_size)).sum(); + let mut pages_served: u64 = 0; + + const EVENT_STOP: u64 = 0; + const EVENT_UFFD: u64 = 1; + + let epoll_fd = epoll::create(true).map_err(io::Error::other)?; + // SAFETY: epoll_fd is valid and owned by this scope. + let _epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + stop_event.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, EVENT_STOP), + ) + .map_err(io::Error::other)?; + + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + uffd_raw_fd, + epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLHUP, EVENT_UFFD), + ) + .map_err(io::Error::other)?; + + ready_tx.send(()).ok(); + + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 2]; + loop { + let num_events = match epoll::wait(epoll_fd, -1, &mut events) { + Ok(n) => n, + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + + let mut got_uffd_data = false; + for event in events.iter().take(num_events) { + let token = event.data; + let evt_flags = event.events; + + if token == EVENT_STOP { + stop_event.read().ok(); + info!("UFFD handler: received stop event, exiting"); + return Ok(()); + } + + if token == EVENT_UFFD + && (evt_flags & epoll::Events::EPOLLHUP.bits()) != 0 + && (evt_flags & epoll::Events::EPOLLIN.bits()) == 0 + { + info!("UFFD handler: fd closed (EPOLLHUP), exiting"); + return Ok(()); + } + + if token == EVENT_UFFD && (evt_flags & epoll::Events::EPOLLIN.bits()) != 0 { + got_uffd_data = true; + } + } + + if !got_uffd_data { + continue; + } + + // SAFETY: UffdMsg is a plain repr(C) struct, safe to zero-init. + let mut msg: uffd::UffdMsg = unsafe { std::mem::zeroed() }; + // SAFETY: reading a uffd_msg-sized struct from the valid uffd fd. + let n = unsafe { + libc::read( + uffd_raw_fd, + &mut msg as *mut uffd::UffdMsg as *mut libc::c_void, + std::mem::size_of::(), + ) + }; + if n < 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::WouldBlock { + continue; + } + return Err(err); + } + if n == 0 { + info!("UFFD handler: EOF on fd, exiting"); + return Ok(()); + } + if n as usize != std::mem::size_of::() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Short read from userfaultfd", + )); + } + + if msg.event != crate::userfaultfd::UFFD_EVENT_PAGEFAULT { + continue; + } + + let fault_addr = msg.pf_address; + + let mut served = false; + for range in ranges { + // Round down to the page boundary containing the faulted address. + let page_addr = fault_addr & !(range.page_size - 1); + if page_addr >= range.host_addr && page_addr < range.host_addr + range.length { + let offset_in_range = page_addr - range.host_addr; + let file_pos = range.file_offset + offset_in_range; + + snapshot_file.seek(SeekFrom::Start(file_pos))?; + snapshot_file.read_exact(&mut page_buf[..range.page_size as usize])?; + + loop { + match uffd::copy( + uffd_fd.as_fd(), + page_addr, + page_buf.as_ptr(), + range.page_size, + ) { + Ok(()) => { + pages_served += 1; + break; + } + Err(e) if e.raw_os_error() == Some(libc::EEXIST) => { + if let Err(e) = + uffd::wake(uffd_fd.as_fd(), page_addr, range.page_size) + { + warn!("UFFDIO_WAKE failed at {page_addr:#x}: {e}"); + } + break; + } + Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => { + // The kernel can report a transient EAGAIN while the fault + // is being resolved; yield and retry instead of aborting restore. + thread::yield_now(); + } + Err(e) => return Err(e), + } + } + served = true; + break; + } + } + + if !served { + return Err(io::Error::other(format!( + "UFFD handler: fault at {fault_addr:#x} does not belong to any registered range", + ))); + } + + if pages_served == total_pages { + info!("UFFD handler: all {pages_served} pages served, exiting"); + return Ok(()); + } + } + } + fn validate_memory_config( config: &MemoryConfig, user_provided_zones: bool, @@ -1227,6 +1641,7 @@ impl MemoryManager { snapshot_memory_ranges: MemoryRangeTable::default(), memory_zones, guest_ram_mappings: Vec::new(), + uffd_handler: None, acpi_address, log_dirty: dynamic, // Cannot log dirty pages on a TD arch_mem_regions, @@ -1240,13 +1655,16 @@ impl MemoryManager { Ok(Arc::new(Mutex::new(memory_manager))) } + #[allow(clippy::too_many_arguments)] pub fn new_from_snapshot( snapshot: &Snapshot, vm: Arc, config: &MemoryConfig, source_url: Option<&str>, prefault: bool, + memory_restore_mode: MemoryRestoreMode, phys_bits: u8, + exit_evt: &EventFd, ) -> Result>, Error> { if let Some(source_url) = source_url { let mut memory_file_path = url_to_path(source_url).map_err(Error::Restore)?; @@ -1266,9 +1684,17 @@ impl MemoryManager { Default::default(), )?; - mm.lock() - .unwrap() - .fill_saved_regions(memory_file_path, &mem_snapshot.memory_ranges)?; + if memory_restore_mode == MemoryRestoreMode::OnDemand { + mm.lock().unwrap().restore_by_uffd( + &memory_file_path, + &mem_snapshot.memory_ranges, + exit_evt, + )?; + } else { + mm.lock() + .unwrap() + .fill_saved_regions(memory_file_path, &mem_snapshot.memory_ranges)?; + } Ok(mm) } else { @@ -2530,6 +2956,12 @@ impl Aml for MemoryManager { impl Pausable for MemoryManager {} +impl Drop for MemoryManager { + fn drop(&mut self) { + self.stop_uffd_handler(); + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct MemoryManagerSnapshotData { memory_ranges: MemoryRangeTable, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index bb40b99f6..938e7832f 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -25,6 +25,8 @@ use vhost::vhost_kern::vhost_binding::{ VHOST_VDPA_SET_STATUS, VHOST_VDPA_SET_VRING_ENABLE, VHOST_VDPA_SUSPEND, }; +use crate::userfaultfd::{UFFDIO_API, UFFDIO_COPY, UFFDIO_REGISTER, UFFDIO_WAKE}; + #[derive(Copy, Clone)] pub enum Thread { HttpApi, @@ -362,6 +364,10 @@ fn create_vmm_ioctl_seccomp_rule_common( VHOST_VDPA_GET_CONFIG_SIZE() )?], and![Cond::new(1, ArgLen::Dword, Eq, VHOST_VDPA_SUSPEND())?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_API)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_COPY)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_REGISTER)?], + and![Cond::new(1, ArgLen::Dword, Eq, UFFDIO_WAKE)?], ]; let hypervisor_rules = create_vmm_ioctl_seccomp_rule_hypervisor(hypervisor_type)?; @@ -691,6 +697,7 @@ fn vmm_thread_rules( (libc::SYS_unlink, vec![]), #[cfg(target_arch = "aarch64")] (libc::SYS_unlinkat, vec![]), + (libc::SYS_userfaultfd, vec![]), (libc::SYS_wait4, vec![]), (libc::SYS_write, vec![]), (libc::SYS_writev, vec![]), diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index a094803ee..710847fab 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -76,7 +76,7 @@ use vm_migration::{ use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::sock_ctrl_msg::ScmSocket; -use crate::config::{ValidationError, add_to_config}; +use crate::config::{MemoryRestoreMode, ValidationError, add_to_config}; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo}; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] use crate::coredump::{ @@ -1265,6 +1265,7 @@ impl Vm { snapshot: Option<&Snapshot>, source_url: Option<&str>, prefault: Option, + memory_restore_mode: Option, ) -> Result { trace_scoped!("Vm::new"); @@ -1300,8 +1301,10 @@ impl Vm { vm.clone(), &vm_config.lock().unwrap().memory.clone(), source_url, - prefault.unwrap(), + prefault.unwrap_or(false), + memory_restore_mode.unwrap_or_default(), phys_bits, + &exit_evt, ) .map_err(Error::MemoryManager)? } else {