From e89e9beb04740fe753b57ac67a46b0e1d799e7cd Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 9 Nov 2022 12:44:46 -0500 Subject: [PATCH 01/34] # This is a combination of 3 commits. # This is the 1st commit message: Create Cargo.toml # This is the commit message #2: Work on rough layout including example # This is the commit message #3: Work on authentication plugin --- include/mysql/plugin_auth_common.h | 1 - rust/Cargo.toml | 6 ++ rust/bindings/Cargo.toml | 6 ++ rust/bindings/build.rs | 41 +++++++++++ rust/bindings/src/lib.rs | 5 ++ rust/bindings/src/wrapper.h | 1 + rust/examples/src/encryption.rs | 80 ++++++++++++++++++++++ rust/macros/README.md | 0 rust/mariadb/Cargo.toml | 0 rust/mariadb/README.md | 0 rust/mariadb/src/plugins.rs | 54 +++++++++++++++ rust/mariadb/src/plugins/authentication.rs | 74 ++++++++++++++++++++ rust/mariadb/src/plugins/common.rs | 4 ++ rust/mariadb/src/plugins/encryption.rs | 68 ++++++++++++++++++ rust/mariadb/src/plugins/vio.rs | 35 ++++++++++ 15 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 rust/Cargo.toml create mode 100644 rust/bindings/Cargo.toml create mode 100644 rust/bindings/build.rs create mode 100644 rust/bindings/src/lib.rs create mode 100644 rust/bindings/src/wrapper.h create mode 100644 rust/examples/src/encryption.rs create mode 100644 rust/macros/README.md create mode 100644 rust/mariadb/Cargo.toml create mode 100644 rust/mariadb/README.md create mode 100644 rust/mariadb/src/plugins.rs create mode 100644 rust/mariadb/src/plugins/authentication.rs create mode 100644 rust/mariadb/src/plugins/common.rs create mode 100644 rust/mariadb/src/plugins/encryption.rs create mode 100644 rust/mariadb/src/plugins/vio.rs diff --git a/include/mysql/plugin_auth_common.h b/include/mysql/plugin_auth_common.h index 8edd712875461..7bbdca5aae214 100644 --- a/include/mysql/plugin_auth_common.h +++ b/include/mysql/plugin_auth_common.h @@ -128,4 +128,3 @@ typedef struct st_plugin_vio } MYSQL_PLUGIN_VIO; #endif - diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000000000..ab586187625c3 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,6 @@ +# Define our crates within a workspace + +[workspace] + +members = [ +] diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml new file mode 100644 index 0000000000000..33f8388c25ca1 --- /dev/null +++ b/rust/bindings/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mariadb-server-sys" +version = "0.1.0" + +[build-dependencies] +bindgen = "0.61.0" diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs new file mode 100644 index 0000000000000..0a537e38428d5 --- /dev/null +++ b/rust/bindings/build.rs @@ -0,0 +1,41 @@ +use std::env; +use std::path::PathBuf; + +use bindgen::EnumVariation; + +fn main() { + // Tell cargo to look for shared libraries in the specified directory + println!("cargo:rustc-link-search=/path/to/lib"); + + // Tell cargo to tell rustc to link the system bzip2 + // shared library. + println!("cargo:rustc-link-lib=bz2"); + + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=wrapper.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("src/wrapper.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Don't derive copy for structs + .derive_copy(false) + // Use rust-style enums labeled with non_exhaustive to represent C enums + .default_enum_style(EnumVariation::Rust{non_exhaustive: true}) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs new file mode 100644 index 0000000000000..024d8f49b68db --- /dev/null +++ b/rust/bindings/src/lib.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/bindings/src/wrapper.h b/rust/bindings/src/wrapper.h new file mode 100644 index 0000000000000..44c82afa8412b --- /dev/null +++ b/rust/bindings/src/wrapper.h @@ -0,0 +1 @@ +#include diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs new file mode 100644 index 0000000000000..d27b2dae8a95a --- /dev/null +++ b/rust/examples/src/encryption.rs @@ -0,0 +1,80 @@ +use std::time::{Instant, Duration}; +use std::sync::Mutex; + +use mariadb::plugin; +use mariadb::plugin::prelude::*; + +plugin_encryption!{ + type: RustEncryption, + init: RustEncryptionInit, // optional + name: "example_key_management", + author: "MariaDB Team", + description: "Example key management plugin using AES", + license: GPL, + stability: EXPERIMENTAL +} + + +const KEY_ROTATION_MIN: u64 = 45; +const KEY_ROTATION_MAX: u64 = 90; +const KEY_ROTATION_INTERVAL: Duration = + Duration::from_secs(KEY_ROTATION_MAX - KEY_ROTATION_MIN); + +struct KeyVersions { + current: Instant, + next: Instant +} + +impl KeyVersions { + fn random_from_now() -> Self { + let now = Instant::now(); + let next = now + KEY_ROTATION_MIN + random; + Self { + current: now, + next: next + } + } +} + +// Uninitialized +static KEY_VERSIONS: Mutex> = Mutex::new(None); + +struct RustEncryptionInit; + + + +impl plugin::Init for RustEncryptionInit { + fn init() -> Self { + let mut versions = KEY_VERSIONS.lock(); + *versions = Some(KeyVersions::random_from_now()); + Self + } +} + + +struct RustEncryption; + +impl plugin::Encryption for RustEncryption { + fn get_latest_key_version(key_id: u32) -> Result { + + } + + /// Given a key ID and a + fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> { + + } + + /// Initialize + fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self { + + } + + /// Initialize + fn update(&mut self, src: &[u8], dst: &mut BufVec); + + fn finish(&mut self, dst: DestBuf) + + fn size(key_id: u32, key_version: u32) -> u32; + + fn encrypted_length(len: u32, ) +} diff --git a/rust/macros/README.md b/rust/macros/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/rust/mariadb/README.md b/rust/mariadb/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/rust/mariadb/src/plugins.rs b/rust/mariadb/src/plugins.rs new file mode 100644 index 0000000000000..b13b15a3664b7 --- /dev/null +++ b/rust/mariadb/src/plugins.rs @@ -0,0 +1,54 @@ +pub mod encryption; + +use mariadb_server_sys::bindings; + +/// Defines possible licenses for plugins +#[repr(C)] +#[non_exhaustive] +pub enum License { + Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY, + Gpl = bindings::PLUGIN_LICENSE_GPL, + Bsd = bindings::PLUGIN_LICENSE_BSD +} + +impl License { + /// Helper method for creating from a string + fn from_str(s: &str) -> Option { + match s.lower() { + "proprietary" => Some(Self::Proprietary) + "gpl" => Some(Self::Gpl) + "bsd" => Some(Self::Bsd) + _ => None + } + } +} + +/// Defines a type of plugin. This determines the required implementation. +#[repr(C)] +#[non_exhaustive] +pub enum PluginType { + MyUdf = bindings::MYSQL_UDF_PLUGIN, + MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN, + MyFtParser = bindings::MYSQL_FTPARSER_PLUGIN, + MyDaemon = bindings::MYSQL_DAEMON_PLUGIN, + MyInformationSchema = bindings::MYSQL_INFORMATION_SCHEMA_PLUGIN, + MyAudit = bindings::MYSQL_AUDIT_PLUGIN, + MyReplication = bindings::MYSQL_REPLICATION_PLUGIN, + MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN, + MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN, + MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN, + MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN, + MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN, +} + +/// Defines possible licenses for plugins +#[repr(C)] +#[non_exhaustive] +pub enum Maturity { + Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN, + Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL, + Alpha = bindings::MariaDB_PLUGIN_MATURITY_ALPHA + Beta = bindings::MariaDB_PLUGIN_MATURITY_BETA + Gamma = bindings::MariaDB_PLUGIN_MATURITY_GAMMA + Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE +} diff --git a/rust/mariadb/src/plugins/authentication.rs b/rust/mariadb/src/plugins/authentication.rs new file mode 100644 index 0000000000000..3c0604b6d85d5 --- /dev/null +++ b/rust/mariadb/src/plugins/authentication.rs @@ -0,0 +1,74 @@ +// st_mysql_server_auth_info +// st_mysql_auth + +//! +//! +//! +//! +//! +//! # Implementation +//! +//! `st_mysql_auth` requires: +//! +//! - `interface_version`: int, set by macro +//! - `client_auth_plugin`: `char*`, indicates client's required plugin for +//! authentication. Set by macro +//! - `authenticate_user`: function, wraps `authenticate_user` +//! +//! +//! +//! +//! +//! + +use std::slice; + +use crate::plugins::vio::Vio; + +#[repr(transparent)] +struct AuthInfo(bindings::MYSQL_SERVER_AUTH_INFO); + +enum PasswordUsage { + NotUsed = bindings::PASSWORD_USED_NO, + NotUsedMention = bindings::PASSWORD_USED_NO_MENTION, + Used = bindings::PASSWORD_USED_YES, +} + +impl AuthInfo { + fn get_user_name(&self) -> &[u8] { + // SAFETY: caller guarantees validity of self + unsafe { slice::from_raw_parts(self.0.user_name, self.0.user_name_length as usize) } + } + + fn get_auth_string(&self) -> &[u8] { + // SAFETY: caller guarantees validity of self + unsafe { slice::from_raw_parts(self.0.auth_string, self.0.auth_string_length as usize) } + } + + fn get_authenticated_as(&self) -> &[u8] { + + } + + fn set_authenticated_as(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { + + } + + fn set_password_usage(u: PasswordUsage) { + + } + + fn set_host(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { + + } +} + +struct AuthError; +struct TruncatedError; + +trait Authentication { + fn authenticate_user(vio: &Vio, info: &AuthInfo) -> Result<(), AuthError>; + + fn hash_password(password: &[u8], buf: X); + + fn preprocess_hash(hash: &[u8], buf: X); +} diff --git a/rust/mariadb/src/plugins/common.rs b/rust/mariadb/src/plugins/common.rs new file mode 100644 index 0000000000000..408f4a78169f0 --- /dev/null +++ b/rust/mariadb/src/plugins/common.rs @@ -0,0 +1,4 @@ +/// Trait for plugins that want to use the init/deinit functions +trait Init { + fn init() -> Self; +} diff --git a/rust/mariadb/src/plugins/encryption.rs b/rust/mariadb/src/plugins/encryption.rs new file mode 100644 index 0000000000000..4927c4a21b36b --- /dev/null +++ b/rust/mariadb/src/plugins/encryption.rs @@ -0,0 +1,68 @@ +//! Requirements to implement an encryption plugin +//! +//! # Usage +//! +//! - Keep key storage context in globals. These need to be mutex-protected +//! +//! # Implementation +//! +//! `plugin_encryption.h` defines `st_mariadb_encryption`, with the following members: +//! +//! - `interface_version`: integer, set via macro +//! - `get_latest_key_version`: function, wrapped in `Encryption::get_latest_key_version` +//! - `get_key`: function, wrapped in `Encryption::get_key` +//! - `crypt_ctx_size`: function, wrapped in `Encryption::size` +//! - `crypt_ctx_init`: function, wrapped in `Encryption::init` +//! - `crypt_ctx_update`: function, wrapped in `Encryption::update` +//! - `crypt_ctx_finish`: function, wrapped in `Encryption::finish` +//! - `encrypted_length`: function, macro provides call to `std::mem::size_of` + + +/// Struct representing an invalid version error for `get_latest_key_version` +pub struct VersionInvalid; + +#[repr(u32)] +#[non_exhaustive] +pub enum KeyError { + VersionInvalid, + BufferTooSmall, + Other +} + +// API that allows safely writing to a memory location +pub struct BufVec { + buf: &mut [u8] + len: usize +} + +impl BufVec { + fn try_push() +} + +/// Implement this trait on a struct that will serve as encryption context +/// +/// +pub trait Encryption { + /// Get the latest version of a key ID + fn get_latest_key_version(key_id: u32) -> Result; + + /// Given a key ID and a + fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> + + /// Initialize + fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self; + + /// Initialize + fn update(&mut self, src: &[u8], dst: &mut BufVec); + + fn finish(&mut self, dst: DestBuf) + + fn size(key_id: u32, key_version: u32) -> u32; + + fn encrypted_length(len: u32, ) + +} + +// get_latest_key_version: #type::get_latest_key_version +// crypt_ctx_size: std::mem::size_of<#type>() +// crypt_ctx_init: #type:init() diff --git a/rust/mariadb/src/plugins/vio.rs b/rust/mariadb/src/plugins/vio.rs new file mode 100644 index 0000000000000..f6209fb4ed8e6 --- /dev/null +++ b/rust/mariadb/src/plugins/vio.rs @@ -0,0 +1,35 @@ + + +#[repr(transparent)] +pub struct Vio (MYSQL_PLUGIN_VIO); + +#[repr(transparent)] +pub struct VioInfo(MYSQL_PLUGIN_VIO_INFO); + +pub struct WriteFailure; + +impl Vio { + fn read_packet(&self) { + let read_fn = self.0.read_packet.expect("read function is null!"); + } + + fn write_packet(&self, packet: &[u8]) -> Result<(), WriteFailure> { + let write_fn = self.0.write_packet.expect("write function is null!"); + let res = unsafe { write_fn(&self, packet.as_ptr(), packet.len()) }; + if res == 0 { + Ok(()) + } else { + Err(WriteFailure) + } + } + + fn info(&self) -> VioInfo { + let info_fn = self.0.write_packet.expect("write function is null!"); + let vi: MaybeUninit::zeroed(); + unsafe { + info_fn(&self, &vi); + vi.assume_init(); + } + vi + } +} From 5ccd335a23db038cc50e1ca1bb76501fddccc779 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 10 Nov 2022 02:34:47 -0500 Subject: [PATCH 02/34] Create Cargo.toml Work on rough layout including example Work on authentication plugin Bindings now build properly Bindings now build properly Further workspace cleanup --- .gitignore | 4 + rust/Cargo.toml | 4 + rust/bindings/Cargo.toml | 14 +- rust/bindings/build.rs | 90 +++++----- rust/bindings/src/lib.rs | 13 +- rust/bindings/src/wrapper.h | 14 +- rust/examples/Cargo.toml | 10 ++ rust/examples/src/encryption.rs | 160 +++++++++--------- rust/examples/src/lib.rs | 1 + rust/macros/Cargo.toml | 8 + rust/macros/src/lib.rs | 0 rust/mariadb/Cargo.toml | 8 + rust/mariadb/src/lib.rs | 1 + rust/mariadb/src/plugin.rs | 52 ++++++ .../src/{plugins => plugin}/authentication.rs | 148 ++++++++-------- .../mariadb/src/{plugins => plugin}/common.rs | 8 +- .../src/{plugins => plugin}/encryption.rs | 136 +++++++-------- rust/mariadb/src/{plugins => plugin}/vio.rs | 70 ++++---- rust/mariadb/src/plugins.rs | 54 ------ 19 files changed, 427 insertions(+), 368 deletions(-) create mode 100644 rust/examples/Cargo.toml create mode 100644 rust/examples/src/lib.rs create mode 100644 rust/macros/Cargo.toml create mode 100644 rust/macros/src/lib.rs create mode 100644 rust/mariadb/src/lib.rs create mode 100644 rust/mariadb/src/plugin.rs rename rust/mariadb/src/{plugins => plugin}/authentication.rs (95%) rename rust/mariadb/src/{plugins => plugin}/common.rs (96%) rename rust/mariadb/src/{plugins => plugin}/encryption.rs (96%) rename rust/mariadb/src/{plugins => plugin}/vio.rs (96%) delete mode 100644 rust/mariadb/src/plugins.rs diff --git a/.gitignore b/.gitignore index 2fb3857120c1f..d427014224d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -622,3 +622,7 @@ tests/mariadb-client-test versioninfo_dll.rc versioninfo_exe.rc win/packaging/ca/symlinks.cc + +# rust output +rust/target +Cargo.lock diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ab586187625c3..754aad3745930 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,4 +3,8 @@ [workspace] members = [ + "bindings", + "mariadb", + "macros", + "examples" ] diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml index 33f8388c25ca1..9b560695681d0 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/bindings/Cargo.toml @@ -1,6 +1,8 @@ -[package] -name = "mariadb-server-sys" -version = "0.1.0" - -[build-dependencies] -bindgen = "0.61.0" +[package] +name = "mariadb-server-sys" +version = "0.1.0" +edition = '2021' + + +[build-dependencies] +bindgen = "0.61.0" diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 0a537e38428d5..f2ec04f292a9a 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -1,41 +1,49 @@ -use std::env; -use std::path::PathBuf; - -use bindgen::EnumVariation; - -fn main() { - // Tell cargo to look for shared libraries in the specified directory - println!("cargo:rustc-link-search=/path/to/lib"); - - // Tell cargo to tell rustc to link the system bzip2 - // shared library. - println!("cargo:rustc-link-lib=bz2"); - - // Tell cargo to invalidate the built crate whenever the wrapper changes - println!("cargo:rerun-if-changed=wrapper.h"); - - // The bindgen::Builder is the main entry point - // to bindgen, and lets you build up options for - // the resulting bindings. - let bindings = bindgen::Builder::default() - // The input header we would like to generate - // bindings for. - .header("src/wrapper.h") - // Tell cargo to invalidate the built crate whenever any of the - // included header files changed. - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) - // Don't derive copy for structs - .derive_copy(false) - // Use rust-style enums labeled with non_exhaustive to represent C enums - .default_enum_style(EnumVariation::Rust{non_exhaustive: true}) - // Finish the builder and generate the bindings. - .generate() - // Unwrap the Result and panic on failure. - .expect("Unable to generate bindings"); - - // Write the bindings to the $OUT_DIR/bindings.rs file. - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("Couldn't write bindings!"); -} +// extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +use bindgen::EnumVariation; + +fn main() { + // Tell cargo to look for shared libraries in the specified directory + // println!("cargo:rustc-link-search=/path/to/lib"); + + // Tell cargo to tell rustc to link the system bzip2 + // shared library. + // println!("cargo:rustc-link-lib=bz2"); + + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=wrapper.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("src/wrapper.h") + .clang_arg("-I../../include/") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Don't derive copy for structs + .derive_copy(false) + // Use rust-style enums labeled with non_exhaustive to represent C enums + .default_enum_style(EnumVariation::Rust { + non_exhaustive: true, + }) + // LLVM has some issues with long dobule and ABI compatibility + // disabling the only relevant function here to suppress errors + .blocklist_function("strtold") + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs index 024d8f49b68db..a53243655751a 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/bindings/src/lib.rs @@ -1,5 +1,8 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(clippy::useless_transmute)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::missing_safety_doc)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/bindings/src/wrapper.h b/rust/bindings/src/wrapper.h index 44c82afa8412b..d30739878fa75 100644 --- a/rust/bindings/src/wrapper.h +++ b/rust/bindings/src/wrapper.h @@ -1 +1,13 @@ -#include +<<<<<<< HEAD +#include +||||||| parent of 6d1ef9414bc (Bindings now build properly) +#include +======= +#include +#include +<<<<<<< HEAD +>>>>>>> 6d1ef9414bc (Bindings now build properly) +||||||| parent of 0b8c8b06d07 (Further workspace cleanup) +======= +#include +>>>>>>> 0b8c8b06d07 (Further workspace cleanup) diff --git a/rust/examples/Cargo.toml b/rust/examples/Cargo.toml new file mode 100644 index 0000000000000..25731d3997d58 --- /dev/null +++ b/rust/examples/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "mariadb-examples" +version = "0.1.0" +edition = '2021' + + +[dependencies] +mariadb-server = { path = "../mariadb" } +ring = "0.16.20" +sha2 = "0.10.6" diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs index d27b2dae8a95a..ef5d7eb319956 100644 --- a/rust/examples/src/encryption.rs +++ b/rust/examples/src/encryption.rs @@ -1,80 +1,80 @@ -use std::time::{Instant, Duration}; -use std::sync::Mutex; - -use mariadb::plugin; -use mariadb::plugin::prelude::*; - -plugin_encryption!{ - type: RustEncryption, - init: RustEncryptionInit, // optional - name: "example_key_management", - author: "MariaDB Team", - description: "Example key management plugin using AES", - license: GPL, - stability: EXPERIMENTAL -} - - -const KEY_ROTATION_MIN: u64 = 45; -const KEY_ROTATION_MAX: u64 = 90; -const KEY_ROTATION_INTERVAL: Duration = - Duration::from_secs(KEY_ROTATION_MAX - KEY_ROTATION_MIN); - -struct KeyVersions { - current: Instant, - next: Instant -} - -impl KeyVersions { - fn random_from_now() -> Self { - let now = Instant::now(); - let next = now + KEY_ROTATION_MIN + random; - Self { - current: now, - next: next - } - } -} - -// Uninitialized -static KEY_VERSIONS: Mutex> = Mutex::new(None); - -struct RustEncryptionInit; - - - -impl plugin::Init for RustEncryptionInit { - fn init() -> Self { - let mut versions = KEY_VERSIONS.lock(); - *versions = Some(KeyVersions::random_from_now()); - Self - } -} - - -struct RustEncryption; - -impl plugin::Encryption for RustEncryption { - fn get_latest_key_version(key_id: u32) -> Result { - - } - - /// Given a key ID and a - fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> { - - } - - /// Initialize - fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self { - - } - - /// Initialize - fn update(&mut self, src: &[u8], dst: &mut BufVec); - - fn finish(&mut self, dst: DestBuf) - - fn size(key_id: u32, key_version: u32) -> u32; - - fn encrypted_length(len: u32, ) -} +use std::time::{Instant, Duration}; +use std::sync::Mutex; + +use mariadb_server::plugin; +// use mariadb_server::plugin::prelude::*; + +plugin_encryption!{ + type: RustEncryption, + init: RustEncryptionInit, // optional + name: "example_key_management", + author: "MariaDB Team", + description: "Example key management plugin using AES", + license: GPL, + stability: EXPERIMENTAL +} + + +const KEY_ROTATION_MIN: u64 = 45; +const KEY_ROTATION_MAX: u64 = 90; +const KEY_ROTATION_INTERVAL: Duration = + Duration::from_secs(KEY_ROTATION_MAX - KEY_ROTATION_MIN); + +struct KeyVersions { + current: Instant, + next: Instant +} + +impl KeyVersions { + fn random_from_now() -> Self { + let now = Instant::now(); + let next = now + KEY_ROTATION_MIN + random; + Self { + current: now, + next: next + } + } +} + +// Uninitialized +static KEY_VERSIONS: Mutex> = Mutex::new(None); + +struct RustEncryptionInit; + + + +impl plugin::Init for RustEncryptionInit { + fn init() -> Self { + let mut versions = KEY_VERSIONS.lock(); + *versions = Some(KeyVersions::random_from_now()); + Self + } +} + + +struct RustEncryption; + +impl plugin::Encryption for RustEncryption { + fn get_latest_key_version(key_id: u32) -> Result { + + } + + /// Given a key ID and a + fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> { + + } + + /// Initialize + fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self { + + } + + /// Initialize + fn update(&mut self, src: &[u8], dst: &mut BufVec); + + fn finish(&mut self, dst: DestBuf) + + fn size(key_id: u32, key_version: u32) -> u32; + + fn encrypted_length(len: u32, ) +} diff --git a/rust/examples/src/lib.rs b/rust/examples/src/lib.rs new file mode 100644 index 0000000000000..e8c7cef1a1ac2 --- /dev/null +++ b/rust/examples/src/lib.rs @@ -0,0 +1 @@ +mod encryption; diff --git a/rust/macros/Cargo.toml b/rust/macros/Cargo.toml new file mode 100644 index 0000000000000..1a91635217dfe --- /dev/null +++ b/rust/macros/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mariadb-server-macros" +version = "0.1.0" +edition = "2021" + + +[lib] +proc-macro = true diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index e69de29bb2d1d..3fa1ecd2024aa 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mariadb-server" +version = "0.1.0" +edition = '2021' + + +[dependencies] +mariadb-server-sys = { path = "../bindings" } diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs new file mode 100644 index 0000000000000..962cb1bb99c61 --- /dev/null +++ b/rust/mariadb/src/lib.rs @@ -0,0 +1 @@ +pub mod plugin; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs new file mode 100644 index 0000000000000..8d344b354378b --- /dev/null +++ b/rust/mariadb/src/plugin.rs @@ -0,0 +1,52 @@ +// pub mod encryption; + +use mariadb_server_sys as bindings; + +/// Defines possible licenses for plugins +#[non_exhaustive] +pub enum License { + Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY as isize, + Gpl = bindings::PLUGIN_LICENSE_GPL as isize, + Bsd = bindings::PLUGIN_LICENSE_BSD as isize +} + +impl License { + /// Helper method for creating from a string + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "proprietary" => Some(Self::Proprietary), + "gpl" => Some(Self::Gpl), + "bsd" => Some(Self::Bsd), + _ => None + } + } +} + +/// Defines a type of plugin. This determines the required implementation. +#[non_exhaustive] +pub enum PluginType { + MyUdf = bindings::MYSQL_UDF_PLUGIN as isize, + MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN as isize, + MyFtParser = bindings::MYSQL_FTPARSER_PLUGIN as isize, + MyDaemon = bindings::MYSQL_DAEMON_PLUGIN as isize, + MyInformationSchema = bindings::MYSQL_INFORMATION_SCHEMA_PLUGIN as isize, + MyAudit = bindings::MYSQL_AUDIT_PLUGIN as isize, + MyReplication = bindings::MYSQL_REPLICATION_PLUGIN as isize, + MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN as isize, + MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, + MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN as isize, + MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN as isize, + MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN as isize, +} + +/// Defines possible licenses for plugins +// repr: we let this one default +#[non_exhaustive] +pub enum Maturity { + Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, + Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, + Alpha = bindings::MariaDB_PLUGIN_MATURITY_ALPHA as isize, + Beta = bindings::MariaDB_PLUGIN_MATURITY_BETA as isize, + Gamma = bindings::MariaDB_PLUGIN_MATURITY_GAMMA as isize, + Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize +} diff --git a/rust/mariadb/src/plugins/authentication.rs b/rust/mariadb/src/plugin/authentication.rs similarity index 95% rename from rust/mariadb/src/plugins/authentication.rs rename to rust/mariadb/src/plugin/authentication.rs index 3c0604b6d85d5..7fa0fe5072e7f 100644 --- a/rust/mariadb/src/plugins/authentication.rs +++ b/rust/mariadb/src/plugin/authentication.rs @@ -1,74 +1,74 @@ -// st_mysql_server_auth_info -// st_mysql_auth - -//! -//! -//! -//! -//! -//! # Implementation -//! -//! `st_mysql_auth` requires: -//! -//! - `interface_version`: int, set by macro -//! - `client_auth_plugin`: `char*`, indicates client's required plugin for -//! authentication. Set by macro -//! - `authenticate_user`: function, wraps `authenticate_user` -//! -//! -//! -//! -//! -//! - -use std::slice; - -use crate::plugins::vio::Vio; - -#[repr(transparent)] -struct AuthInfo(bindings::MYSQL_SERVER_AUTH_INFO); - -enum PasswordUsage { - NotUsed = bindings::PASSWORD_USED_NO, - NotUsedMention = bindings::PASSWORD_USED_NO_MENTION, - Used = bindings::PASSWORD_USED_YES, -} - -impl AuthInfo { - fn get_user_name(&self) -> &[u8] { - // SAFETY: caller guarantees validity of self - unsafe { slice::from_raw_parts(self.0.user_name, self.0.user_name_length as usize) } - } - - fn get_auth_string(&self) -> &[u8] { - // SAFETY: caller guarantees validity of self - unsafe { slice::from_raw_parts(self.0.auth_string, self.0.auth_string_length as usize) } - } - - fn get_authenticated_as(&self) -> &[u8] { - - } - - fn set_authenticated_as(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { - - } - - fn set_password_usage(u: PasswordUsage) { - - } - - fn set_host(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { - - } -} - -struct AuthError; -struct TruncatedError; - -trait Authentication { - fn authenticate_user(vio: &Vio, info: &AuthInfo) -> Result<(), AuthError>; - - fn hash_password(password: &[u8], buf: X); - - fn preprocess_hash(hash: &[u8], buf: X); -} +// st_mysql_server_auth_info +// st_mysql_auth + +//! +//! +//! +//! +//! +//! # Implementation +//! +//! `st_mysql_auth` requires: +//! +//! - `interface_version`: int, set by macro +//! - `client_auth_plugin`: `char*`, indicates client's required plugin for +//! authentication. Set by macro +//! - `authenticate_user`: function, wraps `authenticate_user` +//! +//! +//! +//! +//! +//! + +use std::slice; + +use crate::plugins::vio::Vio; + +#[repr(transparent)] +struct AuthInfo(bindings::MYSQL_SERVER_AUTH_INFO); + +enum PasswordUsage { + NotUsed = bindings::PASSWORD_USED_NO, + NotUsedMention = bindings::PASSWORD_USED_NO_MENTION, + Used = bindings::PASSWORD_USED_YES, +} + +impl AuthInfo { + fn get_user_name(&self) -> &[u8] { + // SAFETY: caller guarantees validity of self + unsafe { slice::from_raw_parts(self.0.user_name, self.0.user_name_length as usize) } + } + + fn get_auth_string(&self) -> &[u8] { + // SAFETY: caller guarantees validity of self + unsafe { slice::from_raw_parts(self.0.auth_string, self.0.auth_string_length as usize) } + } + + fn get_authenticated_as(&self) -> &[u8] { + + } + + fn set_authenticated_as(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { + + } + + fn set_password_usage(u: PasswordUsage) { + + } + + fn set_host(&self, s: AsRef<[u8]>) -> Result<(), TruncatedError> { + + } +} + +struct AuthError; +struct TruncatedError; + +trait Authentication { + fn authenticate_user(vio: &Vio, info: &AuthInfo) -> Result<(), AuthError>; + + fn hash_password(password: &[u8], buf: X); + + fn preprocess_hash(hash: &[u8], buf: X); +} diff --git a/rust/mariadb/src/plugins/common.rs b/rust/mariadb/src/plugin/common.rs similarity index 96% rename from rust/mariadb/src/plugins/common.rs rename to rust/mariadb/src/plugin/common.rs index 408f4a78169f0..b308d6dadf30f 100644 --- a/rust/mariadb/src/plugins/common.rs +++ b/rust/mariadb/src/plugin/common.rs @@ -1,4 +1,4 @@ -/// Trait for plugins that want to use the init/deinit functions -trait Init { - fn init() -> Self; -} +/// Trait for plugins that want to use the init/deinit functions +trait Init { + fn init() -> Self; +} diff --git a/rust/mariadb/src/plugins/encryption.rs b/rust/mariadb/src/plugin/encryption.rs similarity index 96% rename from rust/mariadb/src/plugins/encryption.rs rename to rust/mariadb/src/plugin/encryption.rs index 4927c4a21b36b..130229eb75b83 100644 --- a/rust/mariadb/src/plugins/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -1,68 +1,68 @@ -//! Requirements to implement an encryption plugin -//! -//! # Usage -//! -//! - Keep key storage context in globals. These need to be mutex-protected -//! -//! # Implementation -//! -//! `plugin_encryption.h` defines `st_mariadb_encryption`, with the following members: -//! -//! - `interface_version`: integer, set via macro -//! - `get_latest_key_version`: function, wrapped in `Encryption::get_latest_key_version` -//! - `get_key`: function, wrapped in `Encryption::get_key` -//! - `crypt_ctx_size`: function, wrapped in `Encryption::size` -//! - `crypt_ctx_init`: function, wrapped in `Encryption::init` -//! - `crypt_ctx_update`: function, wrapped in `Encryption::update` -//! - `crypt_ctx_finish`: function, wrapped in `Encryption::finish` -//! - `encrypted_length`: function, macro provides call to `std::mem::size_of` - - -/// Struct representing an invalid version error for `get_latest_key_version` -pub struct VersionInvalid; - -#[repr(u32)] -#[non_exhaustive] -pub enum KeyError { - VersionInvalid, - BufferTooSmall, - Other -} - -// API that allows safely writing to a memory location -pub struct BufVec { - buf: &mut [u8] - len: usize -} - -impl BufVec { - fn try_push() -} - -/// Implement this trait on a struct that will serve as encryption context -/// -/// -pub trait Encryption { - /// Get the latest version of a key ID - fn get_latest_key_version(key_id: u32) -> Result; - - /// Given a key ID and a - fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> - - /// Initialize - fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self; - - /// Initialize - fn update(&mut self, src: &[u8], dst: &mut BufVec); - - fn finish(&mut self, dst: DestBuf) - - fn size(key_id: u32, key_version: u32) -> u32; - - fn encrypted_length(len: u32, ) - -} - -// get_latest_key_version: #type::get_latest_key_version -// crypt_ctx_size: std::mem::size_of<#type>() -// crypt_ctx_init: #type:init() +//! Requirements to implement an encryption plugin +//! +//! # Usage +//! +//! - Keep key storage context in globals. These need to be mutex-protected +//! +//! # Implementation +//! +//! `plugin_encryption.h` defines `st_mariadb_encryption`, with the following members: +//! +//! - `interface_version`: integer, set via macro +//! - `get_latest_key_version`: function, wrapped in `Encryption::get_latest_key_version` +//! - `get_key`: function, wrapped in `Encryption::get_key` +//! - `crypt_ctx_size`: function, wrapped in `Encryption::size` +//! - `crypt_ctx_init`: function, wrapped in `Encryption::init` +//! - `crypt_ctx_update`: function, wrapped in `Encryption::update` +//! - `crypt_ctx_finish`: function, wrapped in `Encryption::finish` +//! - `encrypted_length`: function, macro provides call to `std::mem::size_of` + + +/// Struct representing an invalid version error for `get_latest_key_version` +pub struct VersionInvalid; + +#[repr(u32)] +#[non_exhaustive] +pub enum KeyError { + VersionInvalid, + BufferTooSmall, + Other +} + +// API that allows safely writing to a memory location +pub struct BufVec { + buf: &mut [u8] + len: usize +} + +impl BufVec { + fn try_push() +} + +/// Implement this trait on a struct that will serve as encryption context +/// +/// +pub trait Encryption { + /// Get the latest version of a key ID + fn get_latest_key_version(key_id: u32) -> Result; + + /// Given a key ID and a + fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> + + /// Initialize + fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self; + + /// Initialize + fn update(&mut self, src: &[u8], dst: &mut BufVec); + + fn finish(&mut self, dst: DestBuf) + + fn size(key_id: u32, key_version: u32) -> u32; + + fn encrypted_length(len: u32, ) + +} + +// get_latest_key_version: #type::get_latest_key_version +// crypt_ctx_size: std::mem::size_of<#type>() +// crypt_ctx_init: #type:init() diff --git a/rust/mariadb/src/plugins/vio.rs b/rust/mariadb/src/plugin/vio.rs similarity index 96% rename from rust/mariadb/src/plugins/vio.rs rename to rust/mariadb/src/plugin/vio.rs index f6209fb4ed8e6..316bbed5f42b4 100644 --- a/rust/mariadb/src/plugins/vio.rs +++ b/rust/mariadb/src/plugin/vio.rs @@ -1,35 +1,35 @@ - - -#[repr(transparent)] -pub struct Vio (MYSQL_PLUGIN_VIO); - -#[repr(transparent)] -pub struct VioInfo(MYSQL_PLUGIN_VIO_INFO); - -pub struct WriteFailure; - -impl Vio { - fn read_packet(&self) { - let read_fn = self.0.read_packet.expect("read function is null!"); - } - - fn write_packet(&self, packet: &[u8]) -> Result<(), WriteFailure> { - let write_fn = self.0.write_packet.expect("write function is null!"); - let res = unsafe { write_fn(&self, packet.as_ptr(), packet.len()) }; - if res == 0 { - Ok(()) - } else { - Err(WriteFailure) - } - } - - fn info(&self) -> VioInfo { - let info_fn = self.0.write_packet.expect("write function is null!"); - let vi: MaybeUninit::zeroed(); - unsafe { - info_fn(&self, &vi); - vi.assume_init(); - } - vi - } -} + + +#[repr(transparent)] +pub struct Vio (MYSQL_PLUGIN_VIO); + +#[repr(transparent)] +pub struct VioInfo(MYSQL_PLUGIN_VIO_INFO); + +pub struct WriteFailure; + +impl Vio { + fn read_packet(&self) { + let read_fn = self.0.read_packet.expect("read function is null!"); + } + + fn write_packet(&self, packet: &[u8]) -> Result<(), WriteFailure> { + let write_fn = self.0.write_packet.expect("write function is null!"); + let res = unsafe { write_fn(&self, packet.as_ptr(), packet.len()) }; + if res == 0 { + Ok(()) + } else { + Err(WriteFailure) + } + } + + fn info(&self) -> VioInfo { + let info_fn = self.0.write_packet.expect("write function is null!"); + let vi: MaybeUninit::zeroed(); + unsafe { + info_fn(&self, &vi); + vi.assume_init(); + } + vi + } +} diff --git a/rust/mariadb/src/plugins.rs b/rust/mariadb/src/plugins.rs deleted file mode 100644 index b13b15a3664b7..0000000000000 --- a/rust/mariadb/src/plugins.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub mod encryption; - -use mariadb_server_sys::bindings; - -/// Defines possible licenses for plugins -#[repr(C)] -#[non_exhaustive] -pub enum License { - Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY, - Gpl = bindings::PLUGIN_LICENSE_GPL, - Bsd = bindings::PLUGIN_LICENSE_BSD -} - -impl License { - /// Helper method for creating from a string - fn from_str(s: &str) -> Option { - match s.lower() { - "proprietary" => Some(Self::Proprietary) - "gpl" => Some(Self::Gpl) - "bsd" => Some(Self::Bsd) - _ => None - } - } -} - -/// Defines a type of plugin. This determines the required implementation. -#[repr(C)] -#[non_exhaustive] -pub enum PluginType { - MyUdf = bindings::MYSQL_UDF_PLUGIN, - MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN, - MyFtParser = bindings::MYSQL_FTPARSER_PLUGIN, - MyDaemon = bindings::MYSQL_DAEMON_PLUGIN, - MyInformationSchema = bindings::MYSQL_INFORMATION_SCHEMA_PLUGIN, - MyAudit = bindings::MYSQL_AUDIT_PLUGIN, - MyReplication = bindings::MYSQL_REPLICATION_PLUGIN, - MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN, - MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN, - MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN, - MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN, - MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN, -} - -/// Defines possible licenses for plugins -#[repr(C)] -#[non_exhaustive] -pub enum Maturity { - Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN, - Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL, - Alpha = bindings::MariaDB_PLUGIN_MATURITY_ALPHA - Beta = bindings::MariaDB_PLUGIN_MATURITY_BETA - Gamma = bindings::MariaDB_PLUGIN_MATURITY_GAMMA - Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE -} From c30e85a6b3aeb9441705832fa90532a8125c871d Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 10 Nov 2022 21:43:02 -0500 Subject: [PATCH 03/34] Add workflow to upload book Update workflow Update build to call cmake Update workflow cmake install Correct tests and formatting Added cmake build options Update cmake options Update doc directory Sort build list Simplify build function --- .github/workflows/validation-rust.yaml | 203 +++++++++++++++++++++++++ rust/bindings/Cargo.toml | 1 + rust/bindings/build.rs | 81 ++++++++-- rust/bindings/src/lib.rs | 1 + rust/bindings/src/wrapper.h | 16 +- rust/examples/src/encryption.rs | 4 +- rust/examples/src/lib.rs | 2 +- rust/macros/src/lib.rs | 1 + rust/mariadb/src/lib.rs | 2 + rust/mariadb/src/plugin.rs | 61 +++++--- rust/mariadb/src/plugin/encryption.rs | 67 ++++---- 11 files changed, 359 insertions(+), 80 deletions(-) create mode 100644 .github/workflows/validation-rust.yaml diff --git a/.github/workflows/validation-rust.yaml b/.github/workflows/validation-rust.yaml new file mode 100644 index 0000000000000..be645d04366c3 --- /dev/null +++ b/.github/workflows/validation-rust.yaml @@ -0,0 +1,203 @@ +--- + +# Main "useful" actions config file +# Cache config comes from https://github.com/actions/cache/blob/main/examples.md#rust---cargo +# actions-rs/toolchain configures rustup +# actions-rs/cargo actually runs cargo + +on: + push: + branches: + - rust + # pull_request: + +name: Rust Validation + +jobs: + check: + name: "Check (cargo check)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + rust/target/ + CMakeFiles/ + CMakeCache.txt + CPackConfig.cmake + CPackSourceConfig.cmake + CTestTestfile.cmake + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: sudo apt-get update && sudo apt-get install cmake + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + args: --manifest-path rust/Cargo.toml + + test: + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest] #, windows-latest, macos-latest] + include: + - os: ubuntu-latest + name: linux + # - os: windows-latest + # name: windows + # - os: macos-latest + # name: mac + + name: "Test on ${{ matrix.name }} (cargo test)" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + rust/target/ + CMakeFiles/ + CMakeCache.txt + CPackConfig.cmake + CPackSourceConfig.cmake + CTestTestfile.cmake + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: sudo apt-get update && sudo apt-get install cmake + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + env: + RUST_BACKTRACE: "1" + with: + command: test + args: --manifest-path rust/Cargo.toml + + fmt: + name: "Format (cargo fmt)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + rust/target/ + CMakeFiles/ + CMakeCache.txt + CPackConfig.cmake + CPackSourceConfig.cmake + CTestTestfile.cmake + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all --manifest-path rust/Cargo.toml -- --check + + clippy: + name: "Clippy (cargo clippy)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + rust/target/ + CMakeFiles/ + CMakeCache.txt + CPackConfig.cmake + CPackSourceConfig.cmake + CTestTestfile.cmake + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: sudo apt-get update && sudo apt-get install cmake + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + # Some nice checks are only nightly + toolchain: nightly + override: true + - run: rustup component add clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --manifest-path rust/Cargo.toml -- -D warnings + + doc: + name: "Docs (cargo doc) & Pub" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + rust/target/ + CMakeFiles/ + CMakeCache.txt + CPackConfig.cmake + CPackSourceConfig.cmake + CTestTestfile.cmake + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: sudo apt-get update && sudo apt-get install cmake + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: doc + args: --manifest-path rust/Cargo.toml --no-deps + - run: | + echo `pwd`/rust/target/doc >> $GITHUB_PATH + # fake index.html so github likes us + echo "" > rust/target/doc/index.html + - name: Deploy GitHub Pages + run: | + git worktree add gh-pages + git config user.name "Deploy from CI" + git config user.email "" + cd gh-pages + # Delete the ref to avoid keeping history. + git update-ref -d refs/heads/gh-pages + rm -rf * + mv ../rust/target/doc/* . + git add . + git commit -m "Deploy $GITHUB_SHA to gh-pages" + git push --force --set-upstream origin gh-pages diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml index 9b560695681d0..da4aea30e1215 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/bindings/Cargo.toml @@ -6,3 +6,4 @@ edition = '2021' [build-dependencies] bindgen = "0.61.0" +cmake = "0.1" diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index f2ec04f292a9a..3ba249c13ccd4 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -1,21 +1,67 @@ // extern crate bindgen; +use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; +use bindgen::EnumVariation; +use std::collections::HashSet; use std::env; use std::path::PathBuf; +use std::process::Command; -use bindgen::EnumVariation; +// `math.h` seems to double define some things, To avoid this, we ignore them. +const IGNORE_MACROS +: [&str; 20] = [ + "FE_DIVBYZERO", + "FE_DOWNWARD", + "FE_INEXACT", + "FE_INVALID", + "FE_OVERFLOW", + "FE_TONEAREST", + "FE_TOWARDZERO", + "FE_UNDERFLOW", + "FE_UPWARD", + "FP_INFINITE", + "FP_INT_DOWNWARD", + "FP_INT_TONEAREST", + "FP_INT_TONEARESTFROMZERO", + "FP_INT_TOWARDZERO", + "FP_INT_UPWARD", + "FP_NAN", + "FP_NORMAL", + "FP_SUBNORMAL", + "FP_ZERO", + "IPPORT_RESERVED", +]; -fn main() { - // Tell cargo to look for shared libraries in the specified directory - // println!("cargo:rustc-link-search=/path/to/lib"); +#[derive(Debug)] +struct IgnoreMacros(HashSet); + +impl ParseCallbacks for IgnoreMacros { + fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { + if self.0.contains(name) { + MacroParsingBehavior::Ignore + } else { + MacroParsingBehavior::Default + } + } +} - // Tell cargo to tell rustc to link the system bzip2 - // shared library. - // println!("cargo:rustc-link-lib=bz2"); +impl IgnoreMacros { + fn new() -> Self { + Self(IGNORE_MACROS + .into_iter().map(|s| s.to_owned()).collect()) + } +} +fn main() { // Tell cargo to invalidate the built crate whenever the wrapper changes println!("cargo:rerun-if-changed=wrapper.h"); + // Run cmake to configure only + Command::new("cmake") + .args(["../../", "-B../../"]) + .output() + .expect("failed to invoke cmake"); + // The bindgen::Builder is the main entry point // to bindgen, and lets you build up options for // the resulting bindings. @@ -23,10 +69,12 @@ fn main() { // The input header we would like to generate // bindings for. .header("src/wrapper.h") - .clang_arg("-I../../include/") - // Tell cargo to invalidate the built crate whenever any of the - // included header files changed. - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Fix math.h double defines + .parse_callbacks(Box::new(IgnoreMacros::new())) + .clang_arg("-I../../include") + .clang_arg("-I../../sql") + .clang_arg("-xc++") + .clang_arg("-std=c++17") // Don't derive copy for structs .derive_copy(false) // Use rust-style enums labeled with non_exhaustive to represent C enums @@ -36,6 +84,15 @@ fn main() { // LLVM has some issues with long dobule and ABI compatibility // disabling the only relevant function here to suppress errors .blocklist_function("strtold") + // qvct, evct, qfcvt_r, ... + .blocklist_function("[a-z]{1,2}cvt(?:_r)?") + // c++ things that aren't supported + .blocklist_item("List_iterator") + .blocklist_type("std::char_traits") + .opaque_type("std_.*") + .blocklist_item("std_basic_string") + .blocklist_item("std_collate.*") + .blocklist_item("__gnu_cxx.*") // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. @@ -45,5 +102,5 @@ fn main() { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings .write_to_file(out_path.join("bindings.rs")) - .expect("Couldn't write bindings!"); + .expect("couldn't write bindings"); } diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs index a53243655751a..f26a6cd3b46cb 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/bindings/src/lib.rs @@ -5,4 +5,5 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::missing_safety_doc)] +// Bindings are autogenerated at build time using build.rs include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/bindings/src/wrapper.h b/rust/bindings/src/wrapper.h index d30739878fa75..75cebd4dc055a 100644 --- a/rust/bindings/src/wrapper.h +++ b/rust/bindings/src/wrapper.h @@ -1,13 +1,9 @@ -<<<<<<< HEAD -#include -||||||| parent of 6d1ef9414bc (Bindings now build properly) -#include -======= +// Directives here indicate what to include in bindings + +// sql/ +#include + +// include/ #include #include -<<<<<<< HEAD ->>>>>>> 6d1ef9414bc (Bindings now build properly) -||||||| parent of 0b8c8b06d07 (Further workspace cleanup) -======= #include ->>>>>>> 0b8c8b06d07 (Further workspace cleanup) diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs index ef5d7eb319956..daf21c1e15bdb 100644 --- a/rust/examples/src/encryption.rs +++ b/rust/examples/src/encryption.rs @@ -72,9 +72,9 @@ impl plugin::Encryption for RustEncryption { /// Initialize fn update(&mut self, src: &[u8], dst: &mut BufVec); - fn finish(&mut self, dst: DestBuf) + fn finish(&mut self, dst: DestBuf); fn size(key_id: u32, key_version: u32) -> u32; - fn encrypted_length(len: u32, ) + fn encrypted_length(len: u32); } diff --git a/rust/examples/src/lib.rs b/rust/examples/src/lib.rs index e8c7cef1a1ac2..ef828804506ea 100644 --- a/rust/examples/src/lib.rs +++ b/rust/examples/src/lib.rs @@ -1 +1 @@ -mod encryption; +// mod encryption; diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index e69de29bb2d1d..8b137891791fe 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 962cb1bb99c61..a38ef65d5ec3e 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -1 +1,3 @@ +//! Crate representing safe abstractions over MariaDB bindings + pub mod plugin; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 8d344b354378b..b1875761c0c2e 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -1,23 +1,27 @@ // pub mod encryption; -use mariadb_server_sys as bindings; +use std::cell::UnsafeCell; + +use mariadb_server_sys::include; +pub mod encryption; /// Defines possible licenses for plugins #[non_exhaustive] +#[derive(Clone, Copy, Debug)] pub enum License { - Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY as isize, - Gpl = bindings::PLUGIN_LICENSE_GPL as isize, - Bsd = bindings::PLUGIN_LICENSE_BSD as isize + Proprietary = include::PLUGIN_LICENSE_PROPRIETARY as isize, + Gpl = include::PLUGIN_LICENSE_GPL as isize, + Bsd = include::PLUGIN_LICENSE_BSD as isize, } impl License { - /// Helper method for creating from a string + /// Create a license type from a string pub fn from_str(s: &str) -> Option { match s.to_lowercase().as_str() { "proprietary" => Some(Self::Proprietary), "gpl" => Some(Self::Gpl), "bsd" => Some(Self::Bsd), - _ => None + _ => None, } } } @@ -25,28 +29,37 @@ impl License { /// Defines a type of plugin. This determines the required implementation. #[non_exhaustive] pub enum PluginType { - MyUdf = bindings::MYSQL_UDF_PLUGIN as isize, - MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN as isize, - MyFtParser = bindings::MYSQL_FTPARSER_PLUGIN as isize, - MyDaemon = bindings::MYSQL_DAEMON_PLUGIN as isize, - MyInformationSchema = bindings::MYSQL_INFORMATION_SCHEMA_PLUGIN as isize, - MyAudit = bindings::MYSQL_AUDIT_PLUGIN as isize, - MyReplication = bindings::MYSQL_REPLICATION_PLUGIN as isize, - MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN as isize, - MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, - MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN as isize, - MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN as isize, - MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN as isize, + MyUdf = include::MYSQL_UDF_PLUGIN as isize, + MyStorageEngine = include::MYSQL_STORAGE_ENGINE_PLUGIN as isize, + MyFtParser = include::MYSQL_FTPARSER_PLUGIN as isize, + MyDaemon = include::MYSQL_DAEMON_PLUGIN as isize, + MyInformationSchema = include::MYSQL_INFORMATION_SCHEMA_PLUGIN as isize, + MyAudit = include::MYSQL_AUDIT_PLUGIN as isize, + MyReplication = include::MYSQL_REPLICATION_PLUGIN as isize, + MyAuthentication = include::MYSQL_AUTHENTICATION_PLUGIN as isize, + MariaPasswordValidation = include::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, + MariaEncryption = include::MariaDB_ENCRYPTION_PLUGIN as isize, + MariaDataType = include::MariaDB_DATA_TYPE_PLUGIN as isize, + MariaFunction = include::MariaDB_FUNCTION_PLUGIN as isize, } /// Defines possible licenses for plugins // repr: we let this one default #[non_exhaustive] pub enum Maturity { - Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, - Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, - Alpha = bindings::MariaDB_PLUGIN_MATURITY_ALPHA as isize, - Beta = bindings::MariaDB_PLUGIN_MATURITY_BETA as isize, - Gamma = bindings::MariaDB_PLUGIN_MATURITY_GAMMA as isize, - Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize + Unknown = include::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, + Experimental = include::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, + Alpha = include::MariaDB_PLUGIN_MATURITY_ALPHA as isize, + Beta = include::MariaDB_PLUGIN_MATURITY_BETA as isize, + Gamma = include::MariaDB_PLUGIN_MATURITY_GAMMA as isize, + Stable = include::MariaDB_PLUGIN_MATURITY_STABLE as isize, +} + +#[repr(transparent)] +pub struct PluginContext(pub(crate) UnsafeCell); + +impl PluginContext { + pub(crate) unsafe fn from_ptr<'a>(ptr: *mut include::st_plugin_init) -> &'a Self { + &*ptr.cast() + } } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 130229eb75b83..1ff02969e3f47 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -1,13 +1,13 @@ //! Requirements to implement an encryption plugin -//! +//! //! # Usage -//! +//! //! - Keep key storage context in globals. These need to be mutex-protected -//! +//! //! # Implementation -//! +//! //! `plugin_encryption.h` defines `st_mariadb_encryption`, with the following members: -//! +//! //! - `interface_version`: integer, set via macro //! - `get_latest_key_version`: function, wrapped in `Encryption::get_latest_key_version` //! - `get_key`: function, wrapped in `Encryption::get_key` @@ -17,52 +17,57 @@ //! - `crypt_ctx_finish`: function, wrapped in `Encryption::finish` //! - `encrypted_length`: function, macro provides call to `std::mem::size_of` +use core::cell::UnsafeCell; +use mariadb_server_sys as bindings; -/// Struct representing an invalid version error for `get_latest_key_version` -pub struct VersionInvalid; - +/// A type of error to be used by key functions #[repr(u32)] #[non_exhaustive] pub enum KeyError { VersionInvalid, BufferTooSmall, - Other -} - -// API that allows safely writing to a memory location -pub struct BufVec { - buf: &mut [u8] - len: usize -} - -impl BufVec { - fn try_push() + Other, } /// Implement this trait on a struct that will serve as encryption context /// /// +#[allow(unused_variables)] pub trait Encryption { - /// Get the latest version of a key ID - fn get_latest_key_version(key_id: u32) -> Result; + /// The type of context data passed to various functions + type Context: Send; - /// Given a key ID and a - fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> + /// Get the latest version of a key ID. Return `VersionInvalid` if not found. + fn get_latest_key_version(key_id: u32) -> Result; - /// Initialize - fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self; + /// Return a key for a key version + /// + /// Given a key ID and a version, write the key to the `key` buffer. If the + /// buffer is too small, return [`KeyError::BufferTooSmall`]. + fn get_key(key_id: u32, key_version: u32, key: &mut [u8]) -> Result; + + /// Calculate the length of a key + fn get_key_length(key_id: u32, key_version: u32) -> Result; /// Initialize - fn update(&mut self, src: &[u8], dst: &mut BufVec); + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self::Context { + unimplemented!() + } - fn finish(&mut self, dst: DestBuf) + /// Initialize + fn update(ctx: &mut Self::Context, src: &[u8], dst: &mut [u8]) { + unimplemented!() + } - fn size(key_id: u32, key_version: u32) -> u32; + fn finish(ctx: &mut Self::Context, dst: &mut [u8]) { + unimplemented!() + } - fn encrypted_length(len: u32, ) - + fn encrypted_length(key_id: u32, key_version: u32, slen: usize) { + unimplemented!() + } } // get_latest_key_version: #type::get_latest_key_version -// crypt_ctx_size: std::mem::size_of<#type>() +// crypt_ctx_size: std::mem::size_of<#type::Context>() // crypt_ctx_init: #type:init() From d47893a14fb1096967c33a525992cd99e26aeadc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 4 Jan 2023 04:44:16 -0500 Subject: [PATCH 04/34] Work on encryption example --- rust/README.md | 3 + rust/bindings/build.rs | 2 +- rust/bindings/src/wrapper.h | 5 +- rust/examples/Cargo.toml | 1 + rust/examples/src/encryption.rs | 137 ++++++++++++++++++-------- rust/examples/src/lib.rs | 2 +- rust/mariadb/src/plugin.rs | 61 ++++++------ rust/mariadb/src/plugin/encryption.rs | 34 ++++--- 8 files changed, 154 insertions(+), 91 deletions(-) create mode 100644 rust/README.md diff --git a/rust/README.md b/rust/README.md new file mode 100644 index 0000000000000..110d97475d499 --- /dev/null +++ b/rust/README.md @@ -0,0 +1,3 @@ +# Rust support for MariaDB + +The purpose of this module is to be able to write plugins for MariaDB in Rust. diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 3ba249c13ccd4..273c2f2bcff41 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -1,4 +1,4 @@ -// extern crate bindgen; +//! This file runs `cmake` as needed, then `bindgen` to produce the rust bindings use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; use bindgen::EnumVariation; diff --git a/rust/bindings/src/wrapper.h b/rust/bindings/src/wrapper.h index 75cebd4dc055a..cdc2c07e514f3 100644 --- a/rust/bindings/src/wrapper.h +++ b/rust/bindings/src/wrapper.h @@ -1,9 +1,6 @@ // Directives here indicate what to include in bindings -// sql/ -#include - -// include/ +// #include #include #include #include diff --git a/rust/examples/Cargo.toml b/rust/examples/Cargo.toml index 25731d3997d58..44cb540ba01eb 100644 --- a/rust/examples/Cargo.toml +++ b/rust/examples/Cargo.toml @@ -8,3 +8,4 @@ edition = '2021' mariadb-server = { path = "../mariadb" } ring = "0.16.20" sha2 = "0.10.6" +rand = "0.8.5" diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs index daf21c1e15bdb..2bc3986a31e63 100644 --- a/rust/examples/src/encryption.rs +++ b/rust/examples/src/encryption.rs @@ -1,80 +1,131 @@ +#![allow(unused)] + use std::time::{Instant, Duration}; use std::sync::Mutex; +use rand::{Rng}; -use mariadb_server::plugin; +use mariadb_server::plugin::encryption::{KeyError, Encryption}; +// use mariadb_server::plugin::Init; // use mariadb_server::plugin::prelude::*; -plugin_encryption!{ - type: RustEncryption, - init: RustEncryptionInit, // optional - name: "example_key_management", - author: "MariaDB Team", - description: "Example key management plugin using AES", - license: GPL, - stability: EXPERIMENTAL -} - - -const KEY_ROTATION_MIN: u64 = 45; -const KEY_ROTATION_MAX: u64 = 90; -const KEY_ROTATION_INTERVAL: Duration = - Duration::from_secs(KEY_ROTATION_MAX - KEY_ROTATION_MIN); +// plugin_encryption!{ +// type: RustEncryption, +// init: RustEncryptionInit, // optional +// name: "example_key_management", +// author: "MariaDB Team", +// description: "Example key management plugin using AES", +// license: GPL, +// stability: EXPERIMENTAL +// } + + +/// Range of key rotations, as seconds +const KEY_ROTATION_MIN: f32 = 45.0; +const KEY_ROTATION_MAX: f32 = 90.0; +const KEY_ROTATION_INTERVAL: f32 = KEY_ROTATION_MAX - KEY_ROTATION_MIN; +// const KEY_ROTATION_INTERVAL: Duration = +// KEY_ROTATION_MAX - KEY_ROTATION_MIN; + +/// Our global key version state +static KEY_VERSIONS: Mutex> = Mutex::new(None); +/// Contain the state of our keys. We use `Instant` (the monotonically) +/// increasing clock) instead of `SystemTime` (which may occasionally go +/// backwards) struct KeyVersions { + /// Initialization time of the struct, reference point for key version + start: Instant, + /// Most recent key update time current: Instant, + /// Next time for a key update next: Instant } impl KeyVersions { - fn random_from_now() -> Self { + /// Initialize with a new value. Returns the struct + fn new_now() -> (Self, u64) { let now = Instant::now(); - let next = now + KEY_ROTATION_MIN + random; - Self { + let mut ret = Self { + start: now, current: now, - next: next + next: now + }; + ret.update_next(); + let duration = (ret.next - ret.start).as_secs(); + (ret, duration) + } + + fn update_next(&mut self) { + let mult = rand::thread_rng().gen_range(0.0..1.0); + let add_duration = KEY_ROTATION_MIN + mult * KEY_ROTATION_INTERVAL; + self.next += Duration::from_secs_f32(add_duration); + } + + /// Update the internal duration if needed, and return the elapsed time + fn update_returning_version(&mut self) -> u64 { + let now = Instant::now(); + if now > self.next { + self.current = now; + self.update_next(); } + (self.next - self.start).as_secs() } } // Uninitialized -static KEY_VERSIONS: Mutex> = Mutex::new(None); - -struct RustEncryptionInit; +// struct RustEncryptionInit; -impl plugin::Init for RustEncryptionInit { - fn init() -> Self { - let mut versions = KEY_VERSIONS.lock(); - *versions = Some(KeyVersions::random_from_now()); - Self - } -} +// impl plugin::Init for RustEncryptionInit { +// fn init() -> Self { +// let mut versions = KEY_VERSIONS.lock(); +// *versions = Some(KeyVersions::random_from_now()); +// Self +// } +// } struct RustEncryption; -impl plugin::Encryption for RustEncryption { - fn get_latest_key_version(key_id: u32) -> Result { - +impl Encryption for RustEncryption { + fn get_latest_key_version(_key_id: u32) -> Result { + let mut guard = KEY_VERSIONS.lock().unwrap(); + if let Some(ref mut vers) = *guard { + Ok(vers.update_returning_version() as u32) + } else { + let (kv, ret) = KeyVersions::new_now(); + *guard = Some(kv); + Ok(ret as u32) + } } - + /// Given a key ID and a - fn get_key(key_id: u32, version: u32, key: &mut BufVec) -> Result<(), EncryptionError> { - + fn get_key(key_id: u32, key_version: u32, key: &mut [u8]) -> Result { + + todo!() + } + + fn get_key_length(key_id: u32, key_version: u32) -> Result { + todo!() } /// Initialize - fn init(key: &[u8], iv: &[u8], flags: u32, key_id: u32, ) -> Self { + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self { + todo!() } /// Initialize - fn update(&mut self, src: &[u8], dst: &mut BufVec); - - fn finish(&mut self, dst: DestBuf); - - fn size(key_id: u32, key_version: u32) -> u32; + fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize { + todo!() + } - fn encrypted_length(len: u32); + fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize{ + todo!() + } + + fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) { + todo!() + } } diff --git a/rust/examples/src/lib.rs b/rust/examples/src/lib.rs index ef828804506ea..e8c7cef1a1ac2 100644 --- a/rust/examples/src/lib.rs +++ b/rust/examples/src/lib.rs @@ -1 +1 @@ -// mod encryption; +mod encryption; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index b1875761c0c2e..2f2a9c1d5982f 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -1,17 +1,17 @@ -// pub mod encryption; +//! Parent module for all plugin types use std::cell::UnsafeCell; -use mariadb_server_sys::include; +use mariadb_server_sys as bindings; pub mod encryption; /// Defines possible licenses for plugins #[non_exhaustive] #[derive(Clone, Copy, Debug)] pub enum License { - Proprietary = include::PLUGIN_LICENSE_PROPRIETARY as isize, - Gpl = include::PLUGIN_LICENSE_GPL as isize, - Bsd = include::PLUGIN_LICENSE_BSD as isize, + Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY as isize, + Gpl = bindings::PLUGIN_LICENSE_GPL as isize, + Bsd = bindings::PLUGIN_LICENSE_BSD as isize, } impl License { @@ -29,37 +29,36 @@ impl License { /// Defines a type of plugin. This determines the required implementation. #[non_exhaustive] pub enum PluginType { - MyUdf = include::MYSQL_UDF_PLUGIN as isize, - MyStorageEngine = include::MYSQL_STORAGE_ENGINE_PLUGIN as isize, - MyFtParser = include::MYSQL_FTPARSER_PLUGIN as isize, - MyDaemon = include::MYSQL_DAEMON_PLUGIN as isize, - MyInformationSchema = include::MYSQL_INFORMATION_SCHEMA_PLUGIN as isize, - MyAudit = include::MYSQL_AUDIT_PLUGIN as isize, - MyReplication = include::MYSQL_REPLICATION_PLUGIN as isize, - MyAuthentication = include::MYSQL_AUTHENTICATION_PLUGIN as isize, - MariaPasswordValidation = include::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, - MariaEncryption = include::MariaDB_ENCRYPTION_PLUGIN as isize, - MariaDataType = include::MariaDB_DATA_TYPE_PLUGIN as isize, - MariaFunction = include::MariaDB_FUNCTION_PLUGIN as isize, + MyUdf = bindings::MYSQL_UDF_PLUGIN as isize, + MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN as isize, + MyFtParser = bindings::MYSQL_FTPARSER_PLUGIN as isize, + MyDaemon = bindings::MYSQL_DAEMON_PLUGIN as isize, + MyInformationSchema = bindings::MYSQL_INFORMATION_SCHEMA_PLUGIN as isize, + MyAudit = bindings::MYSQL_AUDIT_PLUGIN as isize, + MyReplication = bindings::MYSQL_REPLICATION_PLUGIN as isize, + MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN as isize, + MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, + MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN as isize, + MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN as isize, + MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN as isize, } /// Defines possible licenses for plugins -// repr: we let this one default #[non_exhaustive] pub enum Maturity { - Unknown = include::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, - Experimental = include::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, - Alpha = include::MariaDB_PLUGIN_MATURITY_ALPHA as isize, - Beta = include::MariaDB_PLUGIN_MATURITY_BETA as isize, - Gamma = include::MariaDB_PLUGIN_MATURITY_GAMMA as isize, - Stable = include::MariaDB_PLUGIN_MATURITY_STABLE as isize, + Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, + Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, + Alpha = bindings::MariaDB_PLUGIN_MATURITY_ALPHA as isize, + Beta = bindings::MariaDB_PLUGIN_MATURITY_BETA as isize, + Gamma = bindings::MariaDB_PLUGIN_MATURITY_GAMMA as isize, + Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize, } -#[repr(transparent)] -pub struct PluginContext(pub(crate) UnsafeCell); +// #[repr(transparent)] +// pub struct PluginContext(pub(crate) UnsafeCell); -impl PluginContext { - pub(crate) unsafe fn from_ptr<'a>(ptr: *mut include::st_plugin_init) -> &'a Self { - &*ptr.cast() - } -} +// impl PluginContext { +// pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::st_plugin_init) -> &'a Self { +// &*ptr.cast() +// } +// } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 1ff02969e3f47..86236f5e94ade 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -24,7 +24,9 @@ use mariadb_server_sys as bindings; #[repr(u32)] #[non_exhaustive] pub enum KeyError { + /// A key ID is invalid or not found. Maps to `ENCRYPTION_KEY_VERSION_INVALID` in C. VersionInvalid, + /// A key buffer is too small. Maps to `ENCRYPTION_KEY_BUFFER_TOO_SMALL` in C. BufferTooSmall, Other, } @@ -32,38 +34,48 @@ pub enum KeyError { /// Implement this trait on a struct that will serve as encryption context /// /// +/// The type of context data that will be passed to various encryption +/// function calls. #[allow(unused_variables)] -pub trait Encryption { - /// The type of context data passed to various functions - type Context: Send; +pub trait Encryption: Send + Sized { + // type Context: Send; /// Get the latest version of a key ID. Return `VersionInvalid` if not found. fn get_latest_key_version(key_id: u32) -> Result; - /// Return a key for a key version + /// Return a key for a key version and ID. /// /// Given a key ID and a version, write the key to the `key` buffer. If the /// buffer is too small, return [`KeyError::BufferTooSmall`]. fn get_key(key_id: u32, key_version: u32, key: &mut [u8]) -> Result; - /// Calculate the length of a key + /// Calculate the length of a key given its ID and version. + /// + /// If the key is not found, return `VersionInvalid`. + /// + /// On the C side, this function is combined with `get_key`. fn get_key_length(key_id: u32, key_version: u32) -> Result; - /// Initialize - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self::Context { + /// Initialize the encryption context object + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self { unimplemented!() } - /// Initialize - fn update(ctx: &mut Self::Context, src: &[u8], dst: &mut [u8]) { + /// Update the encryption context with new data + fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize { unimplemented!() } - fn finish(ctx: &mut Self::Context, dst: &mut [u8]) { + fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize { unimplemented!() } - fn encrypted_length(key_id: u32, key_version: u32, slen: usize) { + /// Return the exact length of the encrypted data based on the source length + /// + /// As this function must have a definitive answer, this API only supports + /// encryption algorithms where this is possible to compute (i.e., + /// compression is not supported). + fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) { unimplemented!() } } From 22b6d1e5def8ea2ef147970b247d6be55649264a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 4 Jan 2023 14:37:32 -0500 Subject: [PATCH 05/34] Progress on demo encryption plugin --- rust/examples/Cargo.toml | 6 +-- rust/examples/src/encryption.rs | 63 ++++++++++++++++----------- rust/mariadb/src/plugin.rs | 4 ++ rust/mariadb/src/plugin/encryption.rs | 33 +++++++++++--- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/rust/examples/Cargo.toml b/rust/examples/Cargo.toml index 44cb540ba01eb..7ed3c4df85b5b 100644 --- a/rust/examples/Cargo.toml +++ b/rust/examples/Cargo.toml @@ -6,6 +6,6 @@ edition = '2021' [dependencies] mariadb-server = { path = "../mariadb" } -ring = "0.16.20" -sha2 = "0.10.6" -rand = "0.8.5" +aes-gcm = "0.10" +sha2 = "0.10" +rand = "0.8" diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs index 2bc3986a31e63..b549ae1d29be0 100644 --- a/rust/examples/src/encryption.rs +++ b/rust/examples/src/encryption.rs @@ -1,10 +1,21 @@ +//! Basic encryption plugin using: +//! +//! - SHA256 as the hasher + #![allow(unused)] use std::time::{Instant, Duration}; use std::sync::Mutex; use rand::{Rng}; +use sha2::{Sha256 as Hasher, Digest}; + +use aes_gcm::{ + aead::{Aead, KeyInit, OsRng}, + Aes256Gcm, Nonce // Or `Aes128Gcm` +}; -use mariadb_server::plugin::encryption::{KeyError, Encryption}; +use mariadb_server::plugin::encryption::{KeyError, Encryption, Flags}; +use mariadb_server::plugin::Init; // use mariadb_server::plugin::Init; // use mariadb_server::plugin::prelude::*; @@ -23,6 +34,7 @@ use mariadb_server::plugin::encryption::{KeyError, Encryption}; const KEY_ROTATION_MIN: f32 = 45.0; const KEY_ROTATION_MAX: f32 = 90.0; const KEY_ROTATION_INTERVAL: f32 = KEY_ROTATION_MAX - KEY_ROTATION_MIN; +const SHA256_SIZE: usize = 32; // const KEY_ROTATION_INTERVAL: Duration = // KEY_ROTATION_MAX - KEY_ROTATION_MIN; @@ -43,7 +55,7 @@ struct KeyVersions { impl KeyVersions { /// Initialize with a new value. Returns the struct - fn new_now() -> (Self, u64) { + fn new_now() -> Self { let now = Instant::now(); let mut ret = Self { start: now, @@ -51,8 +63,7 @@ impl KeyVersions { next: now }; ret.update_next(); - let duration = (ret.next - ret.start).as_secs(); - (ret, duration) + ret } fn update_next(&mut self) { @@ -75,15 +86,14 @@ impl KeyVersions { // Uninitialized -// struct RustEncryptionInit; +struct RustEncryptionInit; -// impl plugin::Init for RustEncryptionInit { -// fn init() -> Self { -// let mut versions = KEY_VERSIONS.lock(); -// *versions = Some(KeyVersions::random_from_now()); -// Self -// } -// } +impl Init for RustEncryptionInit { + fn init() { + let mut guard = KEY_VERSIONS.lock().unwrap(); + *guard = Some(KeyVersions::new_now()); + } +} struct RustEncryption; @@ -91,27 +101,30 @@ struct RustEncryption; impl Encryption for RustEncryption { fn get_latest_key_version(_key_id: u32) -> Result { let mut guard = KEY_VERSIONS.lock().unwrap(); - if let Some(ref mut vers) = *guard { - Ok(vers.update_returning_version() as u32) - } else { - let (kv, ret) = KeyVersions::new_now(); - *guard = Some(kv); - Ok(ret as u32) - } + let mut vers = guard.unwrap(); + Ok(vers.update_returning_version() as u32) } - /// Given a key ID and a - fn get_key(key_id: u32, key_version: u32, key: &mut [u8]) -> Result { - - todo!() + /// Given a key ID and a version, create its hash + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result { + let output_size = Hasher::output_size(); + if dst.len() < output_size { + return Err(KeyError::BufferTooSmall); + } + let mut hasher = Hasher::new(); + hasher.update(key_id.to_ne_bytes()); + hasher.update(key_version.to_ne_bytes()); + dst[..output_size].copy_from_slice(&hasher.finalize()); + Ok(output_size) } fn get_key_length(key_id: u32, key_version: u32) -> Result { - todo!() + // All keys have the same length + Ok(Hasher::output_size()) } /// Initialize - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self { + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self { todo!() } diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 2f2a9c1d5982f..c93ed7bba1f81 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -62,3 +62,7 @@ pub enum Maturity { // &*ptr.cast() // } // } + +pub trait Init { + fn init(); +} diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 86236f5e94ade..b348dab5d6f52 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -31,6 +31,24 @@ pub enum KeyError { Other, } +/// Representation of the flags integer +#[derive(Clone, Copy, PartialEq)] +pub struct Flags(u32); + +impl Flags { + fn should_encrypt(&self) -> bool { + self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT + } + + fn should_decrypt(&self) -> bool { + self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT + } + + pub fn nopad(&self) -> bool { + self.0 & bindings::ENCRYPTION_FLAG_NOPAD + } +} + /// Implement this trait on a struct that will serve as encryption context /// /// @@ -47,25 +65,26 @@ pub trait Encryption: Send + Sized { /// /// Given a key ID and a version, write the key to the `key` buffer. If the /// buffer is too small, return [`KeyError::BufferTooSmall`]. - fn get_key(key_id: u32, key_version: u32, key: &mut [u8]) -> Result; + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result; - /// Calculate the length of a key given its ID and version. - /// - /// If the key is not found, return `VersionInvalid`. + /// Calculate the length of a key given its ID and version. If the key is + /// not found, return `VersionInvalid`. /// /// On the C side, this function is combined with `get_key`. fn get_key_length(key_id: u32, key_version: u32) -> Result; /// Initialize the encryption context object - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: u32) -> Self { + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self { unimplemented!() } - /// Update the encryption context with new data + /// Update the encryption context with new data, return the number of bytes + /// written fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize { unimplemented!() } + /// Write the remaining bytes to the buffer fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize { unimplemented!() } @@ -75,7 +94,7 @@ pub trait Encryption: Send + Sized { /// As this function must have a definitive answer, this API only supports /// encryption algorithms where this is possible to compute (i.e., /// compression is not supported). - fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) { + fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) -> usize { unimplemented!() } } From 353db7a952eac8b24a3685f03c0c4c1ec7c1ab87 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Jan 2023 06:13:58 -0500 Subject: [PATCH 06/34] Basic macro for system variable working --- rust/bindings/Cargo.toml | 3 +- rust/bindings/build.rs | 20 +- rust/bindings/src/hand_imples.rs | 104 +++++++++ rust/bindings/src/lib.rs | 3 + rust/examples/src/debug_key_management.rs | 67 ++++++ rust/examples/src/encryption.rs | 64 ++---- rust/examples/src/lib.rs | 1 + rust/mariadb/Cargo.toml | 1 + rust/mariadb/src/lib.rs | 7 + rust/mariadb/src/plugin.rs | 15 +- rust/mariadb/src/plugin/encryption.rs | 49 ++--- rust/mariadb/src/plugin/encryption_wrapper.rs | 90 ++++++++ rust/mariadb/src/plugin/variables.rs | 203 ++++++++++++++++++ 13 files changed, 537 insertions(+), 90 deletions(-) create mode 100644 rust/bindings/src/hand_imples.rs create mode 100644 rust/examples/src/debug_key_management.rs create mode 100644 rust/mariadb/src/plugin/encryption_wrapper.rs create mode 100644 rust/mariadb/src/plugin/variables.rs diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml index da4aea30e1215..b5c6350e42740 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/bindings/Cargo.toml @@ -5,5 +5,6 @@ edition = '2021' [build-dependencies] -bindgen = "0.61.0" +bindgen = "0.63.0" cmake = "0.1" +doxygen-rs = { git = "https://github.com/Techie-Pi/doxygen-rs.git", rev = "a7fee318" } diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 273c2f2bcff41..669eefbd4a9b6 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -8,8 +8,7 @@ use std::path::PathBuf; use std::process::Command; // `math.h` seems to double define some things, To avoid this, we ignore them. -const IGNORE_MACROS -: [&str; 20] = [ +const IGNORE_MACROS: [&str; 20] = [ "FE_DIVBYZERO", "FE_DOWNWARD", "FE_INEXACT", @@ -33,9 +32,10 @@ const IGNORE_MACROS ]; #[derive(Debug)] -struct IgnoreMacros(HashSet); +struct BuildCallbacks(HashSet); -impl ParseCallbacks for IgnoreMacros { +impl ParseCallbacks for BuildCallbacks { + /// Ignore macros that are in the ignored list fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { if self.0.contains(name) { MacroParsingBehavior::Ignore @@ -43,12 +43,16 @@ impl ParseCallbacks for IgnoreMacros { MacroParsingBehavior::Default } } + + /// Use a converter to turn doxygen comments into rustdoc + fn process_comment(&self, comment: &str) -> Option { + Some(doxygen_rs::transform(comment)) + } } -impl IgnoreMacros { +impl BuildCallbacks { fn new() -> Self { - Self(IGNORE_MACROS - .into_iter().map(|s| s.to_owned()).collect()) + Self(IGNORE_MACROS.into_iter().map(|s| s.to_owned()).collect()) } } @@ -70,7 +74,7 @@ fn main() { // bindings for. .header("src/wrapper.h") // Fix math.h double defines - .parse_callbacks(Box::new(IgnoreMacros::new())) + .parse_callbacks(Box::new(BuildCallbacks::new())) .clang_arg("-I../../include") .clang_arg("-I../../sql") .clang_arg("-xc++") diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs new file mode 100644 index 0000000000000..a071bb9116546 --- /dev/null +++ b/rust/bindings/src/hand_imples.rs @@ -0,0 +1,104 @@ +use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; +use std::ffi::{c_char, c_int, c_uint, c_void}; + +// Defined in service_encryption.h but not imported because of tilde syntax +pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; + +// We hand write these stucts because the definition is tricky, not all fields are +// always present +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_basic { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub value: *mut T, + pub def_val: T, +} + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_const_basic { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub value: *const T, + pub def_val: T, +} + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_simple { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub value: *mut T, + pub def_val: T, + pub min_val: T, + pub max_val: T, + pub blk_sz: T, +} + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_typelib { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub value: *const T, + pub def_val: T, + pub typelib: TYPELIB, +} + +type THDVAR_FUNC = Option *mut T>; + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_thd_basic { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub offset: c_int, + pub def_val: T, + pub resolve: THDVAR_FUNC, +} + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_thd_simple { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub offset: c_int, + pub def_val: T, + pub min_val: T, + pub max_val: T, + pub blk_sz: T, + pub resolve: THDVAR_FUNC, +} + +#[repr(C)] +#[derive(Debug)] +pub struct st_mysql_sys_var_thd_typelib { + pub flags: c_int, + pub name: *const c_char, + pub comment: *const c_char, + pub check: mysql_var_check_func, + pub update: mysql_var_update_func, + pub offset: c_int, + pub def_val: T, + pub resolve: THDVAR_FUNC, + pub typelib: TYPELIB, +} diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs index f26a6cd3b46cb..fa233173b7550 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/bindings/src/lib.rs @@ -7,3 +7,6 @@ // Bindings are autogenerated at build time using build.rs include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +mod hand_imples; +pub use hand_imples::*; diff --git a/rust/examples/src/debug_key_management.rs b/rust/examples/src/debug_key_management.rs new file mode 100644 index 0000000000000..6f2f589df93f1 --- /dev/null +++ b/rust/examples/src/debug_key_management.rs @@ -0,0 +1,67 @@ +//! Debug key management +//! +//! Use to debug the encryption code with a fixed key that changes only on user +//! request. The only valid key ID is 1. +//! +//! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! + +#![allow(unused)] + +use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb_server::plugin::PluginVarInfo; +use mariadb_server::plugin::SysVarAtomic; +use mariadb_server::sysvar_atomic; +use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering; + +const KEY_LENGTH: usize = 4; + +static KEY_VERSION: AtomicU32 = AtomicU32::new(0); + +static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { + ty: u32, + name: "version", + var: KEY_VERSION, + comment: "Latest key version", + flags: [PluginVarInfo::ReqCmdArg], + default: 1, +}; + +struct DebugKeyMgmt; + +impl KeyManager for DebugKeyMgmt { + fn get_latest_key_version(key_id: u32) -> Result { + if key_id != 1 { + Err(KeyError::VersionInvalid) + } else { + Ok(KEY_VERSION.load(Ordering::Relaxed)) + } + } + + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + if key_id != 1 { + return Err(KeyError::VersionInvalid); + } + + // Convert our integer to a native endian byte array + let key_buf = KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes(); + + if dst.len() < key_buf.len() { + return Err(KeyError::BufferTooSmall); + } + + // Copy our slice to the buffer, return the copied length + dst.copy_from_slice(key_buf.as_slice()); + Ok(()) + } + + fn key_length(key_id: u32, key_version: u32) -> Result { + // Return the length of our u32 in bytes + // Just verify our types don't change + debug_assert_eq!( + KEY_LENGTH, + KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes().len() + ); + Ok(KEY_LENGTH) + } +} diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs index b549ae1d29be0..739d8b5a2c11a 100644 --- a/rust/examples/src/encryption.rs +++ b/rust/examples/src/encryption.rs @@ -1,20 +1,21 @@ //! Basic encryption plugin using: -//! +//! //! - SHA256 as the hasher #![allow(unused)] -use std::time::{Instant, Duration}; +use rand::Rng; +use sha2::{Digest, Sha256 as Hasher}; use std::sync::Mutex; -use rand::{Rng}; -use sha2::{Sha256 as Hasher, Digest}; +use std::time::{Duration, Instant}; use aes_gcm::{ aead::{Aead, KeyInit, OsRng}, - Aes256Gcm, Nonce // Or `Aes128Gcm` + Aes256Gcm, + Nonce, // Or `Aes128Gcm` }; -use mariadb_server::plugin::encryption::{KeyError, Encryption, Flags}; +use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb_server::plugin::Init; // use mariadb_server::plugin::Init; // use mariadb_server::plugin::prelude::*; @@ -29,7 +30,6 @@ use mariadb_server::plugin::Init; // stability: EXPERIMENTAL // } - /// Range of key rotations, as seconds const KEY_ROTATION_MIN: f32 = 45.0; const KEY_ROTATION_MAX: f32 = 90.0; @@ -50,17 +50,17 @@ struct KeyVersions { /// Most recent key update time current: Instant, /// Next time for a key update - next: Instant + next: Instant, } impl KeyVersions { - /// Initialize with a new value. Returns the struct + /// Initialize with a new value. Returns the struct fn new_now() -> Self { let now = Instant::now(); let mut ret = Self { start: now, current: now, - next: now + next: now, }; ret.update_next(); ret @@ -83,30 +83,25 @@ impl KeyVersions { } } -// Uninitialized - - -struct RustEncryptionInit; +struct RustEncryption; -impl Init for RustEncryptionInit { +impl Init for RustEncryption { + /// Initialize function: fn init() { let mut guard = KEY_VERSIONS.lock().unwrap(); *guard = Some(KeyVersions::new_now()); } } - -struct RustEncryption; - -impl Encryption for RustEncryption { +impl KeyManager for RustEncryption { fn get_latest_key_version(_key_id: u32) -> Result { let mut guard = KEY_VERSIONS.lock().unwrap(); - let mut vers = guard.unwrap(); + let mut vers = guard.as_mut().unwrap(); Ok(vers.update_returning_version() as u32) } - + /// Given a key ID and a version, create its hash - fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result { + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { let output_size = Hasher::output_size(); if dst.len() < output_size { return Err(KeyError::BufferTooSmall); @@ -115,30 +110,11 @@ impl Encryption for RustEncryption { hasher.update(key_id.to_ne_bytes()); hasher.update(key_version.to_ne_bytes()); dst[..output_size].copy_from_slice(&hasher.finalize()); - Ok(output_size) + Ok(()) } - - fn get_key_length(key_id: u32, key_version: u32) -> Result { + + fn key_length(key_id: u32, key_version: u32) -> Result { // All keys have the same length Ok(Hasher::output_size()) } - - /// Initialize - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self { - todo!() - - } - - /// Initialize - fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize { - todo!() - } - - fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize{ - todo!() - } - - fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) { - todo!() - } } diff --git a/rust/examples/src/lib.rs b/rust/examples/src/lib.rs index e8c7cef1a1ac2..dd0411333475a 100644 --- a/rust/examples/src/lib.rs +++ b/rust/examples/src/lib.rs @@ -1 +1,2 @@ +mod debug_key_management; mod encryption; diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index 3fa1ecd2024aa..83e26679f95f2 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -6,3 +6,4 @@ edition = '2021' [dependencies] mariadb-server-sys = { path = "../bindings" } +cstr = "0.2.11" diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index a38ef65d5ec3e..8042fa52306dc 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -1,3 +1,10 @@ //! Crate representing safe abstractions over MariaDB bindings +#![allow(unused)] pub mod plugin; + +#[doc(hidden)] +pub use mariadb_server_sys as bindings; + +#[doc(hidden)] +pub use cstr; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index c93ed7bba1f81..ad918865c3dd9 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -1,9 +1,12 @@ //! Parent module for all plugin types -use std::cell::UnsafeCell; +// use std::cell::UnsafeCell; use mariadb_server_sys as bindings; pub mod encryption; +mod encryption_wrapper; +mod variables; +pub use variables::{PluginVarInfo, SysVarAtomic}; /// Defines possible licenses for plugins #[non_exhaustive] @@ -54,15 +57,7 @@ pub enum Maturity { Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize, } -// #[repr(transparent)] -// pub struct PluginContext(pub(crate) UnsafeCell); - -// impl PluginContext { -// pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::st_plugin_init) -> &'a Self { -// &*ptr.cast() -// } -// } - +/// Initialize state pub trait Init { fn init(); } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index b348dab5d6f52..af38b26095809 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -17,18 +17,19 @@ //! - `crypt_ctx_finish`: function, wrapped in `Encryption::finish` //! - `encrypted_length`: function, macro provides call to `std::mem::size_of` -use core::cell::UnsafeCell; +// use core::cell::UnsafeCell; use mariadb_server_sys as bindings; /// A type of error to be used by key functions -#[repr(u32)] #[non_exhaustive] +#[repr(u32)] pub enum KeyError { + // Values must be nonzero /// A key ID is invalid or not found. Maps to `ENCRYPTION_KEY_VERSION_INVALID` in C. - VersionInvalid, + VersionInvalid = bindings::ENCRYPTION_KEY_VERSION_INVALID, /// A key buffer is too small. Maps to `ENCRYPTION_KEY_BUFFER_TOO_SMALL` in C. - BufferTooSmall, - Other, + BufferTooSmall = bindings::ENCRYPTION_KEY_BUFFER_TOO_SMALL, + Other = 3, } /// Representation of the flags integer @@ -37,15 +38,15 @@ pub struct Flags(u32); impl Flags { fn should_encrypt(&self) -> bool { - self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT + (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT) != 0 } fn should_decrypt(&self) -> bool { - self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT + (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT) != 0 } pub fn nopad(&self) -> bool { - self.0 & bindings::ENCRYPTION_FLAG_NOPAD + (self.0 & bindings::ENCRYPTION_FLAG_NOPAD) != 0 } } @@ -55,7 +56,7 @@ impl Flags { /// The type of context data that will be passed to various encryption /// function calls. #[allow(unused_variables)] -pub trait Encryption: Send + Sized { +pub trait KeyManager: Send + Sized { // type Context: Send; /// Get the latest version of a key ID. Return `VersionInvalid` if not found. @@ -65,38 +66,32 @@ pub trait Encryption: Send + Sized { /// /// Given a key ID and a version, write the key to the `key` buffer. If the /// buffer is too small, return [`KeyError::BufferTooSmall`]. - fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result; + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError>; - /// Calculate the length of a key given its ID and version. If the key is - /// not found, return `VersionInvalid`. - /// + /// Calculate the length of a key. Usually this is constant, but the key ID + /// and version can be taken into account if needed. + /// /// On the C side, this function is combined with `get_key`. - fn get_key_length(key_id: u32, key_version: u32) -> Result; + fn key_length(key_id: u32, key_version: u32) -> Result; +} +pub trait Encryption { /// Initialize the encryption context object - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self { - unimplemented!() - } + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self; /// Update the encryption context with new data, return the number of bytes /// written - fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize { - unimplemented!() - } + fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize; /// Write the remaining bytes to the buffer - fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize { - unimplemented!() - } + fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize; /// Return the exact length of the encrypted data based on the source length - /// + /// /// As this function must have a definitive answer, this API only supports /// encryption algorithms where this is possible to compute (i.e., /// compression is not supported). - fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) -> usize { - unimplemented!() - } + fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) -> usize; } // get_latest_key_version: #type::get_latest_key_version diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs new file mode 100644 index 0000000000000..476e9d1b98466 --- /dev/null +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -0,0 +1,90 @@ +//! Wrappers needed for the `st_mariadb_encryption` type + +use mariadb_server_sys as bindings; +use std::{ + ffi::{c_int, c_uchar, c_uint, c_void}, + slice, +}; + +use super::encryption::{KeyError, KeyManager}; + +/// Get the key version, simple wrapper +pub unsafe extern "C" fn wrap_get_latest_key_version(key_id: c_uint) -> c_uint { + match T::get_latest_key_version(key_id) { + Ok(v) => v, + Err(_) => KeyError::VersionInvalid as c_uint, + } +} + +/// If key == NULL, return the required buffer size for the key +/// +/// +pub unsafe extern "C" fn wrap_get_key( + key_id: c_uint, + version: c_uint, + dstbuf: *mut c_uchar, + buflen: *mut c_uint, +) -> c_uint { + if dstbuf.is_null() { + match T::key_length(key_id, version) { + // FIXME: don't unwrap + Ok(v) => *buflen = v.try_into().unwrap(), + Err(e) => { + return e as c_uint; + } + } + return bindings::ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } + + // SAFETY: caller guarantees validity + let buf = slice::from_raw_parts_mut(dstbuf, buflen as usize); + + // If successful, return 0. If an error occurs, return it + match T::get_key(key_id, version, buf) { + Ok(_) => 0, + Err(e) => e as c_uint, + } +} + +pub unsafe extern "C" fn wrap_crypt_ctx_size(key_id: c_uint, key_version: c_uint) -> c_uint { + todo!() +} + +pub unsafe extern "C" fn wrap_crypt_ctx_init( + ctx: *mut c_void, + key: *const c_uchar, + klen: c_uint, + iv: *const c_uchar, + ivlen: c_uint, + flags: c_int, + key_id: c_uint, + key_version: c_uint, +) -> c_int { + todo!() +} + +pub unsafe extern "C" fn wrap_crypt_ctx_update( + ctx: *mut c_void, + src: *const c_uchar, + slen: c_uint, + dst: *mut c_uchar, + dlen: *mut c_uint, +) -> c_int { + todo!() +} + +pub unsafe extern "C" fn wrap_crypt_ctx_finish( + ctx: *mut c_void, + dst: *mut c_uchar, + dlen: *mut c_uint, +) -> c_int { + todo!() +} + +pub unsafe extern "C" fn wrap_encrypted_length( + slen: c_uint, + key_id: c_uint, + key_version: c_uint, +) -> c_uint { + todo!() +} diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs new file mode 100644 index 0000000000000..afcd00898fbdc --- /dev/null +++ b/rust/mariadb/src/plugin/variables.rs @@ -0,0 +1,203 @@ +use bindings::st_mysql_sys_var_basic; +use mariadb_server_sys as bindings; +use std::mem::ManuallyDrop; + +#[repr(C)] +union SysVar { + basic: ManuallyDrop>, + basic_const: ManuallyDrop>, + simple: ManuallyDrop>, + typelib: ManuallyDrop>, + thdvar_basic: ManuallyDrop>, + thdvar_simple: ManuallyDrop>, + thdvar_typelib: ManuallyDrop>, +} + +#[repr(transparent)] +pub struct SysVarAtomic(SysVar); + +#[doc(hidden)] +impl SysVarAtomic { + // These functions are all unsafe because preconditions are needed to ensure + // Send and Sync hold true + pub const unsafe fn new_basic(val: bindings::st_mysql_sys_var_basic) -> Self { + Self(SysVar { + basic: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_basic_const(val: bindings::st_mysql_sys_var_const_basic) -> Self { + Self(SysVar { + basic_const: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_simple(val: bindings::st_mysql_sys_var_simple) -> Self { + Self(SysVar { + simple: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_typelib(val: bindings::st_mysql_sys_var_typelib) -> Self { + Self(SysVar { + typelib: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_thdvar_basic(val: bindings::st_mysql_sys_var_thd_basic) -> Self { + Self(SysVar { + thdvar_basic: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_thdvar_simple(val: bindings::st_mysql_sys_var_thd_simple) -> Self { + Self(SysVar { + thdvar_simple: ManuallyDrop::new(val), + }) + } + pub const unsafe fn new_thdvar_typelib(val: bindings::st_mysql_sys_var_thd_typelib) -> Self { + Self(SysVar { + thdvar_typelib: ManuallyDrop::new(val), + }) + } +} + +// SAFETY: totally reliant on the server's calls here +unsafe impl Send for SysVarAtomic {} +unsafe impl Sync for SysVarAtomic {} + +/// Create a system variable from an atomic +/// +/// Supported types are currently `AtomicBool`, `AtomicI32`, `AtomicI64`, and +/// their unsigned versions. +#[macro_export] +macro_rules! sysvar_atomic { + // Match the types manually + // (@var_type bool) => {bindings::PLUGIN_VAR_BOOL}; + (@var_type i32) => {bindings::PLUGIN_VAR_INT}; + (@var_type u32) => {bindings::PLUGIN_VAR_INT | bindings::PLUGIN_VAR_UNSIGNED}; + (@var_type i64) => {bindings::PLUGIN_VAR_LONGLONG}; + (@var_type u64) => {bindings::PLUGIN_VAR_LONGLONG | bindings::PLUGIN_VAR_UNSIGNED}; + (@def $default:expr, ) => {$default}; + (@def $default:expr, $replace:expr) => {$replace}; + + ( + ty: $ty:tt, + name: $name:expr, + var: $var:ident + $(, comment: $comment:expr)? + $(, flags: [$($flag:expr),+ $(,)?])? + $(, default: $default:expr)? + $(, minimum: $minimum:expr)? + $(, maximum: $maximum:expr)? + $(, multiplier: $multiplier:expr)? + $(,)? // trailing comma + ) => { + { + use $crate::bindings; + use $crate::cstr; + use ::std::ffi::{c_void, c_int, c_char}; + use ::std::ptr; + use ::std::mem::ManuallyDrop; + + // Just make syntax cleaner + type IntTy = $ty; + + unsafe extern "C" fn check_val( + _thd: *mut bindings::THD, + self_: *mut bindings::st_mysql_sys_var, + save: *mut c_void, + _mysqld_values: *mut bindings::st_mysql_value + ) -> c_int { + // SAFETY: caller ensures save points to a valid location of the correct type + *save.cast() = $var.load(::std::sync::atomic::Ordering::Relaxed); + 0 + } + + unsafe extern "C" fn update_val( + _thd: *mut bindings::THD, + self_: *mut bindings::st_mysql_sys_var, + var_ptr: *mut c_void, + save: *const c_void + ) { + // SAFETY: `safe` points to a caller-validated value, `var_ptr` is properly sized + *var_ptr.cast() = $var.swap(*save.cast(), ::std::sync::atomic::Ordering::Relaxed); + } + + // Defaults + const FLAGS: i32 = sysvar_atomic!(@var_type $ty) as i32 $( $(| ($flag as i32))+ )?; + + const REF_STRUCT: bindings::st_mysql_sys_var_simple<$ty> = + bindings::st_mysql_sys_var_simple::<$ty> { + flags: FLAGS, + name: cstr::cstr!($name).as_ptr(), + comment: sysvar_atomic!( + @def + ptr::null(), + $(cstr::cstr!($comment).as_ptr())? + ), + check: Some(check_val), + update: Some(update_val), + value: ptr::null_mut(), + def_val: sysvar_atomic!(@def 0, $($default)?), + min_val: sysvar_atomic!(@def IntTy::MIN, $($minimum)?), + max_val: sysvar_atomic!(@def IntTy::MAX, $($maximum)?), + blk_sz: sysvar_atomic!(@def 1, $($multiplier)?), + }; + + unsafe { SysVarAtomic::new_simple(REF_STRUCT) } + } + + }; +} + +#[non_exhaustive] +#[repr(i32)] +pub enum PluginVarInfo { + ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, + /// Variable is read only + ReadOnly = bindings::PLUGIN_VAR_READONLY as i32, + /// Variable is not a server variable + NoSysVar = bindings::PLUGIN_VAR_NOSYSVAR as i32, + /// No command line option + NoCmdOpt = bindings::PLUGIN_VAR_NOCMDOPT as i32, + /// No argument for the command line + NoCmdArg = bindings::PLUGIN_VAR_NOCMDARG as i32, + /// Required CLI argument + ReqCmdArg = bindings::PLUGIN_VAR_RQCMDARG as i32, + /// Optional CLI argument + OptCmdArd = bindings::PLUGIN_VAR_OPCMDARG as i32, + /// Variable is deprecated + Deprecated = bindings::PLUGIN_VAR_DEPRECATED as i32, + // String needs memory allocation + // MemAlloc= bindings::PLUGIN_VAR_MEMALLOC, +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::c_uint; + use std::mem::size_of; + use std::sync::atomic::AtomicU32; + + #[test] + fn test_sizes() { + assert_eq!(size_of::(), size_of::()) + } + + #[test] + fn test_macro() { + static X: AtomicU32 = AtomicU32::new(0); + let x = sysvar_atomic! { + ty: u32, + name: "sql_name", + var: X, + }; + // dbg!(x); + let x = sysvar_atomic! { + ty: u32, + name: "sql_name", + var: X, + comment: "this is a comment", + flags: [PluginVarInfo::ReadOnly], + maximum: 40, + multiplier: 2 + }; + // dbg!(x); + } +} From ea06c90fff603c029c2780b1b413ce89dc92a6cc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 5 Jan 2023 06:59:11 -0500 Subject: [PATCH 07/34] Work on plugin! macro --- rust/mariadb/Cargo.toml | 1 + rust/mariadb/src/plugin.rs | 62 ++++++++++++++++++++++++++++ rust/mariadb/src/plugin/variables.rs | 7 ++-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index 83e26679f95f2..701062cba8cca 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -7,3 +7,4 @@ edition = '2021' [dependencies] mariadb-server-sys = { path = "../bindings" } cstr = "0.2.11" +concat-idents = "1.1.4" diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index ad918865c3dd9..98fdf0f132e7d 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -61,3 +61,65 @@ pub enum Maturity { pub trait Init { fn init(); } + +#[macro_export] +macro_rules! plugin { + (@def $default:expr, ) => {$default}; + (@def $default:expr, $replace:expr) => {$replace}; + + ( + ty: $type:expr, + name: $name:expr, + author: $author:expr, + description: $description:expr, + license: $license:expr, + version: $version:expr, + $(, version_info: $version_info:expr)? + maturity: $maturity:expr, + // Type to use init/deinit functions on, if applicable + $(, init: $init:ty)? + $(,)? // trailing comma + + ) => { + use std::ffi::{c_int, c_uint}; + use $crate::cstr; + + // Use these intermediates to validate types + const ptype: PluginType = $type; + const ltype: License = $license + const vers: u32 = $version; + const maturity: Maturity = $maturity; + + bindings::st_maria_plugin { + type_: ptype as c_int, + info: *mut c_void, + name: cstr::cstr!($name).as_ptr(), + author: cstr::cstr!($author).as_ptr(), + descr: cstr::cstr!($description).as_ptr(), + license: c_int, + init: Option c_int>, + deinit: Option c_int>, + version: vers as c_uint, + status_vars: *mut st_mysql_show_var, + system_vars: *mut *mut st_mysql_sys_var, + version_info: plugin!( + @def + cstr::cstr!($vers).as_ptr(), + $(cstr::cstr!($comment).as_ptr())? + ),, + maturity: maturity as c_uint, + } + + }; +} + + +// plugin_encryption!{ +// type: RustEncryption, +// init: RustEncryptionInit, // optional +// name: "example_key_management", +// author: "MariaDB Team", +// description: "Example key management plugin using AES", +// license: GPL, +// stability: EXPERIMENTAL +// } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index afcd00898fbdc..22cac4e0e3886 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -89,11 +89,12 @@ macro_rules! sysvar_atomic { $(,)? // trailing comma ) => { { - use $crate::bindings; - use $crate::cstr; use ::std::ffi::{c_void, c_int, c_char}; - use ::std::ptr; use ::std::mem::ManuallyDrop; + use ::std::ptr; + + use $crate::bindings; + use $crate::cstr; // Just make syntax cleaner type IntTy = $ty; From a05fbb9352ec4322fc25b4c85ed6cba341ae9ec8 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 6 Jan 2023 06:28:07 -0500 Subject: [PATCH 08/34] Basic plugin compiles (untested) --- .dockerignore | 629 ++++++++++++++++++++++ rust/Dockerfile | 34 ++ rust/bindings/Cargo.toml | 2 +- rust/examples/Cargo.toml | 4 +- rust/examples/src/debug_key_management.rs | 105 +++- rust/macros/Cargo.toml | 11 + rust/macros/src/lib.rs | 15 + rust/macros/src/plugin.rs | 47 ++ rust/mariadb/Cargo.toml | 1 + rust/mariadb/src/lib.rs | 3 + rust/mariadb/src/plugin.rs | 108 ++-- rust/mariadb/src/plugin/variables.rs | 11 +- 12 files changed, 916 insertions(+), 54 deletions(-) create mode 100644 .dockerignore create mode 100644 rust/Dockerfile create mode 100644 rust/macros/src/plugin.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000..5a23a1addcc55 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,629 @@ +*-t +*.ctest +*.reject +*.spec +*.bak +*.dgcov +*.rpm +.*.swp +*.ninja +.ninja_* +*.mri +*.mri.tpl +/.cproject +/.project +.gdb_history +.vs/ +/.settings/ +errmsg.sys +typescript +_CPack_Packages +CMakeCache.txt +CMakeFiles/ +MakeFile +install_manifest*.txt +CPackConfig.cmake +CPackSourceConfig.cmake +CTestTestfile.cmake +Docs/INFO_BIN +Docs/INFO_SRC +Makefile +TAGS +Testing/ +tmp/ +VERSION.dep +configure +client/async_example +client/mysql +client/mysql_plugin +client/mysql_upgrade +client/mysqladmin +client/mysqlbinlog +client/mysqlcheck +client/mysqldump +client/mysqlimport +client/mysqlshow +client/mysqlslap +client/mysqltest +client/mariadb-conv +cmake_install.cmake +dbug/*.r +dbug/factorial +dbug/tests +dbug/user.ps +dbug/user.t +extra/comp_err +extra/innochecksum +extra/jemalloc/build/ +extra/jemalloc/tmp/ +extra/mariabackup/mariabackup +extra/mariabackup/mbstream +extra/my_print_defaults +extra/mysql_waitpid +extra/mysqld_safe_helper +extra/perror +extra/replace +extra/resolve_stack_dump +extra/resolveip +extra/wolfssl/user_settings.h +import_executables.cmake +include/*.h.tmp +include/config.h +include/my_config.h +include/mysql_version.h +include/mysqld_ername.h +include/mysqld_error.h +include/sql_state.h +include/probes_mysql.d +include/probes_mysql_dtrace.h +include/probes_mysql_nodtrace.h +include/source_revision.h +info_macros.cmake +libmysql*/libmysql*_exports_file.cc +libmysql*/merge_archives_mysql*.cmake +libmysql*/mysql*_depends.c +libmysql/libmysql_versions.ld +libmysqld/examples/mysql_client_test_embedded +libmysqld/examples/mysql_embedded +libmysqld/examples/mysqltest_embedded +make_dist.cmake +mariadb-*.*.*.tar.gz +mariadb-*.*.*/ +mysql-test/lib/My/SafeProcess/my_safe_process +mysql-test/lib/My/SafeProcess/wsrep_check_version +mysql-test/mtr +mysql-test/mysql-test-run +mysql-test/mariadb-test-run +mysql-test/mysql-stress-test.pl +mysql-test/mysql-test-run.pl +mysql-test/var* +mysql-test-gcov.err +mysql-test-gcov.msg +mysys/test_hash +mysys/thr_lock +mysys/thr_timer +packaging/rpm-oel/mysql.spec +packaging/rpm-uln/mysql.10.0.11.spec +packaging/solaris/postinstall-solaris +extra/pcre2 +extra/libfmt +plugin/auth_pam/auth_pam_tool +plugin/auth_pam/config_auth_pam.h +plugin/aws_key_management/aws-sdk-cpp +plugin/aws_key_management/aws_sdk_cpp +plugin/aws_key_management/aws_sdk_cpp-prefix +scripts/comp_sql +scripts/make_binary_distribution +scripts/msql2mysql +scripts/mysql_config +scripts/mysql_config.pl +scripts/mysql_convert_table_format +scripts/mysql_find_rows +scripts/mysql_fix_extensions +scripts/mysql_fix_privilege_tables.sql +scripts/mysql_fix_privilege_tables_sql.c +scripts/mysql_install_db +scripts/mysql_secure_installation +scripts/mysql_setpermission +scripts/mysql_zap +scripts/mysqlaccess +scripts/mysqlbug +scripts/mysqld_multi +scripts/mysqld_safe +scripts/mysqldumpslow +scripts/mysqlhotcopy +scripts/mytop +scripts/wsrep_sst_backup +scripts/wsrep_sst_common +scripts/wsrep_sst_mysqldump +scripts/wsrep_sst_rsync +scripts/wsrep_sst_rsync_wan +scripts/wsrep_sst_mariabackup +scripts/wsrep_sst_xtrabackup +scripts/wsrep_sst_xtrabackup-v2 +scripts/maria_add_gis_sp.sql +scripts/maria_add_gis_sp_bootstrap.sql +scripts/galera_new_cluster +scripts/galera_recovery +scripts/mysql_convert_table_format.pl +scripts/mysql_sys_schema.sql +scripts/mysqld_multi.pl +scripts/mysqldumpslow.pl +scripts/mysqlhotcopy.pl +sql-bench/bench-count-distinct +sql-bench/bench-init.pl +sql-bench/compare-results +sql-bench/copy-db +sql-bench/crash-me +sql-bench/graph-compare-results +sql-bench/innotest1 +sql-bench/innotest1a +sql-bench/innotest1b +sql-bench/innotest2 +sql-bench/innotest2a +sql-bench/innotest2b +sql-bench/run-all-tests +sql-bench/server-cfg +sql-bench/test-ATIS +sql-bench/test-alter-table +sql-bench/test-big-tables +sql-bench/test-connect +sql-bench/test-create +sql-bench/test-insert +sql-bench/test-select +sql-bench/test-table-elimination +sql-bench/test-transactions +sql-bench/test-wisconsin +sql-bench/bench-count-distinct.pl +sql-bench/compare-results.pl +sql-bench/copy-db.pl +sql-bench/crash-me.pl +sql-bench/graph-compare-results.pl +sql-bench/innotest1.pl +sql-bench/innotest1a.pl +sql-bench/innotest1b.pl +sql-bench/innotest2.pl +sql-bench/innotest2a.pl +sql-bench/innotest2b.pl +sql-bench/run-all-tests.pl +sql-bench/server-cfg.pl +sql-bench/test-ATIS.pl +sql-bench/test-alter-table.pl +sql-bench/test-big-tables.pl +sql-bench/test-connect.pl +sql-bench/test-create.pl +sql-bench/test-insert.pl +sql-bench/test-select.pl +sql-bench/test-table-elimination.pl +sql-bench/test-transactions.pl +sql-bench/test-wisconsin.pl +sql/make_mysqld_lib.cmake +sql/lex_token.h +sql/gen_lex_token +sql/gen_lex_hash +sql/lex_hash.h +sql/myskel.m4 +sql/mysql_tzinfo_to_sql +sql/mysqld +sql/sql_builtin.cc +sql/yy_mariadb.cc +sql/yy_mariadb.hh +sql/yy_mariadb.yy +sql/yy_oracle.cc +sql/yy_oracle.hh +sql/yy_oracle.yy +storage/heap/hp_test1 +storage/heap/hp_test2 +storage/maria/aria_chk +storage/maria/aria_dump_log +storage/maria/aria_ftdump +storage/maria/aria_pack +storage/maria/aria_read_log +storage/maria/aria_s3_copy +storage/maria/ma_rt_test +storage/maria/ma_sp_test +storage/maria/ma_test1 +storage/maria/ma_test2 +storage/maria/ma_test3 +storage/maria/test_ma_backup +storage/myisam/mi_test1 +storage/myisam/mi_test2 +storage/myisam/mi_test3 +storage/myisam/myisam_ftdump +storage/myisam/myisamchk +storage/myisam/myisamlog +storage/myisam/myisampack +storage/myisam/rt_test +storage/myisam/sp_test +storage/perfschema/pfs_config.h +storage/rocksdb/ldb +storage/rocksdb/myrocks_hotbackup +storage/rocksdb/mysql_ldb +storage/rocksdb/rdb_source_revision.h +storage/rocksdb/sst_dump +strings/conf_to_src +support-files/MySQL-shared-compat.spec +support-files/binary-configure +support-files/config.huge.ini +support-files/config.medium.ini +support-files/config.small.ini +support-files/mariadb.pc +support-files/mariadb.pp +support-files/mariadb.service +support-files/mariadb.socket +support-files/mariadb-extra.socket +support-files/mariadb@.service +support-files/mariadb@.socket +support-files/mariadb-extra@.socket +support-files/mini-benchmark +support-files/my-huge.cnf +support-files/my-innodb-heavy-4G.cnf +support-files/my-large.cnf +support-files/my-medium.cnf +support-files/my-small.cnf +support-files/mariadb.logrotate +support-files/mysql.10.0.11.spec +support-files/mysql.server +support-files/mysql.service +support-files/mysql.spec +support-files/mysqld.service +support-files/mysqld_multi.server +support-files/policy/selinux/mysqld-safe.pp +support-files/sysusers.conf +support-files/tmpfiles.conf +support-files/wsrep.cnf +support-files/wsrep_notify +tags +tests/async_queries +tests/bug25714 +tests/mysql_client_test +storage/mroonga/config.sh +storage/mroonga/mrn_version.h +storage/mroonga/data/install.sql +storage/mroonga/vendor/groonga/config.h +storage/mroonga/vendor/groonga/config.sh +storage/mroonga/vendor/groonga/groonga.pc +storage/mroonga/vendor/groonga/src/grnslap +storage/mroonga/vendor/groonga/src/groonga +storage/mroonga/vendor/groonga/src/groonga-benchmark +storage/mroonga/vendor/groonga/src/suggest/groonga-suggest-create-dataset +storage/mroonga/mysql-test/mroonga/storage/r/information_schema_plugins.result +storage/mroonga/mysql-test/mroonga/storage/r/variable_version.result +zlib/zconf.h +xxx/* +yyy/* +zzz/* + +# C and C++ + +# Compiled Object files +*.slo +*.o +*.ko +*.obj +*.elf +*.exp +*.dep +*.idb +*.res +*.tlog + +# Precompiled Headers +*.gch +*.pch + +# Compiled Static libraries +*.lib +*.a +*.la +*.lai +*.lo + +# Compiled Dynamic libraries +*.so +*.so.* +*.dylib +*.dll + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +*.sln + +*.vcproj +*.vcproj.* +*.vcproj.*.* +*.vcproj.*.*.* +*.vcxproj +*.vcxproj.* +*.vcxproj.*.* +*.vcxproj.*.*.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +/cmake-build-debug/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +# sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +*.stackdump + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# macOS garbage +.DS_Store + +# QtCreator && CodeBlocks +*.cbp + +compile_commands.json +.clang-format +.kscope/ +.vimrc +.editorconfig +.kateconfig +*.kdev4 + +# Visual Studio Code workspace +.vscode/ + +# Clion && other JetBrains ides +/.idea/ + +.cache/clangd + + +client/mariadb +client/mariadb-admin +client/mariadb-binlog +client/mariadb-check +client/mariadb-dump +client/mariadb-import +client/mariadb-plugin +client/mariadb-show +client/mariadb-slap +client/mariadb-test +client/mariadb-upgrade +extra/mariabackup/mariadb-backup +extra/mariadbd-safe-helper +extra/mariadb-waitpid +libmysqld/examples/mariadb-client-test-embedded +libmysqld/examples/mariadb-embedded +libmysqld/examples/mariadb-test-embedded +man/mariadb.1 +man/mariadb-access.1 +man/mariadb-admin.1 +man/mariadb-backup.1 +man/mariadb-binlog.1 +man/mariadb-check.1 +man/mariadb-client-test.1 +man/mariadb-client-test-embedded.1 +man/mariadb_config.1 +man/mariadb-convert-table-format.1 +man/mariadbd.8 +man/mariadbd-multi.1 +man/mariadbd-safe.1 +man/mariadbd-safe-helper.1 +man/mariadb-dump.1 +man/mariadb-dumpslow.1 +man/mariadb-embedded.1 +man/mariadb-find-rows.1 +man/mariadb-fix-extensions.1 +man/mariadb-hotcopy.1 +man/mariadb-import.1 +man/mariadb-install-db.1 +man/mariadb-ldb.1 +man/mariadb-plugin.1 +man/mariadb-secure-installation.1 +man/mariadb-setpermission.1 +man/mariadb-show.1 +man/mariadb-slap.1 +man/mariadb-test.1 +man/mariadb-test-embedded.1 +man/mariadb-tzinfo-to-sql.1 +man/mariadb-upgrade.1 +man/mariadb-waitpid.1 +scripts/mariadb-access +scripts/mariadb-convert-table-format +scripts/mariadbd-multi +scripts/mariadbd-safe +scripts/mariadb-dumpslow +scripts/mariadb-find-rows +scripts/mariadb-fix-extensions +scripts/mariadb-hotcopy +scripts/mariadb-install-db +scripts/mariadb-secure-installation +scripts/mariadb-setpermission +sql/mariadbd +sql/mariadb-tzinfo-to-sql +storage/rocksdb/mariadb-ldb +strings/ctype-uca1400data.h +strings/uca-dump +tests/mariadb-client-test +versioninfo_dll.rc +versioninfo_exe.rc +win/packaging/ca/symlinks.cc + +# rust output +**/target/ +**/.git + diff --git a/rust/Dockerfile b/rust/Dockerfile new file mode 100644 index 0000000000000..6ed9f08a783a8 --- /dev/null +++ b/rust/Dockerfile @@ -0,0 +1,34 @@ +# Quick test for our example plugins, build against the current repo but runs +# with the published 10.11 image +# +# ``` +# # Build the image. Change the directory (../) if not building in `rust/` +# docker build -f Dockerfile ../ --tag mdb-plugin-ex +# +# # Run the container +# docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c mdb-plugin-ex +# +# # Enter a SQL console +# docker exec -it mdb-plugin-ex-c mysql -pexample +# ``` + +FROM rust:latest AS build + +WORKDIR /build + +RUN apt-get update \ + && apt-get install -y cmake clang bison + +COPY . . + +WORKDIR /build/rust + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/build/target \ + cargo build --release -p examples \ + && mkdir /output \ + && cp target/release/*.so /output + +FROM mariadb:10.11-rc + +COPY --from=build /output/* /usr/lib/mysql/plugin/ diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml index b5c6350e42740..4dfdb20db0dc9 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/bindings/Cargo.toml @@ -7,4 +7,4 @@ edition = '2021' [build-dependencies] bindgen = "0.63.0" cmake = "0.1" -doxygen-rs = { git = "https://github.com/Techie-Pi/doxygen-rs.git", rev = "a7fee318" } +doxygen-rs = "0.2" diff --git a/rust/examples/Cargo.toml b/rust/examples/Cargo.toml index 7ed3c4df85b5b..5e309207a7d5a 100644 --- a/rust/examples/Cargo.toml +++ b/rust/examples/Cargo.toml @@ -1,8 +1,10 @@ [package] -name = "mariadb-examples" +name = "examples" version = "0.1.0" edition = '2021' +[lib] +crate-type = ["cdylib"] [dependencies] mariadb-server = { path = "../mariadb" } diff --git a/rust/examples/src/debug_key_management.rs b/rust/examples/src/debug_key_management.rs index 6f2f589df93f1..a84afa40f6cf5 100644 --- a/rust/examples/src/debug_key_management.rs +++ b/rust/examples/src/debug_key_management.rs @@ -8,9 +8,11 @@ #![allow(unused)] use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::PluginVarInfo; -use mariadb_server::plugin::SysVarAtomic; +use mariadb_server::plugin::{SysVarAtomic, License, Maturity}; +use mariadb_server::plugin::{PluginType, PluginVarInfo}; use mariadb_server::sysvar_atomic; +use std::cell::UnsafeCell; +use std::ffi::c_void; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering; @@ -65,3 +67,102 @@ impl KeyManager for DebugKeyMgmt { Ok(KEY_LENGTH) } } + +// bindings::st_maria_plugin { +// type_: PluginType::Encryption, +// info: *mut c_void, +// name: cstr::cstr!($name).as_ptr(), +// author: cstr::cstr!($author).as_ptr(), +// descr: cstr::cstr!($description).as_ptr(), +// license: c_int, +// init: Option c_int>, +// deinit: Option c_int>, +// version: vers as c_uint, +// status_vars: *mut st_mysql_show_var, +// system_vars: *mut *mut st_mysql_sys_var, +// version_info: plugin!( +// @def +// cstr::cstr!($vers).as_ptr(), +// $(cstr::cstr!($comment).as_ptr())? +// ),, +// maturity: maturity as c_uint, +// } + +// #![crate_type = "cdylib"] + +// builtin_debug_key_plugin_interface_version +// builtin_debug_key_sizeof_struct_st_plugin +// builtin_debug_key_plugin + +#[repr(transparent)] +struct UcWrap(UnsafeCell); + +impl UcWrap { + const fn as_ptr(&self) -> *const T { + self.0.get() + } +} + +unsafe impl Send for UcWrap {} +unsafe impl Sync for UcWrap {} + +// PROC: should mangle names with type name + +static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb_server::bindings::st_mysql_sys_var; 2]> = UcWrap(UnsafeCell::new([ + KEY_VERSION_SYSVAR.as_ptr().cast_mut(), + ::std::ptr::null_mut() +])); + +static _ENCRYPTION_ST: UcWrap = UcWrap(UnsafeCell::new(mariadb_server::bindings::st_mariadb_encryption { + interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, + get_latest_key_version: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::), + get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), + crypt_ctx_size: None, + crypt_ctx_init: None, + crypt_ctx_update: None, + crypt_ctx_finish: None, + encrypted_length: None, +})); + +#[no_mangle] +static _maria_plugin_interface_version_: ::std::ffi::c_int = + mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; + +#[no_mangle] +static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = + ::std::mem::size_of::() as ::std::ffi::c_int; + +#[no_mangle] +static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ + mariadb_server::bindings::st_maria_plugin { + type_: PluginType::MariaEncryption as i32, + info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), + name: mariadb_server::cstr::cstr!("debug_key_management").as_ptr(), + author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), + descr: mariadb_server::cstr::cstr!("Debug key management plugin").as_ptr(), + license: License::Gpl as i32, + init: None, + deinit: None, + version: 0x0010, + status_vars: ::std::ptr::null_mut(), + system_vars: _INTERNAL_SYSVARS.0.get().cast(), + version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), + maturity: Maturity::Experimental as u32, + }, + // End with a null object + mariadb_server::bindings::st_maria_plugin { + type_: 0, + info: ::std::ptr::null_mut(), + name: ::std::ptr::null(), + author: ::std::ptr::null(), + descr: ::std::ptr::null(), + license: 0, + init: None, + deinit: None, + version: 0, + status_vars: ::std::ptr::null_mut(), + system_vars: ::std::ptr::null_mut(), + version_info: ::std::ptr::null(), + maturity: 0, + }, +]; diff --git a/rust/macros/Cargo.toml b/rust/macros/Cargo.toml index 1a91635217dfe..b3257610fa15a 100644 --- a/rust/macros/Cargo.toml +++ b/rust/macros/Cargo.toml @@ -6,3 +6,14 @@ edition = "2021" [lib] proc-macro = true + +[dependencies] +# heck = "0.4.0" +# lazy_static = "1.4.0" +proc-macro2 = "1.0.49" +quote = "1.0.23" +syn = { version = "1.0.107", features = ["full", "extra-traits", "parsing"] } + +[dev-dependencies] +# trybuild = { version = "1.0.65", features = ["diff"] } +mariadb-server = { path = "../mariadb" } diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index 8b137891791fe..af014a2960e76 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -1 +1,16 @@ +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::str_to_string)] +#![warn(clippy::missing_inline_in_public_items)] +#![allow(clippy::missing_const_for_fn)] +#![allow(clippy::missing_panics_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::cast_possible_truncation)] +mod plugin; +use proc_macro::TokenStream; + +#[proc_macro] +pub fn plugin(item: TokenStream) -> TokenStream { + plugin::entry(item) +} diff --git a/rust/macros/src/plugin.rs b/rust/macros/src/plugin.rs new file mode 100644 index 0000000000000..bdcd6826089b0 --- /dev/null +++ b/rust/macros/src/plugin.rs @@ -0,0 +1,47 @@ +//! Proc macro plugin! to register a plugin +//! +//! Desired usage: +//! ``` +//! plugin_encryption!{ +//! type: RustEncryption, +//! init: RustEncryptionInit, // optional +//! keymgr: RustEncryptionInit, // optional +//! encryption: RustEncryptionInit, // optional +//! name: "example_key_management", +//! author: "MariaDB Team", +//! description: "Example key management plugin using AES", +//! license: GPL, +//! stability: EXPERIMENTAL +//! } +//! ``` +//! +//! Desired output: +//! +//! ``` +//! bindings::st_maria_plugin { +//! type_: ptype as c_int, +//! info: *mut c_void, +//! name: cstr::cstr!($name).as_ptr(), +//! author: cstr::cstr!($author).as_ptr(), +//! descr: cstr::cstr!($description).as_ptr(), +//! license: c_int, +//! init: Option c_int>, +//! deinit: Option c_int>, +//! version: vers as c_uint, +//! status_vars: *mut st_mysql_show_var, +//! system_vars: *mut *mut st_mysql_sys_var, +//! version_info: plugin!( +//! @def +//! cstr::cstr!($vers).as_ptr(), +//! $(cstr::cstr!($comment).as_ptr())? +//! ),, +//! maturity: maturity as c_uint, +//! } +//! ``` + +use proc_macro::TokenStream; + +pub fn entry(item: TokenStream) -> TokenStream { + dbg!(&item); + item +} diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index 701062cba8cca..b6e56321aed1b 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -6,5 +6,6 @@ edition = '2021' [dependencies] mariadb-server-sys = { path = "../bindings" } +mariadb-server-macros = { path = "../macros" } cstr = "0.2.11" concat-idents = "1.1.4" diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 8042fa52306dc..8cd6765506a14 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -8,3 +8,6 @@ pub use mariadb_server_sys as bindings; #[doc(hidden)] pub use cstr; + +#[doc(inline)] +pub use mariadb_server_macros::plugin; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 98fdf0f132e7d..f35baac789609 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -2,9 +2,12 @@ // use std::cell::UnsafeCell; +use std::ffi::c_void; + use mariadb_server_sys as bindings; pub mod encryption; -mod encryption_wrapper; +#[doc(hidden)] +pub mod encryption_wrapper; mod variables; pub use variables::{PluginVarInfo, SysVarAtomic}; @@ -62,57 +65,64 @@ pub trait Init { fn init(); } -#[macro_export] -macro_rules! plugin { - (@def $default:expr, ) => {$default}; - (@def $default:expr, $replace:expr) => {$replace}; - - ( - ty: $type:expr, - name: $name:expr, - author: $author:expr, - description: $description:expr, - license: $license:expr, - version: $version:expr, - $(, version_info: $version_info:expr)? - maturity: $maturity:expr, - // Type to use init/deinit functions on, if applicable - $(, init: $init:ty)? - $(,)? // trailing comma - - ) => { - use std::ffi::{c_int, c_uint}; - use $crate::cstr; - - // Use these intermediates to validate types - const ptype: PluginType = $type; - const ltype: License = $license - const vers: u32 = $version; - const maturity: Maturity = $maturity; - - bindings::st_maria_plugin { - type_: ptype as c_int, - info: *mut c_void, - name: cstr::cstr!($name).as_ptr(), - author: cstr::cstr!($author).as_ptr(), - descr: cstr::cstr!($description).as_ptr(), - license: c_int, - init: Option c_int>, - deinit: Option c_int>, - version: vers as c_uint, - status_vars: *mut st_mysql_show_var, - system_vars: *mut *mut st_mysql_sys_var, - version_info: plugin!( - @def - cstr::cstr!($vers).as_ptr(), - $(cstr::cstr!($comment).as_ptr())? - ),, - maturity: maturity as c_uint, - } +pub unsafe fn wrap_init(ptr: *mut c_void) { + T::init() +} - }; +pub unsafe fn wrap_deinit(ptr: *mut c_void) { + // } +// #[macro_export] +// macro_rules! plugin { +// (@def $default:expr, ) => {$default}; +// (@def $default:expr, $replace:expr) => {$replace}; + +// ( +// ty: $type:expr, +// name: $name:expr, +// author: $author:expr, +// description: $description:expr, +// license: $license:expr, +// version: $version:expr, +// $(, version_info: $version_info:expr)? +// maturity: $maturity:expr, +// // Type to use init/deinit functions on, if applicable +// $(, init: $init:ty)? +// $(,)? // trailing comma + +// ) => { +// use std::ffi::{c_int, c_uint}; +// use $crate::cstr; + +// // Use these intermediates to validate types +// const ptype: PluginType = $type; +// const ltype: License = $license +// const vers: u32 = $version; +// const maturity: Maturity = $maturity; + +// bindings::st_maria_plugin { +// type_: ptype as c_int, +// info: *mut c_void, +// name: cstr::cstr!($name).as_ptr(), +// author: cstr::cstr!($author).as_ptr(), +// descr: cstr::cstr!($description).as_ptr(), +// license: c_int, +// init: Option c_int>, +// deinit: Option c_int>, +// version: vers as c_uint, +// status_vars: *mut st_mysql_show_var, +// system_vars: *mut *mut st_mysql_sys_var, +// version_info: plugin!( +// @def +// cstr::cstr!($vers).as_ptr(), +// $(cstr::cstr!($comment).as_ptr())? +// ),, +// maturity: maturity as c_uint, +// } + +// }; +// } // plugin_encryption!{ // type: RustEncryption, diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 22cac4e0e3886..b631db8a8eda6 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,6 +1,7 @@ use bindings::st_mysql_sys_var_basic; use mariadb_server_sys as bindings; use std::mem::ManuallyDrop; +use std::ptr; #[repr(C)] union SysVar { @@ -18,6 +19,14 @@ pub struct SysVarAtomic(SysVar); #[doc(hidden)] impl SysVarAtomic { + pub const fn as_ptr(&self) -> *const bindings::st_mysql_sys_var { + ptr::addr_of!(self.0).cast() + } + + pub fn as_mut_ptr(&mut self) -> *mut bindings::st_mysql_sys_var { + ptr::addr_of_mut!(self.0).cast() + } + // These functions are all unsafe because preconditions are needed to ensure // Send and Sync hold true pub const unsafe fn new_basic(val: bindings::st_mysql_sys_var_basic) -> Self { @@ -92,7 +101,7 @@ macro_rules! sysvar_atomic { use ::std::ffi::{c_void, c_int, c_char}; use ::std::mem::ManuallyDrop; use ::std::ptr; - + use $crate::bindings; use $crate::cstr; From 709be007b2450cf9a191001770155391091fa56a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Jan 2023 06:59:33 -0500 Subject: [PATCH 09/34] Update plugin examples --- rust/Cargo.toml | 4 +- rust/Dockerfile | 31 ++- rust/examples/basic/Cargo.toml | 10 + rust/examples/basic/src/lib.rs | 111 ++++++++ rust/examples/debug-key-management/Cargo.toml | 10 + .../src/lib.rs} | 37 +-- rust/examples/{ => encryption}/Cargo.toml | 4 +- rust/examples/encryption/src/lib.rs | 236 ++++++++++++++++++ rust/examples/src/encryption.rs | 120 --------- rust/examples/src/lib.rs | 2 - rust/mariadb/src/plugin.rs | 48 +++- rust/mariadb/src/plugin/authentication.rs | 10 +- rust/mariadb/src/plugin/encryption.rs | 29 ++- rust/mariadb/src/plugin/encryption_wrapper.rs | 105 ++++++-- rust/mariadb/src/plugin/ftparser.rs | 4 + rust/mariadb/src/plugin/vio.rs | 3 +- rust/mariadb/src/plugin/wrapper.rs | 9 + 17 files changed, 593 insertions(+), 180 deletions(-) create mode 100644 rust/examples/basic/Cargo.toml create mode 100644 rust/examples/basic/src/lib.rs create mode 100644 rust/examples/debug-key-management/Cargo.toml rename rust/examples/{src/debug_key_management.rs => debug-key-management/src/lib.rs} (85%) rename rust/examples/{ => encryption}/Cargo.toml (68%) create mode 100644 rust/examples/encryption/src/lib.rs delete mode 100644 rust/examples/src/encryption.rs delete mode 100644 rust/examples/src/lib.rs create mode 100644 rust/mariadb/src/plugin/ftparser.rs create mode 100644 rust/mariadb/src/plugin/wrapper.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 754aad3745930..30845c1398c96 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,5 +6,7 @@ members = [ "bindings", "mariadb", "macros", - "examples" + "examples/basic", + "examples/debug-key-management", + "examples/encryption" ] diff --git a/rust/Dockerfile b/rust/Dockerfile index 6ed9f08a783a8..ab89b96c37c90 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -6,18 +6,30 @@ # docker build -f Dockerfile ../ --tag mdb-plugin-ex # # # Run the container -# docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c mdb-plugin-ex +# docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c \ +# mdb-plugin-ex --plugin-maturity=experimental \ +# --plugin-load=libbasic # # # Enter a SQL console # docker exec -it mdb-plugin-ex-c mysql -pexample +# +# # Install desired plugins +# INSTALL PLUGIN basic_key_management SONAME 'libbasic.so'; +# INSTALL PLUGIN encryption_example SONAME 'libencryption.so'; # ``` +# This build is space inefficient, but should rerun somewhat quick + FROM rust:latest AS build WORKDIR /build RUN apt-get update \ - && apt-get install -y cmake clang bison + # build requirements + && apt-get install -y cmake clang bison \ + # development helpers + xxd less vim-tiny binutils \ + && mkdir /output COPY . . @@ -25,10 +37,19 @@ WORKDIR /build/rust RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/build/target \ - cargo build --release -p examples \ - && mkdir /output \ - && cp target/release/*.so /output + cargo build + # -p basic + # -p encryption + # --release \ + # && cp target/release/*.so /output + +RUN cp target/debug/*.so /output + +RUN export RUST_BACKTRACE=1 FROM mariadb:10.11-rc COPY --from=build /output/* /usr/lib/mysql/plugin/ + +# create database db; use db; create table t1 (id int) encrypted=yes; +# flush tables t1 for export; diff --git a/rust/examples/basic/Cargo.toml b/rust/examples/basic/Cargo.toml new file mode 100644 index 0000000000000..82a070590997e --- /dev/null +++ b/rust/examples/basic/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "basic" +version = "0.1.0" +edition = '2021' + +[lib] +crate-type = ["cdylib"] + +[dependencies] +mariadb-server = { path = "../../mariadb" } diff --git a/rust/examples/basic/src/lib.rs b/rust/examples/basic/src/lib.rs new file mode 100644 index 0000000000000..e4432fae1c553 --- /dev/null +++ b/rust/examples/basic/src/lib.rs @@ -0,0 +1,111 @@ +//! Debug key management +//! +//! Use to debug the encryption code with a fixed key that changes only on user +//! request. The only valid key ID is 1. +//! +//! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! + +#![allow(unused)] + +use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb_server::plugin::{new_null_st_maria_plugin, PluginType, PluginVarInfo, UnsafeSyncCell}; +use mariadb_server::plugin::{License, Maturity, SysVarAtomic}; +use mariadb_server::sysvar_atomic; +use std::cell::UnsafeCell; +use std::ffi::c_void; +use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicU32, AtomicUsize}; + +struct BasicKeyMgt; + +static COUNTER: AtomicUsize = AtomicUsize::new(30); + +impl KeyManager for BasicKeyMgt { + fn get_latest_key_version(key_id: u32) -> Result { + static KCOUNT: AtomicU32 = AtomicU32::new(1); + eprintln!("get key version with {key_id}"); + Ok(KCOUNT.fetch_add(1, Ordering::Relaxed)) + } + + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + let s = format!("get_key: {key_id}:{key_version}"); + eprintln!("{s}, {}", dst.len()); + + if dst.len() < dbg!(COUNTER.fetch_add(1, Ordering::Relaxed)) { + return Err(KeyError::BufferTooSmall); + } + + // Copy our slice to the buffer, return the copied length + dst[..s.len()].copy_from_slice(s.as_str().as_bytes()); + Ok(()) + } + + fn key_length(key_id: u32, key_version: u32) -> Result { + eprintln!("get key length with {key_id}:{key_version}"); + Ok(COUNTER.load(Ordering::Relaxed)) + } +} + +// PROC: should mangle names with type name + +// C plugins manually create this, but we can automate +static _ENCRYPTION_ST: ::mariadb_server::plugin::UnsafeSyncCell< + ::mariadb_server::bindings::st_mariadb_encryption, +> = unsafe { + ::mariadb_server::plugin::UnsafeSyncCell::new(mariadb_server::bindings::st_mariadb_encryption { + interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, + get_latest_key_version: Some( + mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, + ), + get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), + crypt_ctx_size: None, + crypt_ctx_init: None, + crypt_ctx_update: None, + crypt_ctx_finish: None, + encrypted_length: None, + }) +}; + +// If we compile dynamically, use these names. Otherwise, we need to use +// `buildin_maria_NAME_...` +#[no_mangle] +static _maria_plugin_interface_version_: ::std::ffi::c_int = + ::mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; + +#[no_mangle] +static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = + ::std::mem::size_of::() as ::std::ffi::c_int; + +#[no_mangle] +static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ + ::mariadb_server::bindings::st_maria_plugin { + type_: PluginType::MariaEncryption as i32, + info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), + name: mariadb_server::cstr::cstr!("basic_key_management").as_ptr(), + author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), + descr: mariadb_server::cstr::cstr!("Basic key management plugin").as_ptr(), + license: License::Gpl as i32, + init: None, + deinit: None, + version: 0x0010, + status_vars: ::std::ptr::null_mut(), + system_vars: ::std::ptr::null_mut(), + version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), + maturity: Maturity::Experimental as u32, + }, + // End with a null object + ::mariadb_server::plugin::new_null_st_maria_plugin(), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_statics() { + unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; + dbg!(&_maria_plugin_interface_version_); + dbg!(&_maria_sizeof_struct_st_plugin_); + unsafe { dbg!(&_maria_plugin_declarations_) }; + } +} diff --git a/rust/examples/debug-key-management/Cargo.toml b/rust/examples/debug-key-management/Cargo.toml new file mode 100644 index 0000000000000..2044f1ad2b35a --- /dev/null +++ b/rust/examples/debug-key-management/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "debug-key-management" +version = "0.1.0" +edition = '2021' + +[lib] +crate-type = ["cdylib"] + +[dependencies] +mariadb-server = { path = "../../mariadb" } diff --git a/rust/examples/src/debug_key_management.rs b/rust/examples/debug-key-management/src/lib.rs similarity index 85% rename from rust/examples/src/debug_key_management.rs rename to rust/examples/debug-key-management/src/lib.rs index a84afa40f6cf5..8d13099692df4 100644 --- a/rust/examples/src/debug_key_management.rs +++ b/rust/examples/debug-key-management/src/lib.rs @@ -8,7 +8,7 @@ #![allow(unused)] use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::{SysVarAtomic, License, Maturity}; +use mariadb_server::plugin::{License, Maturity, SysVarAtomic}; use mariadb_server::plugin::{PluginType, PluginVarInfo}; use mariadb_server::sysvar_atomic; use std::cell::UnsafeCell; @@ -108,21 +108,26 @@ unsafe impl Sync for UcWrap {} // PROC: should mangle names with type name -static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb_server::bindings::st_mysql_sys_var; 2]> = UcWrap(UnsafeCell::new([ - KEY_VERSION_SYSVAR.as_ptr().cast_mut(), - ::std::ptr::null_mut() -])); - -static _ENCRYPTION_ST: UcWrap = UcWrap(UnsafeCell::new(mariadb_server::bindings::st_mariadb_encryption { - interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, - get_latest_key_version: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::), - get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), - crypt_ctx_size: None, - crypt_ctx_init: None, - crypt_ctx_update: None, - crypt_ctx_finish: None, - encrypted_length: None, -})); +static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb_server::bindings::st_mysql_sys_var; 2]> = + UcWrap(UnsafeCell::new([ + KEY_VERSION_SYSVAR.as_ptr().cast_mut(), + ::std::ptr::null_mut(), + ])); + +static _ENCRYPTION_ST: UcWrap = UcWrap( + UnsafeCell::new(mariadb_server::bindings::st_mariadb_encryption { + interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, + get_latest_key_version: Some( + mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, + ), + get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), + crypt_ctx_size: None, + crypt_ctx_init: None, + crypt_ctx_update: None, + crypt_ctx_finish: None, + encrypted_length: None, + }), +); #[no_mangle] static _maria_plugin_interface_version_: ::std::ffi::c_int = diff --git a/rust/examples/Cargo.toml b/rust/examples/encryption/Cargo.toml similarity index 68% rename from rust/examples/Cargo.toml rename to rust/examples/encryption/Cargo.toml index 5e309207a7d5a..a96b622431a53 100644 --- a/rust/examples/Cargo.toml +++ b/rust/examples/encryption/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "examples" +name = "encryption" version = "0.1.0" edition = '2021' @@ -7,7 +7,7 @@ edition = '2021' crate-type = ["cdylib"] [dependencies] -mariadb-server = { path = "../mariadb" } +mariadb-server = { path = "../../mariadb" } aes-gcm = "0.10" sha2 = "0.10" rand = "0.8" diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs new file mode 100644 index 0000000000000..49b229ce4d8c3 --- /dev/null +++ b/rust/examples/encryption/src/lib.rs @@ -0,0 +1,236 @@ +//! Basic encryption plugin using: +//! +//! - SHA256 as the hasher + +#![allow(unused)] + +use rand::Rng; +use sha2::{Digest, Sha256 as Hasher}; +use std::sync::Mutex; +use std::time::{Duration, Instant}; + +use aes_gcm::{ + aead::{Aead, KeyInit, OsRng}, + Aes256Gcm, + Nonce, // Or `Aes128Gcm` +}; + +use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb_server::plugin::{Init, License, Maturity, PluginType}; +// use mariadb_server::plugin::Init; +// use mariadb_server::plugin::prelude::*; + +// plugin_encryption!{ +// type: RustEncryption, +// init: RustEncryptionInit, // optional +// name: "example_key_management", +// author: "MariaDB Team", +// description: "Example key management plugin using AES", +// license: GPL, +// stability: EXPERIMENTAL +// } + +/// Range of key rotations, as seconds +const KEY_ROTATION_MIN: f32 = 45.0; +const KEY_ROTATION_MAX: f32 = 90.0; +const KEY_ROTATION_INTERVAL: f32 = KEY_ROTATION_MAX - KEY_ROTATION_MIN; +const SHA256_SIZE: usize = 32; +// const KEY_ROTATION_INTERVAL: Duration = +// KEY_ROTATION_MAX - KEY_ROTATION_MIN; + +/// Our global key version state +static KEY_VERSIONS: Mutex> = Mutex::new(None); + +/// Contain the state of our keys. We use `Instant` (the monotonically) +/// increasing clock) instead of `SystemTime` (which may occasionally go +/// backwards) +#[derive(Debug)] +struct KeyVersions { + /// Initialization time of the struct, reference point for key version + start: Instant, + /// Most recent key update time + current: Instant, + /// Next time for a key update + next: Instant, +} + +impl KeyVersions { + /// Initialize with a new value. Returns the struct + fn new_now() -> Self { + let now = Instant::now(); + let mut ret = Self { + start: now, + current: now, + next: now, + }; + ret.update_next(); + eprintln!("made self"); + dbg!(&ret); + ret + } + + fn update_next(&mut self) { + dbg!(&self); + let mult = rand::thread_rng().gen_range(0.0..1.0); + let add_duration = KEY_ROTATION_MIN + mult * KEY_ROTATION_INTERVAL; + self.next += Duration::from_secs_f32(add_duration); + } + + /// Update the internal duration if needed, and return the elapsed time + fn update_returning_version(&mut self) -> u64 { + dbg!(&self); + let now = Instant::now(); + if now > self.next { + self.current = now; + self.update_next(); + } + (self.next - self.start).as_secs() + } +} + +struct RustEncryption; + +impl Init for RustEncryption { + /// Initialize function: + fn init() { + dbg!("init: locking"); + let mut guard = KEY_VERSIONS.lock().unwrap(); + *guard = Some(KeyVersions::new_now()); + } +} + +impl KeyManager for RustEncryption { + fn get_latest_key_version(_key_id: u32) -> Result { + dbg!(_key_id); + let mut guard = KEY_VERSIONS.lock().unwrap(); + let mut vers = guard.as_mut().unwrap(); + Ok(vers.update_returning_version() as u32) + } + + /// Given a key ID and a version, create its hash + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + dbg!(key_id, key_version, dst.len()); + + let output_size = Hasher::output_size(); + if dst.len() < output_size { + return Err(KeyError::BufferTooSmall); + } + let mut hasher = Hasher::new(); + hasher.update(key_id.to_ne_bytes()); + hasher.update(key_version.to_ne_bytes()); + dst[..output_size].copy_from_slice(&hasher.finalize()); + Ok(()) + } + + fn key_length(key_id: u32, key_version: u32) -> Result { + dbg!(key_id, key_version); + // All keys have the same length + Ok(Hasher::output_size()) + } +} + +impl Encryption for RustEncryption { + fn init( + key_id: u32, + key_version: u32, + key: &[u8], + iv: &[u8], + flags: Flags, + ) -> Result { + eprintln!("encryption init"); + dbg!(&key_id, &key_version, &key, &iv); + dbg!(flags); + Ok(Self) + } + + fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), ()> { + eprintln!("encryption update"); + dbg!(src.len(), dst.len()); + dst[..src.len()].copy_from_slice(src); + Ok(()) + } + + fn finish(&mut self, dst: &mut [u8]) -> Result<(), ()> { + eprintln!("encryption finish"); + dbg!(dst.len()); + Ok(()) + } + + fn encrypted_length(key_id: u32, key_version: u32, src_len: usize) -> usize { + eprintln!("encryption length"); + dbg!(key_id, key_version, src_len); + src_len + } +} + +// C plugins manually create this, but we can automate +static _ENCRYPTION_ST: ::mariadb_server::plugin::UnsafeSyncCell< + ::mariadb_server::bindings::st_mariadb_encryption, +> = unsafe { + ::mariadb_server::plugin::UnsafeSyncCell::new(mariadb_server::bindings::st_mariadb_encryption { + interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, + get_latest_key_version: Some( + mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, + ), + get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), + crypt_ctx_size: Some( + mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_size::, + ), + crypt_ctx_init: Some( + mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_init::, + ), + crypt_ctx_update: Some( + mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_update::, + ), + crypt_ctx_finish: Some( + mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_finish::, + ), + encrypted_length: Some( + mariadb_server::plugin::encryption_wrapper::wrap_encrypted_length::, + ), + }) +}; + +// If we compile dynamically, use these names. Otherwise, we need to use +// `buildin_maria_NAME_...` +#[no_mangle] +static _maria_plugin_interface_version_: ::std::ffi::c_int = + ::mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; + +#[no_mangle] +static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = + ::std::mem::size_of::() as ::std::ffi::c_int; + +#[no_mangle] +static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ + ::mariadb_server::bindings::st_maria_plugin { + type_: PluginType::MariaEncryption as i32, + info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), + name: mariadb_server::cstr::cstr!("encryption_example").as_ptr(), + author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), + descr: mariadb_server::cstr::cstr!("Example key management / encryption plugin").as_ptr(), + license: License::Gpl as i32, + init: None, + deinit: None, + version: 0x0010, + status_vars: ::std::ptr::null_mut(), + system_vars: ::std::ptr::null_mut(), + version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), + maturity: Maturity::Experimental as u32, + }, + // End with a null object + ::mariadb_server::plugin::new_null_st_maria_plugin(), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_statics() { + unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; + dbg!(&_maria_plugin_interface_version_); + dbg!(&_maria_sizeof_struct_st_plugin_); + unsafe { dbg!(&_maria_plugin_declarations_) }; + } +} diff --git a/rust/examples/src/encryption.rs b/rust/examples/src/encryption.rs deleted file mode 100644 index 739d8b5a2c11a..0000000000000 --- a/rust/examples/src/encryption.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Basic encryption plugin using: -//! -//! - SHA256 as the hasher - -#![allow(unused)] - -use rand::Rng; -use sha2::{Digest, Sha256 as Hasher}; -use std::sync::Mutex; -use std::time::{Duration, Instant}; - -use aes_gcm::{ - aead::{Aead, KeyInit, OsRng}, - Aes256Gcm, - Nonce, // Or `Aes128Gcm` -}; - -use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::Init; -// use mariadb_server::plugin::Init; -// use mariadb_server::plugin::prelude::*; - -// plugin_encryption!{ -// type: RustEncryption, -// init: RustEncryptionInit, // optional -// name: "example_key_management", -// author: "MariaDB Team", -// description: "Example key management plugin using AES", -// license: GPL, -// stability: EXPERIMENTAL -// } - -/// Range of key rotations, as seconds -const KEY_ROTATION_MIN: f32 = 45.0; -const KEY_ROTATION_MAX: f32 = 90.0; -const KEY_ROTATION_INTERVAL: f32 = KEY_ROTATION_MAX - KEY_ROTATION_MIN; -const SHA256_SIZE: usize = 32; -// const KEY_ROTATION_INTERVAL: Duration = -// KEY_ROTATION_MAX - KEY_ROTATION_MIN; - -/// Our global key version state -static KEY_VERSIONS: Mutex> = Mutex::new(None); - -/// Contain the state of our keys. We use `Instant` (the monotonically) -/// increasing clock) instead of `SystemTime` (which may occasionally go -/// backwards) -struct KeyVersions { - /// Initialization time of the struct, reference point for key version - start: Instant, - /// Most recent key update time - current: Instant, - /// Next time for a key update - next: Instant, -} - -impl KeyVersions { - /// Initialize with a new value. Returns the struct - fn new_now() -> Self { - let now = Instant::now(); - let mut ret = Self { - start: now, - current: now, - next: now, - }; - ret.update_next(); - ret - } - - fn update_next(&mut self) { - let mult = rand::thread_rng().gen_range(0.0..1.0); - let add_duration = KEY_ROTATION_MIN + mult * KEY_ROTATION_INTERVAL; - self.next += Duration::from_secs_f32(add_duration); - } - - /// Update the internal duration if needed, and return the elapsed time - fn update_returning_version(&mut self) -> u64 { - let now = Instant::now(); - if now > self.next { - self.current = now; - self.update_next(); - } - (self.next - self.start).as_secs() - } -} - -struct RustEncryption; - -impl Init for RustEncryption { - /// Initialize function: - fn init() { - let mut guard = KEY_VERSIONS.lock().unwrap(); - *guard = Some(KeyVersions::new_now()); - } -} - -impl KeyManager for RustEncryption { - fn get_latest_key_version(_key_id: u32) -> Result { - let mut guard = KEY_VERSIONS.lock().unwrap(); - let mut vers = guard.as_mut().unwrap(); - Ok(vers.update_returning_version() as u32) - } - - /// Given a key ID and a version, create its hash - fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { - let output_size = Hasher::output_size(); - if dst.len() < output_size { - return Err(KeyError::BufferTooSmall); - } - let mut hasher = Hasher::new(); - hasher.update(key_id.to_ne_bytes()); - hasher.update(key_version.to_ne_bytes()); - dst[..output_size].copy_from_slice(&hasher.finalize()); - Ok(()) - } - - fn key_length(key_id: u32, key_version: u32) -> Result { - // All keys have the same length - Ok(Hasher::output_size()) - } -} diff --git a/rust/examples/src/lib.rs b/rust/examples/src/lib.rs deleted file mode 100644 index dd0411333475a..0000000000000 --- a/rust/examples/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod debug_key_management; -mod encryption; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index f35baac789609..9802548cfaeaf 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -2,12 +2,15 @@ // use std::cell::UnsafeCell; -use std::ffi::c_void; +use std::ptr; +use std::{cell::UnsafeCell, ffi::c_void}; use mariadb_server_sys as bindings; pub mod encryption; #[doc(hidden)] pub mod encryption_wrapper; +#[doc(hidden)] +pub mod wrapper; mod variables; pub use variables::{PluginVarInfo, SysVarAtomic}; @@ -62,7 +65,9 @@ pub enum Maturity { /// Initialize state pub trait Init { - fn init(); + fn init() {} + + fn deinit() {} } pub unsafe fn wrap_init(ptr: *mut c_void) { @@ -73,6 +78,45 @@ pub unsafe fn wrap_deinit(ptr: *mut c_void) { // } +/// New struct with all null values +#[doc(hidden)] +pub const fn new_null_st_maria_plugin() -> bindings::st_maria_plugin { + bindings::st_maria_plugin { + type_: 0, + info: ptr::null_mut(), + name: ptr::null(), + author: ptr::null(), + descr: ptr::null(), + license: 0, + init: None, + deinit: None, + version: 0, + status_vars: ptr::null_mut(), + system_vars: ptr::null_mut(), + version_info: ptr::null(), + maturity: 0, + } +} + +/// Used for plugin registrations, which are in global scope. +#[doc(hidden)] +#[derive(Debug)] +#[repr(transparent)] +pub struct UnsafeSyncCell(UnsafeCell); + +impl UnsafeSyncCell { + pub const unsafe fn new(value: T) -> Self { + Self(UnsafeCell::new(value)) + } + + pub const fn as_ptr(&self) -> *const T { + self.0.get() + } +} + +unsafe impl Send for UnsafeSyncCell {} +unsafe impl Sync for UnsafeSyncCell {} + // #[macro_export] // macro_rules! plugin { // (@def $default:expr, ) => {$default}; diff --git a/rust/mariadb/src/plugin/authentication.rs b/rust/mariadb/src/plugin/authentication.rs index 7fa0fe5072e7f..f9e317c0af9ae 100644 --- a/rust/mariadb/src/plugin/authentication.rs +++ b/rust/mariadb/src/plugin/authentication.rs @@ -63,12 +63,14 @@ impl AuthInfo { } struct AuthError; -struct TruncatedError; trait Authentication { - fn authenticate_user(vio: &Vio, info: &AuthInfo) -> Result<(), AuthError>; + fn authenticate_user(vio: &Vio, info: &AuthInfo) -> Result; - fn hash_password(password: &[u8], buf: X); + /// Hash the provided password and write the output to `hash`. Return the + /// number of written bytes if successful, or `Err` if not. + fn hash_password(password: &[u8], hash: &mut [u8]) -> Result; - fn preprocess_hash(hash: &[u8], buf: X); + /// Prepare the password hash for authentication + fn preprocess_hash(hash: &[u8], out: &mut [u8]) -> Result; } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index af38b26095809..6110f812f4cb9 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -23,6 +23,7 @@ use mariadb_server_sys as bindings; /// A type of error to be used by key functions #[non_exhaustive] #[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum KeyError { // Values must be nonzero /// A key ID is invalid or not found. Maps to `ENCRYPTION_KEY_VERSION_INVALID` in C. @@ -33,20 +34,24 @@ pub enum KeyError { } /// Representation of the flags integer -#[derive(Clone, Copy, PartialEq)] -pub struct Flags(u32); +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Flags(i32); impl Flags { - fn should_encrypt(&self) -> bool { - (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT) != 0 + pub(crate) fn new(value: i32) -> Self { + Self(value) } - fn should_decrypt(&self) -> bool { - (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT) != 0 + pub(crate) fn should_encrypt(&self) -> bool { + (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT as i32) != 0 + } + + pub(crate) fn should_decrypt(&self) -> bool { + (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT as i32) != 0 } pub fn nopad(&self) -> bool { - (self.0 & bindings::ENCRYPTION_FLAG_NOPAD) != 0 + (self.0 & bindings::ENCRYPTION_FLAG_NOPAD as i32) != 0 } } @@ -75,16 +80,18 @@ pub trait KeyManager: Send + Sized { fn key_length(key_id: u32, key_version: u32) -> Result; } -pub trait Encryption { +// TODO: Maybe split into `Encrypt` and `Decrypt` traits +pub trait Encryption: Sized { /// Initialize the encryption context object - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Self; + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) + -> Result; /// Update the encryption context with new data, return the number of bytes /// written - fn update(ctx: &mut Self, src: &[u8], dst: &mut [u8]) -> usize; + fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), ()>; /// Write the remaining bytes to the buffer - fn finish(ctx: &mut Self, dst: &mut [u8]) -> usize; + fn finish(&mut self, dst: &mut [u8]) -> Result<(), ()>; /// Return the exact length of the encrypted data based on the source length /// diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 476e9d1b98466..92d820d2237e6 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -3,10 +3,10 @@ use mariadb_server_sys as bindings; use std::{ ffi::{c_int, c_uchar, c_uint, c_void}, - slice, + mem, slice, }; -use super::encryption::{KeyError, KeyManager}; +use super::encryption::{Encryption, Flags, KeyError, KeyManager}; /// Get the key version, simple wrapper pub unsafe extern "C" fn wrap_get_latest_key_version(key_id: c_uint) -> c_uint { @@ -25,8 +25,12 @@ pub unsafe extern "C" fn wrap_get_key( dstbuf: *mut c_uchar, buflen: *mut c_uint, ) -> c_uint { + dbg!(key_id); + dbg!(version); + dbg!(dstbuf); + dbg!(*buflen); if dstbuf.is_null() { - match T::key_length(key_id, version) { + match dbg!(T::key_length(key_id, version)) { // FIXME: don't unwrap Ok(v) => *buflen = v.try_into().unwrap(), Err(e) => { @@ -37,20 +41,54 @@ pub unsafe extern "C" fn wrap_get_key( } // SAFETY: caller guarantees validity - let buf = slice::from_raw_parts_mut(dstbuf, buflen as usize); + let buf = slice::from_raw_parts_mut(dstbuf, *buflen as usize); // If successful, return 0. If an error occurs, return it - match T::get_key(key_id, version, buf) { + match dbg!(T::get_key(key_id, version, buf)) { Ok(_) => 0, - Err(e) => e as c_uint, + Err(e) => { + dbg!(e); + + // match e { + // // Set the desired buffer size if available + // KeyError::BufferTooSmall => { + // *buflen = dbg!(T::key_length(key_id, version) + // .unwrap_or(0) + // .try_into() + // .unwrap()) + // } + // _ => (), + // } + dbg!(e as c_uint) + } + } +} + +unsafe fn set_buflen_with_check(buflen: *mut c_uint, val: u32) { + if val > 32 { + eprintln!( + "The default encryption does not seem to allow keys above 32 bits. If the server \ + crashes after this message, that is the likely error" + ); } + *buflen = val.try_into().unwrap() } -pub unsafe extern "C" fn wrap_crypt_ctx_size(key_id: c_uint, key_version: c_uint) -> c_uint { - todo!() +pub unsafe extern "C" fn wrap_crypt_ctx_size(_key_id: c_uint, _key_version: c_uint) -> c_uint { + // I believe that key_id and key_version are provided in case this plugin + // uses different structs for different keys. However, it seems safer & more + // user friendly to sidestep that and just make everything the same size + mem::size_of::().try_into().unwrap() } -pub unsafe extern "C" fn wrap_crypt_ctx_init( +/// # Safety +/// +/// The caller must guarantee that the following is tre +/// +/// - `ctx` points to memory with the size of T (may be uninitialized) +/// - `key` exists for `klen` +/// - `iv` exists for `ivlen` +pub unsafe extern "C" fn wrap_crypt_ctx_init( ctx: *mut c_void, key: *const c_uchar, klen: c_uint, @@ -60,31 +98,66 @@ pub unsafe extern "C" fn wrap_crypt_ctx_init( key_id: c_uint, key_version: c_uint, ) -> c_int { - todo!() + /// SAFETY: caller guarantees buffer validity + let keybuf = slice::from_raw_parts(key, klen as usize); + let ivbuf = slice::from_raw_parts(iv, ivlen as usize); + let flags = Flags::new(flags); + match T::init(key_id, key_version, keybuf, ivbuf, flags) { + Ok(newctx) => { + ctx.cast::().write(newctx); + 1 + } + Err(_) => -1, + } } -pub unsafe extern "C" fn wrap_crypt_ctx_update( +/// # Safety +/// +/// The caller must guarantee that the following is true: +/// +/// - `ctx` points to a valid, initialized object of type T +/// - `src` exists for `slen` +/// - `dst` exists for `dlen` +pub unsafe extern "C" fn wrap_crypt_ctx_update( ctx: *mut c_void, src: *const c_uchar, slen: c_uint, dst: *mut c_uchar, dlen: *mut c_uint, ) -> c_int { - todo!() + let sbuf = slice::from_raw_parts(src, slen as usize); + let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); + + let c: &mut T = &mut *ctx.cast(); + match c.update(sbuf, dbuf) { + Ok(_) => 1, + Err(_) => -1, + } } -pub unsafe extern "C" fn wrap_crypt_ctx_finish( +pub unsafe extern "C" fn wrap_crypt_ctx_finish( ctx: *mut c_void, dst: *mut c_uchar, dlen: *mut c_uint, ) -> c_int { - todo!() + let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); + + let c: &mut T = &mut *ctx.cast(); + let ret = match c.finish(dbuf) { + Ok(_) => 1, + Err(_) => -1, + }; + + ctx.drop_in_place(); + ret } -pub unsafe extern "C" fn wrap_encrypted_length( +pub unsafe extern "C" fn wrap_encrypted_length( slen: c_uint, key_id: c_uint, key_version: c_uint, ) -> c_uint { - todo!() + T::encrypted_length(key_id, key_version, slen.try_into().unwrap()) + .try_into() + .unwrap() } diff --git a/rust/mariadb/src/plugin/ftparser.rs b/rust/mariadb/src/plugin/ftparser.rs new file mode 100644 index 0000000000000..8f473441711eb --- /dev/null +++ b/rust/mariadb/src/plugin/ftparser.rs @@ -0,0 +1,4 @@ +trait FtParser { + fn init() -> Self; + fn parse(&self); +} diff --git a/rust/mariadb/src/plugin/vio.rs b/rust/mariadb/src/plugin/vio.rs index 316bbed5f42b4..b2e430c10110d 100644 --- a/rust/mariadb/src/plugin/vio.rs +++ b/rust/mariadb/src/plugin/vio.rs @@ -1,4 +1,5 @@ - +//! Representation of the `MYSQL_PLUGIN_VIO` struct, which has methods for +//! reading and writing packets #[repr(transparent)] pub struct Vio (MYSQL_PLUGIN_VIO); diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs new file mode 100644 index 0000000000000..1ff9bf267326e --- /dev/null +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -0,0 +1,9 @@ +use std::ffi::{c_void,c_int}; + +pub unsafe extern "C" fn wrap_init(_: *const c_void) -> c_int { + T::init() +} + +pub unsafe extern "C" fn wrap_deinit(_: *const c_void) -> c_int { + T::deinit() +} From a42486f08424535f419ac876e6ced0be91276cd1 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Jan 2023 17:29:24 -0500 Subject: [PATCH 10/34] Example runs, but panics on dst len --- rust/Dockerfile | 22 +++++++----- rust/examples/basic/src/lib.rs | 18 ++++++++-- rust/examples/debug-key-management/src/lib.rs | 36 +++++++++---------- rust/examples/encryption/src/lib.rs | 34 ++++++++++-------- rust/examples/encryption/test.sql | 18 ++++++++++ rust/mariadb/src/plugin.rs | 23 ++++++------ rust/mariadb/src/plugin/encryption.rs | 21 +++++++---- rust/mariadb/src/plugin/encryption_wrapper.rs | 26 ++++++++------ rust/mariadb/src/plugin/wrapper.rs | 17 ++++++--- 9 files changed, 138 insertions(+), 77 deletions(-) create mode 100644 rust/examples/encryption/test.sql diff --git a/rust/Dockerfile b/rust/Dockerfile index ab89b96c37c90..7513c7b06c4f0 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -7,8 +7,11 @@ # # # Run the container # docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c \ -# mdb-plugin-ex --plugin-maturity=experimental \ -# --plugin-load=libbasic +# mdb-plugin-ex \ +# --plugin-maturity=experimental \ +# --plugin-load=libbasic \ +# --plugin-load=libencryption +# --plugin-load=libdebug_key_management # # # Enter a SQL console # docker exec -it mdb-plugin-ex-c mysql -pexample @@ -20,15 +23,13 @@ # This build is space inefficient, but should rerun somewhat quick -FROM rust:latest AS build +FROM rust:1.66 AS build WORKDIR /build RUN apt-get update \ # build requirements && apt-get install -y cmake clang bison \ - # development helpers - xxd less vim-tiny binutils \ && mkdir /output COPY . . @@ -36,19 +37,24 @@ COPY . . WORKDIR /build/rust RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/build/target \ - cargo build + --mount=type=cache,target=target \ + cargo build \ # -p basic # -p encryption # --release \ # && cp target/release/*.so /output + && cp target/debug/*.so /output -RUN cp target/debug/*.so /output +# RUN cp target/debug/*.so /output RUN export RUST_BACKTRACE=1 FROM mariadb:10.11-rc +# Deb utils +RUN apt-get update \ + && apt-get install -y xxd less vim-tiny binutils + COPY --from=build /output/* /usr/lib/mysql/plugin/ # create database db; use db; create table t1 (id int) encrypted=yes; diff --git a/rust/examples/basic/src/lib.rs b/rust/examples/basic/src/lib.rs index e4432fae1c553..5501f60aa43f6 100644 --- a/rust/examples/basic/src/lib.rs +++ b/rust/examples/basic/src/lib.rs @@ -9,7 +9,7 @@ use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb_server::plugin::{new_null_st_maria_plugin, PluginType, PluginVarInfo, UnsafeSyncCell}; -use mariadb_server::plugin::{License, Maturity, SysVarAtomic}; +use mariadb_server::plugin::{License, Maturity, SysVarAtomic, InitError, Init}; use mariadb_server::sysvar_atomic; use std::cell::UnsafeCell; use std::ffi::c_void; @@ -20,6 +20,18 @@ struct BasicKeyMgt; static COUNTER: AtomicUsize = AtomicUsize::new(30); +impl Init for BasicKeyMgt { + fn init() -> Result<(), InitError> { + eprintln!("init for BasicKeyMgt"); + Ok(()) + } + + fn deinit() -> Result<(), InitError> { + eprintln!("deinit for BasicKeyMgt"); + Ok(()) + } +} + impl KeyManager for BasicKeyMgt { fn get_latest_key_version(key_id: u32) -> Result { static KCOUNT: AtomicU32 = AtomicU32::new(1); @@ -85,8 +97,8 @@ static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plug author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), descr: mariadb_server::cstr::cstr!("Basic key management plugin").as_ptr(), license: License::Gpl as i32, - init: None, - deinit: None, + init: Some(mariadb_server::plugin::wrapper::wrap_init::), + deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), version: 0x0010, status_vars: ::std::ptr::null_mut(), system_vars: ::std::ptr::null_mut(), diff --git a/rust/examples/debug-key-management/src/lib.rs b/rust/examples/debug-key-management/src/lib.rs index 8d13099692df4..58e720796a31b 100644 --- a/rust/examples/debug-key-management/src/lib.rs +++ b/rust/examples/debug-key-management/src/lib.rs @@ -8,8 +8,8 @@ #![allow(unused)] use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::{License, Maturity, SysVarAtomic}; -use mariadb_server::plugin::{PluginType, PluginVarInfo}; +use mariadb_server::plugin::{Init, License, Maturity, SysVarAtomic}; +use mariadb_server::plugin::{InitError, PluginType, PluginVarInfo}; use mariadb_server::sysvar_atomic; use std::cell::UnsafeCell; use std::ffi::c_void; @@ -31,6 +31,18 @@ static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { struct DebugKeyMgmt; +impl Init for DebugKeyMgmt { + fn init() -> Result<(), InitError> { + eprintln!("init for DebugKeyMgmt"); + Ok(()) + } + + fn deinit() -> Result<(), InitError> { + eprintln!("deinit for DebugKeyMgmt"); + Ok(()) + } +} + impl KeyManager for DebugKeyMgmt { fn get_latest_key_version(key_id: u32) -> Result { if key_id != 1 { @@ -146,8 +158,8 @@ static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plug author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), descr: mariadb_server::cstr::cstr!("Debug key management plugin").as_ptr(), license: License::Gpl as i32, - init: None, - deinit: None, + init: Some(mariadb_server::plugin::wrapper::wrap_init::), + deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), version: 0x0010, status_vars: ::std::ptr::null_mut(), system_vars: _INTERNAL_SYSVARS.0.get().cast(), @@ -155,19 +167,5 @@ static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plug maturity: Maturity::Experimental as u32, }, // End with a null object - mariadb_server::bindings::st_maria_plugin { - type_: 0, - info: ::std::ptr::null_mut(), - name: ::std::ptr::null(), - author: ::std::ptr::null(), - descr: ::std::ptr::null(), - license: 0, - init: None, - deinit: None, - version: 0, - status_vars: ::std::ptr::null_mut(), - system_vars: ::std::ptr::null_mut(), - version_info: ::std::ptr::null(), - maturity: 0, - }, + ::mariadb_server::plugin::new_null_st_maria_plugin(), ]; diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs index 49b229ce4d8c3..cac26f303f2fb 100644 --- a/rust/examples/encryption/src/lib.rs +++ b/rust/examples/encryption/src/lib.rs @@ -15,8 +15,8 @@ use aes_gcm::{ Nonce, // Or `Aes128Gcm` }; -use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::{Init, License, Maturity, PluginType}; +use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager, EncryptionError}; +use mariadb_server::plugin::{Init, InitError, License, Maturity, PluginType}; // use mariadb_server::plugin::Init; // use mariadb_server::plugin::prelude::*; @@ -64,21 +64,17 @@ impl KeyVersions { next: now, }; ret.update_next(); - eprintln!("made self"); - dbg!(&ret); ret } fn update_next(&mut self) { - dbg!(&self); let mult = rand::thread_rng().gen_range(0.0..1.0); let add_duration = KEY_ROTATION_MIN + mult * KEY_ROTATION_INTERVAL; self.next += Duration::from_secs_f32(add_duration); } - + /// Update the internal duration if needed, and return the elapsed time fn update_returning_version(&mut self) -> u64 { - dbg!(&self); let now = Instant::now(); if now > self.next { self.current = now; @@ -92,10 +88,16 @@ struct RustEncryption; impl Init for RustEncryption { /// Initialize function: - fn init() { - dbg!("init: locking"); + fn init() -> Result<(), InitError> { + eprintln!("init called for RustEncryption"); let mut guard = KEY_VERSIONS.lock().unwrap(); *guard = Some(KeyVersions::new_now()); + Ok(()) + } + + fn deinit() -> Result<(), InitError> { + eprintln!("deinit called for RustEncryption"); + Ok(()) } } @@ -136,21 +138,23 @@ impl Encryption for RustEncryption { key: &[u8], iv: &[u8], flags: Flags, - ) -> Result { + ) -> Result { eprintln!("encryption init"); - dbg!(&key_id, &key_version, &key, &iv); + dbg!(&key_id, &key_version); + eprintln!("key: {:x?}", &key); + eprintln!("iv: {:x?}", &iv); dbg!(flags); Ok(Self) } - fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), ()> { + fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), EncryptionError> { eprintln!("encryption update"); dbg!(src.len(), dst.len()); dst[..src.len()].copy_from_slice(src); Ok(()) } - fn finish(&mut self, dst: &mut [u8]) -> Result<(), ()> { + fn finish(&mut self, dst: &mut [u8]) -> Result<(), EncryptionError> { eprintln!("encryption finish"); dbg!(dst.len()); Ok(()) @@ -210,8 +214,8 @@ static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plug author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), descr: mariadb_server::cstr::cstr!("Example key management / encryption plugin").as_ptr(), license: License::Gpl as i32, - init: None, - deinit: None, + init: Some(mariadb_server::plugin::wrapper::wrap_init::), + deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), version: 0x0010, status_vars: ::std::ptr::null_mut(), system_vars: ::std::ptr::null_mut(), diff --git a/rust/examples/encryption/test.sql b/rust/examples/encryption/test.sql new file mode 100644 index 0000000000000..6c661bbb19fa1 --- /dev/null +++ b/rust/examples/encryption/test.sql @@ -0,0 +1,18 @@ + +SET GLOBAL innodb_encrypt_tables=ON; +SET SESSION innodb_default_encryption_key_id=100; + +CREATE DATABASE db; +USE db; + +CREATE TABLE t1 ( + id int PRIMARY KEY, + str varchar(50) +); + +INSERT INTO t1(id, str) VALUES + (1, 'abc'), + (2, 'def'), + (3, 'ghi'); + +FLUSH TABLES t1 FOR EXPORT; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 9802548cfaeaf..65f59c8388a00 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -9,9 +9,9 @@ use mariadb_server_sys as bindings; pub mod encryption; #[doc(hidden)] pub mod encryption_wrapper; +mod variables; #[doc(hidden)] pub mod wrapper; -mod variables; pub use variables::{PluginVarInfo, SysVarAtomic}; /// Defines possible licenses for plugins @@ -63,19 +63,17 @@ pub enum Maturity { Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize, } +pub struct InitError; + /// Initialize state pub trait Init { - fn init() {} - - fn deinit() {} -} - -pub unsafe fn wrap_init(ptr: *mut c_void) { - T::init() -} + fn init() -> Result<(), InitError> { + Ok(()) + } -pub unsafe fn wrap_deinit(ptr: *mut c_void) { - // + fn deinit() -> Result<(), InitError> { + Ok(()) + } } /// New struct with all null values @@ -105,6 +103,9 @@ pub const fn new_null_st_maria_plugin() -> bindings::st_maria_plugin { pub struct UnsafeSyncCell(UnsafeCell); impl UnsafeSyncCell { + /// # Safety + /// + /// This inner value be used in a Sync/Send way pub const unsafe fn new(value: T) -> Self { Self(UnsafeCell::new(value)) } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 6110f812f4cb9..191002d8e6164 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -21,8 +21,8 @@ use mariadb_server_sys as bindings; /// A type of error to be used by key functions -#[non_exhaustive] #[repr(u32)] +#[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq)] pub enum KeyError { // Values must be nonzero @@ -33,6 +33,15 @@ pub enum KeyError { Other = 3, } +#[repr(i32)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum EncryptionError { + BadData = bindings::MY_AES_BAD_DATA, + BadKeySize = bindings::MY_AES_BAD_KEYSIZE, + Other = bindings::MY_AES_OPENSSL_ERROR, +} + /// Representation of the flags integer #[derive(Clone, Copy, Debug, PartialEq)] pub struct Flags(i32); @@ -47,7 +56,8 @@ impl Flags { } pub(crate) fn should_decrypt(&self) -> bool { - (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT as i32) != 0 + // (self.0 & bindings::ENCRYPTION_FLAG_DECRYPT as i32) != 0 + !self.should_encrypt() } pub fn nopad(&self) -> bool { @@ -83,15 +93,14 @@ pub trait KeyManager: Send + Sized { // TODO: Maybe split into `Encrypt` and `Decrypt` traits pub trait Encryption: Sized { /// Initialize the encryption context object - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) - -> Result; + fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Result; /// Update the encryption context with new data, return the number of bytes /// written - fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), ()>; + fn update(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(), EncryptionError>; /// Write the remaining bytes to the buffer - fn finish(&mut self, dst: &mut [u8]) -> Result<(), ()>; + fn finish(&mut self, dst: &mut [u8]) -> Result<(), EncryptionError>; /// Return the exact length of the encrypted data based on the source length /// diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 92d820d2237e6..04580feec37e1 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -105,9 +105,9 @@ pub unsafe extern "C" fn wrap_crypt_ctx_init( match T::init(key_id, key_version, keybuf, ivbuf, flags) { Ok(newctx) => { ctx.cast::().write(newctx); - 1 + bindings::MY_AES_OK.try_into().unwrap() } - Err(_) => -1, + Err(e) => e as c_int, } } @@ -125,14 +125,18 @@ pub unsafe extern "C" fn wrap_crypt_ctx_update( dst: *mut c_uchar, dlen: *mut c_uint, ) -> c_int { + dbg!(slen, dlen, *dlen); let sbuf = slice::from_raw_parts(src, slen as usize); - let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); + let dbuf = slice::from_raw_parts_mut(dst, *dlen as usize); let c: &mut T = &mut *ctx.cast(); - match c.update(sbuf, dbuf) { - Ok(_) => 1, - Err(_) => -1, - } + let (ret, written) = match c.update(sbuf, dbuf) { + // FIXME dlen + Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), + Err(e) => (e as c_int, 0), + }; + *dlen = written; + ret } pub unsafe extern "C" fn wrap_crypt_ctx_finish( @@ -140,12 +144,14 @@ pub unsafe extern "C" fn wrap_crypt_ctx_finish( dst: *mut c_uchar, dlen: *mut c_uint, ) -> c_int { + dbg!(*dlen); let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); let c: &mut T = &mut *ctx.cast(); - let ret = match c.finish(dbuf) { - Ok(_) => 1, - Err(_) => -1, + let (ret, written) = match c.finish(dbuf) { + // FIXME dlen + Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), + Err(e) => (e as c_int, 0), }; ctx.drop_in_place(); diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 1ff9bf267326e..039433003ec68 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -1,9 +1,16 @@ -use std::ffi::{c_void,c_int}; +use super::Init; +use std::ffi::{c_int, c_void}; -pub unsafe extern "C" fn wrap_init(_: *const c_void) -> c_int { - T::init() +pub unsafe extern "C" fn wrap_init(_: *mut c_void) -> c_int { + match T::init() { + Ok(_) => 0, + Err(_) => 1, + } } -pub unsafe extern "C" fn wrap_deinit(_: *const c_void) -> c_int { - T::deinit() +pub unsafe extern "C" fn wrap_deinit(_: *mut c_void) -> c_int { + match T::deinit() { + Ok(_) => 0, + Err(_) => 1, + } } From ae50c66d51bca4900fd0c66f21732d52e0180457 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 11 Jan 2023 20:18:27 -0500 Subject: [PATCH 11/34] Clean up dbg statements --- rust/mariadb/src/plugin/encryption_wrapper.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 04580feec37e1..5378143cecad0 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -25,10 +25,7 @@ pub unsafe extern "C" fn wrap_get_key( dstbuf: *mut c_uchar, buflen: *mut c_uint, ) -> c_uint { - dbg!(key_id); - dbg!(version); - dbg!(dstbuf); - dbg!(*buflen); + dbg!(key_id, version, dstbuf, *buflen); if dstbuf.is_null() { match dbg!(T::key_length(key_id, version)) { // FIXME: don't unwrap From 4b98a8ae6abd7b6872851633219ca435d26a7fbe Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 12 Jan 2023 01:00:32 -0500 Subject: [PATCH 12/34] Add helper scripts --- docker_build.sh | 11 +++++++++++ rust/examples/encryption/test.sql | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docker_build.sh diff --git a/docker_build.sh b/docker_build.sh new file mode 100644 index 0000000000000..b9d7da7362e97 --- /dev/null +++ b/docker_build.sh @@ -0,0 +1,11 @@ +# docker run -it -v $(pwd):/build/server ubuntu +apt-get update +apt-get build-dep mariadb-server +apt-get install -y build-essential libncurses5-dev gnutls-dev bison zlib1g-dev ccache g++ cmake ninja-build vim wget + +cd build +mkdir build-mariadb-server-debug +cd build-mariadb-server-debug +cmake ../server -DCONC_WITH_{UNITTEST,SSL}=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug -DWITH_SAFEMALLOC=OFF -DWITH_SSL=bundled -DMYSQL_MAINTAINER_MODE=OFF -G Ninja + +# cmake --build . --parallel 4 diff --git a/rust/examples/encryption/test.sql b/rust/examples/encryption/test.sql index 6c661bbb19fa1..98f26d36b23af 100644 --- a/rust/examples/encryption/test.sql +++ b/rust/examples/encryption/test.sql @@ -13,6 +13,7 @@ CREATE TABLE t1 ( INSERT INTO t1(id, str) VALUES (1, 'abc'), (2, 'def'), - (3, 'ghi'); + (3, 'ghi'), + (4, 'jkl'); FLUSH TABLES t1 FOR EXPORT; From ee8e9a1cd8eeeed8dd87fd213902e25ed5b0ee09 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 18 Jan 2023 04:50:52 -0500 Subject: [PATCH 13/34] Added proc macro to register plugins Basic plugin working with registration --- rust/bindings/Cargo.toml | 2 +- rust/bindings/build.rs | 10 +- rust/bindings/src/hand_imples.rs | 3 +- rust/examples/basic/Cargo.toml | 2 +- rust/examples/basic/src/lib.rs | 82 +--- rust/examples/debug-key-management/Cargo.toml | 2 +- rust/examples/debug-key-management/src/lib.rs | 134 +++--- rust/examples/encryption/Cargo.toml | 2 +- rust/examples/encryption/src/lib.rs | 92 +--- rust/examples/encryption/test.sql | 2 +- rust/macros/Cargo.toml | 7 +- rust/macros/src/lib.rs | 7 +- rust/macros/src/plugin.rs | 47 -- rust/macros/src/register_plugin.rs | 403 ++++++++++++++++++ rust/mariadb/Cargo.toml | 7 +- rust/mariadb/src/lib.rs | 69 ++- rust/mariadb/src/plugin.rs | 136 ++---- rust/mariadb/src/plugin/encryption.rs | 10 +- rust/mariadb/src/plugin/encryption_wrapper.rs | 302 +++++++------ rust/mariadb/src/plugin/variables.rs | 8 +- rust/mariadb/src/plugin/wrapper.rs | 71 ++- rust/rustfmt.toml | 6 + 22 files changed, 872 insertions(+), 532 deletions(-) delete mode 100644 rust/macros/src/plugin.rs create mode 100644 rust/macros/src/register_plugin.rs create mode 100644 rust/rustfmt.toml diff --git a/rust/bindings/Cargo.toml b/rust/bindings/Cargo.toml index 4dfdb20db0dc9..4417de0938517 100644 --- a/rust/bindings/Cargo.toml +++ b/rust/bindings/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mariadb-server-sys" +name = "mariadb-sys" version = "0.1.0" edition = '2021' diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 669eefbd4a9b6..80607043959c3 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -1,12 +1,13 @@ //! This file runs `cmake` as needed, then `bindgen` to produce the rust bindings -use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; -use bindgen::EnumVariation; use std::collections::HashSet; use std::env; use std::path::PathBuf; use std::process::Command; +use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; +use bindgen::EnumVariation; + // `math.h` seems to double define some things, To avoid this, we ignore them. const IGNORE_MACROS: [&str; 20] = [ "FE_DIVBYZERO", @@ -87,7 +88,12 @@ fn main() { }) // LLVM has some issues with long dobule and ABI compatibility // disabling the only relevant function here to suppress errors + .blocklist_function("strfroml") + .blocklist_function("strfromf64x") + .blocklist_function("strtof64x_l") + .blocklist_function("strtof64x") .blocklist_function("strtold") + .blocklist_function("strtold_l") // qvct, evct, qfcvt_r, ... .blocklist_function("[a-z]{1,2}cvt(?:_r)?") // c++ things that aren't supported diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index a071bb9116546..e676ce07a83de 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -1,6 +1,7 @@ -use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; use std::ffi::{c_char, c_int, c_uint, c_void}; +use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; + // Defined in service_encryption.h but not imported because of tilde syntax pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; diff --git a/rust/examples/basic/Cargo.toml b/rust/examples/basic/Cargo.toml index 82a070590997e..6c9bb133ef1f7 100644 --- a/rust/examples/basic/Cargo.toml +++ b/rust/examples/basic/Cargo.toml @@ -7,4 +7,4 @@ edition = '2021' crate-type = ["cdylib"] [dependencies] -mariadb-server = { path = "../../mariadb" } +mariadb = { path = "../../mariadb" } diff --git a/rust/examples/basic/src/lib.rs b/rust/examples/basic/src/lib.rs index 5501f60aa43f6..142dde50a58c0 100644 --- a/rust/examples/basic/src/lib.rs +++ b/rust/examples/basic/src/lib.rs @@ -7,14 +7,14 @@ #![allow(unused)] -use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::{new_null_st_maria_plugin, PluginType, PluginVarInfo, UnsafeSyncCell}; -use mariadb_server::plugin::{License, Maturity, SysVarAtomic, InitError, Init}; -use mariadb_server::sysvar_atomic; use std::cell::UnsafeCell; use std::ffi::c_void; -use std::sync::atomic::Ordering; -use std::sync::atomic::{AtomicU32, AtomicUsize}; +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; + +use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb::plugin::prelude::*; +use mariadb::plugin::{register_plugin, PluginType, PluginVarInfo, SysVarAtomic}; +use mariadb::sysvar_atomic; struct BasicKeyMgt; @@ -34,9 +34,11 @@ impl Init for BasicKeyMgt { impl KeyManager for BasicKeyMgt { fn get_latest_key_version(key_id: u32) -> Result { + eprintln!("get latest key version with {key_id}"); static KCOUNT: AtomicU32 = AtomicU32::new(1); - eprintln!("get key version with {key_id}"); - Ok(KCOUNT.fetch_add(1, Ordering::Relaxed)) + let ret = KCOUNT.fetch_add(1, Ordering::Relaxed); + dbg!(ret); + Ok(ret) } fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { @@ -58,56 +60,18 @@ impl KeyManager for BasicKeyMgt { } } -// PROC: should mangle names with type name - -// C plugins manually create this, but we can automate -static _ENCRYPTION_ST: ::mariadb_server::plugin::UnsafeSyncCell< - ::mariadb_server::bindings::st_mariadb_encryption, -> = unsafe { - ::mariadb_server::plugin::UnsafeSyncCell::new(mariadb_server::bindings::st_mariadb_encryption { - interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, - get_latest_key_version: Some( - mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, - ), - get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), - crypt_ctx_size: None, - crypt_ctx_init: None, - crypt_ctx_update: None, - crypt_ctx_finish: None, - encrypted_length: None, - }) -}; - -// If we compile dynamically, use these names. Otherwise, we need to use -// `buildin_maria_NAME_...` -#[no_mangle] -static _maria_plugin_interface_version_: ::std::ffi::c_int = - ::mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; - -#[no_mangle] -static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = - ::std::mem::size_of::() as ::std::ffi::c_int; - -#[no_mangle] -static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ - ::mariadb_server::bindings::st_maria_plugin { - type_: PluginType::MariaEncryption as i32, - info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), - name: mariadb_server::cstr::cstr!("basic_key_management").as_ptr(), - author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), - descr: mariadb_server::cstr::cstr!("Basic key management plugin").as_ptr(), - license: License::Gpl as i32, - init: Some(mariadb_server::plugin::wrapper::wrap_init::), - deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), - version: 0x0010, - status_vars: ::std::ptr::null_mut(), - system_vars: ::std::ptr::null_mut(), - version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), - maturity: Maturity::Experimental as u32, - }, - // End with a null object - ::mariadb_server::plugin::new_null_st_maria_plugin(), -]; +register_plugin! { + BasicKeyMgt, + ptype: PluginType::MariaEncryption, + name: "basic_key_management", + author: "Trevor Gross", + description: "Basic key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: BasicKeyMgt, // optional + encryption: false, +} #[cfg(test)] mod tests { @@ -115,7 +79,7 @@ mod tests { #[test] fn print_statics() { - unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; + // unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; dbg!(&_maria_plugin_interface_version_); dbg!(&_maria_sizeof_struct_st_plugin_); unsafe { dbg!(&_maria_plugin_declarations_) }; diff --git a/rust/examples/debug-key-management/Cargo.toml b/rust/examples/debug-key-management/Cargo.toml index 2044f1ad2b35a..c8cba89e8b6db 100644 --- a/rust/examples/debug-key-management/Cargo.toml +++ b/rust/examples/debug-key-management/Cargo.toml @@ -7,4 +7,4 @@ edition = '2021' crate-type = ["cdylib"] [dependencies] -mariadb-server = { path = "../../mariadb" } +mariadb = { path = "../../mariadb" } diff --git a/rust/examples/debug-key-management/src/lib.rs b/rust/examples/debug-key-management/src/lib.rs index 58e720796a31b..52e537df40198 100644 --- a/rust/examples/debug-key-management/src/lib.rs +++ b/rust/examples/debug-key-management/src/lib.rs @@ -7,18 +7,20 @@ #![allow(unused)] -use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb_server::plugin::{Init, License, Maturity, SysVarAtomic}; -use mariadb_server::plugin::{InitError, PluginType, PluginVarInfo}; -use mariadb_server::sysvar_atomic; use std::cell::UnsafeCell; use std::ffi::c_void; -use std::sync::atomic::AtomicU32; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicU32, Ordering}; + +use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb::plugin::prelude::*; +use mariadb::plugin::{ + register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, +}; +use mariadb::sysvar_atomic; const KEY_LENGTH: usize = 4; -static KEY_VERSION: AtomicU32 = AtomicU32::new(0); +static KEY_VERSION: AtomicU32 = AtomicU32::new(1); static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { ty: u32, @@ -65,7 +67,7 @@ impl KeyManager for DebugKeyMgmt { } // Copy our slice to the buffer, return the copied length - dst.copy_from_slice(key_buf.as_slice()); + dst[..key_buf.len()].copy_from_slice(key_buf.as_slice()); Ok(()) } @@ -106,66 +108,64 @@ impl KeyManager for DebugKeyMgmt { // builtin_debug_key_sizeof_struct_st_plugin // builtin_debug_key_plugin -#[repr(transparent)] -struct UcWrap(UnsafeCell); - -impl UcWrap { - const fn as_ptr(&self) -> *const T { - self.0.get() - } +register_plugin! { + DebugKeyMgmt, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: DebugKeyMgmt, // optional + encryption: false, } -unsafe impl Send for UcWrap {} -unsafe impl Sync for UcWrap {} - // PROC: should mangle names with type name -static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb_server::bindings::st_mysql_sys_var; 2]> = - UcWrap(UnsafeCell::new([ - KEY_VERSION_SYSVAR.as_ptr().cast_mut(), - ::std::ptr::null_mut(), - ])); - -static _ENCRYPTION_ST: UcWrap = UcWrap( - UnsafeCell::new(mariadb_server::bindings::st_mariadb_encryption { - interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, - get_latest_key_version: Some( - mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, - ), - get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), - crypt_ctx_size: None, - crypt_ctx_init: None, - crypt_ctx_update: None, - crypt_ctx_finish: None, - encrypted_length: None, - }), -); - -#[no_mangle] -static _maria_plugin_interface_version_: ::std::ffi::c_int = - mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; - -#[no_mangle] -static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = - ::std::mem::size_of::() as ::std::ffi::c_int; - -#[no_mangle] -static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ - mariadb_server::bindings::st_maria_plugin { - type_: PluginType::MariaEncryption as i32, - info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), - name: mariadb_server::cstr::cstr!("debug_key_management").as_ptr(), - author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), - descr: mariadb_server::cstr::cstr!("Debug key management plugin").as_ptr(), - license: License::Gpl as i32, - init: Some(mariadb_server::plugin::wrapper::wrap_init::), - deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), - version: 0x0010, - status_vars: ::std::ptr::null_mut(), - system_vars: _INTERNAL_SYSVARS.0.get().cast(), - version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), - maturity: Maturity::Experimental as u32, - }, - // End with a null object - ::mariadb_server::plugin::new_null_st_maria_plugin(), -]; +// static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb::bindings::st_mysql_sys_var; 2]> = +// UcWrap(UnsafeCell::new([ +// KEY_VERSION_SYSVAR.as_ptr().cast_mut(), +// ::std::ptr::null_mut(), +// ])); + +// static _ENCRYPTION_ST: UcWrap = +// UcWrap(UnsafeCell::new(mariadb::bindings::st_mariadb_encryption { +// interface_version: mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, +// get_latest_key_version: Some(DebugKeyMgmt::wrap_get_latest_key_version), +// get_key: Some(DebugKeyMgmt::wrap_get_key), +// crypt_ctx_size: None, +// crypt_ctx_init: None, +// crypt_ctx_update: None, +// crypt_ctx_finish: None, +// encrypted_length: None, +// })); + +// #[no_mangle] +// static _maria_plugin_interface_version_: ::std::ffi::c_int = +// mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; + +// #[no_mangle] +// static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = +// ::std::mem::size_of::() as ::std::ffi::c_int; + +// #[no_mangle] +// static mut _maria_plugin_declarations_: [mariadb::bindings::st_maria_plugin; 2] = [ +// mariadb::bindings::st_maria_plugin { +// type_: PluginType::MariaEncryption as i32, +// info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), +// name: mariadb::cstr::cstr!("debug_key_management").as_ptr(), +// author: mariadb::cstr::cstr!("Trevor Gross").as_ptr(), +// descr: mariadb::cstr::cstr!("Debug key management plugin").as_ptr(), +// license: License::Gpl as i32, +// init: Some(DebugKeyMgmt::wrap_init), +// deinit: Some(DebugKeyMgmt::wrap_deinit), +// version: 0x0010, +// status_vars: ::std::ptr::null_mut(), +// system_vars: _INTERNAL_SYSVARS.0.get().cast(), +// version_info: mariadb::cstr::cstr!("0.1").as_ptr(), +// maturity: Maturity::Experimental as u32, +// }, +// // End with a null object +// ::mariadb::plugin::wrapper::new_null_st_maria_plugin(), +// ]; diff --git a/rust/examples/encryption/Cargo.toml b/rust/examples/encryption/Cargo.toml index a96b622431a53..6885eb97d6170 100644 --- a/rust/examples/encryption/Cargo.toml +++ b/rust/examples/encryption/Cargo.toml @@ -7,7 +7,7 @@ edition = '2021' crate-type = ["cdylib"] [dependencies] -mariadb-server = { path = "../../mariadb" } +mariadb = { path = "../../mariadb" } aes-gcm = "0.10" sha2 = "0.10" rand = "0.8" diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs index cac26f303f2fb..7cd19ac28ca84 100644 --- a/rust/examples/encryption/src/lib.rs +++ b/rust/examples/encryption/src/lib.rs @@ -4,8 +4,6 @@ #![allow(unused)] -use rand::Rng; -use sha2::{Digest, Sha256 as Hasher}; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -14,21 +12,11 @@ use aes_gcm::{ Aes256Gcm, Nonce, // Or `Aes128Gcm` }; - -use mariadb_server::plugin::encryption::{Encryption, Flags, KeyError, KeyManager, EncryptionError}; -use mariadb_server::plugin::{Init, InitError, License, Maturity, PluginType}; -// use mariadb_server::plugin::Init; -// use mariadb_server::plugin::prelude::*; - -// plugin_encryption!{ -// type: RustEncryption, -// init: RustEncryptionInit, // optional -// name: "example_key_management", -// author: "MariaDB Team", -// description: "Example key management plugin using AES", -// license: GPL, -// stability: EXPERIMENTAL -// } +use mariadb::plugin::encryption::{Encryption, EncryptionError, Flags, KeyError, KeyManager}; +use mariadb::plugin::prelude::*; +use mariadb::plugin::wrapper::WrapInit; +use rand::Rng; +use sha2::{Digest, Sha256 as Hasher}; /// Range of key rotations, as seconds const KEY_ROTATION_MIN: f32 = 45.0; @@ -167,64 +155,18 @@ impl Encryption for RustEncryption { } } -// C plugins manually create this, but we can automate -static _ENCRYPTION_ST: ::mariadb_server::plugin::UnsafeSyncCell< - ::mariadb_server::bindings::st_mariadb_encryption, -> = unsafe { - ::mariadb_server::plugin::UnsafeSyncCell::new(mariadb_server::bindings::st_mariadb_encryption { - interface_version: mariadb_server::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, - get_latest_key_version: Some( - mariadb_server::plugin::encryption_wrapper::wrap_get_latest_key_version::, - ), - get_key: Some(mariadb_server::plugin::encryption_wrapper::wrap_get_key::), - crypt_ctx_size: Some( - mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_size::, - ), - crypt_ctx_init: Some( - mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_init::, - ), - crypt_ctx_update: Some( - mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_update::, - ), - crypt_ctx_finish: Some( - mariadb_server::plugin::encryption_wrapper::wrap_crypt_ctx_finish::, - ), - encrypted_length: Some( - mariadb_server::plugin::encryption_wrapper::wrap_encrypted_length::, - ), - }) -}; - -// If we compile dynamically, use these names. Otherwise, we need to use -// `buildin_maria_NAME_...` -#[no_mangle] -static _maria_plugin_interface_version_: ::std::ffi::c_int = - ::mariadb_server::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; - -#[no_mangle] -static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = - ::std::mem::size_of::() as ::std::ffi::c_int; - -#[no_mangle] -static mut _maria_plugin_declarations_: [mariadb_server::bindings::st_maria_plugin; 2] = [ - ::mariadb_server::bindings::st_maria_plugin { - type_: PluginType::MariaEncryption as i32, - info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), - name: mariadb_server::cstr::cstr!("encryption_example").as_ptr(), - author: mariadb_server::cstr::cstr!("Trevor Gross").as_ptr(), - descr: mariadb_server::cstr::cstr!("Example key management / encryption plugin").as_ptr(), - license: License::Gpl as i32, - init: Some(mariadb_server::plugin::wrapper::wrap_init::), - deinit: Some(mariadb_server::plugin::wrapper::wrap_deinit::), - version: 0x0010, - status_vars: ::std::ptr::null_mut(), - system_vars: ::std::ptr::null_mut(), - version_info: mariadb_server::cstr::cstr!("0.1").as_ptr(), - maturity: Maturity::Experimental as u32, - }, - // End with a null object - ::mariadb_server::plugin::new_null_st_maria_plugin(), -]; +register_plugin! { + RustEncryption, + ptype: PluginType::MariaEncryption, + name: "encryption_example", + author: "Trevor Gross", + description: "Example key management / encryption plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: RustEncryption, // optional + encryption: true, +} #[cfg(test)] mod tests { diff --git a/rust/examples/encryption/test.sql b/rust/examples/encryption/test.sql index 98f26d36b23af..df3059e00e12b 100644 --- a/rust/examples/encryption/test.sql +++ b/rust/examples/encryption/test.sql @@ -1,4 +1,4 @@ - +SET GLOBAL innodb_encryption_threads=1; SET GLOBAL innodb_encrypt_tables=ON; SET SESSION innodb_default_encryption_key_id=100; diff --git a/rust/macros/Cargo.toml b/rust/macros/Cargo.toml index b3257610fa15a..3f7735942834f 100644 --- a/rust/macros/Cargo.toml +++ b/rust/macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mariadb-server-macros" +name = "mariadb-macros" version = "0.1.0" edition = "2021" @@ -12,8 +12,9 @@ proc-macro = true # lazy_static = "1.4.0" proc-macro2 = "1.0.49" quote = "1.0.23" -syn = { version = "1.0.107", features = ["full", "extra-traits", "parsing"] } +syn = { version = "1.0.107", features = [], default-features = false } +# syn = { version = "1.0.107", features = ["full", "extra-traits", "parsing"] } [dev-dependencies] # trybuild = { version = "1.0.65", features = ["diff"] } -mariadb-server = { path = "../mariadb" } +mariadb = { path = "../mariadb" } diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index af014a2960e76..d5e21ffa31a01 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -7,10 +7,11 @@ #![allow(clippy::must_use_candidate)] #![allow(clippy::cast_possible_truncation)] -mod plugin; +mod register_plugin; use proc_macro::TokenStream; +/// Macro to use to register a plugin #[proc_macro] -pub fn plugin(item: TokenStream) -> TokenStream { - plugin::entry(item) +pub fn register_plugin(item: TokenStream) -> TokenStream { + register_plugin::entry(item) } diff --git a/rust/macros/src/plugin.rs b/rust/macros/src/plugin.rs deleted file mode 100644 index bdcd6826089b0..0000000000000 --- a/rust/macros/src/plugin.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Proc macro plugin! to register a plugin -//! -//! Desired usage: -//! ``` -//! plugin_encryption!{ -//! type: RustEncryption, -//! init: RustEncryptionInit, // optional -//! keymgr: RustEncryptionInit, // optional -//! encryption: RustEncryptionInit, // optional -//! name: "example_key_management", -//! author: "MariaDB Team", -//! description: "Example key management plugin using AES", -//! license: GPL, -//! stability: EXPERIMENTAL -//! } -//! ``` -//! -//! Desired output: -//! -//! ``` -//! bindings::st_maria_plugin { -//! type_: ptype as c_int, -//! info: *mut c_void, -//! name: cstr::cstr!($name).as_ptr(), -//! author: cstr::cstr!($author).as_ptr(), -//! descr: cstr::cstr!($description).as_ptr(), -//! license: c_int, -//! init: Option c_int>, -//! deinit: Option c_int>, -//! version: vers as c_uint, -//! status_vars: *mut st_mysql_show_var, -//! system_vars: *mut *mut st_mysql_sys_var, -//! version_info: plugin!( -//! @def -//! cstr::cstr!($vers).as_ptr(), -//! $(cstr::cstr!($comment).as_ptr())? -//! ),, -//! maturity: maturity as c_uint, -//! } -//! ``` - -use proc_macro::TokenStream; - -pub fn entry(item: TokenStream) -> TokenStream { - dbg!(&item); - item -} diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs new file mode 100644 index 0000000000000..70c4a409fe3a6 --- /dev/null +++ b/rust/macros/src/register_plugin.rs @@ -0,0 +1,403 @@ +// FIXME: Doesn't yet verify plugin type is Encryption +#![allow(unused)] + +use proc_macro::TokenStream; +use proc_macro2::{Literal, Span, TokenTree}; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::Group; +use syn::{ + parse_macro_input, parse_quote, Attribute, DeriveInput, Error, Expr, ExprLit, FieldValue, + Ident, ImplItem, ImplItemType, Item, ItemImpl, Lit, LitStr, Path, PathSegment, Token, Type, + TypePath, TypeReference, +}; + +/// Entrypoint for this proc macro +pub fn entry(tokens: TokenStream) -> TokenStream { + let tokens_pm2: proc_macro2::TokenStream = tokens.clone().into(); + let input = parse_macro_input!(tokens as PluginInfo); + let plugindef = input.to_encryption_struct(); + match plugindef { + Ok(ts) => ts.into_output(), + Err(e) => e.into_compile_error().into(), + } +} + +/// A representation of the contents of a registration macro. This macro will be +/// the same for +#[derive(Clone, Debug)] +struct PluginInfo { + /// The main type that has required methods implemented on it + main_ty: Ident, + span: Span, + ptype: Option, + name: Option, + author: Option, + description: Option, + license: Option, + maturity: Option, + version: Option, + init: Option, + encryption: Option, +} + +impl Parse for PluginInfo { + fn parse(input: ParseStream) -> syn::Result { + let main_ty = input.parse()?; + // FIXME: span is only the beginning + let span = input.span(); + let mut ret = Self::new(main_ty, span); + let _: Token![,] = input.parse()?; + + let fields = Punctuated::::parse_terminated(input)?; + let mut field_order: Vec = Vec::new(); + for field in fields.clone() { + let syn::Member::Named(name) = &field.member else { + return Err(Error::new_spanned(field, "missing field name")); + }; + + let name_str = name.to_string(); + let expr = field.expr; + + match name_str.as_str() { + "ptype" => ret.ptype = Some(expr), + "name" => ret.name = Some(expr), + "author" => ret.author = Some(expr), + "description" => ret.description = Some(expr), + "license" => ret.license = Some(expr), + "maturity" => ret.maturity = Some(expr), + "version" => ret.version = Some(expr), + "init" => ret.init = Some(expr), + "encryption" => ret.encryption = Some(expr), + _ => { + return Err(Error::new_spanned( + name, + format!("unexpected field '{name_str}'"), + )) + } + } + field_order.push(name_str); + } + + if let Err(msg) = verify_field_order(field_order.as_slice()) { + return Err(Error::new_spanned(fields, msg)); + } + Ok(ret) + } +} + +impl PluginInfo { + fn new(main_ty: Ident, span: Span) -> Self { + Self { + main_ty, + span, + ptype: None, + name: None, + author: None, + description: None, + license: None, + maturity: None, + version: None, + init: None, + encryption: None, + } + } + + /// Ensure we have the fields that are required for all plugin types + fn validate_correct_fields( + &self, + required: &[&str], + optional: &[&str], + ptype: &str, + ) -> syn::Result<()> { + // These are all required for all plugin types + let name_map = [ + (&self.ptype, "ptype"), + (&self.name, "name"), + (&self.author, "author"), + (&self.description, "description"), + (&self.license, "license"), + (&self.maturity, "maturity"), + (&self.version, "version"), + (&self.init, "init"), + (&self.encryption, "encryption"), + ]; + + let mut req = vec![ + "ptype", + "name", + "author", + "description", + "license", + "maturity", + "version", + ]; + req.extend_from_slice(required); + + for req_field in req.iter() { + let (field_val, fname) = name_map.iter().find(|f| f.1 == *req_field).unwrap(); + + if field_val.is_none() { + let msg = format!("field '{fname}' is expected for plugins of type {ptype}, but not provided\n(in macro 'register_plugin')"); + return Err(Error::new(self.span, msg)); + } + } + + for (field, fname) in name_map { + if field.is_some() && !req.contains(&fname) && !optional.contains(&fname) { + let msg = format!("field '{fname}' is not expected for plugins of type {ptype}\n(in macro 'register_plugin')"); + return Err(Error::new_spanned(field.as_ref().unwrap(), msg)); + } + } + + Ok(()) + } + + /// Ensure we have the fields required for an encryption plugin + fn validate_as_encryption(&self) -> syn::Result<()> { + let optional = ["init"]; + let required = ["encryption"]; + self.validate_correct_fields(&required, &optional, "encryption")?; + Ok(()) + } + + /// Turn `self` into a tokenstream of a single `st_maria_plugin` for an + /// encryption struct. Uses `idx` to mangle the name and avoid conflicts + fn to_encryption_struct(self) -> syn::Result { + self.validate_as_encryption()?; + + let type_ = &self.main_ty; + let name = expect_litstr(&self.name)?; + let plugin_st_name = Ident::new(&format!("_ST_PLUGIN_{}", name.value()), Span::call_site()); + + let ty_as_wkeymgt = quote! {<#type_ as ::mariadb::plugin::encryption_wrapper::WrapKeyMgr>}; + let ty_as_wenc = quote! {<#type_ as ::mariadb::plugin::encryption_wrapper::WrapEncryption>}; + let interface_version = + quote! {::mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as ::std::ffi::c_int}; + let get_key_vers = quote! {Some(#ty_as_wkeymgt::wrap_get_latest_key_version)}; + let get_key = quote! {Some(#ty_as_wkeymgt::wrap_get_key)}; + + let (crypt_size, crypt_init, crypt_update, crypt_finish, crypt_len); + if expect_bool(&self.encryption)? { + crypt_size = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_size)}; + crypt_init = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_init)}; + crypt_update = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_update)}; + crypt_finish = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_finish)}; + crypt_len = quote! {Some(#ty_as_wenc::wrap_encrypted_length)}; + } else { + let none = quote! {None}; + ( + crypt_size, + crypt_init, + crypt_update, + crypt_finish, + crypt_len, + ) = (none.clone(), none.clone(), none.clone(), none.clone(), none); + } + + let info_struct = quote! { + static #plugin_st_name: ::mariadb::plugin::wrapper::UnsafeSyncCell< + ::mariadb::bindings::st_mariadb_encryption, + > = unsafe { + ::mariadb::plugin::wrapper::UnsafeSyncCell::new( + ::mariadb::bindings::st_mariadb_encryption { + interface_version: #interface_version, + get_latest_key_version: #get_key_vers, + get_key: #get_key, + crypt_ctx_size: #crypt_size, + crypt_ctx_init: #crypt_init, + crypt_ctx_update: #crypt_update, + crypt_ctx_finish: #crypt_finish, + encrypted_length: #crypt_len, + } + ) + }; + }; + + let version = version_int(&expect_litstr(&self.version)?.value()) + .map_err(|e| Error::new_spanned(&self.version, e))?; + let author = expect_litstr(&self.author)?; + let description = expect_litstr(&self.description)?; + let license = self.license.unwrap(); + let maturity = self.maturity.unwrap(); + let ptype = self.ptype.unwrap(); + + let (fn_init, fn_deinit); + if let Some(init_ty) = self.init { + let ty_as_init = quote! {<#init_ty as ::mariadb::plugin::wrapper::WrapInit>}; + fn_init = quote! {Some(#ty_as_init::wrap_init)}; + fn_deinit = quote! {Some(#ty_as_init::wrap_deinit)}; + } else { + let none = quote! {None}; + (fn_init, fn_deinit) = (none.clone(), none); + } + + let plugin_struct = quote! { + ::mariadb::bindings::st_maria_plugin { + type_: #ptype.to_ptype_registration(), + info: #plugin_st_name.as_ptr().cast_mut().cast(), + name: mariadb::cstr::cstr!(#name).as_ptr(), + author: mariadb::cstr::cstr!(#author).as_ptr(), + descr: mariadb::cstr::cstr!(#description).as_ptr(), + license: #license.to_license_registration(), + init: #fn_init, + deinit: #fn_deinit, + version: #version as ::std::ffi::c_uint, + status_vars: ::std::ptr::null_mut(), + system_vars: ::std::ptr::null_mut(), + version_info: mariadb::cstr::cstr!("0.1").as_ptr(), + maturity: #maturity.to_maturity_registration(), + }, + }; + + Ok(PluginDef { + name: name.value(), + info_struct, + plugin_struct, + }) + } +} + +/// Contains a struct definition of type `st_mariadb_encryption` or whatever is +/// applicable, plus the struct of `st_maria_plugin` that references it +struct PluginDef { + name: String, + info_struct: proc_macro2::TokenStream, + plugin_struct: proc_macro2::TokenStream, +} + +impl PluginDef { + fn into_output(self) -> TokenStream { + let make_ident = |s| Ident::new(s, Span::call_site()); + let make_ident_fmt = |s: String| Ident::new(s.as_str(), Span::call_site()); + // let vers_idt_stc = + // make_ident_fmt(format!("builtin_{}_plugin_interface_version", self.name)); + let vers_idt_dyn = make_ident("_maria_plugin_interface_version_"); + // let size_idt_stc = make_ident_fmt(format!("builtin_{}_sizeof_struct_st_plugin", self.name)); + let size_idt_dyn = make_ident("_maria_sizeof_struct_st_plugin_"); + // let decl_idt_stc = make_ident_fmt(format!("builtin_{}_plugin", self.name)); + let decl_idt_dyn = make_ident("_maria_plugin_declarations_"); + + let plugin_ty = quote! {::mariadb::bindings::st_maria_plugin}; + let version_val = + quote! {mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int}; + let size_val = quote! {::std::mem::size_of::<#plugin_ty>() as ::std::ffi::c_int}; + + let usynccell = quote! {::mariadb::plugin::wrapper::UnsafeSyncCell}; + let null_ps = quote! {::mariadb::plugin::wrapper::new_null_st_maria_plugin()}; + + let is = self.info_struct; + let ps = self.plugin_struct; + + let ret: TokenStream = quote! { + // Different config based on statically or dynamically lynked + + // #[no_mangle] + // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] + // static #vers_idt_stc: ::std::ffi::c_int = #version_val; + + #[no_mangle] + // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + static #vers_idt_dyn: ::std::ffi::c_int = #version_val; + + // #[no_mangle] + // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] + // static #size_idt_stc: ::std::ffi::c_int = #size_val; + + #[no_mangle] + // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + static #size_idt_dyn: ::std::ffi::c_int = #size_val; + + // #[no_mangle] + // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] + // static #decl_idt_stc: [#usynccell<#plugin_ty>; 2] = unsafe { [ + // #usynccell::new(#ps), + // #usynccell::new(#null_ps), + // ] }; + + #[no_mangle] + // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + static #decl_idt_dyn: [#usynccell<#plugin_ty>; 2] = unsafe { [ + #usynccell::new(#ps), + #usynccell::new(#null_ps), + ] }; + + #is + } + .into(); + + ret + } +} + +/// Verify attribute order +fn verify_field_order(fields: &[String]) -> Result<(), String> { + let mut expected_order = vec![ + "ptype", + "name", + "author", + "description", + "license", + "maturity", + "version", + "init", + "encryption", + ]; + + expected_order.retain(|expected| fields.iter().any(|f| f == expected)); + + if expected_order != fields { + Err(format!( + "fields not in expected order. reorder as:\n{:?}", + expected_order + )) + } else { + Ok(()) + } +} + +/// Get the field as a boolean +fn expect_bool(field_opt: &Option) -> syn::Result { + let field = field_opt.as_ref().unwrap(); + + if field == &parse_quote! {true} { + Ok(true) + } else if field == &parse_quote! {false} { + Ok(false) + } else { + let msg = "unexpected value: only 'true' or 'false' allowed"; + Err(Error::new_spanned(field, msg)) + } +} + +fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { + let field = field_opt.as_ref().unwrap(); + let Expr::Lit(lit) = field else { + let msg = "expected literal expression for this field"; + return Err(Error::new_spanned(field, msg)); + }; + let Lit::Str(litstr) = &lit.lit else { + let msg = "only literal strings are allowed for this field"; + return Err(Error::new_spanned(field, msg)); + }; + + Ok(litstr) +} + +/// Convert a string likx "1.2" to a hex like "0x0102" +fn version_int(s: &str) -> Result { + const USAGE_MSG: &str = r#"expected a two position semvar string, e.g. "1.2""#; + if s.chars().filter(|x| *x == '.').count() != 1 { + return Err(USAGE_MSG.to_owned()); + } + + let splt = s.split_once('.').unwrap(); + let fmt_err = |e| format!("{e}\n{USAGE_MSG}"); + + let major: u16 = splt.0.parse::().map_err(fmt_err)?.into(); + let minor: u16 = splt.1.parse::().map_err(fmt_err)?.into(); + let res: u16 = (major << 8) + minor; + + Ok(res) +} diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index b6e56321aed1b..02d921c2b842b 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "mariadb-server" +name = "mariadb" version = "0.1.0" edition = '2021' [dependencies] -mariadb-server-sys = { path = "../bindings" } -mariadb-server-macros = { path = "../macros" } +mariadb-sys = { path = "../bindings" } +mariadb-macros = { path = "../macros" } cstr = "0.2.11" concat-idents = "1.1.4" +time = { version = "0.3.17", features = ["formatting"]} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 8cd6765506a14..cc5051c136d78 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -1,13 +1,74 @@ //! Crate representing safe abstractions over MariaDB bindings #![allow(unused)] +use time::{format_description, OffsetDateTime}; + pub mod plugin; #[doc(hidden)] -pub use mariadb_server_sys as bindings; +pub use cstr; +#[doc(hidden)] +pub use mariadb_sys as bindings; #[doc(hidden)] -pub use cstr; +pub fn log_timestamped_message(title: &str, msg: &str) { + let t = time::OffsetDateTime::now_utc(); + let fmt = time::format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]", + ) + .unwrap(); + let mut to_print = t.format(&fmt).unwrap(); + to_print.push(' '); + to_print.push_str(title); + to_print.push_str(msg); + eprintln!("{to_print}"); +} -#[doc(inline)] -pub use mariadb_server_macros::plugin; +#[macro_export] +macro_rules! error { + (target: $target:expr, $($msg:tt)+) => {{ + let mut tmp = "[Error] ".to_owned(); + tmp.push_str($target); + tmp.push_str(": "); + $crate::log_timestamped_message(&tmp, &format!($($msg)+)); + }}; + ($($msg:tt)+) => { + $crate::log_timestamped_message("[Error]: ", format!($($msg)+)); + } +} +#[macro_export] +macro_rules! warn { + (target: $target:expr, $($msg:tt)+) => {{ + let mut tmp = "[Warn] ".to_owned(); + tmp.push_str($target); + tmp.push_str(": "); + $crate::log_timestamped_message(&tmp, &format!($($msg)+)); + }}; + ($($msg:tt)+) => { + $crate::log_timestamped_message("[Warn]", format!($($msg)+)); + } +} +#[macro_export] +macro_rules! info { + (target: $target:expr, $($msg:tt)+) => {{ + let mut tmp = "[Info] ".to_owned(); + tmp.push_str($target); + tmp.push_str(": "); + $crate::log_timestamped_message(&tmp, &format!($($msg)+)); + }}; + ($($msg:tt)+) => { + $crate::log_timestamped_message("[Info]", format!($($msg)+)); + } +} +#[macro_export] +macro_rules! debug { + (target: $target:expr, $($msg:tt)+) => {{ + let mut tmp = "[Debuf] ".to_owned(); + tmp.push_str($target); + tmp.push_str(": "); + $crate::log_timestamped_message(&tmp, &format!($($msg)+)); + }}; + ($($msg:tt)+) => { + $crate::log_timestamped_message("[Debuf]", format!($($msg)+)); + } +} diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 65f59c8388a00..15ea8e61f874f 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -2,18 +2,23 @@ // use std::cell::UnsafeCell; -use std::ptr; -use std::{cell::UnsafeCell, ffi::c_void}; +use std::ffi::{c_int, c_uint}; -use mariadb_server_sys as bindings; +use mariadb_sys as bindings; pub mod encryption; #[doc(hidden)] pub mod encryption_wrapper; mod variables; #[doc(hidden)] pub mod wrapper; +pub use mariadb_macros::register_plugin; pub use variables::{PluginVarInfo, SysVarAtomic}; +/// Commonly used plugin types for reexport +pub mod prelude { + pub use super::{register_plugin, Init, InitError, License, Maturity, PluginType}; +} + /// Defines possible licenses for plugins #[non_exhaustive] #[derive(Clone, Copy, Debug)] @@ -33,6 +38,11 @@ impl License { _ => None, } } + + #[doc(hidden)] + pub const fn to_license_registration(self) -> c_int { + self as c_int + } } /// Defines a type of plugin. This determines the required implementation. @@ -47,11 +57,19 @@ pub enum PluginType { MyReplication = bindings::MYSQL_REPLICATION_PLUGIN as isize, MyAuthentication = bindings::MYSQL_AUTHENTICATION_PLUGIN as isize, MariaPasswordValidation = bindings::MariaDB_PASSWORD_VALIDATION_PLUGIN as isize, + /// Use this plugin type both for key managers and for full encryption plugins MariaEncryption = bindings::MariaDB_ENCRYPTION_PLUGIN as isize, MariaDataType = bindings::MariaDB_DATA_TYPE_PLUGIN as isize, MariaFunction = bindings::MariaDB_FUNCTION_PLUGIN as isize, } +impl PluginType { + #[doc(hidden)] + pub const fn to_ptype_registration(self) -> c_int { + self as c_int + } +} + /// Defines possible licenses for plugins #[non_exhaustive] pub enum Maturity { @@ -63,9 +81,16 @@ pub enum Maturity { Stable = bindings::MariaDB_PLUGIN_MATURITY_STABLE as isize, } +impl Maturity { + #[doc(hidden)] + pub const fn to_maturity_registration(self) -> c_uint { + self as c_uint + } +} + pub struct InitError; -/// Initialize state +/// Implement this trait if your plugin requires init or deinit functions pub trait Init { fn init() -> Result<(), InitError> { Ok(()) @@ -75,106 +100,3 @@ pub trait Init { Ok(()) } } - -/// New struct with all null values -#[doc(hidden)] -pub const fn new_null_st_maria_plugin() -> bindings::st_maria_plugin { - bindings::st_maria_plugin { - type_: 0, - info: ptr::null_mut(), - name: ptr::null(), - author: ptr::null(), - descr: ptr::null(), - license: 0, - init: None, - deinit: None, - version: 0, - status_vars: ptr::null_mut(), - system_vars: ptr::null_mut(), - version_info: ptr::null(), - maturity: 0, - } -} - -/// Used for plugin registrations, which are in global scope. -#[doc(hidden)] -#[derive(Debug)] -#[repr(transparent)] -pub struct UnsafeSyncCell(UnsafeCell); - -impl UnsafeSyncCell { - /// # Safety - /// - /// This inner value be used in a Sync/Send way - pub const unsafe fn new(value: T) -> Self { - Self(UnsafeCell::new(value)) - } - - pub const fn as_ptr(&self) -> *const T { - self.0.get() - } -} - -unsafe impl Send for UnsafeSyncCell {} -unsafe impl Sync for UnsafeSyncCell {} - -// #[macro_export] -// macro_rules! plugin { -// (@def $default:expr, ) => {$default}; -// (@def $default:expr, $replace:expr) => {$replace}; - -// ( -// ty: $type:expr, -// name: $name:expr, -// author: $author:expr, -// description: $description:expr, -// license: $license:expr, -// version: $version:expr, -// $(, version_info: $version_info:expr)? -// maturity: $maturity:expr, -// // Type to use init/deinit functions on, if applicable -// $(, init: $init:ty)? -// $(,)? // trailing comma - -// ) => { -// use std::ffi::{c_int, c_uint}; -// use $crate::cstr; - -// // Use these intermediates to validate types -// const ptype: PluginType = $type; -// const ltype: License = $license -// const vers: u32 = $version; -// const maturity: Maturity = $maturity; - -// bindings::st_maria_plugin { -// type_: ptype as c_int, -// info: *mut c_void, -// name: cstr::cstr!($name).as_ptr(), -// author: cstr::cstr!($author).as_ptr(), -// descr: cstr::cstr!($description).as_ptr(), -// license: c_int, -// init: Option c_int>, -// deinit: Option c_int>, -// version: vers as c_uint, -// status_vars: *mut st_mysql_show_var, -// system_vars: *mut *mut st_mysql_sys_var, -// version_info: plugin!( -// @def -// cstr::cstr!($vers).as_ptr(), -// $(cstr::cstr!($comment).as_ptr())? -// ),, -// maturity: maturity as c_uint, -// } - -// }; -// } - -// plugin_encryption!{ -// type: RustEncryption, -// init: RustEncryptionInit, // optional -// name: "example_key_management", -// author: "MariaDB Team", -// description: "Example key management plugin using AES", -// license: GPL, -// stability: EXPERIMENTAL -// } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 191002d8e6164..438f2cf145317 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -18,7 +18,7 @@ //! - `encrypted_length`: function, macro provides call to `std::mem::size_of` // use core::cell::UnsafeCell; -use mariadb_server_sys as bindings; +use mariadb_sys as bindings; /// A type of error to be used by key functions #[repr(u32)] @@ -93,7 +93,13 @@ pub trait KeyManager: Send + Sized { // TODO: Maybe split into `Encrypt` and `Decrypt` traits pub trait Encryption: Sized { /// Initialize the encryption context object - fn init(key_id: u32, key_version: u32, key: &[u8], iv: &[u8], flags: Flags) -> Result; + fn init( + key_id: u32, + key_version: u32, + key: &[u8], + iv: &[u8], + flags: Flags, + ) -> Result; /// Update the encryption context with new data, return the number of bytes /// written diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 5378143cecad0..3fd4ff5d98507 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -1,64 +1,178 @@ //! Wrappers needed for the `st_mariadb_encryption` type -use mariadb_server_sys as bindings; -use std::{ - ffi::{c_int, c_uchar, c_uint, c_void}, - mem, slice, -}; +use std::ffi::{c_int, c_uchar, c_uint, c_void}; +use std::{mem, slice}; + +use mariadb_sys as bindings; use super::encryption::{Encryption, Flags, KeyError, KeyManager}; +use crate::{error, warn}; -/// Get the key version, simple wrapper -pub unsafe extern "C" fn wrap_get_latest_key_version(key_id: c_uint) -> c_uint { - match T::get_latest_key_version(key_id) { - Ok(v) => v, - Err(_) => KeyError::VersionInvalid as c_uint, +/// +pub trait WrapKeyMgr: KeyManager { + /// Get the key version, simple wrapper + extern "C" fn wrap_get_latest_key_version(key_id: c_uint) -> c_uint { + match Self::get_latest_key_version(key_id) { + Ok(v) => { + if v == bindings::ENCRYPTION_KEY_NOT_ENCRYPTED { + error!(target: "KeyManager", "get_latest_key_version returned value {v}, which is reserved for unencrypted keys."); + error!(target: "KeyManager", "the server will likely shut down now."); + } + v + } + Err(_) => KeyError::VersionInvalid as c_uint, + } } -} -/// If key == NULL, return the required buffer size for the key -/// -/// -pub unsafe extern "C" fn wrap_get_key( - key_id: c_uint, - version: c_uint, - dstbuf: *mut c_uchar, - buflen: *mut c_uint, -) -> c_uint { - dbg!(key_id, version, dstbuf, *buflen); - if dstbuf.is_null() { - match dbg!(T::key_length(key_id, version)) { - // FIXME: don't unwrap - Ok(v) => *buflen = v.try_into().unwrap(), + /// If key == NULL, return the required buffer size for the key + /// + /// # Safety + /// + /// `dstbuf` must be valid for `buflen` + unsafe extern "C" fn wrap_get_key( + key_id: c_uint, + version: c_uint, + dstbuf: *mut c_uchar, + buflen: *mut c_uint, + ) -> c_uint { + dbg!(key_id, version, dstbuf, *buflen); + if dstbuf.is_null() { + match dbg!(Self::key_length(key_id, version)) { + // FIXME: don't unwrap + Ok(v) => *buflen = v.try_into().unwrap(), + Err(e) => { + return e as c_uint; + } + } + return bindings::ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } + + // SAFETY: caller guarantees validity + let buf = slice::from_raw_parts_mut(dstbuf, *buflen as usize); + + // If successful, return 0. If an error occurs, return it + match dbg!(Self::get_key(key_id, version, buf)) { + Ok(_) => 0, Err(e) => { - return e as c_uint; + dbg!(e); + + // match e { + // // Set the desired buffer size if available + // KeyError::BufferTooSmall => { + // *buflen = dbg!(Self::key_length(key_id, version) + // .unwrap_or(0) + // .try_into() + // .unwrap()) + // } + // _ => (), + // } + dbg!(e as c_uint) } } - return bindings::ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } +} + +impl WrapKeyMgr for T where T: KeyManager {} +impl WrapEncryption for T where T: Encryption {} + +pub trait WrapEncryption: Encryption { + extern "C" fn wrap_crypt_ctx_size(_key_id: c_uint, _key_version: c_uint) -> c_uint { + // I believe that key_id and key_version are provided in case this plugin + // uses different structs for different keys. However, it seems safer & more + // user friendly to sidestep that and just make everything the same size + mem::size_of::().try_into().unwrap() } - // SAFETY: caller guarantees validity - let buf = slice::from_raw_parts_mut(dstbuf, *buflen as usize); - - // If successful, return 0. If an error occurs, return it - match dbg!(T::get_key(key_id, version, buf)) { - Ok(_) => 0, - Err(e) => { - dbg!(e); - - // match e { - // // Set the desired buffer size if available - // KeyError::BufferTooSmall => { - // *buflen = dbg!(T::key_length(key_id, version) - // .unwrap_or(0) - // .try_into() - // .unwrap()) - // } - // _ => (), - // } - dbg!(e as c_uint) + /// # Safety + /// + /// The caller must guarantee that the following is tre + /// + /// - `ctx` points to memory with the size of T (may be uninitialized) + /// - `key` exists for `klen` + /// - `iv` exists for `ivlen` + unsafe extern "C" fn wrap_crypt_ctx_init( + ctx: *mut c_void, + key: *const c_uchar, + klen: c_uint, + iv: *const c_uchar, + ivlen: c_uint, + flags: c_int, + key_id: c_uint, + key_version: c_uint, + ) -> c_int { + /// SAFETY: caller guarantees buffer validity + let keybuf = slice::from_raw_parts(key, klen as usize); + let ivbuf = slice::from_raw_parts(iv, ivlen as usize); + let flags = Flags::new(flags); + match Self::init(key_id, key_version, keybuf, ivbuf, flags) { + Ok(newctx) => { + ctx.cast::().write(newctx); + bindings::MY_AES_OK.try_into().unwrap() + } + Err(e) => e as c_int, } } + + /// # Safety + /// + /// The caller must guarantee that the following is true: + /// + /// - `ctx` points to a valid, initialized object of type T + /// - `src` exists for `slen` + /// - ~~`dst` exists for `dlen`~~ + /// + /// FIXME: the `*dlen` we receive from the server is unitialized. For now we + /// assume the destination buffer is equal to source buffer length, but this + /// is a bit of a workaround until MDEV-30389 is resolved. + unsafe extern "C" fn wrap_crypt_ctx_update( + ctx: *mut c_void, + src: *const c_uchar, + slen: c_uint, + dst: *mut c_uchar, + dlen: *mut c_uint, + ) -> c_int { + // dbg!(slen, dlen, *dlen); + let sbuf = slice::from_raw_parts(src, slen as usize); + let dbuf = slice::from_raw_parts_mut(dst, slen as usize); + + let c: &mut Self = &mut *ctx.cast(); + let (ret, written) = match c.update(sbuf, dbuf) { + // FIXME dlen + Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), + Err(e) => (e as c_int, 0), + }; + *dlen = written; + ret + } + + unsafe extern "C" fn wrap_crypt_ctx_finish( + ctx: *mut c_void, + dst: *mut c_uchar, + dlen: *mut c_uint, + ) -> c_int { + dbg!(*dlen); + let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); + + let c: &mut Self = &mut *ctx.cast(); + let (ret, written) = match c.finish(dbuf) { + // FIXME dlen + Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), + Err(e) => (e as c_int, 0), + }; + + ctx.drop_in_place(); + ret + } + + unsafe extern "C" fn wrap_encrypted_length( + slen: c_uint, + key_id: c_uint, + key_version: c_uint, + ) -> c_uint { + Self::encrypted_length(key_id, key_version, slen.try_into().unwrap()) + .try_into() + .unwrap() + } } unsafe fn set_buflen_with_check(buflen: *mut c_uint, val: u32) { @@ -70,97 +184,3 @@ unsafe fn set_buflen_with_check(buflen: *mut c_uint, val: u32) { } *buflen = val.try_into().unwrap() } - -pub unsafe extern "C" fn wrap_crypt_ctx_size(_key_id: c_uint, _key_version: c_uint) -> c_uint { - // I believe that key_id and key_version are provided in case this plugin - // uses different structs for different keys. However, it seems safer & more - // user friendly to sidestep that and just make everything the same size - mem::size_of::().try_into().unwrap() -} - -/// # Safety -/// -/// The caller must guarantee that the following is tre -/// -/// - `ctx` points to memory with the size of T (may be uninitialized) -/// - `key` exists for `klen` -/// - `iv` exists for `ivlen` -pub unsafe extern "C" fn wrap_crypt_ctx_init( - ctx: *mut c_void, - key: *const c_uchar, - klen: c_uint, - iv: *const c_uchar, - ivlen: c_uint, - flags: c_int, - key_id: c_uint, - key_version: c_uint, -) -> c_int { - /// SAFETY: caller guarantees buffer validity - let keybuf = slice::from_raw_parts(key, klen as usize); - let ivbuf = slice::from_raw_parts(iv, ivlen as usize); - let flags = Flags::new(flags); - match T::init(key_id, key_version, keybuf, ivbuf, flags) { - Ok(newctx) => { - ctx.cast::().write(newctx); - bindings::MY_AES_OK.try_into().unwrap() - } - Err(e) => e as c_int, - } -} - -/// # Safety -/// -/// The caller must guarantee that the following is true: -/// -/// - `ctx` points to a valid, initialized object of type T -/// - `src` exists for `slen` -/// - `dst` exists for `dlen` -pub unsafe extern "C" fn wrap_crypt_ctx_update( - ctx: *mut c_void, - src: *const c_uchar, - slen: c_uint, - dst: *mut c_uchar, - dlen: *mut c_uint, -) -> c_int { - dbg!(slen, dlen, *dlen); - let sbuf = slice::from_raw_parts(src, slen as usize); - let dbuf = slice::from_raw_parts_mut(dst, *dlen as usize); - - let c: &mut T = &mut *ctx.cast(); - let (ret, written) = match c.update(sbuf, dbuf) { - // FIXME dlen - Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), - Err(e) => (e as c_int, 0), - }; - *dlen = written; - ret -} - -pub unsafe extern "C" fn wrap_crypt_ctx_finish( - ctx: *mut c_void, - dst: *mut c_uchar, - dlen: *mut c_uint, -) -> c_int { - dbg!(*dlen); - let dbuf = slice::from_raw_parts_mut(dst, dlen as usize); - - let c: &mut T = &mut *ctx.cast(); - let (ret, written) = match c.finish(dbuf) { - // FIXME dlen - Ok(_) => (bindings::MY_AES_OK.try_into().unwrap(), 0), - Err(e) => (e as c_int, 0), - }; - - ctx.drop_in_place(); - ret -} - -pub unsafe extern "C" fn wrap_encrypted_length( - slen: c_uint, - key_id: c_uint, - key_version: c_uint, -) -> c_uint { - T::encrypted_length(key_id, key_version, slen.try_into().unwrap()) - .try_into() - .unwrap() -} diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index b631db8a8eda6..736ce5b1e9902 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,8 +1,9 @@ -use bindings::st_mysql_sys_var_basic; -use mariadb_server_sys as bindings; use std::mem::ManuallyDrop; use std::ptr; +use bindings::st_mysql_sys_var_basic; +use mariadb_sys as bindings; + #[repr(C)] union SysVar { basic: ManuallyDrop>, @@ -180,11 +181,12 @@ pub enum PluginVarInfo { #[cfg(test)] mod tests { - use super::*; use std::ffi::c_uint; use std::mem::size_of; use std::sync::atomic::AtomicU32; + use super::*; + #[test] fn test_sizes() { assert_eq!(size_of::(), size_of::()) diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 039433003ec68..295f548a0369c 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -1,16 +1,67 @@ -use super::Init; -use std::ffi::{c_int, c_void}; +use std::cell::UnsafeCell; +use std::ffi::{c_int, c_uint, c_void}; +use std::ptr; -pub unsafe extern "C" fn wrap_init(_: *mut c_void) -> c_int { - match T::init() { - Ok(_) => 0, - Err(_) => 1, +use super::{Init, License, Maturity, PluginType}; +use crate::bindings; + +/// Used for plugin registrations, which are in global scope. +#[doc(hidden)] +#[derive(Debug)] +#[repr(transparent)] +pub struct UnsafeSyncCell(UnsafeCell); + +impl UnsafeSyncCell { + /// # Safety + /// + /// This inner value be used in a Sync/Send way + pub const unsafe fn new(value: T) -> Self { + Self(UnsafeCell::new(value)) + } + + pub const fn as_ptr(&self) -> *const T { + self.0.get() } } -pub unsafe extern "C" fn wrap_deinit(_: *mut c_void) -> c_int { - match T::deinit() { - Ok(_) => 0, - Err(_) => 1, +unsafe impl Send for UnsafeSyncCell {} +unsafe impl Sync for UnsafeSyncCell {} + +/// Trait for easily wrapping init/deinit functions +pub trait WrapInit: Init { + unsafe extern "C" fn wrap_init(_: *mut c_void) -> c_int { + match Self::init() { + Ok(_) => 0, + Err(_) => 1, + } + } + + unsafe extern "C" fn wrap_deinit(_: *mut c_void) -> c_int { + match Self::deinit() { + Ok(_) => 0, + Err(_) => 1, + } + } +} + +impl WrapInit for T where T: Init {} + +/// New struct with all null values +#[doc(hidden)] +pub const fn new_null_st_maria_plugin() -> bindings::st_maria_plugin { + bindings::st_maria_plugin { + type_: 0, + info: ptr::null_mut(), + name: ptr::null(), + author: ptr::null(), + descr: ptr::null(), + license: 0, + init: None, + deinit: None, + version: 0, + status_vars: ptr::null_mut(), + system_vars: ptr::null_mut(), + version_info: ptr::null(), + maturity: 0, } } diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000000000..4dc35d3684f6d --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1,6 @@ +imports_granularity = "Module" +newline_style = "Unix" +group_imports = "StdExternalCrate" +format_code_in_doc_comments = true +format_macro_bodies = true +format_macro_matchers = true From 9e20b7fd2b5354a37e3a352d3b394856da0945d1 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 19 Jan 2023 13:58:18 -0500 Subject: [PATCH 14/34] Reworked some example organization --- rust/Cargo.toml | 5 +- .../{basic => keymgt-basic}/Cargo.toml | 2 +- .../{basic => keymgt-basic}/src/lib.rs | 0 .../Cargo.toml | 2 +- .../src/lib.rs | 67 ----------- rust/examples/keymgt-debug/Cargo.toml | 10 ++ rust/examples/keymgt-debug/src/lib.rs | 104 ++++++++++++++++++ 7 files changed, 119 insertions(+), 71 deletions(-) rename rust/examples/{basic => keymgt-basic}/Cargo.toml (85%) rename rust/examples/{basic => keymgt-basic}/src/lib.rs (100%) rename rust/examples/{debug-key-management => keymgt-clevis}/Cargo.toml (81%) rename rust/examples/{debug-key-management => keymgt-clevis}/src/lib.rs (51%) create mode 100644 rust/examples/keymgt-debug/Cargo.toml create mode 100644 rust/examples/keymgt-debug/src/lib.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 30845c1398c96..7bc9bf79e0e00 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,7 +6,8 @@ members = [ "bindings", "mariadb", "macros", - "examples/basic", - "examples/debug-key-management", + "examples/keymgt-basic", + "examples/keymgt-debug", + "examples/keymgt-clevis", "examples/encryption" ] diff --git a/rust/examples/basic/Cargo.toml b/rust/examples/keymgt-basic/Cargo.toml similarity index 85% rename from rust/examples/basic/Cargo.toml rename to rust/examples/keymgt-basic/Cargo.toml index 6c9bb133ef1f7..28031957effc9 100644 --- a/rust/examples/basic/Cargo.toml +++ b/rust/examples/keymgt-basic/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic" +name = "keymgt-basic" version = "0.1.0" edition = '2021' diff --git a/rust/examples/basic/src/lib.rs b/rust/examples/keymgt-basic/src/lib.rs similarity index 100% rename from rust/examples/basic/src/lib.rs rename to rust/examples/keymgt-basic/src/lib.rs diff --git a/rust/examples/debug-key-management/Cargo.toml b/rust/examples/keymgt-clevis/Cargo.toml similarity index 81% rename from rust/examples/debug-key-management/Cargo.toml rename to rust/examples/keymgt-clevis/Cargo.toml index c8cba89e8b6db..085fe29b36ded 100644 --- a/rust/examples/debug-key-management/Cargo.toml +++ b/rust/examples/keymgt-clevis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "debug-key-management" +name = "keymgt-clevis" version = "0.1.0" edition = '2021' diff --git a/rust/examples/debug-key-management/src/lib.rs b/rust/examples/keymgt-clevis/src/lib.rs similarity index 51% rename from rust/examples/debug-key-management/src/lib.rs rename to rust/examples/keymgt-clevis/src/lib.rs index 52e537df40198..69da549d4e9a1 100644 --- a/rust/examples/debug-key-management/src/lib.rs +++ b/rust/examples/keymgt-clevis/src/lib.rs @@ -82,32 +82,6 @@ impl KeyManager for DebugKeyMgmt { } } -// bindings::st_maria_plugin { -// type_: PluginType::Encryption, -// info: *mut c_void, -// name: cstr::cstr!($name).as_ptr(), -// author: cstr::cstr!($author).as_ptr(), -// descr: cstr::cstr!($description).as_ptr(), -// license: c_int, -// init: Option c_int>, -// deinit: Option c_int>, -// version: vers as c_uint, -// status_vars: *mut st_mysql_show_var, -// system_vars: *mut *mut st_mysql_sys_var, -// version_info: plugin!( -// @def -// cstr::cstr!($vers).as_ptr(), -// $(cstr::cstr!($comment).as_ptr())? -// ),, -// maturity: maturity as c_uint, -// } - -// #![crate_type = "cdylib"] - -// builtin_debug_key_plugin_interface_version -// builtin_debug_key_sizeof_struct_st_plugin -// builtin_debug_key_plugin - register_plugin! { DebugKeyMgmt, ptype: PluginType::MariaEncryption, @@ -128,44 +102,3 @@ register_plugin! { // KEY_VERSION_SYSVAR.as_ptr().cast_mut(), // ::std::ptr::null_mut(), // ])); - -// static _ENCRYPTION_ST: UcWrap = -// UcWrap(UnsafeCell::new(mariadb::bindings::st_mariadb_encryption { -// interface_version: mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as i32, -// get_latest_key_version: Some(DebugKeyMgmt::wrap_get_latest_key_version), -// get_key: Some(DebugKeyMgmt::wrap_get_key), -// crypt_ctx_size: None, -// crypt_ctx_init: None, -// crypt_ctx_update: None, -// crypt_ctx_finish: None, -// encrypted_length: None, -// })); - -// #[no_mangle] -// static _maria_plugin_interface_version_: ::std::ffi::c_int = -// mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int; - -// #[no_mangle] -// static _maria_sizeof_struct_st_plugin_: ::std::ffi::c_int = -// ::std::mem::size_of::() as ::std::ffi::c_int; - -// #[no_mangle] -// static mut _maria_plugin_declarations_: [mariadb::bindings::st_maria_plugin; 2] = [ -// mariadb::bindings::st_maria_plugin { -// type_: PluginType::MariaEncryption as i32, -// info: _ENCRYPTION_ST.as_ptr().cast_mut().cast(), -// name: mariadb::cstr::cstr!("debug_key_management").as_ptr(), -// author: mariadb::cstr::cstr!("Trevor Gross").as_ptr(), -// descr: mariadb::cstr::cstr!("Debug key management plugin").as_ptr(), -// license: License::Gpl as i32, -// init: Some(DebugKeyMgmt::wrap_init), -// deinit: Some(DebugKeyMgmt::wrap_deinit), -// version: 0x0010, -// status_vars: ::std::ptr::null_mut(), -// system_vars: _INTERNAL_SYSVARS.0.get().cast(), -// version_info: mariadb::cstr::cstr!("0.1").as_ptr(), -// maturity: Maturity::Experimental as u32, -// }, -// // End with a null object -// ::mariadb::plugin::wrapper::new_null_st_maria_plugin(), -// ]; diff --git a/rust/examples/keymgt-debug/Cargo.toml b/rust/examples/keymgt-debug/Cargo.toml new file mode 100644 index 0000000000000..0bf4ca5980ba2 --- /dev/null +++ b/rust/examples/keymgt-debug/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "keymgt-debug" +version = "0.1.0" +edition = '2021' + +[lib] +crate-type = ["cdylib"] + +[dependencies] +mariadb = { path = "../../mariadb" } diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs new file mode 100644 index 0000000000000..69da549d4e9a1 --- /dev/null +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -0,0 +1,104 @@ +//! Debug key management +//! +//! Use to debug the encryption code with a fixed key that changes only on user +//! request. The only valid key ID is 1. +//! +//! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! + +#![allow(unused)] + +use std::cell::UnsafeCell; +use std::ffi::c_void; +use std::sync::atomic::{AtomicU32, Ordering}; + +use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb::plugin::prelude::*; +use mariadb::plugin::{ + register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, +}; +use mariadb::sysvar_atomic; + +const KEY_LENGTH: usize = 4; + +static KEY_VERSION: AtomicU32 = AtomicU32::new(1); + +static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { + ty: u32, + name: "version", + var: KEY_VERSION, + comment: "Latest key version", + flags: [PluginVarInfo::ReqCmdArg], + default: 1, +}; + +struct DebugKeyMgmt; + +impl Init for DebugKeyMgmt { + fn init() -> Result<(), InitError> { + eprintln!("init for DebugKeyMgmt"); + Ok(()) + } + + fn deinit() -> Result<(), InitError> { + eprintln!("deinit for DebugKeyMgmt"); + Ok(()) + } +} + +impl KeyManager for DebugKeyMgmt { + fn get_latest_key_version(key_id: u32) -> Result { + if key_id != 1 { + Err(KeyError::VersionInvalid) + } else { + Ok(KEY_VERSION.load(Ordering::Relaxed)) + } + } + + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + if key_id != 1 { + return Err(KeyError::VersionInvalid); + } + + // Convert our integer to a native endian byte array + let key_buf = KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes(); + + if dst.len() < key_buf.len() { + return Err(KeyError::BufferTooSmall); + } + + // Copy our slice to the buffer, return the copied length + dst[..key_buf.len()].copy_from_slice(key_buf.as_slice()); + Ok(()) + } + + fn key_length(key_id: u32, key_version: u32) -> Result { + // Return the length of our u32 in bytes + // Just verify our types don't change + debug_assert_eq!( + KEY_LENGTH, + KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes().len() + ); + Ok(KEY_LENGTH) + } +} + +register_plugin! { + DebugKeyMgmt, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: DebugKeyMgmt, // optional + encryption: false, +} + +// PROC: should mangle names with type name + +// static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb::bindings::st_mysql_sys_var; 2]> = +// UcWrap(UnsafeCell::new([ +// KEY_VERSION_SYSVAR.as_ptr().cast_mut(), +// ::std::ptr::null_mut(), +// ])); From 49f5a069c587ae826fee32076d87a46480e5afec Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 19 Jan 2023 16:57:46 -0500 Subject: [PATCH 15/34] Work on service_sql --- rust/examples/keymgt-clevis/src/lib.rs | 65 ++++---------------- rust/mariadb/src/lib.rs | 20 +++++++ rust/mariadb/src/plugin/wrapper.rs | 8 +++ rust/mariadb/src/service_sql.rs | 82 ++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 53 deletions(-) create mode 100644 rust/mariadb/src/service_sql.rs diff --git a/rust/examples/keymgt-clevis/src/lib.rs b/rust/examples/keymgt-clevis/src/lib.rs index 69da549d4e9a1..ce203412440f6 100644 --- a/rust/examples/keymgt-clevis/src/lib.rs +++ b/rust/examples/keymgt-clevis/src/lib.rs @@ -1,8 +1,3 @@ -//! Debug key management -//! -//! Use to debug the encryption code with a fixed key that changes only on user -//! request. The only valid key ID is 1. -//! //! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! #![allow(unused)] @@ -18,80 +13,44 @@ use mariadb::plugin::{ }; use mariadb::sysvar_atomic; -const KEY_LENGTH: usize = 4; +struct KeyMgtClevis; -static KEY_VERSION: AtomicU32 = AtomicU32::new(1); - -static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { - ty: u32, - name: "version", - var: KEY_VERSION, - comment: "Latest key version", - flags: [PluginVarInfo::ReqCmdArg], - default: 1, -}; - -struct DebugKeyMgmt; - -impl Init for DebugKeyMgmt { +impl Init for KeyMgtClevis { fn init() -> Result<(), InitError> { - eprintln!("init for DebugKeyMgmt"); + eprintln!("init for KeyMgtClevis"); Ok(()) } fn deinit() -> Result<(), InitError> { - eprintln!("deinit for DebugKeyMgmt"); + eprintln!("deinit for KeyMgtClevis"); Ok(()) } } -impl KeyManager for DebugKeyMgmt { +impl KeyManager for KeyMgtClevis { fn get_latest_key_version(key_id: u32) -> Result { - if key_id != 1 { - Err(KeyError::VersionInvalid) - } else { - Ok(KEY_VERSION.load(Ordering::Relaxed)) - } + todo!() } fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { - if key_id != 1 { - return Err(KeyError::VersionInvalid); - } - - // Convert our integer to a native endian byte array - let key_buf = KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes(); - - if dst.len() < key_buf.len() { - return Err(KeyError::BufferTooSmall); - } - - // Copy our slice to the buffer, return the copied length - dst[..key_buf.len()].copy_from_slice(key_buf.as_slice()); - Ok(()) + todo!() } fn key_length(key_id: u32, key_version: u32) -> Result { - // Return the length of our u32 in bytes - // Just verify our types don't change - debug_assert_eq!( - KEY_LENGTH, - KEY_VERSION.load(Ordering::Relaxed).to_ne_bytes().len() - ); - Ok(KEY_LENGTH) + todo!() } } register_plugin! { - DebugKeyMgmt, + KeyMgtClevis, ptype: PluginType::MariaEncryption, - name: "debug_key_management", + name: "clevis_key_management", author: "Trevor Gross", - description: "Debug key management plugin", + description: "Clevis key management plugin", license: License::Gpl, maturity: Maturity::Experimental, version: "0.1", - init: DebugKeyMgmt, // optional + init: KeyMgtClevis, // optional encryption: false, } diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index cc5051c136d78..59e6ae323c8af 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -4,6 +4,7 @@ use time::{format_description, OffsetDateTime}; pub mod plugin; +pub mod service_sql; #[doc(hidden)] pub use cstr; @@ -24,6 +25,19 @@ pub fn log_timestamped_message(title: &str, msg: &str) { eprintln!("{to_print}"); } +/// Provide the name of the calling function (full path) +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] + }}; +} + +/// Log an error to stderr #[macro_export] macro_rules! error { (target: $target:expr, $($msg:tt)+) => {{ @@ -36,6 +50,8 @@ macro_rules! error { $crate::log_timestamped_message("[Error]: ", format!($($msg)+)); } } + +/// Log a warning to stderr #[macro_export] macro_rules! warn { (target: $target:expr, $($msg:tt)+) => {{ @@ -48,6 +64,8 @@ macro_rules! warn { $crate::log_timestamped_message("[Warn]", format!($($msg)+)); } } + +/// Log info to stderr #[macro_export] macro_rules! info { (target: $target:expr, $($msg:tt)+) => {{ @@ -60,6 +78,8 @@ macro_rules! info { $crate::log_timestamped_message("[Info]", format!($($msg)+)); } } + +/// Log debug messages to stderr #[macro_export] macro_rules! debug { (target: $target:expr, $($msg:tt)+) => {{ diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 295f548a0369c..582b23dec6444 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -22,6 +22,14 @@ impl UnsafeSyncCell { pub const fn as_ptr(&self) -> *const T { self.0.get() } + + pub const fn get(&self) -> *mut T { + self.0.get() + } + + pub fn get_mut(&mut self) -> &mut T { + self.0.get_mut() + } } unsafe impl Send for UnsafeSyncCell {} diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs new file mode 100644 index 0000000000000..6a76929a9a254 --- /dev/null +++ b/rust/mariadb/src/service_sql.rs @@ -0,0 +1,82 @@ +//! Safe API for include/mysql/service_sql.h +//! +//! FIXME: I think we need to use a different GLOBAL_SQL_SERVICE if statically +//! linked, but not yet sure where this is + +use std::cell::UnsafeCell; +use std::ffi::CString; +use std::marker::PhantomData; +use std::ptr; + +use bindings::{sql_service as GLOBAL_SQL_SERVICE, sql_service_st, MYSQL}; + +use crate::bindings; +use crate::plugin::wrapper::UnsafeSyncCell; + +pub trait MySqlState {} +pub struct Connected; +pub struct Disconnected; +impl MySqlState for Connected {} +impl MySqlState for Disconnected {} + +pub struct MySql(UnsafeCell, PhantomData); + +impl MySql { + fn try_new<'a>() -> Option<&'a Self> { + unsafe { + // SAFETY: GLOBAL_SQL_SERVICE is valid (linked?) + let p = (*GLOBAL_SQL_SERVICE).mysql_init_func.unwrap()(ptr::null_mut()); + // SAFETY: We are casting to an option, so nullptr is handled that + // way. Otherwise, return of `mysql_init_func` is valid. + *p.cast() + } + } + + /// Attempt to connect + fn try_connect_local<'a>(&'a mut self) -> Option<&'a MySql> { + unsafe { + let p_self: *mut Self = self; + let p = (*GLOBAL_SQL_SERVICE).mysql_real_connect_local_func.unwrap()(p_self.cast()); + *p.cast() + } + } + + // TODO: see CLIENT_X flags in mariadb_com.h + fn try_connect<'a>( + &'a mut self, + host: &str, + database: &str, + user: &str, + password: &str, + port: u16, + unix_socket: &str, + client_flag: u32, + ) -> Option<&'a MySql> { + const EMSG: &str = "provided string contains null"; + let p_self: *mut Self = self; + let host_cs = CString::new(host).expect(EMSG); + let db_cs = CString::new(database).expect(EMSG); + let user_cs = CString::new(user).expect(EMSG); + let pw_cs = CString::new(password).expect(EMSG); + let sock_cs = CString::new(unix_socket).expect(EMSG); + + unsafe { + // see mysql_real_connect in mariadb_lib.c + let p = (*GLOBAL_SQL_SERVICE).mysql_real_connect_func.unwrap()( + p_self.cast(), + host_cs.as_ptr(), + user_cs.as_ptr(), + pw_cs.as_ptr(), + db_cs.as_ptr(), + port.into(), + sock_cs.as_ptr(), + client_flag.into(), + ); + *p.cast() + } + } +} + +impl MySql { + fn real_query() {} +} From 313a5cafc301a01fe9c602908fcab5cdfa4ecf73 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 19 Jan 2023 21:41:57 -0500 Subject: [PATCH 16/34] Work on rows iter --- rust/examples/keymgt-clevis/src/lib.rs | 47 ++++-- rust/mariadb/src/lib.rs | 8 +- rust/mariadb/src/service_sql.rs | 103 ++++++------- rust/mariadb/src/service_sql/error.rs | 15 ++ rust/mariadb/src/service_sql/raw.rs | 191 +++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 76 deletions(-) create mode 100644 rust/mariadb/src/service_sql/error.rs create mode 100644 rust/mariadb/src/service_sql/raw.rs diff --git a/rust/examples/keymgt-clevis/src/lib.rs b/rust/examples/keymgt-clevis/src/lib.rs index ce203412440f6..fe0572dce355d 100644 --- a/rust/examples/keymgt-clevis/src/lib.rs +++ b/rust/examples/keymgt-clevis/src/lib.rs @@ -11,24 +11,57 @@ use mariadb::plugin::prelude::*; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, }; -use mariadb::sysvar_atomic; +use mariadb::service_sql::MySqlConn; +use mariadb::{debug, info, sysvar_atomic}; + +const KEY_TABLE: &str = "mysql.clevis_keys"; +const SERVER_TABLE: &str = "mysql.clevis_servers"; struct KeyMgtClevis; impl Init for KeyMgtClevis { + /// Create needed tables fn init() -> Result<(), InitError> { - eprintln!("init for KeyMgtClevis"); + debug!("init for KeyMgtClevis"); + + let conn = MySqlConn::connect_local().map_err(|_| InitError)?; + conn.execute(&format!( + "CREATE TABLE IF NOT EXISTS {KEY_TABLE} ( + key_id INT UNSIGNED NOT NULL, + key_version INT UNSIGNED NOT NULL, + key_server VARBINARY(64) NOT NULL, + key VARBINARY((16) NOT NULL, + PRIMARY KEY (key_id, key_version) + ) ENGINE=InnoDB" + )) + .map_err(|_| InitError)?; + conn.execute(&format!( + "CREATE TABLE IF NOT EXISTS {SERVER_TABLE} ( + server VARBINARY(64) NOT NULL PRIMARY KEY, + verify VARBINARY(2048) + enc VARBINARY(2048) + ) ENGINE=InnoDB" + )) + .map_err(|_| InitError)?; + + debug!("finished init for KeyMgtClevis"); Ok(()) } fn deinit() -> Result<(), InitError> { - eprintln!("deinit for KeyMgtClevis"); + debug!("deinit for KeyMgtClevis"); Ok(()) } } impl KeyManager for KeyMgtClevis { fn get_latest_key_version(key_id: u32) -> Result { + let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; + conn.query(&format!( + "SELECT key_version FROM {KEY_TABLE} WHERE key_id = {key_id}" + )) + .map_err(|_| KeyError::Other)?; + todo!() } @@ -53,11 +86,3 @@ register_plugin! { init: KeyMgtClevis, // optional encryption: false, } - -// PROC: should mangle names with type name - -// static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb::bindings::st_mysql_sys_var; 2]> = -// UcWrap(UnsafeCell::new([ -// KEY_VERSION_SYSVAR.as_ptr().cast_mut(), -// ::std::ptr::null_mut(), -// ])); diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 59e6ae323c8af..bee0a5a6381c3 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -47,7 +47,7 @@ macro_rules! error { $crate::log_timestamped_message(&tmp, &format!($($msg)+)); }}; ($($msg:tt)+) => { - $crate::log_timestamped_message("[Error]: ", format!($($msg)+)); + $crate::log_timestamped_message("[Error]: ", &format!($($msg)+)); } } @@ -61,7 +61,7 @@ macro_rules! warn { $crate::log_timestamped_message(&tmp, &format!($($msg)+)); }}; ($($msg:tt)+) => { - $crate::log_timestamped_message("[Warn]", format!($($msg)+)); + $crate::log_timestamped_message("[Warn]", &format!($($msg)+)); } } @@ -75,7 +75,7 @@ macro_rules! info { $crate::log_timestamped_message(&tmp, &format!($($msg)+)); }}; ($($msg:tt)+) => { - $crate::log_timestamped_message("[Info]", format!($($msg)+)); + $crate::log_timestamped_message("[Info]", &format!($($msg)+)); } } @@ -89,6 +89,6 @@ macro_rules! debug { $crate::log_timestamped_message(&tmp, &format!($($msg)+)); }}; ($($msg:tt)+) => { - $crate::log_timestamped_message("[Debuf]", format!($($msg)+)); + $crate::log_timestamped_message("[Debuf]", &format!($($msg)+)); } } diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index 6a76929a9a254..9da08c20d3567 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -1,4 +1,5 @@ //! Safe API for include/mysql/service_sql.h + //! //! FIXME: I think we need to use a different GLOBAL_SQL_SERVICE if statically //! linked, but not yet sure where this is @@ -6,77 +7,57 @@ use std::cell::UnsafeCell; use std::ffi::CString; use std::marker::PhantomData; -use std::ptr; +use std::ptr::{self, NonNull}; -use bindings::{sql_service as GLOBAL_SQL_SERVICE, sql_service_st, MYSQL}; +mod error; +mod raw; +use bindings::sql_service as GLOBAL_SQL_SERVICE; +use raw::RawConnection; +pub use self::error::ClientError; +pub use self::raw::{Fetch, Store}; +use self::raw::{RState, RawResult, RawRow, RowsIter}; use crate::bindings; use crate::plugin::wrapper::UnsafeSyncCell; -pub trait MySqlState {} -pub struct Connected; -pub struct Disconnected; -impl MySqlState for Connected {} -impl MySqlState for Disconnected {} +pub struct MySqlConn(RawConnection); -pub struct MySql(UnsafeCell, PhantomData); +/// Need to noodle on this for a bit. I think it makes sense that we have a `&` +/// reference to the creating `MySqlConn`, since that means the connection won't +/// be dropped too early, and can't be changed. +pub struct QueryResult<'a, S: RState> { + res: RawResult, + conn: &'a MySqlConn, +} -impl MySql { - fn try_new<'a>() -> Option<&'a Self> { - unsafe { - // SAFETY: GLOBAL_SQL_SERVICE is valid (linked?) - let p = (*GLOBAL_SQL_SERVICE).mysql_init_func.unwrap()(ptr::null_mut()); - // SAFETY: We are casting to an option, so nullptr is handled that - // way. Otherwise, return of `mysql_init_func` is valid. - *p.cast() - } - } +pub struct Row<'a, S: RState> { + inner: RawRow, + res: &'a RawResult, +} - /// Attempt to connect - fn try_connect_local<'a>(&'a mut self) -> Option<&'a MySql> { - unsafe { - let p_self: *mut Self = self; - let p = (*GLOBAL_SQL_SERVICE).mysql_real_connect_local_func.unwrap()(p_self.cast()); - *p.cast() - } +impl MySqlConn { + /// Connect to the local server + pub fn connect_local() -> Result { + let conn = RawConnection::new(); + conn.connect_local()?; + Ok(Self(conn)) } - // TODO: see CLIENT_X flags in mariadb_com.h - fn try_connect<'a>( - &'a mut self, - host: &str, - database: &str, - user: &str, - password: &str, - port: u16, - unix_socket: &str, - client_flag: u32, - ) -> Option<&'a MySql> { - const EMSG: &str = "provided string contains null"; - let p_self: *mut Self = self; - let host_cs = CString::new(host).expect(EMSG); - let db_cs = CString::new(database).expect(EMSG); - let user_cs = CString::new(user).expect(EMSG); - let pw_cs = CString::new(password).expect(EMSG); - let sock_cs = CString::new(unix_socket).expect(EMSG); - - unsafe { - // see mysql_real_connect in mariadb_lib.c - let p = (*GLOBAL_SQL_SERVICE).mysql_real_connect_func.unwrap()( - p_self.cast(), - host_cs.as_ptr(), - user_cs.as_ptr(), - pw_cs.as_ptr(), - db_cs.as_ptr(), - port.into(), - sock_cs.as_ptr(), - client_flag.into(), - ); - *p.cast() - } + /// Run a query and discard the results + pub fn execute<'a>(&'a self, q: &str) -> Result<(), ClientError> { + self.0.real_query(q)?; + Ok(()) + } + /// Run a query for lazily loaded results + pub fn query<'a>(&'a self, q: &str) -> Result, ClientError> { + self.0.real_query(q)?; + let res = self.0.fetch_result()?; + Ok(QueryResult { res, conn: &self }) } } -impl MySql { - fn real_query() {} -} +// impl QueryResult { +// pub fn iter_rows(self) -> RowsIter { +// RowsIter +// } +// } diff --git a/rust/mariadb/src/service_sql/error.rs b/rust/mariadb/src/service_sql/error.rs new file mode 100644 index 0000000000000..01dd9b0d11b23 --- /dev/null +++ b/rust/mariadb/src/service_sql/error.rs @@ -0,0 +1,15 @@ +use crate::bindings; + +#[non_exhaustive] +pub enum ClientError { + // CommandsOutOfSync = bindings::CR_COMMANDS_OUT_OF_SYNC + Unspecified, +} + +impl From for ClientError { + fn from(value: i32) -> Self { + match value { + _ => Self::Unspecified, + } + } +} diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs new file mode 100644 index 0000000000000..02e22fbc5fa1e --- /dev/null +++ b/rust/mariadb/src/service_sql/raw.rs @@ -0,0 +1,191 @@ +//! Safe API for a MySql connection +//! +//! RawConnection comes almost directly from the `diesel` client crate, since +//! they have that all figured out pretty well. Reference: +//! https://github.com/diesel-rs/diesel/blob/88129db2fbed49d3ecd41bafff2a5932f1621c2c/diesel/src/mysql/connection/raw.rs#L223 + +use std::ffi::{CStr, CString}; +use std::marker::PhantomData; +use std::ptr; +use std::ptr::NonNull; +use std::sync::Once; + +use bindings::sql_service as GLOBAL_SQL_SERVICE; + +use super::error::ClientError; +use super::Row; +use crate::bindings; + +pub struct Fetch; +pub struct Store; +pub trait RState {} + +impl RState for Fetch {} +impl RState for Store {} + +pub struct RawConnection(NonNull); +pub struct RawResult(NonNull, PhantomData); +pub struct RawRow(bindings::MYSQL_ROW); +pub struct RowsIter { + res: RawResult, +} + +pub struct ConnOpts { + host: Option, + database: Option, + user: Option, + password: Option, + port: Option, + unix_socket: Option, + flags: u32, +} + +impl RawConnection { + pub(super) fn new() -> Self { + fn_thread_unsafe_lib_init(); + let p_conn = unsafe { (*GLOBAL_SQL_SERVICE).mysql_init_func.unwrap()(ptr::null_mut()) }; + let p_conn = NonNull::new(p_conn).expect("OOM: connection allocation failed"); + let result = RawConnection(p_conn); + + // let charset_result = unsafe { + // bindings::mysql_options( + // result.0.as_ptr(), + // bindings::mysql_option::MYSQL_SET_CHARSET_NAME, + // b"utf8mb4\0".as_ptr() as *const libc::c_void, + // ) + // }; + // assert_eq!( + // 0, charset_result, + // "MYSQL_SET_CHARSET_NAME was not \ + // recognized as an option by MySQL. This should never \ + // happen." + // ); + + result + } + + pub(super) fn connect_local(&self) -> Result<(), ClientError> { + let res = unsafe { + (*GLOBAL_SQL_SERVICE).mysql_real_connect_local_func.unwrap()(self.0.as_ptr()) + }; + if res.is_null() { + Ok(()) + } else { + Err((res as i32).into()) + } + } + + pub(super) fn connect(&self, conn_opts: &ConnOpts) -> Result<(), ClientError> { + let host = conn_opts.host.as_ref(); + let user = conn_opts.user.as_ref(); + let pw = conn_opts.password.as_ref(); + let db = conn_opts.database.as_ref(); + let port = conn_opts.port; + let sock = conn_opts.unix_socket.as_ref(); + + // TODO: see CLIENT_X flags in mariadb_com.h + let res = unsafe { + // Make sure you don't use the fake one! + (*GLOBAL_SQL_SERVICE).mysql_real_connect_func.unwrap()( + self.0.as_ptr(), + host.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + user.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + pw.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + db.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + port.map(|p| p.into()).unwrap_or(0), + sock.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + conn_opts.flags.into(), + ) + }; + + if res.is_null() { + Ok(()) + } else { + Err((res as i32).into()) + } + + // let last_error_message = self.last_error_message(); + // if last_error_message.is_empty() { + // Ok(()) + // } else { + // Err(ConnectionError::BadConnection(last_error_message)) + // } + } + + pub fn real_query(&self, q: &str) -> Result<(), ClientError> { + unsafe { + let p_self: *const Self = self; + // mysql_real_query in mariadb_lib.c + let res = (*GLOBAL_SQL_SERVICE).mysql_real_query_func.unwrap()( + p_self.cast_mut().cast(), + q.as_ptr().cast(), + q.len().try_into().unwrap(), + ); + + if res == 0 { + Ok(()) + } else { + Err(res.into()) + } + } + } + + pub fn fetch_result(&self) -> Result, ClientError> { + let res = unsafe { bindings::mysql_use_result(self.0.as_ptr()) }; + + match NonNull::new(res) { + Some(ptr) => Ok(RawResult(ptr, PhantomData)), + None => Err((res as i32).into()), + } + } +} + +impl Drop for RawConnection { + fn drop(&mut self) { + unsafe { (*GLOBAL_SQL_SERVICE).mysql_close_func.unwrap()(self.0.as_ptr()) }; + } +} + +impl RawResult { + pub fn iter_rows(self) -> RowsIter { + RowsIter(self) + } +} + +impl Drop for RawResult { + fn drop(&mut self) { + unsafe { (*GLOBAL_SQL_SERVICE).mysql_free_result_func.unwrap()(self.0.as_ptr()) }; + } +} + +impl<'a> Iterator for RowsIter { + type Item = Row<'a>; + + fn next(&mut self) -> Option { + let ptr = + unsafe { (*GLOBAL_SQL_SERVICE).mysql_fetch_row_func.unwrap()(self.0 .0.as_ptr()) }; + + if ptr.is_null() { + None + } else { + Some(Row { + inner: RawRow(ptr), + res: todo!(), + }) + } + } +} + +/// +static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new(); + +fn fn_thread_unsafe_lib_init() { + MYSQL_THREAD_UNSAFE_INIT.call_once(|| { + // mysql_library_init is defined by `#define mysql_library_init mysql_server_init` + // which isn't picked up by bindgen + let ret = unsafe { bindings::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) }; + if ret != 0 { + panic!("Unable to perform MySQL global initialization. Return code: {ret}"); + } + }) +} From 59b11b17cb214ca211b84e90214837fcff714f49 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 19 Jan 2023 22:26:31 -0500 Subject: [PATCH 17/34] Work on iterator --- rust/examples/keymgt-clevis/src/lib.rs | 2 +- rust/mariadb/src/service_sql.rs | 25 ++++++++++----- rust/mariadb/src/service_sql/raw.rs | 44 ++++++++++---------------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/rust/examples/keymgt-clevis/src/lib.rs b/rust/examples/keymgt-clevis/src/lib.rs index fe0572dce355d..fb44ecba59396 100644 --- a/rust/examples/keymgt-clevis/src/lib.rs +++ b/rust/examples/keymgt-clevis/src/lib.rs @@ -57,7 +57,7 @@ impl Init for KeyMgtClevis { impl KeyManager for KeyMgtClevis { fn get_latest_key_version(key_id: u32) -> Result { let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; - conn.query(&format!( + let res = conn.query(&format!( "SELECT key_version FROM {KEY_TABLE} WHERE key_id = {key_id}" )) .map_err(|_| KeyError::Other)?; diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index 9da08c20d3567..cc4eec3a0c076 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -16,7 +16,7 @@ use raw::RawConnection; pub use self::error::ClientError; pub use self::raw::{Fetch, Store}; -use self::raw::{RState, RawResult, RawRow, RowsIter}; +use self::raw::{RState, RawResult,Row}; use crate::bindings; use crate::plugin::wrapper::UnsafeSyncCell; @@ -30,10 +30,7 @@ pub struct QueryResult<'a, S: RState> { conn: &'a MySqlConn, } -pub struct Row<'a, S: RState> { - inner: RawRow, - res: &'a RawResult, -} + impl MySqlConn { /// Connect to the local server @@ -56,8 +53,20 @@ impl MySqlConn { } } -// impl QueryResult { -// pub fn iter_rows(self) -> RowsIter { -// RowsIter + +impl<'a> QueryResult<'a, Fetch> { + /// Return an iterator over this result's rows + pub fn rows(self) -> RowsIter<'a> { + todo!() + } +} + +pub struct RowsIter<'a> (QueryResult<'a, Fetch>); + +// impl<'a> Iterator for RowsIter<'a> { +// type Item = Row<'a, Fetch>; + +// fn next(&'a mut self) -> Option { +// self.0.res.fetch_row() // } // } diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs index 02e22fbc5fa1e..4718b8525a2c8 100644 --- a/rust/mariadb/src/service_sql/raw.rs +++ b/rust/mariadb/src/service_sql/raw.rs @@ -13,7 +13,6 @@ use std::sync::Once; use bindings::sql_service as GLOBAL_SQL_SERVICE; use super::error::ClientError; -use super::Row; use crate::bindings; pub struct Fetch; @@ -25,9 +24,10 @@ impl RState for Store {} pub struct RawConnection(NonNull); pub struct RawResult(NonNull, PhantomData); -pub struct RawRow(bindings::MYSQL_ROW); -pub struct RowsIter { - res: RawResult, + +pub struct Row<'a, S: RState> { + inner: bindings::MYSQL_ROW, + res: &'a RawResult, } pub struct ConnOpts { @@ -140,15 +140,21 @@ impl RawConnection { } } -impl Drop for RawConnection { - fn drop(&mut self) { - unsafe { (*GLOBAL_SQL_SERVICE).mysql_close_func.unwrap()(self.0.as_ptr()) }; +impl RawResult { + pub fn fetch_row(&mut self) -> Option> { + let rptr = unsafe {bindings::server_mysql_fetch_row(self.0.as_ptr())}; + + if rptr.is_null() { + None + } else { + Some(Row { inner: rptr, res: self }) + } } } -impl RawResult { - pub fn iter_rows(self) -> RowsIter { - RowsIter(self) +impl Drop for RawConnection { + fn drop(&mut self) { + unsafe { (*GLOBAL_SQL_SERVICE).mysql_close_func.unwrap()(self.0.as_ptr()) }; } } @@ -158,24 +164,6 @@ impl Drop for RawResult { } } -impl<'a> Iterator for RowsIter { - type Item = Row<'a>; - - fn next(&mut self) -> Option { - let ptr = - unsafe { (*GLOBAL_SQL_SERVICE).mysql_fetch_row_func.unwrap()(self.0 .0.as_ptr()) }; - - if ptr.is_null() { - None - } else { - Some(Row { - inner: RawRow(ptr), - res: todo!(), - }) - } - } -} - /// static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new(); From b8c38de7f97f07a9c0d65b80ea7b4ac971aca669 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 3 Feb 2023 05:04:28 -0500 Subject: [PATCH 18/34] Work on clevis plugin --- rust/examples/keymgt-clevis/src/lib.rs | 88 ---------- .../keymgt-clevis/Cargo.toml | 1 + rust/plugins/keymgt-clevis/src/lib.rs | 159 ++++++++++++++++++ 3 files changed, 160 insertions(+), 88 deletions(-) delete mode 100644 rust/examples/keymgt-clevis/src/lib.rs rename rust/{examples => plugins}/keymgt-clevis/Cargo.toml (91%) create mode 100644 rust/plugins/keymgt-clevis/src/lib.rs diff --git a/rust/examples/keymgt-clevis/src/lib.rs b/rust/examples/keymgt-clevis/src/lib.rs deleted file mode 100644 index fb44ecba59396..0000000000000 --- a/rust/examples/keymgt-clevis/src/lib.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! - -#![allow(unused)] - -use std::cell::UnsafeCell; -use std::ffi::c_void; -use std::sync::atomic::{AtomicU32, Ordering}; - -use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb::plugin::prelude::*; -use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, -}; -use mariadb::service_sql::MySqlConn; -use mariadb::{debug, info, sysvar_atomic}; - -const KEY_TABLE: &str = "mysql.clevis_keys"; -const SERVER_TABLE: &str = "mysql.clevis_servers"; - -struct KeyMgtClevis; - -impl Init for KeyMgtClevis { - /// Create needed tables - fn init() -> Result<(), InitError> { - debug!("init for KeyMgtClevis"); - - let conn = MySqlConn::connect_local().map_err(|_| InitError)?; - conn.execute(&format!( - "CREATE TABLE IF NOT EXISTS {KEY_TABLE} ( - key_id INT UNSIGNED NOT NULL, - key_version INT UNSIGNED NOT NULL, - key_server VARBINARY(64) NOT NULL, - key VARBINARY((16) NOT NULL, - PRIMARY KEY (key_id, key_version) - ) ENGINE=InnoDB" - )) - .map_err(|_| InitError)?; - conn.execute(&format!( - "CREATE TABLE IF NOT EXISTS {SERVER_TABLE} ( - server VARBINARY(64) NOT NULL PRIMARY KEY, - verify VARBINARY(2048) - enc VARBINARY(2048) - ) ENGINE=InnoDB" - )) - .map_err(|_| InitError)?; - - debug!("finished init for KeyMgtClevis"); - Ok(()) - } - - fn deinit() -> Result<(), InitError> { - debug!("deinit for KeyMgtClevis"); - Ok(()) - } -} - -impl KeyManager for KeyMgtClevis { - fn get_latest_key_version(key_id: u32) -> Result { - let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; - let res = conn.query(&format!( - "SELECT key_version FROM {KEY_TABLE} WHERE key_id = {key_id}" - )) - .map_err(|_| KeyError::Other)?; - - todo!() - } - - fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { - todo!() - } - - fn key_length(key_id: u32, key_version: u32) -> Result { - todo!() - } -} - -register_plugin! { - KeyMgtClevis, - ptype: PluginType::MariaEncryption, - name: "clevis_key_management", - author: "Trevor Gross", - description: "Clevis key management plugin", - license: License::Gpl, - maturity: Maturity::Experimental, - version: "0.1", - init: KeyMgtClevis, // optional - encryption: false, -} diff --git a/rust/examples/keymgt-clevis/Cargo.toml b/rust/plugins/keymgt-clevis/Cargo.toml similarity index 91% rename from rust/examples/keymgt-clevis/Cargo.toml rename to rust/plugins/keymgt-clevis/Cargo.toml index 085fe29b36ded..1b4006778873e 100644 --- a/rust/examples/keymgt-clevis/Cargo.toml +++ b/rust/plugins/keymgt-clevis/Cargo.toml @@ -8,3 +8,4 @@ crate-type = ["cdylib"] [dependencies] mariadb = { path = "../../mariadb" } +ureq = "2.6.2" diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs new file mode 100644 index 0000000000000..7713eb4ffbdf0 --- /dev/null +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -0,0 +1,159 @@ +//! EXAMPLE ONLY: DO NOT USE IN PRODUCTION! + +#![allow(unused)] + +use std::cell::UnsafeCell; +use std::ffi::c_void; +use std::fmt::Write; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Mutex; + +use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; +use mariadb::plugin::prelude::*; +use mariadb::plugin::{ + register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, +}; +use mariadb::service_sql::{ClientError, MySqlConn}; +use mariadb::{debug, error, info, sysvar_atomic}; + +const KEY_TABLE: &str = "mysql.clevis_keys"; +const SERVER_TABLE: &str = "mysql.clevis_servers"; +/// Max length a key can be, used for table size and buffer checking +const KEY_MAX_BYTES: usize = 16; + +static TANG_SERVER: Mutex = Mutex::new(String::new()); + +struct KeyMgtClevis; + +/// Get the JWS body from a server +fn fetch_jws() -> String { + // FIXME: error handling + let url = format!("https://{}", TANG_SERVER.lock().unwrap()); + let body: String = ureq::get("http://example.com") + .call() + .expect(&format!("http request for '{}' failed", url)) + .into_string() + .expect("http request larger than 10MB"); + todo!(); + body +} + +fn make_new_key(conn: &MySqlConn) -> Result { + let server = TANG_SERVER.lock().unwrap(); + format!( + "INSERT IGNORE INTO {KEY_TABLE} + SET key_server = {server} + RETURNING jws" + ); + + // get the jws value + let jws: &str = todo!(); + + todo!() +} + +impl Init for KeyMgtClevis { + /// Create needed tables + fn init() -> Result<(), InitError> { + debug!("init for KeyMgtClevis"); + + let conn = MySqlConn::connect_local().map_err(|_| InitError)?; + conn.execute(&format!( + "CREATE TABLE IF NOT EXISTS {KEY_TABLE} ( + key_id INT UNSIGNED NOT NULL, + key_version INT UNSIGNED NOT NULL, + key_server VARBINARY(64) NOT NULL, + key VARBINARY((16) NOT NULL, + PRIMARY KEY (key_id, key_version) + ) ENGINE=InnoDB" + )) + .map_err(|_| InitError)?; + conn.execute(&format!( + "CREATE TABLE IF NOT EXISTS {SERVER_TABLE} ( + server VARBINARY(64) NOT NULL PRIMARY KEY, + verify VARBINARY(2048) + enc VARBINARY(2048) + ) ENGINE=InnoDB" + )) + .map_err(|_| InitError)?; + + debug!("finished init for KeyMgtClevis"); + Ok(()) + } + + fn deinit() -> Result<(), InitError> { + debug!("deinit for KeyMgtClevis"); + Ok(()) + } +} + +impl KeyManager for KeyMgtClevis { + fn get_latest_key_version(key_id: u32) -> Result { + let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; + // Helper function to print a message with our enviornment + let p_err = |q: &str, conn: &MySqlConn| { + // FIXME: also print connection errors + error!("clevis: get_latest_key_version {key_id} - SQL error on {q} - "); + KeyError::Other + }; + + let mut q = format!("SELECT key_version FROM {KEY_TABLE} WHERE key_id = {key_id}"); + let res = conn.query(&q).map_err(|_| p_err(&q, &conn))?; + + // fuund! fetch result, parse to int + // if let Some(row) = todo!() { + if false { + return Ok(todo!()); + } + + // directly push format string + let key_version: u32 = 1; + write!(q, "AND key_version = {key_version} FOR UPDATE"); + conn.execute("START TRANSACTION") + .map_err(|_| p_err("START TRANSACTION", &conn))?; + conn.query(&q).map_err(|_| p_err(&q, &conn))?; + + let Ok(new_key) = make_new_key(&conn) else { + conn.execute("ROLLBACK").map_err(|_| p_err("ROLLBACK", &conn))?; + return todo!(); + }; + + let q = format!( + r#"INSERT INTO {KEY_TABLE} VALUES ( + {key_id}, {key_version}, "{server_name}", {new_key} )"#, + server_name = TANG_SERVER.lock().unwrap() + ); + conn.execute(&q).map_err(|_| p_err(&q, &conn))?; + + todo!() + } + + fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; + let q = format!( + "SELECT key FROM {KEY_TABLE} WHERE key_id = {key_id} AND key_version = {key_version}" + ); + conn.query(&q).map_err(|_| KeyError::Other)?; + // TODO: generate key with server + let key: &[u8] = todo!(); + dst[..key.len()].copy_from_slice(key); + Ok(()) + } + + fn key_length(_key_id: u32, _key_version: u32) -> Result { + Ok(KEY_MAX_BYTES) + } +} + +register_plugin! { + KeyMgtClevis, + ptype: PluginType::MariaEncryption, + name: "clevis_key_management", + author: "Trevor Gross", + description: "Clevis key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: KeyMgtClevis, + encryption: false, +} From b0fc96de6f7cfbd595a5ae90d20e65de37d80c5c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 4 Feb 2023 01:09:15 -0500 Subject: [PATCH 19/34] Fix sys crate rebuilds --- rust/Cargo.toml | 4 ++-- rust/README.md | 30 +++++++++++++++++++++++++++++ rust/bindings/build.rs | 2 +- rust/examples/encryption/Cargo.toml | 2 +- rust/macros/README.md | 3 +++ rust/mariadb/src/service_sql/raw.rs | 9 ++++++++- rust/plugins/README.md | 4 ++++ 7 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 rust/plugins/README.md diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7bc9bf79e0e00..cd5c15254140e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,6 +8,6 @@ members = [ "macros", "examples/keymgt-basic", "examples/keymgt-debug", - "examples/keymgt-clevis", - "examples/encryption" + "examples/encryption", + "plugins/keymgt-clevis", ] diff --git a/rust/README.md b/rust/README.md index 110d97475d499..80850afbdd38c 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,3 +1,33 @@ # Rust support for MariaDB The purpose of this module is to be able to write plugins for MariaDB in Rust. + +## Building + +The Rust portion of this repository does not yet integrate with the main MariaDB +CMake build system to statically link plugins (adding this is a goal). + +To build dynamically, simply run `cargo build` within this `/rust` directory. + +## Testing with Docker + + +```sh +# Build the image. Change the directory (../) if not building in `rust/` +docker build -f Dockerfile ../ --tag mdb-plugin-ex + +# Run the container +docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c \ + mdb-plugin-ex \ + --plugin-maturity=experimental +# --plugin-load=libbasic \ +# --plugin-load=libencryption +# --plugin-load=libdebug_key_management + +# Enter a SQL console +docker exec -it mdb-plugin-ex-c mysql -pexample + +# Install desired plugins +INSTALL PLUGIN basic_key_management SONAME 'libbasic.so'; +INSTALL PLUGIN encryption_example SONAME 'libencryption_example.so'; +``` diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 80607043959c3..d81279a930325 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -59,7 +59,7 @@ impl BuildCallbacks { fn main() { // Tell cargo to invalidate the built crate whenever the wrapper changes - println!("cargo:rerun-if-changed=wrapper.h"); + println!("cargo:rerun-if-changed=src/wrapper.h"); // Run cmake to configure only Command::new("cmake") diff --git a/rust/examples/encryption/Cargo.toml b/rust/examples/encryption/Cargo.toml index 6885eb97d6170..71e17a51ce84b 100644 --- a/rust/examples/encryption/Cargo.toml +++ b/rust/examples/encryption/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "encryption" +name = "encryption-example" version = "0.1.0" edition = '2021' diff --git a/rust/macros/README.md b/rust/macros/README.md index e69de29bb2d1d..88494c0b5e24f 100644 --- a/rust/macros/README.md +++ b/rust/macros/README.md @@ -0,0 +1,3 @@ +# Macros + +This directory contains procedural macros used by the Rust portion of MariaDB diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs index 4718b8525a2c8..168aec0e4f756 100644 --- a/rust/mariadb/src/service_sql/raw.rs +++ b/rust/mariadb/src/service_sql/raw.rs @@ -2,7 +2,7 @@ //! //! RawConnection comes almost directly from the `diesel` client crate, since //! they have that all figured out pretty well. Reference: -//! https://github.com/diesel-rs/diesel/blob/88129db2fbed49d3ecd41bafff2a5932f1621c2c/diesel/src/mysql/connection/raw.rs#L223 +//! use std::ffi::{CStr, CString}; use std::marker::PhantomData; @@ -22,14 +22,19 @@ pub trait RState {} impl RState for Fetch {} impl RState for Store {} +/// pub struct RawConnection(NonNull); + +/// Re pub struct RawResult(NonNull, PhantomData); +/// Representation of a single row, as part of a SQL query pub struct Row<'a, S: RState> { inner: bindings::MYSQL_ROW, res: &'a RawResult, } +/// Options for connecting to a remote SQL server pub struct ConnOpts { host: Option, database: Option, @@ -64,6 +69,7 @@ impl RawConnection { result } + /// Connect to the local SQL server pub(super) fn connect_local(&self) -> Result<(), ClientError> { let res = unsafe { (*GLOBAL_SQL_SERVICE).mysql_real_connect_local_func.unwrap()(self.0.as_ptr()) @@ -75,6 +81,7 @@ impl RawConnection { } } + /// Connect to a remote server pub(super) fn connect(&self, conn_opts: &ConnOpts) -> Result<(), ClientError> { let host = conn_opts.host.as_ref(); let user = conn_opts.user.as_ref(); diff --git a/rust/plugins/README.md b/rust/plugins/README.md new file mode 100644 index 0000000000000..dd2a57e6bf868 --- /dev/null +++ b/rust/plugins/README.md @@ -0,0 +1,4 @@ +# Plugins + +This directory contains plugins that are intended to be usable in practice, as +opposed to example plugins. From 9d018ffcb24e60a5af59d7735593562fd992f283 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 4 Feb 2023 06:41:46 -0500 Subject: [PATCH 20/34] Work on mariadb sql service --- rust/bindings/build.rs | 12 +- rust/bindings/src/hand_imples.rs | 2 + rust/examples/encryption/src/lib.rs | 13 -- rust/macros/src/lib.rs | 2 + rust/macros/src/register_plugin.rs | 53 +++--- rust/mariadb/src/common.rs | 71 ++++++++ rust/mariadb/src/lib.rs | 3 + rust/mariadb/src/plugin.rs | 41 ++++- rust/mariadb/src/plugin/variables.rs | 5 + rust/mariadb/src/service_sql.rs | 55 +++--- rust/mariadb/src/service_sql/error.rs | 17 ++ rust/mariadb/src/service_sql/raw.rs | 240 +++++++++++++++++++------- rust/plugins/keymgt-clevis/src/lib.rs | 49 ++++-- 13 files changed, 412 insertions(+), 151 deletions(-) create mode 100644 rust/mariadb/src/common.rs diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index d81279a930325..3bd1485659fac 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -5,7 +5,7 @@ use std::env; use std::path::PathBuf; use std::process::Command; -use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks}; +use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks, DeriveInfo}; use bindgen::EnumVariation; // `math.h` seems to double define some things, To avoid this, we ignore them. @@ -32,6 +32,8 @@ const IGNORE_MACROS: [&str; 20] = [ "IPPORT_RESERVED", ]; +const DERIVE_COPY_NAMES: [&str; 1] = ["enum_field_types"]; + #[derive(Debug)] struct BuildCallbacks(HashSet); @@ -49,6 +51,14 @@ impl ParseCallbacks for BuildCallbacks { fn process_comment(&self, comment: &str) -> Option { Some(doxygen_rs::transform(comment)) } + + fn add_derives(&self, _info: &DeriveInfo<'_>) -> Vec { + if DERIVE_COPY_NAMES.contains(&_info.name) { + vec!["Copy".to_owned()] + } else { + vec![] + } + } } impl BuildCallbacks { diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index e676ce07a83de..2ea164c28db66 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -7,6 +7,8 @@ pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; // We hand write these stucts because the definition is tricky, not all fields are // always present + +/// A basic system variable #[repr(C)] #[derive(Debug)] pub struct st_mysql_sys_var_basic { diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs index 7cd19ac28ca84..979ee94e0a6ec 100644 --- a/rust/examples/encryption/src/lib.rs +++ b/rust/examples/encryption/src/lib.rs @@ -167,16 +167,3 @@ register_plugin! { init: RustEncryption, // optional encryption: true, } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn print_statics() { - unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; - dbg!(&_maria_plugin_interface_version_); - dbg!(&_maria_sizeof_struct_st_plugin_); - unsafe { dbg!(&_maria_plugin_declarations_) }; - } -} diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index d5e21ffa31a01..68ffa0fed531a 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -11,6 +11,8 @@ mod register_plugin; use proc_macro::TokenStream; /// Macro to use to register a plugin +/// +/// See the `plugin` module in the main `mariadb` crate for examples. #[proc_macro] pub fn register_plugin(item: TokenStream) -> TokenStream { register_plugin::entry(item) diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index 70c4a409fe3a6..9ae43529d8c68 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -1,4 +1,5 @@ -// FIXME: Doesn't yet verify plugin type is Encryption +//! + #![allow(unused)] use proc_macro::TokenStream; @@ -171,22 +172,26 @@ impl PluginInfo { let name = expect_litstr(&self.name)?; let plugin_st_name = Ident::new(&format!("_ST_PLUGIN_{}", name.value()), Span::call_site()); - let ty_as_wkeymgt = quote! {<#type_ as ::mariadb::plugin::encryption_wrapper::WrapKeyMgr>}; - let ty_as_wenc = quote! {<#type_ as ::mariadb::plugin::encryption_wrapper::WrapEncryption>}; - let interface_version = - quote! {::mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as ::std::ffi::c_int}; - let get_key_vers = quote! {Some(#ty_as_wkeymgt::wrap_get_latest_key_version)}; - let get_key = quote! {Some(#ty_as_wkeymgt::wrap_get_key)}; + let ty_as_wkeymgt = + quote! { <#type_ as ::mariadb::plugin::encryption_wrapper::WrapKeyMgr> }; + let ty_as_wenc = + quote! { <#type_ as ::mariadb::plugin::encryption_wrapper::WrapEncryption> }; + let interface_version = quote! { ::mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as ::std::ffi::c_int }; + let get_key_vers = quote! { Some(#ty_as_wkeymgt::wrap_get_latest_key_version) }; + let get_key = quote! { Some(#ty_as_wkeymgt::wrap_get_key) }; let (crypt_size, crypt_init, crypt_update, crypt_finish, crypt_len); + if expect_bool(&self.encryption)? { - crypt_size = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_size)}; - crypt_init = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_init)}; - crypt_update = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_update)}; - crypt_finish = quote! {Some(#ty_as_wenc::wrap_crypt_ctx_finish)}; - crypt_len = quote! {Some(#ty_as_wenc::wrap_encrypted_length)}; + // Use encryption if given + crypt_size = quote! { Some(#ty_as_wenc::wrap_crypt_ctx_size) }; + crypt_init = quote! { Some(#ty_as_wenc::wrap_crypt_ctx_init) }; + crypt_update = quote! { Some(#ty_as_wenc::wrap_crypt_ctx_update) }; + crypt_finish = quote! { Some(#ty_as_wenc::wrap_crypt_ctx_finish) }; + crypt_len = quote! { Some(#ty_as_wenc::wrap_encrypted_length) }; } else { - let none = quote! {None}; + // Default to builtin encryption + let none = quote! { None }; ( crypt_size, crypt_init, @@ -226,10 +231,10 @@ impl PluginInfo { let (fn_init, fn_deinit); if let Some(init_ty) = self.init { let ty_as_init = quote! {<#init_ty as ::mariadb::plugin::wrapper::WrapInit>}; - fn_init = quote! {Some(#ty_as_init::wrap_init)}; - fn_deinit = quote! {Some(#ty_as_init::wrap_deinit)}; + fn_init = quote! { Some(#ty_as_init::wrap_init) }; + fn_deinit = quote! { Some(#ty_as_init::wrap_deinit) }; } else { - let none = quote! {None}; + let none = quote! { None }; (fn_init, fn_deinit) = (none.clone(), none); } @@ -237,16 +242,16 @@ impl PluginInfo { ::mariadb::bindings::st_maria_plugin { type_: #ptype.to_ptype_registration(), info: #plugin_st_name.as_ptr().cast_mut().cast(), - name: mariadb::cstr::cstr!(#name).as_ptr(), - author: mariadb::cstr::cstr!(#author).as_ptr(), - descr: mariadb::cstr::cstr!(#description).as_ptr(), + name: ::mariadb::cstr::cstr!(#name).as_ptr(), + author: ::mariadb::cstr::cstr!(#author).as_ptr(), + descr: ::mariadb::cstr::cstr!(#description).as_ptr(), license: #license.to_license_registration(), init: #fn_init, deinit: #fn_deinit, version: #version as ::std::ffi::c_uint, status_vars: ::std::ptr::null_mut(), system_vars: ::std::ptr::null_mut(), - version_info: mariadb::cstr::cstr!("0.1").as_ptr(), + version_info: ::mariadb::cstr::cstr!("0.1").as_ptr(), maturity: #maturity.to_maturity_registration(), }, }; @@ -371,13 +376,14 @@ fn expect_bool(field_opt: &Option) -> syn::Result { } } +/// Expect a literal string, error if that's not the case fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { let field = field_opt.as_ref().unwrap(); - let Expr::Lit(lit) = field else { + let Expr::Lit(lit) = field else { // got non-literal let msg = "expected literal expression for this field"; return Err(Error::new_spanned(field, msg)); }; - let Lit::Str(litstr) = &lit.lit else { + let Lit::Str(litstr) = &lit.lit else { // got literal that wasn't a string let msg = "only literal strings are allowed for this field"; return Err(Error::new_spanned(field, msg)); }; @@ -385,7 +391,8 @@ fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { Ok(litstr) } -/// Convert a string likx "1.2" to a hex like "0x0102" +/// Convert a string like "1.2" to a hex like "0x0102". Error if no decimal, or +/// if either value exceeds a u8. fn version_int(s: &str) -> Result { const USAGE_MSG: &str = r#"expected a two position semvar string, e.g. "1.2""#; if s.chars().filter(|x| *x == '.').count() != 1 { diff --git a/rust/mariadb/src/common.rs b/rust/mariadb/src/common.rs new file mode 100644 index 0000000000000..90ceeb249ede5 --- /dev/null +++ b/rust/mariadb/src/common.rs @@ -0,0 +1,71 @@ +use std::ffi::c_void; +use std::slice; + +use crate::bindings; + +/// A SQL type and value +#[non_exhaustive] +pub enum Value<'a> { + Decimal(&'a [u8]), + Tiny(i8), + Short(i16), + Long(i64), + Float(f32), + Double(f64), + Null, + Time, // todo + TimeStamp, // todo + Date, // todo + DateTime, // todo + Year, // todo + Varchar(&'a [u8]), + Json(&'a [u8]), +} + +impl<'a> Value<'a> { + /// Supply a + pub(crate) unsafe fn from_ptr( + ty: bindings::enum_field_types, + ptr: *const c_void, + len: usize, + ) -> Self { + // helper function to make a slice + let buf_callback = || slice::from_raw_parts(ptr.cast(), len); + + match ty { + bindings::enum_field_types::MYSQL_TYPE_DECIMAL => Self::Decimal(buf_callback()), + bindings::enum_field_types::MYSQL_TYPE_TINY => Self::Tiny(*ptr.cast()), + bindings::enum_field_types::MYSQL_TYPE_SHORT => Self::Short(*ptr.cast()), + bindings::enum_field_types::MYSQL_TYPE_LONG => Self::Long(*ptr.cast()), + bindings::enum_field_types::MYSQL_TYPE_FLOAT => Self::Float(*ptr.cast()), + bindings::enum_field_types::MYSQL_TYPE_DOUBLE => Self::Double(*ptr.cast()), + bindings::enum_field_types::MYSQL_TYPE_NULL => Self::Null, + bindings::enum_field_types::MYSQL_TYPE_TIMESTAMP => todo!(), + bindings::enum_field_types::MYSQL_TYPE_LONGLONG => todo!(), + bindings::enum_field_types::MYSQL_TYPE_INT24 => todo!(), + bindings::enum_field_types::MYSQL_TYPE_DATE => todo!(), + bindings::enum_field_types::MYSQL_TYPE_TIME => todo!(), + bindings::enum_field_types::MYSQL_TYPE_DATETIME => todo!(), + bindings::enum_field_types::MYSQL_TYPE_YEAR => todo!(), + bindings::enum_field_types::MYSQL_TYPE_NEWDATE => todo!(), + bindings::enum_field_types::MYSQL_TYPE_VARCHAR => todo!(), + bindings::enum_field_types::MYSQL_TYPE_BIT => todo!(), + bindings::enum_field_types::MYSQL_TYPE_TIMESTAMP2 => todo!(), + bindings::enum_field_types::MYSQL_TYPE_DATETIME2 => todo!(), + bindings::enum_field_types::MYSQL_TYPE_TIME2 => todo!(), + bindings::enum_field_types::MYSQL_TYPE_BLOB_COMPRESSED => todo!(), + bindings::enum_field_types::MYSQL_TYPE_VARCHAR_COMPRESSED => todo!(), + bindings::enum_field_types::MYSQL_TYPE_NEWDECIMAL => todo!(), + bindings::enum_field_types::MYSQL_TYPE_ENUM => todo!(), + bindings::enum_field_types::MYSQL_TYPE_SET => todo!(), + bindings::enum_field_types::MYSQL_TYPE_TINY_BLOB => todo!(), + bindings::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB => todo!(), + bindings::enum_field_types::MYSQL_TYPE_LONG_BLOB => todo!(), + bindings::enum_field_types::MYSQL_TYPE_BLOB => todo!(), + bindings::enum_field_types::MYSQL_TYPE_VAR_STRING => todo!(), + bindings::enum_field_types::MYSQL_TYPE_STRING => todo!(), + bindings::enum_field_types::MYSQL_TYPE_GEOMETRY => todo!(), + _ => todo!(), + } + } +} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index bee0a5a6381c3..7439e07d067e8 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -3,9 +3,12 @@ use time::{format_description, OffsetDateTime}; +mod common; pub mod plugin; pub mod service_sql; +#[doc(inline)] +pub use common::*; #[doc(hidden)] pub use cstr; #[doc(hidden)] diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 15ea8e61f874f..cf89f7470d3be 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -1,6 +1,40 @@ -//! Parent module for all plugin types - -// use std::cell::UnsafeCell; +//! Module for everything relevant to plugins +//! +//! Usage: +//! +//! ``` +//! use mariadb::plugin::prelude::*; +//! +//! // May be empty or not +//! struct ExampleKeyManager; +//! +//! impl KeyManager for ExampleKeyManager { +//! // ... +//! # fn get_latest_key_version(key_id: u32) -> Result { todo!() } +//! # fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { todo!() } +//! # fn key_length(_key_id: u32, _key_version: u32) -> Result { todo!() } +//! } +//! +//! impl Init for ExampleKeyManager { +//! // ... +//! # fn init() -> Result<(), InitError> { todo!() } +//! # fn deinit() -> Result<(), InitError> { todo!() } +//! } +//! +//! register_plugin! { +//! ExampleKeyManager, // Name of the struct implementing KeyManager +//! ptype: PluginType::MariaEncryption, // plugin type; only encryption supported for now +//! name: "name_as_sql_server_sees_it", // loadable plugin name +//! author: "Author Name", // author's name +//! description: "Sample key managment plugin", // give a description +//! license: License::Gpl, // select a license type +//! maturity: Maturity::Experimental, // indicate license maturity +//! version: "0.1", // provide an "a.b" version +//! init: ExampleKeyManager // optional: struct implementing Init if needed +//! encryption: false, // false to use default encryption, true if your +//! // struct implements 'Encryption' +//! } +//! ``` use std::ffi::{c_int, c_uint}; @@ -88,6 +122,7 @@ impl Maturity { } } +/// Indicate that an error occured during initialization or deinitialization pub struct InitError; /// Implement this trait if your plugin requires init or deinit functions diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 736ce5b1e9902..1d2da7d1245e5 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,9 +1,14 @@ +//! "show variables" and "system variables" +//! +//! + use std::mem::ManuallyDrop; use std::ptr; use bindings::st_mysql_sys_var_basic; use mariadb_sys as bindings; +/// Basicallly, a system variable will #[repr(C)] union SysVar { basic: ManuallyDrop>, diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index cc4eec3a0c076..f50eeffb1d9b0 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -15,58 +15,49 @@ use bindings::sql_service as GLOBAL_SQL_SERVICE; use raw::RawConnection; pub use self::error::ClientError; +use self::raw::{ClientResult, FetchedRow, RState, RawResult}; pub use self::raw::{Fetch, Store}; -use self::raw::{RState, RawResult,Row}; use crate::bindings; use crate::plugin::wrapper::UnsafeSyncCell; +/// A connection to a local or remote SQL server pub struct MySqlConn(RawConnection); -/// Need to noodle on this for a bit. I think it makes sense that we have a `&` -/// reference to the creating `MySqlConn`, since that means the connection won't -/// be dropped too early, and can't be changed. -pub struct QueryResult<'a, S: RState> { - res: RawResult, - conn: &'a MySqlConn, -} - - - impl MySqlConn { /// Connect to the local server - pub fn connect_local() -> Result { - let conn = RawConnection::new(); + pub fn connect_local() -> ClientResult { + let mut conn = RawConnection::new(); conn.connect_local()?; Ok(Self(conn)) } /// Run a query and discard the results - pub fn execute<'a>(&'a self, q: &str) -> Result<(), ClientError> { - self.0.real_query(q)?; + pub fn execute<'a>(&'a mut self, q: &str) -> ClientResult<()> { + self.0.query(q)?; Ok(()) } + /// Run a query for lazily loaded results - pub fn query<'a>(&'a self, q: &str) -> Result, ClientError> { - self.0.real_query(q)?; - let res = self.0.fetch_result()?; - Ok(QueryResult { res, conn: &self }) + pub fn query<'a>(&'a mut self, q: &str) -> ClientResult> { + self.0.query(q)?; + let res = self.0.prep_fetch_result()?; + // let cols = + Ok(FetchedRows(res)) } } +/// Representation of returned rows from a query +pub struct FetchedRows<'a>(RawResult<'a, Fetch>); -impl<'a> QueryResult<'a, Fetch> { - /// Return an iterator over this result's rows - pub fn rows(self) -> RowsIter<'a> { - todo!() +impl<'a> FetchedRows<'a> { + pub fn next(&mut self) -> Option { + self.0.fetch_next_row() } } -pub struct RowsIter<'a> (QueryResult<'a, Fetch>); - -// impl<'a> Iterator for RowsIter<'a> { -// type Item = Row<'a, Fetch>; - -// fn next(&'a mut self) -> Option { -// self.0.res.fetch_row() -// } -// } +impl Drop for FetchedRows<'_> { + /// Consume the rest of the rows + fn drop(&mut self) { + while let Some(_) = self.next() {} + } +} diff --git a/rust/mariadb/src/service_sql/error.rs b/rust/mariadb/src/service_sql/error.rs index 01dd9b0d11b23..53f9d6b8cba74 100644 --- a/rust/mariadb/src/service_sql/error.rs +++ b/rust/mariadb/src/service_sql/error.rs @@ -1,8 +1,14 @@ +use std::fmt::Display; + use crate::bindings; #[non_exhaustive] pub enum ClientError { // CommandsOutOfSync = bindings::CR_COMMANDS_OUT_OF_SYNC + /// Error connecting + ConnectError(u32, String), + QueryError(u32, String), + FetchError(u32, String), Unspecified, } @@ -13,3 +19,14 @@ impl From for ClientError { } } } + +impl Display for ClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ClientError::ConnectError(n, e) => write!(f, "connection failed with {n}: '{e}'"), + ClientError::QueryError(n, e) => write!(f, "query failed with {n}: '{e}'"), + ClientError::FetchError(n, e) => write!(f, "fetch failed with {n}: '{e}'"), + ClientError::Unspecified => write!(f, "unspecified error"), + } + } +} diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs index 168aec0e4f756..c8859bad4464b 100644 --- a/rust/mariadb/src/service_sql/raw.rs +++ b/rust/mariadb/src/service_sql/raw.rs @@ -4,16 +4,24 @@ //! they have that all figured out pretty well. Reference: //! -use std::ffi::{CStr, CString}; +use std::cell::UnsafeCell; +use std::ffi::{c_void, CStr, CString}; use std::marker::PhantomData; -use std::ptr; use std::ptr::NonNull; use std::sync::Once; +use std::{mem, ptr, slice, str}; -use bindings::sql_service as GLOBAL_SQL_SERVICE; +use bindings::{sql_service as GLOBAL_SQL_SERVICE, sql_service_st}; use super::error::ClientError; -use crate::bindings; +use crate::{bindings, Value}; + +/// Get a function from our global SQL service +macro_rules! global_func { + ($fname:ident) => { + unsafe { (*GLOBAL_SQL_SERVICE).$fname.unwrap() } + }; +} pub struct Fetch; pub struct Store; @@ -22,17 +30,11 @@ pub trait RState {} impl RState for Fetch {} impl RState for Store {} -/// -pub struct RawConnection(NonNull); - -/// Re -pub struct RawResult(NonNull, PhantomData); +/// Type wrapper for `Result` with a `ClientError` error variant +pub type ClientResult = Result; -/// Representation of a single row, as part of a SQL query -pub struct Row<'a, S: RState> { - inner: bindings::MYSQL_ROW, - res: &'a RawResult, -} +/// A connection to a remote or local server +pub struct RawConnection(NonNull); /// Options for connecting to a remote SQL server pub struct ConnOpts { @@ -46,43 +48,46 @@ pub struct ConnOpts { } impl RawConnection { + /// Create a new connection pub(super) fn new() -> Self { fn_thread_unsafe_lib_init(); - let p_conn = unsafe { (*GLOBAL_SQL_SERVICE).mysql_init_func.unwrap()(ptr::null_mut()) }; + // Attempt to connect, fail if nonzero (unexpected) + let p_conn = unsafe { global_func!(mysql_init_func)(ptr::null_mut()) }; let p_conn = NonNull::new(p_conn).expect("OOM: connection allocation failed"); let result = RawConnection(p_conn); - // let charset_result = unsafe { - // bindings::mysql_options( - // result.0.as_ptr(), - // bindings::mysql_option::MYSQL_SET_CHARSET_NAME, - // b"utf8mb4\0".as_ptr() as *const libc::c_void, - // ) - // }; - // assert_eq!( - // 0, charset_result, - // "MYSQL_SET_CHARSET_NAME was not \ - // recognized as an option by MySQL. This should never \ - // happen." - // ); + // Validate we are using an expected charset + let charset = unsafe { + global_func!(mysql_options_func)( + result.0.as_ptr(), + bindings::mysql_option::MYSQL_SET_CHARSET_NAME, + b"utf8mb4\0".as_ptr().cast(), + ) + }; + assert_eq!( + 0, charset, + "MYSQL_SET_CHARSET_NAME value of {charset} not recognized" + ); result } /// Connect to the local SQL server - pub(super) fn connect_local(&self) -> Result<(), ClientError> { - let res = unsafe { - (*GLOBAL_SQL_SERVICE).mysql_real_connect_local_func.unwrap()(self.0.as_ptr()) - }; + pub(super) fn connect_local(&mut self) -> ClientResult<()> { + let res = unsafe { global_func!(mysql_real_connect_local_func)(self.0.as_ptr()) }; + self.check_for_errors(ClientError::ConnectError)?; if res.is_null() { Ok(()) } else { - Err((res as i32).into()) + Err(ClientError::ConnectError( + 0, + "unspecified connect error".to_owned(), + )) } } /// Connect to a remote server - pub(super) fn connect(&self, conn_opts: &ConnOpts) -> Result<(), ClientError> { + pub fn connect(&mut self, conn_opts: &ConnOpts) -> ClientResult<()> { let host = conn_opts.host.as_ref(); let user = conn_opts.user.as_ref(); let pw = conn_opts.password.as_ref(); @@ -93,7 +98,7 @@ impl RawConnection { // TODO: see CLIENT_X flags in mariadb_com.h let res = unsafe { // Make sure you don't use the fake one! - (*GLOBAL_SQL_SERVICE).mysql_real_connect_func.unwrap()( + global_func!(mysql_real_connect_func)( self.0.as_ptr(), host.map(|c| c.as_ptr()).unwrap_or(ptr::null()), user.map(|c| c.as_ptr()).unwrap_or(ptr::null()), @@ -105,76 +110,189 @@ impl RawConnection { ) }; + self.check_for_errors(ClientError::ConnectError)?; + if res.is_null() { Ok(()) } else { - Err((res as i32).into()) + Err(ClientError::QueryError( + 0, + "unspecified query error".to_owned(), + )) } - - // let last_error_message = self.last_error_message(); - // if last_error_message.is_empty() { - // Ok(()) - // } else { - // Err(ConnectionError::BadConnection(last_error_message)) - // } } - pub fn real_query(&self, q: &str) -> Result<(), ClientError> { + /// Execute a query + pub fn query(&mut self, q: &str) -> ClientResult<()> { unsafe { let p_self: *const Self = self; - // mysql_real_query in mariadb_lib.c - let res = (*GLOBAL_SQL_SERVICE).mysql_real_query_func.unwrap()( + // mysql_real_query in mariadb_lib.c. Real just means use buffers + // instead of c strings + let res = global_func!(mysql_real_query_func)( p_self.cast_mut().cast(), q.as_ptr().cast(), q.len().try_into().unwrap(), ); + self.check_for_errors(ClientError::QueryError)?; if res == 0 { Ok(()) } else { - Err(res.into()) + Err(ClientError::QueryError( + 0, + "unspecified query error".to_owned(), + )) } } } - pub fn fetch_result(&self) -> Result, ClientError> { + /// Prepare the result for iteration, but do not store + pub fn prep_fetch_result<'a>(&'a mut self) -> ClientResult> { let res = unsafe { bindings::mysql_use_result(self.0.as_ptr()) }; + self.check_for_errors(ClientError::QueryError)?; match NonNull::new(res) { - Some(ptr) => Ok(RawResult(ptr, PhantomData)), - None => Err((res as i32).into()), + Some(ptr) => unsafe { + let field_count = get_field_count(self, ptr.as_ptr())?; + let field_ptr = bindings::mysql_fetch_fields(ptr.as_ptr()); + let fields = slice::from_raw_parts(field_ptr, field_count as usize); + Ok(RawResult { + inner: ptr, + fields: mem::transmute(fields), + _marker: PhantomData, + }) + }, + None => Err(ClientError::FetchError( + 0, + "unspecified fetch error".to_owned(), + )), + } + } + + /// Get the last error message if available and if so, apply it to function `f` + /// + /// `f` is usually a variant of `ClientError::SomeError`, since those are functions + pub fn check_for_errors(&mut self, f: F) -> ClientResult<()> + where + F: FnOnce(u32, String) -> ClientError, + { + unsafe { + let cs = CStr::from_ptr(global_func!(mysql_error_func)(self.0.as_ptr())); + let emsg = cs.to_string_lossy(); + let errno = global_func!(mysql_errno_func)(self.0.as_ptr()); + + if emsg.is_empty() && errno == 0 { + Ok(()) + } else { + Err(f(errno, emsg.into_owned())) + } } } } -impl RawResult { - pub fn fetch_row(&mut self) -> Option> { - let rptr = unsafe {bindings::server_mysql_fetch_row(self.0.as_ptr())}; +impl Drop for RawConnection { + /// Close the connection + fn drop(&mut self) { + unsafe { global_func!(mysql_close_func)(self.0.as_ptr()) }; + } +} + +/// Thin wrapper over a result +pub struct RawResult<'a, S: RState> { + inner: NonNull, + fields: &'a [Field], + _marker: PhantomData, +} + +impl<'a> RawResult<'a, Fetch> { + pub fn fetch_next_row(&mut self) -> Option { + let rptr = unsafe { global_func!(mysql_fetch_row_func)(self.inner.as_ptr()) }; if rptr.is_null() { None } else { - Some(Row { inner: rptr, res: self }) + Some(FetchedRow { + inner: rptr, + fields: self.fields, + }) } } } -impl Drop for RawConnection { +impl<'a, S: RState> Drop for RawResult<'a, S> { + /// Free the result fn drop(&mut self) { - unsafe { (*GLOBAL_SQL_SERVICE).mysql_close_func.unwrap()(self.0.as_ptr()) }; + unsafe { global_func!(mysql_free_result_func)(self.inner.as_ptr()) }; } } -impl Drop for RawResult { - fn drop(&mut self) { - unsafe { (*GLOBAL_SQL_SERVICE).mysql_free_result_func.unwrap()(self.0.as_ptr()) }; +/// Representation of a single row, as part of a SQL query +pub struct FetchedRow<'a> { + // *mut *mut c_char + inner: bindings::MYSQL_ROW, + fields: &'a [Field], +} + +impl FetchedRow<'_> { + /// Get the field of a given index + pub fn field_value(&self, index: usize) ->Value{ + let field = &self.fields[index]; + assert!(index < self.fields.len()); // extra sanity check + unsafe { + let ptr = *self.inner.add(index); + Value::from_ptr(field.ftype(), ptr.cast(), field.length()) + } + } + + pub fn field_info(&self, index: usize) -> &Field { + &self.fields[index] + } + + /// Get the total number of fields + pub fn field_count(&self) -> usize { + self.fields.len() } } -/// -static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new(); +/// Transparant wrapper around a `MYSQL_FIELD` +#[repr(transparent)] +pub struct Field(UnsafeCell); + +impl Field { + pub fn length(&self) -> usize { + unsafe { (*self.0.get()).length.try_into().unwrap() } + } + + pub fn max_length(&self) -> usize { + unsafe { (*self.0.get()).max_length.try_into().unwrap() } + } + + pub fn name(&self) -> &str { + unsafe { + let inner = &*self.0.get(); + let name_slice = slice::from_raw_parts(inner.name.cast(), inner.name_length as usize); + str::from_utf8(name_slice).expect("unexpected: non-utf8 identifier") + } + } + + fn ftype(&self) -> bindings::enum_field_types { + unsafe { (*self.0.get()).type_ } + } +} + +unsafe fn get_field_count( + conn: &mut RawConnection, + q_result: *const bindings::MYSQL_RES, +) -> ClientResult { + let res = unsafe { global_func!(mysql_num_fields_func)(q_result.cast_mut()) }; + conn.check_for_errors(ClientError::QueryError)?; + Ok(res) +} fn fn_thread_unsafe_lib_init() { + /// + static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new(); + MYSQL_THREAD_UNSAFE_INIT.call_once(|| { // mysql_library_init is defined by `#define mysql_library_init mysql_server_init` // which isn't picked up by bindgen diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 7713eb4ffbdf0..241feae969b5a 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -13,7 +13,7 @@ use mariadb::plugin::prelude::*; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, }; -use mariadb::service_sql::{ClientError, MySqlConn}; +use mariadb::service_sql::{ClientError, Fetch, FetchedRows, MySqlConn}; use mariadb::{debug, error, info, sysvar_atomic}; const KEY_TABLE: &str = "mysql.clevis_keys"; @@ -57,7 +57,7 @@ impl Init for KeyMgtClevis { fn init() -> Result<(), InitError> { debug!("init for KeyMgtClevis"); - let conn = MySqlConn::connect_local().map_err(|_| InitError)?; + let mut conn = MySqlConn::connect_local().map_err(|_| InitError)?; conn.execute(&format!( "CREATE TABLE IF NOT EXISTS {KEY_TABLE} ( key_id INT UNSIGNED NOT NULL, @@ -87,18 +87,31 @@ impl Init for KeyMgtClevis { } } +/// Execute a query, printing an error and returning KeyError if needed. No result +fn run_execute(conn: &mut MySqlConn, q: &str, key_id: u32) -> Result<(), KeyError> { + conn.execute(q).map_err(|e| { + error!("clevis: get_latest_key_version {key_id} - SQL error on {q} - {e}"); + KeyError::Other + }) +} + +/// Execute a query, printing an error, return the result +fn run_query<'a>( + conn: &'a mut MySqlConn, + q: &str, + key_id: u32, +) -> Result, KeyError> { + conn.query(q).map_err(|e| { + error!("clevis: get_latest_key_version {key_id} - SQL error on {q} - {e}"); + KeyError::Other + }) +} + impl KeyManager for KeyMgtClevis { fn get_latest_key_version(key_id: u32) -> Result { - let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; - // Helper function to print a message with our enviornment - let p_err = |q: &str, conn: &MySqlConn| { - // FIXME: also print connection errors - error!("clevis: get_latest_key_version {key_id} - SQL error on {q} - "); - KeyError::Other - }; - + let mut conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; let mut q = format!("SELECT key_version FROM {KEY_TABLE} WHERE key_id = {key_id}"); - let res = conn.query(&q).map_err(|_| p_err(&q, &conn))?; + let _ = run_query(&mut conn, &q, key_id)?; // fuund! fetch result, parse to int // if let Some(row) = todo!() { @@ -109,12 +122,12 @@ impl KeyManager for KeyMgtClevis { // directly push format string let key_version: u32 = 1; write!(q, "AND key_version = {key_version} FOR UPDATE"); - conn.execute("START TRANSACTION") - .map_err(|_| p_err("START TRANSACTION", &conn))?; - conn.query(&q).map_err(|_| p_err(&q, &conn))?; + + run_execute(&mut conn, "START TRANSACTION", key_id)?; + run_query(&mut conn, &q, key_id)?; let Ok(new_key) = make_new_key(&conn) else { - conn.execute("ROLLBACK").map_err(|_| p_err("ROLLBACK", &conn))?; + run_execute(&mut conn, "ROLLBACK", key_id)?; return todo!(); }; @@ -123,13 +136,13 @@ impl KeyManager for KeyMgtClevis { {key_id}, {key_version}, "{server_name}", {new_key} )"#, server_name = TANG_SERVER.lock().unwrap() ); - conn.execute(&q).map_err(|_| p_err(&q, &conn))?; + run_execute(&mut conn, &q, key_id)?; todo!() } fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { - let conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; + let mut conn = MySqlConn::connect_local().map_err(|_| KeyError::Other)?; let q = format!( "SELECT key FROM {KEY_TABLE} WHERE key_id = {key_id} AND key_version = {key_version}" ); @@ -149,7 +162,7 @@ register_plugin! { KeyMgtClevis, ptype: PluginType::MariaEncryption, name: "clevis_key_management", - author: "Trevor Gross", + author: "Daniel Black & Trevor Gross", description: "Clevis key management plugin", license: License::Gpl, maturity: Maturity::Experimental, From 2419c54ecdb6814b157572a9a0b45aad2344c1bc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 5 Feb 2023 02:47:41 -0500 Subject: [PATCH 21/34] Static plugin generation now works --- rust/macros/src/register_plugin.rs | 42 +++++++++++++++------------- rust/mariadb/src/plugin/variables.rs | 3 +- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index 9ae43529d8c68..0333edceee79f 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -276,12 +276,14 @@ impl PluginDef { fn into_output(self) -> TokenStream { let make_ident = |s| Ident::new(s, Span::call_site()); let make_ident_fmt = |s: String| Ident::new(s.as_str(), Span::call_site()); - // let vers_idt_stc = - // make_ident_fmt(format!("builtin_{}_plugin_interface_version", self.name)); + + // static and dynamic identifiers + let vers_idt_stc = + make_ident_fmt(format!("builtin_{}_plugin_interface_version", self.name)); let vers_idt_dyn = make_ident("_maria_plugin_interface_version_"); - // let size_idt_stc = make_ident_fmt(format!("builtin_{}_sizeof_struct_st_plugin", self.name)); + let size_idt_stc = make_ident_fmt(format!("builtin_{}_sizeof_struct_st_plugin", self.name)); let size_idt_dyn = make_ident("_maria_sizeof_struct_st_plugin_"); - // let decl_idt_stc = make_ident_fmt(format!("builtin_{}_plugin", self.name)); + let decl_idt_stc = make_ident_fmt(format!("builtin_{}_plugin", self.name)); let decl_idt_dyn = make_ident("_maria_plugin_declarations_"); let plugin_ty = quote! {::mariadb::bindings::st_maria_plugin}; @@ -298,31 +300,31 @@ impl PluginDef { let ret: TokenStream = quote! { // Different config based on statically or dynamically lynked - // #[no_mangle] - // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] - // static #vers_idt_stc: ::std::ffi::c_int = #version_val; - #[no_mangle] - // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + #[cfg(make_static_lib)] + static #vers_idt_stc: ::std::ffi::c_int = #version_val; + + #[no_mangle] + #[cfg(not(make_static_lib))] static #vers_idt_dyn: ::std::ffi::c_int = #version_val; - // #[no_mangle] - // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] - // static #size_idt_stc: ::std::ffi::c_int = #size_val; + #[no_mangle] + #[cfg(make_static_lib)] + static #size_idt_stc: ::std::ffi::c_int = #size_val; #[no_mangle] - // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + #[cfg(not(make_static_lib))] static #size_idt_dyn: ::std::ffi::c_int = #size_val; - // #[no_mangle] - // #[cfg(not(any(crate_type = "dylib", crate_type = "cdylib")))] - // static #decl_idt_stc: [#usynccell<#plugin_ty>; 2] = unsafe { [ - // #usynccell::new(#ps), - // #usynccell::new(#null_ps), - // ] }; + #[no_mangle] + #[cfg(make_static_lib)] + static #decl_idt_stc: [#usynccell<#plugin_ty>; 2] = unsafe { [ + #usynccell::new(#ps), + #usynccell::new(#null_ps), + ] }; #[no_mangle] - // #[cfg(any(crate_type = "dylib", crate_type = "cdylib"))] + #[cfg(not(make_static_lib))] static #decl_idt_dyn: [#usynccell<#plugin_ty>; 2] = unsafe { [ #usynccell::new(#ps), #usynccell::new(#null_ps), diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 1d2da7d1245e5..bf01a12f88e57 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -162,8 +162,9 @@ macro_rules! sysvar_atomic { }; } -#[non_exhaustive] +/// Possible flags for plugin variables #[repr(i32)] +#[non_exhaustive] pub enum PluginVarInfo { ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, /// Variable is read only From f10980425265ca4a466837f39f45e724397fde2d Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 5 Feb 2023 03:21:15 -0500 Subject: [PATCH 22/34] Fixes for clippy --- rust/.clippy.toml | 1 + rust/{rustfmt.toml => .rustfmt.toml} | 0 rust/bindings/build.rs | 2 +- rust/macros/src/register_plugin.rs | 23 ++++++------ rust/mariadb/src/lib.rs | 11 ++++++ rust/mariadb/src/plugin.rs | 36 +++++++++++++------ rust/mariadb/src/plugin/encryption.rs | 10 +++--- rust/mariadb/src/plugin/encryption_wrapper.rs | 2 +- rust/mariadb/src/plugin/variables.rs | 6 ++-- rust/mariadb/src/plugin/wrapper.rs | 3 ++ rust/mariadb/src/service_sql.rs | 27 +++++++++++--- rust/mariadb/src/service_sql/error.rs | 12 +++---- rust/mariadb/src/service_sql/raw.rs | 33 ++++++++--------- rust/plugins/keymgt-clevis/src/lib.rs | 2 +- 14 files changed, 107 insertions(+), 61 deletions(-) create mode 100644 rust/.clippy.toml rename rust/{rustfmt.toml => .rustfmt.toml} (100%) diff --git a/rust/.clippy.toml b/rust/.clippy.toml new file mode 100644 index 0000000000000..96995253d74c2 --- /dev/null +++ b/rust/.clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["MySQL", "MariaDB"] diff --git a/rust/rustfmt.toml b/rust/.rustfmt.toml similarity index 100% rename from rust/rustfmt.toml rename to rust/.rustfmt.toml diff --git a/rust/bindings/build.rs b/rust/bindings/build.rs index 3bd1485659fac..026486b89da48 100644 --- a/rust/bindings/build.rs +++ b/rust/bindings/build.rs @@ -5,7 +5,7 @@ use std::env; use std::path::PathBuf; use std::process::Command; -use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks, DeriveInfo}; +use bindgen::callbacks::{DeriveInfo, MacroParsingBehavior, ParseCallbacks}; use bindgen::EnumVariation; // `math.h` seems to double define some things, To avoid this, we ignore them. diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index 0333edceee79f..c37463011c448 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -18,7 +18,7 @@ use syn::{ pub fn entry(tokens: TokenStream) -> TokenStream { let tokens_pm2: proc_macro2::TokenStream = tokens.clone().into(); let input = parse_macro_input!(tokens as PluginInfo); - let plugindef = input.to_encryption_struct(); + let plugindef = input.into_encryption_struct(); match plugindef { Ok(ts) => ts.into_output(), Err(e) => e.into_compile_error().into(), @@ -136,7 +136,7 @@ impl PluginInfo { ]; req.extend_from_slice(required); - for req_field in req.iter() { + for req_field in &req { let (field_val, fname) = name_map.iter().find(|f| f.1 == *req_field).unwrap(); if field_val.is_none() { @@ -165,7 +165,7 @@ impl PluginInfo { /// Turn `self` into a tokenstream of a single `st_maria_plugin` for an /// encryption struct. Uses `idx` to mangle the name and avoid conflicts - fn to_encryption_struct(self) -> syn::Result { + fn into_encryption_struct(self) -> syn::Result { self.validate_as_encryption()?; let type_ = &self.main_ty; @@ -276,7 +276,7 @@ impl PluginDef { fn into_output(self) -> TokenStream { let make_ident = |s| Ident::new(s, Span::call_site()); let make_ident_fmt = |s: String| Ident::new(s.as_str(), Span::call_site()); - + // static and dynamic identifiers let vers_idt_stc = make_ident_fmt(format!("builtin_{}_plugin_interface_version", self.name)); @@ -303,7 +303,7 @@ impl PluginDef { #[no_mangle] #[cfg(make_static_lib)] static #vers_idt_stc: ::std::ffi::c_int = #version_val; - + #[no_mangle] #[cfg(not(make_static_lib))] static #vers_idt_dyn: ::std::ffi::c_int = #version_val; @@ -354,14 +354,13 @@ fn verify_field_order(fields: &[String]) -> Result<(), String> { expected_order.retain(|expected| fields.iter().any(|f| f == expected)); - if expected_order != fields { - Err(format!( - "fields not in expected order. reorder as:\n{:?}", - expected_order - )) - } else { - Ok(()) + if expected_order == fields { + return Ok(()); } + + Err(format!( + "fields not in expected order. reorder as:\n{expected_order:?}", + )) } /// Get the field as a boolean diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 7439e07d067e8..397646b45e092 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -1,4 +1,14 @@ //! Crate representing safe abstractions over MariaDB bindings +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::missing_inline_in_public_items)] +#![warn(clippy::str_to_string)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::useless_conversion)] +#![allow(clippy::missing_const_for_fn)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::missing_inline_in_public_items)] #![allow(unused)] use time::{format_description, OffsetDateTime}; @@ -15,6 +25,7 @@ pub use cstr; pub use mariadb_sys as bindings; #[doc(hidden)] +#[inline] pub fn log_timestamped_message(title: &str, msg: &str) { let t = time::OffsetDateTime::now_utc(); let fmt = time::format_description::parse( diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index cf89f7470d3be..df52d09397bd6 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -37,6 +37,7 @@ //! ``` use std::ffi::{c_int, c_uint}; +use std::str::FromStr; use mariadb_sys as bindings; pub mod encryption; @@ -56,6 +57,7 @@ pub mod prelude { /// Defines possible licenses for plugins #[non_exhaustive] #[derive(Clone, Copy, Debug)] +#[allow(clippy::cast_possible_wrap)] pub enum License { Proprietary = bindings::PLUGIN_LICENSE_PROPRIETARY as isize, Gpl = bindings::PLUGIN_LICENSE_GPL as isize, @@ -63,24 +65,33 @@ pub enum License { } impl License { - /// Create a license type from a string - pub fn from_str(s: &str) -> Option { - match s.to_lowercase().as_str() { - "proprietary" => Some(Self::Proprietary), - "gpl" => Some(Self::Gpl), - "bsd" => Some(Self::Bsd), - _ => None, - } - } - + #[must_use] #[doc(hidden)] pub const fn to_license_registration(self) -> c_int { self as c_int } } +#[derive(Clone, Copy, Debug)] +pub struct NoMatchingLicenseError; + +impl FromStr for License { + type Err = NoMatchingLicenseError; + + /// Create a license type from a string + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "proprietary" => Ok(Self::Proprietary), + "gpl" => Ok(Self::Gpl), + "bsd" => Ok(Self::Bsd), + _ => Err(NoMatchingLicenseError), + } + } +} + /// Defines a type of plugin. This determines the required implementation. #[non_exhaustive] +#[allow(clippy::cast_possible_wrap)] pub enum PluginType { MyUdf = bindings::MYSQL_UDF_PLUGIN as isize, MyStorageEngine = bindings::MYSQL_STORAGE_ENGINE_PLUGIN as isize, @@ -98,6 +109,7 @@ pub enum PluginType { } impl PluginType { + #[must_use] #[doc(hidden)] pub const fn to_ptype_registration(self) -> c_int { self as c_int @@ -106,6 +118,7 @@ impl PluginType { /// Defines possible licenses for plugins #[non_exhaustive] +#[allow(clippy::cast_possible_wrap)] pub enum Maturity { Unknown = bindings::MariaDB_PLUGIN_MATURITY_UNKNOWN as isize, Experimental = bindings::MariaDB_PLUGIN_MATURITY_EXPERIMENTAL as isize, @@ -116,6 +129,7 @@ pub enum Maturity { } impl Maturity { + #[must_use] #[doc(hidden)] pub const fn to_maturity_registration(self) -> c_uint { self as c_uint @@ -127,10 +141,12 @@ pub struct InitError; /// Implement this trait if your plugin requires init or deinit functions pub trait Init { + /// Initialize the plugin fn init() -> Result<(), InitError> { Ok(()) } + /// Deinitialize the plugin fn deinit() -> Result<(), InitError> { Ok(()) } diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 438f2cf145317..0a8a81fd425a0 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -23,7 +23,7 @@ use mariadb_sys as bindings; /// A type of error to be used by key functions #[repr(u32)] #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyError { // Values must be nonzero /// A key ID is invalid or not found. Maps to `ENCRYPTION_KEY_VERSION_INVALID` in C. @@ -35,7 +35,7 @@ pub enum KeyError { #[repr(i32)] #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EncryptionError { BadData = bindings::MY_AES_BAD_DATA, BadKeySize = bindings::MY_AES_BAD_KEYSIZE, @@ -43,7 +43,7 @@ pub enum EncryptionError { } /// Representation of the flags integer -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Flags(i32); impl Flags { @@ -51,11 +51,11 @@ impl Flags { Self(value) } - pub(crate) fn should_encrypt(&self) -> bool { + pub(crate) fn should_encrypt(self) -> bool { (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT as i32) != 0 } - pub(crate) fn should_decrypt(&self) -> bool { + pub(crate) fn should_decrypt(self) -> bool { // (self.0 & bindings::ENCRYPTION_FLAG_DECRYPT as i32) != 0 !self.should_encrypt() } diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 3fd4ff5d98507..99d89bc221c19 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -182,5 +182,5 @@ unsafe fn set_buflen_with_check(buflen: *mut c_uint, val: u32) { crashes after this message, that is the likely error" ); } - *buflen = val.try_into().unwrap() + *buflen = val.try_into().unwrap(); } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index bf01a12f88e57..d4ad6529d38fe 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,6 +1,5 @@ //! "show variables" and "system variables" -//! -//! +//! use std::mem::ManuallyDrop; use std::ptr; @@ -8,7 +7,7 @@ use std::ptr; use bindings::st_mysql_sys_var_basic; use mariadb_sys as bindings; -/// Basicallly, a system variable will +/// Basicallly, a system variable will #[repr(C)] union SysVar { basic: ManuallyDrop>, @@ -165,6 +164,7 @@ macro_rules! sysvar_atomic { /// Possible flags for plugin variables #[repr(i32)] #[non_exhaustive] +#[allow(clippy::cast_possible_wrap)] pub enum PluginVarInfo { ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, /// Variable is read only diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 582b23dec6444..6daabe1a91a5b 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -37,6 +37,7 @@ unsafe impl Sync for UnsafeSyncCell {} /// Trait for easily wrapping init/deinit functions pub trait WrapInit: Init { + #[must_use] unsafe extern "C" fn wrap_init(_: *mut c_void) -> c_int { match Self::init() { Ok(_) => 0, @@ -44,6 +45,7 @@ pub trait WrapInit: Init { } } + #[must_use] unsafe extern "C" fn wrap_deinit(_: *mut c_void) -> c_int { match Self::deinit() { Ok(_) => 0, @@ -55,6 +57,7 @@ pub trait WrapInit: Init { impl WrapInit for T where T: Init {} /// New struct with all null values +#[must_use] #[doc(hidden)] pub const fn new_null_st_maria_plugin() -> bindings::st_maria_plugin { bindings::st_maria_plugin { diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index f50eeffb1d9b0..c3548274aa77b 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -1,7 +1,7 @@ -//! Safe API for include/mysql/service_sql.h +//! Safe API for `include/mysql/service_sql.h` //! -//! FIXME: I think we need to use a different GLOBAL_SQL_SERVICE if statically +//! FIXME: I think we need to use a different `GLOBAL_SQL_SERVICE` if statically //! linked, but not yet sure where this is use std::cell::UnsafeCell; @@ -25,6 +25,11 @@ pub struct MySqlConn(RawConnection); impl MySqlConn { /// Connect to the local server + /// + /// # Errors + /// + /// Error if the client could not connect + #[inline] pub fn connect_local() -> ClientResult { let mut conn = RawConnection::new(); conn.connect_local()?; @@ -32,12 +37,22 @@ impl MySqlConn { } /// Run a query and discard the results - pub fn execute<'a>(&'a mut self, q: &str) -> ClientResult<()> { + /// + /// # Errors + /// + /// Error if the query could not be completed + #[inline] + pub fn execute(&mut self, q: &str) -> ClientResult<()> { self.0.query(q)?; Ok(()) } /// Run a query for lazily loaded results + /// + /// # Errors + /// + /// Error if the row could not be fetched + #[inline] pub fn query<'a>(&'a mut self, q: &str) -> ClientResult> { self.0.query(q)?; let res = self.0.prep_fetch_result()?; @@ -50,14 +65,16 @@ impl MySqlConn { pub struct FetchedRows<'a>(RawResult<'a, Fetch>); impl<'a> FetchedRows<'a> { - pub fn next(&mut self) -> Option { + #[inline] + pub fn next_row(&mut self) -> Option { self.0.fetch_next_row() } } impl Drop for FetchedRows<'_> { /// Consume the rest of the rows + #[inline] fn drop(&mut self) { - while let Some(_) = self.next() {} + while self.next_row().is_some() {} } } diff --git a/rust/mariadb/src/service_sql/error.rs b/rust/mariadb/src/service_sql/error.rs index 53f9d6b8cba74..4ceacded958d0 100644 --- a/rust/mariadb/src/service_sql/error.rs +++ b/rust/mariadb/src/service_sql/error.rs @@ -14,19 +14,17 @@ pub enum ClientError { impl From for ClientError { fn from(value: i32) -> Self { - match value { - _ => Self::Unspecified, - } + Self::Unspecified } } impl Display for ClientError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ClientError::ConnectError(n, e) => write!(f, "connection failed with {n}: '{e}'"), - ClientError::QueryError(n, e) => write!(f, "query failed with {n}: '{e}'"), - ClientError::FetchError(n, e) => write!(f, "fetch failed with {n}: '{e}'"), - ClientError::Unspecified => write!(f, "unspecified error"), + Self::ConnectError(n, e) => write!(f, "connection failed with {n}: '{e}'"), + Self::QueryError(n, e) => write!(f, "query failed with {n}: '{e}'"), + Self::FetchError(n, e) => write!(f, "fetch failed with {n}: '{e}'"), + Self::Unspecified => write!(f, "unspecified error"), } } } diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs index c8859bad4464b..eca03435d4a10 100644 --- a/rust/mariadb/src/service_sql/raw.rs +++ b/rust/mariadb/src/service_sql/raw.rs @@ -1,6 +1,6 @@ -//! Safe API for a MySql connection +//! Safe API for a `MySql` connection //! -//! RawConnection comes almost directly from the `diesel` client crate, since +//! `RawConnection` comes almost directly from the `diesel` client crate, since //! they have that all figured out pretty well. Reference: //! @@ -54,7 +54,7 @@ impl RawConnection { // Attempt to connect, fail if nonzero (unexpected) let p_conn = unsafe { global_func!(mysql_init_func)(ptr::null_mut()) }; let p_conn = NonNull::new(p_conn).expect("OOM: connection allocation failed"); - let result = RawConnection(p_conn); + let result = Self(p_conn); // Validate we are using an expected charset let charset = unsafe { @@ -100,12 +100,12 @@ impl RawConnection { // Make sure you don't use the fake one! global_func!(mysql_real_connect_func)( self.0.as_ptr(), - host.map(|c| c.as_ptr()).unwrap_or(ptr::null()), - user.map(|c| c.as_ptr()).unwrap_or(ptr::null()), - pw.map(|c| c.as_ptr()).unwrap_or(ptr::null()), - db.map(|c| c.as_ptr()).unwrap_or(ptr::null()), - port.map(|p| p.into()).unwrap_or(0), - sock.map(|c| c.as_ptr()).unwrap_or(ptr::null()), + host.map_or(ptr::null(), |c| c.as_ptr()), + user.map_or(ptr::null(), |c| c.as_ptr()), + pw.map_or(ptr::null(), |c| c.as_ptr()), + db.map_or(ptr::null(), |c| c.as_ptr()), + port.map_or(0, std::convert::Into::into), + sock.map_or(ptr::null(), |c| c.as_ptr()), conn_opts.flags.into(), ) }; @@ -147,7 +147,7 @@ impl RawConnection { } /// Prepare the result for iteration, but do not store - pub fn prep_fetch_result<'a>(&'a mut self) -> ClientResult> { + pub fn prep_fetch_result(&mut self) -> ClientResult> { let res = unsafe { bindings::mysql_use_result(self.0.as_ptr()) }; self.check_for_errors(ClientError::QueryError)?; @@ -158,7 +158,7 @@ impl RawConnection { let fields = slice::from_raw_parts(field_ptr, field_count as usize); Ok(RawResult { inner: ptr, - fields: mem::transmute(fields), + fields: *fields.as_ptr().cast(), _marker: PhantomData, }) }, @@ -235,7 +235,7 @@ pub struct FetchedRow<'a> { impl FetchedRow<'_> { /// Get the field of a given index - pub fn field_value(&self, index: usize) ->Value{ + pub fn field_value(&self, index: usize) -> Value { let field = &self.fields[index]; assert!(index < self.fields.len()); // extra sanity check unsafe { @@ -297,8 +297,9 @@ fn fn_thread_unsafe_lib_init() { // mysql_library_init is defined by `#define mysql_library_init mysql_server_init` // which isn't picked up by bindgen let ret = unsafe { bindings::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) }; - if ret != 0 { - panic!("Unable to perform MySQL global initialization. Return code: {ret}"); - } - }) + assert_eq!( + ret, 0, + "Unable to perform MySQL global initialization. Return code: {ret}" + ); + }); } diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 241feae969b5a..494babd30bf88 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -31,7 +31,7 @@ fn fetch_jws() -> String { let url = format!("https://{}", TANG_SERVER.lock().unwrap()); let body: String = ureq::get("http://example.com") .call() - .expect(&format!("http request for '{}' failed", url)) + .unwrap_or_else(|_| panic!("http request for '{url}' failed")) .into_string() .expect("http request larger than 10MB"); todo!(); From 7bffbb39e90f2b225f305893669dd5ea251734d1 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 5 Feb 2023 04:43:37 -0500 Subject: [PATCH 23/34] Implement logging via the 'log' crate --- rust/bindings/src/hand_imples.rs | 5 + rust/macros/src/register_plugin.rs | 33 ++++-- rust/mariadb/Cargo.toml | 2 + rust/mariadb/src/lib.rs | 108 +++++++++--------- rust/mariadb/src/plugin/encryption_wrapper.rs | 6 +- rust/mariadb/src/plugin/variables.rs | 1 - rust/plugins/keymgt-clevis/src/lib.rs | 2 +- 7 files changed, 87 insertions(+), 70 deletions(-) diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index 2ea164c28db66..912dc19e8ec4b 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -12,10 +12,15 @@ pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; #[repr(C)] #[derive(Debug)] pub struct st_mysql_sys_var_basic { + /// Variable flags pub flags: c_int, + /// Name of the variable pub name: *const c_char, + /// Variable description pub comment: *const c_char, + /// Function for getting the variable pub check: mysql_var_check_func, + /// Function for setting the variable pub update: mysql_var_update_func, pub value: *mut T, pub def_val: T, diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index c37463011c448..d335f439f7939 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -228,16 +228,27 @@ impl PluginInfo { let maturity = self.maturity.unwrap(); let ptype = self.ptype.unwrap(); - let (fn_init, fn_deinit); + let init_fn_name = make_ident(&format!("_{}_init_fn", name.value())); + + // We always initialize the logger, maybe do init/deinit if struct requires + let (fn_init, fn_deinit, init_fn_body); if let Some(init_ty) = self.init { let ty_as_init = quote! {<#init_ty as ::mariadb::plugin::wrapper::WrapInit>}; + init_fn_body = quote! { ::mariadb::configure_logger!(); #ty_as_init::wrap_init; }; fn_init = quote! { Some(#ty_as_init::wrap_init) }; fn_deinit = quote! { Some(#ty_as_init::wrap_deinit) }; } else { - let none = quote! { None }; - (fn_init, fn_deinit) = (none.clone(), none); + init_fn_body = quote! { ::mariadb::configure_logger!(); }; + fn_init = quote! { Some(#init_fn_name) }; + fn_deinit = quote! { None }; } + let init_fn = quote! { + fn #init_fn_name(_: *mut std::ffi::c_void) { + #init_fn_body + } + }; + let plugin_struct = quote! { ::mariadb::bindings::st_maria_plugin { type_: #ptype.to_ptype_registration(), @@ -258,6 +269,7 @@ impl PluginInfo { Ok(PluginDef { name: name.value(), + init_fn, info_struct, plugin_struct, }) @@ -268,22 +280,19 @@ impl PluginInfo { /// applicable, plus the struct of `st_maria_plugin` that references it struct PluginDef { name: String, + init_fn: proc_macro2::TokenStream, info_struct: proc_macro2::TokenStream, plugin_struct: proc_macro2::TokenStream, } impl PluginDef { fn into_output(self) -> TokenStream { - let make_ident = |s| Ident::new(s, Span::call_site()); - let make_ident_fmt = |s: String| Ident::new(s.as_str(), Span::call_site()); - // static and dynamic identifiers - let vers_idt_stc = - make_ident_fmt(format!("builtin_{}_plugin_interface_version", self.name)); + let vers_idt_stc = make_ident(&format!("builtin_{}_plugin_interface_version", self.name)); let vers_idt_dyn = make_ident("_maria_plugin_interface_version_"); - let size_idt_stc = make_ident_fmt(format!("builtin_{}_sizeof_struct_st_plugin", self.name)); + let size_idt_stc = make_ident(&format!("builtin_{}_sizeof_struct_st_plugin", self.name)); let size_idt_dyn = make_ident("_maria_sizeof_struct_st_plugin_"); - let decl_idt_stc = make_ident_fmt(format!("builtin_{}_plugin", self.name)); + let decl_idt_stc = make_ident(&format!("builtin_{}_plugin", self.name)); let decl_idt_dyn = make_ident("_maria_plugin_declarations_"); let plugin_ty = quote! {::mariadb::bindings::st_maria_plugin}; @@ -409,3 +418,7 @@ fn version_int(s: &str) -> Result { Ok(res) } + +fn make_ident(s: &str) -> Ident { + Ident::new(s, Span::call_site()) +} diff --git a/rust/mariadb/Cargo.toml b/rust/mariadb/Cargo.toml index 02d921c2b842b..4598efd452a0a 100644 --- a/rust/mariadb/Cargo.toml +++ b/rust/mariadb/Cargo.toml @@ -10,3 +10,5 @@ mariadb-macros = { path = "../macros" } cstr = "0.2.11" concat-idents = "1.1.4" time = { version = "0.3.17", features = ["formatting"]} +log = "0.4.17" +env_logger = "0.10.0" diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 397646b45e092..6f66f4a22492c 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -16,16 +16,18 @@ use time::{format_description, OffsetDateTime}; mod common; pub mod plugin; pub mod service_sql; +use std::fmt::Write; #[doc(inline)] pub use common::*; #[doc(hidden)] pub use cstr; +pub use log; #[doc(hidden)] pub use mariadb_sys as bindings; -#[doc(hidden)] #[inline] +#[doc(hidden)] pub fn log_timestamped_message(title: &str, msg: &str) { let t = time::OffsetDateTime::now_utc(); let fmt = time::format_description::parse( @@ -39,70 +41,66 @@ pub fn log_timestamped_message(title: &str, msg: &str) { eprintln!("{to_print}"); } -/// Provide the name of the calling function (full path) -macro_rules! function { - () => {{ - fn f() {} - fn type_name_of(_: T) -> &'static str { - std::any::type_name::() - } - let name = type_name_of(f); - &name[..name.len() - 3] - }}; +#[doc(hidden)] +pub struct MariaLogger { + pkg: Option<&'static str>, } -/// Log an error to stderr -#[macro_export] -macro_rules! error { - (target: $target:expr, $($msg:tt)+) => {{ - let mut tmp = "[Error] ".to_owned(); - tmp.push_str($target); - tmp.push_str(": "); - $crate::log_timestamped_message(&tmp, &format!($($msg)+)); - }}; - ($($msg:tt)+) => { - $crate::log_timestamped_message("[Error]: ", &format!($($msg)+)); +impl MariaLogger { + pub const fn new() -> Self { + Self { + pkg: option_env!("CARGO_PKG_NAME"), + } } } -/// Log a warning to stderr -#[macro_export] -macro_rules! warn { - (target: $target:expr, $($msg:tt)+) => {{ - let mut tmp = "[Warn] ".to_owned(); - tmp.push_str($target); - tmp.push_str(": "); - $crate::log_timestamped_message(&tmp, &format!($($msg)+)); - }}; - ($($msg:tt)+) => { - $crate::log_timestamped_message("[Warn]", &format!($($msg)+)); +impl log::Log for MariaLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + // metadata.level() <= log::Level::Info + true } -} -/// Log info to stderr -#[macro_export] -macro_rules! info { - (target: $target:expr, $($msg:tt)+) => {{ - let mut tmp = "[Info] ".to_owned(); - tmp.push_str($target); - tmp.push_str(": "); - $crate::log_timestamped_message(&tmp, &format!($($msg)+)); - }}; - ($($msg:tt)+) => { - $crate::log_timestamped_message("[Info]", &format!($($msg)+)); + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + let t = time::OffsetDateTime::now_utc(); + let fmt = time::format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]", + ) + .unwrap(); + let mut out_str = t.format(&fmt).unwrap(); + write!(out_str, " [{}]", record.level()).unwrap(); + if let Some(pkg) = self.pkg { + write!(out_str, " {pkg}"); + } + eprintln!("{out_str}:"); } + + fn flush(&self) {} } -/// Log debug messages to stderr +/// Configure t #[macro_export] -macro_rules! debug { - (target: $target:expr, $($msg:tt)+) => {{ - let mut tmp = "[Debuf] ".to_owned(); - tmp.push_str($target); - tmp.push_str(": "); - $crate::log_timestamped_message(&tmp, &format!($($msg)+)); +macro_rules! configure_logger { + () => { + $crate::configure_logger!(log::LevelFilter::Warn) + }; + ($level:expr) => {{ + static LOGGER = $crate::MariaLogger::new(); + $crate::log::set_logger(&LOGGER).map(|()| log::set_max_level($level)) + }} +} + +/// Provide the name of the calling function (full path) +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] }}; - ($($msg:tt)+) => { - $crate::log_timestamped_message("[Debuf]", &format!($($msg)+)); - } } diff --git a/rust/mariadb/src/plugin/encryption_wrapper.rs b/rust/mariadb/src/plugin/encryption_wrapper.rs index 99d89bc221c19..77a586f631324 100644 --- a/rust/mariadb/src/plugin/encryption_wrapper.rs +++ b/rust/mariadb/src/plugin/encryption_wrapper.rs @@ -3,10 +3,10 @@ use std::ffi::{c_int, c_uchar, c_uint, c_void}; use std::{mem, slice}; +use log::{error, warn}; use mariadb_sys as bindings; use super::encryption::{Encryption, Flags, KeyError, KeyManager}; -use crate::{error, warn}; /// pub trait WrapKeyMgr: KeyManager { @@ -15,8 +15,8 @@ pub trait WrapKeyMgr: KeyManager { match Self::get_latest_key_version(key_id) { Ok(v) => { if v == bindings::ENCRYPTION_KEY_NOT_ENCRYPTED { - error!(target: "KeyManager", "get_latest_key_version returned value {v}, which is reserved for unencrypted keys."); - error!(target: "KeyManager", "the server will likely shut down now."); + error!("get_latest_key_version returned value {v}, which is reserved for unencrypted keys."); + error!("the server will likely shut down now."); } v } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index d4ad6529d38fe..8101c9fc01e0d 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,5 +1,4 @@ //! "show variables" and "system variables" -//! use std::mem::ManuallyDrop; use std::ptr; diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 494babd30bf88..9eea94143d2e6 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -8,13 +8,13 @@ use std::fmt::Write; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Mutex; +use mariadb::log::{debug, error, info}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, }; use mariadb::service_sql::{ClientError, Fetch, FetchedRows, MySqlConn}; -use mariadb::{debug, error, info, sysvar_atomic}; const KEY_TABLE: &str = "mysql.clevis_keys"; const SERVER_TABLE: &str = "mysql.clevis_servers"; From 432072d04dc780a7ea8fc15369aaa7abfb2efda6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 5 Feb 2023 06:33:52 -0500 Subject: [PATCH 24/34] Variable parsers Added more system variable helpers Work on sysvar impls for atomics --- rust/bindings/src/hand_imples.rs | 189 ++++++++++---------- rust/examples/keymgt-basic/src/lib.rs | 3 +- rust/examples/keymgt-debug/src/lib.rs | 18 +- rust/macros/src/register_plugin.rs | 46 ++--- rust/mariadb/src/helpers.rs | 13 ++ rust/mariadb/src/lib.rs | 19 +- rust/mariadb/src/plugin.rs | 3 +- rust/mariadb/src/plugin/variables.rs | 193 ++++++++++++++------- rust/mariadb/src/plugin/variables_parse.rs | 151 ++++++++++++++++ rust/plugins/keymgt-clevis/src/lib.rs | 2 +- 10 files changed, 431 insertions(+), 206 deletions(-) create mode 100644 rust/mariadb/src/helpers.rs create mode 100644 rust/mariadb/src/plugin/variables_parse.rs diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index 912dc19e8ec4b..582dc797374f8 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_char, c_int, c_uint, c_void}; +use std::ffi::{c_char, c_double, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong, c_void}; use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; @@ -8,105 +8,104 @@ pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; // We hand write these stucts because the definition is tricky, not all fields are // always present -/// A basic system variable -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_basic { - /// Variable flags - pub flags: c_int, - /// Name of the variable - pub name: *const c_char, - /// Variable description - pub comment: *const c_char, - /// Function for getting the variable - pub check: mysql_var_check_func, - /// Function for setting the variable - pub update: mysql_var_update_func, - pub value: *mut T, - pub def_val: T, -} +// no support for THD yet +macro_rules! declare_sysvar_type { + (@common $name:ident: $(#[$doc:meta] $fname:ident: $fty:ty),+ $(,)*) => { + // Common implementation + #[repr(C)] + #[derive(Debug)] + pub struct $name { + /// Variable flags + pub flags: c_int, + /// Name of the variable + pub name: *const c_char, + /// Variable description + pub comment: *const c_char, + /// Function for getting the variable + pub check: mysql_var_check_func, + /// Function for setting the variable + pub update: mysql_var_update_func, -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_const_basic { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub value: *const T, - pub def_val: T, -} + // Repeated fields + $( + #[$doc] + pub $fname: $fty + ),+ + } + }; + (basic: $name:ident, $ty:ty) => { + // A "basic" sysvar + declare_sysvar_type!{ + @common $name: + #[doc = "Pointer to the value"] + value: *mut $ty, + #[doc = "Default value"] + def_val: $ty, + } + }; + (const basic: $name:ident, $ty:ty) => { + // A "basic" sysvar + declare_sysvar_type!{ + @common $name: + #[doc = "Pointer to the value"] + value: *const $ty, + #[doc = "Default value"] + def_val: $ty, + } + }; + (simple: $name:ident, $ty:ty) => { + // A "simple" sysvar, with minimum maximum and block size + declare_sysvar_type!{ + @common $name: + #[doc = "Pointer to the value"] + value: *mut $ty, + #[doc = "Default value"] + def_val: $ty, + #[doc = "Min value"] + min_val: $ty, + #[doc = "Max value"] + max_val: $ty, + #[doc = "Block size"] + blk_sz: $ty, + } + }; + (typelib: $name:ident, $ty:ty) => { + // A "typelib" sysvar + declare_sysvar_type!{ + @common $name: + #[doc = "Pointer to the value"] + value: *mut $ty, + #[doc = "Default value"] + def_val: $ty, + #[doc = "Typelib"] + typelib: *const TYPELIB + } + }; -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_simple { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub value: *mut T, - pub def_val: T, - pub min_val: T, - pub max_val: T, - pub blk_sz: T, -} -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_typelib { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub value: *const T, - pub def_val: T, - pub typelib: TYPELIB, -} + // (typelib: $name:ident, $ty:ty) => { -type THDVAR_FUNC = Option *mut T>; + // }; + // (thd: $name:ident, $ty:ty) => { -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_thd_basic { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub offset: c_int, - pub def_val: T, - pub resolve: THDVAR_FUNC, + // }; } -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_thd_simple { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub offset: c_int, - pub def_val: T, - pub min_val: T, - pub max_val: T, - pub blk_sz: T, - pub resolve: THDVAR_FUNC, -} +declare_sysvar_type!(basic: sysvar_bool_t, bool); +declare_sysvar_type!(basic: sysvar_str_t, *mut c_char); +declare_sysvar_type!(typelib: sysvar_enum_t, c_ulong); +declare_sysvar_type!(typelib: sysvar_set_t, c_ulonglong); +declare_sysvar_type!(simple: sysvar_int_t, c_int); +declare_sysvar_type!(simple: sysvar_long_t, c_long); +declare_sysvar_type!(simple: sysvar_longlong_t, c_longlong); +declare_sysvar_type!(simple: sysvar_uint_t, c_uint); +declare_sysvar_type!(simple: sysvar_ulong_t, c_ulong); +declare_sysvar_type!(simple: sysvar_ulonglong_t, c_ulonglong); +declare_sysvar_type!(simple: sysvar_double_t, c_double); -#[repr(C)] -#[derive(Debug)] -pub struct st_mysql_sys_var_thd_typelib { - pub flags: c_int, - pub name: *const c_char, - pub comment: *const c_char, - pub check: mysql_var_check_func, - pub update: mysql_var_update_func, - pub offset: c_int, - pub def_val: T, - pub resolve: THDVAR_FUNC, - pub typelib: TYPELIB, -} +// declare_sysvar_type!(thdbasic: thdvar_bool_t, bool); +// declare_sysvar_type!(thdbasic: thdvar_str_t, *mut c_char); +// declare_sysvar_type!(typelib: sysvar_enum_t, c_ulong); +// declare_sysvar_type!(typelib: sysvar_set_t, c_ulonglong); + +// type THDVAR_FUNC = Option *mut T>; diff --git a/rust/examples/keymgt-basic/src/lib.rs b/rust/examples/keymgt-basic/src/lib.rs index 142dde50a58c0..8c44433073b70 100644 --- a/rust/examples/keymgt-basic/src/lib.rs +++ b/rust/examples/keymgt-basic/src/lib.rs @@ -13,8 +13,7 @@ use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; -use mariadb::plugin::{register_plugin, PluginType, PluginVarInfo, SysVarAtomic}; -use mariadb::sysvar_atomic; +use mariadb::plugin::{register_plugin, PluginType, PluginVarInfo}; struct BasicKeyMgt; diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index 69da549d4e9a1..b501607453ed1 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -14,7 +14,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, + register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, }; use mariadb::sysvar_atomic; @@ -22,14 +22,14 @@ const KEY_LENGTH: usize = 4; static KEY_VERSION: AtomicU32 = AtomicU32::new(1); -static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { - ty: u32, - name: "version", - var: KEY_VERSION, - comment: "Latest key version", - flags: [PluginVarInfo::ReqCmdArg], - default: 1, -}; +// static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { +// ty: u32, +// name: "version", +// var: KEY_VERSION, +// comment: "Latest key version", +// flags: [PluginVarInfo::ReqCmdArg], +// default: 1, +// }; struct DebugKeyMgmt; diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index d335f439f7939..fb6339f1d59d3 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -288,58 +288,58 @@ struct PluginDef { impl PluginDef { fn into_output(self) -> TokenStream { // static and dynamic identifiers - let vers_idt_stc = make_ident(&format!("builtin_{}_plugin_interface_version", self.name)); - let vers_idt_dyn = make_ident("_maria_plugin_interface_version_"); - let size_idt_stc = make_ident(&format!("builtin_{}_sizeof_struct_st_plugin", self.name)); - let size_idt_dyn = make_ident("_maria_sizeof_struct_st_plugin_"); - let decl_idt_stc = make_ident(&format!("builtin_{}_plugin", self.name)); - let decl_idt_dyn = make_ident("_maria_plugin_declarations_"); - - let plugin_ty = quote! {::mariadb::bindings::st_maria_plugin}; + let vers_ident_stc = make_ident(&format!("builtin_{}_plugin_interface_version", self.name)); + let vers_ident_dyn = make_ident("_maria_plugin_interface_version_"); + let size_ident_stc = make_ident(&format!("builtin_{}_sizeof_struct_st_plugin", self.name)); + let size_ident_dyn = make_ident("_maria_sizeof_struct_st_plugin_"); + let decl_ident_stc = make_ident(&format!("builtin_{}_plugin", self.name)); + let decl_ident_dyn = make_ident("_maria_plugin_declarations_"); + + let plugin_ty = quote! { ::mariadb::bindings::st_maria_plugin }; let version_val = - quote! {mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int}; - let size_val = quote! {::std::mem::size_of::<#plugin_ty>() as ::std::ffi::c_int}; + quote! { mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int }; + let size_val = quote! { ::std::mem::size_of::<#plugin_ty>() as ::std::ffi::c_int }; - let usynccell = quote! {::mariadb::plugin::wrapper::UnsafeSyncCell}; - let null_ps = quote! {::mariadb::plugin::wrapper::new_null_st_maria_plugin()}; + let usynccell = quote! { ::mariadb::plugin::wrapper::UnsafeSyncCell }; + let null_ps = quote! { ::mariadb::plugin::wrapper::new_null_st_maria_plugin() }; - let is = self.info_struct; - let ps = self.plugin_struct; + let info_st = self.info_struct; + let plugin_st = self.plugin_struct; let ret: TokenStream = quote! { // Different config based on statically or dynamically lynked #[no_mangle] #[cfg(make_static_lib)] - static #vers_idt_stc: ::std::ffi::c_int = #version_val; + static #vers_ident_stc: ::std::ffi::c_int = #version_val; #[no_mangle] #[cfg(not(make_static_lib))] - static #vers_idt_dyn: ::std::ffi::c_int = #version_val; + static #vers_ident_dyn: ::std::ffi::c_int = #version_val; #[no_mangle] #[cfg(make_static_lib)] - static #size_idt_stc: ::std::ffi::c_int = #size_val; + static #size_ident_stc: ::std::ffi::c_int = #size_val; #[no_mangle] #[cfg(not(make_static_lib))] - static #size_idt_dyn: ::std::ffi::c_int = #size_val; + static #size_ident_dyn: ::std::ffi::c_int = #size_val; #[no_mangle] #[cfg(make_static_lib)] - static #decl_idt_stc: [#usynccell<#plugin_ty>; 2] = unsafe { [ - #usynccell::new(#ps), + static #decl_ident_stc: [#usynccell<#plugin_ty>; 2] = unsafe { [ + #usynccell::new(#plugin_st), #usynccell::new(#null_ps), ] }; #[no_mangle] #[cfg(not(make_static_lib))] - static #decl_idt_dyn: [#usynccell<#plugin_ty>; 2] = unsafe { [ - #usynccell::new(#ps), + static #decl_ident_dyn: [#usynccell<#plugin_ty>; 2] = unsafe { [ + #usynccell::new(#plugin_st), #usynccell::new(#null_ps), ] }; - #is + #info_st } .into(); diff --git a/rust/mariadb/src/helpers.rs b/rust/mariadb/src/helpers.rs new file mode 100644 index 0000000000000..26dcce4d6b1a6 --- /dev/null +++ b/rust/mariadb/src/helpers.rs @@ -0,0 +1,13 @@ +/// Pass +pub fn str2bool(s: &str) -> Option { + const TRUE_VALS: [&str; 3] = ["on", "true", "1"]; + const FALSE_VALS: [&str; 3] = ["off", "false", "0"]; + let lower = s.to_lowercase(); + if TRUE_VALS.contains(&lower.as_str()) { + Some(true) + } else if FALSE_VALS.contains(&lower.as_str()) { + Some(false) + } else { + None + } +} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 6f66f4a22492c..5170f4c175bc3 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -14,6 +14,7 @@ use time::{format_description, OffsetDateTime}; mod common; +mod helpers; pub mod plugin; pub mod service_sql; use std::fmt::Write; @@ -26,21 +27,6 @@ pub use log; #[doc(hidden)] pub use mariadb_sys as bindings; -#[inline] -#[doc(hidden)] -pub fn log_timestamped_message(title: &str, msg: &str) { - let t = time::OffsetDateTime::now_utc(); - let fmt = time::format_description::parse( - "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]", - ) - .unwrap(); - let mut to_print = t.format(&fmt).unwrap(); - to_print.push(' '); - to_print.push_str(title); - to_print.push_str(msg); - eprintln!("{to_print}"); -} - #[doc(hidden)] pub struct MariaLogger { pkg: Option<&'static str>, @@ -81,7 +67,6 @@ impl log::Log for MariaLogger { fn flush(&self) {} } -/// Configure t #[macro_export] macro_rules! configure_logger { () => { @@ -94,7 +79,7 @@ macro_rules! configure_logger { } /// Provide the name of the calling function (full path) -macro_rules! function { +macro_rules! function_name { () => {{ fn f() {} fn type_name_of(_: T) -> &'static str { diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index df52d09397bd6..85477af0ac352 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -44,10 +44,11 @@ pub mod encryption; #[doc(hidden)] pub mod encryption_wrapper; mod variables; +mod variables_parse; #[doc(hidden)] pub mod wrapper; pub use mariadb_macros::register_plugin; -pub use variables::{PluginVarInfo, SysVarAtomic}; +pub use variables::PluginVarInfo; /// Commonly used plugin types for reexport pub mod prelude { diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 8101c9fc01e0d..5ce2177653cef 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,78 +1,155 @@ //! "show variables" and "system variables" +use std::cell::UnsafeCell; +use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; use std::mem::ManuallyDrop; +use std::os::raw::{c_char, c_uint}; use std::ptr; +use std::sync::atomic::{AtomicBool, AtomicI32}; -use bindings::st_mysql_sys_var_basic; use mariadb_sys as bindings; -/// Basicallly, a system variable will +use super::variables_parse::CliMysqlValue; + +type SVInfoInner = ManuallyDrop>; + +/// 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)] -union SysVar { - basic: ManuallyDrop>, - basic_const: ManuallyDrop>, - simple: ManuallyDrop>, - typelib: ManuallyDrop>, - thdvar_basic: ManuallyDrop>, - thdvar_simple: ManuallyDrop>, - thdvar_typelib: ManuallyDrop>, +pub union SysVarInfoU { + bool_t: SVInfoInner, + str_t: SVInfoInner, + enum_t: SVInfoInner, + set_t: SVInfoInner, + int_t: SVInfoInner, + long_t: SVInfoInner, + longlong_t: SVInfoInner, + uint_t: SVInfoInner, + ulong_t: SVInfoInner, + ulonglong_t: SVInfoInner, + double_t: SVInfoInner, + // THD types have a function `resolve` that takes a thread pointer and an + // offset (also a field) } -#[repr(transparent)] -pub struct SysVarAtomic(SysVar); +/// Types that can be communicated with the C sysvar interface +trait SysVarCType { + const C_TYPE_FLAG: i32; +} -#[doc(hidden)] -impl SysVarAtomic { - pub const fn as_ptr(&self) -> *const bindings::st_mysql_sys_var { - ptr::addr_of!(self.0).cast() - } +// impl SysVarCType for i32 { +// const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_INT as i32; +// } +impl SysVarCType for i64 { + const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_LONGLONG as i32; +} +impl SysVarCType for f64 { + const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_DOUBLE as i32; +} +impl SysVarCType for *const c_char { + const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_INT as i32; +} - pub fn as_mut_ptr(&mut self) -> *mut bindings::st_mysql_sys_var { - ptr::addr_of_mut!(self.0).cast() - } +/// Internal trait +trait SysVar: Sync { + /// Type for interfacing with the main server + // type CType: SysVarCType; - // These functions are all unsafe because preconditions are needed to ensure - // Send and Sync hold true - pub const unsafe fn new_basic(val: bindings::st_mysql_sys_var_basic) -> Self { - Self(SysVar { - basic: ManuallyDrop::new(val), - }) - } - pub const unsafe fn new_basic_const(val: bindings::st_mysql_sys_var_const_basic) -> Self { - Self(SysVar { - basic_const: ManuallyDrop::new(val), - }) - } - pub const unsafe fn new_simple(val: bindings::st_mysql_sys_var_simple) -> Self { - Self(SysVar { - simple: ManuallyDrop::new(val), - }) - } - pub const unsafe fn new_typelib(val: bindings::st_mysql_sys_var_typelib) -> Self { - Self(SysVar { - typelib: ManuallyDrop::new(val), - }) - } - pub const unsafe fn new_thdvar_basic(val: bindings::st_mysql_sys_var_thd_basic) -> Self { - Self(SysVar { - thdvar_basic: ManuallyDrop::new(val), - }) - } - pub const unsafe fn new_thdvar_simple(val: bindings::st_mysql_sys_var_thd_simple) -> Self { - Self(SysVar { - thdvar_simple: ManuallyDrop::new(val), - }) + const C_FLAGS: i32; + /// C struct + type CStType; + /// C representation of type + type CType; + /// Inner representation type + type Inner; + + // /// For the check function, validate the arguments in `value` and write them to `dest`. + unsafe fn check( + thd: *const c_void, + var: &mut Self::CStType, + save: &mut Self::CType, + value: &CliMysqlValue, + ); + // /// The update function + unsafe fn update( + thd: *const c_void, + var: &mut Self::CStType, + save: &mut Self::CType, + value: &CliMysqlValue, + ); +} + +impl SysVar for AtomicI32 { + const C_FLAGS: i32 = bindings::PLUGIN_VAR_INT as i32; + type CStType = bindings::sysvar_int_t; + + type Inner = i32; + + type CType = i32; + + unsafe fn check( + thd: *const c_void, + var: &mut Self::CStType, + save: &mut Self::CType, + value: &CliMysqlValue, + ) { + todo!() } - pub const unsafe fn new_thdvar_typelib(val: bindings::st_mysql_sys_var_thd_typelib) -> Self { - Self(SysVar { - thdvar_typelib: ManuallyDrop::new(val), - }) + + unsafe fn update( + thd: *const c_void, + var: &mut Self::CStType, + save: &mut Self::CType, + value: &CliMysqlValue, + ) { + todo!() } } -// SAFETY: totally reliant on the server's calls here -unsafe impl Send for SysVarAtomic {} -unsafe impl Sync for SysVarAtomic {} +macro_rules! sysvar { + () => {}; +} + +// pub enum SysVarInfo<'a> { +// Bool(&'a SVInfoInner>), +// String(&'a SVInfoInner>), +// ConstString(&'a SVInfoInner>), +// Int(&'a SVInfoInner>), +// UInt(&'a SVInfoInner>), +// Long(&'a SVInfoInner>), +// ULong(&'a SVInfoInner>), +// LongLong(&'a SVInfoInner>), +// ULongLong(&'a SVInfoInner>), +// UInt64T(&'a SVInfoInner>), +// SizeT(&'a SVInfoInner>), +// Enum(&'a SVInfoInner>), +// Set(&'a SVInfoInner>), +// Double(&'a SVInfoInner>), + +// ThdBool(&'a SVInfoInner>), +// ThdString(&'a SVInfoInner>), +// ThdInt(&'a SVInfoInner>), +// ThdUInt(&'a SVInfoInner>), +// ThdLong(&'a SVInfoInner>), +// ThdULong(&'a SVInfoInner>), +// ThdLongLong(&'a SVInfoInner>), +// ThdULongLong(&'a SVInfoInner>), +// ThdUInt64T(&'a SVInfoInner>), +// ThdSizeT(&'a SVInfoInner>), +// ThdEnum(&'a SVInfoInner>), +// ThdSet(&'a SVInfoInner>), +// ThdDouble(&'a SVInfoInner>), +// } + +impl SysVarInfoU { + // /// Determine the version based on flags + // fn determine_version(&self) -> &SysVarInfo { + // // SAFETY: the flags field is always in the same position + // let flags = unsafe { self.basic.get().flags }; + + // } +} /// Create a system variable from an atomic /// diff --git a/rust/mariadb/src/plugin/variables_parse.rs b/rust/mariadb/src/plugin/variables_parse.rs new file mode 100644 index 0000000000000..ffe6cfb313be1 --- /dev/null +++ b/rust/mariadb/src/plugin/variables_parse.rs @@ -0,0 +1,151 @@ +//! Variables parser +//! +//! Reimplementation of `check_func_x` in `sql_plugin.cc`. It's just easier to +//! reimpelment these because it means we can use thread safe types. +//! +//! All these functions share similar signatures, see `plugin.h` +//! +//! # Check functions +//! +//! Parse input +//! +//! - `thd`: thread handle +//! - `var`: system variable union. SAFETY: must be correct type (caller varifies) +//! - `save`: pointer to temporary storage +//! - `value`: user-provided value +//! +//! # Update function +//! +//! +//! +//! - `thd`: thread handle +//! - `var`: system variable union. SAFETY: must be correct type (caller varifies) +//! - `save`: pointer to temporary storage +//! - `value`: user-provided value + +use std::cell::UnsafeCell; +use std::ffi::{c_int, c_void, CStr}; +use std::os::raw::c_char; +use std::sync::atomic::{AtomicBool, Ordering}; + +use super::variables::SysVarInfoU; +use crate::bindings; +use crate::helpers::str2bool; + +/// # Safety +/// +/// Variable has to be of the right type, bool +pub unsafe fn check_func_atomic_bool( + thd: *const c_void, + var: *mut c_void, + // var: *mut SysVarInfoU, + save: *mut c_void, + value: *const bindings::st_mysql_value, +) -> c_int { + todo!() + // let sql_val = MySqlValue::from_ptr(value); + // let dest: *const AtomicBool = save.cast(); + // let new_val = match sql_val.value() { + // Value::Int(v) => { + // let tmp = v.unwrap_or(0); + // match tmp { + // 0 => false, + // 1 => true, + // _ => return 1, + // } + // } + // Value::String(s) => { + // let inner = s.expect("got null string"); + // str2bool(&inner) + // .unwrap_or_else(|| panic!("value '{inner}' is not a valid bool indicator")) + // } + // Value::Real(_) => panic!("unexpected real value"), + // }; + // (*dest).store(new_val, Ordering::Relaxed); + // 0 +} + +// pub unsafe fn update_func_atomic_bool( +// thd: *const c_void, +// // var: *mut SysVarInfoU, +// var: *mut c_void, +// var_ptr: *mut c_void, +// save: *const c_void, +// ) { +// let dest: *const AtomicBool = var_ptr.cast(); +// let new_val: u8 = *save.cast(); +// let new_val_bool = match new_val { +// 1 => true, +// 0 => false, +// n => panic!("invalid boolean value {n}"), +// }; +// (*dest).store(new_val_bool, Ordering::Relaxed); +// } + +pub(crate) enum CliValue { + Int(Option), + Real(Option), + String(Option), +} + +pub(crate) struct CliMysqlValue(UnsafeCell); + +impl CliMysqlValue { + /// `item_val_str function`, `item_val_int`, `item_val_real` + pub(crate) fn value(&self) -> CliValue { + unsafe { + match (*self.0.get()).value_type.unwrap()(self.0.get()) + .try_into() + .unwrap() + { + bindings::MYSQL_VALUE_TYPE_INT => self.as_int(), + bindings::MYSQL_VALUE_TYPE_REAL => self.as_real(), + bindings::MYSQL_VALUE_TYPE_STRING => self.as_string(), + x => panic!("unrecognized value type {x}"), + } + } + } + + unsafe fn from_ptr<'a>(ptr: *const bindings::st_mysql_value) -> &'a Self { + &*ptr.cast() + } + + unsafe fn as_int(&self) -> CliValue { + let mut res = 0i64; + let nul = (*self.0.get()).val_int.unwrap()(self.0.get(), &mut res); + if nul == 0 { + CliValue::Int(Some(res)) + } else { + CliValue::Int(None) + } + } + + unsafe fn as_real(&self) -> CliValue { + let mut res = 0f64; + let nul = (*self.0.get()).val_real.unwrap()(self.0.get(), &mut res); + if nul == 0 { + CliValue::Real(Some(res)) + } else { + CliValue::Real(None) + } + } + + unsafe fn as_string(&self) -> CliValue { + let mut buf = vec![0u8; 512]; + let mut len: c_int = buf.len().try_into().unwrap(); + // This function copies ot the buffer if it fits, returns a temp + // string otherwisw + let ptr = (*self.0.get()).val_str.unwrap()(self.0.get(), buf.as_mut_ptr().cast(), &mut len); + if ptr.is_null() { + return CliValue::String(None); + } + if ptr.cast() == buf.as_ptr() { + buf.truncate(len.try_into().unwrap()); + let res = String::from_utf8(buf).expect("got a buffer that isn't utf8"); + CliValue::String(Some(res)) + } else { + // figure out where the buffer lives otherwise + panic!("buffer too long: needs length {len}"); + } + } +} diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 9eea94143d2e6..8fc03432bb70f 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -12,7 +12,7 @@ use mariadb::log::{debug, error, info}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, SysVarAtomic, + register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, }; use mariadb::service_sql::{ClientError, Fetch, FetchedRows, MySqlConn}; From b58aa9806eddca18d15f48011360eed0c8c9260f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 6 Feb 2023 02:19:25 -0500 Subject: [PATCH 25/34] Work on proc macro tests --- rust/bindings/src/hand_imples.rs | 2 +- rust/examples/keymgt-basic/src/lib.rs | 13 --- rust/examples/keymgt-debug/src/lib.rs | 28 ++--- rust/macros/Cargo.toml | 6 +- rust/macros/src/register_plugin.rs | 20 ++-- rust/macros/tests/entry.rs | 6 + rust/macros/tests/include.rs | 29 +++++ rust/macros/tests/pass/01-simple.rs | 15 +++ rust/macros/tests/pass/02-with-init.rs | 16 +++ rust/mariadb/src/lib.rs | 8 +- rust/mariadb/src/plugin.rs | 4 + rust/mariadb/src/plugin/variables.rs | 121 +++++++-------------- rust/mariadb/src/plugin/variables_parse.rs | 1 + 13 files changed, 143 insertions(+), 126 deletions(-) create mode 100644 rust/macros/tests/entry.rs create mode 100644 rust/macros/tests/include.rs create mode 100644 rust/macros/tests/pass/01-simple.rs create mode 100644 rust/macros/tests/pass/02-with-init.rs diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index 582dc797374f8..3d9b858ecfd0f 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_char, c_double, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong, c_void}; +use std::ffi::{c_char, c_double, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong}; use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; diff --git a/rust/examples/keymgt-basic/src/lib.rs b/rust/examples/keymgt-basic/src/lib.rs index 8c44433073b70..cc772da25fd2e 100644 --- a/rust/examples/keymgt-basic/src/lib.rs +++ b/rust/examples/keymgt-basic/src/lib.rs @@ -71,16 +71,3 @@ register_plugin! { init: BasicKeyMgt, // optional encryption: false, } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn print_statics() { - // unsafe { dbg!(&*(_ENCRYPTION_ST.as_ptr())) }; - dbg!(&_maria_plugin_interface_version_); - dbg!(&_maria_sizeof_struct_st_plugin_); - unsafe { dbg!(&_maria_plugin_declarations_) }; - } -} diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index b501607453ed1..9d6e4a981860f 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -9,8 +9,10 @@ use std::cell::UnsafeCell; use std::ffi::c_void; +use std::sync::Mutex; use std::sync::atomic::{AtomicU32, Ordering}; +use mariadb::log::{debug}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ @@ -19,18 +21,8 @@ use mariadb::plugin::{ use mariadb::sysvar_atomic; const KEY_LENGTH: usize = 4; - static KEY_VERSION: AtomicU32 = AtomicU32::new(1); -// static KEY_VERSION_SYSVAR: SysVarAtomic = sysvar_atomic! { -// ty: u32, -// name: "version", -// var: KEY_VERSION, -// comment: "Latest key version", -// flags: [PluginVarInfo::ReqCmdArg], -// default: 1, -// }; - struct DebugKeyMgmt; impl Init for DebugKeyMgmt { @@ -47,6 +39,7 @@ impl Init for DebugKeyMgmt { impl KeyManager for DebugKeyMgmt { fn get_latest_key_version(key_id: u32) -> Result { + debug!("DebugKeyMgmt get_latest_key_version"); if key_id != 1 { Err(KeyError::VersionInvalid) } else { @@ -55,6 +48,7 @@ impl KeyManager for DebugKeyMgmt { } fn get_key(key_id: u32, key_version: u32, dst: &mut [u8]) -> Result<(), KeyError> { + debug!("DebugKeyMgmt get_key"); if key_id != 1 { return Err(KeyError::VersionInvalid); } @@ -72,6 +66,7 @@ impl KeyManager for DebugKeyMgmt { } fn key_length(key_id: u32, key_version: u32) -> Result { + debug!("DebugKeyMgmt key_length"); // Return the length of our u32 in bytes // Just verify our types don't change debug_assert_eq!( @@ -82,6 +77,11 @@ impl KeyManager for DebugKeyMgmt { } } +static TEST_SYSVAR_STR: Mutex = Mutex::new(String::new()); + + + + register_plugin! { DebugKeyMgmt, ptype: PluginType::MariaEncryption, @@ -94,11 +94,3 @@ register_plugin! { init: DebugKeyMgmt, // optional encryption: false, } - -// PROC: should mangle names with type name - -// static _INTERNAL_SYSVARS: UcWrap<[*mut mariadb::bindings::st_mysql_sys_var; 2]> = -// UcWrap(UnsafeCell::new([ -// KEY_VERSION_SYSVAR.as_ptr().cast_mut(), -// ::std::ptr::null_mut(), -// ])); diff --git a/rust/macros/Cargo.toml b/rust/macros/Cargo.toml index 3f7735942834f..8288921d27429 100644 --- a/rust/macros/Cargo.toml +++ b/rust/macros/Cargo.toml @@ -3,10 +3,13 @@ name = "mariadb-macros" version = "0.1.0" edition = "2021" - [lib] proc-macro = true +[[test]] +name = "tests" +path = "tests/entry.rs" + [dependencies] # heck = "0.4.0" # lazy_static = "1.4.0" @@ -18,3 +21,4 @@ syn = { version = "1.0.107", features = [], default-features = false } [dev-dependencies] # trybuild = { version = "1.0.65", features = ["diff"] } mariadb = { path = "../mariadb" } +trybuild = { version = "1.0.77", features = ["diff"] } diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index fb6339f1d59d3..d73fd8e5f6983 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -231,20 +231,19 @@ impl PluginInfo { let init_fn_name = make_ident(&format!("_{}_init_fn", name.value())); // We always initialize the logger, maybe do init/deinit if struct requires - let (fn_init, fn_deinit, init_fn_body); + let fn_init = quote! { Some(#init_fn_name) }; + let (fn_deinit, init_fn_body); if let Some(init_ty) = self.init { - let ty_as_init = quote! {<#init_ty as ::mariadb::plugin::wrapper::WrapInit>}; - init_fn_body = quote! { ::mariadb::configure_logger!(); #ty_as_init::wrap_init; }; - fn_init = quote! { Some(#ty_as_init::wrap_init) }; + let ty_as_init = quote! { <#init_ty as ::mariadb::plugin::wrapper::WrapInit> }; + init_fn_body = quote! { ::mariadb::configure_logger!(); #ty_as_init::wrap_init(_p) }; fn_deinit = quote! { Some(#ty_as_init::wrap_deinit) }; } else { - init_fn_body = quote! { ::mariadb::configure_logger!(); }; - fn_init = quote! { Some(#init_fn_name) }; + init_fn_body = quote! { ::mariadb::configure_logger!(); 0 }; fn_deinit = quote! { None }; } let init_fn = quote! { - fn #init_fn_name(_: *mut std::ffi::c_void) { + unsafe extern "C" fn #init_fn_name(_p: *mut std::ffi::c_void) -> std::ffi::c_int { #init_fn_body } }; @@ -305,10 +304,13 @@ impl PluginDef { let info_st = self.info_struct; let plugin_st = self.plugin_struct; + let init_fn = self.init_fn; let ret: TokenStream = quote! { - // Different config based on statically or dynamically lynked + #info_st + #init_fn + // Different config based on statically or dynamically lynked #[no_mangle] #[cfg(make_static_lib)] static #vers_ident_stc: ::std::ffi::c_int = #version_val; @@ -338,8 +340,6 @@ impl PluginDef { #usynccell::new(#plugin_st), #usynccell::new(#null_ps), ] }; - - #info_st } .into(); diff --git a/rust/macros/tests/entry.rs b/rust/macros/tests/entry.rs new file mode 100644 index 0000000000000..c17efeb546783 --- /dev/null +++ b/rust/macros/tests/entry.rs @@ -0,0 +1,6 @@ +#[test] +fn test_build() { + let t = trybuild::TestCases::new(); + t.pass("tests/pass/*.rs"); + t.compile_fail("tests/fail/*.rs"); +} diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs new file mode 100644 index 0000000000000..99caf15ef8b2c --- /dev/null +++ b/rust/macros/tests/include.rs @@ -0,0 +1,29 @@ +/* Simple setup for dummy proc macro testing */ + +use mariadb::plugin::encryption::*; +use mariadb::plugin::prelude::*; +pub use mariadb_macros::register_plugin; + +struct TestPlugin; + +impl KeyManager for TestPlugin { + fn get_latest_key_version(_key_id: u32) -> Result { + todo!() + } + fn get_key(_key_id: u32, _key_version: u32, _dst: &mut [u8]) -> Result<(), KeyError> { + todo!() + } + fn key_length(_key_id: u32, _key_version: u32) -> Result { + todo!() + } +} + +impl Init for TestPlugin { + fn init() -> Result<(), InitError> { + todo!() + } + + fn deinit() -> Result<(), InitError> { + todo!() + } +} diff --git a/rust/macros/tests/pass/01-simple.rs b/rust/macros/tests/pass/01-simple.rs new file mode 100644 index 0000000000000..7a39b8038bc3d --- /dev/null +++ b/rust/macros/tests/pass/01-simple.rs @@ -0,0 +1,15 @@ +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + encryption: false, +} + +fn main() {} diff --git a/rust/macros/tests/pass/02-with-init.rs b/rust/macros/tests/pass/02-with-init.rs new file mode 100644 index 0000000000000..3a8c773fdd118 --- /dev/null +++ b/rust/macros/tests/pass/02-with-init.rs @@ -0,0 +1,16 @@ +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: TestPlugin, + encryption: false, +} + +fn main() {} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 5170f4c175bc3..194ccc4f227d2 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -70,11 +70,13 @@ impl log::Log for MariaLogger { #[macro_export] macro_rules! configure_logger { () => { - $crate::configure_logger!(log::LevelFilter::Warn) + $crate::configure_logger!($crate::log::LevelFilter::Warn) }; ($level:expr) => {{ - static LOGGER = $crate::MariaLogger::new(); - $crate::log::set_logger(&LOGGER).map(|()| log::set_max_level($level)) + static LOGGER: $crate::MariaLogger = $crate::MariaLogger::new(); + $crate::log::set_logger(&LOGGER) + .map(|()| $crate::log::set_max_level($level)) + .expect("failed to configure logger"); }} } diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 85477af0ac352..4605f3a2b3bda 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -33,6 +33,10 @@ //! init: ExampleKeyManager // optional: struct implementing Init if needed //! encryption: false, // false to use default encryption, true if your //! // struct implements 'Encryption' +//! sysvars: [ // sysvars should be a list of typed identifiers +//! some_identifier: Mutex, +//! other_identifier: AtomicI32, +//! ] //! } //! ``` diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 5ce2177653cef..0b680faecd640 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -6,10 +6,11 @@ use std::mem::ManuallyDrop; use std::os::raw::{c_char, c_uint}; use std::ptr; use std::sync::atomic::{AtomicBool, AtomicI32}; +use std::sync::Mutex; use mariadb_sys as bindings; -use super::variables_parse::CliMysqlValue; +use super::variables_parse::{CliMysqlValue, CliValue}; type SVInfoInner = ManuallyDrop>; @@ -33,24 +34,6 @@ pub union SysVarInfoU { // offset (also a field) } -/// Types that can be communicated with the C sysvar interface -trait SysVarCType { - const C_TYPE_FLAG: i32; -} - -// impl SysVarCType for i32 { -// const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_INT as i32; -// } -impl SysVarCType for i64 { - const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_LONGLONG as i32; -} -impl SysVarCType for f64 { - const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_DOUBLE as i32; -} -impl SysVarCType for *const c_char { - const C_TYPE_FLAG: i32 = bindings::PLUGIN_VAR_INT as i32; -} - /// Internal trait trait SysVar: Sync { /// Type for interfacing with the main server @@ -59,89 +42,67 @@ trait SysVar: Sync { const C_FLAGS: i32; /// C struct type CStType; - /// C representation of type - type CType; - /// Inner representation type - type Inner; - - // /// For the check function, validate the arguments in `value` and write them to `dest`. + /// Type shared between `check` and `update` + type Intermediate; + + /// For the check function, validate the arguments in `value` and write them to `dest`. + /// + /// - thd: thread pointer + /// - var: pointer to the c struct + /// - save: a temporary place to stash anything until it gets to update + /// - value: cli value unsafe fn check( thd: *const c_void, var: &mut Self::CStType, - save: &mut Self::CType, + save: &mut Self::Intermediate, value: &CliMysqlValue, - ); - // /// The update function + ) -> c_int; + /// Store the result of the `check` function. + /// + /// - thd: thread pointer + /// - var: pointer to the c struct + /// - var_ptr: where to stash the value + /// - save: stash from the `check` function unsafe fn update( thd: *const c_void, var: &mut Self::CStType, - save: &mut Self::CType, - value: &CliMysqlValue, + var_ptr: &mut Self, + save: &mut Self::Intermediate, ); } -impl SysVar for AtomicI32 { - const C_FLAGS: i32 = bindings::PLUGIN_VAR_INT as i32; - type CStType = bindings::sysvar_int_t; - - type Inner = i32; - - type CType = i32; - +/// Intermediate type is a &String +impl SysVar for Mutex { + const C_FLAGS: i32 = (bindings::PLUGIN_VAR_STR) as i32; + type CStType = bindings::sysvar_str_t; + type Intermediate = Box>; unsafe fn check( thd: *const c_void, var: &mut Self::CStType, - save: &mut Self::CType, + save: &mut Self::Intermediate, value: &CliMysqlValue, - ) { - todo!() + ) -> c_int { + crate::log::warn!("entering sysvar mutex string check"); + let CliValue::String(Some(s)) = value.value() else { + panic!("got wrong string value {:?}", value.value()); + }; + dbg!(&s); + *save = Box::new(Some(s)); + 0 } - unsafe fn update( thd: *const c_void, var: &mut Self::CStType, - save: &mut Self::CType, - value: &CliMysqlValue, + var_ptr: &mut Self, + save: &mut Self::Intermediate, ) { - todo!() + crate::log::warn!("entering sysvar mutex string update"); + let s = save.take().unwrap(); + dbg!(&s); + *var_ptr.lock().expect("failed to lock mutex") = s; } } -macro_rules! sysvar { - () => {}; -} - -// pub enum SysVarInfo<'a> { -// Bool(&'a SVInfoInner>), -// String(&'a SVInfoInner>), -// ConstString(&'a SVInfoInner>), -// Int(&'a SVInfoInner>), -// UInt(&'a SVInfoInner>), -// Long(&'a SVInfoInner>), -// ULong(&'a SVInfoInner>), -// LongLong(&'a SVInfoInner>), -// ULongLong(&'a SVInfoInner>), -// UInt64T(&'a SVInfoInner>), -// SizeT(&'a SVInfoInner>), -// Enum(&'a SVInfoInner>), -// Set(&'a SVInfoInner>), -// Double(&'a SVInfoInner>), - -// ThdBool(&'a SVInfoInner>), -// ThdString(&'a SVInfoInner>), -// ThdInt(&'a SVInfoInner>), -// ThdUInt(&'a SVInfoInner>), -// ThdLong(&'a SVInfoInner>), -// ThdULong(&'a SVInfoInner>), -// ThdLongLong(&'a SVInfoInner>), -// ThdULongLong(&'a SVInfoInner>), -// ThdUInt64T(&'a SVInfoInner>), -// ThdSizeT(&'a SVInfoInner>), -// ThdEnum(&'a SVInfoInner>), -// ThdSet(&'a SVInfoInner>), -// ThdDouble(&'a SVInfoInner>), -// } - impl SysVarInfoU { // /// Determine the version based on flags // fn determine_version(&self) -> &SysVarInfo { diff --git a/rust/mariadb/src/plugin/variables_parse.rs b/rust/mariadb/src/plugin/variables_parse.rs index ffe6cfb313be1..1cb71d62c667c 100644 --- a/rust/mariadb/src/plugin/variables_parse.rs +++ b/rust/mariadb/src/plugin/variables_parse.rs @@ -82,6 +82,7 @@ pub unsafe fn check_func_atomic_bool( // (*dest).store(new_val_bool, Ordering::Relaxed); // } +#[derive(Debug, PartialEq)] pub(crate) enum CliValue { Int(Option), Real(Option), From 39994aa769e9798b10a31cd2c62d1486cd199c6e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 6 Feb 2023 16:23:57 -0500 Subject: [PATCH 26/34] Update 'register_plugin' to accept system variables Update tests to produce correct output for strings --- rust/bindings/src/hand_imples.rs | 5 +- rust/examples/encryption/src/lib.rs | 1 - rust/examples/keymgt-debug/src/lib.rs | 8 +- rust/macros/src/fields.rs | 53 ++++ rust/macros/src/helpers.rs | 36 +++ rust/macros/src/lib.rs | 3 + rust/macros/src/parse_vars.rs | 313 ++++++++++++++++++++ rust/macros/src/register_plugin.rs | 179 ++++++----- rust/macros/tests/fail/01-extra-args.rs | 17 ++ rust/macros/tests/fail/01-extra-args.stderr | 5 + rust/macros/tests/include.rs | 8 +- rust/macros/tests/pass/01-simple.rs | 44 ++- rust/macros/tests/pass/02-with-init.rs | 9 +- rust/macros/tests/pass/03-with-sysargs.rs | 46 +++ rust/mariadb/src/helpers.rs | 36 ++- rust/mariadb/src/lib.rs | 13 +- rust/mariadb/src/plugin.rs | 48 ++- rust/mariadb/src/plugin/variables.rs | 171 ++++------- rust/mariadb/src/plugin/variables_parse.rs | 2 +- rust/mariadb/src/plugin/wrapper.rs | 31 -- rust/mariadb/src/service_sql.rs | 2 +- 21 files changed, 770 insertions(+), 260 deletions(-) create mode 100644 rust/macros/src/fields.rs create mode 100644 rust/macros/src/helpers.rs create mode 100644 rust/macros/src/parse_vars.rs create mode 100644 rust/macros/tests/fail/01-extra-args.rs create mode 100644 rust/macros/tests/fail/01-extra-args.stderr create mode 100644 rust/macros/tests/pass/03-with-sysargs.rs diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_imples.rs index 3d9b858ecfd0f..9540b2e7f897f 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_imples.rs @@ -10,7 +10,7 @@ pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; // no support for THD yet macro_rules! declare_sysvar_type { - (@common $name:ident: $(#[$doc:meta] $fname:ident: $fty:ty),+ $(,)*) => { + (@common $name:ident: $(#[$doc:meta] $fname:ident: $fty:ty),* $(,)*) => { // Common implementation #[repr(C)] #[derive(Debug)] @@ -30,7 +30,7 @@ macro_rules! declare_sysvar_type { $( #[$doc] pub $fname: $fty - ),+ + ),* } }; (basic: $name:ident, $ty:ty) => { @@ -91,6 +91,7 @@ macro_rules! declare_sysvar_type { // }; } +declare_sysvar_type!(@common sysvar_common_t:); declare_sysvar_type!(basic: sysvar_bool_t, bool); declare_sysvar_type!(basic: sysvar_str_t, *mut c_char); declare_sysvar_type!(typelib: sysvar_enum_t, c_ulong); diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs index 979ee94e0a6ec..699140c73b061 100644 --- a/rust/examples/encryption/src/lib.rs +++ b/rust/examples/encryption/src/lib.rs @@ -14,7 +14,6 @@ use aes_gcm::{ }; use mariadb::plugin::encryption::{Encryption, EncryptionError, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; -use mariadb::plugin::wrapper::WrapInit; use rand::Rng; use sha2::{Digest, Sha256 as Hasher}; diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index 9d6e4a981860f..f79dc25c8692f 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -9,16 +9,15 @@ use std::cell::UnsafeCell; use std::ffi::c_void; -use std::sync::Mutex; use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Mutex; -use mariadb::log::{debug}; +use mariadb::log::debug; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, }; -use mariadb::sysvar_atomic; const KEY_LENGTH: usize = 4; static KEY_VERSION: AtomicU32 = AtomicU32::new(1); @@ -79,9 +78,6 @@ impl KeyManager for DebugKeyMgmt { static TEST_SYSVAR_STR: Mutex = Mutex::new(String::new()); - - - register_plugin! { DebugKeyMgmt, ptype: PluginType::MariaEncryption, diff --git a/rust/macros/src/fields.rs b/rust/macros/src/fields.rs new file mode 100644 index 0000000000000..7c9f38df1aeba --- /dev/null +++ b/rust/macros/src/fields.rs @@ -0,0 +1,53 @@ +pub mod plugin { + /// All fields, in expected order + pub const ALL_FIELDS: &[&str] = &[ + "ptype", + "name", + "author", + "description", + "license", + "maturity", + "version", + "init", + "encryption", + "variables", + ]; + + /// Always required + pub const REQ_FIELDS: &[&str] = &[ + "ptype", + "name", + "author", + "description", + "license", + "maturity", + "version", + ]; + + pub const ENCR_REQ_FIELDS: &[&str] = &["encryption"]; + + pub const ENCR_OPT_FIELDS: &[&str] = &["init", "sysvars"]; +} + +pub mod sysvar { + /// All fields, in expected order + pub const ALL_FIELDS: &[&str] = &[ + "ident", + "vtype", + "name", + "description", + "options", + "default", + "min", + "max", + "interval", + ]; + + /// Always required + pub const REQ_FIELDS: &[&str] = &["ident", "vtype", "name", "description"]; + + pub const STR_REQ_FIELDS: &[&str] = &[]; + pub const STR_OPT_FIELDS: &[&str] = &["default"]; + pub const NUM_REQ_FIELDS: &[&str] = &[]; + pub const NUM_OPT_FIELDS: &[&str] = &["default", "min", "max", "interval"]; +} diff --git a/rust/macros/src/helpers.rs b/rust/macros/src/helpers.rs new file mode 100644 index 0000000000000..0c8f9ac540b84 --- /dev/null +++ b/rust/macros/src/helpers.rs @@ -0,0 +1,36 @@ +use proc_macro2::Span; +use syn::{parse_quote, Error, Expr, Ident, Lit, LitStr}; + +/// Get the field as a boolean +pub fn expect_bool(field_opt: &Option) -> syn::Result { + let field = field_opt.as_ref().unwrap(); + + if field == &parse_quote! {true} { + Ok(true) + } else if field == &parse_quote! {false} { + Ok(false) + } else { + let msg = "unexpected value: only 'true' or 'false' allowed"; + Err(Error::new_spanned(field, msg)) + } +} + +/// Expect a literal string, error if that's not the case +pub fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { + let field = field_opt.as_ref().unwrap(); + let Expr::Lit(lit) = field else { // got non-literal + let msg = "expected literal expression for this field"; + return Err(Error::new_spanned(field, msg)); +}; + let Lit::Str(litstr) = &lit.lit else { // got literal that wasn't a string + let msg = "only literal strings are allowed for this field"; + return Err(Error::new_spanned(field, msg)); +}; + + Ok(litstr) +} + +/// Create an identifier from a string with span at the macro call site +pub fn make_ident(s: &str) -> Ident { + Ident::new(s, Span::call_site()) +} diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index 68ffa0fed531a..0ce1d6010d4e7 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -7,6 +7,9 @@ #![allow(clippy::must_use_candidate)] #![allow(clippy::cast_possible_truncation)] +mod fields; +mod helpers; +mod parse_vars; mod register_plugin; use proc_macro::TokenStream; diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs new file mode 100644 index 0000000000000..d0db8a433e264 --- /dev/null +++ b/rust/macros/src/parse_vars.rs @@ -0,0 +1,313 @@ +//! Parse sysvar syntax +//! +//! ```ignore +//! { +//! ident: SOME_IDENT, +//! vtype: String, +//! name: "sql_name", +//! description: "this is a description", +//! options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], +//! default: "something" +//! } +//! ``` +//! +//! or +//! +//! ```ignore +//! { +//! ident: OTHER_IDENT, +//! vtype: AtomicI32, +//! name: "other_sql_name", +//! description: "this is a description", +//! options: [PluginVarInfo::ReqCmdArg], +//! default: 100, +//! min: 10, +//! max: 500, +//! interval: 10 +//! } +//! ``` + +#![allow(unused)] + +// use proc_macro::TokenStream; +use proc_macro2::{Literal, Span, TokenStream, TokenTree}; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::Group; +use syn::{ + bracketed, parse_macro_input, parse_quote, Attribute, DeriveInput, Error, Expr, ExprArray, + ExprLit, ExprStruct, FieldValue, Ident, ImplItem, ImplItemType, Item, ItemImpl, Lit, LitStr, + Path, PathSegment, Token, Type, TypePath, TypeReference, +}; + +use crate::fields::sysvar::{ + ALL_FIELDS, NUM_OPT_FIELDS, NUM_REQ_FIELDS, REQ_FIELDS, STR_OPT_FIELDS, STR_REQ_FIELDS, +}; +use crate::helpers::expect_litstr; + +#[derive(Clone, Copy, Debug)] +enum VarTypeInner { + SysVar, + ShowVar, +} + +/// Identifiers and their bodies +#[derive(Clone, Debug, Default)] +pub struct Variables { + pub sys: Vec, + pub sys_idents: Vec, + pub show: Vec, + pub show_idents: Vec, +} + +impl Parse for Variables { + fn parse(input: ParseStream) -> syn::Result { + let content; + let _ = bracketed!(content in input); + let var_decl = Punctuated::::parse_terminated(&content)?; + let mut ret = Self::default(); + for var in var_decl { + let ty = var.var_type.unwrap(); + match ty { + VarTypeInner::SysVar => { + let tmp = var.make_usable()?; + ret.sys_idents.push(tmp.0); + ret.sys.push(tmp.1); + } + VarTypeInner::ShowVar => { + let tmp = var.make_usable()?; + ret.show_idents.push(tmp.0); + ret.show.push(tmp.1); + } + } + } + Ok(ret) + } +} + +#[derive(Clone, Debug, Default)] +struct VariableInfo { + span: Option, + var_type: Option, + ident: Option, + vtype: Option, + name: Option, + description: Option, + options: Option, + default: Option, + min: Option, + max: Option, + interval: Option, +} + +impl Parse for VariableInfo { + fn parse(input: ParseStream) -> syn::Result { + let span = input.span(); + let mut ret = Self::default(); + ret.span = Some(span); + + // parse struct-like syntax + let st: ExprStruct = input.parse()?; + // verify the weird possible things of a struct are empty + if !st.attrs.is_empty() { + return Err(Error::new_spanned(&st.attrs[0], "no attributes expected")); + } + if st.path != parse_quote! { SysVar } { + return Err(Error::new_spanned( + &st.path, + "only path 'SysVar' is allowed", + )); + } + ret.var_type = Some(VarTypeInner::SysVar); + if st.rest.is_some() { + return Err(Error::new_spanned(&st.rest, "unexpected 'rest' section")); + } + + let fields = st.fields; + let mut field_order: Vec = Vec::new(); + for field in fields.clone() { + let syn::Member::Named(name) = &field.member else { + return Err(Error::new_spanned(field, "missing field name")); + }; + + let name_str = name.to_string(); + let expr = field.expr; + + match name_str.as_str() { + "ident" => ret.ident = Some(expr), + "vtype" => ret.vtype = Some(expr), + "name" => ret.name = Some(expr), + "description" => ret.description = Some(expr), + "options" => ret.options = Some(expr), + "default" => ret.default = Some(expr), + "min" => ret.min = Some(expr), + "max" => ret.max = Some(expr), + "interval" => ret.interval = Some(expr), + _ => { + return Err(Error::new_spanned( + name, + format!("unexpected field '{name_str}'"), + )) + } + } + field_order.push(name_str); + } + + if let Err(msg) = verify_field_order(field_order.as_slice()) { + return Err(Error::new_spanned(fields, msg)); + } + + Ok(ret) + } +} + +impl VariableInfo { + fn make_usable(&self) -> syn::Result<(Ident, TokenStream)> { + match self.var_type.unwrap() { + VarTypeInner::SysVar => self.make_sysvar(), + VarTypeInner::ShowVar => self.make_showvar(), + } + } + + fn make_sysvar(&self) -> syn::Result<(Ident, TokenStream)> { + let Some(vtype) = &self.vtype else { + return Err(Error::new_spanned(&self.vtype, "missing required field 'vtype'")); + }; + let str_ty: Expr = parse_quote!(SysVarString); + let atomic_tys: [Expr; 12] = [ + parse_quote!(AtomicBool), + parse_quote!(AtomicI8), + parse_quote!(AtomicI16), + parse_quote!(AtomicI32), + parse_quote!(AtomicI64), + parse_quote!(AtomicIsize), + parse_quote!(AtomicPtr), + parse_quote!(AtomicU8), + parse_quote!(AtomicU16), + parse_quote!(AtomicU32), + parse_quote!(AtomicU64), + parse_quote!(AtomicUsize), + ]; + + // let tmp: proc_macro2::TokenStream = ; + let name = expect_litstr(&self.name)?; + let st_ident = Ident::new(&format!("_st_sysvar_{}", name.value()), Span::call_site()); + + if vtype == &str_ty { + Ok((st_ident.clone(), self.make_string_sysvar(&st_ident)?)) + } else if atomic_tys.contains(&vtype) { + Ok((st_ident.clone(), self.make_atomic_sysvar(&st_ident)?)) + } else { + Err(Error::new_spanned( + &self.vtype, + "invalid variable type. Only 'SysVarString' and 'AtomicX' currently allowed.", + )) + } + } + + fn make_string_sysvar(&self, st_ident: &Ident) -> syn::Result { + self.validate_correct_fields(STR_REQ_FIELDS, STR_OPT_FIELDS); + + let flags = quote! { ::mariadb::bindings::PLUGIN_VAR_STR as i32 }; + let name = expect_litstr(&self.name)?; + let description = expect_litstr(&self.description)?; + let default = if let Some(def) = &self.default { + quote! { ::mariadb::internals::cstr!(#def).as_ptr().cast_mut() } + } else { + quote! { ::mariadb::internals::cstr!("").as_ptr().cast_mut() } + }; + let disambig_name = quote! { ::std::sync::Mutex }; + let check_fn = quote! { None }; + let update_fn = quote! { + Some(<::mariadb::plugin::SysVarString as + ::mariadb::plugin::internals::SimpleSysvarWrap>::update_wrap) + }; + // let check_fn = quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::check) }; + // let update_fn = + // quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::update) }; + let ident = self.ident.as_ref().unwrap(); + let res = quote! { + static #st_ident: ::mariadb::internals::UnsafeSyncCell< + ::mariadb::bindings::sysvar_str_t, + > = unsafe { + ::mariadb::internals::UnsafeSyncCell::new( + ::mariadb::bindings::sysvar_str_t { + flags: #flags, + name: ::mariadb::internals::cstr!(#name).as_ptr(), + comment: ::mariadb::internals::cstr!(#description).as_ptr(), + check: #check_fn, + update: #update_fn, + value: ::std::ptr::addr_of!(#ident).cast_mut().cast(), // *mut *mut c_char, + def_val: #default, + } + ) + }; + + }; + + Ok(res.into()) + } + + fn make_atomic_sysvar(&self, st_ident: &Ident) -> syn::Result { + self.validate_correct_fields(NUM_REQ_FIELDS, NUM_OPT_FIELDS); + todo!() + } + + fn make_showvar(&self) -> syn::Result<(Ident, TokenStream)> { + todo!() + } + + fn validate_correct_fields(&self, required: &[&str], optional: &[&str]) -> syn::Result<()> { + // These are all required for all plugin types + let name_map = [ + (&self.ident, "ident"), + (&self.vtype, "vtype"), + (&self.name, "name"), + (&self.description, "description"), + (&self.options, "options"), + (&self.default, "default"), + (&self.min, "min"), + (&self.max, "max"), + (&self.interval, "interval"), + ]; + let vtype = self.vtype.as_ref().unwrap(); + let mut req = REQ_FIELDS.to_vec(); + req.extend_from_slice(required); + + for req_field in &req { + let (field_val, fname) = name_map.iter().find(|f| f.1 == *req_field).unwrap(); + + if field_val.is_none() { + let msg = format!( + "field '{fname}' is expected for variables of type {vtype:?}, but not provided" + ); + return Err(Error::new(self.span.unwrap(), msg)); + } + } + + for (field, fname) in name_map { + if field.is_some() && !req.contains(&fname) && !optional.contains(&fname) { + let msg = + format!("field '{fname}' is not expected for variables of type {vtype:?}"); + return Err(Error::new_spanned(field.as_ref().unwrap(), msg)); + } + } + + Ok(()) + } +} + +fn verify_field_order(fields: &[String]) -> Result<(), String> { + let mut expected_order = ALL_FIELDS.to_vec(); + + expected_order.retain(|expected| fields.iter().any(|f| f == expected)); + + if expected_order == fields { + return Ok(()); + } + + Err(format!( + "fields not in expected order. reorder as:\n{expected_order:?}", + )) +} diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index d73fd8e5f6983..c008254e5f1b1 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -2,9 +2,8 @@ #![allow(unused)] -use proc_macro::TokenStream; -use proc_macro2::{Literal, Span, TokenTree}; -use quote::quote; +use proc_macro2::{Literal, Span, TokenStream, TokenTree}; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Group; @@ -14,13 +13,17 @@ use syn::{ TypePath, TypeReference, }; +use crate::fields::plugin::{ALL_FIELDS, ENCR_OPT_FIELDS, ENCR_REQ_FIELDS, REQ_FIELDS}; +use crate::helpers::{expect_bool, expect_litstr, make_ident}; +use crate::parse_vars::{self, Variables}; + /// Entrypoint for this proc macro -pub fn entry(tokens: TokenStream) -> TokenStream { +pub fn entry(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { let tokens_pm2: proc_macro2::TokenStream = tokens.clone().into(); let input = parse_macro_input!(tokens as PluginInfo); let plugindef = input.into_encryption_struct(); match plugindef { - Ok(ts) => ts.into_output(), + Ok(ts) => ts.into_output().into(), Err(e) => e.into_compile_error().into(), } } @@ -41,6 +44,7 @@ struct PluginInfo { version: Option, init: Option, encryption: Option, + variables: Option, } impl Parse for PluginInfo { @@ -71,6 +75,7 @@ impl Parse for PluginInfo { "version" => ret.version = Some(expr), "init" => ret.init = Some(expr), "encryption" => ret.encryption = Some(expr), + "variables" => ret.variables = Some(expr), _ => { return Err(Error::new_spanned( name, @@ -102,6 +107,7 @@ impl PluginInfo { version: None, init: None, encryption: None, + variables: None, } } @@ -123,17 +129,10 @@ impl PluginInfo { (&self.version, "version"), (&self.init, "init"), (&self.encryption, "encryption"), + (&self.variables, "sysvars"), ]; - let mut req = vec![ - "ptype", - "name", - "author", - "description", - "license", - "maturity", - "version", - ]; + let mut req = REQ_FIELDS.to_vec(); req.extend_from_slice(required); for req_field in &req { @@ -155,11 +154,49 @@ impl PluginInfo { Ok(()) } + /// Validate the sysvars definition and create structure. Returns + fn make_variables(&self) -> syn::Result { + let mut ret = VariableBodies { + sysvar_body: TokenStream::new(), + sysvar_field: quote! { ::std::ptr::null_mut() }, + }; + let Some(vars_decl) = &self.variables else { + return Ok(ret); + }; + let vars: Variables = syn::parse(vars_decl.to_token_stream().into())?; + let name = expect_litstr(&self.name)?.value(); + let sysvar_arr_ident = Ident::new(&format!("_plugin_{}_sysvars", name), Span::call_site()); + + let sysvar_bodies = &vars.sys; + let sysvar_idents = &vars.sys_idents; + assert_eq!(sysvar_bodies.len(), sysvar_idents.len()); + + if sysvar_bodies.is_empty() { + return Ok(ret); + } + + let len = sysvar_bodies.len() + 1; + let usynccell = quote! { ::mariadb::internals::UnsafeSyncCell }; + + ret.sysvar_body = quote! { + #(#sysvar_bodies)* + + static #sysvar_arr_ident: + [#usynccell<*mut ::mariadb::bindings::sysvar_common_t>; #len] + = + unsafe { [ + #( #usynccell::new(#sysvar_idents.get().cast()), )* + #usynccell::new(::std::ptr::null_mut()) + ] }; + }; + // panic!("{}", ret.sysvar_body); + ret.sysvar_field = quote! { #sysvar_arr_ident.as_ptr().cast_mut().cast() }; + Ok(ret) + } + /// Ensure we have the fields required for an encryption plugin fn validate_as_encryption(&self) -> syn::Result<()> { - let optional = ["init"]; - let required = ["encryption"]; - self.validate_correct_fields(&required, &optional, "encryption")?; + self.validate_correct_fields(ENCR_REQ_FIELDS, ENCR_OPT_FIELDS, "encryption")?; Ok(()) } @@ -172,13 +209,13 @@ impl PluginInfo { let name = expect_litstr(&self.name)?; let plugin_st_name = Ident::new(&format!("_ST_PLUGIN_{}", name.value()), Span::call_site()); - let ty_as_wkeymgt = - quote! { <#type_ as ::mariadb::plugin::encryption_wrapper::WrapKeyMgr> }; - let ty_as_wenc = - quote! { <#type_ as ::mariadb::plugin::encryption_wrapper::WrapEncryption> }; + let ty_as_wkeymgt = quote! { <#type_ as ::mariadb::plugin::internals::WrapKeyMgr> }; + let ty_as_wenc = quote! { <#type_ as ::mariadb::plugin::internals::WrapEncryption> }; let interface_version = quote! { ::mariadb::bindings::MariaDB_ENCRYPTION_INTERFACE_VERSION as ::std::ffi::c_int }; let get_key_vers = quote! { Some(#ty_as_wkeymgt::wrap_get_latest_key_version) }; let get_key = quote! { Some(#ty_as_wkeymgt::wrap_get_key) }; + let variables = self.make_variables()?; + let variables_body = variables.sysvar_body; let (crypt_size, crypt_init, crypt_update, crypt_finish, crypt_len); @@ -202,10 +239,10 @@ impl PluginInfo { } let info_struct = quote! { - static #plugin_st_name: ::mariadb::plugin::wrapper::UnsafeSyncCell< + static #plugin_st_name: ::mariadb::internals::UnsafeSyncCell< ::mariadb::bindings::st_mariadb_encryption, > = unsafe { - ::mariadb::plugin::wrapper::UnsafeSyncCell::new( + ::mariadb::internals::UnsafeSyncCell::new( ::mariadb::bindings::st_mariadb_encryption { interface_version: #interface_version, get_latest_key_version: #get_key_vers, @@ -220,13 +257,15 @@ impl PluginInfo { }; }; - let version = version_int(&expect_litstr(&self.version)?.value()) - .map_err(|e| Error::new_spanned(&self.version, e))?; + let version_str = &expect_litstr(&self.version)?.value(); + let version_int = + version_int(version_str).map_err(|e| Error::new_spanned(&self.version, e))?; let author = expect_litstr(&self.author)?; let description = expect_litstr(&self.description)?; let license = self.license.unwrap(); let maturity = self.maturity.unwrap(); let ptype = self.ptype.unwrap(); + let system_vars_ptr = variables.sysvar_field; let init_fn_name = make_ident(&format!("_{}_init_fn", name.value())); @@ -234,7 +273,7 @@ impl PluginInfo { let fn_init = quote! { Some(#init_fn_name) }; let (fn_deinit, init_fn_body); if let Some(init_ty) = self.init { - let ty_as_init = quote! { <#init_ty as ::mariadb::plugin::wrapper::WrapInit> }; + let ty_as_init = quote! { <#init_ty as ::mariadb::plugin::internals::WrapInit> }; init_fn_body = quote! { ::mariadb::configure_logger!(); #ty_as_init::wrap_init(_p) }; fn_deinit = quote! { Some(#ty_as_init::wrap_deinit) }; } else { @@ -252,16 +291,16 @@ impl PluginInfo { ::mariadb::bindings::st_maria_plugin { type_: #ptype.to_ptype_registration(), info: #plugin_st_name.as_ptr().cast_mut().cast(), - name: ::mariadb::cstr::cstr!(#name).as_ptr(), - author: ::mariadb::cstr::cstr!(#author).as_ptr(), - descr: ::mariadb::cstr::cstr!(#description).as_ptr(), + name: ::mariadb::internals::cstr!(#name).as_ptr(), + author: ::mariadb::internals::cstr!(#author).as_ptr(), + descr: ::mariadb::internals::cstr!(#description).as_ptr(), license: #license.to_license_registration(), init: #fn_init, deinit: #fn_deinit, - version: #version as ::std::ffi::c_uint, + version: #version_int as ::std::ffi::c_uint, status_vars: ::std::ptr::null_mut(), - system_vars: ::std::ptr::null_mut(), - version_info: ::mariadb::cstr::cstr!("0.1").as_ptr(), + system_vars: #system_vars_ptr, + version_info: ::mariadb::internals::cstr!(#version_str).as_ptr(), maturity: #maturity.to_maturity_registration(), }, }; @@ -271,17 +310,26 @@ impl PluginInfo { init_fn, info_struct, plugin_struct, + variable_body: variables_body, }) } } +struct VariableBodies { + /// This body will be added + sysvar_body: TokenStream, + /// What to put in the registering `st_mariadb_plugin + sysvar_field: TokenStream, +} + /// Contains a struct definition of type `st_mariadb_encryption` or whatever is /// applicable, plus the struct of `st_maria_plugin` that references it struct PluginDef { name: String, - init_fn: proc_macro2::TokenStream, - info_struct: proc_macro2::TokenStream, - plugin_struct: proc_macro2::TokenStream, + init_fn: TokenStream, + info_struct: TokenStream, + plugin_struct: TokenStream, + variable_body: TokenStream, } impl PluginDef { @@ -299,16 +347,20 @@ impl PluginDef { quote! { mariadb::bindings::MARIA_PLUGIN_INTERFACE_VERSION as ::std::ffi::c_int }; let size_val = quote! { ::std::mem::size_of::<#plugin_ty>() as ::std::ffi::c_int }; - let usynccell = quote! { ::mariadb::plugin::wrapper::UnsafeSyncCell }; - let null_ps = quote! { ::mariadb::plugin::wrapper::new_null_st_maria_plugin() }; + let usynccell = quote! { ::mariadb::internals::UnsafeSyncCell }; + let null_ps = quote! { ::mariadb::plugin::internals::new_null_st_maria_plugin() }; let info_st = self.info_struct; let plugin_st = self.plugin_struct; let init_fn = self.init_fn; + let variable_body = self.variable_body; + + quote! { ::std::ptr::null_mut() }; let ret: TokenStream = quote! { #info_st #init_fn + #variable_body // Different config based on statically or dynamically lynked #[no_mangle] @@ -342,24 +394,13 @@ impl PluginDef { ] }; } .into(); - ret } } /// Verify attribute order fn verify_field_order(fields: &[String]) -> Result<(), String> { - let mut expected_order = vec![ - "ptype", - "name", - "author", - "description", - "license", - "maturity", - "version", - "init", - "encryption", - ]; + let mut expected_order = ALL_FIELDS.to_vec(); expected_order.retain(|expected| fields.iter().any(|f| f == expected)); @@ -372,35 +413,6 @@ fn verify_field_order(fields: &[String]) -> Result<(), String> { )) } -/// Get the field as a boolean -fn expect_bool(field_opt: &Option) -> syn::Result { - let field = field_opt.as_ref().unwrap(); - - if field == &parse_quote! {true} { - Ok(true) - } else if field == &parse_quote! {false} { - Ok(false) - } else { - let msg = "unexpected value: only 'true' or 'false' allowed"; - Err(Error::new_spanned(field, msg)) - } -} - -/// Expect a literal string, error if that's not the case -fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { - let field = field_opt.as_ref().unwrap(); - let Expr::Lit(lit) = field else { // got non-literal - let msg = "expected literal expression for this field"; - return Err(Error::new_spanned(field, msg)); - }; - let Lit::Str(litstr) = &lit.lit else { // got literal that wasn't a string - let msg = "only literal strings are allowed for this field"; - return Err(Error::new_spanned(field, msg)); - }; - - Ok(litstr) -} - /// Convert a string like "1.2" to a hex like "0x0102". Error if no decimal, or /// if either value exceeds a u8. fn version_int(s: &str) -> Result { @@ -419,6 +431,13 @@ fn version_int(s: &str) -> Result { Ok(res) } -fn make_ident(s: &str) -> Ident { - Ident::new(s, Span::call_site()) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_version_int() { + assert_eq!(version_int("5.2"), Ok(0x0502)); + assert_eq!(version_int("11.0"), Ok(0x0b00)); + } } diff --git a/rust/macros/tests/fail/01-extra-args.rs b/rust/macros/tests/fail/01-extra-args.rs new file mode 100644 index 0000000000000..2233336ac4c39 --- /dev/null +++ b/rust/macros/tests/fail/01-extra-args.rs @@ -0,0 +1,17 @@ +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: TestPlugin, + encryption: false, + extra: false +} + +fn main() {} diff --git a/rust/macros/tests/fail/01-extra-args.stderr b/rust/macros/tests/fail/01-extra-args.stderr new file mode 100644 index 0000000000000..a8b2d53a367e1 --- /dev/null +++ b/rust/macros/tests/fail/01-extra-args.stderr @@ -0,0 +1,5 @@ +error: unexpected field 'extra' + --> tests/fail/01-extra-args.rs:14:5 + | +14 | extra: false + | ^^^^^ diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs index 99caf15ef8b2c..4f1d9f42120fb 100644 --- a/rust/macros/tests/include.rs +++ b/rust/macros/tests/include.rs @@ -1,9 +1,15 @@ -/* Simple setup for dummy proc macro testing */ +/* Simple setup for dummy proc macro testing. Simple compile pass/fail only. */ + +use std::sync::atomic::AtomicI32; use mariadb::plugin::encryption::*; use mariadb::plugin::prelude::*; +use mariadb::plugin::SysVarString; pub use mariadb_macros::register_plugin; +static _SYSVAR_ATOMIC: AtomicI32 = AtomicI32::new(0); +static _SYSVAR_STR: SysVarString = SysVarString::new(); + struct TestPlugin; impl KeyManager for TestPlugin { diff --git a/rust/macros/tests/pass/01-simple.rs b/rust/macros/tests/pass/01-simple.rs index 7a39b8038bc3d..339089927eecf 100644 --- a/rust/macros/tests/pass/01-simple.rs +++ b/rust/macros/tests/pass/01-simple.rs @@ -3,13 +3,49 @@ include!("../include.rs"); register_plugin! { TestPlugin, ptype: PluginType::MariaEncryption, - name: "debug_key_management", - author: "Trevor Gross", + name: "test_plugin_name", + author: "Test Author", description: "Debug key management plugin", license: License::Gpl, maturity: Maturity::Experimental, - version: "0.1", + version: "1.2", encryption: false, } -fn main() {} +fn main() { + use std::ffi::CStr; + + use mariadb::bindings::st_maria_plugin; + + // verify correct symbols are created + let _: i32 = _maria_plugin_interface_version_; + let _: i32 = _maria_sizeof_struct_st_plugin_; + let plugin_def: &st_maria_plugin = unsafe { &*(_maria_plugin_declarations_[0]).get() }; + + // verify struct has correct fields + let type_ = plugin_def.type_; + let name = unsafe { CStr::from_ptr(plugin_def.name).to_str().unwrap() }; + let author = unsafe { CStr::from_ptr(plugin_def.author).to_str().unwrap() }; + let descr = unsafe { CStr::from_ptr(plugin_def.descr).to_str().unwrap() }; + let license = plugin_def.license; + let init = plugin_def.init; + let deinit = plugin_def.deinit; + let version = plugin_def.version; + let status_vars = plugin_def.status_vars; + let system_vars = plugin_def.system_vars; + let version_info = unsafe { CStr::from_ptr(plugin_def.version_info).to_str().unwrap() }; + let maturity = plugin_def.maturity; + + assert_eq!(type_, PluginType::MariaEncryption as i32); + assert_eq!(name, "test_plugin_name"); + assert_eq!(author, "Test Author"); + assert_eq!(descr, "Debug key management plugin"); + assert_eq!(license, License::Gpl as i32); + assert!(init.is_some()); // we always have an init function to setup logging + assert!(deinit.is_none()); + assert_eq!(version, 0x0102); + assert!(status_vars.is_null()); + assert!(system_vars.is_null()); + assert_eq!(version_info, "1.2"); + assert_eq!(maturity, Maturity::Experimental as u32); +} diff --git a/rust/macros/tests/pass/02-with-init.rs b/rust/macros/tests/pass/02-with-init.rs index 3a8c773fdd118..dc87882ddfc7c 100644 --- a/rust/macros/tests/pass/02-with-init.rs +++ b/rust/macros/tests/pass/02-with-init.rs @@ -13,4 +13,11 @@ register_plugin! { encryption: false, } -fn main() {} +fn main() { + use mariadb::bindings::st_maria_plugin; + + let plugin_def: &st_maria_plugin = unsafe { &*(_maria_plugin_declarations_[0]).get() }; + + assert!(plugin_def.init.is_some()); + assert!(plugin_def.deinit.is_some()); +} diff --git a/rust/macros/tests/pass/03-with-sysargs.rs b/rust/macros/tests/pass/03-with-sysargs.rs new file mode 100644 index 0000000000000..18e59c9c06885 --- /dev/null +++ b/rust/macros/tests/pass/03-with-sysargs.rs @@ -0,0 +1,46 @@ +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: TestPlugin, + encryption: false, + variables: [ + SysVar { + ident: _SYSVAR_STR, + vtype: SysVarString, + name: "test_sysvar", + description: "this is a description", + options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], + default: "something" + } + ] +} + +fn main() { + // use std::ffi::CStr; + use std::ptr; + + use mariadb::bindings::{st_maria_plugin, st_mysql_sys_var, sysvar_common_t, sysvar_str_t}; + use mariadb::internals::UnsafeSyncCell; + + // verify correct symbols are created + let _: i32 = _maria_plugin_interface_version_; + let _: i32 = _maria_sizeof_struct_st_plugin_; + let plugin_def: &st_maria_plugin = unsafe { &*(_maria_plugin_declarations_[0]).get() }; + + let sysv_ptr: *mut *mut st_mysql_sys_var = plugin_def.system_vars; + let sysvar_st: *const sysvar_str_t = _st_sysvar_test_sysvar.get(); + let sysvar_arr: &[UnsafeSyncCell<*mut sysvar_common_t>] = &_plugin_debug_key_management_sysvars; + let idx_0: *mut sysvar_common_t = unsafe { *sysvar_arr[0].get() }; + let idx_1: *mut sysvar_common_t = unsafe { *sysvar_arr[1].get() }; + assert_eq!(idx_0, sysvar_st.cast_mut().cast()); + assert_eq!(idx_1, ptr::null_mut()); + assert_eq!(sysv_ptr, sysvar_arr.as_ptr().cast_mut().cast()); +} diff --git a/rust/mariadb/src/helpers.rs b/rust/mariadb/src/helpers.rs index 26dcce4d6b1a6..981070f94041d 100644 --- a/rust/mariadb/src/helpers.rs +++ b/rust/mariadb/src/helpers.rs @@ -1,4 +1,38 @@ -/// Pass +use std::cell::UnsafeCell; +use std::ptr; + +use super::bindings; + +/// Used for plugin registrations, which are in global scope. +#[doc(hidden)] +#[derive(Debug)] +#[repr(transparent)] +pub struct UnsafeSyncCell(UnsafeCell); + +impl UnsafeSyncCell { + /// # Safety + /// + /// This inner value be used in a Sync/Send way + pub const unsafe fn new(value: T) -> Self { + Self(UnsafeCell::new(value)) + } + + pub const fn as_ptr(&self) -> *const T { + self.0.get() + } + + pub const fn get(&self) -> *mut T { + self.0.get() + } + + pub fn get_mut(&mut self) -> &mut T { + self.0.get_mut() + } +} + +unsafe impl Send for UnsafeSyncCell {} +unsafe impl Sync for UnsafeSyncCell {} + pub fn str2bool(s: &str) -> Option { const TRUE_VALS: [&str; 3] = ["on", "true", "1"]; const FALSE_VALS: [&str; 3] = ["off", "false", "0"]; diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 194ccc4f227d2..8930e5d343a33 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -21,12 +21,17 @@ use std::fmt::Write; #[doc(inline)] pub use common::*; -#[doc(hidden)] -pub use cstr; pub use log; #[doc(hidden)] pub use mariadb_sys as bindings; +#[doc(hidden)] +pub mod internals { + pub use cstr::cstr; + + pub use super::helpers::UnsafeSyncCell; +} + #[doc(hidden)] pub struct MariaLogger { pkg: Option<&'static str>, @@ -67,6 +72,8 @@ impl log::Log for MariaLogger { fn flush(&self) {} } +/// Configure the default logger. This is currently called by default for +/// plugins in the `init` function. #[macro_export] macro_rules! configure_logger { () => { @@ -77,7 +84,7 @@ macro_rules! configure_logger { $crate::log::set_logger(&LOGGER) .map(|()| $crate::log::set_max_level($level)) .expect("failed to configure logger"); - }} + }}; } /// Provide the name of the calling function (full path) diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 4605f3a2b3bda..95cdaa0d49cd2 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -4,6 +4,11 @@ //! //! ``` //! use mariadb::plugin::prelude::*; +//! use mariadb::plugin::encryption::*; +//! use mariadb::plugin::SysVarString; +//! +//! static SYSVAR_STR: SysVarString = SysVarString::new(); +//! //! //! // May be empty or not //! struct ExampleKeyManager; @@ -30,13 +35,30 @@ //! license: License::Gpl, // select a license type //! maturity: Maturity::Experimental, // indicate license maturity //! version: "0.1", // provide an "a.b" version -//! init: ExampleKeyManager // optional: struct implementing Init if needed +//! init: ExampleKeyManager, // optional: struct implementing Init if needed //! encryption: false, // false to use default encryption, true if your //! // struct implements 'Encryption' -//! sysvars: [ // sysvars should be a list of typed identifiers -//! some_identifier: Mutex, -//! other_identifier: AtomicI32, -//! ] +//! variables: [ // variables should be a list of typed identifiers +//! SysVar { +//! ident: SYSVAR_STR, +//! vtype: SysVarString, +//! name: "sql_name", +//! description: "this is a description", +//! options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], +//! default: "something" +//! }, +//! // SysVar { +//! // ident: OTHER_IDENT, +//! // vtype: AtomicI32, +//! // name: "other_sql_name", +//! // description: "this is a description", +//! // options: [PluginVarInfo::ReqCmdArg], +//! // default: 100, +//! // min: 10, +//! // max: 500, +//! // interval: 10 +//! // } +//! ] //! } //! ``` @@ -45,20 +67,26 @@ use std::str::FromStr; use mariadb_sys as bindings; pub mod encryption; -#[doc(hidden)] -pub mod encryption_wrapper; +mod encryption_wrapper; mod variables; mod variables_parse; -#[doc(hidden)] -pub mod wrapper; +mod wrapper; pub use mariadb_macros::register_plugin; -pub use variables::PluginVarInfo; +pub use variables::{PluginVarInfo, SysVarString}; /// Commonly used plugin types for reexport pub mod prelude { pub use super::{register_plugin, Init, InitError, License, Maturity, PluginType}; } +/// Reexports for use in proc macros +#[doc(hidden)] +pub mod internals { + pub use super::encryption_wrapper::{WrapEncryption, WrapKeyMgr}; + pub use super::variables::{SimpleSysvarWrap, SysVarWrap}; + pub use super::wrapper::{new_null_st_maria_plugin, WrapInit}; +} + /// Defines possible licenses for plugins #[non_exhaustive] #[derive(Clone, Copy, Debug)] diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 0b680faecd640..febd1fe11fda1 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,13 +1,14 @@ //! "show variables" and "system variables" use std::cell::UnsafeCell; -use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; +use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void, CStr}; use std::mem::ManuallyDrop; use std::os::raw::{c_char, c_uint}; use std::ptr; use std::sync::atomic::{AtomicBool, AtomicI32}; use std::sync::Mutex; +use bindings::THD; use mariadb_sys as bindings; use super::variables_parse::{CliMysqlValue, CliValue}; @@ -35,7 +36,7 @@ pub union SysVarInfoU { } /// Internal trait -trait SysVar: Sync { +pub trait SysVarWrap: Sync { /// Type for interfacing with the main server // type CType: SysVarCType; @@ -72,7 +73,7 @@ trait SysVar: Sync { } /// Intermediate type is a &String -impl SysVar for Mutex { +impl SysVarWrap for Mutex { const C_FLAGS: i32 = (bindings::PLUGIN_VAR_STR) as i32; type CStType = bindings::sysvar_str_t; type Intermediate = Box>; @@ -112,98 +113,13 @@ impl SysVarInfoU { // } } -/// Create a system variable from an atomic -/// -/// Supported types are currently `AtomicBool`, `AtomicI32`, `AtomicI64`, and -/// their unsigned versions. -#[macro_export] -macro_rules! sysvar_atomic { - // Match the types manually - // (@var_type bool) => {bindings::PLUGIN_VAR_BOOL}; - (@var_type i32) => {bindings::PLUGIN_VAR_INT}; - (@var_type u32) => {bindings::PLUGIN_VAR_INT | bindings::PLUGIN_VAR_UNSIGNED}; - (@var_type i64) => {bindings::PLUGIN_VAR_LONGLONG}; - (@var_type u64) => {bindings::PLUGIN_VAR_LONGLONG | bindings::PLUGIN_VAR_UNSIGNED}; - (@def $default:expr, ) => {$default}; - (@def $default:expr, $replace:expr) => {$replace}; - - ( - ty: $ty:tt, - name: $name:expr, - var: $var:ident - $(, comment: $comment:expr)? - $(, flags: [$($flag:expr),+ $(,)?])? - $(, default: $default:expr)? - $(, minimum: $minimum:expr)? - $(, maximum: $maximum:expr)? - $(, multiplier: $multiplier:expr)? - $(,)? // trailing comma - ) => { - { - use ::std::ffi::{c_void, c_int, c_char}; - use ::std::mem::ManuallyDrop; - use ::std::ptr; - - use $crate::bindings; - use $crate::cstr; - - // Just make syntax cleaner - type IntTy = $ty; - - unsafe extern "C" fn check_val( - _thd: *mut bindings::THD, - self_: *mut bindings::st_mysql_sys_var, - save: *mut c_void, - _mysqld_values: *mut bindings::st_mysql_value - ) -> c_int { - // SAFETY: caller ensures save points to a valid location of the correct type - *save.cast() = $var.load(::std::sync::atomic::Ordering::Relaxed); - 0 - } - - unsafe extern "C" fn update_val( - _thd: *mut bindings::THD, - self_: *mut bindings::st_mysql_sys_var, - var_ptr: *mut c_void, - save: *const c_void - ) { - // SAFETY: `safe` points to a caller-validated value, `var_ptr` is properly sized - *var_ptr.cast() = $var.swap(*save.cast(), ::std::sync::atomic::Ordering::Relaxed); - } - - // Defaults - const FLAGS: i32 = sysvar_atomic!(@var_type $ty) as i32 $( $(| ($flag as i32))+ )?; - - const REF_STRUCT: bindings::st_mysql_sys_var_simple<$ty> = - bindings::st_mysql_sys_var_simple::<$ty> { - flags: FLAGS, - name: cstr::cstr!($name).as_ptr(), - comment: sysvar_atomic!( - @def - ptr::null(), - $(cstr::cstr!($comment).as_ptr())? - ), - check: Some(check_val), - update: Some(update_val), - value: ptr::null_mut(), - def_val: sysvar_atomic!(@def 0, $($default)?), - min_val: sysvar_atomic!(@def IntTy::MIN, $($minimum)?), - max_val: sysvar_atomic!(@def IntTy::MAX, $($maximum)?), - blk_sz: sysvar_atomic!(@def 1, $($multiplier)?), - }; - - unsafe { SysVarAtomic::new_simple(REF_STRUCT) } - } - - }; -} - /// Possible flags for plugin variables #[repr(i32)] #[non_exhaustive] +#[derive(Clone, Copy, PartialEq)] #[allow(clippy::cast_possible_wrap)] pub enum PluginVarInfo { - ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, + // ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, /// Variable is read only ReadOnly = bindings::PLUGIN_VAR_READONLY as i32, /// Variable is not a server variable @@ -222,37 +138,56 @@ pub enum PluginVarInfo { // MemAlloc= bindings::PLUGIN_VAR_MEMALLOC, } -#[cfg(test)] -mod tests { - use std::ffi::c_uint; - use std::mem::size_of; - use std::sync::atomic::AtomicU32; +impl PluginVarInfo { + pub const fn to_i32(self) -> i32 { + self as i32 + } +} - use super::*; +/// A string system variable +pub struct SysVarString(Mutex); - #[test] - fn test_sizes() { - assert_eq!(size_of::(), size_of::()) +impl SysVarString { + pub const fn new() -> Self { + Self(Mutex::new(String::new())) } - #[test] - fn test_macro() { - static X: AtomicU32 = AtomicU32::new(0); - let x = sysvar_atomic! { - ty: u32, - name: "sql_name", - var: X, - }; - // dbg!(x); - let x = sysvar_atomic! { - ty: u32, - name: "sql_name", - var: X, - comment: "this is a comment", - flags: [PluginVarInfo::ReadOnly], - maximum: 40, - multiplier: 2 - }; - // dbg!(x); + /// Get the current value of this variable. This isn't very efficient since + /// it copies the string, but fixes will come later + pub fn get(&self) -> String { + let guard = self.0.lock().expect("failed to lock mutex"); + (*guard).clone() + } +} + +pub trait SimpleSysvarWrap: Sized { + type CStType; + type Intermediate; + /// Store the result of the `check` function. + /// + /// - thd: thread pointer + /// - var: pointer to the c struct + /// - var_ptr: where to stash the value + /// - save: stash from the `check` function + unsafe extern "C" fn update_wrap( + thd: *mut THD, + var: *mut bindings::st_mysql_sys_var, + target: *mut c_void, + save: *const c_void, + ) { + Self::update(&mut *var.cast(), &mut *target.cast(), &*save.cast()) + } + + unsafe fn update(var: &mut Self::CStType, target: &mut Self, save: &Self::Intermediate) {} +} + +impl SimpleSysvarWrap for SysVarString { + type CStType = bindings::sysvar_str_t; + type Intermediate = *mut c_char; + + unsafe fn update(var: &mut Self::CStType, target: &mut Self, save: &Self::Intermediate) { + let cs = CStr::from_ptr(*save).to_string_lossy(); + let mut guard = (*target).0.lock().expect("failed to lock mutex"); + *guard = cs.to_string(); } } diff --git a/rust/mariadb/src/plugin/variables_parse.rs b/rust/mariadb/src/plugin/variables_parse.rs index 1cb71d62c667c..747fd8938b5e7 100644 --- a/rust/mariadb/src/plugin/variables_parse.rs +++ b/rust/mariadb/src/plugin/variables_parse.rs @@ -89,7 +89,7 @@ pub(crate) enum CliValue { String(Option), } -pub(crate) struct CliMysqlValue(UnsafeCell); +pub struct CliMysqlValue(UnsafeCell); impl CliMysqlValue { /// `item_val_str function`, `item_val_int`, `item_val_real` diff --git a/rust/mariadb/src/plugin/wrapper.rs b/rust/mariadb/src/plugin/wrapper.rs index 6daabe1a91a5b..f494b806cdfaa 100644 --- a/rust/mariadb/src/plugin/wrapper.rs +++ b/rust/mariadb/src/plugin/wrapper.rs @@ -1,40 +1,9 @@ -use std::cell::UnsafeCell; use std::ffi::{c_int, c_uint, c_void}; use std::ptr; use super::{Init, License, Maturity, PluginType}; use crate::bindings; -/// Used for plugin registrations, which are in global scope. -#[doc(hidden)] -#[derive(Debug)] -#[repr(transparent)] -pub struct UnsafeSyncCell(UnsafeCell); - -impl UnsafeSyncCell { - /// # Safety - /// - /// This inner value be used in a Sync/Send way - pub const unsafe fn new(value: T) -> Self { - Self(UnsafeCell::new(value)) - } - - pub const fn as_ptr(&self) -> *const T { - self.0.get() - } - - pub const fn get(&self) -> *mut T { - self.0.get() - } - - pub fn get_mut(&mut self) -> &mut T { - self.0.get_mut() - } -} - -unsafe impl Send for UnsafeSyncCell {} -unsafe impl Sync for UnsafeSyncCell {} - /// Trait for easily wrapping init/deinit functions pub trait WrapInit: Init { #[must_use] diff --git a/rust/mariadb/src/service_sql.rs b/rust/mariadb/src/service_sql.rs index c3548274aa77b..742380b7b25d0 100644 --- a/rust/mariadb/src/service_sql.rs +++ b/rust/mariadb/src/service_sql.rs @@ -18,7 +18,7 @@ pub use self::error::ClientError; use self::raw::{ClientResult, FetchedRow, RState, RawResult}; pub use self::raw::{Fetch, Store}; use crate::bindings; -use crate::plugin::wrapper::UnsafeSyncCell; +use crate::helpers::UnsafeSyncCell; /// A connection to a local or remote SQL server pub struct MySqlConn(RawConnection); From c99a2c67f3b7c93261fda03aec5d754e79b4f130 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 6 Feb 2023 18:55:02 -0500 Subject: [PATCH 27/34] Updates for clippy --- rust/macros/src/parse_vars.rs | 10 ++-- rust/macros/src/register_plugin.rs | 5 +- rust/mariadb/src/plugin/variables.rs | 66 ++++++---------------- rust/mariadb/src/plugin/variables_parse.rs | 2 +- 4 files changed, 26 insertions(+), 57 deletions(-) diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index d0db8a433e264..b651970e656e8 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -104,8 +104,10 @@ struct VariableInfo { impl Parse for VariableInfo { fn parse(input: ParseStream) -> syn::Result { let span = input.span(); - let mut ret = Self::default(); - ret.span = Some(span); + let mut ret = Self { + span: Some(span), + ..Default::default() + }; // parse struct-like syntax let st: ExprStruct = input.parse()?; @@ -196,7 +198,7 @@ impl VariableInfo { if vtype == &str_ty { Ok((st_ident.clone(), self.make_string_sysvar(&st_ident)?)) - } else if atomic_tys.contains(&vtype) { + } else if atomic_tys.contains(vtype) { Ok((st_ident.clone(), self.make_atomic_sysvar(&st_ident)?)) } else { Err(Error::new_spanned( @@ -246,7 +248,7 @@ impl VariableInfo { }; - Ok(res.into()) + Ok(res) } fn make_atomic_sysvar(&self, st_ident: &Ident) -> syn::Result { diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index c008254e5f1b1..ea6853589681a 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -165,7 +165,7 @@ impl PluginInfo { }; let vars: Variables = syn::parse(vars_decl.to_token_stream().into())?; let name = expect_litstr(&self.name)?.value(); - let sysvar_arr_ident = Ident::new(&format!("_plugin_{}_sysvars", name), Span::call_site()); + let sysvar_arr_ident = Ident::new(&format!("_plugin_{name}_sysvars"), Span::call_site()); let sysvar_bodies = &vars.sys; let sysvar_idents = &vars.sys_idents; @@ -392,8 +392,7 @@ impl PluginDef { #usynccell::new(#plugin_st), #usynccell::new(#null_ps), ] }; - } - .into(); + }; ret } } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index febd1fe11fda1..4bf5844e65759 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -48,10 +48,10 @@ pub trait SysVarWrap: Sync { /// For the check function, validate the arguments in `value` and write them to `dest`. /// - /// - thd: thread pointer - /// - var: pointer to the c struct - /// - save: a temporary place to stash anything until it gets to update - /// - value: cli value + /// - `thd`: thread pointer + /// - `var`: pointer to the c struct + /// - `save`: a temporary place to stash anything until it gets to update + /// - `value`: cli value unsafe fn check( thd: *const c_void, var: &mut Self::CStType, @@ -60,10 +60,10 @@ pub trait SysVarWrap: Sync { ) -> c_int; /// Store the result of the `check` function. /// - /// - thd: thread pointer - /// - var: pointer to the c struct - /// - var_ptr: where to stash the value - /// - save: stash from the `check` function + /// - `thd`: thread pointer + /// - `var`: pointer to the c struct + /// - `var_ptr`: where to stash the value + /// - `save`: stash from the `check` function unsafe fn update( thd: *const c_void, var: &mut Self::CStType, @@ -72,38 +72,6 @@ pub trait SysVarWrap: Sync { ); } -/// Intermediate type is a &String -impl SysVarWrap for Mutex { - const C_FLAGS: i32 = (bindings::PLUGIN_VAR_STR) as i32; - type CStType = bindings::sysvar_str_t; - type Intermediate = Box>; - unsafe fn check( - thd: *const c_void, - var: &mut Self::CStType, - save: &mut Self::Intermediate, - value: &CliMysqlValue, - ) -> c_int { - crate::log::warn!("entering sysvar mutex string check"); - let CliValue::String(Some(s)) = value.value() else { - panic!("got wrong string value {:?}", value.value()); - }; - dbg!(&s); - *save = Box::new(Some(s)); - 0 - } - unsafe fn update( - thd: *const c_void, - var: &mut Self::CStType, - var_ptr: &mut Self, - save: &mut Self::Intermediate, - ) { - crate::log::warn!("entering sysvar mutex string update"); - let s = save.take().unwrap(); - dbg!(&s); - *var_ptr.lock().expect("failed to lock mutex") = s; - } -} - impl SysVarInfoU { // /// Determine the version based on flags // fn determine_version(&self) -> &SysVarInfo { @@ -116,7 +84,7 @@ impl SysVarInfoU { /// Possible flags for plugin variables #[repr(i32)] #[non_exhaustive] -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq)] #[allow(clippy::cast_possible_wrap)] pub enum PluginVarInfo { // ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, @@ -165,29 +133,29 @@ pub trait SimpleSysvarWrap: Sized { type Intermediate; /// Store the result of the `check` function. /// - /// - thd: thread pointer - /// - var: pointer to the c struct - /// - var_ptr: where to stash the value - /// - save: stash from the `check` function + /// - `thd`: thread pointer + /// - `var`: pointer to the c struct + /// - `var_ptr`: where to stash the value + /// - `save`: stash from the `check` function unsafe extern "C" fn update_wrap( thd: *mut THD, var: *mut bindings::st_mysql_sys_var, target: *mut c_void, save: *const c_void, ) { - Self::update(&mut *var.cast(), &mut *target.cast(), &*save.cast()) + Self::update(&*var.cast(), &*target.cast(), &*save.cast()); } - unsafe fn update(var: &mut Self::CStType, target: &mut Self, save: &Self::Intermediate) {} + unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) {} } impl SimpleSysvarWrap for SysVarString { type CStType = bindings::sysvar_str_t; type Intermediate = *mut c_char; - unsafe fn update(var: &mut Self::CStType, target: &mut Self, save: &Self::Intermediate) { + unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) { let cs = CStr::from_ptr(*save).to_string_lossy(); - let mut guard = (*target).0.lock().expect("failed to lock mutex"); + let mut guard = target.0.lock().expect("failed to lock mutex"); *guard = cs.to_string(); } } diff --git a/rust/mariadb/src/plugin/variables_parse.rs b/rust/mariadb/src/plugin/variables_parse.rs index 747fd8938b5e7..a8293ce3a63cc 100644 --- a/rust/mariadb/src/plugin/variables_parse.rs +++ b/rust/mariadb/src/plugin/variables_parse.rs @@ -83,7 +83,7 @@ pub unsafe fn check_func_atomic_bool( // } #[derive(Debug, PartialEq)] -pub(crate) enum CliValue { +pub enum CliValue { Int(Option), Real(Option), String(Option), From fceadaffbd9bd99c60388abd1a1f0b2674c1a118 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 7 Feb 2023 03:00:40 -0500 Subject: [PATCH 28/34] Update for sysvar output in proc macros Read-only string variable successfully working --- rust/Dockerfile | 14 ++++-- .../src/{hand_imples.rs => hand_impls.rs} | 10 ++++ rust/bindings/src/lib.rs | 4 +- rust/examples/keymgt-basic/src/lib.rs | 2 +- rust/examples/keymgt-debug/src/lib.rs | 23 ++++++--- rust/macros/src/lib.rs | 1 + rust/macros/src/parse_vars.rs | 45 ++++++++++++++--- rust/macros/src/register_plugin.rs | 4 +- rust/macros/tests/include.rs | 2 +- rust/macros/tests/pass/03-with-sysargs.rs | 17 ++++++- rust/mariadb/src/lib.rs | 27 +++++++---- rust/mariadb/src/plugin.rs | 4 +- rust/mariadb/src/plugin/variables.rs | 48 ++++++++++--------- rust/plugins/keymgt-clevis/src/lib.rs | 20 ++++++-- 14 files changed, 158 insertions(+), 63 deletions(-) rename rust/bindings/src/{hand_imples.rs => hand_impls.rs} (91%) diff --git a/rust/Dockerfile b/rust/Dockerfile index 7513c7b06c4f0..206d657aa251a 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -5,13 +5,13 @@ # # Build the image. Change the directory (../) if not building in `rust/` # docker build -f Dockerfile ../ --tag mdb-plugin-ex # -# # Run the container +# # Run the container, select default plugins as desired # docker run --rm -e MARIADB_ROOT_PASSWORD=example --name mdb-plugin-ex-c \ # mdb-plugin-ex \ # --plugin-maturity=experimental \ # --plugin-load=libbasic \ # --plugin-load=libencryption -# --plugin-load=libdebug_key_management +# --plugin-load=libkeymgt_debug # # # Enter a SQL console # docker exec -it mdb-plugin-ex-c mysql -pexample @@ -19,12 +19,16 @@ # # Install desired plugins # INSTALL PLUGIN basic_key_management SONAME 'libbasic.so'; # INSTALL PLUGIN encryption_example SONAME 'libencryption.so'; +# INSTALL PLUGIN debug_key_management SONAME 'libkeymgt_debug.so'; +# +# # Stop server +# docker stop mdb-plugin-ex-c # ``` -# This build is space inefficient, but should rerun somewhat quick - -FROM rust:1.66 AS build +# use nighlty image for faster builds +FROM rustlang/rust:nightly AS build +ENV CARGO_UNSTABLE_SPARSE_REGISTRY=true WORKDIR /build RUN apt-get update \ diff --git a/rust/bindings/src/hand_imples.rs b/rust/bindings/src/hand_impls.rs similarity index 91% rename from rust/bindings/src/hand_imples.rs rename to rust/bindings/src/hand_impls.rs index 9540b2e7f897f..c70f6734d6f53 100644 --- a/rust/bindings/src/hand_imples.rs +++ b/rust/bindings/src/hand_impls.rs @@ -5,6 +5,16 @@ use super::{mysql_var_check_func, mysql_var_update_func, TYPELIB}; // Defined in service_encryption.h but not imported because of tilde syntax pub const ENCRYPTION_KEY_VERSION_INVALID: c_uint = !0; +#[allow(dead_code)] // not sure why this lint hits +pub const PLUGIN_VAR_MASK: u32 = super::PLUGIN_VAR_READONLY + | super::PLUGIN_VAR_NOSYSVAR + | super::PLUGIN_VAR_NOCMDOPT + | super::PLUGIN_VAR_NOCMDARG + | super::PLUGIN_VAR_OPCMDARG + | super::PLUGIN_VAR_RQCMDARG + | super::PLUGIN_VAR_DEPRECATED + | super::PLUGIN_VAR_MEMALLOC; + // We hand write these stucts because the definition is tricky, not all fields are // always present diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs index fa233173b7550..7c909703d023f 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/bindings/src/lib.rs @@ -8,5 +8,5 @@ // Bindings are autogenerated at build time using build.rs include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -mod hand_imples; -pub use hand_imples::*; +mod hand_impls; +pub use hand_impls::*; diff --git a/rust/examples/keymgt-basic/src/lib.rs b/rust/examples/keymgt-basic/src/lib.rs index cc772da25fd2e..b04be8391edf5 100644 --- a/rust/examples/keymgt-basic/src/lib.rs +++ b/rust/examples/keymgt-basic/src/lib.rs @@ -13,7 +13,7 @@ use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; -use mariadb::plugin::{register_plugin, PluginType, PluginVarInfo}; +use mariadb::plugin::{register_plugin, PluginType, SysVarOpt}; struct BasicKeyMgt; diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index f79dc25c8692f..d2e4d5bf6dc13 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -12,21 +12,24 @@ use std::ffi::c_void; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Mutex; -use mariadb::log::debug; +use mariadb::log::{self, debug, trace}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, + register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, }; const KEY_LENGTH: usize = 4; static KEY_VERSION: AtomicU32 = AtomicU32::new(1); +static TEST_SYSVAR_STR: SysVarString = SysVarString::new(); struct DebugKeyMgmt; impl Init for DebugKeyMgmt { fn init() -> Result<(), InitError> { - eprintln!("init for DebugKeyMgmt"); + log::set_max_level(log::LevelFilter::Trace); + debug!("DebugKeyMgmt get_latest_key_version"); + trace!("current sysvar: {}", TEST_SYSVAR_STR.get()); Ok(()) } @@ -76,8 +79,6 @@ impl KeyManager for DebugKeyMgmt { } } -static TEST_SYSVAR_STR: Mutex = Mutex::new(String::new()); - register_plugin! { DebugKeyMgmt, ptype: PluginType::MariaEncryption, @@ -86,7 +87,17 @@ register_plugin! { description: "Debug key management plugin", license: License::Gpl, maturity: Maturity::Experimental, - version: "0.1", + version: "0.2", init: DebugKeyMgmt, // optional encryption: false, + variables: [ + SysVar { + ident: TEST_SYSVAR_STR, + vtype: SysVarString, + name: "test_sysvar", + description: "this is a description", + options: [SysVarOpt::OptCmdArd], + default: "default value" + } + ] } diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index 0ce1d6010d4e7..93111f5bf78c4 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::missing_const_for_fn)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::must_use_candidate)] +#![allow(clippy::option_if_let_else)] #![allow(clippy::cast_possible_truncation)] mod fields; diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index b651970e656e8..2fa05c708cf61 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -32,7 +32,7 @@ // use proc_macro::TokenStream; use proc_macro2::{Literal, Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; -use syn::parse::{Parse, ParseStream}; +use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Group; use syn::{ @@ -208,10 +208,27 @@ impl VariableInfo { } } + fn make_option_fields(&self) -> syn::Result { + let Some(input) = &self.options else { + return Ok(TokenStream::new()); + }; + let flags: OptFields = syn::parse(input.to_token_stream().into())?; + let opts = flags.flags; + if opts.is_empty() { + return Ok(TokenStream::new()); + } + let ret = quote! { + | (( #( #opts .as_plugin_var_info() )|* ) & + ::mariadb::bindings::PLUGIN_VAR_MASK as i32) + }; + Ok(ret) + } + fn make_string_sysvar(&self, st_ident: &Ident) -> syn::Result { self.validate_correct_fields(STR_REQ_FIELDS, STR_OPT_FIELDS); - let flags = quote! { ::mariadb::bindings::PLUGIN_VAR_STR as i32 }; + let opts = self.make_option_fields()?; + let flags = quote! { (::mariadb::bindings::PLUGIN_VAR_STR as i32) #opts }; let name = expect_litstr(&self.name)?; let description = expect_litstr(&self.description)?; let default = if let Some(def) = &self.default { @@ -221,10 +238,11 @@ impl VariableInfo { }; let disambig_name = quote! { ::std::sync::Mutex }; let check_fn = quote! { None }; - let update_fn = quote! { - Some(<::mariadb::plugin::SysVarString as - ::mariadb::plugin::internals::SimpleSysvarWrap>::update_wrap) - }; + let update_fn = quote! { None }; + // let update_fn = quote! { + // Some(<::mariadb::plugin::SysVarString as + // ::mariadb::plugin::internals::SysvarWrap>::update_wrap) + // }; // let check_fn = quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::check) }; // let update_fn = // quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::update) }; @@ -300,6 +318,21 @@ impl VariableInfo { } } +#[derive(Clone, Debug)] +struct OptFields { + flags: Vec, +} + +impl Parse for OptFields { + fn parse(input: ParseStream) -> syn::Result { + let content; + let _ = bracketed!(content in input); + let flags = Punctuated::::parse_terminated(&content)?; + let v = Vec::from_iter(flags); + Ok(Self { flags: v }) + } +} + fn verify_field_order(fields: &[String]) -> Result<(), String> { let mut expected_order = ALL_FIELDS.to_vec(); diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index ea6853589681a..d9eec5dd52136 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -179,9 +179,9 @@ impl PluginInfo { let usynccell = quote! { ::mariadb::internals::UnsafeSyncCell }; ret.sysvar_body = quote! { - #(#sysvar_bodies)* + #( #sysvar_bodies )* - static #sysvar_arr_ident: + pub static #sysvar_arr_ident: [#usynccell<*mut ::mariadb::bindings::sysvar_common_t>; #len] = unsafe { [ diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs index 4f1d9f42120fb..0ec75b6765714 100644 --- a/rust/macros/tests/include.rs +++ b/rust/macros/tests/include.rs @@ -4,7 +4,7 @@ use std::sync::atomic::AtomicI32; use mariadb::plugin::encryption::*; use mariadb::plugin::prelude::*; -use mariadb::plugin::SysVarString; +use mariadb::plugin::*; pub use mariadb_macros::register_plugin; static _SYSVAR_ATOMIC: AtomicI32 = AtomicI32::new(0); diff --git a/rust/macros/tests/pass/03-with-sysargs.rs b/rust/macros/tests/pass/03-with-sysargs.rs index 18e59c9c06885..3ca1b3574a2d1 100644 --- a/rust/macros/tests/pass/03-with-sysargs.rs +++ b/rust/macros/tests/pass/03-with-sysargs.rs @@ -24,9 +24,10 @@ register_plugin! { } fn main() { - // use std::ffi::CStr; + use std::ffi::CStr; use std::ptr; + use mariadb::bindings; use mariadb::bindings::{st_maria_plugin, st_mysql_sys_var, sysvar_common_t, sysvar_str_t}; use mariadb::internals::UnsafeSyncCell; @@ -43,4 +44,18 @@ fn main() { assert_eq!(idx_0, sysvar_st.cast_mut().cast()); assert_eq!(idx_1, ptr::null_mut()); assert_eq!(sysv_ptr, sysvar_arr.as_ptr().cast_mut().cast()); + + // try the C way, slow casting steps to avoid errors here + let sv1_ptr: *mut st_mysql_sys_var = unsafe { *plugin_def.system_vars.add(0) }; + let sv1: &sysvar_common_t = unsafe { &*sv1_ptr.cast() }; + let flags = sv1.flags; + let sv1_name = unsafe { CStr::from_ptr(sv1.name).to_str().unwrap() }; + let sv1_comment = unsafe { CStr::from_ptr(sv1.comment).to_str().unwrap() }; + + let expected_flags = bindings::PLUGIN_VAR_STR + | ((bindings::PLUGIN_VAR_READONLY | bindings::PLUGIN_VAR_NOCMDOPT) + & bindings::PLUGIN_VAR_MASK); + assert_eq!(flags, expected_flags as i32); + assert_eq!(sv1_name, "test_sysvar"); + assert_eq!(sv1_comment, "this is a description"); } diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 8930e5d343a33..8c7d4e4e63c82 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -32,16 +32,16 @@ pub mod internals { pub use super::helpers::UnsafeSyncCell; } +/// Our main logger config +/// +/// Writes a timestamp, log level, and message. For debug & trace, also log the +/// file name. #[doc(hidden)] -pub struct MariaLogger { - pkg: Option<&'static str>, -} +pub struct MariaLogger; impl MariaLogger { pub const fn new() -> Self { - Self { - pkg: option_env!("CARGO_PKG_NAME"), - } + Self } } @@ -61,12 +61,19 @@ impl log::Log for MariaLogger { "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]", ) .unwrap(); + + // Format our string let mut out_str = t.format(&fmt).unwrap(); - write!(out_str, " [{}]", record.level()).unwrap(); - if let Some(pkg) = self.pkg { - write!(out_str, " {pkg}"); + write!(out_str, " [{}", record.level()).unwrap(); + + if record.level() == log::Level::Debug || record.level() == log::Level::Trace { + write!(out_str, " {}", record.file().unwrap_or("")).unwrap(); + if let Some(line) = record.line() { + write!(out_str, ":{line}").unwrap(); + } } - eprintln!("{out_str}:"); + + eprintln!("{out_str}]: {}", record.args()); } fn flush(&self) {} diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 95cdaa0d49cd2..6b49959a14b33 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -72,7 +72,7 @@ mod variables; mod variables_parse; mod wrapper; pub use mariadb_macros::register_plugin; -pub use variables::{PluginVarInfo, SysVarString}; +pub use variables::{SysVarOpt, SysVarString}; /// Commonly used plugin types for reexport pub mod prelude { @@ -83,7 +83,7 @@ pub mod prelude { #[doc(hidden)] pub mod internals { pub use super::encryption_wrapper::{WrapEncryption, WrapKeyMgr}; - pub use super::variables::{SimpleSysvarWrap, SysVarWrap}; + pub use super::variables::{SysVarWrap, SysvarWrap}; pub use super::wrapper::{new_null_st_maria_plugin, WrapInit}; } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 4bf5844e65759..8bf5887aed02e 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -2,13 +2,15 @@ use std::cell::UnsafeCell; use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void, CStr}; +use std::marker::PhantomPinned; use std::mem::ManuallyDrop; use std::os::raw::{c_char, c_uint}; use std::ptr; -use std::sync::atomic::{AtomicBool, AtomicI32}; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, Ordering}; use std::sync::Mutex; use bindings::THD; +use log::trace; use mariadb_sys as bindings; use super::variables_parse::{CliMysqlValue, CliValue}; @@ -72,21 +74,12 @@ pub trait SysVarWrap: Sync { ); } -impl SysVarInfoU { - // /// Determine the version based on flags - // fn determine_version(&self) -> &SysVarInfo { - // // SAFETY: the flags field is always in the same position - // let flags = unsafe { self.basic.get().flags }; - - // } -} - /// Possible flags for plugin variables #[repr(i32)] #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq)] #[allow(clippy::cast_possible_wrap)] -pub enum PluginVarInfo { +pub enum SysVarOpt { // ThdLocal = bindings::PLUGIN_VAR_THDLOCAL as i32, /// Variable is read only ReadOnly = bindings::PLUGIN_VAR_READONLY as i32, @@ -106,29 +99,41 @@ pub enum PluginVarInfo { // MemAlloc= bindings::PLUGIN_VAR_MEMALLOC, } -impl PluginVarInfo { - pub const fn to_i32(self) -> i32 { +impl SysVarOpt { + pub const fn as_plugin_var_info(self) -> i32 { self as i32 } } /// A string system variable -pub struct SysVarString(Mutex); +/// +/// 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 SysVarString(AtomicPtr); + +// unsafe impl Sync for SysVarString {} impl SysVarString { pub const fn new() -> Self { - Self(Mutex::new(String::new())) + 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 pub fn get(&self) -> String { - let guard = self.0.lock().expect("failed to lock mutex"); - (*guard).clone() + 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())) + .to_owned() } } -pub trait SimpleSysvarWrap: Sized { +pub trait SysvarWrap: Sized { type CStType; type Intermediate; /// Store the result of the `check` function. @@ -149,13 +154,12 @@ pub trait SimpleSysvarWrap: Sized { unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) {} } -impl SimpleSysvarWrap for SysVarString { +impl SysvarWrap for SysVarString { type CStType = bindings::sysvar_str_t; type Intermediate = *mut c_char; unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) { - let cs = CStr::from_ptr(*save).to_string_lossy(); - let mut guard = target.0.lock().expect("failed to lock mutex"); - *guard = cs.to_string(); + target.0.store(*save, Ordering::SeqCst); + trace!("updated system variable to '{}'", target.get()); } } diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 8fc03432bb70f..3e3796eff58ce 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -12,7 +12,7 @@ use mariadb::log::{debug, error, info}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::prelude::*; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, PluginVarInfo, + register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, }; use mariadb::service_sql::{ClientError, Fetch, FetchedRows, MySqlConn}; @@ -21,14 +21,14 @@ const SERVER_TABLE: &str = "mysql.clevis_servers"; /// Max length a key can be, used for table size and buffer checking const KEY_MAX_BYTES: usize = 16; -static TANG_SERVER: Mutex = Mutex::new(String::new()); +static TANG_SERVER: SysVarString = SysVarString::new(); struct KeyMgtClevis; /// Get the JWS body from a server fn fetch_jws() -> String { // FIXME: error handling - let url = format!("https://{}", TANG_SERVER.lock().unwrap()); + let url = format!("https://{}", TANG_SERVER.get()); let body: String = ureq::get("http://example.com") .call() .unwrap_or_else(|_| panic!("http request for '{url}' failed")) @@ -39,7 +39,7 @@ fn fetch_jws() -> String { } fn make_new_key(conn: &MySqlConn) -> Result { - let server = TANG_SERVER.lock().unwrap(); + let server = TANG_SERVER.get(); format!( "INSERT IGNORE INTO {KEY_TABLE} SET key_server = {server} @@ -134,7 +134,7 @@ impl KeyManager for KeyMgtClevis { let q = format!( r#"INSERT INTO {KEY_TABLE} VALUES ( {key_id}, {key_version}, "{server_name}", {new_key} )"#, - server_name = TANG_SERVER.lock().unwrap() + server_name = TANG_SERVER.get() ); run_execute(&mut conn, &q, key_id)?; @@ -169,4 +169,14 @@ register_plugin! { version: "0.1", init: KeyMgtClevis, encryption: false, + variables: [ + SysVar { + ident: TANG_SERVER, + vtype: SysVarString, + name: "tang_server", + description: "the tang server for key exchange", + options: [SysVarOpt::OptCmdArd], + default: "localhost" + } + ] } From 51132db8a8083305ca31db3eaf501026698b2982 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 7 Feb 2023 15:27:47 -0500 Subject: [PATCH 29/34] Updates to workflows and docs --- .github/workflows/validation-rust.yaml | 69 ++++++++--------------- README.md | 10 ++++ rust/examples/encryption/src/lib.rs | 2 +- rust/examples/keymgt-basic/src/lib.rs | 3 +- rust/examples/keymgt-debug/src/lib.rs | 3 +- rust/macros/src/parse_vars.rs | 4 +- rust/macros/tests/include.rs | 1 - rust/macros/tests/pass/03-with-sysargs.rs | 2 +- rust/mariadb/src/plugin.rs | 6 +- rust/mariadb/src/plugin/variables.rs | 2 +- rust/plugins/keymgt-clevis/Cargo.toml | 1 + rust/plugins/keymgt-clevis/src/lib.rs | 3 +- 12 files changed, 45 insertions(+), 61 deletions(-) diff --git a/.github/workflows/validation-rust.yaml b/.github/workflows/validation-rust.yaml index be645d04366c3..57f0cc7bce684 100644 --- a/.github/workflows/validation-rust.yaml +++ b/.github/workflows/validation-rust.yaml @@ -35,15 +35,8 @@ jobs: CTestTestfile.cmake key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: sudo apt-get update && sudo apt-get install cmake - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: check - args: --manifest-path rust/Cargo.toml + - uses: dtolnay/rust-toolchain@stable + - run: cargo check --manifest-path rust/Cargo.toml test: strategy: @@ -78,17 +71,11 @@ jobs: CTestTestfile.cmake key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: sudo apt-get update && sudo apt-get install cmake - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --manifest-path rust/Cargo.toml env: RUST_BACKTRACE: "1" - with: - command: test - args: --manifest-path rust/Cargo.toml + fmt: name: "Format (cargo fmt)" @@ -110,16 +97,10 @@ jobs: CPackSourceConfig.cmake CTestTestfile.cmake key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all --manifest-path rust/Cargo.toml -- --check + components: rustfmt + - run: cargo fmt --manifest-path rust/Cargo.toml --all -- --check clippy: name: "Clippy (cargo clippy)" @@ -142,17 +123,10 @@ jobs: CTestTestfile.cmake key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: sudo apt-get update && sudo apt-get install cmake - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - # Some nice checks are only nightly - toolchain: nightly - override: true - - run: rustup component add clippy - - uses: actions-rs/cargo@v1 + - uses: dtolnay/rust-toolchain@nightly with: - command: clippy - args: --manifest-path rust/Cargo.toml -- -D warnings + components: clippy + - run: cargo clippy --manifest-path rust/Cargo.toml -- -D warnings doc: name: "Docs (cargo doc) & Pub" @@ -175,19 +149,20 @@ jobs: CTestTestfile.cmake key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: sudo apt-get update && sudo apt-get install cmake - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: doc - args: --manifest-path rust/Cargo.toml --no-deps + - uses: dtolnay/rust-toolchain@stable + # test docs for everything + - name: Test build all docs + run: cargo doc --manifest-path rust/Cargo.toml --no-deps + # create docs for the crate we care about + - name: Build docs for publish + run: | + rm -rf target/doc/ rust/target/doc/ + cargo doc --manifest-path rust/mariadb/Cargo.toml --no-deps + cargo doc --manifest-path rust/bindings/Cargo.toml --no-deps - run: | echo `pwd`/rust/target/doc >> $GITHUB_PATH # fake index.html so github likes us - echo "" > rust/target/doc/index.html + echo "" > rust/target/doc/index.html - name: Deploy GitHub Pages run: | git worktree add gh-pages diff --git a/README.md b/README.md index 58dbf105fb90a..e09f97ad69769 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# MariaDB: Rust Support Fork + +This is a fork of [MariaDB server](https://github.com/MariaDB/server) with the +goal of experimenting with a Rust plugin API. It is a work in progress, +everything here should be considered unstable. + +View docs for the Rust API here: +. + + Code status: ------------ diff --git a/rust/examples/encryption/src/lib.rs b/rust/examples/encryption/src/lib.rs index 699140c73b061..763f0278d22c3 100644 --- a/rust/examples/encryption/src/lib.rs +++ b/rust/examples/encryption/src/lib.rs @@ -13,7 +13,7 @@ use aes_gcm::{ Nonce, // Or `Aes128Gcm` }; use mariadb::plugin::encryption::{Encryption, EncryptionError, Flags, KeyError, KeyManager}; -use mariadb::plugin::prelude::*; +use mariadb::plugin::*; use rand::Rng; use sha2::{Digest, Sha256 as Hasher}; diff --git a/rust/examples/keymgt-basic/src/lib.rs b/rust/examples/keymgt-basic/src/lib.rs index b04be8391edf5..0a1d6968a344b 100644 --- a/rust/examples/keymgt-basic/src/lib.rs +++ b/rust/examples/keymgt-basic/src/lib.rs @@ -12,8 +12,7 @@ use std::ffi::c_void; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb::plugin::prelude::*; -use mariadb::plugin::{register_plugin, PluginType, SysVarOpt}; +use mariadb::plugin::{register_plugin, PluginType, SysVarOpt, *}; struct BasicKeyMgt; diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index d2e4d5bf6dc13..2627e5ceb127c 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -14,9 +14,8 @@ use std::sync::Mutex; use mariadb::log::{self, debug, trace}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb::plugin::prelude::*; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, + register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, *, }; const KEY_LENGTH: usize = 4; diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index 2fa05c708cf61..b378f3a4888fa 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -6,7 +6,7 @@ //! vtype: String, //! name: "sql_name", //! description: "this is a description", -//! options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], +//! options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], //! default: "something" //! } //! ``` @@ -19,7 +19,7 @@ //! vtype: AtomicI32, //! name: "other_sql_name", //! description: "this is a description", -//! options: [PluginVarInfo::ReqCmdArg], +//! options: [SysVarOpt::ReqCmdArg], //! default: 100, //! min: 10, //! max: 500, diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs index 0ec75b6765714..f56de153b4588 100644 --- a/rust/macros/tests/include.rs +++ b/rust/macros/tests/include.rs @@ -3,7 +3,6 @@ use std::sync::atomic::AtomicI32; use mariadb::plugin::encryption::*; -use mariadb::plugin::prelude::*; use mariadb::plugin::*; pub use mariadb_macros::register_plugin; diff --git a/rust/macros/tests/pass/03-with-sysargs.rs b/rust/macros/tests/pass/03-with-sysargs.rs index 3ca1b3574a2d1..bfdae1e1a60c6 100644 --- a/rust/macros/tests/pass/03-with-sysargs.rs +++ b/rust/macros/tests/pass/03-with-sysargs.rs @@ -17,7 +17,7 @@ register_plugin! { vtype: SysVarString, name: "test_sysvar", description: "this is a description", - options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], + options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], default: "something" } ] diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 6b49959a14b33..a2f0e2e5f9857 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -3,7 +3,7 @@ //! Usage: //! //! ``` -//! use mariadb::plugin::prelude::*; +//! use mariadb::plugin::*; //! use mariadb::plugin::encryption::*; //! use mariadb::plugin::SysVarString; //! @@ -44,7 +44,7 @@ //! vtype: SysVarString, //! name: "sql_name", //! description: "this is a description", -//! options: [PluginVarInfo::ReadOnly, PluginVarInfo::NoCmdOpt], +//! options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], //! default: "something" //! }, //! // SysVar { @@ -52,7 +52,7 @@ //! // vtype: AtomicI32, //! // name: "other_sql_name", //! // description: "this is a description", -//! // options: [PluginVarInfo::ReqCmdArg], +//! // options: [SysVarOpt::ReqCmdArg], //! // default: 100, //! // min: 10, //! // max: 500, diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 8bf5887aed02e..8a2a99bb8a1b1 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -95,7 +95,7 @@ pub enum SysVarOpt { OptCmdArd = bindings::PLUGIN_VAR_OPCMDARG as i32, /// Variable is deprecated Deprecated = bindings::PLUGIN_VAR_DEPRECATED as i32, - // String needs memory allocation + // String needs memory allocation - don't expose this // MemAlloc= bindings::PLUGIN_VAR_MEMALLOC, } diff --git a/rust/plugins/keymgt-clevis/Cargo.toml b/rust/plugins/keymgt-clevis/Cargo.toml index 1b4006778873e..a9384cd5c4227 100644 --- a/rust/plugins/keymgt-clevis/Cargo.toml +++ b/rust/plugins/keymgt-clevis/Cargo.toml @@ -7,5 +7,6 @@ edition = '2021' crate-type = ["cdylib"] [dependencies] +josekit = "0.8.2" mariadb = { path = "../../mariadb" } ureq = "2.6.2" diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 3e3796eff58ce..3a63ecdf83134 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -8,9 +8,9 @@ use std::fmt::Write; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Mutex; +use josekit::jws; use mariadb::log::{debug, error, info}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; -use mariadb::plugin::prelude::*; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, }; @@ -21,6 +21,7 @@ const SERVER_TABLE: &str = "mysql.clevis_servers"; /// Max length a key can be, used for table size and buffer checking const KEY_MAX_BYTES: usize = 16; +/// String system variable to set server address static TANG_SERVER: SysVarString = SysVarString::new(); struct KeyMgtClevis; From dab9219b0f82ba894e50a7446c8d12f5f4d40940 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 8 Feb 2023 04:04:04 -0500 Subject: [PATCH 30/34] Start plugin updates --- rust/examples/keymgt-debug/src/lib.rs | 4 +- rust/macros/src/parse_vars.rs | 3 +- rust/macros/tests/include.rs | 2 +- rust/mariadb/src/plugin.rs | 13 ++-- rust/mariadb/src/plugin/variables.rs | 91 ++++++++------------------- rust/plugins/keymgt-clevis/src/lib.rs | 4 +- 6 files changed, 37 insertions(+), 80 deletions(-) diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index 2627e5ceb127c..e8d94fbf10349 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -15,12 +15,12 @@ use std::sync::Mutex; use mariadb::log::{self, debug, trace}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, *, + register_plugin, Init, InitError, License, Maturity, PluginType, SysVarConstString, SysVarOpt, }; const KEY_LENGTH: usize = 4; static KEY_VERSION: AtomicU32 = AtomicU32::new(1); -static TEST_SYSVAR_STR: SysVarString = SysVarString::new(); +static TEST_SYSVAR_STR: SysVarConstString = SysVarConstString::new(); struct DebugKeyMgmt; diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index b378f3a4888fa..380b10a227e21 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -184,7 +184,6 @@ impl VariableInfo { parse_quote!(AtomicI32), parse_quote!(AtomicI64), parse_quote!(AtomicIsize), - parse_quote!(AtomicPtr), parse_quote!(AtomicU8), parse_quote!(AtomicU16), parse_quote!(AtomicU32), @@ -203,7 +202,7 @@ impl VariableInfo { } else { Err(Error::new_spanned( &self.vtype, - "invalid variable type. Only 'SysVarString' and 'AtomicX' currently allowed.", + "invalid variable type. Only 'SysVarString', 'SysVarConstString', and 'AtomicX' currently allowed.", )) } } diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs index f56de153b4588..0b6eeda01e76f 100644 --- a/rust/macros/tests/include.rs +++ b/rust/macros/tests/include.rs @@ -7,7 +7,7 @@ use mariadb::plugin::*; pub use mariadb_macros::register_plugin; static _SYSVAR_ATOMIC: AtomicI32 = AtomicI32::new(0); -static _SYSVAR_STR: SysVarString = SysVarString::new(); +static _SYSVAR_STR: SysVarConstString = SysVarConstString::new(); struct TestPlugin; diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index a2f0e2e5f9857..540016c29554f 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -72,7 +72,7 @@ mod variables; mod variables_parse; mod wrapper; pub use mariadb_macros::register_plugin; -pub use variables::{SysVarOpt, SysVarString}; +pub use variables::{SysVarConstString, SysVarOpt}; /// Commonly used plugin types for reexport pub mod prelude { @@ -83,7 +83,7 @@ pub mod prelude { #[doc(hidden)] pub mod internals { pub use super::encryption_wrapper::{WrapEncryption, WrapKeyMgr}; - pub use super::variables::{SysVarWrap, SysvarWrap}; + pub use super::variables::SysVarWrap; pub use super::wrapper::{new_null_st_maria_plugin, WrapInit}; } @@ -105,19 +105,16 @@ impl License { } } -#[derive(Clone, Copy, Debug)] -pub struct NoMatchingLicenseError; - impl FromStr for License { - type Err = NoMatchingLicenseError; + type Err = String; /// Create a license type from a string - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "proprietary" => Ok(Self::Proprietary), "gpl" => Ok(Self::Gpl), "bsd" => Ok(Self::Bsd), - _ => Err(NoMatchingLicenseError), + _ => Err(format!("'{s}' has no matching license type")), } } } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 8a2a99bb8a1b1..506981ab1d488 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -15,65 +15,6 @@ use mariadb_sys as bindings; use super::variables_parse::{CliMysqlValue, CliValue}; -type SVInfoInner = ManuallyDrop>; - -/// 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)] -pub union SysVarInfoU { - bool_t: SVInfoInner, - str_t: SVInfoInner, - enum_t: SVInfoInner, - set_t: SVInfoInner, - int_t: SVInfoInner, - long_t: SVInfoInner, - longlong_t: SVInfoInner, - uint_t: SVInfoInner, - ulong_t: SVInfoInner, - ulonglong_t: SVInfoInner, - double_t: SVInfoInner, - // THD types have a function `resolve` that takes a thread pointer and an - // offset (also a field) -} - -/// Internal trait -pub trait SysVarWrap: Sync { - /// Type for interfacing with the main server - // type CType: SysVarCType; - - const C_FLAGS: i32; - /// C struct - type CStType; - /// Type shared between `check` and `update` - type Intermediate; - - /// For the check function, validate the arguments in `value` and write them to `dest`. - /// - /// - `thd`: thread pointer - /// - `var`: pointer to the c struct - /// - `save`: a temporary place to stash anything until it gets to update - /// - `value`: cli value - unsafe fn check( - thd: *const c_void, - var: &mut Self::CStType, - save: &mut Self::Intermediate, - value: &CliMysqlValue, - ) -> c_int; - /// Store the result of the `check` function. - /// - /// - `thd`: thread pointer - /// - `var`: pointer to the c struct - /// - `var_ptr`: where to stash the value - /// - `save`: stash from the `check` function - unsafe fn update( - thd: *const c_void, - var: &mut Self::CStType, - var_ptr: &mut Self, - save: &mut Self::Intermediate, - ); -} - /// Possible flags for plugin variables #[repr(i32)] #[non_exhaustive] @@ -99,6 +40,28 @@ pub enum SysVarOpt { // MemAlloc= bindings::PLUGIN_VAR_MEMALLOC, } +type SVInfoInner = ManuallyDrop>; + +/// 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)] +pub union SysVarInfoU { + bool_t: SVInfoInner, + str_t: SVInfoInner, + enum_t: SVInfoInner, + set_t: SVInfoInner, + int_t: SVInfoInner, + long_t: SVInfoInner, + longlong_t: SVInfoInner, + uint_t: SVInfoInner, + ulong_t: SVInfoInner, + ulonglong_t: SVInfoInner, + double_t: SVInfoInner, + // 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 @@ -113,11 +76,9 @@ impl SysVarOpt { /// 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 SysVarString(AtomicPtr); - -// unsafe impl Sync for SysVarString {} +pub struct SysVarConstString(AtomicPtr); -impl SysVarString { +impl SysVarConstString { pub const fn new() -> Self { Self(AtomicPtr::new(std::ptr::null_mut())) } @@ -133,7 +94,7 @@ impl SysVarString { } } -pub trait SysvarWrap: Sized { +pub trait SysVarWrap: Sized { type CStType; type Intermediate; /// Store the result of the `check` function. @@ -154,7 +115,7 @@ pub trait SysvarWrap: Sized { unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) {} } -impl SysvarWrap for SysVarString { +impl SysVarWrap for SysVarConstString { type CStType = bindings::sysvar_str_t; type Intermediate = *mut c_char; diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 3a63ecdf83134..72effb5d95db5 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -12,7 +12,7 @@ use josekit::jws; use mariadb::log::{debug, error, info}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::{ - register_plugin, Init, InitError, License, Maturity, PluginType, SysVarOpt, SysVarString, + register_plugin, Init, InitError, License, Maturity, PluginType, SysVarConstString, SysVarOpt, }; use mariadb::service_sql::{ClientError, Fetch, FetchedRows, MySqlConn}; @@ -22,7 +22,7 @@ const SERVER_TABLE: &str = "mysql.clevis_servers"; const KEY_MAX_BYTES: usize = 16; /// String system variable to set server address -static TANG_SERVER: SysVarString = SysVarString::new(); +static TANG_SERVER: SysVarConstString = SysVarConstString::new(); struct KeyMgtClevis; From 6041d6cb02017ece50e72208d28d8acfc66e6ff3 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 8 Feb 2023 05:32:34 -0500 Subject: [PATCH 31/34] Wrapped up generic interfaces --- rust/bindings/src/lib.rs | 3 + rust/macros/src/parse_vars.rs | 2 +- rust/mariadb/src/plugin.rs | 2 +- rust/mariadb/src/plugin/variables.rs | 132 +++++++++++++++++++++------ 4 files changed, 111 insertions(+), 28 deletions(-) diff --git a/rust/bindings/src/lib.rs b/rust/bindings/src/lib.rs index 7c909703d023f..402b4d3501ea8 100644 --- a/rust/bindings/src/lib.rs +++ b/rust/bindings/src/lib.rs @@ -1,3 +1,6 @@ +//! Bindings module +//! +//! Autogenerated bindings to C interfaces for Rust #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index 380b10a227e21..9edb9719da2e4 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -177,7 +177,7 @@ impl VariableInfo { return Err(Error::new_spanned(&self.vtype, "missing required field 'vtype'")); }; let str_ty: Expr = parse_quote!(SysVarString); - let atomic_tys: [Expr; 12] = [ + let atomic_tys: [Expr; 11] = [ parse_quote!(AtomicBool), parse_quote!(AtomicI8), parse_quote!(AtomicI16), diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 540016c29554f..79a297de8c8c6 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -83,7 +83,7 @@ pub mod prelude { #[doc(hidden)] pub mod internals { pub use super::encryption_wrapper::{WrapEncryption, WrapKeyMgr}; - pub use super::variables::SysVarWrap; + pub use super::variables::SysVarInterface; pub use super::wrapper::{new_null_st_maria_plugin, WrapInit}; } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 506981ab1d488..611b76b39049d 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -6,7 +6,7 @@ use std::marker::PhantomPinned; use std::mem::ManuallyDrop; use std::os::raw::{c_char, c_uint}; use std::ptr; -use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, Ordering}; +use std::sync::atomic::{self, AtomicBool, AtomicI32, AtomicPtr, AtomicU32, Ordering}; use std::sync::Mutex; use bindings::THD; @@ -68,6 +68,53 @@ impl SysVarOpt { } } +/// `bindings::mysql_var_update_func` without the `Option` +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 +pub trait SysVarInterface: Sized { + /// The C struct representation, e.g. `sysvar_str_t` + type CStType; + + /// Intermediate type, pointed to by the CStType's `value` pointer + type Intermediate; + + /// Associated const with an optional function pointer to an update + /// function. + /// + /// If a sysvar type should use a custom update function, implmeent `update` + /// and set this value to `update_wrap`. + const UPDATE_FUNC: Option = None; + + /// Options to implement by default + const DEFAULT_OPTS: i32; + + /// 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 + 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(); + Self::update(&*target.cast(), &*var.cast(), new_save.as_ref()); + } + + /// Update function: override this if it is pointed to by `UPDATE_FUNC` + unsafe fn update(&self, var: &Self::CStType, save: Option<&Self::Intermediate>) { + unimplemented!() + } +} + /// A string system variable /// /// Consider this very unstable because I don't 100% understand what the SQL @@ -94,33 +141,66 @@ impl SysVarConstString { } } -pub trait SysVarWrap: Sized { - type CStType; - type Intermediate; - /// Store the result of the `check` function. - /// - /// - `thd`: thread pointer - /// - `var`: pointer to the c struct - /// - `var_ptr`: where to stash the value - /// - `save`: stash from the `check` function - unsafe extern "C" fn update_wrap( - thd: *mut THD, - var: *mut bindings::st_mysql_sys_var, - target: *mut c_void, - save: *const c_void, - ) { - Self::update(&*var.cast(), &*target.cast(), &*save.cast()); - } +impl SysVarInterface for SysVarConstString { + type CStType = bindings::sysvar_str_t; + type Intermediate = *mut c_char; + const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32; + // const UPDATE_FUNC: Option = + // Some(Self::update_wrap as bindings::mysql_var_update_func); - unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) {} + // unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) { + // target.0.store(*save, Ordering::SeqCst); + // trace!("updated system variable to '{}'", target.get()); + // } } -impl SysVarWrap for SysVarConstString { - type CStType = bindings::sysvar_str_t; - type Intermediate = *mut c_char; +/// Macro to easily create implementations for all the atomics +macro_rules! atomic_svinterface { + ($atomic_ty:ty, $cs_ty:ty, $inter_ty:ty, $opts:expr) => { + impl SysVarInterface for $atomic_ty { + type CStType = $cs_ty; + type Intermediate = $inter_ty; + const DEFAULT_OPTS: i32 = ($opts) as i32; + const UPDATE_FUNC: Option = Some(Self::update_wrap as SvUpdateFn); - unsafe fn update(var: &Self::CStType, target: &Self, save: &Self::Intermediate) { - target.0.store(*save, Ordering::SeqCst); - trace!("updated system variable to '{}'", target.get()); - } + unsafe fn update(&self, var: &Self::CStType, save: Option<&Self::Intermediate>) { + // based on sql_plugin.cc, seems like there are no null integers + // (can't represent that anyway) + let new = save.expect("somehow got a null pointer"); + self.store(*new, Ordering::SeqCst); + trace!("updated system variable to '{}'", new); + } + } + }; } + +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 +); From d147215c3b4f281b94ccf3125848e5dd741a81fb Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 9 Feb 2023 00:56:48 -0500 Subject: [PATCH 32/34] Fix doctests --- rust/mariadb/src/plugin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 79a297de8c8c6..8abdf37f65d3e 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -5,9 +5,9 @@ //! ``` //! use mariadb::plugin::*; //! use mariadb::plugin::encryption::*; -//! use mariadb::plugin::SysVarString; +//! use mariadb::plugin::SysVarConstString; //! -//! static SYSVAR_STR: SysVarString = SysVarString::new(); +//! static SYSVAR_STR: SysVarConstString = SysVarConstString::new(); //! //! //! // May be empty or not From 5d22e960346b624136bc097fedaba8bd2f4d3dc4 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 9 Feb 2023 05:41:23 -0500 Subject: [PATCH 33/34] Proc macros working with full generic args --- rust/examples/keymgt-debug/src/lib.rs | 2 +- rust/macros/src/fields.rs | 10 +- rust/macros/src/helpers.rs | 12 +- rust/macros/src/parse_vars.rs | 151 +++++++-------- rust/macros/tests/fail/02-extra-sysargs.rs | 27 +++ .../macros/tests/fail/02-extra-sysargs.stderr | 7 + rust/macros/tests/pass/03-with-sysargs.rs | 8 +- rust/mariadb/src/plugin.rs | 2 +- rust/mariadb/src/plugin/variables.rs | 181 +++++++++++++++--- rust/plugins/keymgt-clevis/src/lib.rs | 2 +- 10 files changed, 284 insertions(+), 118 deletions(-) create mode 100644 rust/macros/tests/fail/02-extra-sysargs.rs create mode 100644 rust/macros/tests/fail/02-extra-sysargs.stderr diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index e8d94fbf10349..2405248cdc3d6 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -92,7 +92,7 @@ register_plugin! { variables: [ SysVar { ident: TEST_SYSVAR_STR, - vtype: SysVarString, + vtype: SysVarConstString, name: "test_sysvar", description: "this is a description", options: [SysVarOpt::OptCmdArd], diff --git a/rust/macros/src/fields.rs b/rust/macros/src/fields.rs index 7c9f38df1aeba..9a7a06d182683 100644 --- a/rust/macros/src/fields.rs +++ b/rust/macros/src/fields.rs @@ -45,9 +45,11 @@ pub mod sysvar { /// Always required pub const REQ_FIELDS: &[&str] = &["ident", "vtype", "name", "description"]; + pub const OPT_FIELDS: &[&str] = &["default", "min", "max", "interval"]; - pub const STR_REQ_FIELDS: &[&str] = &[]; - pub const STR_OPT_FIELDS: &[&str] = &["default"]; - pub const NUM_REQ_FIELDS: &[&str] = &[]; - pub const NUM_OPT_FIELDS: &[&str] = &["default", "min", "max", "interval"]; + // unused since we switched to full generics + pub const _STR_REQ_FIELDS: &[&str] = &[]; + pub const _STR_OPT_FIELDS: &[&str] = &["default"]; + pub const _NUM_REQ_FIELDS: &[&str] = &[]; + pub const _NUM_OPT_FIELDS: &[&str] = &["default", "min", "max", "interval"]; } diff --git a/rust/macros/src/helpers.rs b/rust/macros/src/helpers.rs index 0c8f9ac540b84..edb0ce75c7a8e 100644 --- a/rust/macros/src/helpers.rs +++ b/rust/macros/src/helpers.rs @@ -19,13 +19,13 @@ pub fn expect_bool(field_opt: &Option) -> syn::Result { pub fn expect_litstr(field_opt: &Option) -> syn::Result<&LitStr> { let field = field_opt.as_ref().unwrap(); let Expr::Lit(lit) = field else { // got non-literal - let msg = "expected literal expression for this field"; - return Err(Error::new_spanned(field, msg)); -}; + let msg = "expected literal expression for this field"; + return Err(Error::new_spanned(field, msg)); + }; let Lit::Str(litstr) = &lit.lit else { // got literal that wasn't a string - let msg = "only literal strings are allowed for this field"; - return Err(Error::new_spanned(field, msg)); -}; + let msg = "only literal strings are allowed for this field"; + return Err(Error::new_spanned(field, msg)); + }; Ok(litstr) } diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index 9edb9719da2e4..60016ff3284d9 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -34,6 +34,7 @@ use proc_macro2::{Literal, Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::token::Group; use syn::{ bracketed, parse_macro_input, parse_quote, Attribute, DeriveInput, Error, Expr, ExprArray, @@ -41,9 +42,7 @@ use syn::{ Path, PathSegment, Token, Type, TypePath, TypeReference, }; -use crate::fields::sysvar::{ - ALL_FIELDS, NUM_OPT_FIELDS, NUM_REQ_FIELDS, REQ_FIELDS, STR_OPT_FIELDS, STR_REQ_FIELDS, -}; +use crate::fields::sysvar::{ALL_FIELDS, OPT_FIELDS, REQ_FIELDS}; use crate::helpers::expect_litstr; #[derive(Clone, Copy, Debug)] @@ -176,37 +175,59 @@ impl VariableInfo { let Some(vtype) = &self.vtype else { return Err(Error::new_spanned(&self.vtype, "missing required field 'vtype'")); }; - let str_ty: Expr = parse_quote!(SysVarString); - let atomic_tys: [Expr; 11] = [ - parse_quote!(AtomicBool), - parse_quote!(AtomicI8), - parse_quote!(AtomicI16), - parse_quote!(AtomicI32), - parse_quote!(AtomicI64), - parse_quote!(AtomicIsize), - parse_quote!(AtomicU8), - parse_quote!(AtomicU16), - parse_quote!(AtomicU32), - parse_quote!(AtomicU64), - parse_quote!(AtomicUsize), - ]; - // let tmp: proc_macro2::TokenStream = ; + self.validate_correct_fields(REQ_FIELDS, OPT_FIELDS); + + let ty_as_svwrap = quote! { <#vtype as ::mariadb::plugin::internals::SysVarInterface> }; let name = expect_litstr(&self.name)?; + let ident = self.ident.as_ref().unwrap(); + let opts = self.make_option_fields()?; + let flags = quote! { #ty_as_svwrap::DEFAULT_OPTS #opts }; + let description = expect_litstr(&self.description)?; + + let default = process_default_override(&self.default, "def_val")?; + let min = process_default_override(&self.min, "min_val")?; + let max = process_default_override(&self.max, "max_val")?; + let interval = process_default_override(&self.interval, "blk_sz")?; + let st_ident = Ident::new(&format!("_st_sysvar_{}", name.value()), Span::call_site()); + // https://github.com/rust-lang/rust/issues/86935#issuecomment-1146670057 + let ty_wrap = Ident::new( + &format!("_st_sysvar_Type{}", name.value()), + Span::call_site(), + ); - if vtype == &str_ty { - Ok((st_ident.clone(), self.make_string_sysvar(&st_ident)?)) - } else if atomic_tys.contains(vtype) { - Ok((st_ident.clone(), self.make_atomic_sysvar(&st_ident)?)) - } else { - Err(Error::new_spanned( - &self.vtype, - "invalid variable type. Only 'SysVarString', 'SysVarConstString', and 'AtomicX' currently allowed.", - )) - } + let usynccell = quote! { ::mariadb::internals::UnsafeSyncCell }; + + let res = quote! { + type #ty_wrap = T; + + static #st_ident: #usynccell<#ty_wrap::<#ty_as_svwrap::CStructType>> = unsafe { + #usynccell::new( + #ty_wrap::<#ty_as_svwrap::CStructType> { + flags: #flags, + name: ::mariadb::internals::cstr!(#name).as_ptr(), + comment: ::mariadb::internals::cstr!(#description).as_ptr(), + value: ::std::ptr::addr_of!(#ident).cast_mut().cast(), // *mut *mut c_char, + + #default + #min + #max + #interval + + ..#ty_as_svwrap::DEFAULT_C_STRUCT + // def_val: #default, + } + ) + }; + + }; + + Ok((st_ident.clone(), res)) } + /// Take the options vector, parse it as an array, bitwise or the output, + /// cast to i32 fn make_option_fields(&self) -> syn::Result { let Some(input) = &self.options else { return Ok(TokenStream::new()); @@ -223,56 +244,6 @@ impl VariableInfo { Ok(ret) } - fn make_string_sysvar(&self, st_ident: &Ident) -> syn::Result { - self.validate_correct_fields(STR_REQ_FIELDS, STR_OPT_FIELDS); - - let opts = self.make_option_fields()?; - let flags = quote! { (::mariadb::bindings::PLUGIN_VAR_STR as i32) #opts }; - let name = expect_litstr(&self.name)?; - let description = expect_litstr(&self.description)?; - let default = if let Some(def) = &self.default { - quote! { ::mariadb::internals::cstr!(#def).as_ptr().cast_mut() } - } else { - quote! { ::mariadb::internals::cstr!("").as_ptr().cast_mut() } - }; - let disambig_name = quote! { ::std::sync::Mutex }; - let check_fn = quote! { None }; - let update_fn = quote! { None }; - // let update_fn = quote! { - // Some(<::mariadb::plugin::SysVarString as - // ::mariadb::plugin::internals::SysvarWrap>::update_wrap) - // }; - // let check_fn = quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::check) }; - // let update_fn = - // quote! { Some(<#disambig_name as ::mariadb::internals::SysVarWrap>::update) }; - let ident = self.ident.as_ref().unwrap(); - let res = quote! { - static #st_ident: ::mariadb::internals::UnsafeSyncCell< - ::mariadb::bindings::sysvar_str_t, - > = unsafe { - ::mariadb::internals::UnsafeSyncCell::new( - ::mariadb::bindings::sysvar_str_t { - flags: #flags, - name: ::mariadb::internals::cstr!(#name).as_ptr(), - comment: ::mariadb::internals::cstr!(#description).as_ptr(), - check: #check_fn, - update: #update_fn, - value: ::std::ptr::addr_of!(#ident).cast_mut().cast(), // *mut *mut c_char, - def_val: #default, - } - ) - }; - - }; - - Ok(res) - } - - fn make_atomic_sysvar(&self, st_ident: &Ident) -> syn::Result { - self.validate_correct_fields(NUM_REQ_FIELDS, NUM_OPT_FIELDS); - todo!() - } - fn make_showvar(&self) -> syn::Result<(Ident, TokenStream)> { todo!() } @@ -345,3 +316,27 @@ fn verify_field_order(fields: &[String]) -> Result<(), String> { "fields not in expected order. reorder as:\n{expected_order:?}", )) } + +/// Process "default override" style fields by these rules: +/// +/// - If `field` is `None`, return an empty TokenStream +/// - Enforce it is a literal +/// - If it is a literal string, change it to a `cstr` +/// +/// Might want to relax and take consts at some point, but that's someday... +fn process_default_override(field: &Option, fname: &str) -> syn::Result { + let Some(f_inner) = field.as_ref() else { + return Ok(TokenStream::new()); + }; + + let Expr::Lit(exprlit) = f_inner else { + return Err(Error::new_spanned(f_inner, "only literal values are allowed in this position")); + }; + + let fid = Ident::new(fname, f_inner.span()); + if let syn::Lit::Str(litstr) = &exprlit.lit { + Ok(quote! { #fid: ::mariadb::internals::cstr!(#litstr).as_ptr().cast_mut(), }) + } else { + Ok(quote! { #fid: #exprlit, }) + } +} diff --git a/rust/macros/tests/fail/02-extra-sysargs.rs b/rust/macros/tests/fail/02-extra-sysargs.rs new file mode 100644 index 0000000000000..13ae14eee1d35 --- /dev/null +++ b/rust/macros/tests/fail/02-extra-sysargs.rs @@ -0,0 +1,27 @@ +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: TestPlugin, + encryption: false, + variables: [ + SysVar { + ident: _SYSVAR_STR, + vtype: SysVarConstString, + name: "test_sysvar", + description: "this is a description", + options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], + default: "default value", + interval: "50" + } + ] +} + +fn main() {} diff --git a/rust/macros/tests/fail/02-extra-sysargs.stderr b/rust/macros/tests/fail/02-extra-sysargs.stderr new file mode 100644 index 0000000000000..05aa910a006f2 --- /dev/null +++ b/rust/macros/tests/fail/02-extra-sysargs.stderr @@ -0,0 +1,7 @@ +error[E0560]: struct `sysvar_str_t` has no field named `blk_sz` + --> tests/fail/02-extra-sysargs.rs:22:23 + | +22 | interval: "50" + | ^^^^ `sysvar_str_t` does not have this field + | + = note: available fields are: `flags`, `name`, `comment`, `check`, `update` ... and 2 others diff --git a/rust/macros/tests/pass/03-with-sysargs.rs b/rust/macros/tests/pass/03-with-sysargs.rs index bfdae1e1a60c6..c678fa2d4f87a 100644 --- a/rust/macros/tests/pass/03-with-sysargs.rs +++ b/rust/macros/tests/pass/03-with-sysargs.rs @@ -14,11 +14,11 @@ register_plugin! { variables: [ SysVar { ident: _SYSVAR_STR, - vtype: SysVarString, + vtype: SysVarConstString, name: "test_sysvar", description: "this is a description", options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], - default: "something" + default: "default value" } ] } @@ -47,10 +47,11 @@ fn main() { // try the C way, slow casting steps to avoid errors here let sv1_ptr: *mut st_mysql_sys_var = unsafe { *plugin_def.system_vars.add(0) }; - let sv1: &sysvar_common_t = unsafe { &*sv1_ptr.cast() }; + let sv1: &sysvar_str_t = unsafe { &*sv1_ptr.cast() }; let flags = sv1.flags; let sv1_name = unsafe { CStr::from_ptr(sv1.name).to_str().unwrap() }; let sv1_comment = unsafe { CStr::from_ptr(sv1.comment).to_str().unwrap() }; + let sv1_default = unsafe { CStr::from_ptr(sv1.def_val).to_str().unwrap() }; let expected_flags = bindings::PLUGIN_VAR_STR | ((bindings::PLUGIN_VAR_READONLY | bindings::PLUGIN_VAR_NOCMDOPT) @@ -58,4 +59,5 @@ fn main() { assert_eq!(flags, expected_flags as i32); assert_eq!(sv1_name, "test_sysvar"); assert_eq!(sv1_comment, "this is a description"); + assert_eq!(sv1_default, "default value"); } diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 8abdf37f65d3e..8d84e900b13dc 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -41,7 +41,7 @@ //! variables: [ // variables should be a list of typed identifiers //! SysVar { //! ident: SYSVAR_STR, -//! vtype: SysVarString, +//! vtype: SysVarConstString, //! name: "sql_name", //! description: "this is a description", //! options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index 611b76b39049d..f8e7840d645c6 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -1,7 +1,7 @@ //! "show variables" and "system variables" use std::cell::UnsafeCell; -use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void, CStr}; +use std::ffi::{c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void, CStr, CString}; use std::marker::PhantomPinned; use std::mem::ManuallyDrop; use std::os::raw::{c_char, c_uint}; @@ -10,7 +10,8 @@ use std::sync::atomic::{self, AtomicBool, AtomicI32, AtomicPtr, AtomicU32, Order use std::sync::Mutex; use bindings::THD; -use log::trace; +use cstr::cstr; +use log::{trace, warn}; use mariadb_sys as bindings; use super::variables_parse::{CliMysqlValue, CliValue}; @@ -74,12 +75,13 @@ type SvUpdateFn = /// A wrapper for system variables. This won't be exposed externally. /// -/// This provides the interface of update functions -pub trait SysVarInterface: Sized { +/// 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 CStType; + type CStructType; - /// Intermediate type, pointed to by the CStType's `value` pointer + /// Intermediate type, pointed to by the CStructType's `value` pointer type Intermediate; /// Associated const with an optional function pointer to an update @@ -92,6 +94,9 @@ pub trait SysVarInterface: Sized { /// 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". /// @@ -110,12 +115,12 @@ pub trait SysVarInterface: Sized { } /// Update function: override this if it is pointed to by `UPDATE_FUNC` - unsafe fn update(&self, var: &Self::CStType, save: Option<&Self::Intermediate>) { + unsafe fn update(&self, var: &Self::CStructType, save: Option<&Self::Intermediate>) { unimplemented!() } } -/// A string system variable +/// 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 @@ -141,29 +146,157 @@ impl SysVarConstString { } } -impl SysVarInterface for SysVarConstString { - type CStType = bindings::sysvar_str_t; +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, + mutex: Mutex>, +} + +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 + pub fn get(&self) -> Option { + 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 { + warn!("pointer {ptr:p} mismatch with guard {guard:?}"); + // 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 UPDATE_FUNC: Option = Some(Self::update_wrap); const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32; - // const UPDATE_FUNC: Option = - // Some(Self::update_wrap as bindings::mysql_var_update_func); + 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(var: &Self::CStType, target: &Self, save: &Self::Intermediate) { - // target.0.store(*save, Ordering::SeqCst); - // trace!("updated system variable to '{}'", target.get()); - // } + unsafe fn update(&self, var: &Self::CStructType, save: Option<&Self::Intermediate>) { + let to_save = save.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); + } +} + +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 { - ($atomic_ty:ty, $cs_ty:ty, $inter_ty:ty, $opts:expr) => { - impl SysVarInterface for $atomic_ty { - type CStType = $cs_ty; - type Intermediate = $inter_ty; - const DEFAULT_OPTS: i32 = ($opts) as i32; + // 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 UPDATE_FUNC: Option = Some(Self::update_wrap as SvUpdateFn); + const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType { + flags: 0, + name: ptr::null(), + comment: ptr::null(), + check: None, + update: None, + value: ptr::null_mut(), + $( $extra_struct_fields )* + }; - unsafe fn update(&self, var: &Self::CStType, save: Option<&Self::Intermediate>) { + unsafe fn update(&self, var: &Self::CStructType, save: Option<&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) let new = save.expect("somehow got a null pointer"); @@ -178,13 +311,13 @@ atomic_svinterface!( atomic::AtomicBool, bindings::sysvar_bool_t, bool, - bindings::PLUGIN_VAR_BOOL + bindings::PLUGIN_VAR_BOOL, ); atomic_svinterface!( atomic::AtomicI32, bindings::sysvar_int_t, c_int, - bindings::PLUGIN_VAR_INT + bindings::PLUGIN_VAR_INT, ); atomic_svinterface!( atomic::AtomicU32, diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index 72effb5d95db5..dca3f4481b7b6 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -173,7 +173,7 @@ register_plugin! { variables: [ SysVar { ident: TANG_SERVER, - vtype: SysVarString, + vtype: SysVarConstString, name: "tang_server", description: "the tang server for key exchange", options: [SysVarOpt::OptCmdArd], From 48111df59b4fbfdbd514f01bef2018fc958f3bcb Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 9 Feb 2023 07:19:44 -0500 Subject: [PATCH 34/34] System variables working and validated (atomic, const string, string) --- rust/examples/keymgt-debug/src/lib.rs | 37 +++++++++++++-- rust/macros/src/lib.rs | 2 - rust/macros/src/parse_vars.rs | 17 ++++--- rust/macros/src/register_plugin.rs | 2 +- rust/macros/tests/fail/02-extra-sysargs.rs | 2 +- rust/macros/tests/fail/03-wrong-types.rs | 30 ++++++++++++ rust/macros/tests/fail/03-wrong-types.stderr | 15 ++++++ rust/macros/tests/include.rs | 2 +- rust/macros/tests/pass/03-with-sysargs.rs | 4 +- rust/mariadb/src/helpers.rs | 1 + rust/mariadb/src/lib.rs | 4 +- rust/mariadb/src/plugin.rs | 2 +- rust/mariadb/src/plugin/encryption.rs | 8 ++-- rust/mariadb/src/plugin/variables.rs | 48 +++++++++++--------- rust/mariadb/src/plugin/variables_parse.rs | 2 +- rust/mariadb/src/service_sql/raw.rs | 4 +- rust/plugins/keymgt-clevis/src/lib.rs | 10 ++-- 17 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 rust/macros/tests/fail/03-wrong-types.rs create mode 100644 rust/macros/tests/fail/03-wrong-types.stderr diff --git a/rust/examples/keymgt-debug/src/lib.rs b/rust/examples/keymgt-debug/src/lib.rs index 2405248cdc3d6..2a6c12c5ee0b7 100644 --- a/rust/examples/keymgt-debug/src/lib.rs +++ b/rust/examples/keymgt-debug/src/lib.rs @@ -9,18 +9,21 @@ use std::cell::UnsafeCell; use std::ffi::c_void; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; use std::sync::Mutex; use mariadb::log::{self, debug, trace}; use mariadb::plugin::encryption::{Encryption, Flags, KeyError, KeyManager}; use mariadb::plugin::{ register_plugin, Init, InitError, License, Maturity, PluginType, SysVarConstString, SysVarOpt, + SysVarString, }; const KEY_LENGTH: usize = 4; static KEY_VERSION: AtomicU32 = AtomicU32::new(1); -static TEST_SYSVAR_STR: SysVarConstString = SysVarConstString::new(); +static TEST_SYSVAR_CONST_STR: SysVarConstString = SysVarConstString::new(); +static TEST_SYSVAR_STR: SysVarString = SysVarString::new(); +static TEST_SYSVAR_I32: AtomicI32 = AtomicI32::new(10); struct DebugKeyMgmt; @@ -28,7 +31,15 @@ impl Init for DebugKeyMgmt { fn init() -> Result<(), InitError> { log::set_max_level(log::LevelFilter::Trace); debug!("DebugKeyMgmt get_latest_key_version"); - trace!("current sysvar: {}", TEST_SYSVAR_STR.get()); + trace!( + "current const str sysvar: {:?}", + TEST_SYSVAR_CONST_STR.get() + ); + trace!("current str sysvar: {:?}", TEST_SYSVAR_STR.get()); + trace!( + "current sysvar: {}", + TEST_SYSVAR_I32.load(Ordering::Relaxed) + ); Ok(()) } @@ -91,12 +102,28 @@ register_plugin! { encryption: false, variables: [ SysVar { - ident: TEST_SYSVAR_STR, + ident: TEST_SYSVAR_CONST_STR, vtype: SysVarConstString, - name: "test_sysvar", + name: "test_sysvar_const_string", description: "this is a description", options: [SysVarOpt::OptCmdArd], default: "default value" + }, + SysVar { + ident: TEST_SYSVAR_STR, + vtype: SysVarString, + name: "test_sysvar_string", + description: "this is a description", + options: [SysVarOpt::OptCmdArd], + default: "other default value" + }, + SysVar { + ident: TEST_SYSVAR_I32, + vtype: AtomicI32, + name: "test_sysvar_i32", + description: "this is a description", + options: [SysVarOpt::OptCmdArd], + default: 67 } ] } diff --git a/rust/macros/src/lib.rs b/rust/macros/src/lib.rs index 93111f5bf78c4..0eb97cf11c3f3 100644 --- a/rust/macros/src/lib.rs +++ b/rust/macros/src/lib.rs @@ -2,11 +2,9 @@ #![warn(clippy::nursery)] #![warn(clippy::str_to_string)] #![warn(clippy::missing_inline_in_public_items)] -#![allow(clippy::missing_const_for_fn)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::must_use_candidate)] #![allow(clippy::option_if_let_else)] -#![allow(clippy::cast_possible_truncation)] mod fields; mod helpers; diff --git a/rust/macros/src/parse_vars.rs b/rust/macros/src/parse_vars.rs index 60016ff3284d9..beaf120f1c166 100644 --- a/rust/macros/src/parse_vars.rs +++ b/rust/macros/src/parse_vars.rs @@ -190,18 +190,23 @@ impl VariableInfo { let max = process_default_override(&self.max, "max_val")?; let interval = process_default_override(&self.interval, "blk_sz")?; - let st_ident = Ident::new(&format!("_st_sysvar_{}", name.value()), Span::call_site()); - // https://github.com/rust-lang/rust/issues/86935#issuecomment-1146670057 - let ty_wrap = Ident::new( - &format!("_st_sysvar_Type{}", name.value()), + let st_ident = Ident::new(&format!("_sysvar_st_{}", name.value()), Span::call_site()); + let st_tycheck = Ident::new( + &format!("_sysvar_tychk_{}", name.value()), Span::call_site(), ); + // https://github.com/rust-lang/rust/issues/86935#issuecomment-1146670057 + let ty_wrap = Ident::new(&format!("_sysvar_Type{}", name.value()), Span::call_site()); + // check to verify our vars are of the right type for our idents + let ty_check = quote! { static #st_tycheck: &#vtype = &#ident; }; let usynccell = quote! { ::mariadb::internals::UnsafeSyncCell }; let res = quote! { type #ty_wrap = T; + #ty_check + static #st_ident: #usynccell<#ty_wrap::<#ty_as_svwrap::CStructType>> = unsafe { #usynccell::new( #ty_wrap::<#ty_as_svwrap::CStructType> { @@ -223,7 +228,7 @@ impl VariableInfo { }; - Ok((st_ident.clone(), res)) + Ok((st_ident, res)) } /// Take the options vector, parse it as an array, bitwise or the output, @@ -319,7 +324,7 @@ fn verify_field_order(fields: &[String]) -> Result<(), String> { /// Process "default override" style fields by these rules: /// -/// - If `field` is `None`, return an empty TokenStream +/// - If `field` is `None`, return an empty `TokenStream` /// - Enforce it is a literal /// - If it is a literal string, change it to a `cstr` /// diff --git a/rust/macros/src/register_plugin.rs b/rust/macros/src/register_plugin.rs index d9eec5dd52136..02ce3fbc4bdfc 100644 --- a/rust/macros/src/register_plugin.rs +++ b/rust/macros/src/register_plugin.rs @@ -94,7 +94,7 @@ impl Parse for PluginInfo { } impl PluginInfo { - fn new(main_ty: Ident, span: Span) -> Self { + const fn new(main_ty: Ident, span: Span) -> Self { Self { main_ty, span, diff --git a/rust/macros/tests/fail/02-extra-sysargs.rs b/rust/macros/tests/fail/02-extra-sysargs.rs index 13ae14eee1d35..342b35b833af1 100644 --- a/rust/macros/tests/fail/02-extra-sysargs.rs +++ b/rust/macros/tests/fail/02-extra-sysargs.rs @@ -13,7 +13,7 @@ register_plugin! { encryption: false, variables: [ SysVar { - ident: _SYSVAR_STR, + ident: _SYSVAR_CONST_STR, vtype: SysVarConstString, name: "test_sysvar", description: "this is a description", diff --git a/rust/macros/tests/fail/03-wrong-types.rs b/rust/macros/tests/fail/03-wrong-types.rs new file mode 100644 index 0000000000000..eb25475a9b959 --- /dev/null +++ b/rust/macros/tests/fail/03-wrong-types.rs @@ -0,0 +1,30 @@ +/* + * Verify our added check for identifier-type mismatch + */ + +include!("../include.rs"); + +register_plugin! { + TestPlugin, + ptype: PluginType::MariaEncryption, + name: "debug_key_management", + author: "Trevor Gross", + description: "Debug key management plugin", + license: License::Gpl, + maturity: Maturity::Experimental, + version: "0.1", + init: TestPlugin, + encryption: false, + variables: [ + SysVar { + ident: _SYSVAR_ATOMIC, + vtype: SysVarConstString, + name: "test_sysvar", + description: "this is a description", + options: [SysVarOpt::ReadOnly, SysVarOpt::NoCmdOpt], + default: "default value" + } + ] +} + +fn main() {} diff --git a/rust/macros/tests/fail/03-wrong-types.stderr b/rust/macros/tests/fail/03-wrong-types.stderr new file mode 100644 index 0000000000000..31dee11bc8163 --- /dev/null +++ b/rust/macros/tests/fail/03-wrong-types.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> tests/fail/03-wrong-types.rs:7:1 + | +7 | / register_plugin! { +8 | | TestPlugin, +9 | | ptype: PluginType::MariaEncryption, +10 | | name: "debug_key_management", +... | +27 | | ] +28 | | } + | |_^ expected `&SysVarConstString`, found `&AtomicI32` + | + = note: expected reference `&'static mariadb::plugin::SysVarConstString` + found reference `&AtomicI32` + = note: this error originates in the macro `register_plugin` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/rust/macros/tests/include.rs b/rust/macros/tests/include.rs index 0b6eeda01e76f..e618cd5828bba 100644 --- a/rust/macros/tests/include.rs +++ b/rust/macros/tests/include.rs @@ -7,7 +7,7 @@ use mariadb::plugin::*; pub use mariadb_macros::register_plugin; static _SYSVAR_ATOMIC: AtomicI32 = AtomicI32::new(0); -static _SYSVAR_STR: SysVarConstString = SysVarConstString::new(); +static _SYSVAR_CONST_STR: SysVarConstString = SysVarConstString::new(); struct TestPlugin; diff --git a/rust/macros/tests/pass/03-with-sysargs.rs b/rust/macros/tests/pass/03-with-sysargs.rs index c678fa2d4f87a..01f3cc8053a4e 100644 --- a/rust/macros/tests/pass/03-with-sysargs.rs +++ b/rust/macros/tests/pass/03-with-sysargs.rs @@ -13,7 +13,7 @@ register_plugin! { encryption: false, variables: [ SysVar { - ident: _SYSVAR_STR, + ident: _SYSVAR_CONST_STR, vtype: SysVarConstString, name: "test_sysvar", description: "this is a description", @@ -37,7 +37,7 @@ fn main() { let plugin_def: &st_maria_plugin = unsafe { &*(_maria_plugin_declarations_[0]).get() }; let sysv_ptr: *mut *mut st_mysql_sys_var = plugin_def.system_vars; - let sysvar_st: *const sysvar_str_t = _st_sysvar_test_sysvar.get(); + let sysvar_st: *const sysvar_str_t = _sysvar_st_test_sysvar.get(); let sysvar_arr: &[UnsafeSyncCell<*mut sysvar_common_t>] = &_plugin_debug_key_management_sysvars; let idx_0: *mut sysvar_common_t = unsafe { *sysvar_arr[0].get() }; let idx_1: *mut sysvar_common_t = unsafe { *sysvar_arr[1].get() }; diff --git a/rust/mariadb/src/helpers.rs b/rust/mariadb/src/helpers.rs index 981070f94041d..89a61b56dbd1f 100644 --- a/rust/mariadb/src/helpers.rs +++ b/rust/mariadb/src/helpers.rs @@ -30,6 +30,7 @@ impl UnsafeSyncCell { } } +#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for UnsafeSyncCell {} unsafe impl Sync for UnsafeSyncCell {} diff --git a/rust/mariadb/src/lib.rs b/rust/mariadb/src/lib.rs index 8c7d4e4e63c82..240482d7b4aaf 100644 --- a/rust/mariadb/src/lib.rs +++ b/rust/mariadb/src/lib.rs @@ -1,11 +1,13 @@ //! Crate representing safe abstractions over MariaDB bindings #![warn(clippy::pedantic)] #![warn(clippy::nursery)] -#![warn(clippy::missing_inline_in_public_items)] #![warn(clippy::str_to_string)] +#![allow(clippy::option_if_let_else)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::must_use_candidate)] #![allow(clippy::useless_conversion)] +#![allow(clippy::cast_possible_wrap)] +#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_const_for_fn)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::missing_inline_in_public_items)] diff --git a/rust/mariadb/src/plugin.rs b/rust/mariadb/src/plugin.rs index 8d84e900b13dc..cf151447eae16 100644 --- a/rust/mariadb/src/plugin.rs +++ b/rust/mariadb/src/plugin.rs @@ -72,7 +72,7 @@ mod variables; mod variables_parse; mod wrapper; pub use mariadb_macros::register_plugin; -pub use variables::{SysVarConstString, SysVarOpt}; +pub use variables::{SysVarConstString, SysVarOpt, SysVarString}; /// Commonly used plugin types for reexport pub mod prelude { diff --git a/rust/mariadb/src/plugin/encryption.rs b/rust/mariadb/src/plugin/encryption.rs index 0a8a81fd425a0..22f0ca10515c9 100644 --- a/rust/mariadb/src/plugin/encryption.rs +++ b/rust/mariadb/src/plugin/encryption.rs @@ -47,20 +47,20 @@ pub enum EncryptionError { pub struct Flags(i32); impl Flags { - pub(crate) fn new(value: i32) -> Self { + pub(crate) const fn new(value: i32) -> Self { Self(value) } - pub(crate) fn should_encrypt(self) -> bool { + pub(crate) const fn should_encrypt(self) -> bool { (self.0 & bindings::ENCRYPTION_FLAG_ENCRYPT as i32) != 0 } - pub(crate) fn should_decrypt(self) -> bool { + pub(crate) const fn should_decrypt(self) -> bool { // (self.0 & bindings::ENCRYPTION_FLAG_DECRYPT as i32) != 0 !self.should_encrypt() } - pub fn nopad(&self) -> bool { + pub const fn nopad(&self) -> bool { (self.0 & bindings::ENCRYPTION_FLAG_NOPAD as i32) != 0 } } diff --git a/rust/mariadb/src/plugin/variables.rs b/rust/mariadb/src/plugin/variables.rs index f8e7840d645c6..c7e20037f9c49 100644 --- a/rust/mariadb/src/plugin/variables.rs +++ b/rust/mariadb/src/plugin/variables.rs @@ -81,15 +81,8 @@ 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; - - /// Associated const with an optional function pointer to an update - /// function. - /// - /// If a sysvar type should use a custom update function, implmeent `update` - /// and set this value to `update_wrap`. - const UPDATE_FUNC: Option = None; + /// Intermediate type, pointed to by the `CStructType's` `value` pointer + type Intermediate: Copy; /// Options to implement by default const DEFAULT_OPTS: i32; @@ -111,11 +104,15 @@ pub unsafe trait SysVarInterface: Sized { save: *const c_void, ) { let new_save: *const Self::Intermediate = save.cast(); - Self::update(&*target.cast(), &*var.cast(), new_save.as_ref()); + 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` - unsafe fn update(&self, var: &Self::CStructType, save: Option<&Self::Intermediate>) { + unsafe fn update(&self, var: &Self::CStructType, save: Self::Intermediate) { unimplemented!() } } @@ -137,6 +134,10 @@ impl SysVarConstString { /// 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) -> String { let ptr = self.0.load(Ordering::SeqCst); let cs = unsafe { CStr::from_ptr(ptr) }; @@ -184,6 +185,10 @@ impl SysVarString { } /// Get the current value of this variable + /// + /// # Panics + /// + /// Panics if the mutex can't be locked pub fn get(&self) -> Option { let guard = &*self.mutex.lock().expect("failed to lock mutex"); let ptr = self.ptr.load(Ordering::SeqCst); @@ -194,11 +199,11 @@ impl SysVarString { ptr.cast_const() == cs.as_ptr(), "pointer and c string unsynchronized" ); - Some(cstr_to_string(&cs)) + Some(cstr_to_string(cs)) } else if ptr.is_null() && guard.is_none() { None } else { - warn!("pointer {ptr:p} mismatch with guard {guard:?}"); + 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)) @@ -209,7 +214,6 @@ impl SysVarString { unsafe impl SysVarInterface for SysVarString { type CStructType = bindings::sysvar_str_t; type Intermediate = *mut c_char; - const UPDATE_FUNC: Option = Some(Self::update_wrap); const DEFAULT_OPTS: i32 = bindings::PLUGIN_VAR_STR as i32; const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType { flags: 0, @@ -221,14 +225,17 @@ unsafe impl SysVarInterface for SysVarString { def_val: cstr!("").as_ptr().cast_mut(), }; - unsafe fn update(&self, var: &Self::CStructType, save: Option<&Self::Intermediate>) { - let to_save = save.map(|ptr| unsafe { CStr::from_ptr(*ptr).to_owned() }); + 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:?}"); } } @@ -281,27 +288,24 @@ macro_rules! atomic_svinterface { type CStructType = $c_struct_type; type Intermediate = $inter_type; const DEFAULT_OPTS: i32 = ($default_options) as i32; - const UPDATE_FUNC: Option = Some(Self::update_wrap as SvUpdateFn); const DEFAULT_C_STRUCT: Self::CStructType = Self::CStructType { flags: 0, name: ptr::null(), comment: ptr::null(), check: None, - update: None, + update: Some(Self::update_wrap), value: ptr::null_mut(), $( $extra_struct_fields )* }; - unsafe fn update(&self, var: &Self::CStructType, save: Option<&Self::Intermediate>) { + 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) - let new = save.expect("somehow got a null pointer"); - self.store(*new, Ordering::SeqCst); - trace!("updated system variable to '{}'", new); + self.store(save, Ordering::SeqCst); } } }; diff --git a/rust/mariadb/src/plugin/variables_parse.rs b/rust/mariadb/src/plugin/variables_parse.rs index a8293ce3a63cc..7c8149088e728 100644 --- a/rust/mariadb/src/plugin/variables_parse.rs +++ b/rust/mariadb/src/plugin/variables_parse.rs @@ -107,7 +107,7 @@ impl CliMysqlValue { } } - unsafe fn from_ptr<'a>(ptr: *const bindings::st_mysql_value) -> &'a Self { + const unsafe fn from_ptr<'a>(ptr: *const bindings::st_mysql_value) -> &'a Self { &*ptr.cast() } diff --git a/rust/mariadb/src/service_sql/raw.rs b/rust/mariadb/src/service_sql/raw.rs index eca03435d4a10..99647451be793 100644 --- a/rust/mariadb/src/service_sql/raw.rs +++ b/rust/mariadb/src/service_sql/raw.rs @@ -244,12 +244,12 @@ impl FetchedRow<'_> { } } - pub fn field_info(&self, index: usize) -> &Field { + pub const fn field_info(&self, index: usize) -> &Field { &self.fields[index] } /// Get the total number of fields - pub fn field_count(&self) -> usize { + pub const fn field_count(&self) -> usize { self.fields.len() } } diff --git a/rust/plugins/keymgt-clevis/src/lib.rs b/rust/plugins/keymgt-clevis/src/lib.rs index dca3f4481b7b6..f9c783859166a 100644 --- a/rust/plugins/keymgt-clevis/src/lib.rs +++ b/rust/plugins/keymgt-clevis/src/lib.rs @@ -48,7 +48,7 @@ fn make_new_key(conn: &MySqlConn) -> Result { ); // get the jws value - let jws: &str = todo!(); + let jws: &str; todo!() } @@ -117,7 +117,8 @@ impl KeyManager for KeyMgtClevis { // fuund! fetch result, parse to int // if let Some(row) = todo!() { if false { - return Ok(todo!()); + todo!() + // return Ok(); } // directly push format string @@ -129,7 +130,7 @@ impl KeyManager for KeyMgtClevis { let Ok(new_key) = make_new_key(&conn) else { run_execute(&mut conn, "ROLLBACK", key_id)?; - return todo!(); + todo!(); }; let q = format!( @@ -149,7 +150,8 @@ impl KeyManager for KeyMgtClevis { ); conn.query(&q).map_err(|_| KeyError::Other)?; // TODO: generate key with server - let key: &[u8] = todo!(); + let key: &[u8]; + todo!(); dst[..key.len()].copy_from_slice(key); Ok(()) }