From 6119cc2bba0604de317d86fb9919dc2a86404843 Mon Sep 17 00:00:00 2001 From: Chuck Bell <chuck.bell@oracle.com> Date: Tue, 19 Jul 2011 10:17:58 -0400 Subject: [PATCH] WL#5710 : mysql_plugin - enable or disable plugins This patch adds a new client utility that enables or disables plugin features. The utility disables or enables a plugin using values (name, soname, and symbols) provided via a configuration file by the same name. For example, to ENABLE the daemon_example plugin, the utility will read the daemon_example.ini configuration file and use the values contained to enable or disable the plugin. --- client/CMakeLists.txt | 5 +- client/mysql_plugin.c | 1077 +++++++++++++++++ include/my_global.h | 4 + mysql-test/include/daemon_example.ini | 8 + .../include/daemon_example_bad_format.ini | 8 + mysql-test/include/plugin.defs | 1 + mysql-test/mysql-test-run.pl | 3 + mysql-test/r/mysql_plugin.result | 97 ++ mysql-test/t/mysql_plugin-master.opt | 1 + mysql-test/t/mysql_plugin.test | 205 ++++ plugin/daemon_example/CMakeLists.txt | 2 + plugin/daemon_example/daemon_example.ini | 6 + 12 files changed, 1416 insertions(+), 1 deletion(-) create mode 100644 client/mysql_plugin.c create mode 100644 mysql-test/include/daemon_example.ini create mode 100644 mysql-test/include/daemon_example_bad_format.ini create mode 100644 mysql-test/r/mysql_plugin.result create mode 100644 mysql-test/t/mysql_plugin-master.opt create mode 100644 mysql-test/t/mysql_plugin.test create mode 100644 plugin/daemon_example/daemon_example.ini diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 80c5bbd1c9f..c5bda7d8a35 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -54,6 +54,9 @@ ADD_DEPENDENCIES(mysql_upgrade GenFixPrivs) MYSQL_ADD_EXECUTABLE(mysqlshow mysqlshow.c) TARGET_LINK_LIBRARIES(mysqlshow mysqlclient) +MYSQL_ADD_EXECUTABLE(mysql_plugin mysql_plugin.c) +TARGET_LINK_LIBRARIES(mysql_plugin mysqlclient) + MYSQL_ADD_EXECUTABLE(mysqlbinlog mysqlbinlog.cc) TARGET_LINK_LIBRARIES(mysqlbinlog mysqlclient) @@ -69,7 +72,7 @@ IF(WIN32) MYSQL_ADD_EXECUTABLE(echo echo.c) ENDIF(WIN32) -SET_TARGET_PROPERTIES (mysqlcheck mysqldump mysqlimport mysql_upgrade mysqlshow mysqlslap +SET_TARGET_PROPERTIES (mysqlcheck mysqldump mysqlimport mysql_upgrade mysqlshow mysqlslap mysql_plugin PROPERTIES HAS_CXX TRUE) ADD_DEFINITIONS(-DHAVE_DLOPEN) diff --git a/client/mysql_plugin.c b/client/mysql_plugin.c new file mode 100644 index 00000000000..ca3da0086e4 --- /dev/null +++ b/client/mysql_plugin.c @@ -0,0 +1,1077 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <m_string.h> +#include <mysql.h> +#include <my_getopt.h> +#include <sys/stat.h> +#include <my_global.h> +#include <stdio.h> +#include <string.h> + + +#define SHOW_VERSION "1.0.0" +#define PRINT_VERSION do { printf("%s Ver %s Distrib %s\n", \ + my_progname, SHOW_VERSION, MYSQL_SERVER_VERSION); \ + } while(0) + +/* Global variables. */ +static uint my_end_arg= 0; +static uint opt_verbose=0; +static uint opt_no_defaults= 0; +static uint opt_print_defaults= 0; +static char *opt_datadir=0, *opt_basedir=0, + *opt_plugin_dir=0, *opt_plugin_ini=0; +static char bootstrap[FN_REFLEN]; + + +/* plugin struct */ +struct st_plugin +{ + const char *name; /* plugin name */ + const char *so_name; /* plugin so (library) name */ + const char *symbols[16]; /* symbols to load */ +} plugin_data; + + +/* Options */ +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"basedir", 'b', "The basedir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"datadir", 'd', "The datadir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin-dir", 'p', "The plugin dir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin-ini", 'i', "Read plugin information from configuration file " + "specified instead of from <plugin-dir>/<plugin_name>.ini.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"no-defaults", 'n', "Do not read values from configuration file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"print-defaults", 'P', "Show default values from configuration file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', + "More verbose output; you can use this multiple times to get even more " + "verbose output.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +/* Methods */ +static int process_options(int argc, char *argv[], char *operation); +static int check_access(); +static int find_tool(const char *tool_name, char *tool_path); +static int find_plugin(char *tp_path); +static int build_bootstrap_file(char *operation, char *bootstrap); +static int dump_bootstrap_file(char *bootstrap_file); +static int bootstrap_server(char *server_path, char *bootstrap_file); + + +int main(int argc,char *argv[]) +{ + int error= 0; + char tp_path[FN_REFLEN]; + char server_path[FN_REFLEN]; + char operation[16]; + + MY_INIT(argv[0]); + plugin_data.name= 0; // initialize name + + /* + The following operations comprise the method for enabling or disabling + a plugin. We begin by processing the command options then check the + directories specified for --datadir, --basedir, --plugin-dir, and + --plugin-ini (if specified). If the directories are Ok, we then look + for the mysqld executable and the plugin soname. Finally, we build a + bootstrap command file for use in bootstraping the server. + + If any step fails, the method issues an error message and the tool exits. + + 1) Parse, execute, and verify command options. + 2) Check access to directories. + 3) Look for mysqld executable. + 4) Look for the plugin. + 5) Build a bootstrap file with commands to enable or disable plugin. + + */ + if ((error= process_options(argc, argv, operation)) || + (error= check_access()) || + (error= find_tool("mysqld" FN_EXEEXT, server_path)) || + (error= find_plugin(tp_path)) || + (error= build_bootstrap_file(operation, bootstrap))) + goto exit; + + /* Dump the bootstrap file if --verbose specified. */ + if (opt_verbose && ((error= dump_bootstrap_file(bootstrap)))) + goto exit; + + /* Start the server in bootstrap mode and execute bootstrap commands */ + error= bootstrap_server(server_path, bootstrap); + +exit: + /* Remove file */ + my_delete(bootstrap, MYF(0)); + if (opt_verbose && error == 0) + { + printf("# Operation succeeded.\n"); + } + + my_end(my_end_arg); + exit(error ? 1 : 0); + return 0; /* No compiler warnings */ +} + + +/** + Get a temporary file name. + + @param[out] filename The file name of the temporary file + @param[in] ext An extension for the file (optional) + + @retval int error = 1, success = 0 +*/ + +static int make_tempfile(char *filename, const char *ext) +{ + int fd= 0; + + if ((fd=create_temp_file(filename, NullS, ext, O_CREAT | O_WRONLY, + MYF(MY_WME))) < 0) + { + fprintf(stderr, "ERROR: Cannot generate temporary file. Error code: %d.\n", + fd); + return 1; + } + my_close(fd, MYF(0)); + return 0; +} + + +/** + Get the value of an option from a string read from my_print_defaults output. + + @param[in] line The line (string) read from the file + @param[in] item The option to search for (e.g. --datadir) + + @returns NULL if not found, string containing value if found +*/ + +static char *get_value(char *line, const char *item) +{ + char *destination= 0; + int item_len= (int)strlen(item); + int line_len = (int)strlen(line); + + if ((strncasecmp(line, item, item_len) == 0)) + { + int start= 0; + char *s= 0; + + s = line + item_len + 1; + destination= my_strndup(s, line_len - start, MYF(MY_FAE)); + destination[line_len - item_len - 2]= 0; + } + return destination; +} + + +/** + Run a command in a shell. + + This function will attempt to execute the command specified by using the + popen() method to open a shell and execute the command passed and store the + output in a result file. If the --verbose option was specified, it will open + the result file and print the contents to stdout. + + @param[in] cmd The command to execute. + @param[in] mode The mode for popen() (e.g. "r", "w", "rw") + + @return int error code or 0 for success. +*/ + +static int run_command(char* cmd, const char *mode) +{ + char buf[512]= {0}; + FILE *res_file; + int error; + + if (!(res_file= popen(cmd, mode))) + return -1; + + if (opt_verbose) + { + while (fgets(buf, sizeof(buf), res_file)) + { + fprintf(stdout, "%s", buf); + } + } + error= pclose(res_file); + return error; +} + + +/** + Get the default values from the my.cnf file. + + This method gets the default values for the following parameters: + + --datadir + --basedir + --plugin-dir + --plugin-ini + + These values are used if the user has not specified a value. + + @retval int error = 1, success = 0 +*/ + +static int get_default_values() +{ + char tool_path[FN_REFLEN]; + char defaults_cmd[FN_REFLEN]; + char defaults_file[FN_REFLEN]; + char line[FN_REFLEN]; + int error= 0; + int ret= 0; + FILE *file= 0; + + if ((error= find_tool("my_print_defaults" FN_EXEEXT, tool_path))) + goto exit; + else + { + if ((error= make_tempfile(defaults_file, "txt"))) + goto exit; + snprintf(defaults_cmd, sizeof(defaults_cmd), + "%s mysqld > %s", tool_path, defaults_file); + + /* Execute the command */ + if (opt_verbose > 1) + { + printf("# Command: %s\n", defaults_cmd); + } + error= run_command(defaults_cmd, "r"); + if (error) + { + fprintf(stderr, "ERROR: my_print_defaults failed. Error code: %d.\n", + ret); + goto exit; + } + /* Now open the file and read the defaults we want. */ + file= fopen(defaults_file, "r"); + while (fgets(line, FN_REFLEN, file) != NULL) + { + char *value= 0; + + if ((opt_datadir == 0) && ((value= get_value(line, "--datadir")))) + { + opt_datadir= my_strdup(value, MYF(MY_FAE)); + } + if ((opt_basedir == 0) && ((value= get_value(line, "--basedir")))) + { + opt_basedir= my_strdup(value, MYF(MY_FAE)); + } + if ((opt_plugin_dir == 0) && ((value= get_value(line, "--plugin_dir")))) + { + opt_plugin_dir= my_strdup(value, MYF(MY_FAE)); + } + if ((opt_plugin_ini == 0) && ((value= get_value(line, "--plugin_ini")))) + { + opt_plugin_ini= my_strdup(value, MYF(MY_FAE)); + } + } + } +exit: + if (file) + { + fclose(file); + /* Remove file */ + my_delete(defaults_file, MYF(0)); + } + return error; +} + + +/** + Print usage. +*/ + +static void usage(void) +{ + PRINT_VERSION; + puts("Copyright (c) 2011, Oracle and/or its affiliates. " + "All rights reserved.\n"); + puts("Enable or disable plugins."); + printf("\nUsage: %s [options] <plugin> ENABLE|DISABLE\n\nOptions:\n", + my_progname); + my_print_help(my_long_options); + puts("\n"); +} + + +/** + Print the default values as read from the my.cnf file. + + This method displays the default values for the following parameters: + + --datadir + --basedir + --plugin-dir + --plugin-ini + +*/ + +static void print_default_values(void) +{ + printf("%s would have been started with the following arguments:\n", + my_progname); + get_default_values(); + if (opt_datadir) + { + printf("--datadir=%s ", opt_datadir); + } + if (opt_basedir) + { + printf("--basedir=%s ", opt_basedir); + } + if (opt_plugin_dir) + { + printf("--plugin_dir=%s ", opt_plugin_dir); + } + if (opt_plugin_ini) + { + printf("--plugin_ini=%s ", opt_plugin_ini); + } + printf("\n"); +} + + +/** + Process the arguments and identify an option and store its value. + + @param[in] optid The single character shortcut for the argument. + @param[in] my_option Structure of legal options. + @param[in] argument The argument value to process. +*/ + +static my_bool +get_one_option(int optid, + const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case 'n': + opt_no_defaults++; + break; + case 'P': + opt_print_defaults++; + print_default_values(); + break; + case 'v': + opt_verbose++; + break; + case 'V': + PRINT_VERSION; + exit(0); + break; + case '?': + case 'I': /* Info */ + usage(); + exit(0); + case 'd': + opt_datadir= my_strdup(argument, MYF(MY_FAE)); + break; + case 'b': + opt_basedir= my_strdup(argument, MYF(MY_FAE)); + break; + case 'p': + opt_plugin_dir= my_strdup(argument, MYF(MY_FAE)); + break; + case 'i': + opt_plugin_ini= my_strdup(argument, MYF(MY_FAE)); + break; + } + return 0; +} + + +/** + Check to see if a file exists. + + @param[in] filename File to locate. + + @retval int file not found = 1, file found = 0 +*/ + +static int file_exists(char * filename) +{ + struct stat buf; + int i = stat (filename, &buf); + /* File found */ + if (i == 0) + { + return 1; + } + return 0; +} + + +/** + Search a specific path and sub directory for a file name. + + @param[in] base_path Original path to use. + @param[in] tool_name Name of the tool to locate. + @param[in] subdir The sub directory to search. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int search_dir(const char * base_path, const char *tool_name, + const char *subdir, char *tool_path) +{ + char new_path[FN_REFLEN]; + + strcpy(new_path, base_path); + strcat(new_path, subdir); + fn_format(new_path, new_path, "", tool_name, MY_UNPACK_FILENAME); + if (file_exists(new_path)) + { + strcpy(tool_path, new_path); + return 1; + } + return 0; +} + + +/** + Search known common paths and sub directories for a file name. + + @param[in] base_path Original path to use. + @param[in] tool_name Name of the tool to locate. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int search_paths(const char *base_path, const char *tool_name, + char *tool_path) +{ + int i= 0; + + static const char *paths[]= { + "", "/share/", "/scripts/", "/bin/", "/sbin/", "/libexec/", + "/mysql/", "/sql/", + }; + for (i = 0 ; i < (int)array_elements(paths); i++) + { + if (search_dir(base_path, tool_name, paths[i], tool_path)) + { + return 1; + } + } + return 0; +} + + +/** + Read a plugin data element + + This method takes as input a line from the plugin.ini file and splits it + into the st_plugin structure. + + @retval int error = 1, success = 0 +*/ + +static int read_plugin_data(char *line) +{ + const char delimiters[]= " ,"; + char *token, *cp; + int i= 0; + int error= 0; + + cp= my_strdup(line, MYF(MY_FAE)); + token= strtok (cp, delimiters); + if (token != NULL) + { + /* read name */ + plugin_data.name= my_strdup(token, MYF(MY_WME)); + /* read so_name */ + token = strtok(NULL, delimiters); + if (token == NULL) + { + return 1; + } + plugin_data.so_name= my_strdup(token, MYF(MY_WME)); + if (plugin_data.so_name == NULL) + { + error= 1; + goto exit; + } + /* Add proper file extension for soname */ + strcat((char *)plugin_data.so_name, FN_SOEXT); + /* read symbols */ + while (token != NULL) + { + token= strtok (NULL, delimiters); + if ((token != NULL) && (token[0] != '\n')) + { + plugin_data.symbols[i]= my_strdup(token, MYF(MY_WME)); + i++; + } + else + { + plugin_data.symbols[i]= NULL; + } + } + } + +exit: + if (error) + { + fprintf(stderr, "ERROR: Format incorrect for plugin config file.\n"); + } + + return error; +} + + +/** + Read the plugin ini file. + + This function attempts to read the plugin config file from the plugin_dir + path. If the file is not found, an error is generated. + + @retval int error = 1, success = 0 +*/ + +static int load_plugin_data(char *plugin_name) +{ + FILE *file_ptr; + char path[FN_REFLEN]; + char line[1024]; + int i= 0; + + if (opt_plugin_ini == 0) + { + fn_format(path, plugin_name, opt_plugin_dir, "", MYF(0)); + opt_plugin_ini= my_strdup(path, MYF(MY_FAE)); + } + if (!file_exists(opt_plugin_ini)) + { + goto error; + } + + file_ptr= fopen(opt_plugin_ini, "r"); + if (file_ptr == NULL) + { + goto error; + } + i = 0; + while (1) + { + char *res; + res= fgets(line, sizeof(line), file_ptr); + if (res == NULL) + { + break; + } + if (line[0] == '#') // skip comment lines + { + continue; + } + if (read_plugin_data(line)) + { + fclose(file_ptr); + goto error; + } + } + fclose(file_ptr); + return 0; + +error: + fprintf(stderr, "ERROR: Cannot read plugin config file %s.\n", + plugin_name); + return 1; +} + + +/** + Check the options for validity. + + This function checks the arguments for validity issuing the appropriate + error message if arguments are missing or invalid. On success, @operation + is set to either "ENABLE" or "DISABLE". + + @param[in] argc The number of arguments. + @param[in] argv The arguments. + @param[out] operation The operation chosen (enable|disable) + + @retval int error = 1, success = 0 +*/ + +static int check_options(int argc, char **argv, char *operation) +{ + int i= 0; // loop counter + int num_found= 0; // number of options found (shortcut loop) + char config_file[FN_REFLEN]; // configuration file name + char plugin_name[FN_REFLEN]; // plugin name + + /* Form prefix strings for the options. */ + const char *basedir_prefix = "--basedir="; + int basedir_len= strlen(basedir_prefix); + const char *datadir_prefix = "--datadir="; + int datadir_len= strlen(datadir_prefix); + const char *plugin_dir_prefix = "--plugin_dir="; + int plugin_dir_len= strlen(plugin_dir_prefix); + + strcpy(plugin_name, ""); + for (i = 0; i < argc && num_found < 5; i++) + { + + if (!argv[i]) + { + continue; + } + if ((strcasecmp(argv[i], "ENABLE") == 0) || + (strcasecmp(argv[i], "DISABLE") == 0)) + { + strcpy(operation, argv[i]); + num_found++; + } + else if ((strncasecmp(argv[i], basedir_prefix, basedir_len) == 0) && + !opt_basedir) + { + opt_basedir= my_strndup(argv[i]+basedir_len, + strlen(argv[i])-basedir_len, MYF(MY_FAE)); + num_found++; + } + else if ((strncasecmp(argv[i], datadir_prefix, datadir_len) == 0) && + !opt_datadir) + { + opt_datadir= my_strndup(argv[i]+datadir_len, + strlen(argv[i])-datadir_len, MYF(MY_FAE)); + num_found++; + } + else if ((strncasecmp(argv[i], plugin_dir_prefix, plugin_dir_len) == 0) && + !opt_plugin_dir) + { + opt_plugin_dir= my_strndup(argv[i]+plugin_dir_len, + strlen(argv[i])-plugin_dir_len, MYF(MY_FAE)); + num_found++; + } + /* read the plugin config file and check for match against argument */ + else + { + strcpy(plugin_name, argv[i]); + strcpy(config_file, argv[i]); + strcat(config_file, ".ini"); + } + } + + if (!opt_basedir) + { + fprintf(stderr, "ERROR: Missing --basedir option.\n"); + return 1; + } + + if (!opt_datadir) + { + fprintf(stderr, "ERROR: Missing --datadir option.\n"); + return 1; + } + + if (!opt_plugin_dir) + { + fprintf(stderr, "ERROR: Missing --plugin_dir option.\n"); + return 1; + } + /* If a plugin was specified, read the config file. */ + else if (strlen(plugin_name) > 0) + { + if (load_plugin_data(config_file)) + { + return 1; + } + if (strcasecmp(plugin_data.name, plugin_name) != 0) + { + fprintf(stderr, "ERROR: plugin name requested does not match config " + "file data.\n"); + return 1; + } + } + else + { + fprintf(stderr, "ERROR: No plugin specified.\n"); + return 1; + } + + if ((strlen(operation) == 0)) + { + fprintf(stderr, "ERROR: missing operation. Please specify either " + "'<plugin> ENABLE' or '<plugin> DISABLE'.\n"); + return 1; + } + + return 0; +} + + +/** + Parse, execute, and verify command options. + + This method handles all of the option processing including the optional + features for displaying data (--print-defaults, --help ,etc.) that do not + result in an attempt to ENABLE or DISABLE of a plugin. + + @param[in] arc Count of arguments + @param[in] argv Array of arguments + @param[out] operation Operation (ENABLE or DISABLE) + + @retval int error = 1, success = 0, exit program = -1 +*/ + +static int process_options(int argc, char *argv[], char *operation) +{ + int error= 0; + int i= 0; + + /* Parse and execute command-line options */ + if ((error= handle_options(&argc, &argv, my_long_options, get_one_option))) + goto exit; + + /* If the print defaults option used, exit. */ + if (opt_print_defaults) + { + error= -1; + goto exit; + } + + /* + If the user did not specify the option to skip loading defaults from a + config file and the required options are not present or there was an error + generated when the defaults were read from the file, exit. + */ + if (!opt_no_defaults && ((error= get_default_values()))) + { + error= -1; + goto exit; + } + + /* + Check to ensure required options are present and validate the operation. + Note: this method also validates the plugin specified by attempting to + read a configuration file named <plugin_name>.ini from the --plugin-dir + or --plugin-ini location if the --plugin-ini option presented. + */ + strcpy(operation, ""); + if ((error = check_options(argc, argv, operation))) + { + goto exit; + } + + /* Add a trailing directory separator if not present */ + i= (int)strlength(opt_basedir); + if (opt_basedir[i-1] != FN_LIBCHAR || opt_basedir[i-1] != FN_LIBCHAR2) + { + strcat(opt_basedir, FN_DIRSEP); + } + + if (opt_verbose) + { + printf("# basedir = %s\n", opt_basedir); + printf("# plugin_dir = %s\n", opt_plugin_dir); + printf("# datadir = %s\n", opt_datadir); + printf("# plugin_ini = %s\n", opt_plugin_ini); + } + +exit: + return error; +} + + +/** + Check access + + This method checks to ensure all of the directories (opt_basedir, + opt_plugin_dir, opt_datadir, and opt_plugin_ini) are accessible by + the user. + + @retval int error = 1, success = 0 +*/ + +static int check_access() +{ + int error= 0; + + if ((error= my_access(opt_basedir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access basedir at '%s'.\n", + opt_basedir); + goto exit; + } + if ((error= my_access(opt_plugin_dir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access plugin_dir at '%s'.\n", + opt_plugin_dir); + goto exit; + } + if ((error= my_access(opt_datadir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access datadir at '%s'.\n", + opt_datadir); + goto exit; + } + if ((error= my_access(opt_plugin_ini, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access plugin config file at '%s'.\n", + opt_plugin_ini); + goto exit; + } + +exit: + return error; +} + + +/** + Locate the tool and form tool path. + + @param[in] tool_name Name of the tool to locate. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int find_tool(const char *tool_name, char *tool_path) +{ + int i= 0; + + const char *paths[]= { + opt_basedir, "/usr", "/usr/local/mysql", "/usr/sbin", "/usr/share", + }; + for (i= 0; i < (int)array_elements(paths); i++) + { + if (paths[i] && (search_paths(paths[i], tool_name, tool_path))) + goto found; + } + fprintf(stderr, "WARNING: Cannot find %s.\n", tool_name); + return 1; +found: + if (opt_verbose) + printf("# Found tool '%s' as '%s'.\n", tool_name, tool_path); + return 0; +} + + +/** + Find the plugin library. + + This function attempts to use the @c plugin_dir option passed on the + command line to locate the plugin. + + @param[out] tp_path The actual path to plugin with FN_SOEXT applied. + + @retval int error = 1, success = 0 +*/ + +static int find_plugin(char *tp_path) +{ + /* Check for existance of plugin */ + fn_format(tp_path, plugin_data.so_name, opt_plugin_dir, "", MYF(0)); + if (!file_exists(tp_path)) + { + fprintf(stderr, "ERROR: The plugin library is missing or in a different" + " location.\n"); + return 1; + } + else if (opt_verbose) + { + printf("# Found plugin '%s' as '%s'\n", plugin_data.name, tp_path); + } + return 0; +} + + +/** + Build the boostrap file. + + Create a new file and populate it with SQL commands to ENABLE or DISABLE + the plugin via INSERT and DELETE operations on the mysql.plugin table. + + param[in] operation The type of operation (ENABLE or DISABLE) + param[out] bootstrap A FILE* pointer + + @retval int error = 1, success = 0 +*/ + +static int build_bootstrap_file(char *operation, char *bootstrap) +{ + int error= 0; + FILE *file= 0; + + /* + Perform plugin operation : ENABLE or DISABLE + + The following creates a temporary bootstrap file and populates it with + the appropriate SQL commands for the operation. For ENABLE, INSERT + statements are created. For DISABLE, DELETE statements are created. The + values for these statements are derived from the plugin_data read from the + <plugin_name>.ini configuration file. Once the file is built, a call to + mysqld is made in read only, bootstrap modes to read the SQL statements + and execute them. + */ + if ((error= make_tempfile(bootstrap, "sql"))) + { + /* Fail if we cannot create a temporary file for the bootstrap commands. */ + fprintf(stderr, "ERROR: Cannot create bootstrap file.\n"); + goto exit; + } + if ((file= fopen(bootstrap, "w+")) == NULL) + { + fprintf(stderr, "ERROR: Cannot open bootstrap file for writing.\n"); + error= 1; + goto exit; + } + if (strcasecmp(operation, "enable") == 0) + { + int i= 0; + fprintf(file, "INSERT IGNORE INTO mysql.plugin VALUES "); + for (i= 0; i < (int)array_elements(plugin_data.symbols); i++) + { + /* stop when we read the end of the symbol list - marked with NULL */ + if (plugin_data.symbols[i] == NULL) + { + break; + } + if (i > 0) + { + fprintf(file, ", "); + } + fprintf(file, "('%s','%s')", + plugin_data.symbols[i], plugin_data.so_name); + } + fprintf(file, ";\n"); + if (opt_verbose) + { + printf("# Enabling %s...\n", plugin_data.name); + } + } + else + { + fprintf(file, + "DELETE FROM mysql.plugin WHERE name = '%s';", plugin_data.name); + if (opt_verbose) + { + printf("# Disabling %s...\n", plugin_data.name); + } + } + +exit: + fclose(file); + return error; +} + + +/** + Dump bootstrap file. + + Read the contents of the bootstrap file and print it out. + + @param[in] bootstrap_file Name of bootstrap file to read + + @retval int error = 1, success = 0 +*/ + +static int dump_bootstrap_file(char *bootstrap_file) +{ + char *ret= 0; + int error= 0; + char query_str[512]; + FILE *file= 0; + + if ((file= fopen(bootstrap_file, "r")) == NULL) + { + fprintf(stderr, "ERROR: Cannot open bootstrap file for reading.\n"); + error= 1; + goto exit; + } + ret= fgets(query_str, 512, file); + if (ret == 0) + { + fprintf(stderr, "ERROR: Cannot read bootstrap file.\n"); + error= 1; + goto exit; + } + printf("# Query: %s", query_str); + +exit: + if (file) + { + fclose(file); + } + return error; +} + + +/** + Bootstrap the server + + Create a command line sequence to launch mysqld in bootstrap mode. This + will allow mysqld to launch a minimal server instance to read and + execute SQL commands from a file piped in (the boostrap file). We use + the --no-defaults option to skip reading values from the config file. + + The bootstrap mode skips loading of plugins and many other subsystems. + This allows the mysql_plugin tool to insert the correct rows into the + mysql.plugin table (for ENABLE) or delete the rows (for DISABLE). Once + the server is launched in normal mode, the plugin will be loaded + (for ENABLE) or not loaded (for DISABLE). In this way, we avoid the + (sometimes) complicated LOAD PLUGIN commands. + + @param[in] server_path Path to server executable + @param[in] bootstrap_file Name of bootstrap file to read + + @retval int error = 1, success = 0 +*/ + +static int bootstrap_server(char *server_path, char *bootstrap_file) +{ + char bootstrap_cmd[FN_REFLEN]; + int error= 0; + int ret= 0; + + snprintf(bootstrap_cmd, sizeof(bootstrap_cmd), + "%s --no-defaults --bootstrap --datadir=%s --basedir=%s" + " < %s", server_path, opt_datadir, opt_basedir, bootstrap_file); + + /* Execute the command */ + if (opt_verbose > 1) + { + printf("# Command: %s\n", bootstrap_cmd); + } + error= run_command(bootstrap_cmd, "r"); + if (error) + fprintf(stderr, + "ERROR: Unexpected result from bootstrap. Error code: %d.\n", + ret); + + return error; +} diff --git a/include/my_global.h b/include/my_global.h index f465d031b62..924c83cd951 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -603,6 +603,8 @@ typedef SOCKET_SIZE_TYPE size_socket; #define FN_LIBCHAR '\\' #define FN_LIBCHAR2 '/' #define FN_DIRSEP "/\\" /* Valid directory separators */ +#define FN_EXEEXT ".exe" +#define FN_SOEXT ".dll" #define FN_ROOTDIR "\\" #define FN_DEVCHAR ':' #define FN_NETWORK_DRIVES /* Uses \\ to indicate network drives */ @@ -611,6 +613,8 @@ typedef SOCKET_SIZE_TYPE size_socket; #define FN_LIBCHAR '/' #define FN_LIBCHAR2 '/' #define FN_DIRSEP "/" /* Valid directory separators */ +#define FN_EXEEXT "" +#define FN_SOEXT ".so" #define FN_ROOTDIR "/" #endif diff --git a/mysql-test/include/daemon_example.ini b/mysql-test/include/daemon_example.ini new file mode 100644 index 00000000000..2fbfca203ea --- /dev/null +++ b/mysql-test/include/daemon_example.ini @@ -0,0 +1,8 @@ +# +# Plugin initialization file. Format using comma-separated values: +# name, libname, symbol, [symbol, ] +# Note: trailing comma is required. +# +# File is used by mysql_plugin.test for testing missing library error. +# +daemon_example, libdaemon_example, daemon_example, diff --git a/mysql-test/include/daemon_example_bad_format.ini b/mysql-test/include/daemon_example_bad_format.ini new file mode 100644 index 00000000000..a08e50632c9 --- /dev/null +++ b/mysql-test/include/daemon_example_bad_format.ini @@ -0,0 +1,8 @@ +# +# Plugin initialization file. Format using comma-separated values: +# name, libname, symbol, [symbol, ] +# Note: trailing comma is required. +# +# File is used by mysql_plugin.test for testing bad library name. +# +daemon_BADNAME, libdaemon_example, daemon_example, diff --git a/mysql-test/include/plugin.defs b/mysql-test/include/plugin.defs index e07c603c8e5..6fbe4f68328 100644 --- a/mysql-test/include/plugin.defs +++ b/mysql-test/include/plugin.defs @@ -39,3 +39,4 @@ ha_archive storage/archive ARCHIVE_PLUGIN ha_blackhole storage/blackhole BLACKHOLE_PLUGIN ha_federated storage/federated FEDERATED_PLUGIN mypluglib plugin/fulltext SIMPLE_PARSER +libdaemon_example plugin/daemon_example DAEMONEXAMPLE diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 82d17a21f89..ec0ca74b1d1 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -168,6 +168,7 @@ my $opt_suites; our $opt_verbose= 0; # Verbose output, enable with --verbose our $exe_mysql; +our $exe_mysql_plugin; our $exe_mysqladmin; our $exe_mysqltest; our $exe_libtool; @@ -1950,6 +1951,7 @@ sub executable_setup () { # Look for the client binaries $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mysqladmin"); $exe_mysql= mtr_exe_exists("$path_client_bindir/mysql"); + $exe_mysql_plugin= mtr_exe_exists("$path_client_bindir/mysql_plugin"); $exe_mysql_embedded= mtr_exe_maybe_exists("$basedir/libmysqld/examples/mysql_embedded"); @@ -2357,6 +2359,7 @@ sub environment_setup { $ENV{'MYSQLADMIN'}= native_path($exe_mysqladmin); $ENV{'MYSQL_CLIENT_TEST'}= mysql_client_test_arguments(); $ENV{'EXE_MYSQL'}= $exe_mysql; + $ENV{'MYSQL_PLUGIN'}= $exe_mysql_plugin; $ENV{'MYSQL_EMBEDDED'}= $exe_mysql_embedded; # ---------------------------------------------------- diff --git a/mysql-test/r/mysql_plugin.result b/mysql-test/r/mysql_plugin.result new file mode 100644 index 00000000000..4aebf532db1 --- /dev/null +++ b/mysql-test/r/mysql_plugin.result @@ -0,0 +1,97 @@ +# +# Ensure the plugin isn't loaded. +# +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; +name dl +# +# Enable the plugin... +# +# +# Ensure the plugin is now loaded. +# +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; +name dl +daemon_example libdaemon_example.so +# +# Disable the plugin... +# +# +# Ensure the plugin isn't loaded. +# +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; +name dl +# +# Attempt to load non-existant plugin +# +ERROR: Cannot read plugin config file NOT_THERE_AT_ALL.ini. +# +# Attempt to use non-existant plugin.ini file +# +ERROR: Cannot read plugin config file daemon_example.ini. +# +# Attempt to omit the plugin +# +ERROR: No plugin specified. +# +# Attempt to omit DISABLE|ENABLE +# +ERROR: missing operation. Please specify either '<plugin> ENABLE' or '<plugin> DISABLE'. +# +# Attempt to use bad paths - datadir +# +ERROR: Cannot access datadir at '/data_not_there/'. +# +# Attempt to use bad paths - basedir +# +ERROR: Cannot access basedir at '/basedir_not_there/'. +# +# Attempt to use bad paths - plugin_dir +# +ERROR: Cannot read plugin config file daemon_example.ini. +# +# Missing library +# +ERROR: The plugin library is missing or in a different location. +# +# Bad format for config file +# +ERROR: plugin name requested does not match config file data. +# +# Missing base_dir option +# +ERROR: Missing --basedir option. +# +# Missing data_dir option +# +ERROR: Missing --datadir option. +# +# Missing plugin_dir option +# +ERROR: Missing --plugin_dir option. +# +# Show the help. +# +mysql_plugin Ver 1.0.0 Distrib XX.XX.XX +Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + +Enable or disable plugins. + +Usage: mysql_plugin [options] <plugin> ENABLE|DISABLE + +Options: + -?, --help Display this help and exit. + -b, --basedir=name The basedir for the server. + -d, --datadir=name The datadir for the server. + -p, --plugin-dir=name + The plugin dir for the server. + -i, --plugin-ini=name + Read plugin information from configuration file specified + instead of from <plugin-dir>/<plugin_name>.ini. + -n, --no-defaults Do not read values from configuration file. + -P, --print-defaults + Show default values from configuration file. + -v, --verbose More verbose output; you can use this multiple times to + get even more verbose output. + -V, --version Output version information and exit. + + diff --git a/mysql-test/t/mysql_plugin-master.opt b/mysql-test/t/mysql_plugin-master.opt new file mode 100644 index 00000000000..061ca907902 --- /dev/null +++ b/mysql-test/t/mysql_plugin-master.opt @@ -0,0 +1 @@ +--plugin-dir=$DAEMONEXAMPLE_DIR diff --git a/mysql-test/t/mysql_plugin.test b/mysql-test/t/mysql_plugin.test new file mode 100644 index 00000000000..3257adfeb61 --- /dev/null +++ b/mysql-test/t/mysql_plugin.test @@ -0,0 +1,205 @@ +# +# Test mysql_plugin tool +# + +# +# Test currently does not run on Windows because in Windows build trees, +# mysqld.exe is not placed in the ./sql folder - it is placed either +# in ./sql/Debug or ./sql/release. If this behaviour is changed, this test +# can run on Windows. +# +--source include/not_windows.inc + +# Add the datadir, basedir, plugin_dir to the bootstrap command +let $MYSQLD_DATADIR= `select @@datadir`; +let $MYSQLD_BASEDIR= `select @@basedir`; +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN --datadir=$MYSQLD_DATADIR --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=$DAEMONEXAMPLE_DIR; + +--echo # +--echo # Ensure the plugin isn't loaded. +--echo # +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; + +--echo # +--echo # Enable the plugin... +--echo # +let $expect_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; +# MTR will remove this file later, but this might be too late. +--error 0,1 +--remove_file $expect_file +--write_file $expect_file +wait +EOF +--shutdown_server 10 +--source include/wait_until_disconnected.inc + +# +# Enable the plugin +# +--exec $MYSQLD_BOOTSTRAP_CMD ENABLE daemon_example + +# +# Ensure enabling an enabled plugin doesn't fail +--exec $MYSQLD_BOOTSTRAP_CMD ENABLE daemon_example + +# +# Restart the server +# +--append_file $expect_file +restart +EOF +--enable_reconnect +--source include/wait_until_connected_again.inc + +--echo # +--echo # Ensure the plugin is now loaded. +--echo # +--replace_regex /\.dll/.so/ +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; + +--echo # +--echo # Disable the plugin... +--echo # +# MTR will remove this file later, but this might be too late. +--error 0,1 +--remove_file $expect_file +--write_file $expect_file +wait +EOF +--shutdown_server 10 +--source include/wait_until_disconnected.inc + +# +# Disable the plugin +# +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example + +# +# Restart the server +# +--append_file $expect_file +restart +EOF +--enable_reconnect +--source include/wait_until_connected_again.inc + +--echo # +--echo # Ensure the plugin isn't loaded. +--echo # +SELECT * FROM mysql.plugin WHERE name = 'daemon_example' ORDER BY name; + +# +# Stop the server for error conditions +# +let $expect_file= $MYSQLTEST_VARDIR/tmp/mysqld.1.expect; +# MTR will remove this file later, but this might be too late. +--error 0,1 +--remove_file $expect_file +--write_file $expect_file +wait +EOF +--shutdown_server 10 +--source include/wait_until_disconnected.inc + +--echo # +--echo # Attempt to load non-existant plugin +--echo # +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE NOT_THERE_AT_ALL 2>&1 + +--echo # +--echo # Attempt to use non-existant plugin.ini file +--echo # +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example --plugin-ini=/NOT/THERE/pi.ini 2>&1 + +--echo # +--echo # Attempt to omit the plugin +--echo # +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE 2>&1 + +--echo # +--echo # Attempt to omit DISABLE|ENABLE +--echo # +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD daemon_example 2>&1 + +--echo # +--echo # Attempt to use bad paths - datadir +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=/data_not_there/ --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=$DAEMONEXAMPLE_DIR; +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Attempt to use bad paths - basedir +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --basedir=/basedir_not_there/ --plugin-dir=$DAEMONEXAMPLE_DIR; +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Attempt to use bad paths - plugin_dir +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=/plugin_not_there/; +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Missing library +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=$MYSQL_TEST_DIR/include/; +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Bad format for config file +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=$DAEMONEXAMPLE_DIR --plugin-ini=$MYSQL_TEST_DIR/include/daemon_example_bad_format.ini; +--error 1,2,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Missing base_dir option +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --plugin-dir=$DAEMONEXAMPLE_DIR; +--error 1,2,139,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Missing data_dir option +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --basedir=$MYSQLD_BASEDIR/sql --plugin-dir=$DAEMONEXAMPLE_DIR; +--error 1,2,139,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Missing plugin_dir option +--echo # +let $MYSQLD_BOOTSTRAP_CMD= $MYSQL_PLUGIN -n --datadir=$MYSQLD_DATADIR --basedir=$MYSQLD_BASEDIR/sql; +--error 1,2,139,256 +--exec $MYSQLD_BOOTSTRAP_CMD DISABLE daemon_example 2>&1 + +--echo # +--echo # Show the help. +--echo # +replace_result $MYSQL_PLUGIN mysql_plugin; +--replace_regex /Distrib [0-9.]+/Distrib XX.XX.XX/ +--exec $MYSQL_PLUGIN --help + +# +# Restart the server +# +--append_file $expect_file +restart +EOF +--enable_reconnect +--source include/wait_until_connected_again.inc + +# +# Cleanup +# MTR will remove this file later, but this might be too late. +--error 0,1 +--remove_file $expect_file + diff --git a/plugin/daemon_example/CMakeLists.txt b/plugin/daemon_example/CMakeLists.txt index 60aa00687ba..1623c3025d7 100644 --- a/plugin/daemon_example/CMakeLists.txt +++ b/plugin/daemon_example/CMakeLists.txt @@ -15,3 +15,5 @@ MYSQL_ADD_PLUGIN(daemon_example daemon_example.cc MODULE_ONLY MODULE_OUTPUT_NAME "libdaemon_example") + +INSTALL(FILES daemon_example.ini DESTINATION ${INSTALL_PLUGINDIR}) diff --git a/plugin/daemon_example/daemon_example.ini b/plugin/daemon_example/daemon_example.ini new file mode 100644 index 00000000000..8d84456b635 --- /dev/null +++ b/plugin/daemon_example/daemon_example.ini @@ -0,0 +1,6 @@ +# +# Plugin initialization file. Format using comma-separated values: +# name, libname, symbol, [symbol, ] +# Note: trailing comma is required. +# +daemon_example, libdaemon_example, daemon_example,