2658 lines
92 KiB
C++
2658 lines
92 KiB
C++
/* Copyright (c) 2016, 2024, Oracle and/or its affiliates.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is designed to work with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have either included with
|
|
the program or referenced in the documentation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "sql/persisted_variable.h"
|
|
|
|
#include "my_config.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iomanip>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <utility>
|
|
|
|
#include "mysql/components/library_mysys/my_hex_tools.h"
|
|
|
|
#include "keyring_operations_helper.h"
|
|
#include "lex_string.h"
|
|
#include "m_string.h"
|
|
#include "my_aes.h"
|
|
#include "my_compiler.h"
|
|
|
|
#include "my_default.h" // check_file_permissions
|
|
#include "my_getopt.h"
|
|
#include "my_io.h"
|
|
#include "my_macros.h"
|
|
#include "my_rnd.h"
|
|
#include "my_sys.h"
|
|
#include "my_thread.h"
|
|
#include "mysql/components/services/bits/psi_bits.h"
|
|
#include "mysql/components/services/bits/psi_file_bits.h"
|
|
#include "mysql/components/services/bits/psi_memory_bits.h"
|
|
#include "mysql/components/services/bits/psi_mutex_bits.h"
|
|
#include "mysql/components/services/log_builtins.h"
|
|
#include "mysql/components/services/log_shared.h"
|
|
#include "mysql/components/services/system_variable_source_type.h"
|
|
#include "mysql/my_loglevel.h"
|
|
#include "mysql/psi/mysql_file.h"
|
|
#include "mysql/psi/mysql_memory.h"
|
|
#include "mysql/psi/mysql_mutex.h"
|
|
#include "mysql/status_var.h"
|
|
#include "mysql/strings/m_ctype.h"
|
|
#include "mysql_version.h"
|
|
#include "mysqld_error.h"
|
|
#include "nulls.h"
|
|
#include "prealloced_array.h"
|
|
#include "scope_guard.h"
|
|
#include "sql-common/json_dom.h"
|
|
#include "sql/auth/auth_acls.h"
|
|
#include "sql/auth/auth_internal.h"
|
|
#include "sql/auth/sql_security_ctx.h"
|
|
#include "sql/current_thd.h"
|
|
#include "sql/debug_sync.h" // DEBUG_SYNC
|
|
#include "sql/derror.h" // ER_THD
|
|
#include "sql/item.h"
|
|
#include "sql/log.h"
|
|
#include "sql/mysqld.h"
|
|
#include "sql/server_component/mysql_server_keyring_lockable_imp.h"
|
|
#include "sql/set_var.h"
|
|
#include "sql/sql_class.h"
|
|
#include "sql/sql_error.h"
|
|
#include "sql/sql_lex.h"
|
|
#include "sql/sql_list.h"
|
|
#include "sql/sql_show.h"
|
|
#include "sql/sys_vars_shared.h"
|
|
#include "sql/thr_malloc.h"
|
|
#include "sql_string.h"
|
|
#include "template_utils.h"
|
|
#include "thr_mutex.h"
|
|
#include "typelib.h"
|
|
|
|
using std::map;
|
|
using std::string;
|
|
|
|
namespace {
|
|
/* Keys used by mysqld-auto.cnf JSON file */
|
|
|
|
/* Options metadata keys */
|
|
constexpr char s_key_value[] = "Value";
|
|
constexpr char s_key_metadata[] = "Metadata";
|
|
constexpr char s_key_timestamp[] = "Timestamp";
|
|
constexpr char s_key_user[] = "User";
|
|
constexpr char s_key_host[] = "Host";
|
|
/* Options category keys */
|
|
constexpr char s_key_mysql_dynamic_parse_early_variables[] =
|
|
"mysql_dynamic_parse_early_variables";
|
|
constexpr char s_key_mysql_sensitive_dynamic_variables[] =
|
|
"mysql_dynamic_sensitive_variables";
|
|
constexpr char s_key_mysql_dynamic_variables[] = "mysql_dynamic_variables";
|
|
constexpr char s_key_mysql_static_parse_early_variables[] =
|
|
"mysql_static_parse_early_variables";
|
|
constexpr char s_key_mysql_sensitive_static_variables[] =
|
|
"mysql_static_sensitive_variables";
|
|
constexpr char s_key_mysql_static_variables[] = "mysql_static_variables";
|
|
/* Sensitive variables section key */
|
|
constexpr char s_key_mysql_sensitive_variables[] = "mysql_sensitive_variables";
|
|
/* Encrypted section keys */
|
|
constexpr char s_key_master_key_id[] = "master_key_id";
|
|
constexpr char s_key_file_key[] = "file_key";
|
|
constexpr char s_key_file_key_iv[] = "file_key_iv";
|
|
constexpr char s_key_key_encryption_algorithm[] = "key_encryption_algorithm";
|
|
constexpr char s_key_data_encryption_algorithm[] = "data_encryption_algorithm";
|
|
constexpr char s_key_mysql_sensitive_variables_iv[] =
|
|
"mysql_sensitive_variables_iv";
|
|
constexpr char s_key_mysql_sensitive_variables_blob[] =
|
|
"mysql_sensitive_variables_blob";
|
|
constexpr char s_key_mysql_encryption_algorithm_default[] = "AES_256_CBC";
|
|
} // namespace
|
|
|
|
PSI_file_key key_persist_file_cnf;
|
|
|
|
#ifdef HAVE_PSI_FILE_INTERFACE
|
|
static PSI_file_info all_persist_files[] = {
|
|
{&key_persist_file_cnf, "cnf", 0, 0, PSI_DOCUMENT_ME}};
|
|
#endif /* HAVE_PSI_FILE_INTERFACE */
|
|
|
|
PSI_mutex_key key_persist_file, key_persist_variables;
|
|
|
|
#ifdef HAVE_PSI_MUTEX_INTERFACE
|
|
static PSI_mutex_info all_persist_mutexes[] = {
|
|
{&key_persist_file, "m_LOCK_persist_file", 0, 0, PSI_DOCUMENT_ME},
|
|
{&key_persist_variables, "m_LOCK_persist_variables", 0, 0,
|
|
PSI_DOCUMENT_ME}};
|
|
#endif /* HAVE_PSI_MUTEX_INTERFACE */
|
|
|
|
PSI_memory_key key_memory_persisted_variables;
|
|
|
|
#ifdef HAVE_PSI_MEMORY_INTERFACE
|
|
static PSI_memory_info all_options[] = {
|
|
{&key_memory_persisted_variables, "persisted_options_root", 0,
|
|
PSI_FLAG_ONLY_GLOBAL_STAT, PSI_DOCUMENT_ME}};
|
|
#endif /* HAVE_PSI_MEMORY_INTERFACE */
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
void my_init_persist_psi_keys(void) {
|
|
const char *category [[maybe_unused]] = "persist";
|
|
int count [[maybe_unused]];
|
|
|
|
#ifdef HAVE_PSI_FILE_INTERFACE
|
|
count = sizeof(all_persist_files) / sizeof(all_persist_files[0]);
|
|
mysql_file_register(category, all_persist_files, count);
|
|
#endif
|
|
|
|
#ifdef HAVE_PSI_MUTEX_INTERFACE
|
|
count = static_cast<int>(array_elements(all_persist_mutexes));
|
|
mysql_mutex_register(category, all_persist_mutexes, count);
|
|
#endif
|
|
|
|
#ifdef HAVE_PSI_MEMORY_INTERFACE
|
|
count = static_cast<int>(array_elements(all_options));
|
|
mysql_memory_register(category, all_options, count);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/** A comparison operator to sort persistent variables entries by timestamp */
|
|
struct sort_tv_by_timestamp {
|
|
bool operator()(const st_persist_var x, const st_persist_var y) const {
|
|
return x.timestamp < y.timestamp;
|
|
}
|
|
};
|
|
|
|
Persisted_variables_cache *Persisted_variables_cache::m_instance = nullptr;
|
|
|
|
namespace {
|
|
std::string tolower_varname(const char *name) {
|
|
std::string str(name);
|
|
std::transform(str.begin(), str.end(), str.begin(), tolower);
|
|
return str;
|
|
}
|
|
} // namespace
|
|
|
|
/* Standard Constructors for st_persist_var */
|
|
|
|
st_persist_var::st_persist_var() {
|
|
if (current_thd) {
|
|
const my_timeval tv =
|
|
current_thd->query_start_timeval_trunc(DATETIME_MAX_DECIMALS);
|
|
timestamp = tv.m_tv_sec * 1000000ULL + tv.m_tv_usec;
|
|
} else
|
|
timestamp = my_micro_time();
|
|
is_null = false;
|
|
}
|
|
|
|
st_persist_var::st_persist_var(THD *thd) {
|
|
const my_timeval tv = thd->query_start_timeval_trunc(DATETIME_MAX_DECIMALS);
|
|
timestamp = tv.m_tv_sec * 1000000ULL + tv.m_tv_usec;
|
|
user = thd->security_context()->user().str;
|
|
host = thd->security_context()->host().str;
|
|
is_null = false;
|
|
}
|
|
|
|
st_persist_var::st_persist_var(const std::string key, const std::string value,
|
|
const ulonglong timestamp,
|
|
const std::string user, const std::string host,
|
|
const bool is_null) {
|
|
this->key = key;
|
|
this->value = value;
|
|
this->timestamp = timestamp;
|
|
this->user = user;
|
|
this->host = host;
|
|
this->is_null = is_null;
|
|
}
|
|
|
|
Persisted_variables_cache::Persisted_variables_cache()
|
|
: m_persisted_static_sensitive_variables(
|
|
key_memory_persisted_variables_unordered_map),
|
|
m_persisted_static_parse_early_variables(
|
|
key_memory_persisted_variables_unordered_map),
|
|
m_persisted_static_variables(
|
|
key_memory_persisted_variables_unordered_map),
|
|
m_persisted_dynamic_parse_early_variables(
|
|
key_memory_persisted_variables_unordered_set),
|
|
m_persisted_dynamic_sensitive_variables(
|
|
key_memory_persisted_variables_unordered_set),
|
|
m_persisted_dynamic_variables(
|
|
key_memory_persisted_variables_unordered_set),
|
|
m_persisted_dynamic_sensitive_plugin_variables(
|
|
key_memory_persisted_variables_unordered_set),
|
|
m_persisted_dynamic_plugin_variables(
|
|
key_memory_persisted_variables_unordered_set) {}
|
|
|
|
/**
|
|
Initialize class members. This function reads datadir if present in
|
|
config file or set at command line, in order to know from where to
|
|
load this config file. If datadir is not set then read from MYSQL_DATADIR.
|
|
|
|
@param [in] argc Pointer to argc of original program
|
|
@param [in] argv Pointer to argv of original program
|
|
|
|
@return 0 Success
|
|
@return 1 Failure
|
|
|
|
*/
|
|
int Persisted_variables_cache::init(int *argc, char ***argv) {
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
my_init_persist_psi_keys();
|
|
#endif
|
|
|
|
int temp_argc = *argc;
|
|
MEM_ROOT alloc{key_memory_persisted_variables, 512};
|
|
char *ptr, **res, *datadir = nullptr;
|
|
char dir[FN_REFLEN] = {0}, local_datadir_buffer[FN_REFLEN] = {0};
|
|
const char *dirs = nullptr;
|
|
bool persist_load = true;
|
|
|
|
my_option persist_options[] = {
|
|
{"persisted_globals_load", 0, "", &persist_load, &persist_load, nullptr,
|
|
GET_BOOL, OPT_ARG, 1, 0, 0, nullptr, 0, nullptr},
|
|
{"datadir", 0, "", &datadir, nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0,
|
|
nullptr, 0, nullptr},
|
|
{nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
|
|
0, nullptr, 0, nullptr}};
|
|
|
|
/* create temporary args list and pass it to handle_options */
|
|
if (!(ptr =
|
|
(char *)alloc.Alloc(sizeof(alloc) + (*argc + 1) * sizeof(char *))))
|
|
return 1;
|
|
memset(ptr, 0, (sizeof(char *) * (*argc + 1)));
|
|
res = (char **)(ptr);
|
|
memcpy((uchar *)res, (char *)(*argv), (*argc) * sizeof(char *));
|
|
|
|
my_getopt_skip_unknown = true;
|
|
if (my_handle_options(&temp_argc, &res, persist_options, nullptr, nullptr,
|
|
true)) {
|
|
alloc.Clear();
|
|
return 1;
|
|
}
|
|
my_getopt_skip_unknown = false;
|
|
alloc.Clear();
|
|
|
|
persisted_globals_load = persist_load;
|
|
|
|
if (!datadir) {
|
|
// mysql_real_data_home must be initialized at this point
|
|
assert(mysql_real_data_home[0]);
|
|
/*
|
|
mysql_home_ptr should also be initialized at this point.
|
|
See calculate_mysql_home_from_my_progname() for details
|
|
*/
|
|
assert(mysql_home_ptr && mysql_home_ptr[0]);
|
|
convert_dirname(local_datadir_buffer, mysql_real_data_home, NullS);
|
|
(void)my_load_path(local_datadir_buffer, local_datadir_buffer,
|
|
mysql_home_ptr);
|
|
datadir = local_datadir_buffer;
|
|
}
|
|
|
|
dirs = datadir;
|
|
unpack_dirname(dir, dirs);
|
|
my_realpath(datadir_buffer, dir, MYF(0));
|
|
unpack_dirname(datadir_buffer, datadir_buffer);
|
|
if (fn_format(dir, MYSQL_PERSIST_CONFIG_NAME, datadir_buffer, ".cnf",
|
|
MY_UNPACK_FILENAME | MY_SAFE_PATH) == nullptr)
|
|
return 1;
|
|
m_persist_filename = string(dir);
|
|
m_persist_backup_filename = m_persist_filename + ".backup";
|
|
|
|
mysql_mutex_init(key_persist_variables, &m_LOCK_persist_variables,
|
|
MY_MUTEX_INIT_FAST);
|
|
|
|
mysql_mutex_init(key_persist_file, &m_LOCK_persist_file, MY_MUTEX_INIT_FAST);
|
|
|
|
m_instance = this;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Return a singleton object
|
|
*/
|
|
Persisted_variables_cache *Persisted_variables_cache::get_instance() {
|
|
assert(m_instance != nullptr);
|
|
return m_instance;
|
|
}
|
|
|
|
/**
|
|
For boolean variable types do validation on what value is set for the
|
|
variable and then report error in case an invalid value is set.
|
|
|
|
@param [in] value Value which needs to be checked for.
|
|
@param [out] bool_str Target String into which correct value needs to be
|
|
stored after validation.
|
|
|
|
@return true Failure if value is set to anything other than "true", "on",
|
|
"1", "false" , "off", "0"
|
|
@return false Success
|
|
*/
|
|
static bool check_boolean_value(const char *value, String &bool_str) {
|
|
bool ret = false;
|
|
const bool result = get_bool_argument(value, &ret);
|
|
if (ret) return true;
|
|
if (result) {
|
|
bool_str = String("ON", system_charset_info);
|
|
} else {
|
|
bool_str = String("OFF", system_charset_info);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Retrieve variables name/value and update the in-memory copy with
|
|
this new values. If value is default then remove this entry from
|
|
in-memory copy, else update existing key with new value
|
|
|
|
@param [in] thd Pointer to connection handler
|
|
@param [in] setvar Pointer to set_var which is being SET
|
|
|
|
@return true Failure
|
|
@return false Success
|
|
*/
|
|
bool Persisted_variables_cache::set_variable(THD *thd, set_var *setvar) {
|
|
auto f = [this, thd, setvar](const System_variable_tracker &,
|
|
sys_var *system_var) -> bool {
|
|
char val_buf[1024] = {0};
|
|
String utf8_str;
|
|
bool is_null = false;
|
|
|
|
std::string var_name{tolower_varname(setvar->m_var_tracker.get_var_name())};
|
|
|
|
// 1. Fetch value into local variable var_value.
|
|
|
|
const char *var_value = val_buf;
|
|
if (setvar->type == OPT_PERSIST_ONLY) {
|
|
String str(val_buf, sizeof(val_buf), system_charset_info), *res;
|
|
const CHARSET_INFO *tocs = &my_charset_utf8mb4_bin;
|
|
uint dummy_err;
|
|
String bool_str;
|
|
if (setvar->value) {
|
|
res = setvar->value->val_str(&str);
|
|
if (setvar->value->is_null()) is_null = true;
|
|
if (system_var->get_var_type() == GET_BOOL) {
|
|
if (res == nullptr ||
|
|
check_boolean_value(res->c_ptr_quick(), bool_str)) {
|
|
my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var_name.c_str(),
|
|
(res ? res->c_ptr_quick() : "null"));
|
|
return true;
|
|
} else {
|
|
res = &bool_str;
|
|
}
|
|
}
|
|
if (res && res->length()) {
|
|
/*
|
|
value held by Item class can be of different charset,
|
|
so convert to utf8mb4
|
|
*/
|
|
utf8_str.copy(res->ptr(), res->length(), res->charset(), tocs,
|
|
&dummy_err);
|
|
var_value = utf8_str.c_ptr_quick();
|
|
}
|
|
} else {
|
|
/* persist default value */
|
|
system_var->save_default(thd, setvar);
|
|
system_var->saved_value_to_string(thd, setvar, str.ptr());
|
|
res = &str;
|
|
if (system_var->get_var_type() == GET_BOOL) {
|
|
check_boolean_value(res->c_ptr_quick(), bool_str);
|
|
res = &bool_str;
|
|
}
|
|
utf8_str.copy(res->ptr(), res->length(), res->charset(), tocs,
|
|
&dummy_err);
|
|
var_value = utf8_str.c_ptr_quick();
|
|
}
|
|
} else {
|
|
Persisted_variables_cache::get_variable_value(thd, system_var, &utf8_str,
|
|
&is_null);
|
|
var_value = utf8_str.c_ptr_quick();
|
|
}
|
|
|
|
// 2. Store local variable var_value into member st_persist_var object.
|
|
|
|
auto assign_value = [&](const char *name) -> bool {
|
|
struct st_persist_var tmp_var(thd);
|
|
const bool is_sensitive = system_var->is_sensitive();
|
|
|
|
if (is_sensitive && !m_keyring_support_available) {
|
|
if (!opt_persist_sensitive_variables_in_plaintext) {
|
|
my_error(ER_CANNOT_PERSIST_SENSITIVE_VARIABLES, MYF(0), name);
|
|
return true;
|
|
} else {
|
|
push_warning_printf(
|
|
thd, Sql_condition::SL_WARNING,
|
|
ER_WARN_CANNOT_SECURELY_PERSIST_SENSITIVE_VARIABLES,
|
|
ER_THD(thd, ER_WARN_CANNOT_SECURELY_PERSIST_SENSITIVE_VARIABLES),
|
|
name);
|
|
}
|
|
}
|
|
|
|
tmp_var.key = string(name);
|
|
tmp_var.value = var_value;
|
|
tmp_var.is_null = is_null;
|
|
|
|
/* modification to in-memory must be thread safe */
|
|
lock();
|
|
DEBUG_SYNC(thd, "in_set_persist_variables");
|
|
if ((setvar->type == OPT_PERSIST_ONLY && system_var->is_readonly()) ||
|
|
system_var->is_persist_readonly()) {
|
|
/* if present update variable with new value else insert into hash */
|
|
if (system_var->is_parse_early()) {
|
|
m_persisted_static_parse_early_variables[tmp_var.key] = tmp_var;
|
|
} else {
|
|
auto &variables = is_sensitive
|
|
? m_persisted_static_sensitive_variables
|
|
: m_persisted_static_variables;
|
|
variables[tmp_var.key] = tmp_var;
|
|
/**
|
|
If server was upgraded, it is possible that persisted variables were
|
|
initially read from an old format file. If so, all RO variables:
|
|
PARSE_EARLY or otherwise, persisted before the upgrade may be
|
|
present in m_persisted_static_parse_early_variables container.
|
|
|
|
This SET PERSIST/SET PERSIST_ONLY call may be setting one of those
|
|
variables. If so, remove those values from
|
|
m_persisted_static_parse_early_variables.
|
|
*/
|
|
auto it = m_persisted_static_parse_early_variables.find(name);
|
|
if (it != m_persisted_static_parse_early_variables.end()) {
|
|
m_persisted_static_parse_early_variables.erase(it);
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
if element is present remove it and insert
|
|
it again with new value.
|
|
*/
|
|
if (system_var->is_parse_early()) {
|
|
m_persisted_dynamic_parse_early_variables.erase(tmp_var);
|
|
m_persisted_dynamic_parse_early_variables.insert(tmp_var);
|
|
/*
|
|
If server was upgraded, it is possible that persisted variables were
|
|
initially read from an old format file. If so, all variables:
|
|
PARSE_EARLY or otherwise, persisted before the upgrade may be
|
|
present in m_persisted_dynamic_variables container.
|
|
|
|
This SET PERSIST/SET PERSIST_ONLY call may be setting one of those
|
|
variables. If so, remove those values from
|
|
m_persisted_dynamic_variables.
|
|
*/
|
|
m_persisted_dynamic_variables.erase(tmp_var);
|
|
} else {
|
|
auto &variables = is_sensitive
|
|
? m_persisted_dynamic_sensitive_variables
|
|
: m_persisted_dynamic_variables;
|
|
variables.erase(tmp_var);
|
|
variables.insert(tmp_var);
|
|
/*
|
|
for plugin variables update m_persisted_dynamic_plugin_variables
|
|
or m_persisted_dynamic_sensitive_plugin_variables
|
|
*/
|
|
if (system_var->cast_pluginvar()) {
|
|
auto &plugin_variables =
|
|
system_var->is_sensitive()
|
|
? m_persisted_dynamic_sensitive_plugin_variables
|
|
: m_persisted_dynamic_plugin_variables;
|
|
plugin_variables.erase(tmp_var);
|
|
plugin_variables.insert(tmp_var);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setvar->type != OPT_PERSIST_ONLY) {
|
|
setvar->update_source_user_host_timestamp(thd, system_var);
|
|
}
|
|
unlock();
|
|
return false;
|
|
};
|
|
|
|
if (assign_value(var_name.c_str())) return true;
|
|
|
|
const char *alias_var_name = get_variable_alias(system_var);
|
|
|
|
if (alias_var_name) assign_value(alias_var_name);
|
|
|
|
return false;
|
|
};
|
|
|
|
return setvar->m_var_tracker.access_system_variable<bool>(thd, f).value_or(
|
|
true);
|
|
}
|
|
|
|
/**
|
|
Retrieve variables value from sys_var
|
|
|
|
@param [in] thd Pointer to connection handler
|
|
@param [in] system_var Pointer to sys_var which is being SET
|
|
@param [in] str Pointer to String instance into which value
|
|
is copied
|
|
@param [out] is_null Is value NULL or not.
|
|
|
|
@return
|
|
Pointer to String instance holding the value
|
|
*/
|
|
String *Persisted_variables_cache::get_variable_value(THD *thd,
|
|
sys_var *system_var,
|
|
String *str,
|
|
bool *is_null) {
|
|
const char *value;
|
|
char val_buf[1024];
|
|
size_t val_length;
|
|
char show_var_buffer[sizeof(SHOW_VAR)];
|
|
SHOW_VAR *show = (SHOW_VAR *)show_var_buffer;
|
|
const CHARSET_INFO *fromcs;
|
|
const CHARSET_INFO *tocs = &my_charset_utf8mb4_bin;
|
|
uint dummy_err;
|
|
|
|
show->type = SHOW_SYS;
|
|
show->name = system_var->name.str;
|
|
show->value = (char *)system_var;
|
|
|
|
mysql_mutex_lock(&LOCK_global_system_variables);
|
|
value = get_one_variable(thd, show, OPT_GLOBAL, show->type, nullptr, &fromcs,
|
|
val_buf, &val_length, is_null);
|
|
/* convert the retrieved value to utf8mb4 */
|
|
str->copy(value, val_length, fromcs, tocs, &dummy_err);
|
|
mysql_mutex_unlock(&LOCK_global_system_variables);
|
|
return str;
|
|
}
|
|
|
|
const char *Persisted_variables_cache::get_variable_alias(
|
|
const sys_var *system_var) {
|
|
if (system_var->m_persisted_alias)
|
|
return system_var->m_persisted_alias->name.str;
|
|
return nullptr;
|
|
}
|
|
|
|
std::string Persisted_variables_cache::get_variable_alias(const char *name) {
|
|
auto f = [](const System_variable_tracker &, sys_var *sysvar) -> std::string {
|
|
const char *ret = get_variable_alias(sysvar);
|
|
return ret ? ret : std::string{};
|
|
};
|
|
return System_variable_tracker::make_tracker(name)
|
|
.access_system_variable<std::string>(current_thd, f,
|
|
Suppress_not_found_error::YES)
|
|
.value_or(std::string{});
|
|
}
|
|
|
|
static bool format_json(const st_persist_var &entry,
|
|
Json_object §ion_object) {
|
|
/*
|
|
Create metadata array
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
*/
|
|
Json_object object_metadata;
|
|
Json_uint value_timestamp(entry.timestamp);
|
|
if (object_metadata.add_clone(s_key_timestamp, &value_timestamp)) return true;
|
|
|
|
Json_string value_user(entry.user.c_str());
|
|
if (object_metadata.add_clone(s_key_user, &value_user)) return true;
|
|
|
|
Json_string value_host(entry.host.c_str());
|
|
if (object_metadata.add_clone(s_key_host, &value_host)) return true;
|
|
|
|
/*
|
|
Create variable object
|
|
|
|
"variable_name" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
*/
|
|
Json_object variable;
|
|
|
|
Json_string value_value(entry.value.c_str());
|
|
if (variable.add_clone(s_key_value, &value_value)) return true;
|
|
|
|
if (variable.add_clone(s_key_metadata, &object_metadata)) return true;
|
|
|
|
/* Push object to array */
|
|
if (section_object.add_clone(entry.key, &variable)) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool add_json_object(Json_object §ion_object,
|
|
Json_object &json_object, const char *key_section) {
|
|
if (section_object.cardinality() > 0) {
|
|
if (json_object.add_clone(key_section, §ion_object)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool format_set(Persisted_variables_uset §ion, string key_section,
|
|
Json_object &json_object) {
|
|
Json_object section_object;
|
|
for (auto &it : section) {
|
|
if (format_json(it, section_object)) return true;
|
|
}
|
|
if (add_json_object(section_object, json_object, key_section.c_str()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool format_map(Persisted_variables_umap §ion, string key_section,
|
|
Json_object &json_object) {
|
|
Json_object section_object;
|
|
for (auto &it : section) {
|
|
if (format_json(it.second, section_object)) return true;
|
|
}
|
|
if (add_json_object(section_object, json_object, key_section.c_str()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Persisted_variables_cache::write_persist_file_v2(String &dest,
|
|
bool &clean_up) {
|
|
clean_up = false;
|
|
Json_object main_json_object;
|
|
const string key_version("Version");
|
|
Json_int value_version(static_cast<int>(File_version::VERSION_V2));
|
|
main_json_object.add_clone(key_version.c_str(), &value_version);
|
|
|
|
/**
|
|
If original file was of File_version::VERSION_V1, some of the variables
|
|
which may belong to object "mysql_static_variables" could be part of
|
|
"mysql_static_parse_early_variables" object. This is because we move
|
|
such variables to "mysql_static_variables" only when SET PERSIST or
|
|
SET PERSIST_ONLY is executed for them.
|
|
*/
|
|
if (format_map(m_persisted_static_parse_early_variables,
|
|
s_key_mysql_static_parse_early_variables, main_json_object))
|
|
return true;
|
|
|
|
if (format_set(m_persisted_dynamic_parse_early_variables,
|
|
s_key_mysql_dynamic_parse_early_variables, main_json_object))
|
|
return true;
|
|
|
|
if (format_map(m_persisted_static_variables, s_key_mysql_static_variables,
|
|
main_json_object))
|
|
return true;
|
|
|
|
if (format_set(m_persisted_dynamic_variables, s_key_mysql_dynamic_variables,
|
|
main_json_object))
|
|
return true;
|
|
|
|
Json_object sensitive_variables_object;
|
|
auto encryption_success = encrypt_sensitive_variables();
|
|
|
|
if (encryption_success != return_status::NOT_REQUIRED) {
|
|
if (encryption_success == return_status::ERROR &&
|
|
opt_persist_sensitive_variables_in_plaintext == false) {
|
|
LogErr(WARNING_LEVEL, ER_WARN_CANNOT_PERSIST_SENSITIVE_VARIABLES);
|
|
return true;
|
|
}
|
|
|
|
auto add_string = [](Json_object &object, const std::string key,
|
|
const std::string &value) -> bool {
|
|
if (value.length() == 0) return false;
|
|
Json_string string_value(value.c_str());
|
|
return object.add_clone(key, &string_value);
|
|
};
|
|
|
|
if (add_string(sensitive_variables_object, s_key_master_key_id,
|
|
m_key_info.m_master_key_name))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object, s_key_file_key,
|
|
m_key_info.m_file_key))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object, s_key_file_key_iv,
|
|
m_key_info.m_file_key_iv))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object, s_key_key_encryption_algorithm,
|
|
"AES_256_CBC"))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object,
|
|
s_key_mysql_sensitive_variables_blob,
|
|
m_sensitive_variables_blob))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object,
|
|
s_key_mysql_sensitive_variables_iv, m_iv))
|
|
return true;
|
|
|
|
if (add_string(sensitive_variables_object, s_key_data_encryption_algorithm,
|
|
"AES_256_CBC"))
|
|
return true;
|
|
|
|
if (encryption_success == return_status::ERROR) {
|
|
if (format_set(m_persisted_dynamic_sensitive_variables,
|
|
s_key_mysql_sensitive_dynamic_variables,
|
|
sensitive_variables_object))
|
|
return true;
|
|
if (format_map(m_persisted_static_sensitive_variables,
|
|
s_key_mysql_sensitive_static_variables,
|
|
sensitive_variables_object))
|
|
return true;
|
|
}
|
|
|
|
if (add_json_object(sensitive_variables_object, main_json_object,
|
|
s_key_mysql_sensitive_variables))
|
|
return true;
|
|
}
|
|
|
|
Json_wrapper json_wrapper(&main_json_object);
|
|
json_wrapper.set_alias();
|
|
String str;
|
|
json_wrapper.to_string(&str, true, String().ptr(), JsonDepthErrorHandler);
|
|
dest.append(str);
|
|
|
|
if (encryption_success == return_status::SUCCESS) {
|
|
/*
|
|
If we succeeded in writing sensitive variables in blob, clear them
|
|
before next write operation
|
|
*/
|
|
clean_up = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Convert in-memory copy into a stream of characters and write this
|
|
stream to persisted config file
|
|
|
|
@return Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::flush_to_file() {
|
|
lock();
|
|
mysql_mutex_lock(&m_LOCK_persist_file);
|
|
String dest("", &my_charset_utf8mb4_bin);
|
|
bool ret = true;
|
|
bool do_cleanup = false;
|
|
|
|
ret = write_persist_file_v2(dest, do_cleanup);
|
|
|
|
if (ret == true) {
|
|
mysql_mutex_unlock(&m_LOCK_persist_file);
|
|
unlock();
|
|
return ret;
|
|
}
|
|
/*
|
|
Always write to backup file. Once write is successful, rename backup
|
|
file to original file.
|
|
*/
|
|
if (open_persist_backup_file(O_CREAT | O_WRONLY)) {
|
|
ret = true;
|
|
} else {
|
|
DBUG_EXECUTE_IF("crash_after_open_persist_file", DBUG_SUICIDE(););
|
|
/* write to file */
|
|
if (mysql_file_fputs(dest.c_ptr(), m_fd) < 0) {
|
|
ret = true;
|
|
} else {
|
|
DBUG_EXECUTE_IF("crash_after_write_persist_file", DBUG_SUICIDE(););
|
|
/* flush contents to disk immediately */
|
|
if ((mysql_file_fflush(m_fd) != 0) ||
|
|
(my_sync(my_fileno(m_fd->m_file), MYF(MY_WME)) == -1))
|
|
ret = true;
|
|
}
|
|
close_persist_file();
|
|
}
|
|
if (!ret) {
|
|
DBUG_EXECUTE_IF("crash_after_close_persist_file", DBUG_SUICIDE(););
|
|
if (my_rename(m_persist_backup_filename.c_str(), m_persist_filename.c_str(),
|
|
MYF(MY_WME)))
|
|
ret = true;
|
|
DBUG_EXECUTE_IF("crash_after_rename_persist_file", DBUG_SUICIDE(););
|
|
}
|
|
if (ret == false && do_cleanup == true) clear_sensitive_blob_and_iv();
|
|
mysql_mutex_unlock(&m_LOCK_persist_file);
|
|
unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Open persisted config file
|
|
|
|
@param [in] flag File open mode
|
|
@return Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::open_persist_file(int flag) {
|
|
/*
|
|
If file does not exists create one. When persisted_globals_load is 0
|
|
we dont read contents of mysqld-auto.cnf file, thus append any new
|
|
variables which are persisted to this file.
|
|
*/
|
|
if (m_fd) return 1;
|
|
m_fd = mysql_file_fopen(key_persist_file_cnf, m_persist_filename.c_str(),
|
|
flag, MYF(0));
|
|
|
|
return (m_fd ? 0 : 1);
|
|
}
|
|
|
|
/**
|
|
Open persisted backup config file
|
|
|
|
@param [in] flag file open mode
|
|
@return Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::open_persist_backup_file(int flag) {
|
|
if (m_fd) return 1;
|
|
m_fd = mysql_file_fopen(key_persist_file_cnf,
|
|
m_persist_backup_filename.c_str(), flag, MYF(0));
|
|
return (m_fd ? 0 : 1);
|
|
}
|
|
|
|
/**
|
|
Close persisted config file.
|
|
*/
|
|
void Persisted_variables_cache::close_persist_file() {
|
|
mysql_file_fclose(m_fd, MYF(0));
|
|
m_fd = nullptr;
|
|
}
|
|
|
|
/**
|
|
load_persist_file() read persisted config file
|
|
|
|
@return Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::load_persist_file() {
|
|
if (read_persist_file() > 0) return true;
|
|
return false;
|
|
}
|
|
|
|
void Persisted_variables_cache::set_parse_early_sources() {
|
|
/* create a sorted set of values sorted by timestamp */
|
|
std::multiset<st_persist_var, sort_tv_by_timestamp> sorted_vars;
|
|
for (auto &iter : m_persisted_static_parse_early_variables)
|
|
sorted_vars.insert(iter.second);
|
|
sorted_vars.insert(m_persisted_dynamic_parse_early_variables.begin(),
|
|
m_persisted_dynamic_parse_early_variables.end());
|
|
|
|
for (auto &it : sorted_vars) {
|
|
auto sysvar = intern_find_sys_var(it.key.c_str(), it.key.length());
|
|
if (sysvar == nullptr) {
|
|
continue;
|
|
}
|
|
sysvar->set_source(enum_variable_source::PERSISTED);
|
|
#ifndef NDEBUG
|
|
bool source_truncated =
|
|
#endif
|
|
sysvar->set_source_name(m_persist_filename.c_str());
|
|
assert(!source_truncated);
|
|
sysvar->set_timestamp(it.timestamp);
|
|
if (sysvar->set_user(it.user.c_str()))
|
|
LogErr(WARNING_LEVEL, ER_PERSIST_OPTION_USER_TRUNCATED, it.key.c_str());
|
|
if (sysvar->set_host(it.host.c_str()))
|
|
LogErr(WARNING_LEVEL, ER_PERSIST_OPTION_HOST_TRUNCATED, it.key.c_str());
|
|
}
|
|
}
|
|
|
|
/**
|
|
set_persisted_options() will set the options read from persisted config file
|
|
|
|
This function does nothing when --no-defaults is set or if
|
|
persisted_globals_load is set to false.
|
|
Initial call to set_persisted_options(false) is needed to initialize
|
|
m_persisted_dynamic_plugin_variables set, so that next subsequent
|
|
set_persisted_options(true) calls will work with correct state.
|
|
|
|
@param [in] plugin_options Flag which tells what options are being set.
|
|
If set to false non dynamically-registered
|
|
system variables are set
|
|
else plugin- and component-registered
|
|
variables are set.
|
|
@param [in] target_var_name If not-null the name of variable to try and
|
|
set from the persisted cache values
|
|
@param [in] target_var_name_length length of target_var_name
|
|
@return Error state
|
|
@retval true An error occurred
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::set_persisted_options(
|
|
bool plugin_options, const char *target_var_name,
|
|
int target_var_name_length) {
|
|
THD *thd;
|
|
const bool result = false;
|
|
bool new_thd = false;
|
|
const std::vector<std::string> priv_list = {
|
|
"ENCRYPTION_KEY_ADMIN", "ROLE_ADMIN", "SYSTEM_VARIABLES_ADMIN",
|
|
"AUDIT_ADMIN", "TELEMETRY_LOG_ADMIN", "CONNECTION_ADMIN"};
|
|
const ulong static_priv_list = (SUPER_ACL | FILE_ACL);
|
|
Sctx_ptr<Security_context> ctx;
|
|
/*
|
|
if persisted_globals_load is set to false or --no-defaults is set
|
|
then do not set persistent options
|
|
*/
|
|
if (no_defaults || !persisted_globals_load) return false;
|
|
/*
|
|
This function is called in 3 places
|
|
1. During server startup, see mysqld_main()
|
|
2. During install plugin after server has started,
|
|
see update_persisted_plugin_sysvars()
|
|
3. During component installation after server has started,
|
|
see mysql_component_sys_variable_imp::register_variable
|
|
|
|
During server startup before server components are initialized
|
|
current_thd is NULL thus instantiate new temporary THD.
|
|
After server has started we have current_thd so make use of current_thd.
|
|
*/
|
|
if (current_thd) {
|
|
thd = current_thd;
|
|
} else {
|
|
if (!(thd = new THD)) {
|
|
LogErr(ERROR_LEVEL, ER_FAILED_TO_SET_PERSISTED_OPTIONS);
|
|
return true;
|
|
}
|
|
thd->thread_stack = (char *)&thd;
|
|
thd->set_new_thread_id();
|
|
thd->store_globals();
|
|
lex_start(thd);
|
|
/* create security context for bootstrap auth id */
|
|
Security_context_factory default_factory(
|
|
thd, "bootstrap", "localhost", Default_local_authid(thd),
|
|
Grant_temporary_dynamic_privileges(thd, priv_list),
|
|
Grant_temporary_static_privileges(thd, static_priv_list),
|
|
Drop_temporary_dynamic_privileges(priv_list));
|
|
ctx = default_factory.create();
|
|
/* attach this auth id to current security_context */
|
|
thd->set_security_context(ctx.get());
|
|
thd->real_id = my_thread_self();
|
|
#ifndef NDEBUG
|
|
thd->for_debug_only_is_set_persist_options = true;
|
|
#endif
|
|
new_thd = true;
|
|
alloc_and_copy_thd_dynamic_variables(thd, !plugin_options);
|
|
}
|
|
if (plugin_options) {
|
|
assert(!new_thd);
|
|
}
|
|
/*
|
|
locking is not needed as this function is executed only during server
|
|
bootstrap, but we take the lock to be on safer side.
|
|
*/
|
|
lock();
|
|
assert_lock_owner();
|
|
/*
|
|
Based on plugin_options, we decide on what options to be set. If
|
|
plugin_options is false we set all non plugin variables and then
|
|
keep all plugin variables in a map. When the plugin is installed
|
|
plugin variables are read from the map and set.
|
|
*/
|
|
auto &persist_variables =
|
|
(plugin_options ? m_persisted_dynamic_plugin_variables
|
|
: m_persisted_dynamic_variables);
|
|
auto &persist_sensitive_variables =
|
|
(plugin_options ? m_persisted_dynamic_sensitive_plugin_variables
|
|
: m_persisted_dynamic_sensitive_variables);
|
|
|
|
/* create a sorted set of values sorted by timestamp */
|
|
std::multiset<st_persist_var, sort_tv_by_timestamp> sorted_vars;
|
|
|
|
/*
|
|
if a target variable is specified try to find and set only the variable
|
|
and not every value in the persist file
|
|
*/
|
|
if (target_var_name != nullptr && target_var_name_length > 0 &&
|
|
*target_var_name != 0) {
|
|
auto it = std::find_if(
|
|
persist_variables.begin(), persist_variables.end(),
|
|
[target_var_name, target_var_name_length](st_persist_var const &s) {
|
|
return !strncmp(s.key.c_str(), target_var_name,
|
|
target_var_name_length);
|
|
});
|
|
if (it != persist_variables.end()) sorted_vars.insert(*it);
|
|
auto sensitive_it = std::find_if(
|
|
persist_sensitive_variables.begin(), persist_sensitive_variables.end(),
|
|
[target_var_name, target_var_name_length](st_persist_var const &s) {
|
|
return !strncmp(s.key.c_str(), target_var_name,
|
|
target_var_name_length);
|
|
});
|
|
if (sensitive_it != persist_sensitive_variables.end())
|
|
sorted_vars.insert(*sensitive_it);
|
|
} else {
|
|
sorted_vars.insert(persist_variables.begin(), persist_variables.end());
|
|
sorted_vars.insert(persist_sensitive_variables.begin(),
|
|
persist_sensitive_variables.end());
|
|
}
|
|
|
|
for (const st_persist_var &iter : sorted_vars) {
|
|
const std::string &var_name = iter.key;
|
|
auto f = [this, thd, &iter, &var_name, plugin_options](
|
|
const System_variable_tracker &var_tracker,
|
|
sys_var *sysvar) -> bool {
|
|
Item *res = nullptr;
|
|
/*
|
|
For aliases with the m_is_persisted_deprecated flag set, the
|
|
non-alias has its own entry in m_persisted_dynamic_variables. Therefore,
|
|
we rely on setting the value for the non-alias and skip setting
|
|
the value for the alias.
|
|
|
|
It would be harmless to set the value also for the alias, except
|
|
it would generate an extra deprecation warning. The correct
|
|
deprecation warning was already generated, if needed, in the
|
|
previous call to load_aliases().
|
|
*/
|
|
if (!(get_variable_alias(sysvar) && sysvar->m_is_persisted_deprecated)) {
|
|
switch (sysvar->show_type()) {
|
|
case SHOW_INT:
|
|
case SHOW_LONG:
|
|
case SHOW_LONGLONG:
|
|
case SHOW_HA_ROWS:
|
|
res = new (thd->mem_root)
|
|
Item_uint(iter.value.c_str(), (uint)iter.value.length());
|
|
break;
|
|
case SHOW_SIGNED_INT:
|
|
case SHOW_SIGNED_LONG:
|
|
case SHOW_SIGNED_LONGLONG:
|
|
res = new (thd->mem_root)
|
|
Item_int(iter.value.c_str(), (uint)iter.value.length());
|
|
break;
|
|
case SHOW_CHAR:
|
|
case SHOW_LEX_STRING:
|
|
case SHOW_BOOL:
|
|
case SHOW_MY_BOOL:
|
|
res = new (thd->mem_root)
|
|
Item_string(iter.value.c_str(), iter.value.length(),
|
|
&my_charset_utf8mb4_bin);
|
|
break;
|
|
case SHOW_CHAR_PTR:
|
|
if (iter.is_null)
|
|
res = new (thd->mem_root) Item_null();
|
|
else
|
|
res = new (thd->mem_root)
|
|
Item_string(iter.value.c_str(), iter.value.length(),
|
|
&my_charset_utf8mb4_bin);
|
|
break;
|
|
case SHOW_DOUBLE:
|
|
res = new (thd->mem_root)
|
|
Item_float(iter.value.c_str(), (uint)iter.value.length());
|
|
break;
|
|
default:
|
|
my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), sysvar->name.str);
|
|
return true;
|
|
}
|
|
|
|
set_var *var =
|
|
new (thd->mem_root) set_var(OPT_GLOBAL, var_tracker, res);
|
|
List<set_var_base> tmp_var_list;
|
|
tmp_var_list.push_back(var);
|
|
LEX *saved_lex = thd->lex, lex_tmp;
|
|
thd->lex = &lex_tmp;
|
|
lex_start(thd);
|
|
if (sql_set_variables(thd, &tmp_var_list, false)) {
|
|
thd->lex = saved_lex;
|
|
/*
|
|
If there is a connection and an error occurred during install
|
|
plugin then report error at sql layer, else log the error in
|
|
server log.
|
|
*/
|
|
if (current_thd && plugin_options) {
|
|
if (thd->is_error())
|
|
LogErr(ERROR_LEVEL, ER_PERSIST_OPTION_STATUS,
|
|
thd->get_stmt_da()->message_text());
|
|
else
|
|
my_error(ER_CANT_SET_PERSISTED, MYF(0));
|
|
} else {
|
|
if (thd->is_error())
|
|
LogErr(ERROR_LEVEL, ER_PERSIST_OPTION_STATUS,
|
|
thd->get_stmt_da()->message_text());
|
|
else
|
|
LogErr(ERROR_LEVEL, ER_FAILED_TO_SET_PERSISTED_OPTIONS);
|
|
}
|
|
return true;
|
|
}
|
|
thd->lex = saved_lex;
|
|
}
|
|
/*
|
|
Once persisted variables are SET in the server,
|
|
update variables source/user/timestamp/host from
|
|
m_persisted_dynamic_variables.
|
|
*/
|
|
auto set_source = [&](auto &variables) -> bool {
|
|
auto it = variables.find(iter);
|
|
if (it != variables.end()) {
|
|
/* persisted variable is found */
|
|
sysvar->set_source(enum_variable_source::PERSISTED);
|
|
#ifndef NDEBUG
|
|
bool source_truncated =
|
|
#endif
|
|
sysvar->set_source_name(m_persist_filename.c_str());
|
|
assert(!source_truncated);
|
|
sysvar->set_timestamp(it->timestamp);
|
|
if (sysvar->set_user(it->user.c_str()))
|
|
LogErr(WARNING_LEVEL, ER_PERSIST_OPTION_USER_TRUNCATED,
|
|
var_name.c_str());
|
|
if (sysvar->set_host(it->host.c_str()))
|
|
LogErr(WARNING_LEVEL, ER_PERSIST_OPTION_HOST_TRUNCATED,
|
|
var_name.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (set_source(m_persisted_dynamic_variables))
|
|
(void)set_source(m_persisted_dynamic_sensitive_variables);
|
|
|
|
/*
|
|
We need to keep the currently set persisted variable into the in-memory
|
|
copy for plugin vars for further UNINSTALL followed by INSTALL sans
|
|
restart.
|
|
*/
|
|
if (sysvar->cast_pluginvar() && !plugin_options) {
|
|
auto &plugin_vars =
|
|
m_persisted_dynamic_sensitive_plugin_variables.find(iter) ==
|
|
m_persisted_dynamic_sensitive_plugin_variables.end()
|
|
? m_persisted_dynamic_plugin_variables
|
|
: m_persisted_dynamic_sensitive_plugin_variables;
|
|
#ifndef NDEBUG
|
|
auto ret =
|
|
#endif
|
|
plugin_vars.insert(iter);
|
|
// the value should not be present in the plugins copy
|
|
assert(ret.second);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
/*
|
|
There are currently 4 groups of code paths to call the current
|
|
function Persisted_variables_cache::set_persisted_options():
|
|
|
|
1. Directly from mysqld_main().
|
|
|
|
There is only one thread ATM (current_thd == nullptr), so locks aren't
|
|
necessary and we suppress them using the Is_single_thread::YES flag.
|
|
|
|
2. Indirectly from mysqld_main():
|
|
mysqld_main() ->
|
|
init_server_components() ->
|
|
plugin_register_builtin_and_init_core_se() ->
|
|
update_persisted_plugin_sysvars()
|
|
|
|
Ditto: Is_single_thread::YES.
|
|
|
|
3. Indirectly from mysql_install_plugin():
|
|
mysql_install_plugin() ->
|
|
update_persisted_plugin_sysvars()
|
|
|
|
sql_plugin.cc always acquire LOCK_system_variables_hash and
|
|
LOCK_plugin for us before calling method,
|
|
so, we suppress double locks with Is_already_locked::YES.
|
|
|
|
4. Directly from mysql_component_sys_variable_imp::register_variable().
|
|
|
|
mysql_component_sys_variable_imp::register_variable() holds
|
|
LOCK_plugin and LOCK_system_variables_hash for us, so,
|
|
we suppress double locks with Is_already_locked::YES.
|
|
|
|
Note: Is_single_thread::YES implies Is_already_locked::YES,
|
|
so, Is_already_locked is "YES" here unconditionally.
|
|
*/
|
|
std::optional<bool> sv_status =
|
|
System_variable_tracker::make_tracker(var_name)
|
|
.access_system_variable<bool>(
|
|
thd, f, Suppress_not_found_error::YES,
|
|
Force_sensitive_system_variable_access::YES,
|
|
Is_already_locked::YES,
|
|
new_thd ? Is_single_thread::YES : Is_single_thread::NO);
|
|
|
|
if (!sv_status.has_value()) { // not found
|
|
/*
|
|
for dynamically-registered variables we report a warning in error log,
|
|
keep track of this variable so that it is set when plugin
|
|
is loaded and continue with remaining persisted variables
|
|
*/
|
|
if (m_persisted_dynamic_plugin_variables.insert(iter).second)
|
|
LogErr(WARNING_LEVEL, ER_UNKNOWN_VARIABLE_IN_PERSISTED_CONFIG_FILE,
|
|
var_name.c_str());
|
|
continue;
|
|
} else if (sv_status.value()) {
|
|
break; // fatal error
|
|
}
|
|
}
|
|
|
|
/*
|
|
If the function is called at start-up time, set
|
|
source information for PARSE_EARLY variables
|
|
*/
|
|
if (!plugin_options) set_parse_early_sources();
|
|
|
|
if (new_thd) {
|
|
/* check for warnings in DA */
|
|
Diagnostics_area::Sql_condition_iterator it =
|
|
thd->get_stmt_da()->sql_conditions();
|
|
const Sql_condition *err = nullptr;
|
|
while ((err = it++)) {
|
|
if (err->severity() == Sql_condition::SL_WARNING) {
|
|
// Rewrite error number for "deprecated" to error log equivalent.
|
|
if (err->mysql_errno() == ER_WARN_DEPRECATED_SYNTAX)
|
|
LogEvent()
|
|
.type(LOG_TYPE_ERROR)
|
|
.prio(WARNING_LEVEL)
|
|
.errcode(ER_SERVER_WARN_DEPRECATED)
|
|
.verbatim(err->message_text());
|
|
/*
|
|
Any other (unexpected) message is wrapped to preserve its
|
|
original error number, and to explain the issue.
|
|
This is a failsafe; "expected", that is to say, common
|
|
messages should be handled explicitly like the deprecation
|
|
warning above.
|
|
*/
|
|
else
|
|
LogErr(WARNING_LEVEL, ER_ERROR_INFO_FROM_DA, err->mysql_errno(),
|
|
err->message_text());
|
|
}
|
|
}
|
|
thd->free_items();
|
|
lex_end(thd->lex);
|
|
thd->release_resources();
|
|
ctx.reset(nullptr);
|
|
delete thd;
|
|
}
|
|
unlock();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
extract_variables_from_json() is used to extract all the variable information
|
|
which is in the form of Json_object.
|
|
|
|
New format for mysqld-auto.cnf is as below:
|
|
{
|
|
"mysql_static_parse_early_variables": {
|
|
"variable_name_1" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
"variable_name_2" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
...
|
|
...
|
|
"variable_name_n" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
}
|
|
},
|
|
"mysql_dynamic_parse_early_variables": {
|
|
"variable_name_1" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
"variable_name_2" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
...
|
|
...
|
|
"variable_name_n" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
}
|
|
},
|
|
"mysql_static_variables": {
|
|
"variable_name_1" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
"variable_name_2" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
...
|
|
...
|
|
"variable_name_n" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
}
|
|
},
|
|
"mysql_dynamic_variables": {
|
|
"variable_name_1" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
"variable_name_2" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
},
|
|
...
|
|
...
|
|
"variable_name_n" : {
|
|
"Value" : "variable_value",
|
|
"Metadata" : {
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
}
|
|
},
|
|
"mysql_sensitive_variables" : {
|
|
"master_key": "<master_key_name>",
|
|
"mysql_file_key": "<ENCRYPTED_FILE_KEY_IN_HEX>",
|
|
"mysql_file_key_iv": "<IV_IN_HEX>",
|
|
"key_encryption_algorithm": "AES_256_CBC",
|
|
"mysql_sensitive_variables_blob":
|
|
"<SENSITIVE_VARIABLES_INFO_IN_ENCRYPTED_FORM_IN_HEX>",
|
|
"mysql_sensitive_variables_iv": "<IV_IN_HEX>",
|
|
"data_encryption_algorithm": "AES_256_CBC"
|
|
}
|
|
}
|
|
|
|
@param [in] dom Pointer to the Json_dom object which is an
|
|
internal representation of parsed json string
|
|
@param [in] is_read_only Bool value when set to TRUE extracts read only
|
|
variables and dynamic variables when set to FALSE.
|
|
|
|
@return 0 Success
|
|
@return 1 Failure
|
|
*/
|
|
bool Persisted_variables_cache::extract_variables_from_json(const Json_dom *dom,
|
|
bool is_read_only) {
|
|
if (dom->json_type() != enum_json_type::J_OBJECT) goto err;
|
|
for (auto &var_iter : *down_cast<const Json_object *>(dom)) {
|
|
string var_value, var_user, var_host;
|
|
ulonglong timestamp = 0;
|
|
bool is_null = false;
|
|
|
|
const string &var_name = var_iter.first;
|
|
if (var_iter.second->json_type() != enum_json_type::J_OBJECT) goto err;
|
|
const Json_object *dom_obj =
|
|
down_cast<const Json_object *>(var_iter.second.get());
|
|
|
|
/**
|
|
Static variables by themselves is represented as a json object with key
|
|
"mysql_server_static_options" as parent element.
|
|
*/
|
|
if (var_name == "mysql_server_static_options") {
|
|
if (extract_variables_from_json(dom_obj, true)) return true;
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
Every Json object which represents Variable information must have only
|
|
2 elements which is
|
|
{
|
|
"Value" : "variable_value", -- 1st element
|
|
"Metadata" : { -- 2nd element
|
|
"Timestamp" : timestamp_value,
|
|
"User" : "user_name",
|
|
"Host" : "host_name"
|
|
}
|
|
}
|
|
*/
|
|
if (dom_obj->depth() != 3 && dom_obj->cardinality() != 2) goto err;
|
|
|
|
Json_object::const_iterator var_properties_iter = dom_obj->begin();
|
|
/* extract variable value */
|
|
if (var_properties_iter->first != "Value") goto err;
|
|
|
|
const Json_dom *value = var_properties_iter->second.get();
|
|
/* if value is not in string form or null throw error. */
|
|
if (value->json_type() == enum_json_type::J_STRING) {
|
|
var_value = down_cast<const Json_string *>(value)->value();
|
|
} else if (value->json_type() == enum_json_type::J_NULL) {
|
|
var_value = "";
|
|
is_null = true;
|
|
} else {
|
|
goto err;
|
|
}
|
|
|
|
++var_properties_iter;
|
|
/* extract metadata */
|
|
if (var_properties_iter->first != "Metadata") goto err;
|
|
|
|
if (var_properties_iter->second->json_type() != enum_json_type::J_OBJECT)
|
|
goto err;
|
|
dom_obj = down_cast<const Json_object *>(var_properties_iter->second.get());
|
|
if (dom_obj->depth() != 1 && dom_obj->cardinality() != 3) goto err;
|
|
|
|
for (auto &metadata_iter : *dom_obj) {
|
|
const string &metadata_type = metadata_iter.first;
|
|
const Json_dom *metadata_value = metadata_iter.second.get();
|
|
if (metadata_type == "Timestamp") {
|
|
if (metadata_value->json_type() != enum_json_type::J_UINT) goto err;
|
|
const Json_uint *i = down_cast<const Json_uint *>(metadata_value);
|
|
timestamp = i->value();
|
|
} else if (metadata_type == "User" || metadata_type == "Host") {
|
|
if (metadata_value->json_type() != enum_json_type::J_STRING) goto err;
|
|
const Json_string *i = down_cast<const Json_string *>(metadata_value);
|
|
if (metadata_type == "User")
|
|
var_user = i->value();
|
|
else
|
|
var_host = i->value();
|
|
} else {
|
|
goto err;
|
|
}
|
|
}
|
|
st_persist_var persist_var(var_name, var_value, timestamp, var_user,
|
|
var_host, is_null);
|
|
lock();
|
|
assert_lock_owner();
|
|
if (is_read_only) {
|
|
/**
|
|
If we are reading from a v1 persisted file, all static options
|
|
including PARSE_EARLY options are stored in array
|
|
"mysql_server_static_options". Since there is no way to identify
|
|
PARSE_EARLY static variables, we push all these variables to
|
|
m_persisted_static_parse_early_variables.
|
|
*/
|
|
m_persisted_static_parse_early_variables[var_name] = persist_var;
|
|
} else {
|
|
m_persisted_dynamic_variables.insert(persist_var);
|
|
}
|
|
unlock();
|
|
}
|
|
return false;
|
|
|
|
err:
|
|
LogErr(ERROR_LEVEL, ER_JSON_PARSE_ERROR);
|
|
return true;
|
|
}
|
|
|
|
int Persisted_variables_cache::read_persist_file_v1(
|
|
const Json_object *json_object) {
|
|
Json_dom *options_dom = json_object->get("mysql_server");
|
|
if (options_dom == nullptr) {
|
|
LogErr(ERROR_LEVEL, ER_CONFIG_OPTION_WITHOUT_GROUP);
|
|
return 1;
|
|
}
|
|
/* Extract key/value pair and populate in a global hash map */
|
|
if (extract_variables_from_json(options_dom)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static bool extract_variable_value_and_metadata(const Json_object *json_object,
|
|
st_persist_var &output) {
|
|
if (json_object->json_type() != enum_json_type::J_OBJECT) return true;
|
|
|
|
Json_dom *dom = nullptr;
|
|
Json_string *value_string = nullptr;
|
|
Json_uint *value_uint = nullptr;
|
|
|
|
dom = json_object->get(s_key_value);
|
|
if (dom == nullptr || dom->json_type() != enum_json_type::J_STRING)
|
|
return true;
|
|
value_string = down_cast<Json_string *>(dom);
|
|
output.value.assign(value_string->value());
|
|
output.is_null = (output.value.length() == 0);
|
|
|
|
dom = json_object->get(s_key_metadata);
|
|
if (dom == nullptr || dom->json_type() != enum_json_type::J_OBJECT)
|
|
return true;
|
|
Json_object *object_metadata = down_cast<Json_object *>(dom);
|
|
|
|
dom = object_metadata->get(s_key_timestamp);
|
|
if (dom == nullptr || dom->json_type() != enum_json_type::J_UINT) return true;
|
|
value_uint = down_cast<Json_uint *>(dom);
|
|
output.timestamp = value_uint->value();
|
|
|
|
dom = object_metadata->get(s_key_user);
|
|
if (dom == nullptr || dom->json_type() != enum_json_type::J_STRING)
|
|
return true;
|
|
value_string = down_cast<Json_string *>(dom);
|
|
output.user.assign(value_string->value());
|
|
|
|
dom = object_metadata->get(s_key_host);
|
|
if (dom == nullptr || dom->json_type() != enum_json_type::J_STRING)
|
|
return true;
|
|
value_string = down_cast<Json_string *>(dom);
|
|
output.host.assign(value_string->value());
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool extract_set(const Json_object &json_object,
|
|
const std::string set_key,
|
|
Persisted_variables_uset &output) {
|
|
if (json_object.json_type() != enum_json_type::J_OBJECT) return true;
|
|
Json_dom *vector_dom = json_object.get(set_key);
|
|
/* Not having any entry is fine */
|
|
if (vector_dom == nullptr) return false;
|
|
if (vector_dom->json_type() != enum_json_type::J_OBJECT) return true;
|
|
Json_object *section_object = down_cast<Json_object *>(vector_dom);
|
|
for (auto &it : *section_object) {
|
|
/* Ignore problematic entry */
|
|
if (it.second == nullptr ||
|
|
it.second->json_type() != enum_json_type::J_OBJECT)
|
|
continue;
|
|
const Json_object *element =
|
|
down_cast<const Json_object *>(it.second.get());
|
|
st_persist_var entry;
|
|
entry.key = it.first;
|
|
/* Ignore problematic entry */
|
|
if (extract_variable_value_and_metadata(element, entry)) continue;
|
|
output.insert(entry);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool extract_map(const Json_object &json_object,
|
|
const std::string map_key,
|
|
Persisted_variables_umap &output) {
|
|
if (json_object.json_type() != enum_json_type::J_OBJECT) return true;
|
|
Json_dom *map_dom = json_object.get(map_key);
|
|
/* Not having any entry is fine */
|
|
if (map_dom == nullptr) return false;
|
|
if (map_dom->json_type() != enum_json_type::J_OBJECT) return true;
|
|
Json_object *section_object = down_cast<Json_object *>(map_dom);
|
|
for (auto &it : *section_object) {
|
|
/* Ignore problematic entry */
|
|
if (it.second == nullptr ||
|
|
it.second->json_type() != enum_json_type::J_OBJECT)
|
|
continue;
|
|
const Json_object *element =
|
|
down_cast<const Json_object *>(it.second.get());
|
|
st_persist_var entry;
|
|
entry.key = it.first;
|
|
/* Ignore problematic entry */
|
|
if (extract_variable_value_and_metadata(element, entry)) continue;
|
|
output[entry.key] = entry;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool extract_string(Json_object *obj, const std::string &key,
|
|
std::string &value) {
|
|
if (obj != nullptr) {
|
|
Json_dom *json_dom = obj->get(key);
|
|
if (json_dom != nullptr) {
|
|
if (json_dom->json_type() != enum_json_type::J_STRING) return true;
|
|
Json_string *string_dom = down_cast<Json_string *>(json_dom);
|
|
value = string_dom->value();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Persisted_variables_cache::read_persist_file_v2(
|
|
const Json_object *main_json_object) {
|
|
auto process_sensitive_section =
|
|
[](const Json_object &json_object, std::string &master_key_id,
|
|
std::string &file_key, std::string &file_key_iv,
|
|
std::string &sensitive_variables_blob, std::string &iv,
|
|
Json_object **sensitive_variables_section) -> bool {
|
|
sensitive_variables_blob.clear();
|
|
iv.clear();
|
|
if (json_object.json_type() != enum_json_type::J_OBJECT) return true;
|
|
Json_dom *section_dom = json_object.get(s_key_mysql_sensitive_variables);
|
|
if (section_dom == nullptr) return false;
|
|
if (section_dom->json_type() != enum_json_type::J_OBJECT) return true;
|
|
*sensitive_variables_section = down_cast<Json_object *>(section_dom);
|
|
|
|
if (extract_string((*sensitive_variables_section), s_key_master_key_id,
|
|
master_key_id))
|
|
return true;
|
|
|
|
if (extract_string((*sensitive_variables_section), s_key_file_key,
|
|
file_key))
|
|
return true;
|
|
if (extract_string((*sensitive_variables_section), s_key_file_key_iv,
|
|
file_key_iv))
|
|
return true;
|
|
|
|
string encryption_mode{};
|
|
if (extract_string((*sensitive_variables_section),
|
|
s_key_key_encryption_algorithm, encryption_mode))
|
|
return true;
|
|
|
|
std::transform(encryption_mode.begin(), encryption_mode.end(),
|
|
encryption_mode.begin(),
|
|
[](unsigned char c) { return std::toupper(c); });
|
|
|
|
if (encryption_mode != s_key_mysql_encryption_algorithm_default)
|
|
return true;
|
|
|
|
encryption_mode.clear();
|
|
|
|
if (extract_string((*sensitive_variables_section),
|
|
s_key_data_encryption_algorithm, encryption_mode))
|
|
return true;
|
|
|
|
std::transform(encryption_mode.begin(), encryption_mode.end(),
|
|
encryption_mode.begin(),
|
|
[](unsigned char c) { return std::toupper(c); });
|
|
|
|
if (encryption_mode != s_key_mysql_encryption_algorithm_default)
|
|
return true;
|
|
|
|
if (extract_string((*sensitive_variables_section),
|
|
s_key_mysql_sensitive_variables_blob,
|
|
sensitive_variables_blob))
|
|
return true;
|
|
|
|
if (extract_string((*sensitive_variables_section),
|
|
s_key_mysql_sensitive_variables_iv, iv))
|
|
return true;
|
|
return false;
|
|
};
|
|
|
|
lock();
|
|
assert_lock_owner();
|
|
|
|
if (extract_set(*main_json_object, s_key_mysql_dynamic_variables,
|
|
m_persisted_dynamic_variables) ||
|
|
extract_set(*main_json_object, s_key_mysql_dynamic_parse_early_variables,
|
|
m_persisted_dynamic_parse_early_variables) ||
|
|
extract_map(*main_json_object, s_key_mysql_static_variables,
|
|
m_persisted_static_variables) ||
|
|
extract_map(*main_json_object, s_key_mysql_static_parse_early_variables,
|
|
m_persisted_static_parse_early_variables)) {
|
|
unlock();
|
|
LogErr(ERROR_LEVEL, ER_JSON_PARSE_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
Json_object *sensitive_variables_section = nullptr;
|
|
|
|
if (process_sensitive_section(*main_json_object, m_key_info.m_master_key_name,
|
|
m_key_info.m_file_key, m_key_info.m_file_key_iv,
|
|
m_sensitive_variables_blob, m_iv,
|
|
&sensitive_variables_section)) {
|
|
unlock();
|
|
LogErr(ERROR_LEVEL, ER_JSON_PARSE_ERROR);
|
|
return 1;
|
|
}
|
|
|
|
if (sensitive_variables_section != nullptr) {
|
|
if (extract_set(*sensitive_variables_section,
|
|
s_key_mysql_sensitive_dynamic_variables,
|
|
m_persisted_dynamic_sensitive_variables) ||
|
|
extract_map(*sensitive_variables_section,
|
|
s_key_mysql_sensitive_static_variables,
|
|
m_persisted_static_sensitive_variables)) {
|
|
unlock();
|
|
LogErr(ERROR_LEVEL, ER_JSON_PARSE_ERROR);
|
|
return 1;
|
|
}
|
|
}
|
|
unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Persisted_variables_cache::load_aliases() {
|
|
// Store deprecation warnings in a set, so that we can report them
|
|
// in alphabetic order. This makes test cases more deterministic.
|
|
std::map<std::string, std::string> deprecated;
|
|
|
|
std::unordered_set<st_persist_var, st_persist_var_hash> var_set;
|
|
for (auto &var : m_persisted_dynamic_variables) var_set.insert(var);
|
|
|
|
/*
|
|
If variable has an alias, and it does not exist in the container,
|
|
insert the alias in container.
|
|
|
|
This lambda is agnostic to container type, taking arguments that
|
|
are functions that check for existing elements and insert
|
|
elements.
|
|
*/
|
|
auto insert_alias =
|
|
[&](std::function<bool(const char *)> exists,
|
|
std::function<void(st_persist_var &)> insert_in_container,
|
|
st_persist_var &var) {
|
|
auto *sysvar = intern_find_sys_var(var.key.c_str(), var.key.length());
|
|
if (sysvar) {
|
|
const char *alias = get_variable_alias(sysvar);
|
|
if (alias) {
|
|
if (!exists(alias)) {
|
|
st_persist_var alias_var{var};
|
|
alias_var.key = alias;
|
|
insert_in_container(alias_var);
|
|
if (sysvar->m_is_persisted_deprecated)
|
|
deprecated[alias] = var.key;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
lock();
|
|
if (current_thd != nullptr) mysql_mutex_assert_not_owner(&LOCK_plugin);
|
|
mysql_rwlock_rdlock(&LOCK_system_variables_hash);
|
|
|
|
for (auto iter : var_set) {
|
|
insert_alias(
|
|
[&](const char *name) -> bool {
|
|
auto it = std::find_if(var_set.begin(), var_set.end(),
|
|
[name](st_persist_var const &s) {
|
|
return !strcmp(s.key.c_str(), name);
|
|
});
|
|
return it != var_set.end();
|
|
},
|
|
[&](st_persist_var &v) { m_persisted_dynamic_variables.insert(v); },
|
|
iter);
|
|
}
|
|
|
|
var_set.clear();
|
|
for (auto &var : m_persisted_dynamic_sensitive_variables) var_set.insert(var);
|
|
for (auto iter : var_set) {
|
|
insert_alias(
|
|
[&](const char *name) -> bool {
|
|
auto it = std::find_if(var_set.begin(), var_set.end(),
|
|
[name](st_persist_var const &s) {
|
|
return !strcmp(s.key.c_str(), name);
|
|
});
|
|
return it != var_set.end();
|
|
},
|
|
[&](st_persist_var &v) {
|
|
m_persisted_dynamic_sensitive_variables.insert(v);
|
|
},
|
|
iter);
|
|
}
|
|
|
|
for (auto pair : m_persisted_static_variables) {
|
|
insert_alias(
|
|
[&](const char *name) -> bool {
|
|
return m_persisted_static_variables.find(name) !=
|
|
m_persisted_static_variables.end();
|
|
},
|
|
[&](st_persist_var &v) { m_persisted_static_variables[v.key] = v; },
|
|
pair.second);
|
|
}
|
|
|
|
for (auto pair : m_persisted_static_sensitive_variables) {
|
|
insert_alias(
|
|
[&](const char *name) -> bool {
|
|
return m_persisted_static_sensitive_variables.find(name) !=
|
|
m_persisted_static_sensitive_variables.end();
|
|
},
|
|
[&](st_persist_var &v) {
|
|
m_persisted_static_sensitive_variables[v.key] = v;
|
|
},
|
|
pair.second);
|
|
}
|
|
mysql_rwlock_unlock(&LOCK_system_variables_hash);
|
|
unlock();
|
|
|
|
// Generate deprecation warnings
|
|
for (auto pair : deprecated)
|
|
LogErr(WARNING_LEVEL, ER_DEPRECATED_PERSISTED_VARIABLE_WITH_ALIAS,
|
|
pair.second.c_str(), pair.first.c_str());
|
|
}
|
|
|
|
/**
|
|
Function to recategorize variables based on changes in their properties.
|
|
|
|
It is possible that during the course of development, we reclassify
|
|
certain variables. Such an action may have an impact on when and how
|
|
the persisted values of such variables are used/applied.
|
|
|
|
If such variables are persisted using previous versions of the server
|
|
binary, it is important that we move them to correct in-memory
|
|
containers so that,
|
|
1. Variables will be handled as required during bootstrap
|
|
2. Upon next change to persisted option file, variables will be
|
|
placed into appropriate JSON arrays.
|
|
*/
|
|
void Persisted_variables_cache::handle_option_type_change() {
|
|
std::array<std::string, 1> dynamic_to_parse_early_static = {"ssl_fips_mode"};
|
|
|
|
for (auto one : dynamic_to_parse_early_static) {
|
|
auto find_variable = [&one](st_persist_var const &s) -> bool {
|
|
return s.key == one;
|
|
};
|
|
auto it = std::find_if(m_persisted_dynamic_variables.begin(),
|
|
m_persisted_dynamic_variables.end(), find_variable);
|
|
if (it != m_persisted_dynamic_variables.end()) {
|
|
st_persist_var variable_info = *it;
|
|
m_persisted_static_parse_early_variables[one] = variable_info;
|
|
m_persisted_dynamic_variables.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
read_persist_file() reads the persisted config file
|
|
|
|
This function does following:
|
|
1. Read the persisted config file into a string buffer
|
|
2. This string buffer is parsed with JSON parser to check
|
|
if the format is correct or not.
|
|
3. Check for correct group name.
|
|
4. Extract key/value pair and populate in m_persisted_dynamic_variables,
|
|
m_persisted_static_variables.
|
|
mysqld-auto.cnf file will have variable properties like when a
|
|
variable is set, by wholm and on what host this variable was set.
|
|
|
|
@return Error state
|
|
@retval -1 or 1 Failure
|
|
@retval 0 Success
|
|
*/
|
|
int Persisted_variables_cache::read_persist_file() {
|
|
Json_dom_ptr json;
|
|
if ((check_file_permissions(m_persist_filename.c_str(), false)) < 2)
|
|
return -1;
|
|
|
|
auto read_file = [&]() -> bool {
|
|
string parsed_value;
|
|
char buff[4096] = {0};
|
|
do {
|
|
/* Read the persisted config file into a string buffer */
|
|
parsed_value.append(buff);
|
|
buff[0] = '\0';
|
|
} while (mysql_file_fgets(buff, sizeof(buff) - 1, m_fd));
|
|
close_persist_file();
|
|
/* parse the file contents to check if it is in json format or not */
|
|
json = Json_dom::parse(
|
|
parsed_value.c_str(), parsed_value.length(),
|
|
[](const char *, size_t) {}, JsonDepthErrorHandler);
|
|
if (!json.get()) return true;
|
|
return false;
|
|
};
|
|
|
|
if (!(open_persist_backup_file(O_RDONLY) == false && read_file() == false)) {
|
|
/*
|
|
if opening or reading of backup file failed, delete backup file
|
|
and read original file
|
|
*/
|
|
my_delete(m_persist_backup_filename.c_str(), MYF(0));
|
|
if (open_persist_file(O_RDONLY)) return -1;
|
|
if (read_file()) {
|
|
LogErr(ERROR_LEVEL, ER_JSON_PARSE_ERROR);
|
|
return 1;
|
|
}
|
|
} else {
|
|
/* backup file was read successfully, thus rename it to original. */
|
|
my_rename(m_persist_backup_filename.c_str(), m_persist_filename.c_str(),
|
|
MYF(MY_WME));
|
|
}
|
|
Json_object *json_obj = down_cast<Json_object *>(json.get());
|
|
/* Check file version */
|
|
Json_dom *version_dom = json_obj->get("Version");
|
|
if (version_dom == nullptr) {
|
|
LogErr(ERROR_LEVEL, ER_PERSIST_OPTION_STATUS,
|
|
"Persisted config file corrupted.");
|
|
return 1;
|
|
}
|
|
if (version_dom->json_type() != enum_json_type::J_INT) {
|
|
LogErr(ERROR_LEVEL, ER_PERSIST_OPTION_STATUS,
|
|
"Persisted config file version invalid.");
|
|
return 1;
|
|
}
|
|
Json_int *fetched_version = down_cast<Json_int *>(version_dom);
|
|
|
|
int retval = 1;
|
|
switch (static_cast<File_version>(fetched_version->value())) {
|
|
case File_version::VERSION_V1:
|
|
retval = read_persist_file_v1(json_obj);
|
|
break;
|
|
case File_version::VERSION_V2:
|
|
retval = read_persist_file_v2(json_obj);
|
|
break;
|
|
default:
|
|
retval = 1;
|
|
LogErr(ERROR_LEVEL, ER_PERSIST_OPTION_STATUS,
|
|
"Persisted config file version invalid.");
|
|
break;
|
|
};
|
|
|
|
if (!retval) {
|
|
load_aliases();
|
|
handle_option_type_change();
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
append_parse_early_variables() does a lookup into persist_variables
|
|
for parse early variables and place them after the command line options with
|
|
a separator "----persist-args-separator----"
|
|
|
|
This function does nothing when --no-defaults is set or if
|
|
persisted_globals_load is disabled.
|
|
|
|
@param [in] argc Pointer to argc of original program
|
|
@param [in] argv Pointer to argv of original program
|
|
@param [out] arg_separator_added Whether the separator is added or not
|
|
|
|
@return 0 Success
|
|
@return 1 Failure
|
|
*/
|
|
bool Persisted_variables_cache::append_parse_early_variables(
|
|
int *argc, char ***argv, bool &arg_separator_added) {
|
|
Prealloced_array<char *, 100> my_args(key_memory_persisted_variables);
|
|
MEM_ROOT alloc(key_memory_persisted_variables, 512);
|
|
arg_separator_added = false;
|
|
|
|
if (*argc < 2 || no_defaults || !persisted_globals_load) return false;
|
|
|
|
/* create a set of values sorted by timestamp */
|
|
std::multiset<st_persist_var, sort_tv_by_timestamp> sorted_vars;
|
|
for (auto iter : m_persisted_static_parse_early_variables)
|
|
sorted_vars.insert(iter.second);
|
|
for (auto iter : m_persisted_dynamic_parse_early_variables)
|
|
sorted_vars.insert(iter);
|
|
|
|
for (auto iter : sorted_vars) {
|
|
string persist_option = "--loose_" + iter.key + "=" + iter.value;
|
|
char *tmp;
|
|
|
|
if (nullptr == (tmp = strdup_root(&alloc, persist_option.c_str())) ||
|
|
my_args.push_back(tmp))
|
|
return true;
|
|
}
|
|
/*
|
|
Update existing command line options if there are any persisted
|
|
reasd only options to be appendded
|
|
*/
|
|
if (my_args.size()) {
|
|
char **res = new (&alloc) char *[my_args.size() + *argc + 2];
|
|
if (res == nullptr) goto err;
|
|
memset(res, 0, (sizeof(char *) * (my_args.size() + *argc + 2)));
|
|
/* copy all arguments to new array */
|
|
memcpy((uchar *)(res), (char *)(*argv), (*argc) * sizeof(char *));
|
|
|
|
if (!my_args.empty()) {
|
|
/*
|
|
Set args separator to know options set as part of command line and
|
|
options set from persisted config file
|
|
*/
|
|
set_persist_args_separator(&res[*argc]);
|
|
arg_separator_added = true;
|
|
/* copy arguments from persistent config file */
|
|
memcpy((res + *argc + 1), &my_args[0], my_args.size() * sizeof(char *));
|
|
}
|
|
res[my_args.size() + *argc + 1] = nullptr; /* last null */
|
|
(*argc) += (int)my_args.size() + 1;
|
|
*argv = res;
|
|
parse_early_persisted_argv_alloc = std::move(alloc);
|
|
return false;
|
|
}
|
|
return false;
|
|
|
|
err:
|
|
LogErr(ERROR_LEVEL, ER_FAILED_TO_HANDLE_DEFAULTS_FILE);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
append_read_only_variables() does a lookup into persist_variables for read
|
|
only variables and place them after the command line options with a separator
|
|
"----persist-args-separator----"
|
|
|
|
This function does nothing when --no-defaults is set or if
|
|
persisted_globals_load is disabled.
|
|
|
|
@param [in] argc Pointer to argc of original program
|
|
@param [in] argv Pointer to argv of original program
|
|
@param [in] arg_separator_added This flag tells whether arg separator
|
|
has already been added or not
|
|
@param [in] plugin_options This flag tells whether options are
|
|
handled during plugin install.
|
|
If set to true options are handled
|
|
as part of
|
|
@param [in] root The memory root to use for the
|
|
allocations. Null if you want to use
|
|
the PV cache root(s). install plugin.
|
|
|
|
@return 0 Success
|
|
@return 1 Failure
|
|
*/
|
|
bool Persisted_variables_cache::append_read_only_variables(
|
|
int *argc, char ***argv, bool arg_separator_added /* = false */,
|
|
bool plugin_options /* = false */, MEM_ROOT *root /* = nullptr */) {
|
|
Prealloced_array<char *, 100> my_args(key_memory_persisted_variables);
|
|
MEM_ROOT local_alloc{key_memory_persisted_variables, 512};
|
|
MEM_ROOT &alloc = root ? *root : local_alloc;
|
|
|
|
if (plugin_options == false) keyring_support_available();
|
|
|
|
if (*argc < 2 || no_defaults || !persisted_globals_load) return false;
|
|
|
|
auto result = decrypt_sensitive_variables();
|
|
if (result == return_status::ERROR) {
|
|
LogErr(ERROR_LEVEL, ER_CANNOT_INTERPRET_PERSISTED_SENSITIVE_VARIABLES);
|
|
return true;
|
|
}
|
|
|
|
/* create a set of values sorted by timestamp */
|
|
std::multiset<st_persist_var, sort_tv_by_timestamp> sorted_vars;
|
|
for (auto iter : m_persisted_static_variables)
|
|
sorted_vars.insert(iter.second);
|
|
for (auto iter : m_persisted_static_sensitive_variables)
|
|
sorted_vars.insert(iter.second);
|
|
|
|
for (auto iter : sorted_vars) {
|
|
string persist_option = "--loose_" + iter.key + "=" + iter.value;
|
|
char *tmp;
|
|
|
|
if (nullptr == (tmp = strdup_root(&alloc, persist_option.c_str())) ||
|
|
my_args.push_back(tmp))
|
|
return true;
|
|
}
|
|
/*
|
|
Update existing command line options if there are any persisted
|
|
reasd only options to be appendded
|
|
*/
|
|
if (my_args.size()) {
|
|
const unsigned int extra_args = (arg_separator_added == false) ? 2 : 1;
|
|
char **res = new (&alloc) char *[my_args.size() + *argc + extra_args];
|
|
if (res == nullptr) goto err;
|
|
memset(res, 0, (sizeof(char *) * (my_args.size() + *argc + extra_args)));
|
|
/* copy all arguments to new array */
|
|
memcpy((uchar *)(res), (char *)(*argv), (*argc) * sizeof(char *));
|
|
|
|
if (!my_args.empty()) {
|
|
if (arg_separator_added == false) {
|
|
/*
|
|
Set args separator to know options set as part of command line and
|
|
options set from persisted config file
|
|
*/
|
|
set_persist_args_separator(&res[*argc]);
|
|
}
|
|
/* copy arguments from persistent config file */
|
|
memcpy((res + *argc + (extra_args - 1)), &my_args[0],
|
|
my_args.size() * sizeof(char *));
|
|
}
|
|
res[my_args.size() + *argc + (extra_args - 1)] = nullptr; /* last null */
|
|
(*argc) += (int)my_args.size() + (extra_args - 1);
|
|
*argv = res;
|
|
if (!root) {
|
|
if (plugin_options)
|
|
ro_persisted_plugin_argv_alloc =
|
|
std::move(alloc); // Possibly overwrite previous.
|
|
else
|
|
ro_persisted_argv_alloc = std::move(alloc);
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
|
|
err:
|
|
LogErr(ERROR_LEVEL, ER_FAILED_TO_HANDLE_DEFAULTS_FILE);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
reset_persisted_variables() does a lookup into persist_variables and remove
|
|
the variable from the hash if present and flush the hash to file.
|
|
|
|
@param [in] thd Pointer to connection handle.
|
|
@param [in] name Name of variable to remove, if NULL all
|
|
variables are removed from config file.
|
|
@param [in] if_exists Bool value when set to true reports
|
|
warning else error if variable is not
|
|
present in the config file.
|
|
|
|
@return 0 Success
|
|
@return 1 Failure
|
|
*/
|
|
bool Persisted_variables_cache::reset_persisted_variables(THD *thd,
|
|
const char *name,
|
|
bool if_exists) {
|
|
bool result = false, found = false;
|
|
const bool reset_all = (name ? 0 : 1);
|
|
/* update on m_persisted_dynamic_variables/m_persisted_static_variables must
|
|
* be thread safe */
|
|
lock();
|
|
|
|
if (reset_all) {
|
|
/* check for necessary privileges */
|
|
if ((!m_persisted_dynamic_variables.empty() ||
|
|
!m_persisted_dynamic_parse_early_variables.empty() ||
|
|
!m_persisted_dynamic_sensitive_variables.empty()) &&
|
|
check_priv(thd, false))
|
|
goto end;
|
|
|
|
if ((!m_persisted_static_parse_early_variables.empty() ||
|
|
!m_persisted_static_variables.empty() ||
|
|
!m_persisted_static_sensitive_variables.empty()) &&
|
|
check_priv(thd, true))
|
|
goto end;
|
|
|
|
auto clear_one = [&found](auto &variables) {
|
|
if (!variables.empty()) {
|
|
variables.clear();
|
|
found = true;
|
|
}
|
|
};
|
|
|
|
clear_one(m_persisted_dynamic_variables);
|
|
clear_one(m_persisted_dynamic_parse_early_variables);
|
|
clear_one(m_persisted_dynamic_sensitive_variables);
|
|
clear_one(m_persisted_static_variables);
|
|
clear_one(m_persisted_static_sensitive_variables);
|
|
clear_one(m_persisted_static_parse_early_variables);
|
|
clear_one(m_persisted_dynamic_plugin_variables);
|
|
clear_one(m_persisted_static_sensitive_variables);
|
|
} else {
|
|
auto erase_variable = [&](const char *name_cptr) -> bool {
|
|
string name_str{tolower_varname(name_cptr)};
|
|
|
|
auto checkvariable = [&name_str](st_persist_var const &s) -> bool {
|
|
return s.key == name_str;
|
|
};
|
|
|
|
auto update_unordered_set = [&thd, &found,
|
|
&checkvariable](auto &variables) -> bool {
|
|
if (variables.size()) {
|
|
auto it =
|
|
std::find_if(variables.begin(), variables.end(), checkvariable);
|
|
if (it != variables.end()) {
|
|
/* if variable is present in config file remove it */
|
|
if (check_priv(thd, false)) return true;
|
|
variables.erase(it);
|
|
found = true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (update_unordered_set(m_persisted_dynamic_variables) ||
|
|
update_unordered_set(m_persisted_dynamic_parse_early_variables) ||
|
|
update_unordered_set(m_persisted_dynamic_plugin_variables) ||
|
|
update_unordered_set(m_persisted_dynamic_sensitive_variables) ||
|
|
update_unordered_set(m_persisted_dynamic_sensitive_plugin_variables))
|
|
return true;
|
|
|
|
auto update_map = [&thd, &found, &name_str](auto &variables) -> bool {
|
|
auto it = variables.find(name_str);
|
|
if (it != variables.end()) {
|
|
if (check_priv(thd, true)) return true;
|
|
variables.erase(it);
|
|
found = true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (update_map(m_persisted_static_variables) ||
|
|
update_map(m_persisted_static_parse_early_variables) ||
|
|
update_map(m_persisted_static_sensitive_variables))
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
// Erase the named variable
|
|
if (erase_variable(name)) goto end;
|
|
|
|
// If the variable has an alias, erase that too.
|
|
std::string alias_name;
|
|
mysql_mutex_assert_not_owner(&LOCK_plugin);
|
|
mysql_rwlock_rdlock(&LOCK_system_variables_hash);
|
|
{ alias_name = get_variable_alias(name); }
|
|
mysql_rwlock_unlock(&LOCK_system_variables_hash);
|
|
if (!alias_name.empty() && erase_variable(alias_name.c_str())) goto end;
|
|
|
|
if (!found) {
|
|
/* if not present and if exists is specified, report warning */
|
|
if (if_exists) {
|
|
push_warning_printf(thd, Sql_condition::SL_WARNING,
|
|
ER_VAR_DOES_NOT_EXIST,
|
|
ER_THD(thd, ER_VAR_DOES_NOT_EXIST), name);
|
|
} else {
|
|
/* without IF EXISTS, report error */
|
|
my_error(ER_VAR_DOES_NOT_EXIST, MYF(0), name);
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
unlock();
|
|
if (found) flush_to_file();
|
|
|
|
return result;
|
|
|
|
end:
|
|
unlock();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Return in-memory copy persist_variables_
|
|
*/
|
|
Persisted_variables_uset *
|
|
Persisted_variables_cache::get_persisted_dynamic_variables() {
|
|
return &m_persisted_dynamic_variables;
|
|
}
|
|
|
|
/*
|
|
Get SENSITIVE persisted variables
|
|
*/
|
|
Persisted_variables_uset *
|
|
Persisted_variables_cache::get_persisted_dynamic_sensitive_variables(THD *thd) {
|
|
if (thd != nullptr && thd->security_context()
|
|
->has_global_grant(STRING_WITH_LEN(
|
|
"SENSITIVE_VARIABLES_OBSERVER"))
|
|
.first == true) {
|
|
return &m_persisted_dynamic_sensitive_variables;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
Get PARSE_EARLY persisted variables
|
|
*/
|
|
Persisted_variables_uset *
|
|
Persisted_variables_cache::get_persisted_dynamic_parse_early_variables() {
|
|
return &m_persisted_dynamic_parse_early_variables;
|
|
}
|
|
|
|
/**
|
|
Return in-memory copy for static persisted variables
|
|
*/
|
|
Persisted_variables_umap *
|
|
Persisted_variables_cache::get_persisted_static_variables() {
|
|
return &m_persisted_static_variables;
|
|
}
|
|
|
|
/**
|
|
Get SENSITIVE persisted static variables
|
|
*/
|
|
Persisted_variables_umap *
|
|
Persisted_variables_cache::get_persisted_static_sensitive_variables(THD *thd) {
|
|
if (thd != nullptr && thd->security_context()
|
|
->has_global_grant(STRING_WITH_LEN(
|
|
"SENSITIVE_VARIABLES_OBSERVER"))
|
|
.first == true) {
|
|
return &m_persisted_static_sensitive_variables;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Persisted_variables_umap *
|
|
Persisted_variables_cache::get_persisted_static_parse_early_variables() {
|
|
return &m_persisted_static_parse_early_variables;
|
|
}
|
|
|
|
void Persisted_variables_cache::cleanup() {
|
|
mysql_mutex_destroy(&m_LOCK_persist_variables);
|
|
mysql_mutex_destroy(&m_LOCK_persist_file);
|
|
parse_early_persisted_argv_alloc.Clear();
|
|
ro_persisted_argv_alloc.Clear();
|
|
ro_persisted_plugin_argv_alloc.Clear();
|
|
}
|
|
|
|
void Persisted_variables_cache::clear_sensitive_blob_and_iv() {
|
|
m_sensitive_variables_blob.clear();
|
|
m_iv.clear();
|
|
}
|
|
|
|
std::string Persisted_variables_cache::to_hex(const std::string &value) {
|
|
std::stringstream output;
|
|
for (auto const &character : value)
|
|
output << std::hex << std::setfill('0') << std::setw(2)
|
|
<< (int)(unsigned char)character;
|
|
return output.str();
|
|
}
|
|
|
|
std::string Persisted_variables_cache::from_hex(const std::string &value) {
|
|
// string length has to be even
|
|
if (value.length() % 2 != 0) return {};
|
|
|
|
// string must consist of hex digits, all lowercase
|
|
for (auto const &symbol : value)
|
|
if (std::isupper(symbol) || !std::isxdigit(symbol)) return {};
|
|
|
|
string output;
|
|
for (size_t offset = 0; offset < value.length(); offset += 2) {
|
|
std::stringstream chunk;
|
|
chunk << std::hex << value.substr(offset, 2);
|
|
size_t int_value;
|
|
chunk >> int_value;
|
|
output.push_back(static_cast<unsigned char>(int_value));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
Get file encryption key. Use master key from keyring to decrypt it
|
|
|
|
@param [out] file_key Decrypted Key
|
|
@param [out] file_key_length Decrypted key length
|
|
@param [in] generate Generate key if missing
|
|
|
|
@returns status of key extraction operation
|
|
@retval true Error
|
|
@retval false Success
|
|
*/
|
|
bool Persisted_variables_cache::get_file_encryption_key(
|
|
std::unique_ptr<unsigned char[]> &file_key, size_t &file_key_length,
|
|
bool generate /* = false */) {
|
|
bool retval = true;
|
|
file_key_length = 32;
|
|
|
|
/* Check status of keyring service */
|
|
if (m_keyring_support_available == false) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_LACK_KEYRING_SUPPORT);
|
|
return retval;
|
|
}
|
|
|
|
/* First retrieve master key or create one if it's not available */
|
|
unsigned char *secret = nullptr;
|
|
size_t secret_length = 0;
|
|
char *secret_type = nullptr;
|
|
|
|
auto cleanup = create_scope_guard([&]() {
|
|
if (secret != nullptr) my_free(secret);
|
|
if (secret_type != nullptr) my_free(secret_type);
|
|
});
|
|
|
|
auto output = keyring_operations_helper::read_secret(
|
|
srv_keyring_reader, m_key_info.m_master_key_name.c_str(), nullptr,
|
|
&secret, &secret_length, &secret_type, PSI_NOT_INSTRUMENTED);
|
|
|
|
if (output == -1) return retval;
|
|
|
|
if (output == 0) {
|
|
/* If key is missing and generation flag is false, return */
|
|
if (generate == false) return retval;
|
|
|
|
if (m_key_info.m_file_key.length() != 0 ||
|
|
m_key_info.m_file_key_iv.length() != 0) {
|
|
/*
|
|
If an encrypted file key exists but the master key doesn't,
|
|
return from here. This is a problem related to unavailability
|
|
of master key.
|
|
*/
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_MASTER_KEY_NOT_FOUND,
|
|
m_key_info.m_master_key_name.c_str());
|
|
return retval;
|
|
}
|
|
/* Generate master key */
|
|
if (srv_keyring_generator->generate(m_key_info.m_master_key_name.c_str(),
|
|
nullptr,
|
|
m_key_info.m_master_key_type.c_str(),
|
|
m_key_info.m_master_key_size)) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_MASTER_KEY_CANNOT_BE_GENERATED,
|
|
m_key_info.m_master_key_name.c_str());
|
|
return retval;
|
|
}
|
|
|
|
output = keyring_operations_helper::read_secret(
|
|
srv_keyring_reader, m_key_info.m_master_key_name.c_str(), nullptr,
|
|
&secret, &secret_length, &secret_type, PSI_NOT_INSTRUMENTED);
|
|
|
|
/* Doh! */
|
|
if (output != 1) return retval;
|
|
}
|
|
|
|
if (m_key_info.m_file_key.length() == 0) {
|
|
if (generate == false) return retval;
|
|
/* File key does not exist, generate one */
|
|
file_key = std::make_unique<unsigned char[]>(file_key_length);
|
|
unsigned char iv[16];
|
|
|
|
if (my_rand_buffer(iv, sizeof(iv)) ||
|
|
my_rand_buffer(file_key.get(), file_key_length))
|
|
return retval;
|
|
|
|
/* encrypt file key */
|
|
const size_t encrypted_key_length =
|
|
(file_key_length / MY_AES_BLOCK_SIZE) * MY_AES_BLOCK_SIZE;
|
|
std::unique_ptr<unsigned char[]> encrypted_key =
|
|
std::make_unique<unsigned char[]>(encrypted_key_length);
|
|
|
|
auto error =
|
|
my_aes_encrypt(file_key.get(), file_key_length, encrypted_key.get(),
|
|
secret, secret_length, my_aes_256_cbc, iv, false);
|
|
|
|
if (error == MY_AES_BAD_DATA) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_ENCRYPTION_FAILED, "file key",
|
|
"master key");
|
|
return retval;
|
|
}
|
|
|
|
/* Store IV and encrypted key */
|
|
m_key_info.m_file_key_iv =
|
|
to_hex(std::string{reinterpret_cast<char *>(iv), sizeof(iv)});
|
|
m_key_info.m_file_key =
|
|
to_hex(std::string{reinterpret_cast<char *>(encrypted_key.get()),
|
|
static_cast<size_t>(error)});
|
|
retval = false;
|
|
} else {
|
|
/* File key exists, decrypt it */
|
|
const std::string unhex_key = from_hex(m_key_info.m_file_key);
|
|
const std::string unhex_iv = from_hex(m_key_info.m_file_key_iv);
|
|
|
|
std::unique_ptr<unsigned char[]> decrypted_file_key =
|
|
std::make_unique<unsigned char[]>(unhex_key.length());
|
|
|
|
auto error = my_aes_decrypt(
|
|
reinterpret_cast<const unsigned char *>(unhex_key.c_str()),
|
|
static_cast<uint32>(unhex_key.length()), decrypted_file_key.get(),
|
|
secret, secret_length, my_aes_256_cbc,
|
|
reinterpret_cast<const unsigned char *>(unhex_iv.c_str()), false);
|
|
|
|
if (error == MY_AES_BAD_DATA) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_DECRYPTION_FAILED, "file key",
|
|
"master key");
|
|
return retval;
|
|
}
|
|
|
|
file_key = std::make_unique<unsigned char[]>(error);
|
|
memcpy(file_key.get(), decrypted_file_key.get(), error);
|
|
retval = false;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
Encrypt sensitive variables values
|
|
|
|
@returns Status of the operation
|
|
*/
|
|
Persisted_variables_cache::return_status
|
|
Persisted_variables_cache::encrypt_sensitive_variables() {
|
|
if (m_sensitive_variables_blob.length() == 0 && m_iv.length() == 0 &&
|
|
m_persisted_static_sensitive_variables.size() == 0 &&
|
|
m_persisted_dynamic_sensitive_variables.size() == 0)
|
|
return return_status::NOT_REQUIRED;
|
|
|
|
return_status retval = return_status::ERROR;
|
|
/*
|
|
Presence of blob/iv indicates that they could not be parsed at the
|
|
beginning.
|
|
*/
|
|
if (m_sensitive_variables_blob.length() != 0 || m_iv.length() != 0)
|
|
return retval;
|
|
|
|
/* Get file key */
|
|
std::unique_ptr<unsigned char[]> file_key;
|
|
size_t file_key_length;
|
|
if (get_file_encryption_key(file_key, file_key_length, true)) return retval;
|
|
|
|
/* Serialize sensitive variables */
|
|
Json_object sensitive_variables_object;
|
|
if (format_set(m_persisted_dynamic_sensitive_variables,
|
|
s_key_mysql_sensitive_dynamic_variables,
|
|
sensitive_variables_object))
|
|
return retval;
|
|
if (format_map(m_persisted_static_sensitive_variables,
|
|
s_key_mysql_sensitive_static_variables,
|
|
sensitive_variables_object))
|
|
return retval;
|
|
|
|
Json_wrapper json_wrapper(&sensitive_variables_object);
|
|
json_wrapper.set_alias();
|
|
String str;
|
|
json_wrapper.to_string(&str, true, String().ptr(), JsonDepthErrorHandler);
|
|
|
|
/* Encrypt sensitive variables */
|
|
unsigned char iv[16];
|
|
if (my_rand_buffer(iv, sizeof(iv))) return retval;
|
|
const size_t data_len =
|
|
(str.length() / MY_AES_BLOCK_SIZE + 1) * MY_AES_BLOCK_SIZE;
|
|
std::unique_ptr<unsigned char[]> encrypted_buffer =
|
|
std::make_unique<unsigned char[]>(data_len);
|
|
if (encrypted_buffer.get() == nullptr) return retval;
|
|
|
|
auto error = my_aes_encrypt(
|
|
reinterpret_cast<unsigned char *>(str.ptr()),
|
|
static_cast<uint32>(str.length()), encrypted_buffer.get(), file_key.get(),
|
|
static_cast<uint32>(file_key_length), my_aes_256_cbc, iv, true);
|
|
|
|
if (error == MY_AES_BAD_DATA) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_ENCRYPTION_FAILED,
|
|
"persisted SENSITIVE variables", "file key");
|
|
return retval;
|
|
}
|
|
|
|
/* Convert to hex for storage */
|
|
m_iv = to_hex(std::string{reinterpret_cast<char *>(iv), sizeof(iv)});
|
|
m_sensitive_variables_blob =
|
|
to_hex(std::string{reinterpret_cast<char *>(encrypted_buffer.get()),
|
|
static_cast<size_t>(error)});
|
|
|
|
return return_status::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Decrypt sensitive variables values
|
|
|
|
@returns Status of the operation
|
|
@retval false Success
|
|
@retval true Error
|
|
*/
|
|
Persisted_variables_cache::return_status
|
|
Persisted_variables_cache::decrypt_sensitive_variables() {
|
|
if (m_sensitive_variables_blob.length() == 0 && m_iv.length() == 0)
|
|
return return_status::NOT_REQUIRED;
|
|
|
|
return_status retval = return_status::ERROR;
|
|
|
|
/* Get file key */
|
|
std::unique_ptr<unsigned char[]> file_key;
|
|
size_t file_key_length;
|
|
|
|
if (get_file_encryption_key(file_key, file_key_length, false)) return retval;
|
|
|
|
/* Convert from hex to binary */
|
|
const std::string unhex_iv = from_hex(m_iv);
|
|
const std::string unhex_data = from_hex(m_sensitive_variables_blob);
|
|
|
|
/* Decrypt the blob */
|
|
std::unique_ptr<unsigned char[]> decrypted_data =
|
|
std::make_unique<unsigned char[]>(unhex_data.length());
|
|
if (decrypted_data.get() == nullptr) return retval;
|
|
|
|
auto error = my_aes_decrypt(
|
|
reinterpret_cast<const unsigned char *>(unhex_data.c_str()),
|
|
static_cast<uint32>(unhex_data.length()), decrypted_data.get(),
|
|
file_key.get(), file_key_length, my_aes_256_cbc,
|
|
reinterpret_cast<const unsigned char *>(unhex_iv.c_str()), true);
|
|
|
|
if (error == MY_AES_BAD_DATA) {
|
|
LogErr(ERROR_LEVEL, ER_PERSISTED_VARIABLES_DECRYPTION_FAILED,
|
|
"persisted SENSITIVE variables", "file key");
|
|
return retval;
|
|
}
|
|
|
|
/* Parse the decrypted blob */
|
|
std::unique_ptr<Json_dom> json(Json_dom::parse(
|
|
reinterpret_cast<char *>(decrypted_data.get()), error,
|
|
[](const char *, size_t) {}, JsonDepthErrorHandler));
|
|
if (!json.get()) return retval;
|
|
|
|
if (json.get()->json_type() != enum_json_type::J_OBJECT) return retval;
|
|
Json_object *json_obj = down_cast<Json_object *>(json.get());
|
|
|
|
if (extract_set(*json_obj, s_key_mysql_sensitive_dynamic_variables,
|
|
m_persisted_dynamic_sensitive_variables) ||
|
|
extract_map(*json_obj, s_key_mysql_sensitive_static_variables,
|
|
m_persisted_static_sensitive_variables)) {
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
Variables are added to required containers.
|
|
There is no need to maintain the sensitive variables blob.
|
|
*/
|
|
clear_sensitive_blob_and_iv();
|
|
|
|
return return_status::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
We cache keyring support status just after reading manifest file.
|
|
This is required because in the absence of a keyring component,
|
|
keyring plugin may provide some of the services through
|
|
daemon proxy keyring.
|
|
|
|
However, we CANNOT use keyring plugin to encrypt SENSITIVE
|
|
variables because upon server restart, keyring plugins will
|
|
be loaded quite late.
|
|
|
|
Later on, before each encryption operation, we refer to this
|
|
cached value to decide whether to proceed with encryption or
|
|
not.
|
|
*/
|
|
void Persisted_variables_cache::keyring_support_available() {
|
|
m_keyring_support_available = keyring_status_no_error();
|
|
}
|