| //! Raw `unsafe` access to the `malloctl` API. |
| |
| use crate::error::{cvt, Result}; |
| use crate::{mem, ptr, slice}; |
| use libc::c_char; |
| |
| /// Translates `name` to a `mib` (Management Information Base) |
| /// |
| /// `mib`s are used to avoid repeated name lookups for applications that |
| /// repeatedly query the same portion of `jemalloc`s `mallctl` namespace. |
| /// |
| /// On success, `mib` contains an array of integers. It is possible to pass |
| /// `mib` with a length smaller than the number of period-separated name |
| /// components. This results in a partial MIB that can be used as the basis for |
| /// constructing a complete MIB. |
| /// |
| /// For name components that are integers (e.g. the `2` in `arenas.bin.2.size`), |
| /// the corresponding MIB component will always be that integer. Therefore, it |
| /// is legitimate to construct code like the following: |
| /// |
| /// ``` |
| /// #[global_allocator] |
| /// static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; |
| /// |
| /// fn main() { |
| /// use tikv_jemalloc_ctl::raw; |
| /// use libc::{c_uint, c_char}; |
| /// unsafe { |
| /// let mut mib = [0; 4]; |
| /// let nbins: c_uint = raw::read(b"arenas.nbins\0").unwrap(); |
| /// raw::name_to_mib(b"arenas.bin.0.size\0", &mut mib).unwrap(); |
| /// for i in 0..4 { |
| /// mib[2] = i; |
| /// let bin_size: usize = raw::read_mib(&mut mib).unwrap(); |
| /// println!("arena bin {} has size {}", i, bin_size); |
| /// } |
| /// } |
| /// } |
| /// ``` |
| pub fn name_to_mib(name: &[u8], mib: &mut [usize]) -> Result<()> { |
| unsafe { |
| validate_name(name); |
| |
| let mut len = mib.len(); |
| cvt(tikv_jemalloc_sys::mallctlnametomib( |
| name as *const _ as *const c_char, |
| mib.as_mut_ptr(), |
| &mut len, |
| ))?; |
| assert_eq!(mib.len(), len); |
| Ok(()) |
| } |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and reads its value. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=bool` for a key returning `u8`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn read_mib<T: Copy>(mib: &[usize]) -> Result<T> { |
| let mut value = MaybeUninit { init: () }; |
| let mut len = mem::size_of::<T>(); |
| cvt(tikv_jemalloc_sys::mallctlbymib( |
| mib.as_ptr(), |
| mib.len(), |
| &mut value.init as *mut _ as *mut _, |
| &mut len, |
| ptr::null_mut(), |
| 0, |
| ))?; |
| assert_eq!(len, mem::size_of::<T>()); |
| Ok(value.maybe_uninit) |
| } |
| |
| /// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and |
| /// reads its value. |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=bool` for a key returning `u8`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn read<T: Copy>(name: &[u8]) -> Result<T> { |
| validate_name(name); |
| |
| let mut value = MaybeUninit { init: () }; |
| let mut len = mem::size_of::<T>(); |
| cvt(tikv_jemalloc_sys::mallctl( |
| name as *const _ as *const c_char, |
| &mut value.init as *mut _ as *mut _, |
| &mut len, |
| ptr::null_mut(), |
| 0, |
| ))?; |
| assert_eq!(len, mem::size_of::<T>()); |
| Ok(value.maybe_uninit) |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn write_mib<T>(mib: &[usize], mut value: T) -> Result<()> { |
| cvt(tikv_jemalloc_sys::mallctlbymib( |
| mib.as_ptr(), |
| mib.len(), |
| ptr::null_mut(), |
| ptr::null_mut(), |
| &mut value as *mut _ as *mut _, |
| mem::size_of::<T>(), |
| )) |
| } |
| |
| /// Uses the null-terminated string `name` as the key to the _MALLCTL NAMESPACE_ |
| /// and writes it `value` |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn write<T>(name: &[u8], mut value: T) -> Result<()> { |
| validate_name(name); |
| |
| cvt(tikv_jemalloc_sys::mallctl( |
| name as *const _ as *const c_char, |
| ptr::null_mut(), |
| ptr::null_mut(), |
| &mut value as *mut _ as *mut _, |
| mem::size_of::<T>(), |
| )) |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value` |
| /// returning its previous value. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn update_mib<T>(mib: &[usize], mut value: T) -> Result<T> { |
| let mut len = mem::size_of::<T>(); |
| cvt(tikv_jemalloc_sys::mallctlbymib( |
| mib.as_ptr(), |
| mib.len(), |
| &mut value as *mut _ as *mut _, |
| &mut len, |
| &mut value as *mut _ as *mut _, |
| len, |
| ))?; |
| assert_eq!(len, mem::size_of::<T>()); |
| Ok(value) |
| } |
| |
| /// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and |
| /// writes its `value` returning its previous value. |
| /// |
| /// # Safety |
| /// |
| /// This function is `unsafe` because it is possible to use it to construct an |
| /// invalid `T`, for example, by passing `T=u8` for a key expecting `bool`. The |
| /// sizes of `bool` and `u8` match, but `bool` cannot represent all values that |
| /// `u8` can. |
| pub unsafe fn update<T>(name: &[u8], mut value: T) -> Result<T> { |
| validate_name(name); |
| |
| let mut len = mem::size_of::<T>(); |
| cvt(tikv_jemalloc_sys::mallctl( |
| name as *const _ as *const c_char, |
| &mut value as *mut _ as *mut _, |
| &mut len, |
| &mut value as *mut _ as *mut _, |
| len, |
| ))?; |
| assert_eq!(len, mem::size_of::<T>()); |
| Ok(value) |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and reads its value. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Safety |
| /// |
| /// This function is unsafe because if the key does not return a pointer to a |
| /// null-terminated string the behavior is undefined. |
| /// |
| /// For example, a key for a `u64` value can be used to read a pointer on 64-bit |
| /// platform, where this pointer will point to the address denoted by the `u64`s |
| /// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer |
| /// that will not point to a null-terminated string. |
| /// |
| /// This function needs to compute the length of the string by looking for the |
| /// null-terminator: `\0`. This requires reading the memory behind the pointer. |
| /// |
| /// If the pointer is invalid (e.g. because it was converted from a `u64` that |
| /// does not represent a valid address), reading the string to look for `\0` |
| /// will dereference a non-dereferenceable pointer, which is undefined behavior. |
| /// |
| /// If the pointer is valid but it does not point to a null-terminated string, |
| /// looking for `\0` will read garbage and might end up reading out-of-bounds, |
| /// which is undefined behavior. |
| pub unsafe fn read_str_mib(mib: &[usize]) -> Result<&'static [u8]> { |
| let ptr: *const c_char = read_mib(mib)?; |
| Ok(ptr2str(ptr)) |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value`. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Panics |
| /// |
| /// If `value` is not a non-empty null-terminated string. |
| pub fn write_str_mib(mib: &[usize], value: &'static [u8]) -> Result<()> { |
| assert!(!value.is_empty(), "value cannot be empty"); |
| assert_eq!(*value.last().unwrap(), b'\0'); |
| // This is safe because `value` will always point to a null-terminated |
| // string, which makes it safe for all key value types: pointers to |
| // null-terminated strings, pointers, pointer-sized integers, etc. |
| unsafe { write_mib(mib, value.as_ptr() as *const c_char) } |
| } |
| |
| /// Uses the MIB `mib` as key to the _MALLCTL NAMESPACE_ and writes its `value` |
| /// returning its previous value. |
| /// |
| /// The [`name_to_mib`] API translates a string of the key (e.g. `arenas.nbins`) |
| /// to a `mib` (Management Information Base). |
| /// |
| /// # Safety |
| /// |
| /// This function is unsafe because if the key does not return a pointer to a |
| /// null-terminated string the behavior is undefined. |
| /// |
| /// For example, a key for a `u64` value can be used to read a pointer on 64-bit |
| /// platform, where this pointer will point to the address denoted by the `u64`s |
| /// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer |
| /// that will not point to a null-terminated string. |
| /// |
| /// This function needs to compute the length of the string by looking for the |
| /// null-terminator: `\0`. This requires reading the memory behind the pointer. |
| /// |
| /// If the pointer is invalid (e.g. because it was converted from a `u64` that |
| /// does not represent a valid address), reading the string to look for `\0` |
| /// will dereference a non-dereferenceable pointer, which is undefined behavior. |
| /// |
| /// If the pointer is valid but it does not point to a null-terminated string, |
| /// looking for `\0` will read garbage and might end up reading out-of-bounds, |
| /// which is undefined behavior. |
| pub unsafe fn update_str_mib( |
| mib: &[usize], |
| value: &'static [u8], |
| ) -> Result<&'static [u8]> { |
| let ptr: *const c_char = update_mib(mib, value.as_ptr() as *const c_char)?; |
| Ok(ptr2str(ptr)) |
| } |
| |
| /// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and |
| /// reads its value. |
| /// |
| /// # Safety |
| /// |
| /// This function is unsafe because if the key does not return a pointer to a |
| /// null-terminated string the behavior is undefined. |
| /// |
| /// For example, a key for a `u64` value can be used to read a pointer on 64-bit |
| /// platform, where this pointer will point to the address denoted by the `u64`s |
| /// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer |
| /// that will not point to a null-terminated string. |
| /// |
| /// This function needs to compute the length of the string by looking for the |
| /// null-terminator: `\0`. This requires reading the memory behind the pointer. |
| /// |
| /// If the pointer is invalid (e.g. because it was converted from a `u64` that |
| /// does not represent a valid address), reading the string to look for `\0` |
| /// will dereference a non-dereferenceable pointer, which is undefined behavior. |
| /// |
| /// If the pointer is valid but it does not point to a null-terminated string, |
| /// looking for `\0` will read garbage and might end up reading out-of-bounds, |
| /// which is undefined behavior. |
| pub unsafe fn read_str(name: &[u8]) -> Result<&'static [u8]> { |
| let ptr: *const c_char = read(name)?; |
| Ok(ptr2str(ptr)) |
| } |
| |
| /// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and |
| /// writes its `value`. |
| pub fn write_str(name: &[u8], value: &'static [u8]) -> Result<()> { |
| assert!(!value.is_empty(), "value cannot be empty"); |
| assert_eq!(*value.last().unwrap(), b'\0'); |
| // This is safe because `value` will always point to a null-terminated |
| // string, which makes it safe for all key value types: pointers to |
| // null-terminated strings, pointers, pointer-sized integers, etc. |
| unsafe { write(name, value.as_ptr() as *const c_char) } |
| } |
| |
| /// Uses the null-terminated string `name` as key to the _MALLCTL NAMESPACE_ and |
| /// writes its `value` returning its previous value. |
| /// |
| /// # Safety |
| /// |
| /// This function is unsafe because if the key does not return a pointer to a |
| /// null-terminated string the behavior is undefined. |
| /// |
| /// For example, a key for a `u64` value can be used to read a pointer on 64-bit |
| /// platform, where this pointer will point to the address denoted by the `u64`s |
| /// representation. Also, a key to a `*mut extent_hooks_t` will return a pointer |
| /// that will not point to a null-terminated string. |
| /// |
| /// This function needs to compute the length of the string by looking for the |
| /// null-terminator: `\0`. This requires reading the memory behind the pointer. |
| /// |
| /// If the pointer is invalid (e.g. because it was converted from a `u64` that |
| /// does not represent a valid address), reading the string to look for `\0` |
| /// will dereference a non-dereferenceable pointer, which is undefined behavior. |
| /// |
| /// If the pointer is valid but it does not point to a null-terminated string, |
| /// looking for `\0` will read garbage and might end up reading out-of-bounds, |
| /// which is undefined behavior. |
| pub unsafe fn update_str( |
| name: &[u8], |
| value: &'static [u8], |
| ) -> Result<&'static [u8]> { |
| let ptr: *const c_char = update(name, value.as_ptr() as *const c_char)?; |
| Ok(ptr2str(ptr)) |
| } |
| |
| /// Converts a non-empty null-terminated character string at `ptr` into a valid |
| /// null-terminated UTF-8 string. |
| /// |
| /// # Panics |
| /// |
| /// If `ptr.is_null()`. |
| /// |
| /// # Safety |
| /// |
| /// If `ptr` does not point to a null-terminated character string the behavior |
| /// is undefined. |
| unsafe fn ptr2str(ptr: *const c_char) -> &'static [u8] { |
| assert!( |
| !ptr.is_null(), |
| "attempt to convert a null-ptr to a UTF-8 string" |
| ); |
| let len = libc::strlen(ptr); |
| slice::from_raw_parts(ptr as *const u8, len + 1) |
| } |
| |
| fn validate_name(name: &[u8]) { |
| assert!(!name.is_empty(), "empty byte string"); |
| assert_eq!( |
| *name.last().unwrap(), |
| b'\0', |
| "non-null terminated byte string" |
| ); |
| } |
| |
| union MaybeUninit<T: Copy> { |
| init: (), |
| maybe_uninit: T, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| #[test] |
| #[cfg(not(target_arch = "mips64el"))] // FIXME: SIGFPE |
| fn test_ptr2str() { |
| unsafe { |
| //{ // This is undefined behavior: |
| // let cstr = b""; |
| // let rstr = ptr2str(cstr as *const _ as *const c_char); |
| // assert!(rstr.is_err()); |
| // } |
| { |
| let cstr = b"\0"; |
| let rstr = ptr2str(cstr as *const _ as *const c_char); |
| assert_eq!(rstr.len(), 1); |
| assert_eq!(rstr, b"\0"); |
| } |
| { |
| let cstr = b"foo baaar\0"; |
| let rstr = ptr2str(cstr as *const _ as *const c_char); |
| assert_eq!(rstr.len(), b"foo baaar\0".len()); |
| assert_eq!(rstr, b"foo baaar\0"); |
| } |
| } |
| } |
| } |