dyn_loader/helpers.rs
1//! Shared `DynLib` wrapper and helpers used by both [`crate::dyn_mod`]
2//! (Rust fat-pointer bridge) and [`crate::cdyn`] (COM-style vtable loading).
3
4use std::path::Path;
5use std::sync::Arc;
6
7use anyhow::{Context, Result};
8use libloading::Library;
9
10/// Type of the entry point function that dyn plugins must export.
11pub type PluginEntryPointRaw = unsafe extern "C" fn() -> std::ffi::c_void;
12
13// ---------------------------------------------------------------------------
14// DynLib — Arc-shared dynamic library
15// ---------------------------------------------------------------------------
16
17#[derive(Clone)]
18pub struct DynLib {
19 library: Arc<Library>,
20 path: std::path::PathBuf,
21}
22
23impl DynLib {
24 /// Load a dynamic library from the given path.
25 ///
26 /// # Safety
27 ///
28 /// The target file must be a valid dynamic library for the current process.
29 pub unsafe fn load(path: &Path) -> Result<Self> {
30 let library = unsafe { Library::new(path) }
31 .with_context(|| format!("failed to load dynamic library: {}", path.display()))?;
32 Ok(Self {
33 library: Arc::new(library),
34 path: path.to_path_buf(),
35 })
36 }
37
38 /// Wrap an already-loaded `libloading::Library`.
39 ///
40 /// Useful when the library was loaded elsewhere (e.g. by the OS or another
41 /// loader) and only reference-counting ownership is needed here.
42 pub fn from_library(library: Library, path: impl Into<std::path::PathBuf>) -> Self {
43 Self {
44 library: Arc::new(library),
45 path: path.into(),
46 }
47 }
48
49 /// A handle that does NOT own the library (no unload on drop).
50 ///
51 /// For refs obtained from plugins whose library lifetime is managed
52 /// externally (e.g. statically linked, or kept alive by another handle).
53 /// Uses `dlopen(NULL)` — a handle to the current process, which never
54 /// fails and whose "unload" is a no-op.
55 pub fn unowned() -> Self {
56 let library = unsafe { Library::new(std::ffi::OsStr::new("")) }
57 .expect("dlopen(NULL) — handle to the current process — cannot fail");
58 Self {
59 library: Arc::new(library),
60 path: std::path::PathBuf::from("<unowned>"),
61 }
62 }
63
64 pub fn path(&self) -> &Path {
65 &self.path
66 }
67
68 /// Load a symbol from the library.
69 ///
70 /// # Safety
71 ///
72 /// The caller must ensure `T` matches the actual exported symbol type.
73 pub unsafe fn symbol<T: Copy>(&self, name: &[u8]) -> Result<T> {
74 let sym = unsafe { self.library.get::<T>(name) }.with_context(|| {
75 format!(
76 "symbol '{}' not found in {}",
77 display_symbol(name),
78 self.path.display()
79 )
80 })?;
81 Ok(*sym)
82 }
83
84 /// Try to load a symbol; returns `None` if not found.
85 ///
86 /// # Safety
87 ///
88 /// The caller must ensure `T` matches the actual exported symbol type.
89 pub unsafe fn try_symbol<T: Copy>(&self, name: &[u8]) -> Option<T> {
90 unsafe { self.library.get::<T>(name) }.ok().map(|s| *s)
91 }
92}
93
94// ---------------------------------------------------------------------------
95// Helpers
96// ---------------------------------------------------------------------------
97
98pub(crate) fn display_symbol(symbol: &[u8]) -> String {
99 let end = symbol.iter().position(|&b| b == 0).unwrap_or(symbol.len());
100 String::from_utf8_lossy(&symbol[..end]).into_owned()
101}
102
103/// Quick check: does the file look like a plugin with the given entry symbol?
104///
105/// # Safety
106///
107/// Probes an arbitrary dynamic library for a specific exported symbol.
108pub unsafe fn looks_like_plugin(path: &Path, entry_symbol: &[u8]) -> bool {
109 match unsafe { DynLib::load(path) } {
110 Ok(lib) => unsafe { lib.try_symbol::<PluginEntryPointRaw>(entry_symbol) }.is_some(),
111 Err(_) => false,
112 }
113}