mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
offload_daemon: Implement sparse snapshot/restore
Copy only populated extents when writing the snapshot file and when filling the restore memfd, leaving unwritten ranges as holes. Both the on-disk snapshot and the restored guest RAM stay sparse, so that untouched guest pages cost no disk space or host memory. This brings the offload daemon closer to be at feature parity with CH's internal implementation of snapshot/restore. The only missing piece is on-demand paging at this point. Signed-off-by: Sebastien Boeuf <sboeuf@meta.com> Assisted-by: Claude:claude-opus-4-7
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
|
||||
use std::ffi::{CString, NulError};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::fd::{AsRawFd, FromRawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -30,6 +30,7 @@ use vm_migration::MigratableError;
|
||||
use vm_migration::protocol::{Command, Request, Response, Status};
|
||||
use vmm::VmMigrationConfig;
|
||||
use vmm::migration::SNAPSHOT_STATE_FILE;
|
||||
use vmm::sparse::copy_region;
|
||||
use vmm_sys_util::errno;
|
||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||
|
||||
@@ -308,32 +309,15 @@ fn dump_memory_slots(
|
||||
}
|
||||
|
||||
fn dump_fd_to_path(file: &File, src_offset: u64, size: u64, path: &Path) -> Result<()> {
|
||||
let mut out = OpenOptions::new()
|
||||
let out = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(Error::WriteFile)?;
|
||||
out.set_len(size).map_err(Error::WriteFile)?;
|
||||
// dup so our own offset doesn't disturb CH's view of the shared fd.
|
||||
// SAFETY: dup() has no preconditions; result is checked.
|
||||
let raw = unsafe { libc::dup(file.as_raw_fd()) };
|
||||
if raw < 0 {
|
||||
return Err(Error::CopyMemory(io::Error::last_os_error()));
|
||||
}
|
||||
// SAFETY: `raw` is a fresh fd we now own.
|
||||
let mut src = unsafe { File::from_raw_fd(raw) };
|
||||
src.seek(SeekFrom::Start(src_offset))
|
||||
.map_err(Error::CopyMemory)?;
|
||||
let mut remaining = size;
|
||||
let mut buf = vec![0u8; 1 << 20];
|
||||
while remaining > 0 {
|
||||
let want = (remaining as usize).min(buf.len());
|
||||
src.read_exact(&mut buf[..want])
|
||||
.map_err(Error::CopyMemory)?;
|
||||
out.write_all(&buf[..want]).map_err(Error::CopyMemory)?;
|
||||
remaining -= want as u64;
|
||||
}
|
||||
// Keep `out` sparse: holes are left as holes (read back as zero).
|
||||
copy_region(file, src_offset, &out, 0, size).map_err(Error::CopyMemory)?;
|
||||
out.sync_all().map_err(Error::WriteFile)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -479,21 +463,10 @@ fn create_memfd_with_contents(
|
||||
name: &str,
|
||||
) -> Result<File> {
|
||||
// Size the memfd to cover the range CH maps at `file_offset`.
|
||||
let mut memfd = create_empty_memfd(file_offset + size, name)?;
|
||||
memfd
|
||||
.seek(SeekFrom::Start(file_offset))
|
||||
.map_err(Error::CopyMemory)?;
|
||||
let mut src = File::open(src_path).map_err(Error::ReadFile)?;
|
||||
let mut remaining = size;
|
||||
let mut buf = vec![0u8; 1 << 20];
|
||||
while remaining > 0 {
|
||||
let want = (remaining as usize).min(buf.len());
|
||||
src.read_exact(&mut buf[..want])
|
||||
.map_err(Error::CopyMemory)?;
|
||||
memfd.write_all(&buf[..want]).map_err(Error::CopyMemory)?;
|
||||
remaining -= want as u64;
|
||||
}
|
||||
memfd.seek(SeekFrom::Start(0)).map_err(Error::CopyMemory)?;
|
||||
let memfd = create_empty_memfd(file_offset + size, name)?;
|
||||
let src = File::open(src_path).map_err(Error::ReadFile)?;
|
||||
// Copy sparsely so the memfd keeps the snapshot's holes.
|
||||
copy_region(&src, 0, &memfd, file_offset, size).map_err(Error::CopyMemory)?;
|
||||
Ok(memfd)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user