dynpatch-core 0.1.0

Runtime engine for dynpatch - dynamic library loading, ABI validation, and transactional patching
Documentation
//! Dynamic library loading and symbol resolution

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};

/// A loaded patch library
pub struct Library {
    _lib: Arc<LibloadingLibrary>,
    path: PathBuf,
    metadata: PatchMetadata,
}

impl Library {
    /// Load a patch library from the specified path
    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);

        // Load metadata
        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,
        })
    }

    /// Get the patch metadata
    pub fn metadata(&self) -> &PatchMetadata {
        &self.metadata
    }

    /// Get the library path
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Resolve a symbol by name
    ///
    /// # Safety
    /// 
    /// The caller must ensure that the symbol has the correct type signature
    /// and that the library remains loaded for the lifetime of the symbol.
    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(),
        })
    }

    /// Try to get the entry point function and execute it
    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(())
                }
            }
        }
    }

    /// Check if the library has a specific symbol
    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()
    }
}

/// Loader for patch libraries
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
    }

    /// Load a patch library and optionally validate it
    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");
            // Basic validation happens in validator module
        }

        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);
    }
}