dynpatch_core/
registry.rs

1//! Patch registry and lifecycle management
2
3use crate::error::{Error, Result};
4use crate::loader::{Library, PatchLoader};
5use crate::metrics::global_metrics;
6use crate::validator::Validator;
7use arc_swap::ArcSwap;
8use dynpatch_interface::Version;
9use std::sync::{Arc, Mutex};
10use std::time::{SystemTime, UNIX_EPOCH};
11use tracing::{debug, info, error, warn};
12
13/// Information about a loaded patch
14#[derive(Debug, Clone)]
15pub struct PatchInfo {
16    pub name: String,
17    pub version: Version,
18    pub path: String,
19    pub loaded_at: u64,
20}
21
22impl From<&Library> for PatchInfo {
23    fn from(lib: &Library) -> Self {
24        let metadata = lib.metadata();
25        Self {
26            name: metadata.name.clone(),
27            version: metadata.version.clone(),
28            path: lib.path().to_string_lossy().to_string(),
29            loaded_at: SystemTime::now()
30                .duration_since(UNIX_EPOCH)
31                .unwrap()
32                .as_secs(),
33        }
34    }
35}
36
37/// Historical record of a patch load/unload
38#[derive(Debug, Clone)]
39pub struct PatchRecord {
40    pub info: PatchInfo,
41    pub action: PatchAction,
42    pub timestamp: u64,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum PatchAction {
47    Loaded,
48    Unloaded,
49    RolledBack,
50}
51
52/// Registry for managing patches
53pub struct PatchRegistry {
54    active: Arc<ArcSwap<Option<Arc<Library>>>>,
55    history: Arc<Mutex<Vec<Arc<Library>>>>,
56    records: Arc<Mutex<Vec<PatchRecord>>>,
57    loader: PatchLoader,
58    expected_interface_version: Version,
59    expected_type_hash: u64,
60}
61
62impl PatchRegistry {
63    pub fn new(expected_interface_version: Version, expected_type_hash: u64) -> Self {
64        Self {
65            active: Arc::new(ArcSwap::new(Arc::new(None))),
66            history: Arc::new(Mutex::new(Vec::new())),
67            records: Arc::new(Mutex::new(Vec::new())),
68            loader: PatchLoader::new(),
69            expected_interface_version,
70            expected_type_hash,
71        }
72    }
73
74    /// Load a patch from the specified path
75    ///
76    /// This performs a transactional load with the following steps:
77    /// 1. Load the dynamic library
78    /// 2. Validate ABI/version compatibility
79    /// 3. Execute entry point (if present)
80    /// 4. On success, atomically swap the active patch
81    /// 5. On failure at any step, rollback and return error
82    pub fn load(&self, path: &str) -> Result<()> {
83        info!("Loading patch from: {}", path);
84        global_metrics().record_load_attempt();
85
86        // Load the library
87        let library = match self.loader.load(path) {
88            Ok(lib) => lib,
89            Err(e) => {
90                error!("Failed to load library: {}", e);
91                global_metrics().record_load_failure();
92                return Err(e);
93            }
94        };
95
96        // Validate
97        let validator = Validator::new(
98            self.expected_interface_version.clone(),
99            self.expected_type_hash,
100        );
101        
102        if let Err(e) = validator.validate(&library) {
103            error!("Validation failed for {}: {}", path, e);
104            global_metrics().record_validation_failure();
105            global_metrics().record_load_failure();
106            return Err(e);
107        }
108
109        // Execute entry point (transactional - rollback on failure)
110        if let Err(e) = library.call_entry_point() {
111            error!("Entry point execution failed: {}", e);
112            global_metrics().record_load_failure();
113            return Err(e);
114        }
115
116        // Create patch info
117        let patch_info = PatchInfo::from(&library);
118        let library = Arc::new(library);
119
120        // Transactional swap
121        let old_active = self.active.load();
122        
123        // If there was an old patch, move it to history
124        if let Some(ref old_lib) = **old_active {
125            let mut history = self.history.lock().unwrap();
126            history.push(old_lib.clone());
127            debug!("Moved previous patch to history");
128        }
129
130        // Activate new patch
131        self.active.store(Arc::new(Some(library)));
132
133        // Record the action
134        let mut records = self.records.lock().unwrap();
135        records.push(PatchRecord {
136            info: patch_info,
137            action: PatchAction::Loaded,
138            timestamp: SystemTime::now()
139                .duration_since(UNIX_EPOCH)
140                .unwrap()
141                .as_secs(),
142        });
143
144        global_metrics().record_load_success();
145        info!("Patch loaded successfully");
146        Ok(())
147    }
148
149    /// Rollback to the previous patch
150    ///
151    /// Restores the most recent patch from history.
152    /// This is an atomic operation.
153    pub fn rollback(&self) -> Result<()> {
154        info!("Rolling back to previous patch");
155        global_metrics().record_rollback();
156
157        let mut history = self.history.lock().unwrap();
158        
159        let previous = history.pop().ok_or(Error::NoPreviousPatch)?;
160        
161        let patch_info = PatchInfo::from(previous.as_ref());
162        
163        // Swap back to previous
164        self.active.store(Arc::new(Some(previous)));
165
166        // Record the rollback
167        let mut records = self.records.lock().unwrap();
168        records.push(PatchRecord {
169            info: patch_info,
170            action: PatchAction::RolledBack,
171            timestamp: SystemTime::now()
172                .duration_since(UNIX_EPOCH)
173                .unwrap()
174                .as_secs(),
175        });
176
177        info!("Rollback successful");
178        Ok(())
179    }
180
181    /// Get the currently active patch
182    pub fn active_patch(&self) -> Option<PatchInfo> {
183        let active = self.active.load();
184        active.as_ref().as_ref().map(|lib| PatchInfo::from(lib.as_ref()))
185    }
186
187    /// Get the patch history
188    pub fn history(&self) -> Vec<PatchRecord> {
189        self.records.lock().unwrap().clone()
190    }
191
192    /// Clear all patches and history
193    pub fn clear(&self) {
194        self.active.store(Arc::new(None));
195        self.history.lock().unwrap().clear();
196        self.records.lock().unwrap().clear();
197        info!("Registry cleared");
198    }
199}
200
201// Global registry instance
202static GLOBAL_REGISTRY: once_cell::sync::Lazy<PatchRegistry> = once_cell::sync::Lazy::new(|| {
203    // Default version and hash - applications should initialize properly
204    PatchRegistry::new(Version::new(0, 1, 0), 0)
205});
206
207/// Get the global patch registry
208pub fn global_registry() -> &'static PatchRegistry {
209    &GLOBAL_REGISTRY
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_registry_creation() {
218        let registry = PatchRegistry::new(Version::new(1, 0, 0), 0x1234);
219        assert!(registry.active_patch().is_none());
220        assert_eq!(registry.history().len(), 0);
221    }
222
223    #[test]
224    fn test_global_registry() {
225        let registry = global_registry();
226        assert!(registry.active_patch().is_none());
227    }
228}