#![allow(missing_docs)]
#[allow(unused_imports)]
use crate::error::{Result, invalid_config};
#[allow(unused_imports)]
use std::process::Command;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HostArch {
Arm64,
X86_64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Preflight {
macos_version: String,
architecture: HostArch,
nested_virtualization_supported: bool,
rosetta_available: bool,
codesigned: Option<bool>,
has_virtualization_entitlement: Option<bool>,
}
impl Preflight {
#[must_use]
pub const fn new(
macos_version: String,
architecture: HostArch,
nested_virtualization_supported: bool,
rosetta_available: bool,
codesigned: Option<bool>,
has_virtualization_entitlement: Option<bool>,
) -> Self {
Self {
macos_version,
architecture,
nested_virtualization_supported,
rosetta_available,
codesigned,
has_virtualization_entitlement,
}
}
#[must_use]
pub fn macos_version(&self) -> &str {
&self.macos_version
}
#[must_use]
pub const fn architecture(&self) -> HostArch {
self.architecture
}
#[must_use]
pub const fn nested_virtualization_supported(&self) -> bool {
self.nested_virtualization_supported
}
#[must_use]
pub const fn rosetta_available(&self) -> bool {
self.rosetta_available
}
#[must_use]
pub const fn codesigned(&self) -> Option<bool> {
self.codesigned
}
#[must_use]
pub const fn has_virtualization_entitlement(&self) -> Option<bool> {
self.has_virtualization_entitlement
}
}
pub fn preflight() -> Result<Preflight> {
let architecture = host_arch()?;
let version = Command::new("/usr/bin/sw_vers")
.arg("-productVersion")
.output()
.ok()
.and_then(|output| {
output
.status
.success()
.then(|| String::from_utf8_lossy(&output.stdout).trim().to_owned())
})
.filter(|version| !version.is_empty())
.unwrap_or_else(|| "unknown".to_owned());
let signing = std::env::current_exe()
.ok()
.and_then(|path| signing::codesign_check(path).ok());
Ok(Preflight::new(
version,
architecture,
matches!(architecture, HostArch::Arm64),
matches!(architecture, HostArch::Arm64),
signing.as_ref().map(signing::CodeSignInfo::codesigned),
signing
.as_ref()
.map(signing::CodeSignInfo::has_virtualization_entitlement),
))
}
pub mod signing {
use crate::{Error, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CodeSignInfo {
path: PathBuf,
codesigned: bool,
has_virtualization_entitlement: bool,
}
impl CodeSignInfo {
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub const fn codesigned(&self) -> bool {
self.codesigned
}
#[must_use]
pub const fn has_virtualization_entitlement(&self) -> bool {
self.has_virtualization_entitlement
}
}
pub fn codesign_check(path: impl AsRef<Path>) -> Result<CodeSignInfo> {
let path = path.as_ref();
let display = Command::new("/usr/bin/codesign")
.args(["-dv", path.to_str().unwrap_or_default()])
.output()
.map_err(|source| Error::InvalidConfig {
reason: format!("codesign check failed for {}: {source}", path.display()),
})?;
let codesigned = display.status.success();
let entitlements = Command::new("/usr/bin/codesign")
.args([
"-d",
"--entitlements",
":-",
path.to_str().unwrap_or_default(),
])
.output()
.map_err(|source| Error::InvalidConfig {
reason: format!(
"codesign entitlement check failed for {}: {source}",
path.display()
),
})?;
let entitlement_text = String::from_utf8_lossy(&entitlements.stdout);
let has_virtualization_entitlement =
entitlement_text.contains("com.apple.security.virtualization");
Ok(CodeSignInfo {
path: path.to_path_buf(),
codesigned,
has_virtualization_entitlement,
})
}
}
fn host_arch() -> Result<HostArch> {
if cfg!(target_arch = "aarch64") {
Ok(HostArch::Arm64)
} else if cfg!(target_arch = "x86_64") {
Ok(HostArch::X86_64)
} else {
invalid_config("unsupported host architecture")
}
}