falco_plugin/plugin/base/
wrappers.rs

1use crate::base::Plugin;
2use crate::plugin::base::logger::{FalcoPluginLoggerImpl, FALCO_LOGGER};
3use crate::plugin::base::PluginWrapper;
4use crate::plugin::error::ffi_result::FfiResult;
5use crate::plugin::error::last_error::LastError;
6use crate::plugin::schema::{ConfigSchema, ConfigSchemaType};
7use crate::plugin::tables::vtable::TablesInput;
8use crate::strings::from_ptr::try_str_from_ptr;
9use anyhow::Context;
10use falco_plugin_api::{
11    ss_plugin_init_input, ss_plugin_metric, ss_plugin_rc, ss_plugin_rc_SS_PLUGIN_FAILURE,
12    ss_plugin_rc_SS_PLUGIN_SUCCESS, ss_plugin_t,
13};
14use std::collections::BTreeMap;
15use std::ffi::{c_char, CString};
16use std::sync::Mutex;
17
18/// Marker trait to mark a plugin as exported to the API
19///
20/// # Safety
21///
22/// Only implement this trait if you export the plugin either statically or dynamically
23/// to the plugin API. This is handled by the `plugin!` and `static_plugin!` macros, so you
24/// should never need to implement this trait manually.
25#[diagnostic::on_unimplemented(
26    message = "Plugin is not exported",
27    note = "use either `plugin!` or `static_plugin!`"
28)]
29pub unsafe trait BasePluginExported {}
30
31pub extern "C-unwind" fn plugin_get_required_api_version<
32    const MAJOR: usize,
33    const MINOR: usize,
34    const PATCH: usize,
35>() -> *const c_char {
36    static VERSIONS: Mutex<BTreeMap<(usize, usize, usize), CString>> = Mutex::new(BTreeMap::new());
37
38    let mut version = VERSIONS.lock().unwrap();
39    // we only generate the string once and never change or delete it
40    // so the pointer should remain valid for the static lifetime
41    version
42        .entry((MAJOR, MINOR, PATCH))
43        .or_insert_with(|| {
44            let version = format!("{}.{}.{}", MAJOR, MINOR, PATCH);
45            CString::new(version).unwrap()
46        })
47        .as_ptr()
48}
49
50pub extern "C-unwind" fn plugin_get_version<T: Plugin>() -> *const c_char {
51    T::PLUGIN_VERSION.as_ptr()
52}
53
54pub extern "C-unwind" fn plugin_get_name<T: Plugin>() -> *const c_char {
55    T::NAME.as_ptr()
56}
57
58pub extern "C-unwind" fn plugin_get_description<T: Plugin>() -> *const c_char {
59    T::DESCRIPTION.as_ptr()
60}
61
62pub extern "C-unwind" fn plugin_get_contact<T: Plugin>() -> *const c_char {
63    T::CONTACT.as_ptr()
64}
65
66/// # Safety
67///
68/// init_input must be null or a valid pointer
69pub unsafe extern "C-unwind" fn plugin_init<P: Plugin>(
70    init_input: *const ss_plugin_init_input,
71    rc: *mut ss_plugin_rc,
72) -> *mut falco_plugin_api::ss_plugin_t {
73    let res = (|| -> Result<*mut PluginWrapper<P>, anyhow::Error> {
74        let init_input = unsafe { init_input.as_ref() }
75            .ok_or_else(|| anyhow::anyhow!("Got empty init_input"))?;
76
77        let init_config =
78            try_str_from_ptr(&init_input.config).context("Failed to get config string")?;
79
80        let config = P::ConfigType::from_str(init_config).context("Failed to parse config")?;
81        if let Some(log_fn) = init_input.log_fn {
82            let logger_impl = FalcoPluginLoggerImpl {
83                owner: init_input.owner,
84                logger_fn: log_fn,
85            };
86
87            *FALCO_LOGGER.inner.write().unwrap() = Some(logger_impl);
88            log::set_logger(&FALCO_LOGGER).ok();
89
90            #[cfg(debug_assertions)]
91            log::set_max_level(log::LevelFilter::Trace);
92
93            #[cfg(not(debug_assertions))]
94            log::set_max_level(log::LevelFilter::Info);
95        }
96
97        let tables_input =
98            TablesInput::try_from(init_input).context("Failed to build tables input")?;
99
100        let last_error = LastError::from(init_input)?;
101
102        P::new(tables_input.as_ref(), config)
103            .map(|plugin| Box::into_raw(Box::new(PluginWrapper::new(plugin, last_error))))
104    })();
105
106    match res {
107        Ok(plugin) => {
108            *rc = ss_plugin_rc_SS_PLUGIN_SUCCESS;
109            plugin.cast()
110        }
111        Err(e) => {
112            let error_str = format!("{:#}", &e);
113            log::error!("Failed to initialize plugin: {}", error_str);
114            let plugin = Box::new(PluginWrapper::<P>::new_error(error_str));
115            *rc = e.status_code();
116            Box::into_raw(plugin).cast()
117        }
118    }
119}
120
121/// # Safety
122///
123/// schema_type must be null or a valid pointer
124pub unsafe extern "C-unwind" fn plugin_get_init_schema<P: Plugin>(
125    schema_type: *mut falco_plugin_api::ss_plugin_schema_type,
126) -> *const c_char {
127    let Some(schema_type) = schema_type.as_mut() else {
128        return std::ptr::null();
129    };
130    match P::ConfigType::get_schema() {
131        ConfigSchemaType::None => {
132            *schema_type = falco_plugin_api::ss_plugin_schema_type_SS_PLUGIN_SCHEMA_NONE;
133            std::ptr::null()
134        }
135        ConfigSchemaType::Json(s) => {
136            *schema_type = falco_plugin_api::ss_plugin_schema_type_SS_PLUGIN_SCHEMA_JSON;
137            s.as_ptr()
138        }
139    }
140}
141
142/// # Safety
143///
144/// `plugin` must have been created by `init()` and not destroyed since
145pub unsafe extern "C-unwind" fn plugin_destroy<P: Plugin>(
146    plugin: *mut falco_plugin_api::ss_plugin_t,
147) {
148    unsafe {
149        let plugin = plugin as *mut PluginWrapper<P>;
150        let _ = Box::from_raw(plugin);
151    }
152}
153
154/// # Safety
155///
156/// `plugin` must be a valid pointer to `PluginWrapper<P>`
157pub unsafe extern "C-unwind" fn plugin_get_last_error<P: Plugin>(
158    plugin: *mut falco_plugin_api::ss_plugin_t,
159) -> *const c_char {
160    let plugin = plugin as *mut PluginWrapper<P>;
161    match unsafe { plugin.as_mut() } {
162        Some(plugin) => plugin.error_buf.as_ptr(),
163        None => c"no instance".as_ptr(),
164    }
165}
166
167pub unsafe extern "C-unwind" fn plugin_set_config<P: Plugin>(
168    plugin: *mut falco_plugin_api::ss_plugin_t,
169    config_input: *const falco_plugin_api::ss_plugin_set_config_input,
170) -> falco_plugin_api::ss_plugin_rc {
171    let plugin = plugin as *mut PluginWrapper<P>;
172    let Some(plugin) = plugin.as_mut() else {
173        return ss_plugin_rc_SS_PLUGIN_FAILURE;
174    };
175
176    let Some(ref mut actual_plugin) = &mut plugin.plugin else {
177        return ss_plugin_rc_SS_PLUGIN_FAILURE;
178    };
179
180    let res = (|| -> Result<(), anyhow::Error> {
181        let config_input = unsafe { config_input.as_ref() }.context("Got NULL config")?;
182
183        let updated_config =
184            try_str_from_ptr(&config_input.config).context("Failed to get config string")?;
185        let config = P::ConfigType::from_str(updated_config).context("Failed to parse config")?;
186
187        actual_plugin.plugin.set_config(config)
188    })();
189
190    res.rc(&mut plugin.error_buf)
191}
192
193pub unsafe extern "C-unwind" fn plugin_get_metrics<P: Plugin>(
194    plugin: *mut ss_plugin_t,
195    num_metrics: *mut u32,
196) -> *mut ss_plugin_metric {
197    let plugin = plugin as *mut PluginWrapper<P>;
198    let Some(plugin) = plugin.as_mut() else {
199        *num_metrics = 0;
200        return std::ptr::null_mut();
201    };
202    let Some(ref mut actual_plugin) = &mut plugin.plugin else {
203        *num_metrics = 0;
204        return std::ptr::null_mut();
205    };
206    let Some(num_metrics) = num_metrics.as_mut() else {
207        *num_metrics = 0;
208        return std::ptr::null_mut();
209    };
210
211    plugin.metric_storage.clear();
212    for metric in actual_plugin.plugin.get_metrics() {
213        plugin.metric_storage.push(metric.as_raw());
214    }
215
216    *num_metrics = plugin.metric_storage.len() as u32;
217    plugin.metric_storage.as_ptr().cast_mut()
218}
219
220#[doc(hidden)]
221#[macro_export]
222macro_rules! wrap_ffi {
223    (
224        #[$attr:meta]
225        use $mod:path: <$ty:ty>;
226
227    $(unsafe fn $name:ident( $($param:ident: $param_ty:ty),* $(,)*) -> $ret:ty;)*
228    ) => {
229        $(
230        #[$attr]
231        pub unsafe extern "C-unwind" fn $name ( $($param: $param_ty),*) -> $ret {
232            use $mod as wrappers;
233
234            wrappers::$name::<$ty>($($param),*)
235        }
236        )*
237    }
238}
239
240/// # Register a Falco plugin
241///
242/// This macro must be called at most once in a crate (it generates public functions)
243/// with a type implementing [`Plugin`] as the sole parameter:
244///
245/// ```
246/// # use std::ffi::CStr;
247/// use falco_plugin::base::Plugin;
248/// # use falco_plugin::base::Metric;
249/// use falco_plugin::plugin;
250/// use falco_plugin::tables::TablesInput;
251///
252/// struct MyPlugin;
253/// impl Plugin for MyPlugin {
254///     // ...
255/// #    const NAME: &'static CStr = c"sample-plugin-rs";
256/// #    const PLUGIN_VERSION: &'static CStr = c"0.0.1";
257/// #    const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
258/// #    const CONTACT: &'static CStr = c"you@example.com";
259/// #    type ConfigType = ();
260/// #
261/// #    fn new(input: Option<&TablesInput>, config: Self::ConfigType)
262/// #        -> Result<Self, anyhow::Error> {
263/// #        Ok(MyPlugin)
264/// #    }
265/// #
266/// #    fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
267/// #        Ok(())
268/// #    }
269/// #
270/// #    fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
271/// #        []
272/// #    }
273/// }
274///
275/// plugin!(#[no_capabilities] MyPlugin);
276/// ```
277///
278/// It implements a form where you can override the required API version (for example, if
279/// you wish to advertise an older version for increased compatibility):
280///
281/// ```
282/// # use std::ffi::CStr;
283/// use falco_plugin::base::Plugin;
284/// # use falco_plugin::base::Metric;
285/// use falco_plugin::plugin;
286/// use falco_plugin::tables::TablesInput;
287///
288/// struct MyPlugin;
289/// impl Plugin for MyPlugin {
290///     // ...
291/// #    const NAME: &'static CStr = c"sample-plugin-rs";
292/// #    const PLUGIN_VERSION: &'static CStr = c"0.0.1";
293/// #    const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
294/// #    const CONTACT: &'static CStr = c"you@example.com";
295/// #    type ConfigType = ();
296/// #
297/// #    fn new(input: Option<&TablesInput>, config: Self::ConfigType)
298/// #        -> Result<Self, anyhow::Error> {
299/// #        Ok(MyPlugin)
300/// #    }
301/// #
302/// #    fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
303/// #        Ok(())
304/// #    }
305/// #
306/// #    fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
307/// #        []
308/// #    }
309/// }
310///
311/// // require version 3.3.0 of the API
312/// plugin!(unsafe { 3;3;0 } => #[no_capabilities] MyPlugin);
313/// ```
314///
315/// **Note**: this does not affect the actual version supported in any way. If you use this form,
316/// it's **entirely your responsibility** to ensure the advertised version is compatible with the actual
317/// version supported by this crate.
318#[macro_export]
319macro_rules! plugin {
320    (unsafe { $maj:expr; $min:expr; $patch:expr } => #[no_capabilities] $ty:ty) => {
321        unsafe impl $crate::internals::base::wrappers::BasePluginExported for $ty {}
322
323        $crate::base_plugin_ffi_wrappers!($maj; $min; $patch => #[no_mangle] $ty);
324    };
325    (unsafe { $maj:expr; $min:expr; $patch:expr } => $ty:ty) => {
326        plugin!(unsafe {$maj; $min; $patch} => #[no_capabilities] $ty);
327
328        $crate::ensure_plugin_capabilities!($ty);
329    };
330    ($(#[$attr:tt])? $ty:ty) => {
331        plugin!(
332            unsafe {
333                falco_plugin::api::PLUGIN_API_VERSION_MAJOR as usize;
334                falco_plugin::api::PLUGIN_API_VERSION_MINOR as usize;
335                0
336            } => $(#[$attr])? $ty
337        );
338    };
339}
340
341/// # Automatically generate the Falco plugin API structure for static plugins
342///
343/// This macro generates a [`falco_plugin_api::plugin_api`] structure, usable as a statically
344/// linked plugin. It automatically handles all supported capabilities, so you need just one
345/// invocation, regardless of how many capabilities your plugin supports.
346///
347/// ## Basic usage
348///
349/// ```
350///# use std::ffi::CStr;
351///# use falco_plugin::base::Metric;
352/// use falco_plugin::base::Plugin;
353/// use falco_plugin::static_plugin;
354///# use falco_plugin::tables::TablesInput;
355///
356///# struct MyPlugin;
357///#
358/// impl Plugin for MyPlugin {
359///     // ...
360///#     const NAME: &'static CStr = c"sample-plugin-rs";
361///#     const PLUGIN_VERSION: &'static CStr = c"0.0.1";
362///#     const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
363///#     const CONTACT: &'static CStr = c"you@example.com";
364///#     type ConfigType = ();
365///#
366///#     fn new(input: Option<&TablesInput>, config: Self::ConfigType)
367///#         -> Result<Self, anyhow::Error> {
368///#         Ok(MyPlugin)
369///#     }
370///#
371///#     fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
372///#         Ok(())
373///#     }
374///#
375///#     fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
376///#         []
377///#     }
378/// }
379///
380/// static_plugin!(#[no_capabilities] MY_PLUGIN_API = MyPlugin);
381/// ```
382///
383/// This expands to:
384/// ```ignore
385/// #[no_mangle]
386/// static MY_PLUGIN_API: falco_plugin::api::plugin_api = /* ... */;
387/// ```
388///
389/// The symbols referred to in the API structure are still mangled according to default Rust rules.
390///
391/// ## Overriding the supported API version
392///
393/// The macro also implements a form where you can override the required API version (for example,
394/// if you wish to advertise an older version for increased compatibility):
395///
396/// ```
397///# use std::ffi::CStr;
398///# use falco_plugin::base::Metric;
399/// use falco_plugin::base::Plugin;
400/// use falco_plugin::static_plugin;
401///# use falco_plugin::tables::TablesInput;
402///
403///# struct MyPlugin;
404///#
405/// impl Plugin for MyPlugin {
406///     // ...
407///#     const NAME: &'static CStr = c"sample-plugin-rs";
408///#     const PLUGIN_VERSION: &'static CStr = c"0.0.1";
409///#     const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
410///#     const CONTACT: &'static CStr = c"you@example.com";
411///#     type ConfigType = ();
412///#
413///#     fn new(input: Option<&TablesInput>, config: Self::ConfigType)
414///#         -> Result<Self, anyhow::Error> {
415///#         Ok(MyPlugin)
416///#     }
417///#
418///#     fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
419///#         Ok(())
420///#     }
421///#
422///#     fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
423///#         []
424///#     }
425/// }
426///
427/// // advertise API version 3.3.0
428/// static_plugin!(MY_PLUGIN_API @ unsafe { 3;3;0 } = #[no_capabilities] MyPlugin);
429/// ```
430///
431/// **Note**: this does not affect the actual version supported in any way. If you use this form,
432/// it's **entirely your responsibility** to ensure the advertised version is compatible with the actual
433/// version supported by this crate.
434#[macro_export]
435macro_rules! static_plugin {
436    ($(#[$attr:tt])? $name:ident = $ty:ty) => {
437        static_plugin!(
438            $name @ unsafe {
439                falco_plugin::api::PLUGIN_API_VERSION_MAJOR as usize;
440                falco_plugin::api::PLUGIN_API_VERSION_MINOR as usize;
441                0
442            }
443            = $(#[$attr])? $ty
444        );
445    };
446    ($name:ident @ unsafe { $maj:expr; $min:expr; $patch:expr } = #[no_capabilities] $ty:ty) => {
447        #[no_mangle]
448        static $name: falco_plugin::api::plugin_api = const {
449            $crate::base_plugin_ffi_wrappers!($maj; $min; $patch => #[deny(dead_code)] $ty);
450            __plugin_base_api()
451        };
452
453        // a static plugin automatically exports all capabilities
454        unsafe impl $crate::internals::base::wrappers::BasePluginExported for $ty {}
455        unsafe impl $crate::internals::async_event::wrappers::AsyncPluginExported for $ty {}
456        unsafe impl $crate::internals::extract::wrappers::ExtractPluginExported for $ty {}
457        unsafe impl $crate::internals::listen::wrappers::CaptureListenPluginExported for $ty {}
458        unsafe impl $crate::internals::parse::wrappers::ParsePluginExported for $ty {}
459        unsafe impl $crate::internals::source::wrappers::SourcePluginExported for $ty {}
460    };
461    ($name:ident @ unsafe { $maj:expr; $min:expr; $patch:expr } = $ty:ty) => {
462        static_plugin!($name @ unsafe { $maj; $min; $patch } = #[no_capabilities] $ty);
463
464        $crate::ensure_plugin_capabilities!($ty);
465    }
466}
467
468#[doc(hidden)]
469#[macro_export]
470macro_rules! ensure_plugin_capabilities {
471    ($ty:ty) => {
472        const _: () = {
473            use $crate::internals::async_event::wrappers::AsyncPluginFallbackApi;
474            use $crate::internals::extract::wrappers::ExtractPluginFallbackApi;
475            use $crate::internals::listen::wrappers::CaptureListenFallbackApi;
476            use $crate::internals::parse::wrappers::ParsePluginFallbackApi;
477            use $crate::internals::source::wrappers::SourcePluginFallbackApi;
478
479            let impls_async =
480                $crate::internals::async_event::wrappers::AsyncPluginApi::<$ty>::IMPLEMENTS_ASYNC;
481            let impls_extract =
482                $crate::internals::extract::wrappers::ExtractPluginApi::<$ty>::IMPLEMENTS_EXTRACT;
483            let impls_listen =
484                $crate::internals::listen::wrappers::CaptureListenApi::<$ty>::IMPLEMENTS_LISTEN;
485            let impls_parse =
486                $crate::internals::parse::wrappers::ParsePluginApi::<$ty>::IMPLEMENTS_PARSE;
487            let impls_source =
488                $crate::internals::source::wrappers::SourcePluginApi::<$ty>::IMPLEMENTS_SOURCE;
489
490            assert!(
491                impls_async || impls_extract || impls_listen || impls_parse || impls_source,
492                "Plugin must implement at least one capability. If you really want a plugin without capabilities, use the #[no_capabilities] attribute"
493            );
494        };
495    };
496}
497
498#[doc(hidden)]
499#[macro_export]
500macro_rules! base_plugin_ffi_wrappers {
501    ($maj:expr; $min:expr; $patch:expr => #[$attr:meta] $ty:ty) => {
502        #[$attr]
503        pub extern "C-unwind" fn plugin_get_required_api_version() -> *const std::ffi::c_char {
504            $crate::internals::base::wrappers::plugin_get_required_api_version::<
505                { $maj },
506                { $min },
507                { $patch },
508            >()
509        }
510
511        $crate::wrap_ffi! {
512            #[$attr]
513            use $crate::internals::base::wrappers: <$ty>;
514
515            unsafe fn plugin_get_version() -> *const std::ffi::c_char;
516            unsafe fn plugin_get_name() -> *const std::ffi::c_char;
517            unsafe fn plugin_get_description() -> *const std::ffi::c_char;
518            unsafe fn plugin_get_contact() -> *const std::ffi::c_char;
519            unsafe fn plugin_get_init_schema(schema_type: *mut u32) -> *const std::ffi::c_char;
520            unsafe fn plugin_init(
521                args: *const falco_plugin::api::ss_plugin_init_input,
522                rc: *mut i32,
523            ) -> *mut falco_plugin::api::ss_plugin_t;
524            unsafe fn plugin_destroy(plugin: *mut falco_plugin::api::ss_plugin_t) -> ();
525            unsafe fn plugin_get_last_error(
526                plugin: *mut falco_plugin::api::ss_plugin_t,
527            ) -> *const std::ffi::c_char;
528            unsafe fn plugin_set_config(
529                plugin: *mut falco_plugin::api::ss_plugin_t,
530                config_input: *const falco_plugin::api::ss_plugin_set_config_input,
531            ) -> falco_plugin::api::ss_plugin_rc;
532            unsafe fn plugin_get_metrics(
533                plugin: *mut falco_plugin::api::ss_plugin_t,
534                num_metrics: *mut u32,
535            ) -> *mut falco_plugin::api::ss_plugin_metric;
536        }
537
538        #[allow(dead_code)]
539        pub const fn __plugin_base_api() -> falco_plugin::api::plugin_api {
540            use $crate::internals::async_event::wrappers::AsyncPluginFallbackApi;
541            use $crate::internals::extract::wrappers::ExtractPluginFallbackApi;
542            use $crate::internals::listen::wrappers::CaptureListenFallbackApi;
543            use $crate::internals::parse::wrappers::ParsePluginFallbackApi;
544            use $crate::internals::source::wrappers::SourcePluginFallbackApi;
545            falco_plugin::api::plugin_api {
546                get_required_api_version: Some(plugin_get_required_api_version),
547                get_version: Some(plugin_get_version),
548                get_name: Some(plugin_get_name),
549                get_description: Some(plugin_get_description),
550                get_contact: Some(plugin_get_contact),
551                get_init_schema: Some(plugin_get_init_schema),
552                init: Some(plugin_init),
553                destroy: Some(plugin_destroy),
554                get_last_error: Some(plugin_get_last_error),
555                __bindgen_anon_1:
556                    $crate::internals::source::wrappers::SourcePluginApi::<$ty>::SOURCE_API,
557                __bindgen_anon_2:
558                    $crate::internals::extract::wrappers::ExtractPluginApi::<$ty>::EXTRACT_API,
559                __bindgen_anon_3:
560                    $crate::internals::parse::wrappers::ParsePluginApi::<$ty>::PARSE_API,
561                __bindgen_anon_4:
562                    $crate::internals::async_event::wrappers::AsyncPluginApi::<$ty>::ASYNC_API,
563                __bindgen_anon_5:
564                    $crate::internals::listen::wrappers::CaptureListenApi::<$ty>::LISTEN_API,
565                set_config: Some(plugin_set_config),
566                get_metrics: Some(plugin_get_metrics),
567            }
568        }
569    };
570}