#![allow(clippy::new_without_default)]
use std::cell::UnsafeCell;
use std::ffi::{c_int, c_longlong, c_ulonglong, c_void, CStr, CString};
use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_uint};
use std::ptr;
use std::sync::atomic::{self, AtomicPtr, Ordering};
use std::sync::Mutex;
use bindings::THD;
use cstr::cstr;
use log::trace;
use mariadb_sys as bindings;
#[repr(i32)]
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq)]
#[allow(clippy::cast_possible_wrap)]
pub enum SysVarOpt {
ReadOnly = bindings::PLUGIN_VAR_READONLY as i32,
NoServerVariable = bindings::PLUGIN_VAR_NOSYSVAR as i32,
NoCliOption = bindings::PLUGIN_VAR_NOCMDOPT as i32,
NoCliArg = bindings::PLUGIN_VAR_NOCMDARG as i32,
RequiredCliArg = bindings::PLUGIN_VAR_RQCMDARG as i32,
OptionalCliArg = bindings::PLUGIN_VAR_OPCMDARG as i32,
Deprecated = bindings::PLUGIN_VAR_DEPRECATED as i32,
}
type SVInfoInner<T> = ManuallyDrop<UnsafeCell<T>>;
#[repr(C)]
#[allow(dead_code)]
pub union SysVarInfoU {
bool_t: SVInfoInner<bindings::sysvar_bool_t>,
str_t: SVInfoInner<bindings::sysvar_str_t>,
enum_t: SVInfoInner<bindings::sysvar_enum_t>,
set_t: SVInfoInner<bindings::sysvar_set_t>,
int_t: SVInfoInner<bindings::sysvar_int_t>,
long_t: SVInfoInner<bindings::sysvar_long_t>,
longlong_t: SVInfoInner<bindings::sysvar_longlong_t>,
uint_t: SVInfoInner<bindings::sysvar_uint_t>,
ulong_t: SVInfoInner<bindings::sysvar_ulong_t>,
ulonglong_t: SVInfoInner<bindings::sysvar_ulonglong_t>,
double_t: SVInfoInner<bindings::sysvar_double_t>,
}
impl SysVarOpt {
pub const fn as_plugin_var_info(self) -> i32 {
self as i32
}
}
#[allow(dead_code)]
type SvUpdateFn =
unsafe extern "C" fn(*mut THD, *mut bindings::st_mysql_sys_var, *mut c_void, *const c_void);
pub unsafe trait SysVarInterface: Sized {
type CStructType;
type Intermediate: Copy;
const DEFAULT_OPTS: i32;
const DEFAULT_C_STRUCT: Self::CStructType;
#[allow(unused_variables)]
unsafe extern "C" fn update_wrap(
thd: *mut THD,
var: *mut bindings::st_mysql_sys_var,
target: *mut c_void,
save: *const c_void,
) {
let new_save: *const Self::Intermediate = save.cast();
assert!(
!new_save.is_null(),
"got a null pointer from the C interface"
);
Self::update(&*target.cast(), &*var.cast(), *new_save);
}
#[allow(unused_variables)]
unsafe fn update(&self, var: &Self::CStructType, save: Self::Intermediate) {
unimplemented!()
}
}
#[repr(transparent)]
pub struct SysVarConstString(AtomicPtr<c_char>);
impl SysVarConstString {
pub const fn new() -> Self {
Self(AtomicPtr::new(std::ptr::null_mut()))
}
pub fn get(&self) -> &'static str {
let ptr = self.0.load(Ordering::SeqCst);
let cs = unsafe { CStr::from_ptr(ptr) };
cs.to_str()
.unwrap_or_else(|_| panic!("got non-UTF8 string like {}", cs.to_string_lossy()))
}
}
unsafe impl SysVarInterface for SysVarConstString {
type CStructType = bindings::sysvar_str_t;
type Intermediate = *mut c_char;
const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32;
const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
flags: 0,
name: ptr::null(),
comment: ptr::null(),
check: None,
update: None,
value: ptr::null_mut(),
def_val: cstr!("").as_ptr().cast_mut(),
};
}
#[repr(C)]
pub struct SysVarString {
ptr: AtomicPtr<c_char>,
mutex: Mutex<Option<CString>>,
}
impl SysVarString {
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(std::ptr::null_mut()),
mutex: Mutex::new(None),
}
}
pub fn get(&self) -> Option<String> {
let guard = &*self.mutex.lock().expect("failed to lock mutex");
let ptr = self.ptr.load(Ordering::SeqCst);
if !ptr.is_null() && guard.is_some() {
let cs = guard.as_ref().unwrap();
assert!(
ptr.cast_const() == cs.as_ptr(),
"pointer and c string unsynchronized"
);
Some(cstr_to_string(cs))
} else if ptr.is_null() && guard.is_none() {
None
} else {
trace!("pointer {ptr:p} mismatch with guard {guard:?} (likely init condition)");
let cs = unsafe { CStr::from_ptr(ptr) };
Some(cstr_to_string(cs))
}
}
}
unsafe impl SysVarInterface for SysVarString {
type CStructType = bindings::sysvar_str_t;
type Intermediate = *mut c_char;
const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32;
const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
flags: 0,
name: ptr::null(),
comment: ptr::null(),
check: None,
update: Some(Self::update_wrap),
value: ptr::null_mut(),
def_val: cstr!("").as_ptr().cast_mut(),
};
unsafe fn update(&self, _var: &Self::CStructType, save: Self::Intermediate) {
let to_save = save
.as_ref()
.map(|ptr| unsafe { CStr::from_ptr(ptr).to_owned() });
let guard = &mut *self.mutex.lock().expect("failed to lock mutex");
*guard = to_save;
let new_ptr = guard
.as_deref()
.map_or(ptr::null_mut(), |cs| cs.as_ptr().cast_mut());
self.ptr.store(new_ptr, Ordering::SeqCst);
trace!("updated sysvar with inner: {guard:?}");
}
}
fn cstr_to_string(cs: &CStr) -> String {
cs.to_str()
.unwrap_or_else(|_| panic!("got non-UTF8 string like {}", cs.to_string_lossy()))
.to_owned()
}
macro_rules! atomic_svinterface {
( $atomic_type:ty,
$c_struct_type:ty,
bool,
$default_options:expr $(,)?
) => {
atomic_svinterface!{
$atomic_type,
$c_struct_type,
bool,
$default_options,
{ def_val: false }
}
};
( $atomic_type:ty,
$c_struct_type:ty,
$inter_type:ty,
$default_options:expr $(,)?
) => {
atomic_svinterface!{
$atomic_type,
$c_struct_type,
$inter_type,
$default_options,
{ def_val: 0, min_val: <$inter_type>::MIN, max_val: <$inter_type>::MAX, blk_sz: 0 }
}
};
( $atomic_type:ty, $c_struct_type:ty, $inter_type:ty, $default_options:expr, { $( $extra_struct_fields:tt )* } $(,)? ) => {
unsafe impl SysVarInterface for $atomic_type {
type CStructType = $c_struct_type;
type Intermediate = $inter_type;
const DEFAULT_OPTS: i32 = ($default_options) as i32;
const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType {
flags: 0,
name: ptr::null(),
comment: ptr::null(),
check: None,
update: Some(Self::update_wrap),
value: ptr::null_mut(),
$( $extra_struct_fields )*
};
unsafe fn update(&self, _var: &Self::CStructType, save: Self::Intermediate) {
trace!(
"updated {} system variable to '{:?}'",
std::any::type_name::<$atomic_type>(), save
);
self.store(save, Ordering::SeqCst);
}
}
};
}
atomic_svinterface!(
atomic::AtomicBool,
bindings::sysvar_bool_t,
bool,
bindings::PLUGIN_VAR_BOOL,
);
atomic_svinterface!(
atomic::AtomicI32,
bindings::sysvar_int_t,
c_int,
bindings::PLUGIN_VAR_INT,
);
atomic_svinterface!(
atomic::AtomicU32,
bindings::sysvar_uint_t,
c_uint,
bindings::PLUGIN_VAR_INT | bindings::PLUGIN_VAR_UNSIGNED
);
atomic_svinterface!(
atomic::AtomicI64,
bindings::sysvar_longlong_t,
c_longlong,
bindings::PLUGIN_VAR_LONGLONG
);
atomic_svinterface!(
atomic::AtomicU64,
bindings::sysvar_ulonglong_t,
c_ulonglong,
bindings::PLUGIN_VAR_LONGLONG | bindings::PLUGIN_VAR_UNSIGNED
);