Skip to main content

tikv_jemalloc_ctl/
raw.rs

1//! Raw `unsafe` access to the `malloctl` API.
2
3use crate::error::{cvt, Result};
4use crate::{
5    mem::{self, MaybeUninit},
6    ptr, slice,
7};
8use libc::c_char;
9
10/// Translates `name` to a `mib` (Management Information Base)
11///
12/// `mib`s are used to avoid repeated name lookups for applications that
13/// repeatedly query the same portion of `jemalloc`s `mallctl` namespace.
14///
15/// On success, `mib` contains an array of integers. It is possible to pass
16/// `mib` with a length smaller than the number of period-separated name
17/// components. This results in a partial MIB that can be used as the basis for
18/// constructing a complete MIB.
19///
20/// For name components that are integers (e.g. the `2` in `arenas.bin.2.size`),
21/// the corresponding MIB component will always be that integer. Therefore, it
22/// is legitimate to construct code like the following:
23///
24/// ```
25/// #[global_allocator]
26/// static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
27///
28/// fn main() {
29///     use tikv_jemalloc_ctl::raw;
30///     use libc::{c_uint, c_char};
31///     unsafe {
32///         let mut mib = [0; 4];
33///         let nbins: c_uint = raw::read(b"arenas.nbins\0").unwrap();
34///         raw::name_to_mib(b"arenas.bin.0.size\0", &mut mib).unwrap();
35///         for i in 0..4 {
36///             mib[2] = i;
37///             let bin_size: usize = raw::read_mib(&mut mib).unwrap();
38///             println!("arena bin {} has size {}", i, bin_size);
39///         }
40///     }
41/// }
42/// ```
43pub fn name_to_mib(name: &[u8], mib: &mut [usize]) -> Result<()> {
44    unsafe {
45        validate_name(name);
46
47        let mut len = mib.len();
48        cvt(tikv_jemalloc_sys::mallctlnametomib(
49            name as *const _ as *const c_char,
50            mib.as_mut_ptr(),
51            &mut len,
52        ))?;
53        assert_eq!(mib.len(), len);
54        Ok(())
55    }
56}
57
58/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and reads its value.
59///
60/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
61/// to a `mib` (Management Information Base).
62///
63/// # Safety
64///
65/// This function is `unsafe` because it is possible to use it to construct an
66/// invalid `T`, for example, by passing `T=bool` for a key returning `u8`. The
67/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
68/// `u8` can.
69pub unsafe fn read_mib<T: Copy>(mib: &[usize]) -> Result<T> {
70    let mut value = MaybeUninit::<T>::uninit();
71    let mut len = mem::size_of::<T>();
72    cvt(tikv_jemalloc_sys::mallctlbymib(
73        mib.as_ptr(),
74        mib.len(),
75        value.as_mut_ptr() as *mut _,
76        &mut len,
77        ptr::null_mut(),
78        0,
79    ))?;
80    assert_eq!(len, mem::size_of::<T>());
81    Ok(value.assume_init())
82}
83
84/// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and
85/// reads its value.
86///
87/// # Safety
88///
89/// This function is `unsafe` because it is possible to use it to construct an
90/// invalid `T`, for example, by passing `T=bool` for a key returning `u8`. The
91/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
92/// `u8` can.
93pub unsafe fn read<T: Copy>(name: &[u8]) -> Result<T> {
94    validate_name(name);
95
96    let mut value = MaybeUninit::<T>::uninit();
97    let mut len = mem::size_of::<T>();
98    cvt(tikv_jemalloc_sys::mallctl(
99        name as *const _ as *const c_char,
100        value.as_mut_ptr() as *mut _,
101        &mut len,
102        ptr::null_mut(),
103        0,
104    ))?;
105    assert_eq!(len, mem::size_of::<T>());
106    Ok(value.assume_init())
107}
108
109/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`.
110///
111/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
112/// to a `mib` (Management Information Base).
113///
114/// # Safety
115///
116/// This function is `unsafe` because it is possible to use it to construct an
117/// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The
118/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
119/// `u8` can.
120pub unsafe fn write_mib<T>(mib: &[usize], mut value: T) -> Result<()> {
121    cvt(tikv_jemalloc_sys::mallctlbymib(
122        mib.as_ptr(),
123        mib.len(),
124        ptr::null_mut(),
125        ptr::null_mut(),
126        &mut value as *mut _ as *mut _,
127        mem::size_of::<T>(),
128    ))
129}
130
131/// Uses the null-terminated string `name` as the key to the _MALLCTL NAMESPACE_
132/// and writes it `value`
133///
134/// # Safety
135///
136/// This function is `unsafe` because it is possible to use it to construct an
137/// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The
138/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
139/// `u8` can.
140pub unsafe fn write<T>(name: &[u8], mut value: T) -> Result<()> {
141    validate_name(name);
142
143    cvt(tikv_jemalloc_sys::mallctl(
144        name as *const _ as *const c_char,
145        ptr::null_mut(),
146        ptr::null_mut(),
147        &mut value as *mut _ as *mut _,
148        mem::size_of::<T>(),
149    ))
150}
151
152/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`
153/// returning its previous value.
154///
155/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
156/// to a `mib` (Management Information Base).
157///
158/// # Safety
159///
160/// This function is `unsafe` because it is possible to use it to construct an
161/// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The
162/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
163/// `u8` can.
164pub unsafe fn update_mib<T: Copy>(mib: &[usize], mut value: T) -> Result<T> {
165    let mut old_len = mem::size_of::<T>();
166    let mut old_value = MaybeUninit::<T>::uninit();
167    cvt(tikv_jemalloc_sys::mallctlbymib(
168        mib.as_ptr(),
169        mib.len(),
170        old_value.as_mut_ptr() as *mut _,
171        &mut old_len,
172        &mut value as *mut _ as *mut _,
173        mem::size_of::<T>(),
174    ))?;
175    assert_eq!(old_len, mem::size_of::<T>());
176    Ok(old_value.assume_init())
177}
178
179/// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and
180/// writes its `value` returning its previous value.
181///
182/// # Safety
183///
184/// This function is `unsafe` because it is possible to use it to construct an
185/// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The
186/// sizes of `bool` and `u8` match, but `bool` cannot represent all values that
187/// `u8` can.
188pub unsafe fn update<T>(name: &[u8], mut value: T) -> Result<T> {
189    validate_name(name);
190
191    let mut old_len = mem::size_of::<T>();
192    let mut old_value = MaybeUninit::<T>::uninit();
193    cvt(tikv_jemalloc_sys::mallctl(
194        name as *const _ as *const c_char,
195        old_value.as_mut_ptr() as *mut _,
196        &mut old_len,
197        &mut value as *mut _ as *mut _,
198        mem::size_of::<T>(),
199    ))?;
200    assert_eq!(old_len, mem::size_of::<T>());
201    Ok(old_value.assume_init())
202}
203
204/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and reads its value.
205///
206/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
207/// to a `mib` (Management Information Base).
208///
209/// # Safety
210///
211/// This function is unsafe because if the key does not return a pointer to a
212/// null-terminated string the behavior is undefined.
213///
214/// For example, a key for a `u64` value can be used to read a pointer on 64-bit
215/// platform, where this pointer will point to the address denoted by the `u64`s
216/// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer
217/// that will not point to a null-terminated string.
218///
219/// This function needs to compute the length of the string by looking for the
220/// null-terminator: `\0`. This requires reading the memory behind the pointer.
221///
222/// If the pointer is invalid (e.g. because it was converted from a `u64` that
223/// does not represent a valid address), reading the string to look for `\0`
224/// will dereference a non-dereferenceable pointer, which is undefined behavior.
225///
226/// If the pointer is valid but it does not point to a null-terminated string,
227/// looking for `\0` will read garbage and might end up reading out-of-bounds,
228/// which is undefined behavior.
229pub unsafe fn read_str_mib(mib: &[usize]) -> Result<&'static [u8]> {
230    let ptr: *const c_char = read_mib(mib)?;
231    Ok(ptr2str(ptr))
232}
233
234/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`.
235///
236/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
237/// to a `mib` (Management Information Base).
238///
239/// # Panics
240///
241/// If `value` is not a non-empty null-terminated string.
242pub fn write_str_mib(mib: &[usize], value: &'static [u8]) -> Result<()> {
243    assert!(!value.is_empty(), "value cannot be empty");
244    assert_eq!(*value.last().unwrap(), b'\0');
245    // This is safe because `value` will always point to a null-terminated
246    // string, which makes it safe for all key value types: pointers to
247    // null-terminated strings, pointers, pointer-sized integers, etc.
248    unsafe { write_mib(mib, value.as_ptr() as *const c_char) }
249}
250
251/// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`
252/// returning its previous value.
253///
254/// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`)
255/// to a `mib` (Management Information Base).
256///
257/// # Safety
258///
259/// This function is unsafe because if the key does not return a pointer to a
260/// null-terminated string the behavior is undefined.
261///
262/// For example, a key for a `u64` value can be used to read a pointer on 64-bit
263/// platform, where this pointer will point to the address denoted by the `u64`s
264/// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer
265/// that will not point to a null-terminated string.
266///
267/// This function needs to compute the length of the string by looking for the
268/// null-terminator: `\0`. This requires reading the memory behind the pointer.
269///
270/// If the pointer is invalid (e.g. because it was converted from a `u64` that
271/// does not represent a valid address), reading the string to look for `\0`
272/// will dereference a non-dereferenceable pointer, which is undefined behavior.
273///
274/// If the pointer is valid but it does not point to a null-terminated string,
275/// looking for `\0` will read garbage and might end up reading out-of-bounds,
276/// which is undefined behavior.
277pub unsafe fn update_str_mib(
278    mib: &[usize],
279    value: &'static [u8],
280) -> Result<&'static [u8]> {
281    let ptr: *const c_char = update_mib(mib, value.as_ptr() as *const c_char)?;
282    Ok(ptr2str(ptr))
283}
284
285/// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and
286/// reads its value.
287///
288/// # Safety
289///
290/// This function is unsafe because if the key does not return a pointer to a
291/// null-terminated string the behavior is undefined.
292///
293/// For example, a key for a `u64` value can be used to read a pointer on 64-bit
294/// platform, where this pointer will point to the address denoted by the `u64`s
295/// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer
296/// that will not point to a null-terminated string.
297///
298/// This function needs to compute the length of the string by looking for the
299/// null-terminator: `\0`. This requires reading the memory behind the pointer.
300///
301/// If the pointer is invalid (e.g. because it was converted from a `u64` that
302/// does not represent a valid address), reading the string to look for `\0`
303/// will dereference a non-dereferenceable pointer, which is undefined behavior.
304///
305/// If the pointer is valid but it does not point to a null-terminated string,
306/// looking for `\0` will read garbage and might end up reading out-of-bounds,
307/// which is undefined behavior.
308pub unsafe fn read_str(name: &[u8]) -> Result<&'static [u8]> {
309    let ptr: *const c_char = read(name)?;
310    Ok(ptr2str(ptr))
311}
312
313/// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and
314/// writes its `value`.
315pub fn write_str(name: &[u8], value: &'static [u8]) -> Result<()> {
316    assert!(!value.is_empty(), "value cannot be empty");
317    assert_eq!(*value.last().unwrap(), b'\0');
318    // This is safe because `value` will always point to a null-terminated
319    // string, which makes it safe for all key value types: pointers to
320    // null-terminated strings, pointers, pointer-sized integers, etc.
321    unsafe { write(name, value.as_ptr() as *const c_char) }
322}
323
324/// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and
325/// writes its `value` returning its previous value.
326///
327/// # Safety
328///
329/// This function is unsafe because if the key does not return a pointer to a
330/// null-terminated string the behavior is undefined.
331///
332/// For example, a key for a `u64` value can be used to read a pointer on 64-bit
333/// platform, where this pointer will point to the address denoted by the `u64`s
334/// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer
335/// that will not point to a null-terminated string.
336///
337/// This function needs to compute the length of the string by looking for the
338/// null-terminator: `\0`. This requires reading the memory behind the pointer.
339///
340/// If the pointer is invalid (e.g. because it was converted from a `u64` that
341/// does not represent a valid address), reading the string to look for `\0`
342/// will dereference a non-dereferenceable pointer, which is undefined behavior.
343///
344/// If the pointer is valid but it does not point to a null-terminated string,
345/// looking for `\0` will read garbage and might end up reading out-of-bounds,
346/// which is undefined behavior.
347pub unsafe fn update_str(
348    name: &[u8],
349    value: &'static [u8],
350) -> Result<&'static [u8]> {
351    let ptr: *const c_char = update(name, value.as_ptr() as *const c_char)?;
352    Ok(ptr2str(ptr))
353}
354
355/// Converts a non-empty null-terminated character string at `ptr` into a valid
356/// null-terminated UTF-8 string.
357///
358/// # Panics
359///
360/// If `ptr.is_null()`.
361///
362/// # Safety
363///
364/// If `ptr` does not point to a null-terminated character string the behavior
365/// is undefined.
366unsafe fn ptr2str(ptr: *const c_char) -> &'static [u8] {
367    assert!(
368        !ptr.is_null(),
369        "attempt to convert a null-ptr to a UTF-8 string"
370    );
371    let len = libc::strlen(ptr);
372    slice::from_raw_parts(ptr as *const u8, len + 1)
373}
374
375fn validate_name(name: &[u8]) {
376    assert!(!name.is_empty(), "empty byte string");
377    assert_eq!(
378        *name.last().unwrap(),
379        b'\0',
380        "non-null terminated byte string"
381    );
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387    #[test]
388    fn test_ptr2str() {
389        unsafe {
390            //{ // This is undefined behavior:
391            //    let cstr = b"";
392            //    let rstr = ptr2str(cstr as *const _ as *const c_char);
393            //    assert!(rstr.is_err());
394            // }
395            {
396                let cstr = b"\0";
397                let rstr = ptr2str(cstr as *const _ as *const c_char);
398                assert_eq!(rstr.len(), 1);
399                assert_eq!(rstr, b"\0");
400            }
401            {
402                let cstr = b"foo  baaar\0";
403                let rstr = ptr2str(cstr as *const _ as *const c_char);
404                assert_eq!(rstr.len(), b"foo  baaar\0".len());
405                assert_eq!(rstr, b"foo  baaar\0");
406            }
407        }
408    }
409}