Skip to main content

ryra_vm/
lib.rs

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