Skip to main content

a3s_box_runtime/
host_check.rs

1//! Host virtualization support detection.
2//!
3//! Checks if the current host supports hardware virtualization:
4//! - macOS: Hypervisor.framework (Apple Silicon only)
5//! - Linux: KVM (/dev/kvm)
6//! - Windows: WHPX / Windows Hypervisor Platform
7
8use a3s_box_core::error::{BoxError, Result};
9
10/// Information about virtualization support.
11#[derive(Debug, Clone)]
12pub struct VirtualizationSupport {
13    /// Human-readable description of the virtualization backend.
14    pub backend: String,
15    /// Additional details about the support.
16    pub details: String,
17}
18
19/// Check if the current host supports hardware virtualization.
20///
21/// Returns `Ok(VirtualizationSupport)` if supported, or an error explaining why not.
22pub fn check_virtualization_support() -> Result<VirtualizationSupport> {
23    #[cfg(target_os = "macos")]
24    {
25        check_macos_hypervisor()
26    }
27
28    #[cfg(target_os = "linux")]
29    {
30        check_linux_kvm()
31    }
32
33    #[cfg(target_os = "windows")]
34    {
35        check_windows_whpx()
36    }
37
38    #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
39    {
40        Err(BoxError::ConfigError(
41            "Unsupported platform: A3S Box requires macOS (Apple Silicon), Linux with KVM, or Windows with WHPX"
42                .to_string(),
43        ))
44    }
45}
46
47/// Check for Hypervisor.framework support on macOS.
48#[cfg(target_os = "macos")]
49fn check_macos_hypervisor() -> Result<VirtualizationSupport> {
50    #[cfg(target_arch = "aarch64")]
51    {
52        // Query via sysctl kern.hv_support
53        let output = std::process::Command::new("sysctl")
54            .arg("kern.hv_support")
55            .output()
56            .map_err(|e| BoxError::ExecError(format!("Failed to run sysctl: {}", e)))?;
57
58        if !output.status.success() {
59            return Err(BoxError::ConfigError(
60                "Failed to query Hypervisor.framework support via sysctl".to_string(),
61            ));
62        }
63
64        let stdout = String::from_utf8_lossy(&output.stdout);
65        // Parse: "kern.hv_support: 1" (supported) or "0" (not supported)
66        let value = stdout.split(':').nth(1).map(|s| s.trim()).unwrap_or("0");
67
68        if value == "1" {
69            Ok(VirtualizationSupport {
70                backend: "Hypervisor.framework".to_string(),
71                details: "Apple Silicon hardware virtualization is available".to_string(),
72            })
73        } else {
74            Err(BoxError::ConfigError(
75                "Hypervisor.framework is not available on this system. \
76                 Ensure you are running on Apple Silicon and have the necessary entitlements."
77                    .to_string(),
78            ))
79        }
80    }
81
82    #[cfg(not(target_arch = "aarch64"))]
83    {
84        Err(BoxError::ConfigError(
85            "A3S Box on macOS requires Apple Silicon (ARM64). Intel Macs are not supported."
86                .to_string(),
87        ))
88    }
89}
90
91/// Check for KVM support on Linux.
92#[cfg(target_os = "linux")]
93fn check_linux_kvm() -> Result<VirtualizationSupport> {
94    use std::path::Path;
95
96    let kvm_path = Path::new("/dev/kvm");
97
98    if !kvm_path.exists() {
99        return Err(BoxError::ConfigError(
100            "KVM is not available: /dev/kvm not found. \
101             Ensure KVM kernel modules are loaded (modprobe kvm kvm_intel or kvm_amd)."
102                .to_string(),
103        ));
104    }
105
106    // Check if we have read/write access
107    match std::fs::OpenOptions::new()
108        .read(true)
109        .write(true)
110        .open(kvm_path)
111    {
112        Ok(_) => Ok(VirtualizationSupport {
113            backend: "KVM".to_string(),
114            details: "Linux KVM hardware virtualization is available".to_string(),
115        }),
116        Err(e) => {
117            if e.kind() == std::io::ErrorKind::PermissionDenied {
118                Err(BoxError::ConfigError(format!(
119                    "KVM access denied: {}. Add your user to the 'kvm' group: \
120                     sudo usermod -aG kvm $USER",
121                    e
122                )))
123            } else {
124                Err(BoxError::ConfigError(format!(
125                    "Failed to access /dev/kvm: {}",
126                    e
127                )))
128            }
129        }
130    }
131}
132
133/// Check for Windows Hypervisor Platform (WHPX) support on Windows.
134#[cfg(target_os = "windows")]
135fn check_windows_whpx() -> Result<VirtualizationSupport> {
136    #[cfg(not(target_arch = "x86_64"))]
137    {
138        return Err(BoxError::ConfigError(
139            "A3S Box on Windows currently requires x86_64 for the WHPX backend.".to_string(),
140        ));
141    }
142
143    let output = std::process::Command::new("powershell.exe")
144        .args([
145            "-NoProfile",
146            "-NonInteractive",
147            "-Command",
148            "(Get-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform).State",
149        ])
150        .output()
151        .map_err(|e| BoxError::ExecError(format!("Failed to query HypervisorPlatform: {}", e)))?;
152
153    if !output.status.success() {
154        return Err(BoxError::ConfigError(format!(
155            "Failed to query Windows Hypervisor Platform state (exit code {:?})",
156            output.status.code()
157        )));
158    }
159
160    let state = String::from_utf8_lossy(&output.stdout)
161        .trim()
162        .to_ascii_lowercase();
163    if state == "enabled" {
164        Ok(VirtualizationSupport {
165            backend: "WHPX".to_string(),
166            details: "Windows Hypervisor Platform is enabled".to_string(),
167        })
168    } else {
169        Err(BoxError::ConfigError(format!(
170            "Windows Hypervisor Platform is not enabled (current state: {}). Enable the 'Windows Hypervisor Platform' optional feature and reboot.",
171            if state.is_empty() { "unknown" } else { &state }
172        )))
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_check_virtualization_support() {
182        // This test will pass or fail depending on the host system
183        // It's mainly useful for manual testing
184        match check_virtualization_support() {
185            Ok(support) => {
186                println!("Virtualization supported:");
187                println!("  Backend: {}", support.backend);
188                println!("  Details: {}", support.details);
189            }
190            Err(e) => {
191                println!("Virtualization not supported: {}", e);
192            }
193        }
194    }
195}