From f4961c7f952a3ec20687473fe7fa2061c57fc946 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 18 Feb 2026 22:49:12 +0300 Subject: [PATCH] scanner: make plugin interface ffi-safe with handle-based registry Signed-off-by: NotAShelf Change-Id: I8e4790db3cc29f84f4e0d7d8eff36c2c6a6a6964 --- pscand-cli/Cargo.toml | 3 -- pscand-cli/src/main.rs | 15 ++++++-- pscand-core/src/lib.rs | 4 ++- pscand-core/src/scanner.rs | 58 +++++++++++++++++++++++++----- pscand-macros/src/lib.rs | 10 +++--- scanners/scanner-power/src/lib.rs | 16 +++++---- scanners/scanner-proc/src/lib.rs | 10 +++--- scanners/scanner-sensor/src/lib.rs | 7 ++-- scanners/scanner-system/src/lib.rs | 14 +++----- 9 files changed, 93 insertions(+), 44 deletions(-) diff --git a/pscand-cli/Cargo.toml b/pscand-cli/Cargo.toml index ab41096..362bf59 100644 --- a/pscand-cli/Cargo.toml +++ b/pscand-cli/Cargo.toml @@ -26,6 +26,3 @@ ringbuf.workspace = true dirs.workspace = true sysinfo.workspace = true clap.workspace = true - -[profile.release] -lto = true diff --git a/pscand-cli/src/main.rs b/pscand-cli/src/main.rs index 0fd6699..a9d0b8b 100644 --- a/pscand-cli/src/main.rs +++ b/pscand-cli/src/main.rs @@ -1,3 +1,5 @@ +#![allow(improper_ctypes_definitions)] + use clap::Parser; use libloading::Library; use pscand_core::logging::{LogLevel, RingBufferLogger}; @@ -10,7 +12,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio::sync::RwLock; use tokio::time::interval; -type ScannerCreator = unsafe extern "C" fn() -> Box; +type ScannerCreator = pscand_core::ScannerCreatorFfi; #[derive(Parser, Debug)] #[command( @@ -452,7 +454,16 @@ async fn load_scanners( } }; - let scanner = creator(); + let scanner = match pscand_core::get_scanner(creator()) { + Some(s) => s, + None => { + log::error!( + "Failed to get scanner from library {}", + path.display() + ); + continue; + } + }; let name = scanner.name().to_string(); let scanner_enabled = config.is_scanner_enabled(&name); diff --git a/pscand-core/src/lib.rs b/pscand-core/src/lib.rs index 1067fa9..efc05e6 100644 --- a/pscand-core/src/lib.rs +++ b/pscand-core/src/lib.rs @@ -5,5 +5,7 @@ pub mod scanner; pub use config::Config; pub use logging::{DaemonLogEntry, LogLevel, RingBufferLogger}; -pub use scanner::{MetricValue, Scanner, ScannerError}; +pub use scanner::{ + get_scanner, register_scanner, MetricValue, Scanner, ScannerCreatorFfi, ScannerError, +}; pub type Result = std::result::Result; diff --git a/pscand-core/src/scanner.rs b/pscand-core/src/scanner.rs index dccb505..96f0000 100644 --- a/pscand-core/src/scanner.rs +++ b/pscand-core/src/scanner.rs @@ -1,10 +1,24 @@ use std::collections::HashMap; +use std::os::raw::c_void; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::LazyLock; use std::sync::Mutex; use std::time::Duration; use serde::{Deserialize, Serialize}; use thiserror::Error; +pub type ScannerBox = Box; +pub type ScannerResult = Result; +pub type ScannerMetrics = HashMap; +pub type ScannerCollectionResult = Result; +pub type ScannerCollectFn = Box ScannerCollectionResult + Send + Sync>; +pub type ScannerInitFnMut = Mutex Result<()> + Send>>; +pub type ScannerCleanupFnMut = Mutex Result<()> + Send>>; +pub type ScannerCreatorFfi = unsafe extern "C" fn() -> *mut c_void; +pub type ScannerInitFn = unsafe extern "C" fn() -> *mut c_void; +pub type ScannerDropFn = unsafe extern "C" fn(*mut c_void); + #[derive(Error, Debug)] pub enum ScannerError { #[error("IO error: {0}")] @@ -43,7 +57,7 @@ impl MetricValue { MetricValue::Boolean(v) } - pub fn from_str(v: impl Into) -> Self { + pub fn from_string(v: impl Into) -> Self { MetricValue::String(v.into()) } } @@ -52,28 +66,56 @@ pub trait Scanner: Send + Sync { fn name(&self) -> &'static str; fn interval(&self) -> Duration; fn init(&mut self, config: &toml::Value) -> Result<()>; - fn collect(&self) -> Result>; + fn collect(&self) -> ScannerCollectionResult; fn cleanup(&mut self) -> Result<()>; } +static SCANNER_HANDLES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static NEXT_HANDLE: AtomicUsize = AtomicUsize::new(1); + +#[inline] +/// Register a scanner and return a handle. +/// # Safety +/// The handle must only be used with `get_scanner` from the same process. +pub unsafe fn register_scanner(scanner: ScannerBox) -> usize { + let handle = NEXT_HANDLE.fetch_add(1, Ordering::SeqCst); + SCANNER_HANDLES.lock().unwrap().insert(handle, scanner); + handle +} + +#[inline] +/// Retrieve a scanner by handle, consuming the registration. +/// # Safety +/// The handle must have been obtained from `register_scanner` in this process, +/// and the returned Box must be properly dropped. +pub unsafe fn get_scanner(handle: *mut c_void) -> Option { + let handle = handle as usize; + if handle == 0 { + None + } else { + SCANNER_HANDLES.lock().unwrap().remove(&handle) + } +} + pub trait ScannerRegistry: Send + Sync { fn list_scanners(&self) -> Vec<&'static str>; - fn get_scanner(&self, name: &str) -> Option>; + fn get_scanner(&self, name: &str) -> Option; } pub struct DynamicScanner { name: &'static str, interval: Duration, - collect_fn: Box Result> + Send + Sync>, - init_fn: Mutex Result<()> + Send>>, - cleanup_fn: Mutex Result<()> + Send>>, + collect_fn: ScannerCollectFn, + init_fn: ScannerInitFnMut, + cleanup_fn: ScannerCleanupFnMut, } impl DynamicScanner { pub fn new( name: &'static str, interval: Duration, - collect_fn: impl Fn() -> Result> + Send + Sync + 'static, + collect_fn: impl Fn() -> ScannerCollectionResult + Send + Sync + 'static, init_fn: impl FnMut(&toml::Value) -> Result<()> + Send + 'static, cleanup_fn: impl FnMut() -> Result<()> + Send + 'static, ) -> Self { @@ -101,7 +143,7 @@ impl Scanner for DynamicScanner { (init_fn)(config) } - fn collect(&self) -> Result> { + fn collect(&self) -> ScannerCollectionResult { (self.collect_fn)() } diff --git a/pscand-macros/src/lib.rs b/pscand-macros/src/lib.rs index ceb6a8a..781c792 100644 --- a/pscand-macros/src/lib.rs +++ b/pscand-macros/src/lib.rs @@ -12,7 +12,7 @@ pub fn scanner(name: TokenStream, input: TokenStream) -> TokenStream { let result = quote! { #[no_mangle] - pub extern "C" fn pscand_scanner() -> Box { + pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { struct ScannerImpl; impl pscand_core::Scanner for ScannerImpl { @@ -37,7 +37,8 @@ pub fn scanner(name: TokenStream, input: TokenStream) -> TokenStream { } } - Box::new(ScannerImpl) + let handle = unsafe { pscand_core::register_scanner(Box::new(ScannerImpl)) }; + handle as *mut std::os::raw::c_void } }; @@ -53,8 +54,9 @@ pub fn register_scanner(input: TokenStream) -> TokenStream { #input #[no_mangle] - pub extern "C" fn pscand_scanner() -> Box { - Box::new(#fn_name()) + pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { + let handle = unsafe { pscand_core::register_scanner(Box::new(#fn_name())) }; + handle as *mut std::os::raw::c_void } }; diff --git a/scanners/scanner-power/src/lib.rs b/scanners/scanner-power/src/lib.rs index 3bb53d0..e45ac1d 100644 --- a/scanners/scanner-power/src/lib.rs +++ b/scanners/scanner-power/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(improper_ctypes)] use pscand_core::helpers::PowerHelper; use pscand_core::scanner::{MetricValue, Scanner}; use std::collections::HashMap; @@ -6,9 +5,9 @@ use std::time::Duration; struct PowerScanner; -#[unsafe(no_mangle)] -pub extern "C" fn pscand_scanner() -> Box { - Box::new(PowerScanner) +#[no_mangle] +pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(PowerScanner)) as *mut std::os::raw::c_void } impl Default for PowerScanner { @@ -52,7 +51,7 @@ impl Scanner for PowerScanner { ); metrics.insert( "battery_status".to_string(), - MetricValue::from_str(&battery.status), + MetricValue::from_string(&battery.status), ); } @@ -61,7 +60,7 @@ impl Scanner for PowerScanner { if let Some(status) = info.get("status") { metrics.insert( format!("supply_{}_status", name), - MetricValue::from_str(status), + MetricValue::from_string(status), ); } if let Some(online) = info.get("online") { @@ -82,7 +81,10 @@ impl Scanner for PowerScanner { } if let Ok(state) = PowerHelper::suspend_state() { - metrics.insert("suspend_state".to_string(), MetricValue::from_str(&state)); + metrics.insert( + "suspend_state".to_string(), + MetricValue::from_string(&state), + ); } Ok(metrics) diff --git a/scanners/scanner-proc/src/lib.rs b/scanners/scanner-proc/src/lib.rs index 3ab0c26..74036c8 100644 --- a/scanners/scanner-proc/src/lib.rs +++ b/scanners/scanner-proc/src/lib.rs @@ -5,9 +5,9 @@ use std::time::Duration; struct ProcScanner; -#[unsafe(no_mangle)] -pub extern "C" fn pscand_scanner() -> Box { - Box::new(ProcScanner) +#[no_mangle] +pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(ProcScanner)) as *mut std::os::raw::c_void } impl Default for ProcScanner { @@ -72,7 +72,7 @@ impl Scanner for ProcScanner { } metrics.insert( "zombie_processes".to_string(), - MetricValue::from_str(zombie_info.join(",")), + MetricValue::from_string(zombie_info.join(",")), ); } } @@ -81,7 +81,7 @@ impl Scanner for ProcScanner { for (i, proc) in top_mem.iter().enumerate() { metrics.insert( format!("top_mem_{}_name", i + 1), - MetricValue::from_str(&proc.name), + MetricValue::from_string(&proc.name), ); metrics.insert( format!("top_mem_{}_mb", i + 1), diff --git a/scanners/scanner-sensor/src/lib.rs b/scanners/scanner-sensor/src/lib.rs index 988eee5..a2ea662 100644 --- a/scanners/scanner-sensor/src/lib.rs +++ b/scanners/scanner-sensor/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(improper_ctypes)] use pscand_core::helpers::SensorHelper; use pscand_core::scanner::{MetricValue, Scanner}; use pscand_core::Result; @@ -7,9 +6,9 @@ use std::time::Duration; struct SensorScanner; -#[unsafe(no_mangle)] -pub extern "C" fn pscand_scanner() -> Box { - Box::new(SensorScanner) +#[no_mangle] +pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(SensorScanner)) as *mut std::os::raw::c_void } impl Default for SensorScanner { diff --git a/scanners/scanner-system/src/lib.rs b/scanners/scanner-system/src/lib.rs index a97fc0a..2ff476c 100644 --- a/scanners/scanner-system/src/lib.rs +++ b/scanners/scanner-system/src/lib.rs @@ -1,23 +1,17 @@ -#![allow(improper_ctypes)] use pscand_core::helpers::{ResourceHelper, SystemHelper}; use pscand_core::scanner::{MetricValue, Scanner}; use pscand_core::Result; use std::collections::HashMap; use std::time::Duration; +#[derive(Default)] struct SystemScanner { _prev_cpu: Option>, } -impl Default for SystemScanner { - fn default() -> Self { - Self { _prev_cpu: None } - } -} - -#[unsafe(no_mangle)] -pub extern "C" fn pscand_scanner() -> Box { - Box::new(SystemScanner::default()) +#[no_mangle] +pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(SystemScanner::default())) as *mut std::os::raw::c_void } impl Scanner for SystemScanner {