1use crate::base::logger::{FalcoPluginLoggerImpl, FALCO_LOGGER};
2use crate::base::schema::{ConfigSchema, ConfigSchemaType};
3use crate::base::Plugin;
4use crate::error::ffi_result::FfiResult;
5use crate::error::last_error::LastError;
6use crate::strings::from_ptr::try_str_from_ptr;
7use crate::strings::WriteIntoCString;
8use crate::tables::TablesInput;
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::fmt::Display;
17use std::io::Write;
18use std::sync::Mutex;
19
20#[diagnostic::on_unimplemented(
28 message = "Plugin is not exported",
29 note = "use either `plugin!` or `static_plugin!`"
30)]
31pub unsafe trait BasePluginExported {}
32
33pub extern "C-unwind" fn plugin_get_required_api_version<
34 const MAJOR: usize,
35 const MINOR: usize,
36 const PATCH: usize,
37>() -> *const c_char {
38 static VERSIONS: Mutex<BTreeMap<(usize, usize, usize), CString>> = Mutex::new(BTreeMap::new());
39
40 let mut version = VERSIONS.lock().unwrap();
41 version
44 .entry((MAJOR, MINOR, PATCH))
45 .or_insert_with(|| {
46 let version = format!("{MAJOR}.{MINOR}.{PATCH}");
47 CString::new(version).unwrap()
48 })
49 .as_ptr()
50}
51
52pub extern "C-unwind" fn plugin_get_version<T: Plugin>() -> *const c_char {
53 T::PLUGIN_VERSION.as_ptr()
54}
55
56pub extern "C-unwind" fn plugin_get_name<T: Plugin>() -> *const c_char {
57 T::NAME.as_ptr()
58}
59
60pub extern "C-unwind" fn plugin_get_description<T: Plugin>() -> *const c_char {
61 T::DESCRIPTION.as_ptr()
62}
63
64pub extern "C-unwind" fn plugin_get_contact<T: Plugin>() -> *const c_char {
65 T::CONTACT.as_ptr()
66}
67
68pub unsafe extern "C-unwind" fn plugin_init<P: Plugin>(
72 init_input: *const ss_plugin_init_input,
73 rc: *mut ss_plugin_rc,
74) -> *mut falco_plugin_api::ss_plugin_t {
75 let res = (|| -> Result<*mut PluginWrapper<P>, anyhow::Error> {
76 let init_input = unsafe { init_input.as_ref() }
77 .ok_or_else(|| anyhow::anyhow!("Got empty init_input"))?;
78
79 let init_config =
80 try_str_from_ptr(&init_input.config).context("Failed to get config string")?;
81
82 let config = P::ConfigType::from_str(init_config).context("Failed to parse config")?;
83 if let Some(log_fn) = init_input.log_fn {
84 let logger_impl = FalcoPluginLoggerImpl {
85 owner: init_input.owner,
86 logger_fn: log_fn,
87 };
88
89 *FALCO_LOGGER.inner.write().unwrap() = Some(logger_impl);
90 log::set_logger(&FALCO_LOGGER).ok();
91
92 #[cfg(debug_assertions)]
93 log::set_max_level(log::LevelFilter::Trace);
94
95 #[cfg(not(debug_assertions))]
96 log::set_max_level(log::LevelFilter::Info);
97 }
98
99 let tables_input =
100 TablesInput::try_from(init_input).context("Failed to build tables input")?;
101
102 let last_error = unsafe { LastError::from(init_input)? };
103
104 P::new(tables_input.as_ref(), config)
105 .map(|plugin| Box::into_raw(Box::new(PluginWrapper::new(plugin, last_error))))
106 })();
107
108 match res {
109 Ok(plugin) => {
110 unsafe {
111 *rc = ss_plugin_rc_SS_PLUGIN_SUCCESS;
112 }
113 plugin.cast()
114 }
115 Err(e) => {
116 let error_str = format!("{:#}", &e);
117 log::error!("Failed to initialize plugin: {error_str}");
118 let plugin = Box::new(PluginWrapper::<P>::new_error(error_str));
119 unsafe {
120 *rc = e.status_code();
121 }
122 Box::into_raw(plugin).cast()
123 }
124 }
125}
126
127pub unsafe extern "C-unwind" fn plugin_get_init_schema<P: Plugin>(
131 schema_type: *mut falco_plugin_api::ss_plugin_schema_type,
132) -> *const c_char {
133 let schema_type = unsafe {
134 let Some(schema_type) = schema_type.as_mut() else {
135 return std::ptr::null();
136 };
137 schema_type
138 };
139 match P::ConfigType::get_schema() {
140 ConfigSchemaType::None => {
141 *schema_type = falco_plugin_api::ss_plugin_schema_type_SS_PLUGIN_SCHEMA_NONE;
142 std::ptr::null()
143 }
144 ConfigSchemaType::Json(s) => {
145 *schema_type = falco_plugin_api::ss_plugin_schema_type_SS_PLUGIN_SCHEMA_JSON;
146 s.as_ptr()
147 }
148 }
149}
150
151pub unsafe extern "C-unwind" fn plugin_destroy<P: Plugin>(
155 plugin: *mut falco_plugin_api::ss_plugin_t,
156) {
157 unsafe {
158 let plugin = plugin as *mut PluginWrapper<P>;
159 let _ = Box::from_raw(plugin);
160 }
161}
162
163pub unsafe extern "C-unwind" fn plugin_get_last_error<P: Plugin>(
167 plugin: *mut falco_plugin_api::ss_plugin_t,
168) -> *const c_char {
169 let plugin = plugin as *mut PluginWrapper<P>;
170 match unsafe { plugin.as_mut() } {
171 Some(plugin) => plugin.error_buf.as_ptr(),
172 None => c"no instance".as_ptr(),
173 }
174}
175
176pub unsafe extern "C-unwind" fn plugin_set_config<P: Plugin>(
177 plugin: *mut falco_plugin_api::ss_plugin_t,
178 config_input: *const falco_plugin_api::ss_plugin_set_config_input,
179) -> falco_plugin_api::ss_plugin_rc {
180 let plugin = plugin as *mut PluginWrapper<P>;
181 let plugin = unsafe {
182 let Some(plugin) = plugin.as_mut() else {
183 return ss_plugin_rc_SS_PLUGIN_FAILURE;
184 };
185 plugin
186 };
187
188 let Some(actual_plugin) = &mut plugin.plugin else {
189 return ss_plugin_rc_SS_PLUGIN_FAILURE;
190 };
191
192 let res = (|| -> Result<(), anyhow::Error> {
193 let config_input = unsafe { config_input.as_ref() }.context("Got NULL config")?;
194
195 let updated_config =
196 try_str_from_ptr(&config_input.config).context("Failed to get config string")?;
197 let config = P::ConfigType::from_str(updated_config).context("Failed to parse config")?;
198
199 actual_plugin.plugin.set_config(config)
200 })();
201
202 res.rc(&mut plugin.error_buf)
203}
204
205pub unsafe extern "C-unwind" fn plugin_get_metrics<P: Plugin>(
206 plugin: *mut ss_plugin_t,
207 num_metrics: *mut u32,
208) -> *mut ss_plugin_metric {
209 let plugin = plugin as *mut PluginWrapper<P>;
210 let num_metrics = unsafe {
211 let Some(num_metrics) = num_metrics.as_mut() else {
212 return std::ptr::null_mut();
213 };
214 num_metrics
215 };
216
217 let plugin = unsafe {
218 let Some(plugin) = plugin.as_mut() else {
219 *num_metrics = 0;
220 return std::ptr::null_mut();
221 };
222 plugin
223 };
224
225 let Some(actual_plugin) = &mut plugin.plugin else {
226 *num_metrics = 0;
227 return std::ptr::null_mut();
228 };
229
230 plugin.metric_storage.clear();
231 for metric in actual_plugin.plugin.get_metrics() {
232 plugin.metric_storage.push(metric.as_raw());
233 }
234
235 *num_metrics = plugin.metric_storage.len() as u32;
236 plugin.metric_storage.as_ptr().cast_mut()
237}
238
239pub extern "C-unwind" fn plugin_get_required_event_schema_version<T: Plugin>(
240 _plugin: *mut ss_plugin_t,
241) -> *const c_char {
242 T::SCHEMA_VERSION.as_ptr()
243}
244
245#[doc(hidden)]
246#[macro_export]
247macro_rules! wrap_ffi {
248 (
249 #[$attr:meta]
250 use $mod:path: <$ty:ty>;
251
252 $(unsafe fn $name:ident( $($param:ident: $param_ty:ty),* $(,)*) -> $ret:ty;)*
253 ) => {
254 $(
255 #[$attr]
256 pub unsafe extern "C-unwind" fn $name ( $($param: $param_ty),*) -> $ret {
257 use $mod as wrappers;
258
259 wrappers::$name::<$ty>($($param),*)
260 }
261 )*
262 }
263}
264
265#[macro_export]
344macro_rules! plugin {
345 (unsafe { $maj:expr; $min:expr; $patch:expr } => #[no_capabilities] $ty:ty) => {
346 unsafe impl $crate::base::wrappers::BasePluginExported for $ty {}
347
348 $crate::base_plugin_ffi_wrappers!($maj; $min; $patch => #[unsafe(no_mangle)] $ty);
349 };
350 (unsafe { $maj:expr; $min:expr; $patch:expr } => $ty:ty) => {
351 plugin!(unsafe {$maj; $min; $patch} => #[no_capabilities] $ty);
352
353 $crate::ensure_plugin_capabilities!($ty);
354 };
355 ($(#[$attr:tt])? $ty:ty) => {
356 plugin!(
357 unsafe {
358 falco_plugin::api::PLUGIN_API_VERSION_MAJOR as usize;
359 falco_plugin::api::PLUGIN_API_VERSION_MINOR as usize;
360 0
361 } => $(#[$attr])? $ty
362 );
363 };
364}
365
366#[macro_export]
460macro_rules! static_plugin {
461 ($(#[$attr:tt])? $vis:vis $name:ident = $ty:ty) => {
462 static_plugin!(
463 $vis $name @ unsafe {
464 falco_plugin::api::PLUGIN_API_VERSION_MAJOR as usize;
465 falco_plugin::api::PLUGIN_API_VERSION_MINOR as usize;
466 0
467 }
468 = $(#[$attr])? $ty
469 );
470 };
471 ($vis:vis $name:ident @ unsafe { $maj:expr; $min:expr; $patch:expr } = #[no_capabilities] $ty:ty) => {
472 #[unsafe(no_mangle)]
473 $vis static $name: falco_plugin::api::plugin_api = const {
474 $crate::base_plugin_ffi_wrappers!($maj; $min; $patch => #[deny(dead_code)] $ty);
475 __plugin_base_api()
476 };
477
478 unsafe impl $crate::base::wrappers::BasePluginExported for $ty {}
480 unsafe impl $crate::async_event::wrappers::AsyncPluginExported for $ty {}
481 unsafe impl $crate::extract::wrappers::ExtractPluginExported for $ty {}
482 unsafe impl $crate::listen::wrappers::CaptureListenPluginExported for $ty {}
483 unsafe impl $crate::parse::wrappers::ParsePluginExported for $ty {}
484 unsafe impl $crate::source::wrappers::SourcePluginExported for $ty {}
485 };
486 ($vis:vis $name:ident @ unsafe { $maj:expr; $min:expr; $patch:expr } = $ty:ty) => {
487 static_plugin!($vis $name @ unsafe { $maj; $min; $patch } = #[no_capabilities] $ty);
488
489 $crate::ensure_plugin_capabilities!($ty);
490 }
491}
492
493#[doc(hidden)]
494#[macro_export]
495macro_rules! ensure_plugin_capabilities {
496 ($ty:ty) => {
497 const _: () = {
498 use $crate::async_event::wrappers::AsyncPluginFallbackApi;
499 use $crate::extract::wrappers::ExtractPluginFallbackApi;
500 use $crate::listen::wrappers::CaptureListenFallbackApi;
501 use $crate::parse::wrappers::ParsePluginFallbackApi;
502 use $crate::source::wrappers::SourcePluginFallbackApi;
503
504 let impls_async =
505 $crate::async_event::wrappers::AsyncPluginApi::<$ty>::IMPLEMENTS_ASYNC;
506 let impls_extract =
507 $crate::extract::wrappers::ExtractPluginApi::<$ty>::IMPLEMENTS_EXTRACT;
508 let impls_listen =
509 $crate::listen::wrappers::CaptureListenApi::<$ty>::IMPLEMENTS_LISTEN;
510 let impls_parse =
511 $crate::parse::wrappers::ParsePluginApi::<$ty>::IMPLEMENTS_PARSE;
512 let impls_source =
513 $crate::source::wrappers::SourcePluginApi::<$ty>::IMPLEMENTS_SOURCE;
514
515 assert!(
516 impls_async || impls_extract || impls_listen || impls_parse || impls_source,
517 "Plugin must implement at least one capability. If you really want a plugin without capabilities, use the #[no_capabilities] attribute"
518 );
519 };
520 };
521}
522
523#[doc(hidden)]
524#[macro_export]
525macro_rules! base_plugin_ffi_wrappers {
526 ($maj:expr; $min:expr; $patch:expr => #[$attr:meta] $ty:ty) => {
527 #[$attr]
528 pub extern "C-unwind" fn plugin_get_required_api_version() -> *const std::ffi::c_char {
529 $crate::base::wrappers::plugin_get_required_api_version::<
530 { $maj },
531 { $min },
532 { $patch },
533 >()
534 }
535
536 $crate::wrap_ffi! {
537 #[$attr]
538 use $crate::base::wrappers: <$ty>;
539
540 unsafe fn plugin_get_version() -> *const std::ffi::c_char;
541 unsafe fn plugin_get_name() -> *const std::ffi::c_char;
542 unsafe fn plugin_get_description() -> *const std::ffi::c_char;
543 unsafe fn plugin_get_contact() -> *const std::ffi::c_char;
544 unsafe fn plugin_get_init_schema(schema_type: *mut u32) -> *const std::ffi::c_char;
545 unsafe fn plugin_init(
546 args: *const falco_plugin::api::ss_plugin_init_input,
547 rc: *mut i32,
548 ) -> *mut falco_plugin::api::ss_plugin_t;
549 unsafe fn plugin_destroy(plugin: *mut falco_plugin::api::ss_plugin_t) -> ();
550 unsafe fn plugin_get_last_error(
551 plugin: *mut falco_plugin::api::ss_plugin_t,
552 ) -> *const std::ffi::c_char;
553 unsafe fn plugin_set_config(
554 plugin: *mut falco_plugin::api::ss_plugin_t,
555 config_input: *const falco_plugin::api::ss_plugin_set_config_input,
556 ) -> falco_plugin::api::ss_plugin_rc;
557 unsafe fn plugin_get_metrics(
558 plugin: *mut falco_plugin::api::ss_plugin_t,
559 num_metrics: *mut u32,
560 ) -> *mut falco_plugin::api::ss_plugin_metric;
561 unsafe fn plugin_get_required_event_schema_version(
562 plugin: *mut falco_plugin::api::ss_plugin_t
563 ) -> *const std::ffi::c_char;
564 }
565
566 #[allow(dead_code)]
567 pub const fn __plugin_base_api() -> falco_plugin::api::plugin_api {
568 use $crate::async_event::wrappers::AsyncPluginFallbackApi;
569 use $crate::extract::wrappers::ExtractPluginFallbackApi;
570 use $crate::listen::wrappers::CaptureListenFallbackApi;
571 use $crate::parse::wrappers::ParsePluginFallbackApi;
572 use $crate::source::wrappers::SourcePluginFallbackApi;
573 falco_plugin::api::plugin_api {
574 get_required_api_version: Some(plugin_get_required_api_version),
575 get_version: Some(plugin_get_version),
576 get_name: Some(plugin_get_name),
577 get_description: Some(plugin_get_description),
578 get_contact: Some(plugin_get_contact),
579 get_init_schema: Some(plugin_get_init_schema),
580 init: Some(plugin_init),
581 destroy: Some(plugin_destroy),
582 get_last_error: Some(plugin_get_last_error),
583 __bindgen_anon_1:
584 $crate::source::wrappers::SourcePluginApi::<$ty>::SOURCE_API,
585 __bindgen_anon_2:
586 $crate::extract::wrappers::ExtractPluginApi::<$ty>::EXTRACT_API,
587 __bindgen_anon_3:
588 $crate::parse::wrappers::ParsePluginApi::<$ty>::PARSE_API,
589 __bindgen_anon_4:
590 $crate::async_event::wrappers::AsyncPluginApi::<$ty>::ASYNC_API,
591 __bindgen_anon_5:
592 $crate::listen::wrappers::CaptureListenApi::<$ty>::LISTEN_API,
593 set_config: Some(plugin_set_config),
594 get_metrics: Some(plugin_get_metrics),
595 get_required_event_schema_version: Some(plugin_get_required_event_schema_version),
596 }
597 }
598 };
599}
600
601pub(crate) struct ActualPlugin<P: Plugin> {
602 pub(crate) plugin: P,
603 pub(crate) last_error: LastError,
604}
605
606#[doc(hidden)]
610#[allow(missing_debug_implementations)]
611pub struct PluginWrapper<P: Plugin> {
612 pub(crate) plugin: Option<ActualPlugin<P>>,
613 pub(crate) error_buf: CString,
614 pub(crate) field_storage: bumpalo::Bump,
615 pub(crate) string_storage: CString,
616 pub(crate) metric_storage: Vec<ss_plugin_metric>,
617}
618
619impl<P: Plugin> PluginWrapper<P> {
620 pub fn new(plugin: P, last_error: LastError) -> Self {
621 Self {
622 plugin: Some(ActualPlugin { plugin, last_error }),
623 error_buf: Default::default(),
624 field_storage: bumpalo::Bump::new(),
625 string_storage: Default::default(),
626 metric_storage: Default::default(),
627 }
628 }
629
630 pub fn new_error(err: impl Display) -> Self {
631 let mut plugin = Self {
632 plugin: None,
633 error_buf: Default::default(),
634 field_storage: bumpalo::Bump::new(),
635 string_storage: Default::default(),
636 metric_storage: vec![],
637 };
638
639 plugin
640 .error_buf
641 .write_into(|buf| write!(buf, "{err}"))
642 .unwrap_or_else(|err| panic!("Failed to write error message (was: {err})"));
643
644 plugin
645 }
646}