use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::OnceLock;
use std::time::Duration;
pub fn verify_worker_version(worker_path: &Path) -> Result<(), String> {
static VERSION_CHECKED: OnceLock<()> = OnceLock::new();
if VERSION_CHECKED.get().is_some() {
return Ok(());
}
let lib_version = env!("CARGO_PKG_VERSION");
let output = match Command::new(worker_path)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(c) => c,
Err(e) => {
return Err(format!(
"verify_worker_version: spawn {} --version: {e}",
worker_path.display()
));
}
};
let pid = output.id();
let stdout_handle = std::thread::spawn(move || -> Result<(std::process::Output, ()), String> {
let out = output
.wait_with_output()
.map_err(|e| format!("wait_with_output: {e}"))?;
Ok((out, ()))
});
let deadline = std::time::Instant::now() + Duration::from_secs(5);
let out = loop {
if stdout_handle.is_finished() {
break stdout_handle
.join()
.map_err(|_| "version-probe thread panicked".to_string())?
.map(|(o, _)| o)?;
}
if std::time::Instant::now() > deadline {
unsafe {
libc::kill(pid as i32, libc::SIGKILL);
}
return Err(format!(
"supermachine: worker `{}` did not respond to --version within 5s. \
This is almost certainly a stale supermachine-worker from an older \
release (the --version flag was added in 0.4.18). Run `cargo install \
supermachine --force` to update, or set SUPERMACHINE_WORKER_BIN to a \
freshly-built worker matching library version {lib_version}.",
worker_path.display()
));
}
std::thread::sleep(Duration::from_millis(20));
};
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
return Err(format!(
"supermachine: worker `{}` is from an older supermachine release \
(does not recognize --version, added in 0.4.18). Library is \
v{lib_version}; the supervisor protocol changed between releases \
(0.4.6 added BAKE_READY/SNAPSHOT_ASYNC) and a stale worker \
deadlocks pipelined-bake silently. \
Fix: `cargo install supermachine --force` to update the worker, \
or set SUPERMACHINE_WORKER_BIN to a freshly-built one. \
(worker stderr: {})",
worker_path.display(),
stderr.trim(),
));
}
let stdout = String::from_utf8_lossy(&out.stdout);
let line = stdout.lines().next().unwrap_or("").trim();
let worker_version = line
.strip_prefix("supermachine-worker ")
.ok_or_else(|| {
format!(
"supermachine: worker `{}` --version output unparseable: {line:?}. \
Expected `supermachine-worker <semver>`. Suggests a non-supermachine \
binary at this path or a build with a different output format.",
worker_path.display()
)
})?;
if worker_version != lib_version {
return Err(format!(
"supermachine: worker/library version mismatch. \
Worker `{}` reports v{worker_version}; library is v{lib_version}. \
Snapshot format and supervisor protocol are tied to crate version — \
pin both to the same `=`-version. \
Fix: `cargo install supermachine --force` to update the worker, \
or set SUPERMACHINE_WORKER_BIN to a worker matching v{lib_version}.",
worker_path.display()
));
}
VERSION_CHECKED.set(()).ok();
Ok(())
}
pub fn ensure_worker_signed(worker_path: &Path) -> Result<(), String> {
static IN_PROCESS_DONE: OnceLock<()> = OnceLock::new();
if IN_PROCESS_DONE.get().is_some() {
return Ok(());
}
let stat_marker = |path: &Path| -> Option<String> {
let meta = std::fs::metadata(path).ok()?;
let mtime = meta
.modified()
.ok()?
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_secs();
Some(format!(
"size={}\nmtime={}\npath={}\n",
meta.len(),
mtime,
path.display()
))
};
let current_marker = stat_marker(worker_path).ok_or_else(|| {
format!("stat {}: file disappeared", worker_path.display())
})?;
if let Some(sentinel) = sentinel_path() {
if let Ok(existing) = std::fs::read_to_string(&sentinel) {
if existing == current_marker {
IN_PROCESS_DONE.set(()).ok();
return Ok(());
}
}
}
cleanup_stuck_cstemp(worker_path);
let plist = std::env::temp_dir().join(format!(
"supermachine-entitlements-{}.plist",
std::process::id()
));
std::fs::write(&plist, crate::assets::ENTITLEMENTS_PLIST)
.map_err(|e| format!("write entitlements plist: {e}"))?;
let output = Command::new("codesign")
.args(["-s", "-", "--entitlements"])
.arg(&plist)
.arg("--force")
.arg(worker_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output();
let _ = std::fs::remove_file(&plist);
match output {
Ok(o) if o.status.success() => {
if let (Some(sentinel), Some(post_marker)) =
(sentinel_path(), stat_marker(worker_path))
{
if let Some(parent) = sentinel.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&sentinel, post_marker);
}
IN_PROCESS_DONE.set(()).ok();
Ok(())
}
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
let stderr_trim = stderr.trim();
let extra = if stderr_trim.contains("internal error in Code Signing") {
format!(
"\n\nThis is the orphan-`.cstemp` recovery loop: a prior \
codesign attempt left `{cs}` behind with chflags uchg \
set on it, and macOS codesign can't overwrite it. \
Manual fix:\n\
\n\
\x20\x20chflags -R nouchg {dir}\n\
\x20\x20rm -f {cs}\n\
\n\
Then retry your bake.",
cs = cstemp_for(worker_path).display(),
dir = worker_path
.parent()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<worker dir>".to_owned()),
)
} else {
String::new()
};
Err(format!(
"codesign exited with {:?} for {}: {stderr_trim}{extra}",
o.status.code(),
worker_path.display()
))
}
Err(e) => Err(format!(
"failed to spawn codesign for {}: {e}\n\
(codesign ships with macOS by default; if missing, \
reinstall Xcode Command Line Tools)",
worker_path.display()
)),
}
}
fn cstemp_for(worker_path: &Path) -> PathBuf {
let mut s = worker_path.as_os_str().to_owned();
s.push(".cstemp");
PathBuf::from(s)
}
fn cleanup_stuck_cstemp(worker_path: &Path) {
let cstemp = cstemp_for(worker_path);
if !cstemp.exists() {
return;
}
let _ = Command::new("chflags")
.args(["-R", "nouchg"])
.arg(&cstemp)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
let _ = std::fs::remove_file(&cstemp);
}
pub fn locate_worker_bin() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("SUPERMACHINE_WORKER_BIN") {
let p = PathBuf::from(p);
if p.is_file() {
return Some(p);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(p) = sibling_worker(&exe) {
return Some(p);
}
if let Ok(canonical) = std::fs::canonicalize(&exe) {
if canonical != exe {
if let Some(p) = sibling_worker(&canonical) {
return Some(p);
}
}
}
}
if let Some(p) = dev_tree_worker() {
return Some(p);
}
if let Some(p) = cargo_bin_worker() {
return Some(p);
}
if let Some(p) = path_walk_worker() {
return Some(p);
}
if let Ok(exe) = std::env::current_exe() {
for ancestor in exe.ancestors() {
let p = ancestor.join("target/release/supermachine-worker");
if p.is_file() {
return Some(p);
}
}
if let Ok(canonical) = std::fs::canonicalize(&exe) {
for ancestor in canonical.ancestors() {
let p = ancestor.join("target/release/supermachine-worker");
if p.is_file() {
return Some(p);
}
}
}
}
None
}
fn dev_tree_worker() -> Option<PathBuf> {
let mut probes: Vec<PathBuf> = Vec::new();
if let Ok(cwd) = std::env::current_dir() {
probes.push(cwd);
}
if let Ok(exe) = std::env::current_exe() {
probes.push(exe.clone());
if let Ok(canonical) = std::fs::canonicalize(&exe) {
if canonical != exe {
probes.push(canonical);
}
}
}
for start in &probes {
for ancestor in start.ancestors() {
if !ancestor.join("Cargo.toml").is_file() {
continue;
}
let cand = ancestor.join("target/release/supermachine-worker");
if cand.is_file() {
return Some(cand);
}
}
}
None
}
fn sibling_worker(exe: &Path) -> Option<PathBuf> {
let dir = exe.parent()?;
let p = dir.join("supermachine-worker");
if p.is_file() {
Some(p)
} else {
None
}
}
fn cargo_bin_worker() -> Option<PathBuf> {
let bin_dir = if let Some(cargo) = std::env::var_os("CARGO_HOME") {
PathBuf::from(cargo).join("bin")
} else if let Some(home) = std::env::var_os("HOME") {
PathBuf::from(home).join(".cargo").join("bin")
} else {
return None;
};
let p = bin_dir.join("supermachine-worker");
if p.is_file() {
Some(p)
} else {
None
}
}
fn path_walk_worker() -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
let p = dir.join("supermachine-worker");
if p.is_file() {
return Some(p);
}
}
None
}
pub fn check_self_has_hvf_entitlement() -> Result<(), String> {
static CACHED: OnceLock<Result<(), String>> = OnceLock::new();
CACHED
.get_or_init(check_self_has_hvf_entitlement_uncached)
.clone()
}
fn check_self_has_hvf_entitlement_uncached() -> Result<(), String> {
let exe = std::env::current_exe().map_err(|e| {
format!(
"could not resolve current_exe to check HVF entitlement: {e} \
(your binary may need to be codesigned with \
`cargo supermachine build`)"
)
})?;
let output = Command::new("codesign")
.args(["--display", "--entitlements", "-", "--xml"])
.arg(&exe)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output();
let output = match output {
Ok(o) => o,
Err(_) => return Ok(()), };
if !output.status.success() {
return Err(missing_entitlement_message(&exe));
}
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("com.apple.security.hypervisor") {
Ok(())
} else {
Err(missing_entitlement_message(&exe))
}
}
fn missing_entitlement_message(exe: &Path) -> String {
format!(
"this binary lacks the `com.apple.security.hypervisor` entitlement, \
so `Vm::start` cannot call `hv_vm_create` (it would return HV_DENIED).\n\
\n\
Two ways to fix:\n\
\n\
(a) Use `Image::acquire` / `Image::acquire_with` instead of \
`Vm::start`. The library spawns a pre-signed \
`supermachine-worker` subprocess that handles HVF on your \
behalf, so your own binary never calls into HVF and doesn't \
need codesigning. This is the recommended path for embedders.\n\
(b) Build your binary with the bundled cargo plugin:\n\
cargo supermachine build --release\n\
which wraps `cargo build` and codesigns the output with the \
HVF entitlement. Use this if you specifically want the \
in-process VM thread (`Vm::start`).\n\
\n\
Path: {}",
exe.display()
)
}
fn sentinel_path() -> Option<PathBuf> {
let base = if let Some(d) = std::env::var_os("XDG_DATA_HOME") {
PathBuf::from(d)
} else if let Some(h) = std::env::var_os("HOME") {
PathBuf::from(h).join(".local/share")
} else {
return None;
};
Some(
base.join("supermachine")
.join(format!("v{}", env!("CARGO_PKG_VERSION")))
.join(".worker-signed"),
)
}
#[cfg(test)]
mod cstemp_tests {
use super::{cleanup_stuck_cstemp, cstemp_for};
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_ID: AtomicU64 = AtomicU64::new(0);
struct TempWorker {
path: PathBuf,
}
impl TempWorker {
fn new() -> Self {
let id = TEST_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"supermachine-cstemp-test-{}-{id}",
std::process::id()
));
let mut f = std::fs::File::create(&path).expect("create temp worker");
f.write_all(b"fake-worker-bytes").expect("write temp worker");
Self { path }
}
fn cstemp(&self) -> PathBuf {
cstemp_for(&self.path)
}
fn create_cstemp(&self) {
let mut f = std::fs::File::create(self.cstemp()).expect("create cstemp");
f.write_all(b"orphan-cstemp-contents").expect("write cstemp");
}
}
impl Drop for TempWorker {
fn drop(&mut self) {
let cs = self.cstemp();
let _ = std::process::Command::new("chflags")
.args(["-R", "nouchg"])
.arg(&cs)
.arg(&self.path)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
let _ = std::fs::remove_file(&cs);
let _ = std::fs::remove_file(&self.path);
}
}
#[test]
fn cstemp_for_appends_dot_cstemp() {
let worker = PathBuf::from("/usr/local/bin/supermachine-worker");
let cs = cstemp_for(&worker);
assert_eq!(
cs,
PathBuf::from("/usr/local/bin/supermachine-worker.cstemp")
);
}
#[test]
fn cstemp_for_handles_worker_in_root() {
let worker = PathBuf::from("/worker");
assert_eq!(cstemp_for(&worker), PathBuf::from("/worker.cstemp"));
}
#[test]
fn cstemp_for_handles_relative_path() {
let worker = PathBuf::from("./bin/w");
assert_eq!(cstemp_for(&worker), PathBuf::from("./bin/w.cstemp"));
}
#[test]
fn cleanup_no_op_when_no_cstemp_exists() {
let w = TempWorker::new();
assert!(!w.cstemp().exists());
cleanup_stuck_cstemp(&w.path);
assert!(!w.cstemp().exists());
assert!(w.path.exists());
}
#[test]
fn cleanup_removes_plain_orphan_cstemp() {
let w = TempWorker::new();
w.create_cstemp();
assert!(w.cstemp().exists());
cleanup_stuck_cstemp(&w.path);
assert!(
!w.cstemp().exists(),
"cleanup should unlink orphan cstemp"
);
assert!(w.path.exists());
}
#[test]
#[cfg(target_os = "macos")]
fn cleanup_removes_uchg_locked_cstemp() {
let w = TempWorker::new();
w.create_cstemp();
let status = std::process::Command::new("chflags")
.arg("uchg")
.arg(w.cstemp())
.status();
match status {
Ok(s) if s.success() => {}
_ => return,
}
let plain = std::fs::remove_file(w.cstemp());
assert!(plain.is_err(), "uchg-locked file should reject plain unlink");
cleanup_stuck_cstemp(&w.path);
assert!(
!w.cstemp().exists(),
"cleanup should chflags-then-unlink even uchg-locked cstemp"
);
}
#[test]
fn cleanup_silent_when_cstemp_disappears_mid_call() {
let w = TempWorker::new();
w.create_cstemp();
std::fs::remove_file(w.cstemp()).expect("remove cstemp");
cleanup_stuck_cstemp(&w.path);
}
}
#[cfg(test)]
mod dev_tree_worker_tests {
use super::dev_tree_worker;
use std::fs;
use std::path::PathBuf;
struct FakeWorkspace {
root: PathBuf,
prev_cwd: PathBuf,
}
impl FakeWorkspace {
fn new(prefix: &str) -> Self {
let prev_cwd = std::env::current_dir().expect("cwd");
let root = std::env::temp_dir().join(format!(
"sm-dt-{prefix}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
fs::create_dir_all(root.join("target/release"))
.expect("create workspace tree");
fs::write(root.join("Cargo.toml"), b"# fake workspace\n")
.expect("write Cargo.toml");
FakeWorkspace { root, prev_cwd }
}
fn add_worker(&self) {
fs::write(
self.root.join("target/release/supermachine-worker"),
b"#!/bin/sh\necho fake\n",
)
.expect("write worker");
}
fn chdir(&self) {
std::env::set_current_dir(&self.root).expect("chdir");
}
fn worker_path(&self) -> PathBuf {
self.root.join("target/release/supermachine-worker")
}
}
impl Drop for FakeWorkspace {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.prev_cwd);
let _ = fs::remove_dir_all(&self.root);
}
}
#[test]
fn finds_worker_in_cwd_workspace() {
let ws = FakeWorkspace::new("ok");
ws.add_worker();
ws.chdir();
let got = dev_tree_worker().expect("should find worker");
let got_c = fs::canonicalize(&got).expect("canon got");
let want_c = fs::canonicalize(ws.worker_path()).expect("canon want");
assert_eq!(got_c, want_c);
}
#[test]
fn returns_none_when_no_workspace() {
let ws = FakeWorkspace::new("noworkspace");
let probe_dir = ws.root.join("not-a-workspace");
fs::create_dir_all(&probe_dir).unwrap();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&probe_dir).unwrap();
let got = dev_tree_worker();
std::env::set_current_dir(prev).unwrap();
if let Some(p) = got {
assert!(
!p.starts_with(&probe_dir),
"dev_tree_worker leaked the empty probe dir's path"
);
}
}
#[test]
fn cargo_toml_without_target_returns_none_for_that_workspace() {
let ws = FakeWorkspace::new("no_target");
ws.chdir();
let got = dev_tree_worker();
if let Some(p) = got {
assert!(
!p.starts_with(&ws.root),
"dev_tree_worker found a worker that doesn't exist"
);
}
}
}