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}