use crate::error::{VZError, VZResult};
use crate::ffi::{get_class, nsstring, nsurl_file_path, release};
use crate::msg_send;
use objc2::runtime::{AnyClass, AnyObject, Bool};
use std::collections::HashMap;
use std::ffi::c_void;
use std::path::Path;
pub struct SharedDirectory {
inner: *mut AnyObject,
}
unsafe impl Send for SharedDirectory {}
impl SharedDirectory {
pub fn new(path: impl AsRef<Path>, read_only: bool) -> VZResult<Self> {
let path = path.as_ref();
if !path.exists() {
return Err(VZError::NotFound(path.display().to_string()));
}
if !path.is_dir() {
return Err(VZError::InvalidConfiguration(format!(
"Path is not a directory: {}",
path.display()
)));
}
unsafe {
let cls = get_class("VZSharedDirectory").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZSharedDirectory class not found".into(),
})?;
let url = nsurl_file_path(&path.to_string_lossy());
if url.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to create NSURL for path".into(),
});
}
let obj: *mut AnyObject = msg_send!(cls, alloc);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to allocate VZSharedDirectory".into(),
});
}
let init_sel = objc2::sel!(initWithURL:readOnly:);
let init_fn: unsafe extern "C" fn(
*mut AnyObject,
objc2::runtime::Sel,
*mut AnyObject,
Bool,
) -> *mut AnyObject =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let obj = init_fn(obj, init_sel, url, Bool::new(read_only));
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to initialize VZSharedDirectory".into(),
});
}
tracing::debug!(
"Created SharedDirectory for {:?} (read_only={})",
path,
read_only
);
Ok(Self { inner: obj })
}
}
#[must_use]
pub fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for SharedDirectory {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}
pub trait DirectoryShare {
fn as_ptr(&self) -> *mut AnyObject;
fn into_ptr(self) -> *mut AnyObject;
}
pub struct SingleDirectoryShare {
inner: *mut AnyObject,
}
unsafe impl Send for SingleDirectoryShare {}
impl SingleDirectoryShare {
pub fn new(directory: SharedDirectory) -> VZResult<Self> {
unsafe {
let cls = get_class("VZSingleDirectoryShare").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZSingleDirectoryShare class not found".into(),
})?;
let obj: *mut AnyObject = msg_send!(cls, alloc);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to allocate VZSingleDirectoryShare".into(),
});
}
let init_sel = objc2::sel!(initWithDirectory:);
let init_fn: unsafe extern "C" fn(
*mut AnyObject,
objc2::runtime::Sel,
*mut AnyObject,
) -> *mut AnyObject =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let obj = init_fn(obj, init_sel, directory.into_ptr());
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to initialize VZSingleDirectoryShare".into(),
});
}
tracing::debug!("Created SingleDirectoryShare");
Ok(Self { inner: obj })
}
}
}
impl DirectoryShare for SingleDirectoryShare {
fn as_ptr(&self) -> *mut AnyObject {
self.inner
}
fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for SingleDirectoryShare {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}
pub struct MultipleDirectoryShare {
inner: *mut AnyObject,
directories: HashMap<String, *mut AnyObject>,
}
unsafe impl Send for MultipleDirectoryShare {}
impl MultipleDirectoryShare {
pub fn new() -> VZResult<Self> {
unsafe {
let cls = get_class("VZMultipleDirectoryShare").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZMultipleDirectoryShare class not found".into(),
})?;
let obj: *mut AnyObject = msg_send!(cls, new);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to create VZMultipleDirectoryShare".into(),
});
}
let _: *mut AnyObject = msg_send!(obj, retain);
tracing::debug!("Created MultipleDirectoryShare");
Ok(Self {
inner: obj,
directories: HashMap::new(),
})
}
}
pub fn add(&mut self, name: &str, directory: SharedDirectory) -> &mut Self {
unsafe {
let dirs: *mut AnyObject = msg_send!(self.inner, directories);
let set_sel = objc2::sel!(setObject:forKey:);
let set_fn: unsafe extern "C" fn(
*mut AnyObject,
objc2::runtime::Sel,
*mut AnyObject,
*mut AnyObject,
) = std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let key = nsstring(name);
let dir_ptr = directory.into_ptr();
set_fn(dirs, set_sel, dir_ptr, key);
self.directories.insert(name.to_string(), dir_ptr);
tracing::debug!("Added directory '{}' to MultipleDirectoryShare", name);
}
self
}
#[must_use]
pub fn len(&self) -> usize {
self.directories.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.directories.is_empty()
}
}
impl DirectoryShare for MultipleDirectoryShare {
fn as_ptr(&self) -> *mut AnyObject {
self.inner
}
fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for MultipleDirectoryShare {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}
pub struct VirtioFileSystemDeviceConfiguration {
inner: *mut AnyObject,
tag: String,
}
unsafe impl Send for VirtioFileSystemDeviceConfiguration {}
impl VirtioFileSystemDeviceConfiguration {
pub fn new(tag: &str) -> VZResult<Self> {
if tag.is_empty() {
return Err(VZError::InvalidConfiguration(
"VirtioFS tag cannot be empty".into(),
));
}
unsafe {
let cls = get_class("VZVirtioFileSystemDeviceConfiguration").ok_or_else(|| {
VZError::Internal {
code: -1,
message: "VZVirtioFileSystemDeviceConfiguration class not found".into(),
}
})?;
let tag_ns = nsstring(tag);
let mut error: *mut AnyObject = std::ptr::null_mut();
let validate_sel = objc2::sel!(validateTag:error:);
let validate_fn: unsafe extern "C" fn(
*const AnyObject,
objc2::runtime::Sel,
*mut AnyObject,
*mut *mut AnyObject,
) -> Bool = std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let valid = validate_fn(
cls as *const AnyClass as *const AnyObject,
validate_sel,
tag_ns,
&mut error,
);
if !valid.as_bool() {
let error_msg = if error.is_null() {
format!("Invalid VirtioFS tag: {tag}")
} else {
let desc: *mut AnyObject = msg_send!(error, localizedDescription);
crate::ffi::nsstring_to_string(desc)
};
return Err(VZError::InvalidConfiguration(error_msg));
}
let obj: *mut AnyObject = msg_send!(cls, alloc);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to allocate VZVirtioFileSystemDeviceConfiguration".into(),
});
}
let init_sel = objc2::sel!(initWithTag:);
let init_fn: unsafe extern "C" fn(
*mut AnyObject,
objc2::runtime::Sel,
*mut AnyObject,
) -> *mut AnyObject =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let obj = init_fn(obj, init_sel, tag_ns);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to initialize VZVirtioFileSystemDeviceConfiguration".into(),
});
}
tracing::debug!(
"Created VirtioFileSystemDeviceConfiguration with tag '{}'",
tag
);
Ok(Self {
inner: obj,
tag: tag.to_string(),
})
}
}
pub fn set_share<S: DirectoryShare>(&mut self, share: S) -> &mut Self {
unsafe {
let set_sel = objc2::sel!(setShare:);
let set_fn: unsafe extern "C" fn(*mut AnyObject, objc2::runtime::Sel, *mut AnyObject) =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
set_fn(self.inner, set_sel, share.into_ptr());
tracing::debug!("Set share for VirtioFS device '{}'", self.tag);
}
self
}
#[must_use]
pub fn tag(&self) -> &str {
&self.tag
}
#[must_use]
pub fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for VirtioFileSystemDeviceConfiguration {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RosettaAvailability {
NotSupported,
Supported,
NotInstalled,
}
pub struct LinuxRosettaDirectoryShare {
inner: *mut AnyObject,
}
unsafe impl Send for LinuxRosettaDirectoryShare {}
impl LinuxRosettaDirectoryShare {
pub fn availability() -> RosettaAvailability {
unsafe {
let cls = match get_class("VZLinuxRosettaDirectoryShare") {
Some(c) => c,
None => return RosettaAvailability::NotSupported,
};
let avail_sel = objc2::sel!(availability);
let avail_fn: unsafe extern "C" fn(*const AnyObject, objc2::runtime::Sel) -> i64 =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
let avail = avail_fn(cls as *const AnyClass as *const AnyObject, avail_sel);
match avail {
0 => RosettaAvailability::NotSupported,
1 => RosettaAvailability::Supported,
2 => RosettaAvailability::NotInstalled,
_ => RosettaAvailability::NotSupported,
}
}
}
pub fn new() -> VZResult<Self> {
let avail = Self::availability();
if avail != RosettaAvailability::Supported {
return Err(VZError::OperationFailed(format!(
"Rosetta is not available: {avail:?}"
)));
}
unsafe {
let cls =
get_class("VZLinuxRosettaDirectoryShare").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZLinuxRosettaDirectoryShare class not found".into(),
})?;
let obj: *mut AnyObject = msg_send!(cls, new);
if obj.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to create VZLinuxRosettaDirectoryShare".into(),
});
}
let _: *mut AnyObject = msg_send!(obj, retain);
tracing::debug!("Created LinuxRosettaDirectoryShare");
Ok(Self { inner: obj })
}
}
}
impl DirectoryShare for LinuxRosettaDirectoryShare {
fn as_ptr(&self) -> *mut AnyObject {
self.inner
}
fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for LinuxRosettaDirectoryShare {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}