用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3679 lines
121 KiB

/* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
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 also distributed 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 included with MySQL.
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/sql_plugin.h"
#include "my_config.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "m_ctype.h"
#include "m_string.h"
#include "map_helpers.h"
#include "mutex_lock.h" // MUTEX_LOCK
#include "my_alloc.h"
#include "my_base.h"
#include "my_compiler.h"
#include "my_dbug.h"
#include "my_default.h" // free_defaults
#include "my_getopt.h"
#include "my_inttypes.h"
#include "my_list.h"
#include "my_loglevel.h"
#include "my_macros.h"
#include "my_psi_config.h"
#include "my_sharedlib.h"
#include "my_sys.h"
#include "my_thread_local.h"
#include "mysql/components/services/log_builtins.h"
#include "mysql/components/services/log_shared.h"
#include "mysql/components/services/psi_memory_bits.h"
#include "mysql/components/services/psi_mutex_bits.h"
#include "mysql/components/services/system_variable_source_type.h"
#include "mysql/plugin_audit.h"
#include "mysql/plugin_auth.h"
#include "mysql/plugin_clone.h"
#include "mysql/plugin_group_replication.h"
#include "mysql/plugin_keyring.h"
#include "mysql/plugin_validate_password.h"
#include "mysql/psi/mysql_memory.h"
#include "mysql/psi/mysql_mutex.h"
#include "mysql/psi/mysql_rwlock.h"
#include "mysql/psi/mysql_system.h"
#include "mysql/psi/mysql_thread.h"
#include "mysql/psi/psi_base.h"
#include "mysql/service_mysql_alloc.h"
#include "mysql_com.h"
#include "mysql_version.h"
#include "mysqld_error.h"
#include "prealloced_array.h"
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h" // check_table_access
#include "sql/auto_thd.h" // Auto_THD
#include "sql/current_thd.h"
#include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client
#include "sql/dd/dd_schema.h" // dd::Schema_MDL_locker
#include "sql/dd/info_schema/metadata.h" // dd::info_schema::store_dynamic_p...
#include "sql/dd/string_type.h" // dd::String_type
#include "sql/dd_sql_view.h" // update_referencing_views_metadata
#include "sql/debug_sync.h" // DEBUG_SYNC
#include "sql/derror.h" // ER_THD
#include "sql/field.h"
#include "sql/handler.h" // ha_initalize_handlerton
#include "sql/key.h" // key_copy
#include "sql/lock.h" // acquire_shared_global...
#include "sql/log.h"
#include "sql/mdl.h"
#include "sql/mysqld.h" // files_charset_info
#include "sql/persisted_variable.h" // Persisted_variables_cache
#include "sql/protocol_classic.h"
#include "sql/psi_memory_key.h"
#include "sql/records.h" // unique_ptr_destroy_only<RowIterator>
#include "sql/row_iterator.h"
#include "sql/set_var.h"
#include "sql/sql_audit.h" // mysql_audit_acquire_plugins
#include "sql/sql_backup_lock.h" // acquire_shared_backup_lock
#include "sql/sql_base.h" // close_mysql_tables
#include "sql/sql_class.h" // THD
#include "sql/sql_const.h"
#include "sql/sql_error.h"
#include "sql/sql_lex.h"
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // check_string_char_length
#include "sql/sql_plugin_var.h"
#include "sql/sql_show.h" // add_status_vars
#include "sql/sql_table.h"
#include "sql/sys_vars_resource_mgr.h"
#include "sql/sys_vars_shared.h" // intern_find_sys_var
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/thd_raii.h"
#include "sql/thr_malloc.h"
#include "sql/transaction.h" // trans_rollback_stmt
#include "sql_string.h"
#include "template_utils.h" // pointer_cast
#include "thr_lock.h"
#include "thr_mutex.h"
#include "typelib.h"
/* clang-format off */
/**
@page page_ext_plugins Plugins
The Big Picture
----------------
@startuml
actor "SQL client" as client
box "MySQL Server" #LightBlue
participant "Server Code" as server
participant "Plugin" as plugin
endbox
== INSTALL PLUGIN ==
server -> plugin : initialize
activate plugin
plugin --> server : initialization done
== CLIENT SESSION ==
loop many
client -> server : SQL command
server -> server : Add reference for Plugin if absent
loop one or many
server -> plugin : plugin API call
plugin --> server : plugin API call result
end
server -> server : Optionally release reference for Plugin
server --> client : SQL command reply
end
== UNINSTALL PLUGIN ==
server -> plugin : deinitialize
plugin --> server : deinitialization done
deactivate plugin
@enduml
@sa Sql_cmd_install_plugin, Sql_cmd_uninstall_plugin.
*/
/**
@page page_ext_plugin_services Plugin Services
Adding Plugin Services Into The Big Picture
------------------------------------
You probably remember the big picture for @ref page_ext_plugins.
Below is an extended version of it with plugin services added.
@startuml
actor "SQL client" as client
box "MySQL Server" #LightBlue
participant "Server Code" as server
participant "Plugin" as plugin
endbox
== INSTALL PLUGIN ==
server -> plugin : initialize
activate plugin
loop zero or many
plugin -> server : service API call
server --> plugin : service API result
end
plugin --> server : initialization done
== CLIENT SESSION ==
loop many
client -> server : SQL command
server -> server : Add reference for Plugin if absent
loop one or many
server -> plugin : plugin API call
loop zero or many
plugin -> server : service API call
server --> plugin : service API result
end
plugin --> server : plugin API call result
end
server -> server : Optionally release reference for Plugin
server --> client : SQL command reply
end
== UNINSTALL PLUGIN ==
server -> plugin : deinitialize
loop zero or many
plugin -> server : service API call
server --> plugin : service API result
end
plugin --> server : deinitialization done
deactivate plugin
@enduml
Understanding and creating plugin services
-----------------------------
- @subpage page_ext_plugin_svc_anathomy
- @subpage page_ext_plugin_svc_new_service_howto
- @subpage page_ext_plugin_api_goodservices
@section sect_ext_plugin_svc_reference Plugin Services Reference
See @ref group_ext_plugin_services
*/
/**
@page page_ext_plugin_svc_anathomy Plugin Service Anathomy
A "service" is a struct of C function pointers.
It is a tool to expose a pre-exitsing set of server functions to plugins.
You need the actual server functions as a starting point.
The server has all service structs defined and initialized so
that the the function pointers point to the actual service implementation
functions.
The server also keeps a global list of the plugin service reference
structures called ::list_of_services.
See ::st_service_ref for details of what a service reference is.
The server copies of all plugin structures are filled in at compile time
with the function pointers of the actual server functions that implement
the service functions. References to them are stored into the relevant
element of ::list_of_services.
Each plugin must export pointer symbols for every plugin service that
the server knows about.
The plugin service pointers are initialized with the version of the plugin
service that the plugin expects.
When a dynamic plugin shared object is loaded by ::plugin_dl_add it will
iterate over ::list_of_services, find the plugin symbol by name,
check the service version stored in that symbol against the one stored into
::st_service_ref and then will replace the version stored in plugin's struct
pointer with the actual pointer of the server's copy of the same structure.
When that is filled in the plugin can use the newly set server structure
through its local pointer to call into the service method pointers that point
to the server implementaiton functions.
Once set to the server's structure, the plugin's service pointer value is
never reset back to service version.
The plugin service header also defines a set of convenience macros
that replace top level plugin service calls with the corresponding function
pointer call, i.e. for service foo:
~~~~
struct foo_service_st {
int (*foo_mtd_1)(int a);
}
struct foo_service_st *foo_service;
~~~~
a convenience macro is defined for `foo_mtd_1` as follows:
~~~~
#define foo_mtd_1(a) foo_service->foo_mtd_1(a)
~~~~
This trick allows plugin service functions to look as top level function
calls inside the plugin code.
@sa plugin_add, plugin_del, plugin_dl_add, plugin_dl_del, list_of_services,
st_service_ref
*/
/* clang-format on */
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif
#include <algorithm>
#include <memory>
#include <new>
#include <unordered_map>
#include <utility>
#include "sql/srv_session.h" // Srv_session::check_for_stale_threads()
using std::max;
using std::min;
#define REPORT_TO_LOG 1
#define REPORT_TO_USER 2
#ifndef DBUG_OFF
static PSI_memory_key key_memory_plugin_ref;
#endif
static PSI_memory_key key_memory_plugin_mem_root;
static PSI_memory_key key_memory_plugin_init_tmp;
static PSI_memory_key key_memory_plugin_int_mem_root;
static PSI_memory_key key_memory_mysql_plugin;
static PSI_memory_key key_memory_mysql_plugin_dl;
static PSI_memory_key key_memory_plugin_bookmark;
extern st_mysql_plugin *mysql_optional_plugins[];
extern st_mysql_plugin *mysql_mandatory_plugins[];
/**
@note The order of the enumeration is critical.
@see construct_options
*/
const char *global_plugin_typelib_names[] = {"OFF", "ON", "FORCE",
"FORCE_PLUS_PERMANENT", NULL};
static TYPELIB global_plugin_typelib = {
array_elements(global_plugin_typelib_names) - 1, "",
global_plugin_typelib_names, NULL};
static I_List<i_string> opt_plugin_load_list;
I_List<i_string> *opt_plugin_load_list_ptr = &opt_plugin_load_list;
static I_List<i_string> opt_early_plugin_load_list;
I_List<i_string> *opt_early_plugin_load_list_ptr = &opt_early_plugin_load_list;
char *opt_plugin_dir_ptr;
char opt_plugin_dir[FN_REFLEN];
/*
When you ad a new plugin type, add both a string and make sure that the
init and deinit array are correctly updated.
*/
const LEX_CSTRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
{STRING_WITH_LEN("UDF")},
{STRING_WITH_LEN("STORAGE ENGINE")},
{STRING_WITH_LEN("FTPARSER")},
{STRING_WITH_LEN("DAEMON")},
{STRING_WITH_LEN("INFORMATION SCHEMA")},
{STRING_WITH_LEN("AUDIT")},
{STRING_WITH_LEN("REPLICATION")},
{STRING_WITH_LEN("AUTHENTICATION")},
{STRING_WITH_LEN("VALIDATE PASSWORD")},
{STRING_WITH_LEN("GROUP REPLICATION")},
{STRING_WITH_LEN("KEYRING")},
{STRING_WITH_LEN("CLONE")}};
extern int initialize_schema_table(st_plugin_int *plugin);
extern int finalize_schema_table(st_plugin_int *plugin);
/*
The number of elements in both plugin_type_initialize and
plugin_type_deinitialize should equal to the number of plugins
defined.
*/
plugin_type_init plugin_type_initialize[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
0,
ha_initialize_handlerton,
0,
0,
initialize_schema_table,
initialize_audit_plugin,
0,
0,
0};
plugin_type_init plugin_type_deinitialize[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
0,
ha_finalize_handlerton,
0,
0,
finalize_schema_table,
finalize_audit_plugin,
0,
0,
0};
static const char *plugin_interface_version_sym =
"_mysql_plugin_interface_version_";
static const char *sizeof_st_plugin_sym = "_mysql_sizeof_struct_st_plugin_";
static const char *plugin_declarations_sym = "_mysql_plugin_declarations_";
static int min_plugin_interface_version =
MYSQL_PLUGIN_INTERFACE_VERSION & ~0xFF;
static void *innodb_callback_data;
/* Note that 'int version' must be the first field of every plugin
sub-structure (plugin->info).
*/
static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
0x0000,
MYSQL_HANDLERTON_INTERFACE_VERSION,
MYSQL_FTPARSER_INTERFACE_VERSION,
MYSQL_DAEMON_INTERFACE_VERSION,
MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION,
MYSQL_AUDIT_INTERFACE_VERSION,
MYSQL_REPLICATION_INTERFACE_VERSION,
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION,
MYSQL_GROUP_REPLICATION_INTERFACE_VERSION,
MYSQL_KEYRING_INTERFACE_VERSION,
MYSQL_CLONE_INTERFACE_VERSION};
static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM] = {
0x0000, /* UDF: not implemented */
MYSQL_HANDLERTON_INTERFACE_VERSION,
MYSQL_FTPARSER_INTERFACE_VERSION,
MYSQL_DAEMON_INTERFACE_VERSION,
MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION,
MYSQL_AUDIT_INTERFACE_VERSION,
MYSQL_REPLICATION_INTERFACE_VERSION,
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION,
MYSQL_GROUP_REPLICATION_INTERFACE_VERSION,
MYSQL_KEYRING_INTERFACE_VERSION,
MYSQL_CLONE_INTERFACE_VERSION};
/* support for Services */
#include "sql/sql_plugin_services.h"
/*
A mutex LOCK_plugin_delete must be acquired before calling plugin_del
function.
*/
mysql_mutex_t LOCK_plugin_delete;
/**
Serializes access to the global plugin memory list.
LOCK_plugin must be acquired before accessing
plugin_dl_array, plugin_array and plugin_hash.
We are always manipulating ref count, so a rwlock here is unneccessary.
If it must be taken together with the LOCK_system_variables_hash then
LOCK_plugin must be taken before LOCK_system_variables_hash.
*/
mysql_mutex_t LOCK_plugin;
/**
Serializes the INSTALL and UNINSTALL PLUGIN commands.
Must be taken before LOCK_plugin.
*/
mysql_mutex_t LOCK_plugin_install;
static Prealloced_array<st_plugin_dl *, 16> *plugin_dl_array;
static Prealloced_array<st_plugin_int *, 16> *plugin_array;
static collation_unordered_map<std::string, st_plugin_int *>
*plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM] = {nullptr};
static bool reap_needed = false;
static int plugin_array_version = 0;
static bool initialized = false;
static MEM_ROOT plugin_mem_root;
static uint global_variables_dynamic_size = 0;
static malloc_unordered_map<std::string, st_bookmark *> *bookmark_hash;
/** Hash for system variables of string type with MEMALLOC flag. */
static malloc_unordered_map<std::string, st_bookmark *>
*malloced_string_type_sysvars_bookmark_hash;
/* prototypes */
static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv);
static bool plugin_load_list(MEM_ROOT *tmp_root, int *argc, char **argv,
const char *list, bool load_early);
static bool check_if_option_is_deprecated(int optid,
const struct my_option *opt,
char *argument);
static int test_plugin_options(MEM_ROOT *, st_plugin_int *, int *, char **);
static bool register_builtin(st_mysql_plugin *, st_plugin_int *,
st_plugin_int **);
static void unlock_variables(struct System_variables *vars);
static void cleanup_variables(THD *thd, struct System_variables *vars);
static void plugin_vars_free_values(sys_var *vars);
static void plugin_var_memalloc_free(struct System_variables *vars);
static void restore_pluginvar_names(sys_var *first);
#define my_intern_plugin_lock(A, B) intern_plugin_lock(A, B)
#define my_intern_plugin_lock_ci(A, B) intern_plugin_lock(A, B)
static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref plugin);
static void intern_plugin_unlock(LEX *lex, plugin_ref plugin);
static void reap_plugins(void);
malloc_unordered_map<std::string, st_bookmark *> *get_bookmark_hash(void) {
return bookmark_hash;
}
/**
@warning Make sure all errors reported to the log here are
defined at least twice in share/errmsg-utf8.txt
@arg where_to a combination of @ref REPORT_TO_USER and @ref REPORT_TO_LOG
@arg error the code for the mysql_error()
*/
static void report_error(int where_to, uint error, ...) {
va_list args;
if (where_to & REPORT_TO_USER) {
va_start(args, error);
my_printv_error(error, ER_THD_NONCONST(current_thd, error), MYF(0), args);
va_end(args);
}
if (where_to & REPORT_TO_LOG) {
longlong ecode = 0;
switch (error) {
case ER_UDF_NO_PATHS:
ecode = ER_NO_PATH_FOR_SHARED_LIBRARY;
break;
case ER_CANT_OPEN_LIBRARY:
ecode = ER_FAILED_TO_OPEN_SHARED_LIBRARY;
break;
case ER_CANT_FIND_DL_ENTRY:
ecode = ER_FAILED_TO_FIND_DL_ENTRY;
break;
case ER_OUTOFMEMORY:
ecode = ER_SERVER_OUTOFMEMORY;
break;
case ER_UDF_EXISTS:
ecode = ER_UDF_ALREADY_EXISTS;
break;
case ER_PLUGIN_NO_INSTALL:
ecode = ER_PLUGIN_NO_INSTALL_DUP;
break;
case ER_PLUGIN_NOT_EARLY:
ecode = ER_PLUGIN_NOT_EARLY_DUP;
break;
default:
DBUG_ASSERT(false);
return;
}
va_start(args, error);
LogEvent().type(LOG_TYPE_ERROR).prio(ERROR_LEVEL).lookupv(ecode, args);
va_end(args);
}
}
/**
Check if the provided path is valid in the sense that it does cause
a relative reference outside the directory.
@note Currently, this function only check if there are any
characters in FN_DIRSEP in the string, but it might change in the
future.
@code
check_valid_path("../foo.so") -> true
check_valid_path("foo.so") -> false
@endcode
*/
bool check_valid_path(const char *path, size_t len) {
size_t prefix = my_strcspn(files_charset_info, path, path + len, FN_DIRSEP,
strlen(FN_DIRSEP));
return prefix < len;
}
/****************************************************************************
Plugin support code
****************************************************************************/
static st_plugin_dl *plugin_dl_find(const LEX_STRING *dl) {
DBUG_TRACE;
for (st_plugin_dl **it = plugin_dl_array->begin();
it != plugin_dl_array->end(); ++it) {
st_plugin_dl *tmp = *it;
if (tmp->ref_count &&
!my_strnncoll(files_charset_info, pointer_cast<uchar *>(dl->str),
dl->length, pointer_cast<uchar *>(tmp->dl.str),
tmp->dl.length))
return tmp;
}
return NULL;
}
static st_plugin_dl *plugin_dl_insert_or_reuse(st_plugin_dl *plugin_dl) {
DBUG_TRACE;
st_plugin_dl *tmp;
for (st_plugin_dl **it = plugin_dl_array->begin();
it != plugin_dl_array->end(); ++it) {
tmp = *it;
if (!tmp->ref_count) {
memcpy(tmp, plugin_dl, sizeof(st_plugin_dl));
return tmp;
}
}
if (plugin_dl_array->push_back(plugin_dl)) return NULL;
tmp = plugin_dl_array->back() = static_cast<st_plugin_dl *>(
memdup_root(&plugin_mem_root, plugin_dl, sizeof(st_plugin_dl)));
return tmp;
}
static inline void free_plugin_mem(st_plugin_dl *p) {
/*
The valgrind leak report is done at the end of the program execution.
But since the plugins are unloaded from the memory,
it is impossible for valgrind to correctly report the leak locations.
So leave the shared objects (.DLL/.so) open for the symbols definition.
*/
bool preserve_shared_objects_after_unload = false;
DBUG_EXECUTE_IF("preserve_shared_objects_after_unload",
{ preserve_shared_objects_after_unload = true; });
if (p->handle != nullptr && !preserve_shared_objects_after_unload) {
#ifdef HAVE_PSI_SYSTEM_INTERFACE
PSI_SYSTEM_CALL(unload_plugin)
(std::string(p->dl.str, p->dl.length).c_str());
#endif
dlclose(p->handle);
}
my_free(p->dl.str);
if (p->version != MYSQL_PLUGIN_INTERFACE_VERSION) my_free(p->plugins);
}
/**
Loads a dynamic plugin
Fills in a ::st_plugin_dl structure.
Initializes the plugin services pointer inside the plugin.
Does not initialize the individual plugins.
Must have LOCK_plugin and LOCK_system_variables locked(write).
On error releases LOCK_system_variables and LOCK_plugin.
@arg dl The path to the plugin binary to load
@arg report a bitmask that's passed down to report_error()
@arg load_early true if loading the "early" plugins (--early-plugin-load etc)
@return A plugin reference.
@retval NULL failed to load the plugin
*/
static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report,
bool load_early) {
char dlpath[FN_REFLEN];
uint dummy_errors, i;
size_t plugin_dir_len, dlpathlen;
st_plugin_dl *tmp, plugin_dl;
void *sym;
DBUG_TRACE;
DBUG_PRINT("enter",
("dl->str: '%s', dl->length: %d", dl->str, (int)dl->length));
plugin_dir_len = strlen(opt_plugin_dir);
/*
Ensure that the dll doesn't have a path.
This is done to ensure that only approved libraries from the
plugin directory are used (to make this even remotely secure).
*/
LEX_CSTRING dl_cstr = {dl->str, dl->length};
if (check_valid_path(dl->str, dl->length) ||
check_string_char_length(dl_cstr, "", NAME_CHAR_LEN, system_charset_info,
1) ||
plugin_dir_len + dl->length + 1 >= FN_REFLEN) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_UDF_NO_PATHS);
return NULL;
}
/* If this dll is already loaded just increase ref_count. */
if ((tmp = plugin_dl_find(dl))) {
tmp->ref_count++;
return tmp;
}
memset(&plugin_dl, 0, sizeof(plugin_dl));
/* Compile dll path */
dlpathlen = strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", dl->str,
NullS) -
dlpath;
(void)unpack_filename(dlpath, dlpath);
plugin_dl.ref_count = 1;
/* Open new dll handle */
mysql_mutex_assert_owner(&LOCK_plugin);
if (!(plugin_dl.handle = dlopen(dlpath, RTLD_NOW))) {
const char *errmsg;
int error_number = dlopen_errno;
/*
Conforming applications should use a critical section to retrieve
the error pointer and buffer...
*/
DLERROR_GENERATE(errmsg, error_number);
if (!strncmp(
dlpath, errmsg,
dlpathlen)) { // if errmsg starts from dlpath, trim this prefix.
errmsg += dlpathlen;
if (*errmsg == ':') errmsg++;
if (*errmsg == ' ') errmsg++;
}
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, error_number, errmsg);
/*
"The messages returned by dlerror() may reside in a static buffer
that is overwritten on each call to dlerror()."
Some implementations have a static pointer instead, and the memory it
points to may be reported as "still reachable" by Valgrind.
Calling dlerror() once more will free the memory.
*/
#if !defined(_WIN32)
errmsg = dlerror();
DBUG_ASSERT(errmsg == NULL);
#endif
return NULL;
}
/* Determine interface version */
if (!(sym = dlsym(plugin_dl.handle, plugin_interface_version_sym))) {
free_plugin_mem(&plugin_dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_interface_version_sym);
return NULL;
}
plugin_dl.version = *(int *)sym;
/* Versioning */
if (plugin_dl.version < min_plugin_interface_version ||
(plugin_dl.version >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8)) {
free_plugin_mem(&plugin_dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0,
"plugin interface version mismatch");
return NULL;
}
/* link the services in */
for (i = 0; i < array_elements(list_of_services); i++) {
if ((sym = dlsym(plugin_dl.handle, list_of_services[i].name))) {
uint ver = (uint)(intptr) * (void **)sym;
if ((*(void **)sym) !=
list_of_services[i].service && /* already replaced */
(ver > list_of_services[i].version ||
(ver >> 8) < (list_of_services[i].version >> 8))) {
char buf[MYSQL_ERRMSG_SIZE];
snprintf(buf, sizeof(buf), "service '%s' interface version mismatch",
list_of_services[i].name);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0, buf);
return NULL;
}
*(void **)sym = list_of_services[i].service;
}
}
/* Find plugin declarations */
if (!(sym = dlsym(plugin_dl.handle, plugin_declarations_sym))) {
free_plugin_mem(&plugin_dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_FIND_DL_ENTRY, plugin_declarations_sym);
return NULL;
}
if (plugin_dl.version != MYSQL_PLUGIN_INTERFACE_VERSION) {
uint sizeof_st_plugin;
st_mysql_plugin *old, *cur;
char *ptr = (char *)sym;
if ((sym = dlsym(plugin_dl.handle, sizeof_st_plugin_sym)))
sizeof_st_plugin = *(int *)sym;
else {
/*
When the following assert starts failing, we'll have to call
report_error(report, ER_CANT_FIND_DL_ENTRY, sizeof_st_plugin_sym);
*/
DBUG_ASSERT(min_plugin_interface_version == 0);
sizeof_st_plugin = (int)offsetof(st_mysql_plugin, version);
}
/*
What's the purpose of this loop? If the goal is to catch a
missing 0 record at the end of a list, it will fail miserably
since the compiler is likely to optimize this away. /Matz
*/
for (i = 0; ((st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info; i++)
/* no op */;
cur = (st_mysql_plugin *)my_malloc(key_memory_mysql_plugin,
(i + 1) * sizeof(st_mysql_plugin),
MYF(MY_ZEROFILL | MY_WME));
if (!cur) {
free_plugin_mem(&plugin_dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_OUTOFMEMORY,
static_cast<int>(plugin_dl.dl.length));
return NULL;
}
/*
All st_plugin fields not initialized in the plugin explicitly, are
set to 0. It matches C standard behaviour for struct initializers that
have less values than the struct definition.
*/
for (i = 0; (old = (st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info;
i++)
memcpy(cur + i, old, min<size_t>(sizeof(cur[i]), sizeof_st_plugin));
sym = cur;
}
plugin_dl.plugins = (st_mysql_plugin *)sym;
/*
If report is REPORT_TO_USER, we were called from
mysql_install_plugin. Otherwise, we are called
indirectly from plugin_register_dynamic_and_init_all().
*/
if (report == REPORT_TO_USER) {
st_mysql_plugin *plugin = plugin_dl.plugins;
for (; plugin->info; ++plugin)
if (plugin->flags & PLUGIN_OPT_NO_INSTALL) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_PLUGIN_NO_INSTALL, plugin->name);
free_plugin_mem(&plugin_dl);
return NULL;
}
}
if (load_early) {
st_mysql_plugin *plugin = plugin_dl.plugins;
for (; plugin->info; ++plugin)
if (!(plugin->flags & PLUGIN_OPT_ALLOW_EARLY)) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_PLUGIN_NOT_EARLY, plugin->name);
free_plugin_mem(&plugin_dl);
return NULL;
}
}
/* Duplicate and convert dll name */
plugin_dl.dl.length = dl->length * files_charset_info->mbmaxlen + 1;
if (!(plugin_dl.dl.str = (char *)my_malloc(key_memory_mysql_plugin_dl,
plugin_dl.dl.length, MYF(0)))) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
free_plugin_mem(&plugin_dl);
report_error(report, ER_OUTOFMEMORY, static_cast<int>(plugin_dl.dl.length));
return NULL;
}
plugin_dl.dl.length = copy_and_convert(
plugin_dl.dl.str, plugin_dl.dl.length, files_charset_info, dl->str,
dl->length, system_charset_info, &dummy_errors);
plugin_dl.dl.str[plugin_dl.dl.length] = 0;
/* Add this dll to array */
if (!(tmp = plugin_dl_insert_or_reuse(&plugin_dl))) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
free_plugin_mem(&plugin_dl);
report_error(report, ER_OUTOFMEMORY,
static_cast<int>(sizeof(st_plugin_dl)));
return NULL;
}
return tmp;
}
static void plugin_dl_del(const LEX_STRING *dl) {
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
for (st_plugin_dl **it = plugin_dl_array->begin();
it != plugin_dl_array->end(); ++it) {
st_plugin_dl *tmp = *it;
if (tmp->ref_count &&
!my_strnncoll(files_charset_info, pointer_cast<uchar *>(dl->str),
dl->length, pointer_cast<uchar *>(tmp->dl.str),
tmp->dl.length)) {
/* Do not remove this element, unless no other plugin uses this dll. */
if (!--tmp->ref_count) {
free_plugin_mem(tmp);
memset(tmp, 0, sizeof(st_plugin_dl));
}
break;
}
}
}
static st_plugin_int *plugin_find_internal(const LEX_CSTRING &name, int type) {
uint i;
DBUG_TRACE;
if (!initialized) return NULL;
mysql_mutex_assert_owner(&LOCK_plugin);
if (type == MYSQL_ANY_PLUGIN) {
for (i = 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) {
const auto it = plugin_hash[i]->find(to_string(name));
if (it != plugin_hash[i]->end()) return it->second;
}
} else
return find_or_nullptr(*plugin_hash[type], to_string(name));
return NULL;
}
static SHOW_COMP_OPTION plugin_status(const LEX_CSTRING &name, int type) {
SHOW_COMP_OPTION rc = SHOW_OPTION_NO;
st_plugin_int *plugin;
DBUG_TRACE;
mysql_mutex_lock(&LOCK_plugin);
if ((plugin = plugin_find_internal(name, type))) {
rc = SHOW_OPTION_DISABLED;
if (plugin->state == PLUGIN_IS_READY) rc = SHOW_OPTION_YES;
}
mysql_mutex_unlock(&LOCK_plugin);
return rc;
}
bool plugin_is_ready(const LEX_CSTRING &name, int type) {
bool rc = false;
if (plugin_status(name, type) == SHOW_OPTION_YES) rc = true;
return rc;
}
SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type) {
LEX_CSTRING plugin_name = {name, len};
return plugin_status(plugin_name, type);
}
static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc) {
st_plugin_int *pi = plugin_ref_to_int(rc);
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
if (pi->state & (PLUGIN_IS_READY | PLUGIN_IS_UNINITIALIZED)) {
plugin_ref plugin;
#ifdef DBUG_OFF
/* built-in plugins don't need ref counting */
if (!pi->plugin_dl) return pi;
plugin = pi;
#else
/*
For debugging, we do an additional malloc which allows the
memory manager and/or valgrind to track locked references and
double unlocks to aid resolving reference counting problems.
*/
if (!(plugin = (plugin_ref)my_malloc(key_memory_plugin_ref, sizeof(pi),
MYF(MY_WME))))
return NULL;
*plugin = pi;
#endif
pi->ref_count++;
DBUG_PRINT("info", ("thd: %p, plugin: \"%s\", ref_count: %d", current_thd,
pi->name.str, pi->ref_count));
if (lex) lex->plugins.push_back(plugin);
return plugin;
}
return NULL;
}
plugin_ref plugin_lock(THD *thd, plugin_ref *ptr) {
LEX *lex = thd ? thd->lex : 0;
plugin_ref rc;
DBUG_TRACE;
mysql_mutex_lock(&LOCK_plugin);
rc = my_intern_plugin_lock_ci(lex, *ptr);
mysql_mutex_unlock(&LOCK_plugin);
return rc;
}
plugin_ref plugin_lock_by_name(THD *thd, const LEX_CSTRING &name, int type) {
LEX *lex = thd ? thd->lex : 0;
plugin_ref rc = NULL;
st_plugin_int *plugin;
DBUG_TRACE;
mysql_mutex_lock(&LOCK_plugin);
if ((plugin = plugin_find_internal(name, type)))
rc = my_intern_plugin_lock_ci(lex, plugin_int_to_ref(plugin));
mysql_mutex_unlock(&LOCK_plugin);
return rc;
}
static st_plugin_int *plugin_insert_or_reuse(st_plugin_int *plugin) {
DBUG_TRACE;
st_plugin_int *tmp;
/* During server bootstrap, don't reuse free slot. In case some early plugin
load like key_ring fails, an user plugin could occupy that empty slot and
get installed before mandatory plugins like PFS. This will cause issue if
the plugin has dependency on PFS like creating dynamic PFS table. This issue
is observed during clone plugin testing. */
const bool reuse_free_slot = (get_server_state() != SERVER_BOOTING);
if (reuse_free_slot) {
for (st_plugin_int **it = plugin_array->begin(); it != plugin_array->end();
++it) {
tmp = *it;
if (tmp->state == PLUGIN_IS_FREED) {
*tmp = std::move(*plugin);
return tmp;
}
}
}
if (plugin_array->push_back(plugin)) return NULL;
tmp = plugin_array->back() =
new (&plugin_mem_root) st_plugin_int(std::move(*plugin));
return tmp;
}
/**
Adds a plugin to the global plugin list.
Also installs the plugin variables.
In case of error releases ::LOCK_system_variables_hash and ::LOCK_plugin
and reports the error.
@note Requires that a write-lock is held on ::LOCK_plugin and
::LOCK_system_variables_hash
*/
static bool plugin_add(MEM_ROOT *tmp_root, LEX_CSTRING name,
const LEX_STRING *dl, int *argc, char **argv, int report,
bool load_early) {
st_plugin_int tmp;
st_mysql_plugin *plugin;
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
if (plugin_find_internal(name, MYSQL_ANY_PLUGIN)) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_UDF_EXISTS, name.str);
return true;
}
if (!(tmp.plugin_dl = plugin_dl_add(dl, report, load_early))) return true;
/* Find plugin by name */
for (plugin = tmp.plugin_dl->plugins; plugin->info; plugin++) {
size_t name_len = strlen(plugin->name);
if (plugin->type >= 0 && plugin->type < MYSQL_MAX_PLUGIN_TYPE_NUM &&
!my_strnncoll(system_charset_info,
pointer_cast<const uchar *>(name.str), name.length,
pointer_cast<const uchar *>(plugin->name), name_len)) {
st_plugin_int *tmp_plugin_ptr;
if (*(int *)plugin->info <
min_plugin_info_interface_version[plugin->type] ||
((*(int *)plugin->info) >> 8) >
(cur_plugin_info_interface_version[plugin->type] >> 8)) {
char buf[256], dl_name[FN_REFLEN];
strxnmov(buf, sizeof(buf) - 1, "API version for ",
plugin_type_names[plugin->type].str,
" plugin is too different", NullS);
/* copy the library name so we can release the mutex */
strncpy(dl_name, dl->str, sizeof(dl_name) - 1);
dl_name[sizeof(dl_name) - 1] = 0;
plugin_dl_del(dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_OPEN_LIBRARY, dl_name, 0, buf);
return true;
}
tmp.plugin = plugin;
tmp.name.str = plugin->name;
tmp.name.length = name_len;
tmp.ref_count = 0;
tmp.state = PLUGIN_IS_UNINITIALIZED;
tmp.load_option = PLUGIN_ON;
if (test_plugin_options(tmp_root, &tmp, argc, argv))
tmp.state = PLUGIN_IS_DISABLED;
if ((tmp_plugin_ptr = plugin_insert_or_reuse(&tmp))) {
plugin_array_version++;
if (plugin_hash[plugin->type]
->emplace(to_string(tmp_plugin_ptr->name), tmp_plugin_ptr)
.second) {
init_alloc_root(key_memory_plugin_int_mem_root,
&tmp_plugin_ptr->mem_root, 4096, 4096);
return false;
}
tmp_plugin_ptr->state = PLUGIN_IS_FREED;
}
mysql_del_sys_var_chain(tmp.system_vars);
restore_pluginvar_names(tmp.system_vars);
plugin_dl_del(dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
return true;
}
}
plugin_dl_del(dl);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(report, ER_CANT_FIND_DL_ENTRY, name.str);
return true;
}
static void plugin_deinitialize(st_plugin_int *plugin, bool ref_check) {
/*
we don't want to hold the LOCK_plugin mutex as it may cause
deinitialization to deadlock if plugins have worker threads
with plugin locks
*/
mysql_mutex_assert_not_owner(&LOCK_plugin);
if (plugin->plugin->status_vars) {
remove_status_vars(plugin->plugin->status_vars);
}
if (plugin_type_deinitialize[plugin->plugin->type]) {
if ((*plugin_type_deinitialize[plugin->plugin->type])(plugin)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_FAILED_DEINITIALIZATION, plugin->name.str,
plugin_type_names[plugin->plugin->type].str);
}
} else if (plugin->plugin->deinit) {
DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
if (plugin->plugin->deinit(plugin)) {
DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
plugin->name.str));
}
}
plugin->state = PLUGIN_IS_UNINITIALIZED;
Srv_session::check_for_stale_threads(plugin);
/*
We do the check here because NDB has a worker THD which doesn't
exit until NDB is shut down.
*/
if (ref_check && plugin->ref_count)
LogErr(ERROR_LEVEL, ER_PLUGIN_HAS_NONZERO_REFCOUNT_AFTER_DEINITIALIZATION,
plugin->name.str, plugin->ref_count);
}
/*
Unload a plugin.
Note: During valgrind testing, the plugin's shared object (.dll/.so)
is not unloaded in order to keep the call stack
of the leaked objects.
*/
static void plugin_del(st_plugin_int *plugin) {
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
mysql_mutex_assert_owner(&LOCK_plugin_delete);
/* Free allocated strings before deleting the plugin. */
mysql_rwlock_wrlock(&LOCK_system_variables_hash);
mysql_del_sys_var_chain(plugin->system_vars);
mysql_rwlock_unlock(&LOCK_system_variables_hash);
restore_pluginvar_names(plugin->system_vars);
plugin_vars_free_values(plugin->system_vars);
plugin_hash[plugin->plugin->type]->erase(to_string(plugin->name));
if (plugin->plugin_dl) plugin_dl_del(&plugin->plugin_dl->dl);
plugin->state = PLUGIN_IS_FREED;
plugin_array_version++;
free_root(&plugin->mem_root, MYF(0));
}
static void reap_plugins(void) {
st_plugin_int *plugin, **reap, **list;
mysql_mutex_assert_owner(&LOCK_plugin);
if (!reap_needed) return;
reap_needed = false;
const size_t count = plugin_array->size();
reap = (st_plugin_int **)my_alloca(sizeof(plugin) * (count + 1));
*(reap++) = NULL;
for (size_t idx = 0; idx < count; idx++) {
plugin = plugin_array->at(idx);
if (plugin->state == PLUGIN_IS_DELETED && !plugin->ref_count) {
/* change the status flag to prevent reaping by another thread */
plugin->state = PLUGIN_IS_DYING;
*(reap++) = plugin;
}
}
mysql_mutex_unlock(&LOCK_plugin);
list = reap;
while ((plugin = *(--list))) {
if (!opt_initialize)
LogErr(INFORMATION_LEVEL, ER_PLUGIN_SHUTTING_DOWN_PLUGIN,
plugin->name.str);
plugin_deinitialize(plugin, true);
}
mysql_mutex_lock(&LOCK_plugin_delete);
mysql_mutex_lock(&LOCK_plugin);
while ((plugin = *(--reap))) plugin_del(plugin);
mysql_mutex_unlock(&LOCK_plugin_delete);
}
static void intern_plugin_unlock(LEX *lex, plugin_ref plugin) {
st_plugin_int *pi;
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
if (!plugin) return;
pi = plugin_ref_to_int(plugin);
#ifdef DBUG_OFF
if (!pi->plugin_dl) return;
#else
my_free(plugin);
#endif
DBUG_PRINT("info", ("unlocking plugin, name= %s, ref_count= %d", pi->name.str,
pi->ref_count));
if (lex) {
/*
Remove one instance of this plugin from the use list.
We are searching backwards so that plugins locked last
could be unlocked faster - optimizing for LIFO semantics.
*/
plugin_ref *iter = lex->plugins.end() - 1;
bool found_it MY_ATTRIBUTE((unused)) = false;
for (; iter >= lex->plugins.begin() - 1; --iter) {
if (plugin == *iter) {
lex->plugins.erase(iter);
found_it = true;
break;
}
}
DBUG_ASSERT(found_it);
}
DBUG_ASSERT(pi->ref_count);
pi->ref_count--;
if (pi->state == PLUGIN_IS_DELETED && !pi->ref_count) reap_needed = true;
}
void plugin_unlock(THD *thd, plugin_ref plugin) {
LEX *lex = thd ? thd->lex : 0;
DBUG_TRACE;
if (!plugin) return;
#ifdef DBUG_OFF
/* built-in plugins don't need ref counting */
if (!plugin_dlib(plugin)) return;
#endif
mysql_mutex_lock(&LOCK_plugin);
intern_plugin_unlock(lex, plugin);
reap_plugins();
mysql_mutex_unlock(&LOCK_plugin);
}
void plugin_unlock_list(THD *thd, plugin_ref *list, size_t count) {
LEX *lex = thd ? thd->lex : 0;
DBUG_TRACE;
DBUG_ASSERT(list);
/*
In unit tests, LOCK_plugin may be uninitialized, so do not lock it.
Besides: there's no point in locking it, if there are no plugins to unlock.
*/
if (count == 0) return;
mysql_mutex_lock(&LOCK_plugin);
while (count--) intern_plugin_unlock(lex, *list++);
reap_plugins();
mysql_mutex_unlock(&LOCK_plugin);
}
static int plugin_initialize(st_plugin_int *plugin) {
int ret = 1;
DBUG_TRACE;
mysql_mutex_assert_owner(&LOCK_plugin);
uint state = plugin->state;
DBUG_ASSERT(state == PLUGIN_IS_UNINITIALIZED);
mysql_mutex_unlock(&LOCK_plugin);
DEBUG_SYNC(current_thd, "in_plugin_initialize");
if (plugin_type_initialize[plugin->plugin->type]) {
if ((*plugin_type_initialize[plugin->plugin->type])(plugin)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_REGISTRATION_FAILED, plugin->name.str,
plugin_type_names[plugin->plugin->type].str);
goto err;
}
/* FIXME: Need better solution to transfer the callback function
array to memcached */
if (strcmp(plugin->name.str, "InnoDB") == 0) {
innodb_callback_data = ((handlerton *)plugin->data)->data;
}
} else if (plugin->plugin->init) {
if (strcmp(plugin->name.str, "daemon_memcached") == 0) {
plugin->data = innodb_callback_data;
}
if (plugin->plugin->init(plugin)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_INIT_FAILED, plugin->name.str);
goto err;
}
}
state = PLUGIN_IS_READY; // plugin->init() succeeded
if (plugin->plugin->status_vars) {
if (add_status_vars(plugin->plugin->status_vars)) goto err;
}
/*
set the plugin attribute of plugin's sys vars so they are pointing
to the active plugin
*/
if (plugin->system_vars) {
sys_var_pluginvar *var = plugin->system_vars->cast_pluginvar();
for (;;) {
var->plugin = plugin;
if (!var->next) break;
var = var->next->cast_pluginvar();
}
}
ret = 0;
err:
mysql_mutex_lock(&LOCK_plugin);
plugin->state = state;
return ret;
}
static inline void convert_dash_to_underscore(char *str, size_t len) {
for (char *p = str; p <= str + len; p++)
if (*p == '-') *p = '_';
}
#ifdef HAVE_PSI_INTERFACE
static PSI_mutex_key key_LOCK_plugin;
static PSI_mutex_key key_LOCK_plugin_delete;
static PSI_mutex_key key_LOCK_plugin_install;
/* clang-format off */
static PSI_mutex_info all_plugin_mutexes[]=
{
{ &key_LOCK_plugin, "LOCK_plugin", PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME},
{ &key_LOCK_plugin_delete, "LOCK_plugin_delete", PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME},
{ &key_LOCK_plugin_install, "LOCK_plugin_install", PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME}
};
/* clang-format on */
/* clang-format off */
static PSI_memory_info all_plugin_memory[]=
{
#ifndef DBUG_OFF
{ &key_memory_plugin_ref, "plugin_ref", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME},
#endif
{ &key_memory_plugin_mem_root, "plugin_mem_root", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME},
{ &key_memory_plugin_init_tmp, "plugin_init_tmp", 0, 0, PSI_DOCUMENT_ME},
{ &key_memory_plugin_int_mem_root, "plugin_int_mem_root", 0, 0, PSI_DOCUMENT_ME},
{ &key_memory_mysql_plugin_dl, "mysql_plugin_dl", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME},
{ &key_memory_mysql_plugin, "mysql_plugin", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME},
{ &key_memory_plugin_bookmark, "plugin_bookmark", PSI_FLAG_ONLY_GLOBAL_STAT, 0, PSI_DOCUMENT_ME}
};
/* clang-format on */
static void init_plugin_psi_keys(void) {
const char *category = "sql";
int count;
count = array_elements(all_plugin_mutexes);
mysql_mutex_register(category, all_plugin_mutexes, count);
count = array_elements(all_plugin_memory);
mysql_memory_register(category, all_plugin_memory, count);
}
#endif /* HAVE_PSI_INTERFACE */
/**
Initialize the internals of the plugin system. Allocate required
resources, initialize mutex, etc.
@return Operation outcome, false means no errors
*/
static bool plugin_init_internals() {
#ifdef HAVE_PSI_INTERFACE
init_plugin_psi_keys();
#endif
init_alloc_root(key_memory_plugin_mem_root, &plugin_mem_root, 4096, 4096);
bookmark_hash = new malloc_unordered_map<std::string, st_bookmark *>(
key_memory_plugin_bookmark);
malloced_string_type_sysvars_bookmark_hash =
new malloc_unordered_map<std::string, st_bookmark *>(
key_memory_plugin_bookmark);
mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_plugin_delete, &LOCK_plugin_delete,
MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_plugin_install, &LOCK_plugin_install,
MY_MUTEX_INIT_FAST);
plugin_dl_array = new (std::nothrow)
Prealloced_array<st_plugin_dl *, 16>(key_memory_mysql_plugin_dl);
plugin_array = new (std::nothrow)
Prealloced_array<st_plugin_int *, 16>(key_memory_mysql_plugin);
if (plugin_dl_array == NULL || plugin_array == NULL) goto err;
for (uint i = 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) {
plugin_hash[i] = new collation_unordered_map<std::string, st_plugin_int *>(
system_charset_info, key_memory_plugin_mem_root);
}
return false;
err:
return true;
}
/**
Initialize the plugins. Reap those that fail to initialize.
@return Operation outcome, false means no errors
*/
static bool plugin_init_initialize_and_reap() {
struct st_plugin_int *plugin_ptr;
struct st_plugin_int **reap;
/* Now we initialize all plugins that are not already initialized */
mysql_mutex_lock(&LOCK_plugin);
reap =
(st_plugin_int **)my_alloca((plugin_array->size() + 1) * sizeof(void *));
*(reap++) = NULL;
for (st_plugin_int **it = plugin_array->begin(); it != plugin_array->end();
++it) {
plugin_ptr = *it;
if (plugin_ptr->state == PLUGIN_IS_UNINITIALIZED) {
if (plugin_initialize(plugin_ptr)) {
plugin_ptr->state = PLUGIN_IS_DYING;
*(reap++) = plugin_ptr;
}
}
}
/* Check if any plugins have to be reaped */
bool reaped_mandatory_plugin = false;
while ((plugin_ptr = *(--reap))) {
mysql_mutex_unlock(&LOCK_plugin);
if (plugin_ptr->load_option == PLUGIN_FORCE ||
plugin_ptr->load_option == PLUGIN_FORCE_PLUS_PERMANENT)
reaped_mandatory_plugin = true;
plugin_deinitialize(plugin_ptr, true);
mysql_mutex_lock(&LOCK_plugin_delete);
mysql_mutex_lock(&LOCK_plugin);
plugin_del(plugin_ptr);
mysql_mutex_unlock(&LOCK_plugin_delete);
}
mysql_mutex_unlock(&LOCK_plugin);
if (reaped_mandatory_plugin) return true;
return false;
}
/**
Register and initialize early plugins.
@param argc Command line argument counter
@param argv Command line arguments
@param flags Flags to control whether dynamic loading
and plugin initialization should be skipped
@return Operation outcome, false if no errors
*/
bool plugin_register_early_plugins(int *argc, char **argv, int flags) {
bool retval = false;
DBUG_TRACE;
/* Don't allow initializing twice */
DBUG_ASSERT(!initialized);
/* Make sure the internals are initialized */
if ((retval = plugin_init_internals())) return retval;
/* Allocate the temporary mem root, will be freed before returning */
MEM_ROOT tmp_root;
init_alloc_root(key_memory_plugin_init_tmp, &tmp_root, 4096, 4096);
I_List_iterator<i_string> iter(opt_early_plugin_load_list);
i_string *item;
while (NULL != (item = iter++))
plugin_load_list(&tmp_root, argc, argv, item->ptr, true);
/* Temporary mem root not needed anymore, can free it here */
free_root(&tmp_root, MYF(0));
if (!(flags & PLUGIN_INIT_SKIP_INITIALIZATION))
retval = plugin_init_initialize_and_reap();
return retval;
}
/**
Register the builtin plugins. Some of the plugins (MyISAM, CSV and InnoDB)
are also initialized.
@param argc number of arguments, propagated to the plugin
@param argv actual arguments, propagated to the plugin
@return Operation outcome, false means no errors
*/
bool plugin_register_builtin_and_init_core_se(int *argc, char **argv) {
bool mandatory = true;
DBUG_TRACE;
/* Don't allow initializing twice */
DBUG_ASSERT(!initialized);
/* Allocate the temporary mem root, will be freed before returning */
MEM_ROOT tmp_root;
init_alloc_root(key_memory_plugin_init_tmp, &tmp_root, 4096, 4096);
mysql_mutex_lock(&LOCK_plugin);
initialized = true;
/* First we register the builtin mandatory and optional plugins */
for (struct st_mysql_plugin **builtins = mysql_mandatory_plugins;
*builtins || mandatory; builtins++) {
/* Switch to optional plugins when done with the mandatory ones */
if (!*builtins) {
builtins = mysql_optional_plugins;
mandatory = false;
if (!*builtins) break;
}
for (struct st_mysql_plugin *plugin = *builtins; plugin->info; plugin++) {
struct st_plugin_int tmp;
tmp.plugin = plugin;
tmp.name.str = plugin->name;
tmp.name.length = strlen(plugin->name);
tmp.state = 0;
tmp.load_option = mandatory ? PLUGIN_FORCE : PLUGIN_ON;
/*
If the performance schema is compiled in,
treat the storage engine plugin as 'mandatory',
to suppress any plugin-level options such as '--performance-schema'.
This is specific to the performance schema, and is done on purpose:
the server-level option '--performance-schema' controls the overall
performance schema initialization, which consists of much more that
the underlying storage engine initialization.
See mysqld.cc, set_vars.cc.
Suppressing ways to interfere directly with the storage engine alone
prevents awkward situations where:
- the user wants the performance schema functionality, by using
'--enable-performance-schema' (the server option),
- yet disable explicitly a component needed for the functionality
to work, by using '--skip-performance-schema' (the plugin)
*/
if (!my_strcasecmp(&my_charset_latin1, plugin->name,
"PERFORMANCE_SCHEMA")) {
tmp.load_option = PLUGIN_FORCE;
}
free_root(&tmp_root, MYF(MY_MARK_BLOCKS_FREE));
if (test_plugin_options(&tmp_root, &tmp, argc, argv))
tmp.state = PLUGIN_IS_DISABLED;
else
tmp.state = PLUGIN_IS_UNINITIALIZED;
struct st_plugin_int *plugin_ptr; // Pointer to registered plugin
if (register_builtin(plugin, &tmp, &plugin_ptr)) goto err_unlock;
/*
Only initialize MyISAM, InnoDB and CSV at this stage.
Note that when the --help option is supplied, InnoDB is not
initialized because the plugin table will not be read anyway,
as indicated by the flag set when the plugin_init() function
is called.
*/
bool is_myisam =
!my_strcasecmp(&my_charset_latin1, plugin->name, "MyISAM");
bool is_innodb =
!my_strcasecmp(&my_charset_latin1, plugin->name, "InnoDB");
if (!is_myisam && (!is_innodb || is_help_or_validate_option()) &&
my_strcasecmp(&my_charset_latin1, plugin->name, "CSV"))
continue;
if (plugin_ptr->state != PLUGIN_IS_UNINITIALIZED ||
plugin_initialize(plugin_ptr))
goto err_unlock;
/*
Initialize the global default storage engine so that it may
not be null in any child thread.
*/
if (is_myisam) {
DBUG_ASSERT(!global_system_variables.table_plugin);
DBUG_ASSERT(!global_system_variables.temp_table_plugin);
global_system_variables.table_plugin =
my_intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr));
global_system_variables.temp_table_plugin =
my_intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr));
DBUG_ASSERT(plugin_ptr->ref_count == 2);
}
}
}
/* Should now be set to MyISAM storage engine */
DBUG_ASSERT(global_system_variables.table_plugin);
DBUG_ASSERT(global_system_variables.temp_table_plugin);
mysql_mutex_unlock(&LOCK_plugin);
free_root(&tmp_root, MYF(0));
return false;
err_unlock:
mysql_mutex_unlock(&LOCK_plugin);
free_root(&tmp_root, MYF(0));
return true;
}
bool is_builtin_and_core_se_initialized() { return initialized; }
namespace dd {
namespace upgrade {
constexpr const char *delayed_plugins[] = {"audit_log", "mysql_firewall"};
/**
Initialize delayed plugins.
This function is used to initialize plugins that depend on changes in
the environment if there has been upgrade of mysql tables used by
the plugins.
@note This function will initialize all plugins that are in state
PLUGIN_IS_UNINITIALIZED. Plugins that are not in the submitted
list of plugin names will either be in state PLUGIN_IS_READY
at this point, or they should have been reaped already.
@return Operation outcome, false if no errors
*/
bool plugin_initialize_delayed_after_upgrade() {
/* Make sure the internals are initialized and builtins registered */
if (!initialized) return true;
/*
Iterate over named plugins and change state from
PLUGIN_IS_WAITING_FOR_UPGRADE to PLUGIN_IS_UNINITIALIZED.
*/
mysql_mutex_lock(&LOCK_plugin);
for (auto name : delayed_plugins) {
const LEX_CSTRING plugin_name = to_lex_cstring(name);
st_plugin_int *plugin_ptr =
plugin_find_internal(plugin_name, MYSQL_ANY_PLUGIN);
if (plugin_ptr != nullptr &&
plugin_ptr->state == PLUGIN_IS_WAITING_FOR_UPGRADE) {
plugin_ptr->state = PLUGIN_IS_UNINITIALIZED;
}
}
mysql_mutex_unlock(&LOCK_plugin);
/*
Then, initialize all plugins that are in state PLUGIN_UNINITIALIZED,
and reap those that fail to initialize. Plugins that failed to initialize
the last time we tried will have their state set to PLUGIN_IS_DYING, and
be deleted, so the plugins being initialized below should be only those
that are in the submitted list of plugin names.
*/
Auto_THD fake_session;
Disable_autocommit_guard autocommit_guard(fake_session.thd);
dd::cache::Dictionary_client::Auto_releaser releaser(
fake_session.thd->dd_client());
if (plugin_init_initialize_and_reap())
return ::end_transaction(fake_session.thd, true);
return ::end_transaction(fake_session.thd, false);
}
/**
Reset state of delayed plugins to indicate that they wait for upgrade to
complete. This means they will not be initialized yet.
*/
void delay_initialization_of_dependent_plugins() {
mysql_mutex_lock(&LOCK_plugin);
for (auto name : delayed_plugins) {
const LEX_CSTRING plugin_name = to_lex_cstring(name);
st_plugin_int *plugin_ptr =
plugin_find_internal(plugin_name, MYSQL_ANY_PLUGIN);
if (plugin_ptr != nullptr && plugin_ptr->state == PLUGIN_IS_UNINITIALIZED) {
plugin_ptr->state = PLUGIN_IS_WAITING_FOR_UPGRADE;
}
}
mysql_mutex_unlock(&LOCK_plugin);
}
} // namespace upgrade
} // namespace dd
/**
Register and initialize the dynamic plugins. Also initialize
the remaining builtin plugins that are not initialized
already.
@param argc Command line argument counter
@param argv Command line arguments
@param flags Flags to control whether dynamic loading
and plugin initialization should be skipped
@return Operation outcome, false if no errors
*/
bool plugin_register_dynamic_and_init_all(int *argc, char **argv, int flags) {
DBUG_TRACE;
/* Make sure the internals are initialized and builtins registered */
if (!initialized) return true;
/* Register all dynamic plugins */
if (!(flags & PLUGIN_INIT_SKIP_DYNAMIC_LOADING)) {
/* Allocate the temporary mem root, will be freed before returning */
MEM_ROOT tmp_root;
init_alloc_root(key_memory_plugin_init_tmp, &tmp_root, 4096, 4096);
I_List_iterator<i_string> iter(opt_plugin_load_list);
i_string *item;
while (NULL != (item = iter++))
plugin_load_list(&tmp_root, argc, argv, item->ptr, false);
if (!(flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE))
plugin_load(&tmp_root, argc, argv);
/* Temporary mem root not needed anymore, can free it here */
free_root(&tmp_root, MYF(0));
} else if (!opt_plugin_load_list.is_empty()) {
/* Table is always empty at initialize */
DBUG_ASSERT(opt_initialize);
/* Tell the user the plugin-load[-add] is ignored if not empty */
LogErr(WARNING_LEVEL, ER_PLUGIN_LOAD_OPTIONS_IGNORED);
}
/*
Delay initialization of plugins that depend on the environment.
*/
if ((flags & PLUGIN_INIT_DELAY_UNTIL_AFTER_UPGRADE)) {
dd::upgrade::delay_initialization_of_dependent_plugins();
}
/*
Initialize plugins that are in state 'PLUGIN_IS_UNINITIALIZED'.
*/
Auto_THD fake_session;
Disable_autocommit_guard autocommit_guard(fake_session.thd);
dd::cache::Dictionary_client::Auto_releaser releaser(
fake_session.thd->dd_client());
if (!(flags & PLUGIN_INIT_SKIP_INITIALIZATION))
if (plugin_init_initialize_and_reap()) {
return ::end_transaction(fake_session.thd, true);
}
return ::end_transaction(fake_session.thd, false);
}
static bool register_builtin(st_mysql_plugin *plugin, st_plugin_int *tmp,
st_plugin_int **ptr) {
DBUG_TRACE;
tmp->ref_count = 0;
tmp->plugin_dl = 0;
if (plugin_array->push_back(tmp)) return true;
*ptr = plugin_array->back() =
new (&plugin_mem_root) st_plugin_int(std::move(*tmp));
plugin_hash[plugin->type]->emplace(to_string((*ptr)->name), *ptr);
return 0;
}
/**
Reads the plugins from mysql.plugin and loads them
Called only by plugin_register_dynamic_and_init_all()
a.k.a. the bootstrap sequence.
@arg tmp_root memory root to use for plugin_add()
@arg argc number of command line arguments to process
@arg argv array of command line argument to read values from
@retval true failure
@retval false success
*/
static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) {
THD thd;
TABLE *table;
int error;
THD *new_thd = &thd;
bool result;
DBUG_TRACE;
TABLE_LIST tables("mysql", "plugin", TL_READ);
new_thd->thread_stack = (char *)&tables;
new_thd->store_globals();
LEX_CSTRING db_lex_cstr = {STRING_WITH_LEN("mysql")};
new_thd->set_db(db_lex_cstr);
thd.get_protocol_classic()->wipe_net();
result = open_trans_system_tables_for_read(new_thd, &tables);
if (result) {
DBUG_PRINT("error", ("Can't open plugin table"));
LogErr(ERROR_LEVEL, ER_PLUGIN_CANT_OPEN_PLUGIN_TABLE);
return;
}
table = tables.table;
unique_ptr_destroy_only<RowIterator> iterator =
init_table_iterator(new_thd, table, NULL, false,
/*ignore_not_found_rows=*/false);
if (iterator == nullptr) {
close_trans_system_tables(new_thd);
return;
}
table->use_all_columns();
/*
there're no other threads running yet, so we don't need a mutex.
but plugin_add() before is designed to work in multi-threaded
environment, and it uses mysql_mutex_assert_owner(), so we lock
the mutex here to satisfy the assert
*/
while (!(error = iterator->Read())) {
DBUG_PRINT("info", ("init plugin record"));
String str_name, str_dl;
get_field(tmp_root, table->field[0], &str_name);
get_field(tmp_root, table->field[1], &str_dl);
LEX_CSTRING name = str_name.lex_cstring();
LEX_STRING dl = str_dl.lex_string();
/*
The whole locking sequence is not strictly speaking needed since this
is a function that's executed only during server bootstrap, but we do
it properly for uniformity of the environment for plugin_add.
Note that it must be done for each iteration since, unlike INSTALL PLUGIN
the bootstrap process just reports the error and goes on.
So to ensure the right sequence of lock and unlock we need to take and
release both the wlock and the mutex.
*/
mysql_mutex_lock(&LOCK_plugin);
mysql_rwlock_wrlock(&LOCK_system_variables_hash);
if (plugin_add(tmp_root, name, &dl, argc, argv, REPORT_TO_LOG, false)) {
LogErr(WARNING_LEVEL, ER_PLUGIN_CANT_LOAD, str_name.c_ptr(),
str_dl.c_ptr());
} else {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
}
free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
}
if (error > 0) {
char errbuf[MYSQL_ERRMSG_SIZE];
LogErr(ERROR_LEVEL, ER_GET_ERRNO_FROM_STORAGE_ENGINE, my_errno(),
my_strerror(errbuf, MYSQL_ERRMSG_SIZE, my_errno()));
}
iterator.reset();
table->m_needs_reopen = true; // Force close to free memory
close_trans_system_tables(new_thd);
}
/**
Load a list of plugins
Called by plugin_register_early_plugins() and
plugin_register_dynamic_and_init_all(), a.k.a. the bootstrap sequence.
@arg tmp_root memory root to use for plugin_add()
@arg argc number of command line arguments to process
@arg argv array of command line argument to read values from
@arg list list of plugins to load. Ends with a NULL pointer
@arg load_early true if loading plugins via --early-plugin-load or migration
@retval true failure
@retval false success
*/
static bool plugin_load_list(MEM_ROOT *tmp_root, int *argc, char **argv,
const char *list, bool load_early) {
char buffer[FN_REFLEN];
LEX_STRING name = {buffer, 0}, dl = {NULL, 0}, *str = &name;
st_plugin_dl *plugin_dl;
st_mysql_plugin *plugin;
char *p = buffer;
DBUG_TRACE;
while (list) {
if (p == buffer + sizeof(buffer) - 1) {
LogErr(ERROR_LEVEL, ER_PLUGIN_LOAD_PARAMETER_TOO_LONG);
return true;
}
switch ((*(p++) = *(list++))) {
case '\0':
list = NULL; /* terminate the loop */
/* fall through */
case ';':
#ifndef _WIN32
case ':': /* can't use this as delimiter as it may be drive letter */
#endif
str->str[str->length] = '\0';
if (str == &name) // load all plugins in named module
{
if (!name.length) {
p--; /* reset pointer */
continue;
}
dl = name;
/*
The whole locking sequence is not strictly speaking needed since
this is a function that's executed only during server bootstrap, but
we do it properly for uniformity of the environment for plugin_add.
*/
mysql_mutex_lock(&LOCK_plugin);
mysql_rwlock_wrlock(&LOCK_system_variables_hash);
if ((plugin_dl = plugin_dl_add(&dl, REPORT_TO_LOG, load_early))) {
for (plugin = plugin_dl->plugins; plugin->info; plugin++) {
name.str = const_cast<char *>(plugin->name);
name.length = strlen(name.str);
free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
if (plugin_add(tmp_root, to_lex_cstring(name), &dl, argc, argv,
REPORT_TO_LOG, load_early))
goto error;
}
plugin_dl_del(&dl); // reduce ref count
} else
goto error;
} else {
free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
/*
The whole locking sequence is not strictly speaking needed since
this is a function that's executed only during server bootstrap, but
we do it properly for uniformity of the environment for plugin_add.
*/
mysql_mutex_lock(&LOCK_plugin);
mysql_rwlock_wrlock(&LOCK_system_variables_hash);
if (plugin_add(tmp_root, to_lex_cstring(name), &dl, argc, argv,
REPORT_TO_LOG, load_early))
goto error;
}
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
name.length = dl.length = 0;
dl.str = NULL;
name.str = p = buffer;
str = &name;
continue;
case '=':
case '#':
if (str == &name) {
name.str[name.length] = '\0';
str = &dl;
str->str = p;
continue;
}
// Fall through.
default:
str->length++;
continue;
}
}
return false;
error:
LogErr(ERROR_LEVEL, ER_PLUGIN_CANT_LOAD, name.str, dl.str);
return true;
}
/*
Shutdown memcached plugin before binlog shuts down
*/
void memcached_shutdown(void) {
if (initialized) {
for (st_plugin_int **it = plugin_array->begin(); it != plugin_array->end();
++it) {
st_plugin_int *plugin = *it;
if (plugin->state == PLUGIN_IS_READY &&
strcmp(plugin->name.str, "daemon_memcached") == 0) {
plugin_deinitialize(plugin, true);
mysql_mutex_lock(&LOCK_plugin_delete);
mysql_mutex_lock(&LOCK_plugin);
plugin->state = PLUGIN_IS_DYING;
plugin_del(plugin);
mysql_mutex_unlock(&LOCK_plugin);
mysql_mutex_unlock(&LOCK_plugin_delete);
}
}
}
}
/*
Deinitialize and unload all the loaded plugins.
Note: During valgrind testing, the shared objects (.dll/.so)
are not unloaded in order to keep the call stack
of the leaked objects.
*/
void plugin_shutdown(void) {
size_t i;
st_plugin_int **plugins, *plugin;
st_plugin_dl **dl;
bool skip_binlog = true;
DBUG_TRACE;
if (initialized) {
size_t count = plugin_array->size();
mysql_mutex_lock(&LOCK_plugin);
reap_needed = true;
/*
We want to shut down plugins in a reasonable order, this will
become important when we have plugins which depend upon each other.
Circular references cannot be reaped so they are forced afterwards.
TODO: Have an additional step here to notify all active plugins that
shutdown is requested to allow plugins to deinitialize in parallel.
*/
while (reap_needed && (count = plugin_array->size())) {
reap_plugins();
for (i = 0; i < count; i++) {
plugin = plugin_array->at(i);
if (plugin->state == PLUGIN_IS_READY &&
strcmp(plugin->name.str, "binlog") == 0 && skip_binlog) {
skip_binlog = false;
} else if (plugin->state == PLUGIN_IS_READY) {
plugin->state = PLUGIN_IS_DELETED;
reap_needed = true;
}
}
if (!reap_needed) {
/*
release any plugin references held.
*/
unlock_variables(&global_system_variables);
unlock_variables(&max_system_variables);
}
}
plugins = (st_plugin_int **)my_alloca(sizeof(void *) * (count + 1));
/*
If we have any plugins which did not die cleanly, we force shutdown
*/
for (i = 0; i < count; i++) {
plugins[i] = plugin_array->at(i);
/* change the state to ensure no reaping races */
if (plugins[i]->state == PLUGIN_IS_DELETED)
plugins[i]->state = PLUGIN_IS_DYING;
}
mysql_mutex_unlock(&LOCK_plugin);
/*
We loop through all plugins and call deinit() if they have one.
*/
for (i = 0; i < count; i++)
if (!(plugins[i]->state &
(PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_FREED | PLUGIN_IS_DISABLED))) {
LogErr(WARNING_LEVEL, ER_PLUGIN_FORCING_SHUTDOWN, plugins[i]->name.str);
/*
We are forcing deinit on plugins so we don't want to do a ref_count
check until we have processed all the plugins.
*/
plugin_deinitialize(plugins[i], false);
}
/*
It's perfectly safe not to lock LOCK_plugin, LOCK_plugin_delete, as
there're no concurrent threads anymore. But some functions called from
here use mysql_mutex_assert_owner(), so we lock the mutex to satisfy it
*/
mysql_mutex_lock(&LOCK_plugin_delete);
mysql_mutex_lock(&LOCK_plugin);
/*
We defer checking ref_counts until after all plugins are deinitialized
as some may have worker threads holding on to plugin references.
*/
for (i = 0; i < count; i++) {
if (plugins[i]->ref_count)
LogErr(ERROR_LEVEL, ER_PLUGIN_HAS_NONZERO_REFCOUNT_AFTER_SHUTDOWN,
plugins[i]->name.str, plugins[i]->ref_count);
if (plugins[i]->state & PLUGIN_IS_UNINITIALIZED) plugin_del(plugins[i]);
}
/*
Now we can deallocate all memory.
*/
cleanup_variables(NULL, &global_system_variables);
cleanup_variables(NULL, &max_system_variables);
mysql_mutex_unlock(&LOCK_plugin);
mysql_mutex_unlock(&LOCK_plugin_delete);
initialized = false;
mysql_mutex_destroy(&LOCK_plugin);
mysql_mutex_destroy(&LOCK_plugin_delete);
mysql_mutex_destroy(&LOCK_plugin_install);
}
/* Dispose of the memory */
for (i = 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) {
delete plugin_hash[i];
plugin_hash[i] = nullptr;
}
delete plugin_array;
plugin_array = NULL;
if (plugin_dl_array != NULL) {
size_t count = plugin_dl_array->size();
dl = (st_plugin_dl **)my_alloca(sizeof(void *) * count);
for (i = 0; i < count; i++) dl[i] = plugin_dl_array->at(i);
for (i = 0; i < plugin_dl_array->size(); i++) free_plugin_mem(dl[i]);
delete plugin_dl_array;
plugin_dl_array = NULL;
}
delete bookmark_hash;
bookmark_hash = nullptr;
delete malloced_string_type_sysvars_bookmark_hash;
malloced_string_type_sysvars_bookmark_hash = nullptr;
free_root(&plugin_mem_root, MYF(0));
global_variables_dynamic_size = 0;
}
// Helper function to do rollback or commit, depending on error.
bool end_transaction(THD *thd, bool error) {
if (error) {
// Rollback the statement before we can rollback the real transaction.
trans_rollback_stmt(thd);
trans_rollback(thd);
} else if (trans_commit_stmt(thd) || trans_commit(thd)) {
error = true;
trans_rollback(thd);
}
// Close tables regardless of error.
close_thread_tables(thd);
return error;
}
/**
Initialize one plugin. This function is used to early load one single
plugin. This function is used by key migration tool.
@param[in] argc Command line argument counter
@param[in] argv Command line arguments
@param[in] plugin library file name
@return Operation status
@retval 0 OK
@retval 1 ERROR
*/
bool plugin_early_load_one(int *argc, char **argv, const char *plugin) {
bool retval = false;
DBUG_TRACE;
/* Make sure the internals are initialized */
if (!initialized) {
if ((retval = plugin_init_internals()))
return retval;
else
initialized = true;
}
/* Allocate the temporary mem root, will be freed before returning */
MEM_ROOT tmp_root;
init_alloc_root(PSI_NOT_INSTRUMENTED, &tmp_root, 4096, 4096);
plugin_load_list(&tmp_root, argc, argv, plugin, true);
/* Temporary mem root not needed anymore, can free it here */
free_root(&tmp_root, MYF(0));
retval = plugin_init_initialize_and_reap();
return retval;
}
static bool mysql_install_plugin(THD *thd, LEX_CSTRING name,
const LEX_STRING *dl) {
TABLE *table;
bool error = true;
int argc = orig_argc;
char **argv = orig_argv;
st_plugin_int *tmp = nullptr;
bool store_infoschema_metadata = false;
dd::Schema_MDL_locker mdl_handler(thd);
Persisted_variables_cache *pv = Persisted_variables_cache::get_instance();
DBUG_TRACE;
Disable_autocommit_guard autocommit_guard(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
TABLE_LIST tables("mysql", "plugin", TL_WRITE);
if (!opt_noacl &&
check_table_access(thd, INSERT_ACL, &tables, false, 1, false))
return true;
if (acquire_shared_global_read_lock(thd, thd->variables.lock_wait_timeout) ||
acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
return true;
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (!(table = open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
return true;
/*
Pre-acquire audit plugins for events that may potentially occur
during [UN]INSTALL PLUGIN.
When audit event is triggered, audit subsystem acquires interested
plugins by walking through plugin list. Evidently plugin list
iterator protects plugin list by acquiring LOCK_plugin, see
plugin_foreach_with_mask().
On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
rather for a long time.
When audit event is triggered during [UN]INSTALL PLUGIN, plugin
list iterator acquires the same lock (within the same thread)
second time.
This hack should be removed when LOCK_plugin is fixed so it
protects only what it supposed to protect.
*/
mysql_audit_acquire_plugins(thd, MYSQL_AUDIT_GENERAL_CLASS,
MYSQL_AUDIT_GENERAL_ALL);
mysql_mutex_lock(&LOCK_plugin_install);
mysql_mutex_lock(&LOCK_plugin);
DEBUG_SYNC(thd, "acquired_LOCK_plugin");
mysql_rwlock_wrlock(&LOCK_system_variables_hash);
{
MEM_ROOT alloc{PSI_NOT_INSTRUMENTED, 512};
my_getopt_use_args_separator = true;
if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv,
&alloc, NULL)) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name.str);
goto err;
}
my_getopt_use_args_separator = false;
/*
Append static variables present in mysqld-auto.cnf file for the
newly installed plugin to process those options which are specific
to this plugin.
*/
if (pv && pv->append_read_only_variables(&argc, &argv, true)) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
mysql_mutex_unlock(&LOCK_plugin);
report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name.str);
goto err;
}
error =
plugin_add(thd->mem_root, name, dl, &argc, argv, REPORT_TO_USER, false);
}
/* LOCK_plugin and LOCK_system_variables_hash already unlocked by plugin_add()
if error */
if (error) goto err;
mysql_rwlock_unlock(&LOCK_system_variables_hash);
if (!(tmp = plugin_find_internal(name, MYSQL_ANY_PLUGIN))) {
mysql_mutex_unlock(&LOCK_plugin);
goto err;
}
error = false;
if (tmp->state == PLUGIN_IS_DISABLED) {
push_warning_printf(thd, Sql_condition::SL_WARNING, ER_CANT_INITIALIZE_UDF,
ER_THD(thd, ER_CANT_INITIALIZE_UDF), name.str,
"Plugin is disabled");
}
// Check if we need to store I_S plugin metadata in DD.
store_infoschema_metadata =
(tmp->plugin->type == MYSQL_INFORMATION_SCHEMA_PLUGIN &&
tmp->state != PLUGIN_IS_DISABLED);
mysql_mutex_unlock(&LOCK_plugin);
// Acquire MDL lock if we are storing metadata in DD.
if (store_infoschema_metadata) {
if (!mdl_handler.ensure_locked(INFORMATION_SCHEMA_NAME.str)) {
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE,
INFORMATION_SCHEMA_NAME.str, tmp->name.str,
MDL_EXCLUSIVE, MDL_TRANSACTION);
if (thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
error = true;
} else
error = true;
if (error) {
report_error(REPORT_TO_USER, ER_DA_PLUGIN_INSTALL_ERROR, name.str,
"error acquiring metadata lock");
}
}
/*
We do not replicate the INSTALL PLUGIN statement. Disable binlogging
of the insert into the plugin table, so that it is not replicated in
row based mode.
*/
if (!error) {
Disable_binlog_guard binlog_guard(thd);
table->use_all_columns();
restore_record(table, s->default_values);
table->field[0]->store(name.str, name.length, system_charset_info);
table->field[1]->store(dl->str, dl->length, files_charset_info);
error = table->file->ha_write_row(table->record[0]);
if (error) {
const char msg[] = "got '%s' writing to mysql.plugin";
char buf[MYSQL_ERRMSG_SIZE + sizeof(msg) - 2];
char errbuf[MYSQL_ERRMSG_SIZE];
my_strerror(errbuf, sizeof(errbuf), error);
snprintf(buf, sizeof(buf), msg, errbuf);
report_error(REPORT_TO_USER, ER_DA_PLUGIN_INSTALL_ERROR, name.str, buf);
} else {
mysql_mutex_lock(&LOCK_plugin);
if (tmp->state != PLUGIN_IS_DISABLED && plugin_initialize(tmp)) {
my_error(ER_CANT_INITIALIZE_UDF, MYF(0), name.str,
"Plugin initialization function failed.");
error = true;
}
/*
Store plugin I_S table metadata into DD tables. The
tables are closed before the function returns.
*/
error = error || thd->transaction_rollback_request;
if (!error && store_infoschema_metadata) {
error = dd::info_schema::store_dynamic_plugin_I_S_metadata(thd, tmp);
if (error) {
report_error(REPORT_TO_USER, ER_DA_PLUGIN_INSTALL_ERROR, name.str,
"error storing metadata");
}
}
mysql_mutex_unlock(&LOCK_plugin);
if (!error && store_infoschema_metadata) {
Uncommitted_tables_guard uncommitted_tables(thd);
error = update_referencing_views_metadata(
thd, INFORMATION_SCHEMA_NAME.str, tmp->name.str, false,
&uncommitted_tables);
if (error) {
report_error(REPORT_TO_USER, ER_DA_PLUGIN_INSTALL_ERROR, name.str,
"error updating metadata");
}
}
}
}
if (error) {
mysql_mutex_lock(&LOCK_plugin);
tmp->state = PLUGIN_IS_DELETED;
reap_needed = true;
reap_plugins();
mysql_mutex_unlock(&LOCK_plugin);
}
err:
mysql_mutex_unlock(&LOCK_plugin_install);
return end_transaction(thd, error);
}
static bool mysql_uninstall_plugin(THD *thd, LEX_CSTRING name) {
TABLE *table;
st_plugin_int *plugin;
bool error = true;
int rc = 0;
bool remove_IS_metadata_from_dd = false;
dd::Schema_MDL_locker mdl_handler(thd);
dd::String_type orig_plugin_name;
DBUG_TRACE;
TABLE_LIST tables("mysql", 5, "plugin", 6, "plugin", TL_WRITE);
if (!opt_noacl &&
check_table_access(thd, DELETE_ACL, &tables, false, 1, false)) {
DBUG_ASSERT(thd->is_error());
return true;
}
if (acquire_shared_global_read_lock(thd, thd->variables.lock_wait_timeout) ||
acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
return true;
Disable_autocommit_guard autocommit_guard(thd);
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (!(table =
open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) {
DBUG_ASSERT(thd->is_error());
return true;
}
mysql_mutex_lock(&LOCK_plugin_install);
if (!table->key_info) {
my_error(ER_MISSING_KEY, MYF(0), table->s->db.str,
table->s->table_name.str);
goto err;
}
/*
Pre-acquire audit plugins for events that may potentially occur
during [UN]INSTALL PLUGIN.
When audit event is triggered, audit subsystem acquires interested
plugins by walking through plugin list. Evidently plugin list
iterator protects plugin list by acquiring LOCK_plugin, see
plugin_foreach_with_mask().
On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
rather for a long time.
When audit event is triggered during [UN]INSTALL PLUGIN, plugin
list iterator acquires the same lock (within the same thread)
second time.
This hack should be removed when LOCK_plugin is fixed so it
protects only what it supposed to protect.
*/
mysql_audit_acquire_plugins(thd, MYSQL_AUDIT_GENERAL_CLASS,
MYSQL_AUDIT_GENERAL_ALL);
mysql_mutex_lock(&LOCK_plugin);
if (!(plugin = plugin_find_internal(name, MYSQL_ANY_PLUGIN)) ||
plugin->state & (PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_DYING)) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PLUGIN", name.str);
goto err;
}
if (!plugin->plugin_dl) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_PLUGIN_DELETE_BUILTIN, MYF(0));
goto err;
}
if (plugin->load_option == PLUGIN_FORCE_PLUS_PERMANENT) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_PLUGIN_IS_PERMANENT, MYF(0), name.str);
goto err;
}
/*
Error message for ER_PLUGIN_IS_PERMANENT is not suitable for
plugins marked as not dynamically uninstallable, so we have a
separate one instead of changing the old one.
*/
if (plugin->plugin->flags & PLUGIN_OPT_NO_UNINSTALL) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_PLUGIN_NO_UNINSTALL, MYF(0), plugin->plugin->name);
goto err;
}
/*
FIXME: plugin rpl_semi_sync_master, check_uninstall() function.
*/
/* Block Uninstallation of semi_sync plugins (Master/Slave)
when they are busy
*/
char buff[20];
size_t buff_length;
/*
Master: If there are active semi sync slaves for this Master,
then that means it is busy and rpl_semi_sync_master plugin
cannot be uninstalled. To check whether the master
has any semi sync slaves or not, check Rpl_semi_sync_master_cliens
status variable value, if it is not 0, that means it is busy.
*/
if (!strcmp(name.str, "rpl_semi_sync_master") &&
get_status_var(thd, plugin->plugin->status_vars,
"Rpl_semi_sync_master_clients", buff, OPT_DEFAULT,
&buff_length) &&
strcmp(buff, "0")) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_PLUGIN_CANNOT_BE_UNINSTALLED, MYF(0), name.str,
"Stop any active semisynchronous slaves of this master first.");
goto err;
}
/*
FIXME: plugin rpl_semi_sync_slave, check_uninstall() function.
*/
/* Slave: If there is semi sync enabled IO thread active on this Slave,
then that means plugin is busy and rpl_semi_sync_slave plugin
cannot be uninstalled. To check whether semi sync
IO thread is active or not, check Rpl_semi_sync_slave_status status
variable value, if it is ON, that means it is busy.
*/
if (!strcmp(name.str, "rpl_semi_sync_slave") &&
get_status_var(thd, plugin->plugin->status_vars,
"Rpl_semi_sync_slave_status", buff, OPT_DEFAULT,
&buff_length) &&
!strcmp(buff, "ON")) {
mysql_mutex_unlock(&LOCK_plugin);
my_error(
ER_PLUGIN_CANNOT_BE_UNINSTALLED, MYF(0), name.str,
"Stop any active semisynchronous I/O threads on this slave first.");
goto err;
}
if ((plugin->plugin->check_uninstall) && (plugin->state == PLUGIN_IS_READY)) {
int check;
/*
Prevent other threads to uninstall concurrently this plugin.
*/
plugin->state = PLUGIN_IS_DYING;
mysql_mutex_unlock(&LOCK_plugin);
DEBUG_SYNC(current_thd, "in_plugin_check_uninstall");
/*
Check uninstall may perform complex operations,
including acquiring MDL locks, which in turn may need LOCK_plugin.
*/
DBUG_PRINT("info", ("check uninstall plugin: '%s'", plugin->name.str));
check = plugin->plugin->check_uninstall(plugin);
mysql_mutex_lock(&LOCK_plugin);
DBUG_ASSERT(plugin->state == PLUGIN_IS_DYING);
if (check) {
DBUG_PRINT("warning",
("Plugin '%s' blocked uninstall.", plugin->name.str));
plugin->state = PLUGIN_IS_READY;
mysql_mutex_unlock(&LOCK_plugin);
my_error(ER_PLUGIN_CANNOT_BE_UNINSTALLED, MYF(0), name.str,
"Plugin is still in use.");
goto err;
}
}
plugin->state = PLUGIN_IS_DELETED;
if (plugin->ref_count)
push_warning(thd, Sql_condition::SL_WARNING, WARN_PLUGIN_BUSY,
ER_THD(thd, WARN_PLUGIN_BUSY));
else
reap_needed = true;
// Check if we need to remove I_S plugin metadata from DD.
remove_IS_metadata_from_dd =
(plugin->plugin->type == MYSQL_INFORMATION_SCHEMA_PLUGIN &&
plugin->load_option != PLUGIN_OFF);
orig_plugin_name = dd::String_type(plugin->name.str, plugin->name.length);
reap_plugins();
mysql_mutex_unlock(&LOCK_plugin);
uchar user_key[MAX_KEY_LENGTH];
table->use_all_columns();
table->field[0]->store(name.str, name.length, system_charset_info);
key_copy(user_key, table->record[0], table->key_info,
table->key_info->key_length);
if ((rc = table->file->ha_index_read_idx_map(
table->record[0], 0, user_key, HA_WHOLE_KEY, HA_READ_KEY_EXACT)) ==
0) {
/*
We do not replicate the UNINSTALL PLUGIN statement. Disable binlogging
of the delete from the plugin table, so that it is not replicated in
row based mode.
*/
DBUG_ASSERT(!thd->is_error());
Disable_binlog_guard binlog_guard(thd);
rc = table->file->ha_delete_row(table->record[0]);
if (rc) {
DBUG_ASSERT(thd->is_error());
} else
error = false;
} else if (rc != HA_ERR_KEY_NOT_FOUND && rc != HA_ERR_END_OF_FILE) {
DBUG_ASSERT(thd->is_error());
} else
error = false;
if (error) {
const char msg[] = "got '%s' deleting from mysql.plugin";
char buf[MYSQL_ERRMSG_SIZE + sizeof(msg) - 2];
char errbuf[MYSQL_ERRMSG_SIZE];
my_strerror(errbuf, sizeof(errbuf), error);
snprintf(buf, sizeof(buf), msg, errbuf);
report_error(REPORT_TO_USER, ER_PLUGIN_UNINSTALL_ERROR, name.str, buf);
}
if (!error && !thd->transaction_rollback_request &&
remove_IS_metadata_from_dd) {
error = dd::info_schema::remove_I_S_view_metadata(
thd,
dd::String_type(orig_plugin_name.c_str(), orig_plugin_name.length()));
DBUG_ASSERT(!error || thd->is_error());
if (!error) {
Uncommitted_tables_guard uncommitted_tables(thd);
error = update_referencing_views_metadata(
thd, INFORMATION_SCHEMA_NAME.str, orig_plugin_name.c_str(), false,
&uncommitted_tables);
}
if (error) {
report_error(REPORT_TO_USER, ER_PLUGIN_UNINSTALL_ERROR, name.str,
"error updating metadata");
}
}
err:
mysql_mutex_unlock(&LOCK_plugin_install);
return end_transaction(thd, error || thd->transaction_rollback_request);
}
bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func **funcs, int type,
uint state_mask, void *arg) {
size_t idx, total;
st_plugin_int *plugin, **plugins;
int version = plugin_array_version;
DBUG_TRACE;
if (!initialized) return false;
state_mask = ~state_mask; // do it only once
mysql_mutex_lock(&LOCK_plugin);
total = type == MYSQL_ANY_PLUGIN ? plugin_array->size()
: plugin_hash[type]->size();
/*
Do the alloca out here in case we do have a working alloca:
leaving the nested stack frame invalidates alloca allocation.
*/
plugins = (st_plugin_int **)my_alloca(total * sizeof(plugin));
if (type == MYSQL_ANY_PLUGIN) {
for (idx = 0; idx < total; idx++) {
plugin = plugin_array->at(idx);
plugins[idx] = !(plugin->state & state_mask) ? plugin : NULL;
}
} else {
collation_unordered_map<std::string, st_plugin_int *> *hash =
plugin_hash[type];
idx = 0;
for (const auto &key_and_value : *hash) {
plugin = key_and_value.second;
plugins[idx++] = !(plugin->state & state_mask) ? plugin : NULL;
}
}
mysql_mutex_unlock(&LOCK_plugin);
size_t binlog_index = 0;
bool found_binlog = false;
/* Identify binary log SE which we need to invoke first. */
if (type == MYSQL_STORAGE_ENGINE_PLUGIN) {
for (idx = 0; idx < total; idx++) {
/* Note index of binlog */
plugin = plugins[idx];
if (plugin && (0 == std::strcmp(plugin->name.str, "binlog"))) {
binlog_index = idx;
found_binlog = true;
break;
}
}
}
for (; *funcs != NULL; ++funcs) {
/* Call binlog engine function first. This is required as GTID is generated
by binlog to be used by othe SE. */
if (found_binlog) {
DBUG_ASSERT(type == MYSQL_STORAGE_ENGINE_PLUGIN);
plugin = plugins[binlog_index];
if (plugin && (*funcs)(thd, plugin_int_to_ref(plugin), arg)) goto err;
plugins[binlog_index] = nullptr;
}
for (idx = 0; idx < total; idx++) {
if (unlikely(version != plugin_array_version)) {
mysql_mutex_lock(&LOCK_plugin);
for (size_t i = idx; i < total; i++)
if (plugins[i] && plugins[i]->state & state_mask) plugins[i] = 0;
mysql_mutex_unlock(&LOCK_plugin);
}
plugin = plugins[idx];
/* It will stop iterating on first engine error when "func" returns true
*/
if (plugin && (*funcs)(thd, plugin_int_to_ref(plugin), arg)) goto err;
}
}
return false;
err:
return true;
}
bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func, int type,
uint state_mask, void *arg) {
plugin_foreach_func *funcs[] = {func, NULL};
return plugin_foreach_with_mask(thd, funcs, type, state_mask, arg);
}
/****************************************************************************
System Variables support
****************************************************************************/
/*
This function is not thread safe as the pointer returned at the end of
the function is outside mutex.
*/
void lock_plugin_mutex() { mysql_mutex_lock(&LOCK_plugin); }
void unlock_plugin_mutex() { mysql_mutex_unlock(&LOCK_plugin); }
sys_var *find_sys_var_ex(THD *thd, const char *str, size_t length,
bool throw_error, bool locked) {
sys_var *var;
sys_var_pluginvar *pi = NULL;
plugin_ref plugin;
DBUG_TRACE;
if (!locked) mysql_mutex_lock(&LOCK_plugin);
mysql_rwlock_rdlock(&LOCK_system_variables_hash);
if ((var = intern_find_sys_var(str, length)) &&
(pi = var->cast_pluginvar()) && pi->is_plugin) {
mysql_rwlock_unlock(&LOCK_system_variables_hash);
LEX *lex = thd ? thd->lex : 0;
if (!(plugin = my_intern_plugin_lock(lex, plugin_int_to_ref(pi->plugin))))
var = NULL; /* failed to lock it, it must be uninstalling */
else if (!(plugin_state(plugin) & PLUGIN_IS_READY)) {
/* initialization not completed */
var = NULL;
intern_plugin_unlock(lex, plugin);
}
} else
mysql_rwlock_unlock(&LOCK_system_variables_hash);
if (!locked) mysql_mutex_unlock(&LOCK_plugin);
if (!throw_error && !var) my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), str);
return var;
}
sys_var *find_sys_var(THD *thd, const char *str, size_t length) {
return find_sys_var_ex(thd, str, length, false, false);
}
/*
returns a bookmark for thd-local variables, creating if neccessary.
returns null for non thd-local variables.
Requires that a write lock is obtained on LOCK_system_variables_hash
*/
static st_bookmark *register_var(const char *plugin, const char *name,
int flags) {
size_t length = strlen(plugin) + strlen(name) + 3, size = 0, offset, new_size;
st_bookmark *result;
char *varname, *p;
if (!(flags & PLUGIN_VAR_THDLOCAL)) return NULL;
switch (flags & PLUGIN_VAR_TYPEMASK) {
case PLUGIN_VAR_BOOL:
size = sizeof(bool);
break;
case PLUGIN_VAR_INT:
size = sizeof(int);
break;
case PLUGIN_VAR_LONG:
case PLUGIN_VAR_ENUM:
size = sizeof(long);
break;
case PLUGIN_VAR_LONGLONG:
case PLUGIN_VAR_SET:
size = sizeof(ulonglong);
break;
case PLUGIN_VAR_STR:
size = sizeof(char *);
break;
case PLUGIN_VAR_DOUBLE:
size = sizeof(double);
break;
default:
DBUG_ASSERT(0);
return NULL;
};
varname = ((char *)my_alloca(length));
strxmov(varname + 1, plugin, "_", name, NullS);
for (p = varname + 1; *p; p++)
if (*p == '-') *p = '_';
if (!(result = find_bookmark(NULL, varname + 1, flags))) {
result =
(st_bookmark *)plugin_mem_root.Alloc(sizeof(st_bookmark) + length - 1);
varname[0] = flags & PLUGIN_VAR_TYPEMASK;
memcpy(result->key, varname, length);
result->name_len = length - 2;
result->offset = -1;
DBUG_ASSERT(size && !(size & (size - 1))); /* must be power of 2 */
offset = global_system_variables.dynamic_variables_size;
offset = (offset + size - 1) & ~(size - 1);
result->offset = (int)offset;
new_size = (offset + size + 63) & ~63;
if (new_size > global_variables_dynamic_size) {
global_system_variables.dynamic_variables_ptr = (char *)my_realloc(
key_memory_global_system_variables,
global_system_variables.dynamic_variables_ptr, new_size,
MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
max_system_variables.dynamic_variables_ptr = (char *)my_realloc(
key_memory_global_system_variables,
max_system_variables.dynamic_variables_ptr, new_size,
MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
/*
Clear the new variable value space. This is required for string
variables. If their value is non-NULL, it must point to a valid
string.
*/
memset(global_system_variables.dynamic_variables_ptr +
global_variables_dynamic_size,
0, new_size - global_variables_dynamic_size);
memset(max_system_variables.dynamic_variables_ptr +
global_variables_dynamic_size,
0, new_size - global_variables_dynamic_size);
global_variables_dynamic_size = new_size;
}
global_system_variables.dynamic_variables_head = offset;
max_system_variables.dynamic_variables_head = offset;
global_system_variables.dynamic_variables_size = offset + size;
max_system_variables.dynamic_variables_size = offset + size;
global_system_variables.dynamic_variables_version++;
max_system_variables.dynamic_variables_version++;
result->version = global_system_variables.dynamic_variables_version;
/* this should succeed because we have already checked if a dup exists */
std::string key(result->key, result->name_len + 1);
bookmark_hash->emplace(key, result);
/*
Hashing vars of string type with MEMALLOC flag.
*/
if (((flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR) &&
(flags & PLUGIN_VAR_MEMALLOC) &&
!malloced_string_type_sysvars_bookmark_hash->emplace(key, result)
.second) {
fprintf(stderr,
"failed to add placeholder to"
" hash of malloced string type sysvars");
DBUG_ASSERT(0);
}
}
return result;
}
static void restore_pluginvar_names(sys_var *first) {
for (sys_var *var = first; var; var = var->next) {
sys_var_pluginvar *pv = var->cast_pluginvar();
pv->plugin_var->name = pv->orig_pluginvar_name;
}
}
/**
Allocate memory and copy dynamic variables from global system variables
to per-thread system variables copy.
@param thd thread context
@param global_lock If true LOCK_global_system_variables should be
acquired while copying variables from global
variables copy.
*/
void alloc_and_copy_thd_dynamic_variables(THD *thd, bool global_lock) {
mysql_rwlock_rdlock(&LOCK_system_variables_hash);
if (global_lock) mysql_mutex_lock(&LOCK_global_system_variables);
mysql_mutex_assert_owner(&LOCK_global_system_variables);
/*
MAINTAINER:
The following assert is wrong on purpose, useful to debug
when thd dynamic variables are expanded:
DBUG_ASSERT(thd->variables.dynamic_variables_ptr == NULL);
*/
thd->variables.dynamic_variables_ptr = (char *)my_realloc(
key_memory_THD_variables, thd->variables.dynamic_variables_ptr,
global_variables_dynamic_size, MYF(MY_WME | MY_FAE | MY_ALLOW_ZERO_PTR));
/*
Debug hook which allows tests to check that this code is not
called for InnoDB after connection was created.
*/
DBUG_EXECUTE_IF("verify_innodb_thdvars", DBUG_ASSERT(0););
memcpy(thd->variables.dynamic_variables_ptr +
thd->variables.dynamic_variables_size,
global_system_variables.dynamic_variables_ptr +
thd->variables.dynamic_variables_size,
global_system_variables.dynamic_variables_size -
thd->variables.dynamic_variables_size);
/*
Iterate through newly copied vars of string type with MEMALLOC
flag and strdup value.
*/
for (const auto &key_and_value :
*malloced_string_type_sysvars_bookmark_hash) {
sys_var_pluginvar *pi;
sys_var *var;
int varoff;
char **thdvar, **sysvar;
st_bookmark *v = key_and_value.second;
if (v->version <= thd->variables.dynamic_variables_version ||
!(var = intern_find_sys_var(v->key + 1, v->name_len)) ||
!(pi = var->cast_pluginvar()) ||
v->key[0] != (pi->plugin_var->flags & PLUGIN_VAR_TYPEMASK))
continue;
varoff = *(int *)(pi->plugin_var + 1);
thdvar = (char **)(thd->variables.dynamic_variables_ptr + varoff);
sysvar = (char **)(global_system_variables.dynamic_variables_ptr + varoff);
*thdvar = NULL;
plugin_var_memalloc_session_update(thd, NULL, thdvar, *sysvar);
}
if (global_lock) mysql_mutex_unlock(&LOCK_global_system_variables);
thd->variables.dynamic_variables_version =
global_system_variables.dynamic_variables_version;
thd->variables.dynamic_variables_head =
global_system_variables.dynamic_variables_head;
thd->variables.dynamic_variables_size =
global_system_variables.dynamic_variables_size;
mysql_rwlock_unlock(&LOCK_system_variables_hash);
}
/**
For correctness and simplicity's sake, a pointer to a function
must be compatible with pointed-to type, that is, the return and
parameters types must be the same. Thus, a callback function is
defined for each scalar type. The functions are assigned in
construct_options to their respective types.
*/
static bool *mysql_sys_var_bool(THD *thd, int offset) {
return (bool *)intern_sys_var_ptr(thd, offset, true);
}
static int *mysql_sys_var_int(THD *thd, int offset) {
return (int *)intern_sys_var_ptr(thd, offset, true);
}
static unsigned int *mysql_sys_var_uint(THD *thd, int offset) {
return (unsigned int *)intern_sys_var_ptr(thd, offset, true);
}
static unsigned long *mysql_sys_var_ulong(THD *thd, int offset) {
return (unsigned long *)intern_sys_var_ptr(thd, offset, true);
}
static unsigned long long *mysql_sys_var_ulonglong(THD *thd, int offset) {
return (unsigned long long *)intern_sys_var_ptr(thd, offset, true);
}
static char **mysql_sys_var_str(THD *thd, int offset) {
return (char **)intern_sys_var_ptr(thd, offset, true);
}
static double *mysql_sys_var_double(THD *thd, int offset) {
return (double *)intern_sys_var_ptr(thd, offset, true);
}
void plugin_thdvar_init(THD *thd, bool enable_plugins) {
plugin_ref old_table_plugin = thd->variables.table_plugin;
plugin_ref old_temp_table_plugin = thd->variables.temp_table_plugin;
DBUG_TRACE;
thd->variables.table_plugin = NULL;
thd->variables.temp_table_plugin = NULL;
cleanup_variables(thd, &thd->variables);
mysql_mutex_lock(&LOCK_global_system_variables);
thd->variables = global_system_variables;
thd->variables.table_plugin = NULL;
thd->variables.temp_table_plugin = NULL;
thd->variables.dynamic_variables_version = 0;
thd->variables.dynamic_variables_size = 0;
thd->variables.dynamic_variables_ptr = 0;
if (enable_plugins) {
mysql_mutex_lock(&LOCK_plugin);
thd->variables.table_plugin =
my_intern_plugin_lock(NULL, global_system_variables.table_plugin);
intern_plugin_unlock(NULL, old_table_plugin);
thd->variables.temp_table_plugin =
my_intern_plugin_lock(NULL, global_system_variables.temp_table_plugin);
intern_plugin_unlock(NULL, old_temp_table_plugin);
mysql_mutex_unlock(&LOCK_plugin);
}
mysql_mutex_unlock(&LOCK_global_system_variables);
/* Initialize all Sys_var_charptr variables here. */
// @@session.session_track_system_variables
thd->session_sysvar_res_mgr.init(&thd->variables.track_sysvars_ptr);
}
/*
Unlocks all system variables which hold a reference
*/
static void unlock_variables(struct System_variables *vars) {
intern_plugin_unlock(NULL, vars->table_plugin);
intern_plugin_unlock(NULL, vars->temp_table_plugin);
vars->table_plugin = NULL;
vars->temp_table_plugin = NULL;
}
/*
Frees memory used by system variables
Unlike plugin_vars_free_values() it frees all variables of all plugins,
it's used on shutdown.
*/
static void cleanup_variables(THD *thd, struct System_variables *vars) {
if (thd) {
/* Block the Performance Schema from accessing THD::variables. */
mysql_mutex_lock(&thd->LOCK_thd_data);
plugin_var_memalloc_free(&thd->variables);
/* Remove references to session_sysvar_res_mgr memory before freeing it. */
thd->variables.track_sysvars_ptr = NULL;
thd->session_sysvar_res_mgr.deinit();
}
DBUG_ASSERT(vars->table_plugin == NULL);
DBUG_ASSERT(vars->temp_table_plugin == NULL);
my_free(vars->dynamic_variables_ptr);
vars->dynamic_variables_ptr = NULL;
vars->dynamic_variables_size = 0;
vars->dynamic_variables_version = 0;
if (thd) mysql_mutex_unlock(&thd->LOCK_thd_data);
}
void plugin_thdvar_cleanup(THD *thd, bool enable_plugins) {
DBUG_TRACE;
if (enable_plugins) {
MUTEX_LOCK(plugin_lock, &LOCK_plugin);
unlock_variables(&thd->variables);
size_t idx;
if ((idx = thd->lex->plugins.size())) {
plugin_ref *list = thd->lex->plugins.end() - 1;
DBUG_PRINT("info", ("unlocking %u plugins", static_cast<uint>(idx)));
while (list >= thd->lex->plugins.begin())
intern_plugin_unlock(thd->lex, *list--);
}
reap_plugins();
thd->lex->plugins.clear();
}
cleanup_variables(thd, &thd->variables);
}
/**
@brief Free values of thread variables of a plugin.
This must be called before a plugin is deleted. Otherwise its
variables are no longer accessible and the value space is lost. Note
that only string values with PLUGIN_VAR_MEMALLOC are allocated and
must be freed.
@param[in] vars Chain of system variables of a plugin
*/
static void plugin_vars_free_values(sys_var *vars) {
DBUG_TRACE;
for (sys_var *var = vars; var; var = var->next) {
sys_var_pluginvar *piv = var->cast_pluginvar();
if (piv &&
((piv->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR) &&
(piv->plugin_var->flags & PLUGIN_VAR_MEMALLOC)) {
/* Free the string from global_system_variables. */
char **valptr = (char **)piv->real_value_ptr(NULL, OPT_GLOBAL);
DBUG_PRINT("plugin",
("freeing value for: '%s' addr: %p", var->name.str, valptr));
my_free(*valptr);
*valptr = NULL;
}
}
}
/**
Set value for a thread local variable.
@param[in] thd Thread context.
@param[in] var Plugin variable.
@param[in,out] dest Destination memory pointer.
@param[in] value New value.
Note: new value should be '\0'-terminated for string variables.
Used in plugin.h:THDVAR_SET(thd, name, value) macro.
*/
void plugin_thdvar_safe_update(THD *thd, SYS_VAR *var, char **dest,
const char *value) {
DBUG_ASSERT(thd == current_thd);
if (var->flags & PLUGIN_VAR_THDLOCAL) {
if ((var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
var->flags & PLUGIN_VAR_MEMALLOC)
plugin_var_memalloc_session_update(thd, var, dest, value);
else
var->update(thd, var, dest, value);
}
}
/**
Free all elements allocated by plugin_var_memalloc_session_update().
@param[in] vars system variables structure
@see plugin_var_memalloc_session_update
*/
static void plugin_var_memalloc_free(struct System_variables *vars) {
LIST *next, *root;
DBUG_TRACE;
for (root = vars->dynamic_variables_allocs; root; root = next) {
next = root->next;
my_free(root);
}
vars->dynamic_variables_allocs = NULL;
}
extern "C" bool get_one_plugin_option(int, const struct my_option *, char *);
bool get_one_plugin_option(int, const struct my_option *, char *) { return 0; }
/**
Creates a set of my_option objects associated with a specified plugin-
handle.
@param mem_root Memory allocator to be used.
@param tmp A pointer to a plugin handle
@param[out] options A pointer to a pre-allocated static array
The set is stored in the pre-allocated static array supplied to the function.
The size of the array is calculated as (number_of_plugin_varaibles*2+3). The
reason is that each option can have a prefix '--plugin-' in addtion to the
shorter form '--&lt;plugin-name&gt;'. There is also space allocated for
terminating NULL pointers.
@return
@retval -1 An error occurred
@retval 0 Success
*/
static int construct_options(MEM_ROOT *mem_root, st_plugin_int *tmp,
my_option *options) {
const char *plugin_name = tmp->plugin->name;
const LEX_CSTRING plugin_dash = {STRING_WITH_LEN("plugin-")};
size_t plugin_name_len = strlen(plugin_name);
size_t optnamelen;
const int max_comment_len = 180;
char *comment = (char *)mem_root->Alloc(max_comment_len + 1);
char *optname;
int index = 0, offset = 0;
SYS_VAR *opt, **plugin_option;
st_bookmark *v;
/** Used to circumvent the const attribute on my_option::name */
char *plugin_name_ptr, *plugin_name_with_prefix_ptr;
DBUG_TRACE;
plugin_name_ptr = (char *)mem_root->Alloc(plugin_name_len + 1);
strcpy(plugin_name_ptr, plugin_name);
my_casedn_str(&my_charset_latin1, plugin_name_ptr);
convert_underscore_to_dash(plugin_name_ptr, plugin_name_len);
plugin_name_with_prefix_ptr =
(char *)mem_root->Alloc(plugin_name_len + plugin_dash.length + 1);
strxmov(plugin_name_with_prefix_ptr, plugin_dash.str, plugin_name_ptr, NullS);
if (tmp->load_option != PLUGIN_FORCE &&
tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT) {
/* support --skip-plugin-foo syntax */
options[0].name = plugin_name_ptr;
options[1].name = plugin_name_with_prefix_ptr;
options[0].id = 0;
options[1].id = -1;
options[0].var_type = options[1].var_type = GET_ENUM;
options[0].arg_type = options[1].arg_type = OPT_ARG;
options[0].def_value = options[1].def_value = 1; /* ON */
options[0].typelib = options[1].typelib = &global_plugin_typelib;
strxnmov(comment, max_comment_len, "Enable or disable ", plugin_name,
" plugin. Possible values are ON, OFF, FORCE (don't start "
"if the plugin fails to load).",
NullS);
options[0].comment = comment;
/*
Allocate temporary space for the value of the tristate.
This option will have a limited lifetime and is not used beyond
server initialization.
GET_ENUM value is an unsigned long integer.
*/
options[0].value = options[1].value =
(uchar **)mem_root->Alloc(sizeof(ulong));
*((ulong *)options[0].value) = (ulong)options[0].def_value;
options[0].arg_source = options[1].arg_source =
(get_opt_arg_source *)mem_root->Alloc(sizeof(get_opt_arg_source));
memset(options[0].arg_source, 0, sizeof(get_opt_arg_source));
options[0].arg_source->m_path_name[0] = 0;
options[1].arg_source->m_path_name[0] = 0;
options[0].arg_source->m_source = options[1].arg_source->m_source =
enum_variable_source::COMPILED;
options += 2;
}
if (!my_strcasecmp(&my_charset_latin1, plugin_name_ptr, "NDBCLUSTER")) {
plugin_name_ptr = const_cast<char *>("ndb"); // Use legacy "ndb" prefix
plugin_name_len = 3;
}
/*
Two passes as the 2nd pass will take pointer addresses for use
by my_getopt and register_var() in the first pass uses realloc
*/
for (plugin_option = tmp->plugin->system_vars;
plugin_option && *plugin_option; plugin_option++, index++) {
opt = *plugin_option;
if (!(opt->flags & PLUGIN_VAR_THDLOCAL)) continue;
if (!(register_var(plugin_name_ptr, opt->name, opt->flags))) continue;
switch (opt->flags & PLUGIN_VAR_TYPEMASK) {
case PLUGIN_VAR_BOOL:
((thdvar_bool_t *)opt)->resolve = mysql_sys_var_bool;
break;
case PLUGIN_VAR_INT:
// All PLUGIN_VAR_INT variables are actually uint,
// see struct System_variables
// Except: plugin variables declared with MYSQL_THDVAR_INT,
// which may actually be signed.
if (((thdvar_int_t *)opt)->offset == -1 &&
!(opt->flags & PLUGIN_VAR_UNSIGNED))
((thdvar_int_t *)opt)->resolve = mysql_sys_var_int;
else
((thdvar_uint_t *)opt)->resolve = mysql_sys_var_uint;
break;
case PLUGIN_VAR_LONG:
// All PLUGIN_VAR_LONG variables are actually ulong,
// see struct System_variables
((thdvar_ulong_t *)opt)->resolve = mysql_sys_var_ulong;
break;
case PLUGIN_VAR_LONGLONG:
// All PLUGIN_VAR_LONGLONG variables are actually ulonglong,
// see struct System_variables
((thdvar_ulonglong_t *)opt)->resolve = mysql_sys_var_ulonglong;
break;
case PLUGIN_VAR_STR:
((thdvar_str_t *)opt)->resolve = mysql_sys_var_str;
break;
case PLUGIN_VAR_ENUM:
((thdvar_enum_t *)opt)->resolve = mysql_sys_var_ulong;
break;
case PLUGIN_VAR_SET:
((thdvar_set_t *)opt)->resolve = mysql_sys_var_ulonglong;
break;
case PLUGIN_VAR_DOUBLE:
((thdvar_double_t *)opt)->resolve = mysql_sys_var_double;
break;
default:
LogErr(ERROR_LEVEL, ER_PLUGIN_UNKNOWN_VARIABLE_TYPE, opt->flags,
plugin_name);
return -1;
};
}
for (plugin_option = tmp->plugin->system_vars;
plugin_option && *plugin_option; plugin_option++, index++) {
switch ((opt = *plugin_option)->flags & PLUGIN_VAR_TYPEMASK) {
case PLUGIN_VAR_BOOL:
if (!opt->check) opt->check = check_func_bool;
if (!opt->update) opt->update = update_func_bool;
break;
case PLUGIN_VAR_INT:
if (!opt->check) opt->check = check_func_int;
if (!opt->update) opt->update = update_func_int;
break;
case PLUGIN_VAR_LONG:
if (!opt->check) opt->check = check_func_long;
if (!opt->update) opt->update = update_func_long;
break;
case PLUGIN_VAR_LONGLONG:
if (!opt->check) opt->check = check_func_longlong;
if (!opt->update) opt->update = update_func_longlong;
break;
case PLUGIN_VAR_STR:
if (!opt->check) opt->check = check_func_str;
if (!opt->update) {
opt->update = update_func_str;
if (!(opt->flags & (PLUGIN_VAR_MEMALLOC | PLUGIN_VAR_READONLY))) {
opt->flags |= PLUGIN_VAR_READONLY;
LogErr(WARNING_LEVEL, ER_PLUGIN_VARIABLE_SET_READ_ONLY, opt->name,
plugin_name);
}
}
break;
case PLUGIN_VAR_ENUM:
if (!opt->check) opt->check = check_func_enum;
if (!opt->update) opt->update = update_func_long;
break;
case PLUGIN_VAR_SET:
if (!opt->check) opt->check = check_func_set;
if (!opt->update) opt->update = update_func_longlong;
break;
case PLUGIN_VAR_DOUBLE:
if (!opt->check) opt->check = check_func_double;
if (!opt->update) opt->update = update_func_double;
break;
default:
LogErr(ERROR_LEVEL, ER_PLUGIN_UNKNOWN_VARIABLE_TYPE, opt->flags,
plugin_name);
return -1;
}
if ((opt->flags & (PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_THDLOCAL)) ==
PLUGIN_VAR_NOCMDOPT)
continue;
if (!opt->name) {
LogErr(ERROR_LEVEL, ER_PLUGIN_VARIABLE_MISSING_NAME, plugin_name);
return -1;
}
if (!(opt->flags & PLUGIN_VAR_THDLOCAL)) {
optnamelen = strlen(opt->name);
optname = (char *)mem_root->Alloc(plugin_name_len + optnamelen + 2);
strxmov(optname, plugin_name_ptr, "-", opt->name, NullS);
optnamelen = plugin_name_len + optnamelen + 1;
} else {
/* this should not fail because register_var should create entry */
if (!(v = find_bookmark(plugin_name_ptr, opt->name, opt->flags))) {
LogErr(ERROR_LEVEL, ER_PLUGIN_VARIABLE_NOT_ALLOCATED_THREAD_LOCAL,
opt->name, plugin_name);
return -1;
}
*(int *)(opt + 1) = offset = v->offset;
if (opt->flags & PLUGIN_VAR_NOCMDOPT) continue;
optname = (char *)memdup_root(mem_root, v->key + 1,
(optnamelen = v->name_len) + 1);
}
convert_underscore_to_dash(optname, optnamelen);
options->name = optname;
options->comment = opt->comment;
options->app_type = opt;
options->id = 0;
plugin_opt_set_limits(options, opt);
if (opt->flags & PLUGIN_VAR_THDLOCAL)
options->value = options->u_max_value =
(uchar **)(global_system_variables.dynamic_variables_ptr + offset);
else
options->value = options->u_max_value = *(uchar ***)(opt + 1);
char *option_name_ptr;
options[1] = options[0];
options[1].id = -1;
options[1].name = option_name_ptr =
(char *)mem_root->Alloc(plugin_dash.length + optnamelen + 1);
options[1].comment = 0; /* Hidden from the help text */
strxmov(option_name_ptr, plugin_dash.str, optname, NullS);
options[0].arg_source = options[1].arg_source =
(get_opt_arg_source *)mem_root->Alloc(sizeof(get_opt_arg_source));
memset(options[0].arg_source, 0, sizeof(get_opt_arg_source));
options[0].arg_source->m_path_name[0] = 0;
options[1].arg_source->m_path_name[0] = 0;
options[0].arg_source->m_source = options[1].arg_source->m_source =
enum_variable_source::COMPILED;
options += 2;
}
return 0;
}
static my_option *construct_help_options(MEM_ROOT *mem_root, st_plugin_int *p) {
SYS_VAR **opt;
my_option *opts;
uint count = EXTRA_OPTIONS;
DBUG_TRACE;
for (opt = p->plugin->system_vars; opt && *opt; opt++, count += 2)
;
if (!(opts = (my_option *)mem_root->Alloc(sizeof(my_option) * count)))
return NULL;
memset(opts, 0, sizeof(my_option) * count);
/**
some plugin variables (those that don't have PLUGIN_VAR_EXPERIMENTAL flag)
have their names prefixed with the plugin name. Restore the names here
to get the correct (not double-prefixed) help text.
We won't need @@sysvars anymore and don't care about their proper names.
*/
restore_pluginvar_names(p->system_vars);
if (construct_options(mem_root, p, opts)) return NULL;
return opts;
}
/**
Check option being used and raise deprecation warning if required.
@param optid ID of the option that was passed through command line
@param opt List of options
@param argument unused
A deprecation warning will be raised if --plugin-xxx type of option
is used.
@return Always returns success as purpose of the function is to raise
warning only.
@retval 0 Success
*/
static bool check_if_option_is_deprecated(
int optid, const struct my_option *opt,
char *argument MY_ATTRIBUTE((unused))) {
if (optid == -1) {
push_deprecated_warn(NULL, opt->name, (opt->name + strlen("plugin-")));
}
return 0;
}
/**
Create and register system variables supplied from the plugin and
assigns initial values from corresponding command line arguments.
@param tmp_root Temporary scratch space
@param[out] tmp Internal plugin structure
@param argc Number of command line arguments
@param argv Command line argument vector
The plugin will be updated with a policy on how to handle errors during
initialization.
@note Requires that a write-lock is held on LOCK_system_variables_hash
@return How initialization of the plugin should be handled.
@retval 0 Initialization should proceed.
@retval 1 Plugin is disabled.
@retval -1 An error has occurred.
*/
static int test_plugin_options(MEM_ROOT *tmp_root, st_plugin_int *tmp,
int *argc, char **argv) {
struct sys_var_chain chain = {NULL, NULL};
bool disable_plugin;
enum_plugin_load_option plugin_load_option = tmp->load_option;
/*
We should use tmp->mem_root here instead of the global plugin_mem_root,
but tmp->root is not always properly freed, so it will cause leaks in
Valgrind (e.g. the main.validate_password_plugin test).
*/
MEM_ROOT *mem_root = &plugin_mem_root;
SYS_VAR **opt;
my_option *opts = NULL;
LEX_CSTRING plugin_name;
char *varname;
int error;
sys_var *v MY_ATTRIBUTE((unused));
st_bookmark *var;
size_t len;
uint count = EXTRA_OPTIONS;
DBUG_TRACE;
DBUG_ASSERT(tmp->plugin && tmp->name.str);
/*
The 'federated' and 'ndbcluster' storage engines are always disabled by
default.
*/
if (!(my_strcasecmp(&my_charset_latin1, tmp->name.str, "federated") &&
my_strcasecmp(&my_charset_latin1, tmp->name.str, "ndbcluster")))
plugin_load_option = PLUGIN_OFF;
for (opt = tmp->plugin->system_vars; opt && *opt; opt++)
count += 2; /* --{plugin}-{optname} and --plugin-{plugin}-{optname} */
if (count > EXTRA_OPTIONS || (*argc > 1)) {
if (!(opts = (my_option *)tmp_root->Alloc(sizeof(my_option) * count))) {
LogErr(ERROR_LEVEL, ER_PLUGIN_OOM, tmp->name.str);
return -1;
}
memset(opts, 0, sizeof(my_option) * count);
if (construct_options(tmp_root, tmp, opts)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_BAD_OPTIONS, tmp->name.str);
return -1;
}
/*
We adjust the default value to account for the hardcoded exceptions
we have set for the federated and ndbcluster storage engines.
*/
if (tmp->load_option != PLUGIN_FORCE &&
tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT)
opts[0].def_value = opts[1].def_value = plugin_load_option;
error = handle_options(argc, &argv, opts, check_if_option_is_deprecated);
(*argc)++; /* add back one for the program name */
if (error) {
LogErr(ERROR_LEVEL, ER_PLUGIN_PARSING_OPTIONS_FAILED, tmp->name.str);
goto err;
}
/*
Set plugin loading policy from option value. First element in the option
list is always the <plugin name> option value.
*/
if (tmp->load_option != PLUGIN_FORCE &&
tmp->load_option != PLUGIN_FORCE_PLUS_PERMANENT)
plugin_load_option = (enum_plugin_load_option) * (ulong *)opts[0].value;
}
disable_plugin = (plugin_load_option == PLUGIN_OFF);
tmp->load_option = plugin_load_option;
/*
If the plugin is disabled it should not be initialized.
*/
if (disable_plugin) {
LogErr(INFORMATION_LEVEL, ER_PLUGIN_DISABLED, tmp->name.str);
if (opts) my_cleanup_options(opts);
return 1;
}
if (!my_strcasecmp(&my_charset_latin1, tmp->name.str, "NDBCLUSTER")) {
plugin_name.str = const_cast<char *>("ndb"); // Use legacy "ndb" prefix
plugin_name.length = 3;
} else
plugin_name = tmp->name;
error = 1;
for (opt = tmp->plugin->system_vars; opt && *opt; opt++) {
SYS_VAR *o;
if (((o = *opt)->flags & PLUGIN_VAR_NOSYSVAR)) continue;
if ((var = find_bookmark(plugin_name.str, o->name, o->flags)))
v = new (mem_root) sys_var_pluginvar(&chain, var->key + 1, o);
else {
len = plugin_name.length + strlen(o->name) + 2;
varname = (char *)mem_root->Alloc(len);
strxmov(varname, plugin_name.str, "-", o->name, NullS);
my_casedn_str(&my_charset_latin1, varname);
convert_dash_to_underscore(varname, len - 1);
v = new (mem_root) sys_var_pluginvar(&chain, varname, o);
}
DBUG_ASSERT(v); /* check that an object was actually constructed */
const my_option *optp = opts;
if (findopt(o->name, strlen(o->name), &optp))
v->set_arg_source(optp->arg_source);
} /* end for */
if (chain.first) {
chain.last->next = NULL;
if (mysql_add_sys_var_chain(chain.first)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_HAS_CONFLICTING_SYSTEM_VARIABLES,
tmp->name.str);
goto err;
}
tmp->system_vars = chain.first;
}
/*
Once server is started and if there are few persisted plugin variables
which needs to be handled, we do it here.
*/
if (mysqld_server_started) {
Persisted_variables_cache *pv = Persisted_variables_cache::get_instance();
if (pv && pv->set_persist_options(true)) {
LogErr(ERROR_LEVEL, ER_PLUGIN_CANT_SET_PERSISTENT_OPTIONS, tmp->name.str);
goto err;
}
}
return 0;
err:
if (opts) my_cleanup_options(opts);
return error;
}
/****************************************************************************
Help Verbose text with Plugin System Variables
****************************************************************************/
void add_plugin_options(std::vector<my_option> *options, MEM_ROOT *mem_root) {
my_option *opt;
if (!initialized) return;
for (st_plugin_int **it = plugin_array->begin(); it != plugin_array->end();
++it) {
st_plugin_int *p = *it;
if (!(opt = construct_help_options(mem_root, p))) continue;
/* Only options with a non-NULL comment are displayed in help text */
for (; opt->name; opt++)
if (opt->comment) options->push_back(*opt);
}
}
/**
Searches for a correctly loaded plugin of a particular type by name
@param plugin the name of the plugin we're looking for
@param type type of the plugin (0-MYSQL_MAX_PLUGIN_TYPE_NUM)
@return plugin, or NULL if not found
*/
st_plugin_int *plugin_find_by_type(const LEX_CSTRING &plugin, int type) {
st_plugin_int *ret;
DBUG_TRACE;
ret = plugin_find_internal(plugin, type);
return ret && ret->state == PLUGIN_IS_READY ? ret : NULL;
}
/**
Locks the plugin strucutres so calls to plugin_find_inner can be issued.
Must be followed by unlock_plugin_data.
*/
int lock_plugin_data() {
DBUG_TRACE;
return mysql_mutex_lock(&LOCK_plugin);
}
/**
Unlocks the plugin strucutres as locked by lock_plugin_data()
*/
int unlock_plugin_data() {
DBUG_TRACE;
return mysql_mutex_unlock(&LOCK_plugin);
}
bool Sql_cmd_install_plugin::execute(THD *thd) {
bool st = mysql_install_plugin(thd, m_comment, &m_ident);
if (!st) my_ok(thd);
mysql_audit_release(thd);
return st;
}
bool Sql_cmd_uninstall_plugin::execute(THD *thd) {
bool st = mysql_uninstall_plugin(thd, m_comment);
if (!st) my_ok(thd);
return st;
}