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}