use std::ffi::{CStr, CString, OsStr, c_int, c_void};
use std::os::unix::ffi::OsStrExt;
use std::ptr::NonNull;
use super::Lookup;
use crate::{Error, Library, Result};
pub(crate) const DEFAULT_FLAGS: c_int = libc::RTLD_NOW | libc::RTLD_LOCAL;
#[derive(Debug, Clone)]
pub struct OpenOptions {
flags: c_int,
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
impl OpenOptions {
pub fn new() -> Self {
Self {
flags: DEFAULT_FLAGS,
}
}
fn set(&mut self, flag: c_int, on: bool) -> &mut Self {
if on {
self.flags |= flag;
} else {
self.flags &= !flag;
}
self
}
pub fn lazy(&mut self, lazy: bool) -> &mut Self {
self.set(libc::RTLD_NOW, !lazy).set(libc::RTLD_LAZY, lazy)
}
pub fn global(&mut self, global: bool) -> &mut Self {
self.set(libc::RTLD_LOCAL, !global)
.set(libc::RTLD_GLOBAL, global)
}
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
pub fn no_delete(&mut self, no_delete: bool) -> &mut Self {
self.set(libc::RTLD_NODELETE, no_delete)
}
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
pub fn no_load(&mut self, no_load: bool) -> &mut Self {
self.set(libc::RTLD_NOLOAD, no_load)
}
#[cfg(all(target_os = "linux", target_env = "gnu"))]
pub fn deep_bind(&mut self, deep_bind: bool) -> &mut Self {
self.set(libc::RTLD_DEEPBIND, deep_bind)
}
pub fn custom_flags(&mut self, flags: c_int) -> &mut Self {
self.flags = flags;
self
}
pub unsafe fn open(&self, path: impl AsRef<OsStr>) -> Result<Library> {
unsafe { open(path.as_ref(), self.flags) }.map(Library::from_handle)
}
}
pub trait LibraryExt {
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "freebsd"))]
unsafe fn get_versioned<T: crate::SymbolType>(
&self,
name: impl crate::SymbolName,
version: impl crate::SymbolName,
) -> Result<crate::Symbol<'_, T>>;
}
impl LibraryExt for Library {
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "freebsd"))]
unsafe fn get_versioned<T: crate::SymbolType>(
&self,
name: impl crate::SymbolName,
version: impl crate::SymbolName,
) -> Result<crate::Symbol<'_, T>> {
let name = name.to_symbol_name()?;
let version = version.to_symbol_name()?;
take_error();
let addr =
unsafe { libc::dlvsym(self.handle().0.as_ptr(), name.as_ptr(), version.as_ptr()) };
let lookup = match (addr.is_null(), take_error()) {
(true, Some(message)) => Lookup::NotFound(message),
_ => Lookup::Found(addr),
};
crate::library::convert(name, lookup).map(crate::Symbol::new)
}
}
#[derive(Debug)]
pub(crate) struct Handle(NonNull<c_void>);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
fn take_error() -> Option<String> {
let msg = unsafe { libc::dlerror() };
if msg.is_null() {
return None;
}
Some(
unsafe { CStr::from_ptr(msg) }
.to_string_lossy()
.into_owned(),
)
}
unsafe fn raw_open(path: Option<&CStr>, flags: c_int) -> Option<Handle> {
let ptr = path.map_or(std::ptr::null(), CStr::as_ptr);
NonNull::new(unsafe { libc::dlopen(ptr, flags) }).map(Handle)
}
pub(crate) unsafe fn open(path: &OsStr, flags: c_int) -> Result<Handle> {
let cpath = CString::new(path.as_bytes()).map_err(|e| Error::InteriorNul {
position: e.nul_position(),
})?;
unsafe { raw_open(Some(&cpath), flags) }.ok_or_else(|| Error::Open {
path: path.to_owned(),
message: take_error().unwrap_or_else(|| "unknown error".into()),
})
}
pub(crate) fn this() -> Result<Handle> {
unsafe { raw_open(None, DEFAULT_FLAGS) }.ok_or_else(|| Error::Open {
path: Default::default(),
message: take_error().unwrap_or_else(|| "unknown error".into()),
})
}
pub(crate) fn lookup(handle: &Handle, name: &CStr, _function: bool) -> Lookup {
take_error();
let addr = unsafe { libc::dlsym(handle.0.as_ptr(), name.as_ptr()) };
if addr.is_null()
&& let Some(message) = take_error()
{
return Lookup::NotFound(message);
}
Lookup::Found(addr)
}
pub(crate) fn close(handle: Handle) -> Result<()> {
if unsafe { libc::dlclose(handle.0.as_ptr()) } == 0 {
Ok(())
} else {
Err(Error::Close {
message: take_error().unwrap_or_else(|| "unknown error".into()),
})
}
}
pub(crate) fn address_info(addr: *const c_void) -> Option<crate::AddressInfo> {
let mut info: libc::Dl_info = unsafe { std::mem::zeroed() };
if unsafe { libc::dladdr(addr, &mut info) } == 0 || info.dli_fname.is_null() {
return None;
}
let library = unsafe { CStr::from_ptr(info.dli_fname) };
let library = std::ffi::OsStr::from_bytes(library.to_bytes()).into();
let symbol =
(!info.dli_sname.is_null()).then(|| unsafe { CStr::from_ptr(info.dli_sname) }.to_owned());
Some(crate::AddressInfo {
library,
library_base: info.dli_fbase,
symbol,
symbol_address: info.dli_saddr,
})
}
#[cfg(all(target_os = "linux", target_env = "gnu"))]
pub(crate) fn library_path(handle: &Handle) -> Option<std::path::PathBuf> {
#[repr(C)]
struct LinkMap {
l_addr: usize,
l_name: *const std::ffi::c_char,
}
let mut map: *const LinkMap = std::ptr::null();
let rc = unsafe {
libc::dlinfo(
handle.0.as_ptr(),
libc::RTLD_DI_LINKMAP,
(&raw mut map).cast(),
)
};
if rc != 0 || map.is_null() {
take_error();
return None;
}
let name = unsafe { (*map).l_name };
let name = unsafe { CStr::from_ptr(name) };
if name.is_empty() {
return std::env::current_exe().ok();
}
Some(std::ffi::OsStr::from_bytes(name.to_bytes()).into())
}
#[cfg(target_vendor = "apple")]
unsafe extern "C" {
fn _dyld_image_count() -> u32;
fn _dyld_get_image_name(image_index: u32) -> *const std::ffi::c_char;
}
#[cfg(target_vendor = "apple")]
#[allow(clippy::multiple_unsafe_ops_per_block)]
pub(crate) fn library_path(handle: &Handle) -> Option<std::path::PathBuf> {
unsafe {
for i in 0.._dyld_image_count() {
let name = _dyld_get_image_name(i);
if name.is_null() {
continue;
}
let h = libc::dlopen(name, libc::RTLD_NOLOAD | libc::RTLD_LAZY);
if h.is_null() {
continue;
}
libc::dlclose(h);
if h == handle.0.as_ptr() {
return Some(std::ffi::OsStr::from_bytes(CStr::from_ptr(name).to_bytes()).into());
}
}
let main = libc::dlopen(std::ptr::null(), libc::RTLD_NOLOAD | libc::RTLD_LAZY);
if !main.is_null() {
libc::dlclose(main);
if main == handle.0.as_ptr() {
return std::env::current_exe().ok();
}
}
}
None
}
#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_vendor = "apple")))]
pub(crate) fn library_path(_handle: &Handle) -> Option<std::path::PathBuf> {
None
}