Skip to content

Commit

Permalink
feat(bindings/c): framework of add basic io and init logics (#1861)
Browse files Browse the repository at this point in the history
* feat(bindings/c): framework of add basic io and init logics

* Add the init logics for operator
* Add the basic io operations (r/w)
* Add the test for basicio

Fixes: #1201
Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>

* feat(bindings/c): remove place holder code for c binding

* The hello_opendal() works as a place holder for c binding,
  remove it since it will not be used anymore

Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>

* feat(bindings/c): use transparent layout for more native ptr operation

* Previously the type opendal_operator_ptr uses a C layout, i.e.,
  it STORES a pointer inside it. This commit changes it to transparent
  layout, which guarantees it has the SAME layout of the inner pointer.
  This allows the opendal_operator_ptr uses native boolean operation to
  check validity. e.g. (!ptr) means invalid.

Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>

* feat(bindings/c): add error and result types for error handling

* Add error code opendal_error for error handling, which decouples
  the od::ErrorKind

Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>

---------

Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>
  • Loading branch information
xyjixyjixyji authored Apr 7, 2023
1 parent 4a8d464 commit 920c3d6
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 35 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 @@ -35,4 +35,5 @@ doc = false
cbindgen = "0.24.0"

[dependencies]
bytes = "1.4.0"
opendal = { version = "0.30", path = "../../core" }
6 changes: 3 additions & 3 deletions 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 @@ -31,9 +31,9 @@ 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:
cargo clean
rm -rf "./$(OBJ_DIR)"
rm -rf $(OBJ_DIR)
137 changes: 135 additions & 2 deletions bindings/c/include/opendal.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,147 @@
#include <stddef.h>
#include <stdbool.h>

/*
The error code for opendal APIs in C binding
*/
typedef enum opendal_code {
/*
All is well
*/
OPENDAL_OK,
/*
General error
*/
OPENDAL_ERROR,
/*
returning it back. For example, s3 returns an internal service error.
*/
OPENDAL_UNEXPECTED,
/*
Underlying service doesn't support this operation.
*/
OPENDAL_UNSUPPORTED,
/*
The config for backend is invalid.
*/
OPENDAL_CONFIG_INVALID,
/*
The given path is not found.
*/
OPENDAL_NOT_FOUND,
/*
The given path doesn't have enough permission for this operation
*/
OPENDAL_PERMISSION_DENIED,
/*
The given path is a directory.
*/
OPENDAL_IS_A_DIRECTORY,
/*
The given path is not a directory.
*/
OPENDAL_NOT_A_DIRECTORY,
/*
The given path already exists thus we failed to the specified operation on it.
*/
OPENDAL_ALREADY_EXISTS,
/*
Requests that sent to this path is over the limit, please slow down.
*/
OPENDAL_RATE_LIMITED,
/*
The given file paths are same.
*/
OPENDAL_IS_SAME_FILE,
} opendal_code;

/*
The [`OperatorPtr`] owns a pointer to a [`od::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.
The [`OperatorPtr`] also has a transparent layout, allowing you
to check its validity by native boolean operator.
e.g. you could check by (!ptr) on a opendal_operator_ptr type
*/
typedef const void *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 [`opendal_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;

/*
The Rust-like Result type of opendal C binding, it contains
the data that the read operation returns and a error code
If the read operation failed, the `data` fields should be a nullptr
and the error code is NOT OPENDAL_OK.
*/
typedef struct opendal_result_read {
struct opendal_bytes *data;
enum opendal_code code;
} opendal_result_read;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/*
Hello, OpenDAL!
Returns a result type [`opendal_result_op`], with operator_ptr. If the construction succeeds
the error is nullptr, otherwise it contains the error information.
# 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
*/
opendal_operator_ptr opendal_operator_new(const char *scheme);

/*
Write the data into the path blockingly by operator, returns the error code OPENDAL_OK
if succeeds, others 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 false
*/
enum opendal_code opendal_operator_blocking_write(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 result with error code. If the error code is not OPENDAL_OK, the `data` field
of the result points to NULL.
# 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_result_read opendal_operator_blocking_read(opendal_operator_ptr op_ptr,
const char *path);

/*
Frees the heap memory used by the [`Bytes`]
*/
void hello_opendal(void);
void opendal_bytes_free(const struct opendal_bytes *vec);

#ifdef __cplusplus
} // extern "C"
Expand Down
81 changes: 81 additions & 0 deletions bindings/c/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 ::opendal as od;

/// The wrapper type for opendal's error, wrapped because of the
/// orphan rule
pub struct opendal_error(od::Error);

/// The error code for opendal APIs in C binding
#[repr(C)]
pub enum opendal_code {
/// All is well
OPENDAL_OK,
/// General error
// todo: make details in the `opendal_error *`
OPENDAL_ERROR,
/// returning it back. For example, s3 returns an internal service error.
OPENDAL_UNEXPECTED,
/// Underlying service doesn't support this operation.
OPENDAL_UNSUPPORTED,
/// The config for backend is invalid.
OPENDAL_CONFIG_INVALID,
/// The given path is not found.
OPENDAL_NOT_FOUND,
/// The given path doesn't have enough permission for this operation
OPENDAL_PERMISSION_DENIED,
/// The given path is a directory.
OPENDAL_IS_A_DIRECTORY,
/// The given path is not a directory.
OPENDAL_NOT_A_DIRECTORY,
/// The given path already exists thus we failed to the specified operation on it.
OPENDAL_ALREADY_EXISTS,
/// Requests that sent to this path is over the limit, please slow down.
OPENDAL_RATE_LIMITED,
/// The given file paths are same.
OPENDAL_IS_SAME_FILE,
}

impl opendal_code {
pub(crate) fn from_opendal_error(e: od::Error) -> Self {
let error = opendal_error(e);
error.error_code()
}
}

impl opendal_error {
/// Convert the [`od::ErrorKind`] of [`od::Error`] to [`opendal_code`]
pub(crate) fn error_code(&self) -> opendal_code {
let e = &self.0;
match e.kind() {
od::ErrorKind::Unexpected => opendal_code::OPENDAL_UNEXPECTED,
od::ErrorKind::Unsupported => opendal_code::OPENDAL_UNSUPPORTED,
od::ErrorKind::ConfigInvalid => opendal_code::OPENDAL_CONFIG_INVALID,
od::ErrorKind::NotFound => opendal_code::OPENDAL_NOT_FOUND,
od::ErrorKind::PermissionDenied => opendal_code::OPENDAL_PERMISSION_DENIED,
od::ErrorKind::IsADirectory => opendal_code::OPENDAL_IS_A_DIRECTORY,
od::ErrorKind::NotADirectory => opendal_code::OPENDAL_NOT_A_DIRECTORY,
od::ErrorKind::AlreadyExists => opendal_code::OPENDAL_ALREADY_EXISTS,
od::ErrorKind::RateLimited => opendal_code::OPENDAL_RATE_LIMITED,
od::ErrorKind::IsSameFile => opendal_code::OPENDAL_IS_SAME_FILE,
// if this is triggered, check the [`core`] crate and add a
// new error code accordingly
_ => panic!("The newly added ErrorKind in core crate is not handled in C bindings"),
}
}
}
126 changes: 120 additions & 6 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,126 @@
// specific language governing permissions and limitations
// under the License.

use opendal::services::Memory;
use opendal::Operator;
#![allow(non_camel_case_types)]

/// Hello, OpenDAL!
mod error;
mod macros;
mod result;
mod types;

use std::collections::HashMap;
use std::os::raw::c_char;
use std::str::FromStr;

use crate::types::{opendal_bytes, opendal_operator_ptr};

use ::opendal as od;
use error::opendal_code;
use result::opendal_result_read;

/// Returns a result type [`opendal_result_op`], with operator_ptr. If the construction succeeds
/// the error is nullptr, otherwise it contains the error information.
///
/// # 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 extern "C" fn hello_opendal() {
let op = Operator::new(Memory::default()).unwrap().finish();
println!("{op:?}")
pub unsafe extern "C" fn opendal_operator_new(scheme: *const c_char) -> opendal_operator_ptr {
if scheme.is_null() {
return opendal_operator_ptr::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 opendal_operator_ptr::null();
}
};

// todo: api for map construction
let map = HashMap::default();

let op = match scheme {
od::Scheme::Memory => generate_operator!(od::services::Memory, map),
_ => {
return opendal_operator_ptr::null();
}
}
.blocking();

// this prevents the operator memory from being dropped by the Box
let op = Box::leak(Box::new(op));

opendal_operator_ptr::from(op)
}

/// Write the data into the path blockingly by operator, returns the error code OPENDAL_OK
/// if succeeds, others 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 false
#[no_mangle]
pub unsafe extern "C" fn opendal_operator_blocking_write(
op_ptr: opendal_operator_ptr,
path: *const c_char,
bytes: opendal_bytes,
) -> opendal_code {
if path.is_null() {
return opendal_code::OPENDAL_ERROR;
}

let op = op_ptr.get_ref();
let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() };
match op.write(path, bytes) {
Ok(_) => opendal_code::OPENDAL_OK,
Err(e) => opendal_code::from_opendal_error(e),
}
}

/// Read the data out from path into a [`Bytes`] blockingly by operator, returns
/// a result with error code. If the error code is not OPENDAL_OK, the `data` field
/// of the result points to NULL.
///
/// # 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: opendal_operator_ptr,
path: *const c_char,
) -> opendal_result_read {
if path.is_null() {
return opendal_result_read {
data: std::ptr::null_mut(),
code: opendal_code::OPENDAL_ERROR,
};
}

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 v = Box::new(opendal_bytes::from_vec(d));
opendal_result_read {
data: Box::into_raw(v),
code: opendal_code::OPENDAL_OK,
}
}
Err(e) => opendal_result_read {
data: std::ptr::null_mut(),
code: opendal_code::from_opendal_error(e),
},
}
}
Loading

0 comments on commit 920c3d6

Please sign in to comment.