pub use self::consts::*;
use crate::as_filename::AsFilename;
use crate::as_symbol_name::AsSymbolName;
use crate::util::ensure_compatible_types;
use core::ffi::CStr;
use core::ptr::null;
use core::{fmt, marker, mem, ptr};
mod consts;
pub fn with_dlerror<T, F, Error>(closure: F, error: fn(&CStr) -> Error) -> Result<T, Option<Error>>
where
F: FnOnce() -> Option<T>,
{
closure().ok_or_else(|| unsafe {
let dlerror_str = dlerror();
if dlerror_str.is_null() {
None
} else {
Some(error(CStr::from_ptr(dlerror_str)))
}
})
}
pub struct Library {
handle: *mut core::ffi::c_void,
}
unsafe impl Send for Library {}
unsafe impl Sync for Library {}
impl Library {
#[inline]
pub unsafe fn new(filename: impl AsFilename) -> Result<Library, crate::Error> {
Library::open(Some(filename), RTLD_LAZY | RTLD_LOCAL)
}
#[inline]
pub fn this() -> Library {
unsafe {
Library::open_char_ptr(null(), RTLD_LAZY | RTLD_LOCAL).expect("this should never fail")
}
}
pub unsafe fn open<P>(
filename: Option<P>,
flags: core::ffi::c_int,
) -> Result<Library, crate::Error>
where
P: AsFilename,
{
let Some(filename) = filename else {
return Self::open_char_ptr(null(), flags);
};
filename.posix_filename(|posix_filename| Library::open_char_ptr(posix_filename, flags))
}
unsafe fn open_char_ptr(
filename: *const core::ffi::c_char,
flags: core::ffi::c_int,
) -> Result<Library, crate::Error> {
with_dlerror(
move || {
let result = dlopen(filename, flags);
if result.is_null() {
None
} else {
Some(Library { handle: result })
}
},
|desc| crate::Error::DlOpen {
source: desc.into(),
},
)
.map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown))
}
unsafe fn get_impl<T, F>(
&self,
symbol: impl AsSymbolName,
on_null: F,
) -> Result<Symbol<T>, crate::Error>
where
F: FnOnce() -> Result<Symbol<T>, crate::Error>,
{
ensure_compatible_types::<T, *mut core::ffi::c_void>()?;
symbol.symbol_name(|posix_symbol| {
let result = with_dlerror(
|| {
dlerror();
let symbol = dlsym(self.handle, posix_symbol);
if symbol.is_null() {
None
} else {
Some(Symbol {
pointer: symbol,
pd: marker::PhantomData,
})
}
},
|desc| crate::Error::DlSym {
source: desc.into(),
},
);
match result {
Err(None) => on_null(),
Err(Some(e)) => Err(e),
Ok(x) => Ok(x),
}
})
}
#[inline(always)]
pub unsafe fn get<T>(&self, symbol: impl AsSymbolName) -> Result<Symbol<T>, crate::Error> {
#[cfg_attr(libloading_docs, allow(unused_extern_crates))]
#[cfg(libloading_docs)]
extern crate cfg_if;
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "openbsd",
target_os = "macos",
target_os = "ios",
target_os = "solaris",
target_os = "illumos",
target_os = "redox",
target_os = "fuchsia",
target_os = "cygwin",
))] {
self.get_singlethreaded(symbol)
} else {
self.get_impl(symbol, || Err(crate::Error::DlSymUnknown))
}
}
}
#[inline(always)]
pub unsafe fn get_singlethreaded<T>(
&self,
symbol: impl AsSymbolName,
) -> Result<Symbol<T>, crate::Error> {
self.get_impl(symbol, || {
Ok(Symbol {
pointer: ptr::null_mut(),
pd: marker::PhantomData,
})
})
}
pub fn into_raw(self) -> *mut core::ffi::c_void {
let handle = self.handle;
mem::forget(self);
handle
}
pub unsafe fn from_raw(handle: *mut core::ffi::c_void) -> Library {
Library { handle }
}
pub fn close(self) -> Result<(), crate::Error> {
let result = with_dlerror(
|| {
if unsafe { dlclose(self.handle) } == 0 {
Some(())
} else {
None
}
},
|desc| crate::Error::DlClose {
source: desc.into(),
},
)
.map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown));
mem::forget(self);
result
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe {
dlclose(self.handle);
}
}
}
impl fmt::Debug for Library {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!("Library@{:p}", self.handle))
}
}
pub struct Symbol<T> {
pointer: *mut core::ffi::c_void,
pd: marker::PhantomData<T>,
}
impl<T> Symbol<T> {
pub fn into_raw(self) -> *mut core::ffi::c_void {
self.pointer
}
pub fn as_raw_ptr(self) -> *mut core::ffi::c_void {
self.pointer
}
}
impl<T> Symbol<Option<T>> {
pub fn lift_option(self) -> Option<Symbol<T>> {
if self.pointer.is_null() {
None
} else {
Some(Symbol {
pointer: self.pointer,
pd: marker::PhantomData,
})
}
}
}
unsafe impl<T: Send> Send for Symbol<T> {}
unsafe impl<T: Sync> Sync for Symbol<T> {}
impl<T> Clone for Symbol<T> {
fn clone(&self) -> Symbol<T> {
Symbol { ..*self }
}
}
impl<T> core::ops::Deref for Symbol<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe {
&*(&self.pointer as *const *mut _ as *const T)
}
}
}
impl<T> fmt::Debug for Symbol<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let mut info = mem::MaybeUninit::<DlInfo>::uninit();
if dladdr(self.pointer, info.as_mut_ptr()) != 0 {
let info = info.assume_init();
if info.dli_sname.is_null() {
f.write_fmt(format_args!(
"Symbol@{:p} from {:?}",
self.pointer,
CStr::from_ptr(info.dli_fname)
))
} else {
f.write_fmt(format_args!(
"Symbol {:?}@{:p} from {:?}",
CStr::from_ptr(info.dli_sname),
self.pointer,
CStr::from_ptr(info.dli_fname)
))
}
} else {
f.write_fmt(format_args!("Symbol@{:p}", self.pointer))
}
}
}
}
#[cfg_attr(any(target_os = "linux", target_os = "android"), link(name = "dl"))]
#[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name = "c"))]
extern "C" {
fn dlopen(
filename: *const core::ffi::c_char,
flags: core::ffi::c_int,
) -> *mut core::ffi::c_void;
fn dlclose(handle: *mut core::ffi::c_void) -> core::ffi::c_int;
fn dlsym(
handle: *mut core::ffi::c_void,
symbol: *const core::ffi::c_char,
) -> *mut core::ffi::c_void;
fn dlerror() -> *mut core::ffi::c_char;
fn dladdr(addr: *mut core::ffi::c_void, info: *mut DlInfo) -> core::ffi::c_int;
}
#[repr(C)]
struct DlInfo {
dli_fname: *const core::ffi::c_char,
dli_fbase: *mut core::ffi::c_void,
dli_sname: *const core::ffi::c_char,
dli_saddr: *mut core::ffi::c_void,
}