summaryrefslogtreecommitdiffstats
path: root/sql/sql_plugin.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sql/sql_plugin.cc4470
1 files changed, 4470 insertions, 0 deletions
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc
new file mode 100644
index 00000000..330d8b66
--- /dev/null
+++ b/sql/sql_plugin.cc
@@ -0,0 +1,4470 @@
+/*
+ Copyright (c) 2005, 2018, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2020, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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-1335 USA */
+
+#include "sql_plugin.h" // SHOW_MY_BOOL
+#include "sql_priv.h"
+#include "unireg.h"
+#include "sql_class.h" // set_var.h: THD
+#include "sys_vars_shared.h"
+#include "sql_locale.h"
+#include "sql_plugin.h"
+#include "sql_parse.h" // check_table_access
+#include "sql_base.h" // close_mysql_tables
+#include "key.h" // key_copy
+#include "sql_table.h"
+#include "sql_show.h" // remove_status_vars, add_status_vars
+#include "strfunc.h" // find_set
+#include "records.h" // init_read_record, end_read_record
+#include <my_pthread.h>
+#include <my_getopt.h>
+#include "sql_audit.h"
+#include <mysql/plugin_auth.h>
+#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT
+#include <mysql/plugin_auth.h>
+#include <mysql/plugin_password_validation.h>
+#include <mysql/plugin_encryption.h>
+#include <mysql/plugin_data_type.h>
+#include <mysql/plugin_function.h>
+#include "sql_plugin_compat.h"
+
+static PSI_memory_key key_memory_plugin_mem_root;
+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;
+
+#ifdef HAVE_LINK_H
+#include <link.h>
+#endif
+
+extern struct st_maria_plugin *mysql_optional_plugins[];
+extern struct st_maria_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;
+char *opt_plugin_dir_ptr;
+char opt_plugin_dir[FN_REFLEN];
+ulong plugin_maturity;
+
+static LEX_CSTRING MYSQL_PLUGIN_NAME= {STRING_WITH_LEN("plugin") };
+
+/*
+ not really needed now, this map will become essential when we add more
+ maturity levels. We cannot change existing maturity constants,
+ so the next value - even if it will be MariaDB_PLUGIN_MATURITY_VERY_BUGGY -
+ will inevitably be larger than MariaDB_PLUGIN_MATURITY_STABLE.
+ To be able to compare them we use this mapping array
+*/
+uint plugin_maturity_map[]=
+{ 0, 1, 2, 3, 4, 5, 6 };
+
+/*
+ When you add 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("PASSWORD VALIDATION") },
+ { STRING_WITH_LEN("ENCRYPTION") },
+ { STRING_WITH_LEN("DATA TYPE") },
+ { STRING_WITH_LEN("FUNCTION") }
+};
+
+extern int initialize_schema_table(st_plugin_int *plugin);
+extern int finalize_schema_table(st_plugin_int *plugin);
+
+extern int initialize_audit_plugin(st_plugin_int *plugin);
+extern int finalize_audit_plugin(st_plugin_int *plugin);
+
+extern int initialize_encryption_plugin(st_plugin_int *plugin);
+extern int finalize_encryption_plugin(st_plugin_int *plugin);
+
+extern int initialize_data_type_plugin(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, initialize_encryption_plugin,
+ initialize_data_type_plugin, 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, finalize_encryption_plugin, 0,
+ 0 // FUNCTION
+};
+
+/*
+ Defines in which order plugin types have to be initialized.
+ Essentially, we want to initialize MYSQL_KEY_MANAGEMENT_PLUGIN before
+ MYSQL_STORAGE_ENGINE_PLUGIN, and that before MYSQL_INFORMATION_SCHEMA_PLUGIN
+*/
+static int plugin_type_initialization_order[MYSQL_MAX_PLUGIN_TYPE_NUM]=
+{
+ MYSQL_DAEMON_PLUGIN,
+ MariaDB_ENCRYPTION_PLUGIN,
+ MariaDB_DATA_TYPE_PLUGIN,
+ MariaDB_FUNCTION_PLUGIN,
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ MYSQL_FTPARSER_PLUGIN,
+ MYSQL_AUTHENTICATION_PLUGIN,
+ MariaDB_PASSWORD_VALIDATION_PLUGIN,
+ MYSQL_AUDIT_PLUGIN,
+ MYSQL_REPLICATION_PLUGIN,
+ MYSQL_UDF_PLUGIN
+};
+
+#ifdef HAVE_DLOPEN
+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 const char *maria_plugin_interface_version_sym=
+ "_maria_plugin_interface_version_";
+static const char *maria_sizeof_st_plugin_sym=
+ "_maria_sizeof_struct_st_plugin_";
+static const char *maria_plugin_declarations_sym=
+ "_maria_plugin_declarations_";
+static int min_maria_plugin_interface_version=
+ MARIA_PLUGIN_INTERFACE_VERSION & ~0xFF;
+#endif
+
+/* 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,
+ MIN_AUTHENTICATION_INTERFACE_VERSION,
+ MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION,
+ MariaDB_ENCRYPTION_INTERFACE_VERSION,
+ MariaDB_DATA_TYPE_INTERFACE_VERSION,
+ MariaDB_FUNCTION_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,
+ MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION,
+ MariaDB_ENCRYPTION_INTERFACE_VERSION,
+ MariaDB_DATA_TYPE_INTERFACE_VERSION,
+ MariaDB_FUNCTION_INTERFACE_VERSION
+};
+
+static struct
+{
+ const char *plugin_name;
+ enum enum_plugin_load_option override;
+} override_plugin_load_policy[]={
+ /*
+ 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)
+ */
+ { "performance_schema", PLUGIN_FORCE }
+
+ /* we disable few other plugins by default */
+ ,{ "feedback", PLUGIN_OFF }
+};
+
+/* support for Services */
+
+#include "sql_plugin_services.ic"
+
+/*
+ A mutex LOCK_plugin must be acquired before accessing the
+ following variables/structures.
+ We are always manipulating ref count, so a rwlock here is unneccessary.
+*/
+mysql_mutex_t LOCK_plugin;
+static DYNAMIC_ARRAY plugin_dl_array;
+static DYNAMIC_ARRAY plugin_array;
+static HASH plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM];
+static MEM_ROOT plugin_mem_root;
+static bool reap_needed= false;
+volatile int global_plugin_version= 1;
+
+static bool initialized= 0;
+ulong dlopen_count;
+
+
+/*
+ write-lock on LOCK_system_variables_hash is required before modifying
+ the following variables/structures
+*/
+static MEM_ROOT plugin_vars_mem_root;
+static size_t global_variables_dynamic_size= 0;
+static HASH bookmark_hash;
+
+
+/*
+ hidden part of opaque value passed to variable check functions.
+ Used to provide a object-like structure to non C++ consumers.
+*/
+struct st_item_value_holder : public st_mysql_value
+{
+ Item *item;
+};
+
+
+/*
+ stored in bookmark_hash, this structure is never removed from the
+ hash and is used to mark a single offset for a thd local variable
+ even if plugins have been uninstalled and reinstalled, repeatedly.
+ This structure is allocated from plugin_mem_root.
+
+ The key format is as follows:
+ 1 byte - variable type code
+ name_len bytes - variable name
+ '\0' - end of key
+*/
+struct st_bookmark
+{
+ uint name_len;
+ int offset;
+ uint version;
+ bool loaded;
+ char key[1];
+};
+
+
+/*
+ skeleton of a plugin variable - portion of structure common to all.
+*/
+struct st_mysql_sys_var
+{
+ MYSQL_PLUGIN_VAR_HEADER;
+};
+
+enum install_status { INSTALL_GOOD, INSTALL_FAIL_WARN_OK, INSTALL_FAIL_NOT_OK };
+/*
+ sys_var class for access to all plugin variables visible to the user
+*/
+class sys_var_pluginvar: public sys_var, public Sql_alloc
+{
+public:
+ struct st_plugin_int *plugin;
+ struct st_mysql_sys_var *plugin_var;
+
+ sys_var_pluginvar(sys_var_chain *chain, const char *name_arg,
+ st_plugin_int *p, st_mysql_sys_var *plugin_var_arg);
+ sys_var_pluginvar *cast_pluginvar() { return this; }
+ uchar* real_value_ptr(THD *thd, enum_var_type type) const;
+ TYPELIB* plugin_var_typelib(void) const;
+ const uchar* do_value_ptr(THD *thd, enum_var_type type, const LEX_CSTRING *base) const;
+ const uchar* session_value_ptr(THD *thd, const LEX_CSTRING *base) const
+ { return do_value_ptr(thd, OPT_SESSION, base); }
+ const uchar* global_value_ptr(THD *thd, const LEX_CSTRING *base) const
+ { return do_value_ptr(thd, OPT_GLOBAL, base); }
+ const uchar *default_value_ptr(THD *thd) const
+ { return do_value_ptr(thd, OPT_DEFAULT, 0); }
+ bool do_check(THD *thd, set_var *var);
+ virtual void session_save_default(THD *thd, set_var *var) {}
+ virtual void global_save_default(THD *thd, set_var *var) {}
+ bool session_update(THD *thd, set_var *var);
+ bool global_update(THD *thd, set_var *var);
+ bool session_is_default(THD *thd);
+};
+
+
+/* prototypes */
+static void plugin_load(MEM_ROOT *tmp_root);
+static bool plugin_load_list(MEM_ROOT *, const char *);
+static int test_plugin_options(MEM_ROOT *, struct st_plugin_int *,
+ int *, char **);
+static bool register_builtin(struct st_maria_plugin *, struct st_plugin_int *,
+ struct st_plugin_int **);
+static void unlock_variables(THD *thd, struct system_variables *vars);
+static void cleanup_variables(struct system_variables *vars);
+static void plugin_vars_free_values(sys_var *vars);
+static void restore_ptr_backup(uint n, st_ptr_backup *backup);
+static void intern_plugin_unlock(LEX *lex, plugin_ref plugin);
+static void reap_plugins(void);
+
+bool plugin_is_forced(struct st_plugin_int *p)
+{
+ return p->load_option == PLUGIN_FORCE ||
+ p->load_option == PLUGIN_FORCE_PLUS_PERMANENT;
+}
+
+/**
+ 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);
+ return prefix < len;
+}
+
+static void fix_dl_name(MEM_ROOT *root, LEX_CSTRING *dl)
+{
+ const size_t so_ext_len= sizeof(SO_EXT) - 1;
+ if (my_strcasecmp(&my_charset_latin1, dl->str + dl->length - so_ext_len,
+ SO_EXT))
+ {
+ char *s= (char*)alloc_root(root, dl->length + so_ext_len + 1);
+ memcpy(s, dl->str, dl->length);
+ strcpy(s + dl->length, SO_EXT);
+ dl->str= s;
+ dl->length+= so_ext_len;
+ }
+}
+
+
+/****************************************************************************
+ Value type thunks, allows the C world to play in the C++ world
+****************************************************************************/
+
+static int item_value_type(struct st_mysql_value *value)
+{
+ switch (((st_item_value_holder*)value)->item->result_type()) {
+ case INT_RESULT:
+ return MYSQL_VALUE_TYPE_INT;
+ case REAL_RESULT:
+ return MYSQL_VALUE_TYPE_REAL;
+ default:
+ return MYSQL_VALUE_TYPE_STRING;
+ }
+}
+
+static const char *item_val_str(struct st_mysql_value *value,
+ char *buffer, int *length)
+{
+ String str(buffer, *length, system_charset_info), *res;
+ if (!(res= ((st_item_value_holder*)value)->item->val_str(&str)))
+ return NULL;
+ *length= res->length();
+ if (res->c_ptr_quick() == buffer)
+ return buffer;
+
+ /*
+ Lets be nice and create a temporary string since the
+ buffer was too small
+ */
+ return current_thd->strmake(res->ptr(), res->length());
+}
+
+
+static int item_val_int(struct st_mysql_value *value, long long *buf)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ *buf= item->val_int();
+ if (item->is_null())
+ return 1;
+ return 0;
+}
+
+static int item_is_unsigned(struct st_mysql_value *value)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ return item->unsigned_flag;
+}
+
+static int item_val_real(struct st_mysql_value *value, double *buf)
+{
+ Item *item= ((st_item_value_holder*)value)->item;
+ *buf= item->val_real();
+ if (item->is_null())
+ return 1;
+ return 0;
+}
+
+
+/****************************************************************************
+ Plugin support code
+****************************************************************************/
+
+#ifdef HAVE_DLOPEN
+
+static struct st_plugin_dl *plugin_dl_find(const LEX_CSTRING *dl)
+{
+ uint i;
+ struct st_plugin_dl *tmp;
+ DBUG_ENTER("plugin_dl_find");
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ if (tmp->ref_count &&
+ ! files_charset_info->strnncoll(dl->str, dl->length,
+ tmp->dl.str, tmp->dl.length))
+ DBUG_RETURN(tmp);
+ }
+ DBUG_RETURN(0);
+}
+
+
+static st_plugin_dl *plugin_dl_insert_or_reuse(struct st_plugin_dl *plugin_dl)
+{
+ uint i;
+ struct st_plugin_dl *tmp;
+ DBUG_ENTER("plugin_dl_insert_or_reuse");
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ if (! tmp->ref_count)
+ {
+ memcpy(tmp, plugin_dl, sizeof(struct st_plugin_dl));
+ DBUG_RETURN(tmp);
+ }
+ }
+ if (insert_dynamic(&plugin_dl_array, (uchar*)&plugin_dl))
+ DBUG_RETURN(0);
+ tmp= *dynamic_element(&plugin_dl_array, plugin_dl_array.elements - 1,
+ struct st_plugin_dl **)=
+ (struct st_plugin_dl *) memdup_root(&plugin_mem_root, (uchar*)plugin_dl,
+ sizeof(struct st_plugin_dl));
+ DBUG_RETURN(tmp);
+}
+#else
+static struct st_plugin_dl *plugin_dl_find(const LEX_STRING *)
+{
+ return 0;
+}
+#endif /* HAVE_DLOPEN */
+
+
+static void free_plugin_mem(struct st_plugin_dl *p)
+{
+#ifdef HAVE_DLOPEN
+ if (p->ptr_backup)
+ {
+ DBUG_ASSERT(p->nbackups);
+ DBUG_ASSERT(p->handle);
+ restore_ptr_backup(p->nbackups, p->ptr_backup);
+ my_free(p->ptr_backup);
+ }
+ if (p->handle)
+ dlclose(p->handle);
+#endif
+ my_free(const_cast<char*>(p->dl.str));
+ if (p->allocated)
+ my_free(p->plugins);
+}
+
+
+/**
+ Reads data from mysql plugin interface
+
+ @param plugin_dl Structure where the data should be put
+ @param sym Reverence on version info
+ @param dlpath Path to the module
+ @param MyFlags Where errors should be reported (0 or ME_ERROR_LOG)
+
+ @retval FALSE OK
+ @retval TRUE ERROR
+*/
+
+#ifdef HAVE_DLOPEN
+static my_bool read_mysql_plugin_info(struct st_plugin_dl *plugin_dl,
+ void *sym, char *dlpath, myf MyFlags)
+{
+ DBUG_ENTER("read_maria_plugin_info");
+ /* Determine interface version */
+ if (!sym)
+ {
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags, plugin_interface_version_sym);
+ DBUG_RETURN(TRUE);
+ }
+ plugin_dl->mariaversion= 0;
+ plugin_dl->mysqlversion= *(int *)sym;
+ /* Versioning */
+ if (plugin_dl->mysqlversion < min_plugin_interface_version ||
+ (plugin_dl->mysqlversion >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8))
+ {
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dlpath, ENOEXEC,
+ "plugin interface version mismatch");
+ DBUG_RETURN(TRUE);
+ }
+ /* Find plugin declarations */
+ if (!(sym= dlsym(plugin_dl->handle, plugin_declarations_sym)))
+ {
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags, plugin_declarations_sym);
+ DBUG_RETURN(TRUE);
+ }
+
+ /* convert mysql declaration to maria one */
+ {
+ int i;
+ uint sizeof_st_plugin;
+ struct st_mysql_plugin *old;
+ struct st_maria_plugin *cur;
+ char *ptr= (char *)sym;
+
+ if ((sym= dlsym(plugin_dl->handle, sizeof_st_plugin_sym)))
+ sizeof_st_plugin= *(int *)sym;
+ else
+ {
+ DBUG_ASSERT(min_plugin_interface_version == 0);
+ sizeof_st_plugin= (int)offsetof(struct st_mysql_plugin, version);
+ }
+
+ for (i= 0;
+ ((struct st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ /* no op */;
+
+ cur= (struct st_maria_plugin*)
+ my_malloc(key_memory_mysql_plugin, (i + 1) * sizeof(struct st_maria_plugin),
+ MYF(MY_ZEROFILL|MY_WME));
+ if (!cur)
+ {
+ my_error(ER_OUTOFMEMORY, MyFlags,
+ static_cast<int>(plugin_dl->dl.length));
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ 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= (struct st_mysql_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ {
+
+ cur[i].type= old->type;
+ cur[i].info= old->info;
+ cur[i].name= old->name;
+ cur[i].author= old->author;
+ cur[i].descr= old->descr;
+ cur[i].license= old->license;
+ cur[i].init= old->init;
+ cur[i].deinit= old->deinit;
+ cur[i].version= old->version;
+ cur[i].status_vars= old->status_vars;
+ cur[i].system_vars= old->system_vars;
+ /*
+ Something like this should be added to process
+ new mysql plugin versions:
+ if (plugin_dl->mysqlversion > 0x0101)
+ {
+ cur[i].newfield= CONSTANT_MEANS_UNKNOWN;
+ }
+ else
+ {
+ cur[i].newfield= old->newfield;
+ }
+ */
+ /* Maria only fields */
+ cur[i].version_info= "Unknown";
+ cur[i].maturity= MariaDB_PLUGIN_MATURITY_UNKNOWN;
+ }
+ plugin_dl->allocated= true;
+ plugin_dl->plugins= (struct st_maria_plugin *)cur;
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Reads data from maria plugin interface
+
+ @param plugin_dl Structure where the data should be put
+ @param sym Reverence on version info
+ @param dlpath Path to the module
+ @param MyFlags Where errors should be reported (0 or ME_ERROR_LOG)
+
+ @retval FALSE OK
+ @retval TRUE ERROR
+*/
+
+static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl,
+ void *sym, char *dlpath, myf MyFlags)
+{
+ DBUG_ENTER("read_maria_plugin_info");
+
+ /* Determine interface version */
+ if (!(sym))
+ {
+ /*
+ Actually this branch impossible because in case of absence of maria
+ version we try mysql version.
+ */
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags,
+ maria_plugin_interface_version_sym);
+ DBUG_RETURN(TRUE);
+ }
+ plugin_dl->mariaversion= *(int *)sym;
+ plugin_dl->mysqlversion= 0;
+ /* Versioning */
+ if (plugin_dl->mariaversion < min_maria_plugin_interface_version ||
+ (plugin_dl->mariaversion >> 8) > (MARIA_PLUGIN_INTERFACE_VERSION >> 8))
+ {
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dlpath, ENOEXEC,
+ "plugin interface version mismatch");
+ DBUG_RETURN(TRUE);
+ }
+ /* Find plugin declarations */
+ if (!(sym= dlsym(plugin_dl->handle, maria_plugin_declarations_sym)))
+ {
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags, maria_plugin_declarations_sym);
+ DBUG_RETURN(TRUE);
+ }
+ if (plugin_dl->mariaversion != MARIA_PLUGIN_INTERFACE_VERSION)
+ {
+ uint sizeof_st_plugin;
+ struct st_maria_plugin *old, *cur;
+ char *ptr= (char *)sym;
+
+ if ((sym= dlsym(plugin_dl->handle, maria_sizeof_st_plugin_sym)))
+ sizeof_st_plugin= *(int *)sym;
+ else
+ {
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags, maria_sizeof_st_plugin_sym);
+ DBUG_RETURN(TRUE);
+ }
+
+ if (sizeof_st_plugin != sizeof(st_mysql_plugin))
+ {
+ int i;
+ for (i= 0;
+ ((struct st_maria_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ /* no op */;
+
+ cur= (struct st_maria_plugin*)
+ my_malloc(key_memory_mysql_plugin, (i + 1) * sizeof(struct st_maria_plugin),
+ MYF(MY_ZEROFILL|MY_WME));
+ if (!cur)
+ {
+ my_error(ER_OUTOFMEMORY, MyFlags,
+ static_cast<int>(plugin_dl->dl.length));
+ DBUG_RETURN(TRUE);
+ }
+ /*
+ 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= (struct st_maria_plugin *)(ptr + i * sizeof_st_plugin))->info;
+ i++)
+ memcpy(cur + i, old, MY_MIN(sizeof(cur[i]), sizeof_st_plugin));
+
+ sym= cur;
+ plugin_dl->allocated= true;
+ }
+ else
+ sym= ptr;
+ }
+ plugin_dl->plugins= (struct st_maria_plugin *)sym;
+
+ DBUG_RETURN(FALSE);
+}
+#endif /* HAVE_DLOPEN */
+
+static st_plugin_dl *plugin_dl_add(const LEX_CSTRING *dl, myf MyFlags)
+{
+#ifdef HAVE_DLOPEN
+ char dlpath[FN_REFLEN];
+ size_t plugin_dir_len,i;
+ uint dummy_errors;
+ struct st_plugin_dl *tmp= 0, plugin_dl;
+ void *sym;
+ st_ptr_backup tmp_backup[array_elements(list_of_services)];
+ DBUG_ENTER("plugin_dl_add");
+ DBUG_PRINT("enter", ("dl->str: '%s', dl->length: %d",
+ dl->str, (int) dl->length));
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ 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).
+ */
+ if (check_string_char_length((LEX_CSTRING *) dl, 0, NAME_CHAR_LEN,
+ system_charset_info, 1) ||
+ check_valid_path(dl->str, dl->length) ||
+ plugin_dir_len + dl->length + 1 >= FN_REFLEN)
+ {
+ my_error(ER_UDF_NO_PATHS, MyFlags);
+ DBUG_RETURN(0);
+ }
+ /* If this dll is already loaded just increase ref_count. */
+ if ((tmp= plugin_dl_find(dl)))
+ {
+ tmp->ref_count++;
+ DBUG_RETURN(tmp);
+ }
+ bzero(&plugin_dl, sizeof(plugin_dl));
+ /* Compile dll path */
+ strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", dl->str, NullS);
+ (void) unpack_filename(dlpath, dlpath);
+ plugin_dl.ref_count= 1;
+ /* Open new dll handle */
+ if (!(plugin_dl.handle= dlopen(dlpath, RTLD_NOW)))
+ {
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dlpath, errno, my_dlerror(dlpath));
+ goto ret;
+ }
+ dlopen_count++;
+
+#ifdef HAVE_LINK_H
+ if (global_system_variables.log_warnings > 2)
+ {
+ struct link_map *lm = (struct link_map*) plugin_dl.handle;
+ sql_print_information("Loaded '%s' with offset 0x%zx", dl->str, (size_t)lm->l_addr);
+ }
+#endif
+
+ /* Checks which plugin interface present and reads info */
+ if (!(sym= dlsym(plugin_dl.handle, maria_plugin_interface_version_sym)))
+ {
+ if (read_mysql_plugin_info(&plugin_dl,
+ dlsym(plugin_dl.handle,
+ plugin_interface_version_sym),
+ dlpath,
+ MyFlags))
+ goto ret;
+ }
+ else
+ {
+ if (read_maria_plugin_info(&plugin_dl, sym, dlpath, MyFlags))
+ goto ret;
+ }
+
+ /* 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)))
+ {
+ void **ptr= (void **)sym;
+ uint ver= (uint)(intptr)*ptr;
+ if (ver > list_of_services[i].version ||
+ (ver >> 8) < (list_of_services[i].version >> 8))
+ {
+ char buf[MYSQL_ERRMSG_SIZE];
+ my_snprintf(buf, sizeof(buf),
+ "service '%s' interface version mismatch",
+ list_of_services[i].name);
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dlpath, ENOEXEC, buf);
+ goto ret;
+ }
+ tmp_backup[plugin_dl.nbackups++].save(ptr);
+ *ptr= list_of_services[i].service;
+ }
+ }
+
+ if (plugin_dl.nbackups)
+ {
+ size_t bytes= plugin_dl.nbackups * sizeof(plugin_dl.ptr_backup[0]);
+ plugin_dl.ptr_backup= (st_ptr_backup *)my_malloc(key_memory_mysql_plugin_dl,
+ bytes, MYF(0));
+ if (!plugin_dl.ptr_backup)
+ {
+ restore_ptr_backup(plugin_dl.nbackups, tmp_backup);
+ my_error(ER_OUTOFMEMORY, MyFlags, bytes);
+ goto ret;
+ }
+ memcpy(plugin_dl.ptr_backup, tmp_backup, bytes);
+ }
+
+ /* 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))))
+ {
+ my_error(ER_OUTOFMEMORY, MyFlags,
+ static_cast<int>(plugin_dl.dl.length));
+ goto ret;
+ }
+ plugin_dl.dl.length= copy_and_convert((char*) plugin_dl.dl.str,
+ plugin_dl.dl.length,
+ files_charset_info, dl->str,
+ dl->length, system_charset_info,
+ &dummy_errors);
+ ((char*) plugin_dl.dl.str)[plugin_dl.dl.length]= 0;
+ /* Add this dll to array */
+ if (! (tmp= plugin_dl_insert_or_reuse(&plugin_dl)))
+ {
+ my_error(ER_OUTOFMEMORY, MyFlags,
+ static_cast<int>(sizeof(struct st_plugin_dl)));
+ goto ret;
+ }
+
+ret:
+ if (!tmp)
+ free_plugin_mem(&plugin_dl);
+
+ DBUG_RETURN(tmp);
+
+#else
+ DBUG_ENTER("plugin_dl_add");
+ my_error(ER_FEATURE_DISABLED, MyFlags, "plugin", "HAVE_DLOPEN");
+ DBUG_RETURN(0);
+#endif
+}
+
+
+static void plugin_dl_del(struct st_plugin_dl *plugin_dl)
+{
+ DBUG_ENTER("plugin_dl_del");
+
+ if (!plugin_dl)
+ DBUG_VOID_RETURN;
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ /* Do not remove this element, unless no other plugin uses this dll. */
+ if (! --plugin_dl->ref_count)
+ {
+ free_plugin_mem(plugin_dl);
+ bzero(plugin_dl, sizeof(struct st_plugin_dl));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+static struct st_plugin_int *plugin_find_internal(const LEX_CSTRING *name,
+ int type)
+{
+ uint i;
+ DBUG_ENTER("plugin_find_internal");
+ if (! initialized)
+ DBUG_RETURN(0);
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (type == MYSQL_ANY_PLUGIN)
+ {
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ struct st_plugin_int *plugin= (st_plugin_int *)
+ my_hash_search(&plugin_hash[i], (const uchar *)name->str, name->length);
+ if (plugin)
+ DBUG_RETURN(plugin);
+ }
+ }
+ else
+ DBUG_RETURN((st_plugin_int *)
+ my_hash_search(&plugin_hash[type], (const uchar *)name->str,
+ name->length));
+ DBUG_RETURN(0);
+}
+
+
+static SHOW_COMP_OPTION plugin_status(const LEX_CSTRING *name, int type)
+{
+ SHOW_COMP_OPTION rc= SHOW_OPTION_NO;
+ struct st_plugin_int *plugin;
+ DBUG_ENTER("plugin_is_ready");
+ 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);
+ DBUG_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);
+}
+
+
+/*
+ If LEX is passed non-NULL, an automatic unlock of the plugin will happen
+ in the LEX destructor.
+*/
+static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc,
+ uint state_mask= PLUGIN_IS_READY |
+ PLUGIN_IS_UNINITIALIZED |
+ PLUGIN_IS_DELETED)
+{
+ st_plugin_int *pi= plugin_ref_to_int(rc);
+ DBUG_ENTER("intern_plugin_lock");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (pi->state & state_mask)
+ {
+ plugin_ref plugin;
+#ifdef DBUG_OFF
+ /*
+ In optimized builds we don't do reference counting for built-in
+ (plugin->plugin_dl == 0) plugins.
+ */
+ if (!pi->plugin_dl)
+ DBUG_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(PSI_NOT_INSTRUMENTED, sizeof(pi),
+ MYF(MY_WME))))
+ DBUG_RETURN(NULL);
+
+ *plugin= pi;
+#endif
+ pi->ref_count++;
+ DBUG_PRINT("lock",("thd: %p plugin: \"%s\" LOCK ref_count: %d",
+ current_thd, pi->name.str, pi->ref_count));
+
+ if (lex)
+ insert_dynamic(&lex->plugins, (uchar*)&plugin);
+ DBUG_RETURN(plugin);
+ }
+ DBUG_RETURN(NULL);
+}
+
+
+/*
+ Notes on lifetime:
+
+ If THD is passed as non-NULL (and with a non-NULL thd->lex), an entry is made
+ in the thd->lex which will cause an automatic unlock of the plugin in the LEX
+ destructor. In this case, no manual unlock must be done.
+
+ Otherwise, when passing a NULL THD, the caller must arrange that plugin
+ unlock happens later.
+*/
+plugin_ref plugin_lock(THD *thd, plugin_ref ptr)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ plugin_ref rc;
+ DBUG_ENTER("plugin_lock");
+
+#ifdef DBUG_OFF
+ /*
+ In optimized builds we don't do reference counting for built-in
+ (plugin->plugin_dl == 0) plugins.
+
+ Note that we access plugin->plugin_dl outside of LOCK_plugin, and for
+ dynamic plugins a 'plugin' could correspond to plugin that was unloaded
+ meanwhile! But because st_plugin_int is always allocated on
+ plugin_mem_root, the pointer can never be invalid - the memory is never
+ freed.
+ Of course, the memory that 'plugin' points to can be overwritten by
+ another plugin being loaded, but plugin->plugin_dl can never change
+ from zero to non-zero or vice versa.
+ That is, it's always safe to check for plugin->plugin_dl==0 even
+ without a mutex.
+ */
+ if (! plugin_dlib(ptr))
+ {
+ plugin_ref_to_int(ptr)->locks_total++;
+ DBUG_RETURN(ptr);
+ }
+#endif
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_ref_to_int(ptr)->locks_total++;
+ rc= intern_plugin_lock(lex, ptr);
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Notes on lifetime:
+
+ If THD is passed as non-NULL (and with a non-NULL thd->lex), an entry is made
+ in the thd->lex which will cause an automatic unlock of the plugin in the LEX
+ destructor. In this case, no manual unlock must be done.
+
+ Otherwise, when passing a NULL THD, the caller must arrange that plugin
+ unlock happens later.
+*/
+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_ENTER("plugin_lock_by_name");
+ mysql_mutex_lock(&LOCK_plugin);
+ if ((plugin= plugin_find_internal(name, type)))
+ rc= intern_plugin_lock(lex, plugin_int_to_ref(plugin));
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(rc);
+}
+
+
+static st_plugin_int *plugin_insert_or_reuse(struct st_plugin_int *plugin)
+{
+ uint i;
+ struct st_plugin_int *tmp;
+ DBUG_ENTER("plugin_insert_or_reuse");
+ for (i= 0; i < plugin_array.elements; i++)
+ {
+ tmp= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ if (tmp->state == PLUGIN_IS_FREED)
+ {
+ memcpy(tmp, plugin, sizeof(struct st_plugin_int));
+ DBUG_RETURN(tmp);
+ }
+ }
+ if (insert_dynamic(&plugin_array, (uchar*)&plugin))
+ DBUG_RETURN(0);
+ tmp= *dynamic_element(&plugin_array, plugin_array.elements - 1,
+ struct st_plugin_int **)=
+ (struct st_plugin_int *) memdup_root(&plugin_mem_root, (uchar*)plugin,
+ sizeof(struct st_plugin_int));
+ DBUG_RETURN(tmp);
+}
+
+
+/*
+ NOTE
+ Requires that a write-lock is held on LOCK_system_variables_hash
+*/
+static enum install_status plugin_add(MEM_ROOT *tmp_root, bool if_not_exists,
+ const LEX_CSTRING *name, LEX_CSTRING *dl, myf MyFlags)
+{
+ struct st_plugin_int tmp, *maybe_dupe;
+ struct st_maria_plugin *plugin;
+ uint oks= 0, errs= 0, dupes= 0;
+ DBUG_ENTER("plugin_add");
+ DBUG_PRINT("enter", ("name: %s dl: %s", name->str, dl->str));
+
+ if (name->str && plugin_find_internal(name, MYSQL_ANY_PLUGIN))
+ {
+ if (if_not_exists)
+ MyFlags|= ME_NOTE;
+ my_error(ER_PLUGIN_INSTALLED, MyFlags, name->str);
+ DBUG_RETURN(if_not_exists ? INSTALL_FAIL_WARN_OK : INSTALL_FAIL_NOT_OK);
+ }
+ /* Clear the whole struct to catch future extensions. */
+ bzero((char*) &tmp, sizeof(tmp));
+ fix_dl_name(tmp_root, dl);
+ if (! (tmp.plugin_dl= plugin_dl_add(dl, MyFlags)))
+ DBUG_RETURN(INSTALL_FAIL_NOT_OK);
+ /* Find plugin by name */
+ for (plugin= tmp.plugin_dl->plugins; plugin->info; plugin++)
+ {
+ tmp.name.str= (char *)plugin->name;
+ tmp.name.length= strlen(plugin->name);
+
+ if (plugin->type < 0 || plugin->type >= MYSQL_MAX_PLUGIN_TYPE_NUM)
+ continue; // invalid plugin type
+
+ if (plugin->type == MYSQL_UDF_PLUGIN ||
+ (plugin->type == MariaDB_PASSWORD_VALIDATION_PLUGIN &&
+ tmp.plugin_dl->mariaversion == 0))
+ continue; // unsupported plugin type
+
+ if (name->str && system_charset_info->strnncoll(name->str, name->length,
+ tmp.name.str, tmp.name.length))
+ continue; // plugin name doesn't match
+
+ if (!name->str &&
+ (maybe_dupe= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)))
+ {
+ if (plugin->name != maybe_dupe->plugin->name)
+ {
+ my_error(ER_UDF_EXISTS, MyFlags, plugin->name);
+ DBUG_RETURN(INSTALL_FAIL_NOT_OK);
+ }
+ dupes++;
+ continue; // already installed
+ }
+ struct 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];
+ strxnmov(buf, sizeof(buf) - 1, "API version for ",
+ plugin_type_names[plugin->type].str,
+ " plugin ", tmp.name.str,
+ " not supported by this version of the server", NullS);
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dl->str, ENOEXEC, buf);
+ goto err;
+ }
+
+ if (plugin_maturity_map[plugin->maturity] < plugin_maturity)
+ {
+ char buf[256];
+ strxnmov(buf, sizeof(buf) - 1, "Loading of ",
+ plugin_maturity_names[plugin->maturity],
+ " plugin ", tmp.name.str,
+ " is prohibited by --plugin-maturity=",
+ plugin_maturity_names[plugin_maturity],
+ NullS);
+ my_error(ER_CANT_OPEN_LIBRARY, MyFlags, dl->str, EPERM, buf);
+ goto err;
+ }
+ else if (plugin_maturity_map[plugin->maturity] < SERVER_MATURITY_LEVEL)
+ {
+ sql_print_warning("Plugin '%s' is of maturity level %s while the server is %s",
+ tmp.name.str,
+ plugin_maturity_names[plugin->maturity],
+ plugin_maturity_names[SERVER_MATURITY_LEVEL]);
+ }
+
+ tmp.plugin= plugin;
+ tmp.ref_count= 0;
+ tmp.state= PLUGIN_IS_UNINITIALIZED;
+ tmp.load_option= PLUGIN_ON;
+
+ if (!(tmp_plugin_ptr= plugin_insert_or_reuse(&tmp)))
+ goto err;
+ if (my_hash_insert(&plugin_hash[plugin->type], (uchar*)tmp_plugin_ptr))
+ tmp_plugin_ptr->state= PLUGIN_IS_FREED;
+ init_alloc_root(key_memory_plugin_int_mem_root, &tmp_plugin_ptr->mem_root,
+ 4096, 4096, MYF(0));
+
+ if (name->str)
+ DBUG_RETURN(INSTALL_GOOD); // all done
+
+ oks++;
+ tmp.plugin_dl->ref_count++;
+ continue; // otherwise - go on
+
+err:
+ errs++;
+ if (name->str)
+ break;
+ }
+
+ DBUG_ASSERT(!name->str || !dupes); // dupes is ONLY for name->str == 0
+
+ if (errs == 0 && oks == 0 && !dupes) // no plugin was found
+ my_error(ER_CANT_FIND_DL_ENTRY, MyFlags, name->str);
+
+ plugin_dl_del(tmp.plugin_dl);
+ if (errs > 0 || oks + dupes == 0)
+ DBUG_RETURN(INSTALL_FAIL_NOT_OK);
+ DBUG_RETURN(INSTALL_GOOD);
+}
+
+static void plugin_variables_deinit(struct st_plugin_int *plugin)
+{
+
+ for (sys_var *var= plugin->system_vars; var; var= var->next)
+ (*var->test_load)= FALSE;
+ mysql_del_sys_var_chain(plugin->system_vars);
+}
+
+static void plugin_deinitialize(struct 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)
+ {
+ /*
+ historical ndb behavior caused MySQL plugins to specify
+ status var names in full, with the plugin name prefix.
+ this was never fixed in MySQL.
+ MariaDB fixes that but supports MySQL style too.
+ */
+ SHOW_VAR *show_vars= plugin->plugin->status_vars;
+ SHOW_VAR tmp_array[2]= {
+ {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY},
+ {0, 0, SHOW_UNDEF}
+ };
+ if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length))
+ show_vars= tmp_array;
+
+ remove_status_vars(show_vars);
+ }
+
+ if (plugin_type_deinitialize[plugin->plugin->type])
+ {
+ if ((*plugin_type_deinitialize[plugin->plugin->type])(plugin))
+ {
+ sql_print_error("Plugin '%s' of type %s 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;
+
+ if (ref_check && plugin->ref_count)
+ sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.",
+ plugin->name.str, plugin->ref_count);
+ plugin_variables_deinit(plugin);
+}
+
+static void plugin_del(struct st_plugin_int *plugin)
+{
+ DBUG_ENTER("plugin_del");
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ /* Free allocated strings before deleting the plugin. */
+ plugin_vars_free_values(plugin->system_vars);
+ restore_ptr_backup(plugin->nbackups, plugin->ptr_backup);
+ if (plugin->plugin_dl)
+ {
+ my_hash_delete(&plugin_hash[plugin->plugin->type], (uchar*)plugin);
+ plugin_dl_del(plugin->plugin_dl);
+ plugin->state= PLUGIN_IS_FREED;
+ free_root(&plugin->mem_root, MYF(0));
+ }
+ else
+ plugin->state= PLUGIN_IS_UNINITIALIZED;
+ DBUG_VOID_RETURN;
+}
+
+static void reap_plugins(void)
+{
+ uint count;
+ struct st_plugin_int *plugin, **reap, **list;
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!reap_needed)
+ return;
+
+ reap_needed= false;
+ count= plugin_array.elements;
+ reap= (struct st_plugin_int **)my_alloca(sizeof(plugin)*(count+1));
+ *(reap++)= NULL;
+
+ for (uint i=0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ HASH *hash= plugin_hash + plugin_type_initialization_order[i];
+ for (uint j= 0; j < hash->records; j++)
+ {
+ plugin= (struct st_plugin_int *) my_hash_element(hash, j);
+ 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)))
+ plugin_deinitialize(plugin, true);
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ while ((plugin= *(--reap)))
+ plugin_del(plugin);
+
+ my_afree(reap);
+}
+
+static void intern_plugin_unlock(LEX *lex, plugin_ref plugin)
+{
+ int i;
+ st_plugin_int *pi;
+ DBUG_ENTER("intern_plugin_unlock");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!plugin)
+ DBUG_VOID_RETURN;
+
+ pi= plugin_ref_to_int(plugin);
+
+#ifdef DBUG_OFF
+ if (!pi->plugin_dl)
+ DBUG_VOID_RETURN;
+#else
+ my_free(plugin);
+#endif
+
+ 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.
+ */
+ for (i= lex->plugins.elements - 1; i >= 0; i--)
+ if (plugin == *dynamic_element(&lex->plugins, i, plugin_ref*))
+ {
+ delete_dynamic_element(&lex->plugins, i);
+ break;
+ }
+ DBUG_ASSERT(i >= 0);
+ }
+
+ DBUG_ASSERT(pi->ref_count);
+ pi->ref_count--;
+
+ DBUG_PRINT("lock",("thd: %p plugin: \"%s\" UNLOCK ref_count: %d",
+ current_thd, pi->name.str, pi->ref_count));
+
+ if (pi->state == PLUGIN_IS_DELETED && !pi->ref_count)
+ reap_needed= true;
+
+ DBUG_VOID_RETURN;
+}
+
+
+void plugin_unlock(THD *thd, plugin_ref plugin)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ DBUG_ENTER("plugin_unlock");
+ if (!plugin)
+ DBUG_VOID_RETURN;
+#ifdef DBUG_OFF
+ /* built-in plugins don't need ref counting */
+ if (!plugin_dlib(plugin))
+ DBUG_VOID_RETURN;
+#endif
+ mysql_mutex_lock(&LOCK_plugin);
+ intern_plugin_unlock(lex, plugin);
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_VOID_RETURN;
+}
+
+
+void plugin_unlock_list(THD *thd, plugin_ref *list, uint count)
+{
+ LEX *lex= thd ? thd->lex : 0;
+ DBUG_ENTER("plugin_unlock_list");
+ if (count == 0)
+ DBUG_VOID_RETURN;
+
+ DBUG_ASSERT(list);
+ mysql_mutex_lock(&LOCK_plugin);
+ while (count--)
+ intern_plugin_unlock(lex, *list++);
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_VOID_RETURN;
+}
+
+
+static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin,
+ int *argc, char **argv, bool options_only)
+{
+ int ret= 1;
+ DBUG_ENTER("plugin_initialize");
+
+ mysql_mutex_assert_owner(&LOCK_plugin);
+ uint state= plugin->state;
+ DBUG_ASSERT(state == PLUGIN_IS_UNINITIALIZED);
+
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ mysql_prlock_wrlock(&LOCK_system_variables_hash);
+ if (test_plugin_options(tmp_root, plugin, argc, argv))
+ state= PLUGIN_IS_DISABLED;
+ mysql_prlock_unlock(&LOCK_system_variables_hash);
+
+ if (options_only || state == PLUGIN_IS_DISABLED)
+ {
+ ret= !options_only && plugin_is_forced(plugin);
+ state= PLUGIN_IS_DISABLED;
+ goto err;
+ }
+
+ if (plugin_type_initialize[plugin->plugin->type])
+ {
+ if ((*plugin_type_initialize[plugin->plugin->type])(plugin))
+ {
+ sql_print_error("Plugin '%s' registration as a %s failed.",
+ plugin->name.str, plugin_type_names[plugin->plugin->type].str);
+ goto err;
+ }
+ }
+ else if (plugin->plugin->init)
+ {
+ if (plugin->plugin->init(plugin))
+ {
+ sql_print_error("Plugin '%s' init function returned error.",
+ plugin->name.str);
+ goto err;
+ }
+ }
+ state= PLUGIN_IS_READY; // plugin->init() succeeded
+
+ if (plugin->plugin->status_vars)
+ {
+ /*
+ historical ndb behavior caused MySQL plugins to specify
+ status var names in full, with the plugin name prefix.
+ this was never fixed in MySQL.
+ MariaDB fixes that but supports MySQL style too.
+ */
+ SHOW_VAR *show_vars= plugin->plugin->status_vars;
+ SHOW_VAR tmp_array[2]= {
+ {plugin->plugin->name, (char*)plugin->plugin->status_vars, SHOW_ARRAY},
+ {0, 0, SHOW_UNDEF}
+ };
+ if (strncasecmp(show_vars->name, plugin->name.str, plugin->name.length))
+ show_vars= tmp_array;
+
+ if (add_status_vars(show_vars))
+ goto err;
+ }
+
+ ret= 0;
+
+err:
+ if (ret)
+ plugin_variables_deinit(plugin);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin->state= state;
+
+ DBUG_RETURN(ret);
+}
+
+
+extern "C" uchar *get_plugin_hash_key(const uchar *, size_t *, my_bool);
+extern "C" uchar *get_bookmark_hash_key(const uchar *, size_t *, my_bool);
+
+
+uchar *get_plugin_hash_key(const uchar *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ struct st_plugin_int *plugin= (st_plugin_int *)buff;
+ *length= (uint)plugin->name.length;
+ return((uchar *)plugin->name.str);
+}
+
+
+uchar *get_bookmark_hash_key(const uchar *buff, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ struct st_bookmark *var= (st_bookmark *)buff;
+ *length= var->name_len + 1;
+ return (uchar*) var->key;
+}
+
+static inline void convert_dash_to_underscore(char *str, size_t len)
+{
+ for (char *p= str; p <= str+len; p++)
+ if (*p == '-')
+ *p= '_';
+}
+
+static inline void convert_underscore_to_dash(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_info all_plugin_mutexes[]=
+{
+ { &key_LOCK_plugin, "LOCK_plugin", PSI_FLAG_GLOBAL}
+};
+
+static PSI_memory_info all_plugin_memory[]=
+{
+ { &key_memory_plugin_mem_root, "plugin_mem_root", PSI_FLAG_GLOBAL},
+ { &key_memory_plugin_int_mem_root, "plugin_int_mem_root", 0},
+ { &key_memory_mysql_plugin_dl, "mysql_plugin_dl", 0},
+ { &key_memory_mysql_plugin, "mysql_plugin", 0},
+ { &key_memory_plugin_bookmark, "plugin_bookmark", PSI_FLAG_GLOBAL}
+};
+
+static void init_plugin_psi_keys(void)
+{
+ const char* category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_plugin_mutexes);
+ PSI_server->register_mutex(category, all_plugin_mutexes, count);
+
+ count= array_elements(all_plugin_memory);
+ mysql_memory_register(category, all_plugin_memory, count);
+}
+#else
+static void init_plugin_psi_keys(void) {}
+#endif /* HAVE_PSI_INTERFACE */
+
+/*
+ The logic is that we first load and initialize all compiled in plugins.
+ From there we load up the dynamic types (assuming we have not been told to
+ skip this part).
+
+ Finally we initialize everything, aka the dynamic that have yet to initialize.
+*/
+int plugin_init(int *argc, char **argv, int flags)
+{
+ uint i;
+ struct st_maria_plugin **builtins;
+ struct st_maria_plugin *plugin;
+ struct st_plugin_int tmp, *plugin_ptr, **reap;
+ MEM_ROOT tmp_root;
+ bool reaped_mandatory_plugin= false;
+ bool mandatory= true;
+ I_List_iterator<i_string> opt_plugin_load_list_iter(opt_plugin_load_list);
+ char plugin_table_engine_name_buf[NAME_CHAR_LEN + 1];
+ LEX_CSTRING plugin_table_engine_name= { plugin_table_engine_name_buf, 0 };
+ LEX_CSTRING MyISAM= { STRING_WITH_LEN("MyISAM") };
+ DBUG_ENTER("plugin_init");
+
+ if (initialized)
+ DBUG_RETURN(0);
+
+ dlopen_count =0;
+
+ init_plugin_psi_keys();
+
+ init_alloc_root(key_memory_plugin_mem_root, &plugin_mem_root, 4096, 4096, MYF(0));
+ init_alloc_root(key_memory_plugin_mem_root, &plugin_vars_mem_root, 4096, 4096, MYF(0));
+ init_alloc_root(PSI_NOT_INSTRUMENTED, &tmp_root, 4096, 4096, MYF(0));
+
+ if (my_hash_init(key_memory_plugin_bookmark, &bookmark_hash, &my_charset_bin, 32, 0, 0,
+ get_bookmark_hash_key, NULL, HASH_UNIQUE))
+ goto err;
+
+ /*
+ The 80 is from 2016-04-27 when we had 71 default plugins
+ Big enough to avoid many mallocs even in future
+ */
+ if (my_init_dynamic_array(key_memory_mysql_plugin_dl, &plugin_dl_array,
+ sizeof(struct st_plugin_dl *), 16, 16, MYF(0)) ||
+ my_init_dynamic_array(key_memory_mysql_plugin, &plugin_array,
+ sizeof(struct st_plugin_int *), 80, 32, MYF(0)))
+ goto err;
+
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ if (my_hash_init(key_memory_plugin_mem_root, &plugin_hash[i], system_charset_info, 32, 0, 0,
+ get_plugin_hash_key, NULL, HASH_UNIQUE))
+ goto err;
+ }
+
+ /* prepare debug_sync service */
+ DBUG_ASSERT(strcmp(list_of_services[1].name, "debug_sync_service") == 0);
+ list_of_services[1].service= *(void**)&debug_sync_C_callback_ptr;
+
+ /* prepare encryption_keys service */
+ finalize_encryption_plugin(0);
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ initialized= 1;
+
+ /*
+ First we register builtin plugins
+ */
+ if (global_system_variables.log_warnings >= 9)
+ sql_print_information("Initializing built-in plugins");
+
+ for (builtins= mysql_mandatory_plugins; *builtins || mandatory; builtins++)
+ {
+ if (!*builtins)
+ {
+ builtins= mysql_optional_plugins;
+ mandatory= false;
+ if (!*builtins)
+ break;
+ }
+ for (plugin= *builtins; plugin->info; plugin++)
+ {
+ if (opt_ignore_builtin_innodb &&
+ !my_charset_latin1.strnncoll(plugin->name, 6, "InnoDB", 6))
+ continue;
+
+ bzero(&tmp, sizeof(tmp));
+ tmp.plugin= plugin;
+ tmp.name.str= (char *)plugin->name;
+ tmp.name.length= strlen(plugin->name);
+ tmp.state= 0;
+ tmp.load_option= mandatory ? PLUGIN_FORCE : PLUGIN_ON;
+
+ for (i=0; i < array_elements(override_plugin_load_policy); i++)
+ {
+ if (!my_strcasecmp(&my_charset_latin1, plugin->name,
+ override_plugin_load_policy[i].plugin_name))
+ {
+ tmp.load_option= override_plugin_load_policy[i].override;
+ break;
+ }
+ }
+
+ free_root(&tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ tmp.state= PLUGIN_IS_UNINITIALIZED;
+ if (register_builtin(plugin, &tmp, &plugin_ptr))
+ goto err_unlock;
+ }
+ }
+
+ /*
+ First, we initialize only MyISAM - that should almost always succeed
+ (almost always, because plugins can be loaded outside of the server, too).
+ */
+ plugin_ptr= plugin_find_internal(&MyISAM, MYSQL_STORAGE_ENGINE_PLUGIN);
+ DBUG_ASSERT(plugin_ptr || !mysql_mandatory_plugins[0]);
+ if (plugin_ptr)
+ {
+ DBUG_ASSERT(plugin_ptr->load_option == PLUGIN_FORCE);
+
+ if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, false))
+ goto err_unlock;
+
+ /*
+ set the global default storage engine variable so that it will
+ not be null in any child thread.
+ */
+ global_system_variables.table_plugin =
+ intern_plugin_lock(NULL, plugin_int_to_ref(plugin_ptr));
+ DBUG_SLOW_ASSERT(plugin_ptr->ref_count == 1);
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ /* Register (not initialize!) all dynamic plugins */
+ if (global_system_variables.log_warnings >= 9)
+ sql_print_information("Initializing plugins specified on the command line");
+ while (i_string *item= opt_plugin_load_list_iter++)
+ plugin_load_list(&tmp_root, item->ptr);
+
+ if (!(flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE))
+ {
+ char path[FN_REFLEN + 1];
+ build_table_filename(path, sizeof(path) - 1, "mysql", "plugin", reg_ext, 0);
+ Table_type ttype= dd_frm_type(0, path, &plugin_table_engine_name);
+ if (ttype != TABLE_TYPE_NORMAL)
+ plugin_table_engine_name=empty_clex_str;
+ }
+
+ /*
+ Now we initialize all remaining plugins
+ */
+
+ mysql_mutex_lock(&LOCK_plugin);
+ reap= (st_plugin_int **) my_alloca((plugin_array.elements+1) * sizeof(void*));
+ *(reap++)= NULL;
+
+ for(;;)
+ {
+ for (i=0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ {
+ HASH *hash= plugin_hash + plugin_type_initialization_order[i];
+ for (uint idx= 0; idx < hash->records; idx++)
+ {
+ plugin_ptr= (struct st_plugin_int *) my_hash_element(hash, idx);
+ if (plugin_ptr->state == PLUGIN_IS_UNINITIALIZED)
+ {
+ bool plugin_table_engine= lex_string_eq(&plugin_table_engine_name,
+ &plugin_ptr->name);
+ bool opts_only= flags & PLUGIN_INIT_SKIP_INITIALIZATION &&
+ (flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE ||
+ !plugin_table_engine);
+ if (plugin_initialize(&tmp_root, plugin_ptr, argc, argv, opts_only))
+ {
+ plugin_ptr->state= PLUGIN_IS_DYING;
+ *(reap++)= plugin_ptr;
+ }
+ }
+ }
+ }
+
+ /* load and init plugins from the plugin table (unless done already) */
+ if (flags & PLUGIN_INIT_SKIP_PLUGIN_TABLE)
+ break;
+
+ mysql_mutex_unlock(&LOCK_plugin);
+ plugin_load(&tmp_root);
+ flags|= PLUGIN_INIT_SKIP_PLUGIN_TABLE;
+ mysql_mutex_lock(&LOCK_plugin);
+ }
+
+ /*
+ Check if any plugins have to be reaped
+ */
+ while ((plugin_ptr= *(--reap)))
+ {
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (plugin_is_forced(plugin_ptr))
+ reaped_mandatory_plugin= TRUE;
+ plugin_deinitialize(plugin_ptr, true);
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_del(plugin_ptr);
+ }
+
+ mysql_mutex_unlock(&LOCK_plugin);
+ my_afree(reap);
+ if (reaped_mandatory_plugin && !opt_help)
+ goto err;
+
+ free_root(&tmp_root, MYF(0));
+
+ DBUG_RETURN(0);
+
+err_unlock:
+ mysql_mutex_unlock(&LOCK_plugin);
+err:
+ free_root(&tmp_root, MYF(0));
+ DBUG_RETURN(1);
+}
+
+
+static bool register_builtin(struct st_maria_plugin *plugin,
+ struct st_plugin_int *tmp,
+ struct st_plugin_int **ptr)
+{
+ DBUG_ENTER("register_builtin");
+ tmp->ref_count= 0;
+ tmp->plugin_dl= 0;
+
+ if (insert_dynamic(&plugin_array, (uchar*)&tmp))
+ DBUG_RETURN(1);
+
+ *ptr= *dynamic_element(&plugin_array, plugin_array.elements - 1,
+ struct st_plugin_int **)=
+ (struct st_plugin_int *) memdup_root(&plugin_mem_root, (uchar*)tmp,
+ sizeof(struct st_plugin_int));
+
+ if (my_hash_insert(&plugin_hash[plugin->type],(uchar*) *ptr))
+ DBUG_RETURN(1);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ called only by plugin_init()
+*/
+static void plugin_load(MEM_ROOT *tmp_root)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ READ_RECORD read_record_info;
+ int error;
+ THD *new_thd= new THD(0);
+ bool result;
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] =
+ { MYSQL_AUDIT_GENERAL_CLASSMASK };
+ DBUG_ENTER("plugin_load");
+
+ if (global_system_variables.log_warnings >= 9)
+ sql_print_information("Initializing installed plugins");
+
+ new_thd->thread_stack= (char*) &tables;
+ new_thd->store_globals();
+ new_thd->db= MYSQL_SCHEMA_NAME;
+ bzero((char*) &new_thd->net, sizeof(new_thd->net));
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PLUGIN_NAME, 0, TL_READ);
+ tables.open_strategy= TABLE_LIST::OPEN_NORMAL;
+
+ result= open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT);
+
+ table= tables.table;
+ if (result)
+ {
+ DBUG_PRINT("error",("Can't open plugin table"));
+ if (!opt_help)
+ sql_print_error("Could not open mysql.plugin table: \"%s\". "
+ "Some plugins may be not loaded",
+ new_thd->get_stmt_da()->message());
+ else
+ sql_print_warning("Could not open mysql.plugin table: \"%s\". "
+ "Some options may be missing from the help text",
+ new_thd->get_stmt_da()->message());
+ goto end;
+ }
+
+ if (init_read_record(&read_record_info, new_thd, table, NULL, NULL, 1, 0,
+ FALSE))
+ {
+ sql_print_error("Could not initialize init_read_record; Plugins not "
+ "loaded");
+ goto end;
+ }
+ table->use_all_columns();
+ while (!(error= read_record_info.read_record()))
+ {
+ 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.ptr(), str_name.length()};
+ LEX_CSTRING dl= {str_dl.ptr(), str_dl.length()};
+
+ if (!name.length || !dl.length)
+ continue;
+
+ /*
+ 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 plugin_load is acquiring LOCK_plugin
+ rather for a long time.
+
+ When audit event is triggered during plugin_load 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.
+
+ See also mysql_install_plugin(), mysql_uninstall_plugin() and
+ initialize_audit_plugin()
+ */
+ if (mysql_audit_general_enabled())
+ mysql_audit_acquire_plugins(new_thd, event_class_mask);
+
+ /*
+ 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
+ */
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_add(tmp_root, false, &name, &dl, MYF(ME_ERROR_LOG));
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ if (unlikely(error > 0))
+ sql_print_error(ER_THD(new_thd, ER_GET_ERRNO), my_errno,
+ table->file->table_type());
+ end_read_record(&read_record_info);
+ table->mark_table_for_reopen();
+ close_mysql_tables(new_thd);
+end:
+ new_thd->db= null_clex_str; // Avoid free on thd->db
+ delete new_thd;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ called only by plugin_init()
+*/
+static bool plugin_load_list(MEM_ROOT *tmp_root, const char *list)
+{
+ char buffer[FN_REFLEN];
+ LEX_CSTRING name= {buffer, 0}, dl= {NULL, 0}, *str= &name;
+ char *p= buffer;
+ DBUG_ENTER("plugin_load_list");
+ while (list)
+ {
+ if (p == buffer + sizeof(buffer) - 1)
+ {
+ sql_print_error("plugin-load parameter too long");
+ DBUG_RETURN(TRUE);
+ }
+
+ switch ((*(p++)= *(list++))) {
+ case '\0':
+ list= NULL; /* terminate the loop */
+ /* fall through */
+ case ';':
+#ifndef __WIN__
+ case ':': /* can't use this as delimiter as it may be drive letter */
+#endif
+ p[-1]= 0;
+ if (str == &name) // load all plugins in named module
+ {
+ if (!name.length)
+ {
+ p--; /* reset pointer */
+ continue;
+ }
+
+ dl= name;
+ mysql_mutex_lock(&LOCK_plugin);
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ name.str= 0; // load everything
+ if (plugin_add(tmp_root, false, &name, &dl,
+ MYF(ME_ERROR_LOG)) != INSTALL_GOOD)
+ goto error;
+ }
+ else
+ {
+ free_root(tmp_root, MYF(MY_MARK_BLOCKS_FREE));
+ mysql_mutex_lock(&LOCK_plugin);
+ if (plugin_add(tmp_root, false, &name, &dl,
+ MYF(ME_ERROR_LOG)) != INSTALL_GOOD)
+ goto error;
+ }
+ 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)
+ {
+ p[-1]= 0;
+ str= &dl;
+ str->str= p;
+ continue;
+ }
+ /* fall through */
+ default:
+ str->length++;
+ continue;
+ }
+ }
+ DBUG_RETURN(FALSE);
+error:
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (name.str)
+ sql_print_error("Couldn't load plugin '%s' from '%s'.",
+ name.str, dl.str);
+ else
+ sql_print_error("Couldn't load plugins from '%s'.", dl.str);
+ DBUG_RETURN(TRUE);
+}
+
+
+void plugin_shutdown(void)
+{
+ uint i, count= plugin_array.elements;
+ struct st_plugin_int **plugins, *plugin;
+ struct st_plugin_dl **dl;
+ DBUG_ENTER("plugin_shutdown");
+
+ if (initialized)
+ {
+ if (opt_gtid_pos_auto_plugins)
+ {
+ free_engine_list(opt_gtid_pos_auto_plugins);
+ opt_gtid_pos_auto_plugins= NULL;
+ }
+
+ 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.elements))
+ {
+ reap_plugins();
+ for (i= 0; i < count; i++)
+ {
+ plugin= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ if (plugin->state == PLUGIN_IS_READY)
+ {
+ plugin->state= PLUGIN_IS_DELETED;
+ reap_needed= true;
+ }
+ }
+ if (!reap_needed)
+ {
+ /*
+ release any plugin references held.
+ */
+ unlock_variables(NULL, &global_system_variables);
+ unlock_variables(NULL, &max_system_variables);
+ }
+ }
+
+ plugins= (struct 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]= *dynamic_element(&plugin_array, i, struct st_plugin_int **);
+ /* 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)))
+ {
+ /*
+ 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, 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);
+
+ /*
+ 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)
+ sql_print_error("Plugin '%s' has ref_count=%d after shutdown.",
+ plugins[i]->name.str, plugins[i]->ref_count);
+ if (plugins[i]->state & PLUGIN_IS_UNINITIALIZED ||
+ plugins[i]->state & PLUGIN_IS_DISABLED)
+ plugin_del(plugins[i]);
+ }
+
+ /*
+ Now we can deallocate all memory.
+ */
+
+ cleanup_variables(&global_system_variables);
+ cleanup_variables(&max_system_variables);
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ initialized= 0;
+ mysql_mutex_destroy(&LOCK_plugin);
+
+ my_afree(plugins);
+ }
+
+ /* Dispose of the memory */
+
+ for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++)
+ my_hash_free(&plugin_hash[i]);
+ delete_dynamic(&plugin_array);
+
+ count= plugin_dl_array.elements;
+ dl= (struct st_plugin_dl **)my_alloca(sizeof(void*) * count);
+ for (i= 0; i < count; i++)
+ dl[i]= *dynamic_element(&plugin_dl_array, i, struct st_plugin_dl **);
+ for (i= 0; i < plugin_dl_array.elements; i++)
+ free_plugin_mem(dl[i]);
+ my_afree(dl);
+ delete_dynamic(&plugin_dl_array);
+
+ my_hash_free(&bookmark_hash);
+ free_root(&plugin_mem_root, MYF(0));
+ free_root(&plugin_vars_mem_root, MYF(0));
+
+ global_variables_dynamic_size= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+/**
+ complete plugin installation (after plugin_add).
+
+ That is, initialize it, and update mysql.plugin table
+*/
+static bool finalize_install(THD *thd, TABLE *table, const LEX_CSTRING *name,
+ int *argc, char **argv)
+{
+ struct st_plugin_int *tmp= plugin_find_internal(name, MYSQL_ANY_PLUGIN);
+ int error;
+ DBUG_ASSERT(tmp);
+ mysql_mutex_assert_owner(&LOCK_plugin); // because of tmp->state
+
+ if (tmp->state != PLUGIN_IS_UNINITIALIZED)
+ {
+ /* already installed */
+ return 0;
+ }
+ else
+ {
+ if (plugin_initialize(thd->mem_root, tmp, argc, argv, false))
+ {
+ my_error(ER_CANT_INITIALIZE_UDF, MYF(0), name->str,
+ "Plugin initialization function failed.");
+ tmp->state= PLUGIN_IS_DELETED;
+ return 1;
+ }
+ }
+ if (tmp->state == PLUGIN_IS_DISABLED)
+ {
+ if (global_system_variables.log_warnings)
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+ ER_CANT_INITIALIZE_UDF,
+ ER_THD(thd, ER_CANT_INITIALIZE_UDF),
+ name->str, "Plugin is disabled");
+ }
+
+ /*
+ 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.
+ */
+ DBUG_ASSERT(!table->file->row_logging);
+ 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(tmp->plugin_dl->dl.str, tmp->plugin_dl->dl.length,
+ files_charset_info);
+ error= table->file->ha_write_row(table->record[0]);
+ if (unlikely(error))
+ {
+ table->file->print_error(error, MYF(0));
+ tmp->state= PLUGIN_IS_DELETED;
+ return 1;
+ }
+ return 0;
+}
+
+bool mysql_install_plugin(THD *thd, const LEX_CSTRING *name,
+ const LEX_CSTRING *dl_arg)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ LEX_CSTRING dl= *dl_arg;
+ enum install_status error;
+ int argc=orig_argc;
+ char **argv=orig_argv;
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] =
+ { MYSQL_AUDIT_GENERAL_CLASSMASK };
+ DBUG_ENTER("mysql_install_plugin");
+
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PLUGIN_NAME, 0, TL_WRITE);
+ if (!opt_noacl && check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE);
+ WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
+
+ /* need to open before acquiring LOCK_plugin or it will deadlock */
+ if (! (table = open_ltable(thd, &tables, TL_WRITE,
+ MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(TRUE);
+
+ if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL))
+ {
+ my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), name->str);
+ DBUG_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.
+
+ See also mysql_uninstall_plugin() and initialize_audit_plugin()
+ */
+ if (mysql_audit_general_enabled())
+ mysql_audit_acquire_plugins(thd, event_class_mask);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ DEBUG_SYNC(thd, "acquired_LOCK_plugin");
+ error= plugin_add(thd->mem_root, thd->lex->create_info.if_not_exists(),
+ name, &dl, MYF(0));
+ if (unlikely(error != INSTALL_GOOD))
+ goto err;
+
+ if (name->str)
+ error= finalize_install(thd, table, name, &argc, argv)
+ ? INSTALL_FAIL_NOT_OK : INSTALL_GOOD;
+ else
+ {
+ st_plugin_dl *plugin_dl= plugin_dl_find(&dl);
+ struct st_maria_plugin *plugin;
+ for (plugin= plugin_dl->plugins; plugin->info; plugin++)
+ {
+ LEX_CSTRING str= { plugin->name, strlen(plugin->name) };
+ if (finalize_install(thd, table, &str, &argc, argv))
+ error= INSTALL_FAIL_NOT_OK;
+ }
+ }
+
+ if (unlikely(error != INSTALL_GOOD))
+ {
+ reap_needed= true;
+ reap_plugins();
+ }
+err:
+ global_plugin_version++;
+ mysql_mutex_unlock(&LOCK_plugin);
+ if (argv)
+ free_defaults(argv);
+ DBUG_RETURN(error == INSTALL_FAIL_NOT_OK);
+#ifdef WITH_WSREP
+wsrep_error_label:
+ DBUG_RETURN(true);
+#endif
+}
+
+
+static bool do_uninstall(THD *thd, TABLE *table, const LEX_CSTRING *name)
+{
+ struct st_plugin_int *plugin;
+ mysql_mutex_assert_owner(&LOCK_plugin);
+
+ if (!(plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN)) ||
+ plugin->state & (PLUGIN_IS_UNINITIALIZED | PLUGIN_IS_DYING))
+ {
+ // maybe plugin is present in mysql.plugin; postpone the error
+ plugin= nullptr;
+ }
+
+ if (plugin)
+ {
+ if (!plugin->plugin_dl)
+ {
+ my_error(ER_PLUGIN_DELETE_BUILTIN, MYF(0));
+ return 1;
+ }
+ if (plugin->load_option == PLUGIN_FORCE_PLUS_PERMANENT)
+ {
+ my_error(ER_PLUGIN_IS_PERMANENT, MYF(0), name->str);
+ return 1;
+ }
+
+ plugin->state= PLUGIN_IS_DELETED;
+ if (plugin->ref_count)
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
+ WARN_PLUGIN_BUSY, ER_THD(thd, WARN_PLUGIN_BUSY));
+ else
+ reap_needed= true;
+ }
+
+ 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 (! table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
+ HA_WHOLE_KEY, HA_READ_KEY_EXACT))
+ {
+ int error;
+ /*
+ 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.
+ */
+ table->file->row_logging= 0; // No logging
+ error= table->file->ha_delete_row(table->record[0]);
+ if (unlikely(error))
+ {
+ table->file->print_error(error, MYF(0));
+ return 1;
+ }
+ }
+ else if (!plugin)
+ {
+ const myf MyFlags= thd->lex->if_exists() ? ME_NOTE : 0;
+ my_error(ER_SP_DOES_NOT_EXIST, MyFlags, "PLUGIN", name->str);
+ return !MyFlags;
+ }
+ return 0;
+}
+
+
+bool mysql_uninstall_plugin(THD *thd, const LEX_CSTRING *name,
+ const LEX_CSTRING *dl_arg)
+{
+ TABLE *table;
+ TABLE_LIST tables;
+ LEX_CSTRING dl= *dl_arg;
+ bool error= false;
+ unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] =
+ { MYSQL_AUDIT_GENERAL_CLASSMASK };
+ DBUG_ENTER("mysql_uninstall_plugin");
+
+ tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PLUGIN_NAME, 0, TL_WRITE);
+
+ if (!opt_noacl && check_table_access(thd, DELETE_ACL, &tables, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE);
+
+ WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
+
+ /* need to open before acquiring LOCK_plugin or it will deadlock */
+ if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(TRUE);
+
+ if (!table->key_info)
+ {
+ my_printf_error(ER_UNKNOWN_ERROR,
+ "The table %s.%s has no primary key. "
+ "Please check the table definition and "
+ "create the primary key accordingly.", MYF(0),
+ table->s->db.str, table->s->table_name.str);
+ DBUG_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.
+
+ See also mysql_install_plugin() and initialize_audit_plugin()
+ */
+ if (mysql_audit_general_enabled())
+ mysql_audit_acquire_plugins(thd, event_class_mask);
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ if (name->str)
+ error= do_uninstall(thd, table, name);
+ else
+ {
+ fix_dl_name(thd->mem_root, &dl);
+ st_plugin_dl *plugin_dl= plugin_dl_find(&dl);
+ if (plugin_dl)
+ {
+ for (struct st_maria_plugin *plugin= plugin_dl->plugins;
+ plugin->info; plugin++)
+ {
+ LEX_CSTRING str= { plugin->name, strlen(plugin->name) };
+ error|= do_uninstall(thd, table, &str);
+ }
+ }
+ else
+ {
+ myf MyFlags= thd->lex->if_exists() ? ME_NOTE : 0;
+ my_error(ER_SP_DOES_NOT_EXIST, MyFlags, "SONAME", dl.str);
+ error|= !MyFlags;
+ }
+ }
+ reap_plugins();
+
+ global_plugin_version++;
+ mysql_mutex_unlock(&LOCK_plugin);
+ DBUG_RETURN(error);
+#ifdef WITH_WSREP
+wsrep_error_label:
+ DBUG_RETURN(true);
+#endif
+}
+
+
+bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func,
+ int type, uint state_mask, void *arg)
+{
+ uint idx, total= 0;
+ struct st_plugin_int *plugin;
+ plugin_ref *plugins;
+ my_bool res= FALSE;
+ DBUG_ENTER("plugin_foreach_with_mask");
+
+ if (!initialized)
+ DBUG_RETURN(FALSE);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ /*
+ Do the alloca out here in case we do have a working alloca:
+ leaving the nested stack frame invalidates alloca allocation.
+ */
+ if (type == MYSQL_ANY_PLUGIN)
+ {
+ plugins= (plugin_ref*) my_alloca(plugin_array.elements * sizeof(plugin_ref));
+ for (idx= 0; idx < plugin_array.elements; idx++)
+ {
+ plugin= *dynamic_element(&plugin_array, idx, struct st_plugin_int **);
+ if ((plugins[total]= intern_plugin_lock(0, plugin_int_to_ref(plugin),
+ state_mask)))
+ total++;
+ }
+ }
+ else
+ {
+ HASH *hash= plugin_hash + type;
+ plugins= (plugin_ref*) my_alloca(hash->records * sizeof(plugin_ref));
+ for (idx= 0; idx < hash->records; idx++)
+ {
+ plugin= (struct st_plugin_int *) my_hash_element(hash, idx);
+ if ((plugins[total]= intern_plugin_lock(0, plugin_int_to_ref(plugin),
+ state_mask)))
+ total++;
+ }
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ for (idx= 0; idx < total; idx++)
+ {
+ /* It will stop iterating on first engine error when "func" returns TRUE */
+ if ((res= func(thd, plugins[idx], arg)))
+ break;
+ }
+
+ plugin_unlock_list(0, plugins, total);
+ my_afree(plugins);
+ DBUG_RETURN(res);
+}
+
+
+static bool plugin_dl_foreach_internal(THD *thd, st_plugin_dl *plugin_dl,
+ st_maria_plugin *plug,
+ plugin_foreach_func *func, void *arg)
+{
+ for (; plug->name; plug++)
+ {
+ st_plugin_int tmp, *plugin;
+
+ tmp.name.str= const_cast<char*>(plug->name);
+ tmp.name.length= strlen(plug->name);
+ tmp.plugin= plug;
+ tmp.plugin_dl= plugin_dl;
+
+ mysql_mutex_lock(&LOCK_plugin);
+ if ((plugin= plugin_find_internal(&tmp.name, MYSQL_ANY_PLUGIN)) &&
+ plugin->plugin == plug)
+
+ {
+ tmp.state= plugin->state;
+ tmp.load_option= plugin->load_option;
+ }
+ else
+ {
+ tmp.state= PLUGIN_IS_FREED;
+ tmp.load_option= PLUGIN_OFF;
+ }
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ plugin= &tmp;
+ if (func(thd, plugin_int_to_ref(plugin), arg))
+ return 1;
+ }
+ return 0;
+}
+
+bool plugin_dl_foreach(THD *thd, const LEX_CSTRING *dl,
+ plugin_foreach_func *func, void *arg)
+{
+ bool err= 0;
+
+ if (dl)
+ {
+ mysql_mutex_lock(&LOCK_plugin);
+ st_plugin_dl *plugin_dl= plugin_dl_add(dl, MYF(0));
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ if (!plugin_dl)
+ return 1;
+
+ err= plugin_dl_foreach_internal(thd, plugin_dl, plugin_dl->plugins,
+ func, arg);
+
+ mysql_mutex_lock(&LOCK_plugin);
+ plugin_dl_del(plugin_dl);
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ else
+ {
+ struct st_maria_plugin **builtins;
+ for (builtins= mysql_mandatory_plugins; !err && *builtins; builtins++)
+ err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg);
+ for (builtins= mysql_optional_plugins; !err && *builtins; builtins++)
+ err= plugin_dl_foreach_internal(thd, 0, *builtins, func, arg);
+ }
+ return err;
+}
+
+
+/****************************************************************************
+ Internal type declarations for variables support
+****************************************************************************/
+
+#undef MYSQL_SYSVAR_NAME
+#define MYSQL_SYSVAR_NAME(name) name
+#define PLUGIN_VAR_TYPEMASK 0x7f
+#define BOOKMARK_MEMALLOC 0x80
+
+static inline char plugin_var_bookmark_key(uint flags)
+{
+ return (flags & PLUGIN_VAR_TYPEMASK) |
+ (flags & PLUGIN_VAR_MEMALLOC ? BOOKMARK_MEMALLOC : 0);
+}
+
+#define EXTRA_OPTIONS 3 /* options for: 'foo', 'plugin-foo' and NULL */
+
+typedef DECLARE_MYSQL_SYSVAR_BASIC(sysvar_bool_t, my_bool);
+typedef DECLARE_MYSQL_THDVAR_BASIC(thdvar_bool_t, my_bool);
+typedef DECLARE_MYSQL_SYSVAR_BASIC(sysvar_str_t, char *);
+typedef DECLARE_MYSQL_THDVAR_BASIC(thdvar_str_t, char *);
+
+typedef DECLARE_MYSQL_SYSVAR_TYPELIB(sysvar_enum_t, unsigned long);
+typedef DECLARE_MYSQL_THDVAR_TYPELIB(thdvar_enum_t, unsigned long);
+typedef DECLARE_MYSQL_SYSVAR_TYPELIB(sysvar_set_t, ulonglong);
+typedef DECLARE_MYSQL_THDVAR_TYPELIB(thdvar_set_t, ulonglong);
+
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_int_t, int);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_long_t, long);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_longlong_t, longlong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_uint_t, uint);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulong_t, ulong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulonglong_t, ulonglong);
+typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_double_t, double);
+
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_int_t, int);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_long_t, long);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_longlong_t, longlong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_uint_t, uint);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulong_t, ulong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulonglong_t, ulonglong);
+typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_double_t, double);
+
+
+/****************************************************************************
+ default variable data check and update functions
+****************************************************************************/
+
+static int check_func_bool(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ int result, length;
+ long long tmp;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)) ||
+ (result= find_type(&bool_typelib, str, length, 1)-1) < 0)
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, &tmp) < 0)
+ goto err;
+ if (tmp != 0 && tmp != 1)
+ goto err;
+ result= (int) tmp;
+ }
+ *(my_bool *) save= result ? 1 : 0;
+ return 0;
+err:
+ return 1;
+}
+
+
+static int check_func_int(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(uint *)save= (uint) getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(int *)save= (int) getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+
+static int check_func_long(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(ulong *)save= (ulong) getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(long *)save= (long) getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+
+static int check_func_longlong(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ my_bool fixed1, fixed2;
+ long long orig, val;
+ struct my_option options;
+ value->val_int(value, &orig);
+ val= orig;
+ plugin_opt_set_limits(&options, var);
+
+ if (var->flags & PLUGIN_VAR_UNSIGNED)
+ {
+ if ((fixed1= (!value->is_unsigned(value) && val < 0)))
+ val=0;
+ *(ulonglong *)save= getopt_ull_limit_value((ulonglong) val, &options,
+ &fixed2);
+ }
+ else
+ {
+ if ((fixed1= (value->is_unsigned(value) && val < 0)))
+ val=LONGLONG_MAX;
+ *(longlong *)save= getopt_ll_limit_value(val, &options, &fixed2);
+ }
+
+ return throw_bounds_warning(thd, var->name, fixed1 || fixed2,
+ value->is_unsigned(value), (longlong) orig);
+}
+
+static int check_func_str(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ int length;
+
+ length= sizeof(buff);
+ if ((str= value->val_str(value, buff, &length)))
+ str= thd->strmake(str, length);
+ *(const char**)save= str;
+ return 0;
+}
+
+
+static int check_func_enum(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE];
+ const char *str;
+ TYPELIB *typelib;
+ long long tmp;
+ long result;
+ int length;
+
+ if (var->flags & PLUGIN_VAR_THDLOCAL)
+ typelib= ((thdvar_enum_t*) var)->typelib;
+ else
+ typelib= ((sysvar_enum_t*) var)->typelib;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)))
+ goto err;
+ if ((result= (long)find_type(typelib, str, length, 0) - 1) < 0)
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, &tmp))
+ goto err;
+ if (tmp < 0 || tmp >= typelib->count)
+ goto err;
+ result= (long) tmp;
+ }
+ *(long*)save= result;
+ return 0;
+err:
+ return 1;
+}
+
+
+static int check_func_set(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ char buff[STRING_BUFFER_USUAL_SIZE], *error= 0;
+ const char *str;
+ TYPELIB *typelib;
+ ulonglong result;
+ uint error_len= 0; // init as only set on error
+ bool not_used;
+ int length;
+
+ if (var->flags & PLUGIN_VAR_THDLOCAL)
+ typelib= ((thdvar_set_t*) var)->typelib;
+ else
+ typelib= ((sysvar_set_t*)var)->typelib;
+
+ if (value->value_type(value) == MYSQL_VALUE_TYPE_STRING)
+ {
+ length= sizeof(buff);
+ if (!(str= value->val_str(value, buff, &length)))
+ goto err;
+ result= find_set(typelib, str, length, NULL,
+ &error, &error_len, &not_used);
+ if (unlikely(error_len))
+ goto err;
+ }
+ else
+ {
+ if (value->val_int(value, (long long *)&result))
+ goto err;
+ if (unlikely((result >= (1ULL << typelib->count)) &&
+ (typelib->count < sizeof(long)*8)))
+ goto err;
+ }
+ *(ulonglong*)save= result;
+ return 0;
+err:
+ return 1;
+}
+
+static int check_func_double(THD *thd, struct st_mysql_sys_var *var,
+ void *save, st_mysql_value *value)
+{
+ double v;
+ my_bool fixed;
+ struct my_option option;
+
+ value->val_real(value, &v);
+ plugin_opt_set_limits(&option, var);
+ *(double *) save= getopt_double_limit_value(v, &option, &fixed);
+
+ return throw_bounds_warning(thd, var->name, fixed, v);
+}
+
+
+static void update_func_bool(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(my_bool *) tgt= *(my_bool *) save ? 1 : 0;
+}
+
+
+static void update_func_int(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(int *)tgt= *(int *) save;
+}
+
+
+static void update_func_long(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(long *)tgt= *(long *) save;
+}
+
+
+static void update_func_longlong(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(longlong *)tgt= *(ulonglong *) save;
+}
+
+
+static void update_func_str(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ char *value= *(char**) save;
+ if (var->flags & PLUGIN_VAR_MEMALLOC)
+ {
+ char *old= *(char**) tgt;
+ if (value)
+ *(char**) tgt= my_strdup(key_memory_global_system_variables,
+ value, MYF(0));
+ else
+ *(char**) tgt= 0;
+ my_free(old);
+ }
+ else
+ *(char**) tgt= value;
+}
+
+static void update_func_double(THD *thd, struct st_mysql_sys_var *var,
+ void *tgt, const void *save)
+{
+ *(double *) tgt= *(double *) save;
+}
+
+/****************************************************************************
+ System Variables support
+****************************************************************************/
+
+sys_var *find_sys_var(THD *thd, const char *str, size_t length,
+ bool throw_error)
+{
+ sys_var *var;
+ sys_var_pluginvar *pi;
+ DBUG_ENTER("find_sys_var");
+ DBUG_PRINT("enter", ("var '%.*s'", (int)length, str));
+
+ mysql_prlock_rdlock(&LOCK_system_variables_hash);
+ if ((var= intern_find_sys_var(str, length)) &&
+ (pi= var->cast_pluginvar()))
+ {
+ mysql_mutex_lock(&LOCK_plugin);
+ if (!intern_plugin_lock(thd ? thd->lex : 0, plugin_int_to_ref(pi->plugin),
+ PLUGIN_IS_READY))
+ var= NULL; /* failed to lock it, it must be uninstalling */
+ mysql_mutex_unlock(&LOCK_plugin);
+ }
+ mysql_prlock_unlock(&LOCK_system_variables_hash);
+
+ if (unlikely(!throw_error && !var))
+ my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0),
+ (int) (length ? length : strlen(str)), (char*) str);
+ DBUG_RETURN(var);
+}
+
+
+/*
+ called by register_var, construct_options and test_plugin_options.
+ Returns the 'bookmark' for the named variable.
+ LOCK_system_variables_hash should be at least read locked
+*/
+static st_bookmark *find_bookmark(const char *plugin, const char *name,
+ int flags)
+{
+ st_bookmark *result= NULL;
+ size_t namelen, length, pluginlen= 0;
+ char *varname, *p;
+
+ if (!(flags & PLUGIN_VAR_THDLOCAL))
+ return NULL;
+
+ namelen= strlen(name);
+ if (plugin)
+ pluginlen= strlen(plugin) + 1;
+ length= namelen + pluginlen + 2;
+ varname= (char*) my_alloca(length);
+
+ if (plugin)
+ {
+ strxmov(varname + 1, plugin, "_", name, NullS);
+ for (p= varname + 1; *p; p++)
+ if (*p == '-')
+ *p= '_';
+ }
+ else
+ memcpy(varname + 1, name, namelen + 1);
+
+ varname[0]= plugin_var_bookmark_key(flags);
+
+ result= (st_bookmark*) my_hash_search(&bookmark_hash,
+ (const uchar*) varname, length - 1);
+
+ my_afree(varname);
+ return result;
+}
+
+
+static size_t var_storage_size(int flags)
+{
+ switch (flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL: return sizeof(my_bool);
+ case PLUGIN_VAR_INT: return sizeof(int);
+ case PLUGIN_VAR_LONG: return sizeof(long);
+ case PLUGIN_VAR_ENUM: return sizeof(long);
+ case PLUGIN_VAR_LONGLONG: return sizeof(ulonglong);
+ case PLUGIN_VAR_SET: return sizeof(ulonglong);
+ case PLUGIN_VAR_STR: return sizeof(char*);
+ case PLUGIN_VAR_DOUBLE: return sizeof(double);
+ default: DBUG_ASSERT(0); return 0;
+ }
+}
+
+
+/*
+ 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, offset, new_size;
+ st_bookmark *result;
+ char *varname, *p;
+
+ DBUG_ASSERT(flags & PLUGIN_VAR_THDLOCAL);
+
+ size= var_storage_size(flags);
+ 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*) alloc_root(&plugin_vars_mem_root,
+ sizeof(struct st_bookmark) + length-1);
+ varname[0]= plugin_var_bookmark_key(flags);
+ memcpy(result->key, varname, length);
+ result->name_len= (uint)(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.
+ */
+ bzero(global_system_variables.dynamic_variables_ptr +
+ global_variables_dynamic_size,
+ new_size - global_variables_dynamic_size);
+ bzero(max_system_variables.dynamic_variables_ptr +
+ global_variables_dynamic_size,
+ new_size - global_variables_dynamic_size);
+ global_variables_dynamic_size= new_size;
+ }
+
+ global_system_variables.dynamic_variables_head= (uint)offset;
+ max_system_variables.dynamic_variables_head= (uint)offset;
+ global_system_variables.dynamic_variables_size= (uint)(offset + size);
+ max_system_variables.dynamic_variables_size= (uint)(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 */
+ if (my_hash_insert(&bookmark_hash, (uchar*) result))
+ {
+ fprintf(stderr, "failed to add placeholder to hash");
+ DBUG_ASSERT(0);
+ }
+ }
+ my_afree(varname);
+ return result;
+}
+
+
+void sync_dynamic_session_variables(THD* thd, bool global_lock)
+{
+ uint idx;
+
+ 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));
+
+ if (global_lock)
+ mysql_mutex_lock(&LOCK_global_system_variables);
+
+ mysql_mutex_assert_owner(&LOCK_global_system_variables);
+
+ 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);
+
+ /*
+ now we need to iterate through any newly copied 'defaults'
+ and if it is a string type with MEMALLOC flag, we need to strdup
+ */
+ for (idx= 0; idx < bookmark_hash.records; idx++)
+ {
+ st_bookmark *v= (st_bookmark*) my_hash_element(&bookmark_hash,idx);
+
+ if (v->version <= thd->variables.dynamic_variables_version)
+ continue; /* already in thd->variables */
+
+ /* Here we do anything special that may be required of the data types */
+
+ if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
+ v->key[0] & BOOKMARK_MEMALLOC)
+ {
+ char **pp= (char**) (thd->variables.dynamic_variables_ptr + v->offset);
+ if (*pp)
+ *pp= my_strdup(key_memory_THD_variables, *pp, MYF(MY_WME|MY_FAE));
+ }
+ }
+
+ 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;
+}
+
+
+/*
+ returns a pointer to the memory which holds the thd-local variable or
+ a pointer to the global variable if thd==null.
+ If required, will sync with global variables if the requested variable
+ has not yet been allocated in the current thread.
+*/
+static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock)
+{
+ DBUG_ENTER("intern_sys_var_ptr");
+ DBUG_ASSERT(offset >= 0);
+ DBUG_ASSERT((uint)offset <= global_system_variables.dynamic_variables_head);
+
+ if (!thd)
+ DBUG_RETURN((uchar*) global_system_variables.dynamic_variables_ptr + offset);
+
+ /*
+ dynamic_variables_head points to the largest valid offset
+ */
+ if (!thd->variables.dynamic_variables_ptr ||
+ (uint)offset > thd->variables.dynamic_variables_head)
+ {
+ mysql_prlock_rdlock(&LOCK_system_variables_hash);
+ sync_dynamic_session_variables(thd, global_lock);
+ mysql_prlock_unlock(&LOCK_system_variables_hash);
+ }
+ DBUG_RETURN((uchar*)thd->variables.dynamic_variables_ptr + offset);
+}
+
+
+/**
+ 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 char *mysql_sys_var_char(THD* thd, int offset)
+{
+ return (char *) 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 long *mysql_sys_var_long(THD* thd, int offset)
+{
+ return (long *) 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 long long *mysql_sys_var_longlong(THD* thd, int offset)
+{
+ return (long 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)
+{
+ plugin_ref old_table_plugin= thd->variables.table_plugin;
+ plugin_ref old_tmp_table_plugin= thd->variables.tmp_table_plugin;
+ plugin_ref old_enforced_table_plugin= thd->variables.enforced_table_plugin;
+ DBUG_ENTER("plugin_thdvar_init");
+
+ // This function may be called many times per THD (e.g. on COM_CHANGE_USER)
+ thd->variables.table_plugin= NULL;
+ thd->variables.tmp_table_plugin= NULL;
+ thd->variables.enforced_table_plugin= NULL;
+ cleanup_variables(&thd->variables);
+
+ /* This and all other variable cleanups are here for COM_CHANGE_USER :( */
+#ifndef EMBEDDED_LIBRARY
+ thd->session_tracker.sysvars.deinit(thd);
+#endif
+
+ thd->variables= global_system_variables;
+
+ /* we are going to allocate these lazily */
+ thd->variables.dynamic_variables_version= 0;
+ thd->variables.dynamic_variables_size= 0;
+ thd->variables.dynamic_variables_ptr= 0;
+
+ mysql_mutex_lock(&LOCK_plugin);
+ thd->variables.table_plugin=
+ intern_plugin_lock(NULL, global_system_variables.table_plugin);
+ if (global_system_variables.tmp_table_plugin)
+ thd->variables.tmp_table_plugin=
+ intern_plugin_lock(NULL, global_system_variables.tmp_table_plugin);
+ if (global_system_variables.enforced_table_plugin)
+ thd->variables.enforced_table_plugin=
+ intern_plugin_lock(NULL, global_system_variables.enforced_table_plugin);
+ intern_plugin_unlock(NULL, old_table_plugin);
+ intern_plugin_unlock(NULL, old_tmp_table_plugin);
+ intern_plugin_unlock(NULL, old_enforced_table_plugin);
+ mysql_mutex_unlock(&LOCK_plugin);
+
+#ifndef EMBEDDED_LIBRARY
+ thd->session_tracker.sysvars.init(thd);
+#endif
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Unlocks all system variables which hold a reference
+*/
+static void unlock_variables(THD *thd, struct system_variables *vars)
+{
+ intern_plugin_unlock(NULL, vars->table_plugin);
+ intern_plugin_unlock(NULL, vars->tmp_table_plugin);
+ intern_plugin_unlock(NULL, vars->enforced_table_plugin);
+ vars->table_plugin= vars->tmp_table_plugin= vars->enforced_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(struct system_variables *vars)
+{
+ st_bookmark *v;
+ uint idx;
+
+ mysql_prlock_rdlock(&LOCK_system_variables_hash);
+ for (idx= 0; idx < bookmark_hash.records; idx++)
+ {
+ v= (st_bookmark*) my_hash_element(&bookmark_hash, idx);
+
+ if (v->version > vars->dynamic_variables_version)
+ continue; /* not in vars */
+
+ DBUG_ASSERT((uint)v->offset <= vars->dynamic_variables_head);
+
+ /* free allocated strings (PLUGIN_VAR_STR | PLUGIN_VAR_MEMALLOC) */
+ if ((v->key[0] & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
+ v->key[0] & BOOKMARK_MEMALLOC)
+ {
+ char **ptr= (char**)(vars->dynamic_variables_ptr + v->offset);
+ my_free(*ptr);
+ *ptr= NULL;
+ }
+ }
+ mysql_prlock_unlock(&LOCK_system_variables_hash);
+
+ DBUG_ASSERT(vars->table_plugin == NULL);
+ DBUG_ASSERT(vars->tmp_table_plugin == NULL);
+ DBUG_ASSERT(vars->enforced_table_plugin == NULL);
+
+ my_free(vars->dynamic_variables_ptr);
+ vars->dynamic_variables_ptr= NULL;
+ vars->dynamic_variables_size= 0;
+ vars->dynamic_variables_version= 0;
+}
+
+
+void plugin_thdvar_cleanup(THD *thd)
+{
+ uint idx;
+ plugin_ref *list;
+ DBUG_ENTER("plugin_thdvar_cleanup");
+
+#ifndef EMBEDDED_LIBRARY
+ thd->session_tracker.sysvars.deinit(thd);
+#endif
+
+ mysql_mutex_lock(&LOCK_plugin);
+
+ unlock_variables(thd, &thd->variables);
+ cleanup_variables(&thd->variables);
+
+ if ((idx= thd->lex->plugins.elements))
+ {
+ list= ((plugin_ref*) thd->lex->plugins.buffer) + idx - 1;
+ DBUG_PRINT("info",("unlocking %d plugins", idx));
+ while ((uchar*) list >= thd->lex->plugins.buffer)
+ intern_plugin_unlock(NULL, *list--);
+ }
+
+ reap_plugins();
+ mysql_mutex_unlock(&LOCK_plugin);
+
+ reset_dynamic(&thd->lex->plugins);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @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_ENTER("plugin_vars_free_values");
+
+ 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;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+static SHOW_TYPE pluginvar_show_type(const st_mysql_sys_var *plugin_var)
+{
+ switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_UNSIGNED)) {
+ case PLUGIN_VAR_BOOL:
+ return SHOW_MY_BOOL;
+ case PLUGIN_VAR_INT:
+ return SHOW_SINT;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED:
+ return SHOW_UINT;
+ case PLUGIN_VAR_LONG:
+ return SHOW_SLONG;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED:
+ return SHOW_ULONG;
+ case PLUGIN_VAR_LONGLONG:
+ return SHOW_SLONGLONG;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED:
+ return SHOW_ULONGLONG;
+ case PLUGIN_VAR_STR:
+ return SHOW_CHAR_PTR;
+ case PLUGIN_VAR_ENUM:
+ case PLUGIN_VAR_SET:
+ return SHOW_CHAR;
+ case PLUGIN_VAR_DOUBLE:
+ return SHOW_DOUBLE;
+ default:
+ DBUG_ASSERT(0);
+ return SHOW_UNDEF;
+ }
+}
+
+
+static int pluginvar_sysvar_flags(const st_mysql_sys_var *p)
+{
+ return (p->flags & PLUGIN_VAR_THDLOCAL ? sys_var::SESSION : sys_var::GLOBAL)
+ | (p->flags & PLUGIN_VAR_READONLY ? sys_var::READONLY : 0);
+}
+
+sys_var_pluginvar::sys_var_pluginvar(sys_var_chain *chain, const char *name_arg,
+ st_plugin_int *p, st_mysql_sys_var *pv)
+ : sys_var(chain, name_arg, pv->comment, pluginvar_sysvar_flags(pv),
+ 0, pv->flags & PLUGIN_VAR_NOCMDOPT ? -1 : 0, NO_ARG,
+ pluginvar_show_type(pv), 0,
+ NULL, VARIABLE_NOT_IN_BINLOG, NULL, NULL, NULL),
+ plugin(p), plugin_var(pv)
+{
+ plugin_var->name= name_arg;
+ plugin_opt_set_limits(&option, pv);
+}
+
+uchar* sys_var_pluginvar::real_value_ptr(THD *thd, enum_var_type type) const
+{
+ if (type == OPT_DEFAULT)
+ {
+ switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL:
+ thd->sys_var_tmp.my_bool_value= (my_bool)option.def_value;
+ return (uchar*) &thd->sys_var_tmp.my_bool_value;
+ case PLUGIN_VAR_INT:
+ thd->sys_var_tmp.int_value= (int)option.def_value;
+ return (uchar*) &thd->sys_var_tmp.int_value;
+ case PLUGIN_VAR_LONG:
+ case PLUGIN_VAR_ENUM:
+ thd->sys_var_tmp.long_value= (long)option.def_value;
+ return (uchar*) &thd->sys_var_tmp.long_value;
+ case PLUGIN_VAR_LONGLONG:
+ case PLUGIN_VAR_SET:
+ return (uchar*) &option.def_value;
+ case PLUGIN_VAR_STR:
+ thd->sys_var_tmp.ptr_value= (void*) option.def_value;
+ return (uchar*) &thd->sys_var_tmp.ptr_value;
+ case PLUGIN_VAR_DOUBLE:
+ thd->sys_var_tmp.double_value= getopt_ulonglong2double(option.def_value);
+ return (uchar*) &thd->sys_var_tmp.double_value;
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ DBUG_ASSERT(thd || (type == OPT_GLOBAL));
+ if (plugin_var->flags & PLUGIN_VAR_THDLOCAL)
+ {
+ if (type == OPT_GLOBAL)
+ thd= NULL;
+
+ return intern_sys_var_ptr(thd, *(int*) (plugin_var+1), false);
+ }
+ return *(uchar**) (plugin_var+1);
+}
+
+
+bool sys_var_pluginvar::session_is_default(THD *thd)
+{
+ uchar *value= plugin_var->flags & PLUGIN_VAR_THDLOCAL
+ ? intern_sys_var_ptr(thd, *(int*) (plugin_var+1), true)
+ : *(uchar**) (plugin_var+1);
+
+ real_value_ptr(thd, OPT_SESSION);
+
+ switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) {
+ case PLUGIN_VAR_BOOL:
+ return option.def_value == *(my_bool*)value;
+ case PLUGIN_VAR_INT:
+ return option.def_value == *(int*)value;
+ case PLUGIN_VAR_LONG:
+ case PLUGIN_VAR_ENUM:
+ return option.def_value == *(long*)value;
+ case PLUGIN_VAR_LONGLONG:
+ case PLUGIN_VAR_SET:
+ return option.def_value == *(longlong*)value;
+ case PLUGIN_VAR_STR:
+ {
+ const char *a=(char*)option.def_value;
+ const char *b=(char*)value;
+ return (!a && !b) || (a && b && strcmp(a,b));
+ }
+ case PLUGIN_VAR_DOUBLE:
+ return getopt_ulonglong2double(option.def_value) == *(double*)value;
+ }
+ DBUG_ASSERT(0);
+ return 0;
+}
+
+
+TYPELIB* sys_var_pluginvar::plugin_var_typelib(void) const
+{
+ switch (plugin_var->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_THDLOCAL)) {
+ case PLUGIN_VAR_ENUM:
+ return ((sysvar_enum_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_SET:
+ return ((sysvar_set_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ return ((thdvar_enum_t *)plugin_var)->typelib;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ return ((thdvar_set_t *)plugin_var)->typelib;
+ default:
+ return NULL;
+ }
+ return NULL; /* Keep compiler happy */
+}
+
+
+const uchar* sys_var_pluginvar::do_value_ptr(THD *thd, enum_var_type type,
+ const LEX_CSTRING *base) const
+{
+ const uchar* result= real_value_ptr(thd, type);
+
+ if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_ENUM)
+ result= (uchar*) get_type(plugin_var_typelib(), *(ulong*)result);
+ else if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_SET)
+ result= (uchar*) set_to_string(thd, 0, *(ulonglong*) result,
+ plugin_var_typelib()->type_names);
+ return result;
+}
+
+bool sys_var_pluginvar::do_check(THD *thd, set_var *var)
+{
+ st_item_value_holder value;
+ DBUG_ASSERT(!is_readonly());
+ DBUG_ASSERT(plugin_var->check);
+
+ value.value_type= item_value_type;
+ value.val_str= item_val_str;
+ value.val_int= item_val_int;
+ value.val_real= item_val_real;
+ value.is_unsigned= item_is_unsigned;
+ value.item= var->value;
+
+ return plugin_var->check(thd, plugin_var, &var->save_result, &value);
+}
+
+bool sys_var_pluginvar::session_update(THD *thd, set_var *var)
+{
+ DBUG_ASSERT(!is_readonly());
+ DBUG_ASSERT(plugin_var->flags & PLUGIN_VAR_THDLOCAL);
+ DBUG_ASSERT(thd == current_thd);
+
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ void *tgt= real_value_ptr(thd, OPT_SESSION);
+ const void *src= var->value ? (void*)&var->save_result
+ : (void*)real_value_ptr(thd, OPT_GLOBAL);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+
+ plugin_var->update(thd, plugin_var, tgt, src);
+
+ return false;
+}
+
+static const void *var_def_ptr(st_mysql_sys_var *pv)
+{
+ switch (pv->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_THDLOCAL)) {
+ case PLUGIN_VAR_INT:
+ return &((sysvar_uint_t*) pv)->def_val;
+ case PLUGIN_VAR_LONG:
+ return &((sysvar_ulong_t*) pv)->def_val;
+ case PLUGIN_VAR_LONGLONG:
+ return &((sysvar_ulonglong_t*) pv)->def_val;
+ case PLUGIN_VAR_ENUM:
+ return &((sysvar_enum_t*) pv)->def_val;
+ case PLUGIN_VAR_SET:
+ return &((sysvar_set_t*) pv)->def_val;
+ case PLUGIN_VAR_BOOL:
+ return &((sysvar_bool_t*) pv)->def_val;
+ case PLUGIN_VAR_STR:
+ return &((sysvar_str_t*) pv)->def_val;
+ case PLUGIN_VAR_DOUBLE:
+ return &((sysvar_double_t*) pv)->def_val;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_uint_t*) pv)->def_val;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_ulong_t*) pv)->def_val;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_ulonglong_t*) pv)->def_val;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_enum_t*) pv)->def_val;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_set_t*) pv)->def_val;
+ case PLUGIN_VAR_BOOL | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_bool_t*) pv)->def_val;
+ case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_str_t*) pv)->def_val;
+ case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL:
+ return &((thdvar_double_t*) pv)->def_val;
+ default:
+ DBUG_ASSERT(0);
+ return NULL;
+ }
+}
+
+
+bool sys_var_pluginvar::global_update(THD *thd, set_var *var)
+{
+ DBUG_ASSERT(!is_readonly());
+ mysql_mutex_assert_owner(&LOCK_global_system_variables);
+
+ void *tgt= real_value_ptr(thd, OPT_GLOBAL);
+ const void *src= &var->save_result;
+
+ if (!var->value)
+ src= var_def_ptr(plugin_var);
+
+ plugin_var->update(thd, plugin_var, tgt, src);
+ return false;
+}
+
+
+#define OPTION_SET_LIMITS(type, options, opt) \
+ options->var_type= type; \
+ options->def_value= (opt)->def_val; \
+ options->min_value= (opt)->min_val; \
+ options->max_value= (opt)->max_val; \
+ options->block_size= (long) (opt)->blk_sz
+
+#define OPTION_SET_LIMITS_DOUBLE(options, opt) \
+ options->var_type= GET_DOUBLE; \
+ options->def_value= (longlong) getopt_double2ulonglong((opt)->def_val); \
+ options->min_value= (longlong) getopt_double2ulonglong((opt)->min_val); \
+ options->max_value= getopt_double2ulonglong((opt)->max_val); \
+ options->block_size= (long) (opt)->blk_sz;
+
+void plugin_opt_set_limits(struct my_option *options,
+ const struct st_mysql_sys_var *opt)
+{
+ options->sub_size= 0;
+
+ switch (opt->flags & (PLUGIN_VAR_TYPEMASK |
+ PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL)) {
+ /* global system variables */
+ case PLUGIN_VAR_INT:
+ OPTION_SET_LIMITS(GET_INT, options, (sysvar_int_t*) opt);
+ break;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_UINT, options, (sysvar_uint_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG:
+ OPTION_SET_LIMITS(GET_LONG, options, (sysvar_long_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_ULONG, options, (sysvar_ulong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ OPTION_SET_LIMITS(GET_LL, options, (sysvar_longlong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED:
+ OPTION_SET_LIMITS(GET_ULL, options, (sysvar_ulonglong_t*) opt);
+ break;
+ case PLUGIN_VAR_ENUM:
+ options->var_type= GET_ENUM;
+ options->typelib= ((sysvar_enum_t*) opt)->typelib;
+ options->def_value= ((sysvar_enum_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= options->typelib->count - 1;
+ break;
+ case PLUGIN_VAR_SET:
+ options->var_type= GET_SET;
+ options->typelib= ((sysvar_set_t*) opt)->typelib;
+ options->def_value= ((sysvar_set_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= (1ULL << options->typelib->count) - 1;
+ break;
+ case PLUGIN_VAR_BOOL:
+ options->var_type= GET_BOOL;
+ options->def_value= ((sysvar_bool_t*) opt)->def_val;
+ options->typelib= &bool_typelib;
+ break;
+ case PLUGIN_VAR_STR:
+ options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ?
+ GET_STR_ALLOC : GET_STR);
+ options->def_value= (intptr) ((sysvar_str_t*) opt)->def_val;
+ break;
+ case PLUGIN_VAR_DOUBLE:
+ OPTION_SET_LIMITS_DOUBLE(options, (sysvar_double_t*) opt);
+ break;
+ /* threadlocal variables */
+ case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_INT, options, (thdvar_int_t*) opt);
+ break;
+ case PLUGIN_VAR_INT | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_UINT, options, (thdvar_uint_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_LONG, options, (thdvar_long_t*) opt);
+ break;
+ case PLUGIN_VAR_LONG | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_ULONG, options, (thdvar_ulong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_LL, options, (thdvar_longlong_t*) opt);
+ break;
+ case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS(GET_ULL, options, (thdvar_ulonglong_t*) opt);
+ break;
+ case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL:
+ OPTION_SET_LIMITS_DOUBLE(options, (thdvar_double_t*) opt);
+ break;
+ case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_ENUM;
+ options->typelib= ((thdvar_enum_t*) opt)->typelib;
+ options->def_value= ((thdvar_enum_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= options->typelib->count - 1;
+ break;
+ case PLUGIN_VAR_SET | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_SET;
+ options->typelib= ((thdvar_set_t*) opt)->typelib;
+ options->def_value= ((thdvar_set_t*) opt)->def_val;
+ options->min_value= options->block_size= 0;
+ options->max_value= (1ULL << options->typelib->count) - 1;
+ break;
+ case PLUGIN_VAR_BOOL | PLUGIN_VAR_THDLOCAL:
+ options->var_type= GET_BOOL;
+ options->def_value= ((thdvar_bool_t*) opt)->def_val;
+ options->typelib= &bool_typelib;
+ break;
+ case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL:
+ options->var_type= ((opt->flags & PLUGIN_VAR_MEMALLOC) ?
+ GET_STR_ALLOC : GET_STR);
+ options->def_value= (intptr) ((thdvar_str_t*) opt)->def_val;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ options->arg_type= REQUIRED_ARG;
+ if (opt->flags & PLUGIN_VAR_NOCMDARG)
+ options->arg_type= NO_ARG;
+ if (opt->flags & PLUGIN_VAR_OPCMDARG)
+ options->arg_type= OPT_ARG;
+}
+
+/**
+ 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, struct 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= 255;
+ char *comment= (char *) alloc_root(mem_root, max_comment_len + 1);
+ char *optname;
+
+ int index= 0, UNINIT_VAR(offset);
+ st_mysql_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_ENTER("construct_options");
+
+ plugin_name_ptr= (char*) alloc_root(mem_root, 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*) alloc_root(mem_root,
+ plugin_name_len +
+ plugin_dash.length + 1);
+ strxmov(plugin_name_with_prefix_ptr, plugin_dash.str, plugin_name_ptr, NullS);
+
+ if (!plugin_is_forced(tmp))
+ {
+ /* support --skip-plugin-foo syntax */
+ options[0].name= plugin_name_ptr;
+ options[1].name= plugin_name_with_prefix_ptr;
+ options[0].id= options[1].id= 0;
+ 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. One of: ON, OFF, FORCE (don't start if the plugin"
+ " fails to load), FORCE_PLUS_PERMANENT (like FORCE, but the"
+ " plugin can not be uninstalled).", 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 **)alloc_root(mem_root, sizeof(ulong));
+ *((ulong*) options[0].value)= (ulong) options[0].def_value;
+
+ options+= 2;
+ }
+
+ /*
+ 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->name)
+ {
+ sql_print_error("Missing variable name in plugin '%s'.",
+ plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ 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_char;
+ break;
+ case PLUGIN_VAR_INT:
+ ((thdvar_int_t *) opt)->resolve= mysql_sys_var_int;
+ break;
+ case PLUGIN_VAR_LONG:
+ ((thdvar_long_t *) opt)->resolve= mysql_sys_var_long;
+ break;
+ case PLUGIN_VAR_LONGLONG:
+ ((thdvar_longlong_t *) opt)->resolve= mysql_sys_var_longlong;
+ 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:
+ sql_print_error("Unknown variable type code 0x%x in plugin '%s'.",
+ opt->flags, plugin_name);
+ DBUG_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;
+ sql_print_warning("Server variable %s of plugin %s was forced "
+ "to be read-only: string variable without "
+ "update_func and PLUGIN_VAR_MEMALLOC flag",
+ 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:
+ sql_print_error("Unknown variable type code 0x%x in plugin '%s'.",
+ opt->flags, plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ if ((opt->flags & (PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_THDLOCAL))
+ == PLUGIN_VAR_NOCMDOPT)
+ continue;
+
+ if (!(opt->flags & PLUGIN_VAR_THDLOCAL))
+ {
+ optnamelen= strlen(opt->name);
+ optname= (char*) alloc_root(mem_root, 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)))
+ {
+ sql_print_error("Thread local variable '%s' not allocated "
+ "in plugin '%s'.", opt->name, plugin_name);
+ DBUG_RETURN(-1);
+ }
+
+ *(int*)(opt + 1)= offset= v->offset;
+
+ if (opt->flags & PLUGIN_VAR_NOCMDOPT)
+ {
+ char *val= global_system_variables.dynamic_variables_ptr + offset;
+ if (((opt->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR) &&
+ (opt->flags & PLUGIN_VAR_MEMALLOC))
+ {
+ char *def_val= *(char**)var_def_ptr(opt);
+ *(char**)val= def_val ? my_strdup(PSI_INSTRUMENT_ME, def_val, MYF(0)) : NULL;
+ }
+ else
+ memcpy(val, var_def_ptr(opt), var_storage_size(opt->flags));
+ 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->flags & PLUGIN_VAR_NOSYSVAR) ? NULL : 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].name= option_name_ptr= (char*) alloc_root(mem_root,
+ plugin_dash.length +
+ optnamelen + 1);
+ options[1].comment= 0; /* Hidden from the help text */
+ strxmov(option_name_ptr, plugin_dash.str, optname, NullS);
+
+ options+= 2;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+static my_option *construct_help_options(MEM_ROOT *mem_root,
+ struct st_plugin_int *p)
+{
+ st_mysql_sys_var **opt;
+ my_option *opts;
+ uint count= EXTRA_OPTIONS;
+ DBUG_ENTER("construct_help_options");
+
+ for (opt= p->plugin->system_vars; opt && *opt; opt++, count+= 2)
+ ;
+
+ if (!(opts= (my_option*) alloc_root(mem_root, sizeof(my_option) * count)))
+ DBUG_RETURN(NULL);
+
+ bzero(opts, sizeof(my_option) * count);
+
+ /**
+ some plugin variables (those that don't have PLUGIN_VAR_NOSYSVAR 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_ptr_backup(p->nbackups, p->ptr_backup);
+
+ if (construct_options(mem_root, p, opts))
+ DBUG_RETURN(NULL);
+
+ DBUG_RETURN(opts);
+}
+
+extern "C" my_bool mark_changed(const struct my_option *, const char *,
+ const char *);
+my_bool mark_changed(const struct my_option *opt, const char *,
+ const char *filename)
+{
+ if (opt->app_type)
+ {
+ sys_var *var= (sys_var*) opt->app_type;
+ if (*filename)
+ {
+ var->origin_filename= filename;
+ var->value_origin= sys_var::CONFIG;
+ }
+ else
+ var->value_origin= sys_var::COMMAND_LINE;
+ }
+ return 0;
+}
+
+/**
+ It is always false to mark global plugin variable unloaded just to be
+ safe because we have no way now to know truth about them.
+
+ TODO: make correct mechanism for global plugin variables
+*/
+static bool static_unload= FALSE;
+
+/**
+ 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] plugin 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, struct 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;
+
+ MEM_ROOT *mem_root= alloc_root_inited(&tmp->mem_root) ?
+ &tmp->mem_root : &plugin_vars_mem_root;
+ st_mysql_sys_var **opt;
+ my_option *opts= NULL;
+ int error= 1;
+ struct st_bookmark *var;
+ size_t len=0, count= EXTRA_OPTIONS;
+ st_ptr_backup *tmp_backup= 0;
+ DBUG_ENTER("test_plugin_options");
+ DBUG_ASSERT(tmp->plugin && tmp->name.str);
+
+ if (tmp->plugin->system_vars || (*argc > 1))
+ {
+ for (opt= tmp->plugin->system_vars; opt && *opt; opt++)
+ {
+ len++;
+ if (!((*opt)->flags & PLUGIN_VAR_NOCMDOPT))
+ count+= 2; /* --{plugin}-{optname} and --plugin-{plugin}-{optname} */
+ }
+
+ if (!(opts= (my_option*) alloc_root(tmp_root, sizeof(my_option) * count)))
+ {
+ sql_print_error("Out of memory for plugin '%s'.", tmp->name.str);
+ DBUG_RETURN(-1);
+ }
+ bzero(opts, sizeof(my_option) * count);
+
+ if (construct_options(tmp_root, tmp, opts))
+ {
+ sql_print_error("Bad options for plugin '%s'.", tmp->name.str);
+ DBUG_RETURN(-1);
+ }
+
+ if (tmp->plugin->system_vars)
+ {
+ tmp_backup= (st_ptr_backup *)my_alloca(len * sizeof(tmp_backup[0]));
+ DBUG_ASSERT(tmp->nbackups == 0);
+ DBUG_ASSERT(tmp->ptr_backup == 0);
+
+ for (opt= tmp->plugin->system_vars; *opt; opt++)
+ {
+ st_mysql_sys_var *o= *opt;
+ char *varname;
+ sys_var *v;
+
+ if (o->flags & PLUGIN_VAR_NOSYSVAR)
+ continue;
+
+ tmp_backup[tmp->nbackups++].save(&o->name);
+ if ((var= find_bookmark(tmp->name.str, o->name, o->flags)))
+ {
+ varname= var->key + 1;
+ var->loaded= TRUE;
+ }
+ else
+ {
+ var= NULL;
+ len= tmp->name.length + strlen(o->name) + 2;
+ varname= (char*) alloc_root(mem_root, len);
+ strxmov(varname, tmp->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, tmp, o);
+ v->test_load= (var ? &var->loaded : &static_unload);
+ DBUG_ASSERT(static_unload == FALSE);
+
+ if (!(o->flags & PLUGIN_VAR_NOCMDOPT))
+ {
+ // update app_type, used for I_S.SYSTEM_VARIABLES
+ for (my_option *mo=opts; mo->name; mo++)
+ if (mo->app_type == o)
+ mo->app_type= v;
+ }
+ }
+
+ if (tmp->nbackups)
+ {
+ size_t bytes= tmp->nbackups * sizeof(tmp->ptr_backup[0]);
+ tmp->ptr_backup= (st_ptr_backup *)alloc_root(mem_root, bytes);
+ if (!tmp->ptr_backup)
+ {
+ restore_ptr_backup(tmp->nbackups, tmp_backup);
+ my_afree(tmp_backup);
+ goto err;
+ }
+ memcpy(tmp->ptr_backup, tmp_backup, bytes);
+ }
+ my_afree(tmp_backup);
+ }
+
+ /*
+ We adjust the default value to account for the hardcoded exceptions
+ we have set for the federated and ndbcluster storage engines.
+ */
+ if (!plugin_is_forced(tmp))
+ opts[0].def_value= opts[1].def_value= plugin_load_option;
+
+ error= handle_options(argc, &argv, opts, mark_changed);
+ (*argc)++; /* add back one for the program name */
+
+ if (unlikely(error))
+ {
+ sql_print_error("Parsing options for plugin '%s' 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 (!plugin_is_forced(tmp))
+ plugin_load_option= (enum_plugin_load_option) *(ulong*) opts[0].value;
+ }
+
+ disable_plugin= (plugin_load_option == PLUGIN_OFF);
+ tmp->load_option= plugin_load_option;
+
+ error= 1;
+
+ /*
+ If the plugin is disabled it should not be initialized.
+ */
+ if (disable_plugin)
+ {
+ if (global_system_variables.log_warnings && !opt_help)
+ sql_print_information("Plugin '%s' is disabled.",
+ tmp->name.str);
+ goto err;
+ }
+
+ if (tmp->plugin->system_vars)
+ {
+ if (mysqld_server_started)
+ {
+ /*
+ PLUGIN_VAR_STR command-line options without PLUGIN_VAR_MEMALLOC, point
+ directly to values in the argv[] array. For plugins started at the
+ server startup, argv[] array is allocated with load_defaults(), and
+ freed when the server is shut down. But for plugins loaded with
+ INSTALL PLUGIN, the memory allocated with load_defaults() is freed with
+ free() at the end of mysql_install_plugin(). Which means we cannot
+ allow any pointers into that area.
+ Thus, for all plugins loaded after the server was started,
+ we copy string values to a plugin's memroot.
+ */
+ for (opt= tmp->plugin->system_vars; *opt; opt++)
+ {
+ if ((((*opt)->flags & (PLUGIN_VAR_TYPEMASK | PLUGIN_VAR_NOCMDOPT |
+ PLUGIN_VAR_MEMALLOC)) == PLUGIN_VAR_STR))
+ {
+ sysvar_str_t* str= (sysvar_str_t *)*opt;
+ if (*str->value)
+ *str->value= strdup_root(mem_root, *str->value);
+ }
+ }
+ /* same issue with config file names */
+ for (my_option *mo=opts; mo->name; mo++)
+ {
+ sys_var *var= (sys_var*) mo->app_type;
+ if (var && var->value_origin == sys_var::CONFIG)
+ var->origin_filename= strdup_root(mem_root, var->origin_filename);
+ }
+ }
+
+ if (chain.first)
+ {
+ chain.last->next = NULL;
+ if (mysql_add_sys_var_chain(chain.first))
+ {
+ sql_print_error("Plugin '%s' has conflicting system variables",
+ tmp->name.str);
+ goto err;
+ }
+ tmp->system_vars= chain.first;
+ }
+ }
+
+ DBUG_RETURN(0);
+
+err:
+ if (opts)
+ my_cleanup_options(opts);
+ DBUG_RETURN(error);
+}
+
+
+/****************************************************************************
+ Help Verbose text with Plugin System Variables
+****************************************************************************/
+
+
+void add_plugin_options(DYNAMIC_ARRAY *options, MEM_ROOT *mem_root)
+{
+ struct st_plugin_int *p;
+ my_option *opt;
+
+ if (!initialized)
+ return;
+
+ for (uint idx= 0; idx < plugin_array.elements; idx++)
+ {
+ p= *dynamic_element(&plugin_array, idx, struct st_plugin_int **);
+
+ 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)
+ insert_dynamic(options, (uchar*) opt);
+ }
+}
+
+
+/**
+ Returns a sys_var corresponding to a particular MYSQL_SYSVAR(...)
+*/
+sys_var *find_plugin_sysvar(st_plugin_int *plugin, st_mysql_sys_var *plugin_var)
+{
+ for (sys_var *var= plugin->system_vars; var; var= var->next)
+ {
+ sys_var_pluginvar *pvar=var->cast_pluginvar();
+ if (pvar->plugin_var == plugin_var)
+ return var;
+ }
+ return 0;
+}
+
+/*
+ On dlclose() we need to restore values of all symbols that we've modified in
+ the DSO. The reason is - the DSO might not actually be unloaded, so on the
+ next dlopen() these symbols will have old values, they won't be
+ reinitialized.
+
+ Perhaps, there can be many reason, why a DSO won't be unloaded. Strictly
+ speaking, it's implementation defined whether to unload an unused DSO or to
+ keep it in memory.
+
+ In particular, this happens for some plugins: In 2009 a new ELF stub was
+ introduced, see Ulrich Drepper's email "Unique symbols for C++"
+ http://www.redhat.com/archives/posix-c++-wg/2009-August/msg00002.html
+
+ DSO that has objects with this stub (STB_GNU_UNIQUE) cannot be unloaded
+ (this is mentioned in the email, see the url above).
+
+ These "unique" objects are, for example, static variables in templates,
+ in inline functions, in classes. So any DSO that uses them can
+ only be loaded once. And because Boost has them, any DSO that uses Boost
+ almost certainly cannot be unloaded.
+
+ To know whether a particular DSO has these objects, one can use
+
+ readelf -s /path/to/plugin.so|grep UNIQUE
+
+ There's nothing we can do about it, but to reset the DSO to its initial
+ state before dlclose().
+*/
+static void restore_ptr_backup(uint n, st_ptr_backup *backup)
+{
+ while (n--)
+ (backup++)->restore();
+}
+
+/****************************************************************************
+ thd specifics service, see include/mysql/service_thd_specifics.h
+****************************************************************************/
+static const int INVALID_THD_KEY= -1;
+static uint thd_key_no = 42;
+
+int thd_key_create(MYSQL_THD_KEY_T *key)
+{
+ int flags= PLUGIN_VAR_THDLOCAL | PLUGIN_VAR_STR |
+ PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT;
+ char namebuf[256];
+ snprintf(namebuf, sizeof(namebuf), "%u", thd_key_no++);
+ mysql_prlock_wrlock(&LOCK_system_variables_hash);
+ // non-letters in the name as an extra safety
+ st_bookmark *bookmark= register_var("\a\v\a\t\a\r", namebuf, flags);
+ mysql_prlock_unlock(&LOCK_system_variables_hash);
+ if (bookmark)
+ {
+ *key= bookmark->offset;
+ return 0;
+ }
+ return ENOMEM;
+}
+
+void thd_key_delete(MYSQL_THD_KEY_T *key)
+{
+ *key= INVALID_THD_KEY;
+}
+
+void* thd_getspecific(MYSQL_THD thd, MYSQL_THD_KEY_T key)
+{
+ DBUG_ASSERT(key != INVALID_THD_KEY);
+ if (key == INVALID_THD_KEY || (!thd && !(thd= current_thd)))
+ return 0;
+
+ return *(void**)(intern_sys_var_ptr(thd, key, true));
+}
+
+int thd_setspecific(MYSQL_THD thd, MYSQL_THD_KEY_T key, void *value)
+{
+ DBUG_ASSERT(key != INVALID_THD_KEY);
+ if (key == INVALID_THD_KEY || (!thd && !(thd= current_thd)))
+ return EINVAL;
+
+ memcpy(intern_sys_var_ptr(thd, key, true), &value, sizeof(void*));
+ return 0;
+}
+
+void plugin_mutex_init()
+{
+ init_plugin_psi_keys();
+ mysql_mutex_init(key_LOCK_plugin, &LOCK_plugin, MY_MUTEX_INIT_FAST);
+}
+
+#ifdef WITH_WSREP
+
+/*
+ Placeholder for global_system_variables.table_plugin required during
+ initialization of startup wsrep threads.
+*/
+static st_plugin_int wsrep_dummy_plugin;
+static st_plugin_int *wsrep_dummy_plugin_ptr;
+
+/*
+ Initialize wsrep_dummy_plugin and assign it to
+ global_system_variables.table_plugin.
+*/
+void wsrep_plugins_pre_init()
+{
+ wsrep_dummy_plugin_ptr= &wsrep_dummy_plugin;
+ wsrep_dummy_plugin.state= PLUGIN_IS_DISABLED;
+ global_system_variables.table_plugin=
+ plugin_int_to_ref(wsrep_dummy_plugin_ptr);
+}
+
+/*
+ This function is intended to be called after the plugins and related
+ global system variables are initialized. It re-initializes some data
+ members of wsrep startup threads with correct values, as these value
+ were not available at the time these threads were created.
+*/
+
+my_bool post_init_callback(THD *thd, void *)
+{
+ DBUG_ASSERT(!current_thd);
+ if (thd->wsrep_applier)
+ {
+ // Save options_bits as it will get overwritten in
+ // plugin_thdvar_init() (verified)
+ ulonglong option_bits_saved= thd->variables.option_bits;
+
+ set_current_thd(thd);
+ plugin_thdvar_init(thd);
+
+ // Restore option_bits
+ thd->variables.option_bits= option_bits_saved;
+ }
+ set_current_thd(0);
+ return 0;
+}
+
+
+void wsrep_plugins_post_init()
+{
+ mysql_mutex_lock(&LOCK_global_system_variables);
+ server_threads.iterate(post_init_callback);
+ mysql_mutex_unlock(&LOCK_global_system_variables);
+}
+#endif /* WITH_WSREP */