axbuild 0.4.6

An OS build lib toolkit used by arceos
use anyhow::anyhow;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VirtualizationBackend {
    Vmx,
    Svm,
}

impl VirtualizationBackend {
    fn feature(self) -> &'static str {
        match self {
            Self::Vmx => "vmx",
            Self::Svm => "svm",
        }
    }
}

pub(super) fn normalize_backend_features(features: &mut Vec<String>) -> anyhow::Result<()> {
    normalize_backend_features_with(features, detect_host_backend)
}

fn normalize_backend_features_with(
    features: &mut Vec<String>,
    detect_backend: impl FnOnce() -> anyhow::Result<VirtualizationBackend>,
) -> anyhow::Result<()> {
    let has_vmx = features.iter().any(|feature| feature == "vmx");
    let has_svm = features.iter().any(|feature| feature == "svm");

    match (has_vmx, has_svm) {
        (true, true) => Err(anyhow!(
            "x86_64 Axvisor features `vmx` and `svm` are mutually exclusive"
        )),
        (true, false) | (false, true) => Ok(()),
        (false, false) => {
            let backend = detect_backend()?;
            println!(
                "Auto-selected x86_64 virtualization backend: {}",
                backend.feature()
            );
            features.push(backend.feature().to_string());
            Ok(())
        }
    }
}

fn detect_host_backend() -> anyhow::Result<VirtualizationBackend> {
    if let Ok(value) = std::env::var("AXVISOR_X86_BACKEND") {
        return parse_backend(&value);
    }

    detect_host_backend_from_cpuid()
}

fn parse_backend(value: &str) -> anyhow::Result<VirtualizationBackend> {
    match value.trim().to_ascii_lowercase().as_str() {
        "vmx" | "intel" => Ok(VirtualizationBackend::Vmx),
        "svm" | "amd" => Ok(VirtualizationBackend::Svm),
        other => Err(anyhow!(
            "invalid AXVISOR_X86_BACKEND value `{other}`; expected `vmx`/`intel` or `svm`/`amd`"
        )),
    }
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_host_backend_from_cpuid() -> anyhow::Result<VirtualizationBackend> {
    let cpuid = raw_cpuid::CpuId::new();
    let vendor = cpuid
        .get_vendor_info()
        .ok_or_else(|| anyhow!("failed to read x86 CPUID vendor information"))?;

    match vendor.as_str() {
        "GenuineIntel" => Ok(VirtualizationBackend::Vmx),
        "AuthenticAMD" => Ok(VirtualizationBackend::Svm),
        _ => Err(anyhow!(
            "unsupported x86 CPU vendor `{vendor}` for automatic Axvisor backend selection; set \
             AXVISOR_X86_BACKEND=vmx or AXVISOR_X86_BACKEND=svm to override"
        )),
    }
}

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_host_backend_from_cpuid() -> anyhow::Result<VirtualizationBackend> {
    Err(anyhow!(
        "cannot auto-select x86_64 Axvisor virtualization backend on non-x86 host; set \
         AXVISOR_X86_BACKEND=vmx or AXVISOR_X86_BACKEND=svm"
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn backend_auto_selects_vmx_when_missing() {
        let mut features = vec!["ept-level-4".to_string(), "fs".to_string()];

        normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap();

        assert!(features.contains(&"vmx".to_string()));
        assert!(!features.contains(&"svm".to_string()));
    }

    #[test]
    fn backend_auto_selects_svm_when_missing() {
        let mut features = vec!["ept-level-4".to_string(), "fs".to_string()];

        normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Svm)).unwrap();

        assert!(features.contains(&"svm".to_string()));
        assert!(!features.contains(&"vmx".to_string()));
    }

    #[test]
    fn backend_keeps_explicit_choice() {
        let mut features = vec!["ept-level-4".to_string(), "svm".to_string()];

        normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap();

        assert!(features.contains(&"svm".to_string()));
        assert!(!features.contains(&"vmx".to_string()));
    }

    #[test]
    fn backend_rejects_conflicting_features() {
        let mut features = vec!["vmx".to_string(), "svm".to_string()];

        let err = normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx))
            .unwrap_err();

        assert!(err.to_string().contains("mutually exclusive"));
    }

    #[test]
    fn parses_backend_override() {
        assert_eq!(parse_backend("intel").unwrap(), VirtualizationBackend::Vmx);
        assert_eq!(parse_backend("svm").unwrap(), VirtualizationBackend::Svm);
        assert!(parse_backend("unknown").is_err());
    }
}