use std::path::{Path, PathBuf};
use a3s_box_core::error::{BoxError, Result};
#[cfg(feature = "embed-shim")]
static SHIM_BINARY: &[u8] = include_bytes!(env!("A3S_SHIM_BINARY_PATH"));
#[cfg(not(feature = "embed-shim"))]
static SHIM_BINARY: &[u8] = &[];
const SHIM_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn ensure_shim(home_dir: &Path) -> Result<Option<PathBuf>> {
if SHIM_BINARY.is_empty() {
tracing::debug!("No embedded shim binary, deferring to find_shim()");
return Ok(None);
}
let bin_dir = home_dir.join("bin");
std::fs::create_dir_all(&bin_dir).map_err(|e| {
BoxError::ConfigError(format!(
"Failed to create bin directory {}: {}",
bin_dir.display(),
e
))
})?;
let shim_path = bin_dir.join("a3s-box-shim");
let version_path = bin_dir.join("a3s-box-shim.version");
if shim_path.exists() {
if let Ok(existing_version) = std::fs::read_to_string(&version_path) {
if existing_version.trim() == SHIM_VERSION {
tracing::debug!(
path = %shim_path.display(),
version = SHIM_VERSION,
"Shim binary already up-to-date"
);
return Ok(Some(shim_path));
}
}
}
tracing::info!(
path = %shim_path.display(),
version = SHIM_VERSION,
size_bytes = SHIM_BINARY.len(),
"Extracting embedded shim binary"
);
std::fs::write(&shim_path, SHIM_BINARY).map_err(|e| {
BoxError::ConfigError(format!(
"Failed to write shim binary to {}: {}",
shim_path.display(),
e
))
})?;
set_executable(&shim_path)?;
std::fs::write(&version_path, SHIM_VERSION).map_err(|e| {
BoxError::ConfigError(format!(
"Failed to write shim version file {}: {}",
version_path.display(),
e
))
})?;
#[cfg(target_os = "macos")]
sign_with_entitlement(&shim_path, home_dir)?;
Ok(Some(shim_path))
}
#[cfg(unix)]
fn set_executable(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)
.map_err(|e| {
BoxError::ConfigError(format!(
"Failed to read permissions for {}: {}",
path.display(),
e
))
})?
.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms).map_err(|e| {
BoxError::ConfigError(format!(
"Failed to set executable permission on {}: {}",
path.display(),
e
))
})
}
#[cfg(not(unix))]
fn set_executable(_path: &Path) -> Result<()> {
Ok(())
}
#[cfg(target_os = "macos")]
fn sign_with_entitlement(shim_path: &Path, home_dir: &Path) -> Result<()> {
let entitlements_path = home_dir.join("bin").join("entitlements.plist");
if !entitlements_path.exists() {
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>"#;
std::fs::write(&entitlements_path, plist).map_err(|e| {
BoxError::ConfigError(format!("Failed to write entitlements plist: {}", e))
})?;
}
let check = std::process::Command::new("codesign")
.args(["-d", "--entitlements", ":-"])
.arg(shim_path)
.output();
if let Ok(output) = check {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("com.apple.security.hypervisor") {
tracing::debug!("Shim already signed with hypervisor entitlement");
return Ok(());
}
}
}
tracing::info!(path = %shim_path.display(), "Signing shim with hypervisor entitlement");
let status = std::process::Command::new("codesign")
.args(["--sign", "-", "--entitlements"])
.arg(&entitlements_path)
.arg("--force")
.arg(shim_path)
.status()
.map_err(|e| BoxError::ExecError(format!("Failed to run codesign: {}", e)))?;
if !status.success() {
tracing::warn!(
path = %shim_path.display(),
"codesign failed (non-fatal) — VM boot may fail without hypervisor entitlement"
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shim_version_is_set() {
assert!(!SHIM_VERSION.is_empty());
}
#[test]
fn test_ensure_shim_no_embed() {
if SHIM_BINARY.is_empty() {
let tmp = tempfile::TempDir::new().unwrap();
let result = ensure_shim(tmp.path()).unwrap();
assert!(result.is_none());
}
}
#[test]
fn test_ensure_shim_extracts_binary() {
if SHIM_BINARY.is_empty() {
return;
}
let tmp = tempfile::TempDir::new().unwrap();
let result = ensure_shim(tmp.path()).unwrap();
assert!(result.is_some());
let shim_path = result.unwrap();
assert!(shim_path.exists());
let extracted_len = std::fs::read(&shim_path).unwrap().len();
assert!(extracted_len > 0);
let version_path = tmp.path().join("bin").join("a3s-box-shim.version");
assert_eq!(
std::fs::read_to_string(version_path).unwrap().trim(),
SHIM_VERSION
);
}
#[test]
fn test_ensure_shim_idempotent() {
if SHIM_BINARY.is_empty() {
return;
}
let tmp = tempfile::TempDir::new().unwrap();
let path1 = ensure_shim(tmp.path()).unwrap().unwrap();
let path2 = ensure_shim(tmp.path()).unwrap().unwrap();
assert_eq!(path1, path2);
}
#[cfg(unix)]
#[test]
fn test_set_executable() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::TempDir::new().unwrap();
let file = tmp.path().join("test-bin");
std::fs::write(&file, b"test").unwrap();
set_executable(&file).unwrap();
let mode = std::fs::metadata(&file).unwrap().permissions().mode();
assert_eq!(mode & 0o755, 0o755);
}
#[test]
fn test_ensure_shim_creates_bin_dir() {
let tmp = tempfile::TempDir::new().unwrap();
let bin_dir = tmp.path().join("bin");
assert!(!bin_dir.exists());
let _ = ensure_shim(tmp.path());
if !SHIM_BINARY.is_empty() {
assert!(bin_dir.exists());
}
}
}