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_C
(
&mut self,
file_path: &Path,
saved_regions: &MemoryRangeTable,
exit_evt: &EventFd,
)
| 936 | /// is active. The file must remain available until the VM is shut down or |
| 937 | /// all faulted pages have been served. |
| 938 | fn restore_by_uffd( |
| 939 | &mut self, |
| 940 | file_path: &Path, |
| 941 | saved_regions: &MemoryRangeTable, |
| 942 | exit_evt: &EventFd, |
| 943 | ) -> Result<(), Error> { |
| 944 | if saved_regions.is_empty() { |
| 945 | return Ok(()); |
| 946 | } |
| 947 | |
| 948 | let guest_memory = self.guest_memory.memory(); |
| 949 | let required_uffd_features = self.required_uffd_features(); |
| 950 | |
| 951 | // SAFETY: FFI call. Trivially safe. |
| 952 | let base_page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64; |
| 953 | |
| 954 | info!( |
| 955 | "UFFD restore: attempting demand-paged restore for {} region(s)", |
| 956 | saved_regions.regions().len() |
| 957 | ); |
| 958 | |
| 959 | if saved_regions |
| 960 | .regions() |
| 961 | .iter() |
| 962 | .any(|range| range.gpa % base_page_size != 0 || range.length % base_page_size != 0) |
| 963 | { |
| 964 | return Err(UffdError::UnalignedRanges.into()); |
| 965 | } |
| 966 | |
| 967 | let snapshot_file = File::open(file_path).map_err(Error::SnapshotOpen)?; |
| 968 | |
| 969 | let uffd_fd = uffd::create(required_uffd_features).map_err(UffdError::Create)?; |
| 970 | |
| 971 | let mut handler_ranges: Vec<UffdRange> = Vec::new(); |
| 972 | let mut file_offset: u64 = 0; |
| 973 | |
| 974 | for range in saved_regions.regions() { |
| 975 | let host_addr = guest_memory |
| 976 | .get_host_address(GuestAddress(range.gpa)) |
| 977 | .map_err(|_| UffdError::GpaTranslation { gpa: range.gpa })? |
| 978 | as u64; |
| 979 | |
| 980 | let ioctls = uffd::register(uffd_fd.as_fd(), host_addr, range.length).map_err(|e| { |
| 981 | UffdError::Register { |
| 982 | addr: host_addr, |
| 983 | len: range.length, |
| 984 | source: e, |
| 985 | } |
| 986 | })?; |
| 987 | |
| 988 | if ioctls & crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC |
| 989 | != crate::userfaultfd::UFFD_API_RANGE_IOCTLS_BASIC |
| 990 | { |
| 991 | return Err(UffdError::MissingIoctlSupport { |
| 992 | addr: host_addr, |
| 993 | len: range.length, |
| 994 | } |
| 995 | .into()); |
no test coverage detected