use std::ffi::CStr;
use std::path::{Path, PathBuf};
use crate::error::LoaderError;
use crate::platform;
pub struct Library {
name: &'static str,
lib: libloading::Library,
resolved_from: Option<PathBuf>,
}
impl std::fmt::Debug for Library {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Library")
.field("name", &self.name)
.field("resolved_from", &self.resolved_from)
.finish_non_exhaustive()
}
}
impl Library {
pub fn open(name: &'static str, candidates: &[&'static str]) -> Result<Self, LoaderError> {
if matches!(platform::os_family(), platform::OsFamily::Unsupported) {
return Err(LoaderError::UnsupportedPlatform {
platform: std::env::consts::OS,
});
}
if candidates.is_empty() {
return Err(LoaderError::library_not_found(name, candidates));
}
for candidate in candidates {
if let Ok(lib) = unsafe { libloading::Library::new(candidate) } {
return Ok(Self {
name,
lib,
resolved_from: Some(PathBuf::from(candidate)),
});
}
}
let search_paths = platform::library_search_paths();
for dir in &search_paths {
for candidate in candidates {
let full = dir.join(candidate);
if let Ok(lib) = unsafe { libloading::Library::new(&full) } {
return Ok(Self {
name,
lib,
resolved_from: Some(full),
});
}
}
}
Err(LoaderError::library_not_found_with_search(
name,
candidates,
search_paths.len(),
))
}
pub fn open_at(name: &'static str, path: &Path) -> Result<Self, LoaderError> {
let lib = unsafe { libloading::Library::new(path) }?;
Ok(Self {
name,
lib,
resolved_from: Some(path.to_path_buf()),
})
}
#[inline]
pub fn name(&self) -> &'static str {
self.name
}
#[inline]
pub fn resolved_from(&self) -> Option<&Path> {
self.resolved_from.as_deref()
}
pub unsafe fn symbol<T>(
&self,
symbol: &'static str,
) -> Result<libloading::Symbol<'_, T>, LoaderError> {
let bytes_with_nul: Vec<u8> = symbol.bytes().chain(std::iter::once(0)).collect();
let cstr = CStr::from_bytes_with_nul(&bytes_with_nul).map_err(|_| {
LoaderError::SymbolNotFound {
library: self.name,
symbol,
}
})?;
match self.lib.get::<T>(cstr.to_bytes_with_nul()) {
Ok(s) => Ok(s),
Err(_) => Err(LoaderError::SymbolNotFound {
library: self.name,
symbol,
}),
}
}
pub unsafe fn raw_symbol(&self, symbol: &'static str) -> Result<*mut (), LoaderError> {
let sym: libloading::Symbol<'_, *mut ()> = self.symbol(symbol)?;
Ok(*sym)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_library_reports_candidates() {
let err = Library::open(
"unobtanium",
&["libunobtanium.so.42", "unobtanium64_42.dll"],
);
match err {
Err(LoaderError::LibraryNotFound {
library,
candidates,
..
}) => {
assert_eq!(library, "unobtanium");
assert_eq!(candidates.len(), 2);
}
Err(LoaderError::UnsupportedPlatform { .. }) => {
}
other => panic!("expected LibraryNotFound, got {other:?}"),
}
}
#[test]
fn empty_candidates_returns_library_not_found() {
let err = Library::open("nothing", &[]);
match err {
Err(LoaderError::LibraryNotFound { library, .. }) => {
assert_eq!(library, "nothing");
}
Err(LoaderError::UnsupportedPlatform { .. }) => {}
other => panic!("expected LibraryNotFound, got {other:?}"),
}
}
}