diff options
Diffstat (limited to 'server-tools')
53 files changed, 7850 insertions, 3104 deletions
diff --git a/server-tools/CMakeLists.txt b/server-tools/CMakeLists.txt index 3f02ba88f1d..3f02ba88f1d 100755..100644 --- a/server-tools/CMakeLists.txt +++ b/server-tools/CMakeLists.txt diff --git a/server-tools/Makefile.am b/server-tools/Makefile.am index 77612af843e..96e9d5a946e 100644 --- a/server-tools/Makefile.am +++ b/server-tools/Makefile.am @@ -13,8 +13,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -SUBDIRS= instance-manager -DIST_SUBDIRS= instance-manager +SUBDIRS = . instance-manager +DIST_SUBDIRS = . instance-manager # Don't update the files from bitkeeper %::SCCS/s.% diff --git a/server-tools/instance-manager/CMakeLists.txt b/server-tools/instance-manager/CMakeLists.txt index 1452cdaf20b..2b9bce56ff7 100755 --- a/server-tools/instance-manager/CMakeLists.txt +++ b/server-tools/instance-manager/CMakeLists.txt @@ -25,6 +25,7 @@ ADD_EXECUTABLE(mysqlmanager buffer.cc command.cc commands.cc guardian.cc instanc 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 + 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 c2844114873..feccaadbecc 100644 --- a/server-tools/instance-manager/IMService.cpp +++ b/server-tools/instance-manager/IMService.cpp @@ -13,19 +13,21 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <windows.h> +#include <winsock2.h> #include <signal.h> -#include "log.h" -#include "options.h" + #include "IMService.h" + +#include "log.h" #include "manager.h" +#include "options.h" + +static const char * const IM_SVC_USERNAME= NULL; +static const char * const IM_SVC_PASSWORD= NULL; IMService::IMService(void) + :WindowsService("MySqlManager", "MySQL Manager") { - serviceName= "MySqlManager"; - displayName= "MySQL Manager"; - username= NULL; - password= NULL; } IMService::~IMService(void) @@ -35,25 +37,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) @@ -61,37 +62,63 @@ void IMService::Log(const char *msg) log_info(msg); } -int HandleServiceOptions(Options options) +int IMService::main() { - 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"); - else if (winService.Install()) - log_info("Service installed successfully"); + { + log_info("Service is already installed."); + return 1; + } + + if (winService.Install(IM_SVC_USERNAME, IM_SVC_PASSWORD)) + { + log_info("Service installed successfully."); + return 0; + } else { - log_info("Service failed to install"); - ret_val= 1; + log_error("Service failed to install."); + return 1; } } - else if (options.remove_service) + + if (Options::Service::remove_service) { - if (! winService.IsInstalled()) - log_info("Service is not installed"); - else if (winService.Remove()) - log_info("Service removed successfully"); + if (!winService.IsInstalled()) + { + log_info("Service is not installed."); + return 1; + } + + if (winService.Remove()) + { + log_info("Service removed successfully."); + return 0; + } else { - log_info("Service failed to remove"); - ret_val= 1; + log_error("Service failed to remove."); + return 1; } } - else - ret_val= !winService.Init(); - return ret_val; + + 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."); + + return 1; + } + + return 0; } diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h index 5954a8a46af..aceafb2fca6 100644 --- a/server-tools/instance-manager/IMService.h +++ b/server-tools/instance-manager/IMService.h @@ -14,11 +14,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once -#include "windowsservice.h" +#include "WindowsService.h" -class IMService : public WindowsService +class IMService: public WindowsService { public: + static int main(); + +private: IMService(void); ~IMService(void); diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am index 1c47033d4cb..19c4ac8de19 100644 --- a/server-tools/instance-manager/Makefile.am +++ b/server-tools/instance-manager/Makefile.am @@ -17,8 +17,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) @@ -33,7 +32,7 @@ liboptions_la_CXXFLAGS= $(CXXFLAGS) \ -DDEFAULT_SOCKET_FILE_NAME="/tmp/mysqlmanager.sock" \ -DDEFAULT_PASSWORD_FILE_NAME="/etc/mysqlmanager.passwd" \ -DDEFAULT_MYSQLD_PATH="$(libexecdir)/mysqld$(EXEEXT)" \ - -DDEFAULT_CONFIG_FILE="/etc/my.cnf" \ + -DDEFAULT_CONFIG_FILE="my.cnf" \ -DPROTOCOL_VERSION=@PROTOCOL_VERSION@ liboptions_la_SOURCES= options.h options.cc priv.h priv.cc @@ -61,6 +60,8 @@ client_settings.h: libexec_PROGRAMS= mysqlmanager +mysqlmanager_CXXFLAGS= + mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ manager.h manager.cc log.h log.cc \ thread_registry.h thread_registry.cc \ @@ -76,9 +77,15 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ guardian.cc guardian.h \ parse_output.cc parse_output.h \ mysql_manager_error.h \ - portability.h - -mysqlmanager_LDADD= liboptions.la \ + portability.h \ + exit_codes.h \ + user_management_commands.h \ + user_management_commands.cc \ + angel.h \ + angel.cc + +mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ + liboptions.la \ libnet.a \ $(top_builddir)/vio/libvio.a \ $(top_builddir)/mysys/libmysys.a \ @@ -86,6 +93,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 07ef2e6ea39..14795e2225a 100644 --- a/server-tools/instance-manager/WindowsService.cpp +++ b/server-tools/instance-manager/WindowsService.cpp @@ -13,20 +13,30 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "my_global.h" #include <windows.h> -#include <assert.h> -#include ".\windowsservice.h" +#include "WindowsService.h" static WindowsService *gService; -WindowsService::WindowsService(void) : +WindowsService::WindowsService(const char *p_serviceName, + const char *p_displayName) : statusCheckpoint(0), - serviceName(NULL), - inited(false), + serviceName(p_serviceName), + displayName(p_displayName), + inited(FALSE), dwAcceptedControls(SERVICE_ACCEPT_STOP), - debugging(false) + debugging(FALSE) { + DBUG_ASSERT(serviceName != NULL); + + /* TODO: shouldn't we check displayName too (can it really be NULL)? */ + + /* WindowsService is assumed to be singleton. Let's assure this. */ + DBUG_ASSERT(gService == NULL); + gService= this; + status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; status.dwServiceSpecificExitCode= 0; } @@ -35,13 +45,14 @@ WindowsService::~WindowsService(void) { } -BOOL WindowsService::Install() +BOOL WindowsService::Install(const char *username, const char *password) { - 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]; @@ -49,7 +60,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, @@ -60,7 +71,7 @@ BOOL WindowsService::Install() if (newService) { CloseServiceHandle(newService); - ret_val= true; + ret_val= TRUE; } CloseServiceHandle(scm); @@ -69,36 +80,37 @@ BOOL WindowsService::Install() BOOL WindowsService::Init() { - assert(serviceName != NULL); + DBUG_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); } @@ -131,7 +143,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; @@ -204,7 +217,7 @@ void WindowsService::HandleControlCode(DWORD opcode) void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) { - assert(gService != NULL); + DBUG_ASSERT(gService != NULL); // register our service control handler: gService->RegisterAndRun(argc, argv); @@ -212,7 +225,7 @@ void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) void WINAPI WindowsService::ControlHandler(DWORD opcode) { - assert(gService != NULL); + DBUG_ASSERT(gService != NULL); return gService->HandleControlCode(opcode); } diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h index 033e02ecb7f..02a499e5f0c 100644 --- a/server-tools/instance-manager/WindowsService.h +++ b/server-tools/instance-manager/WindowsService.h @@ -21,8 +21,6 @@ protected: bool inited; const char *serviceName; const char *displayName; - const char *username; - const char *password; SERVICE_STATUS_HANDLE statusHandle; DWORD statusCheckpoint; SERVICE_STATUS status; @@ -30,10 +28,10 @@ protected: bool debugging; public: - WindowsService(void); + WindowsService(const char *p_serviceName, const char *p_displayName); ~WindowsService(void); - BOOL Install(); + BOOL Install(const char *username, const char *password); BOOL Remove(); BOOL Init(); BOOL IsInstalled(); diff --git a/server-tools/instance-manager/angel.cc b/server-tools/instance-manager/angel.cc new file mode 100644 index 00000000000..64515c8498c --- /dev/null +++ b/server-tools/instance-manager/angel.cc @@ -0,0 +1,407 @@ +/* Copyright (C) 2003-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; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef __WIN__ + +#include "angel.h" + +#include <sys/wait.h> +/* + sys/wait.h is needed for waitpid(). Unfortunately, there is no MySQL + include file, that can serve for this. Include it before MySQL system + headers so that we can change system defines if needed. +*/ + +#include "my_global.h" +#include "my_alarm.h" +#include "my_dir.h" +#include "my_sys.h" + +/* Include other IM files. */ + +#include "log.h" +#include "manager.h" +#include "options.h" +#include "priv.h" + +/************************************************************************/ + +enum { CHILD_OK= 0, CHILD_NEED_RESPAWN, CHILD_EXIT_ANGEL }; + +static int log_fd; + +static volatile sig_atomic_t child_status= CHILD_OK; +static volatile sig_atomic_t child_exit_code= 0; +static volatile sig_atomic_t shutdown_request_signo= 0; + + +/************************************************************************/ +/** + Open log file. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool open_log_file() +{ + log_info("Angel: opening log file '%s'...", + (const char *) Options::Daemon::log_file_name); + + log_fd= open(Options::Daemon::log_file_name, + O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + if (log_fd < 0) + { + log_error("Can not open log file '%s': %s.", + (const char *) Options::Daemon::log_file_name, + (const char *) strerror(errno)); + + return TRUE; + } + + return FALSE; +} + + +/************************************************************************/ +/** + Detach the process from controlling tty. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool detach_process() +{ + /* + Become a session leader (the goal is not to have a controlling tty). + + setsid() must succeed because child is guaranteed not to be a process + group leader (it belongs to the process group of the parent). + + NOTE: if we now don't have a controlling tty we will not receive + tty-related signals - no need to ignore them. + */ + + if (setsid() < 0) + { + log_error("setsid() failed: %s.", (const char *) strerror(errno)); + return -1; + } + + /* Close STDIN. */ + + log_info("Angel: preparing standard streams."); + + if (close(STDIN_FILENO) < 0) + { + log_error("Warning: can not close stdin (%s)." + "Trying to continue...", + (const char *) strerror(errno)); + } + + /* Dup STDOUT and STDERR to the log file. */ + + if (dup2(log_fd, STDOUT_FILENO) < 0 || + dup2(log_fd, STDERR_FILENO) < 0) + { + log_error("Can not redirect stdout and stderr to the log file: %s.", + (const char *) strerror(errno)); + + return TRUE; + } + + if (log_fd != STDOUT_FILENO && log_fd != STDERR_FILENO) + { + if (close(log_fd) < 0) + { + log_error("Can not close original log file handler (%d): %s. " + "Trying to continue...", + (int) log_fd, + (const char *) strerror(errno)); + } + } + + return FALSE; +} + + +/************************************************************************/ +/** + Create PID file. + + @return + TRUE on error; + FALSE on success. +*************************************************************************/ + +static bool create_pid_file() +{ + if (create_pid_file(Options::Daemon::angel_pid_file_name, getpid())) + { + log_error("Angel: can not create pid file (%s).", + (const char *) Options::Daemon::angel_pid_file_name); + + return TRUE; + } + + log_info("Angel: pid file (%s) created.", + (const char *) Options::Daemon::angel_pid_file_name); + + return FALSE; +} + + +/************************************************************************/ +/** + SIGCHLD handler. + + Reap child, analyze child exit code, and set child_status + appropriately. +*************************************************************************/ + +extern "C" void reap_child(int); + +void reap_child(int __attribute__((unused)) signo) +{ + /* NOTE: As we have only one child, no need to cycle waitpid(). */ + + int exit_code; + + if (waitpid(0, &exit_code, WNOHANG) > 0) + { + child_exit_code= exit_code; + child_status= exit_code ? CHILD_NEED_RESPAWN : CHILD_EXIT_ANGEL; + } +} + + +/************************************************************************/ +/** + SIGTERM, SIGHUP, SIGINT handler. + + Set termination status and return. +*************************************************************************/ + +extern "C" void terminate(int signo); +void terminate(int signo) +{ + shutdown_request_signo= signo; +} + + +/************************************************************************/ +/** + Angel main loop. + + @return + The function returns exit status for global main(): + 0 -- program completed successfully; + !0 -- error occurred. +*************************************************************************/ + +static int angel_main_loop() +{ + /* + Install signal handlers. + + NOTE: Although signal handlers are needed only for parent process + (IM-angel), we should install them before fork() in order to avoid race + condition (i.e. to be sure, that IM-angel will receive SIGCHLD in any + case). + */ + + sigset_t wait_for_signals_mask; + + struct sigaction sa_chld; + struct sigaction sa_term; + struct sigaction sa_chld_orig; + struct sigaction sa_term_orig; + struct sigaction sa_int_orig; + struct sigaction sa_hup_orig; + + log_info("Angel: setting necessary signal actions..."); + + sigemptyset(&wait_for_signals_mask); + + sigemptyset(&sa_chld.sa_mask); + sa_chld.sa_handler= reap_child; + sa_chld.sa_flags= SA_NOCLDSTOP; + + sigemptyset(&sa_term.sa_mask); + sa_term.sa_handler= terminate; + sa_term.sa_flags= 0; + + /* NOTE: sigaction() fails only if arguments are wrong. */ + + sigaction(SIGCHLD, &sa_chld, &sa_chld_orig); + sigaction(SIGTERM, &sa_term, &sa_term_orig); + sigaction(SIGINT, &sa_term, &sa_int_orig); + sigaction(SIGHUP, &sa_term, &sa_hup_orig); + + /* The main Angel loop. */ + + while (true) + { + /* Spawn a new Manager. */ + + log_info("Angel: forking Manager process..."); + + switch (fork()) { + case -1: + log_error("Angel: can not fork IM-main: %s.", + (const char *) strerror(errno)); + + return -1; + + case 0: + /* + We are in child process, which will be IM-main: + - Restore default signal actions to let the IM-main work with + signals as he wishes; + - Call Manager::main(); + */ + + log_info("Angel: Manager process created successfully."); + + /* NOTE: sigaction() fails only if arguments are wrong. */ + + sigaction(SIGCHLD, &sa_chld_orig, NULL); + sigaction(SIGTERM, &sa_term_orig, NULL); + sigaction(SIGINT, &sa_int_orig, NULL); + sigaction(SIGHUP, &sa_hup_orig, NULL); + + log_info("Angel: executing Manager..."); + + return Manager::main(); + } + + /* Wait for signals. */ + + log_info("Angel: waiting for signals..."); + + while (child_status == CHILD_OK && shutdown_request_signo == 0) + sigsuspend(&wait_for_signals_mask); + + /* Exit if one of shutdown signals has been caught. */ + + if (shutdown_request_signo) + { + log_info("Angel: received shutdown signal (%d). Exiting...", + (int) shutdown_request_signo); + + return 0; + } + + /* Manager process died. Respawn it if it was a failure. */ + + if (child_status == CHILD_NEED_RESPAWN) + { + child_status= CHILD_OK; + + log_error("Angel: Manager exited abnormally (exit code: %d).", + (int) child_exit_code); + + log_info("Angel: sleeping 1 second..."); + + sleep(1); /* don't respawn too fast */ + + log_info("Angel: respawning Manager..."); + + continue; + } + + /* Delete IM-angel PID file. */ + + my_delete(Options::Daemon::angel_pid_file_name, MYF(0)); + + /* IM-angel finished. */ + + log_info("Angel: Manager exited normally. Exiting..."); + + return 0; + } +} + + +/************************************************************************/ +/** + Angel main function. + + @return + The function returns exit status for global main(): + 0 -- program completed successfully; + !0 -- error occurred. +*************************************************************************/ + +int Angel::main() +{ + log_info("Angel: started."); + + /* Open log file. */ + + if (open_log_file()) + return -1; + + /* Fork a new process. */ + + log_info("Angel: daemonizing..."); + + switch (fork()) { + case -1: + /* + This is the main Instance Manager process, fork() failed. + Log an error and bail out with error code. + */ + + log_error("fork() failed: %s.", (const char *) strerror(errno)); + return -1; + + case 0: + /* We are in child process. Continue Angel::main() execution. */ + + break; + + default: + /* + We are in the parent process. Return 0 so that parent exits + successfully. + */ + + log_info("Angel: exiting from the original process..."); + + return 0; + } + + /* Detach child from controlling tty. */ + + if (detach_process()) + return -1; + + /* Create PID file. */ + + if (create_pid_file()) + return -1; + + /* Start Angel main loop. */ + + return angel_main_loop(); +} + +#endif // __WIN__ diff --git a/server-tools/instance-manager/angel.h b/server-tools/instance-manager/angel.h new file mode 100644 index 00000000000..db21c250972 --- /dev/null +++ b/server-tools/instance-manager/angel.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2003-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; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef INCLUDES_MYSQL_ANGEL_H +#define INCLUDES_MYSQL_ANGEL_H + +#ifndef __WIN__ + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +#include <my_global.h> + +class Angel +{ +public: + static int main(); +}; + +#endif // INCLUDES_MYSQL_ANGEL_H +#endif // __WIN__ diff --git a/server-tools/instance-manager/buffer.cc b/server-tools/instance-manager/buffer.cc index 57dd1c72f23..f197f42d009 100644 --- a/server-tools/instance-manager/buffer.cc +++ b/server-tools/instance-manager/buffer.cc @@ -26,7 +26,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 @@ -44,12 +44,12 @@ const uint Buffer::MAX_BUFFER_SIZE= 16777216; 1 - got an error in reserve() */ -int Buffer::append(uint position, const char *string, uint len_arg) +int Buffer::append(size_t position, const char *string, size_t len_arg) { if (reserve(position, len_arg)) return 1; - strnmov(buffer + position, string, len_arg); + strnmov((char*) buffer + position, string, len_arg); return 0; } @@ -58,7 +58,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. @@ -75,20 +75,20 @@ int Buffer::append(uint position, const char *string, uint len_arg) 1 - realloc error or we have come to the 16Mb barrier */ -int Buffer::reserve(uint position, uint len_arg) +int Buffer::reserve(size_t position, size_t len_arg) { if (position + len_arg >= MAX_BUFFER_SIZE) goto err; if (position + len_arg >= buffer_size) { - buffer= (char*) my_realloc(buffer, + buffer= (uchar*) my_realloc(buffer, min(MAX_BUFFER_SIZE, max((uint) (buffer_size*1.5), position + len_arg)), MYF(0)); if (!(buffer)) goto err; - buffer_size= (uint) (buffer_size*1.5); + buffer_size= (size_t) (buffer_size*1.5); } return 0; diff --git a/server-tools/instance-manager/buffer.h b/server-tools/instance-manager/buffer.h index a551dd98a25..3bd7a714437 100644 --- a/server-tools/instance-manager/buffer.h +++ b/server-tools/instance-manager/buffer.h @@ -45,7 +45,7 @@ public: /* As append() will invokes realloc() anyway, it's ok if malloc returns 0 */ - if (!(buffer= (char*) my_malloc(buffer_size, MYF(0)))) + if (!(buffer= (uchar*) my_malloc(buffer_size, MYF(0)))) buffer_size= 0; } @@ -55,11 +55,11 @@ public: } public: - char *buffer; + uchar *buffer; int get_size(); int is_error(); - int append(uint position, const char *string, uint len_arg); - int reserve(uint position, uint len_arg); + int append(size_t position, const char *string, size_t len_arg); + int reserve(size_t position, size_t len_arg); }; #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_BUFFER_H */ diff --git a/server-tools/instance-manager/command.cc b/server-tools/instance-manager/command.cc index e68698207d1..ba84285ead2 100644 --- a/server-tools/instance-manager/command.cc +++ b/server-tools/instance-manager/command.cc @@ -17,11 +17,12 @@ #pragma implementation #endif +#include "manager.h" #include "command.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 10f46fae585..25d8c9849e8 100644 --- a/server-tools/instance-manager/command.h +++ b/server-tools/instance-manager/command.h @@ -21,10 +21,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 @@ -33,13 +36,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 bb3763ce8c5..56bd720b3e9 100644 --- a/server-tools/instance-manager/commands.cc +++ b/server-tools/instance-manager/commands.cc @@ -13,781 +13,1740 @@ 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 "manager.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> +/************************************************************************** + {{{ Static functions. +**************************************************************************/ +/** + modify_defaults_to_im_error -- a map of error codes of + mysys::modify_defaults_file() into Instance Manager error codes. +*/ -/* - Add a string to a buffer +static const int modify_defaults_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, + ER_ACCESS_OPTION_FILE }; - SYNOPSYS - put_to_buff() - buff buffer to add the string - str string to add - uint offset in the buff to add a string + +/** + Parse version number from the version string. + + SYNOPSIS + parse_version_number() + version_str + version + version_size DESCRIPTION + TODO - 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. - This is required for MySQL client/server protocol support only. + TODO: Move this function to Instance_options and parse version number + only once. - RETURN - 0 - ok - 1 - error occured + NOTE: This function is used only in SHOW INSTANCE STATUS statement at the + moment. */ - -static inline int put_to_buff(Buffer *buff, const char *str, uint *position) +static int parse_version_number(const char *version_str, char *version, + uint version_size) { - size_t len= strlen(str); - if (buff->append(*position, str, (uint) len)) - return 1; + const char *start= version_str; + const char *end; + + // skip garbage + while (!my_isdigit(default_charset_info, *start)) + start++; + + end= start; + // skip digits and dots + while (my_isdigit(default_charset_info, *end) || *end == '.') + end++; + + if ((uint)(end - start) >= version_size) + return -1; + + strncpy(version, start, end-start); + version[end-start]= '\0'; - *position+= (uint) len; return 0; } +/************************************************************************** + }}} +**************************************************************************/ + +/************************************************************************** + Implementation of Instance_name. +**************************************************************************/ + +Instance_name::Instance_name(const LEX_STRING *name) +{ + str.str= str_buffer; + str.length= name->length; -/* implementation for Show_instances: */ + if (str.length > MAX_INSTANCE_NAME_SIZE - 1) + str.length= MAX_INSTANCE_NAME_SIZE - 1; + strmake(str.str, name->str, str.length); +} -/* - The method sends a list of instances in the instance map to the client. +/************************************************************************** + Implementation of Show_instances. +**************************************************************************/ - SYNOPSYS - Show_instances::execute() - net The network connection to the client. - connection_id Client connection ID +/** + Implementation of SHOW INSTANCES statement. - RETURN - 0 - ok - 1 - error occured + 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->lock(); + + while ((instance= iterator.next())) { - Instance *instance; - Instance_map::Iterator iterator(instance_map); + Buffer send_buf; /* buffer for packets */ + size_t pos= 0; + + instance->lock(); + + const char *instance_name= instance->options.instance_name.str; + const char *state_name= instance->get_state_name(); - 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; } - instance_map->unlock(); + + instance->unlock(); + + if (err_status) + break; } - if (send_eof(net)) - goto err; - if (net_flush(net)) - goto err; - return 0; -err: - return ER_OUT_OF_RESOURCES; + instance_map->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)) - return ER_OUT_OF_RESOURCES; + int err_status= Manager::flush_instances(); - return 0; + if (err_status) + return err_status; + + return net_send_ok(net, connection_id, NULL) ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Show_instance_status: */ +/************************************************************************** + Implementation of Instance_cmd. +**************************************************************************/ -Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Instance_cmd::Instance_cmd(const LEX_STRING *instance_name_arg): + instance_name(instance_name_arg) { + /* + MT-NOTE: we can not make a search for Instance object here, + because it can dissappear after releasing the lock. + */ +} + + +/************************************************************************** + Implementation of Abstract_instance_cmd. +**************************************************************************/ + +Abstract_instance_cmd::Abstract_instance_cmd( + const LEX_STRING *instance_name_arg) + :Instance_cmd(instance_name_arg) +{ +} + + +int Abstract_instance_cmd::execute(st_net *net, ulong connection_id) +{ + int err_code; 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; + instance_map->lock(); + + instance= instance_map->find(get_instance_name()); + + if (!instance) + { + instance_map->unlock(); + return ER_BAD_INSTANCE_NAME; + } + + instance->lock(); + instance_map->unlock(); + + err_code= execute_impl(net, instance); + + instance->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; + + return 0; +} -int Show_instance_status::execute(struct st_net *net, - ulong connection_id) + +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]; + size_t pos= 0; + + const char *state_name= instance->get_state_name(); + const char *version_tag= "unknown"; + const char *version_num= "unknown"; + const char *mysqld_compatible_status= + instance->is_mysqld_compatible() ? "yes" : "no"; + if (instance->options.mysqld_version) { - Instance *instance; - - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (!(instance= instance_map->find(instance_name, (uint) 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, 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. -Show_instance_options::Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len): - Command(instance_map_arg) + 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_options::execute_impl(st_net *net, Instance *instance) { - Instance *instance; + int 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_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 */ + size_t 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, (uint) 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) +{ +} + - /* loop through the options stored in DYNAMIC_ARRAY */ - for (uint i= 0; i < instance->options.options_array.elements; i++) +/** + Implementation of START INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_INSTANCE_MISCONFIGURED The instance configuration is invalid + ER_INSTANCE_ALREADY_STARTED The instance is already started + ER_CANNOT_START_INSTANCE The instance could not have been started + + TODO: as soon as this method operates only with Instance, we probably + should introduce a new method (execute_stop_instance()) in Instance and + just call it from here. +*/ + +int Start_instance::execute_impl(st_net * /* net */, Instance *instance) +{ + if (!instance->is_configured()) + return ER_INSTANCE_MISCONFIGURED; + + if (instance->is_active()) + return ER_INSTANCE_ALREADY_STARTED; + + if (instance->start_mysqld()) + return ER_CANNOT_START_INSTANCE; + + instance->reset_stat(); + instance->set_state(Instance::NOT_STARTED); + + 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 + + TODO: as soon as this method operates only with Instance, we probably + should introduce a new method (execute_stop_instance()) in Instance and + just call it from here. +*/ + +int Stop_instance::execute_impl(st_net * /* net */, Instance *instance) +{ + if (!instance->is_active()) + return ER_INSTANCE_IS_NOT_STARTED; + + instance->set_state(Instance::STOPPED); + + return instance->stop_mysqld() ? ER_STOP_INSTANCE : 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_cmd(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) +{ + size_t len; + + /* Check if we have something (and trim leading spaces). */ + + 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. */ + + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ - position= 0; - if (option_value != NULL) + 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 +*/ + +int Create_instance::execute(st_net *net, ulong connection_id) +{ + int err_code; + Instance *instance; + + /* 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; + } + + instance= instance_map->find(get_instance_name()); + DBUG_ASSERT(instance); -err: - return ER_OUT_OF_RESOURCES; + if ((err_code= create_instance_in_file(get_instance_name(), &options))) + { + instance_map->remove_instance(instance); /* instance is deleted here. */ + + instance_map->unlock(); + return err_code; + } + + /* + CREATE INSTANCE must not lead to start instance, even if it guarded. + + TODO: The problem however is that if Instance Manager restarts after + creating instance, the instance will be restarted (see also BUG#19718). + */ + + instance->set_state(Instance::STOPPED); + + /* 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) + :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(st_net *net, ulong connection_id) { - uint err_code; - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ - else + int err_code; + Instance *instance; + + /* Lock Guardian, then Instance_map. */ + + instance_map->lock(); + + /* Find an instance. */ + + instance= instance_map->find(get_instance_name()); + + if (!instance) + { + instance_map->unlock(); + return ER_BAD_INSTANCE_NAME; + } + + instance->lock(); + + /* Check that the instance is offline. */ + + if (instance->is_active()) + { + instance->unlock(); + instance_map->unlock(); + + return ER_DROP_ACTIVE_INSTANCE; + } + + /* Try to remove instance from the file. */ + + 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); + + if (err_code) { - if ((err_code= instance->start())) - return 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 (!(instance->options.nonguarded)) - instance_map->guardian->guard(instance); + instance->unlock(); + instance_map->unlock(); - net_send_ok(net, connection_id, "Instance started"); - return 0; + return modify_defaults_to_im_error[err_code]; } + + /* Unlock the instance before destroy. */ + + instance->unlock(); + + /* + Remove instance from the instance map + (the instance will be also destroyed here). + */ + + instance_map->remove_instance(instance); + + /* Unlock the instance map. */ + + instance_map->unlock(); + + /* That's all: send ok. */ + + 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; +} + + +/** + 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; - if (offset_arg != NULL) - offset= atoi(offset_arg); - else - offset= 0; - size= atoi(size_arg); - log_type= log_type_arg; + 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_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; -int Show_instance_log::execute(struct st_net *net, ulong connection_id) + return 0; +} + + +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 fileds to be passed to send_fields */ - name_field.name= (char*) "Log"; + /* Create list of the fields to be passed to send_fields(). */ + + 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, - (uint) strlen(instance_name))) == NULL) - goto err; +int Show_instance_log::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + size_t 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; + size_t 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, (uint) 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, read_buff.buffer, buff_size, MYF(0))) == + MY_FILE_ERROR) + { + close(fd); + return ER_READ_FILE; + } - if ((read_len= my_read(fd, (byte*) read_buff.buffer, - (uint) 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, (char*) 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; +} + + +/** + Implementation of SHOW INSTANCE LOG FILES 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_log_files::execute_impl(st_net *net, Instance *instance) +{ + int 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; } -/* - The method sends a table with a list of log files - used by the instance. +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; - SYNOPSYS - Show_instance_log_files::execute() - net The network connection to the client. - connection_id The ID of the client connection + return 0; +} - RETURN - ER_BAD_INSTANCE_NAME The instance name specified is not valid - ER_OUT_OF_RESOURCES some error occured - 0 - ok -*/ -int Show_instance_log_files::execute(struct st_net *net, ulong connection_id) +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, (uint) 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]; + + size_t 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. +**************************************************************************/ + +/** + Instance_options_list -- a data class representing a list of options for + some instance. +*/ -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) +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 uchar* get_item_key(const uchar* item, size_t* len, + my_bool __attribute__((unused)) t) +{ + const Instance_options_list *lst= (const Instance_options_list *) item; + *len= lst->get_instance_name()->length; + return (uchar *) 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. +/** + Lock Instance Map and call execute_impl(). - RETURN - 0 - ok - 1 - error occured + 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) - { - int val; + Instance_options_list *lst= + (Instance_options_list *) hash_search(&instance_options_map, + (uchar *) instance_name->str, + instance_name->length); - val= do_command(net); + if (!lst) + { + lst= new Instance_options_list(instance_name); - if (val == 0) - net_send_ok(net, connection_id, NULL); + if (!lst) + return NULL; - return val; + if (lst->init() || my_hash_insert(&instance_options_map, (uchar *) lst)) + { + delete lst; + return NULL; + } } - return ER_BAD_INSTANCE_NAME; + return lst; } -/* the only function from Unset_option we need to Implement */ +/** + Skeleton implementation of option-management command. -int Unset_option::do_command(struct st_net *net) + MT-NOTE: Instance Map is locked before calling this operation. +*/ +int Abstract_option_cmd::execute_impl(st_net *net, ulong connection_id) { - return correct_file(TRUE); + 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); + + bool instance_is_active; + + lst->instance= instance_map->find(lst->get_instance_name()); + + if (!lst->instance) + return ER_BAD_INSTANCE_NAME; + + lst->instance->lock(); + instance_is_active= lst->instance->is_active(); + lst->instance->unlock(); + + if (instance_is_active) + return ER_INSTANCE_IS_ACTIVE; + } + + /* 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); + + lst->instance->lock(); + + 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; + } + + lst->instance->unlock(); + + if (err_code) + break; + } + + if (err_code == 0) + net_send_ok(net, connection_id, NULL); + + return err_code; } -/* Implementation for Stop_instance: */ +/************************************************************************** + Implementation of Set_option. +**************************************************************************/ + +/** + 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) +{ + size_t 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. */ + } -Stop_instance::Stop_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) + { + 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. */ + } +} + + +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; + size_t 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. */ - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + 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 (!(instance->options.nonguarded)) - instance_map->guardian->stop_guard(instance); + 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 ((err_code= instance->stop())) + 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 2ab31c18b32..5c2b2f9bbb7 100644 --- a/server-tools/instance-manager/commands.h +++ b/server-tools/instance-manager/commands.h @@ -15,201 +15,379 @@ 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() + { } + +public: + int execute(st_net *net, ulong connection_id); +}; + + +/** + Base class for Instance-specific commands + (commands that operate on one instance). + + Instance_cmd extends Command class by: + - an attribute for storing instance name; + - code to initialize instance name in constructor; + - an accessor to get instance name. +*/ + +class Instance_cmd : public Command +{ +public: + Instance_cmd(const LEX_STRING *instance_name_arg); + +protected: + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + Instance_name instance_name; +}; + + +/** + Abstract class for Instance-specific commands. + + Abstract_instance_cmd extends Instance_cmd by providing a common + framework for writing command-implementations. Basically, the class + implements Command::execute() pure virtual function in the following + way: + - Lock Instance_map; + - Get an instance by name. Return an error, if there is no such + instance; + - Lock the instance; + - Unlock Instance_map; + - Call execute_impl(), which should be implemented in derived class; + - Unlock the instance; + - Send response to the client and return error status. +*/ + +class Abstract_instance_cmd: public Instance_cmd { public: - Flush_instances(Instance_map *instance_map_arg): Command(instance_map_arg) - {} + Abstract_instance_cmd(const LEX_STRING *instance_name_arg); + +public: + virtual int execute(st_net *net, ulong connection_id); + +protected: + /** + This operation is intended to contain command-specific implementation. + + MT-NOTE: this operation is called under acquired Instance'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. - int execute(struct st_net *net, ulong connection_id); + MT-NOTE: this operation is called under released Instance's lock. + */ + virtual int send_ok_response(st_net *net, ulong connection_id) = 0; }; -/* +/** 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); - 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; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +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); }; -/* +/** Start an instance. 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); }; -/* +/** Stop an instance. Grammar: STOP INSTANCE <instance_name> */ -class Stop_instance : public Command +class Stop_instance: public Abstract_instance_cmd { public: - Stop_instance(Instance_map *instance_map_arg, const char *name, uint len); + Stop_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 +/** + Create an instance. + Grammar: CREATE INSTANCE <instance_name> [<options>] +*/ + +class Create_instance: public Instance_cmd +{ +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); + +private: + bool parse_args(const char **text); + +private: + 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 Instance_cmd +{ +public: + Drop_instance(const LEX_STRING *instance_name_arg); + +protected: + virtual int execute(st_net *net, ulong connection_id); +}; + + +/** + 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); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); - 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); +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; }; -/* +/** Shows the list of the log files, used by an instance. 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: - Syntax_error() {} - 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..1609debd8f9 --- /dev/null +++ b/server-tools/instance-manager/exit_codes.h @@ -0,0 +1,40 @@ +#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; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + 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 ad5d766ea14..b49b0ec0a00 100644 --- a/server-tools/instance-manager/guardian.cc +++ b/server-tools/instance-manager/guardian.cc @@ -19,171 +19,228 @@ #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 "options.h" -#include <string.h> -#include <sys/types.h> -#include <signal.h> +/************************************************************************* + {{{ Constructor & destructor. +*************************************************************************/ +/** + Guardian constructor. -pthread_handler_t guardian(void *arg) -{ - Guardian_thread *guardian_thread= (Guardian_thread *) arg; - guardian_thread->run(); - return 0; -} + SYNOPSIS + Guardian() + thread_registry_arg + instance_map_arg + + DESCRIPTION + Nominal contructor intended for assigning references and initialize + trivial objects. Real initialization is made by init() method. +*/ -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) +Guardian::Guardian(Thread_registry *thread_registry_arg, + Instance_map *instance_map_arg) + :shutdown_requested(FALSE), + stopped(FALSE), + thread_registry(thread_registry_arg), + instance_map(instance_map_arg) { 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); - free_root(&alloc, MYF(0)); - pthread_mutex_unlock(&LOCK_guardian); + /* + NOTE: it's necessary to synchronize here, because Guiardian thread can be + still alive an hold the mutex (because it is detached and we have no + control over it). + */ + + lock(); + unlock(); + pthread_mutex_destroy(&LOCK_guardian); pthread_cond_destroy(&COND_guardian); } +/************************************************************************* + }}} +*************************************************************************/ + -void Guardian_thread::request_shutdown() +/** + Send request to stop Guardian. + + SYNOPSIS + request_shutdown() +*/ + +void Guardian::request_shutdown() { - pthread_mutex_lock(&LOCK_guardian); - /* stop instances or just clean up Guardian repository */ stop_instances(); + + lock(); shutdown_requested= TRUE; - pthread_mutex_unlock(&LOCK_guardian); + unlock(); + + ping(); } -void Guardian_thread::process_instance(Instance *instance, - GUARD_NODE *current_node, - LIST **guarded_instances_arg, - LIST *node) +/** + Process an instance. + + SYNOPSIS + process_instance() + instance a pointer to the instance for processing + + MT-NOTE: + - the given instance must be locked before calling this operation; + - Guardian must be locked before calling this operation. +*/ + +void Guardian::process_instance(Instance *instance) { - uint waitchild= (uint) Instance::DEFAULT_SHUTDOWN_DELAY; - /* The amount of times, Guardian attempts to restart an instance */ int restart_retry= 100; time_t current_time= time(NULL); - if (current_node->state == STOPPING) + if (instance->get_state() == Instance::STOPPING) { - /* this brach is executed during shutdown */ - if (instance->options.shutdown_delay_val) - waitchild= instance->options.shutdown_delay_val; + /* This brach is executed during shutdown. */ - /* 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_arg= list_delete(*guarded_instances_arg, node); - else if ( (uint) (current_time - current_node->last_checked) > waitchild) { - instance->kill_instance(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. - */ - *guarded_instances_arg= list_delete(*guarded_instances_arg, node); + log_info("Guardian: '%s' stopped.", + (const char *) instance->get_name()->str); + + instance->set_state(Instance::STOPPED); + } + else if ((uint) (current_time - instance->last_checked) >= + instance->options.get_shutdown_delay()) + { + log_info("Guardian: '%s' hasn't stopped within %d secs.", + (const char *) instance->get_name()->str, + (int) instance->options.get_shutdown_delay()); + + instance->kill_mysqld(SIGKILL); + + log_info("Guardian: pretend that '%s' is killed.", + (const char *) instance->get_name()->str); + + instance->set_state(Instance::STOPPED); + } + else + { + log_info("Guardian: waiting for '%s' to stop (%d secs left).", + (const char *) instance->get_name()->str, + (int) (instance->options.get_shutdown_delay() - + current_time + instance->last_checked)); } 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) + if (instance->get_state() == Instance::STARTING && + instance->options.load_pid() == 0) { /* Pid file not created yet, don't go to STARTED state yet */ } - else if (current_node->state != STARTED) + else if (instance->get_state() != Instance::STARTED) { /* clear status fields */ - log_info("guardian: instance '%s' is running, set state to STARTED.", - (const char *) instance->options.instance_name); - current_node->restart_counter= 0; - current_node->crash_moment= 0; - current_node->state= STARTED; + log_info("Guardian: '%s' is running, set state to STARTED.", + (const char *) instance->options.instance_name.str); + instance->reset_stat(); + instance->set_state(Instance::STARTED); } } else { - switch (current_node->state) { - case NOT_STARTED: - log_info("guardian: starting instance '%s'...", - (const char *) instance->options.instance_name); - - /* NOTE, set state to STARTING _before_ start() is called */ - current_node->state= STARTING; - instance->start(); - current_node->last_checked= current_time; - break; - case STARTED: /* fallthrough */ - case STARTING: /* let the instance start or crash */ - if (instance->is_crashed()) + switch (instance->get_state()) { + case Instance::NOT_STARTED: + log_info("Guardian: starting '%s'...", + (const char *) instance->options.instance_name.str); + + /* NOTE: set state to STARTING _before_ start() is called. */ + instance->set_state(Instance::STARTING); + instance->last_checked= current_time; + + instance->start_mysqld(); + + return; + + case Instance::STARTED: /* fallthrough */ + case Instance::STARTING: /* let the instance start or crash */ + if (!instance->is_crashed()) + return; + + instance->crash_moment= current_time; + instance->last_checked= current_time; + instance->set_state(Instance::JUST_CRASHED); + /* fallthrough -- restart an instance immediately */ + + case Instance::JUST_CRASHED: + if (current_time - instance->crash_moment <= 2) { - current_node->crash_moment= current_time; - current_node->last_checked= current_time; - current_node->state= JUST_CRASHED; - /* fallthrough -- restart an instance immediately */ + if (instance->is_crashed()) + { + instance->start_mysqld(); + log_info("Guardian: starting '%s'...", + (const char *) instance->options.instance_name.str); + } } else - break; - case JUST_CRASHED: - if (current_time - current_node->crash_moment <= 2) + instance->set_state(Instance::CRASHED); + + return; + + case Instance::CRASHED: /* just regular restarts */ + if ((ulong) (current_time - instance->last_checked) <= + (ulong) Options::Main::monitoring_interval) + return; + + if (instance->restart_counter < restart_retry) { if (instance->is_crashed()) { - instance->start(); - log_info("guardian: starting instance '%s'...", - (const char *) instance->options.instance_name); + instance->start_mysqld(); + instance->last_checked= current_time; + + log_info("Guardian: restarting '%s'...", + (const char *) instance->options.instance_name.str); } } else - current_node->state= CRASHED; - break; - case CRASHED: /* just regular restarts */ - if (current_time - current_node->last_checked > - monitoring_interval) { - if ((current_node->restart_counter < restart_retry)) - { - if (instance->is_crashed()) - { - instance->start(); - current_node->last_checked= current_time; - current_node->restart_counter++; - log_info("guardian: restarting instance '%s'...", - (const char *) instance->options.instance_name); - } - } - else - current_node->state= CRASHED_AND_ABANDONED; + log_info("Guardian: can not start '%s'. " + "Abandoning attempts to (re)start it", + (const char *) instance->options.instance_name.str); + + instance->set_state(Instance::CRASHED_AND_ABANDONED); } - break; - case CRASHED_AND_ABANDONED: - break; /* do nothing */ + + return; + + case Instance::CRASHED_AND_ABANDONED: + return; /* do nothing */ + default: DBUG_ASSERT(0); } @@ -191,192 +248,147 @@ 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. + Check for all guarded instances and restart them if needed. */ -void Guardian_thread::run() +void Guardian::run() { - Instance *instance; - LIST *node; struct timespec timeout; - thread_registry.register_thread(&thread_info); + log_info("Guardian: started."); - my_thread_init(); - pthread_mutex_lock(&LOCK_guardian); + thread_registry->register_thread(&thread_info); + + /* Loop, until all instances were shut down at the end. */ - /* loop, until all instances were shut down at the end */ - while (!(shutdown_requested && (guarded_instances == NULL))) + while (true) { - node= guarded_instances; + Instance_map::Iterator instances_it(instance_map); + Instance *instance; + bool all_instances_stopped= TRUE; + + instance_map->lock(); - while (node != NULL) + while ((instance= instances_it.next())) { - GUARD_NODE *current_node= (GUARD_NODE *) node->data; - instance= ((GUARD_NODE *) node->data)->instance; - process_instance(instance, current_node, &guarded_instances, node); + instance->lock(); - node= node->next; - } - set_timespec(timeout, monitoring_interval); - - /* check the loop predicate before sleeping */ - if (!(shutdown_requested && (!(guarded_instances)))) - thread_registry.cond_timedwait(&thread_info, &COND_guardian, - &LOCK_guardian, &timeout); - } + if (!instance->is_guarded() || + instance->get_state() == Instance::STOPPED) + { + instance->unlock(); + continue; + } - 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(); -} + process_instance(instance); + if (instance->get_state() != Instance::STOPPED) + all_instances_stopped= FALSE; -int Guardian_thread::is_stopped() -{ - int var; - pthread_mutex_lock(&LOCK_guardian); - var= stopped; - pthread_mutex_unlock(&LOCK_guardian); - return var; -} + instance->unlock(); + } + instance_map->unlock(); -/* - Initialize the list of guarded instances: loop through the Instance_map and - add all of the instances, which don't have 'nonguarded' option specified. + lock(); - SYNOPSYS - Guardian_thread::init() + if (shutdown_requested && all_instances_stopped) + { + log_info("Guardian: all guarded mysqlds stopped."); - NOTE: One should always lock guardian before calling this routine. + stopped= TRUE; + unlock(); + break; + } - RETURN - 0 - ok - 1 - error occured -*/ + set_timespec(timeout, Options::Main::monitoring_interval); -int Guardian_thread::init() -{ - Instance *instance; - Instance_map::Iterator iterator(instance_map); + thread_registry->cond_timedwait(&thread_info, &COND_guardian, + &LOCK_guardian, &timeout); + unlock(); + } - /* clear the list of guarded instances */ - free_root(&alloc, MYF(0)); - init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); - guarded_instances= NULL; + log_info("Guardian: stopped."); - while ((instance= iterator.next())) - { - if (!(instance->options.nonguarded)) - if (guard(instance, TRUE)) /* do not lock guardian */ - return 1; - } + /* Now, when the Guardian is stopped we can stop the IM. */ + + thread_registry->unregister_thread(&thread_info); + thread_registry->request_shutdown(); - return 0; + log_info("Guardian: finished."); } -/* - Add instance to the Guardian list +/** + Return the value of stopped flag. +*/ - SYNOPSYS - guard() - instance the instance to be guarded - nolock whether we prefer do not lock Guardian here, - but use external locking instead +bool Guardian::is_stopped() +{ + int var; - DESCRIPTION + lock(); + var= stopped; + unlock(); + + return var; +} - The instance is added to the guarded instances list. Usually guard() is - called after we start an instance. - RETURN - 0 - ok - 1 - error occured +/** + Wake up Guardian thread. + + MT-NOTE: though usually the mutex associated with condition variable should + be acquired before signalling the variable, here this is not needed. + Signalling under locked mutex is used to avoid lost signals. In the current + logic however locking mutex does not guarantee that the signal will not be + lost. */ -int Guardian_thread::guard(Instance *instance, bool nolock) +void Guardian::ping() { - LIST *node; - GUARD_NODE *content; - - node= (LIST *) alloc_root(&alloc, sizeof(LIST)); - content= (GUARD_NODE *) alloc_root(&alloc, sizeof(GUARD_NODE)); - - if ((!(node)) || (!(content))) - return 1; - /* we store the pointers to instances from the instance_map's MEM_ROOT */ - content->instance= instance; - content->restart_counter= 0; - content->crash_moment= 0; - content->state= NOT_STARTED; - node->data= (void*) content; - - if (nolock) - guarded_instances= list_add(guarded_instances, node); - else - { - pthread_mutex_lock(&LOCK_guardian); - guarded_instances= list_add(guarded_instances, node); - pthread_mutex_unlock(&LOCK_guardian); - } - - return 0; + pthread_cond_signal(&COND_guardian); } -/* - TODO: perhaps it would make sense to create a pool of the LIST nodeents - and give them upon request. Now we are loosing a bit of memory when - guarded instance was stopped and then restarted (since we cannot free just - a piece of the MEM_ROOT). +/** + Prepare list of instances. + + SYNOPSIS + init() + + MT-NOTE: Instance Map must be locked before calling the operation. */ -int Guardian_thread::stop_guard(Instance *instance) +void Guardian::init() { - LIST *node; - - pthread_mutex_lock(&LOCK_guardian); - node= guarded_instances; + Instance *instance; + Instance_map::Iterator iterator(instance_map); - while (node != NULL) + while ((instance= iterator.next())) { - /* - 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; + instance->lock(); + + instance->reset_stat(); + instance->set_state(Instance::NOT_STARTED); + + instance->unlock(); } - pthread_mutex_unlock(&LOCK_guardian); - /* if there is nothing to delete it is also fine */ - return 0; } -/* + +/** An internal method which is called at shutdown to unregister instances and attempt to stop them if requested. - SYNOPSYS + SYNOPSIS stop_instances() DESCRIPTION @@ -385,48 +397,100 @@ int Guardian_thread::stop_guard(Instance *instance) accordingly. NOTE - Guardian object should be locked by the calling function. + Guardian object should be locked by the caller. - RETURN - 0 - ok - 1 - error occured */ -int Guardian_thread::stop_instances() +void Guardian::stop_instances() { - LIST *node; - node= guarded_instances; - while (node != NULL) + static const int NUM_STOP_ATTEMPTS = 100; + + Instance_map::Iterator instances_it(instance_map); + Instance *instance; + + instance_map->lock(); + + while ((instance= instances_it.next())) { - GUARD_NODE *current_node= (GUARD_NODE *) node->data; + instance->lock(); + + if (!instance->is_guarded() || + instance->get_state() == Instance::STOPPED) + { + instance->unlock(); + continue; + } + /* If instance is running or was running (and now probably hanging), request stop. */ - if (current_node->instance->is_running() || - (current_node->state == STARTED)) + + if (instance->is_mysqld_running() || + instance->get_state() == Instance::STARTED) { - current_node->state= STOPPING; - current_node->last_checked= time(NULL); + instance->set_state(Instance::STOPPING); + instance->last_checked= time(NULL); } else - /* 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); - node= node->next; + { + /* Otherwise mark it as STOPPED. */ + instance->set_state(Instance::STOPPED); + } + + /* Request mysqld to stop. */ + + bool instance_stopped= FALSE; + + for (int cur_attempt= 0; cur_attempt < NUM_STOP_ATTEMPTS; ++cur_attempt) + { + if (!instance->kill_mysqld(SIGTERM)) + { + instance_stopped= TRUE; + break; + } + + if (!instance->is_active()) + { + instance_stopped= TRUE; + break; + } + + /* Sleep for 0.3 sec and check again. */ + + my_sleep(300000); + } + + /* + Abort if we failed to stop mysqld instance. That should not happen, + but if it happened, we don't know what to do and prefer to have clear + failure with coredump. + */ + + DBUG_ASSERT(instance_stopped); + + instance->unlock(); } - return 0; + + instance_map->unlock(); } -void Guardian_thread::lock() +/** + Lock Guardian. +*/ + +void Guardian::lock() { pthread_mutex_lock(&LOCK_guardian); } -void Guardian_thread::unlock() +/** + Unlock Guardian. +*/ + +void Guardian::unlock() { pthread_mutex_unlock(&LOCK_guardian); } diff --git a/server-tools/instance-manager/guardian.h b/server-tools/instance-manager/guardian.h index 63150de79f4..d78058a6fc5 100644 --- a/server-tools/instance-manager/guardian.h +++ b/server-tools/instance-manager/guardian.h @@ -15,12 +15,13 @@ 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 <my_global.h> #include <my_sys.h> #include <my_list.h> +#include "thread_registry.h" + #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif @@ -28,95 +29,82 @@ class Instance; 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 */ - enum enum_instance_state { NOT_STARTED= 1, STARTING, STARTED, JUST_CRASHED, - CRASHED, CRASHED_AND_ABANDONED, STOPPING }; + Guardian(Thread_registry *thread_registry_arg, + Instance_map *instance_map_arg); + ~Guardian(); - /* - The Guardian list node structure. Guardian utilizes it to store - guarded instances plus some additional info. - */ + void init(); - struct GUARD_NODE - { - Instance *instance; - /* state of an instance (i.e. STARTED, CRASHED, etc.) */ - enum_instance_state state; - /* the amount of attemts to restart instance (cleaned up at success) */ - int restart_counter; - /* triggered at a crash */ - time_t crash_moment; - /* General time field. Used to provide timeouts (at shutdown and restart) */ - time_t last_checked; - }; - - - 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(); - /* Initialize or refresh the list of guarded instances */ - int init(); - /* Request guardian shutdown. Stop instances if needed */ +public: void request_shutdown(); - /* Start instance protection */ - int guard(Instance *instance, bool nolock= FALSE); - /* Stop instance protection */ - int stop_guard(Instance *instance); - /* Returns true if guardian thread is stopped */ - int is_stopped(); + + bool is_stopped(); + void lock(); void unlock(); -public: - pthread_cond_t COND_guardian; + void ping(); + +protected: + virtual void run(); private: - /* Prepares Guardian shutdown. Stops instances is needed */ - int stop_instances(); - /* check instance state and act accordingly */ - void process_instance(Instance *instance, GUARD_NODE *current_node, - LIST **guarded_instances, LIST *elem); - int stopped; + void stop_instances(); + + void process_instance(Instance *instance); private: + /* + LOCK_guardian protectes the members in this section: + - shutdown_requested; + - stopped; + + Also, it is used for COND_guardian. + */ pthread_mutex_t LOCK_guardian; - Thread_info thread_info; - 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 */ + + /* + Guardian's main loop waits on this condition. So, it should be signalled + each time, when instance state has been changed and we want Guardian to + wake up. + + TODO: Change this to having data-scoped conditions, i.e. conditions, + which indicate that some data has been changed. + */ + pthread_cond_t COND_guardian; + + /* + This variable is set to TRUE, when Manager thread is shutting down. + The flag is used by Guardian thread to understand that it's time to + finish. + */ bool shutdown_requested; + + /* + This flag is set to TRUE on shutdown by Guardian thread, when all guarded + mysqlds are stopped. + + The flag is used in the Manager thread to wait for Guardian to stop all + mysqlds. + */ + bool stopped; + + Thread_info thread_info; + Thread_registry *thread_registry; + Instance_map *instance_map; + +private: + Guardian(const Guardian &); + Guardian&operator =(const Guardian &); }; #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */ diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc index e08bf2f66df..1ad728d40eb 100644 --- a/server-tools/instance-manager/instance.cc +++ b/server-tools/instance-manager/instance.cc @@ -17,24 +17,28 @@ #pragma implementation #endif -#include "instance.h" +#include <my_global.h> +#include <mysql.h> -#include "mysql_manager_error.h" -#include "log.h" -#include "instance_map.h" -#include "priv.h" -#include "portability.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 "manager.h" +#include "guardian.h" +#include "instance.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; @@ -43,24 +47,9 @@ typedef PROCESS_INFORMATION My_process_info; #endif /* - Proxy thread is a simple way to avoid all pitfalls of the threads - implementation in the OS (e.g. LinuxThreads). 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. -*/ - -pthread_handler_t proxy(void *arg) -{ - Instance *instance= (Instance *) arg; - start_and_monitor_instance(&instance->options, - instance->get_map()); - return 0; -} - -/* Wait for an instance - SYNOPSYS + SYNOPSIS wait_process() pi Pointer to the process information structure (platform-dependent). @@ -89,7 +78,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); @@ -117,11 +107,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. @@ -129,13 +118,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(); @@ -151,19 +140,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; @@ -178,8 +168,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++) { @@ -202,228 +192,425 @@ static int start_process(Instance_options *instance_options, pi); /* Pointer to PROCESS_INFORMATION structure */ delete cmdline; - return (!result); + return !result; } #endif -/* - Fork child, exec an instance and monitor it. +#ifdef __WIN__ - SYNOPSYS - start_and_monitor_instance() - old_instance_options Pointer to the options of the instance to be - launched. This info is likely to become obsolete - when function returns from wait_process() - instance_map Pointer to the instance_map. We use it to protect - the instance from deletion, while we are working - with it. +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; - DESCRIPTION - Fork a child, then exec and monitor it. When the child is dead, - find appropriate instance (for this purpose we save its name), - set appropriate flags and wake all threads waiting for instance - to stop. + BOOL bDup= DuplicateHandle(GetCurrentProcess(), + hProcess, GetCurrentProcess(), &hProcessDup, + PROCESS_ALL_ACCESS, FALSE, 0); - RETURN - Function returns no value + // 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") }; + +/************************************************************************* + }}} +*************************************************************************/ + + +/************************************************************************* + {{{ Instance Monitor thread. +*************************************************************************/ + +/** + Proxy thread is a simple way to avoid all pitfalls of the threads + implementation in the OS (e.g. LinuxThreads). 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. + + Instance Monitor Thread forks a child process, execs mysqld and waits for + the child to die. + + Instance Monitor assumes that the monitoring instance will not be dropped. + This is guaranteed by having flag monitoring_thread_active and + Instance::is_active() operation. */ -static void start_and_monitor_instance(Instance_options *old_instance_options, - Instance_map *instance_map) +class Instance_monitor: public Thread { - enum { MAX_INSTANCE_NAME_LEN= 512 }; - char instance_name_buff[MAX_INSTANCE_NAME_LEN]; - uint instance_name_len; - Instance *current_instance; - My_process_info process_info; +public: + Instance_monitor(Instance *instance_arg) :instance(instance_arg) {} +protected: + virtual void run(); + void start_and_monitor_instance(); +private: + Instance *instance; +}; - /* - Lock instance map to guarantee that no instances are deleted during - strmake() and execv() calls. - */ - instance_map->lock(); + +void Instance_monitor::run() +{ + start_and_monitor_instance(); + delete this; +} + + +void Instance_monitor::start_and_monitor_instance() +{ + Thread_registry *thread_registry= Manager::get_thread_registry(); + Guardian *guardian= Manager::get_guardian(); + + My_process_info mysqld_process_info; + Thread_info monitor_thread_info; + + log_info("Instance '%s': Monitor: started.", + (const char *) instance->get_name()->str); /* - Save the instance name in the case if Instance object we - are using is destroyed. (E.g. by "FLUSH INSTANCES") + For guarded instance register the thread in Thread_registry to wait for + the thread to stop on shutdown (nonguarded instances are not stopped on + shutdown, so the thread will no finish). */ - 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); + if (instance->is_guarded()) + { + thread_registry->register_thread(&monitor_thread_info, FALSE); + } + + /* Starting mysqld. */ - if (start_process(old_instance_options, &process_info)) + log_info("Instance '%s': Monitor: starting mysqld...", + (const char *) instance->get_name()->str); + + if (start_process(&instance->options, &mysqld_process_info)) { - instance_map->unlock(); - return; /* error is logged */ + instance->lock(); + instance->monitoring_thread_active= FALSE; + instance->unlock(); + + return; } - /* allow users to delete instances */ - instance_map->unlock(); + /* Waiting for mysqld to die. */ - /* 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); - current_instance= instance_map->find(instance_name_buff, instance_name_len); + wait_process(&mysqld_process_info); /* Don't check for return value. */ - if (current_instance) - current_instance->set_crash_flag_n_wake_all(); + log_info("Instance '%s': Monitor: mysqld stopped.", + (const char *) instance->get_name()->str); - return; -} + /* Update instance status. */ + instance->lock(); -Instance_map *Instance::get_map() -{ - return instance_map; -} + if (instance->is_guarded()) + thread_registry->unregister_thread(&monitor_thread_info); + instance->crashed= TRUE; + instance->monitoring_thread_active= FALSE; -void Instance::remove_pid() -{ - 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); + log_info("Instance '%s': Monitor: finished.", + (const char *) instance->get_name()->str); + + instance->unlock(); + + /* Wake up guardian. */ + + guardian->ping(); } +/************************************************************************** + }}} +**************************************************************************/ -/* - The method starts an instance. - SYNOPSYS - start() +/************************************************************************** + {{{ Static operations. +**************************************************************************/ + +/** + The operation is intended to check whether string is a well-formed + instance name or not. + + SYNOPSIS + is_name_valid() + name string to check RETURN - 0 ok - ER_CANNOT_START_INSTANCE Cannot start instance - ER_INSTANCE_ALREADY_STARTED The instance on the specified port/socket - is already started + TRUE string is a valid instance name + FALSE string is not a valid instance name + + TODO: Move to Instance_name class: Instance_name::is_valid(). */ -int Instance::start() +bool Instance::is_name_valid(const LEX_STRING *name) { - /* clear crash flag */ - pthread_mutex_lock(&LOCK_instance); - crashed= 0; - pthread_mutex_unlock(&LOCK_instance); - - - if (!is_running()) - { - remove_pid(); + const char *name_suffix= name->str + DFLT_INSTANCE_NAME.length; - /* - 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; - } + if (strncmp(name->str, Instance::DFLT_INSTANCE_NAME.str, + Instance::DFLT_INSTANCE_NAME.length) != 0) + return FALSE; - /* the instance is started already */ - return ER_INSTANCE_ALREADY_STARTED; + return *name_suffix == 0 || my_isdigit(default_charset_info, *name_suffix); } -/* - The method sets the crash flag and wakes all waiters on - COND_instance_stopped and COND_guardian - SYNOPSYS - set_crash_flag_n_wake_all() +/** + The operation is intended to check if the given instance name is + mysqld-compatible or not. - 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 + is_mysqld_compatible_name() + name name to check RETURN - Function returns no value + TRUE name is mysqld-compatible + FALSE otherwise + + TODO: Move to Instance_name class: Instance_name::is_mysqld_compatible(). */ -void Instance::set_crash_flag_n_wake_all() +bool Instance::is_mysqld_compatible_name(const LEX_STRING *name) { - /* set instance state to crashed */ - pthread_mutex_lock(&LOCK_instance); - crashed= 1; - pthread_mutex_unlock(&LOCK_instance); + return strcmp(name->str, DFLT_INSTANCE_NAME.str) == 0; +} - /* - 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 client state name. Must not be used outside the class. + Use Instance::get_state_name() instead. +*/ + +const char * Instance::get_instance_state_name(enum_instance_state state) +{ + switch (state) { + case STOPPED: + return "offline"; + + case NOT_STARTED: + return "not started"; + + case STARTING: + return "starting"; + + case STARTED: + return "online"; + + 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. */ } +/************************************************************************** + }}} +**************************************************************************/ -Instance::Instance(): crashed(0) +/************************************************************************** + {{{ Initialization & deinitialization. +**************************************************************************/ + +Instance::Instance() + :monitoring_thread_active(FALSE), + crashed(FALSE), + configured(FALSE), + /* mysqld_compatible is initialized in init() */ + state(NOT_STARTED), + restart_counter(0), + crash_moment(0), + last_checked(0) { pthread_mutex_init(&LOCK_instance, 0); - pthread_cond_init(&COND_instance_stopped, 0); } Instance::~Instance() { - pthread_cond_destroy(&COND_instance_stopped); + log_info("Instance '%s': destroying...", (const char *) get_name()->str); + pthread_mutex_destroy(&LOCK_instance); } -int Instance::is_crashed() +/** + Initialize instance options. + + SYNOPSIS + init() + name_arg name of the instance + + RETURN: + FALSE - ok + TRUE - error +*/ + +bool Instance::init(const LEX_STRING *name_arg) +{ + mysqld_compatible= is_mysqld_compatible_name(name_arg); + + return options.init(name_arg); +} + + +/** + @brief Complete instance options initialization. + + @return Error status. + @retval FALSE ok + @retval TRUE error +*/ + +bool Instance::complete_initialization() { - int val; - pthread_mutex_lock(&LOCK_instance); - val= crashed; - pthread_mutex_unlock(&LOCK_instance); - return val; + configured= ! options.complete_initialization(); + return !configured; +} + +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Instance: public interface implementation. +**************************************************************************/ + +/** + Determine if there is some activity with the instance. + + SYNOPSIS + is_active() + + DESCRIPTION + An instance is active if one of the following conditions is true: + - Instance-monitoring thread is running; + - Instance is guarded and its state is other than STOPPED; + - Corresponding mysqld-server accepts connections. + + MT-NOTE: instance must be locked before calling the operation. + + RETURN + TRUE - instance is active + FALSE - otherwise. +*/ + +bool Instance::is_active() +{ + if (monitoring_thread_active) + return TRUE; + + if (is_guarded() && get_state() != STOPPED) + return TRUE; + + return is_mysqld_running(); } -bool Instance::is_running() +/** + Determine if mysqld is accepting connections. + + 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: instance must be locked before calling the operation. + + RETURN + TRUE - mysqld is alive and accept connections + FALSE - otherwise. +*/ + +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); - mysql_init(&mysql); /* try to connect to a server with a fake username/password pair */ if (mysql_real_connect(&mysql, LOCAL_HOST, username, @@ -435,10 +622,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."); - pthread_mutex_unlock(&LOCK_instance); + log_error("Instance '%s': was able to log into mysqld.", + (const char *) get_name()->str); return_val= TRUE; /* server is alive */ } else @@ -446,163 +631,314 @@ bool Instance::is_running() sizeof(access_denied_message) - 1)); mysql_close(&mysql); - pthread_mutex_unlock(&LOCK_instance); return return_val; } -/* - Stop an instance. +/** + @brief Start mysqld. - SYNOPSYS - stop() + Reset flags and start Instance Monitor thread, which will start mysqld. - RETURN: - 0 ok - ER_INSTANCE_IS_NOT_STARTED Looks like the instance it is not started - ER_STOP_INSTANCE mysql_shutdown reported an error + @note Instance must be locked before calling the operation. + + @return Error status code + @retval FALSE Ok + @retval TRUE Could not start instance */ -int Instance::stop() +bool Instance::start_mysqld() { - struct timespec timeout; - uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; + Instance_monitor *instance_monitor; - if (is_running()) - { - if (options.shutdown_delay_val) - waitchild= options.shutdown_delay_val; + if (!configured) + return TRUE; - kill_instance(SIGTERM); + /* + Prepare instance to start Instance Monitor thread. - /* sleep on condition to wait for SIGCHLD */ - set_timespec(timeout, waitchild); - if (pthread_mutex_lock(&LOCK_instance)) - return ER_STOP_INSTANCE; + NOTE: It's important to set these actions here in order to avoid + race conditions -- these actions must be done under acquired lock on + Instance. + */ + + crashed= FALSE; + monitoring_thread_active= TRUE; + + remove_pid(); - while (options.get_pid() != 0) /* while server isn't stopped */ - { - int status; + /* Create and start the Instance Monitor thread. */ - status= pthread_cond_timedwait(&COND_instance_stopped, - &LOCK_instance, - &timeout); - if (status == ETIMEDOUT || status == ETIME) - break; - } + instance_monitor= new Instance_monitor(this); - pthread_mutex_unlock(&LOCK_instance); + if (instance_monitor == NULL || instance_monitor->start(Thread::DETACHED)) + { + delete instance_monitor; + monitoring_thread_active= FALSE; - kill_instance(SIGKILL); + log_error("Instance '%s': can not create instance monitor thread.", + (const char *) get_name()->str); - return 0; + return TRUE; } - return ER_INSTANCE_IS_NOT_STARTED; + ++restart_counter; + + /* The Instance Monitor thread will delete itself when it's finished. */ + + return FALSE; } -#ifdef __WIN__ -BOOL SafeTerminateProcess(HANDLE hProcess, UINT uExitCode) +/** + Stop mysqld. + + SYNOPSIS + stop_mysqld() + + DESCRIPTION + Try to stop mysqld gracefully. Otherwise kill it with SIGKILL. + + MT-NOTE: instance must be locked before calling the operation. + + RETURN + FALSE - ok + TRUE - could not stop the instance +*/ + +bool Instance::stop_mysqld() { - DWORD dwTID, dwCode, dwErr= 0; - HANDLE hProcessDup= INVALID_HANDLE_VALUE; - HANDLE hRT= NULL; - HINSTANCE hKernel= GetModuleHandle("Kernel32"); - BOOL bSuccess= FALSE; + log_info("Instance '%s': stopping mysqld...", + (const char *) get_name()->str); - BOOL bDup= DuplicateHandle(GetCurrentProcess(), - hProcess, GetCurrentProcess(), &hProcessDup, - PROCESS_ALL_ACCESS, FALSE, 0); + kill_mysqld(SIGTERM); - // Detect the special case where the process is - // already dead... - if (GetExitCodeProcess((bDup) ? hProcessDup : hProcess, &dwCode) && - (dwCode == STILL_ACTIVE)) + if (!wait_for_stop()) { - FARPROC pfnExitProc; + log_info("Instance '%s': mysqld stopped gracefully.", + (const char *) get_name()->str); + return FALSE; + } - pfnExitProc= GetProcAddress(hKernel, "ExitProcess"); + log_info("Instance '%s': mysqld failed to stop gracefully within %d seconds.", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); - hRT= CreateRemoteThread((bDup) ? hProcessDup : hProcess, NULL, 0, - (LPTHREAD_START_ROUTINE)pfnExitProc, - (PVOID)uExitCode, 0, &dwTID); + log_info("Instance'%s': killing mysqld...", + (const char *) get_name()->str); - if (hRT == NULL) - dwErr= GetLastError(); + kill_mysqld(SIGKILL); + + if (!wait_for_stop()) + { + log_info("Instance '%s': mysqld has been killed.", + (const char *) get_name()->str); + return FALSE; } - else - dwErr= ERROR_PROCESS_ABORTED; - if (hRT) + log_info("Instance '%s': can not kill mysqld within %d seconds.", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); + + return TRUE; +} + + +/** + Send signal to mysqld. + + SYNOPSIS + kill_mysqld() + + DESCRIPTION + Load pid from the pid file and send the given signal to that process. + If the signal is SIGKILL, remove the pid file after sending the signal. + + MT-NOTE: instance must be locked before calling the operation. + + TODO + This too low-level and OS-specific operation for public interface. + Also, it has some implicit behaviour for SIGKILL signal. Probably, we + should have the following public operations instead: + - start_mysqld() -- as is; + - stop_mysqld -- request mysqld to shutdown gracefully (send SIGTERM); + don't wait for complete shutdown; + - wait_for_stop() (or join_mysqld()) -- wait for mysqld to stop within + time interval; + - kill_mysqld() -- request to terminate mysqld; don't wait for + completion. + These operations should also be used in Guardian to manage instances. +*/ + +bool Instance::kill_mysqld(int signum) +{ + pid_t mysqld_pid= options.load_pid(); + + if (mysqld_pid == 0) { - // Must wait process to terminate to - // guarantee that it has exited... - WaitForSingleObject((bDup) ? hProcessDup : hProcess, INFINITE); + log_info("Instance '%s': no pid file to send a signal (%d).", + (const char *) get_name()->str, + (int) signum); + return TRUE; + } - CloseHandle(hRT); - bSuccess= TRUE; + log_info("Instance '%s': sending %d to %d...", + (const char *) get_name()->str, + (int) signum, + (int) mysqld_pid); + + if (kill(mysqld_pid, signum)) + { + log_info("Instance '%s': kill() failed.", + (const char *) get_name()->str); + return TRUE; } - if (bDup) - CloseHandle(hProcessDup); + /* Kill suceeded */ + if (signum == SIGKILL) /* really killed instance with SIGKILL */ + { + log_error("Instance '%s': killed.", + (const char *) options.instance_name.str); - if (!bSuccess) - SetLastError(dwErr); + /* After sucessful hard kill the pidfile need to be removed */ + options.unlink_pidfile(); + } - return bSuccess; + return FALSE; } -int kill(pid_t pid, int signum) + +/** + Lock instance. +*/ + +void Instance::lock() { - HANDLE processhandle= ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - if (signum == SIGTERM) - ::SafeTerminateProcess(processhandle, 0); - else - ::TerminateProcess(processhandle, -1); - return 0; + pthread_mutex_lock(&LOCK_instance); +} + + +/** + Unlock instance. +*/ + +void Instance::unlock() +{ + pthread_mutex_unlock(&LOCK_instance); } -#endif -void Instance::kill_instance(int signum) + +/** + Return instance state name. + + SYNOPSIS + get_state_name() + + DESCRIPTION + The operation returns user-friendly state name. The operation can be + used both for guarded and non-guarded instances. + + MT-NOTE: instance must be locked before calling the operation. + + TODO: Replace with the static get_state_name(state_code) function. +*/ + +const char *Instance::get_state_name() { - pid_t pid; - /* if there are no pid, everything seems to be fine */ - if ((pid= options.get_pid()) != 0) /* get pid from pidfile */ + if (!is_configured()) + return "misconfigured"; + + if (is_guarded()) { - 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(); - } - } + /* The instance is managed by Guardian: we can report precise state. */ + + return get_instance_state_name(get_state()); } - return; + + /* The instance is not managed by Guardian: we can report status only. */ + + return is_active() ? "online" : "offline"; } -/* - We execute this function to initialize instance parameters. - Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. + +/** + Reset statistics. + + SYNOPSIS + reset_stat() + + DESCRIPTION + The operation resets statistics used for guarding the instance. + + MT-NOTE: instance must be locked before calling the operation. + + TODO: Make private. */ -int Instance::init(const char *name_arg) +void Instance::reset_stat() { - return options.init(name_arg); + restart_counter= 0; + crash_moment= 0; + last_checked= 0; } +/************************************************************************** + }}} +**************************************************************************/ + + +/************************************************************************** + {{{ Instance: implementation of private operations. +**************************************************************************/ -int Instance::complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, - uint instance_type) +/** + Remove pid file. +*/ + +void Instance::remove_pid() { - instance_map= instance_map_arg; - return options.complete_initialization(mysqld_path, instance_type); + int mysqld_pid= options.load_pid(); + + if (mysqld_pid == 0) + return; + + if (options.unlink_pidfile()) + { + log_error("Instance '%s': can not unlink pid file.", + (const char *) options.instance_name.str); + } } + + +/** + Wait for mysqld to stop within shutdown interval. +*/ + +bool Instance::wait_for_stop() +{ + int start_time= (int) time(NULL); + int finish_time= start_time + options.get_shutdown_delay(); + + log_info("Instance '%s': waiting for mysqld to stop " + "(timeout: %d seconds)...", + (const char *) get_name()->str, + (int) options.get_shutdown_delay()); + + while (true) + { + if (options.load_pid() == 0 && !is_mysqld_running()) + return FALSE; + + if (time(NULL) >= finish_time) + return TRUE; + + /* Sleep for 0.3 sec and check again. */ + + my_sleep(300000); + } +} + +/************************************************************************** + }}} +**************************************************************************/ diff --git a/server-tools/instance-manager/instance.h b/server-tools/instance-manager/instance.h index 21511be546f..aa9c923cba1 100644 --- a/server-tools/instance-manager/instance.h +++ b/server-tools/instance-manager/instance.h @@ -16,53 +16,258 @@ 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: - Instance(); + /* States of an instance. */ + enum enum_instance_state + { + STOPPED, + NOT_STARTED, + STARTING, + STARTED, + JUST_CRASHED, + CRASHED, + CRASHED_AND_ABANDONED, + STOPPING + }; + +public: + /** + The constant defines name of the default mysqld-instance ("mysqld"). + */ + static const LEX_STRING DFLT_INSTANCE_NAME; + +public: + static bool is_name_valid(const LEX_STRING *name); + 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 is_running(); - int start(); - int stop(); - /* send a signal to the instance */ - void kill_instance(int signo); - int is_crashed(); - void set_crash_flag_n_wake_all(); - Instance_map *get_map(); + + bool init(const LEX_STRING *name_arg); + bool complete_initialization(); + +public: + bool is_active(); + + bool is_mysqld_running(); + + bool start_mysqld(); + bool stop_mysqld(); + bool kill_mysqld(int signo); + + void lock(); + void unlock(); + + const char *get_state_name(); + + void reset_stat(); + +public: + /** + 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; + + /** + The operation returns TRUE if the instance is guarded and FALSE otherwise. + */ + inline bool is_guarded() const; + + /** + The operation returns name of the instance. + */ + inline const LEX_STRING *get_name() const; + + /** + The operation returns the current state of the instance. + + NOTE: At the moment should be used only for guarded instances. + */ + inline enum_instance_state get_state() const; + + /** + The operation changes the state of the instance. + + NOTE: At the moment should be used only for guarded instances. + TODO: Make private. + */ + inline void set_state(enum_instance_state new_state); + + /** + The operation returns crashed flag. + */ + inline bool is_crashed(); public: - enum { DEFAULT_SHUTDOWN_DELAY= 35 }; + /** + This attributes contains instance options. + + TODO: Make private. + */ Instance_options options; private: - int crashed; + /** + monitoring_thread_active is TRUE if there is a thread that monitors the + corresponding mysqld-process. + */ + bool monitoring_thread_active; + + /** + crashed is TRUE when corresponding mysqld-process has been died after + start. + */ + bool crashed; + + /** + configured is TRUE when the instance is configured and FALSE otherwise. + Misconfigured instances are not managed. + */ + bool configured; + /* - Mutex protecting the instance. Currently we use it to avoid the - double start of the instance. This happens when the instance is starting - and we issue the start command once more. + mysqld_compatible 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. */ - pthread_mutex_t LOCK_instance; + bool mysqld_compatible; + /* - This condition variable is used to wake threads waiting for instance to - stop in Instance::stop() + Mutex protecting the instance. */ - pthread_cond_t COND_instance_stopped; - Instance_map *instance_map; + pthread_mutex_t LOCK_instance; + +private: + /* Guarded-instance attributes. */ + + /* state of an instance (i.e. STARTED, CRASHED, etc.) */ + enum_instance_state state; + +public: + /* the amount of attemts to restart instance (cleaned up at success) */ + int restart_counter; + + /* triggered at a crash */ + time_t crash_moment; + + /* General time field. Used to provide timeouts (at shutdown and restart) */ + time_t last_checked; + +private: + static const char *get_instance_state_name(enum_instance_state state); + +private: + void remove_pid(); - void remove_pid(); + bool wait_for_stop(); + +private: + friend class Instance_monitor; }; + +inline bool Instance::is_mysqld_compatible() const +{ + return mysqld_compatible; +} + + +inline bool Instance::is_configured() const +{ + return configured; +} + + +inline bool Instance::is_guarded() const +{ + return !options.nonguarded; +} + + +inline const LEX_STRING *Instance::get_name() const +{ + return &options.instance_name; +} + + +inline Instance::enum_instance_state Instance::get_state() const +{ + return state; +} + + +inline void Instance::set_state(enum_instance_state new_state) +{ + state= new_state; +} + + +inline bool Instance::is_crashed() +{ + return crashed; +} + #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 571826edd7b..d7328d51cfe 100644 --- a/server-tools/instance-manager/instance_map.cc +++ b/server-tools/instance-manager/instance_map.cc @@ -19,45 +19,46 @@ #include "instance_map.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql_com.h> + #include "buffer.h" #include "instance.h" #include "log.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> - -/* - Note: As we are going to suppost different types of connections, - we shouldn't have connection-specific functions. To avoid it we could - put such functions to the Command-derived class instead. - The command could be easily constructed for a specific connection if - we would provide a special factory for each connection. -*/ +#include "priv.h" C_MODE_START -/* Procedure needed for HASH initialization */ +/** + HASH-routines: get key of instance for storing in hash. +*/ -static byte* get_instance_key(const byte* u, uint* len, - my_bool __attribute__((unused)) t) +static uchar* get_instance_key(const uchar* u, size_t* 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 (uchar *) instance->options.instance_name.str; } +/** + HASH-routines: cleanup handler. +*/ + static void delete_instance(void *u) { Instance *instance= (Instance *) u; delete instance; } -/* - The option handler to pass to the process_default_option_files finction. +/** + The option handler to pass to the process_default_option_files function. - 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. @@ -78,16 +79,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 @@ -101,180 +146,354 @@ C_MODE_END process_option(). The caller ensures proper locking of the instance map object. */ + /* + Process a given option and assign it to appropricate instance. This is + required for the option handler, passed to my_search_option_files(). + */ -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, (uchar *) 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, - (uint) 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 constructor. +*/ + +Instance_map::Instance_map() { pthread_mutex_init(&LOCK_instance_map, 0); } -int Instance_map::init() +/** + Initialize Instance_map internals. +*/ + +bool Instance_map::init() { return hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_instance_key, delete_instance, 0); } + +/** + Reset Instance_map data. +*/ + +bool Instance_map::reset() +{ + hash_free(&hash); + return init(); +} + + +/** + Instance_map destructor. +*/ + Instance_map::~Instance_map() { - pthread_mutex_lock(&LOCK_instance_map); + lock(); + + /* + NOTE: it's necessary to synchronize on each instance before removal, + because Instance-monitoring thread can be still alive an hold the mutex + (because it is detached and we have no control over it). + */ + + while (true) + { + Iterator it(this); + Instance *instance= it.next(); + + if (!instance) + break; + + instance->lock(); + instance->unlock(); + + remove_instance(instance); + } + hash_free(&hash); - pthread_mutex_unlock(&LOCK_instance_map); + unlock(); + pthread_mutex_destroy(&LOCK_instance_map); } +/** + Lock Instance_map. +*/ + void Instance_map::lock() { pthread_mutex_lock(&LOCK_instance_map); } +/** + Unlock Instance_map. +*/ + void Instance_map::unlock() { pthread_mutex_unlock(&LOCK_instance_map); } -/* - Re-read instance configuration file. - SYNOPSIS - Instance_map::flush_instances() +/** + Check if there is an active instance or not. +*/ - DESCRIPTION - This function will: - - clear the current list of instances. This removes both - running and stopped instances. - - load a new instance configuration from the file. - - 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 - at the given port), this function has some peculiar - side-effects: - * if the port number of a running instance was changed, the - old instance is forgotten, even if it was running. The new - instance will be started at the new port. - * if the configuration was changed in a way that two - instances swapped their port numbers, the guardian thread - will not notice that and simply report that both instances - are configured successfully and running. - 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. +bool Instance_map::is_there_active_instance() +{ + Instance *instance; + Iterator iterator(this); + + while ((instance= iterator.next())) + { + bool active_instance_found; + + instance->lock(); + active_instance_found= instance->is_active(); + instance->unlock(); + + if (active_instance_found) + return TRUE; + } + + return FALSE; +} + + +/** + Add an instance into the internal hash. + + MT-NOTE: Instance Map must be locked before calling the operation. */ -int Instance_map::flush_instances() +int Instance_map::add_instance(Instance *instance) { - int rc; + return my_hash_insert(&hash, (uchar *) instance); +} - /* - Guardian thread relies on the instance map repository for guarding - instances. This is why refreshing instance map, we need (1) to stop - 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; + +/** + Remove instance from the internal hash. + + MT-NOTE: Instance Map must be locked before calling the operation. +*/ + +int Instance_map::remove_instance(Instance *instance) +{ + return hash_delete(&hash, (uchar *) instance); } -Instance * -Instance_map::find(const char *name, uint name_len) +/** + Create a new instance and register it in the internal hash. + + MT-NOTE: Instance Map must be locked before calling the operation. +*/ + +int Instance_map::create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options) { - 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; + Instance *instance= new Instance(); + + if (!instance) + { + log_error("Can not allocate instance (name: '%s').", + (const char *) instance_name->str); + return ER_OUT_OF_RESOURCES; + } + + 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())) + { + 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; } -int Instance_map::complete_initialization() +/** + Return a pointer to the instance or NULL, if there is no such instance. + + MT-NOTE: Instance Map must be locked before calling the operation. +*/ + +Instance * Instance_map::find(const LEX_STRING *name) { - Instance *instance; - uint i= 0; + return (Instance *) hash_search(&hash, (uchar *) name->str, name->length); +} + + +/** + Init instances command line arguments after all options have been loaded. +*/ + +bool Instance_map::complete_initialization() +{ + bool mysqld_found; + /* Complete initialization of all registered instances. */ - if (hash.records == 0) /* no instances found */ + for (uint i= 0; i < hash.records; ++i) { - if ((instance= new Instance) == 0) - goto err; + 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. */ - if (instance->init("mysqld") || my_hash_insert(&hash, (byte *) instance)) - goto err_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: /* - After an instance have been added to the instance_map, - hash_free should handle it's deletion => goto err, not - err_instance. + Continue if the instance has been added to the config file + successfully, or the config file just does not exist. */ - if (instance->complete_initialization(this, mysqld_path, - DEFAULT_SINGLE_INSTANCE)) - goto err; + 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; } - else - while (i < hash.records) - { - instance= (Instance *) hash_element(&hash, i); - if (instance->complete_initialization(this, mysqld_path, USUAL_INSTANCE)) - goto err; - i++; - } - return 0; -err_instance: - delete instance; -err: - return 1; + return FALSE; } -/* load options from config files and create appropriate instance structures */ +/** + Load options from config files and create appropriate instance + structures. +*/ int Instance_map::load() { @@ -298,10 +517,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'; @@ -315,20 +534,18 @@ 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"); - - if (complete_initialization()) - return 1; + log_info("Falling back to compiled-in defaults."); - return 0; + return complete_initialization(); } -/*--- Implementaton of the Instance map iterator class ---*/ - +/************************************************************************* + {{{ Instance_map::Iterator implementation. +*************************************************************************/ void Instance_map::Iterator::go_to_first() { @@ -344,3 +561,88 @@ Instance *Instance_map::Iterator::next() return NULL; } +/************************************************************************* + }}} +*************************************************************************/ + + +/** + 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, (uchar*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP)) || + my_write(cnf_file, (uchar*)"[", 1, MYF(MY_NABP)) || + my_write(cnf_file, (uchar*)instance_name->str, instance_name->length, + MYF(MY_NABP)) || + my_write(cnf_file, (uchar*)"]", 1, MYF(MY_NABP)) || + my_write(cnf_file, (uchar*)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, (uchar*)option_str, option_str_len, MYF(MY_NABP)) || + my_write(cnf_file, (uchar*)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 18e82f0106b..af2f1868195 100644 --- a/server-tools/instance-manager/instance_map.h +++ b/server-tools/instance-manager/instance_map.h @@ -16,30 +16,37 @@ 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 */ class Instance_map { public: - /* Instance_map iterator */ + /** + Instance_map iterator + */ + class Iterator { private: @@ -53,37 +60,43 @@ public: void go_to_first(); Instance *next(); }; - 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); + Instance *find(const LEX_STRING *name); + + bool is_there_active_instance(); - int flush_instances(); void lock(); void unlock(); - int 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); - Instance_map(const char *default_mysqld_path_arg); - ~Instance_map(); + bool init(); + bool reset(); + + int load(); + + int process_one_option(const LEX_STRING *group, const char *option); + + int add_instance(Instance *instance); + + int remove_instance(Instance *instance); + + int create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options); public: - const char *mysqld_path; - Guardian_thread *guardian; + Instance_map(); + ~Instance_map(); 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; HASH hash; + +private: + friend class Iterator; }; #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_MAP_H */ diff --git a/server-tools/instance-manager/instance_options.cc b/server-tools/instance-manager/instance_options.cc index 9d88bb0e669..8b96d6f0f96 100644 --- a/server-tools/instance-manager/instance_options.cc +++ b/server-tools/instance-manager/instance_options.cc @@ -19,28 +19,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; @@ -49,24 +46,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 @@ -86,17 +146,18 @@ 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") */ - rc= parse_output_and_get_value(cmd.buffer, option_name + 2, + rc= parse_output_and_get_value((char*) cmd.buffer, + option_name + 2, strlen(option_name + 2), result, result_len, GET_VALUE); err: return rc; @@ -106,7 +167,7 @@ err: /* Fill mysqld_version option (used at initialization stage) - SYNOPSYS + SYNOPSIS fill_instance_version() DESCRIPTION @@ -114,46 +175,55 @@ 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((char*) cmd.buffer, STRING_WITH_LEN("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') { - /* chop the newline from the end of the version string */ - result[strlen(result) - NEWLINE_LEN]= '\0'; - mysqld_version= strdup_root(&alloc, result); + char *start; + + /* 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 @@ -165,46 +235,55 @@ 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((char*) cmd.buffer, + STRING_WITH_LEN("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 @@ -214,11 +293,11 @@ 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; enum { MAX_LOG_OPTION_LENGTH= 256 }; @@ -244,20 +323,19 @@ 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) strmov(hostname, "mysql"); hostname[MAX_LOG_OPTION_LENGTH - 1]= 0; /* Safety */ - hostname_length= (uint) strlen(hostname); + hostname_length= strlen(hostname); for (log_files= logs_st; log_files->name; log_files++) @@ -283,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); @@ -303,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 @@ -336,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) @@ -346,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; } @@ -364,7 +435,7 @@ int Instance_options::unlink_pidfile() } -pid_t Instance_options::get_pid() +pid_t Instance_options::load_pid() { FILE *pid_file_stream; @@ -383,36 +454,62 @@ 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, (uint) 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= strdup_root(&alloc, Options::Main::default_mysqld_path); + if (!mysqld_path.str) + return TRUE; } - // 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); + + /* + 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); - mysqld_path_len= (uint) strlen(mysqld_path); + /* 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]; @@ -421,127 +518,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; } @@ -559,7 +687,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]); } @@ -567,32 +698,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= (uint) 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 fdae77985c2..b0503815036 100644 --- a/server-tools/instance-manager/instance_options.h +++ b/server-tools/instance-manager/instance_options.h @@ -17,8 +17,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 @@ -34,30 +35,34 @@ 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); - int add_option(const char* option); - int init(const char *instance_name_arg); - pid_t get_pid(); + 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; + +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 @@ -65,7 +70,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; /* @@ -76,33 +80,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 36f0cbe85e1..4d8a33e7db1 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -18,97 +18,58 @@ #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" -#ifndef __WIN__ static void set_non_blocking(int socket) { +#ifndef __WIN__ int flags= fcntl(socket, F_GETFL, 0); fcntl(socket, F_SETFL, flags | O_NONBLOCK); #else -static void set_non_blocking(SOCKET socket) -{ u_long arg= 1; ioctlsocket(socket, FIONBIO, &arg); #endif } -#ifndef __WIN__ static void set_no_inherit(int socket) { +#ifndef __WIN__ int flags= fcntl(socket, F_GETFD, 0); fcntl(socket, F_SETFD, flags | FD_CLOEXEC); -#else -static void set_no_inherit(SOCKET socket) -{ #endif } +const int Listener::LISTEN_BACK_LOG_SIZE= 5; /* standard backlog size */ -/* - 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; - ulong total_connection_count; - Thread_info thread_info; - -#ifdef __WIN__ - SOCKET sockets[2]; -#else - int sockets[2]; -#endif - 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); -}; - -const int Listener_thread::LISTEN_BACK_LOG_SIZE = 5; /* standard backlog size */ - - -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 @@ -117,27 +78,17 @@ Listener_thread::~Listener_thread() architecture. */ -void Listener_thread::run() +void Listener::run() { - int i= 0; + int i, n= 0; #ifndef __WIN__ - int n= 0; - /* 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); -#else - SOCKET n= 0; #endif - thread_registry.register_thread(&thread_info); + log_info("Listener: started."); - my_thread_init(); + thread_registry->register_thread(&thread_info); FD_ZERO(&read_fds); @@ -156,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; /* @@ -171,17 +122,13 @@ void Listener_thread::run() signal during shutdown. This results in failing assert (Thread_registry::~Thread_registry). Valgrind 2.2 works fine. */ -#ifdef __WIN__ - int rc= select(0, &read_fds_arg, 0, 0, &tv); -#else int rc= select(n, &read_fds_arg, 0, 0, &tv); -#endif 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; } @@ -191,23 +138,18 @@ void Listener_thread::run() /* Assuming that rc > 0 as we asked to wait forever */ if (FD_ISSET(sockets[socket_index], &read_fds_arg)) { -#ifdef __WIN__ - SOCKET client_fd= accept(sockets[socket_index], 0, 0); - /* accept may return INVALID_SOCKET on failure */ - if (client_fd != INVALID_SOCKET) - { -#else int client_fd= accept(sockets[socket_index], 0, 0); /* accept may return -1 (failure or spurious wakeup) */ if (client_fd >= 0) // connection established { set_no_inherit(client_fd); -#endif - 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 { @@ -221,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++) closesocket(sockets[i]); @@ -230,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: @@ -241,27 +184,22 @@ err: for (i= 0; i < num_sockets; i++) closesocket(sockets[i]); - thread_registry.set_error_status(); - thread_registry.unregister_thread(&thread_info); - thread_registry.request_shutdown(); - my_thread_end(); + thread_registry->set_error_status(); + 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; -#ifdef __WIN__ - SOCKET ip_socket= socket(AF_INET, SOCK_STREAM, 0); -#else int ip_socket= socket(AF_INET, SOCK_STREAM, 0); -#endif 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; } @@ -269,14 +207,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; @@ -289,16 +229,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)); closesocket(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)); closesocket(ip_socket); return -1; } @@ -311,26 +251,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 @@ -342,9 +283,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; } @@ -353,8 +294,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; } @@ -365,8 +306,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; @@ -376,51 +317,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 a42f8b48fe5..964fb361fb5 100644 --- a/server-tools/instance-manager/listener.h +++ b/server-tools/instance-manager/listener.h @@ -16,37 +16,46 @@ #ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H #define INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H -#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; + +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 b63696e2ce6..9f276523e49 100644 --- a/server-tools/instance-manager/log.cc +++ b/server-tools/instance-manager/log.cc @@ -13,14 +13,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 @@ -30,11 +32,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() @@ -49,14 +52,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]; @@ -70,7 +76,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) { @@ -104,57 +110,79 @@ 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 and stdout. + + We want to print it on stdout to be able to know in which context we got the + error + + 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(stdout, "ERROR", format, args); + fflush(stdout); + log(stderr, "ERROR", format, args); + fflush(stderr); 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 139dcd552ee..e6c3b55c54c 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 cadb7a4aa0c..792461e41a9 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -13,40 +13,34 @@ 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 "mysql_manager_error.h" +#include "mysqld_error.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; -} + +/********************************************************************** + {{{ Platform-specific implementation. +**********************************************************************/ #ifndef __WIN__ void set_signals(sigset_t *mask) @@ -81,14 +75,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) @@ -102,65 +96,228 @@ int my_sigwait(const sigset_t *set, int *sig) #endif +/********************************************************************** + }}} +**********************************************************************/ + + +/********************************************************************** + {{{ 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__ + + +/** + Request shutdown of guardian and threads registered in Thread_registry. + + SYNOPSIS + stop_all_threads() +*/ -void stop_all(Guardian_thread *guardian, Thread_registry *registry) +void Manager::stop_all_threads() { /* - Let guardian thread know that it should break it's processing cycle, + Let Guardian thread know that it should break it's processing cycle, once it wakes up. */ - guardian->request_shutdown(); - /* wake guardian */ - pthread_cond_signal(&guardian->COND_guardian); - /* stop all threads */ - registry->deliver_shutdown(); + p_guardian->request_shutdown(); + + /* Stop all threads. */ + p_thread_registry->deliver_shutdown(); /* Set error status in the thread registry. */ - registry->set_error_status(); + p_thread_registry->set_error_status(); } -/* - 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. + +/** + Initialize user map and load password file. + + SYNOPSIS + init_user_map() + + RETURN + FALSE on success + TRUE on failure */ -int manager(const Options &options) +bool Manager::init_user_map(User_map *user_map) { - Thread_registry thread_registry; + int err_code; + const char *err_msg; + + if (user_map->init()) + { + log_error("Manager: can not initialize user list: out of memory."); + return TRUE; + } + + err_code= user_map->load(Options::Main::password_file_name, &err_msg); + + if (!err_code) + return FALSE; + + 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."); + + return FALSE; + } + + log_error("Manager: %s.", (const char *) err_msg); + + return TRUE; +} + + +/** + Main manager function. + + SYNOPSIS + main() + + DESCRIPTION + This is an 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. + + RETURNS + main() returns exit status (exit code). +*/ + +int Manager::main() +{ + bool shutdown_complete= FALSE; + pid_t manager_pid= getpid(); + + log_info("Manager: initializing..."); + +#ifndef __WIN__ + if (check_if_linux_threads(&linux_threads)) + { + log_error("Manager: can not determine thread model."); + return 1; + } + + log_info("Manager: detected threads model: %s.", + (const char *) (linux_threads ? "LINUX threads" : "POSIX threads")); +#endif // __WIN__ + /* - All objects created in the manager() function live as long as - thread_registry lives, and thread_registry is alive until there are - working threads. + All objects created in the Manager object live as long as thread_registry + lives, and thread_registry is alive until there are working threads. + + There are two main purposes of the Thread Registry: + 1. Interrupt blocking I/O and signal condition variables in case of + shutdown; + 2. Wait for detached threads before shutting down the main thread. + + NOTE: + 1. Handling shutdown can be done in more elegant manner by introducing + Event (or Condition) object with support of logical operations. + 2. Using Thread Registry to wait for detached threads is definitely not + the best way, because when Thread Registry unregisters an thread, the + thread is still alive. Accurate way to wait for threads to stop is + not using detached threads and join all threads before shutdown. */ + Thread_registry thread_registry; 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); - Listener_thread_args listener_args(thread_registry, options, user_map, - instance_map); + Listener listener(&thread_registry, &user_map); - manager_pid= getpid(); - instance_map.guardian= &guardian_thread; + p_instance_map= &instance_map; + p_guardian= &guardian; + p_thread_registry= &thread_registry; + p_user_map= &user_map; - if (instance_map.init() || user_map.init()) - return 1; + /* Initialize instance map. */ - if (user_map.load(options.password_file_name)) + if (instance_map.init()) + { + log_error("Manager: can not initialize instance list: out of memory."); return 1; + } - /* write Instance Manager pid file */ + /* Initialize user db. */ - log_info("IM pid file: '%s'; PID: %d.", - (const char *) options.pid_file_name, - (int) manager_pid); + if (init_user_map(&user_map)) + return 1; /* logging has been already done. */ - if (create_pid_file(options.pid_file_name, manager_pid)) - return 1; + /* Write Instance Manager pid file. */ + + if (create_pid_file(Options::Main::pid_file_name, manager_pid)) + return 1; /* necessary logging has been already done. */ + + log_info("Manager: pid file (%s) created.", + (const char *) Options::Main::pid_file_name); /* Initialize signals and alarm-infrastructure. @@ -168,104 +325,94 @@ int 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 */ - { - 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; - } + /* + 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)) + { + log_error("Manager: can not start Guardian thread."); + goto err; } /* Load instances. */ - int signo; - bool shutdown_complete; - - shutdown_complete= FALSE; - - if (instance_map.flush_instances()) + if (Manager::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); + log_error("Manager: can not init instances repository."); + stop_all_threads(); goto err; } - /* 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) - { - log_error("manager(): set_stacksize_n_create_thread(listener) failed"); - stop_all(&guardian_thread, &thread_registry); - goto err; - } + /* Initialize the Listener. */ + if (listener.start(Thread::DETACHED)) + { + log_error("Manager: 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); + + guardian.ping(); + + /* 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); + log_error("Manager: sigwait() failed"); + 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 @@ -274,7 +421,7 @@ int 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) @@ -282,10 +429,11 @@ int 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(); } else { @@ -295,15 +443,84 @@ int manager(const Options &options) } } + log_info("Manager: finished."); + 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 thread_registry.get_error_status() ? 1 : 0; } + + +/** + Re-read instance configuration file. + + SYNOPSIS + flush_instances() + + DESCRIPTION + This function will: + - clear the current list of instances. This removes both + running and stopped instances. + - load a new instance configuration from the file. + - 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 + at the given port), this function has some peculiar + side-effects: + * if the port number of a running instance was changed, the + old instance is forgotten, even if it was running. The new + instance will be started at the new port. + * if the configuration was changed in a way that two + instances swapped their port numbers, the guardian thread + will not notice that and simply report that both instances + are configured successfully and running. + + In order to avoid such side effects one should never call + FLUSH INSTANCES without prior stop of all running instances. + + RETURN + 0 On success + ER_OUT_OF_RESOURCES Not enough resources to complete the operation + ER_THERE_IS_ACTIVE_INSTACE If there is an active instance +*/ + +int Manager::flush_instances() +{ + p_instance_map->lock(); + + if (p_instance_map->is_there_active_instance()) + { + p_instance_map->unlock(); + return ER_THERE_IS_ACTIVE_INSTACE; + } + + if (p_instance_map->reset()) + { + p_instance_map->unlock(); + return ER_OUT_OF_RESOURCES; + } + + if (p_instance_map->load()) + { + p_instance_map->unlock(); + + /* Don't init guardian if we failed to load instances. */ + return ER_OUT_OF_RESOURCES; + } + + get_guardian()->init(); + get_guardian()->ping(); + + p_instance_map->unlock(); + + return 0; +} diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h index 22af0d39115..e6956884603 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -16,10 +16,56 @@ #ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H #define INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H -struct Options; +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif -int manager(const Options &options); +#include <my_global.h> -int create_pid_file(const char *pid_file_name, int pid); +class Guardian; +class Instance_map; +class Thread_registry; +class User_map; + +class Manager +{ +public: + static int main(); + + static int flush_instances(); + +public: + /** + These methods return a non-NULL 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; } + +public: +#ifndef __WIN__ + static bool is_linux_threads() { return linux_threads; } +#endif // __WIN__ + +private: + static void stop_all_threads(); + static bool init_user_map(User_map *user_map); + +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 a1a45b05f7c..201ebfd62fc 100644 --- a/server-tools/instance-manager/messages.cc +++ b/server-tools/instance-manager/messages.cc @@ -13,15 +13,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) { @@ -45,7 +44,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."; @@ -69,6 +68,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 1803108c39d..bf08f963aa3 100644 --- a/server-tools/instance-manager/mysql_connection.cc +++ b/server-tools/instance-manager/mysql_connection.cc @@ -19,84 +19,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); } @@ -126,83 +76,87 @@ 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() + (ulong) time(0); + ulong seed2= (ulong) rand() + (ulong) 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 - 4 + // connection id - SCRAMBLE_LENGTH + 2 + // scramble (in 2 pieces) - 18]; // server variables: flags, + uchar buff[MAX_VERSION_LENGTH + 1 + // server version, 0-ended + 4 + // connection id + SCRAMBLE_LENGTH + 2 + // scramble (in 2 pieces) + 18]; // server variables: flags, // charset number, status, - char *pos= buff; + uchar *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; @@ -241,7 +195,8 @@ int Mysql_connection_thread::check_connection() /* write connection message and read reply */ enum { MIN_HANDSHAKE_SIZE= 2 }; - if (net_write_command(&net, protocol_version, "", 0, buff, (uint) (pos - buff)) || + if (net_write_command(&net, protocol_version, (uchar*) "", 0, + buff, pos - buff) || (pkt_len= my_net_read(&net)) == packet_error || pkt_len < MIN_HANDSHAKE_SIZE) { @@ -257,25 +212,27 @@ int Mysql_connection_thread::check_connection() } client_capabilities|= ((ulong) uint2korr(net.read_pos + 2)) << 16; - pos= (char*) net.read_pos + 32; + pos= net.read_pos + 32; /* At least one byte for username and one byte for password */ - if (pos >= (char*) net.read_pos + pkt_len + 2) + if (pos >= net.read_pos + pkt_len + 2) { /*TODO add user and password handling in error messages*/ net_send_error(&net, ER_HANDSHAKE_ERROR); return 1; } - const char *user= pos; + const char *user= (char*) 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, (uint) (password - user - 2), password, scramble)) + if (user_map->authenticate(&user_name, password, scramble)) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; @@ -285,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; @@ -298,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; @@ -306,78 +263,102 @@ 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 *com= parse_command(&instance_map, packet)) + log_info("Connection %lu: received QUERY command: '%s'.", + (unsigned long) connection_id, + (const char *) packet); + + if (Command *com= 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= com->execute(&net, connection_id); delete com; 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 6f5836af4c7..56bbf76e146 100644 --- a/server-tools/instance-manager/mysql_connection.h +++ b/server-tools/instance-manager/mysql_connection.h @@ -16,33 +16,59 @@ #ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_CONNECTION_H #define INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_CONNECTION_H -#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 930ad5ed6fa..e50e5d24f6d 100644 --- a/server-tools/instance-manager/mysql_manager_error.h +++ b/server-tools/instance-manager/mysql_manager_error.h @@ -28,5 +28,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 5a442e2e49a..276d1ca3b49 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -14,117 +14,145 @@ 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_dir.h> #include <my_sys.h> + #include <string.h> -#include <signal.h> + #ifndef __WIN__ #include <pwd.h> #include <grp.h> -#include <sys/wait.h> #endif -#include <sys/types.h> -#include <sys/stat.h> + +#include "angel.h" +#include "log.h" +#include "manager.h" +#include "options.h" +#include "user_management_commands.h" + #ifdef __WIN__ -#include "windowsservice.h" +#include "IMService.h" #endif + /* - Few notes about Instance Manager architecture: - Instance Manager consisits of two processes: the angel process, and the - instance manager process. Responsibilities of the angel process is to - monitor the instance manager process, and restart it in case of - failure/shutdown. The angel process is started only if startup option - '--run-as-service' is provided. - The Instance Manager process consists of several - subsystems (thread sets): - - the signal handling thread: it's responsibilities are to handle - user signals and propogate them to the other threads. All other threads - are accounted in the signal handler thread Thread Registry. - - the listener: listens all sockets. There is a listening - socket for each (mysql, http, snmp, rendezvous (?)) subsystem. - - mysql subsystem: Instance Manager acts like an ordinary MySQL Server, - but with very restricted command set. Each MySQL client connection is - handled in a separate thread. All MySQL client connections threads - constitute mysql subsystem. - - http subsystem: it is also possible to talk with Instance Manager via - http. One thread per http connection is used. Threads are pooled. - - 'snmp' connections (FIXME: I know nothing about it yet) - - rendezvous threads + Instance Manager consists of two processes: the angel process (IM-angel), + and the manager process (IM-main). Responsibilities of IM-angel is to + monitor IM-main, and restart it in case of failure/shutdown. IM-angel is + started only if startup option '--run-as-service' is provided. + + IM-main consists of several subsystems (thread sets): + + - the signal handling thread + + The signal thread handles user signals and propagates them to the + other threads. All other threads are accounted in the signal handler + thread Thread Registry. + + - the listener + + The listener listens to all sockets. There is a listening socket for + each subsystem (TCP/IP, UNIX socket). + + - mysql subsystem + + Instance Manager acts like an ordinary MySQL Server, but with very + restricted command set. Each MySQL client connection is handled in a + separate thread. All MySQL client connections threads constitute + mysql subsystem. */ -static void init_environment(char *progname); +static int main_impl(int argc, char *argv[]); + #ifndef __WIN__ -static void daemonize(const char *log_file_name); -static void angel(const Options &options); -static struct passwd *check_user(const char *user); -static int set_user(const char *user, struct passwd *user_info); -#else -int HandleServiceOptions(Options options); +static struct passwd *check_user(); +static bool switch_user(); #endif -/* - main, entry point - - init environment - - handle options - - daemonize and run angel process (if necessary) - - run manager process -*/ +/************************************************************************/ +/** + The entry point. +*************************************************************************/ int main(int argc, char *argv[]) { - int return_value= 1; - init_environment(argv[0]); - Options options; + int return_value; - if (options.load(argc, argv)) - goto err; + puts("\n" + "WARNING: This program is deprecated and will be removed in 6.0.\n"); -#ifndef __WIN__ - struct passwd *user_info; + /* Initialize. */ - if ((user_info= check_user(options.user))) - { - if (set_user(options.user, user_info)) - goto err; - } + MY_INIT(argv[0]); + log_init(); + umask(0117); + srand((uint) time(0)); - if (options.run_as_service) - { - /* forks, and returns only in child */ - daemonize(options.log_file_name); - /* forks again, and returns only in child: parent becomes angel */ - angel(options); - } -#else - if (!options.stand_alone) - { - if (HandleServiceOptions(options)) - goto err; - } - else -#endif + /* Main function. */ - return_value= manager(options); + log_info("IM: started."); -err: - options.cleanup(); + return_value= main_impl(argc, argv); + + log_info("IM: finished."); + + /* Cleanup. */ + + Options::cleanup(); my_end(0); + return return_value; } -/******************* Auxilary functions implementation **********************/ + +/************************************************************************/ +/** + Instance Manager main functionality. +*************************************************************************/ + +int main_impl(int argc, char *argv[]) +{ + int rc; + + if ((rc= Options::load(argc, argv))) + return rc; + + if (Options::User_management::cmd) + return Options::User_management::cmd->execute(); + +#ifndef __WIN__ + + if (switch_user()) + return 1; + + return Options::Daemon::run_as_service ? + Angel::main() : + Manager::main(); + +#else + + return Options::Service::stand_alone ? + Manager::main() : + IMService::main(); + +#endif +} + +/************************************************************************** + OS-specific functions implementation. +**************************************************************************/ #if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) -/* Change to run as another user if started with --user */ -static struct passwd *check_user(const char *user) +/************************************************************************/ +/** + Change to run as another user if started with --user. +*************************************************************************/ + +static struct passwd *check_user() { + const char *user= Options::Daemon::user; struct passwd *user_info; uid_t user_id= geteuid(); @@ -164,208 +192,41 @@ 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; } -static int set_user(const char *user, struct passwd *user_info) + +/************************************************************************/ +/** + Switch user. +*************************************************************************/ + +static bool switch_user() { - DBUG_ASSERT(user_info); + struct passwd *user_info= check_user(); + + if (!user_info) + return FALSE; + #ifdef HAVE_INITGROUPS - initgroups((char*) user,user_info->pw_gid); + initgroups(Options::Daemon::user, user_info->pw_gid); #endif + if (setgid(user_info->pw_gid) == -1) { log_error("setgid() failed"); - return 1; + return TRUE; } + if (setuid(user_info->pw_uid) == -1) { log_error("setuid() failed"); - return 1; - } - return 0; -} -#endif - - -/* - Init environment, common for daemon and non-daemon -*/ - -static void init_environment(char *progname) -{ - MY_INIT(progname); - log_init(); - umask(0117); - srand((uint) time(0)); -} - - -#ifndef __WIN__ -/* - Become a UNIX service - SYNOPSYS - daemonize() -*/ - -static void daemonize(const char *log_file_name) -{ - pid_t pid= fork(); - switch (pid) { - case -1: // parent, fork error - die("daemonize(): fork failed, %s", strerror(errno)); - case 0: // child, fork ok - int fd; - /* - Become a session leader: setsid must succeed because child is - guaranteed not to be a process group leader (it belongs to the - process group of the parent.) - The goal is not to have a controlling terminal. - */ - setsid(); - /* - As we now don't have a controlling terminal we will not receive - tty-related signals - no need to ignore them. - */ - - close(STDIN_FILENO); - - fd= open(log_file_name, O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (fd < 0) - die("daemonize(): failed to open log file %s, %s", log_file_name, - strerror(errno)); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd != STDOUT_FILENO && fd != STDERR_FILENO) - close(fd); - - /* TODO: chroot() and/or chdir() here */ - break; - default: - /* successfully exit from parent */ - exit(0); - } -} - - -enum { CHILD_OK= 0, CHILD_NEED_RESPAWN, CHILD_EXIT_ANGEL }; - -static volatile sig_atomic_t child_status= CHILD_OK; -static volatile sig_atomic_t child_exit_code= 0; - -/* - Signal handler for SIGCHLD: reap child, analyze child exit code, and set - child_status appropriately. -*/ - -void reap_child(int __attribute__((unused)) signo) -{ - /* NOTE: As we have only one child, no need to cycle waitpid(). */ - - int exit_code; - - if (waitpid(0, &exit_code, WNOHANG) > 0) - { - child_exit_code= exit_code; - child_status= exit_code ? CHILD_NEED_RESPAWN : CHILD_EXIT_ANGEL; + return TRUE; } -} - -static volatile sig_atomic_t is_terminated= 0; - -/* - Signal handler for terminate signals - SIGTERM, SIGHUP, SIGINT. - Set termination status and return. - (q) do we need to handle SIGQUIT? -*/ - -void terminate(int signo) -{ - is_terminated= signo; -} - -/* - Fork a child and monitor it. - User can explicitly kill the angel process with SIGTERM/SIGHUP/SIGINT. - Angel process will exit silently if mysqlmanager exits normally. -*/ - -static void angel(const Options &options) -{ - /* install signal handlers */ - sigset_t zeromask; // to sigsuspend in parent - struct sigaction sa_chld, sa_term; - struct sigaction sa_chld_out, sa_term_out, sa_int_out, sa_hup_out; - - sigemptyset(&zeromask); - sigemptyset(&sa_chld.sa_mask); - sigemptyset(&sa_term.sa_mask); - - sa_chld.sa_handler= reap_child; - sa_chld.sa_flags= SA_NOCLDSTOP; - sa_term.sa_handler= terminate; - sa_term.sa_flags= 0; - - /* sigaction can fail only on wrong arguments */ - sigaction(SIGCHLD, &sa_chld, &sa_chld_out); - sigaction(SIGTERM, &sa_term, &sa_term_out); - sigaction(SIGINT, &sa_term, &sa_int_out); - sigaction(SIGHUP, &sa_term, &sa_hup_out); - - /* spawn a child */ -spawn: - pid_t pid= fork(); - switch (pid) { - case -1: - die("angel(): fork failed, %s", strerror(errno)); - case 0: // child, success - /* - restore default actions for signals to let the manager work with - signals as he wishes - */ - sigaction(SIGCHLD, &sa_chld_out, 0); - sigaction(SIGTERM, &sa_term_out, 0); - sigaction(SIGINT, &sa_int_out, 0); - sigaction(SIGHUP, &sa_hup_out, 0); - /* Here we return to main, and fall into manager */ - break; - default: // parent, success - pid= getpid(); /* Get our pid. */ - - log_info("Angel pid file: '%s'; PID: %d.", - (const char *) options.angel_pid_file_name, - (int) pid); - - create_pid_file(Options::angel_pid_file_name, pid); - - while (child_status == CHILD_OK && is_terminated == 0) - sigsuspend(&zeromask); - - if (is_terminated) - log_info("angel got signal %d, exiting", is_terminated); - else if (child_status == CHILD_NEED_RESPAWN) - { - child_status= CHILD_OK; - log_error("angel(): mysqlmanager exited abnormally (exit code: %d):" - "respawning...", - (int) child_exit_code); - sleep(1); /* don't respawn too fast */ - goto spawn; - } - - /* Delete IM-angel pid file. */ - my_delete(Options::angel_pid_file_name, MYF(0)); - - /* - mysqlmanager successfully exited, let's silently evaporate - If we return to main we fall into the manager() function, so let's - simply exit(). - */ - exit(0); - } + return FALSE; } #endif diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index 9eb148c4e3b..d8667e155b5 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -19,44 +19,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= (uint) strlen(ANGEL_PID_FILE_SUFFIX); @@ -67,24 +111,34 @@ static const int ANGEL_PID_FILE_SUFFIX_LEN= (uint) 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[] = @@ -92,88 +146,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, + (uchar* *) &Options::Daemon::angel_pid_file_name, + (uchar* *) &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, + (uchar* *) &Options::Main::bind_address, + (uchar* *) &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.", + (uchar* *) &Options::Debug::config_str, + (uchar* *) &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, + (uchar* *) &Options::Main::default_mysqld_path, + (uchar* *) &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.", + (uchar* *) &Options::Service::install_as_service, + (uchar* *) &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.", + (uchar* *) &Options::Daemon::log_file_name, + (uchar* *) &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, + (uchar* *) &Options::Main::monitoring_interval, + (uchar* *) &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", + (uchar* *) &Options::Main::mysqld_safe_compatible, + (uchar* *) &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", + (uchar* *) &Options::User_management::password, + (uchar* *) &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.", + (uchar* *) &Options::Main::password_file_name, + (uchar* *) &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.", + (uchar* *) &Options::Main::pid_file_name, + (uchar* *) &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", + (uchar* *) &Options::Main::port_number, + (uchar* *) &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, + (uchar* *) &Options::Service::remove_service, + (uchar* *) &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.", + (uchar* *) &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.", + (uchar* *) &Options::Main::socket_file_name, + (uchar* *) &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.", + (uchar* *) &Options::Service::stand_alone, + (uchar* *) &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, + (uchar* *) &Options::Daemon::user, + (uchar* *) &Options::Daemon::user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif + + { "username", OPT_USERNAME, + "Username to update the password file", + (uchar* *) &Options::User_management::user_name, + (uchar* *) &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 }, + (uchar* *) &net_read_timeout, + (uchar* *) &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); } @@ -201,54 +327,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; } @@ -272,8 +409,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")) @@ -282,29 +419,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 @@ -316,10 +468,11 @@ int Options::load(int argc, char **argv) char *base_name_ptr; char *ext_ptr; - local_angel_pid_file_name= (char *) malloc(strlen(Options::pid_file_name) + - ANGEL_PID_FILE_SUFFIX_LEN); + local_angel_pid_file_name= + (char *) malloc(strlen(Options::Main::pid_file_name) + + ANGEL_PID_FILE_SUFFIX_LEN); - strcpy(local_angel_pid_file_name, Options::pid_file_name); + strcpy(local_angel_pid_file_name, Options::Main::pid_file_name); base_name_ptr= strrchr(local_angel_pid_file_name, '/'); @@ -332,54 +485,71 @@ int Options::load(int argc, char **argv) strcat(local_angel_pid_file_name, ANGEL_PID_FILE_SUFFIX); - Options::angel_pid_file_name= local_angel_pid_file_name; + Options::Daemon::angel_pid_file_name= local_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 f7186bb4ff4..0202ca271c9 100644 --- a/server-tools/instance-manager/options.h +++ b/server-tools/instance-manager/options.h @@ -17,47 +17,89 @@ #define INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H /* - 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); - Options() {} - 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 bbbadf3e91a..cd20e3bc7ab 100644 --- a/server-tools/instance-manager/parse.cc +++ b/server-tools/instance-manager/parse.cc @@ -16,12 +16,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, @@ -49,6 +49,8 @@ struct tokens_st static struct tokens_st tokens[]= { + {6, "CREATE"}, + {4, "DROP"}, {5, "ERROR"}, {5, "FILES"}, {5, "FLUSH"}, @@ -66,13 +68,44 @@ 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 TOK_NOT_FOUND */ -inline Token find_token(const char *word, uint word_len) +inline Token find_token(const char *word, size_t word_len) { int i= 0; do @@ -86,7 +119,7 @@ inline Token find_token(const char *word, uint word_len) } -Token get_token(const char **text, uint *word_len) +Token get_token(const char **text, size_t *word_len) { get_word(text, word_len); if (*word_len) @@ -95,7 +128,7 @@ Token get_token(const char **text, uint *word_len) } -Token shift_token(const char **text, uint *word_len) +Token shift_token(const char **text, size_t *word_len) { Token save= get_token(text, word_len); (*text)+= *word_len; @@ -103,52 +136,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, size_t *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; - bool skip= false; - const char *tmp; + size_t word_len; + 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) @@ -158,92 +338,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= (uint) (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)) { @@ -252,8 +412,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: @@ -273,12 +432,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) { @@ -288,23 +449,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; @@ -323,5 +502,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 9f7f7d7933c..9c50ace5948 100644 --- a/server-tools/instance-manager/parse.h +++ b/server-tools/instance-manager/parse.h @@ -17,9 +17,9 @@ #include <my_global.h> #include <my_sys.h> +#include <m_string.h> class Command; -class Instance_map; enum Log_type { @@ -28,10 +28,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, size_t *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, (uchar*) &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, (uchar*) 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, (uchar*) option, idx); +} + +/************************************************************************/ /* tries to find next word in the text @@ -39,8 +177,8 @@ enum { ALPHANUM= 1, NONSPACE }; if not found returns pointer to first non-space or to '\0', word_len == 0 */ -inline void get_word(const char **text, uint *word_len, - int seek_method= ALPHANUM) +inline void get_word(const char **text, size_t *word_len, + enum_seek_method seek_method= ALPHANUM) { const char *word_end; @@ -50,13 +188,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= (uint) (word_end - *text); } diff --git a/server-tools/instance-manager/parse_output.cc b/server-tools/instance-manager/parse_output.cc index 377671b4401..3511589acd6 100644 --- a/server-tools/instance-manager/parse_output.cc +++ b/server-tools/instance-manager/parse_output.cc @@ -13,15 +13,24 @@ 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" +/************************************************************************** + Private module implementation. +**************************************************************************/ + +namespace { /* no-indent */ + +/*************************************************************************/ void trim_space(const char **text, uint *word_len) { @@ -30,97 +39,369 @@ void trim_space(const char **text, uint *word_len) start++; *text= start; - size_t len= strlen(start); + int len= strlen(start); const char *end= start + len - 1; while (end > start && my_isspace(&my_charset_latin1, *end)) end--; - *word_len= (uint) (end - start)+1; + *word_len= (end - start)+1; } -/* - Parse output of the given command +/*************************************************************************/ - SYNOPSYS - parse_output_and_get_value() +/** + @brief A facade to the internal workings of optaining the output from an + executed system process. +*/ - command the command to execue with popen. - word the word to look for (usually an option name) - result the buffer to store the next word (option value) - input_buffer_len self-explanatory - flag this equals to GET_LINE if we want to get all the line after - the matched word and GET_VALUE otherwise. +class Mysqld_output_parser +{ +public: + Mysqld_output_parser() + { } - DESCRIPTION + virtual ~Mysqld_output_parser() + { } - Parse output of the "command". Find the "word" and return the next one - if flag is GET_VALUE. Return the rest of the parsed string otherwise. +public: + bool parse(const char *command, + const char *option_name_str, + uint option_name_length, + char *option_value_buf, + size_t option_value_buf_size, + enum_option_type option_type); - RETURN - 0 - ok, the word has been found - 1 - error occured or the word is not found -*/ +protected: + /** + @brief Run a process and attach stdout- and stdin-pipes to it. -int parse_output_and_get_value(const char *command, const char *word, - char *result, size_t input_buffer_len, - uint flag) -{ - FILE *output; - size_t wordlen; - /* should be enough to store the string from the output */ - enum { MAX_LINE_LEN= 512 }; - char linebuf[MAX_LINE_LEN]; - int rc= 1; + @param command The path to the process to be executed - wordlen= strlen(word); + @return Error status. + @retval TRUE An error occurred + @retval FALSE Operation was a success + */ - /* - Successful return of popen does not tell us whether the command has been - executed successfully: if the command was not found, we'll get EOF - when reading the output buffer below. + virtual bool run_command(const char *command)= 0; + + + /** + @brief Read a sequence of bytes from the executed process' stdout pipe. + + The sequence is terminated by either '\0', LF or CRLF tokens. The + terminating token is excluded from the result. + + @param line_buffer A pointer to a character buffer + @param line_buffer_size The size of the buffer in bytes + + @return Error status. + @retval TRUE An error occured + @retval FALSE Operation was a success */ - if (!(output= popen(command, "r"))) - goto err; - /* - We want fully buffered stream. We also want system to - allocate appropriate buffer. + virtual bool read_line(char *line_buffer, + uint line_buffer_size)= 0; + + + /** + @brief Release any resources needed after a execution and parsing. */ - setvbuf(output, NULL, _IOFBF, 0); - while (fgets(linebuf, sizeof(linebuf) - 1, output)) + virtual bool cleanup()= 0; +}; + +/*************************************************************************/ + +bool Mysqld_output_parser::parse(const char *command, + const char *option_name_str, + uint option_name_length, + char *option_value_buf, + size_t option_value_buf_size, + enum_option_type option_type) +{ + /* should be enough to store the string from the output */ + const int LINE_BUFFER_SIZE= 512; + char line_buffer[LINE_BUFFER_SIZE]; + + if (run_command(command)) + return TRUE; + + while (true) { + if (read_line(line_buffer, LINE_BUFFER_SIZE)) + { + cleanup(); + return TRUE; + } + uint found_word_len= 0; - char *linep= linebuf; + char *linep= line_buffer; + + line_buffer[sizeof(line_buffer) - 1]= '\0'; /* safety */ + + /* Find the word(s) we are looking for in the line. */ + + linep= strstr(linep, option_name_str); + + if (!linep) + continue; - linebuf[sizeof(linebuf) - 1]= '\0'; /* safety */ + linep+= option_name_length; - /* - Compare the start of our line with the word(s) we are looking for. - */ - if (!strncmp(word, linep, wordlen)) + switch (option_type) { - /* - If we have found our word(s), then move linep past the word(s) - */ - linep+= wordlen; - if (flag & GET_VALUE) + case GET_VALUE: + trim_space((const char**) &linep, &found_word_len); + + if (option_value_buf_size <= found_word_len) { - trim_space((const char**) &linep, &found_word_len); - if (input_buffer_len <= found_word_len) - goto err; - strmake(result, linep, found_word_len); + cleanup(); + return TRUE; } - else /* currently there are only two options */ - strmake(result, linep, (uint) (input_buffer_len - 1)); - rc= 0; + + strmake(option_value_buf, linep, found_word_len); + + break; + + case GET_LINE: + strmake(option_value_buf, linep, option_value_buf_size - 1); + break; } + + cleanup(); + + return FALSE; } +} + +/************************************************************************** + Platform-specific implementation: UNIX. +**************************************************************************/ + +#ifndef __WIN__ + +class Mysqld_output_parser_unix : public Mysqld_output_parser +{ +public: + Mysqld_output_parser_unix() : + m_stdout(NULL) + { } + +protected: + virtual bool run_command(const char *command); + + virtual bool read_line(char *line_buffer, + uint line_buffer_size); + + virtual bool cleanup(); + +private: + FILE *m_stdout; +}; + +bool Mysqld_output_parser_unix::run_command(const char *command) +{ + if (!(m_stdout= popen(command, "r"))) + return TRUE; + + /* + We want fully buffered stream. We also want system to allocate + appropriate buffer. + */ - /* we are not interested in the termination status */ - pclose(output); + setvbuf(m_stdout, NULL, _IOFBF, 0); -err: - return rc; + return FALSE; } +bool Mysqld_output_parser_unix::read_line(char *line_buffer, + uint line_buffer_size) +{ + char *retbuff = fgets(line_buffer, line_buffer_size, m_stdout); + /* Remove any tailing new line charaters */ + if (line_buffer[line_buffer_size-1] == LF) + line_buffer[line_buffer_size-1]= '\0'; + return (retbuff == NULL); +} + +bool Mysqld_output_parser_unix::cleanup() +{ + if (m_stdout) + pclose(m_stdout); + + return FALSE; +} + +#else /* Windows */ + +/************************************************************************** + Platform-specific implementation: Windows. +**************************************************************************/ + +class Mysqld_output_parser_win : public Mysqld_output_parser +{ +public: + Mysqld_output_parser_win() : + m_internal_buffer(NULL), + m_internal_buffer_offset(0), + m_internal_buffer_size(0) + { } + +protected: + virtual bool run_command(const char *command); + virtual bool read_line(char *line_buffer, + uint line_buffer_size); + virtual bool cleanup(); + +private: + HANDLE m_h_child_stdout_wr; + HANDLE m_h_child_stdout_rd; + uint m_internal_buffer_offset; + uint m_internal_buffer_size; + char *m_internal_buffer; +}; + +bool Mysqld_output_parser_win::run_command(const char *command) +{ + BOOL op_status; + + SECURITY_ATTRIBUTES sa_attr; + sa_attr.nLength= sizeof(SECURITY_ATTRIBUTES); + sa_attr.bInheritHandle= TRUE; + sa_attr.lpSecurityDescriptor= NULL; + + op_status= CreatePipe(&m_h_child_stdout_rd, + &m_h_child_stdout_wr, + &sa_attr, + 0 /* Use system-default buffer size. */); + + if (!op_status) + return TRUE; + + SetHandleInformation(m_h_child_stdout_rd, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFO si_start_info; + ZeroMemory(&si_start_info, sizeof(STARTUPINFO)); + si_start_info.cb= sizeof(STARTUPINFO); + si_start_info.hStdError= m_h_child_stdout_wr; + si_start_info.hStdOutput= m_h_child_stdout_wr; + si_start_info.dwFlags|= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi_proc_info; + + op_status= CreateProcess(NULL, /* Application name. */ + (char*)command, /* Command line. */ + NULL, /* Process security attributes. */ + NULL, /* Primary thread security attr.*/ + TRUE, /* Handles are inherited. */ + 0, /* Creation flags. */ + NULL, /* Use parent's environment. */ + NULL, /* Use parent's curr. directory. */ + &si_start_info, /* STARTUPINFO pointer. */ + &pi_proc_info); /* Rec. PROCESS_INFORMATION. */ + + if (!op_status) + { + CloseHandle(m_h_child_stdout_rd); + CloseHandle(m_h_child_stdout_wr); + + return TRUE; + } + + /* Close unnessary handles. */ + + CloseHandle(pi_proc_info.hProcess); + CloseHandle(pi_proc_info.hThread); + + return FALSE; +} + +bool Mysqld_output_parser_win::read_line(char *line_buffer, + uint line_buffer_size) +{ + DWORD dw_read_count= m_internal_buffer_size; + bzero(line_buffer,line_buffer_size); + char *buff_ptr= line_buffer; + char ch; + + while ((unsigned)(buff_ptr - line_buffer) < line_buffer_size) + { + do + { + ReadFile(m_h_child_stdout_rd, &ch, + 1, &dw_read_count, NULL); + } while ((ch == CR || ch == LF) && buff_ptr == line_buffer); + + if (dw_read_count == 0) + return TRUE; + + if (ch == CR || ch == LF) + break; + + *buff_ptr++ = ch; + } + + return FALSE; +} + +bool Mysqld_output_parser_win::cleanup() +{ + /* Close all handles. */ + + CloseHandle(m_h_child_stdout_wr); + CloseHandle(m_h_child_stdout_rd); + + return FALSE; +} +#endif + +/*************************************************************************/ + +} /* End of private module implementation. */ + +/*************************************************************************/ + +/** + @brief Parse output of the given command + + @param command The command to execute. + @param option_name_str Option name. + @param option_name_length Length of the option name. + @param[out] option_value_buf The buffer to store option value. + @param option_value_buf_size Size of the option value buffer. + @param option_type Type of the option: + - GET_LINE if we want to get all the + line after the option name; + - GET_VALUE otherwise. + + Execute the process by running "command". Find the "option name" and + return the next word if "option_type" is GET_VALUE. Return the rest of + the parsed string otherwise. + + @note This function has a separate windows implementation. + + @return The error status. + @retval FALSE Ok, the option name has been found. + @retval TRUE Error occured or the option name is not found. +*/ + +bool parse_output_and_get_value(const char *command, + const char *option_name_str, + uint option_name_length, + char *option_value_buf, + size_t option_value_buf_size, + enum_option_type option_type) +{ +#ifndef __WIN__ + Mysqld_output_parser_unix parser; +#else /* __WIN__ */ + Mysqld_output_parser_win parser; +#endif + + return parser.parse(command, + option_name_str, + option_name_length, + option_value_buf, + option_value_buf_size, + option_type); +} diff --git a/server-tools/instance-manager/parse_output.h b/server-tools/instance-manager/parse_output.h index c236357a200..41618f643a3 100644 --- a/server-tools/instance-manager/parse_output.h +++ b/server-tools/instance-manager/parse_output.h @@ -15,11 +15,19 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define GET_VALUE 1 -#define GET_LINE 2 +#include <my_global.h> -int parse_output_and_get_value(const char *command, const char *word, - char *result, size_t input_buffer_len, - uint flag); +enum enum_option_type +{ + GET_VALUE = 1, + GET_LINE +}; + +bool parse_output_and_get_value(const char *command, + const char *option_name_str, + uint option_name_length, + char *option_value_buf, + size_t option_value_buf_size, + enum_option_type option_type); #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_OUTPUT_H */ diff --git a/server-tools/instance-manager/portability.h b/server-tools/instance-manager/portability.h index 31bebd1b33a..990e6140a9e 100644 --- a/server-tools/instance-manager/portability.h +++ b/server-tools/instance-manager/portability.h @@ -34,11 +34,30 @@ /*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 + +const char CR = '\r'; +const char LF = '\n'; + +#else /* ! __WIN__ */ + +#define NEWLINE "\n" +#define NEWLINE_LEN 1 + +const char LF = '\n'; + #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 9e08189887e..74263934924 100644 --- a/server-tools/instance-manager/priv.cc +++ b/server-tools/instance-manager/priv.cc @@ -13,38 +13,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; @@ -64,30 +45,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) + +bool 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 TRUE; + } + + 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 TRUE; + } + + my_fclose(pid_file, MYF(0)); + + return FALSE; } diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h index 2e55e0ac8e6..1c2124c0e77 100644 --- a/server-tools/instance-manager/priv.h +++ b/server-tools/instance-manager/priv.h @@ -16,13 +16,17 @@ #ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H #define INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H +#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 MYSQL_PORT @@ -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); +bool 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 faeee4e95e9..8d71fcb8026 100644 --- a/server-tools/instance-manager/protocol.cc +++ b/server-tools/instance-manager/protocol.cc @@ -21,7 +21,7 @@ #include <m_string.h> -static char eof_buff[1]= { (char) 254 }; /* Marker for end of fields */ +static uchar eof_buff[1]= { (char) 254 }; /* Marker for end of fields */ static const char ERROR_PACKET_CODE= (char) 255; @@ -38,7 +38,7 @@ int net_send_ok(struct st_net *net, unsigned long connection_id, 1-9 + message length message to send (isn't stored if no message) */ Buffer buff; - char *pos= buff.buffer; + uchar *pos= buff.buffer; /* check that we have space to hold mandatory fields */ buff.reserve(0, 23); @@ -53,11 +53,11 @@ int net_send_ok(struct st_net *net, unsigned long connection_id, int2store(pos, 0); pos+= 2; - uint position= (uint) (pos - buff.buffer); /* we might need it for message */ + size_t position= pos - buff.buffer; /* we might need it for message */ if (message != NULL) { - buff.reserve(position, 9 + (uint) strlen(message)); + buff.reserve(position, 9 + strlen(message)); store_to_protocol_packet(&buff, message, &position); } @@ -82,7 +82,8 @@ int net_send_error(struct st_net *net, uint sql_errno) memcpy(pos, errno_to_sqlstate(sql_errno), SQLSTATE_LENGTH); pos+= SQLSTATE_LENGTH; pos= strmake(pos, err, MYSQL_ERRMSG_SIZE - 1) + 1; - return my_net_write(net, buff, (uint) (pos - buff)) || net_flush(net); + return (my_net_write(net, (uchar*) buff, (size_t) (pos - buff)) || + net_flush(net)); } @@ -98,7 +99,8 @@ int net_send_error_323(struct st_net *net, uint sql_errno) int2store(pos, sql_errno); pos+= 2; pos= strmake(pos, err, MYSQL_ERRMSG_SIZE - 1) + 1; - return my_net_write(net, buff, (uint) (pos - buff)) || net_flush(net); + return (my_net_write(net, (uchar*) buff, (size_t) (pos - buff)) || + net_flush(net)); } char *net_store_length(char *pkg, uint length) @@ -115,15 +117,15 @@ char *net_store_length(char *pkg, uint length) } -int store_to_protocol_packet(Buffer *buf, const char *string, uint *position, - uint string_len) +int store_to_protocol_packet(Buffer *buf, const char *string, size_t *position, + size_t string_len) { uint currpos; /* reserve max amount of bytes needed to store length */ if (buf->reserve(*position, 9)) goto err; - currpos= (uint) (net_store_length(buf->buffer + *position, + currpos= (net_store_length(buf->buffer + *position, (ulonglong) string_len) - buf->buffer); if (buf->append(currpos, string, string_len)) goto err; @@ -135,11 +137,12 @@ err: } -int store_to_protocol_packet(Buffer *buf, const char *string, uint *position) +int store_to_protocol_packet(Buffer *buf, const char *string, + size_t *position) { - uint string_len; + size_t string_len; - string_len= (uint) strlen(string); + string_len= strlen(string); return store_to_protocol_packet(buf, string, position, string_len); } @@ -153,16 +156,17 @@ int send_eof(struct st_net *net) buff[0]=254; int2store(buff+1, 0); int2store(buff+3, 0); - return my_net_write(net, (char*) buff, sizeof buff); + return my_net_write(net, buff, sizeof(buff)); } + int send_fields(struct st_net *net, LIST *fields) { LIST *tmp= fields; Buffer send_buff; - char small_buff[4]; - uint position= 0; - NAME_WITH_LENGTH *field; + uchar small_buff[4]; + size_t position= 0; + LEX_STRING *field; /* send the number of fileds */ net_store_length(small_buff, (uint) list_length(fields)); @@ -172,7 +176,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 */ @@ -183,9 +187,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; @@ -193,7 +197,7 @@ int send_fields(struct st_net *net, LIST *fields) int2store(send_buff.buffer + position, 1); /* charsetnr */ int4store(send_buff.buffer + position + 2, field->length); /* field length */ - send_buff.buffer[position+6]= (char) FIELD_TYPE_STRING; /* type */ + send_buff.buffer[position+6]= (char) MYSQL_TYPE_STRING; /* type */ int2store(send_buff.buffer + position + 7, 0); /* flags */ send_buff.buffer[position + 9]= (char) 0; /* decimals */ send_buff.buffer[position + 10]= 0; diff --git a/server-tools/instance-manager/protocol.h b/server-tools/instance-manager/protocol.h index b8cd4b3d807..b06ae4dfdb2 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 }; @@ -41,10 +36,11 @@ int send_fields(struct st_net *net, LIST *fields); char *net_store_length(char *pkg, uint length); -int store_to_protocol_packet(Buffer *buf, const char *string, uint *position); +int store_to_protocol_packet(Buffer *buf, const char *string, + size_t *position); -int store_to_protocol_packet(Buffer *buf, const char *string, uint *position, - uint string_len); +int store_to_protocol_packet(Buffer *buf, const char *string, size_t *position, + size_t string_len); int send_eof(struct st_net *net); diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc index 5bb4206982e..489caa0aaa8 100644 --- a/server-tools/instance-manager/thread_registry.cc +++ b/server-tools/instance-manager/thread_registry.cc @@ -18,31 +18,29 @@ #endif #include "thread_registry.h" - -#include "log.h" - -#include <assert.h> -#include <signal.h> #include <thr_alarm.h> - +#include <signal.h> +#include "log.h" #ifndef __WIN__ /* Kick-off signal handler */ enum { THREAD_KICK_OFF_SIGNAL= SIGUSR2 }; -static void handle_signal(int __attribute__((unused)) sig_no) +extern "C" void handle_signal(int); + +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) @@ -51,7 +49,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()) ,error_status(FALSE) { @@ -68,6 +66,12 @@ Thread_registry::~Thread_registry() /* Check that no one uses the repository. */ pthread_mutex_lock(&LOCK_thread_registry); + for (Thread_info *ti= head.next; ti != &head; ti= ti->next) + { + log_error("Thread_registry: unregistered thread: %lu.", + (unsigned long) ti->thread_id); + } + /* All threads must unregister */ DBUG_ASSERT(head.next == &head); @@ -83,8 +87,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 +121,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 +198,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 +211,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 +227,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); } @@ -246,6 +263,142 @@ void Thread_registry::request_shutdown() } +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; +} + + int Thread_registry::get_error_status() { int ret_error_status; diff --git a/server-tools/instance-manager/thread_registry.h b/server-tools/instance-manager/thread_registry.h index f6de49cd9ad..d04c8442e44 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(); @@ -101,12 +147,20 @@ public: void set_error_status(); 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; bool error_status; + +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..2eb0ae30aa5 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.cc @@ -0,0 +1,421 @@ +/* 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; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#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..c925e6ae363 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.h @@ -0,0 +1,167 @@ +#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; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + 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 7871cad7814..49c35c16ca9 100644 --- a/server-tools/instance-manager/user_map.cc +++ b/server-tools/instance-manager/user_map.cc @@ -18,33 +18,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]; - User() {} - int init(const char *line); -}; - + user_length= (uint8) (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,44 +51,58 @@ 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= (uint) (name_end - name_begin); + + user_length= (uint8) (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= (int) 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, + (const char *) 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; } C_MODE_START -static byte* get_user_key(const byte* u, uint* len, - my_bool __attribute__((unused)) t) +static uchar* get_user_key(const uchar* u, size_t* len, + my_bool __attribute__((unused)) t) { const User *user= (const User *) u; *len= user->user_length; - return (byte *) user->user; + return (uchar *) user->user; } static void delete_user(void *u) @@ -101,30 +114,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)) + 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 +185,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, (uchar *) 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 +363,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, (uchar*) 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, (uchar*) user) == 0 ? FALSE : TRUE; +} + + +bool User_map::remove_user(User *user) +{ + return hash_delete(&hash, (uchar*) user) == 0 ? FALSE : TRUE; } diff --git a/server-tools/instance-manager/user_map.h b/server-tools/instance-manager/user_map.h index ca9bc861bda..6168f2b04fa 100644 --- a/server-tools/instance-manager/user_map.h +++ b/server-tools/instance-manager/user_map.h @@ -17,14 +17,35 @@ #define INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H #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 */ @@ -32,16 +53,51 @@ class User_map { public: - User_map() {} + /* 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 |