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 as *const u8, size) }.to_vec())
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
68#[cfg(unix)]
69pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
70    CString::new(path.as_ref().as_os_str().as_bytes())
71        .map_err(|e| Error::new(format!("Failed to convert path to CString: {e}")))
72}
73
74#[cfg(not(unix))]
75pub(crate) fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> {
76    match CString::new(path.as_ref().to_string_lossy().as_bytes()) {
77        Ok(c) => Ok(c),
78        Err(e) => Err(Error::new(format!(
79            "Failed to convert path to CString: {e}"
80        ))),
81    }
82}
83
84/// Calls a RocksDB C API function that returns an error as a pointer to a C string as the last
85/// argument. The C function result is converted into `Result<T, Error>`. This ensures the error
86/// message pointer is not leaked. See [`convert_rocksdb_error`] for details.
87macro_rules! ffi_try {
88    ( $($function:ident)::*() ) => {
89        ffi_try_impl!($($function)::*())
90    };
91
92    ( $($function:ident)::*( $arg1:expr $(, $arg:expr)* $(,)? ) ) => {
93        ffi_try_impl!($($function)::*($arg1 $(, $arg)* ,))
94    };
95}
96
97macro_rules! ffi_try_impl {
98    ( $($function:ident)::*( $($arg:expr,)*) ) => {{
99        let mut err: *mut ::libc::c_char = ::std::ptr::null_mut();
100        let result = $($function)::*($($arg,)* &mut err);
101        if !err.is_null() {
102            return Err($crate::ffi_util::convert_rocksdb_error(err));
103        }
104        result
105    }};
106}
107
108/// Value which can be converted into a C string.
109///
110/// The trait is used as argument to functions which wish to accept either
111/// [`&str`] or [`&CStr`](CStr) arguments while internally need to interact with
112/// C APIs.  Accepting [`&str`] may be more convenient for users but requires
113/// conversion into [`CString`] internally which requires allocation.  With this
114/// trait, latency-conscious users may choose to prepare [`CStr`] in advance and
115/// then pass it directly without having to incur the conversion cost.
116///
117/// To use the trait, function should accept `impl CStrLike` and after baking
118/// the argument (with [`CStrLike::bake`] method) it can use it as a [`&CStr`](CStr)
119/// (since the baked result dereferences into [`CStr`]).
120///
121/// # Example
122///
123/// ```
124/// use std::ffi::{CStr, CString};
125/// use rust_rocksdb::CStrLike;
126///
127/// fn strlen(arg: impl CStrLike) -> std::result::Result<usize, String> {
128///     let baked = arg.bake().map_err(|err| err.to_string())?;
129///     Ok(unsafe { libc::strlen(baked.as_ptr()) })
130/// }
131///
132/// const FOO: &str = "foo";
133/// const BAR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"bar\0") };
134///
135/// assert_eq!(Ok(3), strlen(FOO));
136/// assert_eq!(Ok(3), strlen(BAR));
137/// ```
138pub trait CStrLike {
139    type Baked: std::ops::Deref<Target = CStr>;
140    type Error: std::fmt::Debug + std::fmt::Display;
141
142    /// Bakes self into value which can be freely converted into [`&CStr`](CStr).
143    ///
144    /// This may require allocation and may fail if `self` has invalid value.
145    fn bake(self) -> Result<Self::Baked, Self::Error>;
146
147    /// Consumers and converts value into an owned [`CString`].
148    ///
149    /// If `Self` is already a `CString` simply returns it; if it’s a reference
150    /// to a `CString` then the value is cloned.  In other cases this may
151    /// require allocation and may fail if `self` has invalid value.
152    fn into_c_string(self) -> Result<CString, Self::Error>;
153}
154
155impl CStrLike for &str {
156    type Baked = CString;
157    type Error = std::ffi::NulError;
158
159    fn bake(self) -> Result<Self::Baked, Self::Error> {
160        CString::new(self)
161    }
162    fn into_c_string(self) -> Result<CString, Self::Error> {
163        CString::new(self)
164    }
165}
166
167// This is redundant for the most part and exists so that `foo(&string)` (where
168// `string: String` works just as if `foo` took `arg: &str` argument.
169impl CStrLike for &String {
170    type Baked = CString;
171    type Error = std::ffi::NulError;
172
173    fn bake(self) -> Result<Self::Baked, Self::Error> {
174        CString::new(self.as_bytes())
175    }
176    fn into_c_string(self) -> Result<CString, Self::Error> {
177        CString::new(self.as_bytes())
178    }
179}
180
181impl CStrLike for &CStr {
182    type Baked = Self;
183    type Error = std::convert::Infallible;
184
185    fn bake(self) -> Result<Self::Baked, Self::Error> {
186        Ok(self)
187    }
188    fn into_c_string(self) -> Result<CString, Self::Error> {
189        Ok(self.to_owned())
190    }
191}
192
193// This exists so that if caller constructs a `CString` they can pass it into
194// the function accepting `CStrLike` argument.  Some of such functions may take
195// the argument whereas otherwise they would need to allocated a new owned
196// object.
197impl CStrLike for CString {
198    type Baked = CString;
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)
206    }
207}
208
209// This is redundant for the most part and exists so that `foo(&cstring)` (where
210// `string: CString` works just as if `foo` took `arg: &CStr` argument.
211impl<'a> CStrLike for &'a CString {
212    type Baked = &'a CStr;
213    type Error = std::convert::Infallible;
214
215    fn bake(self) -> Result<Self::Baked, Self::Error> {
216        Ok(self)
217    }
218    fn into_c_string(self) -> Result<CString, Self::Error> {
219        Ok(self.clone())
220    }
221}
222
223/// Owned malloc-allocated memory slice.
224/// Do not derive `Clone` for this because it will cause double-free.
225pub struct CSlice {
226    data: *const c_char,
227    len: size_t,
228}
229
230impl CSlice {
231    /// Constructing such a slice may be unsafe.
232    ///
233    /// # Safety
234    /// The caller must ensure that the pointer and length are valid.
235    /// Moreover, `CSlice` takes the ownership of the memory and will free it
236    /// using `rocksdb_free`. The caller must ensure that the memory is
237    /// allocated by `malloc` in RocksDB and will not be freed by any other
238    /// means.
239    pub(crate) unsafe fn from_raw_parts(data: *const c_char, len: size_t) -> Self {
240        Self { data, len }
241    }
242}
243
244impl AsRef<[u8]> for CSlice {
245    fn as_ref(&self) -> &[u8] {
246        unsafe { std::slice::from_raw_parts(self.data as *const u8, self.len) }
247    }
248}
249
250impl Drop for CSlice {
251    fn drop(&mut self) {
252        unsafe {
253            ffi::rocksdb_free(self.data as *mut c_void);
254        }
255    }
256}
257
258#[test]
259fn test_c_str_like_bake() {
260    fn test<S: CStrLike>(value: S) -> Result<usize, S::Error> {
261        value
262            .bake()
263            .map(|value| unsafe { libc::strlen(value.as_ptr()) })
264    }
265
266    assert_eq!(Ok(3), test("foo")); // &str
267    assert_eq!(Ok(3), test(&String::from("foo"))); // String
268    assert_eq!(Ok(3), test(CString::new("foo").unwrap().as_ref())); // &CStr
269    assert_eq!(Ok(3), test(&CString::new("foo").unwrap())); // &CString
270    assert_eq!(Ok(3), test(CString::new("foo").unwrap())); // CString
271
272    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
273}
274
275#[test]
276fn test_c_str_like_into() {
277    fn test<S: CStrLike>(value: S) -> Result<CString, S::Error> {
278        value.into_c_string()
279    }
280
281    let want = CString::new("foo").unwrap();
282
283    assert_eq!(Ok(want.clone()), test("foo")); // &str
284    assert_eq!(Ok(want.clone()), test(&String::from("foo"))); // &String
285    assert_eq!(
286        Ok(want.clone()),
287        test(CString::new("foo").unwrap().as_ref())
288    ); // &CStr
289    assert_eq!(Ok(want.clone()), test(&CString::new("foo").unwrap())); // &CString
290    assert_eq!(Ok(want), test(CString::new("foo").unwrap())); // CString
291
292    assert_eq!(3, test("foo\0bar").err().unwrap().nul_position());
293}