use serde::{Deserialize, Serialize};
use super::common::{SafePath, UdsPath};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
pub enum SnapshotType {
#[default]
Full,
Diff,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawSnapshotCreateConfig {
pub snapshot_path: String,
pub mem_file_path: String,
#[serde(default)]
pub snapshot_type: SnapshotType,
#[serde(default)]
pub version: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct SnapshotCreateConfig {
pub snapshot_path: SafePath,
pub mem_file_path: SafePath,
pub snapshot_type: SnapshotType,
pub version: Option<String>,
}
impl TryFrom<RawSnapshotCreateConfig> for SnapshotCreateConfig {
type Error = String;
fn try_from(raw: RawSnapshotCreateConfig) -> Result<Self, Self::Error> {
let snapshot_path =
SafePath::new(raw.snapshot_path).map_err(|e| format!("Invalid snapshot_path: {e}"))?;
let mem_file_path =
SafePath::new(raw.mem_file_path).map_err(|e| format!("Invalid mem_file_path: {e}"))?;
if let Some(v) = raw.version.as_deref()
&& (v.is_empty() || v.len() > 32)
{
return Err("Invalid version: must be 1..=32 bytes".into());
}
Ok(Self {
snapshot_path,
mem_file_path,
snapshot_type: raw.snapshot_type,
version: raw.version,
})
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
pub enum MemBackendType {
#[default]
File,
Uffd,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawMemBackend {
pub backend_type: MemBackendType,
pub backend_path: String,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct MemBackend {
pub backend_type: MemBackendType,
pub backend_path_file: Option<SafePath>,
pub backend_path_uds: Option<UdsPath>,
}
impl TryFrom<RawMemBackend> for MemBackend {
type Error = String;
fn try_from(raw: RawMemBackend) -> Result<Self, Self::Error> {
match raw.backend_type {
MemBackendType::File => {
let p = SafePath::new(raw.backend_path)
.map_err(|e| format!("Invalid mem_backend.backend_path: {e}"))?;
Ok(Self {
backend_type: raw.backend_type,
backend_path_file: Some(p),
backend_path_uds: None,
})
}
MemBackendType::Uffd => {
let p = UdsPath::new(raw.backend_path)
.map_err(|e| format!("Invalid mem_backend.backend_path: {e}"))?;
Ok(Self {
backend_type: raw.backend_type,
backend_path_file: None,
backend_path_uds: Some(p),
})
}
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawSnapshotLoadConfig {
pub snapshot_path: String,
#[serde(default)]
pub mem_backend: Option<RawMemBackend>,
#[serde(default)]
pub mem_file_path: Option<String>,
#[serde(default)]
pub track_dirty_pages: bool,
#[serde(default)]
pub resume_vm: bool,
#[serde(default)]
pub clock_realtime: Option<u64>,
#[serde(default)]
pub network_overrides: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub vsock_override: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct SnapshotLoadConfig {
pub snapshot_path: SafePath,
pub mem_backend: MemBackend,
pub track_dirty_pages: bool,
pub resume_vm: bool,
pub clock_realtime: Option<u64>,
pub network_overrides: Vec<serde_json::Value>,
pub vsock_override: Option<serde_json::Value>,
}
impl TryFrom<RawSnapshotLoadConfig> for SnapshotLoadConfig {
type Error = String;
fn try_from(raw: RawSnapshotLoadConfig) -> Result<Self, Self::Error> {
let snapshot_path =
SafePath::new(raw.snapshot_path).map_err(|e| format!("Invalid snapshot_path: {e}"))?;
let mem_backend = match (raw.mem_backend, raw.mem_file_path) {
(Some(mb), None) => MemBackend::try_from(mb)?,
(None, Some(p)) => {
let p = SafePath::new(p).map_err(|e| format!("Invalid mem_file_path: {e}"))?;
MemBackend {
backend_type: MemBackendType::File,
backend_path_file: Some(p),
backend_path_uds: None,
}
}
(Some(_), Some(_)) => {
return Err(
"Invalid snapshot/load: provide exactly one of mem_backend or mem_file_path"
.into(),
);
}
(None, None) => {
return Err(
"Invalid snapshot/load: must provide mem_backend or mem_file_path".into(),
);
}
};
Ok(Self {
snapshot_path,
mem_backend,
track_dirty_pages: raw.track_dirty_pages,
resume_vm: raw.resume_vm,
clock_realtime: raw.clock_realtime,
network_overrides: raw.network_overrides.unwrap_or_default(),
vsock_override: raw.vsock_override,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_validate_snapshot_create_paths() {
let cfg = SnapshotCreateConfig::try_from(RawSnapshotCreateConfig {
snapshot_path: "/tmp/x.snap".into(),
mem_file_path: "/tmp/x.mem".into(),
snapshot_type: SnapshotType::Full,
version: None,
})
.unwrap();
assert_eq!(cfg.snapshot_path.as_path().as_os_str(), "/tmp/x.snap");
}
#[test]
fn test_should_default_snapshot_type_to_full() {
let json = r#"{"snapshot_path":"/tmp/x.snap","mem_file_path":"/tmp/x.mem"}"#;
let raw: RawSnapshotCreateConfig = serde_json::from_str(json).unwrap();
assert_eq!(raw.snapshot_type, SnapshotType::Full);
}
#[test]
fn test_should_accept_back_compat_mem_file_path() {
let cfg = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
snapshot_path: "/tmp/x.snap".into(),
mem_backend: None,
mem_file_path: Some("/tmp/x.mem".into()),
track_dirty_pages: false,
resume_vm: false,
clock_realtime: None,
network_overrides: None,
vsock_override: None,
})
.unwrap();
assert_eq!(cfg.mem_backend.backend_type, MemBackendType::File);
}
#[test]
fn test_should_reject_both_mem_backend_and_mem_file_path() {
let mb = RawMemBackend {
backend_type: MemBackendType::File,
backend_path: "/tmp/x.mem".into(),
};
let res = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
snapshot_path: "/tmp/x.snap".into(),
mem_backend: Some(mb),
mem_file_path: Some("/tmp/y.mem".into()),
track_dirty_pages: false,
resume_vm: false,
clock_realtime: None,
network_overrides: None,
vsock_override: None,
});
assert!(res.is_err());
}
#[test]
fn test_should_validate_uffd_backend_uds_path() {
let mb = RawMemBackend {
backend_type: MemBackendType::Uffd,
backend_path: "/tmp/pager.sock".into(),
};
let cfg = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
snapshot_path: "/tmp/x.snap".into(),
mem_backend: Some(mb),
mem_file_path: None,
track_dirty_pages: true,
resume_vm: true,
clock_realtime: None,
network_overrides: None,
vsock_override: None,
})
.unwrap();
assert_eq!(cfg.mem_backend.backend_type, MemBackendType::Uffd);
assert!(cfg.mem_backend.backend_path_uds.is_some());
}
}