#![allow(unsafe_code)]
use std::path::Path;
use std::sync::Arc;
use rich_plugin_api::abi::{
read_descriptor, AbiOutput, AbiStr, PluginDescriptor, PluginFunctions, DYLIB_ENTRY_SYMBOL,
STATUS_OK,
};
use super::{AbiBackend, LoadError, RuntimeKind, RuntimePlugin, MAX_OUTPUT_BYTES};
struct NativeBackend {
vtable: PluginFunctions,
_library: libloading::Library,
}
impl AbiBackend for NativeBackend {
fn call(&self, capability: usize, input: &str, width: u32) -> Result<String, String> {
let mut output = AbiOutput::empty();
let status =
unsafe { (self.vtable.call)(capability, AbiStr::new(input), width, &mut output) };
let text = if output.ptr.is_null() {
Err("the plugin returned no output".to_string())
} else if output.len > MAX_OUTPUT_BYTES {
Err(format!(
"the plugin returned {} bytes; at most {MAX_OUTPUT_BYTES} are accepted",
output.len
))
} else {
let bytes = unsafe { std::slice::from_raw_parts(output.ptr, output.len) };
String::from_utf8(bytes.to_vec()).map_err(|_| "the plugin returned non-UTF-8".into())
};
if !output.ptr.is_null() {
unsafe { (self.vtable.free)(output) };
}
match (status, text) {
(STATUS_OK, text) => text,
(_, Ok(message)) => Err(message),
(_, Err(error)) => Err(error),
}
}
}
pub fn load_native(path: &Path) -> Result<RuntimePlugin, LoadError> {
let path = std::fs::canonicalize(path).map_err(|error| LoadError::Io {
path: path.to_path_buf(),
message: error.to_string(),
})?;
let library =
unsafe { libloading::Library::new(&path) }.map_err(|error| LoadError::NotAPlugin {
path: path.clone(),
message: error_chain(&error),
})?;
let descriptor = {
let entry: libloading::Symbol<unsafe extern "C" fn() -> *const PluginDescriptor> = unsafe {
library.get(DYLIB_ENTRY_SYMBOL.as_bytes())
}
.map_err(|_| LoadError::NotAPlugin {
path: path.clone(),
message: format!("it does not export {DYLIB_ENTRY_SYMBOL}"),
})?;
unsafe { entry() }
};
let (abi, vtable) = unsafe { read_descriptor(descriptor) }.map_err(|error| LoadError::Abi {
path: path.clone(),
error,
})?;
let backend = NativeBackend {
vtable,
_library: library,
};
RuntimePlugin::new(abi, RuntimeKind::Native, path, Arc::new(backend))
}
fn error_chain(error: &dyn std::error::Error) -> String {
let mut message = error.to_string();
let mut source = error.source();
while let Some(cause) = source {
let text = cause.to_string();
if !message.contains(&text) {
message.push_str(": ");
message.push_str(&text);
}
source = cause.source();
}
message
}