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}