Skip to main content

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