diff --git a/Cargo.lock b/Cargo.lock index eb62479b5383..c57103aefd2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2275,6 +2275,7 @@ dependencies = [ name = "opendal-c" version = "0.1.0" dependencies = [ + "bytes", "cbindgen", "opendal", ] diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml index d5e30e870a76..df673a10e823 100644 --- a/bindings/c/Cargo.toml +++ b/bindings/c/Cargo.toml @@ -33,4 +33,5 @@ doc = false cbindgen = "0.24.0" [dependencies] +bytes = "1.4.0" opendal = { version = "0.30", path = "../../core" } diff --git a/bindings/c/Makefile b/bindings/c/Makefile index f949cc09169a..a3545d04969a 100644 --- a/bindings/c/Makefile +++ b/bindings/c/Makefile @@ -19,7 +19,7 @@ RPATH=$(PWD)/../../target/debug CFLAGS = -I./include LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lopendal_c -OBJ_DIR=build +OBJ_DIR=./build .PHONY: all all: build test @@ -32,6 +32,7 @@ build: .PHONY: test test: $(CC) tests/hello.c -o $(OBJ_DIR)/hello $(CFLAGS) $(LDFLAGS) $(LIBS) + $(CC) tests/basicio.c -o $(OBJ_DIR)/basicio $(CFLAGS) $(LDFLAGS) $(LIBS) .PHONY: clean clean: diff --git a/bindings/c/cbindgen.toml b/bindings/c/cbindgen.toml index e871498f4fc9..af300186ddef 100644 --- a/bindings/c/cbindgen.toml +++ b/bindings/c/cbindgen.toml @@ -44,3 +44,11 @@ include_guard = "_OPENDAL_H" language = "C" no_includes = true sys_includes = ["stdint.h", "stddef.h", "stdbool.h"] + +[export] +prefix = "opendal_" + +[export.rename] +"OperatorPtr" = "operator_ptr" +"Bytes" = "bytes" + diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 3cd506b4f2b0..3f5730530452 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -25,15 +25,90 @@ #include #include +/* + The [`OperatorPtr`] owns a pointer to a [`BlockingOperator`]. + It is also the key struct that OpenDAL's APIs access the real + operator's memory. The use of OperatorPtr is zero cost, it + only returns a reference of the underlying Operator. + */ +typedef struct opendal_operator_ptr { + const void *ptr; +} opendal_operator_ptr; + +/* + The [`Bytes`] type is a C-compatible substitute for [`Bytes`] + in Rust, it will not be deallocated automatically like what + has been done in Rust. Instead, you have to call [`free_bytes`] + to free the heap memory to avoid memory leak. + The field `data` should not be modified since it might causes + the reallocation of the Vector. + */ +typedef struct opendal_bytes { + const uint8_t *data; + uintptr_t len; +} opendal_bytes; + #ifdef __cplusplus extern "C" { #endif // __cplusplus +/* + Constructs a new [`OperatorPtr`] which contains a underlying [`BlockingOperator`] + If the scheme is invalid, or the operator constructions failed, a nullptr is returned + + # Safety + + It is [safe] under two cases below + * The memory pointed to by `scheme` must contain a valid nul terminator at the end of + the string. + * The `scheme` points to NULL, this function simply returns you a null opendal_operator_ptr + */ +struct opendal_operator_ptr opendal_new_operator(const char *scheme); + +/* + Write the data into the path blockingly by operator, returns whether the write succeeds + + # Safety + + It is [safe] under two cases below + * The memory pointed to by `path` must contain a valid nul terminator at the end of + the string. + * The `path` points to NULL, this function simply returns you false + */ +bool opendal_operator_blocking_write(struct opendal_operator_ptr op_ptr, + const char *path, + struct opendal_bytes bytes); + +/* + Read the data out from path into a [`Bytes`] blockingly by operator, returns + a pointer to the [`Bytes`] if succeeds, nullptr otherwise + + # Safety + + It is [safe] under two cases below + * The memory pointed to by `path` must contain a valid nul terminator at the end of + the string. + * The `path` points to NULL, this function simply returns you a nullptr + */ +struct opendal_bytes *opendal_operator_blocking_read(struct opendal_operator_ptr op_ptr, + const char *path); + /* Hello, OpenDAL! */ void hello_opendal(void); +/* + Returns whether the [`OperatorPtr`] is valid, i.e. whether + there exists a underlying [`BlockingOperator`] + */ +bool opendal_is_ptr_valid(const struct opendal_operator_ptr *self); + +/* + Frees the heap memory used by the [`Bytes`] + */ +void opendal_free_bytes(const struct opendal_bytes *vec); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index 814db98caa67..44406b5c1d39 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -15,12 +15,124 @@ // specific language governing permissions and limitations // under the License. -use opendal::services::Memory; -use opendal::Operator; +mod types; + +use std::collections::HashMap; +use std::os::raw::c_char; +use std::str::FromStr; + +use crate::types::{Bytes, OperatorPtr}; +use ::opendal as od; + +/// Constructs a new [`OperatorPtr`] which contains a underlying [`BlockingOperator`] +/// If the scheme is invalid, or the operator constructions failed, a nullptr is returned +/// +/// # Safety +/// +/// It is [safe] under two cases below +/// * The memory pointed to by `scheme` must contain a valid nul terminator at the end of +/// the string. +/// * The `scheme` points to NULL, this function simply returns you a null opendal_operator_ptr +#[no_mangle] +pub unsafe extern "C" fn opendal_new_operator(scheme: *const c_char) -> OperatorPtr { + use od::services::*; + + if scheme.is_null() { + return OperatorPtr::null(); + } + + let scheme_str = unsafe { std::ffi::CStr::from_ptr(scheme).to_str().unwrap() }; + let scheme = match od::Scheme::from_str(scheme_str) { + Ok(s) => s, + Err(_) => return OperatorPtr::null(), + }; + + // todo: api for map construction + let map = HashMap::default(); + + let op = match scheme { + od::Scheme::Memory => { + let b = od::Operator::from_map::(map); + match b { + Ok(b) => b.finish(), + Err(_) => return OperatorPtr::null(), + } + } + od::Scheme::Fs => { + let b = od::Operator::from_map::(map); + match b { + Ok(b) => b.finish(), + Err(_) => return OperatorPtr::null(), + } + } + _ => return OperatorPtr::null(), + } + .blocking(); + + // this prevents the operator memory from being dropped by the Box + let op = Box::leak(Box::new(op)); + + OperatorPtr::from(op) +} + +/// Write the data into the path blockingly by operator, returns whether the write succeeds +/// +/// # Safety +/// +/// It is [safe] under two cases below +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// * The `path` points to NULL, this function simply returns you false +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_blocking_write( + op_ptr: OperatorPtr, + path: *const c_char, + bytes: Bytes, +) -> bool { + if path.is_null() { + return false; + } + + let op = op_ptr.get_ref(); + let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; + op.write(path, bytes).is_ok() +} + +/// Read the data out from path into a [`Bytes`] blockingly by operator, returns +/// a pointer to the [`Bytes`] if succeeds, nullptr otherwise +/// +/// # Safety +/// +/// It is [safe] under two cases below +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// * The `path` points to NULL, this function simply returns you a nullptr +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_blocking_read( + op_ptr: OperatorPtr, + path: *const c_char, +) -> *mut Bytes { + if path.is_null() { + return std::ptr::null_mut(); + } + + let op = op_ptr.get_ref(); + let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; + let data = op.read(path); + match data { + Ok(d) => { + let mut v = std::mem::ManuallyDrop::new(Bytes::from_vec(d)); + &mut *v + } + Err(_) => std::ptr::null_mut(), + } +} /// Hello, OpenDAL! #[no_mangle] pub extern "C" fn hello_opendal() { - let op = Operator::new(Memory::default()).unwrap().finish(); + let op = od::Operator::new(od::services::Memory::default()) + .unwrap() + .finish(); println!("{op:?}") } diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs new file mode 100644 index 000000000000..199c2136c4a1 --- /dev/null +++ b/bindings/c/src/types.rs @@ -0,0 +1,111 @@ +// 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::os::raw::c_void; + +use ::opendal as od; + +/// The [`OperatorPtr`] owns a pointer to a [`BlockingOperator`]. +/// It is also the key struct that OpenDAL's APIs access the real +/// operator's memory. The use of OperatorPtr is zero cost, it +/// only returns a reference of the underlying Operator. +#[repr(C)] +pub struct OperatorPtr { + // this is typed with [`c_void`] because cbindgen does not + // support our own custom type. + ptr: *const c_void, +} + +impl OperatorPtr { + /// Creates an OperatorPtr will nullptr, indicating this [`OperatorPtr`] + /// is invalid + pub(crate) fn null() -> Self { + Self { + ptr: std::ptr::null(), + } + } + + /// Returns a reference to the underlying [`BlockingOperator`] + pub(crate) fn get_ref(&self) -> &od::BlockingOperator { + unsafe { &*(self.ptr as *const od::BlockingOperator) } + } + + /// Returns whether the [`OperatorPtr`] is valid, i.e. whether + /// there exists a underlying [`BlockingOperator`] + #[no_mangle] + pub extern "C" fn opendal_is_ptr_valid(&self) -> bool { + !self.ptr.is_null() + } +} + +#[allow(clippy::from_over_into)] +impl From<&od::BlockingOperator> for OperatorPtr { + fn from(value: &od::BlockingOperator) -> Self { + Self { + ptr: value as *const _ as *const c_void, + } + } +} + +#[allow(clippy::from_over_into)] +impl From<&mut od::BlockingOperator> for OperatorPtr { + fn from(value: &mut od::BlockingOperator) -> Self { + Self { + ptr: value as *const _ as *const c_void, + } + } +} + +/// The [`Bytes`] type is a C-compatible substitute for [`Bytes`] +/// in Rust, it will not be deallocated automatically like what +/// has been done in Rust. Instead, you have to call [`free_bytes`] +/// to free the heap memory to avoid memory leak. +/// The field `data` should not be modified since it might causes +/// the reallocation of the Vector. +#[repr(C)] +pub struct Bytes { + pub data: *const u8, + pub len: usize, +} + +impl Bytes { + /// Construct a [`Vector`] from the Rust [`Vec`] of bytes + pub(crate) fn from_vec(vec: Vec) -> Self { + let data = vec.as_ptr() as *const u8; + let len = vec.len(); + std::mem::forget(vec); // To avoid deallocation of the vec. + Self { data, len } + } +} + +#[allow(clippy::from_over_into)] +impl Into for Bytes { + fn into(self) -> bytes::Bytes { + let slice = unsafe { std::slice::from_raw_parts(self.data, self.len) }; + bytes::Bytes::from_static(slice) + } +} + +/// Frees the heap memory used by the [`Bytes`] +#[no_mangle] +pub extern "C" fn opendal_free_bytes(vec: *const Bytes) { + unsafe { + // this deallocates the vector by reconstructing the vector and letting + // it be dropped when its out of scope + Vec::from_raw_parts((*vec).data as *mut u8, (*vec).len, (*vec).len); + } +} diff --git a/bindings/c/tests/basicio.c b/bindings/c/tests/basicio.c new file mode 100644 index 000000000000..dd1d149781bf --- /dev/null +++ b/bindings/c/tests/basicio.c @@ -0,0 +1,51 @@ +/** + * 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. + */ +#include "stdio.h" +#include "opendal.h" + +int main(int argc, char *argv[]) { + // creates a memory operator + char scheme[] = "memory"; + opendal_operator_ptr ptr = opendal_new_operator(scheme); + if (!opendal_is_ptr_valid(&ptr)) { + return -1; + } + + // write some contents by the operator + char path[] = "test"; + char content[] = "Hello World"; + const opendal_bytes data = { + .len = sizeof(content) - 1, + .data = (uint8_t*)content, + }; + if (!opendal_operator_blocking_write(ptr, path, data)) { + return -2; + } + + // reads the data out from the bytes + opendal_bytes* v = opendal_operator_blocking_read(ptr, path); + for (int i = 0; i < v->len; i++) { + printf("%c", (char)(v->data[i])); + } + + // free the bytes's heap memory + opendal_free_bytes(v); + + return 0; +}