blob: 4d0da9d9dbae05063e441d881893e477de38209a [file]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::ffi::c_void;
use std::mem::size_of;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::slice;
use opendal_core::Operator;
use opendal_dynamic_extension_sdk::{
ByteSlice, ExtensionApiV1, KeyValue, OutputBuffer, RUNTIME_PROTOCOL, STATUS_INVALID_ARGUMENT,
STATUS_OK, STATUS_OPERATION_FAILED, write_output,
};
use opendal_service_fs::Fs;
static API: ExtensionApiV1 = ExtensionApiV1 {
struct_size: size_of::<ExtensionApiV1>(),
required_runtime_protocol: RUNTIME_PROTOCOL,
opendal_version: ByteSlice::from_static(opendal_core::raw::VERSION),
package_id: ByteSlice::from_static("opendal-service-fs-poc"),
component_id: ByteSlice::from_static("fs"),
entry_symbol: ByteSlice::from_static("opendal_service_fs_bootstrap_v1"),
create_operator,
destroy_operator,
operator_info,
operator_write,
operator_read,
};
static TOKIO_RUNTIME: std::sync::LazyLock<tokio::runtime::Runtime> =
std::sync::LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("prototype FS runtime should build")
});
fn fail(error: *mut OutputBuffer, message: impl AsRef<str>) -> i32 {
let _ = unsafe { write_output(error, message.as_ref().as_bytes()) };
STATUS_OPERATION_FAILED
}
unsafe fn option_pairs<'a>(
options: *const KeyValue,
options_len: usize,
) -> Result<Vec<(&'a str, &'a str)>, &'static str> {
if options_len == 0 {
return Ok(Vec::new());
}
if options.is_null() {
return Err("options pointer is null");
}
unsafe { slice::from_raw_parts(options, options_len) }
.iter()
.map(|pair| unsafe { Ok((pair.key.as_str()?, pair.value.as_str()?)) })
.collect()
}
unsafe extern "C" fn create_operator(
options: *const KeyValue,
options_len: usize,
operator: *mut *mut c_void,
error: *mut OutputBuffer,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| unsafe {
if operator.is_null() {
return STATUS_INVALID_ARGUMENT;
}
*operator = std::ptr::null_mut();
let options = match option_pairs(options, options_len) {
Ok(options) => options,
Err(message) => return fail(error, message),
};
let mut builder = Fs::default();
for (key, value) in options {
builder = match key {
"root" => builder.root(value),
"atomic_write_dir" => builder.atomic_write_dir(value),
_ => return fail(error, format!("unsupported FS option {key}")),
};
}
match Operator::new(builder) {
Ok(value) => {
*operator = Box::into_raw(Box::new(value)).cast::<c_void>();
STATUS_OK
}
Err(err) => fail(error, err.to_string()),
}
}))
.unwrap_or_else(|_| fail(error, "FS operator construction panicked"))
}
unsafe extern "C" fn destroy_operator(operator: *mut c_void) {
if operator.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
drop(Box::from_raw(operator.cast::<Operator>()));
}));
}
unsafe fn as_operator<'a>(operator: *mut c_void) -> Result<&'a Operator, &'static str> {
if operator.is_null() {
return Err("FS operator is null");
}
Ok(unsafe { &*operator.cast::<Operator>() })
}
unsafe extern "C" fn operator_info(
operator: *mut c_void,
output: *mut OutputBuffer,
error: *mut OutputBuffer,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| unsafe {
let operator = match as_operator(operator) {
Ok(operator) => operator,
Err(message) => return fail(error, message),
};
let info = operator.info();
let value = serde_json::json!({
"scheme": info.scheme().to_string(),
"name": info.name(),
"root": info.root(),
});
write_output(output, value.to_string().as_bytes())
}))
.unwrap_or_else(|_| fail(error, "FS operator info panicked"))
}
unsafe extern "C" fn operator_write(
operator: *mut c_void,
path: ByteSlice,
data: ByteSlice,
error: *mut OutputBuffer,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| unsafe {
let operator = match as_operator(operator) {
Ok(operator) => operator,
Err(message) => return fail(error, message),
};
let path = match path.as_str() {
Ok(path) => path,
Err(message) => return fail(error, message),
};
let data = match data.as_bytes() {
Ok(data) => data,
Err(message) => return fail(error, message),
};
let _guard = TOKIO_RUNTIME.enter();
let blocking = match opendal_core::blocking::Operator::new(operator.clone()) {
Ok(blocking) => blocking,
Err(err) => return fail(error, err.to_string()),
};
match blocking.write(path, data.to_vec()) {
Ok(_) => STATUS_OK,
Err(err) => fail(error, err.to_string()),
}
}))
.unwrap_or_else(|_| fail(error, "FS write panicked"))
}
unsafe extern "C" fn operator_read(
operator: *mut c_void,
path: ByteSlice,
output: *mut OutputBuffer,
error: *mut OutputBuffer,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| unsafe {
let operator = match as_operator(operator) {
Ok(operator) => operator,
Err(message) => return fail(error, message),
};
let path = match path.as_str() {
Ok(path) => path,
Err(message) => return fail(error, message),
};
let _guard = TOKIO_RUNTIME.enter();
let blocking = match opendal_core::blocking::Operator::new(operator.clone()) {
Ok(blocking) => blocking,
Err(err) => return fail(error, err.to_string()),
};
match blocking.read(path) {
Ok(value) => write_output(output, &value.to_vec()),
Err(err) => fail(error, err.to_string()),
}
}))
.unwrap_or_else(|_| fail(error, "FS read panicked"))
}
#[unsafe(no_mangle)]
/// Returns the FS extension function table.
///
/// # Safety
///
/// `extension` must point to writable storage for an extension table pointer.
pub unsafe extern "C" fn opendal_service_fs_bootstrap_v1(
extension: *mut *const ExtensionApiV1,
) -> i32 {
if extension.is_null() {
return STATUS_INVALID_ARGUMENT;
}
unsafe { *extension = &API };
STATUS_OK
}