use log::debug; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Mutex; use once_cell::sync::Lazy; #[cfg(feature = "magic")] pub mod magic_file; pub mod exec; pub mod digest; pub mod text; pub mod read_time; pub mod read_rate; pub mod hostname; pub mod cwd; pub mod user; pub mod shell; pub mod shell_pid; pub mod keep_pid; pub mod env; #[cfg(feature = "magic")] pub use magic_file::MagicFileMetaPlugin; pub use exec::MetaPluginExec; pub use digest::DigestMetaPlugin; pub use text::TextMetaPlugin; pub use read_time::ReadTimeMetaPlugin; pub use read_rate::ReadRateMetaPlugin; pub use hostname::HostnameMetaPlugin; pub use cwd::CwdMetaPlugin; pub use user::UserMetaPlugin; pub use shell::ShellMetaPlugin; pub use shell_pid::ShellPidMetaPlugin; pub use keep_pid::KeepPidMetaPlugin; pub use env::EnvMetaPlugin; /// Represents metadata to be stored. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MetaData { /// The name of the metadata field. pub name: String, /// The value of the metadata field. pub value: String, } /// Response from meta plugin operations. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MetaPluginResponse { /// The generated metadata items. pub metadata: Vec, /// Indicates if the plugin has finished processing. pub is_finalized: bool, } /// Base implementation for meta plugins to reduce boilerplate. #[derive(Debug, Clone, Default)] pub struct BaseMetaPlugin { /// Output mappings for metadata. pub outputs: std::collections::HashMap, /// Configuration options for the plugin. pub options: std::collections::HashMap, /// Whether the plugin is finalized. pub is_finalized: bool, } impl BaseMetaPlugin { /// Creates a new `BaseMetaPlugin`. /// /// # Returns /// /// A new instance of `BaseMetaPlugin`. pub fn new() -> Self { Self::default() } /// Returns a reference to the outputs mapping. /// /// # Returns /// /// A reference to the `HashMap` of outputs. pub fn outputs(&self) -> &std::collections::HashMap { &self.outputs } /// Returns a mutable reference to the outputs mapping. /// /// # Returns /// /// A mutable reference to the `HashMap` of outputs. pub fn outputs_mut(&mut self) -> &mut std::collections::HashMap { &mut self.outputs } /// Returns a reference to the options mapping. /// /// # Returns /// /// A reference to the `HashMap` of options. pub fn options(&self) -> &std::collections::HashMap { &self.options } /// Returns a mutable reference to the options mapping. /// /// # Returns /// /// A mutable reference to the `HashMap` of options. pub fn options_mut(&mut self) -> &mut std::collections::HashMap { &mut self.options } /// Helper function to initialize plugin options and outputs. /// /// # Arguments /// /// * `default_outputs` - Slice of default output names. /// * `options` - Optional user-provided options. /// * `outputs` - Optional user-provided outputs. pub fn initialize_plugin( &mut self, default_outputs: &[&str], options: Option>, outputs: Option>, ) { // Set default outputs for output_name in default_outputs { self.outputs.insert(output_name.to_string(), serde_yaml::Value::String(output_name.to_string())); } // Apply provided options and outputs if let Some(opts) = options { self.options.extend(opts); } if let Some(outs) = outputs { self.outputs.extend(outs); } } } impl MetaPlugin for BaseMetaPlugin { /// Returns the type of this meta plugin. /// /// # Returns /// /// `MetaPluginType::Text` (default for base). fn meta_type(&self) -> MetaPluginType { // This is a base implementation, so we need to return something // This might not be used, but we need to satisfy the trait MetaPluginType::Text } /// Returns a reference to the outputs mapping. /// /// # Returns /// /// A reference to the `HashMap` of outputs. fn outputs(&self) -> &std::collections::HashMap { &self.outputs } /// Returns a mutable reference to the outputs mapping. /// /// # Returns /// /// A mutable reference to the `HashMap` of outputs. fn outputs_mut(&mut self) -> &mut std::collections::HashMap { &mut self.outputs } /// Returns a reference to the options mapping. /// /// # Returns /// /// A reference to the `HashMap` of options. fn options(&self) -> &std::collections::HashMap { &self.options } /// Returns a mutable reference to the options mapping. /// /// # Returns /// /// A mutable reference to the `HashMap` of options. fn options_mut(&mut self) -> &mut std::collections::HashMap { &mut self.options } } #[derive(Debug, Eq, PartialEq, Clone, Hash, strum::EnumIter, strum::Display, strum::EnumString, Serialize, Deserialize)] #[strum(serialize_all = "snake_case", ascii_case_insensitive)] pub enum MetaPluginType { #[cfg(feature = "magic")] MagicFile, Cwd, Text, User, Shell, ShellPid, KeepPid, Digest, ReadTime, ReadRate, Hostname, Exec, Env, } /// Central function to handle metadata output with name mapping. /// /// # Arguments /// /// * `internal_name` - The internal name of the metadata. /// * `value` - The value to process. /// * `outputs` - The outputs mapping. /// /// # Returns /// /// An optional `MetaData` if the output is enabled, `None` if disabled. pub fn process_metadata_outputs(internal_name: &str, value: serde_yaml::Value, outputs: &std::collections::HashMap) -> Option { // Check if this output is disabled if let Some(mapping) = outputs.get(internal_name) { // Check for null to disable the output if mapping.is_null() { debug!("META: Skipping disabled output (null): {}", internal_name); return None; } // Check for boolean false to disable the output if let Some(false_val) = mapping.as_bool() { if !false_val { debug!("META: Skipping disabled output: {}", internal_name); return None; } } if let Some(custom_name) = mapping.as_str() { // Convert the value to a string representation let value_str = match &value { serde_yaml::Value::Null => "null".to_string(), serde_yaml::Value::Bool(b) => b.to_string(), serde_yaml::Value::Number(n) => n.to_string(), serde_yaml::Value::String(s) => s.clone(), serde_yaml::Value::Sequence(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), serde_yaml::Value::Mapping(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), serde_yaml::Value::Tagged(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), }; debug!("META: Processing metadata: internal_name={}, custom_name={}, value={}", internal_name, custom_name, value_str); return Some(MetaData { name: custom_name.to_string(), value: value_str, }); } } // Convert the value to a string representation let value_str = match &value { serde_yaml::Value::Null => "null".to_string(), serde_yaml::Value::Bool(b) => b.to_string(), serde_yaml::Value::Number(n) => n.to_string(), serde_yaml::Value::String(s) => s.clone(), serde_yaml::Value::Sequence(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), serde_yaml::Value::Mapping(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), serde_yaml::Value::Tagged(_) => serde_yaml::to_string(&value).unwrap_or_else(|_| "".to_string()), }; // Default: use internal name as output name debug!("META: Processing metadata: name={}, value={}", internal_name, value_str); Some(MetaData { name: internal_name.to_string(), value: value_str, }) } pub trait MetaPlugin where Self: 'static { /// Returns the type of this meta plugin. /// /// # Returns /// /// The `MetaPluginType` enum variant for this plugin. fn meta_type(&self) -> MetaPluginType; /// Checks if the plugin is supported on the current system. /// /// # Returns /// /// `true` if supported, `false` otherwise. fn is_supported(&self) -> bool { true } /// Checks if the plugin is internal (built-in). /// /// # Returns /// /// `true` if internal, `false` otherwise. fn is_internal(&self) -> bool { true } /// Checks if the plugin is already finalized. /// /// # Returns /// /// `true` if finalized, `false` otherwise. fn is_finalized(&self) -> bool { false } /// Sets the finalized state (only for plugins that can track this). /// /// # Arguments /// /// * `_finalized` - The new finalized state (unused in default). fn set_finalized(&mut self, _finalized: bool) {} /// Updates the meta plugin with new data. /// /// # Arguments /// /// * `_data` - The data chunk to process (unused in default). /// /// # Returns /// /// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`. fn update(&mut self, _data: &[u8]) -> MetaPluginResponse { // Default implementation does nothing MetaPluginResponse { metadata: Vec::new(), is_finalized: false, } } /// Finalizes the plugin. /// /// # Returns /// /// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `true`. fn finalize(&mut self) -> MetaPluginResponse { // Default implementation does nothing MetaPluginResponse { metadata: Vec::new(), is_finalized: true, } } /// Gets program information for display in status. /// /// # Returns /// /// An optional tuple of program name and arguments, or `None`. fn program_info(&self) -> Option<(&str, Vec<&str>)> { None } /// Initializes the plugin. /// /// # Returns /// /// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`. fn initialize(&mut self) -> MetaPluginResponse { // Default implementation does nothing MetaPluginResponse { metadata: Vec::new(), is_finalized: false, } } /// Returns a reference to the outputs mapping. /// /// # Returns /// /// An empty `HashMap` (default implementation). fn outputs(&self) -> &std::collections::HashMap { use once_cell::sync::Lazy; static EMPTY: Lazy> = Lazy::new(|| std::collections::HashMap::new()); &EMPTY } /// Returns a mutable reference to the outputs mapping. /// /// # Panics /// /// Panics with "outputs_mut() not implemented for this plugin". fn outputs_mut(&mut self) -> &mut std::collections::HashMap { panic!("outputs_mut() not implemented for this plugin") } /// Returns a reference to the options mapping. /// /// # Returns /// /// An empty `HashMap` (default implementation). fn options(&self) -> &std::collections::HashMap { use once_cell::sync::Lazy; static EMPTY: Lazy> = Lazy::new(|| std::collections::HashMap::new()); &EMPTY } /// Returns a mutable reference to the options mapping. /// /// # Panics /// /// Panics with "options_mut() not implemented for this plugin". fn options_mut(&mut self) -> &mut std::collections::HashMap { panic!("options_mut() not implemented for this plugin") } /// Gets the default output names this plugin can produce. /// /// # Returns /// /// A vector containing the meta type as a string (default). fn default_outputs(&self) -> Vec { // Default implementation returns the meta type as a string vec![self.meta_type().to_string()] } /// Method to downcast to concrete type (for checking finalization state). /// /// # Returns /// /// A mutable reference to `self` as `dyn Any`. fn as_any_mut(&mut self) -> &mut dyn std::any::Any where Self: Sized { self } } /// Global registry for meta plugins. static META_PLUGIN_REGISTRY: Lazy>, Option>) -> Box>>> = Lazy::new(|| Mutex::new(HashMap::new())); /// Register a meta plugin with the global registry. /// /// # Arguments /// /// * `meta_plugin_type` - The type of the meta plugin to register. /// * `constructor` - The constructor function for creating plugin instances. pub fn register_meta_plugin( meta_plugin_type: MetaPluginType, constructor: fn(Option>, Option>) -> Box ) { META_PLUGIN_REGISTRY.lock().unwrap().insert(meta_plugin_type, constructor); } pub fn get_meta_plugin( meta_plugin_type: MetaPluginType, options: Option>, outputs: Option>, ) -> Box { let registry = META_PLUGIN_REGISTRY.lock().unwrap(); if let Some(constructor) = registry.get(&meta_plugin_type) { return constructor(options, outputs); } // Fallback for exec plugin which needs special handling if meta_plugin_type == MetaPluginType::Exec { // For exec type, we need to parse the command from options let mut program_name = String::new(); let mut args = Vec::new(); let mut meta_name = MetaPluginType::Exec.to_string(); let mut split_whitespace = true; if let Some(opts) = &options { if let Some(command_value) = opts.get("command") { if let Some(command_str) = command_value.as_str() { let parts: Vec<&str> = command_str.split_whitespace().collect(); if !parts.is_empty() { program_name = parts[0].to_string(); args = parts[1..].iter().map(|s| s.to_string()).collect(); } } } // Handle other options if needed if let Some(split_value) = opts.get("split_whitespace") { if let Some(split_bool) = split_value.as_bool() { split_whitespace = split_bool; } } if let Some(name_value) = opts.get("name") { if let Some(name_str) = name_value.as_str() { meta_name = name_str.to_string(); } } } return Box::new(MetaPluginExec::new(&program_name, args.iter().map(|s| s.as_str()).collect(), meta_name, split_whitespace, options, outputs)); } // Fallback for unknown plugins panic!("Meta plugin {:?} not registered", meta_plugin_type); }