Skip to main content

ryra_vm/
lib.rs

1mod assert;
2pub mod image;
3pub mod machine;
4pub mod ports;
5
6use std::process::Stdio;
7
8use anyhow::Result;
9
10/// Host memory snapshot in MB.
11pub struct HostMemory {
12    pub total_mb: u64,
13    pub available_mb: u64,
14    pub swap_total_mb: u64,
15    pub swap_used_mb: u64,
16}
17
18/// Read current memory usage.
19pub fn read_host_memory() -> Option<HostMemory> {
20    let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
21    let parse_kb = |key: &str| -> Option<u64> {
22        meminfo
23            .lines()
24            .find(|l| l.starts_with(key))
25            .and_then(|l| l.split_whitespace().nth(1))
26            .and_then(|v| v.parse::<u64>().ok())
27    };
28    let total_kb = parse_kb("MemTotal:")?;
29    let avail_kb = parse_kb("MemAvailable:")?;
30    let swap_total_kb = parse_kb("SwapTotal:").unwrap_or(0);
31    let swap_free_kb = parse_kb("SwapFree:").unwrap_or(0);
32    Some(HostMemory {
33        total_mb: total_kb / 1024,
34        available_mb: avail_kb / 1024,
35        swap_total_mb: swap_total_kb / 1024,
36        swap_used_mb: swap_total_kb.saturating_sub(swap_free_kb) / 1024,
37    })
38}
39
40/// Check that required tools are installed.
41pub fn check_prerequisites(use_kvm: bool) -> Result<()> {
42    let required = [
43        "qemu-system-aarch64",
44        "qemu-img",
45        "ssh",
46        "scp",
47        "ssh-keygen",
48        "curl",
49    ];
50    let mut missing: Vec<&str> = required
51        .iter()
52        .filter(|c| !has_command(c))
53        .copied()
54        .collect();
55
56    if !has_any_iso_tool() {
57        missing.push("genisoimage");
58    }
59
60    if !missing.is_empty() {
61        anyhow::bail!(
62            "missing required tools: {}\n\
63             Install with:\n  \
64             sudo apt install qemu-system-arm qemu-utils qemu-efi-aarch64 \\\n    \
65             genisoimage openssh-client curl                    # Debian/Ubuntu\n  \
66             sudo dnf install qemu-system-aarch64 qemu-img edk2-aarch64 \\\n    \
67             genisoimage openssh-clients curl                   # Fedora\n  \
68             sudo pacman -S qemu-system-aarch64 qemu-img edk2-aarch64 \\\n    \
69             cdrtools openssh curl                              # Arch",
70            missing.join(", ")
71        );
72    }
73
74    if use_kvm {
75        check_kvm()?;
76    }
77
78    Ok(())
79}
80
81fn has_command(cmd: &str) -> bool {
82    std::process::Command::new("which")
83        .arg(cmd)
84        .stdout(Stdio::null())
85        .stderr(Stdio::null())
86        .status()
87        .map(|s| s.success())
88        .unwrap_or(false)
89}
90
91fn has_any_iso_tool() -> bool {
92    ["genisoimage", "mkisofs"]
93        .iter()
94        .any(|cmd| has_command(cmd))
95}
96
97/// Check for KVM acceleration support.
98fn check_kvm() -> Result<()> {
99    let kvm = std::path::Path::new("/dev/kvm");
100    if !kvm.exists() {
101        anyhow::bail!(
102            "/dev/kvm not found — KVM is not available on this machine.\n\
103             Run with --no-kvm to use software emulation (slower), or \
104             run on a machine with KVM support."
105        );
106    }
107    let accessible = std::fs::File::open(kvm).is_ok();
108    if !accessible {
109        anyhow::bail!(
110            "/dev/kvm exists but is not accessible — permission denied.\n\
111             Add your user to the kvm group and re-login:\n  \
112             sudo usermod -aG kvm $USER\n  \
113             # then log out and back in, or run: newgrp kvm"
114        );
115    }
116    Ok(())
117}
118
119/// Return QEMU KVM acceleration arguments.
120pub fn accel_args() -> &'static [&'static str] {
121    &["-enable-kvm"]
122}