use std::{
ffi::{CStr, CString, c_char, c_int, c_void},
path::{Path, PathBuf},
sync::OnceLock,
};
use anyhow::Context;
use log::debug;
const CORE_SO: &str = "libbaryl-core.so";
type OpenFn = unsafe extern "C" fn(
*const c_char,
*const crate::sys::BarylOptions,
*mut c_char,
usize,
) -> *mut c_void;
type RunFn = unsafe extern "C" fn(crate::sys::BarylRef, *mut crate::sys::BarylOutcome) -> c_int;
type ControlFn = unsafe extern "C" fn(crate::sys::BarylRef) -> *const crate::sys::Control;
type CloseFn = unsafe extern "C" fn(crate::sys::BarylRef);
pub struct BarylCore {
pub(crate) descriptor: &'static crate::sys::BarylDescriptor,
pub(crate) open: OpenFn,
pub(crate) run: RunFn,
pub(crate) control: ControlFn,
pub(crate) close: CloseFn,
path: PathBuf,
}
unsafe impl Send for BarylCore {}
unsafe impl Sync for BarylCore {}
static CORE: OnceLock<BarylCore> = OnceLock::new();
impl BarylCore {
pub fn load() -> anyhow::Result<&'static BarylCore> {
if let Some(core) = CORE.get() {
return Ok(core);
}
let core = BarylCore::open_so(&locate()?)?;
debug!("loaded {} from {}", core.name(), core.path.display());
Ok(CORE.get_or_init(|| core))
}
pub fn name(&self) -> &'static str {
unsafe { CStr::from_ptr(self.descriptor.name) }
.to_str()
.unwrap_or(CORE_SO)
}
fn open_so(path: &Path) -> anyhow::Result<BarylCore> {
let c_path = CString::new(path.as_os_str().as_encoded_bytes())
.with_context(|| format!("core path has an interior NUL: {}", path.display()))?;
let handle = unsafe { libc::dlopen(c_path.as_ptr(), libc::RTLD_NOW | libc::RTLD_GLOBAL) };
anyhow::ensure!(!handle.is_null(), "dlopen {}: {}", path.display(), dlerror());
let sym = unsafe { libc::dlsym(handle, c"baryl_descriptor".as_ptr()) };
anyhow::ensure!(!sym.is_null(), "{} exports no baryl_descriptor", path.display());
let d: &'static crate::sys::BarylDescriptor = unsafe { &*sym.cast() };
let (min, cur) = (d.min_version, d.version);
anyhow::ensure!(
cur >= crate::sys::BARYL_MIN_ABI_VERSION && min <= crate::sys::BARYL_ABI_VERSION,
"{} serves ABI {min}..={cur}, outside this build's {}..={}; \
run `baryl core install`",
path.display(),
crate::sys::BARYL_MIN_ABI_VERSION,
crate::sys::BARYL_ABI_VERSION,
);
let open: OpenFn = d.open.context("the core's descriptor has no `open`")?;
let run: RunFn = d.run.context("the core's descriptor has no `run`")?;
let control: ControlFn = d
.control
.context("the core's descriptor has no `control`")?;
let close: CloseFn = d.close.context("the core's descriptor has no `close`")?;
Ok(BarylCore {
descriptor: d,
open: open,
run: run,
control: control,
close: close,
path: path.to_path_buf(),
})
}
}
fn dlerror() -> String {
let e = unsafe { libc::dlerror() };
if e.is_null() {
return "no reason from the loader".to_string();
}
unsafe { CStr::from_ptr(e) }.to_string_lossy().into_owned()
}
fn locate() -> anyhow::Result<PathBuf> {
let dirs = search_dirs();
if let Some(found) = dirs.iter().map(|d| d.join(CORE_SO)).find(|p| p.is_file()) {
return Ok(found);
}
let looked: Vec<String> = dirs.iter().map(|d| d.display().to_string()).collect();
anyhow::bail!(
"no runtime installed: run `baryl setup`, or set $BARYL_INSTALL_DIR to a \
prefix holding lib/{CORE_SO}. Looked in {}",
looked.join(", ")
)
}
fn search_dirs() -> Vec<PathBuf> {
let mut dirs: Vec<PathBuf> = Vec::new();
if let Some(prefix) = std::env::var_os("BARYL_INSTALL_DIR") {
dirs.push(PathBuf::from(prefix).join("lib"));
}
if let Some(exe) = std::env::current_exe()
.ok()
.and_then(|e| e.parent().map(Path::to_path_buf))
{
dirs.push(exe.join("../lib"));
dirs.push(exe);
}
dirs.push(PathBuf::from("/opt/baryl/lib"));
dirs
}