Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bindings/C): Initially support stat in C binding #2249

Merged
merged 6 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_meta_free(const opendal_metadata *self);
xyjixyjixyji marked this conversation as resolved.
Show resolved Hide resolved

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

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

/*
Return whether the path represents a directory
*/
bool opendal_meta_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_meta(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_meta_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_meta_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_meta_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_meta_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_meta(m: od::Metadata) -> Self {
xyjixyjixyji marked this conversation as resolved.
Show resolved Hide resolved
Self {
inner: Box::leak(Box::new(m)),
}
}
}
Loading