use crate::error::{Error, Result};
use dynpatch_interface::PatchMetadata;
use libloading::{Library as LibloadingLibrary, Symbol};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{debug, info};
pub struct Library {
_lib: Arc<LibloadingLibrary>,
path: PathBuf,
metadata: PatchMetadata,
}
impl Library {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
debug!("Loading library from: {:?}", path);
let lib = unsafe {
LibloadingLibrary::new(path).map_err(|e| Error::LibraryLoadError {
path: path.to_path_buf(),
source: e,
})?
};
let lib = Arc::new(lib);
let metadata = unsafe {
let get_metadata: Symbol<fn() -> PatchMetadata> = lib
.get(b"__dynpatch_metadata\0")
.map_err(|_| Error::SymbolNotFound {
symbol: "__dynpatch_metadata".to_string(),
})?;
get_metadata()
};
info!(
"Loaded patch: {} v{} (interface v{})",
metadata.name, metadata.version, metadata.interface_version
);
Ok(Self {
_lib: lib,
path: path.to_path_buf(),
metadata,
})
}
pub fn metadata(&self) -> &PatchMetadata {
&self.metadata
}
pub fn path(&self) -> &Path {
&self.path
}
pub unsafe fn get_symbol<T>(&self, name: &[u8]) -> Result<Symbol<T>> {
self._lib.get(name).map_err(|_| Error::SymbolNotFound {
symbol: String::from_utf8_lossy(name).to_string(),
})
}
pub fn call_entry_point(&self) -> Result<()> {
unsafe {
match self.get_symbol::<fn() -> i32>(b"__dynpatch_entry\0") {
Ok(entry) => {
let result = entry();
if result != 0 {
return Err(Error::InitializationFailed {
code: result,
message: "Patch entry point returned non-zero".to_string(),
});
}
info!("Patch entry point executed successfully");
Ok(())
}
Err(_) => {
debug!("No entry point found, skipping initialization");
Ok(())
}
}
}
}
pub fn has_symbol(&self, name: &[u8]) -> bool {
unsafe { self._lib.get::<*const ()>(name).is_ok() }
}
}
impl std::fmt::Debug for Library {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Library")
.field("path", &self.path)
.field("metadata", &self.metadata)
.finish()
}
}
pub struct PatchLoader {
validation_enabled: bool,
}
impl PatchLoader {
pub fn new() -> Self {
Self {
validation_enabled: true,
}
}
pub fn with_validation(mut self, enabled: bool) -> Self {
self.validation_enabled = enabled;
self
}
pub fn load<P: AsRef<Path>>(&self, path: P) -> Result<Library> {
let library = Library::load(path)?;
if self.validation_enabled {
debug!("Validation enabled, running checks");
}
Ok(library)
}
}
impl Default for PatchLoader {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loader_creation() {
let loader = PatchLoader::new();
assert!(loader.validation_enabled);
let loader = PatchLoader::new().with_validation(false);
assert!(!loader.validation_enabled);
}
}