summaryrefslogtreecommitdiff
path: root/server-tools
diff options
context:
space:
mode:
Diffstat (limited to 'server-tools')
-rwxr-xr-xserver-tools/CMakeLists.txt18
-rw-r--r--server-tools/Makefile.am2
-rwxr-xr-xserver-tools/instance-manager/CMakeLists.txt17
-rw-r--r--server-tools/instance-manager/IMService.cpp82
-rw-r--r--server-tools/instance-manager/IMService.h14
-rw-r--r--server-tools/instance-manager/Makefile.am95
-rw-r--r--server-tools/instance-manager/README11
-rw-r--r--server-tools/instance-manager/WindowsService.cpp203
-rw-r--r--server-tools/instance-manager/WindowsService.h43
-rw-r--r--server-tools/instance-manager/buffer.cc111
-rw-r--r--server-tools/instance-manager/buffer.h66
-rw-r--r--server-tools/instance-manager/command.cc30
-rw-r--r--server-tools/instance-manager/command.h47
-rw-r--r--server-tools/instance-manager/commands.cc794
-rw-r--r--server-tools/instance-manager/commands.h215
-rw-r--r--server-tools/instance-manager/guardian.cc435
-rw-r--r--server-tools/instance-manager/guardian.h123
-rw-r--r--server-tools/instance-manager/instance.cc608
-rw-r--r--server-tools/instance-manager/instance.h69
-rw-r--r--server-tools/instance-manager/instance_map.cc345
-rw-r--r--server-tools/instance-manager/instance_map.h90
-rw-r--r--server-tools/instance-manager/instance_options.cc600
-rw-r--r--server-tools/instance-manager/instance_options.h109
-rw-r--r--server-tools/instance-manager/listener.cc391
-rw-r--r--server-tools/instance-manager/listener.h52
-rw-r--r--server-tools/instance-manager/log.cc169
-rw-r--r--server-tools/instance-manager/log.h81
-rw-r--r--server-tools/instance-manager/manager.cc291
-rw-r--r--server-tools/instance-manager/manager.h25
-rw-r--r--server-tools/instance-manager/messages.cc89
-rw-r--r--server-tools/instance-manager/messages.h23
-rw-r--r--server-tools/instance-manager/mysql_connection.cc384
-rw-r--r--server-tools/instance-manager/mysql_connection.h48
-rw-r--r--server-tools/instance-manager/mysql_manager_error.h33
-rw-r--r--server-tools/instance-manager/mysqlmanager.cc370
-rw-r--r--server-tools/instance-manager/mysqlmanager.vcproj373
-rw-r--r--server-tools/instance-manager/options.cc387
-rw-r--r--server-tools/instance-manager/options.h62
-rw-r--r--server-tools/instance-manager/parse.cc329
-rw-r--r--server-tools/instance-manager/parse.h65
-rw-r--r--server-tools/instance-manager/parse_output.cc127
-rw-r--r--server-tools/instance-manager/parse_output.h26
-rw-r--r--server-tools/instance-manager/portability.h32
-rw-r--r--server-tools/instance-manager/priv.cc94
-rw-r--r--server-tools/instance-manager/priv.h94
-rw-r--r--server-tools/instance-manager/protocol.cc214
-rw-r--r--server-tools/instance-manager/protocol.h51
-rw-r--r--server-tools/instance-manager/thread_registry.cc246
-rw-r--r--server-tools/instance-manager/thread_registry.h118
-rw-r--r--server-tools/instance-manager/user_map.cc184
-rw-r--r--server-tools/instance-manager/user_map.h47
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