Skip to content

Commit

Permalink
feat(bindings/c): framework of add basic io and init logics
Browse files Browse the repository at this point in the history
* Add the init logics for operator
* Add the basic io operations (r/w)
* Add the test for basicio

Fixes: apache#1201
Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>
  • Loading branch information
xyjixyjixyji committed Apr 6, 2023
1 parent 889c84e commit 433162e
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bindings/c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ doc = false
cbindgen = "0.24.0"

[dependencies]
bytes = "1.4.0"
opendal = { version = "0.30", path = "../../core" }
3 changes: 2 additions & 1 deletion bindings/c/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions bindings/c/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

75 changes: 75 additions & 0 deletions bindings/c/include/opendal.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,90 @@
#include <stddef.h>
#include <stdbool.h>

/*
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
Expand Down
118 changes: 115 additions & 3 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Memory>(map);
match b {
Ok(b) => b.finish(),
Err(_) => return OperatorPtr::null(),
}
}
od::Scheme::Fs => {
let b = od::Operator::from_map::<Fs>(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:?}")
}
111 changes: 111 additions & 0 deletions bindings/c/src/types.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) -> 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<bytes::Bytes> 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);
}
}
Loading

0 comments on commit 433162e

Please sign in to comment.