#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::ffi::{CStr, c_char, c_int, c_void};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use crate::engine::{BlobId, Engine, EngineError, EngineOpenOptions, ErrorCode};
pub const EFS_ABI_VERSION: u32 = 1;
pub const EFS_ENGINE_OPEN: c_int = 0;
pub const EFS_ENGINE_CREATE: c_int = 1;
pub const EFS_ENGINE_OPEN_RO: c_int = 2;
pub const EFS_OK: c_int = ErrorCode::Ok.as_i32();
pub const EFS_NOT_FOUND: c_int = ErrorCode::NotFound.as_i32();
pub const EFS_INVALID_ARGUMENT: c_int = ErrorCode::InvalidArgument.as_i32();
pub const EFS_CORRUPT_STORE: c_int = ErrorCode::CorruptStore.as_i32();
pub const EFS_INCOMPATIBLE_FORMAT: c_int = ErrorCode::IncompatibleFormat.as_i32();
pub const EFS_RESOURCE_LIMIT: c_int = ErrorCode::ResourceLimit.as_i32();
pub const EFS_IO: c_int = ErrorCode::Io.as_i32();
pub const EFS_BUSY: c_int = ErrorCode::Busy.as_i32();
pub const EFS_UNSUPPORTED: c_int = ErrorCode::Unsupported.as_i32();
pub const EFS_INTERNAL: c_int = ErrorCode::Internal.as_i32();
pub const EFS_CLOSED: c_int = ErrorCode::Closed.as_i32();
pub fn take_output(ptr: *mut u8, len: usize) -> Vec<u8> {
if len == 0 || ptr.is_null() {
return Vec::new();
}
let out = unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec();
entropyfs_free(ptr);
out
}
thread_local! {
static LAST_ERROR: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
}
fn set_last_error(msg: impl Into<String>) {
LAST_ERROR.with(|m| *m.borrow_mut() = msg.into());
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_last_error(buf: *mut c_char, cap: usize) -> c_int {
LAST_ERROR.with(|m| {
let s = m.borrow();
let failed = if s.is_empty() { 0 } else { 1 };
if !buf.is_null() && cap > 0 {
let bytes = s.as_bytes();
let n = bytes.len().min(cap - 1);
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, n);
*buf.add(n) = 0;
}
}
failed
})
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_free(ptr: *mut u8) {
if ptr.is_null() {
return;
}
unsafe {
let base = ptr.sub(16);
let len = ptr::read_unaligned(base as *const u64) as usize;
let cap = len + 16;
drop(Vec::from_raw_parts(base, cap, cap));
}
}
unsafe fn alloc_output(len: usize) -> *mut u8 {
let cap = len + 16;
let mut v: Vec<u8> = vec![0; cap];
let base = v.as_mut_ptr();
unsafe {
ptr::write_unaligned(base as *mut u64, len as u64);
std::mem::forget(v); base.add(16)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_abi_version() -> u32 {
EFS_ABI_VERSION
}
fn open_impl(
path: *const c_char,
mode: c_int,
out_handle: *mut *mut Engine,
) -> Result<(), EngineError> {
if path.is_null() || out_handle.is_null() {
return Err(EngineError::new(
ErrorCode::InvalidArgument,
"null path or out_handle",
));
}
let path = unsafe { CStr::from_ptr(path) }
.to_str()
.map_err(|_| EngineError::new(ErrorCode::InvalidArgument, "path is not valid UTF-8"))?
.to_owned();
let opts = EngineOpenOptions::default();
let engine = match mode {
EFS_ENGINE_OPEN => Engine::open(std::path::Path::new(&path), &opts)?,
EFS_ENGINE_CREATE => Engine::create(std::path::Path::new(&path), &opts)?,
EFS_ENGINE_OPEN_RO => {
let ro = EngineOpenOptions {
read_only: true,
..opts
};
Engine::open(std::path::Path::new(&path), &ro)?
}
other => {
return Err(EngineError::new(
ErrorCode::InvalidArgument,
format!("unknown engine open mode {other} (0=open, 1=create, 2=open read-only)"),
));
}
};
unsafe {
ptr::write(out_handle, Box::into_raw(Box::new(engine)));
}
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_engine_open(
path: *const c_char,
mode: c_int,
out_handle: *mut *mut Engine,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| open_impl(path, mode, out_handle)));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_engine_open: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error(
"entropyfs_engine_open panicked at the FFI boundary (a bug — see unsafe-ledger panic-boundary note)",
);
eprintln!("entropyfs_engine_open: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_engine_close(handle: *mut Engine) -> c_int {
if handle.is_null() {
set_last_error("entropyfs_engine_close: null handle");
return ErrorCode::InvalidArgument.as_i32();
}
let r = catch_unwind(AssertUnwindSafe(|| {
let engine = unsafe { Box::from_raw(handle) };
engine.close()
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_engine_close: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_engine_close panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_engine_close: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
fn put_impl(
handle: *mut Engine,
data: *const u8,
len: usize,
id: *mut u8,
) -> Result<BlobId, EngineError> {
if handle.is_null() || id.is_null() {
return Err(EngineError::new(
ErrorCode::InvalidArgument,
"null handle or id",
));
}
if len > 0 && data.is_null() {
return Err(EngineError::new(
ErrorCode::InvalidArgument,
"null data with nonzero len",
));
}
let bytes: &[u8] = if len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(data, len) }
};
let engine = unsafe { &*handle };
let bid = engine.put_blob(bytes)?;
unsafe {
ptr::copy_nonoverlapping(bid.as_bytes().as_ptr(), id, 32);
}
Ok(bid)
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_blob_put(
handle: *mut Engine,
data: *const u8,
len: usize,
id: *mut u8,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| put_impl(handle, data, len, id)));
match r {
Ok(Ok(_)) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_blob_put: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_blob_put panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_blob_put: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
fn get_impl(
handle: *mut Engine,
id: *const u8,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> Result<(), EngineError> {
if handle.is_null() || id.is_null() || out_buf.is_null() || out_len.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null arg"));
}
let bid = BlobId::new(unsafe { ptr::read(id as *const [u8; 32]) });
let engine = unsafe { &*handle };
let bytes = engine.get_blob(bid)?;
let len = bytes.len();
let ptr = unsafe { alloc_output(len) };
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, len);
ptr::write(out_buf, ptr);
ptr::write(out_len, len);
}
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_blob_get(
handle: *mut Engine,
id: *const u8,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| get_impl(handle, id, out_buf, out_len)));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_blob_get: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_blob_get panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_blob_get: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
fn range_impl(
handle: *mut Engine,
id: *const u8,
offset: u64,
len: usize,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> Result<(), EngineError> {
if handle.is_null() || id.is_null() || out_buf.is_null() || out_len.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null arg"));
}
let bid = BlobId::new(unsafe { ptr::read(id as *const [u8; 32]) });
let engine = unsafe { &*handle };
let bytes = engine.read_blob_range(bid, offset, len)?;
let n = bytes.len();
let ptr = unsafe { alloc_output(n) };
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, n);
ptr::write(out_buf, ptr);
ptr::write(out_len, n);
}
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_blob_read_range(
handle: *mut Engine,
id: *const u8,
offset: u64,
len: usize,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| {
range_impl(handle, id, offset, len, out_buf, out_len)
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_blob_read_range: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_blob_read_range panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_blob_read_range: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_contains(handle: *mut Engine, id: *const u8, out: *mut c_int) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| {
if handle.is_null() || id.is_null() || out.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null arg"));
}
let bid = BlobId::new(unsafe { ptr::read(id as *const [u8; 32]) });
let engine = unsafe { &*handle };
let c = engine.contains(bid)?;
unsafe { ptr::write(out, if c { 1 } else { 0 }) };
Ok(())
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_contains: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_contains panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_contains: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_sync(handle: *mut Engine) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| {
if handle.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null handle"));
}
let engine = unsafe { &*handle };
engine.sync()
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_sync: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_sync panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_sync: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_compact(
handle: *mut Engine,
out_reclaimed: *mut u64,
out_physical: *mut u64,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| {
if handle.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null handle"));
}
let engine = unsafe { &*handle };
let rep = engine.compact()?;
if !out_reclaimed.is_null() {
unsafe { ptr::write(out_reclaimed, rep.reclaimed_bytes) };
}
if !out_physical.is_null() {
unsafe { ptr::write(out_physical, rep.physical_used_after_bytes) };
}
Ok(())
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_compact: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_compact panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_compact: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn entropyfs_metrics_json(
handle: *mut Engine,
out_buf: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
let r = catch_unwind(AssertUnwindSafe(|| {
if handle.is_null() || out_buf.is_null() || out_len.is_null() {
return Err(EngineError::new(ErrorCode::InvalidArgument, "null arg"));
}
let engine = unsafe { &*handle };
let json = engine.metrics()?.to_json();
let bytes = json.into_bytes();
let n = bytes.len();
let ptr = unsafe { alloc_output(n) };
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, n);
ptr::write(out_buf, ptr);
ptr::write(out_len, n);
}
Ok(())
}));
match r {
Ok(Ok(())) => {
set_last_error("");
ErrorCode::Ok.as_i32()
}
Ok(Err(e)) => {
set_last_error(format!("entropyfs_metrics_json: {}", e.message));
e.code.as_i32()
}
Err(_) => {
set_last_error("entropyfs_metrics_json panicked at the FFI boundary (a bug)");
eprintln!("entropyfs_metrics_json: panic caught at the FFI boundary");
ErrorCode::Internal.as_i32()
}
}
}
#[allow(unused_imports)]
use c_void as _Cvoid;