diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index f4ad4704f6ed..2d4e82fd597d 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -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 @@ -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 @@ -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. @@ -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. @@ -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. @@ -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 diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index c248502e74a1..649154ea6cd0 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -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; @@ -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. /// @@ -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. /// @@ -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. /// @@ -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), + }, + } +} diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs index 6f869973bd65..36591c30c5e1 100644 --- a/bindings/c/src/result.rs +++ b/bindings/c/src/result.rs @@ -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 @@ -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, +} diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs index b8752fa0893a..5bbebb0221c6 100644 --- a/bindings/c/src/types.rs +++ b/bindings/c/src/types.rs @@ -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 @@ -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)] @@ -111,3 +114,67 @@ impl Into 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)), + } + } +} diff --git a/bindings/c/tests/basicio.c b/bindings/c/tests/basicio.c index 765669f2c284..36524594325a 100644 --- a/bindings/c/tests/basicio.c +++ b/bindings/c/tests/basicio.c @@ -27,13 +27,12 @@ // * A valid ptr is given // * The blocking write operation is successful // * The blocking read operation is successful and works as expected -void test_operator_rw(opendal_operator_ptr ptr) +void test_operator_rw(opendal_operator_ptr ptr, char* path) { // have to be valid ptr assert(ptr); // write some contents by the operator, must be successful - char path[] = "test"; char content[] = "Hello World"; const opendal_bytes data = { .len = sizeof(content) - 1, @@ -55,17 +54,36 @@ void test_operator_rw(opendal_operator_ptr ptr) opendal_bytes_free(r.data); } +void test_operator_stat(opendal_operator_ptr ptr, char* path) +{ + assert(ptr); + opendal_result_stat r = opendal_operator_stat(ptr, path); + assert(r.code == OPENDAL_OK); + + opendal_metadata meta = r.meta; + assert(meta); + + assert(opendal_metadata_is_file(&meta)); + + uint64_t content_length = opendal_metadata_content_length(&meta); + assert(content_length == 11); + + opendal_metadata_free(&meta); +} + int main(int argc, char* argv[]) { // construct the memory operator char scheme1[] = "memory"; + char path[] = "test"; opendal_operator_ptr p1 = opendal_operator_new(scheme1); assert(p1); - test_operator_rw(p1); + test_operator_rw(p1, path); + test_operator_stat(p1, path); // free the operator - opendal_operator_free(p1); + opendal_operator_free(&p1); return 0; }