diff options
Diffstat (limited to 'sql/sql_plugin.cc')
-rw-r--r-- | sql/sql_plugin.cc | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc new file mode 100644 index 00000000000..efe8f256af5 --- /dev/null +++ b/sql/sql_plugin.cc @@ -0,0 +1,622 @@ +/* Copyright (C) 2005 MySQL AB + + 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; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include <my_pthread.h> +#define REPORT_TO_LOG 1 +#define REPORT_TO_USER 2 + +char *opt_plugin_dir_ptr; +char opt_plugin_dir[FN_REFLEN]; + +static const char *plugin_interface_version_sym= + "_mysql_plugin_interface_version_"; +static const char *plugin_declarations_sym= "_mysql_plugin_declarations_"; +static int min_plugin_interface_version= 0x0000; + +static DYNAMIC_ARRAY plugin_dl_array; +static DYNAMIC_ARRAY plugin_array; +static HASH plugin_hash[MYSQL_MAX_PLUGIN_TYPE_NUM]; +static rw_lock_t THR_LOCK_plugin; +static bool initialized= 0; + + +static struct st_plugin_dl *plugin_dl_find(LEX_STRING *dl) +{ + uint i; + DBUG_ENTER("plugin_dl_find"); + for (i= 0; i < plugin_dl_array.elements; i++) + { + struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i, + struct st_plugin_dl *); + if (tmp->ref_count && + ! my_strnncoll(files_charset_info, + (const uchar *)dl->str, dl->length, + (const uchar *)tmp->dl.str, tmp->dl.length)) + DBUG_RETURN(tmp); + } + DBUG_RETURN(0); +} + + +static st_plugin_dl *plugin_dl_add(LEX_STRING *dl, int report) +{ +#ifdef HAVE_DLOPEN + char dlpath[FN_REFLEN]; + uint plugin_dir_len, dummy_errors; + struct st_plugin_dl *tmp, plugin_dl; + void *sym; + DBUG_ENTER("plugin_dl_add"); + 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 (my_strchr(files_charset_info, dl->str, dl->str + dl->length, FN_LIBCHAR) || + dl->length > NAME_LEN || + plugin_dir_len + dl->length + 1 >= FN_REFLEN) + { + if (report & REPORT_TO_USER) + my_error(ER_UDF_NO_PATHS, MYF(0)); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_UDF_NO_PATHS)); + 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); + } + /* Compile dll path */ + strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", dl->str, NullS); + plugin_dl.ref_count= 1; + /* Open new dll handle */ + if (!(plugin_dl.handle= dlopen(dlpath, RTLD_NOW))) + { + if (report & REPORT_TO_USER) + my_error(ER_CANT_OPEN_LIBRARY, MYF(0), dlpath, errno, dlerror()); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_CANT_OPEN_LIBRARY), dlpath, errno, dlerror()); + DBUG_RETURN(0); + } + /* Determine interface version */ + if (!(sym= dlsym(plugin_dl.handle, plugin_interface_version_sym))) + { + dlclose(plugin_dl.handle); + if (report & REPORT_TO_USER) + my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), plugin_interface_version_sym); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), plugin_interface_version_sym); + DBUG_RETURN(0); + } + plugin_dl.version= *(int *)sym; + /* Versioning */ + if (plugin_dl.version < min_plugin_interface_version || + (plugin_dl.version >> 8) > (MYSQL_PLUGIN_INTERFACE_VERSION >> 8)) + { + dlclose(plugin_dl.handle); + if (report & REPORT_TO_USER) + my_error(ER_CANT_OPEN_LIBRARY, MYF(0), dlpath, 0, + "plugin interface version mismatch"); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_CANT_OPEN_LIBRARY), dlpath, 0, + "plugin interface version mismatch"); + DBUG_RETURN(0); + } + /* Find plugin declarations */ + if (!(sym= dlsym(plugin_dl.handle, plugin_declarations_sym))) + { + dlclose(plugin_dl.handle); + if (report & REPORT_TO_USER) + my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), plugin_declarations_sym); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), plugin_declarations_sym); + DBUG_RETURN(0); + } + plugin_dl.plugins= (struct st_mysql_plugin *)sym; + /* Duplicate and convert dll name */ + plugin_dl.dl.length= dl->length * files_charset_info->mbmaxlen + 1; + if (! (plugin_dl.dl.str= my_malloc(plugin_dl.dl.length, MYF(0)))) + { + dlclose(plugin_dl.handle); + if (report & REPORT_TO_USER) + my_error(ER_OUTOFMEMORY, MYF(0), plugin_dl.dl.length); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_OUTOFMEMORY), plugin_dl.dl.length); + DBUG_RETURN(0); + } + plugin_dl.dl.length= copy_and_convert(plugin_dl.dl.str, plugin_dl.dl.length, + files_charset_info, dl->str, dl->length, system_charset_info, + &dummy_errors); + plugin_dl.dl.str[plugin_dl.dl.length]= 0; + /* Add this dll to array */ + if (insert_dynamic(&plugin_dl_array, (gptr)&plugin_dl)) + { + dlclose(plugin_dl.handle); + my_free(plugin_dl.dl.str, MYF(0)); + if (report & REPORT_TO_USER) + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_dl)); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_dl)); + DBUG_RETURN(0); + } + DBUG_RETURN(dynamic_element(&plugin_dl_array, plugin_dl_array.elements - 1, + struct st_plugin_dl *)); +#else + DBUG_ENTER("plugin_dl_add"); + if (report & REPORT_TO_USER) + my_error(ER_FEATURE_DISABLED, MYF(0), "plugin", "HAVE_DLOPEN"); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_FEATURE_DISABLED), "plugin", "HAVE_DLOPEN"); + DBUG_RETURN(0); +#endif +} + + +static void plugin_dl_del(LEX_STRING *dl) +{ +#ifdef HAVE_DLOPEN + uint i; + DBUG_ENTER("plugin_dl_del"); + for (i= 0; i < plugin_dl_array.elements; i++) + { + struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i, + struct st_plugin_dl *); + if (tmp->ref_count && + ! my_strnncoll(files_charset_info, + (const uchar *)dl->str, dl->length, + (const uchar *)tmp->dl.str, tmp->dl.length)) + { + /* Do not remove this element, unless no other plugin uses this dll. */ + if (! --tmp->ref_count) + { + dlclose(tmp->handle); + my_free(tmp->dl.str, MYF(0)); + bzero(tmp, sizeof(struct st_plugin_dl)); + } + break; + } + } + DBUG_VOID_RETURN; +#endif +} + + +static struct st_plugin_int *plugin_find_internal(LEX_STRING *name, int type) +{ + uint i; + DBUG_ENTER("plugin_find_internal"); + if (! initialized) + DBUG_RETURN(0); + if (type == MYSQL_ANY_PLUGIN) + { + for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) + { + struct st_plugin_int *plugin= (st_plugin_int *) + hash_search(&plugin_hash[i], name->str, name->length); + if (plugin) + DBUG_RETURN(plugin); + } + } + else + DBUG_RETURN((st_plugin_int *) + hash_search(&plugin_hash[type], name->str, name->length)); + DBUG_RETURN(0); +} + + +my_bool plugin_is_ready(LEX_STRING *name, int type) +{ + my_bool rc= FALSE; + struct st_plugin_int *plugin; + DBUG_ENTER("plugin_is_ready"); + rw_rdlock(&THR_LOCK_plugin); + if ((plugin= plugin_find_internal(name, type)) && + plugin->state == PLUGIN_IS_READY) + rc= TRUE; + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(rc); +} + + +struct st_plugin_int *plugin_lock(LEX_STRING *name, int type) +{ + struct st_plugin_int *rc; + DBUG_ENTER("plugin_find"); + rw_wrlock(&THR_LOCK_plugin); + if ((rc= plugin_find_internal(name, type))) + { + if (rc->state == PLUGIN_IS_READY) + rc->ref_count++; + else + rc= 0; + } + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(rc); +} + + +static my_bool plugin_add(LEX_STRING *name, LEX_STRING *dl, int report) +{ + struct st_plugin_int tmp; + struct st_mysql_plugin *plugin; + DBUG_ENTER("plugin_add"); + if (plugin_find_internal(name, MYSQL_ANY_PLUGIN)) + { + if (report & REPORT_TO_USER) + my_error(ER_UDF_EXISTS, MYF(0), name->str); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_UDF_EXISTS), name->str); + DBUG_RETURN(TRUE); + } + if (! (tmp.plugin_dl= plugin_dl_add(dl, report))) + DBUG_RETURN(TRUE); + /* Find plugin by name */ + for (plugin= tmp.plugin_dl->plugins; plugin->info; plugin++) + { + uint name_len= strlen(plugin->name); + if (plugin->type >= 0 && plugin->type < MYSQL_MAX_PLUGIN_TYPE_NUM && + ! my_strnncoll(system_charset_info, + (const uchar *)name->str, name->length, + (const uchar *)plugin->name, + name_len)) + { + tmp.plugin= plugin; + tmp.name.str= (char *)plugin->name; + tmp.name.length= name_len; + tmp.ref_count= 0; + tmp.state= PLUGIN_IS_UNINITIALIZED; + if (insert_dynamic(&plugin_array, (gptr)&tmp)) + { + if (report & REPORT_TO_USER) + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_int)); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_int)); + goto err; + } + if (my_hash_insert(&plugin_hash[plugin->type], + (byte*)dynamic_element(&plugin_array, + plugin_array.elements - 1, + struct st_plugin_int *))) + { + struct st_plugin_int *tmp_plugin= dynamic_element(&plugin_array, + plugin_array.elements - 1, struct st_plugin_int *); + tmp_plugin->state= PLUGIN_IS_FREED; + if (report & REPORT_TO_USER) + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(struct st_plugin_int)); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_OUTOFMEMORY), sizeof(struct st_plugin_int)); + goto err; + } + DBUG_RETURN(FALSE); + } + } + if (report & REPORT_TO_USER) + my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), name->str); + if (report & REPORT_TO_LOG) + sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), name->str); +err: + plugin_dl_del(dl); + DBUG_RETURN(TRUE); +} + + +static void plugin_del(LEX_STRING *name) +{ + uint i; + struct st_plugin_int *plugin; + DBUG_ENTER("plugin_del"); + if ((plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN))) + { + hash_delete(&plugin_hash[plugin->plugin->type], (byte*)plugin); + plugin_dl_del(&plugin->plugin_dl->dl); + plugin->state= PLUGIN_IS_FREED; + } + DBUG_VOID_RETURN; +} + + +void plugin_unlock(struct st_plugin_int *plugin) +{ + DBUG_ENTER("plugin_release"); + rw_wrlock(&THR_LOCK_plugin); + DBUG_ASSERT(plugin && plugin->ref_count); + plugin->ref_count--; + if (plugin->state == PLUGIN_IS_DELETED && ! plugin->ref_count) + { + if (plugin->plugin->deinit) + plugin->plugin->deinit(); + plugin_del(&plugin->name); + } + rw_unlock(&THR_LOCK_plugin); + DBUG_VOID_RETURN; +} + + +static void plugin_call_initializer(void) +{ + uint i; + DBUG_ENTER("plugin_call_initializer"); + for (i= 0; i < plugin_array.elements; i++) + { + struct st_plugin_int *tmp= dynamic_element(&plugin_array, i, + struct st_plugin_int *); + if (tmp->state == PLUGIN_IS_UNINITIALIZED && tmp->plugin->init) + { + DBUG_PRINT("info", ("Initializing plugin: '%s'", tmp->name.str)); + if (tmp->plugin->init()) + { + sql_print_error("Plugin '%s' init function returned error.", + tmp->name.str); + DBUG_PRINT("warning", ("Plugin '%s' init function returned error.", + tmp->name.str)) + plugin_del(&tmp->name); + } + } + if (tmp->state == PLUGIN_IS_UNINITIALIZED) + tmp->state= PLUGIN_IS_READY; + } + DBUG_VOID_RETURN; +} + + +static void plugin_call_deinitializer(void) +{ + uint i; + DBUG_ENTER("plugin_call_deinitializer"); + for (i= 0; i < plugin_array.elements; i++) + { + struct st_plugin_int *tmp= dynamic_element(&plugin_array, i, + struct st_plugin_int *); + if (tmp->state == PLUGIN_IS_READY) + { + if (tmp->plugin->deinit) + { + DBUG_PRINT("info", ("Deinitializing plugin: '%s'", tmp->name.str)); + if (tmp->plugin->deinit()) + { + DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", + tmp->name.str)) + } + } + tmp->state= PLUGIN_IS_UNINITIALIZED; + } + } + DBUG_VOID_RETURN; +} + + +static byte *get_hash_key(const byte *buff, uint *length, + my_bool not_used __attribute__((unused))) +{ + struct st_plugin_int *plugin= (st_plugin_int *)buff; + *length= (uint)plugin->name.length; + return((byte *)plugin->name.str); +} + + +void plugin_init(void) +{ + TABLE_LIST tables; + TABLE *table; + READ_RECORD read_record_info; + int error, i; + MEM_ROOT mem; + DBUG_ENTER("plugin_init"); + if (initialized) + DBUG_VOID_RETURN; + my_rwlock_init(&THR_LOCK_plugin, NULL); + THD *new_thd = new THD; + if (!new_thd || + my_init_dynamic_array(&plugin_dl_array,sizeof(struct st_plugin_dl),16,16) || + my_init_dynamic_array(&plugin_array,sizeof(struct st_plugin_int),16,16)) + { + sql_print_error("Can't allocate memory for plugin structures"); + delete new_thd; + delete_dynamic(&plugin_dl_array); + delete_dynamic(&plugin_array); + DBUG_VOID_RETURN; + } + for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) + { + if (hash_init(&plugin_hash[i], system_charset_info, 16, 0, 0, + get_hash_key, NULL, 0)) + { + sql_print_error("Can't allocate memory for plugin structures"); + delete new_thd; + delete_dynamic(&plugin_dl_array); + delete_dynamic(&plugin_array); + DBUG_VOID_RETURN; + } + } + init_sql_alloc(&mem, 1024, 0); + initialized= 1; + new_thd->store_globals(); + new_thd->db= my_strdup("mysql", MYF(0)); + new_thd->db_length= 5; + bzero((gptr)&tables, sizeof(tables)); + tables.alias= tables.table_name= (char*)"plugin"; + tables.lock_type= TL_READ; + tables.db= new_thd->db; + if (simple_open_n_lock_tables(new_thd, &tables)) + { + DBUG_PRINT("error",("Can't open plugin table")); + sql_print_error("Can't open the mysql.plugin table. Please run the mysql_install_db script to create it."); + delete_dynamic(&plugin_dl_array); + delete_dynamic(&plugin_array); + for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) + hash_free(&plugin_hash[i]); + goto end; + } + table= tables.table; + init_read_record(&read_record_info, new_thd, table, NULL, 1, 0); + while (!(error= read_record_info.read_record(&read_record_info))) + { + DBUG_PRINT("info", ("init plugin record")); + LEX_STRING name, dl; + name.str= get_field(&mem, table->field[0]); + name.length= strlen(name.str); + dl.str= get_field(&mem, table->field[1]); + dl.length= strlen(dl.str); + if (plugin_add(&name, &dl, REPORT_TO_LOG)) + DBUG_PRINT("warning", ("Couldn't load plugin named '%s' with soname '%s'.", + name.str, dl.str)); + } + plugin_call_initializer(); + if (error > 0) + sql_print_error(ER(ER_GET_ERRNO), my_errno); + end_read_record(&read_record_info); + new_thd->version--; // Force close to free memory +end: + free_root(&mem, MYF(0)); + close_thread_tables(new_thd); + delete new_thd; + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, 0); + DBUG_VOID_RETURN; +} + + +void plugin_free(void) +{ + uint i; + DBUG_ENTER("plugin_free"); + plugin_call_deinitializer(); + for (i= 0; i < MYSQL_MAX_PLUGIN_TYPE_NUM; i++) + hash_free(&plugin_hash[i]); + delete_dynamic(&plugin_array); + for (i= 0; i < plugin_dl_array.elements; i++) + { + struct st_plugin_dl *tmp= dynamic_element(&plugin_dl_array, i, + struct st_plugin_dl *); +#ifdef HAVE_DLOPEN + if (tmp->handle) + { + dlclose(tmp->handle); + my_free(tmp->dl.str, MYF(0)); + } +#endif + } + delete_dynamic(&plugin_dl_array); + if (initialized) + { + initialized= 0; + rwlock_destroy(&THR_LOCK_plugin); + } + DBUG_VOID_RETURN; +} + + +my_bool mysql_install_plugin(THD *thd, LEX_STRING *name, LEX_STRING *dl) +{ + TABLE_LIST tables; + TABLE *table; + int error; + struct st_plugin_int *tmp; + DBUG_ENTER("mysql_install_plugin"); + bzero(&tables, sizeof(tables)); + tables.db= (char *)"mysql"; + tables.table_name= tables.alias= (char *)"plugin"; + if (check_table_access(thd, INSERT_ACL, &tables, 0)) + DBUG_RETURN(TRUE); + rw_wrlock(&THR_LOCK_plugin); + if (plugin_add(name, dl, REPORT_TO_USER)) + goto err; + tmp= plugin_find_internal(name, MYSQL_ANY_PLUGIN); + if (tmp->plugin->init) + { + if (tmp->plugin->init()) + { + my_error(ER_CANT_INITIALIZE_UDF, MYF(0), name->str, + "Plugin initialization function failed."); + goto err; + } + tmp->state= PLUGIN_IS_READY; + } + if (! (table = open_ltable(thd, &tables, TL_WRITE))) + goto deinit; + restore_record(table, s->default_values); + table->field[0]->store(name->str, name->length, system_charset_info); + table->field[1]->store(dl->str, dl->length, files_charset_info); + error= table->file->write_row(table->record[0]); + if (error) + { + table->file->print_error(error, MYF(0)); + goto deinit; + } + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(FALSE); +deinit: + if (tmp->plugin->deinit) + tmp->plugin->deinit(); +err: + plugin_del(name); + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(TRUE); +} + + +my_bool mysql_uninstall_plugin(THD *thd, LEX_STRING *name) +{ + TABLE *table; + TABLE_LIST tables; + struct st_plugin_int *plugin; + DBUG_ENTER("mysql_uninstall_plugin"); + rw_wrlock(&THR_LOCK_plugin); + if (! (plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN))) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PLUGIN", name->str); + goto err; + } + if (plugin->ref_count) + { + plugin->state= PLUGIN_IS_DELETED; + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0, + "Plugin is not deleted, waiting on tables."); + } + else + { + if (plugin->plugin->deinit) + plugin->plugin->deinit(); + plugin_del(name); + } + bzero(&tables, sizeof(tables)); + tables.db= (char *)"mysql"; + tables.table_name= tables.alias= (char *)"plugin"; + if (! (table= open_ltable(thd, &tables, TL_WRITE))) + goto err; + table->field[0]->store(name->str, name->length, system_charset_info); + table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); + if (! table->file->index_read_idx(table->record[0], 0, + (byte *)table->field[0]->ptr, + table->key_info[0].key_length, + HA_READ_KEY_EXACT)) + { + int error; + if ((error= table->file->delete_row(table->record[0]))) + { + table->file->print_error(error, MYF(0)); + goto err; + } + } + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(FALSE); +err: + rw_unlock(&THR_LOCK_plugin); + DBUG_RETURN(TRUE); +} |