Skip to main content

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}