mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
e0c30390a7
port the client-side implementation from C/C to the internal client. add the test.
509 lines
14 KiB
C
509 lines
14 KiB
C
/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
|
|
Copyright (c) 2010, 2011, Oracle and/or its affiliates.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License 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 */
|
|
|
|
/**
|
|
@file
|
|
|
|
Support code for the client side (libmysql) plugins
|
|
|
|
Client plugins are somewhat different from server plugins, they are simpler.
|
|
|
|
They do not need to be installed or in any way explicitly loaded on the
|
|
client, they are loaded automatically on demand.
|
|
One client plugin per shared object, soname *must* match the plugin name.
|
|
|
|
There is no reference counting and no unloading either.
|
|
*/
|
|
|
|
#include <my_global.h>
|
|
#include "mysql.h"
|
|
#include <my_sys.h>
|
|
#include <m_string.h>
|
|
#include <my_pthread.h>
|
|
|
|
#include <sql_common.h>
|
|
#include "errmsg.h"
|
|
#include <mysql/client_plugin.h>
|
|
|
|
PSI_memory_key key_memory_root;
|
|
PSI_memory_key key_memory_load_env_plugins;
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
PSI_mutex_key key_mutex_LOCK_load_client_plugin;
|
|
|
|
static PSI_mutex_info all_client_plugin_mutexes[]=
|
|
{
|
|
{&key_mutex_LOCK_load_client_plugin, "LOCK_load_client_plugin", PSI_FLAG_GLOBAL}
|
|
};
|
|
|
|
static PSI_memory_info all_client_plugin_memory[]=
|
|
{
|
|
{&key_memory_root, "root", PSI_FLAG_GLOBAL},
|
|
{&key_memory_load_env_plugins, "load_env_plugins", PSI_FLAG_GLOBAL}
|
|
};
|
|
|
|
static void init_client_plugin_psi_keys()
|
|
{
|
|
const char* category= "sql";
|
|
int count;
|
|
|
|
count= array_elements(all_client_plugin_mutexes);
|
|
mysql_mutex_register(category, all_client_plugin_mutexes, count);
|
|
|
|
count= array_elements(all_client_plugin_memory);
|
|
mysql_memory_register(category, all_client_plugin_memory, count);
|
|
}
|
|
#endif /* HAVE_PSI_INTERFACE */
|
|
|
|
struct st_client_plugin_int {
|
|
struct st_client_plugin_int *next;
|
|
void *dlhandle;
|
|
struct st_mysql_client_plugin *plugin;
|
|
};
|
|
|
|
static my_bool initialized= 0;
|
|
static MEM_ROOT mem_root;
|
|
|
|
#define plugin_declarations_sym "_mysql_client_plugin_declaration_"
|
|
|
|
static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]=
|
|
{
|
|
0, /* these two are taken by Connector/C */
|
|
0, /* these two are taken by Connector/C */
|
|
MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION
|
|
};
|
|
|
|
/*
|
|
Loaded plugins are stored in a linked list.
|
|
The list is append-only, the elements are added to the head (like in a stack).
|
|
The elements are added under a mutex, but the list can be read and traversed
|
|
without any mutex because once an element is added to the list, it stays
|
|
there. The main purpose of a mutex is to prevent two threads from
|
|
loading the same plugin twice in parallel.
|
|
*/
|
|
struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS];
|
|
static mysql_mutex_t LOCK_load_client_plugin;
|
|
|
|
static int is_not_initialized(MYSQL *mysql, const char *name)
|
|
{
|
|
DBUG_ENTER("is_not_initialized");
|
|
|
|
if (initialized)
|
|
DBUG_RETURN(0);
|
|
|
|
set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
|
|
unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
|
|
name, "not initialized");
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
/**
|
|
finds a plugin in the list
|
|
|
|
@param name plugin name to search for
|
|
@param type plugin type
|
|
|
|
@note this does NOT necessarily need a mutex, take care!
|
|
|
|
@retval a pointer to a found plugin or 0
|
|
*/
|
|
static struct st_mysql_client_plugin *
|
|
find_plugin(const char *name, int type)
|
|
{
|
|
struct st_client_plugin_int *p;
|
|
DBUG_ENTER("find_plugin");
|
|
|
|
DBUG_ASSERT(initialized);
|
|
DBUG_ASSERT(type >= 0 && type < MYSQL_CLIENT_MAX_PLUGINS);
|
|
if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
|
|
DBUG_RETURN(0);
|
|
|
|
for (p= plugin_list[type]; p; p= p->next)
|
|
{
|
|
if (strcmp(p->plugin->name, name) == 0)
|
|
DBUG_RETURN(p->plugin);
|
|
}
|
|
DBUG_RETURN(NULL);
|
|
}
|
|
|
|
/**
|
|
verifies the plugin and adds it to the list
|
|
|
|
@param mysql MYSQL structure (for error reporting)
|
|
@param plugin plugin to install
|
|
@param dlhandle a handle to the shared object (returned by dlopen)
|
|
or 0 if the plugin was not dynamically loaded
|
|
@param argc number of arguments in the 'va_list args'
|
|
@param args arguments passed to the plugin initialization function
|
|
|
|
@retval a pointer to an installed plugin or 0
|
|
*/
|
|
static struct st_mysql_client_plugin *
|
|
add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle,
|
|
int argc, va_list args)
|
|
{
|
|
const char *errmsg;
|
|
struct st_client_plugin_int plugin_int, *p;
|
|
char errbuf[1024];
|
|
DBUG_ENTER("add_plugin");
|
|
|
|
DBUG_ASSERT(initialized);
|
|
|
|
plugin_int.plugin= plugin;
|
|
plugin_int.dlhandle= dlhandle;
|
|
|
|
if (plugin->type >= MYSQL_CLIENT_MAX_PLUGINS)
|
|
{
|
|
errmsg= "Unknown client plugin type";
|
|
goto err1;
|
|
}
|
|
|
|
if (plugin->interface_version >> 8 != plugin_version[plugin->type] >> 8)
|
|
{
|
|
errmsg= "Incompatible client plugin interface";
|
|
goto err1;
|
|
}
|
|
|
|
/* Call the plugin initialization function, if any */
|
|
if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args))
|
|
{
|
|
errmsg= errbuf;
|
|
goto err1;
|
|
}
|
|
|
|
p= (struct st_client_plugin_int *)
|
|
memdup_root(&mem_root, &plugin_int, sizeof(plugin_int));
|
|
|
|
if (!p)
|
|
{
|
|
errmsg= "Out of memory";
|
|
goto err2;
|
|
}
|
|
|
|
mysql_mutex_assert_owner(&LOCK_load_client_plugin);
|
|
|
|
p->next= plugin_list[plugin->type];
|
|
plugin_list[plugin->type]= p;
|
|
net_clear_error(&mysql->net);
|
|
|
|
DBUG_RETURN(plugin);
|
|
|
|
err2:
|
|
if (plugin->deinit)
|
|
plugin->deinit();
|
|
err1:
|
|
set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
|
|
ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name,
|
|
errmsg);
|
|
if (dlhandle)
|
|
(void)dlclose(dlhandle);
|
|
DBUG_RETURN(NULL);
|
|
}
|
|
|
|
/**
|
|
Loads plugins which are specified in the environment variable
|
|
LIBMYSQL_PLUGINS.
|
|
|
|
Multiple plugins must be separated by semicolon. This function doesn't
|
|
return or log an error.
|
|
|
|
The function is be called by mysql_client_plugin_init
|
|
|
|
@todo
|
|
Support extended syntax, passing parameters to plugins, for example
|
|
LIBMYSQL_PLUGINS="plugin1(param1,param2);plugin2;..."
|
|
or
|
|
LIBMYSQL_PLUGINS="plugin1=int:param1,str:param2;plugin2;..."
|
|
*/
|
|
static void load_env_plugins(MYSQL *mysql)
|
|
{
|
|
char *plugs, *free_env, *s= getenv("LIBMYSQL_PLUGINS");
|
|
DBUG_ENTER("load_env_plugins");
|
|
|
|
/* no plugins to load */
|
|
if (!s)
|
|
DBUG_VOID_RETURN;
|
|
|
|
free_env= plugs= my_strdup(key_memory_load_env_plugins, s, MYF(MY_WME));
|
|
|
|
do {
|
|
if ((s= strchr(plugs, ';')))
|
|
*s= '\0';
|
|
mysql_load_plugin(mysql, plugs, -1, 0);
|
|
plugs= s + 1;
|
|
} while (s);
|
|
|
|
my_free(free_env);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/********** extern functions to be used by libmysql *********************/
|
|
|
|
/**
|
|
Initializes the client plugin layer.
|
|
|
|
This function must be called before any other client plugin function.
|
|
|
|
@retval 0 successful
|
|
@retval != 0 error occurred
|
|
*/
|
|
int mysql_client_plugin_init()
|
|
{
|
|
MYSQL mysql;
|
|
struct st_mysql_client_plugin **builtin;
|
|
va_list unused;
|
|
DBUG_ENTER("mysql_client_plugin_init");
|
|
|
|
if (initialized)
|
|
DBUG_RETURN(0);
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
init_client_plugin_psi_keys();
|
|
#endif /* HAVE_PSI_INTERFACE */
|
|
|
|
bzero(&mysql, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */
|
|
bzero(&unused, sizeof unused);
|
|
|
|
mysql_mutex_init(key_mutex_LOCK_load_client_plugin,
|
|
&LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW);
|
|
init_alloc_root(key_memory_root, &mem_root, 128, 128, MYF(0));
|
|
|
|
bzero(&plugin_list, sizeof(plugin_list));
|
|
|
|
initialized= 1;
|
|
|
|
mysql_mutex_lock(&LOCK_load_client_plugin);
|
|
|
|
for (builtin= mysql_client_builtins; *builtin; builtin++)
|
|
add_plugin(&mysql, *builtin, 0, 0, unused);
|
|
|
|
mysql_mutex_unlock(&LOCK_load_client_plugin);
|
|
|
|
load_env_plugins(&mysql);
|
|
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
/**
|
|
Deinitializes the client plugin layer.
|
|
|
|
Unloades all client plugins and frees any associated resources.
|
|
*/
|
|
void mysql_client_plugin_deinit()
|
|
{
|
|
int i;
|
|
struct st_client_plugin_int *p;
|
|
DBUG_ENTER("mysql_client_plugin_deinit");
|
|
|
|
if (!initialized)
|
|
DBUG_VOID_RETURN;
|
|
|
|
for (i=0; i < MYSQL_CLIENT_MAX_PLUGINS; i++)
|
|
for (p= plugin_list[i]; p; p= p->next)
|
|
{
|
|
if (p->plugin->deinit)
|
|
p->plugin->deinit();
|
|
if (p->dlhandle)
|
|
(void)dlclose(p->dlhandle);
|
|
}
|
|
|
|
bzero(&plugin_list, sizeof(plugin_list));
|
|
initialized= 0;
|
|
free_root(&mem_root, MYF(0));
|
|
mysql_mutex_destroy(&LOCK_load_client_plugin);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/************* public facing functions, for client consumption *********/
|
|
|
|
/* see <mysql/client_plugin.h> for a full description */
|
|
struct st_mysql_client_plugin *
|
|
mysql_client_register_plugin(MYSQL *mysql,
|
|
struct st_mysql_client_plugin *plugin)
|
|
{
|
|
DBUG_ENTER("mysql_client_register_plugin");
|
|
|
|
if (is_not_initialized(mysql, plugin->name))
|
|
DBUG_RETURN(NULL);
|
|
|
|
mysql_mutex_lock(&LOCK_load_client_plugin);
|
|
|
|
/* make sure the plugin wasn't loaded meanwhile */
|
|
if (find_plugin(plugin->name, plugin->type))
|
|
{
|
|
set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
|
|
unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
|
|
plugin->name, "it is already loaded");
|
|
plugin= NULL;
|
|
}
|
|
else
|
|
{
|
|
va_list unused;
|
|
bzero(&unused, sizeof unused);
|
|
plugin= add_plugin(mysql, plugin, 0, 0, unused);
|
|
}
|
|
|
|
mysql_mutex_unlock(&LOCK_load_client_plugin);
|
|
DBUG_RETURN(plugin);
|
|
}
|
|
|
|
/* see <mysql/client_plugin.h> for a full description */
|
|
struct st_mysql_client_plugin *
|
|
mysql_load_plugin_v(MYSQL *mysql, const char *name, int type,
|
|
int argc, va_list args)
|
|
{
|
|
const char *errmsg;
|
|
char dlpath[FN_REFLEN+1];
|
|
void *sym, *dlhandle;
|
|
struct st_mysql_client_plugin *plugin;
|
|
DBUG_ENTER("mysql_load_plugin_v");
|
|
|
|
DBUG_PRINT ("entry", ("name=%s type=%d int argc=%d", name, type, argc));
|
|
if (is_not_initialized(mysql, name))
|
|
{
|
|
DBUG_PRINT ("leave", ("mysql not initialized"));
|
|
DBUG_RETURN (NULL);
|
|
}
|
|
|
|
mysql_mutex_lock(&LOCK_load_client_plugin);
|
|
|
|
/* make sure the plugin wasn't loaded meanwhile */
|
|
if (type >= 0 && find_plugin(name, type))
|
|
{
|
|
errmsg= "it is already loaded";
|
|
goto err;
|
|
}
|
|
|
|
/* Compile dll path */
|
|
strxnmov(dlpath, sizeof(dlpath) - 1,
|
|
mysql->options.extension && mysql->options.extension->plugin_dir ?
|
|
mysql->options.extension->plugin_dir : PLUGINDIR, "/",
|
|
name, SO_EXT, NullS);
|
|
|
|
if (strpbrk(name, "()[]!@#$%^&/*;.,'?\\"))
|
|
{
|
|
errmsg= "invalid plugin name";
|
|
goto err;
|
|
}
|
|
|
|
DBUG_PRINT ("info", ("dlopeninig %s", dlpath));
|
|
/* Open new dll handle */
|
|
if (!(dlhandle= dlopen(dlpath, RTLD_NOW)))
|
|
{
|
|
DBUG_PRINT ("info", ("failed to dlopen"));
|
|
errmsg= dlerror();
|
|
goto err;
|
|
}
|
|
|
|
if (!(sym= dlsym(dlhandle, plugin_declarations_sym)))
|
|
{
|
|
errmsg= "not a plugin";
|
|
goto errc;
|
|
}
|
|
|
|
plugin= (struct st_mysql_client_plugin*)sym;
|
|
|
|
if (type >=0 && type != plugin->type)
|
|
{
|
|
errmsg= "type mismatch";
|
|
goto errc;
|
|
}
|
|
|
|
if (strcmp(name, plugin->name))
|
|
{
|
|
errmsg= "name mismatch";
|
|
goto errc;
|
|
}
|
|
|
|
if (type < 0 && find_plugin(name, plugin->type))
|
|
{
|
|
errmsg= "it is already loaded";
|
|
goto errc;
|
|
}
|
|
|
|
plugin= add_plugin(mysql, plugin, dlhandle, argc, args);
|
|
|
|
mysql_mutex_unlock(&LOCK_load_client_plugin);
|
|
|
|
DBUG_PRINT ("leave", ("plugin loaded ok"));
|
|
DBUG_RETURN (plugin);
|
|
|
|
errc:
|
|
dlclose(dlhandle);
|
|
err:
|
|
mysql_mutex_unlock(&LOCK_load_client_plugin);
|
|
DBUG_PRINT ("leave", ("plugin load error : %s", errmsg));
|
|
set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
|
|
ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg);
|
|
DBUG_RETURN (NULL);
|
|
}
|
|
|
|
/* see <mysql/client_plugin.h> for a full description */
|
|
struct st_mysql_client_plugin *
|
|
mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...)
|
|
{
|
|
struct st_mysql_client_plugin *p;
|
|
va_list args;
|
|
DBUG_ENTER("mysql_load_plugin");
|
|
|
|
va_start(args, argc);
|
|
p= mysql_load_plugin_v(mysql, name, type, argc, args);
|
|
va_end(args);
|
|
DBUG_RETURN(p);
|
|
}
|
|
|
|
/* see <mysql/client_plugin.h> for a full description */
|
|
struct st_mysql_client_plugin *
|
|
mysql_client_find_plugin(MYSQL *mysql, const char *name, int type)
|
|
{
|
|
struct st_mysql_client_plugin *p;
|
|
DBUG_ENTER("mysql_client_find_plugin");
|
|
|
|
DBUG_PRINT ("entry", ("name=%s, type=%d", name, type));
|
|
if (is_not_initialized(mysql, name))
|
|
DBUG_RETURN (NULL);
|
|
|
|
if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
|
|
{
|
|
set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
|
|
ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name,
|
|
"invalid type");
|
|
}
|
|
|
|
if ((p= find_plugin(name, type)))
|
|
{
|
|
DBUG_PRINT ("leave", ("found %p", p));
|
|
DBUG_RETURN (p);
|
|
}
|
|
|
|
/* not found, load it */
|
|
p= mysql_load_plugin(mysql, name, type, 0);
|
|
DBUG_PRINT ("leave", ("loaded %p", p));
|
|
DBUG_RETURN (p);
|
|
}
|
|
|
|
|
|
/* see <mysql/client_plugin.h> for a full description */
|
|
int mysql_plugin_options(struct st_mysql_client_plugin *plugin,
|
|
const char *option,
|
|
const void *value)
|
|
{
|
|
DBUG_ENTER("mysql_plugin_options");
|
|
/* does the plugin support options call? */
|
|
if (!plugin || !plugin->options)
|
|
DBUG_RETURN(1);
|
|
DBUG_RETURN(plugin->options(option, value));
|
|
}
|