1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
//! "show variables" and "system variables"
#![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;

/// Possible flags for plugin variables
#[repr(i32)]
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq)]
#[allow(clippy::cast_possible_wrap)]
pub enum SysVarOpt {
    // ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32,
    /// Variable is read only
    ReadOnly = bindings::PLUGIN_VAR_READONLY as i32,
    /// Variable is not a server variable
    NoServerVariable = bindings::PLUGIN_VAR_NOSYSVAR as i32,
    /// No command line option
    NoCliOption = bindings::PLUGIN_VAR_NOCMDOPT as i32,
    /// No argument for the command line
    NoCliArg = bindings::PLUGIN_VAR_NOCMDARG as i32,
    /// Required CLI argument
    RequiredCliArg = bindings::PLUGIN_VAR_RQCMDARG as i32,
    /// Optional CLI argument
    OptionalCliArg = bindings::PLUGIN_VAR_OPCMDARG as i32,
    /// Variable is deprecated
    Deprecated = bindings::PLUGIN_VAR_DEPRECATED as i32,
    // String needs memory allocation - don't expose this
    //  MemAlloc= bindings::PLUGIN_VAR_MEMALLOC,
}

type SVInfoInner<T> = ManuallyDrop<UnsafeCell<T>>;

/// Basicallly, a system variable will be one of these types, which are dynamic
/// structures on C. Kind of yucky to work with but I think the generic union is
/// a lot more clear.
#[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>,
    // THD types have a function `resolve` that takes a thread pointer and an
    // offset (also a field)
}

impl SysVarOpt {
    pub const fn as_plugin_var_info(self) -> i32 {
        self as i32
    }
}

/// `bindings::mysql_var_update_func` without the `Option`
#[allow(dead_code)]
type SvUpdateFn =
    unsafe extern "C" fn(*mut THD, *mut bindings::st_mysql_sys_var, *mut c_void, *const c_void);

/// A wrapper for system variables. This won't be exposed externally.
///
/// This provides the interface of update functions. Trait is unsafe because
/// using the wrong structures would cause UB.
pub unsafe trait SysVarInterface: Sized {
    /// The C struct representation, e.g. `sysvar_str_t`
    type CStructType;

    /// Intermediate type, pointed to by the `CStructType's` `value` pointer
    type Intermediate: Copy;

    /// Options to implement by default
    const DEFAULT_OPTS: i32;

    /// C struct filled with default values.
    const DEFAULT_C_STRUCT: Self::CStructType;

    /// Wrapper for the task of storing the result of the `check` function.
    /// Simply converts to our safe rust function "update".
    ///
    /// - `thd`: thread pointer
    /// - `var`: pointer to the c struct
    /// - `var_ptr`: where to stash the value
    /// - `save`: stash from the `check` function
    #[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);
    }

    /// Update function: override this if it is pointed to by `UPDATE_FUNC`
    #[allow(unused_variables)]
    unsafe fn update(&self, var: &Self::CStructType, save: Self::Intermediate) {
        unimplemented!()
    }
}

/// A const string system variable
///
/// Consider this very unstable because I don't 100% understand what the SQL
/// side of things does with the malloc / const options
///
/// BUG: it seems like after updating, the SQL server cannot read the
/// variable... but we can? Do we need to do more in our `update` function?
#[repr(transparent)]
pub struct SysVarConstString(AtomicPtr<c_char>);

impl SysVarConstString {
    /// Create a new string variable
    pub const fn new() -> Self {
        Self(AtomicPtr::new(std::ptr::null_mut()))
    }

    /// Get the current value of this variable. This isn't very efficient since
    /// it copies the string, but fixes will come later
    ///
    /// # Panics
    ///
    /// Panics if it gets a non-UTF8 C string
    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(),
    };
}

/// An editable c string
///
/// Note on race conditions:
///
/// There is a race if the C side reads data while being updated on the Rust
/// side. No worse than what would exist if the plugin was written in C, but
/// important to note it does exist.
#[repr(C)]
pub struct SysVarString {
    /// This points to our c string
    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),
        }
    }

    /// Get the current value of this variable
    ///
    /// # Panics
    ///
    /// Panics if the mutex can't be locked
    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)");
            // prefer the pointer, must have been set on the C side
            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 to easily create implementations for all the atomics
macro_rules! atomic_svinterface {
    // Special case for boolean, which doesn't have as many fields
    (   $atomic_type:ty,
        $c_struct_type:ty,
        bool,
        $default_options:expr $(,)?
    ) => {
        atomic_svinterface!{
            $atomic_type,
            $c_struct_type,
            bool,
            $default_options,
            { def_val: false }
        }
    };

    // All other integer types have the same fields
    (   $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 }
        }
    };

    // Full generic implementation
    (   $atomic_type:ty,                // e.g., AtomicI32
        $c_struct_type:ty,              // e.g. sysvar_int_t
        $inter_type:ty,                 // e.g. i32
        $default_options:expr,          // e.g. PLUGIN_VAR_INT
        { $( $extra_struct_fields:tt )* } $(,)?  // e.g. default, min, max fields
    ) => {
        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
                );
                // based on sql_plugin.cc, seems like there are no null integers
                // (can't represent that anyway)
                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
);