unix: expose with_dlerror to the end users
Useful for those who are using platform-specific openers like `dlmopen`.
This commit is contained in:
parent
775a3fd95c
commit
d97c71c1c8
|
@ -10,10 +10,12 @@
|
||||||
///
|
///
|
||||||
/// ## Non-breaking changes
|
/// ## Non-breaking changes
|
||||||
///
|
///
|
||||||
/// The crate switches the dependency on `windows-sys` to a `windows-target` one for Windows
|
/// * The crate switches the dependency on `windows-sys` to a `windows-target` one for Windows
|
||||||
/// bindings. In order to enable this `libloading` defines any bindings necessary for its operation
|
/// bindings. In order to enable this `libloading` defines any bindings necessary for its operation
|
||||||
/// internally, just like has been done for `unix` targets. This should result in leaner
|
/// internally, just like has been done for `unix` targets. This should result in leaner dependency
|
||||||
/// dependency trees.
|
/// trees.
|
||||||
|
/// * `os::unix::with_dlerror` has been exposed for the users who need to invoke `dl*` family of
|
||||||
|
/// functions manually.
|
||||||
pub mod r0_8_2 {}
|
pub mod r0_8_2 {}
|
||||||
|
|
||||||
/// Release 0.8.1 (2023-09-30)
|
/// Release 0.8.1 (2023-09-30)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::ffi::CString;
|
use std::ffi::{CString, CStr};
|
||||||
|
|
||||||
/// A `dlerror` error.
|
/// A `dlerror` error.
|
||||||
pub struct DlDescription(pub(crate) CString);
|
pub struct DlDescription(pub(crate) CString);
|
||||||
|
@ -9,6 +9,12 @@ impl std::fmt::Debug for DlDescription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&CStr> for DlDescription {
|
||||||
|
fn from(value: &CStr) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Windows API error.
|
/// A Windows API error.
|
||||||
pub struct WindowsError(pub(crate) std::io::Error);
|
pub struct WindowsError(pub(crate) std::io::Error);
|
||||||
|
|
||||||
|
|
|
@ -16,18 +16,33 @@ use util::{cstr_cow_from_bytes, ensure_compatible_types};
|
||||||
|
|
||||||
mod consts;
|
mod consts;
|
||||||
|
|
||||||
// dl* family of functions did not have enough thought put into it.
|
/// Run code and handle errors reported by `dlerror`.
|
||||||
//
|
///
|
||||||
// Whole error handling scheme is done via setting and querying some global state, therefore it is
|
/// This function first executes the `closure` function containing calls to the functions that
|
||||||
// not safe to use dynamic library loading in MT-capable environment at all. Only in POSIX 2008+TC1
|
/// report their errors via `dlerror`. This closure may return either `None` or `Some(*)` to
|
||||||
// a thread-local state was allowed for `dlerror`, making the dl* family of functions MT-safe.
|
/// further affect operation of this function.
|
||||||
//
|
///
|
||||||
// In practice (as of 2020-04-01) most of the widely used targets use a thread-local for error
|
/// In case the `closure` returns `None`, `with_dlerror` inspects the `dlerror`. `dlerror` may
|
||||||
// state and have been doing so for a long time. Regardless the comments in this function shall
|
/// decide to not provide any error description, in which case `Err(None)` is returned to the
|
||||||
// remain as a documentation for the future generations.
|
/// caller. Otherwise the `error` callback is invoked to allow inspection and conversion of the
|
||||||
fn with_dlerror<T, F>(wrap: fn(crate::error::DlDescription) -> crate::Error, closure: F)
|
/// error message. The conversion result is returned as `Err(Some(Error))`.
|
||||||
-> Result<T, Option<crate::Error>>
|
///
|
||||||
where F: FnOnce() -> Option<T> {
|
/// If the operations that report their errors via `dlerror` were all successful, `closure` should
|
||||||
|
/// return `Some(T)` instead. In this case `dlerror` is not inspected at all.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// The whole `dlerror` handling scheme is done via setting and querying some global state. For
|
||||||
|
/// that reason it is not safe to use dynamic library loading in MT-capable environment at all.
|
||||||
|
/// Only in POSIX 2008+TC1 a thread-local state was allowed for `dlerror`, making the dl* family of
|
||||||
|
/// functions possibly MT-safe, depending on the implementation of `dlerror`.
|
||||||
|
///
|
||||||
|
/// In practice (as of 2020-04-01) most of the widely used targets use a thread-local for error
|
||||||
|
/// state and have been doing so for a long time.
|
||||||
|
pub fn with_dlerror<T, F, Error>(closure: F, error: fn(&CStr) -> Error) -> Result<T, Option<Error>>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Option<T>,
|
||||||
|
{
|
||||||
// We used to guard all uses of dl* functions with our own mutex. This made them safe to use in
|
// We used to guard all uses of dl* functions with our own mutex. This made them safe to use in
|
||||||
// MT programs provided the only way a program used dl* was via this library. However, it also
|
// MT programs provided the only way a program used dl* was via this library. However, it also
|
||||||
// had a number of downsides or cases where it failed to handle the problems. For instance,
|
// had a number of downsides or cases where it failed to handle the problems. For instance,
|
||||||
|
@ -53,8 +68,8 @@ where F: FnOnce() -> Option<T> {
|
||||||
// or a bug in implementation of dl* family of functions.
|
// or a bug in implementation of dl* family of functions.
|
||||||
closure().ok_or_else(|| unsafe {
|
closure().ok_or_else(|| unsafe {
|
||||||
// This code will only get executed if the `closure` returns `None`.
|
// This code will only get executed if the `closure` returns `None`.
|
||||||
let error = dlerror();
|
let dlerror_str = dlerror();
|
||||||
if error.is_null() {
|
if dlerror_str.is_null() {
|
||||||
// In non-dlsym case this may happen when there’re bugs in our bindings or there’s
|
// In non-dlsym case this may happen when there’re bugs in our bindings or there’s
|
||||||
// non-libloading user of libdl; possibly in another thread.
|
// non-libloading user of libdl; possibly in another thread.
|
||||||
None
|
None
|
||||||
|
@ -64,8 +79,7 @@ where F: FnOnce() -> Option<T> {
|
||||||
// ownership over the message?
|
// ownership over the message?
|
||||||
// TODO: should do locale-aware conversion here. OTOH Rust doesn’t seem to work well in
|
// TODO: should do locale-aware conversion here. OTOH Rust doesn’t seem to work well in
|
||||||
// any system that uses non-utf8 locale, so I doubt there’s a problem here.
|
// any system that uses non-utf8 locale, so I doubt there’s a problem here.
|
||||||
let message = CStr::from_ptr(error).into();
|
Some(error(CStr::from_ptr(dlerror_str)))
|
||||||
Some(wrap(crate::error::DlDescription(message)))
|
|
||||||
// Since we do a copy of the error string above, maybe we should call dlerror again to
|
// Since we do a copy of the error string above, maybe we should call dlerror again to
|
||||||
// let libdl know it may free its copy of the string now?
|
// let libdl know it may free its copy of the string now?
|
||||||
}
|
}
|
||||||
|
@ -74,7 +88,7 @@ where F: FnOnce() -> Option<T> {
|
||||||
|
|
||||||
/// A platform-specific counterpart of the cross-platform [`Library`](crate::Library).
|
/// A platform-specific counterpart of the cross-platform [`Library`](crate::Library).
|
||||||
pub struct Library {
|
pub struct Library {
|
||||||
handle: *mut raw::c_void
|
handle: *mut raw::c_void,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Library {}
|
unsafe impl Send for Library {}
|
||||||
|
@ -164,30 +178,38 @@ impl Library {
|
||||||
/// termination routines contained within the library is safe as well. These routines may be
|
/// termination routines contained within the library is safe as well. These routines may be
|
||||||
/// executed when the library is unloaded.
|
/// executed when the library is unloaded.
|
||||||
pub unsafe fn open<P>(filename: Option<P>, flags: raw::c_int) -> Result<Library, crate::Error>
|
pub unsafe fn open<P>(filename: Option<P>, flags: raw::c_int) -> Result<Library, crate::Error>
|
||||||
where P: AsRef<OsStr> {
|
where
|
||||||
|
P: AsRef<OsStr>,
|
||||||
|
{
|
||||||
let filename = match filename {
|
let filename = match filename {
|
||||||
None => None,
|
None => None,
|
||||||
Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?),
|
Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?),
|
||||||
};
|
};
|
||||||
with_dlerror(|desc| crate::Error::DlOpen { desc }, move || {
|
with_dlerror(
|
||||||
let result = dlopen(match filename {
|
move || {
|
||||||
None => ptr::null(),
|
let result = dlopen(
|
||||||
Some(ref f) => f.as_ptr()
|
match filename {
|
||||||
}, flags);
|
None => ptr::null(),
|
||||||
// ensure filename lives until dlopen completes
|
Some(ref f) => f.as_ptr(),
|
||||||
drop(filename);
|
},
|
||||||
if result.is_null() {
|
flags,
|
||||||
None
|
);
|
||||||
} else {
|
// ensure filename lives until dlopen completes
|
||||||
Some(Library {
|
drop(filename);
|
||||||
handle: result
|
if result.is_null() {
|
||||||
})
|
None
|
||||||
}
|
} else {
|
||||||
}).map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown))
|
Some(Library { handle: result })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|desc| crate::Error::DlOpen { desc: desc.into() },
|
||||||
|
)
|
||||||
|
.map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown))
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_impl<T, F>(&self, symbol: &[u8], on_null: F) -> Result<Symbol<T>, crate::Error>
|
unsafe fn get_impl<T, F>(&self, symbol: &[u8], on_null: F) -> Result<Symbol<T>, crate::Error>
|
||||||
where F: FnOnce() -> Result<Symbol<T>, crate::Error>
|
where
|
||||||
|
F: FnOnce() -> Result<Symbol<T>, crate::Error>,
|
||||||
{
|
{
|
||||||
ensure_compatible_types::<T, *mut raw::c_void>()?;
|
ensure_compatible_types::<T, *mut raw::c_void>()?;
|
||||||
let symbol = cstr_cow_from_bytes(symbol)?;
|
let symbol = cstr_cow_from_bytes(symbol)?;
|
||||||
|
@ -197,24 +219,26 @@ impl Library {
|
||||||
//
|
//
|
||||||
// We try to leave as little space as possible for this to occur, but we can’t exactly
|
// We try to leave as little space as possible for this to occur, but we can’t exactly
|
||||||
// fully prevent it.
|
// fully prevent it.
|
||||||
let result = with_dlerror(|desc| crate::Error::DlSym { desc }, || {
|
let result = with_dlerror(
|
||||||
dlerror();
|
|| {
|
||||||
let symbol = dlsym(self.handle, symbol.as_ptr());
|
dlerror();
|
||||||
if symbol.is_null() {
|
let symbol = dlsym(self.handle, symbol.as_ptr());
|
||||||
None
|
if symbol.is_null() {
|
||||||
} else {
|
None
|
||||||
Some(Symbol {
|
} else {
|
||||||
pointer: symbol,
|
Some(Symbol {
|
||||||
pd: marker::PhantomData
|
pointer: symbol,
|
||||||
})
|
pd: marker::PhantomData,
|
||||||
}
|
})
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
|desc| crate::Error::DlSym { desc: desc.into() },
|
||||||
|
);
|
||||||
match result {
|
match result {
|
||||||
Err(None) => on_null(),
|
Err(None) => on_null(),
|
||||||
Err(Some(e)) => Err(e),
|
Err(Some(e)) => Err(e),
|
||||||
Ok(x) => Ok(x)
|
Ok(x) => Ok(x),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a pointer to a function or static variable by symbol name.
|
/// Get a pointer to a function or static variable by symbol name.
|
||||||
|
@ -285,10 +309,12 @@ impl Library {
|
||||||
/// variables that work on e.g. Linux may have unintended behaviour on other targets.
|
/// variables that work on e.g. Linux may have unintended behaviour on other targets.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn get_singlethreaded<T>(&self, symbol: &[u8]) -> Result<Symbol<T>, crate::Error> {
|
pub unsafe fn get_singlethreaded<T>(&self, symbol: &[u8]) -> Result<Symbol<T>, crate::Error> {
|
||||||
self.get_impl(symbol, || Ok(Symbol {
|
self.get_impl(symbol, || {
|
||||||
pointer: ptr::null_mut(),
|
Ok(Symbol {
|
||||||
pd: marker::PhantomData
|
pointer: ptr::null_mut(),
|
||||||
}))
|
pd: marker::PhantomData,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the `Library` to a raw handle.
|
/// Convert the `Library` to a raw handle.
|
||||||
|
@ -309,9 +335,7 @@ impl Library {
|
||||||
/// pointer previously returned by `Library::into_raw` call. It must be valid to call `dlclose`
|
/// pointer previously returned by `Library::into_raw` call. It must be valid to call `dlclose`
|
||||||
/// with this pointer as an argument.
|
/// with this pointer as an argument.
|
||||||
pub unsafe fn from_raw(handle: *mut raw::c_void) -> Library {
|
pub unsafe fn from_raw(handle: *mut raw::c_void) -> Library {
|
||||||
Library {
|
Library { handle }
|
||||||
handle
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unload the library.
|
/// Unload the library.
|
||||||
|
@ -325,13 +349,17 @@ impl Library {
|
||||||
///
|
///
|
||||||
/// The underlying data structures may still get leaked if an error does occur.
|
/// The underlying data structures may still get leaked if an error does occur.
|
||||||
pub fn close(self) -> Result<(), crate::Error> {
|
pub fn close(self) -> Result<(), crate::Error> {
|
||||||
let result = with_dlerror(|desc| crate::Error::DlClose { desc }, || {
|
let result = with_dlerror(
|
||||||
if unsafe { dlclose(self.handle) } == 0 {
|
|| {
|
||||||
Some(())
|
if unsafe { dlclose(self.handle) } == 0 {
|
||||||
} else {
|
Some(())
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
}).map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown));
|
}
|
||||||
|
},
|
||||||
|
|desc| crate::Error::DlClose { desc: desc.into() },
|
||||||
|
)
|
||||||
|
.map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown));
|
||||||
// While the library is not free'd yet in case of an error, there is no reason to try
|
// While the library is not free'd yet in case of an error, there is no reason to try
|
||||||
// dropping it again, because all that will do is try calling `dlclose` again. only
|
// dropping it again, because all that will do is try calling `dlclose` again. only
|
||||||
// this time it would ignore the return result, which we already seen failing…
|
// this time it would ignore the return result, which we already seen failing…
|
||||||
|
@ -360,7 +388,7 @@ impl fmt::Debug for Library {
|
||||||
/// `Symbol` does not outlive the `Library` it comes from.
|
/// `Symbol` does not outlive the `Library` it comes from.
|
||||||
pub struct Symbol<T> {
|
pub struct Symbol<T> {
|
||||||
pointer: *mut raw::c_void,
|
pointer: *mut raw::c_void,
|
||||||
pd: marker::PhantomData<T>
|
pd: marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Symbol<T> {
|
impl<T> Symbol<T> {
|
||||||
|
@ -410,13 +438,18 @@ impl<T> fmt::Debug for Symbol<T> {
|
||||||
if dladdr(self.pointer, info.as_mut_ptr()) != 0 {
|
if dladdr(self.pointer, info.as_mut_ptr()) != 0 {
|
||||||
let info = info.assume_init();
|
let info = info.assume_init();
|
||||||
if info.dli_sname.is_null() {
|
if info.dli_sname.is_null() {
|
||||||
f.write_str(&format!("Symbol@{:p} from {:?}",
|
f.write_str(&format!(
|
||||||
self.pointer,
|
"Symbol@{:p} from {:?}",
|
||||||
CStr::from_ptr(info.dli_fname)))
|
self.pointer,
|
||||||
|
CStr::from_ptr(info.dli_fname)
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
f.write_str(&format!("Symbol {:?}@{:p} from {:?}",
|
f.write_str(&format!(
|
||||||
CStr::from_ptr(info.dli_sname), self.pointer,
|
"Symbol {:?}@{:p} from {:?}",
|
||||||
CStr::from_ptr(info.dli_fname)))
|
CStr::from_ptr(info.dli_sname),
|
||||||
|
self.pointer,
|
||||||
|
CStr::from_ptr(info.dli_fname)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.write_str(&format!("Symbol@{:p}", self.pointer))
|
f.write_str(&format!("Symbol@{:p}", self.pointer))
|
||||||
|
@ -426,9 +459,9 @@ impl<T> fmt::Debug for Symbol<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform specific things
|
// Platform specific things
|
||||||
#[cfg_attr(any(target_os = "linux", target_os = "android"), link(name="dl"))]
|
#[cfg_attr(any(target_os = "linux", target_os = "android"), link(name = "dl"))]
|
||||||
#[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name="c"))]
|
#[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name = "c"))]
|
||||||
extern {
|
extern "C" {
|
||||||
fn dlopen(filename: *const raw::c_char, flags: raw::c_int) -> *mut raw::c_void;
|
fn dlopen(filename: *const raw::c_char, flags: raw::c_int) -> *mut raw::c_void;
|
||||||
fn dlclose(handle: *mut raw::c_void) -> raw::c_int;
|
fn dlclose(handle: *mut raw::c_void) -> raw::c_int;
|
||||||
fn dlsym(handle: *mut raw::c_void, symbol: *const raw::c_char) -> *mut raw::c_void;
|
fn dlsym(handle: *mut raw::c_void, symbol: *const raw::c_char) -> *mut raw::c_void;
|
||||||
|
@ -438,8 +471,8 @@ extern {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct DlInfo {
|
struct DlInfo {
|
||||||
dli_fname: *const raw::c_char,
|
dli_fname: *const raw::c_char,
|
||||||
dli_fbase: *mut raw::c_void,
|
dli_fbase: *mut raw::c_void,
|
||||||
dli_sname: *const raw::c_char,
|
dli_sname: *const raw::c_char,
|
||||||
dli_saddr: *mut raw::c_void
|
dli_saddr: *mut raw::c_void,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue