use std::ffi::{OsStr, OsString};
use pmpx_plugin_abi::{
PmpxCommand, PmpxContext, PmpxStr, PMPX_ERR_INTERNAL, PMPX_ERR_INVALID_ARGS,
PMPX_ERR_UNSUPPORTED_VERB, PMPX_MAX_ITEMS, PMPX_OK,
};
use crate::{CommandSpec, Context, PackageManager, Verb};
pub const PANIC_MARKER: &str = "<the plugin panicked>";
pub unsafe fn write_command(out: *mut PmpxCommand, spec: CommandSpec) {
let program = leak_bytes(&os_to_bytes(&spec.program));
let args: Vec<PmpxStr> = spec
.args
.iter()
.map(|arg| leak_bytes(&os_to_bytes(arg)))
.collect();
let args = args.into_boxed_slice();
let args_len = args.len();
let args = Box::into_raw(args) as *const PmpxStr;
let cwd = match &spec.cwd {
Some(dir) => leak_bytes(&os_to_bytes(dir.as_os_str())),
None => PmpxStr::EMPTY,
};
unsafe {
(*out).size = std::mem::size_of::<PmpxCommand>();
(*out).program = program;
(*out).args = pmpx_plugin_abi::PmpxSlice::new(args, args_len);
(*out).cwd = cwd;
}
}
pub unsafe fn free_command(command: *mut PmpxCommand) {
if command.is_null() {
return;
}
let command = unsafe { &*command };
free_str(command.program);
free_str(command.cwd);
if !command.args.is_absent() {
let args = unsafe {
Box::from_raw(std::ptr::slice_from_raw_parts_mut(
command.args.ptr as *mut PmpxStr,
command.args.len,
))
};
for arg in args.iter() {
free_str(*arg);
}
}
}
pub unsafe fn free_str(s: PmpxStr) {
if s.ptr.is_null() {
return;
}
let bytes =
unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(s.ptr as *mut u8, s.len)) };
drop(bytes);
}
pub fn leak_bytes(bytes: &[u8]) -> PmpxStr {
let boxed = bytes.to_vec().into_boxed_slice();
let len = boxed.len();
PmpxStr::new(Box::into_raw(boxed) as *const u8, len)
}
pub fn leak_str(s: &str) -> PmpxStr {
leak_bytes(s.as_bytes())
}
pub unsafe fn read_str(s: PmpxStr) -> Option<&'static str> {
let bytes = unsafe { s.as_bytes() }?;
std::str::from_utf8(bytes).ok().map(|text| {
unsafe { std::mem::transmute::<&str, &'static str>(text) }
})
}
pub unsafe fn read_os(s: PmpxStr) -> OsString {
let bytes = unsafe { s.as_bytes() }.unwrap_or(&[]);
pmpx_plugin_abi::bytes_to_os(bytes)
}
pub unsafe fn dispatch(
plugin: &dyn PackageManager,
context: *const PmpxContext,
out: *mut PmpxCommand,
) -> u32 {
if out.is_null() || context.is_null() {
return PMPX_ERR_INVALID_ARGS;
}
let raw = unsafe { &*context };
if raw.size < std::mem::size_of::<PmpxContext>() {
return PMPX_ERR_INVALID_ARGS;
}
let Some(verb) = Verb::from_abi(raw.verb) else {
return PMPX_ERR_UNSUPPORTED_VERB;
};
let Ok(args) = (unsafe { read_args(context) }) else {
return PMPX_ERR_INVALID_ARGS;
};
let context = unsafe { Context::from_host(context) };
let description = context.describe(verb, args.len());
let answer = crate::debug::with_call(description, || plugin.command(&context, verb, &args));
match answer {
Ok(spec) => {
unsafe { write_command(out, spec) };
PMPX_OK
}
Err(error) => error.code(),
}
}
unsafe fn read_args(context: *const PmpxContext) -> Result<Vec<OsString>, ()> {
let raw = unsafe { &*context };
let key = PmpxStr::new(
pmpx_plugin_abi::PMPX_KEY_ARGS.as_ptr(),
pmpx_plugin_abi::PMPX_KEY_ARGS.len(),
);
let count = unsafe { (raw.count)(context, key) };
if count > PMPX_MAX_ITEMS {
return Err(());
}
let mut args = Vec::with_capacity(count);
for index in 0..count {
let value = unsafe { (raw.get)(context, key, index) };
if value.is_absent() {
return Err(());
}
args.push(unsafe { read_os(value) });
}
Ok(args)
}
pub unsafe fn dispatch_free_str(s: PmpxStr) {
unsafe { free_str(s) };
}
pub unsafe fn dispatch_free_command(command: *mut PmpxCommand) {
unsafe { free_command(command) };
}
pub fn guard(f: impl FnOnce() -> u32) -> u32 {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(PMPX_ERR_INTERNAL)
}
pub fn guard_str(f: impl FnOnce() -> PmpxStr) -> PmpxStr {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|_| {
let marker = leak_str(PANIC_MARKER);
marker
})
}
pub(crate) fn os_to_bytes(s: &OsStr) -> Vec<u8> {
pmpx_plugin_abi::os_to_bytes(s)
}
pub const BUILD_RUSTC: &str = env!("PMPX_BUILD_RUSTC");
pub const BUILD_TARGET: &str = env!("PMPX_BUILD_TARGET");
pub fn remember_name(name: &str) {
crate::debug::remember_name(name);
}
pub unsafe fn install_log_table(table: *const pmpx_plugin_abi::PmpxLog) {
unsafe { crate::debug::set_log_table(table) };
}