use std::collections::HashMap;
use std::os::raw::c_char;
use std::rc::Rc;
use libloading::{Library, Symbol};
use snafu::ResultExt;
use crate::error::Error;
use crate::ffi::hash::HashInterface;
use crate::ffi::hash::create_hash_interface;
use crate::ffi::utils::cstring;
use crate::options::Options;
use crate::{Confium, Plugin, Provider, Result};
use std::env::consts::DLL_EXTENSION;
use std::path::PathBuf;
#[derive(Debug)]
pub enum PluginInterface {
Hash(HashInterface),
}
type InterfaceVersionFn = extern "C" fn(*mut Confium) -> u32;
const INTERFACE_VERSION_FN_NAME: &[u8] = b"cfmp_interface_version\0";
type InitializeFnV0 = extern "C" fn(*mut Confium, opts: *const Options) -> u32;
const INITIALIZE_FN_V0_NAME: &[u8] = b"cfmp_initialize\0";
type FinalizeFnV0 = extern "C" fn(*mut Confium) -> u32;
const FINALIZE_FN_V0_NAME: &[u8] = b"cfmp_finalize\0";
type QueryInterfacesFnV0 = extern "C" fn(*mut Confium) -> *const u8;
const QUERY_INTERFACES_FN_V0_NAME: &[u8] = b"cfmp_query_interfaces\0";
pub struct PluginV0 {
finalize: Box<FinalizeFnV0>,
query_interfaces: Box<QueryInterfacesFnV0>,
}
pub enum PluginVTable {
V0(PluginV0),
}
macro_rules! check_not_null {
($param:ident) => {{
if $param.is_null() {
return $crate::error::NullPointerSnafu {
param: stringify!($param),
}
.fail();
}
}};
}
pub(crate) fn get_plugin_symbol<T>(
lib: &Library,
name: &str,
symbol: &'static [u8],
) -> Result<Box<T>>
where
T: Copy,
{
let func: Symbol<T> = unsafe { lib.get::<T>(symbol) }
.context(crate::error::PluginSymbolSnafu { name, symbol })?;
Ok(Box::new(*func))
}
fn load_plugin_v0(
cfm: &mut Confium,
name: &str,
lib: Library,
opts: &mut Options,
) -> Result<Plugin> {
let initialize = get_plugin_symbol::<InitializeFnV0>(&lib, name, INITIALIZE_FN_V0_NAME)?;
let finalize = get_plugin_symbol::<FinalizeFnV0>(&lib, name, FINALIZE_FN_V0_NAME)?;
let query_interfaces =
get_plugin_symbol::<QueryInterfacesFnV0>(&lib, name, QUERY_INTERFACES_FN_V0_NAME)?;
let code = initialize(cfm, opts);
if code != 0 {
return crate::error::PluginInternalSnafu { name, code }.fail();
}
let vtable = PluginVTable::V0(PluginV0 {
finalize,
query_interfaces,
});
Ok(Plugin {
library: Rc::new(lib),
vtable,
interfaces: Vec::new(),
})
}
type InterfaceList = HashMap<String, Vec<u8>>;
fn enumerate_plugin_interfaces(cfm: &mut Confium, vtable: &PluginVTable) -> Result<InterfaceList> {
let mut list = InterfaceList::new();
match vtable {
PluginVTable::V0(v0) => {
let ifs = (*v0.query_interfaces)(cfm);
let mut idx: usize = 0;
loop {
let start = idx;
let mut end = start;
while unsafe { *(ifs.add(end)) } != 0 {
end += 1;
}
let name = unsafe { std::slice::from_raw_parts(ifs.add(start), end - start) };
let name = std::str::from_utf8(name).context(crate::error::InvalidUTF8Snafu {})?;
if name.is_empty() {
break;
}
let version = unsafe { *ifs.add(end + 1) };
if !list.contains_key(name) {
list.insert(name.to_string(), Vec::<u8>::new());
}
let iflist = list.get_mut(name).unwrap();
iflist.push(version);
idx = end + 2;
}
}
}
for versions in list.values_mut() {
versions.sort();
}
Ok(list)
}
fn create_plugin_interface(
_cfm: &mut Confium,
lib: &Library,
name: &str,
versions: &[u8],
) -> Result<Option<PluginInterface>> {
for version in versions.iter().rev() {
match name {
"hash" => {
if let Some(iface) = create_hash_interface(lib, name, *version)? {
return Ok(Some(PluginInterface::Hash(iface)));
}
}
_ => continue,
}
}
Ok(None)
}
fn load_plugin_interfaces(
cfm: &mut Confium,
lib: &Library,
vtable: &PluginVTable,
) -> Result<Vec<Rc<PluginInterface>>> {
let mut interfaces = Vec::new();
let advertised_ifs = enumerate_plugin_interfaces(cfm, vtable)?;
for (name, versions) in advertised_ifs {
if let Some(iface) = create_plugin_interface(cfm, lib, &name, &versions)? {
interfaces.push(Rc::new(iface));
}
}
Ok(interfaces)
}
fn finalize_plugin(cfm: &mut Confium, plugin: &Plugin) {
match &plugin.vtable {
PluginVTable::V0(v0) => {
(*v0.finalize)(cfm);
}
}
}
fn plugin_load_lib(name: &str, paths: &[PathBuf]) -> Result<libloading::Library> {
let mut last_err: Option<libloading::Error> = None;
for path in paths {
match unsafe { Library::new(path) } {
Ok(lib) => return Ok(lib),
Err(e) => last_err = Some(e),
}
}
let source = last_err.expect("paths must contain at least one element");
Err(crate::error::Error::PluginLoadFailed {
name: name.to_string(),
source,
})
}
fn cfm_plugin_load_(
cfm: *mut Confium,
c_name: *const c_char,
c_path: *const c_char,
opts: *mut Options,
) -> Result<()> {
check_not_null!(cfm);
check_not_null!(c_name);
check_not_null!(c_path);
let cfm = unsafe { &mut *cfm };
let name = cstring(c_name)?;
for provider in &cfm.providers {
if provider.name == name {
return crate::error::PluginNameCollisionSnafu { name }.fail();
}
}
let path = PathBuf::from(cstring(c_path)?);
let mut paths: Vec<PathBuf> = vec![path.clone()];
if path.extension().and_then(std::ffi::OsStr::to_str) != Some(DLL_EXTENSION) {
let path_with_ext = path.with_extension(DLL_EXTENSION);
paths.push(path_with_ext.clone());
if let Some(filename) = path_with_ext.file_name() {
let mut prefixed_filename = std::ffi::OsString::new();
prefixed_filename.push("lib");
prefixed_filename.push(filename);
paths.push(path_with_ext.with_file_name(&prefixed_filename));
}
}
let lib = plugin_load_lib(&name, &paths)?;
let plugin_iface_ver =
get_plugin_symbol::<InterfaceVersionFn>(&lib, &name, INTERFACE_VERSION_FN_NAME)?;
let plugin = match plugin_iface_ver(cfm) {
0 => load_plugin_v0(cfm, &name, lib, unsafe { &mut *opts })?,
_ => return crate::error::PluginInterfaceVersionUnsupportedSnafu { name }.fail(),
};
let mut plugin = plugin;
plugin.interfaces =
load_plugin_interfaces(cfm, &plugin.library, &plugin.vtable).inspect_err(|_e| {
finalize_plugin(cfm, &plugin);
})?;
cfm.providers.push(Provider { name, plugin });
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn cfm_plugin_load(
cfm: *mut Confium,
c_name: *const c_char,
c_path: *const c_char,
opts: *mut Options,
errptr: *mut *mut Error,
) -> u32 {
cfm_plugin_load_(cfm, c_name, c_path, opts).map_or_else(|e| ffi_return_err!(e, errptr), |_| 0)
}
#[unsafe(no_mangle)]
pub extern "C" fn cfm_plugin_unload(_cfm: *mut Confium, _c_name: *const c_char) -> u32 {
unimplemented!();
}