1use serde::{Deserialize, Serialize};
4
5use super::common::{SafePath, UdsPath};
6
7#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
9pub enum SnapshotType {
10 #[default]
12 Full,
13 Diff,
15}
16
17#[derive(Debug, Clone, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct RawSnapshotCreateConfig {
21 pub snapshot_path: String,
23 pub mem_file_path: String,
25 #[serde(default)]
27 pub snapshot_type: SnapshotType,
28 #[serde(default)]
30 pub version: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize)]
35#[non_exhaustive]
36pub struct SnapshotCreateConfig {
37 pub snapshot_path: SafePath,
39 pub mem_file_path: SafePath,
41 pub snapshot_type: SnapshotType,
43 pub version: Option<String>,
45}
46
47impl TryFrom<RawSnapshotCreateConfig> for SnapshotCreateConfig {
48 type Error = String;
49
50 fn try_from(raw: RawSnapshotCreateConfig) -> Result<Self, Self::Error> {
51 let snapshot_path =
52 SafePath::new(raw.snapshot_path).map_err(|e| format!("Invalid snapshot_path: {e}"))?;
53 let mem_file_path =
54 SafePath::new(raw.mem_file_path).map_err(|e| format!("Invalid mem_file_path: {e}"))?;
55 if let Some(v) = raw.version.as_deref()
56 && (v.is_empty() || v.len() > 32)
57 {
58 return Err("Invalid version: must be 1..=32 bytes".into());
59 }
60 Ok(Self {
61 snapshot_path,
62 mem_file_path,
63 snapshot_type: raw.snapshot_type,
64 version: raw.version,
65 })
66 }
67}
68
69#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
71pub enum MemBackendType {
72 #[default]
74 File,
75 Uffd,
78}
79
80#[derive(Debug, Clone, Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct RawMemBackend {
84 pub backend_type: MemBackendType,
86 pub backend_path: String,
88}
89
90#[derive(Debug, Clone, Serialize)]
92#[non_exhaustive]
93pub struct MemBackend {
94 pub backend_type: MemBackendType,
96 pub backend_path_file: Option<SafePath>,
99 pub backend_path_uds: Option<UdsPath>,
101}
102
103impl TryFrom<RawMemBackend> for MemBackend {
104 type Error = String;
105
106 fn try_from(raw: RawMemBackend) -> Result<Self, Self::Error> {
107 match raw.backend_type {
108 MemBackendType::File => {
109 let p = SafePath::new(raw.backend_path)
110 .map_err(|e| format!("Invalid mem_backend.backend_path: {e}"))?;
111 Ok(Self {
112 backend_type: raw.backend_type,
113 backend_path_file: Some(p),
114 backend_path_uds: None,
115 })
116 }
117 MemBackendType::Uffd => {
118 let p = UdsPath::new(raw.backend_path)
119 .map_err(|e| format!("Invalid mem_backend.backend_path: {e}"))?;
120 Ok(Self {
121 backend_type: raw.backend_type,
122 backend_path_file: None,
123 backend_path_uds: Some(p),
124 })
125 }
126 }
127 }
128}
129
130#[derive(Debug, Clone, Deserialize)]
132#[serde(deny_unknown_fields)]
133pub struct RawSnapshotLoadConfig {
134 pub snapshot_path: String,
136 #[serde(default)]
138 pub mem_backend: Option<RawMemBackend>,
139 #[serde(default)]
142 pub mem_file_path: Option<String>,
143 #[serde(default)]
145 pub track_dirty_pages: bool,
146 #[serde(default)]
148 pub resume_vm: bool,
149 #[serde(default)]
151 pub clock_realtime: Option<u64>,
152 #[serde(default)]
154 pub network_overrides: Option<Vec<serde_json::Value>>,
155 #[serde(default)]
157 pub vsock_override: Option<serde_json::Value>,
158}
159
160#[derive(Debug, Clone, Serialize)]
162#[non_exhaustive]
163pub struct SnapshotLoadConfig {
164 pub snapshot_path: SafePath,
166 pub mem_backend: MemBackend,
168 pub track_dirty_pages: bool,
170 pub resume_vm: bool,
172 pub clock_realtime: Option<u64>,
174 pub network_overrides: Vec<serde_json::Value>,
176 pub vsock_override: Option<serde_json::Value>,
178}
179
180impl TryFrom<RawSnapshotLoadConfig> for SnapshotLoadConfig {
181 type Error = String;
182
183 fn try_from(raw: RawSnapshotLoadConfig) -> Result<Self, Self::Error> {
184 let snapshot_path =
185 SafePath::new(raw.snapshot_path).map_err(|e| format!("Invalid snapshot_path: {e}"))?;
186 let mem_backend = match (raw.mem_backend, raw.mem_file_path) {
187 (Some(mb), None) => MemBackend::try_from(mb)?,
188 (None, Some(p)) => {
189 let p = SafePath::new(p).map_err(|e| format!("Invalid mem_file_path: {e}"))?;
191 MemBackend {
192 backend_type: MemBackendType::File,
193 backend_path_file: Some(p),
194 backend_path_uds: None,
195 }
196 }
197 (Some(_), Some(_)) => {
198 return Err(
199 "Invalid snapshot/load: provide exactly one of mem_backend or mem_file_path"
200 .into(),
201 );
202 }
203 (None, None) => {
204 return Err(
205 "Invalid snapshot/load: must provide mem_backend or mem_file_path".into(),
206 );
207 }
208 };
209 Ok(Self {
210 snapshot_path,
211 mem_backend,
212 track_dirty_pages: raw.track_dirty_pages,
213 resume_vm: raw.resume_vm,
214 clock_realtime: raw.clock_realtime,
215 network_overrides: raw.network_overrides.unwrap_or_default(),
216 vsock_override: raw.vsock_override,
217 })
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_should_validate_snapshot_create_paths() {
227 let cfg = SnapshotCreateConfig::try_from(RawSnapshotCreateConfig {
228 snapshot_path: "/tmp/x.snap".into(),
229 mem_file_path: "/tmp/x.mem".into(),
230 snapshot_type: SnapshotType::Full,
231 version: None,
232 })
233 .unwrap();
234 assert_eq!(cfg.snapshot_path.as_path().as_os_str(), "/tmp/x.snap");
235 }
236
237 #[test]
238 fn test_should_default_snapshot_type_to_full() {
239 let json = r#"{"snapshot_path":"/tmp/x.snap","mem_file_path":"/tmp/x.mem"}"#;
240 let raw: RawSnapshotCreateConfig = serde_json::from_str(json).unwrap();
241 assert_eq!(raw.snapshot_type, SnapshotType::Full);
242 }
243
244 #[test]
245 fn test_should_accept_back_compat_mem_file_path() {
246 let cfg = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
247 snapshot_path: "/tmp/x.snap".into(),
248 mem_backend: None,
249 mem_file_path: Some("/tmp/x.mem".into()),
250 track_dirty_pages: false,
251 resume_vm: false,
252 clock_realtime: None,
253 network_overrides: None,
254 vsock_override: None,
255 })
256 .unwrap();
257 assert_eq!(cfg.mem_backend.backend_type, MemBackendType::File);
258 }
259
260 #[test]
261 fn test_should_reject_both_mem_backend_and_mem_file_path() {
262 let mb = RawMemBackend {
263 backend_type: MemBackendType::File,
264 backend_path: "/tmp/x.mem".into(),
265 };
266 let res = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
267 snapshot_path: "/tmp/x.snap".into(),
268 mem_backend: Some(mb),
269 mem_file_path: Some("/tmp/y.mem".into()),
270 track_dirty_pages: false,
271 resume_vm: false,
272 clock_realtime: None,
273 network_overrides: None,
274 vsock_override: None,
275 });
276 assert!(res.is_err());
277 }
278
279 #[test]
280 fn test_should_validate_uffd_backend_uds_path() {
281 let mb = RawMemBackend {
282 backend_type: MemBackendType::Uffd,
283 backend_path: "/tmp/pager.sock".into(),
284 };
285 let cfg = SnapshotLoadConfig::try_from(RawSnapshotLoadConfig {
286 snapshot_path: "/tmp/x.snap".into(),
287 mem_backend: Some(mb),
288 mem_file_path: None,
289 track_dirty_pages: true,
290 resume_vm: true,
291 clock_realtime: None,
292 network_overrides: None,
293 vsock_override: None,
294 })
295 .unwrap();
296 assert_eq!(cfg.mem_backend.backend_type, MemBackendType::Uffd);
297 assert!(cfg.mem_backend.backend_path_uds.is_some());
298 }
299}