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