use core::ffi::{c_char, c_void};
use core::panic::AssertUnwindSafe;
use crate::support::{RawBytes, RawCString};
fn guard<T>(fallback: T, f: impl FnOnce() -> T) -> T {
match std::panic::catch_unwind(AssertUnwindSafe(f)) {
Ok(v) => v,
Err(_) => {
eprintln!(
"whiteout: a panic in a host-implemented interface was contained \
at the FFI boundary; the operation reports failure"
);
fallback
}
}
}
const BUF_HEADER: usize = 16;
fn leak_buffer(data: Vec<u8>, out_data: *mut *mut u8, out_size: *mut usize) {
unsafe {
*out_data = core::ptr::null_mut();
*out_size = 0;
}
if data.is_empty() {
return;
}
let len = data.len();
let Ok(layout) = std::alloc::Layout::from_size_align(BUF_HEADER + len, BUF_HEADER) else {
return;
};
let base = unsafe { std::alloc::alloc(layout) };
if base.is_null() {
return;
}
unsafe {
(base as *mut usize).write(len);
core::ptr::copy_nonoverlapping(data.as_ptr(), base.add(BUF_HEADER), len);
*out_data = base.add(BUF_HEADER);
*out_size = len;
}
}
unsafe extern "C" fn free_buffer(data: *mut u8) {
if data.is_null() {
return;
}
unsafe {
let base = data.sub(BUF_HEADER);
let len = (base as *const usize).read();
let layout = std::alloc::Layout::from_size_align_unchecked(BUF_HEADER + len, BUF_HEADER);
std::alloc::dealloc(base, layout);
}
}
unsafe fn str_of<'a>(p: *const c_char, len: usize) -> &'a str {
if p.is_null() || len == 0 {
return "";
}
let bytes = unsafe { core::slice::from_raw_parts(p as *const u8, len) };
core::str::from_utf8(bytes).unwrap_or("")
}
unsafe fn bytes_of<'a>(data: *const u8, size: usize) -> &'a [u8] {
if data.is_null() || size == 0 {
return &[];
}
unsafe { core::slice::from_raw_parts(data, size) }
}
pub trait FileSystem: Send + Sync {
fn read_file(&self, path: &str) -> Option<Vec<u8>>;
fn write_file(&self, _path: &str, _data: &[u8]) -> bool {
false
}
fn file_exists(&self, path: &str) -> bool {
self.read_file(path).is_some()
}
}
#[repr(C)]
struct VfsFnTable {
read_file: unsafe extern "C" fn(*mut c_void, *const c_char, usize, *mut *mut u8, *mut usize),
free_buffer: unsafe extern "C" fn(*mut u8),
write_file: unsafe extern "C" fn(*mut c_void, *const c_char, usize, *const u8, usize) -> i32,
file_exists: unsafe extern "C" fn(*mut c_void, *const c_char, usize) -> i32,
}
unsafe fn vfs_of<'a>(userdata: *mut c_void) -> &'a dyn FileSystem {
unsafe { &**(userdata as *const Box<dyn FileSystem>) }
}
unsafe extern "C" fn vfs_read_file(
userdata: *mut c_void,
path: *const c_char,
path_len: usize,
out_data: *mut *mut u8,
out_size: *mut usize,
) {
let data = guard(Vec::new(), || {
let fs = unsafe { vfs_of(userdata) };
let path = unsafe { str_of(path, path_len) };
fs.read_file(path).unwrap_or_default()
});
leak_buffer(data, out_data, out_size);
}
unsafe extern "C" fn vfs_write_file(
userdata: *mut c_void,
path: *const c_char,
path_len: usize,
data: *const u8,
size: usize,
) -> i32 {
guard(0, || {
let fs = unsafe { vfs_of(userdata) };
let path = unsafe { str_of(path, path_len) };
let bytes = unsafe { bytes_of(data, size) };
i32::from(fs.write_file(path, bytes))
})
}
unsafe extern "C" fn vfs_file_exists(
userdata: *mut c_void,
path: *const c_char,
path_len: usize,
) -> i32 {
guard(0, || {
let fs = unsafe { vfs_of(userdata) };
let path = unsafe { str_of(path, path_len) };
i32::from(fs.file_exists(path))
})
}
extern "C" {
fn whiteout_hostimpl_VirtualPathFileSystem_create(
userdata: *mut c_void,
fns: *const VfsFnTable,
) -> *mut c_void;
fn whiteout_hostimpl_VirtualPathFileSystem_delete(handle: *mut c_void);
fn whiteout_hostimpl_test_VirtualPathFileSystem_readFile(
handle: *mut c_void,
path: *const c_char,
) -> RawBytes;
fn whiteout_hostimpl_test_VirtualPathFileSystem_fileExists(
handle: *mut c_void,
path: *const c_char,
) -> i32;
}
pub struct HostFileSystem {
handle: *mut c_void,
userdata: *mut Box<dyn FileSystem>,
}
impl HostFileSystem {
pub fn new<F: FileSystem + 'static>(fs: F) -> Self {
let boxed: Box<dyn FileSystem> = Box::new(fs);
let userdata = Box::into_raw(Box::new(boxed));
let table = VfsFnTable {
read_file: vfs_read_file,
free_buffer,
write_file: vfs_write_file,
file_exists: vfs_file_exists,
};
let handle = unsafe {
whiteout_hostimpl_VirtualPathFileSystem_create(userdata as *mut c_void, &table)
};
HostFileSystem { handle, userdata }
}
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn read_through_native(&self, path: &str) -> Option<Vec<u8>> {
let c = std::ffi::CString::new(path).ok()?;
let raw = unsafe {
whiteout_hostimpl_test_VirtualPathFileSystem_readFile(self.handle, c.as_ptr())
};
unsafe { crate::support::Bytes::from_raw(raw) }.map(|b| b.to_vec())
}
pub fn exists_through_native(&self, path: &str) -> bool {
let Ok(c) = std::ffi::CString::new(path) else {
return false;
};
unsafe {
whiteout_hostimpl_test_VirtualPathFileSystem_fileExists(self.handle, c.as_ptr()) != 0
}
}
}
impl Drop for HostFileSystem {
fn drop(&mut self) {
unsafe {
whiteout_hostimpl_VirtualPathFileSystem_delete(self.handle);
drop(Box::from_raw(self.userdata));
}
}
}
impl core::fmt::Debug for HostFileSystem {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostFileSystem").finish_non_exhaustive()
}
}
unsafe impl Send for HostFileSystem {}
unsafe impl Sync for HostFileSystem {}
pub trait CascFileSystem: Send + Sync {
fn read_file(&self, file_id: u32) -> Option<Vec<u8>>;
fn reserve_file_id(&self, _path: &str) -> Option<u32> {
None
}
fn write_file(&self, _file_id: u32, _data: &[u8]) -> bool {
false
}
fn file_exists(&self, file_id: u32) -> bool {
self.read_file(file_id).is_some()
}
}
#[repr(C)]
struct CascFsFnTable {
read_file: unsafe extern "C" fn(*mut c_void, u32, *mut *mut u8, *mut usize),
free_buffer: unsafe extern "C" fn(*mut u8),
reserve_file_id: unsafe extern "C" fn(*mut c_void, *const c_char, usize, *mut u32) -> i32,
write_file: unsafe extern "C" fn(*mut c_void, u32, *const u8, usize) -> i32,
file_exists: unsafe extern "C" fn(*mut c_void, u32) -> i32,
}
unsafe fn casc_of<'a>(userdata: *mut c_void) -> &'a dyn CascFileSystem {
unsafe { &**(userdata as *const Box<dyn CascFileSystem>) }
}
unsafe extern "C" fn casc_read_file(
userdata: *mut c_void,
file_id: u32,
out_data: *mut *mut u8,
out_size: *mut usize,
) {
let data = guard(Vec::new(), || {
unsafe { casc_of(userdata) }
.read_file(file_id)
.unwrap_or_default()
});
leak_buffer(data, out_data, out_size);
}
unsafe extern "C" fn casc_reserve_file_id(
userdata: *mut c_void,
path: *const c_char,
path_len: usize,
out_id: *mut u32,
) -> i32 {
guard(0, || {
let fs = unsafe { casc_of(userdata) };
let path = unsafe { str_of(path, path_len) };
match fs.reserve_file_id(path) {
Some(id) => {
unsafe { *out_id = id };
1
}
None => 0,
}
})
}
unsafe extern "C" fn casc_write_file(
userdata: *mut c_void,
file_id: u32,
data: *const u8,
size: usize,
) -> i32 {
guard(0, || {
let fs = unsafe { casc_of(userdata) };
let bytes = unsafe { bytes_of(data, size) };
i32::from(fs.write_file(file_id, bytes))
})
}
unsafe extern "C" fn casc_file_exists(userdata: *mut c_void, file_id: u32) -> i32 {
guard(0, || {
i32::from(unsafe { casc_of(userdata) }.file_exists(file_id))
})
}
extern "C" {
fn whiteout_hostimpl_CascFileSystem_create(
userdata: *mut c_void,
fns: *const CascFsFnTable,
) -> *mut c_void;
fn whiteout_hostimpl_CascFileSystem_delete(handle: *mut c_void);
fn whiteout_hostimpl_test_CascFileSystem_readFile(handle: *mut c_void, id: u32) -> RawBytes;
fn whiteout_hostimpl_test_CascFileSystem_fileExists(handle: *mut c_void, id: u32) -> i32;
}
pub struct HostCascFileSystem {
handle: *mut c_void,
userdata: *mut Box<dyn CascFileSystem>,
}
impl HostCascFileSystem {
pub fn new<F: CascFileSystem + 'static>(fs: F) -> Self {
let boxed: Box<dyn CascFileSystem> = Box::new(fs);
let userdata = Box::into_raw(Box::new(boxed));
let table = CascFsFnTable {
read_file: casc_read_file,
free_buffer,
reserve_file_id: casc_reserve_file_id,
write_file: casc_write_file,
file_exists: casc_file_exists,
};
let handle =
unsafe { whiteout_hostimpl_CascFileSystem_create(userdata as *mut c_void, &table) };
HostCascFileSystem { handle, userdata }
}
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn read_through_native(&self, file_id: u32) -> Option<Vec<u8>> {
let raw = unsafe { whiteout_hostimpl_test_CascFileSystem_readFile(self.handle, file_id) };
unsafe { crate::support::Bytes::from_raw(raw) }.map(|b| b.to_vec())
}
pub fn exists_through_native(&self, file_id: u32) -> bool {
unsafe { whiteout_hostimpl_test_CascFileSystem_fileExists(self.handle, file_id) != 0 }
}
}
impl Drop for HostCascFileSystem {
fn drop(&mut self) {
unsafe {
whiteout_hostimpl_CascFileSystem_delete(self.handle);
drop(Box::from_raw(self.userdata));
}
}
}
impl core::fmt::Debug for HostCascFileSystem {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostCascFileSystem").finish_non_exhaustive()
}
}
unsafe impl Send for HostCascFileSystem {}
unsafe impl Sync for HostCascFileSystem {}
pub mod http_capability {
pub const NONE: u32 = 0;
pub const HTTP2_MULTIPLEXING: u32 = 0x1;
}
pub struct HttpResponder {
handle: *mut c_void,
}
impl HttpResponder {
pub fn respond(self, status: i32, body: &[u8]) {
let me = core::mem::ManuallyDrop::new(self);
unsafe {
whiteout_hostimpl_HttpResponseCallback_fire(
me.handle,
status,
body.as_ptr(),
body.len(),
core::ptr::null(),
);
}
}
pub fn fail(self, error: &str) {
let me = core::mem::ManuallyDrop::new(self);
let c = std::ffi::CString::new(error).unwrap_or_default();
unsafe {
whiteout_hostimpl_HttpResponseCallback_fire(
me.handle,
0,
core::ptr::null(),
0,
c.as_ptr(),
);
}
}
}
impl Drop for HttpResponder {
fn drop(&mut self) {
unsafe { whiteout_hostimpl_HttpResponseCallback_cancel(self.handle) };
}
}
impl core::fmt::Debug for HttpResponder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HttpResponder").finish_non_exhaustive()
}
}
unsafe impl Send for HttpResponder {}
pub trait HttpHandler: Send + Sync {
fn capabilities(&self) -> u32 {
http_capability::NONE
}
fn get(&self, url: &str, responder: HttpResponder);
fn get_range(&self, url: &str, start: u64, end: u64, responder: HttpResponder);
}
#[repr(C)]
struct HttpFnTable {
capabilities: unsafe extern "C" fn(*mut c_void) -> u32,
get_async: unsafe extern "C" fn(*mut c_void, *const c_char, usize, *mut c_void),
get_range_async: unsafe extern "C" fn(*mut c_void, *const c_char, usize, u64, u64, *mut c_void),
}
unsafe fn http_of<'a>(userdata: *mut c_void) -> &'a dyn HttpHandler {
unsafe { &**(userdata as *const Box<dyn HttpHandler>) }
}
unsafe extern "C" fn http_capabilities(userdata: *mut c_void) -> u32 {
guard(http_capability::NONE, || {
unsafe { http_of(userdata) }.capabilities()
})
}
unsafe extern "C" fn http_get_async(
userdata: *mut c_void,
url: *const c_char,
url_len: usize,
callback: *mut c_void,
) {
let responder = HttpResponder { handle: callback };
guard((), move || {
let h = unsafe { http_of(userdata) };
let url = unsafe { str_of(url, url_len) };
h.get(url, responder);
});
}
unsafe extern "C" fn http_get_range_async(
userdata: *mut c_void,
url: *const c_char,
url_len: usize,
start: u64,
end: u64,
callback: *mut c_void,
) {
let responder = HttpResponder { handle: callback };
guard((), move || {
let h = unsafe { http_of(userdata) };
let url = unsafe { str_of(url, url_len) };
h.get_range(url, start, end, responder);
});
}
extern "C" {
fn whiteout_hostimpl_HttpHandler_create(
userdata: *mut c_void,
fns: *const HttpFnTable,
) -> *mut c_void;
fn whiteout_hostimpl_HttpHandler_delete(handle: *mut c_void);
fn whiteout_hostimpl_HttpResponseCallback_fire(
callback: *mut c_void,
status: i32,
body: *const u8,
body_len: usize,
error: *const c_char,
);
fn whiteout_hostimpl_HttpResponseCallback_cancel(callback: *mut c_void);
fn whiteout_hostimpl_test_HttpHandler_capabilities(handle: *mut c_void) -> u32;
fn whiteout_hostimpl_test_HttpHandler_getAsync(
handle: *mut c_void,
url: *const c_char,
out_status: *mut i32,
out_body: *mut RawBytes,
out_error: *mut RawCString,
);
fn whiteout_hostimpl_test_HttpHandler_getRangeAsync(
handle: *mut c_void,
url: *const c_char,
start: u64,
end: u64,
out_status: *mut i32,
out_body: *mut RawBytes,
out_error: *mut RawCString,
);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpOutcome {
pub status: i32,
pub body: Vec<u8>,
pub error: String,
}
fn empty_bytes() -> RawBytes {
RawBytes {
data: core::ptr::null(),
size: 0,
owner: core::ptr::null_mut(),
}
}
fn empty_cstring() -> RawCString {
RawCString {
chars: core::ptr::null(),
length: 0,
owner: core::ptr::null_mut(),
}
}
unsafe fn collect_outcome(status: i32, body: RawBytes, error: RawCString) -> HttpOutcome {
let body = unsafe { crate::support::Bytes::from_raw(body) }
.map(|b| b.to_vec())
.unwrap_or_default();
let error = unsafe { crate::support::take_string_opt(error) }.unwrap_or_default();
HttpOutcome {
status,
body,
error,
}
}
pub struct HostHttpHandler {
handle: *mut c_void,
userdata: *mut Box<dyn HttpHandler>,
}
impl HostHttpHandler {
pub fn new<H: HttpHandler + 'static>(handler: H) -> Self {
let boxed: Box<dyn HttpHandler> = Box::new(handler);
let userdata = Box::into_raw(Box::new(boxed));
let table = HttpFnTable {
capabilities: http_capabilities,
get_async: http_get_async,
get_range_async: http_get_range_async,
};
let handle =
unsafe { whiteout_hostimpl_HttpHandler_create(userdata as *mut c_void, &table) };
HostHttpHandler { handle, userdata }
}
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn capabilities_through_native(&self) -> u32 {
unsafe { whiteout_hostimpl_test_HttpHandler_capabilities(self.handle) }
}
pub fn get_through_native(&self, url: &str) -> HttpOutcome {
let c = std::ffi::CString::new(url).unwrap_or_default();
let mut status = 0i32;
let mut body = empty_bytes();
let mut error = empty_cstring();
unsafe {
whiteout_hostimpl_test_HttpHandler_getAsync(
self.handle,
c.as_ptr(),
&mut status,
&mut body,
&mut error,
);
collect_outcome(status, body, error)
}
}
pub fn get_range_through_native(&self, url: &str, start: u64, end: u64) -> HttpOutcome {
let c = std::ffi::CString::new(url).unwrap_or_default();
let mut status = 0i32;
let mut body = empty_bytes();
let mut error = empty_cstring();
unsafe {
whiteout_hostimpl_test_HttpHandler_getRangeAsync(
self.handle,
c.as_ptr(),
start,
end,
&mut status,
&mut body,
&mut error,
);
collect_outcome(status, body, error)
}
}
}
impl Drop for HostHttpHandler {
fn drop(&mut self) {
unsafe {
whiteout_hostimpl_HttpHandler_delete(self.handle);
drop(Box::from_raw(self.userdata));
}
}
}
impl core::fmt::Debug for HostHttpHandler {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostHttpHandler").finish_non_exhaustive()
}
}
unsafe impl Send for HostHttpHandler {}
unsafe impl Sync for HostHttpHandler {}
pub struct WorkerTask {
fn_handle: *mut c_void,
wait: Option<(*mut c_void, u64)>,
signal: Option<(*mut c_void, u64)>,
}
impl WorkerTask {
pub fn run(self) {
let me = core::mem::ManuallyDrop::new(self);
unsafe {
if let Some((sem, value)) = me.wait {
whiteout_hostimpl_TimelineSemaphore_await(sem, value);
}
whiteout_hostimpl_WorkerTaskFn_fire(me.fn_handle);
if let Some((sem, value)) = me.signal {
whiteout_hostimpl_TimelineSemaphore_signal(sem, value);
}
}
}
}
impl Drop for WorkerTask {
fn drop(&mut self) {
unsafe { whiteout_hostimpl_WorkerTaskFn_cancel(self.fn_handle) };
}
}
impl core::fmt::Debug for WorkerTask {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WorkerTask").finish_non_exhaustive()
}
}
unsafe impl Send for WorkerTask {}
pub trait WorkerPool: Send + Sync {
fn submit(&self, task: WorkerTask);
fn wait_idle(&self);
fn thread_count(&self) -> usize;
}
#[repr(C)]
struct WorkerTaskFlat {
fn_handle: *mut c_void,
wait_semaphore: *mut c_void,
wait_value: u64,
signal_semaphore: *mut c_void,
signal_value: u64,
}
#[repr(C)]
struct WorkerPoolFnTable {
submit: unsafe extern "C" fn(*mut c_void, *const WorkerTaskFlat),
wait_idle: unsafe extern "C" fn(*mut c_void),
thread_count: unsafe extern "C" fn(*mut c_void) -> usize,
}
unsafe fn pool_of<'a>(userdata: *mut c_void) -> &'a dyn WorkerPool {
unsafe { &**(userdata as *const Box<dyn WorkerPool>) }
}
unsafe extern "C" fn pool_submit(userdata: *mut c_void, flat: *const WorkerTaskFlat) {
if flat.is_null() {
return;
}
let flat = unsafe { &*flat };
let task = WorkerTask {
fn_handle: flat.fn_handle,
wait: (!flat.wait_semaphore.is_null()).then_some((flat.wait_semaphore, flat.wait_value)),
signal: (!flat.signal_semaphore.is_null())
.then_some((flat.signal_semaphore, flat.signal_value)),
};
guard((), move || {
unsafe { pool_of(userdata) }.submit(task);
});
}
unsafe extern "C" fn pool_wait_idle(userdata: *mut c_void) {
guard((), || {
unsafe { pool_of(userdata) }.wait_idle();
});
}
unsafe extern "C" fn pool_thread_count(userdata: *mut c_void) -> usize {
guard(1, || {
unsafe { pool_of(userdata) }.thread_count()
})
}
extern "C" {
fn whiteout_hostimpl_WorkerPool_create(
userdata: *mut c_void,
fns: *const WorkerPoolFnTable,
) -> *mut c_void;
fn whiteout_hostimpl_WorkerPool_delete(handle: *mut c_void);
fn whiteout_hostimpl_WorkerTaskFn_fire(fn_handle: *mut c_void);
fn whiteout_hostimpl_WorkerTaskFn_cancel(fn_handle: *mut c_void);
fn whiteout_hostimpl_TimelineSemaphore_await(sem: *mut c_void, value: u64);
fn whiteout_hostimpl_TimelineSemaphore_signal(sem: *mut c_void, value: u64);
fn whiteout_hostimpl_test_WorkerPool_threadCount(handle: *mut c_void) -> usize;
fn whiteout_hostimpl_test_WorkerPool_waitIdle(handle: *mut c_void);
fn whiteout_hostimpl_test_WorkerPool_submitIncrementSentinel(
handle: *mut c_void,
out_sentinel: *mut i32,
);
}
pub struct HostWorkerPool {
handle: *mut c_void,
userdata: *mut Box<dyn WorkerPool>,
}
impl HostWorkerPool {
pub fn new<P: WorkerPool + 'static>(pool: P) -> Self {
let boxed: Box<dyn WorkerPool> = Box::new(pool);
let userdata = Box::into_raw(Box::new(boxed));
let table = WorkerPoolFnTable {
submit: pool_submit,
wait_idle: pool_wait_idle,
thread_count: pool_thread_count,
};
let handle =
unsafe { whiteout_hostimpl_WorkerPool_create(userdata as *mut c_void, &table) };
HostWorkerPool { handle, userdata }
}
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn thread_count_through_native(&self) -> usize {
unsafe { whiteout_hostimpl_test_WorkerPool_threadCount(self.handle) }
}
pub fn wait_idle_through_native(&self) {
unsafe { whiteout_hostimpl_test_WorkerPool_waitIdle(self.handle) }
}
pub fn submit_sentinel_through_native(&self, sentinel: &mut i32) {
unsafe {
whiteout_hostimpl_test_WorkerPool_submitIncrementSentinel(self.handle, sentinel);
}
}
}
impl Drop for HostWorkerPool {
fn drop(&mut self) {
unsafe {
whiteout_hostimpl_WorkerPool_delete(self.handle);
drop(Box::from_raw(self.userdata));
}
}
}
impl core::fmt::Debug for HostWorkerPool {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostWorkerPool").finish_non_exhaustive()
}
}
unsafe impl Send for HostWorkerPool {}
unsafe impl Sync for HostWorkerPool {}