use std::ffi::{CString, c_char, c_void};
use std::path::Path;
use std::ptr;
use std::time::Duration;
use tokio::sync::oneshot;
use tokio::time::sleep;
use crate::configuration::MacHardwareModel;
use crate::error::{VZError, VZResult};
use crate::shim_ffi;
use crate::vm::{VirtualMachine, state_trampoline};
pub struct MacOSConfigurationRequirements {
pub hardware_model: MacHardwareModel,
pub minimum_cpu_count: u64,
pub minimum_memory_size: u64,
}
unsafe extern "C" fn object_trampoline(ctx: *mut c_void, handle: *mut c_void, err: *mut c_char) {
unsafe {
let sender = Box::from_raw(ctx.cast::<oneshot::Sender<Result<usize, String>>>());
let result = if err.is_null() {
Ok(handle as usize)
} else {
Err(shim_ffi::take_error_string(err))
};
if let Err(Ok(bits)) = sender.send(result) {
shim_ffi::abx_object_release(bits as *mut c_void);
}
}
}
pub struct MacOSRestoreImage {
inner: *mut c_void,
}
unsafe impl Send for MacOSRestoreImage {}
impl MacOSRestoreImage {
pub async fn latest_supported() -> VZResult<Self> {
let (tx, rx) = oneshot::channel::<Result<usize, String>>();
let ctx: *mut c_void = Box::into_raw(Box::new(tx)).cast();
unsafe { shim_ffi::abx_restore_image_fetch_latest(ctx, object_trampoline) };
Self::from_received(rx.await)
}
pub async fn load_from_url(path: impl AsRef<Path>) -> VZResult<Self> {
let c_path = CString::new(path.as_ref().to_string_lossy().as_bytes()).map_err(|_| {
VZError::InvalidConfiguration(format!("path contains NUL: {}", path.as_ref().display()))
})?;
let (tx, rx) = oneshot::channel::<Result<usize, String>>();
let ctx: *mut c_void = Box::into_raw(Box::new(tx)).cast();
unsafe { shim_ffi::abx_restore_image_load(c_path.as_ptr(), ctx, object_trampoline) };
Self::from_received(rx.await)
}
fn from_received(
received: Result<Result<usize, String>, oneshot::error::RecvError>,
) -> VZResult<Self> {
let bits = received
.map_err(|_| VZError::Internal {
code: -1,
message: "restore image completion handler dropped".into(),
})?
.map_err(|message| VZError::Internal { code: -1, message })?;
Ok(Self {
inner: bits as *mut c_void,
})
}
pub fn requirements(&self) -> VZResult<MacOSConfigurationRequirements> {
unsafe {
let mut hw: *mut c_void = ptr::null_mut();
let mut min_cpu: u64 = 0;
let mut min_memory: u64 = 0;
let mut error: *mut c_char = ptr::null_mut();
if !shim_ffi::abx_restore_image_requirements(
self.inner,
&raw mut hw,
&raw mut min_cpu,
&raw mut min_memory,
&raw mut error,
) {
return Err(VZError::InvalidConfiguration(shim_ffi::take_error_string(
error,
)));
}
let hardware_model =
MacHardwareModel::from_owned_handle(hw).ok_or_else(|| VZError::Internal {
code: -1,
message: "restore image requirements returned no hardware model".into(),
})?;
Ok(MacOSConfigurationRequirements {
hardware_model,
minimum_cpu_count: min_cpu,
minimum_memory_size: min_memory,
})
}
}
#[must_use]
pub fn url(&self) -> Option<String> {
unsafe {
let s = shim_ffi::abx_restore_image_url(self.inner);
shim_ffi::take_string(s).filter(|url| !url.is_empty())
}
}
}
impl Drop for MacOSRestoreImage {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { shim_ffi::abx_object_release(self.inner) };
}
}
}
pub struct MacOSInstaller {
inner: *mut c_void,
}
unsafe impl Send for MacOSInstaller {}
unsafe impl Sync for MacOSInstaller {}
impl MacOSInstaller {
pub fn new(
virtual_machine: &VirtualMachine,
restore_image_path: impl AsRef<Path>,
) -> VZResult<Self> {
let c_path = CString::new(restore_image_path.as_ref().to_string_lossy().as_bytes())
.map_err(|_| {
VZError::InvalidConfiguration(format!(
"path contains NUL: {}",
restore_image_path.as_ref().display()
))
})?;
let obj = unsafe { shim_ffi::abx_installer_new(virtual_machine.vm_box(), c_path.as_ptr()) };
Ok(Self { inner: obj })
}
#[must_use]
pub fn fraction_completed(&self) -> f64 {
unsafe { shim_ffi::abx_installer_fraction(self.inner) }
}
pub async fn install(
&self,
_virtual_machine: &VirtualMachine,
mut on_progress: impl FnMut(f64),
) -> VZResult<()> {
let (tx, mut rx) = oneshot::channel::<Result<(), String>>();
let ctx: *mut c_void = Box::into_raw(Box::new(tx)).cast();
unsafe { shim_ffi::abx_installer_install(self.inner, ctx, state_trampoline) };
let result = loop {
tokio::select! {
biased;
received = &mut rx => {
break received
.unwrap_or_else(|_| Err("install completion handler dropped".to_string()));
}
() = sleep(Duration::from_secs(2)) => {
on_progress(self.fraction_completed());
}
}
};
result.map_err(|message| VZError::Internal { code: -1, message })
}
}
impl Drop for MacOSInstaller {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { shim_ffi::abx_object_release(self.inner) };
}
}
}