⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/pkg/
plugin.rs

1//! Package Plugin System for VeridianOS
2//!
3//! Defines the contract for package lifecycle hooks. Plugins can register
4//! for specific lifecycle events (install, remove, update, etc.) and declare
5//! the capabilities they require.
6//!
7//! NOTE: Actual ELF dynamic loading of plugin code is deferred to user-space.
8//! This module defines the type-safe contract, state machine, and plugin
9//! registry that the kernel uses to manage plugin metadata.
10
11// Plugin contract types -- see NOTE above
12
13#[cfg(feature = "alloc")]
14use alloc::{collections::BTreeMap, string::String, vec::Vec};
15
16use crate::error::{KernelError, KernelResult};
17
18// ============================================================================
19// PluginHook
20// ============================================================================
21
22/// Lifecycle hook points at which a plugin can execute.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum PluginHook {
25    /// Invoked before a package is installed.
26    PreInstall,
27    /// Invoked after a package is successfully installed.
28    PostInstall,
29    /// Invoked before a package is removed.
30    PreRemove,
31    /// Invoked after a package is successfully removed.
32    PostRemove,
33    /// Invoked before a package is updated.
34    PreUpdate,
35    /// Invoked after a package is successfully updated.
36    PostUpdate,
37    /// Invoked when a package's configuration is being applied.
38    Configure,
39    /// Invoked to verify package integrity.
40    Verify,
41}
42
43impl PluginHook {
44    /// Return a short string identifier for this hook.
45    pub fn as_str(&self) -> &'static str {
46        match self {
47            Self::PreInstall => "pre-install",
48            Self::PostInstall => "post-install",
49            Self::PreRemove => "pre-remove",
50            Self::PostRemove => "post-remove",
51            Self::PreUpdate => "pre-update",
52            Self::PostUpdate => "post-update",
53            Self::Configure => "configure",
54            Self::Verify => "verify",
55        }
56    }
57}
58
59// ============================================================================
60// PluginCapability
61// ============================================================================
62
63/// Capabilities that a plugin may request in order to perform its work.
64///
65/// Each capability maps to a kernel-level permission check during plugin
66/// invocation.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum PluginCapability {
69    /// Read/write access to the filesystem.
70    FileSystemAccess,
71    /// Outbound network access (e.g. for downloading resources).
72    NetworkAccess,
73    /// Ability to spawn child processes.
74    ProcessSpawn,
75    /// Permission to modify package configuration files.
76    ConfigModify,
77    /// Control system services (start, stop, restart).
78    ServiceControl,
79}
80
81impl PluginCapability {
82    /// Return a short string identifier for this capability.
83    pub fn as_str(&self) -> &'static str {
84        match self {
85            Self::FileSystemAccess => "fs-access",
86            Self::NetworkAccess => "net-access",
87            Self::ProcessSpawn => "process-spawn",
88            Self::ConfigModify => "config-modify",
89            Self::ServiceControl => "service-control",
90        }
91    }
92}
93
94// ============================================================================
95// PluginMetadata
96// ============================================================================
97
98/// Metadata describing a plugin, including its identity, required capabilities,
99/// and the hooks it supports.
100#[cfg(feature = "alloc")]
101#[derive(Debug, Clone)]
102pub struct PluginMetadata {
103    /// Unique plugin name.
104    pub name: String,
105    /// Plugin version string (semver).
106    pub version: String,
107    /// Human-readable description of the plugin.
108    pub description: String,
109    /// Capabilities this plugin requires.
110    pub capabilities: Vec<PluginCapability>,
111    /// Lifecycle hooks this plugin handles.
112    pub hooks: Vec<PluginHook>,
113}
114
115#[cfg(feature = "alloc")]
116impl PluginMetadata {
117    /// Create a new plugin metadata with the given identity.
118    pub fn new(name: &str, version: &str, description: &str) -> Self {
119        Self {
120            name: String::from(name),
121            version: String::from(version),
122            description: String::from(description),
123            capabilities: Vec::new(),
124            hooks: Vec::new(),
125        }
126    }
127
128    /// Declare that this plugin requires the given capability.
129    pub fn add_capability(&mut self, capability: PluginCapability) {
130        if !self.capabilities.contains(&capability) {
131            self.capabilities.push(capability);
132        }
133    }
134
135    /// Register a lifecycle hook that this plugin handles.
136    pub fn add_hook(&mut self, hook: PluginHook) {
137        if !self.hooks.contains(&hook) {
138            self.hooks.push(hook);
139        }
140    }
141
142    /// Check whether this plugin requires the given capability.
143    pub fn has_capability(&self, capability: PluginCapability) -> bool {
144        self.capabilities.contains(&capability)
145    }
146
147    /// Check whether this plugin supports the given hook.
148    pub fn supports_hook(&self, hook: PluginHook) -> bool {
149        self.hooks.contains(&hook)
150    }
151}
152
153// ============================================================================
154// PluginState
155// ============================================================================
156
157/// Lifecycle state of a loaded plugin instance.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum PluginState {
160    /// Plugin is registered but not yet loaded into memory.
161    Unloaded,
162    /// Plugin binary has been loaded.
163    Loaded,
164    /// Plugin has been initialized (init function called).
165    Initialized,
166    /// Plugin is actively handling hooks.
167    Active,
168    /// Plugin encountered an error and is disabled.
169    Error,
170}
171
172// ============================================================================
173// PluginInstance
174// ============================================================================
175
176/// A registered plugin instance combining metadata and runtime state.
177#[cfg(feature = "alloc")]
178#[derive(Debug, Clone)]
179pub struct PluginInstance {
180    /// Descriptive metadata for this plugin.
181    pub metadata: PluginMetadata,
182    /// Current lifecycle state.
183    pub state: PluginState,
184}
185
186#[cfg(feature = "alloc")]
187impl PluginInstance {
188    /// Create a new plugin instance in the `Unloaded` state.
189    pub fn new(metadata: PluginMetadata) -> Self {
190        Self {
191            metadata,
192            state: PluginState::Unloaded,
193        }
194    }
195
196    /// Attempt to transition to the given state.
197    ///
198    /// Valid transitions:
199    /// - Unloaded -> Loaded
200    /// - Loaded -> Initialized
201    /// - Initialized -> Active
202    /// - Any state -> Error
203    ///
204    /// Returns an error for invalid transitions.
205    pub fn transition_to(&mut self, new_state: PluginState) -> KernelResult<()> {
206        // Any state can transition to Error
207        if new_state == PluginState::Error {
208            self.state = new_state;
209            return Ok(());
210        }
211
212        let valid = matches!(
213            (self.state, new_state),
214            (PluginState::Unloaded, PluginState::Loaded)
215                | (PluginState::Loaded, PluginState::Initialized)
216                | (PluginState::Initialized, PluginState::Active)
217        );
218
219        if valid {
220            self.state = new_state;
221            Ok(())
222        } else {
223            Err(KernelError::InvalidState {
224                expected: match self.state {
225                    PluginState::Unloaded => "Loaded",
226                    PluginState::Loaded => "Initialized",
227                    PluginState::Initialized => "Active",
228                    PluginState::Active => "Error (terminal state)",
229                    PluginState::Error => "Error (terminal state)",
230                },
231                actual: match new_state {
232                    PluginState::Unloaded => "Unloaded",
233                    PluginState::Loaded => "Loaded",
234                    PluginState::Initialized => "Initialized",
235                    PluginState::Active => "Active",
236                    PluginState::Error => "Error",
237                },
238            })
239        }
240    }
241}
242
243// ============================================================================
244// PluginManager
245// ============================================================================
246
247/// Registry and manager for package lifecycle plugins.
248///
249/// Tracks registered plugins and dispatches lifecycle hooks to all plugins
250/// that declare support for a given hook point.
251#[cfg(feature = "alloc")]
252#[derive(Debug, Clone)]
253pub struct PluginManager {
254    /// Registered plugins, keyed by plugin name.
255    plugins: BTreeMap<String, PluginInstance>,
256}
257
258#[cfg(feature = "alloc")]
259impl PluginManager {
260    /// Create a new empty plugin manager.
261    pub fn new() -> Self {
262        Self {
263            plugins: BTreeMap::new(),
264        }
265    }
266
267    /// Register a new plugin. Returns an error if a plugin with the same name
268    /// is already registered.
269    pub fn register(&mut self, metadata: PluginMetadata) -> KernelResult<()> {
270        if self.plugins.contains_key(&metadata.name) {
271            return Err(KernelError::AlreadyExists {
272                resource: "plugin",
273                id: 0,
274            });
275        }
276        let name = metadata.name.clone();
277        self.plugins.insert(name, PluginInstance::new(metadata));
278        Ok(())
279    }
280
281    /// Unregister a plugin by name. Returns an error if the plugin is not
282    /// found.
283    pub fn unregister(&mut self, name: &str) -> KernelResult<()> {
284        if self.plugins.remove(name).is_none() {
285            return Err(KernelError::NotFound {
286                resource: "plugin",
287                id: 0,
288            });
289        }
290        Ok(())
291    }
292
293    /// List all registered plugin names.
294    pub fn list(&self) -> Vec<&str> {
295        self.plugins.keys().map(|k| k.as_str()).collect()
296    }
297
298    /// Look up a plugin instance by name.
299    pub fn get(&self, name: &str) -> Option<&PluginInstance> {
300        self.plugins.get(name)
301    }
302
303    /// Invoke a lifecycle hook on all plugins that support it.
304    ///
305    /// Iterates through every registered plugin and, for those that declare
306    /// support for `hook`, logs the invocation.
307    ///
308    /// TODO(user-space): Actual plugin execution requires ELF dynamic loading
309    /// and user-space process spawning. Currently this validates and logs
310    /// which plugins would be invoked.
311    #[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
312    pub fn invoke_hook(&self, hook: PluginHook, package_name: &str) -> KernelResult<()> {
313        for (plugin_name, instance) in &self.plugins {
314            if instance.metadata.supports_hook(hook) {
315                // TODO(user-space): Load plugin ELF and call hook entry point
316                crate::println!(
317                    "[PLUGIN] Would invoke {} on plugin '{}' for package '{}'",
318                    hook.as_str(),
319                    plugin_name,
320                    package_name
321                );
322            }
323        }
324        Ok(())
325    }
326
327    /// Return the number of registered plugins.
328    pub fn count(&self) -> usize {
329        self.plugins.len()
330    }
331}
332
333#[cfg(feature = "alloc")]
334impl Default for PluginManager {
335    fn default() -> Self {
336        Self::new()
337    }
338}