use crate::cglue::*;
use cglue::trait_group::c_void;
use core::convert::{TryFrom, TryInto};
use std::prelude::v1::*;
pub mod args;
#[doc(hidden)]
pub use args::{ArgDescriptor, Args, ArgsValidator};
pub type OptionVoid = Option<&'static mut c_void>;
pub type LibArc = CArc<c_void>;
pub mod connector;
pub use connector::{
cglue_connectorinstance::*, ConnectorArgs, ConnectorDescriptor, ConnectorMiddlewareArgs,
LoadableConnector,
};
pub type ConnectorInputArg = <LoadableConnector as Loadable>::InputArg;
pub mod os;
pub use os::{
cglue_intoprocessinstance::*, cglue_osinstance::*, cglue_processinstance::*,
IntoProcessInstanceArcBox, LoadableOs, MuOsInstanceArcBox, OsArgs, OsDescriptor,
OsInstanceArcBox, ProcessInstanceArcBox,
};
pub type OsInputArg = <LoadableOs as Loadable>::InputArg;
pub mod logger;
pub use logger::*;
pub(crate) mod util;
pub use util::{wrap, wrap_with_input};
use crate::error::{Result, *};
use log::*;
use std::fs::read_dir;
use std::mem::MaybeUninit;
use std::path::{Path, PathBuf};
use abi_stable::{type_layout::TypeLayout, StableAbi};
use libloading::Library;
use once_cell::sync::OnceCell;
pub const MEMFLOW_PLUGIN_VERSION: i32 = -9;
pub type HelpCallback<'a> = OpaqueCallback<'a, ReprCString>;
pub struct LibContext {
lib: Library,
logger: OnceCell<Box<PluginLogger>>,
}
impl From<Library> for LibContext {
fn from(lib: Library) -> Self {
Self {
lib,
logger: Default::default(),
}
}
}
impl LibContext {
pub unsafe fn get_logger(&self) -> &'static PluginLogger {
(&**self.logger.get_or_init(|| Box::new(PluginLogger::new())) as *const PluginLogger)
.as_ref()
.unwrap()
}
pub fn try_get_logger(&self) -> Option<&PluginLogger> {
self.logger.get().map(|l| &**l)
}
}
#[repr(C)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct TargetInfo {
pub name: ReprCString,
}
pub type TargetCallback<'a> = OpaqueCallback<'a, TargetInfo>;
#[repr(C)]
pub struct PluginDescriptor<T: Loadable> {
pub plugin_version: i32,
pub accept_input: bool,
pub input_layout: &'static TypeLayout,
pub output_layout: &'static TypeLayout,
pub name: CSliceRef<'static, u8>,
pub version: CSliceRef<'static, u8>,
pub description: CSliceRef<'static, u8>,
pub help_callback: Option<extern "C" fn(callback: HelpCallback) -> ()>,
pub target_list_callback: Option<extern "C" fn(callback: TargetCallback) -> i32>,
pub create: CreateFn<T>,
}
pub type CreateFn<T> = extern "C" fn(
Option<&<T as Loadable>::ArgsType>,
<T as Loadable>::CInputArg,
lib: LibArc,
logger: Option<&'static PluginLogger>,
&mut MaybeUninit<<T as Loadable>::Instance>,
) -> i32;
pub trait Loadable: Sized {
type Instance: StableAbi;
type InputArg;
type CInputArg: StableAbi;
type ArgsType;
fn exists(&self, instances: &[LibInstance<Self>]) -> bool {
instances
.iter()
.filter_map(|i| i.state.as_option())
.any(|(_, l)| l.ident() == self.ident())
}
fn ident(&self) -> &str;
fn plugin_type() -> &'static str;
fn export_prefix() -> &'static str;
fn new(descriptor: PluginDescriptor<Self>) -> Self;
fn load(
path: impl AsRef<Path>,
library: &CArc<LibContext>,
export: &str,
) -> Result<LibInstance<Self>> {
let descriptor = unsafe {
library
.as_ref()
.ok_or(Error(ErrorOrigin::Inventory, ErrorKind::Uninitialized))?
.lib
.get::<*mut PluginDescriptor<Self>>(format!("{}\0", export).as_bytes())
.map_err(|_| Error(ErrorOrigin::Inventory, ErrorKind::MemflowExportsNotFound))?
.read()
};
if descriptor.plugin_version != MEMFLOW_PLUGIN_VERSION {
warn!(
"{} has a different version. version {} required, found {}.",
export, MEMFLOW_PLUGIN_VERSION, descriptor.plugin_version
);
Ok(LibInstance {
path: path.as_ref().to_path_buf(),
state: LibInstanceState::VersionMismatch,
})
} else if VerifyLayout::check::<Self::CInputArg>(Some(descriptor.input_layout))
.and(VerifyLayout::check::<Self::Instance>(Some(
descriptor.output_layout,
)))
.is_valid_strict()
{
Ok(LibInstance {
path: path.as_ref().to_path_buf(),
state: LibInstanceState::Loaded {
library: library.clone(),
loader: Self::new(descriptor),
},
})
} else {
warn!("{} has invalid ABI.", export);
Ok(LibInstance {
path: path.as_ref().to_path_buf(),
state: LibInstanceState::InvalidAbi,
})
}
}
fn load_all(path: impl AsRef<Path>) -> Result<Vec<LibInstance<Self>>> {
let exports = util::find_export_by_prefix(path.as_ref(), Self::export_prefix())?;
if exports.is_empty() {
return Err(Error(
ErrorOrigin::Inventory,
ErrorKind::MemflowExportsNotFound,
));
}
let library = unsafe { Library::new(path.as_ref()) }
.map_err(|err| {
debug!(
"found {:?} in library '{:?}' but could not load it: {}",
exports,
path.as_ref(),
err
);
Error(ErrorOrigin::Inventory, ErrorKind::UnableToLoadLibrary)
})
.map(LibContext::from)
.map(CArc::from)?;
Ok(exports
.into_iter()
.filter_map(|e| Self::load(path.as_ref(), &library, &e).ok())
.collect())
}
fn load_append(path: impl AsRef<Path>, out: &mut Vec<LibInstance<Self>>) -> Result<()> {
let canonical_path =
std::fs::canonicalize(path.as_ref()).unwrap_or_else(|_| path.as_ref().to_owned());
let libs = Self::load_all(path.as_ref())?;
for lib in libs.into_iter() {
if !out.iter().any(|o| o.path == canonical_path) {
if let LibInstanceState::Loaded { library: _, loader } = &lib.state {
if !loader.exists(out) {
info!(
"adding plugin '{}/{}': {:?}",
Self::plugin_type(),
loader.ident(),
path.as_ref()
);
out.push(lib);
} else {
debug!(
"skipping library '{}' because it was added already: {:?}",
loader.ident(),
path.as_ref()
);
return Err(Error(ErrorOrigin::Inventory, ErrorKind::AlreadyExists));
}
} else {
out.push(lib);
}
} else {
debug!(
"skipping library at '{:?}' because it was added already",
path.as_ref()
);
return Err(Error(ErrorOrigin::Inventory, ErrorKind::AlreadyExists));
}
}
Ok(())
}
fn help(&self) -> Result<String>;
fn target_list(&self) -> Result<Vec<TargetInfo>>;
fn instantiate(
&self,
library: CArc<LibContext>,
input: Self::InputArg,
args: Option<&Self::ArgsType>,
) -> Result<Self::Instance>;
}
pub struct Inventory {
connectors: Vec<LibInstance<connector::LoadableConnector>>,
os_layers: Vec<LibInstance<os::LoadableOs>>,
}
impl Inventory {
pub fn scan_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut dir = PathBuf::default();
dir.push(path);
let mut ret = Self {
connectors: vec![],
os_layers: vec![],
};
ret.add_dir(dir)?;
Ok(ret)
}
pub fn scan() -> Self {
#[cfg(unix)]
let extra_paths: Vec<&str> = vec![
"/usr/lib", "/usr/local/lib",
];
#[cfg(not(unix))]
let extra_paths = if let Some(Some(programfiles)) =
std::env::var_os("PROGRAMFILES").map(|v| v.to_str().map(|s| s.to_owned()))
{
vec![programfiles]
} else {
vec![]
};
let path_iter = extra_paths.into_iter().map(PathBuf::from);
let path_var = std::env::var_os("MEMFLOW_PLUGIN_PATH");
let path_iter = path_iter.chain(
path_var
.as_ref()
.map(std::env::split_paths)
.into_iter()
.flatten(),
);
#[cfg(unix)]
let path_iter = path_iter.chain(
dirs::home_dir()
.map(|dir| dir.join(".local").join("lib"))
.into_iter(),
);
#[cfg(not(unix))]
let path_iter = path_iter.chain(dirs::document_dir().into_iter());
let mut ret = Self {
connectors: vec![],
os_layers: vec![],
};
for mut path in path_iter {
path.push("memflow");
ret.add_dir(path).ok();
}
if let Some(extra_plugin_paths) = option_env!("MEMFLOW_PLUGIN_PATH") {
for p in std::env::split_paths(extra_plugin_paths) {
ret.add_dir(p).ok();
}
}
if let Ok(pwd) = std::env::current_dir() {
ret.add_dir(pwd).ok();
}
ret
}
pub fn add_dir_filtered(&mut self, dir: PathBuf, filter: &str) -> Result<&mut Self> {
if !dir.is_dir() {
return Err(Error(ErrorOrigin::Inventory, ErrorKind::InvalidPath));
}
info!("scanning {:?} for libraries", dir,);
for entry in
read_dir(dir).map_err(|_| Error(ErrorOrigin::Inventory, ErrorKind::UnableToReadDir))?
{
let entry = entry
.map_err(|_| Error(ErrorOrigin::Inventory, ErrorKind::UnableToReadDirEntry))?;
if let Some(true) = entry.file_name().to_str().map(|n| n.contains(filter)) {
self.load(entry.path());
}
}
Ok(self)
}
pub fn add_dir(&mut self, dir: PathBuf) -> Result<&mut Self> {
self.add_dir_filtered(dir, "")
}
pub fn with_workspace(mut self) -> Result<Self> {
let paths = std::fs::read_dir("../target/").map_err(|_| ErrorKind::UnableToReadDir)?;
for path in paths {
match path.unwrap().file_name().to_str() {
Some("release") | Some("debug") | None => {}
Some(x) => {
self.add_dir_filtered(format!("../target/{}/release/deps", x).into(), "ffi")
.ok();
self.add_dir_filtered(format!("../target/{}/debug/deps", x).into(), "ffi")
.ok();
}
}
}
self.add_dir_filtered("../target/release/deps".into(), "ffi")
.ok();
self.add_dir_filtered("../target/debug/deps".into(), "ffi")
.ok();
Ok(self)
}
pub fn load(&mut self, path: PathBuf) -> &mut Self {
Loadable::load_append(&path, &mut self.connectors).ok();
Loadable::load_append(&path, &mut self.os_layers).ok();
self
}
pub fn available_connectors(&self) -> Vec<String> {
self.connectors
.iter()
.filter_map(|c| c.state.as_option())
.map(|s| s.1.ident().to_string())
.collect::<Vec<_>>()
}
pub fn available_os(&self) -> Vec<String> {
self.os_layers
.iter()
.filter_map(|c| c.state.as_option())
.map(|s| s.1.ident().to_string())
.collect::<Vec<_>>()
}
pub fn connector_help(&self, name: &str) -> Result<String> {
Self::help_internal(&self.connectors, name)
}
pub fn os_help(&self, name: &str) -> Result<String> {
Self::help_internal(&self.os_layers, name)
}
fn help_internal<T: Loadable>(libs: &[LibInstance<T>], name: &str) -> Result<String> {
let loader = libs
.iter()
.filter_map(|c| c.state.as_option().map(|s| s.1))
.find(|s| s.ident() == name)
.ok_or_else(|| {
error!("unable to find plugin with name '{}'.", name,);
error!(
"possible available `{}` plugins are: {}",
T::plugin_type(),
Self::plugin_list_available(libs),
);
error!(
"outdated/mismatched `{}` plugins where found at: {}",
T::plugin_type(),
Self::plugin_list_unavailable(libs),
);
Error(ErrorOrigin::Inventory, ErrorKind::PluginNotFound)
})?;
loader.help()
}
pub fn connector_target_list(&self, name: &str) -> Result<Vec<TargetInfo>> {
let loader = self
.connectors
.iter()
.filter_map(|c| c.state.as_option().map(|s| s.1))
.find(|s| s.ident() == name)
.ok_or_else(|| {
error!("unable to find plugin with name '{}'.", name,);
error!(
"possible available `{}` plugins are: {}",
LoadableConnector::plugin_type(),
Self::plugin_list_available(&self.connectors),
);
error!(
"outdated/mismatched `{}` plugins where found at: {}",
LoadableConnector::plugin_type(),
Self::plugin_list_unavailable(&self.connectors),
);
Error(ErrorOrigin::Inventory, ErrorKind::PluginNotFound)
})?;
loader.target_list()
}
pub fn builder(&self) -> BuilderEmpty {
BuilderEmpty { inventory: self }
}
pub fn create_connector(
&self,
name: &str,
input: ConnectorInputArg,
args: Option<&ConnectorArgs>,
) -> Result<ConnectorInstanceArcBox<'static>> {
Self::create_internal(&self.connectors, name, input, args)
}
pub fn create_os(
&self,
name: &str,
input: OsInputArg,
args: Option<&OsArgs>,
) -> Result<OsInstanceArcBox<'static>> {
Self::create_internal(&self.os_layers, name, input, args)
}
fn create_internal<T: Loadable>(
libs: &[LibInstance<T>],
name: &str,
input: T::InputArg,
args: Option<&T::ArgsType>,
) -> Result<T::Instance> {
let lib = libs
.iter()
.filter(|l| l.state.is_loaded())
.find(|l| l.ident() == Some(name))
.ok_or_else(|| {
error!("unable to find plugin with name '{}'.", name,);
error!(
"possible available `{}` plugins are: {}",
T::plugin_type(),
Self::plugin_list_available(libs),
);
error!(
"outdated/mismatched `{}` plugins where found at: {}",
T::plugin_type(),
Self::plugin_list_unavailable(libs),
);
Error(ErrorOrigin::Inventory, ErrorKind::PluginNotFound)
})?;
if let LibInstanceState::Loaded { library, loader } = &lib.state {
info!(
"attempting to load `{}` type plugin `{}` from `{}`",
T::plugin_type(),
loader.ident(),
lib.path.to_string_lossy(),
);
loader.instantiate(library.clone(), input, args)
} else {
unreachable!()
}
}
pub fn set_max_log_level(&self, level: LevelFilter) {
log::set_max_level(level);
self.update_max_log_level()
}
fn update_max_log_level(&self) {
let level = log::max_level();
self.connectors
.iter()
.filter_map(|c| c.state.as_option())
.map(|s| s.0)
.chain(
self.os_layers
.iter()
.filter_map(|o| o.state.as_option())
.map(|s| s.0),
)
.filter_map(|s| *s.as_ref())
.filter_map(LibContext::try_get_logger)
.for_each(|l| l.on_level_change(level));
}
fn plugin_list_available<T: Loadable>(libs: &[LibInstance<T>]) -> String {
libs.iter()
.filter_map(|c| c.state.as_option().map(|s| s.1.ident().to_string()))
.collect::<Vec<_>>()
.join(", ")
}
fn plugin_list_unavailable<T: Loadable>(libs: &[LibInstance<T>]) -> String {
libs.iter()
.filter(|c| !c.state.is_loaded())
.map(|c| c.path.to_string_lossy())
.collect::<Vec<_>>()
.join(", ")
}
}
pub enum BuildStep<'a> {
Connector {
name: &'a str,
args: Option<ConnectorArgs>,
},
Os {
name: &'a str,
args: Option<OsArgs>,
},
}
impl<'a> BuildStep<'a> {
pub fn new_connector(input: &'a str) -> Result<Self> {
let (name, args) = input.split_once(':').unwrap_or((input, ""));
Ok(Self::Connector {
name,
args: if args.is_empty() {
None
} else {
Some(str::parse(args)?)
},
})
}
pub fn new_os(input: &'a str) -> Result<Self> {
let (name, args) = input.split_once(':').unwrap_or((input, ""));
Ok(Self::Os {
name,
args: if args.is_empty() {
None
} else {
Some(str::parse(args)?)
},
})
}
pub fn validate_next(&self, next: &Self) -> bool {
!matches!(
(self, next),
(BuildStep::Connector { .. }, BuildStep::Connector { .. })
| (BuildStep::Os { .. }, BuildStep::Os { .. })
)
}
}
fn builder_from_args<'a>(
connectors: impl Iterator<Item = (usize, &'a str)>,
os_layers: impl Iterator<Item = (usize, &'a str)>,
) -> Result<Vec<BuildStep<'a>>> {
let mut layers = connectors
.map(|(i, a)| BuildStep::new_connector(a).map(|a| (i, a)))
.chain(os_layers.map(|(i, a)| BuildStep::new_os(a).map(|a| (i, a))))
.collect::<Result<Vec<_>>>()?;
layers.sort_by(|(a, _), (b, _)| a.cmp(b));
if layers.windows(2).any(|w| !w[0].1.validate_next(&w[1].1)) {
return Err(
Error(ErrorOrigin::Other, ErrorKind::ArgValidation).log_error(
"invalid builder configuration, build steps cannot be used in the given order",
),
);
}
Ok(layers.into_iter().map(|(_, s)| s).collect())
}
pub struct ConnectorChain<'a>(Vec<BuildStep<'a>>);
impl<'a> ConnectorChain<'a> {
pub fn new(
connectors: impl Iterator<Item = (usize, &'a str)>,
os_layers: impl Iterator<Item = (usize, &'a str)>,
) -> Result<Self> {
let steps = builder_from_args(connectors, os_layers)?;
steps.try_into()
}
}
impl<'a> TryFrom<Vec<BuildStep<'a>>> for ConnectorChain<'a> {
type Error = Error;
fn try_from(steps: Vec<BuildStep<'a>>) -> Result<Self> {
if !matches!(steps.last(), Some(BuildStep::Connector { .. })) {
return Err(
Error(ErrorOrigin::Other, ErrorKind::ArgValidation).log_error(
"invalid builder configuration, last build step has to be a connector",
),
);
}
Ok(Self(steps))
}
}
pub struct OsChain<'a>(Vec<BuildStep<'a>>);
impl<'a> OsChain<'a> {
pub fn new(
connectors: impl Iterator<Item = (usize, &'a str)>,
os_layers: impl Iterator<Item = (usize, &'a str)>,
) -> Result<Self> {
let steps = builder_from_args(connectors, os_layers)?;
steps.try_into()
}
}
impl<'a> TryFrom<Vec<BuildStep<'a>>> for OsChain<'a> {
type Error = Error;
fn try_from(steps: Vec<BuildStep<'a>>) -> Result<Self> {
if !matches!(steps.last(), Some(BuildStep::Os { .. })) {
return Err(Error(ErrorOrigin::Other, ErrorKind::ArgValidation)
.log_error("invalid builder configuration, last build step has to be a os"));
}
Ok(Self(steps))
}
}
pub struct BuilderEmpty<'a> {
inventory: &'a Inventory,
}
impl<'a> BuilderEmpty<'a> {
pub fn connector(self, name: &'a str) -> OsBuilder<'a> {
OsBuilder {
inventory: self.inventory,
steps: vec![BuildStep::Connector { name, args: None }],
}
}
pub fn os(self, name: &'a str) -> ConnectorBuilder<'a> {
ConnectorBuilder {
inventory: self.inventory,
steps: vec![BuildStep::Os { name, args: None }],
}
}
pub fn os_chain(self, chain: OsChain<'a>) -> ConnectorBuilder<'a> {
ConnectorBuilder {
inventory: self.inventory,
steps: chain.0,
}
}
pub fn connector_chain(self, chain: ConnectorChain<'a>) -> OsBuilder<'a> {
OsBuilder {
inventory: self.inventory,
steps: chain.0,
}
}
}
pub struct ConnectorBuilder<'a> {
inventory: &'a Inventory,
steps: Vec<BuildStep<'a>>,
}
impl<'a> ConnectorBuilder<'a> {
pub fn connector(self, name: &'a str) -> OsBuilder<'a> {
let mut steps = self.steps;
steps.push(BuildStep::Connector { name, args: None });
OsBuilder {
inventory: self.inventory,
steps,
}
}
pub fn args(mut self, os_args: OsArgs) -> ConnectorBuilder<'a> {
if let Some(BuildStep::Os { name: _, args }) = self.steps.iter_mut().last() {
*args = Some(os_args);
}
self
}
pub fn build(self) -> Result<OsInstanceArcBox<'static>> {
let mut connector: Option<ConnectorInstanceArcBox<'static>> = None;
let mut os: Option<OsInstanceArcBox<'static>> = None;
for step in self.steps.iter() {
match step {
BuildStep::Connector { name, args } => {
connector = Some(self.inventory.create_connector(name, os, args.as_ref())?);
os = None;
}
BuildStep::Os { name, args } => {
os = Some(self.inventory.create_os(name, connector, args.as_ref())?);
connector = None;
}
};
}
os.ok_or(Error(ErrorOrigin::Inventory, ErrorKind::Configuration))
}
}
pub struct OsBuilder<'a> {
inventory: &'a Inventory,
steps: Vec<BuildStep<'a>>,
}
impl<'a> OsBuilder<'a> {
pub fn os(self, name: &'a str) -> ConnectorBuilder<'a> {
let mut steps = self.steps;
steps.push(BuildStep::Os { name, args: None });
ConnectorBuilder {
inventory: self.inventory,
steps,
}
}
pub fn args(mut self, conn_args: ConnectorArgs) -> OsBuilder<'a> {
if let Some(BuildStep::Connector { name: _, args }) = self.steps.iter_mut().last() {
*args = Some(conn_args);
}
self
}
pub fn build(self) -> Result<ConnectorInstanceArcBox<'static>> {
let mut connector: Option<ConnectorInstanceArcBox<'static>> = None;
let mut os: Option<OsInstanceArcBox<'static>> = None;
for step in self.steps.iter() {
match step {
BuildStep::Connector { name, args } => {
connector = Some(self.inventory.create_connector(name, os, args.as_ref())?);
os = None;
}
BuildStep::Os { name, args } => {
os = Some(self.inventory.create_os(name, connector, args.as_ref())?);
connector = None;
}
};
}
connector.ok_or(Error(ErrorOrigin::Inventory, ErrorKind::Configuration))
}
}
#[repr(C)]
#[derive(Clone)]
pub struct LibInstance<T> {
path: PathBuf,
state: LibInstanceState<T>,
}
impl<T: Loadable> LibInstance<T> {
pub fn ident(&self) -> Option<&str> {
self.state.as_option().map(|s| s.1.ident())
}
}
#[repr(C)]
#[derive(Clone)]
pub enum LibInstanceState<T> {
Loaded {
library: CArc<LibContext>,
loader: T,
},
VersionMismatch,
InvalidAbi,
}
impl<T> LibInstanceState<T> {
pub fn is_loaded(&self) -> bool {
matches!(
self,
LibInstanceState::Loaded {
library: _,
loader: _,
}
)
}
pub fn as_option(&self) -> Option<(&CArc<LibContext>, &T)> {
match self {
LibInstanceState::Loaded { library, loader } => Some((library, loader)),
_ => None,
}
}
}