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