use std::cell::RefCell; use std::ffi::{CStr, c_char}; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::ptr; use std::slice; use crate::runtime::Storage; // C ABI:Haskell 侧唯一存储入口,请求与响应都是 UTF-8 JSON。 // 不 panic 穿过边界,失败走线程局部 last_error。 const OK: i32 = 0; const ERR: i32 = -1; thread_local! { static LAST_ERROR: RefCell> = const { RefCell::new(Vec::new()) }; } /// 记下本线程最近的失败信息 fn set_error(message: impl AsRef) { let text = message.as_ref().as_bytes().to_vec(); LAST_ERROR.with(|slot| *slot.borrow_mut() = text); } /// 版本号字符串(静态常量,调用方不要释放)。 #[unsafe(no_mangle)] pub extern "C" fn chusql_storage_version() -> *const c_char { const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); VERSION.as_ptr() as *const c_char } /// 打开数据目录;失败返回 NULL /// /// # Safety /// `config_path` 要么是 NULL,要么指向以 NUL 结尾的有效字符串。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_open(config_path: *const c_char) -> *mut Storage { let opened = catch_unwind(AssertUnwindSafe(|| { let path = if config_path.is_null() { None } else { let text = unsafe { CStr::from_ptr(config_path) } .to_str() .map_err(|_| "config path is not valid UTF-8".to_string())?; if text.is_empty() { None } else { Some(text.to_string()) } }; Storage::open(path.as_deref()).map(Box::new) })); match opened { Ok(Ok(storage)) => Box::into_raw(storage), Ok(Err(e)) => { set_error(e); ptr::null_mut() } Err(_) => { set_error("panic in chusql_storage_open"); ptr::null_mut() } } } /// 关闭句柄(NULL 安全) /// /// # Safety /// `handle` 必须是 chusql_storage_open 返回且未被关闭过的句柄。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_close(handle: *mut Storage) { if handle.is_null() { return; } let _ = catch_unwind(AssertUnwindSafe(|| unsafe { drop(Box::from_raw(handle)) })); } /// 离线维护系统目录并返回 JSON /// /// # Safety /// `config_path` 为 NULL 或有效 NUL 结尾字符串;请求字节在调用期间有效。 /// 输出指针可写且不与输入重叠;响应使用 free 释放。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_maintenance( config_path: *const c_char, request: *const u8, request_len: usize, out: *mut *mut u8, out_len: *mut usize, ) -> i32 { if !out.is_null() { unsafe { *out = ptr::null_mut(); } } if !out_len.is_null() { unsafe { *out_len = 0; } } if request.is_null() || out.is_null() || out_len.is_null() || request_len > isize::MAX as usize { set_error("invalid maintenance argument"); return ERR; } let result = catch_unwind(AssertUnwindSafe(|| { let path = if config_path.is_null() { None } else { Some( unsafe { CStr::from_ptr(config_path) } .to_str() .map_err(|e| e.to_string())?, ) }; let text = std::str::from_utf8(unsafe { slice::from_raw_parts(request, request_len) }) .map_err(|e| e.to_string())?; Storage::maintenance(path, text) })); let response = match result { Ok(Ok(response)) => response, Ok(Err(err)) => { set_error(err); return ERR; } Err(_) => { set_error("panic in system maintenance"); return ERR; } }; let buffer = response.into_bytes().into_boxed_slice(); unsafe { *out_len = buffer.len(); *out = Box::into_raw(buffer) as *mut u8; } OK } /// 处理一条请求,响应缓冲须归还 /// /// # Safety /// `handle` 在调用期间有效且不能并发关闭;请求字节在调用期间有效。 /// `out`/`out_len` 可写且不与输入重叠;响应使用 free 释放。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_request( handle: *const Storage, request: *const u8, request_len: usize, out: *mut *mut u8, out_len: *mut usize, ) -> i32 { if !out.is_null() { unsafe { *out = ptr::null_mut() }; } if !out_len.is_null() { unsafe { *out_len = 0 }; } if handle.is_null() || request.is_null() || out.is_null() || out_len.is_null() || request_len > isize::MAX as usize { set_error("invalid argument"); return ERR; } let storage = unsafe { &*handle }; let bytes = unsafe { slice::from_raw_parts(request, request_len) }; let text = match std::str::from_utf8(bytes) { Ok(text) => text, Err(e) => { set_error(format!("request is not valid UTF-8: {}", e)); return ERR; } }; let response = match catch_unwind(AssertUnwindSafe(|| storage.request_line(text))) { Ok(response) => response, Err(_) => { set_error("panic in chusql_storage_request"); return ERR; } }; let buffer = response.into_bytes().into_boxed_slice(); unsafe { *out_len = buffer.len(); *out = Box::into_raw(buffer) as *mut u8; } OK } /// 归还响应缓冲 /// /// # Safety /// `data`/`len` 为 request 或 maintenance 原样返回且未释放的一对值。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_free(data: *mut u8, len: usize) { if data.is_null() { return; } let _ = catch_unwind(AssertUnwindSafe(|| unsafe { let slice = ptr::slice_from_raw_parts_mut(data, len); let _owned: Box<[u8]> = Box::from_raw(slice); })); } /// 取本线程最近的失败信息 /// /// # Safety /// `out_len` 可写,或为 NULL;返回字节仅在本线程下次设置错误前有效,不能释放。 #[unsafe(no_mangle)] pub unsafe extern "C" fn chusql_storage_last_error(out_len: *mut usize) -> *const u8 { LAST_ERROR.with(|slot| { let bytes = slot.borrow(); if !out_len.is_null() { unsafe { *out_len = bytes.len() }; } if bytes.is_empty() { ptr::null() } else { bytes.as_ptr() } }) }