1use serde::{Deserialize, Serialize};
4
5#[must_use]
14pub fn host_memory_size() -> u64 {
15 #[cfg(target_os = "macos")]
16 {
17 let mut size: u64 = 0;
18 let mut len = std::mem::size_of::<u64>();
19 let ret = unsafe {
22 libc::sysctlbyname(
23 c"hw.memsize".as_ptr(),
24 std::ptr::addr_of_mut!(size).cast(),
25 &mut len,
26 std::ptr::null_mut(),
27 0,
28 )
29 };
30 if ret == 0 { size } else { 0 }
31 }
32
33 #[cfg(target_os = "linux")]
34 {
35 let physical = unsafe {
39 let mut info: libc::sysinfo = std::mem::zeroed();
40 if libc::sysinfo(&mut info) == 0 {
41 info.totalram * u64::from(info.mem_unit)
42 } else {
43 return 0;
44 }
45 };
46
47 let cgroup = cgroup_memory_limit();
50 if cgroup > 0 && cgroup < physical {
51 cgroup
52 } else {
53 physical
54 }
55 }
56}
57
58#[cfg(target_os = "linux")]
65fn cgroup_memory_limit() -> u64 {
66 if let Some(v) = cgroup_v2_memory_limit() {
68 return v;
69 }
70 if let Some(v) = cgroup_v1_memory_limit() {
71 return v;
72 }
73 0
74}
75
76#[cfg(target_os = "linux")]
78fn cgroup_v2_memory_limit() -> Option<u64> {
79 let cgroup_path = std::fs::read_to_string("/proc/self/cgroup")
82 .ok()?
83 .lines()
84 .find(|l| l.starts_with("0::"))
85 .map(|l| l.strip_prefix("0::").unwrap_or("/").to_string())?;
86
87 let paths = [
89 format!("/sys/fs/cgroup{cgroup_path}/memory.max"),
90 "/sys/fs/cgroup/memory.max".to_string(),
91 ];
92 for path in &paths {
93 if let Ok(s) = std::fs::read_to_string(path) {
94 let s = s.trim();
95 if s != "max" {
96 if let Ok(v) = s.parse::<u64>() {
97 return Some(v);
98 }
99 }
100 }
101 }
102 None
103}
104
105#[cfg(target_os = "linux")]
107fn cgroup_v1_memory_limit() -> Option<u64> {
108 let cgroup_path = std::fs::read_to_string("/proc/self/cgroup")
111 .ok()?
112 .lines()
113 .find_map(|l| {
114 let parts: Vec<&str> = l.splitn(3, ':').collect();
115 if parts.len() == 3 && parts[1].split(',').any(|c| c == "memory") {
116 Some(parts[2].to_string())
117 } else {
118 None
119 }
120 })?;
121
122 let paths = [
123 format!("/sys/fs/cgroup/memory{cgroup_path}/memory.limit_in_bytes"),
124 "/sys/fs/cgroup/memory/memory.limit_in_bytes".to_string(),
125 ];
126 for path in &paths {
127 if let Ok(s) = std::fs::read_to_string(path) {
128 let s = s.trim();
129 if let Ok(v) = s.parse::<u64>() {
130 if v < (1 << 62) {
133 return Some(v);
134 }
135 }
136 }
137 }
138 None
139}
140
141#[must_use]
148pub fn default_vm_memory_size() -> u64 {
149 const MIN_DEFAULT: u64 = 512 * 1024 * 1024; const MAX_DEFAULT: u64 = 16 * 1024 * 1024 * 1024; const FALLBACK: u64 = 4 * 1024 * 1024 * 1024; const MIB: u64 = 1024 * 1024;
153
154 let host = host_memory_size();
155 if host == 0 {
156 return FALLBACK;
157 }
158
159 let size = (host / 2).clamp(MIN_DEFAULT, MAX_DEFAULT);
160 size & !(MIB - 1)
162}
163
164#[must_use]
168pub fn default_vm_cpu_count() -> u32 {
169 std::thread::available_parallelism().map_or(4, |n| u32::try_from(n.get()).unwrap_or(u32::MAX))
170}
171
172pub fn warn_memory_exceeds_host_half(memory_size: u64) {
177 let host_mem = host_memory_size();
178 if host_mem > 0 && memory_size > host_mem / 2 {
179 tracing::warn!(
180 "VM memory {}MB exceeds 50% of host RAM ({}MB total) — host may experience memory pressure",
181 memory_size / (1024 * 1024),
182 host_mem / (1024 * 1024),
183 );
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
189pub enum CpuArch {
190 X86_64,
192 Aarch64,
194}
195
196impl CpuArch {
197 #[must_use]
199 pub const fn native() -> Self {
200 #[cfg(target_arch = "x86_64")]
201 {
202 Self::X86_64
203 }
204 #[cfg(target_arch = "aarch64")]
205 {
206 Self::Aarch64
207 }
208 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
209 {
210 compile_error!("Unsupported CPU architecture")
211 }
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct PlatformCapabilities {
218 pub supported_archs: Vec<CpuArch>,
220 pub max_vcpus: u32,
222 pub max_memory: u64,
224 pub nested_virt: bool,
226 pub rosetta: bool,
228}
229
230impl Default for PlatformCapabilities {
231 fn default() -> Self {
232 Self {
233 supported_archs: vec![CpuArch::native()],
234 max_vcpus: 1,
235 max_memory: 1024 * 1024 * 1024, nested_virt: false,
237 rosetta: false,
238 }
239 }
240}
241
242#[derive(Debug, Clone, Default, Serialize, Deserialize)]
244pub struct Registers {
245 pub rax: u64,
247 pub rbx: u64,
248 pub rcx: u64,
249 pub rdx: u64,
250 pub rsi: u64,
251 pub rdi: u64,
252 pub rsp: u64,
253 pub rbp: u64,
254 pub r8: u64,
255 pub r9: u64,
256 pub r10: u64,
257 pub r11: u64,
258 pub r12: u64,
259 pub r13: u64,
260 pub r14: u64,
261 pub r15: u64,
262
263 pub rip: u64,
265 pub rflags: u64,
266}
267
268#[derive(Debug, Clone)]
270pub enum VcpuExit {
271 Halt,
273 IoOut {
275 port: u16,
276 size: u8,
277 data: u64,
278 },
279 IoIn {
280 port: u16,
281 size: u8,
282 },
283 MmioRead {
285 addr: u64,
286 size: u8,
287 },
288 MmioWrite {
289 addr: u64,
290 size: u8,
291 data: u64,
292 },
293 Hypercall {
295 nr: u64,
296 args: [u64; 6],
297 },
298 SystemReset,
300 Shutdown,
302 Debug,
304 Unknown(i32),
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct VirtioDeviceConfig {
311 pub device_type: VirtioDeviceType,
313 pub config: Vec<u8>,
315 pub path: Option<String>,
317 pub read_only: bool,
319 pub tag: Option<String>,
321 #[serde(skip)]
323 pub net_fd: Option<i32>,
324 pub mac_address: Option<String>,
326}
327
328impl VirtioDeviceConfig {
329 pub fn block(path: impl Into<String>, read_only: bool) -> Self {
331 Self {
332 device_type: VirtioDeviceType::Block,
333 config: Vec::new(),
334 path: Some(path.into()),
335 read_only,
336 tag: None,
337 net_fd: None,
338 mac_address: None,
339 }
340 }
341
342 #[must_use]
344 pub const fn network() -> Self {
345 Self {
346 device_type: VirtioDeviceType::Net,
347 config: Vec::new(),
348 path: None,
349 read_only: false,
350 tag: None,
351 net_fd: None,
352 mac_address: None,
353 }
354 }
355
356 pub fn network_with_mac(mac_address: impl Into<String>) -> Self {
358 Self {
359 device_type: VirtioDeviceType::Net,
360 config: Vec::new(),
361 path: None,
362 read_only: false,
363 tag: None,
364 net_fd: None,
365 mac_address: Some(mac_address.into()),
366 }
367 }
368
369 #[must_use]
374 pub const fn network_file_handle(fd: i32) -> Self {
375 Self {
376 device_type: VirtioDeviceType::Net,
377 config: Vec::new(),
378 path: None,
379 read_only: false,
380 tag: None,
381 net_fd: Some(fd),
382 mac_address: None,
383 }
384 }
385
386 pub fn network_file_handle_with_mac(fd: i32, mac_address: impl Into<String>) -> Self {
392 Self {
393 device_type: VirtioDeviceType::Net,
394 config: Vec::new(),
395 path: None,
396 read_only: false,
397 tag: None,
398 net_fd: Some(fd),
399 mac_address: Some(mac_address.into()),
400 }
401 }
402
403 #[must_use]
405 pub const fn console() -> Self {
406 Self {
407 device_type: VirtioDeviceType::Console,
408 config: Vec::new(),
409 path: None,
410 read_only: false,
411 tag: None,
412 net_fd: None,
413 mac_address: None,
414 }
415 }
416
417 pub fn filesystem(path: impl Into<String>, tag: impl Into<String>, read_only: bool) -> Self {
419 Self {
420 device_type: VirtioDeviceType::Fs,
421 config: Vec::new(),
422 path: Some(path.into()),
423 read_only,
424 tag: Some(tag.into()),
425 net_fd: None,
426 mac_address: None,
427 }
428 }
429
430 #[must_use]
432 pub const fn vsock() -> Self {
433 Self {
434 device_type: VirtioDeviceType::Vsock,
435 config: Vec::new(),
436 path: None,
437 read_only: false,
438 tag: None,
439 net_fd: None,
440 mac_address: None,
441 }
442 }
443
444 #[must_use]
446 pub const fn entropy() -> Self {
447 Self {
448 device_type: VirtioDeviceType::Rng,
449 config: Vec::new(),
450 path: None,
451 read_only: false,
452 tag: None,
453 net_fd: None,
454 mac_address: None,
455 }
456 }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
461pub enum VirtioDeviceType {
462 Block,
464 Net,
466 Console,
468 Fs,
470 Vsock,
472 Rng,
474 Balloon,
476 Gpu,
478}
479
480#[derive(Debug, Clone, Default, Serialize, Deserialize)]
482pub struct BalloonStats {
483 pub target_bytes: u64,
487
488 pub current_bytes: u64,
493
494 pub configured_bytes: u64,
499}
500
501impl BalloonStats {
502 #[must_use]
506 pub const fn effective_memory(&self) -> u64 {
507 self.configured_bytes.saturating_sub(self.current_bytes)
508 }
509
510 #[must_use]
512 pub fn target_percent(&self) -> f64 {
513 if self.configured_bytes == 0 {
514 return 100.0;
515 }
516 (self.target_bytes as f64 / self.configured_bytes as f64) * 100.0
517 }
518}
519
520impl VirtioDeviceConfig {
521 #[must_use]
526 pub const fn balloon() -> Self {
527 Self {
528 device_type: VirtioDeviceType::Balloon,
529 config: Vec::new(),
530 path: None,
531 read_only: false,
532 tag: None,
533 net_fd: None,
534 mac_address: None,
535 }
536 }
537}
538
539#[derive(Debug, Clone, Default, Serialize, Deserialize)]
541pub struct Arm64Registers {
542 pub x: [u64; 31],
544 pub sp: u64,
546 pub pc: u64,
548 pub pstate: u64,
550 pub fpcr: u64,
552 pub fpsr: u64,
554 pub v: [[u64; 2]; 32],
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
560pub struct VcpuSnapshot {
561 pub id: u32,
563 pub arch: CpuArch,
565 pub x86_regs: Option<Registers>,
567 pub arm64_regs: Option<Arm64Registers>,
569 pub extra_state: Vec<u8>,
571}
572
573impl VcpuSnapshot {
574 #[must_use]
576 pub const fn new_x86(id: u32, regs: Registers) -> Self {
577 Self {
578 id,
579 arch: CpuArch::X86_64,
580 x86_regs: Some(regs),
581 arm64_regs: None,
582 extra_state: Vec::new(),
583 }
584 }
585
586 #[must_use]
588 pub const fn new_arm64(id: u32, regs: Arm64Registers) -> Self {
589 Self {
590 id,
591 arch: CpuArch::Aarch64,
592 x86_regs: None,
593 arm64_regs: Some(regs),
594 extra_state: Vec::new(),
595 }
596 }
597
598 #[must_use]
602 pub fn is_placeholder(&self) -> bool {
603 match (&self.x86_regs, &self.arm64_regs) {
604 (Some(regs), _) => regs.rip == 0 && regs.rsp == 0 && regs.rflags == 0,
605 (_, Some(regs)) => regs.pc == 0 && regs.sp == 0 && regs.pstate == 0,
606 (None, None) => true,
607 }
608 }
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct DeviceSnapshot {
614 pub device_type: VirtioDeviceType,
616 pub name: String,
618 pub state: Vec<u8>,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
624pub struct MemoryRegionSnapshot {
625 pub guest_addr: u64,
627 pub size: u64,
629 pub read_only: bool,
631 pub file_offset: u64,
633}
634
635#[derive(Debug, Clone)]
637pub struct DirtyPageInfo {
638 pub guest_addr: u64,
640 pub size: u64,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct VmSnapshot {
647 pub version: u32,
649 pub arch: CpuArch,
651 pub vcpus: Vec<VcpuSnapshot>,
653 pub devices: Vec<DeviceSnapshot>,
655 pub memory_regions: Vec<MemoryRegionSnapshot>,
657 pub total_memory: u64,
659 pub compressed: bool,
661 pub compression: Option<String>,
663 pub parent_id: Option<String>,
665}