use std::ffi::{CStr, CString, OsStr, c_char, c_int, c_void};
use std::os::unix::ffi::OsStrExt;
use std::ptr::NonNull;
use std::sync::{Mutex, OnceLock};
pub use dldota::Context;
use dldota::OpenFlags;
use super::Lookup;
use crate::{Error, Result};
pub(crate) const DEFAULT_FLAGS: c_int = OpenFlags::NOW.0 | OpenFlags::LOCAL.0;
type DlSym = unsafe extern "C" fn(*mut c_void, *const c_char) -> *mut c_void;
type DlError = unsafe extern "C" fn() -> *const c_char;
type ErrnoLocation = unsafe extern "C" fn() -> *mut c_int;
struct Foreign {
ctx: Context,
dlsym: DlSym,
dlerror: DlError,
data_lookup: Mutex<()>,
errno_location: Option<ErrnoLocation>,
}
impl Foreign {
fn boot() -> std::result::Result<Self, String> {
let ctx = Context::new().map_err(|e| format!("cannot boot the system loader: {e}"))?;
let global = ctx
.open_raw(None, OpenFlags::LAZY)
.map_err(|e| e.to_string())?;
let function = |name: &CStr| ctx.symbol_raw(global, name).map_err(|e| e.to_string());
let dlsym = function(c"dlsym")?;
let dlerror = function(c"dlerror")?;
let errno_location = function(c"__errno_location").ok();
let (dlsym, dlerror, errno_location) = unsafe {
(
std::mem::transmute::<NonNull<c_void>, DlSym>(dlsym),
std::mem::transmute::<NonNull<c_void>, DlError>(dlerror),
errno_location.map(|f| std::mem::transmute::<NonNull<c_void>, ErrnoLocation>(f)),
)
};
Ok(Foreign {
ctx,
dlsym,
dlerror,
data_lookup: Mutex::new(()),
errno_location,
})
}
fn get() -> std::result::Result<&'static Foreign, &'static str> {
static FOREIGN: OnceLock<std::result::Result<Foreign, String>> = OnceLock::new();
FOREIGN
.get_or_init(Foreign::boot)
.as_ref()
.map_err(String::as_str)
}
fn take_error(&self) -> Option<String> {
let msg = unsafe { (self.dlerror)() };
(!msg.is_null()).then(|| {
unsafe { CStr::from_ptr(msg) }
.to_string_lossy()
.into_owned()
})
}
}
pub fn context() -> Result<&'static Context> {
Foreign::get()
.map(|f| &f.ctx)
.map_err(|message| Error::Open {
path: Default::default(),
message: message.to_owned(),
})
}
#[derive(Debug)]
pub(crate) struct Handle(NonNull<c_void>);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
fn open_error(path: &OsStr, message: impl Into<String>) -> Error {
Error::Open {
path: path.to_owned(),
message: message.into(),
}
}
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(),
})?;
let foreign = Foreign::get().map_err(|m| open_error(path, m))?;
foreign
.ctx
.open_raw(Some(&cpath), OpenFlags(flags))
.map(Handle)
.map_err(|e| open_error(path, e.to_string()))
}
pub(crate) fn this() -> Result<Handle> {
let foreign = Foreign::get().map_err(|m| open_error(OsStr::new(""), m))?;
foreign
.ctx
.open_raw(None, OpenFlags::LAZY)
.map(Handle)
.map_err(|e| open_error(OsStr::new(""), e.to_string()))
}
pub(crate) fn lookup(handle: &Handle, name: &CStr, function: bool) -> Lookup {
let foreign = match Foreign::get() {
Ok(foreign) => foreign,
Err(message) => return Lookup::NotFound(message.to_owned()),
};
if function {
return match foreign.ctx.symbol_raw(handle.0, name) {
Ok(thunk) => Lookup::Found(thunk.as_ptr()),
Err(e) => Lookup::NotFound(e.to_string()),
};
}
let _guard = foreign
.data_lookup
.lock()
.unwrap_or_else(|e| e.into_inner());
foreign.take_error();
let addr = unsafe { (foreign.dlsym)(handle.0.as_ptr(), name.as_ptr()) };
if addr.is_null()
&& let Some(message) = foreign.take_error()
{
return Lookup::NotFound(message);
}
Lookup::Found(addr)
}
pub(crate) fn close(handle: Handle) -> Result<()> {
let foreign = Foreign::get().map_err(|m| Error::Close {
message: m.to_owned(),
})?;
foreign.ctx.close_raw(handle.0).map_err(|e| Error::Close {
message: e.to_string(),
})
}
pub(crate) fn errno() -> c_int {
match Foreign::get().ok().and_then(|f| f.errno_location) {
Some(location) => {
let errno = unsafe { location() };
unsafe { *errno }
}
None => 0,
}
}
pub(crate) fn address_info(_addr: *const c_void) -> Option<crate::AddressInfo> {
None
}
pub(crate) fn library_path(_handle: &Handle) -> Option<std::path::PathBuf> {
None
}