2026-10-02 14:53:02 +08:00
|
|
|
|
use std::cell::RefCell;
|
2026-10-16 23:46:04 +08:00
|
|
|
|
use std::ffi::{CStr, c_char};
|
|
|
|
|
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
2026-10-02 14:53:02 +08:00
|
|
|
|
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<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 记下本线程最近的失败信息
|
|
|
|
|
|
fn set_error(message: impl AsRef<str>) {
|
|
|
|
|
|
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())?;
|
2026-10-16 23:46:04 +08:00
|
|
|
|
if text.is_empty() {
|
|
|
|
|
|
None
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Some(text.to_string())
|
|
|
|
|
|
}
|
2026-10-02 14:53:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
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)) }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-05 00:15:01 +08:00
|
|
|
|
/// 离线维护系统目录并返回 JSON
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Safety
|
2026-10-11 17:32:12 +08:00
|
|
|
|
/// `config_path` 为 NULL 或有效 NUL 结尾字符串;请求字节在调用期间有效。
|
|
|
|
|
|
/// 输出指针可写且不与输入重叠;响应使用 free 释放。
|
2026-10-05 00:15:01 +08:00
|
|
|
|
#[unsafe(no_mangle)]
|
|
|
|
|
|
pub unsafe extern "C" fn chusql_storage_maintenance(
|
2026-10-16 23:46:04 +08:00
|
|
|
|
config_path: *const c_char,
|
|
|
|
|
|
request: *const u8,
|
|
|
|
|
|
request_len: usize,
|
|
|
|
|
|
out: *mut *mut u8,
|
|
|
|
|
|
out_len: *mut usize,
|
2026-10-05 00:15:01 +08:00
|
|
|
|
) -> i32 {
|
2026-10-16 23:46:04 +08:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-10-11 17:32:12 +08:00
|
|
|
|
set_error("invalid maintenance argument");
|
|
|
|
|
|
return ERR;
|
|
|
|
|
|
}
|
2026-10-05 00:15:01 +08:00
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
2026-10-16 23:46:04 +08:00
|
|
|
|
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())?;
|
2026-10-05 00:15:01 +08:00
|
|
|
|
Storage::maintenance(path, text)
|
|
|
|
|
|
}));
|
|
|
|
|
|
let response = match result {
|
|
|
|
|
|
Ok(Ok(response)) => response,
|
2026-10-16 23:46:04 +08:00
|
|
|
|
Ok(Err(err)) => {
|
|
|
|
|
|
set_error(err);
|
|
|
|
|
|
return ERR;
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(_) => {
|
|
|
|
|
|
set_error("panic in system maintenance");
|
|
|
|
|
|
return ERR;
|
|
|
|
|
|
}
|
2026-10-05 00:15:01 +08:00
|
|
|
|
};
|
2026-10-11 17:32:12 +08:00
|
|
|
|
let buffer = response.into_bytes().into_boxed_slice();
|
2026-10-16 23:46:04 +08:00
|
|
|
|
unsafe {
|
|
|
|
|
|
*out_len = buffer.len();
|
|
|
|
|
|
*out = Box::into_raw(buffer) as *mut u8;
|
|
|
|
|
|
}
|
2026-10-05 00:15:01 +08:00
|
|
|
|
OK
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
|
/// 处理一条请求,响应缓冲须归还
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Safety
|
2026-10-11 17:32:12 +08:00
|
|
|
|
/// `handle` 在调用期间有效且不能并发关闭;请求字节在调用期间有效。
|
|
|
|
|
|
/// `out`/`out_len` 可写且不与输入重叠;响应使用 free 释放。
|
2026-10-02 14:53:02 +08:00
|
|
|
|
#[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 };
|
|
|
|
|
|
}
|
2026-10-16 23:46:04 +08:00
|
|
|
|
if handle.is_null()
|
|
|
|
|
|
|| request.is_null()
|
|
|
|
|
|
|| out.is_null()
|
|
|
|
|
|
|| out_len.is_null()
|
|
|
|
|
|
|| request_len > isize::MAX as usize
|
|
|
|
|
|
{
|
2026-10-02 14:53:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-11 17:32:12 +08:00
|
|
|
|
let buffer = response.into_bytes().into_boxed_slice();
|
2026-10-02 14:53:02 +08:00
|
|
|
|
unsafe {
|
2026-10-11 17:32:12 +08:00
|
|
|
|
*out_len = buffer.len();
|
|
|
|
|
|
*out = Box::into_raw(buffer) as *mut u8;
|
2026-10-02 14:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
OK
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 归还响应缓冲
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Safety
|
2026-10-11 17:32:12 +08:00
|
|
|
|
/// `data`/`len` 为 request 或 maintenance 原样返回且未释放的一对值。
|
2026-10-02 14:53:02 +08:00
|
|
|
|
#[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
|
2026-10-11 17:32:12 +08:00
|
|
|
|
/// `out_len` 可写,或为 NULL;返回字节仅在本线程下次设置错误前有效,不能释放。
|
2026-10-02 14:53:02 +08:00
|
|
|
|
#[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() };
|
|
|
|
|
|
}
|
2026-10-16 23:46:04 +08:00
|
|
|
|
if bytes.is_empty() {
|
|
|
|
|
|
ptr::null()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bytes.as_ptr()
|
|
|
|
|
|
}
|
2026-10-02 14:53:02 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|