rust_rocksdb/
ffi_util.rs

1// Copyright 2016 Alex Regueiro
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use crate::{ffi, Error};
17use libc::{self, c_char, c_void, size_t};
18use std::ffi::{CStr, CString};
19#[cfg(unix)]
20use std::os::unix::ffi::OsStrExt;
21use std::path::Path;
22use std::ptr;
23
24pub(crate) unsafe fn from_cstr(ptr: *const c_char) -> String {
25    unsafe {
26        let cstr = CStr::from_ptr(ptr as *const _);
27        String::from_utf8_lossy(cstr.to_bytes()).into_owned()
28    }
29}
30
31pub(crate) unsafe fn raw_data(ptr: *const c_char, size: usize) -> Option<Vec<u8>> {
32    if ptr.is_null() {
33        None
34    } else {
35        // Safe: caller guarantees `ptr` points to `size` bytes; we immediately copy them.
36        Some(std::slice::from_raw_parts(ptr as *const u8, size).to_vec())
37    }
38}
39
40pub fn error_message(ptr: *const c_char) -> String {
41    unsafe {
42        let s = from_cstr(ptr);
43        ffi::rocksdb_free(ptr as *mut c_void);
44        s
45    }
46}
47
48/// Returns a raw pointer to borrowed bytes, or null if None.
49///
50/// # Safety
51/// - The input must outlive the returned pointer.
52/// - Common types: `&str`, `&[u8]`, `&String`, `&Vec<u8>`
53pub fn opt_bytes_to_ptr<T: AsRef<[u8]> + ?Sized>(opt: Option<&T>) -> *const c_char {
54    match opt {
55        Some(v) => v.as_ref().as_ptr() as *const c_char,
56        None => ptr::null(),
57    }
58}
59
60#[cfg(unix)]
61pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
62    CString::new(path.as_ref().as_os_str().as_bytes())
63        .map_err(|e| Error::new(format!("Failed to convert path to CString: {e}")))
64}
65
66#[cfg(not(unix))]
67pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
68    match CString::new(path.as_ref().to_string_lossy().as_bytes()) {
69        Ok(c) => Ok(c),
70        Err(e) => Err(Error::new(format!(
71            "Failed to convert path to CString: {e}"
72        ))),
73    }
74}
75
76macro_rules! ffi_try {
77    ( $($function:ident)::*() ) => {
78        ffi_try_impl!($($function)::*())
79    };
80
81    ( $($function:ident)::*( $arg1:expr $(, $arg:expr)* $(,)? ) ) => {
82        ffi_try_impl!($($function)::*($arg1 $(, $arg)* ,))
83    };
84}
85
86macro_rules! ffi_try_impl {
87    ( $($function:ident)::*( $($arg:expr,)*) ) => {{
88        let mut err: *mut ::libc::c_char = ::std::ptr::null_mut();
89        let result = $($function)::*($($arg,)* &mut err);
90        if !err.is_null() {
91            return Err(Error::new($crate::ffi_util::error_message(err)));
92        }
93        result
94    }};
95}
96
97/// Value which can be converted into a C string.
98///
99/// The trait is used as argument to functions which wish to accept either
100/// [`&str`] or [`&CStr`](CStr) arguments while internally need to interact with
101/// C APIs.  Accepting [`&str`] may be more convenient for users but requires
102/// conversion into [`CString`] internally which requires allocation.  With this
103/// trait, latency-conscious users may choose to prepare [`CStr`] in advance and
104/// then pass it directly without having to incur the conversion cost.
105///
106/// To use the trait, function should accept `impl CStrLike` and after baking
107/// the argument (with [`CStrLike::bake`] method) it can use it as a [`&CStr`](CStr)
108/// (since the baked result dereferences into [`CStr`]).
109///
110/// # Example
111///
112/// ```
113/// use std::ffi::{CStr, CString};
114/// use rust_rocksdb::CStrLike;
115///
116/// fn strlen(arg: impl CStrLike) -> std::result::Result<usize, String> {
117///     let baked = arg.bake().map_err(|err| err.to_string())?;
118///     Ok(unsafe { libc::strlen(baked.as_ptr()) })
119/// }
120///
121/// const FOO: &str = "foo";
122/// const BAR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"bar\0") };
123///
124/// assert_eq!(Ok(3), strlen(FOO));
125/// assert_eq!(Ok(3), strlen(BAR));
126/// ```
127pub trait CStrLike {
128    type Baked: std::ops::Deref<Target = CStr>;
129    type Error: std::fmt::Debug + std::fmt::Display;
130
131    /// Bakes self into value which can be freely converted into [`&CStr`](CStr).
132    ///
133    /// This may require allocation and may fail if `self` has invalid value.
134    fn bake(self) -> Result<Self::Baked, Self::Error>;
135
136    /// Consumers and converts value into an owned [`CString`].
137    ///
138    /// If `Self` is already a `CString` simply returns it; if it’s a reference
139    /// to a `CString` then the value is cloned.  In other cases this may
140    /// require allocation and may fail if `self` has invalid value.
141    fn into_c_string(self) -> Result<CString, Self::Error>;
142}
143
144impl CStrLike for &str {
145    type Baked = CString;
146    type Error = std::ffi::NulError;
147
148    fn bake(self) -> Result<Self::Baked, Self::Error> {
149        CString::new(self)
150    }
151    fn into_c_string(self) -> Result<CString, Self::Error> {
152        CString::new(self)
153    }
154}
155
156// This is redundant for the most part and exists so that `foo(&string)` (where
157// `string: String` works just as if `foo` took `arg: &str` argument.
158impl CStrLike for &String {
159    type Baked = CString;
160    type Error = std::ffi::NulError;
161
162    fn bake(self) -> Result<Self::Baked, Self::Error> {
163        CString::new(self.as_bytes())
164    }
165    fn into_c_string(self) -> Result<CString, Self::Error> {
166        CString::new(self.as_bytes())
167    }
168}
169
170impl CStrLike for &CStr {
171    type Baked = Self;
172    type Error = std::convert::Infallible;
173
174    fn bake(self) -> Result<Self::Baked, Self::Error> {
175        Ok(self)
176    }
177    fn into_c_string(self) -> Result<CString, Self::Error> {
178        Ok(self.to_owned())
179    }
180}
181
182// This exists so that if caller constructs a `CString` they can pass it into
183// the function accepting `CStrLike` argument.  Some of such functions may take
184// the argument whereas otherwise they would need to allocated a new owned
185// object.
186impl CStrLike for CString {
187    type Baked = CString;
188    type Error = std::convert::Infallible;
189
190    fn bake(self) -> Result<Self::Baked, Self::Error> {
191        Ok(self)
192    }
193    fn into_c_string(self) -> Result<CString, Self::Error> {
194        Ok(self)
195    }
196}
197
198// This is redundant for the most part and exists so that `foo(&cstring)` (where
199// `string: CString` works just as if `foo` took `arg: &CStr` argument.
200impl<'a> CStrLike for &'a CString {
201    type Baked = &'a CStr;
202    type Error = std::convert::Infallible;
203
204    fn bake(self) -> Result<Self::Baked, Self::Error> {
205        Ok(self)
206    }
207    fn into_c_string(self) -> Result<CString, Self::Error> {
208        Ok(self.clone())
209    }
210}
211
212/// Owned malloc-allocated memory slice.
213/// Do not derive `Clone` for this because it will cause double-free.
214pub struct CSlice {
215    data: *const c_char,
216    len: size_t,
217}
218
219impl CSlice {
220    /// Constructing such a slice may be unsafe.
221    ///
222    /// # Safety
223    /// The caller must ensure that the pointer and length are valid.
224    /// Moreover, `CSlice` takes the ownership of the memory and will free it
225    /// using `rocksdb_free`. The caller must ensure that the memory is
226    /// allocated by `malloc` in RocksDB and will not be freed by any other
227    /// means.
228    pub(crate) unsafe fn from_raw_parts(data: *const c_char, len: size_t) -> Self {
229        Self { data, len }
230    }
231}
232
233impl AsRef<[u8]> for CSlice {
234    fn as_ref(&self) -> &[u8] {
235        unsafe { std::slice::from_raw_parts(self.data as *const u8, self.len) }
236    }
237}
238
239impl Drop for CSlice {
240    fn drop(&mut self) {
241        unsafe {
242            ffi::rocksdb_free(self.data as *mut c_void);
243        }
244    }
245}
246
247#[test]
248fn test_c_str_like_bake() {
249    fn test<S: CStrLike>(value: S) -> Result<usize, S::Error> {
250        value
251            .bake()
252            .map(|value| unsafe { libc::strlen(value.as_ptr()) })
253    }
254
255    assert_eq!(Ok(3), test("foo")); // &str
256    assert_eq!(Ok(3), test(&String::from("foo"))); // String
257    assert_eq!(Ok(3), test(CString::new("foo").unwrap().as_ref())); // &CStr
258    assert_eq!(Ok(3), test(&CString::new("foo").unwrap())); // &CString
259    assert_eq!(Ok(3), test(CString::new("foo").unwrap())); // CString
260
261    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
262}
263
264#[test]
265fn test_c_str_like_into() {
266    fn test<S: CStrLike>(value: S) -> Result<CString, S::Error> {
267        value.into_c_string()
268    }
269
270    let want = CString::new("foo").unwrap();
271
272    assert_eq!(Ok(want.clone()), test("foo")); // &str
273    assert_eq!(Ok(want.clone()), test(&String::from("foo"))); // &String
274    assert_eq!(
275        Ok(want.clone()),
276        test(CString::new("foo").unwrap().as_ref())
277    ); // &CStr
278    assert_eq!(Ok(want.clone()), test(&CString::new("foo").unwrap())); // &CString
279    assert_eq!(Ok(want), test(CString::new("foo").unwrap())); // CString
280
281    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
282}