diff options
Diffstat (limited to 'server-tools')
51 files changed, 8532 insertions, 0 deletions
diff --git a/server-tools/CMakeLists.txt b/server-tools/CMakeLists.txt new file mode 100755 index 00000000000..1983d459ce2 --- /dev/null +++ b/server-tools/CMakeLists.txt @@ -0,0 +1,18 @@ +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") + +ADD_DEFINITIONS(-DMYSQL_SERVER -DMYSQL_INSTANCE_MANAGER) +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/sql + ${PROJECT_SOURCE_DIR}/extra/yassl/include) + +ADD_EXECUTABLE(mysqlmanager buffer.cc command.cc commands.cc guardian.cc instance.cc instance_map.cc + instance_options.cc listener.cc log.cc manager.cc messages.cc mysql_connection.cc + mysqlmanager.cc options.cc parse.cc parse_output.cc priv.cc protocol.cc + thread_registry.cc user_map.cc imservice.cpp windowsservice.cpp + 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) + +ADD_DEPENDENCIES(mysqlmanager GenError) +TARGET_LINK_LIBRARIES(mysqlmanager dbug mysys strings taocrypt vio yassl zlib wsock32) diff --git a/server-tools/Makefile.am b/server-tools/Makefile.am new file mode 100644 index 00000000000..573bf07ccff --- /dev/null +++ b/server-tools/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS= instance-manager +DIST_SUBDIRS= instance-manager diff --git a/server-tools/instance-manager/CMakeLists.txt b/server-tools/instance-manager/CMakeLists.txt new file mode 100755 index 00000000000..fafc3df4108 --- /dev/null +++ b/server-tools/instance-manager/CMakeLists.txt @@ -0,0 +1,17 @@ +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") + +ADD_DEFINITIONS(-DMYSQL_SERVER -DMYSQL_INSTANCE_MANAGER) +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/sql + ${PROJECT_SOURCE_DIR}/extra/yassl/include) + +ADD_EXECUTABLE(mysqlmanager buffer.cc command.cc commands.cc guardian.cc instance.cc instance_map.cc + instance_options.cc listener.cc log.cc manager.cc messages.cc mysql_connection.cc + mysqlmanager.cc options.cc parse.cc parse_output.cc priv.cc protocol.cc + thread_registry.cc user_map.cc IMService.cpp WindowsService.cpp + ../../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) + +ADD_DEPENDENCIES(mysqlmanager GenError) +TARGET_LINK_LIBRARIES(mysqlmanager dbug mysys strings taocrypt vio yassl zlib wsock32) diff --git a/server-tools/instance-manager/IMService.cpp b/server-tools/instance-manager/IMService.cpp new file mode 100644 index 00000000000..b7ea8e7eb81 --- /dev/null +++ b/server-tools/instance-manager/IMService.cpp @@ -0,0 +1,82 @@ +#include <windows.h> +#include <signal.h> +#include "log.h" +#include "options.h" +#include "IMService.h" +#include "manager.h" + +IMService::IMService(void) +{ + serviceName= "MySqlManager"; + displayName= "MySQL Manager"; + username= NULL; + password= NULL; +} + +IMService::~IMService(void) +{ +} + +void IMService::Stop() +{ + ReportStatus(SERVICE_STOP_PENDING); + + // stop the IM work + raise(SIGTERM); +} + +void IMService::Run(DWORD argc, LPTSTR *argv) +{ + // report to the SCM that we're about to start + ReportStatus((DWORD)SERVICE_START_PENDING); + + Options o; + o.load(argc, argv); + + // init goes here + ReportStatus((DWORD)SERVICE_RUNNING); + + // wait for main loop to terminate + manager(o); + o.cleanup(); +} + +void IMService::Log(const char *msg) +{ + log_info(msg); +} + +int HandleServiceOptions(Options options) +{ + int ret_val= 0; + + IMService winService; + + if (options.install_as_service) + { + if (winService.IsInstalled()) + log_info("Service is already installed"); + else if (winService.Install()) + log_info("Service installed successfully"); + else + { + log_info("Service failed to install"); + ret_val= 1; + } + } + else if (options.remove_service) + { + if (! winService.IsInstalled()) + log_info("Service is not installed"); + else if (winService.Remove()) + log_info("Service removed successfully"); + else + { + log_info("Service failed to remove"); + ret_val= 1; + } + } + else + ret_val= !winService.Init(); + return ret_val; +} diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h new file mode 100644 index 00000000000..cad38bebdaf --- /dev/null +++ b/server-tools/instance-manager/IMService.h @@ -0,0 +1,14 @@ +#pragma once +#include "windowsservice.h" + +class IMService : public WindowsService +{ +public: + IMService(void); + ~IMService(void); + +protected: + void Log(const char *msg); + void Stop(); + void Run(DWORD argc, LPTSTR *argv); +}; diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am new file mode 100644 index 00000000000..b1d77506efa --- /dev/null +++ b/server-tools/instance-manager/Makefile.am @@ -0,0 +1,95 @@ +# Copyright (C) 2004 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +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) +# See automake/autoconf docs for details + +noinst_LTLIBRARIES= liboptions.la +noinst_LIBRARIES= libnet.a + +liboptions_la_CXXFLAGS= $(CXXFLAGS) \ + -DDEFAULT_PID_FILE_NAME="$(localstatedir)/mysqlmanager.pid" \ + -DDEFAULT_LOG_FILE_NAME="$(localstatedir)/mysqlmanager.log" \ + -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" \ + -DPROTOCOL_VERSION=@PROTOCOL_VERSION@ + +liboptions_la_SOURCES= options.h options.cc priv.h priv.cc +liboptions_la_LIBADD= $(top_builddir)/libmysql/get_password.lo + +# MySQL sometimes uses symlinks to reuse code +# All symlinked files are grouped in libnet.a + +nodist_libnet_a_SOURCES= net_serv.cc client_settings.h +libnet_a_LIBADD= $(top_builddir)/sql/password.$(OBJEXT) \ + $(top_builddir)/sql/pack.$(OBJEXT) \ + $(top_builddir)/sql/sql_state.$(OBJEXT) \ + $(top_builddir)/sql/mini_client_errors.$(OBJEXT)\ + $(top_builddir)/sql/client.$(OBJEXT) + +CLEANFILES= net_serv.cc client_settings.h + +net_serv.cc: + rm -f net_serv.cc + @LN_CP_F@ $(top_srcdir)/sql/net_serv.cc net_serv.cc + +client_settings.h: + rm -f client_settings.h + @LN_CP_F@ $(top_srcdir)/sql/client_settings.h client_settings.h + +libexec_PROGRAMS= mysqlmanager + +mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ + manager.h manager.cc log.h log.cc \ + thread_registry.h thread_registry.cc \ + listener.h listener.cc protocol.h protocol.cc \ + mysql_connection.h mysql_connection.cc \ + user_map.h user_map.cc \ + messages.h messages.cc \ + commands.h commands.cc \ + instance.h instance.cc \ + instance_map.h instance_map.cc\ + instance_options.h instance_options.cc \ + buffer.h buffer.cc parse.cc parse.h \ + guardian.cc guardian.h \ + parse_output.cc parse_output.h \ + mysql_manager_error.h \ + portability.h + +mysqlmanager_LDADD= liboptions.la \ + libnet.a \ + $(top_builddir)/vio/libvio.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/strings/libmystrings.a \ + $(top_builddir)/dbug/libdbug.a \ + @openssl_libs@ @yassl_libs@ @ZLIB_LIBS@ + + +tags: + ctags -R *.h *.cc + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/server-tools/instance-manager/README b/server-tools/instance-manager/README new file mode 100644 index 00000000000..ac799775003 --- /dev/null +++ b/server-tools/instance-manager/README @@ -0,0 +1,11 @@ +Instance Manager - manage MySQL instances locally and remotely. + +File description: + mysqlmanager.cc - entry point to the manager, main, + options.{h,cc} - handle startup options + manager.{h,cc} - manager process + mysql_connection.{h,cc} - handle one connection with mysql client. + +See also instance manager architecture description in mysqlmanager.cc. + + diff --git a/server-tools/instance-manager/WindowsService.cpp b/server-tools/instance-manager/WindowsService.cpp new file mode 100644 index 00000000000..192045b7a4c --- /dev/null +++ b/server-tools/instance-manager/WindowsService.cpp @@ -0,0 +1,203 @@ +#include <windows.h> +#include <assert.h> +#include ".\windowsservice.h" + +static WindowsService *gService; + +WindowsService::WindowsService(void) : + statusCheckpoint(0), + serviceName(NULL), + inited(false), + dwAcceptedControls(SERVICE_ACCEPT_STOP), + debugging(false) +{ + gService= this; + status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; + status.dwServiceSpecificExitCode= 0; +} + +WindowsService::~WindowsService(void) +{ +} + +BOOL WindowsService::Install() +{ + bool ret_val= false; + SC_HANDLE newService; + SC_HANDLE scm; + + if (IsInstalled()) return true; + + // determine the name of the currently executing file + char szFilePath[_MAX_PATH]; + GetModuleFileName(NULL, szFilePath, sizeof(szFilePath)); + + // open a connection to the SCM + if (!(scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE))) + return false; + + newService= CreateService(scm, serviceName, displayName, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, + szFilePath, NULL, NULL, NULL, username, + password); + + if (newService) + { + CloseServiceHandle(newService); + ret_val= true; + } + + CloseServiceHandle(scm); + return ret_val; +} + +BOOL WindowsService::Init() +{ + assert(serviceName != NULL); + + if (inited) return true; + + SERVICE_TABLE_ENTRY stb[] = + { + { (LPSTR)serviceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, + { NULL, NULL } + }; + inited= true; + return StartServiceCtrlDispatcher(stb); //register with the Service Manager +} + +BOOL WindowsService::Remove() +{ + bool ret_val= false; + + if (! IsInstalled()) + return true; + + // open a connection to the SCM + SC_HANDLE scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE); + if (! scm) + return false; + + SC_HANDLE service= OpenService(scm, serviceName, DELETE); + if (service) + { + if (DeleteService(service)) + ret_val= true; + DWORD dw= ::GetLastError(); + CloseServiceHandle(service); + } + + CloseServiceHandle(scm); + return ret_val; +} + +BOOL WindowsService::IsInstalled() +{ + BOOL ret_val= FALSE; + + SC_HANDLE scm= ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + SC_HANDLE serv_handle= ::OpenService(scm, serviceName, SERVICE_QUERY_STATUS); + + ret_val= serv_handle != NULL; + + ::CloseServiceHandle(serv_handle); + ::CloseServiceHandle(scm); + + return ret_val; +} + +void WindowsService::SetAcceptedControls(DWORD acceptedControls) +{ + dwAcceptedControls= acceptedControls; +} + + +BOOL WindowsService::ReportStatus(DWORD currentState, DWORD waitHint, + DWORD dwError) +{ + if(debugging) return TRUE; + + if(currentState == SERVICE_START_PENDING) + status.dwControlsAccepted= 0; + else + status.dwControlsAccepted= dwAcceptedControls; + + status.dwCurrentState= currentState; + status.dwWin32ExitCode= dwError != 0 ? + ERROR_SERVICE_SPECIFIC_ERROR : NO_ERROR; + status.dwWaitHint= waitHint; + status.dwServiceSpecificExitCode= dwError; + + if(currentState == SERVICE_RUNNING || currentState == SERVICE_STOPPED) + { + status.dwCheckPoint= 0; + statusCheckpoint= 0; + } + else + status.dwCheckPoint= ++statusCheckpoint; + + // Report the status of the service to the service control manager. + BOOL result= SetServiceStatus(statusHandle, &status); + if (!result) + Log("ReportStatus failed"); + + return result; +} + +void WindowsService::RegisterAndRun(DWORD argc, LPTSTR *argv) +{ + statusHandle= ::RegisterServiceCtrlHandler(serviceName, ControlHandler); + if (statusHandle && ReportStatus(SERVICE_START_PENDING)) + Run(argc, argv); + ReportStatus(SERVICE_STOPPED); +} + +void WindowsService::HandleControlCode(DWORD opcode) +{ + // Handle the requested control code. + switch(opcode) { + case SERVICE_CONTROL_STOP: + // Stop the service. + status.dwCurrentState= SERVICE_STOP_PENDING; + Stop(); + break; + + case SERVICE_CONTROL_PAUSE: + status.dwCurrentState= SERVICE_PAUSE_PENDING; + Pause(); + break; + + case SERVICE_CONTROL_CONTINUE: + status.dwCurrentState= SERVICE_CONTINUE_PENDING; + Continue(); + break; + + case SERVICE_CONTROL_SHUTDOWN: + Shutdown(); + break; + + case SERVICE_CONTROL_INTERROGATE: + ReportStatus(status.dwCurrentState); + break; + + default: + // invalid control code + break; + } +} + +void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) +{ + assert(gService != NULL); + + // register our service control handler: + gService->RegisterAndRun(argc, argv); +} + +void WINAPI WindowsService::ControlHandler(DWORD opcode) +{ + assert(gService != NULL); + + return gService->HandleControlCode(opcode); +} diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h new file mode 100644 index 00000000000..1a034ce1351 --- /dev/null +++ b/server-tools/instance-manager/WindowsService.h @@ -0,0 +1,43 @@ +#pragma once + +class WindowsService +{ +protected: + bool inited; + const char *serviceName; + const char *displayName; + const char *username; + const char *password; + SERVICE_STATUS_HANDLE statusHandle; + DWORD statusCheckpoint; + SERVICE_STATUS status; + DWORD dwAcceptedControls; + bool debugging; + +public: + WindowsService(void); + ~WindowsService(void); + + BOOL Install(); + BOOL Remove(); + BOOL Init(); + BOOL IsInstalled(); + void SetAcceptedControls(DWORD acceptedControls); + void Debug(bool debugFlag) { debugging= debugFlag; } + +public: + static void WINAPI ServiceMain(DWORD argc, LPTSTR *argv); + static void WINAPI ControlHandler(DWORD CtrlType); + +protected: + virtual void Run(DWORD argc, LPTSTR *argv)= 0; + virtual void Stop() {} + virtual void Shutdown() {} + virtual void Pause() {} + virtual void Continue() {} + virtual void Log(const char *msg) {} + + BOOL ReportStatus(DWORD currentStatus, DWORD waitHint= 3000, DWORD dwError=0); + void HandleControlCode(DWORD opcode); + void RegisterAndRun(DWORD argc, LPTSTR *argv); +}; diff --git a/server-tools/instance-manager/buffer.cc b/server-tools/instance-manager/buffer.cc new file mode 100644 index 00000000000..8039ab24481 --- /dev/null +++ b/server-tools/instance-manager/buffer.cc @@ -0,0 +1,111 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "buffer.h" +#include <m_string.h> + +const uint Buffer::BUFFER_INITIAL_SIZE= 4096; +const uint Buffer::MAX_BUFFER_SIZE= 16777216; + +/* + Puts the given string to the buffer. + + SYNOPSYS + append() + position start position in the buffer + string string to be put in the buffer + len_arg the length of the string. This way we can avoid some + strlens. + + DESCRIPTION + + The method puts a string into the buffer, starting from position . + In the case when the buffer is too small it reallocs the buffer. The + total size of the buffer is restricted with 16. + + RETURN + 0 - ok + 1 - got an error in reserve() +*/ + +int Buffer::append(uint position, const char *string, uint len_arg) +{ + if (reserve(position, len_arg)) + return 1; + + strnmov(buffer + position, string, len_arg); + return 0; +} + + +/* + 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 + reserve() + position the number starting byte on the buffer to store a buffer + len_arg the length of the string. + + DESCRIPTION + + The method checks whether it is possible to put a string of the "len_arg" + length into the buffer, starting from "position" byte. In the case when the + buffer is too small it reallocs the buffer. The total size of the buffer is + restricted with 16 Mb. + + RETURN + 0 - ok + 1 - realloc error or we have come to the 16Mb barrier +*/ + +int Buffer::reserve(uint position, uint len_arg) +{ + if (position + len_arg >= MAX_BUFFER_SIZE) + goto err; + + if (position + len_arg >= buffer_size) + { + buffer= (char*) 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); + } + return 0; + +err: + error= 1; + return 1; +} + + +int Buffer::get_size() +{ + return buffer_size; +} + + +int Buffer::is_error() +{ + return error; +} diff --git a/server-tools/instance-manager/buffer.h b/server-tools/instance-manager/buffer.h new file mode 100644 index 00000000000..afc71320ecb --- /dev/null +++ b/server-tools/instance-manager/buffer.h @@ -0,0 +1,66 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_BUFFER_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_BUFFER_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_sys.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/* + This class is a simple implementation of the buffer of varying size. + It is used to store MySQL client-server protocol packets. This is why + the maximum buffer size if 16Mb. (See internals manual section + 7. MySQL Client/Server Protocol) +*/ + +class Buffer +{ +private: + static const uint BUFFER_INITIAL_SIZE; + /* maximum buffer size is 16Mb */ + static const uint MAX_BUFFER_SIZE; + size_t buffer_size; + /* Error flag. Triggered if we get an error of some kind */ + int error; +public: + Buffer(size_t buffer_size_arg= BUFFER_INITIAL_SIZE) + :buffer_size(buffer_size_arg), error(0) + { + /* + As append() will invokes realloc() anyway, it's ok if malloc returns 0 + */ + if (!(buffer= (char*) my_malloc(buffer_size, MYF(0)))) + buffer_size= 0; + } + + ~Buffer() + { + my_free(buffer, MYF(0)); + } + +public: + char *buffer; + int get_size(); + int is_error(); + int append(uint position, const char *string, uint len_arg); + int reserve(uint position, uint 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 new file mode 100644 index 00000000000..f76366d5661 --- /dev/null +++ b/server-tools/instance-manager/command.cc @@ -0,0 +1,30 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "command.h" + + +Command::Command(Instance_map *instance_map_arg) + :instance_map(instance_map_arg) +{} + +Command::~Command() +{} + diff --git a/server-tools/instance-manager/command.h b/server-tools/instance-manager/command.h new file mode 100644 index 00000000000..b84cc6a8e9e --- /dev/null +++ b/server-tools/instance-manager/command.h @@ -0,0 +1,47 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_COMMAND_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_COMMAND_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/* Class responsible for allocation of im commands. */ + +class Instance_map; + +/* + Command - entry point for any command. + GangOf4: 'Command' design pattern +*/ + +class Command +{ +public: + Command(Instance_map *instance_map_arg= 0); + virtual ~Command(); + + /* method of executing: */ + virtual int execute(struct st_net *net, ulong connection_id) = 0; + +protected: + Instance_map *instance_map; +}; + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_COMMAND_H */ diff --git a/server-tools/instance-manager/commands.cc b/server-tools/instance-manager/commands.cc new file mode 100644 index 00000000000..2c9d3236720 --- /dev/null +++ b/server-tools/instance-manager/commands.cc @@ -0,0 +1,794 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "commands.h" + +#include "instance_map.h" +#include "messages.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" +#include "protocol.h" +#include "buffer.h" +#include "options.h" + +#include <m_string.h> +#include <mysql.h> +#include <my_dir.h> + + +/* + Add a string to a buffer + + SYNOPSYS + put_to_buff() + buff buffer to add the string + str string to add + uint offset in the buff to add a string + + DESCRIPTION + + Function to add a string to the buffer. It is different from + store_to_protocol_packet, which is used in the protocol.cc. The last + one also stores the length of the string in a special way. + This is required for MySQL client/server protocol support only. + + RETURN + 0 - ok + 1 - error occured +*/ + + +static inline int put_to_buff(Buffer *buff, const char *str, uint *position) +{ + uint len= strlen(str); + if (buff->append(*position, str, len)) + return 1; + + *position+= len; + return 0; +} + + +/* implementation for Show_instances: */ + + +/* + The method sends a list of instances in the instance map to the client. + + SYNOPSYS + Show_instances::execute() + net The network connection to the client. + connection_id Client connection ID + + RETURN + 0 - ok + 1 - error occured +*/ + +int Show_instances::execute(struct st_net *net, ulong connection_id) +{ + Buffer send_buff; /* buffer for packets */ + LIST name, status; + NAME_WITH_LENGTH name_field, status_field; + LIST *field_list; + uint position=0; + + name_field.name= (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); + field_list= list_add(field_list, &name); + + send_fields(net, field_list); + + { + Instance *instance; + Instance_map::Iterator iterator(instance_map); + + instance_map->lock(); + while ((instance= iterator.next())) + { + 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; + } + instance_map->unlock(); + } + if (send_eof(net)) + goto err; + if (net_flush(net)) + goto err; + + return 0; +err: + return ER_OUT_OF_RESOURCES; +} + + +/* implementation for Flush_instances: */ + +int Flush_instances::execute(struct st_net *net, ulong connection_id) +{ + if (instance_map->flush_instances() || + net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/* implementation for Show_instance_status: */ + +Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, + const char *name, uint len) + :Command(instance_map_arg) +{ + Instance *instance; + + /* we make a search here, since we don't want to store the name */ + if ((instance= instance_map->find(name, len))) + instance_name= instance->options.instance_name; + else + instance_name= NULL; +} + + +/* + The method sends a table with a status of requested instance to the client. + + SYNOPSYS + Show_instance_status::do_command() + net The network connection to the client. + instance_name The name of the instance. + + RETURN + 0 - ok + 1 - error occured +*/ + + +int Show_instance_status::execute(struct st_net *net, + ulong connection_id) +{ + enum { MAX_VERSION_LENGTH= 40 }; + Buffer send_buff; /* buffer for packets */ + LIST name, status, version; + LIST *field_list; + NAME_WITH_LENGTH name_field, status_field, version_field; + uint position=0; + + if (!instance_name) + return ER_BAD_INSTANCE_NAME; + + /* create list of the fileds to be passed to send_fields */ + name_field.name= (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"; + version_field.length= MAX_VERSION_LENGTH; + version.data= &version_field; + field_list= list_add(NULL, &version); + field_list= list_add(field_list, &status); + field_list= list_add(field_list, &name); + + send_fields(net, field_list); + + { + Instance *instance; + + store_to_protocol_packet(&send_buff, (char*) instance_name, &position); + if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) + goto err; + if (instance->is_running()) + store_to_protocol_packet(&send_buff, (char*) "online", &position); + else + store_to_protocol_packet(&send_buff, (char*) "offline", &position); + + if (instance->options.mysqld_version) + store_to_protocol_packet(&send_buff, instance->options.mysqld_version, + &position); + else + store_to_protocol_packet(&send_buff, (char*) "unknown", &position); + + + if (send_buff.is_error() || + my_net_write(net, send_buff.buffer, (uint) position)) + goto err; + } + + if (send_eof(net) || net_flush(net)) + goto err; + + return 0; + +err: + return ER_OUT_OF_RESOURCES; +} + + +/* Implementation for Show_instance_options */ + +Show_instance_options::Show_instance_options(Instance_map *instance_map_arg, + const char *name, uint len): + Command(instance_map_arg) +{ + Instance *instance; + + /* we make a search here, since we don't want to store the name */ + if ((instance= instance_map->find(name, len))) + instance_name= instance->options.instance_name; + else + instance_name= NULL; +} + + +int Show_instance_options::execute(struct st_net *net, ulong connection_id) +{ + Buffer send_buff; /* buffer for packets */ + LIST name, option; + LIST *field_list; + NAME_WITH_LENGTH name_field, option_field; + uint position=0; + + if (!instance_name) + return ER_BAD_INSTANCE_NAME; + + /* create list of the fileds to be passed to send_fields */ + name_field.name= (char*) "option_name"; + name_field.length= DEFAULT_FIELD_LENGTH; + name.data= &name_field; + option_field.name= (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); + + { + Instance *instance; + + if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) + goto err; + store_to_protocol_packet(&send_buff, (char*) "instance_name", &position); + store_to_protocol_packet(&send_buff, (char*) instance_name, &position); + if (my_net_write(net, send_buff.buffer, (uint) position)) + goto err; + if ((instance->options.mysqld_path)) + { + position= 0; + store_to_protocol_packet(&send_buff, (char*) "mysqld-path", &position); + store_to_protocol_packet(&send_buff, + (char*) instance->options.mysqld_path, + &position); + if (send_buff.is_error() || + my_net_write(net, send_buff.buffer, (uint) position)) + goto err; + } + + if ((instance->options.nonguarded)) + { + 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; + } + + /* loop through the options stored in DYNAMIC_ARRAY */ + for (uint i= 0; i < instance->options.options_array.elements; i++) + { + 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 */ + + position= 0; + if (option_value != NULL) + { + *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= '='; + } + else + { + store_to_protocol_packet(&send_buff, tmp_option + 2, &position); + store_to_protocol_packet(&send_buff, "", &position); + } + + if (send_buff.is_error() || + my_net_write(net, send_buff.buffer, (uint) position)) + goto err; + } + } + + if (send_eof(net) || net_flush(net)) + goto err; + + return 0; + +err: + return ER_OUT_OF_RESOURCES; +} + + +/* Implementation for Start_instance */ + +Start_instance::Start_instance(Instance_map *instance_map_arg, + const char *name, uint len) + :Command(instance_map_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) +{ + uint err_code; + if (instance == 0) + return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + else + { + if ((err_code= instance->start())) + return err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->guard(instance); + + net_send_ok(net, connection_id, "Instance started"); + return 0; + } +} + + +/* implementation for Show_instance_log: */ + +Show_instance_log::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) + :Command(instance_map_arg) +{ + Instance *instance; + + if (offset_arg != NULL) + offset= atoi(offset_arg); + else + offset= 0; + size= atoi(size_arg); + log_type= log_type_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; + else + instance_name= NULL; +} + + + +/* + Open the logfile, read requested part of the log and send the info + to the client. + + SYNOPSYS + Show_instance_log::execute() + net The network connection to the client. + connection_id Client connection ID + + DESCRIPTION + + Send a table with the content of the log requested. The function also + deals with errro handling, to be verbose. + + 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 +*/ + +int Show_instance_log::execute(struct st_net *net, ulong connection_id) +{ + Buffer send_buff; /* buffer for packets */ + LIST name; + LIST *field_list; + NAME_WITH_LENGTH name_field; + uint position= 0; + + /* create list of the fileds to be passed to send_fields */ + name_field.name= (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; + + /* cannot read negative number of bytes */ + if (offset > size) + return ER_OFFSET_ERROR; + + send_fields(net, field_list); + + { + Instance *instance; + const char *logpath; + File fd; + + if ((instance= instance_map->find(instance_name, + strlen(instance_name))) == NULL) + goto err; + + logpath= instance->options.logs[log_type]; + + /* Instance has no such log */ + if (logpath == NULL) + return ER_NO_SUCH_LOG; + + if (*logpath == '\0') + return ER_GUESS_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))) + goto err; + + buff_size= (size - offset); + + read_buff.reserve(0, buff_size); + + /* read in one chunk */ + read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); + + if ((read_len= my_read(fd, (byte*) read_buff.buffer, + buff_size, MYF(0))) < 0) + return ER_READ_FILE; + store_to_protocol_packet(&send_buff, read_buff.buffer, + &position, read_len); + close(fd); + } + else + return ER_OPEN_LOGFILE; + + if (my_net_write(net, send_buff.buffer, (uint) position)) + goto err; + } + + if (send_eof(net) || net_flush(net)) + goto err; + + return 0; + +err: + return ER_OUT_OF_RESOURCES; +} + + +/* implementation for 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) +{ + Instance *instance; + + /* we make a search here, since we don't want to store the name */ + if ((instance= instance_map->find(name, len))) + instance_name= instance->options.instance_name; + else + instance_name= NULL; +} + + +/* + The method sends a table with a list of log files + used by the instance. + + SYNOPSYS + Show_instance_log_files::execute() + net The network connection to the client. + connection_id The ID of the client connection + + 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) +{ + 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; + + if (!instance_name) + return ER_BAD_INSTANCE_NAME; + + /* create list of the fileds to be passed to send_fields */ + name_field.name= (char*) "Logfile"; + name_field.length= DEFAULT_FIELD_LENGTH; + name.data= &name_field; + path_field.name= (char*) "Path"; + path_field.length= DEFAULT_FIELD_LENGTH; + path.data= &path_field; + size_field.name= (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); + + Instance *instance; + + if ((instance= instance_map-> + find(instance_name, strlen(instance_name))) == NULL) + goto err; + + { + /* + 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 != 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; + } + } + } + + if (send_eof(net) || net_flush(net)) + goto err; + + return 0; + +err: + return ER_OUT_OF_RESOURCES; +} + + +/* implementation for SET instance_name.option=option_value: */ + +Set_option::Set_option(Instance_map *instance_map_arg, + const char *name, uint len, + const char *option_arg, uint option_len_arg, + const char *option_value_arg, uint option_value_len_arg) + :Command(instance_map_arg) +{ + Instance *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; + + /* 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 + { + instance_name= NULL; + instance_name_len= 0; + } +} + + +/* + The method sends a table with a list of log files + used by the instance. + + SYNOPSYS + Set_option::correct_file() + skip Skip the option, being searched while writing the result file. + That is, to delete it. + + DESCRIPTION + + Correct the option file. The "skip" option is used to remove the found + option. + + RETURN + ER_OUT_OF_RESOURCES out of resources + ER_ACCESS_OPTION_FILE Cannot access the option file + 0 - ok +*/ + +int Set_option::correct_file(int skip) +{ + static const int mysys_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, + ER_ACCESS_OPTION_FILE }; + int error; + + error= modify_defaults_file(Options::config_file, option, + option_value, instance_name, skip); + DBUG_ASSERT(error >= 0 && error <= 2); + + return mysys_to_im_error[error]; +} + + +/* + The method sets an option in the the default config file (/etc/my.cnf). + + SYNOPSYS + Set_option::do_command() + net The network connection to the client. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Set_option::do_command(struct st_net *net) +{ + int error; + + /* we must hold the instance_map mutex while changing config file */ + instance_map->lock(); + error= correct_file(FALSE); + instance_map->unlock(); + + return error; +} + + +int Set_option::execute(struct st_net *net, ulong connection_id) +{ + if (instance_name != NULL) + { + int val; + + val= do_command(net); + + if (val == 0) + net_send_ok(net, connection_id, NULL); + + return val; + } + + return ER_BAD_INSTANCE_NAME; +} + + +/* the only function from Unset_option we need to Implement */ + +int Unset_option::do_command(struct st_net *net) +{ + return correct_file(TRUE); +} + + +/* Implementation for Stop_instance: */ + +Stop_instance::Stop_instance(Instance_map *instance_map_arg, + const char *name, uint len) + :Command(instance_map_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 Stop_instance::execute(struct st_net *net, ulong connection_id) +{ + uint err_code; + + if (instance == 0) + return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + + if (!(instance->options.nonguarded)) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + net_send_ok(net, connection_id, NULL); + return 0; +} + + +int Syntax_error::execute(struct 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 new file mode 100644 index 00000000000..bfd38d34889 --- /dev/null +++ b/server-tools/instance-manager/commands.h @@ -0,0 +1,215 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "command.h" +#include "instance.h" +#include "parse.h" + +/* + Print all instances of this instance manager. + Grammar: SHOW ISTANCES +*/ + +class Show_instances : public Command +{ +public: + Show_instances(Instance_map *instance_map_arg): Command(instance_map_arg) + {} + + int execute(struct st_net *net, ulong connection_id); +}; + + +/* + Reread configuration file and refresh instance map. + Grammar: FLUSH INSTANCES +*/ + +class Flush_instances : public Command +{ +public: + Flush_instances(Instance_map *instance_map_arg): Command(instance_map_arg) + {} + + int execute(struct st_net *net, ulong connection_id); +}; + + +/* + Print status of an instance. + Grammar: SHOW ISTANCE STATUS <instance_name> +*/ + +class Show_instance_status : public Command +{ +public: + + 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; +}; + + +/* + Print options if chosen instance. + Grammar: SHOW INSTANCE OPTIONS <instance_name> +*/ + +class Show_instance_options : public Command +{ +public: + + Show_instance_options(Instance_map *instance_map_arg, + const char *name, uint len); + + int execute(struct st_net *net, ulong connection_id); + const char *instance_name; +}; + + +/* + Start an instance. + Grammar: START INSTANCE <instance_name> +*/ + +class Start_instance : public Command +{ +public: + Start_instance(Instance_map *instance_map_arg, const char *name, uint len); + + int execute(struct st_net *net, ulong connection_id); + const char *instance_name; + Instance *instance; +}; + + +/* + Stop an instance. + Grammar: STOP INSTANCE <instance_name> +*/ + +class Stop_instance : public Command +{ +public: + Stop_instance(Instance_map *instance_map_arg, const char *name, uint len); + + Instance *instance; + int execute(struct st_net *net, ulong connection_id); + const char *instance_name; +}; + + +/* + Print requested part of the log + Grammar: + SHOW <instance_name> log {ERROR | SLOW | GENERAL} size[, offset_from_end] +*/ + +class Show_instance_log : public Command +{ +public: + + 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); + 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 +{ +public: + + 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; +}; + + +/* + 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. +*/ + +class Syntax_error : public Command +{ +public: + int execute(struct st_net *net, ulong connection_id); +}; + +/* + Set an option for the instance. + Grammar: SET instance_name.option=option_value +*/ + +class Set_option : public Command +{ +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); +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]; +}; + + +/* + Remove option of the instance from config file + Grammar: UNSET instance_name.option +*/ + +class Unset_option: public Set_option +{ +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); +}; + + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H */ diff --git a/server-tools/instance-manager/guardian.cc b/server-tools/instance-manager/guardian.cc new file mode 100644 index 00000000000..24844e05776 --- /dev/null +++ b/server-tools/instance-manager/guardian.cc @@ -0,0 +1,435 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "guardian.h" + +#include "instance_map.h" +#include "instance.h" +#include "mysql_manager_error.h" +#include "log.h" +#include "portability.h" + +#include <string.h> +#include <sys/types.h> +#include <signal.h> + + + +pthread_handler_t guardian(void *arg) +{ + Guardian_thread *guardian_thread= (Guardian_thread *) arg; + guardian_thread->run(); + return 0; +} + +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) +{ + 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() +{ + /* 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); + pthread_mutex_destroy(&LOCK_guardian); + pthread_cond_destroy(&COND_guardian); +} + + +void Guardian_thread::request_shutdown(bool stop_instances_arg) +{ + pthread_mutex_lock(&LOCK_guardian); + /* stop instances or just clean up Guardian repository */ + stop_instances(stop_instances_arg); + shutdown_requested= TRUE; + pthread_mutex_unlock(&LOCK_guardian); +} + + +void Guardian_thread::process_instance(Instance *instance, + GUARD_NODE *current_node, + LIST **guarded_instances, + LIST *node) +{ + uint waitchild= (uint) Instance::DEFAULT_SHUTDOWN_DELAY; + /* The amount of times, Guardian attempts to restart an instance */ + int restart_retry= 100; + time_t current_time= time(NULL); + + if (current_node->state == STOPPING) + { + /* this brach is executed during shutdown */ + if (instance->options.shutdown_delay_val) + waitchild= instance->options.shutdown_delay_val; + + /* this returns true if and only if an instance was stopped for sure */ + if (instance->is_crashed()) + *guarded_instances= list_delete(*guarded_instances, node); + else if ( (uint) (current_time - current_node->last_checked) > waitchild) + { + instance->kill_instance(SIGKILL); + /* + 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= list_delete(*guarded_instances, node); + } + + return; + } + + if (instance->is_running()) + { + /* clear status fields */ + current_node->restart_counter= 0; + current_node->crash_moment= 0; + current_node->state= STARTED; + } + else + { + switch (current_node->state) { + case NOT_STARTED: + instance->start(); + current_node->last_checked= current_time; + log_info("guardian: starting instance %s", + instance->options.instance_name); + current_node->state= STARTING; + break; + case STARTED: /* fallthrough */ + case STARTING: /* let the instance start or crash */ + if (instance->is_crashed()) + { + current_node->crash_moment= current_time; + current_node->last_checked= current_time; + current_node->state= JUST_CRASHED; + /* fallthrough -- restart an instance immediately */ + } + else + break; + case JUST_CRASHED: + if (current_time - current_node->crash_moment <= 2) + { + if (instance->is_crashed()) + { + instance->start(); + log_info("guardian: starting instance %s", + instance->options.instance_name); + } + } + 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", + instance->options.instance_name); + } + } + else + current_node->state= CRASHED_AND_ABANDONED; + } + break; + case CRASHED_AND_ABANDONED: + break; /* do nothing */ + default: + DBUG_ASSERT(0); + } + } +} + + +/* + Run guardian thread + + SYNOPSYS + run() + + DESCRIPTION + + Check for all guarded instances and restart them if needed. If everything + is fine go and sleep for some time. +*/ + +void Guardian_thread::run() +{ + Instance *instance; + LIST *node; + struct timespec timeout; + + thread_registry.register_thread(&thread_info); + + my_thread_init(); + pthread_mutex_lock(&LOCK_guardian); + + /* loop, until all instances were shut down at the end */ + while (!(shutdown_requested && (guarded_instances == NULL))) + { + node= guarded_instances; + + while (node != NULL) + { + struct timespec timeout; + + GUARD_NODE *current_node= (GUARD_NODE *) node->data; + instance= ((GUARD_NODE *) node->data)->instance; + process_instance(instance, current_node, &guarded_instances, node); + + node= node->next; + } + timeout.tv_sec= time(NULL) + monitoring_interval; + timeout.tv_nsec= 0; + + /* check the loop predicate before sleeping */ + if (!(shutdown_requested && (!(guarded_instances)))) + thread_registry.cond_timedwait(&thread_info, &COND_guardian, + &LOCK_guardian, &timeout); + } + + 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(); +} + + +int Guardian_thread::is_stopped() +{ + int var; + pthread_mutex_lock(&LOCK_guardian); + var= stopped; + pthread_mutex_unlock(&LOCK_guardian); + return var; +} + + +/* + Initialize the list of guarded instances: loop through the Instance_map and + add all of the instances, which don't have 'nonguarded' option specified. + + SYNOPSYS + Guardian_thread::init() + + NOTE: One should always lock guardian before calling this routine. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Guardian_thread::init() +{ + Instance *instance; + Instance_map::Iterator iterator(instance_map); + + /* clear the list of guarded instances */ + free_root(&alloc, MYF(0)); + init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); + guarded_instances= NULL; + + while ((instance= iterator.next())) + { + if (!(instance->options.nonguarded)) + if (guard(instance, TRUE)) /* do not lock guardian */ + return 1; + } + + return 0; +} + + +/* + Add instance to the Guardian list + + SYNOPSYS + guard() + instance the instance to be guarded + nolock whether we prefer do not lock Guardian here, + but use external locking instead + + DESCRIPTION + + The instance is added to the guarded instances list. Usually guard() is + called after we start an instance. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Guardian_thread::guard(Instance *instance, bool nolock) +{ + 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; +} + + +/* + 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). +*/ + +int Guardian_thread::stop_guard(Instance *instance) +{ + LIST *node; + + pthread_mutex_lock(&LOCK_guardian); + node= guarded_instances; + + while (node != NULL) + { + /* + We compare only pointers, as we always use pointers from the + instance_map's MEM_ROOT. + */ + if (((GUARD_NODE *) node->data)->instance == instance) + { + guarded_instances= list_delete(guarded_instances, node); + pthread_mutex_unlock(&LOCK_guardian); + return 0; + } + else + node= node->next; + } + 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 + stop_instances() + stop_instances_arg whether we should stop instances at shutdown + + DESCRIPTION + Loops through the guarded_instances list and prepares them for shutdown. + If stop_instances was requested, we need to issue a stop command and change + the state accordingly. Otherwise we simply delete an entry. + + NOTE + Guardian object should be locked by the calling function. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Guardian_thread::stop_instances(bool stop_instances_arg) +{ + LIST *node; + node= guarded_instances; + while (node != NULL) + { + if (!stop_instances_arg) + { + /* just forget about an instance */ + guarded_instances= list_delete(guarded_instances, node); + /* + This should still work fine, as we have only removed the + node from the list. The pointer to the next one is still valid + */ + node= node->next; + } + else + { + GUARD_NODE *current_node= (GUARD_NODE *) node->data; + /* + If instance is running or was running (and now probably hanging), + request stop. + */ + if (current_node->instance->is_running() || + (current_node->state == STARTED)) + { + current_node->state= STOPPING; + current_node->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; + } + } + return 0; +} + + +void Guardian_thread::lock() +{ + pthread_mutex_lock(&LOCK_guardian); +} + + +void Guardian_thread::unlock() +{ + pthread_mutex_unlock(&LOCK_guardian); +} diff --git a/server-tools/instance-manager/guardian.h b/server-tools/instance-manager/guardian.h new file mode 100644 index 00000000000..16b4c373c91 --- /dev/null +++ b/server-tools/instance-manager/guardian.h @@ -0,0 +1,123 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "thread_registry.h" + +#include <my_sys.h> +#include <my_list.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +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 +{ +public: + /* states of an instance */ + enum enum_instance_state { NOT_STARTED= 1, STARTING, STARTED, JUST_CRASHED, + CRASHED, CRASHED_AND_ABANDONED, STOPPING }; + + /* + The Guardian list node structure. Guardian utilizes it to store + guarded instances plus some additional info. + */ + + 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 */ + void request_shutdown(bool stop_instances); + /* 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(); + void lock(); + void unlock(); + +public: + pthread_cond_t COND_guardian; + +private: + /* Prepares Guardian shutdown. Stops instances is needed */ + int stop_instances(bool stop_instances_arg); + /* check instance state and act accordingly */ + void process_instance(Instance *instance, GUARD_NODE *current_node, + LIST **guarded_instances, LIST *elem); + int stopped; + +private: + pthread_mutex_t LOCK_guardian; + Thread_info thread_info; + LIST *guarded_instances; + MEM_ROOT alloc; + enum { MEM_ROOT_BLOCK_SIZE= 512 }; + /* this variable is set to TRUE when we want to stop Guardian thread */ + bool shutdown_requested; +}; + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */ diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc new file mode 100644 index 00000000000..2ed369ba245 --- /dev/null +++ b/server-tools/instance-manager/instance.cc @@ -0,0 +1,608 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "instance.h" + +#include "mysql_manager_error.h" +#include "log.h" +#include "instance_map.h" +#include "priv.h" +#include "portability.h" +#ifndef __WIN__ +#include <sys/wait.h> +#endif +#include <my_sys.h> +#include <signal.h> +#include <m_string.h> +#include <mysql.h> + + +static void start_and_monitor_instance(Instance_options *old_instance_options, + Instance_map *instance_map); + +#ifndef __WIN__ +typedef pid_t My_process_info; +#else +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 + wait_process() + pi Pointer to the process information structure + (platform-dependent). + + RETURN + 0 - Success + 1 - Error +*/ + +#ifndef __WIN__ +static int wait_process(My_process_info *pi) +{ + /* + Here we wait for the child created. This process differs for systems + running LinuxThreads and POSIX Threads compliant systems. This is because + according to POSIX we could wait() for a child in any thread of the + process. While LinuxThreads require that wait() is called by the thread, + which created the child. + On the other hand we could not expect mysqld to return the pid, we + got in from fork(), to wait4() fucntion when running on LinuxThreads. + This is because MySQL shutdown thread is not the one, which was created + by our fork() call. + So basically we have two options: whether the wait() call returns only in + the creator thread, but we cannot use waitpid() since we have no idea + which pid we should wait for (in fact it should be the pid of shutdown + 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) + wait(NULL); /* LinuxThreads were detected */ + else + waitpid(*pi, NULL, 0); + + return 0; +} +#else +static int wait_process(My_process_info *pi) +{ + /* Wait until child process exits. */ + WaitForSingleObject(pi->hProcess, INFINITE); + + DWORD exitcode; + ::GetExitCodeProcess(pi->hProcess, &exitcode); + + /* Close process and thread handles. */ + CloseHandle(pi->hProcess); + CloseHandle(pi->hThread); + + /* + GetExitCodeProces returns zero on failure. We should revert this value + to report an error. + */ + return (!exitcode); +} +#endif + + +/* + Launch an instance + + SYNOPSYS + start_process() + instance_options Pointer to the options of the instance to be + launched. + pi Pointer to the process information structure + (platform-dependent). + + RETURN + 0 - Success + 1 - Cannot create an instance +*/ + +#ifndef __WIN__ +static int start_process(Instance_options *instance_options, + My_process_info *pi) +{ +#ifndef __QNX__ + *pi= fork(); +#else + /* + On QNX one cannot use fork() in multithreaded environment and we + should use spawn() or one of it's siblings instead. + Here we use spawnv(), which is a combination of fork() and execv() + in one call. It returns the pid of newly created process (>0) or -1 + */ + *pi= spawnv(P_NOWAIT, instance_options->mysqld_path, instance_options->argv); +#endif + + switch (*pi) { + case 0: /* never happens on QNX */ + execv(instance_options->mysqld_path, instance_options->argv); + /* exec never returns */ + exit(1); + case -1: + log_info("cannot create a new process to start instance %s", + instance_options->instance_name); + return 1; + } + return 0; +} +#else +static int start_process(Instance_options *instance_options, + My_process_info *pi) +{ + STARTUPINFO si; + + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb= sizeof(STARTUPINFO); + ZeroMemory(pi, sizeof(PROCESS_INFORMATION)); + + int cmdlen= 0; + for (int i= 0; instance_options->argv[i] != 0; i++) + cmdlen+= strlen(instance_options->argv[i]) + 3; + cmdlen++; /* make room for the null */ + + char *cmdline= new char[cmdlen]; + if (cmdline == NULL) + return 1; + + cmdline[0]= 0; + for (int i= 0; instance_options->argv[i] != 0; i++) + { + strcat(cmdline, "\""); + strcat(cmdline, instance_options->argv[i]); + strcat(cmdline, "\" "); + } + + /* Start the child process */ + BOOL result= + CreateProcess(NULL, /* Put it all in cmdline */ + cmdline, /* Command line */ + NULL, /* Process handle not inheritable */ + NULL, /* Thread handle not inheritable */ + FALSE, /* Set handle inheritance to FALSE */ + 0, /* No creation flags */ + NULL, /* Use parent's environment block */ + NULL, /* Use parent's starting directory */ + &si, /* Pointer to STARTUPINFO structure */ + pi); /* Pointer to PROCESS_INFORMATION structure */ + delete cmdline; + + return (!result); +} +#endif + +/* + Fork child, exec an instance and monitor it. + + 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. + + 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. + + RETURN + Function returns no value +*/ + +static void start_and_monitor_instance(Instance_options *old_instance_options, + Instance_map *instance_map) +{ + 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; + + /* + Lock instance map to guarantee that no instances are deleted during + strmake() and execv() calls. + */ + instance_map->lock(); + + /* + Save the instance name in the case if Instance object we + are using is destroyed. (E.g. by "FLUSH INSTANCES") + */ + strmake(instance_name_buff, old_instance_options->instance_name, + MAX_INSTANCE_NAME_LEN - 1); + instance_name_len= old_instance_options->instance_name_len; + + log_info("starting instance %s", instance_name_buff); + + if (start_process(old_instance_options, &process_info)) + { + instance_map->unlock(); + return; /* error is logged */ + } + + /* allow users to delete instances */ + instance_map->unlock(); + + /* don't check for return value */ + wait_process(&process_info); + + current_instance= instance_map->find(instance_name_buff, instance_name_len); + + if (current_instance) + current_instance->set_crash_flag_n_wake_all(); + + return; +} + + +Instance_map *Instance::get_map() +{ + return instance_map; +} + + +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 %i, this might be \ + since IM lacks permmissions or hasn't found the pidifle", + options.instance_name); +} + + +/* + The method starts an instance. + + SYNOPSYS + start() + + RETURN + 0 ok + ER_CANNOT_START_INSTANCE Cannot start instance + ER_INSTANCE_ALREADY_STARTED The instance on the specified port/socket + is already started +*/ + +int Instance::start() +{ + /* clear crash flag */ + pthread_mutex_lock(&LOCK_instance); + crashed= 0; + pthread_mutex_unlock(&LOCK_instance); + + + if (!is_running()) + { + remove_pid(); + + /* + 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; + } + + /* the instance is started already */ + return ER_INSTANCE_ALREADY_STARTED; +} + +/* + The method sets the crash flag and wakes all waiters on + COND_instance_stopped and COND_guardian + + SYNOPSYS + set_crash_flag_n_wake_all() + + DESCRIPTION + The method is called when an instance is crashed or terminated. + In the former case it might indicate that guardian probably should + restart it. + + RETURN + Function returns no value +*/ + +void Instance::set_crash_flag_n_wake_all() +{ + /* set instance state to crashed */ + pthread_mutex_lock(&LOCK_instance); + crashed= 1; + pthread_mutex_unlock(&LOCK_instance); + + /* + Wake connection threads waiting for an instance to stop. This + is needed if a user issued command to stop an instance via + mysql connection. This is not the case if Guardian stop the thread. + */ + pthread_cond_signal(&COND_instance_stopped); + /* wake guardian */ + pthread_cond_signal(&instance_map->guardian->COND_guardian); +} + + + +Instance::Instance(): crashed(0) +{ + pthread_mutex_init(&LOCK_instance, 0); + pthread_cond_init(&COND_instance_stopped, 0); +} + + +Instance::~Instance() +{ + pthread_cond_destroy(&COND_instance_stopped); + pthread_mutex_destroy(&LOCK_instance); +} + + +int Instance::is_crashed() +{ + int val; + pthread_mutex_lock(&LOCK_instance); + val= crashed; + pthread_mutex_unlock(&LOCK_instance); + return val; +} + + +bool Instance::is_running() +{ + MYSQL mysql; + uint port= 0; + 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; + + /* no port was specified => instance falled back to default value */ + if (!options.mysqld_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, + password, + NullS, port, + socket, 0)) + { + /* + 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); + return_val= TRUE; /* server is alive */ + } + else + return_val= test(!strncmp(access_denied_message, mysql_error(&mysql), + sizeof(access_denied_message) - 1)); + + mysql_close(&mysql); + pthread_mutex_unlock(&LOCK_instance); + + return return_val; +} + + +/* + Stop an instance. + + SYNOPSYS + stop() + + RETURN: + 0 ok + ER_INSTANCE_IS_NOT_STARTED Looks like the instance it is not started + ER_STOP_INSTANCE mysql_shutdown reported an error +*/ + +int Instance::stop() +{ + struct timespec timeout; + uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; + + if (is_running()) + { + if (options.shutdown_delay_val) + waitchild= options.shutdown_delay_val; + + kill_instance(SIGTERM); + /* sleep on condition to wait for SIGCHLD */ + + timeout.tv_sec= time(NULL) + waitchild; + timeout.tv_nsec= 0; + if (pthread_mutex_lock(&LOCK_instance)) + return ER_STOP_INSTANCE; + + while (options.get_pid() != 0) /* while server isn't stopped */ + { + int status; + + status= pthread_cond_timedwait(&COND_instance_stopped, + &LOCK_instance, + &timeout); + if (status == ETIMEDOUT || status == ETIME) + break; + } + + pthread_mutex_unlock(&LOCK_instance); + + kill_instance(SIGKILL); + + return 0; + } + + return ER_INSTANCE_IS_NOT_STARTED; +} + +#ifdef __WIN__ + +BOOL SafeTerminateProcess(HANDLE hProcess, UINT uExitCode) +{ + DWORD dwTID, dwCode, dwErr= 0; + HANDLE hProcessDup= INVALID_HANDLE_VALUE; + HANDLE hRT= NULL; + HINSTANCE hKernel= GetModuleHandle("Kernel32"); + BOOL bSuccess= FALSE; + + BOOL bDup= DuplicateHandle(GetCurrentProcess(), + hProcess, GetCurrentProcess(), &hProcessDup, + PROCESS_ALL_ACCESS, FALSE, 0); + + // Detect the special case where the process is + // already dead... + if (GetExitCodeProcess((bDup) ? hProcessDup : hProcess, &dwCode) && + (dwCode == STILL_ACTIVE)) + { + FARPROC pfnExitProc; + + pfnExitProc= GetProcAddress(hKernel, "ExitProcess"); + + hRT= CreateRemoteThread((bDup) ? hProcessDup : hProcess, NULL, 0, + (LPTHREAD_START_ROUTINE)pfnExitProc, + (PVOID)uExitCode, 0, &dwTID); + + if (hRT == NULL) + dwErr= GetLastError(); + } + else + dwErr= ERROR_PROCESS_ABORTED; + + if (hRT) + { + // Must wait process to terminate to + // guarantee that it has exited... + WaitForSingleObject((bDup) ? hProcessDup : hProcess, INFINITE); + + CloseHandle(hRT); + bSuccess= TRUE; + } + + if (bDup) + CloseHandle(hProcessDup); + + if (!bSuccess) + SetLastError(dwErr); + + return bSuccess; +} + +int kill(pid_t pid, int signum) +{ + HANDLE processhandle= ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if (signum == SIGTERM) + ::SafeTerminateProcess(processhandle, 0); + else + ::TerminateProcess(processhandle, -1); + return 0; +} +#endif + +void Instance::kill_instance(int signum) +{ + pid_t pid; + /* if there are no pid, everything seems to be fine */ + if ((pid= options.get_pid()) != 0) /* get pid from pidfile */ + { + /* + If we cannot kill mysqld, then it has propably crashed. + Let us try to remove staled pidfile and return successfully + as mysqld is probably stopped. + */ + if (!kill(pid, signum)) + options.unlink_pidfile(); + else if (signum == SIGKILL) /* really killed instance with SIGKILL */ + log_error("The instance %s is being stopped forsibly. Normally \ + it should not happed. Probably the instance has been \ + hanging. You should also check your IM setup", + options.instance_name); + } + return; +} + +/* + We execute this function to initialize instance parameters. + Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. +*/ + +int Instance::init(const char *name_arg) +{ + return options.init(name_arg); +} + + +int Instance::complete_initialization(Instance_map *instance_map_arg, + const char *mysqld_path, + uint instance_type) +{ + instance_map= instance_map_arg; + return options.complete_initialization(mysqld_path, instance_type); +} diff --git a/server-tools/instance-manager/instance.h b/server-tools/instance-manager/instance.h new file mode 100644 index 00000000000..adb66991685 --- /dev/null +++ b/server-tools/instance-manager/instance.h @@ -0,0 +1,69 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "instance_options.h" + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +class Instance_map; + +class Instance +{ +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(); + +public: + enum { DEFAULT_SHUTDOWN_DELAY= 35 }; + Instance_options options; + +private: + int crashed; + /* + 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. + */ + pthread_mutex_t LOCK_instance; + /* + This condition variable is used to wake threads waiting for instance to + stop in Instance::stop() + */ + pthread_cond_t COND_instance_stopped; + Instance_map *instance_map; + + void remove_pid(); +}; + +#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 new file mode 100644 index 00000000000..3b7f58d8a09 --- /dev/null +++ b/server-tools/instance-manager/instance_map.cc @@ -0,0 +1,345 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "instance_map.h" + +#include "buffer.h" +#include "instance.h" +#include "log.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. +*/ + +C_MODE_START + +/* Procedure needed for HASH initialization */ + +static byte* get_instance_key(const byte* u, uint* len, + my_bool __attribute__((unused)) t) +{ + const Instance *instance= (const Instance *) u; + *len= instance->options.instance_name_len; + return (byte *) instance->options.instance_name; +} + +static void delete_instance(void *u) +{ + Instance *instance= (Instance *) u; + delete instance; +} + +/* + The option handler to pass to the process_default_option_files finction. + + SYNOPSYS + process_option() + ctx Handler context. Here it is an instance_map structure. + group_name The name of the group the option belongs to. + option The very option to be processed. It is already + prepared to be used in argv (has -- prefix) + + DESCRIPTION + + This handler checks whether a group is an instance group and adds + an option to the appropriate instance class. If this is the first + occurence of an instance name, we'll also create the instance + with such name and add it to the instance map. + + RETURN + 0 - ok + 1 - error occured +*/ + +static int process_option(void *ctx, const char *group, const char *option) +{ + Instance_map *map= NULL; + + map = (Instance_map*) ctx; + return map->process_one_option(group, option); +} + +C_MODE_END + + +/* + Process one option from the configuration file. + + SYNOPSIS + Instance_map::process_one_option() + group group name + option option string (e.g. "--name=value") + + DESCRIPTION + This is an auxiliary function and should not be used externally. + It is used only by flush_instances(), which pass it to + process_option(). The caller ensures proper locking + of the instance map object. +*/ + +int Instance_map::process_one_option(const char *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= (Instance *) hash_search(&hash, (byte *) group, + strlen(group)))) + { + if (!(instance= new Instance)) + goto err; + if (instance->init(group) || my_hash_insert(&hash, (byte *) instance)) + goto err_instance; + } + + if (instance->options.add_option(option)) + goto err; /* the instance'll be deleted when we destroy the map */ + } + + return 0; + +err_instance: + delete instance; +err: + return 1; +} + + +Instance_map::Instance_map(const char *default_mysqld_path_arg): +mysqld_path(default_mysqld_path_arg) +{ + pthread_mutex_init(&LOCK_instance_map, 0); +} + + +int Instance_map::init() +{ + return hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, + get_instance_key, delete_instance, 0); +} + +Instance_map::~Instance_map() +{ + pthread_mutex_lock(&LOCK_instance_map); + hash_free(&hash); + pthread_mutex_unlock(&LOCK_instance_map); + pthread_mutex_destroy(&LOCK_instance_map); +} + + +void Instance_map::lock() +{ + pthread_mutex_lock(&LOCK_instance_map); +} + + +void Instance_map::unlock() +{ + pthread_mutex_unlock(&LOCK_instance_map); +} + +/* + Re-read instance configuration file. + + SYNOPSIS + Instance_map::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. + + TODO + FLUSH INSTANCES should return an error if it's called + while there is a running instance. +*/ + +int Instance_map::flush_instances() +{ + int rc; + + /* + 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(); + guardian->init(); // TODO: check error status. + pthread_mutex_unlock(&LOCK_instance_map); + guardian->unlock(); + return rc; +} + + +Instance * +Instance_map::find(const char *name, uint name_len) +{ + 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; +} + + +int Instance_map::complete_initialization() +{ + Instance *instance; + uint i= 0; + + + if (hash.records == 0) /* no instances found */ + { + if ((instance= new Instance) == 0) + goto err; + + if (instance->init("mysqld") || my_hash_insert(&hash, (byte *) instance)) + goto err_instance; + + /* + After an instance have been added to the instance_map, + hash_free should handle it's deletion => goto err, not + err_instance. + */ + if (instance->complete_initialization(this, mysqld_path, + DEFAULT_SINGLE_INSTANCE)) + goto err; + } + 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; +} + + +/* load options from config files and create appropriate instance structures */ + +int Instance_map::load() +{ + int argc= 1; + /* this is a dummy variable for search_option_files() */ + uint args_used= 0; + const char *argv_options[3]; + char **argv= (char **) &argv_options; + char defaults_file_arg[FN_REFLEN]; + + /* the name of the program may be orbitrary here in fact */ + argv_options[0]= "mysqlmanager"; + + /* + If the option file was forced by the user when starting + the IM with --defaults-file=xxxx, make sure it is also + passed as --defaults-file, not only as Options::config_file. + This is important for option files given with relative path: + e.g. --defaults-file=my.cnf. + Otherwise my_search_option_files will treat "my.cnf" as a group + 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) + { + snprintf(defaults_file_arg, FN_REFLEN, "--defaults-file=%s", + Options::config_file); + + argv_options[1]= defaults_file_arg; + argv_options[2]= '\0'; + + argc= 2; + } + else + argv_options[1]= '\0'; + + /* + If the routine failed, we'll simply fallback to defaults in + complete_initialization(). + */ + if (my_search_option_files(Options::config_file, &argc, + (char ***) &argv, &args_used, + process_option, (void*) this)) + log_info("Falling back to compiled-in defaults"); + + if (complete_initialization()) + return 1; + + return 0; +} + + +/*--- Implementaton of the Instance map iterator class ---*/ + + +void Instance_map::Iterator::go_to_first() +{ + current_instance=0; +} + + +Instance *Instance_map::Iterator::next() +{ + if (current_instance < instance_map->hash.records) + return (Instance *) hash_element(&instance_map->hash, current_instance++); + + return NULL; +} + diff --git a/server-tools/instance-manager/instance_map.h b/server-tools/instance-manager/instance_map.h new file mode 100644 index 00000000000..d3de42f4d80 --- /dev/null +++ b/server-tools/instance-manager/instance_map.h @@ -0,0 +1,90 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_MAP_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_MAP_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> + +#include "protocol.h" +#include "guardian.h" + +#include <my_sys.h> +#include <hash.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +class Instance; +extern int load_all_groups(char ***groups, const char *filename); +extern void free_groups(char **groups); + + +/* + Instance_map - stores all existing instances +*/ + +class Instance_map +{ +public: + /* Instance_map iterator */ + class Iterator + { + private: + uint current_instance; + Instance_map *instance_map; + public: + Iterator(Instance_map *instance_map_arg) : + current_instance(0), instance_map(instance_map_arg) + {} + + 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); + + 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(); + +public: + const char *mysqld_path; + Guardian_thread *guardian; + +private: + /* loads options from config files */ + int load(); + /* inits instances argv's after all options have been loaded */ + int complete_initialization(); +private: + enum { START_HASH_SIZE = 16 }; + pthread_mutex_t LOCK_instance_map; + HASH hash; +}; + +#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 new file mode 100644 index 00000000000..72621ed1662 --- /dev/null +++ b/server-tools/instance-manager/instance_options.cc @@ -0,0 +1,600 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "instance_options.h" + +#include "parse_output.h" +#include "buffer.h" +#include "log.h" + +#include <my_sys.h> +#include <signal.h> +#include <m_string.h> + +#ifdef __WIN__ +#define NEWLINE_LEN 2 +#else +#define NEWLINE_LEN 1 +#endif + + +/* 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) +{ + int position= 0; + + if (buf->get_size()) /* malloc succeeded */ + { +#ifdef __WIN__ + buf->append(position++, "\"", 1); +#endif + buf->append(position, mysqld_path_str, mysqld_path_len); + position+= mysqld_path_len; +#ifdef __WIN__ + buf->append(position++, "\"", 1); +#endif + /* here the '\0' character is copied from the option string */ + buf->append(position, option, option_len); + + return buf->is_error(); + } + return 1; +} + + +/* + Get compiled-in value of default_option + + SYNOPSYS + get_default_option() + result buffer to put found value + result_len buffer size + option_name the name of the option, prefixed with "--" + + DESCRIPTION + + Get compile-in value of requested option from server + + RETURN + 0 - ok + 1 - error occured +*/ + + +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"; + + /* reserve space fot the path + option + final '\0' */ + Buffer cmd(mysqld_path_len + sizeof(verbose_option)); + + if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, + verbose_option, sizeof(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, + result, result_len, GET_VALUE); +err: + return rc; +} + + +/* + Fill mysqld_version option (used at initialization stage) + + SYNOPSYS + fill_instance_version() + + DESCRIPTION + + Get mysqld version string from "mysqld --version" output. + + RETURN + 0 - ok + 1 - error occured +*/ + +int 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)); + + if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, + version_option, sizeof(version_option))) + goto err; + + bzero(result, MAX_VERSION_STRING_LENGTH); + + rc= parse_output_and_get_value(cmd.buffer, mysqld_real_path, + result, MAX_VERSION_STRING_LENGTH, + GET_LINE); + + 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); + } +err: + if (rc) + log_error("fill_instance_version: Failed to get version of '%s'", + mysqld_path); + return rc; +} + + +/* + Fill mysqld_real_path + + SYNOPSYS + fill_mysqld_real_path() + + DESCRIPTION + + Get the real path to mysqld from "mysqld --help" output. + Will print the realpath of mysqld between "Usage: " and "[OPTIONS]" + + This is needed if the mysqld_path variable is pointing at a + script(for example libtool) or a symlink. + + RETURN + 0 - ok + 1 - error occured +*/ + +int 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)); + + if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, + help_option, sizeof(help_option))) + goto err; + + bzero(result, FN_REFLEN); + + rc= parse_output_and_get_value(cmd.buffer, "Usage: ", + result, FN_REFLEN, + GET_LINE); + + 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); + } +err: + if (rc) + log_error("fill_mysqld_real_path: Failed to get real path of mysqld"); + return rc; +} + + +/* + Fill various log options + + SYNOPSYS + fill_log_options() + + DESCRIPTION + + Compute paths to enabled log files. If the path is not specified in the + instance explicitly (I.e. log=/home/user/mysql.log), we try to guess the + file name and placement. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Instance_options::fill_log_options() +{ + Buffer buff; + uint position= 0; + char **tmp_argv= argv; + enum { MAX_LOG_OPTION_LENGTH= 256 }; + char datadir[MAX_LOG_OPTION_LENGTH]; + char hostname[MAX_LOG_OPTION_LENGTH]; + uint hostname_length; + struct log_files_st + { + const char *name; + uint length; + char **value; + const char *default_suffix; + } logs_st[]= + { + {"--log-error", 11, &(logs[IM_LOG_ERROR]), ".err"}, + {"--log", 5, &(logs[IM_LOG_GENERAL]), ".log"}, + {"--log-slow-queries", 18, &(logs[IM_LOG_SLOW]), "-slow.log"}, + {NULL, 0, NULL, NULL} + }; + struct log_files_st *log_files; + + /* compute hostname and datadir for the instance */ + if (mysqld_datadir == NULL) + { + if (get_default_option(datadir, MAX_LOG_OPTION_LENGTH, "--datadir")) + goto err; + } + else + { + /* below is safe, as --datadir always has a value */ + strmake(datadir, + strchr(mysqld_datadir, '=') + 1, 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= strlen(hostname); + + + for (log_files= logs_st; log_files->name; log_files++) + { + for (int i=0; (argv[i] != 0); i++) + { + if (!strncmp(argv[i], log_files->name, log_files->length)) + { + /* + This is really log_files->name option if and only if it is followed + by '=', '\0' or space character. This way we can distinguish such + options as '--log' and '--log-bin'. This is checked in the following + two statements. + */ + if (argv[i][log_files->length] == '\0' || + my_isspace(default_charset_info, argv[i][log_files->length])) + { + char full_name[MAX_LOG_OPTION_LENGTH]; + + fn_format(full_name, hostname, datadir, "", + MY_UNPACK_FILENAME | MY_SAFE_PATH); + + + if ((MAX_LOG_OPTION_LENGTH - strlen(full_name)) <= + strlen(log_files->default_suffix)) + goto err; + + strmov(full_name + strlen(full_name), log_files->default_suffix); + + /* + If there were specified two identical logfiles options, + we would loose some memory in MEM_ROOT here. However + this situation is not typical. + */ + *(log_files->value)= strdup_root(&alloc, full_name); + } + + if (argv[i][log_files->length] == '=') + { + char full_name[MAX_LOG_OPTION_LENGTH]; + + fn_format(full_name, argv[i] +log_files->length + 1, + datadir, "", MY_UNPACK_FILENAME | MY_SAFE_PATH); + + if (!(*(log_files->value)= strdup_root(&alloc, full_name))) + goto err; + } + } + } + } + + return 0; +err: + return 1; +} + + +/* + Get the full pid file name with path + + SYNOPSYS + get_pid_filaname() + result buffer to sotre the pidfile value + + IMPLEMENTATION + Get the data directory, then get the pid filename + (which is always set for an instance), then load the + full path with my_load_path(). It takes into account + whether it is already an absolute path or it should be + prefixed with the datadir and so on. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Instance_options::get_pid_filename(char *result) +{ + const char *pid_file= mysqld_pid_file; + char datadir[MAX_PATH_LEN]; + + if (mysqld_datadir == NULL) + { + /* we might get an error here if we have wrong path to the mysqld binary */ + if (get_default_option(datadir, sizeof(datadir), "--datadir")) + 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; + + /* get the full path to the pidfile */ + my_load_path(result, pid_file, datadir); + return 0; +} + + +int Instance_options::unlink_pidfile() +{ + return unlink(pid_file_with_path); +} + + +pid_t Instance_options::get_pid() +{ + FILE *pid_file_stream; + + /* get the pid */ + if ((pid_file_stream= my_fopen(pid_file_with_path, + O_RDONLY | O_BINARY, MYF(0))) != NULL) + { + pid_t pid; + + fscanf(pid_file_stream, "%i", &pid); + my_fclose(pid_file_stream, MYF(0)); + return pid; + } + return 0; +} + + +int Instance_options::complete_initialization(const char *default_path, + uint instance_type) +{ + const char *tmp; + char *end; + + if (!mysqld_path) + { + // Need one extra byte, as convert_dirname() adds a slash at the end. + if (!(mysqld_path= alloc_root(&alloc, strlen(default_path) + 2))) + goto err; + strcpy((char *)mysqld_path, default_path); + } + + // 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_len= strlen(mysqld_path); + + if (mysqld_port) + mysqld_port_val= atoi(strchr(mysqld_port, '=') + 1); + + if (shutdown_delay) + shutdown_delay_val= atoi(shutdown_delay); + + if (!(tmp= strdup_root(&alloc, "--no-defaults"))) + goto err; + + if (!(mysqld_pid_file)) + { + char pidfilename[MAX_PATH_LEN]; + char hostname[MAX_PATH_LEN]; + + /* + 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); + else + strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, + "-", hostname, ".pid", NullS); + } + else + { + if (instance_type & DEFAULT_SINGLE_INSTANCE) + strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", "mysql", + ".pid", NullS); + else + strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, + ".pid", NullS); + } + + add_option(pidfilename); + } + + if (get_pid_filename(pid_file_with_path)) + goto err; + + /* we need to reserve space for the final zero + possible default options */ + if (!(argv= (char**) + alloc_root(&alloc, (options_array.elements + 1 + + MAX_NUMBER_OF_DEFAULT_OPTIONS) * sizeof(char*)))) + goto err; + + /* the path must be first in the argv */ + if (add_to_argv(mysqld_path)) + goto err; + + if (add_to_argv(tmp)) + goto err; + + memcpy((gptr) (argv + filled_default_options), options_array.buffer, + options_array.elements*sizeof(char*)); + argv[filled_default_options + options_array.elements]= 0; + + if (fill_log_options() || fill_mysqld_real_path() || fill_instance_version()) + goto err; + + return 0; + +err: + return 1; +} + + +/* + Assigns given value to the appropriate option from the class. + + SYNOPSYS + add_option() + option string with the option prefixed by -- + + DESCRIPTION + + The method is called from the option handling routine. + + RETURN + 0 - ok + 1 - error occured +*/ + +int Instance_options::add_option(const char* option) +{ + char *tmp; + enum { SAVE_VALUE= 1, SAVE_WHOLE, SAVE_WHOLE_AND_ADD }; + struct selected_options_st + { + const char *name; + uint length; + const char **value; + uint type; + } options[]= + { + {"--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} + }; + struct selected_options_st *selected_options; + + if (!(tmp= strdup_root(&alloc, option))) + goto err; + + 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; + +err: + return 1; +} + + +int Instance_options::add_to_argv(const char* option) +{ + DBUG_ASSERT(filled_default_options < MAX_NUMBER_OF_DEFAULT_OPTIONS); + + if (option) + argv[filled_default_options++]= (char*) option; + return 0; +} + + +/* function for debug purposes */ +void Instance_options::print_argv() +{ + int i; + printf("printing out an instance %s argv:\n", instance_name); + for (i=0; argv[i] != NULL; i++) + printf("argv: %s\n", argv[i]); +} + + +/* + We execute this function to initialize some options. + Return value: 0 - ok. 1 - unable to allocate memory. +*/ + +int Instance_options::init(const char *instance_name_arg) +{ + instance_name_len= strlen(instance_name_arg); + + init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); + + if (my_init_dynamic_array(&options_array, sizeof(char*), 0, 32)) + goto err; + + if (!(instance_name= strmake_root(&alloc, (char*) instance_name_arg, + instance_name_len))) + goto err; + + return 0; + +err: + return 1; +} + + +Instance_options::~Instance_options() +{ + free_root(&alloc, MYF(0)); + delete_dynamic(&options_array); +} + diff --git a/server-tools/instance-manager/instance_options.h b/server-tools/instance-manager/instance_options.h new file mode 100644 index 00000000000..b316dbf00fc --- /dev/null +++ b/server-tools/instance-manager/instance_options.h @@ -0,0 +1,109 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_sys.h> +#include "parse.h" +#include "portability.h" + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + + +/* + This class contains options of an instance and methods to operate them. + + We do not provide this class with the means of synchronization as it is + supposed that options for instances are all loaded at once during the + instance_map initilization and we do not change them later. This way we + 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) + {} + ~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(); + int get_pid_filename(char *result); + int unlink_pidfile(); + void print_argv(); + +public: + /* + We need this value to be greater or equal then FN_REFLEN found in + my_global.h to use my_load_path() + */ + 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; + /* + Here we cache the version string, obtained from mysqld --version. + In the case when mysqld binary is not found we get NULL here. + */ + const char *mysqld_version; + /* 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; + 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(); + int add_to_argv(const char *option); + int get_default_option(char *result, size_t result_len, + const char *option_name); +private: + uint filled_default_options; + MEM_ROOT alloc; +}; + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H */ diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc new file mode 100644 index 00000000000..500b25bec03 --- /dev/null +++ b/server-tools/instance-manager/listener.cc @@ -0,0 +1,391 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "listener.h" +#include "priv.h" +#include <m_string.h> +#include <mysql.h> +#include <violite.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 "portability.h" + + +/* + Listener_thread - incapsulates listening functionality +*/ + +class Listener_thread: public Listener_thread_args +{ +public: + Listener_thread(const Listener_thread_args &args); + ~Listener_thread(); + void run(); +private: + static const int LISTEN_BACK_LOG_SIZE= 5; /* standard backlog size */ + ulong total_connection_count; + Thread_info thread_info; + + int sockets[2]; + int num_sockets; + fd_set read_fds; +private: + void handle_new_mysql_connection(Vio *vio); + int create_tcp_socket(); + int create_unix_socket(struct sockaddr_un &unix_socket_address); +}; + + +Listener_thread::Listener_thread(const Listener_thread_args &args) : + Listener_thread_args(args.thread_registry, args.options, args.user_map, + args.instance_map) + ,total_connection_count(0) + ,thread_info(pthread_self()) + ,num_sockets(0) +{ +} + + +Listener_thread::~Listener_thread() +{ +} + + +/* + Listener_thread::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 + one day. + See also comments in mysqlmanager.cc to picture general Instance Manager + architecture. +*/ + +void Listener_thread::run() +{ + int i, n= 0; + +#ifndef __WIN__ + /* we use this var to check whether we are running on LinuxThreads */ + pid_t thread_pid; + + thread_pid= getpid(); + + struct sockaddr_un unix_socket_address; + /* set global variable */ + linuxthreads= (thread_pid != manager_pid); +#endif + + thread_registry.register_thread(&thread_info); + + my_thread_init(); + + FD_ZERO(&read_fds); + + /* I. prepare 'listen' sockets */ + if (create_tcp_socket()) + goto err; + +#ifndef __WIN__ + if (create_unix_socket(unix_socket_address)) + goto err; +#endif + + /* II. Listen sockets and spawn childs */ + for (i= 0; i < num_sockets; i++) + n= max(n, sockets[i]); + n++; + + timeval tv; + while (!thread_registry.is_shutdown()) + { + fd_set read_fds_arg= read_fds; + /* + We should reintialize timer as on linux it is modified + to reflect amount of time not slept. + */ + tv.tv_sec= 0; + tv.tv_usec= 100000; + + /* + When using valgrind 2.0 this syscall doesn't get kicked off by a + signal during shutdown. This results in failing assert + (Thread_registry::~Thread_registry). Valgrind 2.2 works fine. + */ + int rc= select(n, &read_fds_arg, 0, 0, &tv); + + if (rc == 0 || rc == -1) + { + if (rc == -1 && errno != EINTR) + log_error("Listener_thread::run(): select() failed, %s", + strerror(errno)); + continue; + } + + + for (int socket_index= 0; socket_index < num_sockets; socket_index++) + { + /* Assuming that rc > 0 as we asked to wait forever */ + if (FD_ISSET(sockets[socket_index], &read_fds_arg)) + { + int client_fd= accept(sockets[socket_index], 0, 0); + /* accept may return -1 (failure or spurious wakeup) */ + if (client_fd >= 0) // connection established + { + Vio *vio= vio_new(client_fd, socket_index == 0 ? + VIO_TYPE_SOCKET : VIO_TYPE_TCPIP, + socket_index == 0 ? 1 : 0); + if (vio != 0) + handle_new_mysql_connection(vio); + else + { + shutdown(client_fd, SHUT_RDWR); + close(client_fd); + } + } + } + } + } + + /* III. Release all resources and exit */ + + log_info("Listener_thread::run(): shutdown requested, exiting..."); + + for (i= 0; i < num_sockets; i++) + close(sockets[i]); + +#ifndef __WIN__ + unlink(unix_socket_address.sun_path); +#endif + + thread_registry.unregister_thread(&thread_info); + my_thread_end(); + return; + +err: + // we have to close the ip sockets in case of error + for (i= 0; i < num_sockets; i++) + close(sockets[i]); + + thread_registry.unregister_thread(&thread_info); + thread_registry.request_shutdown(); + my_thread_end(); + return; +} + +void set_non_blocking(int socket) +{ +#ifndef __WIN__ + int flags= fcntl(socket, F_GETFL, 0); + fcntl(socket, F_SETFL, flags | O_NONBLOCK); +#else + u_long arg= 1; + ioctlsocket(socket, FIONBIO, &arg); +#endif +} + +void set_no_inherit(int socket) +{ +#ifndef __WIN__ + int flags= fcntl(socket, F_GETFD, 0); + fcntl(socket, F_SETFD, flags | FD_CLOEXEC); +#endif +} + +int Listener_thread::create_tcp_socket() +{ + /* value to be set by setsockopt */ + int arg= 1; + + int ip_socket= socket(AF_INET, SOCK_STREAM, 0); + if (ip_socket == INVALID_SOCKET) + { + log_error("Listener_thead::run(): socket(AF_INET) failed, %s", + strerror(errno)); + return -1; + } + + struct sockaddr_in ip_socket_address; + bzero(&ip_socket_address, sizeof(ip_socket_address)); + + ulong im_bind_addr; + if (options.bind_address != 0) + { + if ((im_bind_addr= (ulong) inet_addr(options.bind_address)) == INADDR_NONE) + im_bind_addr= htonl(INADDR_ANY); + } + else + im_bind_addr= htonl(INADDR_ANY); + uint im_port= options.port_number; + + ip_socket_address.sin_family= AF_INET; + ip_socket_address.sin_addr.s_addr= im_bind_addr; + + + ip_socket_address.sin_port= (unsigned short) + htons((unsigned short) im_port); + + setsockopt(ip_socket, SOL_SOCKET, SO_REUSEADDR, (char*) &arg, sizeof(arg)); + 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)); + close(ip_socket); + return -1; + } + + if (listen(ip_socket, LISTEN_BACK_LOG_SIZE)) + { + log_error("Listener_thread::run(): listen(ip socket) failed, %s", + strerror(errno)); + close(ip_socket); + return -1; + } + + /* set the socket nonblocking */ + set_non_blocking(ip_socket); + + /* make sure that instances won't be listening our sockets */ + set_no_inherit(ip_socket); + + FD_SET(ip_socket, &read_fds); + sockets[num_sockets++]= ip_socket; + log_info("accepting connections on ip socket"); + return 0; +} + +#ifndef __WIN__ +int Listener_thread:: +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)); + 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, + sizeof(unix_socket_address.sun_path)); + unlink(unix_socket_address.sun_path); // in case we have stale socket file + + /* + POSIX specifies default permissions for a pathname created by bind + to be 0777. We need everybody to have access to the socket. + */ + mode_t old_mask= umask(0); + 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)); + close(unix_socket); + return -1; + } + + umask(old_mask); + + if (listen(unix_socket, LISTEN_BACK_LOG_SIZE)) + { + log_error("Listener_thread::run(): listen(unix socket) failed, %s", + strerror(errno)); + close(unix_socket); + return -1; + } + + /* set the socket nonblocking */ + set_non_blocking(unix_socket); + + /* 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); + sockets[num_sockets++]= unix_socket; + FD_SET(unix_socket, &read_fds); + return 0; +} +#endif + + +/* + Create new mysql connection. Created thread is responsible for deletion of + the Mysql_connection_thread_args and Vio instances passed to it. + SYNOPSYS + handle_new_mysql_connection() +*/ + +void Listener_thread::handle_new_mysql_connection(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) + ) + { + /* + 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 + vio_delete(vio); +} + + +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 new file mode 100644 index 00000000000..28ccbf91731 --- /dev/null +++ b/server-tools/instance-manager/listener.h @@ -0,0 +1,52 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_pthread.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 +{ + 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) + {} +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_LISTENER_H diff --git a/server-tools/instance-manager/log.cc b/server-tools/instance-manager/log.cc new file mode 100644 index 00000000000..3f54bc0649a --- /dev/null +++ b/server-tools/instance-manager/log.cc @@ -0,0 +1,169 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> + +#include "log.h" +#include "portability.h" +#include <stdarg.h> +#include <m_string.h> +#include <my_sys.h> + +/* + TODO: + - add flexible header support + - rewrite all fprintf with fwrite + - think about using 'write' instead of fwrite/fprintf on POSIX systems +*/ + +/* + Format log entry and write it to the given stream. + SYNOPSYS + log() +*/ + +static inline void log(FILE *file, const char *format, va_list args) +{ + /* + log() should be thread-safe; it implies that we either call fprintf() + once per log(), or use flockfile()/funlockfile(). But flockfile() is + POSIX, not ANSI C, so we try to vsnprintf the whole message to the + stack, and if stack buffer is not enough, to malloced string. When + message is formatted, it is fprintf()'ed to the file. + */ + + /* Format time like MYSQL_LOG does. */ + time_t now= time(0); + 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); + /* Format the message */ + char buff_stack[256]; + + int n= vsnprintf(buff_stack, sizeof(buff_stack), format, args); + /* + return value of vsnprintf can vary, according to various standards; + try to check all cases. + */ + char *buff_msg= buff_stack; + if (n < 0 || n == sizeof(buff_stack)) + { + int size= sizeof(buff_stack) * 2; + buff_msg= (char*) my_malloc(size, MYF(0)); + while (true) + { + if (buff_msg == 0) + { + strmake(buff_stack, "log(): message is too big, my_malloc() failed", + sizeof(buff_stack) - 1); + buff_msg= buff_stack; + break; + } + n = vsnprintf(buff_msg, size, format, args); + if (n >= 0 && n < size) + break; + size*= 2; + /* realloc() does unnecessary memcpy */ + my_free(buff_msg, 0); + buff_msg= (char*) my_malloc(size, MYF(0)); + } + } + else if ((size_t) n > sizeof(buff_stack)) + { + buff_msg= (char*) my_malloc(n + 1, MYF(0)); +#ifdef DBUG + DBUG_ASSERT(n == vsnprintf(buff_msg, n + 1, format, args)); +#else + vsnprintf(buff_msg, n + 1, format, args); +#endif + } + fprintf(file, "%s%s\n", buff_date, buff_msg); + if (buff_msg != buff_stack) + my_free(buff_msg, 0); + + /* don't fflush() the file: buffering strategy is set in log_init() */ +} + + +void log_error(const char *format, ...) +{ + va_list args; + va_start(args, format); + log(stderr, format, args); + va_end(args); +} + + +void log_info(const char *format, ...) +{ + va_list args; + va_start(args, format); + log(stdout, format, args); + va_end(args); +} + +/* TODO: rewrite with buffering print */ +void print_info(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); +} + +void print_error(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +/* + log_init() + RETURN VALUE + 0 ok + !0 error +*/ + +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); +} + +void die(const char *format, ...) +{ + va_list args; + fprintf(stderr,"%s: ", my_progname); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(1); +} diff --git a/server-tools/instance-manager/log.h b/server-tools/instance-manager/log.h new file mode 100644 index 00000000000..825d7515513 --- /dev/null +++ b/server-tools/instance-manager/log.h @@ -0,0 +1,81 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Logging facilities. + + 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. + + 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_info(const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 1, 2))) +#endif + ; + + +/* 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))) +#endif + ; + + +/* + 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))) +#endif + ; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_LOG_H diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc new file mode 100644 index 00000000000..353dfcf64dc --- /dev/null +++ b/server-tools/instance-manager/manager.cc @@ -0,0 +1,291 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <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 <m_string.h> +#include <signal.h> +#include <thr_alarm.h> +#ifndef __WIN__ +#include <sys/wait.h> +#endif + + +int create_pid_file(const char *pid_file_name, int pid) +{ + if (FILE *pid_file= my_fopen(pid_file_name, + O_WRONLY | O_CREAT | O_BINARY, MYF(0))) + { + fprintf(pid_file, "%d\n", (int) pid); + my_fclose(pid_file, MYF(0)); + return 0; + } + log_error("can't create pid file %s: errno=%d, %s", + pid_file_name, errno, strerror(errno)); + return 1; +} + +#ifndef __WIN__ +void set_signals(sigset_t *mask) +{ + /* block signals */ + sigemptyset(mask); + sigaddset(mask, SIGINT); + sigaddset(mask, SIGTERM); + sigaddset(mask, SIGPIPE); + sigaddset(mask, SIGHUP); + signal(SIGPIPE, SIG_IGN); + + /* + We want this signal to be blocked in all theads but the signal + one. It is needed for the thr_alarm subsystem to work. + */ + sigaddset(mask,THR_SERVER_ALARM); + + /* all new threads will inherite this signal mask */ + pthread_sigmask(SIG_BLOCK, mask, NULL); + + /* + In our case the signal thread also implements functions of alarm thread. + Here we init alarm thread functionality. We suppose that we won't have + more then 10 alarms at the same time. + */ + init_thr_alarm(10); +} +#else + +bool have_signal; + +void onsignal(int signo) +{ + have_signal= true; +} + +void set_signals(sigset_t *set) +{ + signal(SIGINT, onsignal); + signal(SIGTERM, onsignal); + have_signal= false; +} + +int my_sigwait(const sigset_t *set, int *sig) +{ + while (!have_signal) + { + Sleep(100); + } + return 0; +} + +#endif + + +/* + 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. +*/ + +void manager(const Options &options) +{ + Thread_registry thread_registry; + /* + All objects created in the manager() function live as long as + thread_registry lives, and thread_registry is alive until there are + working threads. + */ + + User_map user_map; + Instance_map instance_map(options.default_mysqld_path); + Guardian_thread guardian_thread(thread_registry, + &instance_map, + options.monitoring_interval); + + Listener_thread_args listener_args(thread_registry, options, user_map, + instance_map); + + manager_pid= getpid(); + instance_map.guardian= &guardian_thread; + + if (instance_map.init() || user_map.init()) + return; + + if (user_map.load(options.password_file_name)) + return; + + /* write Instance Manager pid file */ + + log_info("IM pid file: '%s'; PID: %d.", + (const char *) options.pid_file_name, + (int) manager_pid); + + if (create_pid_file(options.pid_file_name, manager_pid)) + return; + + /* + Initialize signals and alarm-infrastructure. + + 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(). + */ + + 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; + } + + } + + /* Load instances. */ + + int signo; + bool shutdown_complete; + + shutdown_complete= FALSE; + + if (instance_map.flush_instances()) + { + log_error("Cannot init instances repository. This might be caused by " + "the wrong config file options. For instance, missing mysqld " + "binary. Aborting."); + return; + } + + /* 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"); + goto err; + } + + } + + /* + After the list of guarded instances have been initialized, + Guardian should start them. + */ + pthread_cond_signal(&guardian_thread.COND_guardian); + + while (!shutdown_complete) + { + int status= 0; + + if ((status= my_sigwait(&mask, &signo)) != 0) + { + log_error("sigwait() failed"); + goto err; + } + +#ifndef __WIN__ +/* + On some Darwin kernels SIGHUP is delivered along with most + signals. This is why we skip it's processing on these + platforms. For more details and test program see + Bug #14164 IM tests fail on MacOS X (powermacg5) +*/ +#ifdef IGNORE_SIGHUP_SIGQUIT + if ( SIGHUP == signo ) + continue; +#endif + if (THR_SERVER_ALARM == signo) + process_alarm(signo); + else +#endif + { + if (!guardian_thread.is_stopped()) + { + bool stop_instances= true; + guardian_thread.request_shutdown(stop_instances); + pthread_cond_signal(&guardian_thread.COND_guardian); + } + else + { + thread_registry.deliver_shutdown(); + shutdown_complete= TRUE; + } + } + } + +err: + /* delete the pid file */ + my_delete(options.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 +} + diff --git a/server-tools/instance-manager/manager.h b/server-tools/instance-manager/manager.h new file mode 100644 index 00000000000..3ddf292132e --- /dev/null +++ b/server-tools/instance-manager/manager.h @@ -0,0 +1,25 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +struct Options; + +void manager(const Options &options); + +int create_pid_file(const char *pid_file_name, int pid); + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H diff --git a/server-tools/instance-manager/messages.cc b/server-tools/instance-manager/messages.cc new file mode 100644 index 00000000000..d2595638de0 --- /dev/null +++ b/server-tools/instance-manager/messages.cc @@ -0,0 +1,89 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "messages.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) +{ + switch (sql_errno) { + case ER_HANDSHAKE_ERROR: + return "Bad handshake"; + case ER_OUT_OF_RESOURCES: + return "Out of memory; Check if mysqld or some other process" + " uses all available memory. If not you may have to use" + " 'ulimit' to allow mysqld to use more memory or you can" + " add more swap space"; + case ER_ACCESS_DENIED_ERROR: + return "Access denied. Bad username/password pair"; + case ER_NOT_SUPPORTED_AUTH_MODE: + return "Client does not support authentication protocol requested by" + " server; consider upgrading MySQL client"; + case ER_UNKNOWN_COM_ERROR: + return "Unknown command"; + case ER_SYNTAX_ERROR: + return "You have an error in your command syntax. Check the manual that" + " 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"; + 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."; + case ER_INSTANCE_ALREADY_STARTED: + return "The instance is already started"; + case ER_CANNOT_START_INSTANCE: + return "Cannot start instance. Possible reasons are wrong instance options" + " or resources shortage"; + case ER_OFFSET_ERROR: + return "Cannot read negative number of bytes"; + case ER_STOP_INSTANCE: + return "Cannot stop instance"; + case ER_READ_FILE: + return "Cannot read requested part of the logfile"; + case ER_NO_SUCH_LOG: + return "The instance has no such log enabled"; + case ER_OPEN_LOGFILE: + return "Cannot open log file"; + case ER_GUESS_LOGFILE: + return "Cannot guess the log filename. Try specifying full log name" + " in the instance options"; + case ER_ACCESS_OPTION_FILE: + return "Cannot open the option file to edit. Check permissions"; + default: + DBUG_ASSERT(0); + return 0; + } +} + + +const char *message(unsigned sql_errno) +{ + return mysqld_error_message(sql_errno); +} + + +const char *errno_to_sqlstate(unsigned sql_errno) +{ + return mysql_errno_to_sqlstate(sql_errno); +} diff --git a/server-tools/instance-manager/messages.h b/server-tools/instance-manager/messages.h new file mode 100644 index 00000000000..b771efe5e13 --- /dev/null +++ b/server-tools/instance-manager/messages.h @@ -0,0 +1,23 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MESSAGES_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_MESSAGES_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +const char *message(unsigned sql_errno); + +const char *errno_to_sqlstate(unsigned sql_errno); + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MESSAGES_H diff --git a/server-tools/instance-manager/mysql_connection.cc b/server-tools/instance-manager/mysql_connection.cc new file mode 100644 index 00000000000..bf39c843f0a --- /dev/null +++ b/server-tools/instance-manager/mysql_connection.cc @@ -0,0 +1,384 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#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 <my_sys.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()) +{ + thread_registry.register_thread(&thread_info); +} + + +/* + NET subsystem requieres its user to provide my_net_local_init extern + C function (exactly as declared below). my_net_local_init is called by + my_net_init and is supposed to set NET controlling variables. + See also priv.h for variables description. +*/ + +C_MODE_START + +void my_net_local_init(NET *net) +{ + net->max_packet= net_buffer_length; + net->read_timeout= net_read_timeout; + net->write_timeout= net_write_timeout; + net->retry_count= net_retry_count; + net->max_packet_size= max_allowed_packet; +} + +C_MODE_END + + +/* + Every resource, which we can fail to acquire, is allocated in init(). + This function is complementary to cleanup(). +*/ + +int Mysql_connection_thread::init() +{ + /* Allocate buffers for network I/O */ + if (my_net_init(&net, vio)) + return 1; + net.return_status= &status; + /* Initialize random number generator */ + { + ulong seed1= (ulong) &rand_st + rand(); + ulong seed2= rand() + time(0); + randominit(&rand_st, seed1, seed2); + } + /* Fill scramble - server's random message used for handshake */ + create_random_string(scramble, SCRAMBLE_LENGTH, &rand_st); + /* We don't support transactions, every query is atomic */ + status= SERVER_STATUS_AUTOCOMMIT; + return 0; +} + + +void Mysql_connection_thread::cleanup() +{ + net_end(&net); +} + + +Mysql_connection_thread::~Mysql_connection_thread() +{ + /* vio_delete closes the socket if necessary */ + vio_delete(vio); + thread_registry.unregister_thread(&thread_info); +} + + +void Mysql_connection_thread::run() +{ + log_info("accepted mysql connection %d", connection_id); + + my_thread_init(); + + if (check_connection()) + { + my_thread_end(); + return; + } + + log_info("connection %d is checked successfully", connection_id); + + vio_keepalive(vio, TRUE); + + while (!net.error && net.vio && !thread_registry.is_shutdown()) + { + if (do_command()) + break; + } + + my_thread_end(); +} + + +int Mysql_connection_thread::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, + // charset number, status, + char *pos= buff; + ulong server_flags; + + memcpy(pos, mysqlmanager_version, mysqlmanager_version_length + 1); + pos+= mysqlmanager_version_length + 1; + + int4store((uchar*) pos, connection_id); + pos+= 4; + + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of the scramble is placed here, and second + part at the end of packet (even though we don't support old clients, + we must follow standard packet format.) + */ + memcpy(pos, scramble, SCRAMBLE_LENGTH_323); + pos+= SCRAMBLE_LENGTH_323; + *pos++= '\0'; + + server_flags= CLIENT_LONG_FLAG | CLIENT_PROTOCOL_41 | + CLIENT_SECURE_CONNECTION; + + /* + 18-bytes long section for various flags/variables + + Every flag occupies a bit in first half of ulong; int2store will + gracefully pick up all flags. + */ + int2store(pos, server_flags); + pos+= 2; + *pos++= (char) default_charset_info->number; // global mysys variable + int2store(pos, status); // connection status + pos+= 2; + bzero(pos, 13); // not used now + pos+= 13; + + /* second part of the scramble, null-terminated */ + memcpy(pos, scramble + SCRAMBLE_LENGTH_323, + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1); + pos+= SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1; + + /* write connection message and read reply */ + enum { MIN_HANDSHAKE_SIZE= 2 }; + if (net_write_command(&net, protocol_version, "", 0, buff, pos - buff) || + (pkt_len= my_net_read(&net)) == packet_error || + pkt_len < MIN_HANDSHAKE_SIZE) + { + net_send_error(&net, ER_HANDSHAKE_ERROR); + return 1; + } + + client_capabilities= uint2korr(net.read_pos); + if (!(client_capabilities & CLIENT_PROTOCOL_41)) + { + net_send_error_323(&net, ER_NOT_SUPPORTED_AUTH_MODE); + return 1; + } + client_capabilities|= ((ulong) uint2korr(net.read_pos + 2)) << 16; + + pos= (char*) net.read_pos + 32; + + /* At least one byte for username and one byte for password */ + if (pos >= (char*) 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 *password= strend(user)+1; + ulong password_len= *password++; + if (password_len != SCRAMBLE_LENGTH) + { + net_send_error(&net, ER_ACCESS_DENIED_ERROR); + return 1; + } + if (user_map.authenticate(user, password-user-2, password, scramble)) + { + net_send_error(&net, ER_ACCESS_DENIED_ERROR); + return 1; + } + net_send_ok(&net, connection_id, NULL); + return 0; +} + + +int Mysql_connection_thread::do_command() +{ + char *packet; + ulong packet_length; + + /* We start to count packets from 0 for each new command */ + net.pkt_nr= 0; + + if ((packet_length=my_net_read(&net)) == packet_error) + { + /* 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()) + return 1; + net_send_error(&net, net.last_errno); + net.error= 0; + return 0; + } + else + { + 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 %d: packet_length=%d, command=%d", + connection_id, packet_length, command); + return dispatch_command(command, packet + 1, packet_length - 1); + } +} + +int Mysql_connection_thread::dispatch_command(enum enum_server_command command, + const char *packet, uint len) +{ + switch (command) { + case COM_QUIT: // client exit + log_info("query for connection %d received quit command", connection_id); + return 1; + case COM_PING: + log_info("query for connection %d received ping command", connection_id); + net_send_ok(&net, connection_id, NULL); + break; + case COM_QUERY: + { + log_info("query for connection %d : ----\n%s\n-------------------------", + connection_id,packet); + if (Command *command= parse_command(&instance_map, packet)) + { + int res= 0; + log_info("query for connection %d successefully parsed",connection_id); + res= command->execute(&net, connection_id); + delete command; + if (!res) + log_info("query for connection %d executed ok",connection_id); + else + { + log_info("query for connection %d executed err=%d",connection_id,res); + net_send_error(&net, res); + return 0; + } + } + else + { + net_send_error(&net,ER_OUT_OF_RESOURCES); + return 0; + } + break; + } + default: + log_info("query for connection %d received unknown command",connection_id); + net_send_error(&net, ER_UNKNOWN_COM_ERROR); + break; + } + return 0; +} + + +pthread_handler_t mysql_connection(void *arg) +{ + 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"); + else + { + mysql_connection_thread.run(); + mysql_connection_thread.cleanup(); + } + return 0; +} + +/* + vim: fdm=marker +*/ diff --git a/server-tools/instance-manager/mysql_connection.h b/server-tools/instance-manager/mysql_connection.h new file mode 100644 index 00000000000..3496cc05815 --- /dev/null +++ b/server-tools/instance-manager/mysql_connection.h @@ -0,0 +1,48 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_CONNECTION_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_CONNECTION_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_pthread.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; + +struct Mysql_connection_thread_args +{ + struct st_vio *vio; + Thread_registry &thread_registry; + const User_map &user_map; + ulong connection_id; + Instance_map &instance_map; + + 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); +}; + +#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 new file mode 100644 index 00000000000..ff782923a8e --- /dev/null +++ b/server-tools/instance-manager/mysql_manager_error.h @@ -0,0 +1,33 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_MANAGER_ERROR_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_MANAGER_ERROR_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Definefile for instance manager error messagenumbers */ + +#define ER_BAD_INSTANCE_NAME 3000 +#define ER_INSTANCE_IS_NOT_STARTED 3001 +#define ER_INSTANCE_ALREADY_STARTED 3002 +#define ER_CANNOT_START_INSTANCE 3003 +#define ER_STOP_INSTANCE 3004 +#define ER_NO_SUCH_LOG 3005 +#define ER_OPEN_LOGFILE 3006 +#define ER_GUESS_LOGFILE 3007 +#define ER_ACCESS_OPTION_FILE 3008 +#define ER_OFFSET_ERROR 3009 +#define ER_READ_FILE 3010 + +#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 new file mode 100644 index 00000000000..ef714099de7 --- /dev/null +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -0,0 +1,370 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "manager.h" + +#include "options.h" +#include "log.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> +#ifdef __WIN__ +#include "windowsservice.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 +*/ + +static void init_environment(char *progname); +#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); +#endif + + +/* + main, entry point + - init environment + - handle options + - daemonize and run angel process (if necessary) + - run manager process +*/ + +int main(int argc, char *argv[]) +{ + int return_value= 1; + init_environment(argv[0]); + Options options; + + if (options.load(argc, argv)) + goto err; + +#ifndef __WIN__ + struct passwd *user_info; + + if ((user_info= check_user(options.user))) + { + if (set_user(options.user, user_info)) + goto err; + } + + 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 + + manager(options); + return_value= 0; + +err: + options.cleanup(); + my_end(0); + return return_value; +} + +/******************* Auxilary 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) +{ + struct passwd *user_info; + uid_t user_id= geteuid(); + + /* Don't bother if we aren't superuser */ + if (user_id) + { + if (user) + { + /* Don't give a warning, if real user is same as given with --user */ + user_info= getpwnam(user); + if ((!user_info || user_id != user_info->pw_uid)) + log_info("One can only use the --user switch if running as root\n"); + } + return NULL; + } + if (!user) + { + log_info("You are running mysqlmanager as root! This might introduce security problems. It is safer to use --user option istead.\n"); + return NULL; + } + if (!strcmp(user, "root")) + return NULL; /* Avoid problem with dynamic libraries */ + if (!(user_info= getpwnam(user))) + { + /* Allow a numeric uid to be used */ + const char *pos; + for (pos= user; my_isdigit(default_charset_info, *pos); pos++) + {} + if (*pos) /* Not numeric id */ + goto err; + if (!(user_info= getpwuid(atoi(user)))) + goto err; + else + return user_info; + } + else + return user_info; + +err: + log_error("Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n", user); + return NULL; +} + +static int set_user(const char *user, struct passwd *user_info) +{ + DBUG_ASSERT(user_info); +#ifdef HAVE_INITGROUPS + initgroups((char*) user,user_info->pw_gid); +#endif + if (setgid(user_info->pw_gid) == -1) + { + log_error("setgid() failed"); + return 1; + } + 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(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; + +/* + Signal handler for SIGCHLD: reap child, analyze child exit status, and set + child_status appropriately. +*/ + +void reap_child(int __attribute__((unused)) signo) +{ + int child_exit_status; + /* As we have only one child, no need to cycle waitpid */ + if (waitpid(0, &child_exit_status, WNOHANG) > 0) + { + if (WIFSIGNALED(child_exit_status)) + child_status= CHILD_NEED_RESPAWN; + else + /* + As reap_child is not called for SIGSTOP, we should be here only + if the child exited normally. + */ + child_status= CHILD_EXIT_ANGEL; + } +} + +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: respawning..."); + sleep(1); /* don't respawn too fast */ + goto spawn; + } + /* + 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); + } +} + +#endif diff --git a/server-tools/instance-manager/mysqlmanager.vcproj b/server-tools/instance-manager/mysqlmanager.vcproj new file mode 100644 index 00000000000..d835e242eb1 --- /dev/null +++ b/server-tools/instance-manager/mysqlmanager.vcproj @@ -0,0 +1,373 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="mysqlmanager" + ProjectGUID="{6D524B3E-210A-4FCD-8D41-FEC0D21E83AC}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="../../client_debug" + IntermediateDirectory="Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\include,../../extra/yassl/include" + PreprocessorDefinitions="MYSQL_INSTANCE_MANAGER;MYSQL_SERVER;_DEBUG;SAFEMALLOC;SAFE_MUTEX;_WINDOWS;CONSOLE" + MinimalRebuild="TRUE" + ExceptionHandling="FALSE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="wsock32.lib" + OutputFile="../../client_debug/mysqlmanager.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="../../client_debug/mysqlmanager.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="../../client_release" + IntermediateDirectory="Release" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..\..\include,../../extra/yassl/include" + PreprocessorDefinitions="MYSQL_INSTANCE_MANAGER;MYSQL_SERVER;_WINDOWS;CONSOLE" + ExceptionHandling="FALSE" + RuntimeLibrary="0" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="wsock32.lib" + OutputFile="../../client_release/mysqlmanager.exe" + LinkIncremental="1" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <File + RelativePath=".\buffer.cpp"> + </File> + <File + RelativePath="..\..\sql\client.c"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\command.cpp"> + </File> + <File + RelativePath=".\commands.cpp"> + </File> + <File + RelativePath="..\..\libmysql\get_password.c"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\guardian.cpp"> + </File> + <File + RelativePath=".\IMService.cpp"> + </File> + <File + RelativePath=".\instance.cpp"> + </File> + <File + RelativePath=".\instance_map.cpp"> + </File> + <File + RelativePath=".\instance_options.cpp"> + </File> + <File + RelativePath=".\listener.cpp"> + </File> + <File + RelativePath=".\log.cpp"> + </File> + <File + RelativePath=".\manager.cpp"> + </File> + <File + RelativePath=".\messages.cpp"> + </File> + <File + RelativePath="..\..\sql\mini_client_errors.c"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\mysql_connection.cpp"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\mysqlmanager.cpp"> + </File> + <File + RelativePath="..\..\sql\net_serv.cpp"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\options.cpp"> + </File> + <File + RelativePath="..\..\sql\pack.c"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\parse.cpp"> + </File> + <File + RelativePath=".\parse_output.cpp"> + </File> + <File + RelativePath="..\..\sql\password.c"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + ObjectFile="$(IntDir)/$(InputName)1.obj"/> + </FileConfiguration> + </File> + <File + RelativePath=".\priv.cpp"> + </File> + <File + RelativePath=".\protocol.cpp"> + </File> + <File + RelativePath="..\..\sql\sql_state.c"> + </File> + <File + RelativePath=".\thread_registry.cpp"> + </File> + <File + RelativePath=".\user_map.cpp"> + </File> + <File + RelativePath=".\WindowsService.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <File + RelativePath=".\buffer.h"> + </File> + <File + RelativePath=".\command.h"> + </File> + <File + RelativePath=".\commands.h"> + </File> + <File + RelativePath=".\factory.h"> + </File> + <File + RelativePath=".\guardian.h"> + </File> + <File + RelativePath=".\IMService.h"> + </File> + <File + RelativePath=".\instance.h"> + </File> + <File + RelativePath=".\instance_map.h"> + </File> + <File + RelativePath=".\instance_options.h"> + </File> + <File + RelativePath=".\listener.h"> + </File> + <File + RelativePath=".\log.h"> + </File> + <File + RelativePath=".\manager.h"> + </File> + <File + RelativePath=".\messages.h"> + </File> + <File + RelativePath=".\mysql_connection.h"> + </File> + <File + RelativePath=".\mysql_manager_error.h"> + </File> + <File + RelativePath=".\options.h"> + </File> + <File + RelativePath=".\parse.h"> + </File> + <File + RelativePath=".\parse_output.h"> + </File> + <File + RelativePath=".\portability.h"> + </File> + <File + RelativePath=".\priv.h"> + </File> + <File + RelativePath=".\protocol.h"> + </File> + <File + RelativePath=".\thread_registry.h"> + </File> + <File + RelativePath=".\user_map.h"> + </File> + <File + RelativePath=".\WindowsService.h"> + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc new file mode 100644 index 00000000000..a98c0e291be --- /dev/null +++ b/server-tools/instance-manager/options.cc @@ -0,0 +1,387 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "options.h" + +#include "priv.h" +#include "portability.h" +#include <my_sys.h> +#include <my_getopt.h> +#include <m_string.h> +#include <mysql_com.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; +#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::socket_file_name= QUOTE(DEFAULT_SOCKET_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 */ +char **Options::saved_argv= NULL; +/* Remember if the config file was forced */ +bool Options::is_forced_default_file= 0; + +static const char * const ANGEL_PID_FILE_SUFFIX= ".angel.pid"; +static const int ANGEL_PID_FILE_SUFFIX_LEN= strlen(ANGEL_PID_FILE_SUFFIX); + +/* + List of options, accepted by the instance manager. + List must be closed with empty option. +*/ + +enum options { + 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 + OPT_INSTALL_SERVICE, + OPT_REMOVE_SERVICE, + OPT_STAND_ALONE, +#endif + OPT_MONITORING_INTERVAL, + OPT_PORT, + OPT_WAIT_TIMEOUT, + OPT_BIND_ADDRESS +}; + +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 }, + +#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 }, +#endif + + { "socket", OPT_SOCKET, "Socket file to use for connection.", + (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + + { "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, + 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 }, + + { "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 }, + + { "default-mysqld-path", OPT_MYSQLD_PATH, "Where to look for MySQL" + " Server binary.", + (gptr *) &Options::default_mysqld_path, + (gptr *) &Options::default_mysqld_path, + 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }, + + { "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor" + " instances in seconds.", + (gptr *) &Options::monitoring_interval, + (gptr *) &Options::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, + 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, + { "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, + 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, + 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, + + { "user", OPT_USER, "Username to start mysqlmanager", + (gptr *) &Options::user, + (gptr *) &Options::user, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif + { "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 }, + + { 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, + SYSTEM_TYPE, MACHINE_TYPE); +} + + +static const char *default_groups[]= { "manager", 0 }; + + +static void usage() +{ + version(); + + printf("Copyright (C) 2003, 2004 MySQL AB\n" + "This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" + "and you are welcome to modify and redistribute it under the GPL license\n"); + printf("Usage: %s [OPTIONS] \n", my_progname); + + my_print_help(my_long_options); + printf("\nThe following options may be given as the first argument:\n" + "--print-defaults Print the program argument list and exit\n" + "--defaults-file=# Only read manager configuration and instance\n" + " setings from the given file #. The same file\n" + " will be used to modify configuration of instances\n" + " with SET commands.\n"); + my_print_variables(my_long_options); +} + + +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))) +{ + switch(optid) { + case 'V': + version(); + exit(0); + case 'P': + passwd(); + exit(0); + case '?': + usage(); + exit(0); + } + return 0; +} + +C_MODE_END + + +/* + - Process argv of original program: get tid of --defaults-extra-file + and print a message if met there. + - call load_defaults to load configuration file section and save the pointer + for free_defaults. + - call handle_options to assign defaults and command-line arguments + to the class members. + if either of these function fail, return the error code. +*/ + +int Options::load(int argc, char **argv) +{ + if (argc >= 2) + { + if (is_prefix(argv[1], "--defaults-file=")) + { + Options::config_file= strchr(argv[1], '=') + 1; + Options::is_forced_default_file= 1; + } + if (is_prefix(argv[1], "--defaults-extra-file=") || + is_prefix(argv[1], "--no-defaults")) + { + /* the log is not enabled yet */ + fprintf(stderr, "The --defaults-extra-file and --no-defaults options" + " are not supported by\n" + "Instance Manager. Program aborted.\n"); + goto err; + } + } + +#ifdef __WIN__ + if (setup_windows_defaults()) + goto err; +#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; + +#ifndef __WIN__ + if (Options::run_as_service) + { + if (Options::angel_pid_file_name == NULL) + { + /* + Calculate angel pid file on the IM pid file basis: replace the + extension (everything after the last dot) of the pid file basename to + '.angel.pid'. + */ + + char *angel_pid_file_name; + char *base_name_ptr; + char *ext_ptr; + + angel_pid_file_name= (char *) malloc(strlen(Options::pid_file_name) + + ANGEL_PID_FILE_SUFFIX_LEN); + + strcpy(angel_pid_file_name, Options::pid_file_name); + + base_name_ptr= strrchr(angel_pid_file_name, '/'); + + if (!base_name_ptr) + base_name_ptr= angel_pid_file_name + 1; + + ext_ptr= strrchr(base_name_ptr, '.'); + if (ext_ptr) + *ext_ptr= 0; + + strcat(angel_pid_file_name, ANGEL_PID_FILE_SUFFIX); + + Options::angel_pid_file_name= angel_pid_file_name; + } + else + { + Options::angel_pid_file_name= strdup(Options::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); + +#ifndef __WIN__ + if (Options::run_as_service) + free((void *) Options::angel_pid_file_name); +#endif +} + +#ifdef __WIN__ + +int Options::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))) + return 1; + filename= strstr(default_log_file_name, ".exe"); + strcpy(filename, ".log"); + + if (!GetModuleFileName(NULL, windows_config_file, + sizeof(windows_config_file))) + return 1; + char *slash= strrchr(windows_config_file, '\\'); + strcpy(slash, "\\my.ini"); + return 0; +} + +#endif diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h new file mode 100644 index 00000000000..ad3458869b6 --- /dev/null +++ b/server-tools/instance-manager/options.h @@ -0,0 +1,62 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Options - all possible options for the instance manager grouped in one + struct. +*/ +#include <my_global.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +struct Options +{ +#ifdef __WIN__ + static char install_as_service; + static char remove_service; + static char stand_alone; +#else + static char run_as_service; /* handle_options doesn't support bool */ + static const char *user; + static const char *angel_pid_file_name; +#endif + static bool is_forced_default_file; + static const char *log_file_name; + static const char *pid_file_name; + static const char *socket_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; + + /* argv pointer returned by load_defaults() to be used by free_defaults() */ + static char **saved_argv; + + int load(int argc, char **argv); + void cleanup(); +#ifdef __WIN__ + int setup_windows_defaults(); +#endif +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H diff --git a/server-tools/instance-manager/parse.cc b/server-tools/instance-manager/parse.cc new file mode 100644 index 00000000000..14b3db16b45 --- /dev/null +++ b/server-tools/instance-manager/parse.cc @@ -0,0 +1,329 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "parse.h" +#include "commands.h" + +#include <string.h> + + +enum Token +{ + TOK_ERROR= 0, /* Encodes the "ERROR" word, it doesn't indicate error. */ + TOK_FILES, + TOK_FLUSH, + TOK_GENERAL, + TOK_INSTANCE, + TOK_INSTANCES, + TOK_LOG, + TOK_OPTIONS, + TOK_SET, + TOK_SLOW, + TOK_START, + TOK_STATUS, + TOK_STOP, + TOK_SHOW, + TOK_UNSET, + TOK_NOT_FOUND, // must be after all tokens + TOK_END +}; + + +struct tokens_st +{ + uint length; + const char *tok_name; +}; + + +static struct tokens_st tokens[]= { + {5, "ERROR"}, + {5, "FILES"}, + {5, "FLUSH"}, + {7, "GENERAL"}, + {8, "INSTANCE"}, + {9, "INSTANCES"}, + {3, "LOG"}, + {7, "OPTIONS"}, + {3, "SET"}, + {4, "SLOW"}, + {5, "START"}, + {6, "STATUS"}, + {4, "STOP"}, + {4, "SHOW"}, + {5, "UNSET"} +}; + + +/* + Returns token no if word corresponds to some token, otherwise returns + TOK_NOT_FOUND +*/ + +inline Token find_token(const char *word, uint word_len) +{ + int i= 0; + do + { + if (my_strnncoll(default_charset_info, (const uchar *) tokens[i].tok_name, + tokens[i].length, (const uchar *) word, word_len) == 0) + break; + } + while (++i < TOK_NOT_FOUND); + return (Token) i; +} + + +Token get_token(const char **text, uint *word_len) +{ + get_word(text, word_len); + if (*word_len) + return find_token(*text, *word_len); + return TOK_END; +} + + +Token shift_token(const char **text, uint *word_len) +{ + Token save= get_token(text, word_len); + (*text)+= *word_len; + return save; +} + + +int get_text_id(const char **text, uint *word_len, const char **id) +{ + get_word(text, word_len); + if (*word_len == 0) + return 1; + *id= *text; + return 0; +} + + +Command *parse_command(Instance_map *map, const char *text) +{ + uint word_len; + const char *instance_name; + uint instance_name_len; + const char *option; + uint option_len; + const char *option_value; + uint option_value_len; + const char *log_size; + Command *command; + const char *saved_text= text; + bool skip= false; + const char *tmp; + + Token tok1= shift_token(&text, &word_len); + + switch (tok1) { + case TOK_START: // fallthrough + case TOK_STOP: + 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; + 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); + else + command= new Stop_instance(map, instance_name, instance_name_len); + break; + case TOK_FLUSH: + if (shift_token(&text, &word_len) != TOK_INSTANCES) + goto syntax_error; + + get_word(&text, &word_len, NONSPACE); + if (word_len) + goto syntax_error; + + command= new Flush_instances(map); + break; + case TOK_UNSET: + skip= true; + case TOK_SET: + + 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++; + + get_word(&text, &option_len, NONSPACE); + option= text; + if ((tmp= strchr(text, '=')) != NULL) + option_len= tmp - text; + text+= option_len; + + 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; + } + + /* should be empty */ + get_word(&text, &word_len, NONSPACE); + if (word_len) + goto syntax_error; + + 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; + 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); + 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)) + goto syntax_error; + text+= instance_name_len; + /* 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); + else + command= new Show_instance_options(map, instance_name, + instance_name_len); + break; + default: + goto syntax_error; + } + break; + default: + instance_name= text - word_len; + instance_name_len= word_len; + if (instance_name_len) + { + Log_type log_type; + switch (shift_token(&text, &word_len)) { + case TOK_LOG: + switch (Token tok3= shift_token(&text, &word_len)) { + case TOK_FILES: + get_word(&text, &word_len, NONSPACE); + /* 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); + break; + case TOK_ERROR: + case TOK_GENERAL: + case TOK_SLOW: + /* define a log type */ + switch (tok3) { + case TOK_ERROR: + log_type= IM_LOG_ERROR; + break; + case TOK_GENERAL: + log_type= IM_LOG_GENERAL; + break; + case TOK_SLOW: + log_type= IM_LOG_SLOW; + break; + default: + goto syntax_error; + } + /* get the size of the log we want to retrieve */ + if (get_text_id(&text, &word_len, &log_size)) + goto syntax_error; + text+= word_len; + /* this parameter is required */ + if (!word_len) + goto syntax_error; + /* the next token should be comma, or nothing */ + get_word(&text, &word_len); + switch (*text) { + case ',': + text++; /* swallow the comma */ + /* read the next word */ + get_word(&text, &word_len); + if (!word_len) + goto syntax_error; + 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; + } + break; + default: + goto syntax_error; + } + break; + default: + goto syntax_error; + } + } + else + goto syntax_error; + break; + } + break; + default: +syntax_error: + command= new Syntax_error(); + } + return command; +} diff --git a/server-tools/instance-manager/parse.h b/server-tools/instance-manager/parse.h new file mode 100644 index 00000000000..3da53e3a61e --- /dev/null +++ b/server-tools/instance-manager/parse.h @@ -0,0 +1,65 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_sys.h> + +class Command; +class Instance_map; + +enum Log_type +{ + IM_LOG_ERROR= 0, + IM_LOG_GENERAL, + IM_LOG_SLOW +}; + +Command *parse_command(Instance_map *instance_map, const char *text); + +/* define kinds of the word seek method */ +enum { ALPHANUM= 1, NONSPACE }; + +/* + tries to find next word in the text + if found, returns the beginning and puts word length to word_len argument. + 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) +{ + const char *word_end; + + /* skip space */ + while (my_isspace(default_charset_info, **text)) + ++(*text); + + word_end= *text; + + if (seek_method == ALPHANUM) + while (my_isalnum(default_charset_info, *word_end)) + ++word_end; + else + while (!my_isspace(default_charset_info, *word_end) && + (*word_end != '\0')) + ++word_end; + + *word_len= word_end - *text; +} + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_H */ diff --git a/server-tools/instance-manager/parse_output.cc b/server-tools/instance-manager/parse_output.cc new file mode 100644 index 00000000000..ebc45c1f7d4 --- /dev/null +++ b/server-tools/instance-manager/parse_output.cc @@ -0,0 +1,127 @@ +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "parse.h" +#include "parse_output.h" + +#include <stdio.h> +#include <my_sys.h> +#include <m_string.h> +#include "portability.h" + + +void trim_space(const char **text, uint *word_len) +{ + const char *start= *text; + while (*start != 0 && *start == ' ') + start++; + *text= start; + + int len= strlen(start); + const char *end= start + len - 1; + while (end > start && my_isspace(&my_charset_latin1, *end)) + end--; + *word_len= (end - start)+1; +} + +/* + Parse output of the given command + + SYNOPSYS + parse_output_and_get_value() + + 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. + + DESCRIPTION + + 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. + + RETURN + 0 - ok, the word has been found + 1 - error occured or the word is not found +*/ + +int parse_output_and_get_value(const char *command, const char *word, + char *result, size_t input_buffer_len, + uint flag) +{ + FILE *output; + uint wordlen; + /* should be enough to store the string from the output */ + enum { MAX_LINE_LEN= 512 }; + char linebuf[MAX_LINE_LEN]; + int rc= 1; + + wordlen= strlen(word); + + /* + 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. + */ + if (!(output= popen(command, "r"))) + goto err; + + /* + We want fully buffered stream. We also want system to + allocate appropriate buffer. + */ + setvbuf(output, NULL, _IOFBF, 0); + + while (fgets(linebuf, sizeof(linebuf) - 1, output)) + { + uint found_word_len= 0; + char *linep= linebuf; + + linebuf[sizeof(linebuf) - 1]= '\0'; /* safety */ + + /* + Compare the start of our line with the word(s) we are looking for. + */ + if (!strncmp(word, linep, wordlen)) + { + /* + If we have found our word(s), then move linep past the word(s) + */ + linep+= wordlen; + if (flag & GET_VALUE) + { + trim_space((const char**) &linep, &found_word_len); + if (input_buffer_len <= found_word_len) + goto err; + strmake(result, linep, found_word_len); + } + else /* currently there are only two options */ + strmake(result, linep, input_buffer_len - 1); + rc= 0; + break; + } + } + + /* we are not interested in the termination status */ + pclose(output); + +err: + return rc; +} + diff --git a/server-tools/instance-manager/parse_output.h b/server-tools/instance-manager/parse_output.h new file mode 100644 index 00000000000..6a84fabbf17 --- /dev/null +++ b/server-tools/instance-manager/parse_output.h @@ -0,0 +1,26 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_OUTPUT_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_OUTPUT_H +/* Copyright (C) 2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#define GET_VALUE 1 +#define GET_LINE 2 + +int parse_output_and_get_value(const char *command, const char *word, + char *result, size_t input_buffer_len, + uint flag); + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_OUTPUT_H */ diff --git a/server-tools/instance-manager/portability.h b/server-tools/instance-manager/portability.h new file mode 100644 index 00000000000..23a5a5bd14c --- /dev/null +++ b/server-tools/instance-manager/portability.h @@ -0,0 +1,32 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PORTABILITY_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_PORTABILITY_H + +#if (defined(_SCO_DS) || defined(UNIXWARE_7)) && !defined(SHUT_RDWR) +/* + SHUT_* functions are defined only if + "(defined(_XOPEN_SOURCE) && _XOPEN_SOURCE_EXTENDED - 0 >= 1)" +*/ +#define SHUT_RDWR 2 +#endif + +#ifdef __WIN__ + +#define vsnprintf _vsnprintf +#define snprintf _snprintf + +#define SIGKILL 9 +#define SHUT_RDWR 0x2 + +/*TODO: fix this */ +#define PROTOCOL_VERSION 10 + +typedef int pid_t; + +#undef popen +#define popen(A,B) _popen(A,B) + +#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 new file mode 100644 index 00000000000..d2d6a3f636c --- /dev/null +++ b/server-tools/instance-manager/priv.cc @@ -0,0 +1,94 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <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; + +/* + This flag is set if mysqlmanager has detected that it is running on the + system using LinuxThreads +*/ +bool linuxthreads; + +/* + 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 unsigned char protocol_version= PROTOCOL_VERSION; + +unsigned long net_buffer_length= 16384; + +unsigned long max_allowed_packet= 16384; + +unsigned long net_read_timeout= NET_WAIT_TIMEOUT; // same as in mysqld + +unsigned long net_write_timeout= 60; // same as in mysqld + +unsigned long net_retry_count= 10; // same as in mysqld + +/* needed by net_serv.cc */ +unsigned int test_flags= 0; +unsigned long bytes_sent = 0L, bytes_received = 0L; +unsigned long mysqld_net_retry_count = 10L; +unsigned long open_files_limit; + +/* + Change the stack size and start a thread. Return an error if either + pthread_attr_setstacksize or pthread_create fails. + Arguments are the same as for pthread_create(). +*/ + +int set_stacksize_n_create_thread(pthread_t *thread, pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg) +{ + int 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; +} diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h new file mode 100644 index 00000000000..52d7aa1d23d --- /dev/null +++ b/server-tools/instance-manager/priv.h @@ -0,0 +1,94 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <sys/types.h> +#ifdef __WIN__ +#include "portability.h" +#else +#include <unistd.h> +#endif +#include "my_pthread.h" + +/* IM-wide platform-independent defines */ +#define SERVER_DEFAULT_PORT 3306 +#define DEFAULT_MONITORING_INTERVAL 20 +#define DEFAULT_PORT 2273 +/* 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; + +#ifndef __WIN__ +/* + This flag is set if mysqlmanager has detected that it is running on the + system using LinuxThreads +*/ +extern bool linuxthreads; +#endif + +extern const char mysqlmanager_version[]; +extern const int mysqlmanager_version_length; + +/* MySQL client-server protocol version: substituted from configure */ +extern const unsigned char protocol_version; + +/* + These variables are used in MySQL subsystem to work with mysql clients + To be moved to a config file/options one day. +*/ + + +/* Buffer length for TCP/IP and socket communication */ +extern unsigned long net_buffer_length; + + +/* Maximum allowed incoming/ougoung packet length */ +extern unsigned long max_allowed_packet; + + +/* + Number of seconds to wait for more data from a connection before aborting + the read +*/ +extern unsigned long net_read_timeout; + + +/* + Number of seconds to wait for a block to be written to a connection + before aborting the write. +*/ +extern unsigned long net_write_timeout; + + +/* + If a read on a communication port is interrupted, retry this many times + before giving up. +*/ +extern unsigned long net_retry_count; + +extern unsigned int test_flags; +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); + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_PRIV_H diff --git a/server-tools/instance-manager/protocol.cc b/server-tools/instance-manager/protocol.cc new file mode 100644 index 00000000000..73e07f993ae --- /dev/null +++ b/server-tools/instance-manager/protocol.cc @@ -0,0 +1,214 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "protocol.h" + +#include "messages.h" + +#include <mysql_com.h> +#include <m_string.h> + + +static char eof_buff[1]= { (char) 254 }; /* Marker for end of fields */ +static const char ERROR_PACKET_CODE= (char) 255; + + +int net_send_ok(struct st_net *net, unsigned long connection_id, + const char *message) +{ + /* + The format of a packet + 1 packet type code + 1-9 affected rows count + 1-9 connection id + 2 thread return status + 2 warning count + 1-9 + message length message to send (isn't stored if no message) + */ + Buffer buff; + char *pos= buff.buffer; + + /* check that we have space to hold mandatory fields */ + buff.reserve(0, 23); + + enum { OK_PACKET_CODE= 0 }; + *pos++= OK_PACKET_CODE; + pos= net_store_length(pos, (ulonglong) 0); + pos= net_store_length(pos, (ulonglong) connection_id); + int2store(pos, *net->return_status); + pos+= 2; + /* We don't support warnings, so store 0 for total warning count */ + int2store(pos, 0); + pos+= 2; + + uint position= pos - buff.buffer; /* we might need it for message */ + + if (message != NULL) + { + buff.reserve(position, 9 + strlen(message)); + store_to_protocol_packet(&buff, message, &position); + } + + return my_net_write(net, buff.buffer, position) || net_flush(net); +} + + +int net_send_error(struct st_net *net, uint sql_errno) +{ + const char *err= message(sql_errno); + char buff[1 + // packet type code + 2 + // sql error number + 1 + SQLSTATE_LENGTH + // sql state + MYSQL_ERRMSG_SIZE]; // message + char *pos= buff; + + *pos++= ERROR_PACKET_CODE; + int2store(pos, sql_errno); + pos+= 2; + /* The first # is to make the protocol backward compatible */ + *pos++= '#'; + 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, pos - buff) || net_flush(net); +} + + +int net_send_error_323(struct st_net *net, uint sql_errno) +{ + const char *err= message(sql_errno); + char buff[1 + // packet type code + 2 + // sql error number + MYSQL_ERRMSG_SIZE]; // message + char *pos= buff; + + *pos++= ERROR_PACKET_CODE; + int2store(pos, sql_errno); + pos+= 2; + pos= strmake(pos, err, MYSQL_ERRMSG_SIZE - 1) + 1; + return my_net_write(net, buff, pos - buff) || net_flush(net); +} + +char *net_store_length(char *pkg, uint length) +{ + uchar *packet=(uchar*) pkg; + if (length < 251) + { + *packet=(uchar) length; + return (char*) packet+1; + } + *packet++=252; + int2store(packet,(uint) length); + return (char*) packet+2; +} + + +int store_to_protocol_packet(Buffer *buf, const char *string, uint *position, + uint string_len) +{ + uint currpos; + + /* reserve max amount of bytes needed to store length */ + if (buf->reserve(*position, 9)) + goto err; + currpos= (net_store_length(buf->buffer + *position, + (ulonglong) string_len) - buf->buffer); + if (buf->append(currpos, string, string_len)) + goto err; + *position= *position + string_len + (currpos - *position); + + return 0; +err: + return 1; +} + + +int store_to_protocol_packet(Buffer *buf, const char *string, uint *position) +{ + uint string_len; + + string_len= strlen(string); + return store_to_protocol_packet(buf, string, position, string_len); +} + + +int send_eof(struct st_net *net) +{ + uchar buff[1 + /* eof packet code */ + 2 + /* warning count */ + 2]; /* server status */ + + buff[0]=254; + int2store(buff+1, 0); + int2store(buff+3, 0); + return my_net_write(net, (char*) 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; + + /* send the number of fileds */ + net_store_length(small_buff, (uint) list_length(fields)); + if (my_net_write(net, small_buff, (uint) 1)) + goto err; + + while (tmp) + { + position= 0; + field= (NAME_WITH_LENGTH *) tmp->data; + + store_to_protocol_packet(&send_buff, + (char*) "", &position); /* catalog name */ + store_to_protocol_packet(&send_buff, + (char*) "", &position); /* db name */ + store_to_protocol_packet(&send_buff, + (char*) "", &position); /* table name */ + store_to_protocol_packet(&send_buff, + (char*) "", &position); /* table name alias */ + store_to_protocol_packet(&send_buff, + field->name, &position); /* column name */ + store_to_protocol_packet(&send_buff, + field->name, &position); /* column name alias */ + send_buff.reserve(position, 12); + if (send_buff.is_error()) + goto err; + send_buff.buffer[position++]= 12; + 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 */ + int2store(send_buff.buffer + position + 7, 0); /* flags */ + send_buff.buffer[position + 9]= (char) 0; /* decimals */ + send_buff.buffer[position + 10]= 0; + send_buff.buffer[position + 11]= 0; + position+= 12; + if (my_net_write(net, send_buff.buffer, (uint) position+1)) + goto err; + tmp= list_rest(tmp); + } + + if (my_net_write(net, eof_buff, 1)) + goto err; + return 0; + +err: + return 1; +} diff --git a/server-tools/instance-manager/protocol.h b/server-tools/instance-manager/protocol.h new file mode 100644 index 00000000000..f38eac6b079 --- /dev/null +++ b/server-tools/instance-manager/protocol.h @@ -0,0 +1,51 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_PROTOCOL_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_PROTOCOL_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "buffer.h" + +#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 }; + +struct st_net; + +int net_send_ok(struct st_net *net, unsigned long connection_id, + const char *message); + +int net_send_error(struct st_net *net, unsigned sql_errno); + +int net_send_error_323(struct st_net *net, unsigned sql_errno); + +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, uint *position, + uint string_len); + +int send_eof(struct st_net *net); + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PROTOCOL_H */ diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc new file mode 100644 index 00000000000..0091d713a91 --- /dev/null +++ b/server-tools/instance-manager/thread_registry.cc @@ -0,0 +1,246 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "thread_registry.h" + +#include "log.h" + +#include <assert.h> +#include <signal.h> +#include <thr_alarm.h> + + +#ifndef __WIN__ +/* Kick-off signal handler */ + +enum { THREAD_KICK_OFF_SIGNAL= SIGUSR2 }; + +static void handle_signal(int __attribute__((unused)) sig_no) +{ +} +#endif + +/* + Thread_info initializer methods +*/ + +Thread_info::Thread_info() {} +Thread_info::Thread_info(pthread_t thread_id_arg) : + thread_id(thread_id_arg) {} + +/* + TODO: think about moving signal information (now it's shutdown_in_progress) + to Thread_info. It will reduce contention and allow signal deliverence to + a particular thread, not to the whole worker crew +*/ + +Thread_registry::Thread_registry() : + shutdown_in_progress(false) + ,sigwait_thread_pid(pthread_self()) +{ + pthread_mutex_init(&LOCK_thread_registry, 0); + pthread_cond_init(&COND_thread_registry_is_empty, 0); + + /* head is used by-value to simplify nodes inserting */ + head.next= head.prev= &head; +} + + +Thread_registry::~Thread_registry() +{ + /* Check that no one uses the repository. */ + pthread_mutex_lock(&LOCK_thread_registry); + + /* All threads must unregister */ + DBUG_ASSERT(head.next == &head); + + pthread_mutex_unlock(&LOCK_thread_registry); + pthread_cond_destroy(&COND_thread_registry_is_empty); + pthread_mutex_destroy(&LOCK_thread_registry); +} + + +/* + Set signal handler for kick-off thread, and insert a thread info to the + repository. New node is appended to the end of the list; head.prev always + points to the last node. +*/ + +void Thread_registry::register_thread(Thread_info *info) +{ +#ifndef __WIN__ + struct sigaction sa; + sa.sa_handler= handle_signal; + sa.sa_flags= 0; + sigemptyset(&sa.sa_mask); + sigaction(THREAD_KICK_OFF_SIGNAL, &sa, 0); +#endif + info->current_cond= 0; + + pthread_mutex_lock(&LOCK_thread_registry); + info->next= &head; + info->prev= head.prev; + head.prev->next= info; + head.prev= info; + pthread_mutex_unlock(&LOCK_thread_registry); +} + + +/* + Unregister a thread from the repository and free Thread_info structure. + Every registered thread must unregister. Unregistering should be the last + thing a thread is doing, otherwise it could have no time to finalize. +*/ + +void Thread_registry::unregister_thread(Thread_info *info) +{ + pthread_mutex_lock(&LOCK_thread_registry); + info->prev->next= info->next; + info->next->prev= info->prev; + if (head.next == &head) + pthread_cond_signal(&COND_thread_registry_is_empty); + pthread_mutex_unlock(&LOCK_thread_registry); +} + + +/* + Check whether shutdown is in progress, and if yes, return immediately. + Else set info->current_cond and call pthread_cond_wait. When + pthread_cond_wait returns, unregister current cond and check the shutdown + status again. + RETURN VALUE + return value from pthread_cond_wait +*/ + +int Thread_registry::cond_wait(Thread_info *info, pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + pthread_mutex_lock(&LOCK_thread_registry); + if (shutdown_in_progress) + { + pthread_mutex_unlock(&LOCK_thread_registry); + return 0; + } + info->current_cond= cond; + pthread_mutex_unlock(&LOCK_thread_registry); + /* sic: race condition here, cond can be signaled in deliver_shutdown */ + int rc= pthread_cond_wait(cond, mutex); + pthread_mutex_lock(&LOCK_thread_registry); + info->current_cond= 0; + pthread_mutex_unlock(&LOCK_thread_registry); + return rc; +} + + +int Thread_registry::cond_timedwait(Thread_info *info, pthread_cond_t *cond, + pthread_mutex_t *mutex, + struct timespec *wait_time) +{ + int rc; + pthread_mutex_lock(&LOCK_thread_registry); + if (shutdown_in_progress) + { + pthread_mutex_unlock(&LOCK_thread_registry); + return 0; + } + info->current_cond= cond; + pthread_mutex_unlock(&LOCK_thread_registry); + /* sic: race condition here, cond can be signaled in deliver_shutdown */ + if ((rc= pthread_cond_timedwait(cond, mutex, wait_time)) == ETIME) + rc= ETIMEDOUT; // For easier usage + pthread_mutex_lock(&LOCK_thread_registry); + info->current_cond= 0; + pthread_mutex_unlock(&LOCK_thread_registry); + return rc; +} + + +/* + Deliver shutdown message to the workers crew. + As it's impossible to avoid all race conditions, signal latecomers + again. +*/ + +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; + +#ifndef __WIN__ + /* to stop reading from the network we need to flush alarm queue */ + end_thr_alarm(0); + /* + We have to deliver final alarms this way, as the main thread has already + stopped alarm processing. + */ + 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. + */ + while (((error= pthread_cond_timedwait(&COND_thread_registry_is_empty, + &LOCK_thread_registry, + &shutdown_time)) != ETIMEDOUT && + error != ETIME) && + head.next != &head) + ; + + /* + If previous signals did not reach some threads, they must be sleeping + in pthread_cond_wait or in a blocking syscall. Wake them up: + every thread shall check signal variables after each syscall/cond_wait, + 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) + { + pthread_kill(info->thread_id, THREAD_KICK_OFF_SIGNAL); + if (info->current_cond) + pthread_cond_signal(info->current_cond); + } + + pthread_mutex_unlock(&LOCK_thread_registry); +} + + +void Thread_registry::request_shutdown() +{ + pthread_kill(sigwait_thread_pid, SIGTERM); +} diff --git a/server-tools/instance-manager/thread_registry.h b/server-tools/instance-manager/thread_registry.h new file mode 100644 index 00000000000..6dc320a8533 --- /dev/null +++ b/server-tools/instance-manager/thread_registry.h @@ -0,0 +1,118 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REGISTRY_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REGISTRY_H +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + A multi-threaded application shall nicely work with signals. + + This means it shall, first of all, shut down nicely on ``quit'' signals: + stop all running threads, cleanup and exit. + + Note, that a thread can't be shut down nicely if it doesn't want to be. + That's why to perform clean shutdown, all threads constituting a process + must observe certain rules. Here we use the rules, described in Butenhof + book 'Programming with POSIX threads', namely: + - all user signals are handled in 'signal thread' in synchronous manner + (by means of sigwait). To guarantee that the signal thread is the only who + can receive user signals, all threads block them, and signal thread is + the only who calls sigwait() with an apporpriate sigmask. + To propogate a signal to the workers the signal thread sets + a variable, corresponding to the signal. Additionally the signal thread + sends each worker an internal signal (by means of pthread_kill) to kick it + out from possible blocking syscall, and possibly pthread_cond_signal if + some thread is blocked in pthread_cond_[timed]wait. + - a worker handles only internal 'kick' signal (the handler does nothing). + In case when a syscall returns 'EINTR' the worker checks all + signal-related variables and behaves accordingly. + Also these variables shall be checked from time to time in long + CPU-bounded operations, and before/after pthread_cond_wait. (It's supposed + that a worker thread either waits in a syscall/conditional variable, or + computes something.) + - to guarantee signal deliverence, there should be some kind of feedback, + e. g. all workers shall account in the signal thread Thread Repository and + unregister from it on exit. + + Configuration reload (on SIGHUP) and thread timeouts/alarms can be handled + in manner, similar to ``quit'' signals. +*/ + +#include <my_global.h> +#include <my_pthread.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/* + Thread_info - repository entry for each worker thread + All entries comprise double-linked list like: + 0 -- entry -- entry -- entry - 0 + Double-linked list is used to unregister threads easy. +*/ + +class Thread_info +{ +public: + Thread_info(); + Thread_info(pthread_t thread_id_arg); + friend class Thread_registry; +private: + pthread_cond_t *current_cond; + Thread_info *prev, *next; + pthread_t thread_id; +}; + + +/* + Thread_registry - contains handles for each worker thread to deliver + signal information to workers. +*/ + +class Thread_registry +{ +public: + Thread_registry(); + ~Thread_registry(); + + void register_thread(Thread_info *info); + void unregister_thread(Thread_info *info); + void deliver_shutdown(); + void request_shutdown(); + inline bool is_shutdown(); + int cond_wait(Thread_info *info, pthread_cond_t *cond, + pthread_mutex_t *mutex); + int cond_timedwait(Thread_info *info, pthread_cond_t *cond, + pthread_mutex_t *mutex, struct timespec *wait_time); +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; +}; + + +inline bool Thread_registry::is_shutdown() +{ + pthread_mutex_lock(&LOCK_thread_registry); + bool res= shutdown_in_progress; + pthread_mutex_unlock(&LOCK_thread_registry); + return res; +} + + +#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REGISTRY_H */ diff --git a/server-tools/instance-manager/user_map.cc b/server-tools/instance-manager/user_map.cc new file mode 100644 index 00000000000..9cb15307131 --- /dev/null +++ b/server-tools/instance-manager/user_map.cc @@ -0,0 +1,184 @@ +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "user_map.h" + +#include <mysql_com.h> +#include <m_string.h> + +#include "log.h" + +struct User +{ + char user[USERNAME_LENGTH + 1]; + uint8 user_length; + uint8 salt[SCRAMBLE_LENGTH]; + int init(const char *line); +}; + + +int User::init(const char *line) +{ + const char *name_begin, *name_end, *password; + int line_ending_len= 1; + + 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; + password= name_end + 2; + } + else + { + name_begin= line; + name_end= strchr(name_begin, ':'); + if (name_end == 0) + goto err; + password= name_end + 1; + } + user_length= name_end - name_begin; + if (user_length > USERNAME_LENGTH) + goto err; + + /* + assume that newline characater is present + we support reading password files that end in \n or \r\n on + either platform. + */ + if (password[strlen(password)-2] == '\r') + line_ending_len= 2; + if (strlen(password) != (uint) (SCRAMBLED_PASSWORD_CHAR_LENGTH + + line_ending_len)) + goto err; + + memcpy(user, name_begin, user_length); + user[user_length]= 0; + get_salt_from_password(salt, password); + log_info("loaded user %s", 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) +{ + const User *user= (const User *) u; + *len= user->user_length; + return (byte *) user->user; +} + +static void delete_user(void *u) +{ + User *user= (User *) u; + delete user; +} + +C_MODE_END + + +int User_map::init() +{ + enum { START_HASH_SIZE= 16 }; + if (hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, + get_user_key, delete_user, 0)) + return 1; + return 0; +} + + +User_map::~User_map() +{ + 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 +*/ + +int User_map::load(const char *password_file_name) +{ + FILE *file; + char line[USERNAME_LENGTH + SCRAMBLED_PASSWORD_CHAR_LENGTH + + 2 + /* for possible quotes */ + 1 + /* for ':' */ + 2 + /* for newline */ + 1]; /* for trailing zero */ + User *user; + int rc= 1; + + 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; + } + + while (fgets(line, sizeof(line), file)) + { + /* skip comments and empty lines */ + if (line[0] == '#' || line[0] == '\n' && + (line[1] == '\0' || line[1] == '\r')) + continue; + if ((user= new User) == 0) + goto done; + if (user->init(line) || my_hash_insert(&hash, (byte *) user)) + goto err_init_user; + } + if (feof(file)) + rc= 0; + goto done; +err_init_user: + delete user; +done: + my_fclose(file, MYF(0)); + return rc; +} + + +/* + Check if user exists and password is correct + RETURN VALUE + 0 - user found and password OK + 1 - password mismatch + 2 - user not found +*/ + +int User_map::authenticate(const char *user_name, uint length, + 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; +} diff --git a/server-tools/instance-manager/user_map.h b/server-tools/instance-manager/user_map.h new file mode 100644 index 00000000000..4134017dd9b --- /dev/null +++ b/server-tools/instance-manager/user_map.h @@ -0,0 +1,47 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H +/* Copyright (C) 2003 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include <my_global.h> + +#include <my_sys.h> +#include <hash.h> + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/* + User_map -- all users and passwords +*/ + +class User_map +{ +public: + ~User_map(); + + int init(); + int load(const char *password_file_name); + int authenticate(const char *user_name, uint length, + const char *scrambled_password, + const char *scramble) const; +private: + HASH hash; +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H |