Skip to content

Commit

Permalink
feat(bindings/C): Initially support stat in C binding (#2249)
Browse files Browse the repository at this point in the history
* basic support of stat

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

* content length

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

* more docs

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

* format basicio.c

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

* typo

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

* no abbreviation for meta

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

---------

Signed-off-by: Ji-Xinyou <jerryji0414@outlook.com>
  • Loading branch information
xyjixyjixyji authored May 10, 2023
1 parent c25a6ac commit 20d8232
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 36 deletions.
88 changes: 80 additions & 8 deletions bindings/c/include/opendal.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ typedef enum opendal_code {
*/
typedef struct BlockingOperator BlockingOperator;

/*
Metadata carries all metadata associated with an path.
# Notes
mode and content_length are required metadata that all services
should provide during `stat` operation. But in `list` operation,
a.k.a., `Entry`'s content length could be `None`.
*/
typedef struct Metadata Metadata;

/*
The [`opendal_operator_ptr`] owns a pointer to a [`od::BlockingOperator`].
It is also the key struct that OpenDAL's APIs access the real
Expand Down Expand Up @@ -155,6 +166,27 @@ typedef struct opendal_result_is_exist {
enum opendal_code code;
} opendal_result_is_exist;

/*
Metadata carries all metadata associated with an path.
# Notes
mode and content_length are required metadata that all services
should provide during `stat` operation. But in `list` operation,
a.k.a., `Entry`'s content length could be NULL.
*/
typedef const struct Metadata *opendal_metadata;

/*
The result type for [`opendal_operator_stat()`], the meta contains the metadata
of the path, the code represents whether the stat operation is successful. Note
that the operation could be successful even if the path does not exist.
*/
typedef struct opendal_result_stat {
opendal_metadata meta;
enum opendal_code code;
} opendal_result_stat;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
Expand All @@ -172,18 +204,13 @@ extern "C" {
*/
opendal_operator_ptr opendal_operator_new(const char *scheme);

/*
Free the allocated operator pointed by [`opendal_operator_ptr`]
*/
void opendal_operator_free(opendal_operator_ptr op_ptr);

/*
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
It is [safe] under the cases below
* The memory pointed to by `path` must contain a valid nul terminator at the end of
the string.
Expand All @@ -202,7 +229,7 @@ enum opendal_code opendal_operator_blocking_write(opendal_operator_ptr op_ptr,
# Safety
It is [safe] under two cases below
It is [safe] under the cases below
* The memory pointed to by `path` must contain a valid nul terminator at the end of
the string.
Expand All @@ -223,7 +250,7 @@ struct opendal_result_read opendal_operator_blocking_read(opendal_operator_ptr o
# Safety
It is [safe] under two cases below
It is [safe] under the cases below
* The memory pointed to by `path` must contain a valid nul terminator at the end of
the string.
Expand All @@ -234,11 +261,56 @@ struct opendal_result_read opendal_operator_blocking_read(opendal_operator_ptr o
struct opendal_result_is_exist opendal_operator_is_exist(opendal_operator_ptr op_ptr,
const char *path);

/*
Stat the path, return its metadata.
If the operation succeeds, no matter the path exists or not,
the error code should be opendal_code::OPENDAL_OK. Otherwise,
the field `meata` is filled with a NULL pointer, and the error code
is set correspondingly.
# Safety
It is [safe] under the cases below
* The memory pointed to by `path` must contain a valid nul terminator at the end of
the string.
# Panic
* If the `path` points to NULL, this function panics
*/
struct opendal_result_stat opendal_operator_stat(opendal_operator_ptr op_ptr, const char *path);

/*
Free the allocated operator pointed by [`opendal_operator_ptr`]
*/
void opendal_operator_free(const opendal_operator_ptr *self);

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

/*
Free the allocated metadata
*/
void opendal_metadata_free(const opendal_metadata *self);

/*
Return the content_length of the metadata
*/
uint64_t opendal_metadata_content_length(const opendal_metadata *self);

/*
Return whether the path represents a file
*/
bool opendal_metadata_is_file(const opendal_metadata *self);

/*
Return whether the path represents a directory
*/
bool opendal_metadata_is_dir(const opendal_metadata *self);

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
Expand Down
59 changes: 44 additions & 15 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use std::str::FromStr;

use ::opendal as od;
use error::opendal_code;
use result::opendal_result_is_exist;
use result::opendal_result_read;
use result::{opendal_result_is_exist, opendal_result_read, opendal_result_stat};
use types::opendal_metadata;

use crate::types::opendal_bytes;
use crate::types::opendal_operator_ptr;
Expand Down Expand Up @@ -74,22 +74,12 @@ pub unsafe extern "C" fn opendal_operator_new(scheme: *const c_char) -> opendal_
opendal_operator_ptr::from(op)
}

/// Free the allocated operator pointed by [`opendal_operator_ptr`]
#[no_mangle]
pub extern "C" fn opendal_operator_free(op_ptr: opendal_operator_ptr) {
if op_ptr.is_null() {
return;
}
let _ = unsafe { Box::from_raw(op_ptr.get_ref_mut()) };
// dropped
}

/// 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
/// It is [safe] under the cases below
/// * The memory pointed to by `path` must contain a valid nul terminator at the end of
/// the string.
///
Expand Down Expand Up @@ -120,7 +110,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_write(
///
/// # Safety
///
/// It is [safe] under two cases below
/// It is [safe] under the cases below
/// * The memory pointed to by `path` must contain a valid nul terminator at the end of
/// the string.
///
Expand Down Expand Up @@ -163,7 +153,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_read(
///
/// # Safety
///
/// It is [safe] under two cases below
/// It is [safe] under the cases below
/// * The memory pointed to by `path` must contain a valid nul terminator at the end of
/// the string.
///
Expand Down Expand Up @@ -192,3 +182,42 @@ pub unsafe extern "C" fn opendal_operator_is_exist(
},
}
}

/// Stat the path, return its metadata.
///
/// If the operation succeeds, no matter the path exists or not,
/// the error code should be opendal_code::OPENDAL_OK. Otherwise,
/// the field `meata` is filled with a NULL pointer, and the error code
/// is set correspondingly.
///
/// # Safety
///
/// It is [safe] under the cases below
/// * The memory pointed to by `path` must contain a valid nul terminator at the end of
/// the string.
///
/// # Panic
///
/// * If the `path` points to NULL, this function panics
#[no_mangle]
pub unsafe extern "C" fn opendal_operator_stat(
op_ptr: opendal_operator_ptr,
path: *const c_char,
) -> opendal_result_stat {
if path.is_null() {
panic!("The path given is pointing at NULL");
}

let op = op_ptr.get_ref();
let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() };
match op.stat(path) {
Ok(m) => opendal_result_stat {
meta: opendal_metadata::from_metadata(m),
code: opendal_code::OPENDAL_OK,
},
Err(err) => opendal_result_stat {
meta: opendal_metadata::null(),
code: opendal_code::from_opendal_error(err),
},
}
}
11 changes: 10 additions & 1 deletion bindings/c/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
//! we are defining all Result types here

use crate::error::opendal_code;
use crate::types::opendal_bytes;
use crate::types::{opendal_bytes, opendal_metadata};

/// The Rust-like Result type of opendal C binding, it contains
/// the data that the read operation returns and a error code
Expand All @@ -41,3 +41,12 @@ pub struct opendal_result_is_exist {
pub is_exist: bool,
pub code: opendal_code,
}

/// The result type for [`opendal_operator_stat()`], the meta contains the metadata
/// of the path, the code represents whether the stat operation is successful. Note
/// that the operation could be successful even if the path does not exist.
#[repr(C)]
pub struct opendal_result_stat {
pub meta: opendal_metadata,
pub code: opendal_code,
}
83 changes: 75 additions & 8 deletions bindings/c/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ pub struct opendal_operator_ptr {
ptr: *const od::BlockingOperator,
}

impl opendal_operator_ptr {
/// Free the allocated operator pointed by [`opendal_operator_ptr`]
#[no_mangle]
pub extern "C" fn opendal_operator_free(&self) {
if self.is_null() {
return;
}
let _ = unsafe { Box::from_raw(self.ptr as *mut od::BlockingOperator) };
}
}

impl opendal_operator_ptr {
/// Creates an OperatorPtr will nullptr, indicating this [`opendal_operator_ptr`]
/// is invalid. The `transparent` layout also guarantees that if the
Expand All @@ -50,14 +61,6 @@ impl opendal_operator_ptr {
pub(crate) fn get_ref(&self) -> &od::BlockingOperator {
unsafe { &*(self.ptr) }
}

/// Returns a mutable reference to the underlying [`od::BlockingOperator`].
/// Note that this should be only used when the operator is being freed
#[allow(clippy::mut_from_ref)]
pub(crate) fn get_ref_mut(&self) -> &mut od::BlockingOperator {
let ptr_mut = self.ptr as *mut od::BlockingOperator;
unsafe { &mut (*ptr_mut) }
}
}

#[allow(clippy::from_over_into)]
Expand Down Expand Up @@ -111,3 +114,67 @@ impl Into<bytes::Bytes> for opendal_bytes {
bytes::Bytes::from_static(slice)
}
}

/// Metadata carries all metadata associated with an path.
///
/// # Notes
///
/// mode and content_length are required metadata that all services
/// should provide during `stat` operation. But in `list` operation,
/// a.k.a., `Entry`'s content length could be NULL.
#[repr(transparent)]
pub struct opendal_metadata {
pub inner: *const od::Metadata,
}

impl opendal_metadata {
/// Free the allocated metadata
#[no_mangle]
pub extern "C" fn opendal_metadata_free(&self) {
if self.inner.is_null() {
return;
}
let _ = unsafe { Box::from_raw(self.inner as *mut od::Metadata) };
}

/// Return the content_length of the metadata
#[no_mangle]
pub extern "C" fn opendal_metadata_content_length(&self) -> u64 {
// Safety: the inner should never be null once constructed
// The use-after-free is undefined behavior
unsafe { (*self.inner).content_length() }
}

/// Return whether the path represents a file
#[no_mangle]
pub extern "C" fn opendal_metadata_is_file(&self) -> bool {
// Safety: the inner should never be null once constructed
// The use-after-free is undefined behavior
unsafe { (*self.inner).is_file() }
}

/// Return whether the path represents a directory
#[no_mangle]
pub extern "C" fn opendal_metadata_is_dir(&self) -> bool {
// Safety: the inner should never be null once constructed
// The use-after-free is undefined behavior
unsafe { (*self.inner).is_dir() }
}
}

impl opendal_metadata {
/// Return a null metadata
pub(crate) fn null() -> Self {
Self {
inner: std::ptr::null(),
}
}

/// Convert a Rust core [`od::Metadata`] into a heap allocated C-compatible
/// [`opendal_metadata`]
pub(crate) fn from_metadata(m: od::Metadata) -> Self {
Self {
inner: Box::leak(Box::new(m)),
}
}
}
Loading

0 comments on commit 20d8232

Please sign in to comment.