//! Native bridge to macOS Virtualization.framework for code-browser's local //! sandboxes. Loaded by the Electron main process as a napi addon (feature //! `node`); `vz` is also used directly by the `vzbench` binary. #[cfg(target_os = "macos")] pub mod vz; #[cfg(feature = "node")] mod node { use napi_derive::napi; /// Whether this host can run Virtualization.framework VMs at all. /// (The process also needs the `com.apple.security.virtualization` /// entitlement to actually start one.) #[napi] pub fn is_supported() -> bool { #[cfg(target_os = "macos")] unsafe { objc2_virtualization::VZVirtualMachine::isSupported() } #[cfg(not(target_os = "macos"))] false } } /// napi surface over `vz::Vm`. All blocking work runs on libuv's threadpool /// (AsyncTask), never on Electron's main thread. #[cfg(all(feature = "node", target_os = "macos"))] mod vm_node { use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use napi::bindgen_prelude::AsyncTask; use napi::{Env, Error, Result, Task}; use napi_derive::napi; use crate::vz; #[napi(object)] pub struct ShareOptions { pub tag: String, pub path: Option, pub read_only: bool, } #[napi(object)] pub struct DiskOptions { pub path: String, pub read_only: bool, } #[napi(object)] pub struct VmOptions { pub kernel: String, pub initrd: Option, pub cmdline: String, pub cpus: u32, pub memory_mib: u32, pub shares: Vec, /// virtio-blk disks in order: /dev/vda, /dev/vdb, ... pub disks: Option>, pub console_log: Option, } enum Op { Start, Stop, SetShare { tag: String, path: Option, read_only: bool }, Connect { port: u32, timeout_ms: u32 }, } pub struct VmTask { vm: Arc, op: Op, } impl Task for VmTask { type Output = i32; type JsValue = i32; fn compute(&mut self) -> Result { let r = match &self.op { Op::Start => self.vm.start().map(|_| 0), Op::Stop => self.vm.stop().map(|_| 0), Op::SetShare { tag, path, read_only } => self.vm.set_share(tag, path.as_deref(), *read_only).map(|_| 0), Op::Connect { port, timeout_ms } => { use std::os::fd::IntoRawFd; self.vm .wait_for_port(*port, Duration::from_millis(*timeout_ms as u64)) .map(|s| s.into_raw_fd()) } }; r.map_err(Error::from_reason) } fn resolve(&mut self, _env: Env, out: i32) -> Result { Ok(out) } } #[napi] pub struct VzVm { vm: Arc, } #[napi] impl VzVm { #[napi(constructor)] pub fn new(opts: VmOptions) -> Result { let cfg = vz::VmConfig { kernel: opts.kernel.into(), initrd: opts.initrd.map(Into::into), cmdline: opts.cmdline, cpus: opts.cpus as usize, memory_mib: opts.memory_mib as u64, shares: opts .shares .into_iter() .map(|s| vz::Share { tag: s.tag, path: s.path.map(Into::into), read_only: s.read_only }) .collect(), disks: opts .disks .unwrap_or_default() .into_iter() .map(|d| vz::Disk { path: d.path.into(), read_only: d.read_only }) .collect(), console_log: opts.console_log.map(Into::into), machine_id: None, }; Ok(VzVm { vm: Arc::new(vz::Vm::new(&cfg).map_err(Error::from_reason)?) }) } fn task(&self, op: Op) -> AsyncTask { AsyncTask::new(VmTask { vm: self.vm.clone(), op }) } #[napi(ts_return_type = "Promise")] pub fn start(&self) -> AsyncTask { self.task(Op::Start) } #[napi(ts_return_type = "Promise")] pub fn stop(&self) -> AsyncTask { self.task(Op::Stop) } /// Point a virtio-fs device at a host directory (null detaches). Works while running. #[napi(ts_return_type = "Promise")] pub fn set_share(&self, tag: String, path: Option, read_only: bool) -> AsyncTask { self.task(Op::SetShare { tag, path: path.map(Into::into), read_only }) } /// Connect to a guest vsock port, retrying until it listens. Resolves to a /// socket fd owned by the caller (wrap with `new net.Socket({ fd })`). #[napi(ts_return_type = "Promise")] pub fn connect(&self, port: u32, timeout_ms: u32) -> AsyncTask { self.task(Op::Connect { port, timeout_ms }) } } }