use crate::{Error, RefCounter, Result};
use std::ffi::CString;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Mutex;
type MachPortT = u32;
type KernReturnT = i32;
const KERN_SUCCESS: KernReturnT = 0;
const MACH_PORT_NULL: MachPortT = 0;
const MACH_PORT_RIGHT_RECEIVE: i32 = 1;
const MACH_PORT_RIGHT_SEND: i32 = 0;
const BOOTSTRAP_SUCCESS: KernReturnT = 0;
extern "C" {
fn mach_task_self() -> MachPortT;
fn mach_port_allocate(task: MachPortT, right: i32, name: *mut MachPortT) -> KernReturnT;
fn mach_port_insert_right(
task: MachPortT,
name: MachPortT,
poly: MachPortT,
poly_poly: i32,
) -> KernReturnT;
fn mach_port_deallocate(task: MachPortT, name: MachPortT) -> KernReturnT;
fn mach_port_mod_refs(
task: MachPortT,
name: MachPortT,
right: i32,
delta: i32,
) -> KernReturnT;
fn mach_port_get_refs(
task: MachPortT,
name: MachPortT,
right: i32,
refs: *mut u32,
) -> KernReturnT;
fn bootstrap_port() -> MachPortT;
fn bootstrap_check_in(
bp: MachPortT,
service_name: *const i8,
sp: *mut MachPortT,
) -> KernReturnT;
fn bootstrap_look_up(
bp: MachPortT,
service_name: *const i8,
sp: *mut MachPortT,
) -> KernReturnT;
fn bootstrap_register(
bp: MachPortT,
service_name: *const i8,
sp: MachPortT,
) -> KernReturnT;
}
pub struct MacOSRefCounter {
send_right: Mutex<Option<MachPortT>>,
is_owner: AtomicBool,
receive_right: Mutex<Option<MachPortT>>,
local_count: AtomicU32,
has_reference: AtomicBool,
has_lock: AtomicBool,
name: String,
}
impl MacOSRefCounter {
pub fn new(name: &str) -> Result<Self> {
let service_name = format!("com.procref.{}", name);
let c_name = CString::new(service_name.clone())
.map_err(|e| Error::RefCounterInit(format!("Invalid service name: {}", e)))?;
let mut counter = Self {
send_right: Mutex::new(None),
is_owner: AtomicBool::new(false),
receive_right: Mutex::new(None),
local_count: AtomicU32::new(0),
has_reference: AtomicBool::new(false),
has_lock: AtomicBool::new(false),
name: name.to_string(),
};
let mut port: MachPortT = MACH_PORT_NULL;
let bp = unsafe { bootstrap_port() };
let kr = unsafe { bootstrap_look_up(bp, c_name.as_ptr(), &mut port) };
if kr == BOOTSTRAP_SUCCESS && port != MACH_PORT_NULL {
*counter.send_right.lock().unwrap() = Some(port);
} else {
counter.create_service(&c_name)?;
}
Ok(counter)
}
fn create_service(&mut self, c_name: &CString) -> Result<()> {
let task = unsafe { mach_task_self() };
let mut port: MachPortT = MACH_PORT_NULL;
let kr = unsafe { mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &mut port) };
if kr != KERN_SUCCESS {
return Err(Error::RefCounterInit(format!(
"mach_port_allocate failed: {}",
kr
)));
}
let kr = unsafe {
mach_port_insert_right(task, port, port, MACH_PORT_RIGHT_SEND)
};
if kr != KERN_SUCCESS {
return Err(Error::RefCounterInit(format!(
"mach_port_insert_right failed: {}",
kr
)));
}
let bp = unsafe { bootstrap_port() };
let kr = unsafe { bootstrap_register(bp, c_name.as_ptr(), port) };
if kr != BOOTSTRAP_SUCCESS {
tracing::warn!("bootstrap_register failed: {}, using local port", kr);
}
*self.receive_right.lock().unwrap() = Some(port);
*self.send_right.lock().unwrap() = Some(port);
self.is_owner.store(true, Ordering::SeqCst);
Ok(())
}
}
impl RefCounter for MacOSRefCounter {
fn acquire(&self) -> Result<u32> {
if self.has_reference.load(Ordering::SeqCst) {
return self.count();
}
let send_right = self.send_right.lock().unwrap();
if let Some(port) = *send_right {
let task = unsafe { mach_task_self() };
let kr = unsafe { mach_port_mod_refs(task, port, MACH_PORT_RIGHT_SEND, 1) };
if kr != KERN_SUCCESS {
return Err(Error::Acquire(format!("mach_port_mod_refs failed: {}", kr)));
}
}
self.has_reference.store(true, Ordering::SeqCst);
let count = self.local_count.fetch_add(1, Ordering::SeqCst) + 1;
Ok(count)
}
fn release(&self) -> Result<u32> {
if !self.has_reference.load(Ordering::SeqCst) {
return self.count();
}
let send_right = self.send_right.lock().unwrap();
if let Some(port) = *send_right {
let task = unsafe { mach_task_self() };
let kr = unsafe { mach_port_mod_refs(task, port, MACH_PORT_RIGHT_SEND, -1) };
if kr != KERN_SUCCESS {
return Err(Error::Release(format!("mach_port_mod_refs failed: {}", kr)));
}
}
self.has_reference.store(false, Ordering::SeqCst);
let count = self.local_count.fetch_sub(1, Ordering::SeqCst) - 1;
Ok(count)
}
fn count(&self) -> Result<u32> {
let send_right = self.send_right.lock().unwrap();
if let Some(port) = *send_right {
let task = unsafe { mach_task_self() };
let mut refs: u32 = 0;
let kr = unsafe { mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, &mut refs) };
if kr == KERN_SUCCESS {
return Ok(refs);
}
}
Ok(self.local_count.load(Ordering::SeqCst))
}
fn try_lock(&self) -> Result<bool> {
if self.has_lock.load(Ordering::SeqCst) {
return Ok(true);
}
let lock_path = format!("/tmp/procref-{}.lock", self.name);
let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(&lock_path)
.map_err(|e| Error::Lock(format!("Failed to open lock file: {}", e)))?;
use std::os::unix::fs::FileExt;
let result = unsafe {
libc::flock(
std::os::unix::io::AsRawFd::as_raw_fd(&file),
libc::LOCK_EX | libc::LOCK_NB,
)
};
if result == 0 {
self.has_lock.store(true, Ordering::SeqCst);
std::mem::forget(file);
Ok(true)
} else {
Ok(false)
}
}
fn unlock(&self) -> Result<()> {
if !self.has_lock.load(Ordering::SeqCst) {
return Ok(());
}
let lock_path = format!("/tmp/procref-{}.lock", self.name);
if let Ok(file) = std::fs::File::open(&lock_path) {
unsafe {
libc::flock(
std::os::unix::io::AsRawFd::as_raw_fd(&file),
libc::LOCK_UN,
);
}
}
self.has_lock.store(false, Ordering::SeqCst);
Ok(())
}
}
impl Drop for MacOSRefCounter {
fn drop(&mut self) {
if self.has_lock.load(Ordering::SeqCst) {
let _ = self.unlock();
}
if self.is_owner.load(Ordering::SeqCst) {
if let Some(port) = *self.receive_right.lock().unwrap() {
let task = unsafe { mach_task_self() };
unsafe { mach_port_deallocate(task, port) };
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_macos_ref_counter_basic() {
let counter = MacOSRefCounter::new("test-procref-macos").unwrap();
let count = counter.acquire().unwrap();
assert!(count >= 1);
let count = counter.release().unwrap();
assert!(count >= 0);
}
}