diff options
Diffstat (limited to 'server-tools')
49 files changed, 6012 insertions, 2287 deletions
diff --git a/server-tools/Makefile.am b/server-tools/Makefile.am index 573bf07ccff..a249a6f6792 100644 --- a/server-tools/Makefile.am +++ b/server-tools/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS= instance-manager -DIST_SUBDIRS= instance-manager +SUBDIRS = . instance-manager +DIST_SUBDIRS = . instance-manager diff --git a/server-tools/instance-manager/CMakeLists.txt b/server-tools/instance-manager/CMakeLists.txt index fafc3df4108..1983d459ce2 100755..100644 --- a/server-tools/instance-manager/CMakeLists.txt +++ b/server-tools/instance-manager/CMakeLists.txt @@ -8,7 +8,8 @@ INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/sql ADD_EXECUTABLE(mysqlmanager buffer.cc command.cc commands.cc guardian.cc instance.cc instance_map.cc instance_options.cc listener.cc log.cc manager.cc messages.cc mysql_connection.cc mysqlmanager.cc options.cc parse.cc parse_output.cc priv.cc protocol.cc - thread_registry.cc user_map.cc IMService.cpp WindowsService.cpp + thread_registry.cc user_map.cc imservice.cpp windowsservice.cpp + user_management_commands.cc ../../sql/net_serv.cc ../../sql-common/pack.c ../../sql/password.c ../../sql/sql_state.c ../../sql-common/client.c ../../libmysql/get_password.c ../../libmysql/errmsg.c) diff --git a/server-tools/instance-manager/IMService.cpp b/server-tools/instance-manager/IMService.cpp index b7ea8e7eb81..c8968fe9c16 100644 --- a/server-tools/instance-manager/IMService.cpp +++ b/server-tools/instance-manager/IMService.cpp @@ -20,25 +20,24 @@ IMService::~IMService(void) void IMService::Stop() { ReportStatus(SERVICE_STOP_PENDING); - - // stop the IM work + + /* stop the IM work */ raise(SIGTERM); } void IMService::Run(DWORD argc, LPTSTR *argv) { - // report to the SCM that we're about to start + /* report to the SCM that we're about to start */ ReportStatus((DWORD)SERVICE_START_PENDING); - Options o; - o.load(argc, argv); - - // init goes here + Options::load(argc, argv); + + /* init goes here */ ReportStatus((DWORD)SERVICE_RUNNING); - // wait for main loop to terminate - manager(o); - o.cleanup(); + /* wait for main loop to terminate */ + (void) Manager::main(); + Options::cleanup(); } void IMService::Log(const char *msg) @@ -46,37 +45,50 @@ void IMService::Log(const char *msg) log_info(msg); } -int HandleServiceOptions(Options options) +int HandleServiceOptions() { int ret_val= 0; IMService winService; - if (options.install_as_service) + if (Options::Service::install_as_service) { if (winService.IsInstalled()) - log_info("Service is already installed"); + log_info("Service is already installed."); else if (winService.Install()) - log_info("Service installed successfully"); + log_info("Service installed successfully."); else { - log_info("Service failed to install"); + log_error("Service failed to install."); ret_val= 1; } } - else if (options.remove_service) + else if (Options::Service::remove_service) { if (! winService.IsInstalled()) - log_info("Service is not installed"); + log_info("Service is not installed."); else if (winService.Remove()) - log_info("Service removed successfully"); + log_info("Service removed successfully."); else { - log_info("Service failed to remove"); + log_error("Service failed to remove."); ret_val= 1; } } else - ret_val= !winService.Init(); + { + log_info("Initializing Instance Manager service..."); + + if (!winService.Init()) + { + log_error("Service failed to initialize."); + fprintf(stderr, + "The service should be started by Windows Service Manager.\n" + "The MySQL Manager should be started with '--standalone'\n" + "to run from command line."); + ret_val= 1; + } + } + return ret_val; } diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h index cad38bebdaf..aacc957e195 100644 --- a/server-tools/instance-manager/IMService.h +++ b/server-tools/instance-manager/IMService.h @@ -1,7 +1,25 @@ +/* + Copyright (C) 2006 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 +*/ + #pragma once #include "windowsservice.h" -class IMService : public WindowsService +class IMService: public WindowsService { public: IMService(void); @@ -12,3 +30,5 @@ protected: void Stop(); void Run(DWORD argc, LPTSTR *argv); }; + +extern int HandleServiceOptions(); diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am index b1d77506efa..2d2365caa44 100644 --- a/server-tools/instance-manager/Makefile.am +++ b/server-tools/instance-manager/Makefile.am @@ -18,8 +18,7 @@ INCLUDES= @ZLIB_INCLUDES@ -I$(top_srcdir)/include \ @openssl_includes@ -I$(top_builddir)/include DEFS= -DMYSQL_INSTANCE_MANAGER -DMYSQL_SERVER -EXTRA_DIST = IMService.cpp IMService.h WindowsService.cpp WindowsService.h \ - CMakeLists.txt + # As all autoconf variables depend from ${prefix} and being resolved only when # make is run, we can not put these defines to a header file (e.g. to # default_options.h, generated from default_options.h.in) @@ -62,6 +61,8 @@ client_settings.h: libexec_PROGRAMS= mysqlmanager +mysqlmanager_CXXFLAGS= -Wall -W + mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ manager.h manager.cc log.h log.cc \ thread_registry.h thread_registry.cc \ @@ -77,9 +78,13 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ guardian.cc guardian.h \ parse_output.cc parse_output.h \ mysql_manager_error.h \ - portability.h + portability.h \ + exit_codes.h \ + user_management_commands.h \ + user_management_commands.cc -mysqlmanager_LDADD= liboptions.la \ +mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ + liboptions.la \ libnet.a \ $(top_builddir)/vio/libvio.a \ $(top_builddir)/mysys/libmysys.a \ @@ -87,6 +92,8 @@ mysqlmanager_LDADD= liboptions.la \ $(top_builddir)/dbug/libdbug.a \ @openssl_libs@ @yassl_libs@ @ZLIB_LIBS@ +EXTRA_DIST = WindowsService.cpp WindowsService.h IMService.cpp \ + IMService.h CMakeLists.txt tags: ctags -R *.h *.cc diff --git a/server-tools/instance-manager/WindowsService.cpp b/server-tools/instance-manager/WindowsService.cpp index 192045b7a4c..8a36a2f2fdd 100644 --- a/server-tools/instance-manager/WindowsService.cpp +++ b/server-tools/instance-manager/WindowsService.cpp @@ -7,9 +7,9 @@ static WindowsService *gService; WindowsService::WindowsService(void) : statusCheckpoint(0), serviceName(NULL), - inited(false), + inited(FALSE), dwAcceptedControls(SERVICE_ACCEPT_STOP), - debugging(false) + debugging(FALSE) { gService= this; status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; @@ -22,11 +22,12 @@ WindowsService::~WindowsService(void) BOOL WindowsService::Install() { - bool ret_val= false; + bool ret_val= FALSE; SC_HANDLE newService; SC_HANDLE scm; - if (IsInstalled()) return true; + if (IsInstalled()) + return TRUE; // determine the name of the currently executing file char szFilePath[_MAX_PATH]; @@ -34,7 +35,7 @@ BOOL WindowsService::Install() // open a connection to the SCM if (!(scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE))) - return false; + return FALSE; newService= CreateService(scm, serviceName, displayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, @@ -45,7 +46,7 @@ BOOL WindowsService::Install() if (newService) { CloseServiceHandle(newService); - ret_val= true; + ret_val= TRUE; } CloseServiceHandle(scm); @@ -56,34 +57,35 @@ BOOL WindowsService::Init() { assert(serviceName != NULL); - if (inited) return true; + if (inited) + return TRUE; SERVICE_TABLE_ENTRY stb[] = { { (LPSTR)serviceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, { NULL, NULL } }; - inited= true; + inited= TRUE; return StartServiceCtrlDispatcher(stb); //register with the Service Manager } BOOL WindowsService::Remove() { - bool ret_val= false; + bool ret_val= FALSE; - if (! IsInstalled()) - return true; + if (!IsInstalled()) + return TRUE; // open a connection to the SCM SC_HANDLE scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE); - if (! scm) - return false; + if (!scm) + return FALSE; SC_HANDLE service= OpenService(scm, serviceName, DELETE); if (service) { if (DeleteService(service)) - ret_val= true; + ret_val= TRUE; DWORD dw= ::GetLastError(); CloseServiceHandle(service); } @@ -116,7 +118,8 @@ void WindowsService::SetAcceptedControls(DWORD acceptedControls) BOOL WindowsService::ReportStatus(DWORD currentState, DWORD waitHint, DWORD dwError) { - if(debugging) return TRUE; + if (debugging) + return TRUE; if(currentState == SERVICE_START_PENDING) status.dwControlsAccepted= 0; diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h index 1a034ce1351..3af7cdf39a7 100644 --- a/server-tools/instance-manager/WindowsService.h +++ b/server-tools/instance-manager/WindowsService.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2006 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 +*/ + #pragma once class WindowsService diff --git a/server-tools/instance-manager/buffer.cc b/server-tools/instance-manager/buffer.cc index 8039ab24481..e1a57a53087 100644 --- a/server-tools/instance-manager/buffer.cc +++ b/server-tools/instance-manager/buffer.cc @@ -27,7 +27,7 @@ const uint Buffer::MAX_BUFFER_SIZE= 16777216; /* Puts the given string to the buffer. - SYNOPSYS + SYNOPSIS append() position start position in the buffer string string to be put in the buffer @@ -59,7 +59,7 @@ int Buffer::append(uint position, const char *string, uint len_arg) Checks whether the current buffer size is ok to put a string of the length "len_arg" starting from "position" and reallocs it if no. - SYNOPSYS + SYNOPSIS reserve() position the number starting byte on the buffer to store a buffer len_arg the length of the string. diff --git a/server-tools/instance-manager/command.cc b/server-tools/instance-manager/command.cc index f76366d5661..0ada11e8865 100644 --- a/server-tools/instance-manager/command.cc +++ b/server-tools/instance-manager/command.cc @@ -19,10 +19,12 @@ #endif #include "command.h" +#include "manager.h" -Command::Command(Instance_map *instance_map_arg) - :instance_map(instance_map_arg) +Command::Command() + :guardian(Manager::get_guardian()), + instance_map(Manager::get_instance_map()) {} Command::~Command() diff --git a/server-tools/instance-manager/command.h b/server-tools/instance-manager/command.h index b84cc6a8e9e..e5f7d6e97e6 100644 --- a/server-tools/instance-manager/command.h +++ b/server-tools/instance-manager/command.h @@ -22,10 +22,13 @@ #pragma interface #endif -/* Class responsible for allocation of im commands. */ +/* Class responsible for allocation of IM commands. */ +class Guardian; class Instance_map; +struct st_net; + /* Command - entry point for any command. GangOf4: 'Command' design pattern @@ -34,13 +37,24 @@ class Instance_map; class Command { public: - Command(Instance_map *instance_map_arg= 0); + Command(); virtual ~Command(); - /* method of executing: */ - virtual int execute(struct st_net *net, ulong connection_id) = 0; + /* + This operation incapsulates behaviour of the command. + + SYNOPSIS + net The network connection to the client. + connection_id Client connection ID + + RETURN + 0 On success + non 0 On error. Client error code is returned. + */ + virtual int execute(st_net *net, ulong connection_id) = 0; protected: + Guardian *guardian; Instance_map *instance_map; }; diff --git a/server-tools/instance-manager/commands.cc b/server-tools/instance-manager/commands.cc index 2c9d3236720..15738f8ebb3 100644 --- a/server-tools/instance-manager/commands.cc +++ b/server-tools/instance-manager/commands.cc @@ -14,35 +14,52 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + #include "commands.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql.h> +#include <my_dir.h> + +#include "buffer.h" +#include "guardian.h" #include "instance_map.h" +#include "log.h" #include "messages.h" #include "mysqld_error.h" #include "mysql_manager_error.h" -#include "protocol.h" -#include "buffer.h" #include "options.h" +#include "priv.h" +#include "protocol.h" -#include <m_string.h> -#include <mysql.h> -#include <my_dir.h> + +/* + modify_defaults_to_im_error -- a map of error codes of + mysys::modify_defaults_file() into Instance Manager error codes. +*/ + +static const int modify_defaults_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, + ER_ACCESS_OPTION_FILE }; /* - Add a string to a buffer + Add a string to a buffer. - SYNOPSYS + SYNOPSIS put_to_buff() buff buffer to add the string str string to add - uint offset in the buff to add a string + position offset in the buff to add a string DESCRIPTION Function to add a string to the buffer. It is different from - store_to_protocol_packet, which is used in the protocol.cc. The last - one also stores the length of the string in a special way. + store_to_protocol_packet, which is used in the protocol.cc. + The last one also stores the length of the string in a special way. This is required for MySQL client/server protocol support only. RETURN @@ -50,7 +67,6 @@ 1 - error occured */ - static inline int put_to_buff(Buffer *buff, const char *str, uint *position) { uint len= strlen(str); @@ -62,733 +78,1621 @@ static inline int put_to_buff(Buffer *buff, const char *str, uint *position) } -/* implementation for Show_instances: */ +static int parse_version_number(const char *version_str, char *version, + uint version_size) +{ + const char *start= version_str; + const char *end; + // skip garbage + while (!my_isdigit(default_charset_info, *start)) + start++; -/* - The method sends a list of instances in the instance map to the client. + end= start; + // skip digits and dots + while (my_isdigit(default_charset_info, *end) || *end == '.') + end++; - SYNOPSYS - Show_instances::execute() - net The network connection to the client. - connection_id Client connection ID + if ((uint)(end - start) >= version_size) + return -1; - RETURN - 0 - ok - 1 - error occured + strncpy(version, start, end-start); + version[end-start]= '\0'; + + return 0; +} + + +/************************************************************************** + Implementation of Instance_name. +**************************************************************************/ + +Instance_name::Instance_name(const LEX_STRING *name) +{ + str.str= str_buffer; + str.length= name->length; + + if (str.length > MAX_INSTANCE_NAME_SIZE - 1) + str.length= MAX_INSTANCE_NAME_SIZE - 1; + + strmake(str.str, name->str, str.length); +} + +/************************************************************************** + Implementation of Show_instances. +**************************************************************************/ + +/* + Implementation of SHOW INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instances::execute(struct st_net *net, ulong connection_id) +int Show_instances::execute(st_net *net, ulong /* connection_id */) { - Buffer send_buff; /* buffer for packets */ - LIST name, status; - NAME_WITH_LENGTH name_field, status_field; + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net))) + return err_code; + + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instances::write_header(st_net *net) +{ + LIST name, state; + LEX_STRING name_field, state_field; LIST *field_list; - uint position=0; - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - field_list= list_add(NULL, &status); + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + field_list= list_add(NULL, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + + +int Show_instances::write_data(st_net *net) +{ + my_bool err_status= FALSE; + + Instance *instance; + Instance_map::Iterator iterator(instance_map); + instance_map->guardian->lock(); + instance_map->lock(); + + while ((instance= iterator.next())) { - Instance *instance; - Instance_map::Iterator iterator(instance_map); + Buffer send_buf; /* buffer for packets */ + uint pos= 0; + + const char *instance_name= instance->options.instance_name.str; + const char *state_name= instance_map->get_instance_state_name(instance); - instance_map->lock(); - while ((instance= iterator.next())) + if (store_to_protocol_packet(&send_buf, instance_name, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + my_net_write(net, send_buf.buffer, pos)) { - position= 0; - store_to_protocol_packet(&send_buff, instance->options.instance_name, - &position); - if (instance->is_running()) - store_to_protocol_packet(&send_buff, (char*) "online", &position); - else - store_to_protocol_packet(&send_buff, (char*) "offline", &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + err_status= TRUE; + break; } - instance_map->unlock(); } - if (send_eof(net)) - goto err; - if (net_flush(net)) - goto err; - return 0; -err: - return ER_OUT_OF_RESOURCES; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return err_status ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Flush_instances: */ +/************************************************************************** + Implementation of Flush_instances. +**************************************************************************/ + +/* + Implementation of FLUSH INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation + ER_THERE_IS_ACTIVE_INSTACE If there is an active instance +*/ -int Flush_instances::execute(struct st_net *net, ulong connection_id) +int Flush_instances::execute(st_net *net, ulong connection_id) { - if (instance_map->flush_instances() || - net_send_ok(net, connection_id, NULL)) + instance_map->guardian->lock(); + instance_map->lock(); + + if (instance_map->is_there_active_instance()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); + return ER_THERE_IS_ACTIVE_INSTACE; + } + + if (instance_map->flush_instances()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); return ER_OUT_OF_RESOURCES; + } - return 0; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return net_send_ok(net, connection_id, NULL) ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Show_instance_status: */ +/************************************************************************** + Implementation of Abstract_instance_cmd. +**************************************************************************/ -Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Abstract_instance_cmd::Abstract_instance_cmd(const LEX_STRING *instance_name_arg) + :instance_name(instance_name_arg) { - Instance *instance; + /* + MT-NOTE: we can not make a search for Instance object here, + because it can dissappear after releasing the lock. + */ +} + + +int Abstract_instance_cmd::execute(st_net *net, ulong connection_id) +{ + int err_code; + + instance_map->lock(); - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + { + Instance *instance= instance_map->find(get_instance_name()); + + if (!instance) + { + instance_map->unlock(); + return ER_BAD_INSTANCE_NAME; + } + + err_code= execute_impl(net, instance); + } + + instance_map->unlock(); + + if (!err_code) + err_code= send_ok_response(net, connection_id); + + return err_code; } -/* - The method sends a table with a status of requested instance to the client. +/************************************************************************** + Implementation of Show_instance_status. +**************************************************************************/ - SYNOPSYS - Show_instance_status::do_command() - net The network connection to the client. - instance_name The name of the instance. +Show_instance_status::Show_instance_status(const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) +{ +} - RETURN - 0 - ok - 1 - error occured + +/* + Implementation of SHOW INSTANCE STATUS statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ +int Show_instance_status::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; -int Show_instance_status::execute(struct st_net *net, - ulong connection_id) + return 0; +} + + +int Show_instance_status::send_ok_response(st_net *net, + ulong /* connection_id */) { - enum { MAX_VERSION_LENGTH= 40 }; - Buffer send_buff; /* buffer for packets */ - LIST name, status, version; + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_status::write_header(st_net *net) +{ + LIST name, state, version, version_number, mysqld_compatible; LIST *field_list; - NAME_WITH_LENGTH name_field, status_field, version_field; - uint position=0; + LEX_STRING name_field, state_field, version_field, + version_number_field, mysqld_compatible_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - version_field.name= (char*) "version"; + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + version_field.str= (char *) "version"; version_field.length= MAX_VERSION_LENGTH; version.data= &version_field; - field_list= list_add(NULL, &version); - field_list= list_add(field_list, &status); + + version_number_field.str= (char *) "version_number"; + version_number_field.length= MAX_VERSION_LENGTH; + version_number.data= &version_number_field; + + mysqld_compatible_field.str= (char *) "mysqld_compatible"; + mysqld_compatible_field.length= DEFAULT_FIELD_LENGTH; + mysqld_compatible.data= &mysqld_compatible_field; + + field_list= list_add(NULL, &mysqld_compatible); + field_list= list_add(field_list, &version); + field_list= list_add(field_list, &version_number); + field_list= list_add(field_list, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + +int Show_instance_status::write_data(st_net *net, Instance *instance) +{ + Buffer send_buf; /* buffer for packets */ + char version_num_buf[MAX_VERSION_LENGTH]; + uint pos= 0; + + const char *state_name; + const char *version_tag= "unknown"; + const char *version_num= "unknown"; + const char *mysqld_compatible_status; + + instance_map->guardian->lock(); + state_name= instance_map->get_instance_state_name(instance); + mysqld_compatible_status= instance->is_mysqld_compatible() ? "yes" : "no"; + instance_map->guardian->unlock(); + + if (instance->options.mysqld_version) { - Instance *instance; - - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) - goto err; - if (instance->is_running()) - store_to_protocol_packet(&send_buff, (char*) "online", &position); - else - store_to_protocol_packet(&send_buff, (char*) "offline", &position); - - if (instance->options.mysqld_version) - store_to_protocol_packet(&send_buff, instance->options.mysqld_version, - &position); - else - store_to_protocol_packet(&send_buff, (char*) "unknown", &position); - - - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + + if (parse_version_number(instance->options.mysqld_version, version_num_buf, + sizeof(version_num_buf))) + return ER_OUT_OF_RESOURCES; + + version_num= version_num_buf; + version_tag= instance->options.mysqld_version; } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buf, get_instance_name()->str, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + store_to_protocol_packet(&send_buf, version_num, &pos) || + store_to_protocol_packet(&send_buf, version_tag, &pos) || + store_to_protocol_packet(&send_buf, mysqld_compatible_status, &pos) || + my_net_write(net, send_buf.buffer, (uint) pos)) + { + return ER_OUT_OF_RESOURCES; + } return 0; +} + -err: - return ER_OUT_OF_RESOURCES; +/************************************************************************** + Implementation of Show_instance_options. +**************************************************************************/ + +Show_instance_options::Show_instance_options( + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) +{ } -/* Implementation for Show_instance_options */ +/* + Implementation of SHOW INSTANCE OPTIONS statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ -Show_instance_options::Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len): - Command(instance_map_arg) +int Show_instance_options::execute_impl(st_net *net, Instance *instance) { - Instance *instance; + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + return 0; } -int Show_instance_options::execute(struct st_net *net, ulong connection_id) +int Show_instance_options::send_ok_response(st_net *net, + ulong /* connection_id */) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_options::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name, option; LIST *field_list; - NAME_WITH_LENGTH name_field, option_field; - uint position=0; + LEX_STRING name_field, option_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "option_name"; + name_field.str= (char *) "option_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - option_field.name= (char*) "value"; + + option_field.str= (char *) "value"; option_field.length= DEFAULT_FIELD_LENGTH; option.data= &option_field; + field_list= list_add(NULL, &option); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + +int Show_instance_options::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + uint pos= 0; + + if (store_to_protocol_packet(&send_buff, "instance_name", &pos) || + store_to_protocol_packet(&send_buff, get_instance_name()->str, &pos) || + my_net_write(net, send_buff.buffer, pos)) { - Instance *instance; - - if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) - goto err; - store_to_protocol_packet(&send_buff, (char*) "instance_name", &position); - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - if ((instance->options.mysqld_path)) - { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "mysqld-path", &position); - store_to_protocol_packet(&send_buff, - (char*) instance->options.mysqld_path, - &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + return ER_OUT_OF_RESOURCES; + } + + /* Loop through the options. */ + + for (int i= 0; i < instance->options.get_num_options(); i++) + { + Named_value option= instance->options.get_option(i); + const char *option_value= option.get_value()[0] ? option.get_value() : ""; + + pos= 0; - if ((instance->options.nonguarded)) + if (store_to_protocol_packet(&send_buff, option.get_name(), &pos) || + store_to_protocol_packet(&send_buff, option_value, &pos) || + my_net_write(net, send_buff.buffer, pos)) { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "nonguarded", &position); - store_to_protocol_packet(&send_buff, "", &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + return ER_OUT_OF_RESOURCES; } + } + + return 0; +} + + +/************************************************************************** + Implementation of Start_instance. +**************************************************************************/ + +Start_instance::Start_instance(const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) +{ +} + + +/* + Implementation of START INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Start_instance::execute_impl(st_net * /* net */, Instance *instance) +{ + int err_code; + + if ((err_code= instance->start())) + return err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->guard(instance); + + return 0; +} + + +int Start_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance started")) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation of Stop_instance. +**************************************************************************/ + +Stop_instance::Stop_instance(const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) +{ +} + + +/* + Implementation of STOP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Stop_instance::execute_impl(st_net * /* net */, Instance *instance) +{ + int err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + return 0; +} + + +int Stop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation for Create_instance. +**************************************************************************/ + +Create_instance::Create_instance(const LEX_STRING *instance_name_arg) + :instance_name(instance_name_arg) +{ +} + + +/* + This operation initializes Create_instance object. + + SYNOPSIS + text [IN/OUT] a pointer to the text containing instance options. + + RETURN + FALSE On success. + TRUE On error. +*/ + +bool Create_instance::init(const char **text) +{ + return options.init() || parse_args(text); +} + + +/* + This operation parses CREATE INSTANCE options. + + SYNOPSIS + text [IN/OUT] a pointer to the text containing instance options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Create_instance::parse_args(const char **text) +{ + uint len; + + /* Check if we have something (and trim leading spaces). */ - /* loop through the options stored in DYNAMIC_ARRAY */ - for (uint i= 0; i < instance->options.options_array.elements; i++) + get_word(text, &len, NONSPACE); + + if (len == 0) + return FALSE; /* OK: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + /* Looking for equal sign. */ + + skip_spaces(text); + + if (**text == '=') { - char *tmp_option, *option_value; - get_dynamic(&(instance->options.options_array), (gptr) &tmp_option, i); - option_value= strchr(tmp_option, '='); - /* split the option string into two parts if it has a value */ + ++(*text); /* Skip an equal sign. */ - position= 0; - if (option_value != NULL) + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ + + if (**text != '\'' && **text != '"') { - *option_value= 0; - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, option_value + 1, &position); - /* join name and the value into the same option again */ - *option_value= '='; + /* Option value is a simple token. */ + + LEX_STRING option_value; + + get_word(text, &option_value.length, ALPHANUM); + + if (option_value.length == 0) + return TRUE; /* internal parser error. */ + + option_value.str= (char *) *text; + *text+= option_value.length; + + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ } else { - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, "", &position); + /* Option value is a string. */ + + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ + + *text+= len; } + } - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_LEN("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_value_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); + + if (options.add_element(&option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); } +} - if (send_eof(net) || net_flush(net)) - goto err; - return 0; +/* + Implementation of CREATE INSTANCE statement. + + Possible error codes: + ER_MALFORMED_INSTANCE_NAME Instance name is malformed + ER_CREATE_EXISTING_INSTANCE There is an instance with the given name + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ -err: - return ER_OUT_OF_RESOURCES; +int Create_instance::execute(st_net *net, ulong connection_id) +{ + int err_code; + + /* Check that the name is valid and there is no instance with such name. */ + + if (!Instance::is_name_valid(get_instance_name())) + return ER_MALFORMED_INSTANCE_NAME; + + /* + NOTE: In order to prevent race condition, we should perform all operations + on under acquired lock. + */ + + instance_map->lock(); + + if (instance_map->find(get_instance_name())) + { + instance_map->unlock(); + return ER_CREATE_EXISTING_INSTANCE; + } + + if ((err_code= instance_map->create_instance(get_instance_name(), &options))) + { + instance_map->unlock(); + return err_code; + } + + if ((err_code= create_instance_in_file(get_instance_name(), &options))) + { + Instance *instance= instance_map->find(get_instance_name()); + + if (instance) + instance_map->remove_instance(instance); /* instance is deleted here. */ + + instance_map->unlock(); + return err_code; + } + + /* That's all. */ + + instance_map->unlock(); + + /* Send the result. */ + + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; } -/* Implementation for Start_instance */ +/************************************************************************** + Implementation for Drop_instance. +**************************************************************************/ -Start_instance::Start_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Drop_instance::Drop_instance(const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) { - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; } -int Start_instance::execute(struct st_net *net, ulong connection_id) +/* + Implementation of DROP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_DROP_ACTIVE_INSTANCE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Drop_instance::execute_impl(st_net * /* net */, Instance *instance) { - uint err_code; - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ - else - { - if ((err_code= instance->start())) - return err_code; + int err_code; + + /* Check that the instance is offline. */ + + if (instance_map->guardian->is_active(instance)) + return ER_DROP_ACTIVE_INSTANCE; - if (!(instance->options.nonguarded)) - instance_map->guardian->guard(instance); + err_code= modify_defaults_file(Options::Main::config_file, NULL, NULL, + get_instance_name()->str, MY_REMOVE_SECTION); + DBUG_ASSERT(err_code >= 0 && err_code <= 2); - net_send_ok(net, connection_id, "Instance started"); - return 0; + if (err_code) + { + log_error("Can not remove instance '%s' from defaults file (%s). " + "Original error code: %d.", + (const char *) get_instance_name()->str, + (const char *) Options::Main::config_file, + (int) err_code); } + + if (err_code) + return modify_defaults_to_im_error[err_code]; + + /* Remove instance from the instance map hash and Guardian's list. */ + + if (!instance->options.nonguarded) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + instance_map->remove_instance(instance); + + return 0; +} + + +int Drop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance dropped")) + return ER_OUT_OF_RESOURCES; + + return 0; } -/* implementation for Show_instance_log: */ +/************************************************************************** + Implementation for Show_instance_log. +**************************************************************************/ -Show_instance_log::Show_instance_log(Instance_map *instance_map_arg, - const char *name, uint len, +Show_instance_log::Show_instance_log(const LEX_STRING *instance_name_arg, Log_type log_type_arg, - const char *size_arg, - const char *offset_arg) - :Command(instance_map_arg) + uint size_arg, uint offset_arg) + :Abstract_instance_cmd(instance_name_arg), + log_type(log_type_arg), + size(size_arg), + offset(offset_arg) { - Instance *instance; +} + - if (offset_arg != NULL) - offset= atoi(offset_arg); - else - offset= 0; - size= atoi(size_arg); - log_type= log_type_arg; +/* + Implementation of SHOW INSTANCE LOG statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OFFSET_ERROR We were requested to read negative number of + bytes from the log + ER_NO_SUCH_LOG The specified type of log is not available for + the given instance + ER_GUESS_LOGFILE IM wasn't able to figure out the log + placement, while it is enabled. Probably user + should specify the path to the logfile + explicitly. + ER_OPEN_LOGFILE Cannot open the logfile + ER_READ_FILE Cannot read the logfile + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Show_instance_log::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= check_params(instance))) + return err_code; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; } +int Show_instance_log::send_ok_response(st_net *net, + ulong /* connection_id */) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; -/* - Open the logfile, read requested part of the log and send the info - to the client. + return 0; +} - SYNOPSYS - Show_instance_log::execute() - net The network connection to the client. - connection_id Client connection ID - DESCRIPTION +int Show_instance_log::check_params(Instance *instance) +{ + const char *logpath= instance->options.logs[log_type]; - Send a table with the content of the log requested. The function also - deals with errro handling, to be verbose. + /* Cannot read negative number of bytes. */ - RETURN - ER_OFFSET_ERROR We were requested to read negative number of bytes - from the log - ER_NO_SUCH_LOG The kind log being read is not enabled in the instance - ER_GUESS_LOGFILE IM wasn't able to figure out the log placement, while - it is enabled. Probably user should specify the path - to the logfile explicitly. - ER_OPEN_LOGFILE Cannot open the logfile - ER_READ_FILE Cannot read the logfile - ER_OUT_OF_RESOURCES We weren't able to allocate some resources -*/ + if (offset > size) + return ER_OFFSET_ERROR; + + /* Instance has no such log. */ + + if (logpath == NULL) + return ER_NO_SUCH_LOG; + + if (*logpath == '\0') + return ER_GUESS_LOGFILE; + + return 0; +} -int Show_instance_log::execute(struct st_net *net, ulong connection_id) + +int Show_instance_log::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name; LIST *field_list; - NAME_WITH_LENGTH name_field; - uint position= 0; + LEX_STRING name_field; + + /* Create list of the fields to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Log"; + name_field.str= (char *) "Log"; name_field.length= DEFAULT_FIELD_LENGTH; - name.data= &name_field; - field_list= list_add(NULL, &name); - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + name.data= &name_field; - /* cannot read negative number of bytes */ - if (offset > size) - return ER_OFFSET_ERROR; + field_list= list_add(NULL, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - { - Instance *instance; - const char *logpath; - File fd; - if ((instance= instance_map->find(instance_name, - strlen(instance_name))) == NULL) - goto err; +int Show_instance_log::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + uint pos= 0; - logpath= instance->options.logs[log_type]; + const char *logpath= instance->options.logs[log_type]; + File fd; - /* Instance has no such log */ - if (logpath == NULL) - return ER_NO_SUCH_LOG; + size_t buff_size; + int read_len; - if (*logpath == '\0') - return ER_GUESS_LOGFILE; + MY_STAT file_stat; + Buffer read_buff; + if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) <= 0) + return ER_OPEN_LOGFILE; - if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) >= 0) - { - size_t buff_size; - int read_len; - /* calculate buffer size */ - MY_STAT file_stat; - Buffer read_buff; + /* my_fstat doesn't use the flag parameter */ + if (my_fstat(fd, &file_stat, MYF(0))) + { + close(fd); + return ER_OUT_OF_RESOURCES; + } - /* my_fstat doesn't use the flag parameter */ - if (my_fstat(fd, &file_stat, MYF(0))) - goto err; + /* calculate buffer size */ + buff_size= (size - offset); - buff_size= (size - offset); + read_buff.reserve(0, buff_size); - read_buff.reserve(0, buff_size); + /* read in one chunk */ + read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); - /* read in one chunk */ - read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); + if ((read_len= my_read(fd, (byte*) read_buff.buffer, + buff_size, MYF(0))) < 0) + { + close(fd); + return ER_READ_FILE; + } - if ((read_len= my_read(fd, (byte*) read_buff.buffer, - buff_size, MYF(0))) < 0) - return ER_READ_FILE; - store_to_protocol_packet(&send_buff, read_buff.buffer, - &position, read_len); - close(fd); - } - else - return ER_OPEN_LOGFILE; + close(fd); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + if (store_to_protocol_packet(&send_buff, read_buff.buffer, &pos, read_len) || + my_net_write(net, send_buff.buffer, pos)) + { + return ER_OUT_OF_RESOURCES; } - if (send_eof(net) || net_flush(net)) - goto err; - return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* implementation for Show_instance_log_files: */ +/************************************************************************** + Implementation of Show_instance_log_files. +**************************************************************************/ Show_instance_log_files::Show_instance_log_files - (Instance_map *instance_map_arg, const char *name, uint len) - :Command(instance_map_arg) + (const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_name_arg) { - Instance *instance; - - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; } /* - The method sends a table with a list of log files - used by the instance. - - SYNOPSYS - Show_instance_log_files::execute() - net The network connection to the client. - connection_id The ID of the client connection + Implementation of SHOW INSTANCE LOG FILES statement. - RETURN - ER_BAD_INSTANCE_NAME The instance name specified is not valid - ER_OUT_OF_RESOURCES some error occured - 0 - ok + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instance_log_files::execute(struct st_net *net, ulong connection_id) +int Show_instance_log_files::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} + + +int Show_instance_log_files::send_ok_response(st_net *net, + ulong /* connection_id */) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_log_files::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name, path, size; LIST *field_list; - NAME_WITH_LENGTH name_field, path_field, size_field; - uint position= 0; + LEX_STRING name_field, path_field, size_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Logfile"; + name_field.str= (char *) "Logfile"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - path_field.name= (char*) "Path"; + + path_field.str= (char *) "Path"; path_field.length= DEFAULT_FIELD_LENGTH; path.data= &path_field; - size_field.name= (char*) "File size"; + + size_field.str= (char *) "File size"; size_field.length= DEFAULT_FIELD_LENGTH; size.data= &size_field; + field_list= list_add(NULL, &size); field_list= list_add(field_list, &path); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - Instance *instance; - if ((instance= instance_map-> - find(instance_name, strlen(instance_name))) == NULL) - goto err; +int Show_instance_log_files::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + /* + We have alike structure in instance_options.cc. We use such to be able + to loop through the options, which we need to handle in some common way. + */ + struct log_files_st { + const char *name; + const char *value; + } logs[]= + { + {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, + {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, + {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, + {NULL, NULL} + }; + struct log_files_st *log_files; + + for (log_files= logs; log_files->name; log_files++) + { + if (!log_files->value) + continue; + + struct stat file_stat; /* - We have alike structure in instance_options.cc. We use such to be able - to loop through the options, which we need to handle in some common way. + Save some more space for the log file names. In fact all + we need is strlen("GENERAL_LOG") + 1 */ - struct log_files_st - { - const char *name; - const char *value; - } logs[]= - { - {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, - {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, - {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, - {NULL, NULL} - }; - struct log_files_st *log_files; - - for (log_files= logs; log_files->name; log_files++) + enum { LOG_NAME_BUFFER_SIZE= 20 }; + char buff[LOG_NAME_BUFFER_SIZE]; + + uint pos= 0; + + const char *log_path= ""; + const char *log_size= "0"; + + if (!stat(log_files->value, &file_stat) && + MY_S_ISREG(file_stat.st_mode)) { - if (log_files->value != NULL) - { - struct stat file_stat; - /* - Save some more space for the log file names. In fact all - we need is srtlen("GENERAL_LOG") + 1 - */ - enum { LOG_NAME_BUFFER_SIZE= 20 }; - char buff[LOG_NAME_BUFFER_SIZE]; - - position= 0; - /* store the type of the log in the send buffer */ - store_to_protocol_packet(&send_buff, log_files->name, &position); - if (stat(log_files->value, &file_stat)) - { - store_to_protocol_packet(&send_buff, "", &position); - store_to_protocol_packet(&send_buff, (char*) "0", &position); - } - else if (MY_S_ISREG(file_stat.st_mode)) - { - store_to_protocol_packet(&send_buff, - (char*) log_files->value, - &position); - int10_to_str(file_stat.st_size, buff, 10); - store_to_protocol_packet(&send_buff, (char*) buff, &position); - } - - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + int10_to_str(file_stat.st_size, buff, 10); + + log_path= log_files->value; + log_size= buff; } - } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buff, log_files->name, &pos) || + store_to_protocol_packet(&send_buff, log_path, &pos) || + store_to_protocol_packet(&send_buff, log_size, &pos) || + my_net_write(net, send_buff.buffer, pos)) + return ER_OUT_OF_RESOURCES; + } return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* implementation for SET instance_name.option=option_value: */ +/************************************************************************** + Implementation of Abstract_option_cmd. +**************************************************************************/ -Set_option::Set_option(Instance_map *instance_map_arg, - const char *name, uint len, - const char *option_arg, uint option_len_arg, - const char *option_value_arg, uint option_value_len_arg) - :Command(instance_map_arg) +/* + Instance_options_list -- a data class representing a list of options for + some instance. +*/ + +class Instance_options_list { - Instance *instance; +public: + Instance_options_list(const LEX_STRING *instance_name_arg); - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - { - instance_name= instance->options.instance_name; +public: + bool init(); - /* add prefix for add_option */ - if ((option_len_arg < MAX_OPTION_LEN - 1) || - (option_value_len_arg < MAX_OPTION_LEN - 1)) - { - strmake(option, option_arg, option_len_arg); - strmake(option_value, option_value_arg, option_value_len_arg); - } - else - { - option[0]= 0; - option_value[0]= 0; - } - instance_name_len= len; - } - else + const LEX_STRING *get_instance_name() const { - instance_name= NULL; - instance_name_len= 0; + return instance_name.get_str(); } + +public: + /* + This member is set and used only in Abstract_option_cmd::execute_impl(). + Normally it is not used (and should not). + + The problem is that construction and execution of commands are made not + in one transaction (not under one lock session). So, we can not initialize + instance in constructor and use it in execution. + */ + Instance *instance; + + Named_value_arr options; + +private: + Instance_name instance_name; +}; + + +/**************************************************************************/ + +Instance_options_list::Instance_options_list( + const LEX_STRING *instance_name_arg) + :instance(NULL), + instance_name(instance_name_arg) +{ } -/* - The method sends a table with a list of log files - used by the instance. +bool Instance_options_list::init() +{ + return options.init(); +} - SYNOPSYS - Set_option::correct_file() - skip Skip the option, being searched while writing the result file. - That is, to delete it. - DESCRIPTION +/**************************************************************************/ + +C_MODE_START + +static byte* get_item_key(const byte* item, uint* len, + my_bool __attribute__((unused)) t) +{ + const Instance_options_list *lst= (const Instance_options_list *) item; + *len= lst->get_instance_name()->length; + return (byte *) lst->get_instance_name()->str; +} + +static void delete_item(void *item) +{ + delete (Instance_options_list *) item; +} + +C_MODE_END + + +/**************************************************************************/ + +Abstract_option_cmd::Abstract_option_cmd() + :initialized(FALSE) +{ +} + +Abstract_option_cmd::~Abstract_option_cmd() +{ + if (initialized) + hash_free(&instance_options_map); +} + + +bool Abstract_option_cmd::add_option(const LEX_STRING *instance_name, + Named_value *option) +{ + Instance_options_list *lst= get_instance_options_list(instance_name); + + if (!lst) + return TRUE; + + lst->options.add_element(option); + + return FALSE; +} + + +bool Abstract_option_cmd::init(const char **text) +{ + static const int INITIAL_HASH_SIZE= 16; + + if (hash_init(&instance_options_map, default_charset_info, + INITIAL_HASH_SIZE, 0, 0, get_item_key, delete_item, 0)) + return TRUE; + + if (parse_args(text)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +/* Correct the option file. The "skip" option is used to remove the found option. + SYNOPSIS + Abstract_option_cmd::correct_file() + skip Skip the option, being searched while writing the result file. + That is, to delete it. + RETURN - ER_OUT_OF_RESOURCES out of resources + 0 Success + ER_OUT_OF_RESOURCES Not enough resources to complete the operation ER_ACCESS_OPTION_FILE Cannot access the option file - 0 - ok */ -int Set_option::correct_file(int skip) +int Abstract_option_cmd::correct_file(Instance *instance, Named_value *option, + bool skip) { - static const int mysys_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, - ER_ACCESS_OPTION_FILE }; - int error; + int err_code= modify_defaults_file(Options::Main::config_file, + option->get_name(), + option->get_value(), + instance->get_name()->str, + skip); - error= modify_defaults_file(Options::config_file, option, - option_value, instance_name, skip); - DBUG_ASSERT(error >= 0 && error <= 2); + DBUG_ASSERT(err_code >= 0 && err_code <= 2); - return mysys_to_im_error[error]; + if (err_code) + { + log_error("Can not modify option (%s) in defaults file (%s). " + "Original error code: %d.", + (const char *) option->get_name(), + (const char *) Options::Main::config_file, + (int) err_code); + } + + return modify_defaults_to_im_error[err_code]; } /* - The method sets an option in the the default config file (/etc/my.cnf). - - SYNOPSYS - Set_option::do_command() - net The network connection to the client. - - RETURN - 0 - ok - 1 - error occured + Implementation of SET statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_INCOMPATIBLE_OPTION The specified option can not be set for + mysqld-compatible instance + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Set_option::do_command(struct st_net *net) +int Abstract_option_cmd::execute(st_net *net, ulong connection_id) { - int error; + int err_code; - /* we must hold the instance_map mutex while changing config file */ instance_map->lock(); - error= correct_file(FALSE); + + err_code= execute_impl(net, connection_id); + instance_map->unlock(); - return error; + return err_code; } -int Set_option::execute(struct st_net *net, ulong connection_id) +Instance_options_list * +Abstract_option_cmd::get_instance_options_list(const LEX_STRING *instance_name) { - if (instance_name != NULL) + Instance_options_list *lst= + (Instance_options_list *) hash_search(&instance_options_map, + (byte *) instance_name->str, + instance_name->length); + + if (!lst) { - int val; + lst= new Instance_options_list(instance_name); - val= do_command(net); + if (!lst) + return NULL; - if (val == 0) - net_send_ok(net, connection_id, NULL); + if (lst->init() || my_hash_insert(&instance_options_map, (byte *) lst)) + { + delete lst; + return NULL; + } + } + + return lst; +} - return val; + +int Abstract_option_cmd::execute_impl(st_net *net, ulong connection_id) +{ + int err_code= 0; + + /* Check that all the specified instances exist and are offline. */ + + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + lst->instance= instance_map->find(lst->get_instance_name()); + + if (!lst->instance) + return ER_BAD_INSTANCE_NAME; + + if (instance_map->guardian->is_active(lst->instance)) + return ER_INSTANCE_IS_ACTIVE; } - return ER_BAD_INSTANCE_NAME; + /* Perform command-specific (SET/UNSET) actions. */ + + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + for (int j= 0; j < lst->options.get_size(); ++j) + { + Named_value option= lst->options.get_element(j); + err_code= process_option(lst->instance, &option); + + if (err_code) + break; + } + + if (err_code) + break; + } + + if (err_code == 0) + net_send_ok(net, connection_id, NULL); + + return err_code; } -/* the only function from Unset_option we need to Implement */ +/************************************************************************** + Implementation of Set_option. +**************************************************************************/ -int Unset_option::do_command(struct st_net *net) +/* + This operation parses SET options. + + SYNOPSIS + text [IN/OUT] a pointer to the text containing options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Set_option::parse_args(const char **text) { - return correct_file(TRUE); -} + uint len; + + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + /* Looking for equal sign. */ + + skip_spaces(text); + + if (**text == '=') + { + ++(*text); /* Skip an equal sign. */ + + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ + + if (**text != '\'' && **text != '"') + { + /* Option value is a simple token. */ + + LEX_STRING option_value; + + get_word(text, &option_value.length, ALPHANUM); + + if (option_value.length == 0) + return TRUE; /* internal parser error. */ + + option_value.str= (char *) *text; + *text+= option_value.length; + + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ + } + else + { + /* Option value is a string. */ + + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ + + *text+= len; + } + } + + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_LEN("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_name_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } +} -/* Implementation for Stop_instance: */ -Stop_instance::Stop_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +int Set_option::process_option(Instance *instance, Named_value *option) { - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; + /* Check that the option is valid. */ + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option->get_name())) + { + log_error("IM-option (%s) can not be used " + "in the configuration of mysqld-compatible instance (%s).", + (const char *) option->get_name(), + (const char *) instance->get_name()->str); + return ER_INCOMPATIBLE_OPTION; + } + + /* Update the configuration file. */ + + int err_code= correct_file(instance, option, FALSE); + + if (err_code) + return err_code; + + /* Update the internal cache. */ + + if (instance->options.set_option(option)) + return ER_OUT_OF_RESOURCES; + + return 0; } -int Stop_instance::execute(struct st_net *net, ulong connection_id) +/************************************************************************** + Implementation of Unset_option. +**************************************************************************/ + +/* + This operation parses UNSET options. + + SYNOPSIS + text [IN/OUT] a pointer to the text containing options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Unset_option::parse_args(const char **text) { - uint err_code; + uint len; - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + /* Check if we have something (and trim leading spaces). */ - if (!(instance->options.nonguarded)) - instance_map->guardian->stop_guard(instance); + get_word(text, &len, NONSPACE); - if ((err_code= instance->stop())) + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); /* Skip a dot. */ + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + return TRUE; /* out of memory during parsing. */ + + { + LEX_STRING empty_str= { C_STRING_WITH_LEN("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } +} + + +/* + Implementation of UNSET statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance name specified is not valid + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Unset_option::process_option(Instance *instance, Named_value *option) +{ + /* Update the configuration file. */ + + int err_code= correct_file(instance, option, TRUE); + + if (err_code) return err_code; - net_send_ok(net, connection_id, NULL); + /* Update the internal cache. */ + + instance->options.unset_option(option->get_name()); + return 0; } -int Syntax_error::execute(struct st_net *net, ulong connection_id) +/************************************************************************** + Implementation of Syntax_error. +**************************************************************************/ + +int Syntax_error::execute(st_net * /* net */, ulong /* connection_id */) { return ER_SYNTAX_ERROR; } diff --git a/server-tools/instance-manager/commands.h b/server-tools/instance-manager/commands.h index bfd38d34889..8768aaab121 100644 --- a/server-tools/instance-manager/commands.h +++ b/server-tools/instance-manager/commands.h @@ -16,70 +16,128 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include <hash.h> + #include "command.h" #include "instance.h" #include "parse.h" +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + + /* Print all instances of this instance manager. - Grammar: SHOW ISTANCES + Grammar: SHOW INSTANCES */ -class Show_instances : public Command +class Show_instances: public Command { public: - Show_instances(Instance_map *instance_map_arg): Command(instance_map_arg) - {} + Show_instances() + { } - int execute(struct st_net *net, ulong connection_id); +public: + int execute(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net); }; /* - Reread configuration file and refresh instance map. + Reread configuration file and refresh internal cache. Grammar: FLUSH INSTANCES */ -class Flush_instances : public Command +class Flush_instances: public Command { public: - Flush_instances(Instance_map *instance_map_arg): Command(instance_map_arg) - {} + Flush_instances() + { } + +public: + int execute(st_net *net, ulong connection_id); +}; + + +/* + Abstract class for Instance-specific commands. +*/ + +class Abstract_instance_cmd: public Command +{ +public: + Abstract_instance_cmd(const LEX_STRING *instance_name_arg); + +public: + virtual int execute(st_net *net, ulong connection_id); + +protected: + /* MT-NOTE: this operation is called under acquired Instance_map's lock. */ + virtual int execute_impl(st_net *net, Instance *instance) = 0; + + /* + This operation is invoked on successful return of execute_impl() and is + intended to send closing data. + + MT-NOTE: this operation is called under released Instance_map's lock. + */ + virtual int send_ok_response(st_net *net, ulong connection_id) = 0; - int execute(struct st_net *net, ulong connection_id); +protected: + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + Instance_name instance_name; }; /* Print status of an instance. - Grammar: SHOW ISTANCE STATUS <instance_name> + Grammar: SHOW INSTANCE STATUS <instance_name> */ -class Show_instance_status : public Command +class Show_instance_status: public Abstract_instance_cmd { public: + Show_instance_status(const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); - Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Print options if chosen instance. + Print options of chosen instance. Grammar: SHOW INSTANCE OPTIONS <instance_name> */ -class Show_instance_options : public Command +class Show_instance_options: public Abstract_instance_cmd { public: + Show_instance_options(const LEX_STRING *instance_name_arg); - Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len); +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; @@ -88,14 +146,14 @@ public: Grammar: START INSTANCE <instance_name> */ -class Start_instance : public Command +class Start_instance: public Abstract_instance_cmd { public: - Start_instance(Instance_map *instance_map_arg, const char *name, uint len); + Start_instance(const LEX_STRING *instance_name_arg); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; - Instance *instance; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; @@ -104,33 +162,91 @@ public: Grammar: STOP INSTANCE <instance_name> */ -class Stop_instance : public Command +class Stop_instance: public Abstract_instance_cmd +{ +public: + Stop_instance(const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); +}; + + +/* + Create an instance. + Grammar: CREATE INSTANCE <instance_name> [<options>] +*/ + +class Create_instance: public Command +{ +public: + Create_instance(const LEX_STRING *instance_name_arg); + +public: + bool init(const char **text); + +protected: + virtual int execute(st_net *net, ulong connection_id); + + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + bool parse_args(const char **text); + +private: + Instance_name instance_name; + + Named_value_arr options; +}; + + +/* + Drop an instance. + Grammar: DROP INSTANCE <instance_name> + + Operation is permitted only if the instance is stopped. On successful + completion the instance section is removed from config file and the instance + is removed from the instance map. +*/ + +class Drop_instance: public Abstract_instance_cmd { public: - Stop_instance(Instance_map *instance_map_arg, const char *name, uint len); + Drop_instance(const LEX_STRING *instance_name_arg); - Instance *instance; - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; /* - Print requested part of the log + Print requested part of the log. Grammar: - SHOW <instance_name> log {ERROR | SLOW | GENERAL} size[, offset_from_end] + SHOW <instance_name> LOG {ERROR | SLOW | GENERAL} size[, offset_from_end] */ -class Show_instance_log : public Command +class Show_instance_log: public Abstract_instance_cmd { public: + Show_instance_log(const LEX_STRING *instance_name_arg, + Log_type log_type_arg, uint size_arg, uint offset_arg); - Show_instance_log(Instance_map *instance_map_arg, const char *name, - uint len, Log_type log_type_arg, const char *size_arg, - const char *offset_arg); - int execute(struct st_net *net, ulong connection_id); +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int check_params(Instance *instance); + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); + +private: Log_type log_type; - const char *instance_name; uint size; uint offset; }; @@ -141,75 +257,112 @@ public: Grammar: SHOW <instance_name> LOG FILES */ -class Show_instance_log_files : public Command +class Show_instance_log_files: public Abstract_instance_cmd { public: + Show_instance_log_files(const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); - Show_instance_log_files(Instance_map *instance_map_arg, - const char *name, uint len); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; - const char *option; +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Syntax error command. This command is issued if parser reported a syntax - error. We need it to distinguish the parse error and the situation when - parser internal error occured. E.g. parsing failed because we hadn't had - enought memory. In the latter case parse_command() should return an error. + Abstract class for option-management commands. */ -class Syntax_error : public Command +class Instance_options_list; + +class Abstract_option_cmd: public Command { public: - int execute(struct st_net *net, ulong connection_id); + ~Abstract_option_cmd(); + +public: + bool add_option(const LEX_STRING *instance_name, Named_value *option); + +public: + bool init(const char **text); + + virtual int execute(st_net *net, ulong connection_id); + +protected: + Abstract_option_cmd(); + + int correct_file(Instance *instance, Named_value *option, bool skip); + +protected: + virtual bool parse_args(const char **text) = 0; + virtual int process_option(Instance *instance, Named_value *option) = 0; + +private: + Instance_options_list * + get_instance_options_list(const LEX_STRING *instance_name); + + int execute_impl(st_net *net, ulong connection_id); + +private: + HASH instance_options_map; + bool initialized; }; + /* Set an option for the instance. - Grammar: SET instance_name.option=option_value + Grammar: SET instance_name.option[=option_value][, ...] */ -class Set_option : public Command +class Set_option: public Abstract_option_cmd { public: - Set_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len); - /* - the following function is virtual to let Unset_option to use - */ - virtual int do_command(struct st_net *net); - int execute(struct st_net *net, ulong connection_id); + Set_option() + { } + protected: - int correct_file(int skip); -public: - const char *instance_name; - uint instance_name_len; - /* buffer for the option */ - enum { MAX_OPTION_LEN= 1024 }; - char option[MAX_OPTION_LEN]; - char option_value[MAX_OPTION_LEN]; + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; /* - Remove option of the instance from config file - Grammar: UNSET instance_name.option + Remove option of the instance. + Grammar: UNSET instance_name.option[, ...] */ -class Unset_option: public Set_option +class Unset_option: public Abstract_option_cmd { public: - Unset_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len): - Set_option(instance_map_arg, name, len, option_arg, option_len, - option_value_arg, option_value_len) - {} - int do_command(struct st_net *net); + Unset_option() + { } + +protected: + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; +/* + Syntax error command. + + This command is issued if parser reported a syntax error. We need it to + distinguish between syntax error and internal parser error. E.g. parsing + failed because we hadn't had enought memory. In the latter case the parser + just returns NULL. +*/ + +class Syntax_error: public Command +{ +public: + Syntax_error() + { } + +public: + int execute(st_net *net, ulong connection_id); +}; + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H */ diff --git a/server-tools/instance-manager/exit_codes.h b/server-tools/instance-manager/exit_codes.h new file mode 100644 index 00000000000..560ce30b7aa --- /dev/null +++ b/server-tools/instance-manager/exit_codes.h @@ -0,0 +1,41 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H + +/* + Copyright (C) 2006 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 +*/ + +/* + This file contains a list of exit codes, which are used when Instance + Manager is working in user-management mode. +*/ + +const int ERR_OK = 0; + +const int ERR_OUT_OF_MEMORY = 1; +const int ERR_INVALID_USAGE = 2; +const int ERR_INTERNAL_ERROR = 3; +const int ERR_IO_ERROR = 4; +const int ERR_PASSWORD_FILE_CORRUPTED = 5; +const int ERR_PASSWORD_FILE_DOES_NOT_EXIST = 6; + +const int ERR_CAN_NOT_READ_USER_NAME = 10; +const int ERR_CAN_NOT_READ_PASSWORD = 11; +const int ERR_USER_ALREADY_EXISTS = 12; +const int ERR_USER_NOT_FOUND = 13; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H diff --git a/server-tools/instance-manager/guardian.cc b/server-tools/instance-manager/guardian.cc index 9fabb1923a8..3aaef3b451b 100644 --- a/server-tools/instance-manager/guardian.cc +++ b/server-tools/instance-manager/guardian.cc @@ -20,42 +20,63 @@ #endif #include "guardian.h" +#include <string.h> +#include <sys/types.h> +#include <signal.h> -#include "instance_map.h" #include "instance.h" -#include "mysql_manager_error.h" +#include "instance_map.h" #include "log.h" -#include "portability.h" +#include "mysql_manager_error.h" -#include <string.h> -#include <sys/types.h> -#include <signal.h> +const char * +Guardian::get_instance_state_name(enum_instance_state state) +{ + switch (state) { + case NOT_STARTED: + return "offline"; + case STARTING: + return "starting"; + case STARTED: + return "online"; -pthread_handler_t guardian(void *arg) -{ - Guardian_thread *guardian_thread= (Guardian_thread *) arg; - guardian_thread->run(); - return 0; + case JUST_CRASHED: + return "failed"; + + case CRASHED: + return "crashed"; + + case CRASHED_AND_ABANDONED: + return "abandoned"; + + case STOPPING: + return "stopping"; + } + + return NULL; /* just to ignore compiler warning. */ } -Guardian_thread::Guardian_thread(Thread_registry &thread_registry_arg, - Instance_map *instance_map_arg, - uint monitoring_interval_arg) : - Guardian_thread_args(thread_registry_arg, instance_map_arg, - monitoring_interval_arg), - thread_info(pthread_self()), guarded_instances(0) +/* {{{ Constructor & destructor. */ + +Guardian::Guardian(Thread_registry *thread_registry_arg, + Instance_map *instance_map_arg, + uint monitoring_interval_arg) + :stopped(FALSE), + monitoring_interval(monitoring_interval_arg), + thread_registry(thread_registry_arg), + instance_map(instance_map_arg), + guarded_instances(0), + shutdown_requested(FALSE) { pthread_mutex_init(&LOCK_guardian, 0); pthread_cond_init(&COND_guardian, 0); - shutdown_requested= FALSE; - stopped= FALSE; init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); } -Guardian_thread::~Guardian_thread() +Guardian::~Guardian() { /* delay guardian destruction to the moment when no one needs it */ pthread_mutex_lock(&LOCK_guardian); @@ -65,21 +86,23 @@ Guardian_thread::~Guardian_thread() pthread_cond_destroy(&COND_guardian); } +/* }}} */ + -void Guardian_thread::request_shutdown() +void Guardian::request_shutdown() { pthread_mutex_lock(&LOCK_guardian); - /* stop instances or just clean up Guardian repository */ + /* STOP Instances or just clean up Guardian repository */ stop_instances(); shutdown_requested= TRUE; pthread_mutex_unlock(&LOCK_guardian); } -void Guardian_thread::process_instance(Instance *instance, - GUARD_NODE *current_node, - LIST **guarded_instances, - LIST *node) +void Guardian::process_instance(Instance *instance, + GUARD_NODE *current_node, + LIST **guarded_instances, + LIST *node) { uint waitchild= (uint) Instance::DEFAULT_SHUTDOWN_DELAY; /* The amount of times, Guardian attempts to restart an instance */ @@ -88,16 +111,14 @@ void Guardian_thread::process_instance(Instance *instance, if (current_node->state == STOPPING) { - /* this brach is executed during shutdown */ - if (instance->options.shutdown_delay_val) - waitchild= instance->options.shutdown_delay_val; + waitchild= instance->options.get_shutdown_delay(); - /* this returns true if and only if an instance was stopped for sure */ + /* this returns TRUE if and only if an instance was stopped for sure */ if (instance->is_crashed()) *guarded_instances= list_delete(*guarded_instances, node); else if ( (uint) (current_time - current_node->last_checked) > waitchild) { - instance->kill_instance(SIGKILL); + instance->kill_mysqld(SIGKILL); /* Later we do node= node->next. This is ok, as we are only removing the node from the list. The pointer to the next one is still valid. @@ -108,21 +129,21 @@ void Guardian_thread::process_instance(Instance *instance, return; } - if (instance->is_running()) + if (instance->is_mysqld_running()) { /* The instance can be contacted on it's port */ /* If STARTING also check that pidfile has been created */ if (current_node->state == STARTING && - current_node->instance->options.get_pid() == 0) + current_node->instance->options.load_pid() == 0) { /* Pid file not created yet, don't go to STARTED state yet */ } else if (current_node->state != STARTED) { /* clear status fields */ - log_info("guardian: instance '%s' is running, set state to STARTED.", - (const char *) instance->options.instance_name); + log_info("Guardian: '%s' is running, set state to STARTED.", + (const char *) instance->options.instance_name.str); current_node->restart_counter= 0; current_node->crash_moment= 0; current_node->state= STARTED; @@ -132,8 +153,8 @@ void Guardian_thread::process_instance(Instance *instance, { switch (current_node->state) { case NOT_STARTED: - log_info("guardian: starting instance '%s'...", - (const char *) instance->options.instance_name); + log_info("Guardian: starting '%s'...", + (const char *) instance->options.instance_name.str); /* NOTE, set state to STARTING _before_ start() is called */ current_node->state= STARTING; @@ -157,8 +178,8 @@ void Guardian_thread::process_instance(Instance *instance, if (instance->is_crashed()) { instance->start(); - log_info("guardian: starting instance '%s'...", - (const char *) instance->options.instance_name); + log_info("Guardian: starting '%s'...", + (const char *) instance->options.instance_name.str); } } else @@ -175,12 +196,17 @@ void Guardian_thread::process_instance(Instance *instance, instance->start(); current_node->last_checked= current_time; current_node->restart_counter++; - log_info("guardian: restarting instance '%s'...", - (const char *) instance->options.instance_name); + log_info("Guardian: restarting '%s'...", + (const char *) instance->options.instance_name.str); } } else + { + log_info("Guardian: can not start '%s'. " + "Abandoning attempts to (re)start it", + (const char *) instance->options.instance_name.str); current_node->state= CRASHED_AND_ABANDONED; + } } break; case CRASHED_AND_ABANDONED: @@ -193,26 +219,26 @@ void Guardian_thread::process_instance(Instance *instance, /* - Run guardian thread + Main function of Guardian thread. - SYNOPSYS + SYNOPSIS run() DESCRIPTION - Check for all guarded instances and restart them if needed. If everything is fine go and sleep for some time. */ -void Guardian_thread::run() +void Guardian::run() { Instance *instance; LIST *node; struct timespec timeout; - thread_registry.register_thread(&thread_info); + log_info("Guardian: started."); + + thread_registry->register_thread(&thread_info); - my_thread_init(); pthread_mutex_lock(&LOCK_guardian); /* loop, until all instances were shut down at the end */ @@ -232,20 +258,23 @@ void Guardian_thread::run() /* check the loop predicate before sleeping */ if (!(shutdown_requested && (!(guarded_instances)))) - thread_registry.cond_timedwait(&thread_info, &COND_guardian, - &LOCK_guardian, &timeout); + thread_registry->cond_timedwait(&thread_info, &COND_guardian, + &LOCK_guardian, &timeout); } + log_info("Guardian: stopped."); + stopped= TRUE; pthread_mutex_unlock(&LOCK_guardian); /* now, when the Guardian is stopped we can stop the IM */ - thread_registry.unregister_thread(&thread_info); - thread_registry.request_shutdown(); - my_thread_end(); + thread_registry->unregister_thread(&thread_info); + thread_registry->request_shutdown(); + + log_info("Guardian: finished."); } -int Guardian_thread::is_stopped() +int Guardian::is_stopped() { int var; pthread_mutex_lock(&LOCK_guardian); @@ -259,17 +288,19 @@ int Guardian_thread::is_stopped() Initialize the list of guarded instances: loop through the Instance_map and add all of the instances, which don't have 'nonguarded' option specified. - SYNOPSYS - Guardian_thread::init() + SYNOPSIS + Guardian::init() - NOTE: One should always lock guardian before calling this routine. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian; + - Instance_map; RETURN 0 - ok - 1 - error occured + 1 - error occurred */ -int Guardian_thread::init() +int Guardian::init() { Instance *instance; Instance_map::Iterator iterator(instance_map); @@ -281,9 +312,11 @@ int Guardian_thread::init() while ((instance= iterator.next())) { - if (!(instance->options.nonguarded)) - if (guard(instance, TRUE)) /* do not lock guardian */ - return 1; + if (instance->options.nonguarded) + continue; + + if (guard(instance, TRUE)) /* do not lock guardian */ + return 1; } return 0; @@ -293,7 +326,7 @@ int Guardian_thread::init() /* Add instance to the Guardian list - SYNOPSYS + SYNOPSIS guard() instance the instance to be guarded nolock whether we prefer do not lock Guardian here, @@ -306,10 +339,10 @@ int Guardian_thread::init() RETURN 0 - ok - 1 - error occured + 1 - error occurred */ -int Guardian_thread::guard(Instance *instance, bool nolock) +int Guardian::guard(Instance *instance, bool nolock) { LIST *node; GUARD_NODE *content; @@ -346,29 +379,19 @@ int Guardian_thread::guard(Instance *instance, bool nolock) a piece of the MEM_ROOT). */ -int Guardian_thread::stop_guard(Instance *instance) +int Guardian::stop_guard(Instance *instance) { LIST *node; pthread_mutex_lock(&LOCK_guardian); - node= guarded_instances; - while (node != NULL) - { - /* - We compare only pointers, as we always use pointers from the - instance_map's MEM_ROOT. - */ - if (((GUARD_NODE *) node->data)->instance == instance) - { - guarded_instances= list_delete(guarded_instances, node); - pthread_mutex_unlock(&LOCK_guardian); - return 0; - } - else - node= node->next; - } + node= find_instance_node(instance); + + if (node != NULL) + guarded_instances= list_delete(guarded_instances, node); + pthread_mutex_unlock(&LOCK_guardian); + /* if there is nothing to delete it is also fine */ return 0; } @@ -377,7 +400,7 @@ int Guardian_thread::stop_guard(Instance *instance) An internal method which is called at shutdown to unregister instances and attempt to stop them if requested. - SYNOPSYS + SYNOPSIS stop_instances() DESCRIPTION @@ -390,10 +413,10 @@ int Guardian_thread::stop_guard(Instance *instance) RETURN 0 - ok - 1 - error occured + 1 - error occurred */ -int Guardian_thread::stop_instances() +int Guardian::stop_instances() { LIST *node; node= guarded_instances; @@ -404,7 +427,7 @@ int Guardian_thread::stop_instances() If instance is running or was running (and now probably hanging), request stop. */ - if (current_node->instance->is_running() || + if (current_node->instance->is_mysqld_running() || (current_node->state == STARTED)) { current_node->state= STOPPING; @@ -414,20 +437,58 @@ int Guardian_thread::stop_instances() /* otherwise remove it from the list */ guarded_instances= list_delete(guarded_instances, node); /* But try to kill it anyway. Just in case */ - current_node->instance->kill_instance(SIGTERM); + current_node->instance->kill_mysqld(SIGTERM); node= node->next; } return 0; } -void Guardian_thread::lock() +void Guardian::lock() { pthread_mutex_lock(&LOCK_guardian); } -void Guardian_thread::unlock() +void Guardian::unlock() { pthread_mutex_unlock(&LOCK_guardian); } + + +LIST *Guardian::find_instance_node(Instance *instance) +{ + LIST *node= guarded_instances; + + while (node != NULL) + { + /* + We compare only pointers, as we always use pointers from the + instance_map's MEM_ROOT. + */ + if (((GUARD_NODE *) node->data)->instance == instance) + return node; + + node= node->next; + } + + return NULL; +} + + +bool Guardian::is_active(Instance *instance) +{ + bool guarded; + + lock(); + + guarded= find_instance_node(instance) != NULL; + + /* is_running() can take a long time, so let's unlock mutex first. */ + unlock(); + + if (guarded) + return true; + + return instance->is_mysqld_running(); +} diff --git a/server-tools/instance-manager/guardian.h b/server-tools/instance-manager/guardian.h index f1c54262c12..0eee1dc631d 100644 --- a/server-tools/instance-manager/guardian.h +++ b/server-tools/instance-manager/guardian.h @@ -16,9 +16,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include "thread_registry.h" +#include "thread_registry.h" #include <my_sys.h> #include <my_list.h> @@ -31,30 +30,12 @@ class Instance_map; class Thread_registry; struct GUARD_NODE; -pthread_handler_t guardian(void *arg); - -struct Guardian_thread_args -{ - Thread_registry &thread_registry; - Instance_map *instance_map; - int monitoring_interval; - - Guardian_thread_args(Thread_registry &thread_registry_arg, - Instance_map *instance_map_arg, - uint monitoring_interval_arg) : - thread_registry(thread_registry_arg), - instance_map(instance_map_arg), - monitoring_interval(monitoring_interval_arg) - {} -}; - - -/* +/** The guardian thread is responsible for monitoring and restarting of guarded instances. */ -class Guardian_thread: public Guardian_thread_args +class Guardian: public Thread { public: /* states of an instance */ @@ -79,13 +60,13 @@ public: time_t last_checked; }; + /* Return client state name. */ + static const char *get_instance_state_name(enum_instance_state state); - Guardian_thread(Thread_registry &thread_registry_arg, - Instance_map *instance_map_arg, - uint monitoring_interval_arg); - ~Guardian_thread(); - /* Main funtion of the thread */ - void run(); + Guardian(Thread_registry *thread_registry_arg, + Instance_map *instance_map_arg, + uint monitoring_interval_arg); + virtual ~Guardian(); /* Initialize or refresh the list of guarded instances */ int init(); /* Request guardian shutdown. Stop instances if needed */ @@ -94,11 +75,31 @@ public: int guard(Instance *instance, bool nolock= FALSE); /* Stop instance protection */ int stop_guard(Instance *instance); - /* Returns true if guardian thread is stopped */ + /* Returns TRUE if guardian thread is stopped */ int is_stopped(); void lock(); void unlock(); + /* + Return an internal list node for the given instance if the instance is + managed by Guardian. Otherwise, return NULL. + + MT-NOTE: must be called under acquired lock. + */ + LIST *find_instance_node(Instance *instance); + + /* The operation is used to check if the instance is active or not. */ + bool is_active(Instance *instance); + + /* + Return state of the given instance list node. The pointer must specify + a valid list node. + */ + inline enum_instance_state get_instance_state(LIST *instance_node); +protected: + /* Main funtion of the thread */ + virtual void run(); + public: pthread_cond_t COND_guardian; @@ -108,16 +109,26 @@ private: /* check instance state and act accordingly */ void process_instance(Instance *instance, GUARD_NODE *current_node, LIST **guarded_instances, LIST *elem); + int stopped; private: pthread_mutex_t LOCK_guardian; Thread_info thread_info; + int monitoring_interval; + Thread_registry *thread_registry; + Instance_map *instance_map; LIST *guarded_instances; MEM_ROOT alloc; - enum { MEM_ROOT_BLOCK_SIZE= 512 }; /* this variable is set to TRUE when we want to stop Guardian thread */ bool shutdown_requested; }; + +inline Guardian::enum_instance_state +Guardian::get_instance_state(LIST *instance_node) +{ + return ((GUARD_NODE *) instance_node->data)->state; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */ diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc index 39793b9a234..2b3df068ce2 100644 --- a/server-tools/instance-manager/instance.cc +++ b/server-tools/instance-manager/instance.cc @@ -20,22 +20,23 @@ #include "instance.h" -#include "mysql_manager_error.h" -#include "log.h" -#include "instance_map.h" -#include "priv.h" -#include "portability.h" +#include <mysql.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif -#include <my_sys.h> -#include <signal.h> -#include <m_string.h> -#include <mysql.h> +#include "guardian.h" +#include "manager.h" +#include "log.h" +#include "mysql_manager_error.h" +#include "portability.h" +#include "priv.h" +#include "thread_registry.h" +#include "instance_map.h" -static void start_and_monitor_instance(Instance_options *old_instance_options, - Instance_map *instance_map); +/* {{{ Platform-specific functions. */ #ifndef __WIN__ typedef pid_t My_process_info; @@ -50,18 +51,31 @@ typedef PROCESS_INFORMATION My_process_info; to do it in a portable way. */ -pthread_handler_t proxy(void *arg) +class Instance_monitor: public Thread +{ +public: + Instance_monitor(Instance *instance_arg) :instance(instance_arg) {} +protected: + virtual void run(); + void start_and_monitor_instance(Instance_options *old_instance_options, + Instance_map *instance_map, + Thread_registry *thread_registry); +private: + Instance *instance; +}; + +void Instance_monitor::run() { - Instance *instance= (Instance *) arg; start_and_monitor_instance(&instance->options, - instance->get_map()); - return 0; + Manager::get_instance_map(), + Manager::get_thread_registry()); + delete this; } /* Wait for an instance - SYNOPSYS + SYNOPSIS wait_process() pi Pointer to the process information structure (platform-dependent). @@ -90,7 +104,8 @@ static int wait_process(My_process_info *pi) thread, but we don't know this one). Or we could use waitpid(), but couldn't use wait(), because it could return in any wait() in the program. */ - if (linuxthreads) + + if (Manager::is_linux_threads()) wait(NULL); /* LinuxThreads were detected */ else waitpid(*pi, NULL, 0); @@ -118,11 +133,10 @@ static int wait_process(My_process_info *pi) } #endif - /* Launch an instance - SYNOPSYS + SYNOPSIS start_process() instance_options Pointer to the options of the instance to be launched. @@ -130,13 +144,13 @@ static int wait_process(My_process_info *pi) (platform-dependent). RETURN - 0 - Success - 1 - Cannot create an instance + FALSE - Success + TRUE - Cannot create an instance */ #ifndef __WIN__ -static int start_process(Instance_options *instance_options, - My_process_info *pi) +static bool start_process(Instance_options *instance_options, + My_process_info *pi) { #ifndef __QNX__ *pi= fork(); @@ -152,19 +166,20 @@ static int start_process(Instance_options *instance_options, switch (*pi) { case 0: /* never happens on QNX */ - execv(instance_options->mysqld_path, instance_options->argv); + execv(instance_options->mysqld_path.str, instance_options->argv); /* exec never returns */ exit(1); case -1: - log_info("cannot create a new process to start instance '%s'.", - (const char *) instance_options->instance_name); - return 1; + log_error("Instance '%s': can not start mysqld: fork() failed.", + (const char *) instance_options->instance_name.str); + return TRUE; } - return 0; + + return FALSE; } #else -static int start_process(Instance_options *instance_options, - My_process_info *pi) +static bool start_process(Instance_options *instance_options, + My_process_info *pi) { STARTUPINFO si; @@ -179,8 +194,8 @@ static int start_process(Instance_options *instance_options, char *cmdline= new char[cmdlen]; if (cmdline == NULL) - return 1; - + return TRUE; + cmdline[0]= 0; for (int i= 0; instance_options->argv[i] != 0; i++) { @@ -203,14 +218,87 @@ static int start_process(Instance_options *instance_options, pi); /* Pointer to PROCESS_INFORMATION structure */ delete cmdline; - return (!result); + return !result; } #endif +#ifdef __WIN__ + +BOOL SafeTerminateProcess(HANDLE hProcess, UINT uExitCode) +{ + DWORD dwTID, dwCode, dwErr= 0; + HANDLE hProcessDup= INVALID_HANDLE_VALUE; + HANDLE hRT= NULL; + HINSTANCE hKernel= GetModuleHandle("Kernel32"); + BOOL bSuccess= FALSE; + + BOOL bDup= DuplicateHandle(GetCurrentProcess(), + hProcess, GetCurrentProcess(), &hProcessDup, + PROCESS_ALL_ACCESS, FALSE, 0); + + // Detect the special case where the process is + // already dead... + if (GetExitCodeProcess((bDup) ? hProcessDup : hProcess, &dwCode) && + (dwCode == STILL_ACTIVE)) + { + FARPROC pfnExitProc; + + pfnExitProc= GetProcAddress(hKernel, "ExitProcess"); + + hRT= CreateRemoteThread((bDup) ? hProcessDup : hProcess, NULL, 0, + (LPTHREAD_START_ROUTINE)pfnExitProc, + (PVOID)uExitCode, 0, &dwTID); + + if (hRT == NULL) + dwErr= GetLastError(); + } + else + dwErr= ERROR_PROCESS_ABORTED; + + if (hRT) + { + // Must wait process to terminate to + // guarantee that it has exited... + WaitForSingleObject((bDup) ? hProcessDup : hProcess, INFINITE); + + CloseHandle(hRT); + bSuccess= TRUE; + } + + if (bDup) + CloseHandle(hProcessDup); + + if (!bSuccess) + SetLastError(dwErr); + + return bSuccess; +} + +int kill(pid_t pid, int signum) +{ + HANDLE processhandle= ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if (signum == SIGTERM) + ::SafeTerminateProcess(processhandle, 0); + else + ::TerminateProcess(processhandle, -1); + return 0; +} +#endif + +/* }}} */ + +/* {{{ Static constants. */ + +const LEX_STRING +Instance::DFLT_INSTANCE_NAME= { C_STRING_WITH_LEN("mysqld") }; + +/* }}} */ + + /* Fork child, exec an instance and monitor it. - SYNOPSYS + SYNOPSIS start_and_monitor_instance() old_instance_options Pointer to the options of the instance to be launched. This info is likely to become obsolete @@ -225,18 +313,40 @@ static int start_process(Instance_options *instance_options, set appropriate flags and wake all threads waiting for instance to stop. + NOTE + A separate thread for starting/monitoring instance is a simple way + to avoid all pitfalls of the threads implementation in the OS (e.g. + LinuxThreads). For one, with such a thread we don't have to process + SIGCHLD, which is a tricky business if we want to do it in a + portable way. + RETURN Function returns no value */ -static void start_and_monitor_instance(Instance_options *old_instance_options, - Instance_map *instance_map) +void +Instance_monitor:: +start_and_monitor_instance(Instance_options *old_instance_options, + Instance_map *instance_map, + Thread_registry *thread_registry) { - enum { MAX_INSTANCE_NAME_LEN= 512 }; - char instance_name_buff[MAX_INSTANCE_NAME_LEN]; - uint instance_name_len; + Instance_name instance_name(&old_instance_options->instance_name); Instance *current_instance; My_process_info process_info; + Thread_info thread_info; + + log_info("Instance '%s': Monitor: started.", + (const char *) instance->get_name()->str); + + if (!old_instance_options->nonguarded) + { + /* + Register thread in Thread_registry to wait for it to stop on shutdown + only if instance is guarded. If instance is guarded, the thread will not + finish, because nonguarded instances are not stopped on shutdown. + */ + thread_registry->register_thread(&thread_info, FALSE); + } /* Lock instance map to guarantee that no instances are deleted during @@ -248,12 +358,9 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, Save the instance name in the case if Instance object we are using is destroyed. (E.g. by "FLUSH INSTANCES") */ - strmake(instance_name_buff, old_instance_options->instance_name, - MAX_INSTANCE_NAME_LEN - 1); - instance_name_len= old_instance_options->instance_name_len; - log_info("starting instance '%s'...", - (const char *) instance_name_buff); + log_info("Instance '%s': Monitor: starting mysqld...", + (const char *) instance->get_name()->str); if (start_process(old_instance_options, &process_info)) { @@ -264,163 +371,141 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, /* allow users to delete instances */ instance_map->unlock(); - /* don't check for return value */ - wait_process(&process_info); + log_info("Instance '%s': Monitor: waiting for mysqld to stop...", + (const char *) instance->get_name()->str); + + wait_process(&process_info); /* Don't check for return value. */ + + instance_map->lock(); - current_instance= instance_map->find(instance_name_buff, instance_name_len); + current_instance= instance_map->find(instance_name.get_str()); if (current_instance) current_instance->set_crash_flag_n_wake_all(); - return; + instance_map->unlock(); + + if (!old_instance_options->nonguarded) + thread_registry->unregister_thread(&thread_info); + + log_info("Instance '%s': Monitor: finished.", + (const char *) instance->get_name()->str); } -Instance_map *Instance::get_map() +bool Instance::is_name_valid(const LEX_STRING *name) { - return instance_map; + const char *name_suffix= name->str + DFLT_INSTANCE_NAME.length; + + if (strncmp(name->str, Instance::DFLT_INSTANCE_NAME.str, + Instance::DFLT_INSTANCE_NAME.length) != 0) + return FALSE; + + return *name_suffix == 0 || my_isdigit(default_charset_info, *name_suffix); } -void Instance::remove_pid() +bool Instance::is_mysqld_compatible_name(const LEX_STRING *name) { - int pid; - if ((pid= options.get_pid()) != 0) /* check the pidfile */ - if (options.unlink_pidfile()) /* remove stalled pidfile */ - log_error("cannot remove pidfile for instance '%s', this might be \ - since IM lacks permmissions or hasn't found the pidifle", - (const char *) options.instance_name); + return strcmp(name->str, DFLT_INSTANCE_NAME.str) == 0; } -/* - The method starts an instance. - - SYNOPSYS - start() - RETURN - 0 ok - ER_CANNOT_START_INSTANCE Cannot start instance - ER_INSTANCE_ALREADY_STARTED The instance on the specified port/socket - is already started -*/ +/* {{{ Constructor & destructor */ -int Instance::start() +Instance::Instance() + :crashed(FALSE), + configured(FALSE) { - /* clear crash flag */ - pthread_mutex_lock(&LOCK_instance); - crashed= 0; - pthread_mutex_unlock(&LOCK_instance); - - - if (!is_running()) - { - remove_pid(); + pthread_mutex_init(&LOCK_instance, 0); + pthread_cond_init(&COND_instance_stopped, 0); +} - /* - No need to monitor this thread in the Thread_registry, as all - instances are to be stopped during shutdown. - */ - pthread_t proxy_thd_id; - pthread_attr_t proxy_thd_attr; - int rc; - - pthread_attr_init(&proxy_thd_attr); - pthread_attr_setdetachstate(&proxy_thd_attr, PTHREAD_CREATE_DETACHED); - rc= pthread_create(&proxy_thd_id, &proxy_thd_attr, proxy, - this); - pthread_attr_destroy(&proxy_thd_attr); - if (rc) - { - log_error("Instance::start(): pthread_create(proxy) failed"); - return ER_CANNOT_START_INSTANCE; - } - return 0; - } +Instance::~Instance() +{ + log_info("Instance '%s': destroying...", (const char *) get_name()->str); - /* the instance is started already */ - return ER_INSTANCE_ALREADY_STARTED; + pthread_cond_destroy(&COND_instance_stopped); + pthread_mutex_destroy(&LOCK_instance); } -/* - The method sets the crash flag and wakes all waiters on - COND_instance_stopped and COND_guardian +/* }}} */ - SYNOPSYS - set_crash_flag_n_wake_all() +/* + Initialize instance options. - DESCRIPTION - The method is called when an instance is crashed or terminated. - In the former case it might indicate that guardian probably should - restart it. + SYNOPSIS + init() + name_arg name of the instance - RETURN - Function returns no value + RETURN: + FALSE - ok + TRUE - error */ -void Instance::set_crash_flag_n_wake_all() +bool Instance::init(const LEX_STRING *name_arg) { - /* set instance state to crashed */ - pthread_mutex_lock(&LOCK_instance); - crashed= 1; - pthread_mutex_unlock(&LOCK_instance); + mysqld_compatible= is_mysqld_compatible_name(name_arg); - /* - Wake connection threads waiting for an instance to stop. This - is needed if a user issued command to stop an instance via - mysql connection. This is not the case if Guardian stop the thread. - */ - pthread_cond_signal(&COND_instance_stopped); - /* wake guardian */ - pthread_cond_signal(&instance_map->guardian->COND_guardian); + return options.init(name_arg); } +/* + Complete instance options initialization. -Instance::Instance(): crashed(0) -{ - pthread_mutex_init(&LOCK_instance, 0); - pthread_cond_init(&COND_instance_stopped, 0); -} + SYNOPSIS + complete_initialization() + RETURN + FALSE - ok + TRUE - error +*/ -Instance::~Instance() +bool Instance::complete_initialization() { - pthread_cond_destroy(&COND_instance_stopped); - pthread_mutex_destroy(&LOCK_instance); + configured= ! options.complete_initialization(); + return FALSE; + /* + TODO: return actual status (from + Instance_options::complete_initialization()) here. + */ } +/* + Determine if mysqld is accepting connections. -int Instance::is_crashed() -{ - int val; - pthread_mutex_lock(&LOCK_instance); - val= crashed; - pthread_mutex_unlock(&LOCK_instance); - return val; -} + SYNOPSIS + is_mysqld_running() + DESCRIPTION + Try to connect to mysqld with fake login/password to check whether it is + accepting connections or not. + + MT-NOTE: this operation must be called under acquired LOCK_instance. + + RETURN + TRUE - mysqld is alive and accept connections + FALSE - otherwise. +*/ -bool Instance::is_running() +bool Instance::is_mysqld_running() { MYSQL mysql; - uint port= 0; + uint port= options.get_mysqld_port(); /* 0 if not specified. */ const char *socket= NULL; static const char *password= "check_connection"; static const char *username= "MySQL_Instance_Manager"; static const char *access_denied_message= "Access denied for user"; bool return_val; - if (options.mysqld_port) - port= options.mysqld_port_val; - if (options.mysqld_socket) - socket= strchr(options.mysqld_socket, '=') + 1; + socket= options.mysqld_socket; /* no port was specified => instance falled back to default value */ - if (!options.mysqld_port && !options.mysqld_socket) + if (!port && !options.mysqld_socket) port= SERVER_DEFAULT_PORT; pthread_mutex_lock(&LOCK_instance); @@ -436,9 +521,8 @@ bool Instance::is_running() We have successfully connected to the server using fake username/password. Write a warning to the logfile. */ - log_info("The Instance Manager was able to log into you server " - "with faked compiled-in password while checking server status. " - "Looks like something is wrong."); + log_error("Instance '%s': was able to log into mysqld.", + (const char *) get_name()->str); pthread_mutex_unlock(&LOCK_instance); return_val= TRUE; /* server is alive */ } @@ -452,11 +536,88 @@ bool Instance::is_running() return return_val; } +/* + The method starts an instance. + + SYNOPSIS + start() + + RETURN + 0 ok + ER_CANNOT_START_INSTANCE Cannot start instance + ER_INSTANCE_ALREADY_STARTED The instance on the specified port/socket + is already started +*/ + +int Instance::start() +{ + /* clear crash flag */ + pthread_mutex_lock(&LOCK_instance); + crashed= FALSE; + pthread_mutex_unlock(&LOCK_instance); + + + if (configured && !is_mysqld_running()) + { + Instance_monitor *instance_monitor; + remove_pid(); + + instance_monitor= new Instance_monitor(this); + + if (instance_monitor == NULL || instance_monitor->start(Thread::DETACHED)) + { + delete instance_monitor; + log_error("Instance::start(): failed to create the monitoring thread" + " to start an instance"); + return ER_CANNOT_START_INSTANCE; + } + /* The monitoring thread will delete itself when it's finished. */ + + return 0; + } + + /* The instance is started already or misconfigured. */ + return configured ? ER_INSTANCE_ALREADY_STARTED : ER_INSTANCE_MISCONFIGURED; +} + +/* + The method sets the crash flag and wakes all waiters on + COND_instance_stopped and COND_guardian + + SYNOPSIS + set_crash_flag_n_wake_all() + + DESCRIPTION + The method is called when an instance is crashed or terminated. + In the former case it might indicate that guardian probably should + restart it. + + RETURN + Function returns no value +*/ + +void Instance::set_crash_flag_n_wake_all() +{ + /* set instance state to crashed */ + pthread_mutex_lock(&LOCK_instance); + crashed= TRUE; + pthread_mutex_unlock(&LOCK_instance); + + /* + Wake connection threads waiting for an instance to stop. This + is needed if a user issued command to stop an instance via + mysql connection. This is not the case if Guardian stop the thread. + */ + pthread_cond_signal(&COND_instance_stopped); + /* wake guardian */ + pthread_cond_signal(&Manager::get_guardian()->COND_guardian); +} + /* Stop an instance. - SYNOPSYS + SYNOPSIS stop() RETURN: @@ -470,19 +631,18 @@ int Instance::stop() struct timespec timeout; uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; - if (is_running()) + if (is_mysqld_running()) { - if (options.shutdown_delay_val) - waitchild= options.shutdown_delay_val; + waitchild= options.get_shutdown_delay(); - kill_instance(SIGTERM); + kill_mysqld(SIGTERM); /* sleep on condition to wait for SIGCHLD */ set_timespec(timeout, waitchild); if (pthread_mutex_lock(&LOCK_instance)) return ER_STOP_INSTANCE; - while (options.get_pid() != 0) /* while server isn't stopped */ + while (options.load_pid() != 0) /* while server isn't stopped */ { int status; @@ -495,7 +655,7 @@ int Instance::stop() pthread_mutex_unlock(&LOCK_instance); - kill_instance(SIGKILL); + kill_mysqld(SIGKILL); return 0; } @@ -503,107 +663,83 @@ int Instance::stop() return ER_INSTANCE_IS_NOT_STARTED; } -#ifdef __WIN__ -BOOL SafeTerminateProcess(HANDLE hProcess, UINT uExitCode) -{ - DWORD dwTID, dwCode, dwErr= 0; - HANDLE hProcessDup= INVALID_HANDLE_VALUE; - HANDLE hRT= NULL; - HINSTANCE hKernel= GetModuleHandle("Kernel32"); - BOOL bSuccess= FALSE; +/* + Send signal to mysqld. - BOOL bDup= DuplicateHandle(GetCurrentProcess(), - hProcess, GetCurrentProcess(), &hProcessDup, - PROCESS_ALL_ACCESS, FALSE, 0); + SYNOPSIS + kill_mysqld() +*/ - // Detect the special case where the process is - // already dead... - if (GetExitCodeProcess((bDup) ? hProcessDup : hProcess, &dwCode) && - (dwCode == STILL_ACTIVE)) - { - FARPROC pfnExitProc; +void Instance::kill_mysqld(int signum) +{ + pid_t mysqld_pid= options.load_pid(); - pfnExitProc= GetProcAddress(hKernel, "ExitProcess"); + if (mysqld_pid == 0) + { + log_info("Instance '%s': no pid file to send a signal (%d).", + (const char *) get_name()->str, + (int) signum); + return; + } - hRT= CreateRemoteThread((bDup) ? hProcessDup : hProcess, NULL, 0, - (LPTHREAD_START_ROUTINE)pfnExitProc, - (PVOID)uExitCode, 0, &dwTID); + log_info("Instance '%s': sending %d to %d...", + (const char *) get_name()->str, + (int) signum, + (int) mysqld_pid); - if (hRT == NULL) - dwErr= GetLastError(); + if (kill(mysqld_pid, signum)) + { + log_info("Instance '%s': kill() failed.", + (const char *) get_name()->str); + return; } - else - dwErr= ERROR_PROCESS_ABORTED; - if (hRT) + /* Kill suceeded */ + if (signum == SIGKILL) /* really killed instance with SIGKILL */ { - // Must wait process to terminate to - // guarantee that it has exited... - WaitForSingleObject((bDup) ? hProcessDup : hProcess, INFINITE); + log_error("Instance '%s': killed.", + (const char *) options.instance_name.str); - CloseHandle(hRT); - bSuccess= TRUE; + /* After sucessful hard kill the pidfile need to be removed */ + options.unlink_pidfile(); } +} - if (bDup) - CloseHandle(hProcessDup); - - if (!bSuccess) - SetLastError(dwErr); +/* + Return crashed flag. - return bSuccess; -} + SYNOPSIS + is_crashed() -int kill(pid_t pid, int signum) -{ - HANDLE processhandle= ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - if (signum == SIGTERM) - ::SafeTerminateProcess(processhandle, 0); - else - ::TerminateProcess(processhandle, -1); - return 0; -} -#endif + RETURN + TRUE - mysqld crashed + FALSE - mysqld hasn't crashed yet +*/ -void Instance::kill_instance(int signum) +bool Instance::is_crashed() { - pid_t pid; - /* if there are no pid, everything seems to be fine */ - if ((pid= options.get_pid()) != 0) /* get pid from pidfile */ - { - if (kill(pid, signum) == 0) - { - /* Kill suceeded */ - if (signum == SIGKILL) /* really killed instance with SIGKILL */ - { - log_error("The instance '%s' is being stopped forcibly. Normally" - "it should not happen. Probably the instance has been" - "hanging. You should also check your IM setup", - (const char *) options.instance_name); - /* After sucessful hard kill the pidfile need to be removed */ - options.unlink_pidfile(); - } - } - } - return; + bool val; + pthread_mutex_lock(&LOCK_instance); + val= crashed; + pthread_mutex_unlock(&LOCK_instance); + return val; } /* - We execute this function to initialize instance parameters. - Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. + Remove pid file. */ -int Instance::init(const char *name_arg) +void Instance::remove_pid() { - return options.init(name_arg); -} + int mysqld_pid= options.load_pid(); + if (mysqld_pid == 0) + return; -int Instance::complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, - uint instance_type) -{ - instance_map= instance_map_arg; - return options.complete_initialization(mysqld_path, instance_type); + if (options.unlink_pidfile()) + { + log_error("Instance '%s': can not unlink pid file.", + (const char *) options.instance_name.str); + } } diff --git a/server-tools/instance-manager/instance.h b/server-tools/instance-manager/instance.h index adb66991685..412d01acc46 100644 --- a/server-tools/instance-manager/instance.h +++ b/server-tools/instance-manager/instance.h @@ -17,39 +17,130 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> +#include <m_string.h> + #include "instance_options.h" +#include "priv.h" #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif class Instance_map; +class Thread_registry; + + +/* + Instance_name -- the class represents instance name -- a string of length + less than MAX_INSTANCE_NAME_SIZE. + + Generally, this is just a string with self-memory-management and should be + eliminated in the future. +*/ + +class Instance_name +{ +public: + Instance_name(const LEX_STRING *name); + +public: + inline const LEX_STRING *get_str() const + { + return &str; + } + + inline const char *get_c_str() const + { + return str.str; + } + + inline uint get_length() const + { + return str.length; + } + +private: + LEX_STRING str; + char str_buffer[MAX_INSTANCE_NAME_SIZE]; +}; + class Instance { public: + /* + The following two constants defines name of the default mysqld-instance + ("mysqld"). + */ + static const LEX_STRING DFLT_INSTANCE_NAME; + +public: + /* + The operation is intended to check whether string is a well-formed + instance name or not. + */ + static bool is_name_valid(const LEX_STRING *name); + + /* + The operation is intended to check if the given instance name is + mysqld-compatible or not. + */ + static bool is_mysqld_compatible_name(const LEX_STRING *name); + +public: Instance(); ~Instance(); - int init(const char *name); - int complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, uint instance_type); + bool init(const LEX_STRING *name_arg); + bool complete_initialization(); - bool is_running(); + bool is_mysqld_running(); int start(); int stop(); /* send a signal to the instance */ - void kill_instance(int signo); - int is_crashed(); + void kill_mysqld(int signo); + bool is_crashed(); void set_crash_flag_n_wake_all(); - Instance_map *get_map(); + + /* + The operation is intended to check if the instance is mysqld-compatible + or not. + */ + inline bool is_mysqld_compatible() const; + + /* + The operation is intended to check if the instance is configured properly + or not. Misconfigured instances are not managed. + */ + inline bool is_configured() const; + + inline const LEX_STRING *get_name() const; public: enum { DEFAULT_SHUTDOWN_DELAY= 35 }; Instance_options options; private: - int crashed; + /* This attributes is a flag, specifies if the instance has been crashed. */ + bool crashed; + + /* + This attribute specifies if the instance is configured properly or not. + Misconfigured instances are not managed. + */ + bool configured; + + /* + This attribute specifies whether the instance is mysqld-compatible or not. + Mysqld-compatible instances can contain only mysqld-specific options. + At the moment an instance is mysqld-compatible if its name is "mysqld". + + The idea is that [mysqld] section should contain only mysqld-specific + options (no Instance Manager-specific options) to be readable by mysqld + program. + */ + bool mysqld_compatible; + /* Mutex protecting the instance. Currently we use it to avoid the double start of the instance. This happens when the instance is starting @@ -61,9 +152,26 @@ private: stop in Instance::stop() */ pthread_cond_t COND_instance_stopped; - Instance_map *instance_map; void remove_pid(); }; + +inline bool Instance::is_mysqld_compatible() const +{ + return mysqld_compatible; +} + + +inline bool Instance::is_configured() const +{ + return configured; +} + + +inline const LEX_STRING *Instance::get_name() const +{ + return &options.instance_name; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H */ diff --git a/server-tools/instance-manager/instance_map.cc b/server-tools/instance-manager/instance_map.cc index f6662847440..a356e308e44 100644 --- a/server-tools/instance-manager/instance_map.cc +++ b/server-tools/instance-manager/instance_map.cc @@ -20,14 +20,19 @@ #include "instance_map.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql_com.h> + #include "buffer.h" +#include "guardian.h" #include "instance.h" #include "log.h" +#include "manager.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" #include "options.h" - -#include <m_ctype.h> -#include <mysql_com.h> -#include <m_string.h> +#include "priv.h" /* Note: As we are going to suppost different types of connections, @@ -45,8 +50,8 @@ static byte* get_instance_key(const byte* u, uint* len, my_bool __attribute__((unused)) t) { const Instance *instance= (const Instance *) u; - *len= instance->options.instance_name_len; - return (byte *) instance->options.instance_name; + *len= instance->options.instance_name.length; + return (byte *) instance->options.instance_name.str; } static void delete_instance(void *u) @@ -58,7 +63,7 @@ static void delete_instance(void *u) /* The option handler to pass to the process_default_option_files finction. - SYNOPSYS + SYNOPSIS process_option() ctx Handler context. Here it is an instance_map structure. group_name The name of the group the option belongs to. @@ -79,16 +84,60 @@ static void delete_instance(void *u) static int process_option(void *ctx, const char *group, const char *option) { - Instance_map *map= NULL; + Instance_map *map= (Instance_map*) ctx; + LEX_STRING group_str; + + group_str.str= (char *) group; + group_str.length= strlen(group); - map = (Instance_map*) ctx; - return map->process_one_option(group, option); + return map->process_one_option(&group_str, option); } C_MODE_END /* + Parse option string. + + SYNOPSIS + parse_option() + option_str [IN] option string (e.g. "--name=value") + option_name_buf [OUT] parsed name of the option. + Must be of (MAX_OPTION_LEN + 1) size. + option_value_buf [OUT] parsed value of the option. + Must be of (MAX_OPTION_LEN + 1) size. + + DESCRIPTION + This is an auxiliary function and should not be used externally. It is + intended to parse whole option string into option name and option value. +*/ + +static void parse_option(const char *option_str, + char *option_name_buf, + char *option_value_buf) +{ + const char *eq_pos; + const char *ptr= option_str; + + while (*ptr == '-') + ++ptr; + + strmake(option_name_buf, ptr, MAX_OPTION_LEN + 1); + + eq_pos= strchr(ptr, '='); + if (eq_pos) + { + option_name_buf[eq_pos - ptr]= 0; + strmake(option_value_buf, eq_pos + 1, MAX_OPTION_LEN + 1); + } + else + { + option_value_buf[0]= 0; + } +} + + +/* Process one option from the configuration file. SYNOPSIS @@ -103,45 +152,74 @@ C_MODE_END of the instance map object. */ -int Instance_map::process_one_option(const char *group, const char *option) +int Instance_map::process_one_option(const LEX_STRING *group, + const char *option) { Instance *instance= NULL; - static const char prefix[]= { 'm', 'y', 's', 'q', 'l', 'd' }; - if (strncmp(group, prefix, sizeof prefix) == 0 && - ((my_isdigit(default_charset_info, group[sizeof prefix])) - || group[sizeof(prefix)] == '\0')) + if (!Instance::is_name_valid(group)) + { + /* + Current section name is not a valid instance name. + We should skip it w/o error. + */ + return 0; + } + + if (!(instance= (Instance *) hash_search(&hash, (byte *) group->str, + group->length))) + { + if (!(instance= new Instance())) + return 1; + + if (instance->init(group) || add_instance(instance)) { - if (!(instance= (Instance *) hash_search(&hash, (byte *) group, - strlen(group)))) - { - if (!(instance= new Instance)) - goto err; - if (instance->init(group) || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - } - - if (instance->options.add_option(option)) - goto err; /* the instance'll be deleted when we destroy the map */ + delete instance; + return 1; } - return 0; + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) group->str); -err_instance: - delete instance; -err: - return 1; + log_info("mysqld instance '%s' has been added successfully.", + (const char *) group->str); + } + + if (option) + { + char option_name[MAX_OPTION_LEN + 1]; + char option_value[MAX_OPTION_LEN + 1]; + + parse_option(option, option_name, option_value); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option_name)) + { + log_info("Warning: configuration of mysqld-compatible instance '%s' " + "contains IM-specific option '%s'. " + "This breaks backward compatibility for the configuration file.", + (const char *) group->str, + (const char *) option_name); + } + + Named_value option(option_name, option_value); + + if (instance->options.set_option(&option)) + return 1; /* the instance'll be deleted when we destroy the map */ + } + + return 0; } -Instance_map::Instance_map(const char *default_mysqld_path_arg): -mysqld_path(default_mysqld_path_arg) +Instance_map::Instance_map() { pthread_mutex_init(&LOCK_instance_map, 0); } -int Instance_map::init() +bool Instance_map::init() { return hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_instance_key, delete_instance, 0); @@ -181,7 +259,7 @@ void Instance_map::unlock() - pass on the new map to the guardian thread: it will start all instances that are marked `guarded' and not yet started. Note, as the check whether an instance is started is currently - very simple (returns true if there is a MySQL server running + very simple (returns TRUE if there is a MySQL server running at the given port), this function has some peculiar side-effects: * if the port number of a running instance was changed, the @@ -194,9 +272,9 @@ void Instance_map::unlock() In order to avoid such side effects one should never call FLUSH INSTANCES without prior stop of all running instances. - TODO - FLUSH INSTANCES should return an error if it's called - while there is a running instance. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian; + - Instance_map; */ int Instance_map::flush_instances() @@ -209,69 +287,171 @@ int Instance_map::flush_instances() guardian (2) reload the instance map (3) reinitialize the guardian with new instances. */ - guardian->lock(); - pthread_mutex_lock(&LOCK_instance_map); hash_free(&hash); hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_instance_key, delete_instance, 0); + rc= load(); /* don't init guardian if we failed to load instances */ if (!rc) guardian->init(); // TODO: check error status. - pthread_mutex_unlock(&LOCK_instance_map); - guardian->unlock(); return rc; } -Instance * -Instance_map::find(const char *name, uint name_len) +bool Instance_map::is_there_active_instance() { Instance *instance; - pthread_mutex_lock(&LOCK_instance_map); - instance= (Instance *) hash_search(&hash, (byte *) name, name_len); - pthread_mutex_unlock(&LOCK_instance_map); - return instance; + Iterator iterator(this); + + while ((instance= iterator.next())) + { + if (guardian->find_instance_node(instance) != NULL || + instance->is_mysqld_running()) + { + return TRUE; + } + } + + return FALSE; } -int Instance_map::complete_initialization() +int Instance_map::add_instance(Instance *instance) { - Instance *instance; - uint i= 0; + return my_hash_insert(&hash, (byte *) instance); +} - if (hash.records == 0) /* no instances found */ - { - if ((instance= new Instance) == 0) - goto err; +int Instance_map::remove_instance(Instance *instance) +{ + return hash_delete(&hash, (byte *) instance); +} - if (instance->init("mysqld") || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - /* - After an instance have been added to the instance_map, - hash_free should handle it's deletion => goto err, not - err_instance. - */ - if (instance->complete_initialization(this, mysqld_path, - DEFAULT_SINGLE_INSTANCE)) - goto err; +int Instance_map::create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + Instance *instance= new Instance(); + + if (!instance) + { + log_error("Can not allocate instance (name: '%s').", + (const char *) instance_name->str); + return ER_OUT_OF_RESOURCES; } - else - while (i < hash.records) + + if (instance->init(instance_name)) + { + log_error("Can not initialize instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + Named_value option= options->get_element(i); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option.get_name())) { - instance= (Instance *) hash_element(&hash, i); - if (instance->complete_initialization(this, mysqld_path, USUAL_INSTANCE)) - goto err; - i++; + log_error("IM-option (%s) can not be used " + "in configuration of mysqld-compatible instance (%s).", + (const char *) option.get_name(), + (const char *) instance_name->str); + delete instance; + return ER_INCOMPATIBLE_OPTION; } + instance->options.set_option(&option); + } + + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) instance_name->str); + + if (instance->complete_initialization()) + { + log_error("Can not complete initialization of instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + /* TODO: return more appropriate error code in this case. */ + } + + if (add_instance(instance)) + { + log_error("Can not register instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + return 0; -err_instance: - delete instance; -err: - return 1; +} + + +Instance * Instance_map::find(const LEX_STRING *name) +{ + return (Instance *) hash_search(&hash, (byte *) name->str, name->length); +} + + +bool Instance_map::complete_initialization() +{ + bool mysqld_found; + + /* Complete initialization of all registered instances. */ + + for (uint i= 0; i < hash.records; ++i) + { + Instance *instance= (Instance *) hash_element(&hash, i); + + if (instance->complete_initialization()) + return TRUE; + } + + /* That's all if we are runnning in an ordinary mode. */ + + if (!Options::Main::mysqld_safe_compatible) + return FALSE; + + /* In mysqld-compatible mode we must ensure that there 'mysqld' instance. */ + + mysqld_found= find(&Instance::DFLT_INSTANCE_NAME) != NULL; + + if (mysqld_found) + return FALSE; + + if (create_instance(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + log_error("Can not create default instance."); + return TRUE; + } + + switch (create_instance_in_file(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + case 0: + case ER_CONF_FILE_DOES_NOT_EXIST: + /* + Continue if the instance has been added to the config file + successfully, or the config file just does not exist. + */ + break; + + default: + log_error("Can not add default instance to the config file."); + + Instance *instance= find(&Instance::DFLT_INSTANCE_NAME); + + if (instance) + remove_instance(instance); /* instance is deleted here. */ + + return TRUE; + } + + return FALSE; } @@ -299,10 +479,10 @@ int Instance_map::load() name and start looking for files named "my.cnf.cnf" in all default dirs. Which is not what we want. */ - if (Options::is_forced_default_file) + if (Options::Main::is_forced_default_file) { snprintf(defaults_file_arg, FN_REFLEN, "--defaults-file=%s", - Options::config_file); + Options::Main::config_file); argv_options[1]= defaults_file_arg; argv_options[2]= '\0'; @@ -316,15 +496,12 @@ int Instance_map::load() If the routine failed, we'll simply fallback to defaults in complete_initialization(). */ - if (my_search_option_files(Options::config_file, &argc, + if (my_search_option_files(Options::Main::config_file, &argc, (char ***) &argv, &args_used, process_option, (void*) this)) - log_info("Falling back to compiled-in defaults"); + log_info("Falling back to compiled-in defaults."); - if (complete_initialization()) - return 1; - - return 0; + return complete_initialization(); } @@ -345,3 +522,105 @@ Instance *Instance_map::Iterator::next() return NULL; } + +const char *Instance_map::get_instance_state_name(Instance *instance) +{ + LIST *instance_node; + + if (!instance->is_configured()) + return "misconfigured"; + + if ((instance_node= guardian->find_instance_node(instance)) != NULL) + { + /* The instance is managed by Guardian: we can report precise state. */ + + return Guardian::get_instance_state_name( + guardian->get_instance_state(instance_node)); + } + + /* The instance is not managed by Guardian: we can report status only. */ + + return instance->is_mysqld_running() ? "online" : "offline"; +} + + +/* + Create a new configuration section for mysqld-instance in the config file. + + SYNOPSIS + create_instance_in_file() + instance_name mysqld-instance name + options options for the new mysqld-instance + + RETURN + 0 On success + ER_CONF_FILE_DOES_NOT_EXIST If config file does not exist + ER_ACCESS_OPTION_FILE If config file is not writable or some I/O + error ocurred during writing configuration +*/ + +int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + File cnf_file; + + if (my_access(Options::Main::config_file, W_OK)) + { + log_error("Configuration file (%s) does not exist.", + (const char *) Options::Main::config_file); + return ER_CONF_FILE_DOES_NOT_EXIST; + } + + cnf_file= my_open(Options::Main::config_file, O_WRONLY | O_APPEND, MYF(0)); + + if (cnf_file <= 0) + { + log_error("Can not open configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + return ER_ACCESS_OPTION_FILE; + } + + if (my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"[", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)instance_name->str, instance_name->length, + MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"]", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + char option_str[MAX_OPTION_STR_LEN]; + char *ptr; + int option_str_len; + Named_value option= options->get_element(i); + + ptr= strxnmov(option_str, MAX_OPTION_LEN + 1, option.get_name(), NullS); + + if (option.get_value()[0]) + ptr= strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + option_str_len= ptr - option_str; + + if (my_write(cnf_file, (byte*)option_str, option_str_len, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + } + + my_close(cnf_file, MYF(0)); + + return 0; +} diff --git a/server-tools/instance-manager/instance_map.h b/server-tools/instance-manager/instance_map.h index d3de42f4d80..69d225c89f7 100644 --- a/server-tools/instance-manager/instance_map.h +++ b/server-tools/instance-manager/instance_map.h @@ -17,21 +17,25 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> - -#include "protocol.h" -#include "guardian.h" - #include <my_sys.h> +#include <m_string.h> #include <hash.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +class Guardian; class Instance; +class Named_value_arr; +class Thread_registry; + extern int load_all_groups(char ***groups, const char *filename); extern void free_groups(char **groups); +extern int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options); + /* Instance_map - stores all existing instances @@ -56,31 +60,73 @@ public: }; friend class Iterator; public: - /* returns a pointer to the instance or NULL, if there is no such instance */ - Instance *find(const char *name, uint name_len); + /* + Return a pointer to the instance or NULL, if there is no such instance. + MT-NOTE: must be called under acquired lock. + */ + Instance *find(const LEX_STRING *name); + /* Clear the configuration cache and reload the configuration file. */ int flush_instances(); + + /* The operation is used to check if there is an active instance or not. */ + bool is_there_active_instance(); + void lock(); void unlock(); - int init(); + + bool init(); + /* Process a given option and assign it to appropricate instance. This is required for the option handler, passed to my_search_option_files(). */ - int process_one_option(const char *group, const char *option); + int process_one_option(const LEX_STRING *group, const char *option); + + /* + Add an instance into the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int add_instance(Instance *instance); + + /* + Remove instance from the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int remove_instance(Instance *instance); + + /* + Create a new instance and register it in the internal hash. - Instance_map(const char *default_mysqld_path_arg); + MT-NOTE: the operation must be called under acquired lock. + */ + int create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options); + + Instance_map(); ~Instance_map(); + /* + Retrieve client state name of the given instance. + + MT-NOTE: the options must be called under acquired locks of the following + objects: + - Instance_map; + - Guardian; + */ + const char *get_instance_state_name(Instance *instance); + public: const char *mysqld_path; - Guardian_thread *guardian; + Guardian *guardian; private: /* loads options from config files */ int load(); /* inits instances argv's after all options have been loaded */ - int complete_initialization(); + bool complete_initialization(); private: enum { START_HASH_SIZE = 16 }; pthread_mutex_t LOCK_instance_map; diff --git a/server-tools/instance-manager/instance_options.cc b/server-tools/instance-manager/instance_options.cc index f86f359959b..58e7b630fe9 100644 --- a/server-tools/instance-manager/instance_options.cc +++ b/server-tools/instance-manager/instance_options.cc @@ -20,28 +20,25 @@ #include "instance_options.h" -#include "parse_output.h" -#include "buffer.h" -#include "log.h" - +#include <my_global.h> #include <my_sys.h> -#include <signal.h> #include <m_string.h> -#ifdef __WIN__ -#define NEWLINE_LEN 2 -#else -#define NEWLINE_LEN 1 -#endif +#include <signal.h> + +#include "buffer.h" +#include "instance.h" +#include "log.h" +#include "options.h" +#include "parse_output.h" +#include "priv.h" /* Create "mysqld ..." command in the buffer */ -static inline int create_mysqld_command(Buffer *buf, - const char *mysqld_path_str, - uint mysqld_path_len, - const char *option, - uint option_len) +static inline bool create_mysqld_command(Buffer *buf, + const LEX_STRING *mysqld_path, + const LEX_STRING *option) { int position= 0; @@ -50,24 +47,87 @@ static inline int create_mysqld_command(Buffer *buf, #ifdef __WIN__ buf->append(position++, "\"", 1); #endif - buf->append(position, mysqld_path_str, mysqld_path_len); - position+= mysqld_path_len; + buf->append(position, mysqld_path->str, mysqld_path->length); + position+= mysqld_path->length; #ifdef __WIN__ buf->append(position++, "\"", 1); #endif /* here the '\0' character is copied from the option string */ - buf->append(position, option, option_len); + buf->append(position, option->str, option->length + 1); + + return buf->is_error() ? TRUE : FALSE; + } + return TRUE; +} + +static inline bool is_path_separator(char ch) +{ +#if defined(__WIN__) || defined(__NETWARE__) + /* On windows and netware more delimiters are possible */ + return ch == FN_LIBCHAR || ch == FN_DEVCHAR || ch == '/'; +#else + return ch == FN_LIBCHAR; /* Unixes */ +#endif +} + - return buf->is_error(); +static char *find_last_path_separator(char *path, uint length) +{ + while (length) + { + if (is_path_separator(path[length])) + return path + length; + length--; } - return 1; + return NULL; /* No path separator found */ +} + + + +bool Instance_options::is_option_im_specific(const char *option_name) +{ + static const char *IM_SPECIFIC_OPTIONS[] = + { + "nonguarded", + "mysqld-path", + "shutdown-delay", + NULL + }; + + for (int i= 0; IM_SPECIFIC_OPTIONS[i]; ++i) + { + if (!strcmp(option_name, IM_SPECIFIC_OPTIONS[i])) + return TRUE; + } + + return FALSE; +} + + +Instance_options::Instance_options() + :mysqld_version(NULL), mysqld_socket(NULL), mysqld_datadir(NULL), + mysqld_pid_file(NULL), + nonguarded(NULL), + mysqld_port(NULL), + mysqld_port_val(0), + shutdown_delay(NULL), + shutdown_delay_val(0), + filled_default_options(0) +{ + mysqld_path.str= NULL; + mysqld_path.length= 0; + + mysqld_real_path.str= NULL; + mysqld_real_path.length= 0; + + memset(logs, 0, sizeof(logs)); } /* Get compiled-in value of default_option - SYNOPSYS + SYNOPSIS get_default_option() result buffer to put found value result_len buffer size @@ -87,13 +147,13 @@ int Instance_options::get_default_option(char *result, size_t result_len, const char *option_name) { int rc= 1; - char verbose_option[]= " --no-defaults --verbose --help"; + LEX_STRING verbose_option= + { C_STRING_WITH_LEN(" --no-defaults --verbose --help") }; - /* reserve space fot the path + option + final '\0' */ - Buffer cmd(mysqld_path_len + sizeof(verbose_option)); + /* reserve space for the path + option + final '\0' */ + Buffer cmd(mysqld_path.length + verbose_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - verbose_option, sizeof(verbose_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &verbose_option)) goto err; /* +2 eats first "--" from the option string (E.g. "--datadir") */ @@ -107,7 +167,7 @@ err: /* Fill mysqld_version option (used at initialization stage) - SYNOPSYS + SYNOPSIS fill_instance_version() DESCRIPTION @@ -115,46 +175,56 @@ err: Get mysqld version string from "mysqld --version" output. RETURN - 0 - ok - 1 - error occured + FALSE - ok + TRUE - error occured */ -int Instance_options::fill_instance_version() +bool Instance_options::fill_instance_version() { - enum { MAX_VERSION_STRING_LENGTH= 160 }; - char result[MAX_VERSION_STRING_LENGTH]; - char version_option[]= " --no-defaults --version"; - int rc= 1; - Buffer cmd(mysqld_path_len + sizeof(version_option)); + char result[MAX_VERSION_LENGTH]; + LEX_STRING version_option= + { C_STRING_WITH_LEN(" --no-defaults --version") }; + Buffer cmd(mysqld_path.length + version_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - version_option, sizeof(version_option))) - goto err; + if (create_mysqld_command(&cmd, &mysqld_path, &version_option)) + { + log_error("Failed to get version of '%s': out of memory.", + (const char *) mysqld_path.str); + return TRUE; + } - bzero(result, MAX_VERSION_STRING_LENGTH); + bzero(result, MAX_VERSION_LENGTH); - rc= parse_output_and_get_value(cmd.buffer, mysqld_real_path, - result, MAX_VERSION_STRING_LENGTH, - GET_LINE); + if (parse_output_and_get_value(cmd.buffer, "Ver", result, + MAX_VERSION_LENGTH, GET_LINE)) + { + log_error("Failed to get version of '%s': unexpected output.", + (const char *) mysqld_path.str); + return TRUE; + } + + DBUG_ASSERT(*result != '\0'); - if (*result != '\0') { + char *start; /* chop the newline from the end of the version string */ result[strlen(result) - NEWLINE_LEN]= '\0'; - mysqld_version= strdup_root(&alloc, result); + /* trim leading whitespaces */ + start= result; + while (my_isspace(default_charset_info, *start)) + ++start; + + mysqld_version= strdup_root(&alloc, start); } -err: - if (rc) - log_error("fill_instance_version: Failed to get version of '%s'", - mysqld_path); - return rc; + + return FALSE; } /* Fill mysqld_real_path - SYNOPSYS + SYNOPSIS fill_mysqld_real_path() DESCRIPTION @@ -166,46 +236,54 @@ err: script(for example libtool) or a symlink. RETURN - 0 - ok - 1 - error occured + FALSE - ok + TRUE - error occured */ -int Instance_options::fill_mysqld_real_path() +bool Instance_options::fill_mysqld_real_path() { char result[FN_REFLEN]; - char help_option[]= " --no-defaults --help"; - int rc= 1; - Buffer cmd(mysqld_path_len + sizeof(help_option)); + LEX_STRING help_option= + { C_STRING_WITH_LEN(" --no-defaults --help") }; + Buffer cmd(mysqld_path.length + help_option.length); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - help_option, sizeof(help_option))) - goto err; + if (create_mysqld_command(&cmd, &mysqld_path, &help_option)) + { + log_error("Failed to get real path of '%s': out of memory.", + (const char *) mysqld_path.str); + return TRUE; + } bzero(result, FN_REFLEN); - rc= parse_output_and_get_value(cmd.buffer, "Usage: ", + if (parse_output_and_get_value(cmd.buffer, "Usage: ", result, FN_REFLEN, - GET_LINE); + GET_LINE)) + { + log_error("Failed to get real path of '%s': unexpected output.", + (const char *) mysqld_path.str); + return TRUE; + } + + DBUG_ASSERT(*result != '\0'); - if (*result != '\0') { char* options_str; /* chop the path of at [OPTIONS] */ if ((options_str= strstr(result, "[OPTIONS]"))) *options_str= '\0'; - mysqld_real_path= strdup_root(&alloc, result); + mysqld_real_path.str= strdup_root(&alloc, result); + mysqld_real_path.length= strlen(mysqld_real_path.str); } -err: - if (rc) - log_error("fill_mysqld_real_path: Failed to get real path of mysqld"); - return rc; + + return FALSE; } /* Fill various log options - SYNOPSYS + SYNOPSIS fill_log_options() DESCRIPTION @@ -215,15 +293,13 @@ err: file name and placement. RETURN - 0 - ok - 1 - error occured + FALSE - ok + TRUE - error occured */ -int Instance_options::fill_log_options() +bool Instance_options::fill_log_options() { Buffer buff; - uint position= 0; - char **tmp_argv= argv; enum { MAX_LOG_OPTION_LENGTH= 256 }; char datadir[MAX_LOG_OPTION_LENGTH]; char hostname[MAX_LOG_OPTION_LENGTH]; @@ -247,13 +323,12 @@ int Instance_options::fill_log_options() if (mysqld_datadir == NULL) { if (get_default_option(datadir, MAX_LOG_OPTION_LENGTH, "--datadir")) - goto err; + return TRUE; } else { /* below is safe, as --datadir always has a value */ - strmake(datadir, - strchr(mysqld_datadir, '=') + 1, MAX_LOG_OPTION_LENGTH - 1); + strmake(datadir, mysqld_datadir, MAX_LOG_OPTION_LENGTH - 1); } if (gethostname(hostname,sizeof(hostname)-1) < 0) @@ -286,7 +361,7 @@ int Instance_options::fill_log_options() if ((MAX_LOG_OPTION_LENGTH - strlen(full_name)) <= strlen(log_files->default_suffix)) - goto err; + return TRUE; strmov(full_name + strlen(full_name), log_files->default_suffix); @@ -306,22 +381,20 @@ int Instance_options::fill_log_options() datadir, "", MY_UNPACK_FILENAME | MY_SAFE_PATH); if (!(*(log_files->value)= strdup_root(&alloc, full_name))) - goto err; + return TRUE; } } } } - return 0; -err: - return 1; + return FALSE; } /* Get the full pid file name with path - SYNOPSYS + SYNOPSIS get_pid_filaname() result buffer to sotre the pidfile value @@ -339,7 +412,6 @@ err: int Instance_options::get_pid_filename(char *result) { - const char *pid_file= mysqld_pid_file; char datadir[MAX_PATH_LEN]; if (mysqld_datadir == NULL) @@ -349,14 +421,10 @@ int Instance_options::get_pid_filename(char *result) return 1; } else - strxnmov(datadir, MAX_PATH_LEN - 1, strchr(mysqld_datadir, '=') + 1, - "/", NullS); - - DBUG_ASSERT(mysqld_pid_file); - pid_file= strchr(pid_file, '=') + 1; + strxnmov(datadir, MAX_PATH_LEN - 1, mysqld_datadir, "/", NullS); /* get the full path to the pidfile */ - my_load_path(result, pid_file, datadir); + my_load_path(result, mysqld_pid_file, datadir); return 0; } @@ -367,7 +435,7 @@ int Instance_options::unlink_pidfile() } -pid_t Instance_options::get_pid() +pid_t Instance_options::load_pid() { FILE *pid_file_stream; @@ -386,36 +454,66 @@ pid_t Instance_options::get_pid() } -int Instance_options::complete_initialization(const char *default_path, - uint instance_type) +bool Instance_options::complete_initialization() { + int arg_idx; const char *tmp; char *end; + char bin_name_firstchar; - if (!mysqld_path) + if (!mysqld_path.str) { - // Need one extra byte, as convert_dirname() adds a slash at the end. - if (!(mysqld_path= alloc_root(&alloc, strlen(default_path) + 2))) - goto err; - strcpy((char *)mysqld_path, default_path); + /* + Need to copy the path to allocated memory, as convert_dirname() might + need to change it + */ + mysqld_path.str= + alloc_root(&alloc, strlen(Options::Main::default_mysqld_path) + 1); + + if (!mysqld_path.str) + return TRUE; + + strcpy(mysqld_path.str, Options::Main::default_mysqld_path); } - // it's safe to cast this to char* since this is a buffer we are allocating - end= convert_dirname((char*)mysqld_path, mysqld_path, NullS); - end[-1]= 0; + mysqld_path.length= strlen(mysqld_path.str); - mysqld_path_len= strlen(mysqld_path); + /* + If we found path with no slashes (end == NULL), we should not call + convert_dirname() at all. As we have got relative path to the binary. + That is, user supposes that mysqld resides in the same dir as + mysqlmanager. + */ + if ((end= find_last_path_separator(mysqld_path.str, mysqld_path.length))) + { + bin_name_firstchar= end[1]; + + /* + Below we will conver the path to mysqld in the case, it was given + in a format of another OS (e.g. uses '/' instead of '\' etc). + Here we strip the path to get rid of the binary name ("mysqld"), + we do it by removing first letter of the binary name (e.g. 'm' + in "mysqld"). Later we put it back. + */ + end[1]= 0; + + /* convert dirname to the format of current OS */ + convert_dirname((char*)mysqld_path.str, mysqld_path.str, NullS); + + /* put back the first character of the binary name*/ + end[1]= bin_name_firstchar; + } if (mysqld_port) - mysqld_port_val= atoi(strchr(mysqld_port, '=') + 1); + mysqld_port_val= atoi(mysqld_port); if (shutdown_delay) shutdown_delay_val= atoi(shutdown_delay); if (!(tmp= strdup_root(&alloc, "--no-defaults"))) - goto err; + return TRUE; - if (!(mysqld_pid_file)) + if (!mysqld_pid_file) { char pidfilename[MAX_PATH_LEN]; char hostname[MAX_PATH_LEN]; @@ -424,127 +522,158 @@ int Instance_options::complete_initialization(const char *default_path, If we created only one istance [mysqld], because no config. files were found, we would like to model mysqld pid file values. */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", hostname, - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, hostname, ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - "-", hostname, ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, "-", + hostname, ".pid", NullS); } else { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", "mysql", - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, "mysql", ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, ".pid", + NullS); } - add_option(pidfilename); + Named_value option((char *) "pid-file", pidfilename); + + set_option(&option); } if (get_pid_filename(pid_file_with_path)) - goto err; + return TRUE; /* we need to reserve space for the final zero + possible default options */ if (!(argv= (char**) - alloc_root(&alloc, (options_array.elements + 1 + alloc_root(&alloc, (get_num_options() + 1 + MAX_NUMBER_OF_DEFAULT_OPTIONS) * sizeof(char*)))) - goto err; + return TRUE; + filled_default_options= 0; /* the path must be first in the argv */ - if (add_to_argv(mysqld_path)) - goto err; + if (add_to_argv(mysqld_path.str)) + return TRUE; if (add_to_argv(tmp)) - goto err; + return TRUE; + + arg_idx= filled_default_options; + for (int opt_idx= 0; opt_idx < get_num_options(); ++opt_idx) + { + char option_str[MAX_OPTION_STR_LEN]; + Named_value option= get_option(opt_idx); + + if (is_option_im_specific(option.get_name())) + continue; + + char *ptr= strxnmov(option_str, MAX_OPTION_LEN + 3, "--", option.get_name(), + NullS); + + if (option.get_value()[0]) + strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + argv[arg_idx++]= strdup_root(&alloc, option_str); + } - memcpy((gptr) (argv + filled_default_options), options_array.buffer, - options_array.elements*sizeof(char*)); - argv[filled_default_options + options_array.elements]= 0; + argv[arg_idx]= 0; if (fill_log_options() || fill_mysqld_real_path() || fill_instance_version()) - goto err; + return TRUE; - return 0; + return FALSE; +} -err: - return 1; + +bool Instance_options::set_option(Named_value *option) +{ + bool err_status; + int idx= find_option(option->get_name()); + char *option_name_str; + char *option_value_str; + + if (!(option_name_str= Named_value::alloc_str(option->get_name()))) + return TRUE; + + if (!(option_value_str= Named_value::alloc_str(option->get_value()))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } + + Named_value option_copy(option_name_str, option_value_str); + + if (idx < 0) + err_status= options.add_element(&option_copy); + else + err_status= options.replace_element(idx, &option_copy); + + if (!err_status) + update_var(option_copy.get_name(), option_copy.get_value()); + else + option_copy.free(); + + return err_status; } -/* - Assigns given value to the appropriate option from the class. +void Instance_options::unset_option(const char *option_name) +{ + int idx= find_option(option_name); - SYNOPSYS - add_option() - option string with the option prefixed by -- + if (idx < 0) + return; /* the option has not been set. */ - DESCRIPTION + options.remove_element(idx); - The method is called from the option handling routine. + update_var(option_name, NULL); +} - RETURN - 0 - ok - 1 - error occured -*/ -int Instance_options::add_option(const char* option) +void Instance_options::update_var(const char *option_name, + const char *option_value) { - char *tmp; - enum { SAVE_VALUE= 1, SAVE_WHOLE, SAVE_WHOLE_AND_ADD }; - struct selected_options_st + struct options_st { const char *name; - uint length; - const char **value; - uint type; - } options[]= + uint name_len; + const char **var; + } options_def[]= { - {"--socket=", 9, &mysqld_socket, SAVE_WHOLE_AND_ADD}, - {"--port=", 7, &mysqld_port, SAVE_WHOLE_AND_ADD}, - {"--datadir=", 10, &mysqld_datadir, SAVE_WHOLE_AND_ADD}, - {"--bind-address=", 15, &mysqld_bind_address, SAVE_WHOLE_AND_ADD}, - {"--pid-file=", 11, &mysqld_pid_file, SAVE_WHOLE_AND_ADD}, - {"--mysqld-path=", 14, &mysqld_path, SAVE_VALUE}, - {"--nonguarded", 9, &nonguarded, SAVE_WHOLE}, - {"--shutdown_delay", 9, &shutdown_delay, SAVE_VALUE}, - {NULL, 0, NULL, 0} + {"socket", 6, &mysqld_socket}, + {"port", 4, &mysqld_port}, + {"datadir", 7, &mysqld_datadir}, + {"pid-file", 8, &mysqld_pid_file}, + {"nonguarded", 10, &nonguarded}, + {"mysqld-path", 11, (const char **) &mysqld_path.str}, + {"shutdown-delay", 14, &shutdown_delay}, + {NULL, 0, NULL} }; - struct selected_options_st *selected_options; - if (!(tmp= strdup_root(&alloc, option))) - goto err; + for (options_st *opt= options_def; opt->name; ++opt) + { + if (!strncmp(opt->name, option_name, opt->name_len)) + { + *(opt->var)= option_value; + break; + } + } +} - for (selected_options= options; selected_options->name; selected_options++) - { - if (strncmp(tmp, selected_options->name, selected_options->length) == 0) - switch (selected_options->type) { - case SAVE_WHOLE_AND_ADD: - *(selected_options->value)= tmp; - insert_dynamic(&options_array,(gptr) &tmp); - return 0; - case SAVE_VALUE: - *(selected_options->value)= strchr(tmp, '=') + 1; - return 0; - case SAVE_WHOLE: - *(selected_options->value)= tmp; - return 0; - default: - break; - } - } - - /* if we haven't returned earlier we should just save the option */ - insert_dynamic(&options_array,(gptr) &tmp); - return 0; +int Instance_options::find_option(const char *option_name) +{ + for (int i= 0; i < get_num_options(); i++) + { + if (!strcmp(get_option(i).get_name(), option_name)) + return i; + } -err: - return 1; + return -1; } @@ -562,7 +691,10 @@ int Instance_options::add_to_argv(const char* option) void Instance_options::print_argv() { int i; - printf("printing out an instance %s argv:\n", instance_name); + + printf("printing out an instance %s argv:\n", + (const char *) instance_name.str); + for (i=0; argv[i] != NULL; i++) printf("argv: %s\n", argv[i]); } @@ -570,32 +702,56 @@ void Instance_options::print_argv() /* We execute this function to initialize some options. - Return value: 0 - ok. 1 - unable to allocate memory. + + RETURN + FALSE - ok + TRUE - memory allocation error */ -int Instance_options::init(const char *instance_name_arg) +bool Instance_options::init(const LEX_STRING *instance_name_arg) { - instance_name_len= strlen(instance_name_arg); + instance_name.length= instance_name_arg->length; init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); - if (my_init_dynamic_array(&options_array, sizeof(char*), 0, 32)) - goto err; + if (options.init()) + return TRUE; - if (!(instance_name= strmake_root(&alloc, (char*) instance_name_arg, - instance_name_len))) - goto err; + if (!(instance_name.str= strmake_root(&alloc, instance_name_arg->str, + instance_name_arg->length))) + return TRUE; - return 0; - -err: - return 1; + return FALSE; } Instance_options::~Instance_options() { free_root(&alloc, MYF(0)); - delete_dynamic(&options_array); +} + + +uint Instance_options::get_shutdown_delay() const +{ + static const uint DEFAULT_SHUTDOWN_DELAY= 35; + + /* + NOTE: it is important to check shutdown_delay here, but use + shutdown_delay_val. The idea is that if the option is unset, + shutdown_delay will be NULL, but shutdown_delay_val will not be reset. + */ + + return shutdown_delay ? shutdown_delay_val : DEFAULT_SHUTDOWN_DELAY; +} + +int Instance_options::get_mysqld_port() const +{ + /* + NOTE: it is important to check mysqld_port here, but use mysqld_port_val. + The idea is that if the option is unset, mysqld_port will be NULL, but + mysqld_port_val will not be reset. + */ + + return mysqld_port ? mysqld_port_val : 0; } diff --git a/server-tools/instance-manager/instance_options.h b/server-tools/instance-manager/instance_options.h index b316dbf00fc..7c1e1a8dcf3 100644 --- a/server-tools/instance-manager/instance_options.h +++ b/server-tools/instance-manager/instance_options.h @@ -18,8 +18,9 @@ #include <my_global.h> #include <my_sys.h> + #include "parse.h" -#include "portability.h" +#include "portability.h" /* for pid_t on Win32 */ #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface @@ -35,30 +36,35 @@ don't have to synchronize between threads. */ -#define USUAL_INSTANCE 0 -#define DEFAULT_SINGLE_INSTANCE 1 - class Instance_options { public: - Instance_options() : - mysqld_version(0), mysqld_socket(0), mysqld_datadir(0), - mysqld_bind_address(0), mysqld_pid_file(0), mysqld_port(0), - mysqld_port_val(0), mysqld_path(0), mysqld_real_path(0), - nonguarded(0), shutdown_delay(0), - shutdown_delay_val(0), filled_default_options(0) - {} + /* The operation is used to check if the option is IM-specific or not. */ + static bool is_option_im_specific(const char *option_name); + +public: + Instance_options(); ~Instance_options(); + /* fills in argv */ - int complete_initialization(const char *default_path, uint instance_type); + bool complete_initialization(); + + bool set_option(Named_value *option); + void unset_option(const char *option_name); + + inline int get_num_options() const; + inline Named_value get_option(int idx) const; - int add_option(const char* option); - int init(const char *instance_name_arg); - pid_t get_pid(); +public: + bool init(const LEX_STRING *instance_name_arg); + pid_t load_pid(); int get_pid_filename(char *result); int unlink_pidfile(); void print_argv(); + uint get_shutdown_delay() const; + int get_mysqld_port() const; + public: /* We need this value to be greater or equal then FN_REFLEN found in @@ -66,7 +72,6 @@ public: */ enum { MAX_PATH_LEN= 512 }; enum { MAX_NUMBER_OF_DEFAULT_OPTIONS= 2 }; - enum { MEM_ROOT_BLOCK_SIZE= 512 }; char pid_file_with_path[MAX_PATH_LEN]; char **argv; /* @@ -77,33 +82,47 @@ public: /* We need the some options, so we store them as a separate pointers */ const char *mysqld_socket; const char *mysqld_datadir; - const char *mysqld_bind_address; const char *mysqld_pid_file; - const char *mysqld_port; - uint mysqld_port_val; - const char *instance_name; - uint instance_name_len; - const char *mysqld_path; - uint mysqld_path_len; - const char *mysqld_real_path; + LEX_STRING instance_name; + LEX_STRING mysqld_path; + LEX_STRING mysqld_real_path; const char *nonguarded; - const char *shutdown_delay; - uint shutdown_delay_val; /* log enums are defined in parse.h */ char *logs[3]; - /* this value is computed and cashed here */ - DYNAMIC_ARRAY options_array; private: - int fill_log_options(); - int fill_instance_version(); - int fill_mysqld_real_path(); + bool fill_log_options(); + bool fill_instance_version(); + bool fill_mysqld_real_path(); int add_to_argv(const char *option); int get_default_option(char *result, size_t result_len, const char *option_name); + + void update_var(const char *option_name, const char *option_value); + int find_option(const char *option_name); + private: + const char *mysqld_port; + uint mysqld_port_val; + const char *shutdown_delay; + uint shutdown_delay_val; + uint filled_default_options; MEM_ROOT alloc; + + Named_value_arr options; }; + +inline int Instance_options::get_num_options() const +{ + return options.get_size(); +} + + +inline Named_value Instance_options::get_option(int idx) const +{ + return options.get_element(idx); +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H */ diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc index 3f60c2cc74a..f18a594985c 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -19,21 +19,22 @@ #endif #include "listener.h" -#include "priv.h" -#include <m_string.h> + +#include <my_global.h> #include <mysql.h> #include <violite.h> + +#include <sys/stat.h> #ifndef __WIN__ #include <sys/un.h> #endif -#include <sys/stat.h> -#include "thread_registry.h" -#include "options.h" -#include "instance_map.h" #include "log.h" #include "mysql_connection.h" +#include "options.h" #include "portability.h" +#include "priv.h" +#include "thread_registry.h" static void set_non_blocking(int socket) @@ -57,48 +58,18 @@ static void set_no_inherit(int socket) } -/* - Listener_thread - incapsulates listening functionality -*/ - -class Listener_thread: public Listener_thread_args -{ -public: - Listener_thread(const Listener_thread_args &args); - ~Listener_thread(); - void run(); -private: - static const int LISTEN_BACK_LOG_SIZE= 5; /* standard backlog size */ - ulong total_connection_count; - Thread_info thread_info; - - int sockets[2]; - int num_sockets; - fd_set read_fds; -private: - void handle_new_mysql_connection(Vio *vio); - int create_tcp_socket(); - int create_unix_socket(struct sockaddr_un &unix_socket_address); -}; - - -Listener_thread::Listener_thread(const Listener_thread_args &args) : - Listener_thread_args(args.thread_registry, args.options, args.user_map, - args.instance_map) - ,total_connection_count(0) - ,thread_info(pthread_self()) - ,num_sockets(0) -{ -} - - -Listener_thread::~Listener_thread() +Listener::Listener(Thread_registry *thread_registry_arg, + User_map *user_map_arg) + :thread_registry(thread_registry_arg), + user_map(user_map_arg), + total_connection_count(0), + num_sockets(0) { } /* - Listener_thread::run() - listen all supported sockets and spawn a thread + Listener::run() - listen all supported sockets and spawn a thread to handle incoming connection. Using 'die' in case of syscall failure is OK now - we don't hold any resources and 'die' kills the signal thread automatically. To be rewritten @@ -107,24 +78,17 @@ Listener_thread::~Listener_thread() architecture. */ -void Listener_thread::run() +void Listener::run() { int i, n= 0; #ifndef __WIN__ - /* we use this var to check whether we are running on LinuxThreads */ - pid_t thread_pid; - - thread_pid= getpid(); - struct sockaddr_un unix_socket_address; - /* set global variable */ - linuxthreads= (thread_pid != manager_pid); #endif - thread_registry.register_thread(&thread_info); + log_info("Listener: started."); - my_thread_init(); + thread_registry->register_thread(&thread_info); FD_ZERO(&read_fds); @@ -143,7 +107,7 @@ void Listener_thread::run() n++; timeval tv; - while (!thread_registry.is_shutdown()) + while (!thread_registry->is_shutdown()) { fd_set read_fds_arg= read_fds; /* @@ -163,8 +127,8 @@ void Listener_thread::run() if (rc == 0 || rc == -1) { if (rc == -1 && errno != EINTR) - log_error("Listener_thread::run(): select() failed, %s", - strerror(errno)); + log_error("Listener: select() failed: %s.", + (const char *) strerror(errno)); continue; } @@ -180,10 +144,12 @@ void Listener_thread::run() { set_no_inherit(client_fd); - Vio *vio= vio_new(client_fd, socket_index == 0 ? - VIO_TYPE_SOCKET : VIO_TYPE_TCPIP, - socket_index == 0 ? 1 : 0); - if (vio != 0) + struct st_vio *vio= + vio_new(client_fd, + socket_index == 0 ? VIO_TYPE_SOCKET : VIO_TYPE_TCPIP, + socket_index == 0 ? 1 : 0); + + if (vio != NULL) handle_new_mysql_connection(vio); else { @@ -197,7 +163,7 @@ void Listener_thread::run() /* III. Release all resources and exit */ - log_info("Listener_thread::run(): shutdown requested, exiting..."); + log_info("Listener: shutdown requested, exiting..."); for (i= 0; i < num_sockets; i++) close(sockets[i]); @@ -206,8 +172,9 @@ void Listener_thread::run() unlink(unix_socket_address.sun_path); #endif - thread_registry.unregister_thread(&thread_info); - my_thread_end(); + thread_registry->unregister_thread(&thread_info); + + log_info("Listener: finished."); return; err: @@ -215,13 +182,12 @@ err: for (i= 0; i < num_sockets; i++) close(sockets[i]); - thread_registry.unregister_thread(&thread_info); - thread_registry.request_shutdown(); - my_thread_end(); + thread_registry->unregister_thread(&thread_info); + thread_registry->request_shutdown(); return; } -int Listener_thread::create_tcp_socket() +int Listener::create_tcp_socket() { /* value to be set by setsockopt */ int arg= 1; @@ -229,8 +195,8 @@ int Listener_thread::create_tcp_socket() int ip_socket= socket(AF_INET, SOCK_STREAM, 0); if (ip_socket == INVALID_SOCKET) { - log_error("Listener_thead::run(): socket(AF_INET) failed, %s", - strerror(errno)); + log_error("Listener: socket(AF_INET) failed: %s.", + (const char *) strerror(errno)); return -1; } @@ -238,14 +204,16 @@ int Listener_thread::create_tcp_socket() bzero(&ip_socket_address, sizeof(ip_socket_address)); ulong im_bind_addr; - if (options.bind_address != 0) + if (Options::Main::bind_address != 0) { - if ((im_bind_addr= (ulong) inet_addr(options.bind_address)) == INADDR_NONE) + im_bind_addr= (ulong) inet_addr(Options::Main::bind_address); + + if (im_bind_addr == (ulong) INADDR_NONE) im_bind_addr= htonl(INADDR_ANY); } else im_bind_addr= htonl(INADDR_ANY); - uint im_port= options.port_number; + uint im_port= Options::Main::port_number; ip_socket_address.sin_family= AF_INET; ip_socket_address.sin_addr.s_addr= im_bind_addr; @@ -258,16 +226,16 @@ int Listener_thread::create_tcp_socket() if (bind(ip_socket, (struct sockaddr *) &ip_socket_address, sizeof(ip_socket_address))) { - log_error("Listener_thread::run(): bind(ip socket) failed, '%s'", - strerror(errno)); + log_error("Listener: bind(ip socket) failed: %s.", + (const char *) strerror(errno)); close(ip_socket); return -1; } if (listen(ip_socket, LISTEN_BACK_LOG_SIZE)) { - log_error("Listener_thread::run(): listen(ip socket) failed, %s", - strerror(errno)); + log_error("Listener: listen(ip socket) failed: %s.", + (const char *) strerror(errno)); close(ip_socket); return -1; } @@ -280,26 +248,27 @@ int Listener_thread::create_tcp_socket() FD_SET(ip_socket, &read_fds); sockets[num_sockets++]= ip_socket; - log_info("accepting connections on ip socket (port: %d)", (int) im_port); + log_info("Listener: accepting connections on ip socket (port: %d)...", + (int) im_port); return 0; } #ifndef __WIN__ -int Listener_thread:: +int Listener:: create_unix_socket(struct sockaddr_un &unix_socket_address) { int unix_socket= socket(AF_UNIX, SOCK_STREAM, 0); if (unix_socket == INVALID_SOCKET) { - log_error("Listener_thead::run(): socket(AF_UNIX) failed, %s", - strerror(errno)); + log_error("Listener: socket(AF_UNIX) failed: %s.", + (const char *) strerror(errno)); return -1; } bzero(&unix_socket_address, sizeof(unix_socket_address)); unix_socket_address.sun_family= AF_UNIX; - strmake(unix_socket_address.sun_path, options.socket_file_name, + strmake(unix_socket_address.sun_path, Options::Main::socket_file_name, sizeof(unix_socket_address.sun_path)); unlink(unix_socket_address.sun_path); // in case we have stale socket file @@ -311,9 +280,9 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) if (bind(unix_socket, (struct sockaddr *) &unix_socket_address, sizeof(unix_socket_address))) { - log_error("Listener_thread::run(): bind(unix socket) failed, " - "socket file name is '%s', error '%s'", - unix_socket_address.sun_path, strerror(errno)); + log_error("Listener: bind(unix socket) failed for '%s': %s.", + (const char *) unix_socket_address.sun_path, + (const char *) strerror(errno)); close(unix_socket); return -1; } @@ -322,8 +291,8 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) if (listen(unix_socket, LISTEN_BACK_LOG_SIZE)) { - log_error("Listener_thread::run(): listen(unix socket) failed, %s", - strerror(errno)); + log_error("Listener: listen(unix socket) failed: %s.", + (const char *) strerror(errno)); close(unix_socket); return -1; } @@ -334,8 +303,8 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) /* make sure that instances won't be listening our sockets */ set_no_inherit(unix_socket); - log_info("accepting connections on unix socket '%s'", - unix_socket_address.sun_path); + log_info("Listener: accepting connections on unix socket '%s'...", + (const char *) unix_socket_address.sun_path); sockets[num_sockets++]= unix_socket; FD_SET(unix_socket, &read_fds); return 0; @@ -345,51 +314,21 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) /* Create new mysql connection. Created thread is responsible for deletion of - the Mysql_connection_thread_args and Vio instances passed to it. - SYNOPSYS + the Mysql_connection and Vio instances passed to it. + SYNOPSIS handle_new_mysql_connection() */ -void Listener_thread::handle_new_mysql_connection(Vio *vio) +void Listener::handle_new_mysql_connection(struct st_vio *vio) { - if (Mysql_connection_thread_args *mysql_thread_args= - new Mysql_connection_thread_args(vio, thread_registry, user_map, - ++total_connection_count, - instance_map) - ) + Mysql_connection *mysql_connection= + new Mysql_connection(thread_registry, user_map, + vio, ++total_connection_count); + if (mysql_connection == NULL || mysql_connection->start(Thread::DETACHED)) { - /* - Initialize thread attributes to create detached thread; it seems - easier to do it ad-hoc than have a global variable for attributes. - */ - pthread_t mysql_thd_id; - pthread_attr_t mysql_thd_attr; - pthread_attr_init(&mysql_thd_attr); - pthread_attr_setdetachstate(&mysql_thd_attr, PTHREAD_CREATE_DETACHED); - if (set_stacksize_n_create_thread(&mysql_thd_id, &mysql_thd_attr, - mysql_connection, mysql_thread_args)) - { - delete mysql_thread_args; - vio_delete(vio); - log_error("handle_one_mysql_connection():" - "set_stacksize_n_create_thread(mysql) failed"); - } - pthread_attr_destroy(&mysql_thd_attr); - } - else + log_error("Listener: can not start connection handler."); + delete mysql_connection; vio_delete(vio); + } + /* The connection will delete itself when the thread is finished */ } - - -pthread_handler_t listener(void *arg) -{ - Listener_thread_args *args= (Listener_thread_args *) arg; - Listener_thread listener(*args); - listener.run(); - /* - args is a stack variable because listener thread lives as long as the - manager process itself - */ - return 0; -} - diff --git a/server-tools/instance-manager/listener.h b/server-tools/instance-manager/listener.h index 28ccbf91731..811744f8ea6 100644 --- a/server-tools/instance-manager/listener.h +++ b/server-tools/instance-manager/listener.h @@ -16,37 +16,46 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include <my_pthread.h> +#include "thread_registry.h" #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif - -pthread_handler_t listener(void *arg); - class Thread_registry; -struct Options; class User_map; -class Instance_map; -struct Listener_thread_args +/** + Listener - a thread listening on sockets and spawning + connection threads. +*/ + +class Listener: public Thread { - Thread_registry &thread_registry; - const Options &options; - const User_map &user_map; - Instance_map &instance_map; - - Listener_thread_args(Thread_registry &thread_registry_arg, - const Options &options_arg, - const User_map &user_map_arg, - Instance_map &instance_map_arg) : - thread_registry(thread_registry_arg) - ,options(options_arg) - ,user_map(user_map_arg) - ,instance_map(instance_map_arg) - {} +public: + Listener(Thread_registry *thread_registry_arg, User_map *user_map_arg); + +protected: + virtual void run(); + +private: + static const int LISTEN_BACK_LOG_SIZE= 5; /* standard backlog size */ + +private: + Thread_info thread_info; + Thread_registry *thread_registry; + User_map *user_map; + + ulong total_connection_count; + + int sockets[2]; + int num_sockets; + fd_set read_fds; + +private: + void handle_new_mysql_connection(struct st_vio *vio); + int create_tcp_socket(); + int create_unix_socket(struct sockaddr_un &unix_socket_address); }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H diff --git a/server-tools/instance-manager/log.cc b/server-tools/instance-manager/log.cc index 3f54bc0649a..7ff45a15432 100644 --- a/server-tools/instance-manager/log.cc +++ b/server-tools/instance-manager/log.cc @@ -14,14 +14,16 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> - #include "log.h" -#include "portability.h" -#include <stdarg.h> + +#include <my_global.h> #include <m_string.h> #include <my_sys.h> +#include <stdarg.h> + +#include "portability.h" /* for vsnprintf() on Windows. */ + /* TODO: - add flexible header support @@ -31,11 +33,12 @@ /* Format log entry and write it to the given stream. - SYNOPSYS + SYNOPSIS log() */ -static inline void log(FILE *file, const char *format, va_list args) +static void log(FILE *file,const char *level_tag, const char *format, + va_list args) { /* log() should be thread-safe; it implies that we either call fprintf() @@ -50,14 +53,17 @@ static inline void log(FILE *file, const char *format, va_list args) struct tm bd_time; // broken-down time localtime_r(&now, &bd_time); - char buff_date[32]; - sprintf(buff_date, "%02d%02d%02d %2d:%02d:%02d\t", - bd_time.tm_year % 100, - bd_time.tm_mon + 1, - bd_time.tm_mday, - bd_time.tm_hour, - bd_time.tm_min, - bd_time.tm_sec); + char buff_date[128]; + sprintf(buff_date, "[%d/%lu] [%02d/%02d/%02d %02d:%02d:%02d] [%s] ", + (int) getpid(), + (unsigned long) pthread_self(), + (int) bd_time.tm_year % 100, + (int) bd_time.tm_mon + 1, + (int) bd_time.tm_mday, + (int) bd_time.tm_hour, + (int) bd_time.tm_min, + (int) bd_time.tm_sec, + (const char *) level_tag); /* Format the message */ char buff_stack[256]; @@ -71,7 +77,7 @@ static inline void log(FILE *file, const char *format, va_list args) { int size= sizeof(buff_stack) * 2; buff_msg= (char*) my_malloc(size, MYF(0)); - while (true) + while (TRUE) { if (buff_msg == 0) { @@ -105,57 +111,73 @@ static inline void log(FILE *file, const char *format, va_list args) /* don't fflush() the file: buffering strategy is set in log_init() */ } +/************************************************************************** + Logging: implementation of public interface. +**************************************************************************/ -void log_error(const char *format, ...) -{ - va_list args; - va_start(args, format); - log(stderr, format, args); - va_end(args); -} +/* + The function initializes logging sub-system. + SYNOPSIS + log_init() +*/ -void log_info(const char *format, ...) +void log_init() { - va_list args; - va_start(args, format); - log(stdout, format, args); - va_end(args); + /* + stderr is unbuffered by default; there is no good of line buffering, + as all logging is performed linewise - so remove buffering from stdout + also + */ + setbuf(stdout, 0); } -/* TODO: rewrite with buffering print */ -void print_info(const char *format, ...) + +/* + The function is intended to log error messages. It precedes a message + with date, time and [ERROR] tag and print it to the stderr. + + SYNOPSIS + log_error() + format [IN] format string + ... [IN] arguments to format +*/ + +void log_error(const char *format, ...) { va_list args; va_start(args, format); - vfprintf(stdout, format, args); + log(stderr, "ERROR", format, args); va_end(args); } -void print_error(const char *format, ...) + +/* + The function is intended to log information messages. It precedes + a message with date, time and [INFO] tag and print it to the stdout. + + SYNOPSIS + log_error() + format [IN] format string + ... [IN] arguments to format +*/ + +void log_info(const char *format, ...) { va_list args; va_start(args, format); - vfprintf(stderr, format, args); + log(stdout, "INFO", format, args); va_end(args); } /* - log_init() - RETURN VALUE - 0 ok - !0 error -*/ + The function prints information to the error log and eixt(1). -void log_init() -{ - /* - stderr is unbuffered by default; there is no good of line buffering, - as all logging is performed linewise - so remove buffering from stdout - also - */ - setbuf(stdout, 0); -} + SYNOPSIS + die() + format [IN] format string + ... [IN] arguments to format +*/ void die(const char *format, ...) { diff --git a/server-tools/instance-manager/log.h b/server-tools/instance-manager/log.h index 825d7515513..9c47dbe04f5 100644 --- a/server-tools/instance-manager/log.h +++ b/server-tools/instance-manager/log.h @@ -19,20 +19,23 @@ /* Logging facilities. - Two logging streams are supported: error log and info log. Additionally - libdbug may be used for debug information output. + Two logging streams are supported: error log and info log. + Additionally libdbug may be used for debug information output. + ANSI C buffered I/O is used to perform logging. + Logging is performed via stdout/stder, so one can reopen them to point to - ordinary files. To initialize loggin environment log_init() must be called. + ordinary files. To initialize logging environment log_init() must be called. Rationale: - no MYSQL_LOG as it has BIN mode, and not easy to fetch from sql_class.h - no constructors/desctructors to make logging available all the time - Function names are subject to change. */ -/* Precede error message with date and time and print it to the stdout */ +void log_init(); + + void log_info(const char *format, ...) #ifdef __GNUC__ __attribute__ ((format(printf, 1, 2))) @@ -40,7 +43,6 @@ void log_info(const char *format, ...) ; -/* Precede error message with date and time and print it to the stderr */ void log_error(const char *format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) @@ -48,30 +50,6 @@ void log_error(const char *format, ...) ; -/* - Now this is simple catchouts for printf (no date/time is logged), to be - able to replace underlying streams in future. -*/ - -void print_info(const char *format, ...) -#ifdef __GNUC__ - __attribute__ ((format (printf, 1, 2))) -#endif - ; - - -void print_error(const char *format, ...) -#ifdef __GNUC__ - __attribute__ ((format (printf, 1, 2))) -#endif - ; - -/* initialize logs */ -void log_init(); - - -/* print information to the error log and eixt(1) */ - void die(const char *format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc index ec22e5c4fbb..a002902bd56 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -14,40 +14,29 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "manager.h" -#include "priv.h" -#include "thread_registry.h" -#include "listener.h" -#include "instance_map.h" -#include "options.h" -#include "user_map.h" -#include "log.h" -#include "guardian.h" - -#include <my_sys.h> +#include <my_global.h> #include <m_string.h> -#include <signal.h> +#include <my_sys.h> #include <thr_alarm.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif +#include "exit_codes.h" +#include "guardian.h" +#include "instance_map.h" +#include "listener.h" +#include "log.h" +#include "options.h" +#include "priv.h" +#include "thread_registry.h" +#include "user_map.h" + -int create_pid_file(const char *pid_file_name, int pid) -{ - if (FILE *pid_file= my_fopen(pid_file_name, - O_WRONLY | O_CREAT | O_BINARY, MYF(0))) - { - fprintf(pid_file, "%d\n", (int) pid); - my_fclose(pid_file, MYF(0)); - return 0; - } - log_error("can't create pid file %s: errno=%d, %s", - pid_file_name, errno, strerror(errno)); - return 1; -} #ifndef __WIN__ void set_signals(sigset_t *mask) @@ -82,14 +71,14 @@ bool have_signal; void onsignal(int signo) { - have_signal= true; + have_signal= TRUE; } void set_signals(sigset_t *set) { signal(SIGINT, onsignal); signal(SIGTERM, onsignal); - have_signal= false; + have_signal= FALSE; } int my_sigwait(const sigset_t *set, int *sig) @@ -104,28 +93,107 @@ int my_sigwait(const sigset_t *set, int *sig) #endif -void stop_all(Guardian_thread *guardian, Thread_registry *registry) +/********************************************************************** + Implementation of checking the actual thread model. +***********************************************************************/ + +namespace { /* no-indent */ + +class ThreadModelChecker: public Thread +{ +public: + ThreadModelChecker() + :main_pid(getpid()) + { } + +public: + inline bool is_linux_threads() const + { + return linux_threads; + } + +protected: + virtual void run() + { + linux_threads= main_pid != getpid(); + } + +private: + pid_t main_pid; + bool linux_threads; +}; + +bool check_if_linux_threads(bool *linux_threads) +{ + ThreadModelChecker checker; + + if (checker.start() || checker.join()) + return TRUE; + + *linux_threads= checker.is_linux_threads(); + + return FALSE; +} + +} + + +/********************************************************************** + Manager implementation +***********************************************************************/ + +Guardian *Manager::p_guardian; +Instance_map *Manager::p_instance_map; +Thread_registry *Manager::p_thread_registry; +User_map *Manager::p_user_map; + +#ifndef __WIN__ +bool Manager::linux_threads; +#endif // __WIN__ + + +void Manager::stop_all_threads() { /* Let guardian thread know that it should break it's processing cycle, once it wakes up. */ - guardian->request_shutdown(); + p_guardian->request_shutdown(); /* wake guardian */ - pthread_cond_signal(&guardian->COND_guardian); + pthread_cond_signal(&p_guardian->COND_guardian); /* stop all threads */ - registry->deliver_shutdown(); + p_thread_registry->deliver_shutdown(); } + /* manager - entry point to the main instance manager process: start listener thread, write pid file and enter into signal handling. See also comments in mysqlmanager.cc to picture general Instance Manager architecture. + + TODO: how about returning error status. */ -void manager(const Options &options) +int Manager::main() { + int err_code; + int rc= 1; + const char *err_msg; + bool shutdown_complete= FALSE; + pid_t manager_pid= getpid(); + +#ifndef __WIN__ + if (check_if_linux_threads(&linux_threads)) + { + log_error("Can not determine thread model."); + return 1; + } + + log_info("Detected threads model: %s.", + (const char *) (linux_threads ? "LINUX threads" : "POSIX threads")); +#endif // __WIN__ + Thread_registry thread_registry; /* All objects created in the manager() function live as long as @@ -134,31 +202,61 @@ void manager(const Options &options) */ User_map user_map; - Instance_map instance_map(options.default_mysqld_path); - Guardian_thread guardian_thread(thread_registry, - &instance_map, - options.monitoring_interval); + Instance_map instance_map; + Guardian guardian(&thread_registry, &instance_map, + Options::Main::monitoring_interval); + + Listener listener(&thread_registry, &user_map); - Listener_thread_args listener_args(thread_registry, options, user_map, - instance_map); + p_instance_map= &instance_map; + p_guardian= instance_map.guardian= &guardian; + p_thread_registry= &thread_registry; + p_user_map= &user_map; - manager_pid= getpid(); - instance_map.guardian= &guardian_thread; + /* Initialize instance map. */ - if (instance_map.init() || user_map.init()) - return; + if (instance_map.init()) + { + log_error("Can not initialize instance list: out of memory."); + return 1; + } - if (user_map.load(options.password_file_name)) - return; + /* Initialize user map and load password file. */ + + if (user_map.init()) + { + log_error("Can not initialize user list: out of memory."); + return 1; + } + + if ((err_code= user_map.load(Options::Main::password_file_name, &err_msg))) + { + if (err_code == ERR_PASSWORD_FILE_DOES_NOT_EXIST && + Options::Main::mysqld_safe_compatible) + { + /* + The password file does not exist, but we are running in + mysqld_safe-compatible mode. Continue, but complain in log. + */ + + log_info("Warning: password file does not exist, " + "nobody will be able to connect to Instance Manager."); + } + else + { + log_error("%s.", (const char *) err_msg); + return 1; + } + } /* write Instance Manager pid file */ log_info("IM pid file: '%s'; PID: %d.", - (const char *) options.pid_file_name, + (const char *) Options::Main::pid_file_name, (int) manager_pid); - if (create_pid_file(options.pid_file_name, manager_pid)) - return; + if (create_pid_file(Options::Main::pid_file_name, manager_pid)) + return 1; /* necessary logging has been already done. */ /* Initialize signals and alarm-infrastructure. @@ -166,104 +264,102 @@ void manager(const Options &options) NOTE: To work nicely with LinuxThreads, the signal thread is the first thread in the process. - NOTE: - After init_thr_alarm() call it's possible to call thr_alarm() (from - different threads), that results in sending ALARM signal to the alarm - thread (which can be the main thread). That signal can interrupt - blocking calls. - - In other words, a blocking call can be interrupted in the main thread - after init_thr_alarm(). + NOTE: After init_thr_alarm() call it's possible to call thr_alarm() + (from different threads), that results in sending ALARM signal to the + alarm thread (which can be the main thread). That signal can interrupt + blocking calls. In other words, a blocking call can be interrupted in + the main thread after init_thr_alarm(). */ sigset_t mask; set_signals(&mask); - /* create guardian thread */ + /* + Create the guardian thread. The newly started thread will block until + we actually load instances. + + NOTE: Guardian should be shutdown first. Only then all other threads + can be stopped. This should be done in this order because the guardian + is responsible for shutting down all the guarded instances, and this + is a long operation. + + NOTE: Guardian uses thr_alarm() when detects the current state of an + instance (is_running()), but this does not interfere with + flush_instances() call later in the code, because until + flush_instances() completes in the main thread, Guardian thread is not + permitted to process instances. And before flush_instances() has + completed, there are no instances to guard. + */ + if (guardian.start(Thread::DETACHED)) { - pthread_t guardian_thd_id; - pthread_attr_t guardian_thd_attr; - int rc; - - /* - NOTE: Guardian should be shutdown first. Only then all other threads - need to be stopped. This should be done, as guardian is responsible - for shutting down the instances, and this is a long operation. - - NOTE: Guardian uses thr_alarm() when detects current state of - instances (is_running()), but it is not interfere with - flush_instances() later in the code, because until flush_instances() - complete in the main thread, Guardian thread is not permitted to - process instances. And before flush_instances() there is no instances - to proceed. - */ - - pthread_attr_init(&guardian_thd_attr); - pthread_attr_setdetachstate(&guardian_thd_attr, PTHREAD_CREATE_DETACHED); - rc= set_stacksize_n_create_thread(&guardian_thd_id, &guardian_thd_attr, - guardian, &guardian_thread); - pthread_attr_destroy(&guardian_thd_attr); - if (rc) - { - log_error("manager(): set_stacksize_n_create_thread(guardian) failed"); - goto err; - } - + log_error("Can not start Guardian thread."); + goto err; } /* Load instances. */ - int signo; - bool shutdown_complete; + { + instance_map.guardian->lock(); + instance_map.lock(); - shutdown_complete= FALSE; + int flush_instances_status= instance_map.flush_instances(); - if (instance_map.flush_instances()) - { - log_error("Cannot init instances repository. This might be caused by " - "the wrong config file options. For instance, missing mysqld " - "binary. Aborting."); - stop_all(&guardian_thread, &thread_registry); - goto err; - } + instance_map.unlock(); + instance_map.guardian->unlock(); - /* create the listener */ - { - pthread_t listener_thd_id; - pthread_attr_t listener_thd_attr; - int rc; - - pthread_attr_init(&listener_thd_attr); - pthread_attr_setdetachstate(&listener_thd_attr, PTHREAD_CREATE_DETACHED); - rc= set_stacksize_n_create_thread(&listener_thd_id, &listener_thd_attr, - listener, &listener_args); - pthread_attr_destroy(&listener_thd_attr); - if (rc) + if (flush_instances_status) { - log_error("manager(): set_stacksize_n_create_thread(listener) failed"); - stop_all(&guardian_thread, &thread_registry); + log_error("Can not init instances repository."); + stop_all_threads(); goto err; } + } + + /* Initialize the Listener. */ + if (listener.start(Thread::DETACHED)) + { + log_error("Can not start Listener thread."); + stop_all_threads(); + goto err; } /* After the list of guarded instances have been initialized, Guardian should start them. */ - pthread_cond_signal(&guardian_thread.COND_guardian); + pthread_cond_signal(&guardian.COND_guardian); + + /* Main loop. */ + + log_info("Manager: started."); while (!shutdown_complete) { + int signo; int status= 0; if ((status= my_sigwait(&mask, &signo)) != 0) { log_error("sigwait() failed"); - stop_all(&guardian_thread, &thread_registry); + stop_all_threads(); goto err; } + /* + The general idea in this loop is the following: + - we are waiting for SIGINT, SIGTERM -- signals that mean we should + shutdown; + - as shutdown signal is caught, we stop Guardian thread (by calling + Guardian::request_shutdown()); + - as Guardian is stopped, it sends SIGTERM to this thread + (by calling Thread_registry::request_shutdown()), so that the + my_sigwait() above returns; + - as we catch the second SIGTERM, we send signals to all threads + registered in Thread_registry (by calling + Thread_registry::deliver_shutdown()) and waiting for threads to stop; + */ + #ifndef __WIN__ /* On some Darwin kernels SIGHUP is delivered along with most @@ -272,7 +368,7 @@ void manager(const Options &options) Bug #14164 IM tests fail on MacOS X (powermacg5) */ #ifdef IGNORE_SIGHUP_SIGQUIT - if ( SIGHUP == signo ) + if (SIGHUP == signo) continue; #endif if (THR_SERVER_ALARM == signo) @@ -280,10 +376,12 @@ void manager(const Options &options) else #endif { - if (!guardian_thread.is_stopped()) + log_info("Manager: got shutdown signal."); + + if (!guardian.is_stopped()) { - guardian_thread.request_shutdown(); - pthread_cond_signal(&guardian_thread.COND_guardian); + guardian.request_shutdown(); + pthread_cond_signal(&guardian.COND_guardian); } else { @@ -293,14 +391,18 @@ void manager(const Options &options) } } + log_info("Manager: finished."); + + rc= 0; + err: /* delete the pid file */ - my_delete(options.pid_file_name, MYF(0)); + my_delete(Options::Main::pid_file_name, MYF(0)); #ifndef __WIN__ /* free alarm structures */ end_thr_alarm(1); /* don't pthread_exit to kill all threads who did not shut down in time */ #endif + return rc; } - diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h index 3ddf292132e..a77809cca6d 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -16,10 +16,49 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -struct Options; +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif +#include <my_global.h> -void manager(const Options &options); +class Guardian; +class Instance_map; +class Thread_registry; +class User_map; -int create_pid_file(const char *pid_file_name, int pid); +class Manager +{ +public: + static int main(); + /** + These methods return a non-zero value only for the duration + of main(). + */ + static Instance_map *get_instance_map() { return p_instance_map; } + static Guardian *get_guardian() { return p_guardian; } + static Thread_registry *get_thread_registry() { return p_thread_registry; } + static User_map *get_user_map() { return p_user_map; } + +#ifndef __WIN__ + static bool is_linux_threads() { return linux_threads; } +#endif // __WIN__ + +private: + static void stop_all_threads(); + +private: + static Guardian *p_guardian; + static Instance_map *p_instance_map; + static Thread_registry *p_thread_registry; + static User_map *p_user_map; + +#ifndef __WIN__ + /* + This flag is set if Instance Manager is running on the system using + LinuxThreads. + */ + static bool linux_threads; +#endif // __WIN__ +}; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H diff --git a/server-tools/instance-manager/messages.cc b/server-tools/instance-manager/messages.cc index d2595638de0..236e33cc900 100644 --- a/server-tools/instance-manager/messages.cc +++ b/server-tools/instance-manager/messages.cc @@ -14,15 +14,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "messages.h" +#include <my_global.h> +#include <mysql_com.h> + #include "mysqld_error.h" #include "mysql_manager_error.h" -#include <mysql_com.h> -#include <assert.h> - static const char *mysqld_error_message(unsigned sql_errno) { @@ -46,7 +45,7 @@ static const char *mysqld_error_message(unsigned sql_errno) " corresponds to your MySQL Instance Manager version for the right" " syntax to use"; case ER_BAD_INSTANCE_NAME: - return "Bad instance name. Check that the instance with such a name exists"; + return "Unknown instance name"; case ER_INSTANCE_IS_NOT_STARTED: return "Cannot stop instance. Perhaps the instance is not started, or was" " started manually, so IM cannot find the pidfile."; @@ -70,6 +69,23 @@ static const char *mysqld_error_message(unsigned sql_errno) " in the instance options"; case ER_ACCESS_OPTION_FILE: return "Cannot open the option file to edit. Check permissions"; + case ER_DROP_ACTIVE_INSTANCE: + return "Cannot drop an active instance. You should stop it first"; + case ER_CREATE_EXISTING_INSTANCE: + return "Instance already exists"; + case ER_INSTANCE_MISCONFIGURED: + return "Instance is misconfigured. Cannot start it"; + case ER_MALFORMED_INSTANCE_NAME: + return "Malformed instance name."; + case ER_INSTANCE_IS_ACTIVE: + return "The instance is active. Stop the instance first"; + case ER_THERE_IS_ACTIVE_INSTACE: + return "At least one instance is active. Stop all instances first"; + case ER_INCOMPATIBLE_OPTION: + return "Instance Manager-specific options are prohibited from being used " + "in the configuration of mysqld-compatible instances"; + case ER_CONF_FILE_DOES_NOT_EXIST: + return "Configuration file does not exist"; default: DBUG_ASSERT(0); return 0; diff --git a/server-tools/instance-manager/mysql_connection.cc b/server-tools/instance-manager/mysql_connection.cc index a19914dfdd2..952bdcd0e73 100644 --- a/server-tools/instance-manager/mysql_connection.cc +++ b/server-tools/instance-manager/mysql_connection.cc @@ -20,84 +20,34 @@ #include "mysql_connection.h" -#include "priv.h" -#include "mysql_manager_error.h" -#include "mysqld_error.h" -#include "thread_registry.h" -#include "log.h" -#include "user_map.h" -#include "protocol.h" -#include "messages.h" -#include "command.h" -#include "parse.h" - -#include <mysql.h> -#include <violite.h> -#include <mysql_com.h> #include <m_string.h> +#include <m_string.h> +#include <my_global.h> +#include <mysql.h> #include <my_sys.h> +#include <violite.h> +#include "command.h" +#include "log.h" +#include "messages.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" +#include "parse.h" +#include "priv.h" +#include "protocol.h" +#include "thread_registry.h" +#include "user_map.h" -Mysql_connection_thread_args::Mysql_connection_thread_args( - struct st_vio *vio_arg, - Thread_registry &thread_registry_arg, - const User_map &user_map_arg, - ulong connection_id_arg, - Instance_map &instance_map_arg) : - vio(vio_arg) - ,thread_registry(thread_registry_arg) - ,user_map(user_map_arg) - ,connection_id(connection_id_arg) - ,instance_map(instance_map_arg) - {} - -/* - MySQL connection - handle one connection with mysql command line client - See also comments in mysqlmanager.cc to picture general Instance Manager - architecture. - We use conventional technique to work with classes without exceptions: - class acquires all vital resource in init(); Thus if init() succeed, - a user must call cleanup(). All other methods are valid only between - init() and cleanup(). -*/ -class Mysql_connection_thread: public Mysql_connection_thread_args -{ -public: - Mysql_connection_thread(const Mysql_connection_thread_args &args); - - int init(); - void cleanup(); - - void run(); - - ~Mysql_connection_thread(); -private: - Thread_info thread_info; - NET net; - struct rand_struct rand_st; - char scramble[SCRAMBLE_LENGTH + 1]; - uint status; - ulong client_capabilities; -private: - /* Names are conventionally the same as in mysqld */ - int check_connection(); - int do_command(); - int dispatch_command(enum enum_server_command command, - const char *text, uint len); -}; - - -Mysql_connection_thread::Mysql_connection_thread( - const Mysql_connection_thread_args &args) : - Mysql_connection_thread_args(args.vio, - args.thread_registry, - args.user_map, - args.connection_id, - args.instance_map) - ,thread_info(pthread_self()) +Mysql_connection::Mysql_connection(Thread_registry *thread_registry_arg, + User_map *user_map_arg, + struct st_vio *vio_arg, ulong + connection_id_arg) + :vio(vio_arg), + connection_id(connection_id_arg), + thread_registry(thread_registry_arg), + user_map(user_map_arg) { - thread_registry.register_thread(&thread_info); } @@ -127,71 +77,75 @@ C_MODE_END This function is complementary to cleanup(). */ -int Mysql_connection_thread::init() +bool Mysql_connection::init() { /* Allocate buffers for network I/O */ if (my_net_init(&net, vio)) - return 1; + return TRUE; + net.return_status= &status; + /* Initialize random number generator */ { ulong seed1= (ulong) &rand_st + rand(); - ulong seed2= rand() + time(0); + ulong seed2= (ulong) rand() + time(0); randominit(&rand_st, seed1, seed2); } + /* Fill scramble - server's random message used for handshake */ create_random_string(scramble, SCRAMBLE_LENGTH, &rand_st); + /* We don't support transactions, every query is atomic */ status= SERVER_STATUS_AUTOCOMMIT; - return 0; + + thread_registry->register_thread(&thread_info); + + return FALSE; } -void Mysql_connection_thread::cleanup() +void Mysql_connection::cleanup() { net_end(&net); + thread_registry->unregister_thread(&thread_info); } -Mysql_connection_thread::~Mysql_connection_thread() +Mysql_connection::~Mysql_connection() { /* vio_delete closes the socket if necessary */ vio_delete(vio); - thread_registry.unregister_thread(&thread_info); } -void Mysql_connection_thread::run() +void Mysql_connection::main() { - log_info("accepted mysql connection %lu", connection_id); - - my_thread_init(); + log_info("Connection %lu: accepted.", (unsigned long) connection_id); if (check_connection()) { - my_thread_end(); + log_info("Connection %lu: failed to authorize the user.", + (unsigned long) connection_id); + return; } - log_info("connection %lu is checked successfully", connection_id); + log_info("Connection %lu: the user was authorized successfully.", + (unsigned long) connection_id); vio_keepalive(vio, TRUE); - while (!net.error && net.vio && !thread_registry.is_shutdown()) + while (!net.error && net.vio && !thread_registry->is_shutdown()) { if (do_command()) break; } - - my_thread_end(); } -int Mysql_connection_thread::check_connection() +int Mysql_connection::check_connection() { ulong pkt_len=0; // to hold client reply length - /* maximum size of the version string */ - enum { MAX_VERSION_LENGTH= 80 }; /* buffer for the first packet */ /* packet contains: */ char buff[MAX_VERSION_LENGTH + 1 + // server version, 0-ended @@ -202,8 +156,8 @@ int Mysql_connection_thread::check_connection() char *pos= buff; ulong server_flags; - memcpy(pos, mysqlmanager_version, mysqlmanager_version_length + 1); - pos+= mysqlmanager_version_length + 1; + memcpy(pos, mysqlmanager_version.str, mysqlmanager_version.length + 1); + pos+= mysqlmanager_version.length + 1; int4store((uchar*) pos, connection_id); pos+= 4; @@ -271,12 +225,14 @@ int Mysql_connection_thread::check_connection() const char *user= pos; const char *password= strend(user)+1; ulong password_len= *password++; + LEX_STRING user_name= { (char *) user, password - user - 2 }; + if (password_len != SCRAMBLE_LENGTH) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; } - if (user_map.authenticate(user, password-user-2, password, scramble)) + if (user_map->authenticate(&user_name, password, scramble)) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; @@ -286,7 +242,7 @@ int Mysql_connection_thread::check_connection() } -int Mysql_connection_thread::do_command() +int Mysql_connection::do_command() { char *packet; ulong packet_length; @@ -299,7 +255,7 @@ int Mysql_connection_thread::do_command() /* Check if we can continue without closing the connection */ if (net.error != 3) // what is 3 - find out return 1; - if (thread_registry.is_shutdown()) + if (thread_registry->is_shutdown()) return 1; net_send_error(&net, net.last_errno); net.error= 0; @@ -307,78 +263,103 @@ int Mysql_connection_thread::do_command() } else { - if (thread_registry.is_shutdown()) + if (thread_registry->is_shutdown()) return 1; packet= (char*) net.read_pos; enum enum_server_command command= (enum enum_server_command) (uchar) *packet; - log_info("connection %lu: packet_length=%lu, command=%d", - connection_id, packet_length, command); - return dispatch_command(command, packet + 1, packet_length - 1); + log_info("Connection %lu: received packet (length: %lu; command: %d).", + (unsigned long) connection_id, + (unsigned long) packet_length, + (int) command); + + return dispatch_command(command, packet + 1); } } -int Mysql_connection_thread::dispatch_command(enum enum_server_command command, - const char *packet, uint len) +int Mysql_connection::dispatch_command(enum enum_server_command command, + const char *packet) { switch (command) { case COM_QUIT: // client exit - log_info("query for connection %lu received quit command", connection_id); + log_info("Connection %lu: received QUIT command.", + (unsigned long) connection_id); return 1; + case COM_PING: - log_info("query for connection %lu received ping command", connection_id); + log_info("Connection %lu: received PING command.", + (unsigned long) connection_id); net_send_ok(&net, connection_id, NULL); - break; + return 0; + case COM_QUERY: { - log_info("query for connection %lu : ----\n%s\n-------------------------", - connection_id,packet); - if (Command *command= parse_command(&instance_map, packet)) + log_info("Connection %lu: received QUERY command: '%s'.", + (unsigned long) connection_id, + (const char *) packet); + + if (Command *command= parse_command(packet)) { int res= 0; - log_info("query for connection %lu successefully parsed",connection_id); + + log_info("Connection %lu: query parsed successfully.", + (unsigned long) connection_id); + res= command->execute(&net, connection_id); delete command; + if (!res) - log_info("query for connection %lu executed ok",connection_id); + { + log_info("Connection %lu: query executed successfully", + (unsigned long) connection_id); + } else { - log_info("query for connection %lu executed err=%d",connection_id,res); + log_info("Connection %lu: can not execute query (error: %d).", + (unsigned long) connection_id, + (int) res); + net_send_error(&net, res); - return 0; } } else { + log_error("Connection %lu: can not parse query: out ot resources.", + (unsigned long) connection_id); + net_send_error(&net,ER_OUT_OF_RESOURCES); - return 0; } - break; + + return 0; } + default: - log_info("query for connection %lu received unknown command",connection_id); + log_info("Connection %lu: received unsupported command (%d).", + (unsigned long) connection_id, + (int) command); + net_send_error(&net, ER_UNKNOWN_COM_ERROR); - break; + return 0; } - return 0; + + return 0; /* Just to make compiler happy. */ } -pthread_handler_t mysql_connection(void *arg) +void Mysql_connection::run() { - Mysql_connection_thread_args *args= (Mysql_connection_thread_args *) arg; - Mysql_connection_thread mysql_connection_thread(*args); - delete args; - if (mysql_connection_thread.init()) - log_info("mysql_connection(): error initializing thread"); + if (init()) + log_error("Connection %lu: can not init handler.", + (unsigned long) connection_id); else { - mysql_connection_thread.run(); - mysql_connection_thread.cleanup(); + main(); + cleanup(); } - return 0; + + delete this; } /* - vim: fdm=marker + vim: fdm=marker */ diff --git a/server-tools/instance-manager/mysql_connection.h b/server-tools/instance-manager/mysql_connection.h index 3496cc05815..ef58d921637 100644 --- a/server-tools/instance-manager/mysql_connection.h +++ b/server-tools/instance-manager/mysql_connection.h @@ -16,33 +16,59 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include <my_pthread.h> +#include "thread_registry.h" +#include <mysql_com.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif -pthread_handler_t mysql_connection(void *arg); - -class Thread_registry; -class User_map; -class Instance_map; struct st_vio; +class User_map; -struct Mysql_connection_thread_args +/* + MySQL connection - handle one connection with mysql command line client + See also comments in mysqlmanager.cc to picture general Instance Manager + architecture. + We use conventional technique to work with classes without exceptions: + class acquires all vital resource in init(); Thus if init() succeed, + a user must call cleanup(). All other methods are valid only between + init() and cleanup(). +*/ + +class Mysql_connection: public Thread { +public: + Mysql_connection(Thread_registry *thread_registry_arg, + User_map *user_map_arg, + struct st_vio *vio_arg, + ulong connection_id_arg); + virtual ~Mysql_connection(); + +protected: + virtual void run(); + +private: struct st_vio *vio; - Thread_registry &thread_registry; - const User_map &user_map; ulong connection_id; - Instance_map &instance_map; + Thread_info thread_info; + Thread_registry *thread_registry; + User_map *user_map; + NET net; + struct rand_struct rand_st; + char scramble[SCRAMBLE_LENGTH + 1]; + uint status; + ulong client_capabilities; +private: + /* The main loop implementation triad */ + bool init(); + void main(); + void cleanup(); - Mysql_connection_thread_args(struct st_vio *vio_arg, - Thread_registry &thread_registry_arg, - const User_map &user_map_arg, - ulong connection_id_arg, - Instance_map &instance_map_arg); + /* Names are conventionally the same as in mysqld */ + int check_connection(); + int do_command(); + int dispatch_command(enum enum_server_command command, const char *text); }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_CONNECTION_H diff --git a/server-tools/instance-manager/mysql_manager_error.h b/server-tools/instance-manager/mysql_manager_error.h index ff782923a8e..373c7d798a0 100644 --- a/server-tools/instance-manager/mysql_manager_error.h +++ b/server-tools/instance-manager/mysql_manager_error.h @@ -29,5 +29,13 @@ #define ER_ACCESS_OPTION_FILE 3008 #define ER_OFFSET_ERROR 3009 #define ER_READ_FILE 3010 +#define ER_DROP_ACTIVE_INSTANCE 3011 +#define ER_CREATE_EXISTING_INSTANCE 3012 +#define ER_INSTANCE_MISCONFIGURED 3013 +#define ER_MALFORMED_INSTANCE_NAME 3014 +#define ER_INSTANCE_IS_ACTIVE 3015 +#define ER_THERE_IS_ACTIVE_INSTACE 3016 +#define ER_INCOMPATIBLE_OPTION 3017 +#define ER_CONF_FILE_DOES_NOT_EXIST 3018 #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_MANAGER_ERROR_H */ diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index ef714099de7..8ee8321fffc 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -15,25 +15,31 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> -#include "manager.h" - -#include "options.h" -#include "log.h" - #include <my_sys.h> + #include <string.h> #include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> + #ifndef __WIN__ #include <pwd.h> #include <grp.h> #include <sys/wait.h> #endif -#include <sys/types.h> -#include <sys/stat.h> + +#include "log.h" +#include "manager.h" +#include "options.h" +#include "priv.h" +#include "user_management_commands.h" + #ifdef __WIN__ -#include "windowsservice.h" +#include "IMService.h" +#include "WindowsService.h" #endif + /* Few notes about Instance Manager architecture: Instance Manager consisits of two processes: the angel process, and the @@ -59,13 +65,12 @@ */ static void init_environment(char *progname); + #ifndef __WIN__ static void daemonize(const char *log_file_name); -static void angel(const Options &options); +static void angel(); static struct passwd *check_user(const char *user); static int set_user(const char *user, struct passwd *user_info); -#else -int HandleServiceOptions(Options options); #endif @@ -81,41 +86,61 @@ int main(int argc, char *argv[]) { int return_value= 1; init_environment(argv[0]); - Options options; - if (options.load(argc, argv)) - goto err; + if ((return_value= Options::load(argc, argv))) + goto main_end; + + if (Options::User_management::cmd) + { + return_value= Options::User_management::cmd->execute(); + + goto main_end; + } #ifndef __WIN__ + struct passwd *user_info; - if ((user_info= check_user(options.user))) + if ((user_info= check_user(Options::Daemon::user))) { - if (set_user(options.user, user_info)) - goto err; + if (set_user(Options::Daemon::user, user_info)) + { + return_value= 1; + goto main_end; + } } - if (options.run_as_service) + if (Options::Daemon::run_as_service) { /* forks, and returns only in child */ - daemonize(options.log_file_name); + daemonize(Options::Daemon::log_file_name); /* forks again, and returns only in child: parent becomes angel */ - angel(options); + angel(); } + + (void) Manager::main(); /* ignore the return value for now */ + #else - if (!options.stand_alone) + + if (!Options::Service::stand_alone) { - if (HandleServiceOptions(options)) - goto err; + if (HandleServiceOptions()) + { + return_value= 1; + goto main_end; + } } else + { + (void) Manager::main(); /* ignore the return value for now */ + } + #endif - manager(options); return_value= 0; -err: - options.cleanup(); +main_end: + Options::cleanup(); my_end(0); return return_value; } @@ -166,7 +191,8 @@ static struct passwd *check_user(const char *user) return user_info; err: - log_error("Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n", user); + log_error("Can not start under user '%s'.", + (const char *) user); return NULL; } @@ -200,14 +226,14 @@ static void init_environment(char *progname) MY_INIT(progname); log_init(); umask(0117); - srand(time(0)); + srand((unsigned int) time(0)); } #ifndef __WIN__ /* Become a UNIX service - SYNOPSYS + SYNOPSIS daemonize() */ @@ -298,7 +324,7 @@ void terminate(int signo) Angel process will exit silently if mysqlmanager exits normally. */ -static void angel(const Options &options) +static void angel() { /* install signal handlers */ sigset_t zeromask; // to sigsuspend in parent @@ -341,10 +367,10 @@ spawn: pid= getpid(); /* Get our pid. */ log_info("Angel pid file: '%s'; PID: %d.", - (const char *) options.angel_pid_file_name, + (const char *) Options::Daemon::angel_pid_file_name, (int) pid); - create_pid_file(Options::angel_pid_file_name, pid); + create_pid_file(Options::Daemon::angel_pid_file_name, pid); while (child_status == CHILD_OK && is_terminated == 0) sigsuspend(&zeromask); @@ -360,11 +386,10 @@ spawn: } /* mysqlmanager successfully exited, let's silently evaporate - If we return to main we fall into the manager() function, so let's - simply exit(). + If we return to main we will fall into the manager functionality, + so let's simply exit(). */ exit(0); } } - #endif diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index c6c709295bc..0fff68fbd4f 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -20,44 +20,88 @@ #include "options.h" -#include "priv.h" -#include "portability.h" +#include <my_global.h> #include <my_sys.h> #include <my_getopt.h> -#include <m_string.h> #include <mysql_com.h> +#include "exit_codes.h" +#include "log.h" +#include "portability.h" +#include "priv.h" +#include "user_management_commands.h" + #define QUOTE2(x) #x #define QUOTE(x) QUOTE2(x) #ifdef __WIN__ -char Options::install_as_service; -char Options::remove_service; -char Options::stand_alone; -char windows_config_file[FN_REFLEN]; -char default_password_file_name[FN_REFLEN]; -char default_log_file_name[FN_REFLEN]; -const char *Options::config_file= windows_config_file; -#else -char Options::run_as_service; -const char *Options::user= 0; /* No default value */ -const char *default_password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); -const char *default_log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); -const char *Options::config_file= QUOTE(DEFAULT_CONFIG_FILE); -const char *Options::angel_pid_file_name= NULL; -const char *Options::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME); + +/* Define holders for default values. */ + +static char win_dflt_config_file_name[FN_REFLEN]; +static char win_dflt_password_file_name[FN_REFLEN]; +static char win_dflt_pid_file_name[FN_REFLEN]; + +static char win_dflt_mysqld_path[FN_REFLEN]; + +/* Define and initialize Windows-specific options. */ + +my_bool Options::Service::install_as_service; +my_bool Options::Service::remove_service; +my_bool Options::Service::stand_alone; + +const char *Options::Main::config_file= win_dflt_config_file_name; +const char *Options::Main::password_file_name= win_dflt_password_file_name; +const char *Options::Main::pid_file_name= win_dflt_pid_file_name; + +const char *Options::Main::default_mysqld_path= win_dflt_mysqld_path; + +static int setup_windows_defaults(); + +#else /* UNIX */ + +/* Define and initialize UNIX-specific options. */ + +my_bool Options::Daemon::run_as_service= FALSE; +const char *Options::Daemon::log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); +const char *Options::Daemon::user= NULL; /* No default value */ +const char *Options::Daemon::angel_pid_file_name= NULL; + +const char *Options::Main::config_file= QUOTE(DEFAULT_CONFIG_FILE); +const char * +Options::Main::password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); +const char *Options::Main::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); +const char *Options::Main::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME); + +const char *Options::Main::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); + #endif -const char *Options::log_file_name= default_log_file_name; -const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); -const char *Options::password_file_name= default_password_file_name; -const char *Options::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); -const char *Options::bind_address= 0; /* No default value */ -uint Options::monitoring_interval= DEFAULT_MONITORING_INTERVAL; -uint Options::port_number= DEFAULT_PORT; -/* just to declare */ + +/* Remember if the config file was forced. */ + +bool Options::Main::is_forced_default_file= FALSE; + +/* Define and initialize common options. */ + +const char *Options::Main::bind_address= NULL; /* No default value */ +uint Options::Main::monitoring_interval= DEFAULT_MONITORING_INTERVAL; +uint Options::Main::port_number= DEFAULT_PORT; +my_bool Options::Main::mysqld_safe_compatible= FALSE; + +/* Options::User_management */ + +char *Options::User_management::user_name= NULL; +char *Options::User_management::password= NULL; + +User_management_cmd *Options::User_management::cmd= NULL; + +/* Private members. */ + char **Options::saved_argv= NULL; -/* Remember if the config file was forced */ -bool Options::is_forced_default_file= 0; + +#ifndef DBUG_OFF +const char *Options::Debug::config_str= "d:t:i:O,im.trace"; +#endif static const char * const ANGEL_PID_FILE_SUFFIX= ".angel.pid"; static const int ANGEL_PID_FILE_SUFFIX_LEN= strlen(ANGEL_PID_FILE_SUFFIX); @@ -68,24 +112,34 @@ static const int ANGEL_PID_FILE_SUFFIX_LEN= strlen(ANGEL_PID_FILE_SUFFIX); */ enum options { + OPT_USERNAME= 'u', + OPT_PASSWORD= 'p', OPT_LOG= 256, OPT_PID_FILE, OPT_SOCKET, OPT_PASSWORD_FILE, OPT_MYSQLD_PATH, -#ifndef __WIN__ - OPT_RUN_AS_SERVICE, - OPT_USER, - OPT_ANGEL_PID_FILE, -#else +#ifdef __WIN__ OPT_INSTALL_SERVICE, OPT_REMOVE_SERVICE, OPT_STAND_ALONE, +#else + OPT_RUN_AS_SERVICE, + OPT_USER, + OPT_ANGEL_PID_FILE, #endif OPT_MONITORING_INTERVAL, OPT_PORT, OPT_WAIT_TIMEOUT, - OPT_BIND_ADDRESS + OPT_BIND_ADDRESS, + OPT_PRINT_PASSWORD_LINE, + OPT_ADD_USER, + OPT_DROP_USER, + OPT_EDIT_USER, + OPT_CLEAN_PASSWORD_FILE, + OPT_CHECK_PASSWORD_FILE, + OPT_LIST_USERS, + OPT_MYSQLD_SAFE_COMPATIBLE }; static struct my_option my_long_options[] = @@ -93,88 +147,160 @@ 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 }, - { "log", OPT_LOG, "Path to log file. Used only with --run-as-service.", - (gptr *) &Options::log_file_name, (gptr *) &Options::log_file_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - - { "pid-file", OPT_PID_FILE, "Pid file to use.", - (gptr *) &Options::pid_file_name, (gptr *) &Options::pid_file_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "add-user", OPT_ADD_USER, + "Add a user to the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, #ifndef __WIN__ { "angel-pid-file", OPT_ANGEL_PID_FILE, "Pid file for angel process.", - (gptr *) &Options::angel_pid_file_name, - (gptr *) &Options::angel_pid_file_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - - { "socket", OPT_SOCKET, "Socket file to use for connection.", - (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, + (gptr *) &Options::Daemon::angel_pid_file_name, + (gptr *) &Options::Daemon::angel_pid_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif - { "passwd", 'P', "Prepare entry for passwd file and exit.", 0, 0, 0, - GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, - { "bind-address", OPT_BIND_ADDRESS, "Bind address to use for connection.", - (gptr *) &Options::bind_address, (gptr *) &Options::bind_address, + (gptr *) &Options::Main::bind_address, + (gptr *) &Options::Main::bind_address, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - { "port", OPT_PORT, "Port number to use for connections", - (gptr *) &Options::port_number, (gptr *) &Options::port_number, - 0, GET_UINT, REQUIRED_ARG, DEFAULT_PORT, 0, 0, 0, 0, 0 }, + { "check-password-file", OPT_CHECK_PASSWORD_FILE, + "Check the password file for consistency", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, - { "password-file", OPT_PASSWORD_FILE, "Look for Instance Manager users" - " and passwords here.", - (gptr *) &Options::password_file_name, - (gptr *) &Options::password_file_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "clean-password-file", OPT_CLEAN_PASSWORD_FILE, + "Clean the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifndef DBUG_OFF + {"debug", '#', "Debug log.", + (gptr *) &Options::Debug::config_str, + (gptr *) &Options::Debug::config_str, + 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif { "default-mysqld-path", OPT_MYSQLD_PATH, "Where to look for MySQL" " Server binary.", - (gptr *) &Options::default_mysqld_path, - (gptr *) &Options::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }, + { "drop-user", OPT_DROP_USER, + "Drop existing user from the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "edit-user", OPT_EDIT_USER, + "Edit existing user in the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifdef __WIN__ + { "install", OPT_INSTALL_SERVICE, "Install as system service.", + (gptr *) &Options::Service::install_as_service, + (gptr *) &Options::Service::install_as_service, + 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, +#endif + + { "list-users", OPT_LIST_USERS, + "Print out a list of registered users", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifndef __WIN__ + { "log", OPT_LOG, "Path to log file. Used only with --run-as-service.", + (gptr *) &Options::Daemon::log_file_name, + (gptr *) &Options::Daemon::log_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif + { "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor" " instances in seconds.", - (gptr *) &Options::monitoring_interval, - (gptr *) &Options::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, 0, GET_UINT, REQUIRED_ARG, DEFAULT_MONITORING_INTERVAL, 0, 0, 0, 0, 0 }, -#ifdef __WIN__ - { "install", OPT_INSTALL_SERVICE, "Install as system service.", - (gptr *) &Options::install_as_service, (gptr*) &Options::install_as_service, + + { "mysqld-safe-compatible", OPT_MYSQLD_SAFE_COMPATIBLE, + "Start Instance Manager in mysqld_safe compatible manner", + (gptr *) &Options::Main::mysqld_safe_compatible, + (gptr *) &Options::Main::mysqld_safe_compatible, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, + + { "print-password-line", OPT_PRINT_PASSWORD_LINE, + "Print out a user entry as a line for the password file and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "password", OPT_PASSWORD, "Password to update the password file", + (gptr *) &Options::User_management::password, + (gptr *) &Options::User_management::password, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "password-file", OPT_PASSWORD_FILE, + "Look for Instance Manager users and passwords here.", + (gptr *) &Options::Main::password_file_name, + (gptr *) &Options::Main::password_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "pid-file", OPT_PID_FILE, "Pid file to use.", + (gptr *) &Options::Main::pid_file_name, + (gptr *) &Options::Main::pid_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "port", OPT_PORT, "Port number to use for connections", + (gptr *) &Options::Main::port_number, + (gptr *) &Options::Main::port_number, + 0, GET_UINT, REQUIRED_ARG, DEFAULT_PORT, 0, 0, 0, 0, 0 }, + +#ifdef __WIN__ { "remove", OPT_REMOVE_SERVICE, "Remove system service.", - (gptr *)&Options::remove_service, (gptr*) &Options::remove_service, - 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, - { "standalone", OPT_STAND_ALONE, "Run the application in stand alone mode.", - (gptr *)&Options::stand_alone, (gptr*) &Options::stand_alone, + (gptr *) &Options::Service::remove_service, + (gptr *) &Options::Service::remove_service, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, #else { "run-as-service", OPT_RUN_AS_SERVICE, - "Daemonize and start angel process.", (gptr *) &Options::run_as_service, + "Daemonize and start angel process.", + (gptr *) &Options::Daemon::run_as_service, 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, +#endif +#ifndef __WIN__ + { "socket", OPT_SOCKET, "Socket file to use for connection.", + (gptr *) &Options::Main::socket_file_name, + (gptr *) &Options::Main::socket_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif + +#ifdef __WIN__ + { "standalone", OPT_STAND_ALONE, "Run the application in stand alone mode.", + (gptr *) &Options::Service::stand_alone, + (gptr *) &Options::Service::stand_alone, + 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, +#else { "user", OPT_USER, "Username to start mysqlmanager", - (gptr *) &Options::user, - (gptr *) &Options::user, + (gptr *) &Options::Daemon::user, + (gptr *) &Options::Daemon::user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif + + { "username", OPT_USERNAME, + "Username to update the password file", + (gptr *) &Options::User_management::user_name, + (gptr *) &Options::User_management::user_name, + 0, GET_STR, REQUIRED_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 }, { "wait-timeout", OPT_WAIT_TIMEOUT, "The number of seconds IM waits " "for activity on a connection before closing it.", - (gptr *) &net_read_timeout, (gptr *) &net_read_timeout, 0, GET_ULONG, - REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, + (gptr *) &net_read_timeout, + (gptr *) &net_read_timeout, + 0, GET_ULONG, REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 } }; static void version() { - printf("%s Ver %s for %s on %s\n", my_progname, mysqlmanager_version, + printf("%s Ver %s for %s on %s\n", my_progname, + (const char *) mysqlmanager_version.str, SYSTEM_TYPE, MACHINE_TYPE); } @@ -202,54 +328,65 @@ static void usage() } -static void passwd() -{ - char user[1024], *p; - const char *pw1, *pw2; - char pw1msg[]= "Enter password: "; - char pw2msg[]= "Re-type password: "; - char crypted_pw[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; - - fprintf(stderr, "Creating record for new user.\n"); - fprintf(stderr, "Enter user name: "); - if (!fgets(user, sizeof(user), stdin)) - { - fprintf(stderr, "Unable to read user.\n"); - return; - } - if ((p= strchr(user, '\n'))) *p= 0; - - pw1= get_tty_password(pw1msg); - pw2= get_tty_password(pw2msg); - - if (strcmp(pw1, pw2)) - { - fprintf(stderr, "Sorry, passwords do not match.\n"); - return; - } - - make_scrambled_password(crypted_pw, pw1); - printf("%s:%s\n", user, crypted_pw); -} - - C_MODE_START static my_bool get_one_option(int optid, const struct my_option *opt __attribute__((unused)), - char *argument __attribute__((unused))) + char *argument) { switch(optid) { case 'V': version(); exit(0); - case 'P': - passwd(); - exit(0); + case OPT_PRINT_PASSWORD_LINE: + case OPT_ADD_USER: + case OPT_DROP_USER: + case OPT_EDIT_USER: + case OPT_CLEAN_PASSWORD_FILE: + case OPT_CHECK_PASSWORD_FILE: + case OPT_LIST_USERS: + if (Options::User_management::cmd) + { + fprintf(stderr, "Error: only one password-management command " + "can be specified at a time.\n"); + exit(ERR_INVALID_USAGE); + } + + switch (optid) { + case OPT_PRINT_PASSWORD_LINE: + Options::User_management::cmd= new Print_password_line_cmd(); + break; + case OPT_ADD_USER: + Options::User_management::cmd= new Add_user_cmd(); + break; + case OPT_DROP_USER: + Options::User_management::cmd= new Drop_user_cmd(); + break; + case OPT_EDIT_USER: + Options::User_management::cmd= new Edit_user_cmd(); + break; + case OPT_CLEAN_PASSWORD_FILE: + Options::User_management::cmd= new Clean_db_cmd(); + break; + case OPT_CHECK_PASSWORD_FILE: + Options::User_management::cmd= new Check_db_cmd(); + break; + case OPT_LIST_USERS: + Options::User_management::cmd= new List_users_cmd(); + break; + } + + break; case '?': usage(); exit(0); + case '#': +#ifndef DBUG_OFF + DBUG_SET(argument ? argument : Options::Debug::config_str); + DBUG_SET_INITIAL(argument ? argument : Options::Debug::config_str); +#endif + break; } return 0; } @@ -273,8 +410,8 @@ int Options::load(int argc, char **argv) { if (is_prefix(argv[1], "--defaults-file=")) { - Options::config_file= strchr(argv[1], '=') + 1; - Options::is_forced_default_file= 1; + Main::config_file= strchr(argv[1], '=') + 1; + Main::is_forced_default_file= TRUE; } if (is_prefix(argv[1], "--defaults-extra-file=") || is_prefix(argv[1], "--no-defaults")) @@ -283,29 +420,44 @@ int Options::load(int argc, char **argv) fprintf(stderr, "The --defaults-extra-file and --no-defaults options" " are not supported by\n" "Instance Manager. Program aborted.\n"); - goto err; + return ERR_INVALID_USAGE; } } #ifdef __WIN__ if (setup_windows_defaults()) - goto err; + { + fprintf(stderr, "Internal error: could not setup default values.\n"); + return ERR_OUT_OF_MEMORY; + } #endif + /* load_defaults will reset saved_argv with a new allocated list */ saved_argv= argv; /* config-file options are prepended to command-line ones */ - load_defaults(config_file, default_groups, &argc, - &saved_argv); - if ((handle_options(&argc, &saved_argv, my_long_options, - get_one_option)) != 0) - goto err; + log_info("Loading config file '%s'...", + (const char *) Main::config_file); + + load_defaults(Main::config_file, default_groups, &argc, &saved_argv); + + if ((handle_options(&argc, &saved_argv, my_long_options, get_one_option))) + return ERR_INVALID_USAGE; + + if (!User_management::cmd && + (User_management::user_name || User_management::password)) + { + fprintf(stderr, + "--username and/or --password options have been specified, " + "but no password-management command has been given.\n"); + return ERR_INVALID_USAGE; + } #ifndef __WIN__ - if (Options::run_as_service) + if (Options::Daemon::run_as_service) { - if (Options::angel_pid_file_name == NULL) + if (Options::Daemon::angel_pid_file_name == NULL) { /* Calculate angel pid file on the IM pid file basis: replace the @@ -317,10 +469,11 @@ int Options::load(int argc, char **argv) char *base_name_ptr; char *ext_ptr; - angel_pid_file_name= (char *) malloc(strlen(Options::pid_file_name) + - ANGEL_PID_FILE_SUFFIX_LEN); + angel_pid_file_name= + (char *) malloc(strlen(Options::Main::pid_file_name) + + ANGEL_PID_FILE_SUFFIX_LEN); - strcpy(angel_pid_file_name, Options::pid_file_name); + strcpy(angel_pid_file_name, Options::Main::pid_file_name); base_name_ptr= strrchr(angel_pid_file_name, '/'); @@ -333,54 +486,71 @@ int Options::load(int argc, char **argv) strcat(angel_pid_file_name, ANGEL_PID_FILE_SUFFIX); - Options::angel_pid_file_name= angel_pid_file_name; + Options::Daemon::angel_pid_file_name= angel_pid_file_name; } else { - Options::angel_pid_file_name= strdup(Options::angel_pid_file_name); + Options::Daemon::angel_pid_file_name= + strdup(Options::Daemon::angel_pid_file_name); } } #endif return 0; - -err: - return 1; } void Options::cleanup() { - /* free_defaults returns nothing */ - if (Options::saved_argv != NULL) - free_defaults(Options::saved_argv); + if (saved_argv) + free_defaults(saved_argv); + + delete User_management::cmd; #ifndef __WIN__ - if (Options::run_as_service) - free((void *) Options::angel_pid_file_name); + if (Options::Daemon::run_as_service) + free((void *) Options::Daemon::angel_pid_file_name); #endif } #ifdef __WIN__ -int Options::setup_windows_defaults() +static int setup_windows_defaults() { - if (!GetModuleFileName(NULL, default_password_file_name, - sizeof(default_password_file_name))) - return 1; - char *filename= strstr(default_password_file_name, ".exe"); - strcpy(filename, ".passwd"); - - if (!GetModuleFileName(NULL, default_log_file_name, - sizeof(default_log_file_name))) + char module_full_name[FN_REFLEN]; + char dir_name[FN_REFLEN]; + char base_name[FN_REFLEN]; + char im_name[FN_REFLEN]; + char *base_name_ptr; + char *ptr; + + /* Determine dirname and basename. */ + + if (!GetModuleFileName(NULL, module_full_name, sizeof (module_full_name)) || + !GetFullPathName(module_full_name, sizeof (dir_name), dir_name, + &base_name_ptr)) + { return 1; - filename= strstr(default_log_file_name, ".exe"); - strcpy(filename, ".log"); + } + + strmake(base_name, base_name_ptr, FN_REFLEN); + *base_name_ptr= 0; + + strmake(im_name, base_name, FN_REFLEN); + ptr= strrchr(im_name, '.'); + + if (!ptr) + return 1; + + *ptr= 0; + + /* Initialize the defaults. */ + + strxmov(win_dflt_config_file_name, dir_name, DFLT_CONFIG_FILE_NAME, NullS); + strxmov(win_dflt_mysqld_path, dir_name, DFLT_MYSQLD_PATH, NullS); + strxmov(win_dflt_password_file_name, dir_name, im_name, DFLT_PASSWD_FILE_EXT, + NullS); + strxmov(win_dflt_pid_file_name, dir_name, im_name, DFLT_PID_FILE_EXT, NullS); - if (!GetModuleFileName(NULL, windows_config_file, - sizeof(windows_config_file))) - return 1; - char *slash= strrchr(windows_config_file, '\\'); - strcpy(slash, "\\my.ini"); return 0; } diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h index f32e106bd7a..ac4f6507892 100644 --- a/server-tools/instance-manager/options.h +++ b/server-tools/instance-manager/options.h @@ -17,46 +17,89 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - Options - all possible options for the instance manager grouped in one - struct. + Options - all possible command-line options for the Instance Manager grouped + in one struct. */ + #include <my_global.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +class User_management_cmd; + struct Options { -#ifdef __WIN__ - static char install_as_service; - static char remove_service; - static char stand_alone; + /* + NOTE: handle_options() expects value of my_bool type for GET_BOOL + accessor (i.e. bool must not be used). + */ + + struct User_management + { + static User_management_cmd *cmd; + + static char *user_name; + static char *password; + }; + + struct Main + { + /* this is not an option parsed by handle_options(). */ + static bool is_forced_default_file; + + static const char *pid_file_name; +#ifndef __WIN__ + static const char *socket_file_name; +#endif + static const char *password_file_name; + static const char *default_mysqld_path; + static uint monitoring_interval; + static uint port_number; + static const char *bind_address; + static const char *config_file; + static my_bool mysqld_safe_compatible; + }; + +#ifndef DBUG_OFF + struct Debug + { + static const char *config_str; + }; +#endif + +#ifndef __WIN__ + + struct Daemon + { + static my_bool run_as_service; + static const char *log_file_name; + static const char *user; + static const char *angel_pid_file_name; + }; + #else - static char run_as_service; /* handle_options doesn't support bool */ - static const char *user; - static const char *angel_pid_file_name; - static const char *socket_file_name; + + struct Service + { + static my_bool install_as_service; + static my_bool remove_service; + static my_bool stand_alone; + }; + #endif - static bool is_forced_default_file; - static const char *log_file_name; - static const char *pid_file_name; - static const char *password_file_name; - static const char *default_mysqld_path; - /* the option which should be passed to process_default_option_files */ - static uint monitoring_interval; - static uint port_number; - static const char *bind_address; - static const char *config_file; +public: + static int load(int argc, char **argv); + static void cleanup(); + +private: + Options(); /* Deny instantiation of this class. */ + +private: /* argv pointer returned by load_defaults() to be used by free_defaults() */ static char **saved_argv; - - int load(int argc, char **argv); - void cleanup(); -#ifdef __WIN__ - int setup_windows_defaults(); -#endif }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H diff --git a/server-tools/instance-manager/parse.cc b/server-tools/instance-manager/parse.cc index 14b3db16b45..e81320584ea 100644 --- a/server-tools/instance-manager/parse.cc +++ b/server-tools/instance-manager/parse.cc @@ -17,12 +17,12 @@ #include "parse.h" #include "commands.h" -#include <string.h> - enum Token { - TOK_ERROR= 0, /* Encodes the "ERROR" word, it doesn't indicate error. */ + TOK_CREATE= 0, + TOK_DROP, + TOK_ERROR, /* Encodes the "ERROR" word, it doesn't indicate error. */ TOK_FILES, TOK_FLUSH, TOK_GENERAL, @@ -50,6 +50,8 @@ struct tokens_st static struct tokens_st tokens[]= { + {6, "CREATE"}, + {4, "DROP"}, {5, "ERROR"}, {5, "FILES"}, {5, "FLUSH"}, @@ -67,6 +69,37 @@ static struct tokens_st tokens[]= { {5, "UNSET"} }; +/************************************************************************/ + +Named_value_arr::Named_value_arr() : + initialized(FALSE) +{ +} + + +bool Named_value_arr::init() +{ + if (my_init_dynamic_array(&arr, sizeof(Named_value), 0, 32)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +Named_value_arr::~Named_value_arr() +{ + if (!initialized) + return; + + for (int i= 0; i < get_size(); ++i) + get_element(i).free(); + + delete_dynamic(&arr); +} + +/************************************************************************/ /* Returns token no if word corresponds to some token, otherwise returns @@ -104,53 +137,199 @@ Token shift_token(const char **text, uint *word_len) } -int get_text_id(const char **text, uint *word_len, const char **id) +int get_text_id(const char **text, LEX_STRING *token) { - get_word(text, word_len); - if (*word_len == 0) + get_word(text, &token->length); + if (token->length == 0) return 1; - *id= *text; + token->str= (char *) *text; return 0; } -Command *parse_command(Instance_map *map, const char *text) +static bool parse_long(const LEX_STRING *token, long *value) +{ + int err_code; + char *end_ptr= token->str + token->length; + + *value= (long)my_strtoll10(token->str, &end_ptr, &err_code); + + return err_code != 0; +} + + +bool parse_option_value(const char *text, uint *text_len, char **value) +{ + char beginning_quote; + const char *text_start_ptr; + char *v; + bool escape_mode= FALSE; + + if (!*text || (*text != '\'' && *text != '"')) + return TRUE; /* syntax error: string expected. */ + + beginning_quote= *text; + + ++text; /* skip the beginning quote. */ + + text_start_ptr= text; + + if (!(v= Named_value::alloc_str(text))) + return TRUE; + + *value= v; + + while (TRUE) + { + if (!*text) + { + Named_value::free_str(value); + return TRUE; /* syntax error: missing terminating ' character. */ + } + + if (*text == '\n' || *text == '\r') + { + Named_value::free_str(value); + return TRUE; /* syntax error: option value should be a single line. */ + } + + if (!escape_mode && *text == beginning_quote) + break; + + if (escape_mode) + { + switch (*text) + { + case 'b': /* \b -- backspace */ + if (v > *value) + --v; + break; + + case 't': /* \t -- tab */ + *v= '\t'; + ++v; + break; + + case 'n': /* \n -- newline */ + *v= '\n'; + ++v; + break; + + case 'r': /* \r -- carriage return */ + *v= '\r'; + ++v; + break; + + case '\\': /* \\ -- back slash */ + *v= '\\'; + ++v; + break; + + case 's': /* \s -- space */ + *v= ' '; + ++v; + break; + + default: /* Unknown escape sequence. Treat as error. */ + Named_value::free_str(value); + return TRUE; + } + + escape_mode= FALSE; + } + else + { + if (*text == '\\') + { + escape_mode= TRUE; + } + else + { + *v= *text; + ++v; + } + } + + ++text; + } + + *v= 0; + + /* "2" below stands for beginning and ending quotes. */ + *text_len= text - text_start_ptr + 2; + + return FALSE; +} + + +void skip_spaces(const char **text) +{ + while (**text && my_isspace(default_charset_info, **text)) + ++(*text); +} + + +Command *parse_command(const char *text) { uint word_len; - const char *instance_name; - uint instance_name_len; - const char *option; - uint option_len; - const char *option_value; - uint option_value_len; - const char *log_size; - Command *command; - const char *saved_text= text; - bool skip= false; - const char *tmp; + LEX_STRING instance_name; + Command *command= 0; Token tok1= shift_token(&text, &word_len); switch (tok1) { case TOK_START: // fallthrough case TOK_STOP: + case TOK_CREATE: + case TOK_DROP: if (shift_token(&text, &word_len) != TOK_INSTANCE) goto syntax_error; get_word(&text, &word_len); if (word_len == 0) goto syntax_error; - instance_name= text; - instance_name_len= word_len; + instance_name.str= (char *) text; + instance_name.length= word_len; text+= word_len; - /* it should be the end of command */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; - if (tok1 == TOK_START) - command= new Start_instance(map, instance_name, instance_name_len); + if (tok1 == TOK_CREATE) + { + Create_instance *cmd= new Create_instance(&instance_name); + + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ + + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } + + command= cmd; + } else - command= new Stop_instance(map, instance_name, instance_name_len); + { + /* it should be the end of command */ + get_word(&text, &word_len, NONSPACE); + if (word_len) + goto syntax_error; + } + + switch (tok1) { + case TOK_START: + command= new Start_instance(&instance_name); + break; + case TOK_STOP: + command= new Stop_instance(&instance_name); + break; + case TOK_CREATE: + ; /* command already initialized. */ + break; + case TOK_DROP: + command= new Drop_instance(&instance_name); + break; + default: /* this is impossible, but nevertheless... */ + DBUG_ASSERT(0); + } break; case TOK_FLUSH: if (shift_token(&text, &word_len) != TOK_INSTANCES) @@ -160,92 +339,72 @@ Command *parse_command(Instance_map *map, const char *text) if (word_len) goto syntax_error; - command= new Flush_instances(map); + command= new Flush_instances(); break; case TOK_UNSET: - skip= true; case TOK_SET: + { + Abstract_option_cmd *cmd; - if (get_text_id(&text, &instance_name_len, &instance_name)) - goto syntax_error; - text+= instance_name_len; - - /* the next token should be a dot */ - get_word(&text, &word_len); - if (*text != '.') - goto syntax_error; - text++; + if (tok1 == TOK_SET) + cmd= new Set_option(); + else + cmd= new Unset_option(); - get_word(&text, &option_len, NONSPACE); - option= text; - if ((tmp= strchr(text, '=')) != NULL) - option_len= tmp - text; - text+= option_len; + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ - get_word(&text, &word_len); - if (*text == '=') - { - text++; /* skip '=' */ - get_word(&text, &option_value_len, NONSPACE); - option_value= text; - text+= option_value_len; - } - else - { - option_value= ""; - option_value_len= 0; - } + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } - /* should be empty */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; + command= cmd; - if (skip) - command= new Unset_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - else - command= new Set_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - break; + break; + } case TOK_SHOW: switch (shift_token(&text, &word_len)) { case TOK_INSTANCES: get_word(&text, &word_len, NONSPACE); if (word_len) goto syntax_error; - command= new Show_instances(map); + command= new Show_instances(); break; case TOK_INSTANCE: switch (Token tok2= shift_token(&text, &word_len)) { case TOK_OPTIONS: case TOK_STATUS: - if (get_text_id(&text, &instance_name_len, &instance_name)) + if (get_text_id(&text, &instance_name)) goto syntax_error; - text+= instance_name_len; + text+= instance_name.length; /* check that this is the end of the command */ get_word(&text, &word_len, NONSPACE); if (word_len) goto syntax_error; if (tok2 == TOK_STATUS) - command= new Show_instance_status(map, instance_name, - instance_name_len); + command= new Show_instance_status(&instance_name); else - command= new Show_instance_options(map, instance_name, - instance_name_len); + command= new Show_instance_options(&instance_name); break; default: goto syntax_error; } break; default: - instance_name= text - word_len; - instance_name_len= word_len; - if (instance_name_len) + instance_name.str= (char *) text - word_len; + instance_name.length= word_len; + if (instance_name.length) { Log_type log_type; + + long log_size; + LEX_STRING log_size_str; + + long log_offset= 0; + LEX_STRING log_offset_str= { NULL, 0 }; + switch (shift_token(&text, &word_len)) { case TOK_LOG: switch (Token tok3= shift_token(&text, &word_len)) { @@ -254,8 +413,7 @@ Command *parse_command(Instance_map *map, const char *text) /* check that this is the end of the command */ if (word_len) goto syntax_error; - command= new Show_instance_log_files(map, instance_name, - instance_name_len); + command= new Show_instance_log_files(&instance_name); break; case TOK_ERROR: case TOK_GENERAL: @@ -275,12 +433,14 @@ Command *parse_command(Instance_map *map, const char *text) goto syntax_error; } /* get the size of the log we want to retrieve */ - if (get_text_id(&text, &word_len, &log_size)) + if (get_text_id(&text, &log_size_str)) goto syntax_error; - text+= word_len; + text+= log_size_str.length; + /* this parameter is required */ - if (!word_len) + if (!log_size_str.length) goto syntax_error; + /* the next token should be comma, or nothing */ get_word(&text, &word_len); switch (*text) { @@ -290,23 +450,41 @@ Command *parse_command(Instance_map *map, const char *text) get_word(&text, &word_len); if (!word_len) goto syntax_error; + log_offset_str.str= (char *) text; + log_offset_str.length= word_len; text+= word_len; - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, text); get_word(&text, &word_len, NONSPACE); /* check that this is the end of the command */ if (word_len) goto syntax_error; break; case '\0': - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, NULL); break; /* this is ok */ default: + goto syntax_error; + } + + /* Parse size parameter. */ + + if (parse_long(&log_size_str, &log_size)) goto syntax_error; + + if (log_size <= 0) + goto syntax_error; + + /* Parse offset parameter (if specified). */ + + if (log_offset_str.length) + { + if (parse_long(&log_offset_str, &log_offset)) + goto syntax_error; + + if (log_offset <= 0) + goto syntax_error; } + + command= new Show_instance_log(&instance_name, + log_type, log_size, log_offset); break; default: goto syntax_error; @@ -325,5 +503,8 @@ Command *parse_command(Instance_map *map, const char *text) syntax_error: command= new Syntax_error(); } + + DBUG_ASSERT(command); + return command; } diff --git a/server-tools/instance-manager/parse.h b/server-tools/instance-manager/parse.h index 3da53e3a61e..7e954b4918f 100644 --- a/server-tools/instance-manager/parse.h +++ b/server-tools/instance-manager/parse.h @@ -18,9 +18,9 @@ #include <my_global.h> #include <my_sys.h> +#include <m_string.h> class Command; -class Instance_map; enum Log_type { @@ -29,10 +29,148 @@ enum Log_type IM_LOG_SLOW }; -Command *parse_command(Instance_map *instance_map, const char *text); +Command *parse_command(const char *text); + +bool parse_option_value(const char *text, uint *text_len, char **value); + +void skip_spaces(const char **text); /* define kinds of the word seek method */ -enum { ALPHANUM= 1, NONSPACE }; +enum enum_seek_method { ALPHANUM= 1, NONSPACE, OPTION_NAME }; + +/************************************************************************/ + +class Named_value +{ +public: + /* + The purpose of these methods is just to have one method for + allocating/deallocating memory for strings for Named_value. + */ + + static inline char *alloc_str(const LEX_STRING *str); + static inline char *alloc_str(const char *str); + static inline void free_str(char **str); + +public: + inline Named_value(); + inline Named_value(char *name_arg, char *value_arg); + + inline char *get_name(); + inline char *get_value(); + + inline void free(); + +private: + char *name; + char *value; +}; + +inline char *Named_value::alloc_str(const LEX_STRING *str) +{ + return my_strndup(str->str, str->length, MYF(0)); +} + +inline char *Named_value::alloc_str(const char *str) +{ + return my_strdup(str, MYF(0)); +} + +inline void Named_value::free_str(char **str) +{ + my_free(*str, MYF(MY_ALLOW_ZERO_PTR)); + *str= NULL; +} + +inline Named_value::Named_value() + :name(NULL), value(NULL) +{ } + +inline Named_value::Named_value(char *name_arg, char *value_arg) + :name(name_arg), value(value_arg) +{ } + +inline char *Named_value::get_name() +{ + return name; +} + +inline char *Named_value::get_value() +{ + return value; +} + +void Named_value::free() +{ + free_str(&name); + free_str(&value); +} + +/************************************************************************/ + +class Named_value_arr +{ +public: + Named_value_arr(); + ~Named_value_arr(); + + bool init(); + + inline int get_size() const; + inline Named_value get_element(int idx) const; + inline void remove_element(int idx); + inline bool add_element(Named_value *option); + inline bool replace_element(int idx, Named_value *option); + +private: + bool initialized; + DYNAMIC_ARRAY arr; +}; + + +inline int Named_value_arr::get_size() const +{ + return arr.elements; +} + + +inline Named_value Named_value_arr::get_element(int idx) const +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + Named_value option; + get_dynamic((DYNAMIC_ARRAY *) &arr, (gptr) &option, idx); + + return option; +} + + +inline void Named_value_arr::remove_element(int idx) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + delete_dynamic_element(&arr, idx); +} + + +inline bool Named_value_arr::add_element(Named_value *option) +{ + return insert_dynamic(&arr, (gptr) option); +} + + +inline bool Named_value_arr::replace_element(int idx, Named_value *option) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + return set_dynamic(&arr, (gptr) option, idx); +} + +/************************************************************************/ /* tries to find next word in the text @@ -41,7 +179,7 @@ enum { ALPHANUM= 1, NONSPACE }; */ inline void get_word(const char **text, uint *word_len, - int seek_method= ALPHANUM) + enum_seek_method seek_method= ALPHANUM) { const char *word_end; @@ -51,13 +189,23 @@ inline void get_word(const char **text, uint *word_len, word_end= *text; - if (seek_method == ALPHANUM) + switch (seek_method) { + case ALPHANUM: while (my_isalnum(default_charset_info, *word_end)) ++word_end; - else + break; + case NONSPACE: while (!my_isspace(default_charset_info, *word_end) && (*word_end != '\0')) ++word_end; + break; + case OPTION_NAME: + while (my_isalnum(default_charset_info, *word_end) || + *word_end == '-' || + *word_end == '_') + ++word_end; + break; + } *word_len= word_end - *text; } diff --git a/server-tools/instance-manager/parse_output.cc b/server-tools/instance-manager/parse_output.cc index ebc45c1f7d4..9213de82e1d 100644 --- a/server-tools/instance-manager/parse_output.cc +++ b/server-tools/instance-manager/parse_output.cc @@ -14,13 +14,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include "parse.h" #include "parse_output.h" -#include <stdio.h> +#include <my_global.h> #include <my_sys.h> #include <m_string.h> + +#include <stdio.h> + +#include "parse.h" #include "portability.h" @@ -41,7 +43,7 @@ void trim_space(const char **text, uint *word_len) /* Parse output of the given command - SYNOPSYS + SYNOPSIS parse_output_and_get_value() command the command to execue with popen. @@ -96,14 +98,14 @@ int parse_output_and_get_value(const char *command, const char *word, linebuf[sizeof(linebuf) - 1]= '\0'; /* safety */ /* - Compare the start of our line with the word(s) we are looking for. + Find the word(s) we are looking for in the line */ - if (!strncmp(word, linep, wordlen)) + if ((linep= strstr(linep, word))) { /* If we have found our word(s), then move linep past the word(s) */ - linep+= wordlen; + linep+= wordlen; if (flag & GET_VALUE) { trim_space((const char**) &linep, &found_word_len); diff --git a/server-tools/instance-manager/parse_output.h b/server-tools/instance-manager/parse_output.h index 6a84fabbf17..b86363a4452 100644 --- a/server-tools/instance-manager/parse_output.h +++ b/server-tools/instance-manager/parse_output.h @@ -16,6 +16,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> + #define GET_VALUE 1 #define GET_LINE 2 diff --git a/server-tools/instance-manager/portability.h b/server-tools/instance-manager/portability.h index 7992a06c336..ef1fc31c1ea 100644 --- a/server-tools/instance-manager/portability.h +++ b/server-tools/instance-manager/portability.h @@ -19,11 +19,25 @@ /*TODO: fix this */ #define PROTOCOL_VERSION 10 +#define DFLT_CONFIG_FILE_NAME "my.ini" +#define DFLT_MYSQLD_PATH "mysqld" +#define DFLT_PASSWD_FILE_EXT ".passwd" +#define DFLT_PID_FILE_EXT ".pid" +#define DFLT_SOCKET_FILE_EXT ".sock" + typedef int pid_t; #undef popen #define popen(A,B) _popen(A,B) +#define NEWLINE "\r\n" +#define NEWLINE_LEN 2 + +#else /* ! __WIN__ */ + +#define NEWLINE "\n" +#define NEWLINE_LEN 1 + #endif /* __WIN__ */ #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PORTABILITY_H */ diff --git a/server-tools/instance-manager/priv.cc b/server-tools/instance-manager/priv.cc index d2d6a3f636c..7695b585169 100644 --- a/server-tools/instance-manager/priv.cc +++ b/server-tools/instance-manager/priv.cc @@ -14,38 +14,19 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include <mysql_com.h> #include "priv.h" -#include "portability.h" -#if defined(__ia64__) || defined(__ia64) -/* - We can live with 32K, but reserve 64K. Just to be safe. - On ia64 we need to reserve double of the size. -*/ -#define IM_THREAD_STACK_SIZE (128*1024L) -#else -#define IM_THREAD_STACK_SIZE (64*1024) -#endif - - -/* the pid of the manager process (of the signal thread on the LinuxThreads) */ -pid_t manager_pid; +#include <my_global.h> +#include <mysql_com.h> +#include <my_sys.h> -/* - This flag is set if mysqlmanager has detected that it is running on the - system using LinuxThreads -*/ -bool linuxthreads; +#include "log.h" /* The following string must be less then 80 characters, as mysql_connection.cc relies on it */ -const char mysqlmanager_version[] = "0.2-alpha"; - -const int mysqlmanager_version_length= sizeof(mysqlmanager_version) - 1; +const LEX_STRING mysqlmanager_version= { C_STRING_WITH_LEN("1.0-beta") }; const unsigned char protocol_version= PROTOCOL_VERSION; @@ -65,30 +46,32 @@ unsigned long bytes_sent = 0L, bytes_received = 0L; unsigned long mysqld_net_retry_count = 10L; unsigned long open_files_limit; -/* - Change the stack size and start a thread. Return an error if either - pthread_attr_setstacksize or pthread_create fails. - Arguments are the same as for pthread_create(). -*/ -int set_stacksize_n_create_thread(pthread_t *thread, pthread_attr_t *attr, - void *(*start_routine)(void *), void *arg) + +int create_pid_file(const char *pid_file_name, int pid) { - int rc= 0; - -#ifndef __WIN__ -#ifndef PTHREAD_STACK_MIN -#define PTHREAD_STACK_MIN 32768 -#endif - /* - Set stack size to be safe on the platforms with too small - default thread stack. - */ - rc= pthread_attr_setstacksize(attr, - (size_t) (PTHREAD_STACK_MIN + - IM_THREAD_STACK_SIZE)); -#endif - if (!rc) - rc= pthread_create(thread, attr, start_routine, arg); - return rc; + FILE *pid_file; + + if (!(pid_file= my_fopen(pid_file_name, O_WRONLY | O_CREAT | O_BINARY, + MYF(0)))) + { + log_error("Can not create pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; + } + + if (fprintf(pid_file, "%d\n", (int) pid) <= 0) + { + log_error("Can not write to pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; + } + + my_fclose(pid_file, MYF(0)); + + return 0; } diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h index 52d7aa1d23d..4d434213781 100644 --- a/server-tools/instance-manager/priv.h +++ b/server-tools/instance-manager/priv.h @@ -16,13 +16,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> +#include <m_string.h> +#include <my_pthread.h> + #include <sys/types.h> -#ifdef __WIN__ -#include "portability.h" -#else + +#ifndef __WIN__ #include <unistd.h> #endif -#include "my_pthread.h" + +#include "portability.h" /* IM-wide platform-independent defines */ #define SERVER_DEFAULT_PORT 3306 @@ -31,19 +35,22 @@ /* three-week timeout should be enough */ #define LONG_TIMEOUT ((ulong) 3600L*24L*21L) -/* the pid of the manager process (of the signal thread on the LinuxThreads) */ -extern pid_t manager_pid; +const int MEM_ROOT_BLOCK_SIZE= 512; + +/* The maximal length of option name and option value. */ +const int MAX_OPTION_LEN= 1024; -#ifndef __WIN__ /* - This flag is set if mysqlmanager has detected that it is running on the - system using LinuxThreads + The maximal length of whole option string: + --<option name>=<option value> */ -extern bool linuxthreads; -#endif +const int MAX_OPTION_STR_LEN= 2 + MAX_OPTION_LEN + 1 + MAX_OPTION_LEN + 1; -extern const char mysqlmanager_version[]; -extern const int mysqlmanager_version_length; +const int MAX_VERSION_LENGTH= 160; + +const int MAX_INSTANCE_NAME_SIZE= FN_REFLEN; + +extern const LEX_STRING mysqlmanager_version; /* MySQL client-server protocol version: substituted from configure */ extern const unsigned char protocol_version; @@ -87,8 +94,6 @@ extern unsigned long bytes_sent, bytes_received; extern unsigned long mysqld_net_retry_count; extern unsigned long open_files_limit; - -int set_stacksize_n_create_thread(pthread_t *thread, pthread_attr_t *attr, - void *(*start_routine)(void *), void *arg); +int create_pid_file(const char *pid_file_name, int pid); #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H diff --git a/server-tools/instance-manager/protocol.cc b/server-tools/instance-manager/protocol.cc index 73e07f993ae..4a8c4d0b88d 100644 --- a/server-tools/instance-manager/protocol.cc +++ b/server-tools/instance-manager/protocol.cc @@ -163,7 +163,7 @@ int send_fields(struct st_net *net, LIST *fields) Buffer send_buff; char small_buff[4]; uint position= 0; - NAME_WITH_LENGTH *field; + LEX_STRING *field; /* send the number of fileds */ net_store_length(small_buff, (uint) list_length(fields)); @@ -173,7 +173,7 @@ int send_fields(struct st_net *net, LIST *fields) while (tmp) { position= 0; - field= (NAME_WITH_LENGTH *) tmp->data; + field= (LEX_STRING *) tmp->data; store_to_protocol_packet(&send_buff, (char*) "", &position); /* catalog name */ @@ -184,9 +184,9 @@ int send_fields(struct st_net *net, LIST *fields) store_to_protocol_packet(&send_buff, (char*) "", &position); /* table name alias */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name */ + field->str, &position); /* column name */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name alias */ + field->str, &position); /* column name alias */ send_buff.reserve(position, 12); if (send_buff.is_error()) goto err; diff --git a/server-tools/instance-manager/protocol.h b/server-tools/instance-manager/protocol.h index f38eac6b079..2c84ad394b4 100644 --- a/server-tools/instance-manager/protocol.h +++ b/server-tools/instance-manager/protocol.h @@ -20,11 +20,6 @@ #include <my_list.h> -typedef struct field { - char *name; - uint length; -} NAME_WITH_LENGTH; - /* default field length to be used in various field-realted functions */ enum { DEFAULT_FIELD_LENGTH= 20 }; diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc index 0091d713a91..5cbf1eb044e 100644 --- a/server-tools/instance-manager/thread_registry.cc +++ b/server-tools/instance-manager/thread_registry.cc @@ -20,11 +20,10 @@ #include "thread_registry.h" -#include "log.h" +#include <my_global.h> +#include <thr_alarm.h> -#include <assert.h> #include <signal.h> -#include <thr_alarm.h> #ifndef __WIN__ @@ -37,13 +36,13 @@ static void handle_signal(int __attribute__((unused)) sig_no) } #endif -/* - Thread_info initializer methods -*/ +/* Thread_info initializer methods */ -Thread_info::Thread_info() {} -Thread_info::Thread_info(pthread_t thread_id_arg) : - thread_id(thread_id_arg) {} +void Thread_info::init(bool send_signal_on_shutdown_arg) +{ + thread_id= pthread_self(); + send_signal_on_shutdown= send_signal_on_shutdown_arg; +} /* TODO: think about moving signal information (now it's shutdown_in_progress) @@ -52,7 +51,7 @@ Thread_info::Thread_info(pthread_t thread_id_arg) : */ Thread_registry::Thread_registry() : - shutdown_in_progress(false) + shutdown_in_progress(FALSE) ,sigwait_thread_pid(pthread_self()) { pthread_mutex_init(&LOCK_thread_registry, 0); @@ -83,8 +82,14 @@ Thread_registry::~Thread_registry() points to the last node. */ -void Thread_registry::register_thread(Thread_info *info) +void Thread_registry::register_thread(Thread_info *info, + bool send_signal_on_shutdown) { + info->init(send_signal_on_shutdown); + + DBUG_PRINT("info", ("Thread_registry: registering thread %lu...", + (unsigned long) info->thread_id)); + #ifndef __WIN__ struct sigaction sa; sa.sa_handler= handle_signal; @@ -111,11 +116,19 @@ void Thread_registry::register_thread(Thread_info *info) void Thread_registry::unregister_thread(Thread_info *info) { + DBUG_PRINT("info", ("Thread_registry: unregistering thread %lu...", + (unsigned long) info->thread_id)); + pthread_mutex_lock(&LOCK_thread_registry); info->prev->next= info->next; info->next->prev= info->prev; + if (head.next == &head) + { + DBUG_PRINT("info", ("Thread_registry: thread registry is empty!")); pthread_cond_signal(&COND_thread_registry_is_empty); + } + pthread_mutex_unlock(&LOCK_thread_registry); } @@ -180,13 +193,8 @@ int Thread_registry::cond_timedwait(Thread_info *info, pthread_cond_t *cond, void Thread_registry::deliver_shutdown() { - Thread_info *info; - struct timespec shutdown_time; - int error; - set_timespec(shutdown_time, 1); - pthread_mutex_lock(&LOCK_thread_registry); - shutdown_in_progress= true; + shutdown_in_progress= TRUE; #ifndef __WIN__ /* to stop reading from the network we need to flush alarm queue */ @@ -198,29 +206,14 @@ void Thread_registry::deliver_shutdown() process_alarm(THR_SERVER_ALARM); #endif - for (info= head.next; info != &head; info= info->next) - { - pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL); - /* - sic: race condition here, the thread may not yet fall into - pthread_cond_wait. - */ - if (info->current_cond) - pthread_cond_signal(info->current_cond); - } /* - The common practice is to test predicate before pthread_cond_wait. - I don't do that here because the predicate is practically always false - before wait - is_shutdown's been just set, and the lock's still not - released - the only case when the predicate is false is when no other - threads exist. + sic: race condition here, the thread may not yet fall into + pthread_cond_wait. */ - while (((error= pthread_cond_timedwait(&COND_thread_registry_is_empty, - &LOCK_thread_registry, - &shutdown_time)) != ETIMEDOUT && - error != ETIME) && - head.next != &head) - ; + + interrupt_threads(); + + wait_for_threads_to_unregister(); /* If previous signals did not reach some threads, they must be sleeping @@ -229,12 +222,31 @@ void Thread_registry::deliver_shutdown() so this time everybody should be informed (presumably each worker can get CPU during shutdown_time.) */ - for (info= head.next; info != &head; info= info->next) + + interrupt_threads(); + + /* Get the last chance to threads to stop. */ + + wait_for_threads_to_unregister(); + +#ifndef DBUG_OFF + /* + Print out threads, that didn't stopped. Thread_registry destructor will + probably abort the program if there is still any alive thread. + */ + + if (head.next != &head) { - pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL); - if (info->current_cond) - pthread_cond_signal(info->current_cond); + DBUG_PRINT("info", ("Thread_registry: non-stopped threads:")); + + for (Thread_info *info= head.next; info != &head; info= info->next) + DBUG_PRINT("info", (" - %lu", (unsigned long) info->thread_id)); + } + else + { + DBUG_PRINT("info", ("Thread_registry: all threads stopped.")); } +#endif // DBUG_OFF pthread_mutex_unlock(&LOCK_thread_registry); } @@ -244,3 +256,139 @@ void Thread_registry::request_shutdown() { pthread_kill(sigwait_thread_pid, SIGTERM); } + + +void Thread_registry::interrupt_threads() +{ + for (Thread_info *info= head.next; info != &head; info= info->next) + { + if (!info->send_signal_on_shutdown) + continue; + + pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL); + if (info->current_cond) + pthread_cond_signal(info->current_cond); + } +} + + +void Thread_registry::wait_for_threads_to_unregister() +{ + struct timespec shutdown_time; + + set_timespec(shutdown_time, 1); + + DBUG_PRINT("info", ("Thread_registry: joining threads...")); + + while (true) + { + if (head.next == &head) + { + DBUG_PRINT("info", ("Thread_registry: emptied.")); + return; + } + + int error= pthread_cond_timedwait(&COND_thread_registry_is_empty, + &LOCK_thread_registry, + &shutdown_time); + + if (error == ETIMEDOUT || error == ETIME) + { + DBUG_PRINT("info", ("Thread_registry: threads shutdown timed out.")); + return; + } + } +} + + +/********************************************************************* + class Thread +*********************************************************************/ + +#if defined(__ia64__) || defined(__ia64) +/* + We can live with 32K, but reserve 64K. Just to be safe. + On ia64 we need to reserve double of the size. +*/ +#define IM_THREAD_STACK_SIZE (128*1024L) +#else +#define IM_THREAD_STACK_SIZE (64*1024) +#endif + +/* + Change the stack size and start a thread. Return an error if either + pthread_attr_setstacksize or pthread_create fails. + Arguments are the same as for pthread_create(). +*/ + +static +int set_stacksize_and_create_thread(pthread_t *thread, pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) +{ + int rc= 0; + +#ifndef __WIN__ +#ifndef PTHREAD_STACK_MIN +#define PTHREAD_STACK_MIN 32768 +#endif + /* + Set stack size to be safe on the platforms with too small + default thread stack. + */ + rc= pthread_attr_setstacksize(attr, + (size_t) (PTHREAD_STACK_MIN + + IM_THREAD_STACK_SIZE)); +#endif + if (!rc) + rc= pthread_create(thread, attr, start_routine, arg); + return rc; +} + + +Thread::~Thread() +{ +} + + +void *Thread::thread_func(void *arg) +{ + Thread *thread= (Thread *) arg; + my_thread_init(); + + thread->run(); + + my_thread_end(); + return NULL; +} + + +bool Thread::start(enum_thread_type thread_type) +{ + pthread_attr_t attr; + int rc; + + pthread_attr_init(&attr); + + if (thread_type == DETACHED) + { + detached = TRUE; + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + } + else + { + detached = FALSE; + } + + rc= set_stacksize_and_create_thread(&id, &attr, Thread::thread_func, this); + pthread_attr_destroy(&attr); + + return rc != 0; +} + + +bool Thread::join() +{ + DBUG_ASSERT(!detached); + + return pthread_join(id, NULL) != 0; +} diff --git a/server-tools/instance-manager/thread_registry.h b/server-tools/instance-manager/thread_registry.h index 6dc320a8533..b9ece271c21 100644 --- a/server-tools/instance-manager/thread_registry.h +++ b/server-tools/instance-manager/thread_registry.h @@ -57,7 +57,7 @@ #pragma interface #endif -/* +/** Thread_info - repository entry for each worker thread All entries comprise double-linked list like: 0 -- entry -- entry -- entry - 0 @@ -67,17 +67,63 @@ class Thread_info { public: - Thread_info(); - Thread_info(pthread_t thread_id_arg); + Thread_info() {} friend class Thread_registry; private: + void init(bool send_signal_on_shutdown); +private: pthread_cond_t *current_cond; Thread_info *prev, *next; pthread_t thread_id; + bool send_signal_on_shutdown; }; -/* +/** + A base class for a detached thread. +*/ + +class Thread +{ +public: + enum enum_thread_type + { + DETACHED, + JOINABLE + }; +public: + Thread() + { } + +public: + inline bool is_detached() const; + + bool start(enum_thread_type thread_type = JOINABLE); + bool join(); + +protected: + virtual void run()= 0; + virtual ~Thread(); + +private: + pthread_t id; + bool detached; + +private: + static void *thread_func(void *arg); + +private: + Thread(const Thread & /* rhs */); /* not implemented */ + Thread &operator=(const Thread & /* rhs */); /* not implemented */ +}; + +inline bool Thread::is_detached() const +{ + return detached; +} + + +/** Thread_registry - contains handles for each worker thread to deliver signal information to workers. */ @@ -88,7 +134,7 @@ public: Thread_registry(); ~Thread_registry(); - void register_thread(Thread_info *info); + void register_thread(Thread_info *info, bool send_signal_on_shutdown= TRUE); void unregister_thread(Thread_info *info); void deliver_shutdown(); void request_shutdown(); @@ -97,12 +143,21 @@ public: pthread_mutex_t *mutex); int cond_timedwait(Thread_info *info, pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *wait_time); + +private: + void interrupt_threads(); + void wait_for_threads_to_unregister(); + private: Thread_info head; bool shutdown_in_progress; pthread_mutex_t LOCK_thread_registry; pthread_cond_t COND_thread_registry_is_empty; pthread_t sigwait_thread_pid; + +private: + Thread_registry(const Thread_registry &); + Thread_registry &operator =(const Thread_registry &); }; diff --git a/server-tools/instance-manager/user_management_commands.cc b/server-tools/instance-manager/user_management_commands.cc new file mode 100644 index 00000000000..a32a4e94415 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.cc @@ -0,0 +1,406 @@ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "user_management_commands.h" + +#include "exit_codes.h" +#include "options.h" +#include "user_map.h" + +/************************************************************************* + Module-specific (internal) functions. +*************************************************************************/ + +/* + The function returns user name. The user name is retrieved from command-line + options (if specified) or from console. + + NOTE + This function must not be used in user-management command implementations. + Use get_user_name() instead. + + SYNOPSIS + get_user_name_impl() + + RETURN + NULL on error + valid pointer on success +*/ + +static char *get_user_name_impl() +{ + static char user_name_buf[1024]; + char *ptr; + + if (Options::User_management::user_name) + return Options::User_management::user_name; + + printf("Enter user name: "); + fflush(stdout); + + if (!fgets(user_name_buf, sizeof (user_name_buf), stdin)) + return NULL; + + if ((ptr= strchr(user_name_buf, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_name_buf, '\r'))) + *ptr= 0; + + return user_name_buf; +} + + +/* + The function is intended to provide user name for user-management + operations. It also checks that length of the specified user name is correct + (not empty, not exceeds USERNAME_LENGTH). Report to stderr if something is + wrong. + + SYNOPSIS + get_user_name() + user_name [OUT] on success contains user name + + RETURN + TRUE on error + FALSE on success +*/ + +static bool get_user_name(LEX_STRING *user_name) +{ + char *user_name_str= get_user_name_impl(); + + if (!user_name_str) + { + fprintf(stderr, "Error: unable to read user name from stdin.\n"); + return TRUE; + } + + user_name->str= user_name_str; + user_name->length= strlen(user_name->str); + + if (user_name->length == 0) + { + fprintf(stderr, "Error: user name can not be empty.\n"); + return TRUE; + } + + if (user_name->length > USERNAME_LENGTH) + { + fprintf(stderr, "Error: user name must not exceed %d characters.\n", + (int) USERNAME_LENGTH); + return TRUE; + } + + return FALSE; +} + + +/* + The function is intended to provide password for user-management operations. + The password is retrieved from command-line options (if specified) or from + console. + + SYNOPSIS + get_password() + + RETURN + NULL on error + valid pointer on success +*/ + +static const char *get_password() +{ + if (Options::User_management::password) + return Options::User_management::password; + + const char *passwd1= get_tty_password("Enter password: "); + const char *passwd2= get_tty_password("Re-type password: "); + + if (strcmp(passwd1, passwd2)) + { + fprintf(stderr, "Error: passwords do not match.\n"); + return 0; + } + + return passwd1; +} + + +/* + Load password file into user map. + + SYNOPSIS + load_password_file() + user_map target user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int load_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if (user_map->init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + if ((err_code= user_map->load(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + + +/* + Save user map into password file. + + SYNOPSIS + save_password_file() + user_map user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int save_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if ((err_code= user_map->save(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + +/************************************************************************* + Print_password_line_cmd +*************************************************************************/ + +int Print_password_line_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + printf("Creating record for new user.\n"); + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + { + User user(&user_name, password); + + printf("%s:%s\n", + (const char *) user.user, + (const char *) user.scrambled_password); + } + + return ERR_OK; +} + + +/************************************************************************* + Add_user_cmd +*************************************************************************/ + +int Add_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + User_map user_map; + User *new_user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Check that the user does not exist. */ + + if (user_map.find_user(&user_name)) + { + fprintf(stderr, "Error: user '%s' already exists.\n", + (const char *) user_name.str); + return ERR_USER_ALREADY_EXISTS; + } + + /* Add the user. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + if (!(new_user= new User(&user_name, password))) + return ERR_OUT_OF_MEMORY; + + if (user_map.add_user(new_user)) + { + delete new_user; + return ERR_OUT_OF_MEMORY; + } + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Drop_user_cmd +*************************************************************************/ + +int Drop_user_cmd::execute() +{ + LEX_STRING user_name; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Remove the user (ignore possible errors). */ + + user_map.remove_user(user); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Edit_user_cmd +*************************************************************************/ + +int Edit_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Modify user's password. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + user->set_password(password); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Clean_db_cmd +*************************************************************************/ + +int Clean_db_cmd::execute() +{ + User_map user_map; + + if (user_map.init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + return save_password_file(&user_map); +} + + +/************************************************************************* + Check_db_cmd +*************************************************************************/ + +int Check_db_cmd::execute() +{ + User_map user_map; + + return load_password_file(&user_map); +} + + +/************************************************************************* + List_users_cmd +*************************************************************************/ + +int List_users_cmd::execute() +{ + User_map user_map; + + int err_code; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map))) + return err_code; + + /* Print out registered users. */ + + { + User_map::Iterator it(&user_map); + User *user; + + while ((user= it.next())) + fprintf(stderr, "%s\n", (const char *) user->user); + } + + return ERR_OK; +} diff --git a/server-tools/instance-manager/user_management_commands.h b/server-tools/instance-manager/user_management_commands.h new file mode 100644 index 00000000000..17c11ccfef0 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.h @@ -0,0 +1,168 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H + +/* + Copyright (C) 2006 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 +*/ + +/* + This header contains declarations of classes inteded to support + user-management commands (such as add user, get list of users, etc). + + The general idea is to have one interface (pure abstract class) for such a + command. Each concrete user-management command is implemented in concrete + class, derived from the common interface. +*/ + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/************************************************************************* + User_management_cmd -- base class for User-management commands. +*************************************************************************/ + +class User_management_cmd +{ +public: + User_management_cmd() + { } + + virtual ~User_management_cmd() + { } + +public: + /* + Executes user-management command. + + SYNOPSIS + execute() + + RETURN + See exit_codes.h for possible values. + */ + + virtual int execute() = 0; +}; + + +/************************************************************************* + Print_password_line_cmd: support for --print-password-line command-line + option. +*************************************************************************/ + +class Print_password_line_cmd: public User_management_cmd +{ +public: + Print_password_line_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Add_user_cmd: support for --add-user command-line option. +*************************************************************************/ + +class Add_user_cmd: public User_management_cmd +{ +public: + Add_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Drop_user_cmd: support for --drop-user command-line option. +*************************************************************************/ + +class Drop_user_cmd: public User_management_cmd +{ +public: + Drop_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Edit_user_cmd: support for --edit-user command-line option. +*************************************************************************/ + +class Edit_user_cmd: public User_management_cmd +{ +public: + Edit_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Clean_db_cmd: support for --clean-db command-line option. +*************************************************************************/ + +class Clean_db_cmd: public User_management_cmd +{ +public: + Clean_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Check_db_cmd: support for --check-db command-line option. +*************************************************************************/ + +class Check_db_cmd: public User_management_cmd +{ +public: + Check_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + List_users_cmd: support for --list-users command-line option. +*************************************************************************/ + +class List_users_cmd: public User_management_cmd +{ +public: + List_users_cmd() + { } + +public: + virtual int execute(); +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H diff --git a/server-tools/instance-manager/user_map.cc b/server-tools/instance-manager/user_map.cc index 9cb15307131..f3a6e3cd76c 100644 --- a/server-tools/instance-manager/user_map.cc +++ b/server-tools/instance-manager/user_map.cc @@ -19,32 +19,32 @@ #endif #include "user_map.h" - -#include <mysql_com.h> -#include <m_string.h> - +#include "exit_codes.h" #include "log.h" +#include "portability.h" -struct User +User::User(const LEX_STRING *user_name_arg, const char *password) { - char user[USERNAME_LENGTH + 1]; - uint8 user_length; - uint8 salt[SCRAMBLE_LENGTH]; - int init(const char *line); -}; + user_length= strmake(user, user_name_arg->str, USERNAME_LENGTH + 1) - user; + set_password(password); +} int User::init(const char *line) { const char *name_begin, *name_end, *password; - int line_ending_len= 1; + int password_length; if (line[0] == '\'' || line[0] == '"') { name_begin= line + 1; name_end= strchr(name_begin, line[0]); if (name_end == 0 || name_end[1] != ':') - goto err; + { + log_error("Invalid format (unmatched quote) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 2; } else @@ -52,33 +52,47 @@ int User::init(const char *line) name_begin= line; name_end= strchr(name_begin, ':'); if (name_end == 0) - goto err; + { + log_error("Invalid format (no delimiter) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 1; } + user_length= name_end - name_begin; if (user_length > USERNAME_LENGTH) - goto err; - - /* - assume that newline characater is present - we support reading password files that end in \n or \r\n on - either platform. - */ - if (password[strlen(password)-2] == '\r') - line_ending_len= 2; - if (strlen(password) != (uint) (SCRAMBLED_PASSWORD_CHAR_LENGTH + - line_ending_len)) - goto err; + { + log_error("User name is too long (%d). Max length: %d. " + "User line: '%s'.", + (int) user_length, + (int) USERNAME_LENGTH, + (const char *) line); + return 1; + } + + password_length= strlen(password); + if (password_length > SCRAMBLED_PASSWORD_CHAR_LENGTH) + { + log_error("Password is too long (%d). Max length: %d." + "User line: '%s'.", + (int) password_length, + (int) SCRAMBLED_PASSWORD_CHAR_LENGTH, + line); + return 1; + } memcpy(user, name_begin, user_length); user[user_length]= 0; + + memcpy(scrambled_password, password, password_length); + scrambled_password[password_length]= 0; + get_salt_from_password(salt, password); - log_info("loaded user %s", user); + + log_info("Loaded user '%s'.", (const char *) user); return 0; -err: - log_error("error parsing user and password at line %s", line); - return 1; } @@ -101,30 +115,70 @@ static void delete_user(void *u) C_MODE_END +void User_map::Iterator::reset() +{ + cur_idx= 0; +} + + +User *User_map::Iterator::next() +{ + if (cur_idx < user_map->hash.records) + return (User *) hash_element(&user_map->hash, cur_idx++); + + return NULL; +} + + int User_map::init() { enum { START_HASH_SIZE= 16 }; if (hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_user_key, delete_user, 0)) return 1; + + initialized= TRUE; + return 0; } +User_map::User_map() + :initialized(FALSE) +{ +} + + User_map::~User_map() { - hash_free(&hash); + if (initialized) + hash_free(&hash); } /* - Load all users from the password file. Must be called once right after - construction. - In case of failure, puts error message to the log file and returns 1 + Load password database. + + SYNOPSIS + load() + password_file_name [IN] password file path + err_msg [OUT] error message + + DESCRIPTION + Load all users from the password file. Must be called once right after + construction. In case of failure, puts error message to the log file and + returns specific error code. + + RETURN + 0 on success + !0 on error */ -int User_map::load(const char *password_file_name) +int User_map::load(const char *password_file_name, const char **err_msg) { + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + FILE *file; char line[USERNAME_LENGTH + SCRAMBLED_PASSWORD_CHAR_LENGTH + 2 + /* for possible quotes */ @@ -132,35 +186,173 @@ int User_map::load(const char *password_file_name) 2 + /* for newline */ 1]; /* for trailing zero */ User *user; - int rc= 1; + + if (my_access(password_file_name, F_OK) != 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) does not exist", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_DOES_NOT_EXIST; + } if ((file= my_fopen(password_file_name, O_RDONLY | O_BINARY, MYF(0))) == 0) { - /* Probably the password file wasn't specified. Try to leave without it */ - log_info("[WARNING] can't open password file %s: errno=%d, %s", password_file_name, - errno, strerror(errno)); - return 0; + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; } + log_info("Loading the password database..."); + while (fgets(line, sizeof(line), file)) { + char *user_line= line; + + /* + We need to skip EOL-symbols also from the beginning of the line, because + if the previous line was ended by \n\r sequence, we get \r in our line. + */ + + while (user_line[0] == '\r' || user_line[0] == '\n') + ++user_line; + + /* Skip EOL-symbols in the end of the line. */ + + { + char *ptr; + + if ((ptr= strchr(user_line, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_line, '\r'))) + *ptr= 0; + } + /* skip comments and empty lines */ - if (line[0] == '#' || line[0] == '\n' && - (line[1] == '\0' || line[1] == '\r')) + if (!user_line[0] || user_line[0] == '#') continue; + if ((user= new User) == 0) - goto done; - if (user->init(line) || my_hash_insert(&hash, (byte *) user)) - goto err_init_user; + { + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } + + if (user->init(user_line)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) corrupted", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_CORRUPTED; + } + + if (my_hash_insert(&hash, (byte *) user)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } } - if (feof(file)) - rc= 0; - goto done; -err_init_user: - delete user; -done: + + log_info("The password database loaded successfully."); + my_fclose(file, MYF(0)); - return rc; + + if (err_msg) + *err_msg= NULL; + + return ERR_OK; +} + + +int User_map::save(const char *password_file_name, const char **err_msg) +{ + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + + FILE *file; + + if ((file= my_fopen(password_file_name, O_WRONLY | O_TRUNC | O_BINARY, + MYF(0))) == 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s) for writing: %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; + } + + { + User_map::Iterator it(this); + User *user; + + while ((user= it.next())) + { + if (fprintf(file, "%s:%s\n", (const char *) user->user, + (const char *) user->scrambled_password) < 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not write to password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + my_fclose(file, MYF(0)); + + return ERR_IO_ERROR; + } + } + } + + my_fclose(file, MYF(0)); + + return ERR_OK; } @@ -172,13 +364,33 @@ done: 2 - user not found */ -int User_map::authenticate(const char *user_name, uint length, +int User_map::authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const { - const User *user= (const User *) hash_search((HASH *) &hash, - (byte *) user_name, length); - if (user) - return check_scramble(scrambled_password, scramble, user->salt); - return 2; + const User *user= find_user(user_name); + return user ? check_scramble(scrambled_password, scramble, user->salt) : 2; +} + + +User *User_map::find_user(const LEX_STRING *user_name) +{ + return (User*) hash_search(&hash, (byte*) user_name->str, user_name->length); +} + +const User *User_map::find_user(const LEX_STRING *user_name) const +{ + return const_cast<User_map *> (this)->find_user(user_name); +} + + +bool User_map::add_user(User *user) +{ + return my_hash_insert(&hash, (byte*) user) == 0 ? FALSE : TRUE; +} + + +bool User_map::remove_user(User *user) +{ + return hash_delete(&hash, (byte*) user) == 0 ? FALSE : TRUE; } diff --git a/server-tools/instance-manager/user_map.h b/server-tools/instance-manager/user_map.h index 4134017dd9b..5325af12058 100644 --- a/server-tools/instance-manager/user_map.h +++ b/server-tools/instance-manager/user_map.h @@ -18,14 +18,35 @@ #include <my_global.h> - #include <my_sys.h> +#include <mysql_com.h> +#include <m_string.h> #include <hash.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +struct User +{ + User() + {} + + User(const LEX_STRING *user_name_arg, const char *password); + + int init(const char *line); + + inline void set_password(const char *password) + { + make_scrambled_password(scrambled_password, password); + } + + char user[USERNAME_LENGTH + 1]; + char scrambled_password[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; + uint8 user_length; + uint8 salt[SCRAMBLE_LENGTH]; +}; + /* User_map -- all users and passwords */ @@ -33,15 +54,51 @@ class User_map { public: + /* User_map iterator */ + + class Iterator + { + public: + Iterator(User_map *user_map_arg) : + user_map(user_map_arg), cur_idx(0) + { } + + public: + void reset(); + + User *next(); + + private: + User_map *user_map; + uint cur_idx; + }; + +public: + User_map(); ~User_map(); int init(); - int load(const char *password_file_name); - int authenticate(const char *user_name, uint length, + int load(const char *password_file_name, const char **err_msg); + int save(const char *password_file_name, const char **err_msg); + int authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const; + + const User *find_user(const LEX_STRING *user_name) const; + User *find_user(const LEX_STRING *user_name); + + bool add_user(User *user); + bool remove_user(User *user); + +private: + User_map(const User_map &); + User_map &operator =(const User_map &); + private: HASH hash; + bool initialized; + + friend class Iterator; }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H |