Skip to main content

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::{Error, ffi};
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
24/// Copies `ptr` into a String, replacing invalid UTF-8 using [`String::from_utf8_lossy`], *without*
25/// freeing it. Prefer [`from_cstr_and_free`] to make leaks less likely.
26pub(crate) unsafe fn from_cstr_without_free(ptr: *const c_char) -> String {
27    let cstr = unsafe { CStr::from_ptr(ptr as *const _) };
28    String::from_utf8_lossy(cstr.to_bytes()).into_owned()
29}
30
31/// Copies `ptr` into a String, replacing invalid UTF-8 using [`String::from_utf8_lossy`], then
32/// frees it using `rocksdb_free`.
33pub(crate) unsafe fn from_cstr_and_free(ptr: *const c_char) -> String {
34    let cstr = unsafe { CStr::from_ptr(ptr as *const _) };
35    let s = String::from_utf8_lossy(cstr.to_bytes()).into_owned();
36    unsafe { ffi::rocksdb_free(ptr as *mut c_void) };
37    s
38}
39
40pub(crate) unsafe fn raw_data(ptr: *const c_char, size: usize) -> Option<Vec<u8>> {
41    if ptr.is_null() {
42        None
43    } else {
44        // SAFETY: Caller guarantees `ptr` points to `size` bytes; we immediately copy them.
45        Some(unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), size) }.to_vec())
46    }
47}
48
49/// Copies `size` bytes out of a buffer that the RocksDB C API allocated with
50/// `malloc` (see `CopyString` in `rocksdb/db/c.cc`), then releases the original
51/// with `rocksdb_free`.
52///
53/// The copy is not optional. Handing a `malloc`ed pointer to
54/// `Vec::from_raw_parts` makes Rust's global allocator responsible for freeing
55/// memory it never allocated, which is undefined behaviour and corrupts the
56/// heap whenever the two allocators are not the same one: any downstream
57/// `#[global_allocator]` (mimalloc, jemallocator, snmalloc), or Windows, where
58/// Rust's `System` allocator uses `HeapAlloc`/`HeapFree` and the C runtime
59/// does not.
60pub(crate) unsafe fn raw_data_and_free(ptr: *mut c_char, size: usize) -> Option<Vec<u8>> {
61    if ptr.is_null() {
62        return None;
63    }
64    // A zero-length value is a legitimate RocksDB value. `malloc(0)` still
65    // returns a live pointer that has to be freed, but it is not necessarily
66    // valid to read from, so skip the copy rather than calling
67    // `from_raw_parts` on it.
68    let data = if size == 0 {
69        Vec::new()
70    } else {
71        // SAFETY: Caller guarantees `ptr` points to `size` readable bytes.
72        unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), size) }.to_vec()
73    };
74    // SAFETY: `ptr` came from `malloc` inside the RocksDB C API, so it must be
75    // released by the matching `free`, which is what `rocksdb_free` calls.
76    unsafe { ffi::rocksdb_free(ptr.cast::<c_void>()) };
77    Some(data)
78}
79
80/// Convert a RocksDB error message to an Error and frees it. The argument must not be used after
81/// this function is called.
82pub fn convert_rocksdb_error(rocksdb_err: *const c_char) -> Error {
83    let rocksdb_err_str = unsafe { from_cstr_and_free(rocksdb_err) };
84    Error::new(rocksdb_err_str)
85}
86
87/// Returns a raw pointer to borrowed bytes, or null if None.
88///
89/// # Safety
90/// - The input must outlive the returned pointer.
91/// - Common types: `&str`, `&[u8]`, `&String`, `&Vec<u8>`
92pub fn opt_bytes_to_ptr<T: AsRef<[u8]> + ?Sized>(opt: Option<&T>) -> *const c_char {
93    match opt {
94        Some(v) => v.as_ref().as_ptr() as *const c_char,
95        None => ptr::null(),
96    }
97}
98
99#[cfg(unix)]
100pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
101    CString::new(path.as_ref().as_os_str().as_bytes())
102        .map_err(|e| Error::new(format!("Failed to convert path to CString: {e}")))
103}
104
105#[cfg(not(unix))]
106pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
107    match CString::new(path.as_ref().to_string_lossy().as_bytes()) {
108        Ok(c) => Ok(c),
109        Err(e) => Err(Error::new(format!(
110            "Failed to convert path to CString: {e}"
111        ))),
112    }
113}
114
115/// Calls a RocksDB C API function that returns an error as a pointer to a C string as the last
116/// argument. The C function result is converted into `Result<T, Error>`. This ensures the error
117/// message pointer is not leaked. See [`convert_rocksdb_error`] for details.
118macro_rules! ffi_try {
119    ( $($function:ident)::*() ) => {
120        ffi_try_impl!($($function)::*())
121    };
122
123    ( $($function:ident)::*( $arg1:expr $(, $arg:expr)* $(,)? ) ) => {
124        ffi_try_impl!($($function)::*($arg1 $(, $arg)* ,))
125    };
126}
127
128macro_rules! ffi_try_impl {
129    ( $($function:ident)::*( $($arg:expr,)*) ) => {{
130        let mut err: *mut ::libc::c_char = ::std::ptr::null_mut();
131        let result = $($function)::*($($arg,)* &mut err);
132        if !err.is_null() {
133            return Err($crate::ffi_util::convert_rocksdb_error(err));
134        }
135        result
136    }};
137}
138
139/// Value which can be converted into a C string.
140///
141/// The trait is used as argument to functions which wish to accept either
142/// [`&str`] or [`&CStr`](CStr) arguments while internally need to interact with
143/// C APIs.  Accepting [`&str`] may be more convenient for users but requires
144/// conversion into [`CString`] internally which requires allocation.  With this
145/// trait, latency-conscious users may choose to prepare [`CStr`] in advance and
146/// then pass it directly without having to incur the conversion cost.
147///
148/// To use the trait, function should accept `impl CStrLike` and after baking
149/// the argument (with [`CStrLike::bake`] method) it can use it as a [`&CStr`](CStr)
150/// (since the baked result dereferences into [`CStr`]).
151///
152/// # Example
153///
154/// ```
155/// use std::ffi::{CStr, CString};
156/// use rust_rocksdb::CStrLike;
157///
158/// fn strlen(arg: impl CStrLike) -> std::result::Result<usize, String> {
159///     let baked = arg.bake().map_err(|err| err.to_string())?;
160///     Ok(unsafe { libc::strlen(baked.as_ptr()) })
161/// }
162///
163/// const FOO: &str = "foo";
164/// const BAR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"bar\0") };
165///
166/// assert_eq!(Ok(3), strlen(FOO));
167/// assert_eq!(Ok(3), strlen(BAR));
168/// ```
169pub trait CStrLike {
170    type Baked: std::ops::Deref<Target = CStr>;
171    type Error: std::fmt::Debug + std::fmt::Display;
172
173    /// Bakes self into value which can be freely converted into [`&CStr`](CStr).
174    ///
175    /// This may require allocation and may fail if `self` has invalid value.
176    fn bake(self) -> Result<Self::Baked, Self::Error>;
177
178    /// Consumers and converts value into an owned [`CString`].
179    ///
180    /// If `Self` is already a `CString` simply returns it; if it’s a reference
181    /// to a `CString` then the value is cloned.  In other cases this may
182    /// require allocation and may fail if `self` has invalid value.
183    fn into_c_string(self) -> Result<CString, Self::Error>;
184}
185
186impl CStrLike for &str {
187    type Baked = CString;
188    type Error = std::ffi::NulError;
189
190    fn bake(self) -> Result<Self::Baked, Self::Error> {
191        CString::new(self)
192    }
193    fn into_c_string(self) -> Result<CString, Self::Error> {
194        CString::new(self)
195    }
196}
197
198// This is redundant for the most part and exists so that `foo(&string)` (where
199// `string: String` works just as if `foo` took `arg: &str` argument.
200impl CStrLike for &String {
201    type Baked = CString;
202    type Error = std::ffi::NulError;
203
204    fn bake(self) -> Result<Self::Baked, Self::Error> {
205        CString::new(self.as_bytes())
206    }
207    fn into_c_string(self) -> Result<CString, Self::Error> {
208        CString::new(self.as_bytes())
209    }
210}
211
212impl CStrLike for &CStr {
213    type Baked = Self;
214    type Error = std::convert::Infallible;
215
216    fn bake(self) -> Result<Self::Baked, Self::Error> {
217        Ok(self)
218    }
219    fn into_c_string(self) -> Result<CString, Self::Error> {
220        Ok(self.to_owned())
221    }
222}
223
224// This exists so that if caller constructs a `CString` they can pass it into
225// the function accepting `CStrLike` argument.  Some of such functions may take
226// the argument whereas otherwise they would need to allocated a new owned
227// object.
228impl CStrLike for CString {
229    type Baked = CString;
230    type Error = std::convert::Infallible;
231
232    fn bake(self) -> Result<Self::Baked, Self::Error> {
233        Ok(self)
234    }
235    fn into_c_string(self) -> Result<CString, Self::Error> {
236        Ok(self)
237    }
238}
239
240// This is redundant for the most part and exists so that `foo(&cstring)` (where
241// `string: CString` works just as if `foo` took `arg: &CStr` argument.
242impl<'a> CStrLike for &'a CString {
243    type Baked = &'a CStr;
244    type Error = std::convert::Infallible;
245
246    fn bake(self) -> Result<Self::Baked, Self::Error> {
247        Ok(self)
248    }
249    fn into_c_string(self) -> Result<CString, Self::Error> {
250        Ok(self.clone())
251    }
252}
253
254/// Owned malloc-allocated memory slice.
255/// Do not derive `Clone` for this because it will cause double-free.
256pub struct CSlice {
257    data: *const c_char,
258    len: size_t,
259}
260
261impl CSlice {
262    /// Constructing such a slice may be unsafe.
263    ///
264    /// # Safety
265    /// The caller must ensure that the pointer and length are valid.
266    /// Moreover, `CSlice` takes the ownership of the memory and will free it
267    /// using `rocksdb_free`. The caller must ensure that the memory is
268    /// allocated by `malloc` in RocksDB and will not be freed by any other
269    /// means.
270    pub(crate) unsafe fn from_raw_parts(data: *const c_char, len: size_t) -> Self {
271        Self { data, len }
272    }
273}
274
275impl AsRef<[u8]> for CSlice {
276    #[inline]
277    fn as_ref(&self) -> &[u8] {
278        if self.len == 0 {
279            // `CopyString` returns `malloc(0)` for an empty value, and
280            // `malloc(0)` is allowed to return a pointer that must not be
281            // dereferenced. `slice::from_raw_parts(ptr, 0)` requires a
282            // dereferenceable pointer, so short-circuit instead.
283            return &[];
284        }
285        unsafe { std::slice::from_raw_parts(self.data.cast::<u8>(), self.len) }
286    }
287}
288
289impl Drop for CSlice {
290    fn drop(&mut self) {
291        unsafe {
292            ffi::rocksdb_free(self.data as *mut c_void);
293        }
294    }
295}
296
297#[test]
298fn test_c_str_like_bake() {
299    fn test<S: CStrLike>(value: S) -> Result<usize, S::Error> {
300        value.bake().map(|value| value.count_bytes())
301    }
302
303    assert_eq!(Ok(3), test("foo")); // &str
304    assert_eq!(Ok(3), test(&String::from("foo"))); // String
305    assert_eq!(Ok(3), test(CString::new("foo").unwrap().as_ref())); // &CStr
306    assert_eq!(Ok(3), test(&CString::new("foo").unwrap())); // &CString
307    assert_eq!(Ok(3), test(CString::new("foo").unwrap())); // CString
308
309    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
310}
311
312#[test]
313fn test_c_str_like_into() {
314    fn test<S: CStrLike>(value: S) -> Result<CString, S::Error> {
315        value.into_c_string()
316    }
317
318    let want = CString::new("foo").unwrap();
319
320    assert_eq!(Ok(want.clone()), test("foo")); // &str
321    assert_eq!(Ok(want.clone()), test(&String::from("foo"))); // &String
322    assert_eq!(
323        Ok(want.clone()),
324        test(CString::new("foo").unwrap().as_ref())
325    ); // &CStr
326    assert_eq!(Ok(want.clone()), test(&CString::new("foo").unwrap())); // &CString
327    assert_eq!(Ok(want), test(CString::new("foo").unwrap())); // CString
328
329    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
330}