use log::{debug, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; pub mod cwd; pub mod digest; pub mod env; pub mod exec; pub mod hostname; #[cfg(feature = "meta_infer")] pub mod infer_plugin; pub mod keep_pid; pub mod magic_file; pub mod read_rate; pub mod read_time; pub mod shell; pub mod shell_pid; pub mod text; #[cfg(feature = "meta_tokens")] pub mod tokens; #[cfg(feature = "meta_tree_magic_mini")] pub mod tree_magic_mini; pub mod user; pub use digest::DigestMetaPlugin; pub use exec::MetaPluginExec; #[cfg(feature = "meta_magic")] pub use magic_file::MagicFileMetaPlugin; // pub use text::TextMetaPlugin; // Removed duplicate pub use cwd::CwdMetaPlugin; pub use env::EnvMetaPlugin; pub use hostname::HostnameMetaPlugin; #[cfg(feature = "meta_infer")] pub use infer_plugin::InferMetaPlugin; pub use keep_pid::KeepPidMetaPlugin; pub use read_rate::ReadRateMetaPlugin; pub use read_time::ReadTimeMetaPlugin; pub use shell::ShellMetaPlugin; pub use shell_pid::ShellPidMetaPlugin; #[cfg(feature = "meta_tree_magic_mini")] pub use tree_magic_mini::TreeMagicMiniMetaPlugin; pub use user::UserMetaPlugin; #[cfg(not(feature = "meta_magic"))] pub use magic_file::FallbackMagicFileMetaPlugin as MagicFileMetaPlugin; type PluginConstructor = fn( Option>, Option>, ) -> Box; /// 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, } /// Type alias for the save_meta callback shared by all plugins. pub type SaveMetaFn = Arc>; /// Creates a no-op save_meta for plugins not wired through MetaService. pub fn noop_save_meta() -> SaveMetaFn { Arc::new(Mutex::new(|_: &str, _: &str| {})) } /// Base implementation for meta plugins to reduce boilerplate. #[derive(Clone)] 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, /// Callback to store metadata. Called directly by plugins. pub save_meta: SaveMetaFn, } impl std::fmt::Debug for BaseMetaPlugin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BaseMetaPlugin") .field("outputs", &self.outputs) .field("options", &self.options) .field("is_finalized", &self.is_finalized) .finish_non_exhaustive() } } impl Default for BaseMetaPlugin { fn default() -> Self { Self { outputs: HashMap::new(), options: HashMap::new(), is_finalized: false, save_meta: noop_save_meta(), } } } 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. pub fn outputs(&self) -> &std::collections::HashMap { &self.outputs } /// Returns a mutable reference to the outputs mapping. pub fn outputs_mut(&mut self) -> &mut std::collections::HashMap { &mut self.outputs } /// Returns a reference to the options mapping. pub fn options(&self) -> &std::collections::HashMap { &self.options } /// Returns a mutable reference to the options mapping. pub fn options_mut(&mut self) -> &mut std::collections::HashMap { &mut self.options } /// Sets the save_meta callback on the base plugin. pub fn set_save_meta(&mut self, save_meta: SaveMetaFn) { self.save_meta = save_meta; } /// Saves a metadata entry via the save_meta callback. pub fn save_meta(&self, name: &str, value: &str) { if let Ok(mut f) = self.save_meta.lock() { f(name, value); } else { warn!("META_PLUGIN: save_meta lock poisoned, dropping metadata: {name}={value}"); } } /// 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 { for (key, value) in opts { self.options.insert(key.clone(), value.clone()); } } if let Some(outs) = outputs { for (key, value) in outs { self.outputs.insert(key.clone(), value.clone()); } } } } 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, ) -> anyhow::Result<&mut std::collections::HashMap> { Ok(&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, ) -> anyhow::Result<&mut std::collections::HashMap> { Ok(&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 { MagicFile, Cwd, Text, User, Shell, ShellPid, KeepPid, Digest, ReadTime, ReadRate, Hostname, Exec, Env, Tokens, TreeMagicMini, Infer, } /// 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() && !false_val { debug!("META: Skipping disabled output: {internal_name}"); return None; } if let Some(custom_name) = mapping.as_str() { let value_str = yaml_value_to_string(&value); debug!( "META: Processing metadata: internal_name={internal_name}, custom_name={custom_name}, value={value_str}" ); return Some(MetaData { name: custom_name.to_string(), value: value_str, }); } } let value_str = yaml_value_to_string(&value); // Default: use internal name as output name debug!("META: Processing metadata: name={internal_name}, value={value_str}"); Some(MetaData { name: internal_name.to_string(), value: value_str, }) } fn yaml_value_to_string(value: &serde_yaml::Value) -> String { 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::Value::Mapping(_) | serde_yaml::Value::Tagged(_) => { serde_yaml::to_string(value).unwrap_or_else(|_| "".to_string()) } } } pub trait MetaPlugin: Send 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 std::sync::LazyLock; static EMPTY: LazyLock> = LazyLock::new(std::collections::HashMap::new); &EMPTY } /// Returns a mutable reference to the outputs mapping. /// /// # Returns /// /// A mutable reference to the outputs `HashMap`. /// /// # Errors /// /// Returns an error if the plugin does not support mutable outputs. fn outputs_mut( &mut self, ) -> anyhow::Result<&mut std::collections::HashMap> { anyhow::bail!("outputs_mut() not supported by this plugin") } /// Returns a reference to the options mapping. /// /// # Returns /// /// An empty `HashMap` (default implementation). fn options(&self) -> &std::collections::HashMap { use std::sync::LazyLock; static EMPTY: LazyLock> = LazyLock::new(std::collections::HashMap::new); &EMPTY } /// Returns a mutable reference to the options mapping. /// /// # Returns /// /// A mutable reference to the options `HashMap`. /// /// # Errors /// /// Returns an error if the plugin does not support mutable options. fn options_mut( &mut self, ) -> anyhow::Result<&mut std::collections::HashMap> { anyhow::bail!("options_mut() not supported by 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()] } /// Returns a description of this plugin for display in config templates. /// /// # Returns /// /// A description string (empty by default). fn description(&self) -> &str { "" } /// Returns true if this plugin can execute concurrently with other /// parallel-safe plugins. /// /// Plugins that do significant per-chunk work (hashing, tokenization, /// piping to child processes) should return true. The MetaService will /// run all parallel-safe plugins in separate threads per phase, then /// process results sequentially. fn parallel_safe(&self) -> bool { false } /// Builds the schema for this plugin from its options and outputs. /// /// Default implementation infers option types from YAML values and /// collects enabled outputs. /// /// # Returns /// /// A `PluginSchema` describing this plugin's configuration. fn schema(&self) -> crate::common::schema::PluginSchema { use crate::common::schema::{OptionSchema, OptionType, OutputSchema, PluginSchema}; let options: Vec = self .options() .iter() .map(|(key, value)| { let option_type = OptionType::from_yaml_value(value); let (default, required) = if value.is_null() { (None, true) } else { (Some(value.clone()), false) }; OptionSchema { name: key.clone(), option_type, default, required, } }) .collect(); let mut outputs: Vec = Vec::new(); for (key, value) in self.outputs() { if !value.is_null() { outputs.push(OutputSchema { name: key.clone(), description: key.clone(), }); } } if outputs.is_empty() { for output_name in self.default_outputs() { outputs.push(OutputSchema { name: output_name.clone(), description: output_name, }); } } PluginSchema { name: self.meta_type().to_string(), description: self.description().to_string(), options, outputs, } } /// 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 } /// Sets the save_meta callback for this plugin. /// /// Called by MetaService to wire the plugin to the metadata storage. fn set_save_meta(&mut self, _save_meta: SaveMetaFn) {} /// Saves a metadata entry via the save_meta callback. /// /// Plugins call this during initialize/update/finalize to persist metadata. fn save_meta(&self, _name: &str, _value: &str) {} } /// Global registry for meta plugins. static META_PLUGIN_REGISTRY: std::sync::LazyLock< Mutex>, > = std::sync::LazyLock::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: PluginConstructor, ) -> anyhow::Result<()> { META_PLUGIN_REGISTRY .lock() .map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))? .insert(meta_plugin_type, constructor); Ok(()) } pub fn get_meta_plugin( meta_plugin_type: MetaPluginType, options: Option>, outputs: Option>, ) -> anyhow::Result> { get_meta_plugin_with_save(meta_plugin_type, options, outputs, None) } /// Creates a meta plugin instance with an optional save_meta callback. /// /// If `save_meta` is provided, it is wired to the plugin so it can /// store metadata directly during initialize/update/finalize. pub fn get_meta_plugin_with_save( meta_plugin_type: MetaPluginType, options: Option>, outputs: Option>, save_meta: Option, ) -> anyhow::Result> { let registry = META_PLUGIN_REGISTRY .lock() .map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?; if let Some(constructor) = registry.get(&meta_plugin_type) { let mut plugin = constructor(options, outputs); if let Some(sm) = save_meta { plugin.set_save_meta(sm); } return Ok(plugin); } anyhow::bail!("Meta plugin {meta_plugin_type:?} not registered") }