use alloc::{borrow::ToOwned as _, ffi::CString};
use core::{
error::Error,
ffi::{CStr, c_char, c_int, c_void},
fmt,
ptr::NonNull,
};
pub const RTLD_LAZY: c_int = 0x1;
pub const RTLD_NOW: c_int = 0x2;
unsafe extern "C" {
pub fn dlopen(path: *const c_char, mode: c_int) -> *mut c_void;
pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
pub fn dlclose(handle: *mut c_void) -> c_int;
pub fn dlerror() -> *const c_char;
}
#[derive(Debug)]
pub struct LoadError {
message: CString,
}
impl fmt::Display for LoadError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.message, fmt)
}
}
impl Error for LoadError {}
#[derive(Debug)]
#[repr(transparent)]
pub struct LibrarySymbol(NonNull<c_void>);
#[derive(Debug)]
#[repr(transparent)]
pub struct LibraryHandle(Option<NonNull<c_void>>);
unsafe impl Send for LibraryHandle {}
unsafe impl Sync for LibraryHandle {}
impl LibraryHandle {
pub fn open(path: &CStr) -> Result<Self, LoadError> {
let ptr = unsafe { dlopen(path.as_ptr(), RTLD_LAZY) };
let Some(ptr) = NonNull::new(ptr) else {
let message = unsafe { CStr::from_ptr(dlerror()).to_owned() };
return Err(LoadError { message });
};
Ok(Self(Some(ptr)))
}
pub unsafe fn symbol(&self, name: &CStr) -> Result<LibrarySymbol, LoadError> {
let ptr = unsafe { dlsym(self.handle().as_ptr(), name.as_ptr()) };
let Some(ptr) = NonNull::new(ptr) else {
let message = unsafe { CStr::from_ptr(dlerror()).to_owned() };
return Err(LoadError { message });
};
Ok(LibrarySymbol(ptr))
}
const fn handle(&self) -> NonNull<c_void> {
self.0.expect("library should not have been closed")
}
fn close_handle(handle: NonNull<c_void>) -> Result<(), LoadError> {
let res = unsafe { dlclose(handle.as_ptr()) };
if res == 0 {
return Ok(());
}
let message = unsafe { CStr::from_ptr(dlerror()).to_owned() };
Err(LoadError { message })
}
pub unsafe fn close(mut self) -> Result<(), LoadError> {
self.0.take().map_or(Ok(()), Self::close_handle)
}
}
impl Drop for LibraryHandle {
fn drop(&mut self) {
if let Some(handle) = self.0.take() {
let _result = Self::close_handle(handle);
}
}
}