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