use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::fmt;
use std::mem::MaybeUninit;
use std::path::{Path, PathBuf};
use crate::config::MachineConfig;
use crate::control::Control;
use anyhow::Context;
use log::LevelFilter;
use crate::loader::BarylCore;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RunEnd {
Engine { code: i32 },
Component { code: i32, message: Option<String> },
}
impl RunEnd {
pub fn code(&self) -> i32 {
match *self {
RunEnd::Engine { code } | RunEnd::Component { code, .. } => code,
}
}
pub fn exit_status(&self) -> u8 {
(self.code() & 0xff) as u8
}
unsafe fn from_raw(raw: &crate::sys::BarylOutcome) -> RunEnd {
if raw.kind == BARYL_EXIT_COMPONENT {
let message = (!raw.message.is_null()).then(|| {
unsafe { CStr::from_ptr(raw.message) }
.to_string_lossy()
.into_owned()
});
RunEnd::Component { code: raw.code, message: message }
} else {
RunEnd::Engine { code: raw.code }
}
}
}
impl fmt::Display for RunEnd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RunEnd::Engine { code } => write!(f, "engine exited ({code})"),
RunEnd::Component { code, message: None } => {
write!(f, "component requested exit ({code})")
},
RunEnd::Component { code, message: Some(m) } => {
write!(f, "component requested exit ({code}): {m}")
},
}
}
}
const BARYL_EXIT_COMPONENT: u32 = 1;
pub struct Baryl {
core: &'static BarylCore,
ctx: *mut c_void,
}
impl Baryl {
pub fn open(image: &Path, options: &Options) -> anyhow::Result<Baryl> {
let core = BarylCore::load()?;
let path = cstr(image)?;
let abi = options.as_abi();
let mut err = [0u8; crate::sys::BARYL_ERROR_LEN as usize];
let ctx = unsafe {
(core.open)(path.as_ptr(), &raw const abi, err.as_mut_ptr().cast(), err.len())
};
if ctx.is_null() {
return Err(match open_error_message_read(&err) {
Some(why) => anyhow::anyhow!("{why}"),
None => anyhow::anyhow!("{} could not open {}", core.name(), image.display()),
});
}
Ok(Baryl { core: core, ctx: ctx })
}
pub fn control(&self) -> &Control {
unsafe { &*(self.core.control)(self.abi()).cast() }
}
pub fn run(&mut self) -> anyhow::Result<RunEnd> {
let mut outcome = MaybeUninit::<crate::sys::BarylOutcome>::uninit();
let rc = unsafe { (self.core.run)(self.abi(), outcome.as_mut_ptr()) };
unsafe { run_end(rc, outcome) }
}
fn abi(&self) -> crate::sys::BarylRef {
crate::sys::BarylRef {
ctx: self.ctx,
descriptor: self.core.descriptor,
}
}
}
impl Drop for Baryl {
fn drop(&mut self) {
unsafe { (self.core.close)(self.abi()) };
}
}
#[derive(Default)]
pub struct Options {
machine: Option<Box<MachineConfig>>,
state_dir: Option<CString>,
log_level: c_int,
seed: u64,
owned: Vec<OwnedComponent>,
rows: Vec<crate::sys::BarylComponent>,
}
impl Options {
pub fn machine(mut self, machine: Option<MachineConfig>) -> Options {
self.machine = machine.map(Box::new);
self
}
pub fn state_dir(mut self, path: Option<&Path>) -> anyhow::Result<Options> {
self.state_dir = path.map(cstr).transpose()?;
Ok(self)
}
pub fn log_level(mut self, level: LevelFilter) -> Options {
self.log_level = level as c_int;
self
}
pub fn seed(mut self, seed: u64) -> Options {
self.seed = seed;
self
}
pub fn components(mut self, components: &[(PathBuf, Vec<String>)]) -> anyhow::Result<Options> {
self.owned = components
.iter()
.map(OwnedComponent::new)
.collect::<anyhow::Result<_>>()?;
self.rows = self.owned.iter().map(OwnedComponent::as_abi).collect();
Ok(self)
}
fn as_abi(&self) -> crate::sys::BarylOptions {
crate::sys::BarylOptions {
machine: self
.machine
.as_deref()
.map_or(std::ptr::null(), |m| std::ptr::from_ref(m).cast()),
components: self.rows.as_ptr(),
ncomponents: self.rows.len() as u32,
state_dir: or_null(&self.state_dir),
log_level: self.log_level,
seed: self.seed,
}
}
}
struct OwnedComponent {
path: CString,
argv: Vec<CString>,
argv_ptrs: Vec<*const c_char>,
}
impl OwnedComponent {
fn new((path, argv): &(PathBuf, Vec<String>)) -> anyhow::Result<OwnedComponent> {
let path = cstr(path)?;
let argv: Vec<CString> = argv
.iter()
.map(|s| CString::new(s.as_str()))
.collect::<Result<_, _>>()
.context("a component argument contains an interior NUL")?;
let argv_ptrs: Vec<*const c_char> = argv.iter().map(|a| a.as_ptr()).collect();
Ok(OwnedComponent {
path: path,
argv: argv,
argv_ptrs: argv_ptrs,
})
}
fn as_abi(&self) -> crate::sys::BarylComponent {
crate::sys::BarylComponent {
path: self.path.as_ptr(),
argv: self.argv_ptrs.as_ptr(),
argc: self.argv.len() as u32,
}
}
}
fn open_error_message_read(err: &[u8]) -> Option<String> {
let end = err.iter().position(|b| *b == 0).unwrap_or(err.len());
(end != 0).then(|| String::from_utf8_lossy(&err[..end]).into_owned())
}
fn or_null(s: &Option<CString>) -> *const c_char {
s.as_ref().map_or(std::ptr::null(), |s| s.as_ptr())
}
unsafe fn run_end(
rc: c_int,
outcome: MaybeUninit<crate::sys::BarylOutcome>,
) -> anyhow::Result<RunEnd> {
anyhow::ensure!(rc == 0, "the run did not complete; the reason is in the run's log");
Ok(unsafe { RunEnd::from_raw(outcome.assume_init_ref()) })
}
fn cstr(path: &Path) -> anyhow::Result<CString> {
let s = path
.to_str()
.with_context(|| format!("path {} is not UTF-8", path.display()))?;
CString::new(s).with_context(|| format!("path {} contains an interior NUL", path.display()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_values_match_the_header() {
assert_eq!(BARYL_EXIT_COMPONENT, crate::sys::BarylExitKind_BARYL_EXIT_COMPONENT);
assert_ne!(BARYL_EXIT_COMPONENT, crate::sys::BarylExitKind_BARYL_EXIT_ENGINE);
}
#[test]
fn outcome_layout_matches_the_static_assert() {
assert_eq!(size_of::<crate::sys::BarylOutcome>(), 0x10);
}
#[test]
fn engine_and_component_ends_are_distinguished() {
let engine = crate::sys::BarylOutcome {
kind: crate::sys::BarylExitKind_BARYL_EXIT_ENGINE,
code: 3,
message: std::ptr::null(),
};
assert_eq!(unsafe { RunEnd::from_raw(&engine) }, RunEnd::Engine { code: 3 });
let component = crate::sys::BarylOutcome {
kind: BARYL_EXIT_COMPONENT,
code: 0,
message: c"took the checkpoint".as_ptr(),
};
assert_eq!(
unsafe { RunEnd::from_raw(&component) },
RunEnd::Component {
code: 0,
message: Some("took the checkpoint".into())
}
);
}
#[test]
fn unknown_kind_degrades_to_an_engine_exit() {
let future = crate::sys::BarylOutcome {
kind: 99,
code: 7,
message: std::ptr::null(),
};
assert_eq!(unsafe { RunEnd::from_raw(&future) }, RunEnd::Engine { code: 7 });
}
#[test]
fn exit_status_is_the_low_byte() {
assert_eq!(RunEnd::Engine { code: 0 }.exit_status(), 0);
assert_eq!(RunEnd::Engine { code: 3 }.exit_status(), 3);
assert_eq!(RunEnd::Engine { code: 255 }.exit_status(), 255);
assert_eq!(RunEnd::Engine { code: -1 }.exit_status(), 255);
assert_eq!(RunEnd::Engine { code: 256 }.exit_status(), 0);
assert_eq!(RunEnd::Engine { code: -1 }.code(), -1);
}
}