Commit 83cce1ca authored by unknown's avatar unknown

Merge mysql.com:/home/cps/mysql/trees/mysql-5.0

into mysql.com:/home/cps/mysql/devel/im-fix-review


server-tools/instance-manager/Makefile.am:
  Auto merged
support-files/Makefile.am:
  Auto merged
support-files/mysql.spec.sh:
  Auto merged
parents 265bc984 deba12cd
...@@ -30,10 +30,8 @@ liboptions_a_CPPFLAGS= $(CPPFLAGS) \ ...@@ -30,10 +30,8 @@ liboptions_a_CPPFLAGS= $(CPPFLAGS) \
-DDEFAULT_LOG_FILE_NAME="$(localstatedir)/mysqlmanager.log" \ -DDEFAULT_LOG_FILE_NAME="$(localstatedir)/mysqlmanager.log" \
-DDEFAULT_SOCKET_FILE_NAME="$(localstatedir)/mysqlmanager.sock" \ -DDEFAULT_SOCKET_FILE_NAME="$(localstatedir)/mysqlmanager.sock" \
-DDEFAULT_PASSWORD_FILE_NAME="$(sysconfdir)/mysqlmanager.passwd" \ -DDEFAULT_PASSWORD_FILE_NAME="$(sysconfdir)/mysqlmanager.passwd" \
-DDEFAULT_MYSQLD_PATH="$(bindir)/mysqld$(EXEEXT)" \ -DDEFAULT_MYSQLD_PATH="$(libexecdir)/mysqld$(EXEEXT)" \
-DDEFAULT_USER="root" \ -DDEFAULT_MONITORING_INTERVAL="20" \
-DDEFAULT_PASSWORD="" \
-DDEFAULT_MONITORING_INTERVAL="5" \
-DDEFAULT_PORT="2273" \ -DDEFAULT_PORT="2273" \
-DPROTOCOL_VERSION=@PROTOCOL_VERSION@ -DPROTOCOL_VERSION=@PROTOCOL_VERSION@
...@@ -59,7 +57,7 @@ client_settings.h: Makefile ...@@ -59,7 +57,7 @@ client_settings.h: Makefile
rm -f $(srcdir)/client_settings.h rm -f $(srcdir)/client_settings.h
@LN_CP_F@ $(top_srcdir)/sql/client_settings.h $(srcdir)/client_settings.h @LN_CP_F@ $(top_srcdir)/sql/client_settings.h $(srcdir)/client_settings.h
bin_PROGRAMS= mysqlmanager libexec_PROGRAMS= mysqlmanager
mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \
manager.h manager.cc log.h log.cc \ manager.h manager.cc log.h log.cc \
...@@ -75,7 +73,8 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ ...@@ -75,7 +73,8 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \
instance_options.h instance_options.cc \ instance_options.h instance_options.cc \
buffer.h buffer.cc parse.cc parse.h \ buffer.h buffer.cc parse.cc parse.h \
guardian.cc guardian.h \ guardian.cc guardian.h \
mysql_manager_error.h client_func.c parse_output.cc parse_output.h \
mysql_manager_error.h
mysqlmanager_LDADD= liboptions.a \ mysqlmanager_LDADD= liboptions.a \
libnet.a \ libnet.a \
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
RETURN RETURN
0 - ok 0 - ok
1 - The buffer came to 16Mb barrier 1 - got an error in reserve()
*/ */
int Buffer::append(uint position, const char *string, uint len_arg) int Buffer::append(uint position, const char *string, uint len_arg)
...@@ -71,7 +71,7 @@ int Buffer::append(uint position, const char *string, uint len_arg) ...@@ -71,7 +71,7 @@ int Buffer::append(uint position, const char *string, uint len_arg)
RETURN RETURN
0 - ok 0 - ok
1 - The buffer came to 16Mb barrier 1 - realloc error or we have come to the 16Mb barrier
*/ */
int Buffer::reserve(uint position, uint len_arg) int Buffer::reserve(uint position, uint len_arg)
...@@ -81,10 +81,10 @@ int Buffer::reserve(uint position, uint len_arg) ...@@ -81,10 +81,10 @@ int Buffer::reserve(uint position, uint len_arg)
if (position + len_arg>= buffer_size) if (position + len_arg>= buffer_size)
{ {
buffer= (char *) realloc(buffer, buffer= (char *) my_realloc(buffer,
min(MAX_BUFFER_SIZE, min(MAX_BUFFER_SIZE,
max((uint) (buffer_size*1.5), max((uint) (buffer_size*1.5),
position + len_arg))); position + len_arg)), MYF(0));
if (buffer == NULL) if (buffer == NULL)
goto err; goto err;
buffer_size= (uint) (buffer_size*1.5); buffer_size= (uint) (buffer_size*1.5);
...@@ -92,6 +92,18 @@ int Buffer::reserve(uint position, uint len_arg) ...@@ -92,6 +92,18 @@ int Buffer::reserve(uint position, uint len_arg)
return 0; return 0;
err: err:
error= 1;
return 1; return 1;
} }
int Buffer::get_size()
{
return buffer_size;
}
int Buffer::is_error()
{
return error;
}
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <my_global.h> #include <my_global.h>
#include <my_sys.h>
#ifdef __GNUC__ #ifdef __GNUC__
#pragma interface #pragma interface
...@@ -36,11 +37,17 @@ private: ...@@ -36,11 +37,17 @@ private:
/* maximum buffer size is 16Mb */ /* maximum buffer size is 16Mb */
enum { MAX_BUFFER_SIZE= 16777216 }; enum { MAX_BUFFER_SIZE= 16777216 };
size_t buffer_size; size_t buffer_size;
/* Error flag. Triggered if we get an error of some kind */
int error;
public: public:
Buffer() Buffer(size_t buffer_size_arg= BUFFER_INITIAL_SIZE)
:buffer_size(BUFFER_INITIAL_SIZE), error(0)
{ {
buffer=(char *) malloc(BUFFER_INITIAL_SIZE); /*
buffer_size= BUFFER_INITIAL_SIZE; 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() ~Buffer()
...@@ -50,6 +57,8 @@ public: ...@@ -50,6 +57,8 @@ public:
public: public:
char *buffer; char *buffer;
int get_size();
int is_error();
int append(uint position, const char *string, uint len_arg); int append(uint position, const char *string, uint len_arg);
int reserve(uint position, uint len_arg); int reserve(uint position, uint len_arg);
}; };
......
#include <my_global.h>
#include <my_sys.h>
#include <mysql.h>
/*
Currently we cannot use libmysqlclient directly becouse of the linking
issues. Here we provide needed libmysqlclient functions.
TODO: to think how to use libmysqlclient code instead of copy&paste.
The other possible solution is to use simple_command directly.
*/
const char * STDCALL
mysql_get_server_info(MYSQL *mysql)
{
return((char*) mysql->server_version);
}
int STDCALL
mysql_ping(MYSQL *mysql)
{
DBUG_ENTER("mysql_ping");
DBUG_RETURN(simple_command(mysql,COM_PING,0,0,0));
}
int STDCALL
mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level)
{
uchar level[1];
DBUG_ENTER("mysql_shutdown");
level[0]= (uchar) shutdown_level;
DBUG_RETURN(simple_command(mysql, COM_SHUTDOWN, (char *)level, 1, 0));
}
...@@ -175,7 +175,7 @@ int Show_instance_status::do_command(struct st_net *net, ...@@ -175,7 +175,7 @@ int Show_instance_status::do_command(struct st_net *net,
if (instance->is_running()) if (instance->is_running())
{ {
store_to_string(&send_buff, (char *) "online", &position); store_to_string(&send_buff, (char *) "online", &position);
store_to_string(&send_buff, mysql_get_server_info(&(instance->mysql)), &position); store_to_string(&send_buff, "unknown", &position);
} }
else else
{ {
...@@ -184,7 +184,8 @@ int Show_instance_status::do_command(struct st_net *net, ...@@ -184,7 +184,8 @@ int Show_instance_status::do_command(struct st_net *net,
} }
if (my_net_write(net, send_buff.buffer, (uint) position)) if (send_buff.is_error() ||
my_net_write(net, send_buff.buffer, (uint) position))
goto err; goto err;
} }
...@@ -270,38 +271,18 @@ int Show_instance_options::do_command(struct st_net *net, ...@@ -270,38 +271,18 @@ int Show_instance_options::do_command(struct st_net *net,
store_to_string(&send_buff, store_to_string(&send_buff,
(char *) instance->options.mysqld_path, (char *) instance->options.mysqld_path,
&position); &position);
if (my_net_write(net, send_buff.buffer, (uint) position)) if (send_buff.is_error() ||
my_net_write(net, send_buff.buffer, (uint) position))
goto err; goto err;
} }
if (instance->options.is_guarded != NULL) if (instance->options.nonguarded != NULL)
{ {
position= 0; position= 0;
store_to_string(&send_buff, (char *) "guarded", &position); store_to_string(&send_buff, (char *) "nonguarded", &position);
store_to_string(&send_buff, "", &position); store_to_string(&send_buff, "", &position);
if (my_net_write(net, send_buff.buffer, (uint) position)) if (send_buff.is_error() ||
goto err; my_net_write(net, send_buff.buffer, (uint) position))
}
if (instance->options.mysqld_user != NULL)
{
position= 0;
store_to_string(&send_buff, (char *) "admin-user", &position);
store_to_string(&send_buff,
(char *) instance->options.mysqld_user,
&position);
if (my_net_write(net, send_buff.buffer, (uint) position))
goto err;
}
if (instance->options.mysqld_password != NULL)
{
position= 0;
store_to_string(&send_buff, (char *) "admin-password", &position);
store_to_string(&send_buff,
(char *) instance->options.mysqld_password,
&position);
if (my_net_write(net, send_buff.buffer, (uint) position))
goto err; goto err;
} }
...@@ -318,7 +299,8 @@ int Show_instance_options::do_command(struct st_net *net, ...@@ -318,7 +299,8 @@ int Show_instance_options::do_command(struct st_net *net,
store_to_string(&send_buff, option_value + 1, &position); store_to_string(&send_buff, option_value + 1, &position);
/* join name and the value into the same option again */ /* join name and the value into the same option again */
*option_value= '='; *option_value= '=';
if (my_net_write(net, send_buff.buffer, (uint) position)) if (send_buff.is_error() ||
my_net_write(net, send_buff.buffer, (uint) position))
goto err; goto err;
} }
} }
...@@ -372,7 +354,7 @@ int Start_instance::execute(struct st_net *net, ulong connection_id) ...@@ -372,7 +354,7 @@ int Start_instance::execute(struct st_net *net, ulong connection_id)
if (err_code= instance->start()) if (err_code= instance->start())
return err_code; return err_code;
if (instance->options.is_guarded != NULL) if (instance->options.nonguarded == NULL)
instance_map->guardian->guard(instance); instance_map->guardian->guard(instance);
net_send_ok(net, connection_id); net_send_ok(net, connection_id);
...@@ -403,7 +385,7 @@ int Stop_instance::execute(struct st_net *net, ulong connection_id) ...@@ -403,7 +385,7 @@ int Stop_instance::execute(struct st_net *net, ulong connection_id)
} }
else else
{ {
if (instance->options.is_guarded != NULL) if (instance->options.nonguarded == NULL)
instance_map->guardian-> instance_map->guardian->
stop_guard(instance); stop_guard(instance);
if ((err_code= instance->stop())) if ((err_code= instance->stop()))
......
This diff is collapsed.
...@@ -19,15 +19,16 @@ ...@@ -19,15 +19,16 @@
#include <my_global.h> #include <my_global.h>
#include <my_sys.h> #include <my_sys.h>
#include <my_list.h> #include <my_list.h>
#include "thread_registry.h"
#ifdef __GNUC__ #ifdef __GNUC__
#pragma interface #pragma interface
#endif #endif
class Instance;
class Instance_map; class Instance_map;
class Thread_registry;
#include "thread_registry.h" struct GUARD_NODE;
#include "instance.h"
C_MODE_START C_MODE_START
...@@ -35,12 +36,11 @@ pthread_handler_decl(guardian, arg); ...@@ -35,12 +36,11 @@ pthread_handler_decl(guardian, arg);
C_MODE_END C_MODE_END
struct Guardian_thread_args struct Guardian_thread_args
{ {
Thread_registry &thread_registry; Thread_registry &thread_registry;
Instance_map *instance_map; Instance_map *instance_map;
uint monitoring_interval; int monitoring_interval;
Guardian_thread_args(Thread_registry &thread_registry_arg, Guardian_thread_args(Thread_registry &thread_registry_arg,
Instance_map *instance_map_arg, Instance_map *instance_map_arg,
...@@ -64,23 +64,41 @@ public: ...@@ -64,23 +64,41 @@ public:
Instance_map *instance_map_arg, Instance_map *instance_map_arg,
uint monitoring_interval_arg); uint monitoring_interval_arg);
~Guardian_thread(); ~Guardian_thread();
/* Main funtion of the thread */
void run(); void run();
/* Initialize list of guarded instances */
int init(); int init();
int start(); /* Request guardian shutdown. Stop instances if needed */
void request_shutdown(bool stop_instances);
/* Start instance protection */
int guard(Instance *instance); int guard(Instance *instance);
/* Stop instance protection */
int stop_guard(Instance *instance); int stop_guard(Instance *instance);
/* Returns true if guardian thread is stopped */
int is_stopped();
public:
pthread_cond_t COND_guardian;
private: private:
int add_instance_to_list(Instance *instance, LIST **list); /* Prepares Guardian shutdown. Stops instances is needed */
void move_to_list(LIST **from, LIST **to); 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: private:
/* states of an instance */
enum { NOT_STARTED= 1, STARTING, STARTED, JUST_CRASHED, CRASHED,
CRASHED_AND_ABANDONED, STOPPING };
pthread_mutex_t LOCK_guardian; pthread_mutex_t LOCK_guardian;
Thread_info thread_info; Thread_info thread_info;
LIST *guarded_instances; LIST *guarded_instances;
LIST *starting_instances;
MEM_ROOT alloc; MEM_ROOT alloc;
enum { MEM_ROOT_BLOCK_SIZE= 512 }; 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 */ #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */
...@@ -21,11 +21,25 @@ ...@@ -21,11 +21,25 @@
#include "instance.h" #include "instance.h"
#include "mysql_manager_error.h" #include "mysql_manager_error.h"
#include "log.h" #include "log.h"
#include "instance_map.h"
#include <my_sys.h> #include <my_sys.h>
#include <signal.h> #include <signal.h>
#include <m_string.h> #include <m_string.h>
#include <sys/wait.h> #include <sys/wait.h>
C_MODE_START
pthread_handler_decl(proxy, arg)
{
Instance *instance= (Instance *) arg;
instance->fork_and_monitor();
return 0;
}
C_MODE_END
/* /*
The method starts an instance. The method starts an instance.
...@@ -43,53 +57,115 @@ int Instance::start() ...@@ -43,53 +57,115 @@ int Instance::start()
{ {
pid_t pid; pid_t pid;
/* clear crash flag */
pthread_mutex_lock(&LOCK_instance);
crashed= 0;
pthread_mutex_unlock(&LOCK_instance);
if (!is_running()) if (!is_running())
{ {
log_info("trying to start instance %s", options.instance_name); if ((pid= options.get_pid()) != 0) /* check the pidfile */
switch (pid= fork()) { if (options.unlink_pidfile()) /* remove stalled pidfile */
case 0: log_error("cannot remove pidfile for instance %i, this might be \
if (fork()) /* zombie protection */ since IM lacks permmissions or hasn't found the pidifle",
exit(0); /* parent goes bye-bye */ options.instance_name);
else
{ /*
execv(options.mysqld_path, options.argv); No need to monitor this thread in the Thread_registry, as all
exit(1); instances are to be stopped during shutdown.
} */
case -1: 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 ER_CANNOT_START_INSTANCE;
default:
waitpid(pid, NULL, 0);
return 0;
} }
return 0;
} }
/* the instance is started already */ /* the instance is started already */
return ER_INSTANCE_ALREADY_STARTED; return ER_INSTANCE_ALREADY_STARTED;
} }
int Instance::cleanup()
void Instance::fork_and_monitor()
{ {
/* pid_t pid;
We cannot close connection in destructor, as mysql_close needs alarm log_info("starting instance %s", options.instance_name);
services which are definitely unavailaible at the time of destructor switch (pid= fork()) {
call. case 0:
*/ execv(options.mysqld_path, options.argv);
if (is_connected) /* exec never returns */
mysql_close(&mysql); exit(1);
return 0; case -1:
log_info("cannot fork() to start instance %s", options.instance_name);
return;
default:
wait(NULL);
/* 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_restarted);
/* wake guardian */
pthread_cond_signal(&instance_map->guardian->COND_guardian);
/* thread exits */
return;
}
/* we should never end up here */
DBUG_ASSERT(0);
}
Instance::Instance(): crashed(0)
{
pthread_mutex_init(&LOCK_instance, 0);
pthread_cond_init(&COND_instance_restarted, 0);
} }
Instance::~Instance() Instance::~Instance()
{ {
pthread_mutex_destroy(&LOCK_instance); pthread_mutex_destroy(&LOCK_instance);
pthread_cond_destroy(&COND_instance_restarted);
}
int Instance::is_crashed()
{
int val;
pthread_mutex_lock(&LOCK_instance);
val= crashed;
pthread_mutex_unlock(&LOCK_instance);
return val;
} }
bool Instance::is_running() bool Instance::is_running()
{ {
MYSQL mysql;
uint port= 0; uint port= 0;
const char *socket= NULL; const char *socket= NULL;
const char *password= "321rarepassword213";
const char *username= "645rareusername945";
const char *access_denied_message= "Access denied for user";
bool return_val;
if (options.mysqld_port) if (options.mysqld_port)
port= atoi(strchr(options.mysqld_port, '=') + 1); port= atoi(strchr(options.mysqld_port, '=') + 1);
...@@ -98,30 +174,39 @@ bool Instance::is_running() ...@@ -98,30 +174,39 @@ bool Instance::is_running()
socket= strchr(options.mysqld_socket, '=') + 1; socket= strchr(options.mysqld_socket, '=') + 1;
pthread_mutex_lock(&LOCK_instance); pthread_mutex_lock(&LOCK_instance);
if (!is_connected)
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))
{ {
mysql_init(&mysql); /*
if (mysql_real_connect(&mysql, LOCAL_HOST, options.mysqld_user, We have successfully connected to the server using fake
options.mysqld_password, username/password. Write a warning to the logfile.
NullS, port, */
socket, 0)) log_info("The Instance Manager was able to log into you server \
{ with faked compiled-in password while checking server status. \
mysql.reconnect= 1; Looks like something is wrong.");
is_connected= TRUE;
pthread_mutex_unlock(&LOCK_instance);
return TRUE;
}
mysql_close(&mysql);
pthread_mutex_unlock(&LOCK_instance); pthread_mutex_unlock(&LOCK_instance);
return FALSE; return_val= TRUE; /* server is alive */
} }
else if (!mysql_ping(&mysql)) else
{ {
pthread_mutex_unlock(&LOCK_instance); if (!strncmp(access_denied_message, mysql_error(&mysql),
return TRUE; sizeof(access_denied_message)-1))
{
return_val= TRUE;
}
else
return_val= FALSE;
} }
mysql_close(&mysql);
pthread_mutex_unlock(&LOCK_instance); pthread_mutex_unlock(&LOCK_instance);
return FALSE;
return return_val;
} }
...@@ -139,22 +224,67 @@ bool Instance::is_running() ...@@ -139,22 +224,67 @@ bool Instance::is_running()
int Instance::stop() int Instance::stop()
{ {
if (is_running()) pid_t pid;
struct timespec timeout;
int waitchild= DEFAULT_SHUTDOWN_DELAY;
if (options.shutdown_delay != NULL)
waitchild= atoi(options.shutdown_delay);
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))
goto err;
while (options.get_pid() != 0) /* while server isn't stopped */
{ {
if (mysql_shutdown(&mysql, SHUTDOWN_DEFAULT)) int status;
goto err;
mysql_close(&mysql); status= pthread_cond_timedwait(&COND_instance_restarted,
is_connected= FALSE; &LOCK_instance,
return 0; &timeout);
if (status == ETIMEDOUT)
break;
} }
pthread_mutex_unlock(&LOCK_instance);
kill_instance(SIGKILL);
return 0;
return ER_INSTANCE_IS_NOT_STARTED; return ER_INSTANCE_IS_NOT_STARTED;
err: err:
return ER_STOP_INSTANCE; return ER_STOP_INSTANCE;
} }
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. We execute this function to initialize instance parameters.
Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY.
...@@ -162,7 +292,13 @@ err: ...@@ -162,7 +292,13 @@ err:
int Instance::init(const char *name_arg) int Instance::init(const char *name_arg)
{ {
pthread_mutex_init(&LOCK_instance, 0);
return options.init(name_arg); return options.init(name_arg);
} }
int Instance::complete_initialization(Instance_map *instance_map_arg,
const char *mysqld_path)
{
instance_map= instance_map_arg;
return options.complete_initialization(mysqld_path);
}
...@@ -25,36 +25,40 @@ ...@@ -25,36 +25,40 @@
#pragma interface #pragma interface
#endif #endif
class Instance_map;
class Instance class Instance
{ {
public: public:
Instance(): is_connected(FALSE) Instance();
{}
~Instance();
~Instance();
int init(const char *name); int init(const char *name);
int complete_initialization(Instance_map *instance_map_arg,
const char *mysqld_path);
/* check if the instance is running and set up mysql connection if yes */
bool is_running(); bool is_running();
int start(); int start();
int stop(); int stop();
int cleanup(); /* send a signal to the instance */
void kill_instance(int signo);
int is_crashed();
void fork_and_monitor();
public: public:
enum { DEFAULT_SHUTDOWN_DELAY= 35 };
Instance_options options; Instance_options options;
/* connection to the instance */
MYSQL mysql;
private: private:
/* /*
Mutex protecting the instance. Currently we use it to avoid the Mutex protecting the instance. Currently we use it to avoid the
double start of the instance. This happens when the instance is starting double start of the instance. This happens when the instance is starting
and we issue the start command once more. and we issue the start command once more.
*/ */
int crashed;
pthread_mutex_t LOCK_instance; pthread_mutex_t LOCK_instance;
/* Here we store the state of the following connection */ pthread_cond_t COND_instance_restarted;
bool is_connected; Instance_map *instance_map;
}; };
#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H */ #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H */
...@@ -74,7 +74,7 @@ static void delete_instance(void *u) ...@@ -74,7 +74,7 @@ static void delete_instance(void *u)
1 - error occured 1 - error occured
*/ */
static int process_option(void * ctx, const char *group, const char *option) static int process_option(void *ctx, const char *group, const char *option)
{ {
Instance_map *map= NULL; Instance_map *map= NULL;
Instance *instance= NULL; Instance *instance= NULL;
...@@ -82,7 +82,8 @@ static int process_option(void * ctx, const char *group, const char *option) ...@@ -82,7 +82,8 @@ static int process_option(void * ctx, const char *group, const char *option)
map = (Instance_map*) ctx; map = (Instance_map*) ctx;
if (strncmp(group, prefix, sizeof prefix) == 0 && if (strncmp(group, prefix, sizeof prefix) == 0 &&
(my_isdigit(default_charset_info, group[sizeof prefix]))) ((my_isdigit(default_charset_info, group[sizeof prefix]))
|| group[sizeof(prefix)] == '\0'))
{ {
if ((instance= map->find(group, strlen(group))) == NULL) if ((instance= map->find(group, strlen(group))) == NULL)
{ {
...@@ -109,13 +110,9 @@ err_new_instance: ...@@ -109,13 +110,9 @@ err_new_instance:
C_MODE_END C_MODE_END
Instance_map::Instance_map(const char *default_mysqld_path_arg, Instance_map::Instance_map(const char *default_mysqld_path_arg)
const char *default_admin_user_arg,
const char *default_admin_password_arg)
{ {
mysqld_path= default_mysqld_path_arg; mysqld_path= default_mysqld_path_arg;
user= default_admin_user_arg;
password= default_admin_password_arg;
pthread_mutex_init(&LOCK_instance_map, 0); pthread_mutex_init(&LOCK_instance_map, 0);
} }
...@@ -181,48 +178,41 @@ Instance_map::find(const char *name, uint name_len) ...@@ -181,48 +178,41 @@ Instance_map::find(const char *name, uint name_len)
} }
void Instance_map::complete_initialization() int Instance_map::complete_initialization()
{ {
Instance *instance; Instance *instance;
uint i= 0; uint i= 0;
while (i < hash.records) if (hash.records == 0) /* no instances found */
{ {
instance= (Instance *) hash_element(&hash, i); if ((instance= new Instance) == 0)
instance->options.complete_initialization(mysqld_path, user, password); goto err;
i++;
if (instance->init("mysqld") || add_instance(instance))
goto err_instance;
/*
After an instance have been added to the instance_map,
hash_free should handle it's deletion.
*/
if (instance->complete_initialization(this, mysqld_path))
goto err;
} }
} else
while (i < hash.records)
{
int Instance_map::cleanup() instance= (Instance *) hash_element(&hash, i);
{ if (instance->complete_initialization(this, mysqld_path))
Instance *instance; goto err;
uint i= 0; i++;
}
while (i < hash.records)
{
instance= (Instance *) hash_element(&hash, i);
if (instance->cleanup())
return 1;
i++;
}
return 0;
}
Instance *
Instance_map::find(uint instance_number)
{
Instance *instance;
char name[80];
sprintf(name, "mysqld%i", instance_number); return 0;
pthread_mutex_lock(&LOCK_instance_map); err:
instance= (Instance *) hash_search(&hash, (byte *) name, strlen(name)); return 1;
pthread_mutex_unlock(&LOCK_instance_map); err_instance:
return instance; delete instance;
return 1;
} }
...@@ -230,13 +220,11 @@ Instance_map::find(uint instance_number) ...@@ -230,13 +220,11 @@ Instance_map::find(uint instance_number)
int Instance_map::load() int Instance_map::load()
{ {
int error; if (process_default_option_files("my", process_option, (void *) this) ||
complete_initialization())
error= process_default_option_files("my", process_option, (void *) this); return 1;
complete_initialization();
return error; return 0;
} }
......
...@@ -60,14 +60,11 @@ public: ...@@ -60,14 +60,11 @@ public:
Instance *find(uint instance_number); Instance *find(uint instance_number);
int flush_instances(); int flush_instances();
int cleanup();
int lock(); int lock();
int unlock(); int unlock();
int init(); int init();
Instance_map(const char *default_mysqld_path_arg, Instance_map(const char *default_mysqld_path_arg);
const char *default_admin_user_arg,
const char *default_admin_password_arg);
~Instance_map(); ~Instance_map();
/* loads options from config files */ /* loads options from config files */
...@@ -75,13 +72,10 @@ public: ...@@ -75,13 +72,10 @@ public:
/* adds instance to internal hash */ /* adds instance to internal hash */
int add_instance(Instance *instance); int add_instance(Instance *instance);
/* inits instances argv's after all options have been loaded */ /* inits instances argv's after all options have been loaded */
void complete_initialization(); int complete_initialization();
public: public:
const char *mysqld_path; const char *mysqld_path;
/* user an password to shutdown MySQL */
const char *user;
const char *password;
Guardian_thread *guardian; Guardian_thread *guardian;
private: private:
......
...@@ -19,20 +19,113 @@ ...@@ -19,20 +19,113 @@
#endif #endif
#include "instance_options.h" #include "instance_options.h"
#include "parse_output.h"
#include "buffer.h"
#include <my_sys.h> #include <my_sys.h>
#include <mysql.h> #include <mysql.h>
#include <signal.h> #include <signal.h>
#include <m_string.h> #include <m_string.h>
int Instance_options::complete_initialization(const char *default_path,
const char *default_user, /*
const char *default_password) Get compiled-in value of default_option
SYNOPSYS
get_default_option()
result buffer to put found value
result_len buffer size
oprion_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)
{ {
/* we need to reserve space for the final zero + possible default options */ int position= 0;
if (!(argv= (char**) alloc_root(&alloc, (options_array.elements + 1 int rc= 1;
+ MAX_NUMBER_OF_DEFAULT_OPTIONS) * sizeof(char*)))) char verbose_option[]= " --no-defaults --verbose --help";
goto err;
Buffer cmd(strlen(mysqld_path)+sizeof(verbose_option)+1);
if (cmd.get_size()) /* malloc succeeded */
{
cmd.append(position, mysqld_path, strlen(mysqld_path));
position+= strlen(mysqld_path);
cmd.append(position, verbose_option, sizeof(verbose_option) - 1);
position+= sizeof(verbose_option) - 1;
cmd.append(position, "\0", 1);
if (cmd.is_error())
goto err;
/* get the value from "mysqld --help --verbose" */
rc= parse_output_and_get_value(cmd.buffer, option_name + 2,
result, result_len);
}
return rc;
err:
return 1;
}
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;
}
else
return 0;
}
int Instance_options::complete_initialization(const char *default_path)
{
const char *tmp;
if (mysqld_path == NULL) if (mysqld_path == NULL)
{ {
...@@ -40,22 +133,37 @@ int Instance_options::complete_initialization(const char *default_path, ...@@ -40,22 +133,37 @@ int Instance_options::complete_initialization(const char *default_path,
goto err; goto err;
} }
/* this option must be first in the argv */ if (!(tmp= strdup_root(&alloc, "--no-defaults")))
if (add_to_argv(mysqld_path))
goto err; goto err;
/* the following options are not for argv */ if (mysqld_pid_file == NULL)
if (mysqld_user == NULL)
{ {
if (!(mysqld_user= strdup_root(&alloc, default_user))) char pidfilename[MAX_PATH_LEN];
goto err; char hostname[MAX_PATH_LEN];
if (!gethostname(hostname, sizeof(hostname) - 1))
strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", hostname, "-",
instance_name, ".pid", NullS);
else
strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name,
".pid", NullS);
add_option(pidfilename);
} }
if (mysqld_password == NULL) if (get_pid_filename(pid_file_with_path))
{ goto err;
if (!(mysqld_password= strdup_root(&alloc, default_password)))
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, memcpy((gptr) (argv + filled_default_options), options_array.buffer,
options_array.elements*sizeof(char*)); options_array.elements*sizeof(char*));
...@@ -102,9 +210,8 @@ int Instance_options::add_option(const char* option) ...@@ -102,9 +210,8 @@ int Instance_options::add_option(const char* option)
{"--bind-address=", 15, &mysqld_bind_address, SAVE_WHOLE_AND_ADD}, {"--bind-address=", 15, &mysqld_bind_address, SAVE_WHOLE_AND_ADD},
{"--pid-file=", 11, &mysqld_pid_file, SAVE_WHOLE_AND_ADD}, {"--pid-file=", 11, &mysqld_pid_file, SAVE_WHOLE_AND_ADD},
{"--mysqld-path=", 14, &mysqld_path, SAVE_VALUE}, {"--mysqld-path=", 14, &mysqld_path, SAVE_VALUE},
{"--admin-user=", 13, &mysqld_user, SAVE_VALUE}, {"--nonguarded", 9, &nonguarded, SAVE_WHOLE},
{"--admin-password=", 17, &mysqld_password, SAVE_VALUE}, {"--shutdown_delay", 9, &shutdown_delay, SAVE_VALUE},
{"--guarded", 9, &is_guarded, SAVE_WHOLE},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
struct selected_options_st *selected_options; struct selected_options_st *selected_options;
...@@ -131,6 +238,9 @@ int Instance_options::add_option(const char* option) ...@@ -131,6 +238,9 @@ int Instance_options::add_option(const char* option)
} }
} }
/* if we haven't returned earlier we should just save the option */
insert_dynamic(&options_array,(gptr) &tmp);
return 0; return 0;
err: err:
...@@ -148,6 +258,18 @@ int Instance_options::add_to_argv(const char* option) ...@@ -148,6 +258,18 @@ int Instance_options::add_to_argv(const char* option)
} }
/* 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. We execute this function to initialize some options.
Return value: 0 - ok. 1 - unable to allocate memory. Return value: 0 - ok. 1 - unable to allocate memory.
......
...@@ -38,21 +38,29 @@ class Instance_options ...@@ -38,21 +38,29 @@ class Instance_options
public: public:
Instance_options() : Instance_options() :
mysqld_socket(0), mysqld_datadir(0), mysqld_bind_address(0), mysqld_socket(0), mysqld_datadir(0), mysqld_bind_address(0),
mysqld_pid_file(0), mysqld_port(0), mysqld_path(0), mysqld_user(0), mysqld_pid_file(0), mysqld_port(0), mysqld_path(0), nonguarded(0),
mysqld_password(0), is_guarded(0), filled_default_options(0) shutdown_delay(0), filled_default_options(0)
{} {}
~Instance_options(); ~Instance_options();
/* fills in argv */ /* fills in argv */
int complete_initialization(const char *default_path, int complete_initialization(const char *default_path);
const char *default_user,
const char *default_password);
int add_option(const char* option); int add_option(const char* option);
int init(const char *instance_name_arg); int init(const char *instance_name_arg);
pid_t get_pid();
int get_pid_filename(char *result);
int unlink_pidfile();
void print_argv();
public: public:
enum { MAX_NUMBER_OF_DEFAULT_OPTIONS= 1 }; /*
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 }; enum { MEM_ROOT_BLOCK_SIZE= 512 };
char pid_file_with_path[MAX_PATH_LEN];
char **argv; char **argv;
/* We need the some options, so we store them as a separate pointers */ /* We need the some options, so we store them as a separate pointers */
const char *mysqld_socket; const char *mysqld_socket;
...@@ -63,12 +71,14 @@ public: ...@@ -63,12 +71,14 @@ public:
uint instance_name_len; uint instance_name_len;
const char *instance_name; const char *instance_name;
const char *mysqld_path; const char *mysqld_path;
const char *mysqld_user; const char *nonguarded;
const char *mysqld_password; const char *shutdown_delay;
const char *is_guarded; /* this value is computed and cashed here */
DYNAMIC_ARRAY options_array; DYNAMIC_ARRAY options_array;
private: private:
int add_to_argv(const char *option); int add_to_argv(const char *option);
int get_default_option(char *result, size_t result_len,
const char *option_name);
private: private:
uint filled_default_options; uint filled_default_options;
MEM_ROOT alloc; MEM_ROOT alloc;
......
...@@ -57,13 +57,11 @@ Listener_thread::Listener_thread(const Listener_thread_args &args) : ...@@ -57,13 +57,11 @@ Listener_thread::Listener_thread(const Listener_thread_args &args) :
,total_connection_count(0) ,total_connection_count(0)
,thread_info(pthread_self()) ,thread_info(pthread_self())
{ {
thread_registry.register_thread(&thread_info);
} }
Listener_thread::~Listener_thread() Listener_thread::~Listener_thread()
{ {
thread_registry.unregister_thread(&thread_info);
} }
...@@ -82,6 +80,13 @@ void Listener_thread::run() ...@@ -82,6 +80,13 @@ void Listener_thread::run()
enum { LISTEN_BACK_LOG_SIZE = 5 }; // standard backlog size enum { LISTEN_BACK_LOG_SIZE = 5 }; // standard backlog size
int flags; int flags;
int arg= 1; /* value to be set by setsockopt */ int arg= 1; /* value to be set by setsockopt */
int unix_socket;
uint im_port;
thread_registry.register_thread(&thread_info);
my_thread_init();
/* I. prepare 'listen' sockets */ /* I. prepare 'listen' sockets */
int ip_socket= socket(AF_INET, SOCK_STREAM, 0); int ip_socket= socket(AF_INET, SOCK_STREAM, 0);
...@@ -89,8 +94,7 @@ void Listener_thread::run() ...@@ -89,8 +94,7 @@ void Listener_thread::run()
{ {
log_error("Listener_thead::run(): socket(AF_INET) failed, %s", log_error("Listener_thead::run(): socket(AF_INET) failed, %s",
strerror(errno)); strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
struct sockaddr_in ip_socket_address; struct sockaddr_in ip_socket_address;
...@@ -104,7 +108,7 @@ void Listener_thread::run() ...@@ -104,7 +108,7 @@ void Listener_thread::run()
} }
else else
im_bind_addr= htonl(INADDR_ANY); im_bind_addr= htonl(INADDR_ANY);
uint im_port= options.port_number; im_port= options.port_number;
ip_socket_address.sin_family= AF_INET; ip_socket_address.sin_family= AF_INET;
ip_socket_address.sin_addr.s_addr = im_bind_addr; ip_socket_address.sin_addr.s_addr = im_bind_addr;
...@@ -119,16 +123,14 @@ void Listener_thread::run() ...@@ -119,16 +123,14 @@ void Listener_thread::run()
{ {
log_error("Listener_thread::run(): bind(ip socket) failed, '%s'", log_error("Listener_thread::run(): bind(ip socket) failed, '%s'",
strerror(errno)); strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
if (listen(ip_socket, LISTEN_BACK_LOG_SIZE)) if (listen(ip_socket, LISTEN_BACK_LOG_SIZE))
{ {
log_error("Listener_thread::run(): listen(ip socket) failed, %s", log_error("Listener_thread::run(): listen(ip socket) failed, %s",
strerror(errno)); strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
/* set the socket nonblocking */ /* set the socket nonblocking */
flags= fcntl(ip_socket, F_GETFL, 0); flags= fcntl(ip_socket, F_GETFL, 0);
...@@ -140,13 +142,12 @@ void Listener_thread::run() ...@@ -140,13 +142,12 @@ void Listener_thread::run()
log_info("accepting connections on ip socket"); log_info("accepting connections on ip socket");
/*--------------------------------------------------------------*/ /*--------------------------------------------------------------*/
int unix_socket= socket(AF_UNIX, SOCK_STREAM, 0); unix_socket= socket(AF_UNIX, SOCK_STREAM, 0);
if (unix_socket == INVALID_SOCKET) if (unix_socket == INVALID_SOCKET)
{ {
log_error("Listener_thead::run(): socket(AF_UNIX) failed, %s", log_error("Listener_thead::run(): socket(AF_UNIX) failed, %s",
strerror(errno)); strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
struct sockaddr_un unix_socket_address; struct sockaddr_un unix_socket_address;
...@@ -169,8 +170,7 @@ void Listener_thread::run() ...@@ -169,8 +170,7 @@ void Listener_thread::run()
log_error("Listener_thread::run(): bind(unix socket) failed, " log_error("Listener_thread::run(): bind(unix socket) failed, "
"socket file name is '%s', error '%s'", "socket file name is '%s', error '%s'",
unix_socket_address.sun_path, strerror(errno)); unix_socket_address.sun_path, strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
umask(old_mask); umask(old_mask);
...@@ -178,8 +178,7 @@ void Listener_thread::run() ...@@ -178,8 +178,7 @@ void Listener_thread::run()
{ {
log_error("Listener_thread::run(): listen(unix socket) failed, %s", log_error("Listener_thread::run(): listen(unix socket) failed, %s",
strerror(errno)); strerror(errno));
thread_registry.request_shutdown(); goto err;
return;
} }
/* set the socket nonblocking */ /* set the socket nonblocking */
...@@ -205,7 +204,15 @@ void Listener_thread::run() ...@@ -205,7 +204,15 @@ void Listener_thread::run()
while (thread_registry.is_shutdown() == false) while (thread_registry.is_shutdown() == false)
{ {
fd_set read_fds_arg= read_fds; fd_set read_fds_arg= read_fds;
/*
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, 0); int rc= select(n, &read_fds_arg, 0, 0, 0);
if (rc == -1 && errno != EINTR) if (rc == -1 && errno != EINTR)
log_error("Listener_thread::run(): select() failed, %s", log_error("Listener_thread::run(): select() failed, %s",
strerror(errno)); strerror(errno));
...@@ -256,6 +263,16 @@ void Listener_thread::run() ...@@ -256,6 +263,16 @@ void Listener_thread::run()
close(unix_socket); close(unix_socket);
close(ip_socket); close(ip_socket);
unlink(unix_socket_address.sun_path); unlink(unix_socket_address.sun_path);
thread_registry.unregister_thread(&thread_info);
my_thread_end();
return;
err:
thread_registry.unregister_thread(&thread_info);
thread_registry.request_shutdown();
my_thread_end();
return;
} }
......
...@@ -32,8 +32,8 @@ ...@@ -32,8 +32,8 @@
SYNOPSYS SYNOPSYS
log() log()
*/ */
static inline void log(FILE *file, const char *format, va_list args) static inline void log(FILE *file, const char *format, va_list args)
{ {
/* /*
log() should be thread-safe; it implies that we either call fprintf() log() should be thread-safe; it implies that we either call fprintf()
......
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* /*
Logging facilities. Logging facilities.
Two logging streams are supported: error log and info log. Additionally Two logging streams are supported: error log and info log. Additionally
libdbug may be used for debug information output. libdbug may be used for debug information output.
ANSI C buffered I/O is used to perform logging. ANSI C buffered I/O is used to perform logging.
Logging is performed via stdout/stder, so one can reopen them to point to Logging is performed via stdout/stder, so one can reopen them to point to
ordinary files. To initialize loggin environment log_init() must be called. ordinary files. To initialize loggin environment log_init() must be called.
Rationale: Rationale:
- no MYSQL_LOG as it has BIN mode, and not easy to fetch from sql_class.h - 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 - no constructors/desctructors to make logging available all the time
......
...@@ -16,12 +16,6 @@ ...@@ -16,12 +16,6 @@
#include "manager.h" #include "manager.h"
#include <my_global.h>
#include <my_sys.h>
#include <m_string.h>
#include <signal.h>
#include <thr_alarm.h>
#include "thread_registry.h" #include "thread_registry.h"
#include "listener.h" #include "listener.h"
#include "instance_map.h" #include "instance_map.h"
...@@ -30,6 +24,14 @@ ...@@ -30,6 +24,14 @@
#include "log.h" #include "log.h"
#include "guardian.h" #include "guardian.h"
#include <my_global.h>
#include <my_sys.h>
#include <m_string.h>
#include <signal.h>
#include <thr_alarm.h>
#include <sys/wait.h>
static int create_pid_file(const char *pid_file_name) static int create_pid_file(const char *pid_file_name)
{ {
if (FILE *pid_file= my_fopen(pid_file_name, if (FILE *pid_file= my_fopen(pid_file_name,
...@@ -65,9 +67,7 @@ void manager(const Options &options) ...@@ -65,9 +67,7 @@ void manager(const Options &options)
*/ */
User_map user_map; User_map user_map;
Instance_map instance_map(options.default_mysqld_path, Instance_map instance_map(options.default_mysqld_path);
options.default_admin_user,
options.default_admin_password);
Guardian_thread guardian_thread(thread_registry, Guardian_thread guardian_thread(thread_registry,
&instance_map, &instance_map,
options.monitoring_interval); options.monitoring_interval);
...@@ -77,8 +77,18 @@ void manager(const Options &options) ...@@ -77,8 +77,18 @@ void manager(const Options &options)
instance_map.guardian= &guardian_thread; instance_map.guardian= &guardian_thread;
if (instance_map.init() || user_map.init() || instance_map.load() || if (instance_map.init() || user_map.init())
user_map.load(options.password_file_name)) return;
if (instance_map.load())
{
log_error("Cannot init instances repository. This might be caused by "
"the wrong config file options. For instance, missing mysqld "
"binary. Aborting.");
return;
}
if (user_map.load(options.password_file_name))
return; return;
/* write pid file */ /* write pid file */
...@@ -126,6 +136,12 @@ void manager(const Options &options) ...@@ -126,6 +136,12 @@ void manager(const Options &options)
pthread_attr_t guardian_thd_attr; pthread_attr_t guardian_thd_attr;
int rc; 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.
*/
pthread_attr_init(&guardian_thd_attr); pthread_attr_init(&guardian_thd_attr);
pthread_attr_setdetachstate(&guardian_thd_attr, PTHREAD_CREATE_DETACHED); pthread_attr_setdetachstate(&guardian_thd_attr, PTHREAD_CREATE_DETACHED);
rc= pthread_create(&guardian_thd_id, &guardian_thd_attr, guardian, rc= pthread_create(&guardian_thd_id, &guardian_thd_attr, guardian,
...@@ -153,26 +169,45 @@ void manager(const Options &options) ...@@ -153,26 +169,45 @@ void manager(const Options &options)
more then 10 alarms at the same time. more then 10 alarms at the same time.
*/ */
init_thr_alarm(10); init_thr_alarm(10);
/* init list of guarded instances */
guardian_thread.init();
/* /*
Now we can init the list of guarded instances. We have to do it after After the list of guarded instances have been initialized,
alarm structures initialization as we have to use net_* functions while Guardian should start them.
making the list. And they in their turn need alarms for timeout suppport.
*/ */
guardian_thread.start(); pthread_cond_signal(&guardian_thread.COND_guardian);
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
while (!shutdown_complete) while (!shutdown_complete)
{ {
sigwait(&mask, &signo); int status= 0;
if ((status= my_sigwait(&mask, &signo)) != 0)
{
log_error("sigwait() failed");
goto err;
}
switch (signo) switch (signo)
{ {
case THR_SERVER_ALARM: case THR_SERVER_ALARM:
process_alarm(signo); process_alarm(signo);
break; break;
default: default:
thread_registry.deliver_shutdown(); {
shutdown_complete= TRUE; 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;
}
}
break; break;
} }
} }
...@@ -181,9 +216,6 @@ err: ...@@ -181,9 +216,6 @@ err:
/* delete the pid file */ /* delete the pid file */
my_delete(options.pid_file_name, MYF(0)); my_delete(options.pid_file_name, MYF(0));
/* close permanent connections to the running instances */
instance_map.cleanup();
/* free alarm structures */ /* free alarm structures */
end_thr_alarm(1); end_thr_alarm(1);
/* don't pthread_exit to kill all threads who did not shut down in time */ /* don't pthread_exit to kill all threads who did not shut down in time */
......
...@@ -45,8 +45,8 @@ static const char *mysqld_error_message(unsigned sql_errno) ...@@ -45,8 +45,8 @@ static const char *mysqld_error_message(unsigned sql_errno)
case ER_BAD_INSTANCE_NAME: case ER_BAD_INSTANCE_NAME:
return "Bad instance name. Check that the instance with such a name exists"; return "Bad instance name. Check that the instance with such a name exists";
case ER_INSTANCE_IS_NOT_STARTED: case ER_INSTANCE_IS_NOT_STARTED:
return "Cannot stop instance. Perhaps the instance is not started or you" return "Cannot stop instance. Perhaps the instance is not started, or was started"
" have specified wrong username/password in the config file"; "manually, so IM cannot find the pidfile.";
case ER_INSTANCE_ALREADY_STARTED: case ER_INSTANCE_ALREADY_STARTED:
return "The instance is already started"; return "The instance is already started";
case ER_CANNOT_START_INSTANCE: case ER_CANNOT_START_INSTANCE:
......
...@@ -82,7 +82,6 @@ private: ...@@ -82,7 +82,6 @@ private:
private: private:
/* Names are conventionally the same as in mysqld */ /* Names are conventionally the same as in mysqld */
int check_connection(); int check_connection();
int check_user(const char *user, const char *password);
int do_command(); int do_command();
int dispatch_command(enum enum_server_command command, int dispatch_command(enum enum_server_command command,
const char *text, uint len); const char *text, uint len);
...@@ -287,12 +286,6 @@ int Mysql_connection_thread::check_connection() ...@@ -287,12 +286,6 @@ int Mysql_connection_thread::check_connection()
} }
int Mysql_connection_thread::check_user(const char *user, const char *password)
{
return 0;
}
int Mysql_connection_thread::do_command() int Mysql_connection_thread::do_command()
{ {
char *packet; char *packet;
......
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
#include <my_sys.h> #include <my_sys.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
...@@ -54,6 +56,8 @@ ...@@ -54,6 +56,8 @@
static void init_environment(char *progname); static void init_environment(char *progname);
static void daemonize(const char *log_file_name); static void daemonize(const char *log_file_name);
static void angel(const Options &options); 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);
/* /*
...@@ -68,7 +72,20 @@ int main(int argc, char *argv[]) ...@@ -68,7 +72,20 @@ int main(int argc, char *argv[])
{ {
init_environment(argv[0]); init_environment(argv[0]);
Options options; Options options;
options.load(argc, argv); struct passwd *user_info;
if (options.load(argc, argv))
goto err;
if ((user_info= check_user(options.user)))
{
if (set_user(options.user, user_info))
{
options.cleanup();
return 1;
}
}
if (options.run_as_service) if (options.run_as_service)
{ {
/* forks, and returns only in child */ /* forks, and returns only in child */
...@@ -77,11 +94,84 @@ int main(int argc, char *argv[]) ...@@ -77,11 +94,84 @@ int main(int argc, char *argv[])
angel(options); angel(options);
} }
manager(options); manager(options);
options.cleanup();
my_end(0);
return 0; return 0;
err:
my_end(0);
return 1;
} }
/******************* Auxilary functions implementation **********************/ /******************* Auxilary functions implementation **********************/
/* Change to run as another user if started with --user */
static struct passwd *check_user(const char *user)
{
#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__)
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);
#endif
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;
}
/* /*
Init environment, common for daemon and non-daemon Init environment, common for daemon and non-daemon
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include <my_global.h> #include <my_global.h>
#include <my_sys.h> #include <my_sys.h>
#include <my_getopt.h> #include <my_getopt.h>
#include <m_string.h>
#include <mysql_com.h>
#include "priv.h" #include "priv.h"
...@@ -35,11 +37,12 @@ const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); ...@@ -35,11 +37,12 @@ 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::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME);
const char *Options::password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); const char *Options::password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME);
const char *Options::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); const char *Options::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH);
const char *Options::default_admin_user= QUOTE(DEFAULT_USER); const char *Options::bind_address= 0; /* No default value */
const char *Options::default_admin_password= QUOTE(DEFAULT_PASSWORD); const char *Options::user= 0; /* No default value */
const char *Options::bind_address= 0; /* No default value */
uint Options::monitoring_interval= DEFAULT_MONITORING_INTERVAL; uint Options::monitoring_interval= DEFAULT_MONITORING_INTERVAL;
uint Options::port_number= DEFAULT_PORT; uint Options::port_number= DEFAULT_PORT;
/* just to declare */
char **Options::saved_argv;
/* /*
List of options, accepted by the instance manager. List of options, accepted by the instance manager.
...@@ -54,9 +57,6 @@ enum options { ...@@ -54,9 +57,6 @@ enum options {
OPT_MYSQLD_PATH, OPT_MYSQLD_PATH,
OPT_RUN_AS_SERVICE, OPT_RUN_AS_SERVICE,
OPT_USER, OPT_USER,
OPT_PASSWORD,
OPT_DEFAULT_ADMIN_USER,
OPT_DEFAULT_ADMIN_PASSWORD,
OPT_MONITORING_INTERVAL, OPT_MONITORING_INTERVAL,
OPT_PORT, OPT_PORT,
OPT_BIND_ADDRESS OPT_BIND_ADDRESS
...@@ -79,13 +79,16 @@ static struct my_option my_long_options[] = ...@@ -79,13 +79,16 @@ static struct my_option my_long_options[] =
(gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 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.", { "bind-address", OPT_BIND_ADDRESS, "Bind address to use for connection.",
(gptr *) &Options::bind_address, (gptr *) &Options::bind_address, (gptr *) &Options::bind_address, (gptr *) &Options::bind_address,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
{ "port", OPT_PORT, "Port number to use for connections", { "port", OPT_PORT, "Port number to use for connections",
(gptr *) &Options::port_number, (gptr *) &Options::port_number, (gptr *) &Options::port_number, (gptr *) &Options::port_number,
0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 0, GET_UINT, REQUIRED_ARG, DEFAULT_PORT, 0, 0, 0, 0, 0 },
{ "password-file", OPT_PASSWORD_FILE, "Look for Instane Manager users" { "password-file", OPT_PASSWORD_FILE, "Look for Instane Manager users"
" and passwords here.", " and passwords here.",
...@@ -98,28 +101,22 @@ static struct my_option my_long_options[] = ...@@ -98,28 +101,22 @@ static struct my_option my_long_options[] =
(gptr *) &Options::default_mysqld_path, (gptr *) &Options::default_mysqld_path, (gptr *) &Options::default_mysqld_path, (gptr *) &Options::default_mysqld_path,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
{ "default-admin-user", OPT_DEFAULT_ADMIN_USER, "Username to shutdown MySQL"
" instances.",
(gptr *) &Options::default_admin_user,
(gptr *) &Options::default_admin_user,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
{ "default-admin-password", OPT_DEFAULT_ADMIN_PASSWORD, "Password to"
"shutdown MySQL instances.",
(gptr *) &Options::default_admin_password,
(gptr *) &Options::default_admin_password,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
{ "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor instances" { "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor instances"
" in seconds.", " in seconds.",
(gptr *) &Options::monitoring_interval, (gptr *) &Options::monitoring_interval,
(gptr *) &Options::monitoring_interval, (gptr *) &Options::monitoring_interval,
0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 0, GET_UINT, REQUIRED_ARG, DEFAULT_MONITORING_INTERVAL,
0, 0, 0, 0, 0 },
{ "run-as-service", OPT_RUN_AS_SERVICE, { "run-as-service", OPT_RUN_AS_SERVICE,
"Daemonize and start angel process.", (gptr *) &Options::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 }, 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 },
{ "version", 'V', "Output version information and exit.", 0, 0, 0, { "version", 'V', "Output version information and exit.", 0, 0, 0,
GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 },
...@@ -150,6 +147,34 @@ static void usage() ...@@ -150,6 +147,34 @@ static void usage()
my_print_variables(my_long_options); my_print_variables(my_long_options);
} }
static void passwd()
{
char user[1024], pw[1024], *p;
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;
fprintf(stderr, "Enter password: ");
if (! fgets(pw, sizeof(pw), stdin))
{
fprintf(stderr, "Unable to read password.\n");
return;
}
if ((p= strchr(pw, '\n'))) *p= 0;
make_scrambled_password(crypted_pw, pw);
printf("%s:%s\n", user, crypted_pw);
}
C_MODE_START C_MODE_START
static my_bool static my_bool
...@@ -161,7 +186,9 @@ get_one_option(int optid, ...@@ -161,7 +186,9 @@ get_one_option(int optid,
case 'V': case 'V':
version(); version();
exit(0); exit(0);
case 'I': case 'P':
passwd();
exit(0);
case '?': case '?':
usage(); usage();
exit(0); exit(0);
...@@ -180,12 +207,20 @@ C_MODE_END ...@@ -180,12 +207,20 @@ C_MODE_END
May not return. May not return.
*/ */
void Options::load(int argc, char **argv) int Options::load(int argc, char **argv)
{ {
int rc;
/* config-file options are prepended to command-line ones */ /* config-file options are prepended to command-line ones */
load_defaults("my", default_groups, &argc, &argv); load_defaults("my", default_groups, &argc, &argv);
if (int rc= handle_options(&argc, &argv, my_long_options, get_one_option)) if (rc= handle_options(&argc, &argv, my_long_options, get_one_option))
exit(rc); return rc;
Options::saved_argv= argv;
return 0;
} }
void Options::cleanup()
{
/* free_defaults returns nothing */
free_defaults(Options::saved_argv);
}
...@@ -34,13 +34,15 @@ struct Options ...@@ -34,13 +34,15 @@ struct Options
static const char *socket_file_name; static const char *socket_file_name;
static const char *password_file_name; static const char *password_file_name;
static const char *default_mysqld_path; static const char *default_mysqld_path;
static const char *default_admin_user; static const char *user;
static const char *default_admin_password;
static uint monitoring_interval; static uint monitoring_interval;
static uint port_number; static uint port_number;
static const char *bind_address; static const char *bind_address;
static void load(int argc, char **argv); static char **saved_argv;
static int load(int argc, char **argv);
void cleanup();
}; };
#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H
...@@ -49,29 +49,6 @@ static struct tokens_st tokens[]= { ...@@ -49,29 +49,6 @@ static struct tokens_st tokens[]= {
}; };
/*
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)
{
const char *word_end;
/* skip space */
while (my_isspace(default_charset_info, **text))
++(*text);
word_end= *text;
while (my_isalnum(default_charset_info, *word_end))
++word_end;
*word_len= word_end - *text;
}
/* /*
Returns token no if word corresponds to some token, otherwise returns Returns token no if word corresponds to some token, otherwise returns
TOK_NOT_FOUND TOK_NOT_FOUND
......
...@@ -20,4 +20,34 @@ ...@@ -20,4 +20,34 @@
Command *parse_command(Command_factory *factory, const char *text); Command *parse_command(Command_factory *factory, 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;
*word_len= word_end - *text;
}
#endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PARSE_H */ #endif /* 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 "parse.h"
#include <stdio.h>
#include <my_global.h>
#include <my_sys.h>
#include <string.h>
/*
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)
result_len self-explanatory
DESCRIPTION
Parse output of the "command". Find the "word" and return the next one
RETURN
0 - ok
1 - error occured
*/
int parse_output_and_get_value(const char *command, const char *word,
char *result, size_t result_len)
{
FILE *output;
uint wordlen;
/* should be enought to store the string from the output */
enum { MAX_LINE_LEN= 512 };
char linebuf[MAX_LINE_LEN];
wordlen= strlen(word);
if ((output= popen(command, "r")) == NULL)
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 lineword_len= 0;
char *linep= linebuf;
linebuf[sizeof(linebuf) - 1]= '\0'; /* safety */
/*
Get the word, which might contain non-alphanumeric characters. (Usually
these are '/', '-' and '.' in the path expressions and filenames)
*/
get_word((const char **) &linep, &lineword_len, NONSPACE);
if (!strncmp(word, linep, wordlen))
{
/*
If we have found the word, return the next one. This is usually
an option value.
*/
linep+= lineword_len; /* swallow the previous one */
get_word((const char **) &linep, &lineword_len, NONSPACE);
if (result_len <= lineword_len)
goto err;
strncpy(result, linep, lineword_len);
result[lineword_len]= '\0';
goto pclose;
}
}
pclose:
/* we are not interested in the termination status */
pclose(output);
return 0;
err:
return 1;
}
/* 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 */
int parse_output_and_get_value(const char *command, const char *word,
char *result, size_t result_len);
...@@ -154,7 +154,8 @@ int send_fields(struct st_net *net, LIST *fields) ...@@ -154,7 +154,8 @@ int send_fields(struct st_net *net, LIST *fields)
store_to_string(&send_buff, (char *) "", &position); /* table name alias */ store_to_string(&send_buff, (char *) "", &position); /* table name alias */
store_to_string(&send_buff, field->name, &position); /* column name */ store_to_string(&send_buff, field->name, &position); /* column name */
store_to_string(&send_buff, field->name, &position); /* column name alias */ store_to_string(&send_buff, field->name, &position); /* column name alias */
if (send_buff.reserve(position, 12)) send_buff.reserve(position, 12);
if (send_buff.is_error())
goto err; goto err;
send_buff.buffer[position++]= 12; send_buff.buffer[position++]= 12;
int2store(send_buff.buffer + position, 1); /* charsetnr */ int2store(send_buff.buffer + position, 1); /* charsetnr */
......
...@@ -197,6 +197,7 @@ void Thread_registry::deliver_shutdown() ...@@ -197,6 +197,7 @@ void Thread_registry::deliver_shutdown()
if (info->current_cond) if (info->current_cond)
pthread_cond_signal(info->current_cond); pthread_cond_signal(info->current_cond);
} }
pthread_mutex_unlock(&LOCK_thread_registry); pthread_mutex_unlock(&LOCK_thread_registry);
} }
......
/* 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 */
#ifdef __GNUC__
#pragma implementation
#endif
#include "thread_repository.h"
#include <assert.h>
#include <signal.h>
#include "log.h"
/* Kick-off signal handler */
enum { THREAD_KICK_OFF_SIGNAL= SIGUSR2 };
static void handle_signal(int __attribute__((unused)) sig_no)
{
}
/*
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_repository::Thread_repository() :
shutdown_in_progress(false)
{
pthread_mutex_init(&LOCK_thread_repository, 0);
pthread_cond_init(&COND_thread_repository_is_empty, 0);
/* head is used by-value to simplify nodes inserting */
head.next= head.prev= &head;
}
Thread_repository::~Thread_repository()
{
/* Check that no one uses the repository. */
pthread_mutex_lock(&LOCK_thread_repository);
/* All threads must unregister */
DBUG_ASSERT(head.next == &head);
pthread_mutex_unlock(&LOCK_thread_repository);
pthread_cond_destroy(&COND_thread_repository_is_empty);
pthread_mutex_destroy(&LOCK_thread_repository);
}
/*
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_repository::register_thread(Thread_info *info)
{
struct sigaction sa;
sa.sa_handler= handle_signal;
sa.sa_flags= 0;
sigemptyset(&sa.sa_mask);
sigaction(THREAD_KICK_OFF_SIGNAL, &sa, 0);
info->current_cond= 0;
pthread_mutex_lock(&LOCK_thread_repository);
info->next= &head;
info->prev= head.prev;
head.prev->next= info;
head.prev= info;
pthread_mutex_unlock(&LOCK_thread_repository);
}
/*
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_repository::unregister_thread(Thread_info *info)
{
pthread_mutex_lock(&LOCK_thread_repository);
info->prev->next= info->next;
info->next->prev= info->prev;
if (head.next == &head)
pthread_cond_signal(&COND_thread_repository_is_empty);
pthread_mutex_unlock(&LOCK_thread_repository);
}
/*
Check whether shutdown is in progress, and if yes, return immidiately.
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_repository::cond_wait(Thread_info *info, pthread_cond_t *cond,
pthread_mutex_t *mutex, bool *is_shutdown)
{
pthread_mutex_lock(&LOCK_thread_repository);
*is_shutdown= shutdown_in_progress;
if (*is_shutdown)
{
pthread_mutex_unlock(&LOCK_thread_repository);
return 0;
}
info->current_cond= cond;
pthread_mutex_unlock(&LOCK_thread_repository);
/* sic: race condition here, cond can be signaled in deliver_shutdown */
int rc= pthread_cond_wait(cond, mutex);
pthread_mutex_lock(&LOCK_thread_repository);
info->current_cond= 0;
*is_shutdown= shutdown_in_progress;
pthread_mutex_unlock(&LOCK_thread_repository);
return rc;
}
/*
Deliver shutdown message to the workers crew.
As it's impossible to avoid all race conditions, we signal latecomers
again.
*/
void Thread_repository::deliver_shutdown()
{
struct timespec shutdown_time;
set_timespec(shutdown_time, 1);
Thread_info *info;
pthread_mutex_lock(&LOCK_thread_repository);
shutdown_in_progress= true;
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);
}
while (pthread_cond_timedwait(&COND_thread_repository_is_empty,
&LOCK_thread_repository,
&shutdown_time) != ETIMEDOUT &&
head.next != &head)
;
/*
If previous signals did not reach some threads, they must be sleeping
in pthread_cond_wait or 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_repository);
}
#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH
#define INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH
/* 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 consituting 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.
*/
#ifdef __GNUC__
#pragma interface
#endif
#include <my_global.h>
#include <my_pthread.h>
/*
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
{
pthread_cond_t *current_cond;
Thread_info *prev, *next;
pthread_t thread_id;
Thread_info() {}
friend class Thread_repository;
public:
Thread_info(pthread_t thread_id_arg) : thread_id(thread_id_arg) {}
};
/*
Thread_repository - contains handles for each worker thread to deliver
signal information to workers.
*/
class Thread_repository
{
public:
Thread_repository();
~Thread_repository();
void register_thread(Thread_info *info);
void unregister_thread(Thread_info *info);
void deliver_shutdown();
inline bool is_shutdown();
int cond_wait(Thread_info *info, pthread_cond_t *cond,
pthread_mutex_t *mutex, bool *is_shutdown);
private:
Thread_info head;
bool shutdown_in_progress;
pthread_mutex_t LOCK_thread_repository;
pthread_cond_t COND_thread_repository_is_empty;
};
inline bool Thread_repository::is_shutdown()
{
pthread_mutex_lock(&LOCK_thread_repository);
bool res= shutdown_in_progress;
pthread_mutex_unlock(&LOCK_thread_repository);
return res;
}
#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_THREAD_REPOSITORY_HH
...@@ -69,7 +69,7 @@ int User::init(const char *line) ...@@ -69,7 +69,7 @@ int User::init(const char *line)
return 0; return 0;
err: err:
log_error("error parsing user and password at line %d", line); log_error("error parsing user and password at line %s", line);
return 1; return 1;
} }
...@@ -128,9 +128,10 @@ int User_map::load(const char *password_file_name) ...@@ -128,9 +128,10 @@ int User_map::load(const char *password_file_name)
if ((file= my_fopen(password_file_name, O_RDONLY | O_BINARY, MYF(0))) == 0) if ((file= my_fopen(password_file_name, O_RDONLY | O_BINARY, MYF(0))) == 0)
{ {
log_error("can't open password file %s: errno=%d, %s", password_file_name, /* 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)); errno, strerror(errno));
return 1; return 0;
} }
while (fgets(line, sizeof(line), file)) while (fgets(line, sizeof(line), file))
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
EXTRA_DIST = mysql.spec.sh \ EXTRA_DIST = mysql.spec.sh \
my-small.cnf.sh \ my-small.cnf.sh \
my.cnf \
my-medium.cnf.sh \ my-medium.cnf.sh \
my-large.cnf.sh \ my-large.cnf.sh \
my-huge.cnf.sh \ my-huge.cnf.sh \
......
[mysqld]
port=3307
#!/bin/sh #!/bin/sh
# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB # Copyright (C) 2005 MySQL AB
# This file is public domain and comes with NO WARRANTY of any kind # This file is public domain and comes with NO WARRANTY of any kind
# MySQL daemon start/stop script. # MySQL server management daemon start/stop script.
# Usually this is put in /etc/init.d (at least on machines SYSV R4 based # Usually this is put in /etc/init.d (at least on machines SYSV R4 based
# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql. # systems) and linked to /etc/rc3.d/S99mysqlmanager and
# /etc/rc0.d/K01mysqlmanager
# When this is done the mysql server will be started when the machine is # When this is done the mysql server will be started when the machine is
# started and shut down when the systems goes down. # started and shut down when the systems goes down.
# Comments to support chkconfig on RedHat Linux # description: MySQL database server Instance Manager
# chkconfig: 2345 90 20
# description: A very fast and reliable SQL database engine.
# Comments to support LSB init script conventions # Comments to support LSB init script conventions
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: mysql # Provides: mysqlmanager
# Required-Start: $local_fs $network $remote_fs # Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs # Required-Stop: $local_fs $network $remote_fs
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: start and stop MySQL # Short-Description: start and stop MySQL Instance Manager
# Description: MySQL is a very fast and reliable SQL database engine. # Description: MySQL Instance Manager is used to start/stop/status/monitor
# MySQL server instances
### END INIT INFO ### END INIT INFO
# If you install MySQL on some other places than @prefix@, then you
# have to do one of the following things for this script to work:
#
# - Run this script from within the MySQL installation directory
# - Create a /etc/my.cnf file with the following information:
# [mysqld]
# basedir=<path-to-mysql-installation-directory>
# - Add the above to any other configuration file (for example ~/.my.ini)
# and copy my_print_defaults to /usr/bin
# - Add the path to the mysql-installation-directory to the basedir variable
# below.
#
# If you want to affect other MySQL variables, you should make your changes
# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files.
basedir= basedir=
# The following variables are only set for letting mysql.server find things. # The following variables are only set for letting mysql.server find things.
...@@ -50,8 +35,10 @@ if test -z "$basedir" ...@@ -50,8 +35,10 @@ if test -z "$basedir"
then then
basedir=@prefix@ basedir=@prefix@
bindir=@bindir@ bindir=@bindir@
sbindir=@sbindir@
else else
bindir="$basedir/bin" bindir="$basedir/bin"
sbindir="$basedir/sbin"
fi fi
PATH=/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin PATH=/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin
...@@ -65,11 +52,18 @@ case `echo "testing\c"`,`echo -n testing` in ...@@ -65,11 +52,18 @@ case `echo "testing\c"`,`echo -n testing` in
*) echo_n= echo_c='\c' ;; *) echo_n= echo_c='\c' ;;
esac esac
parse_arguments() { parse_server_arguments() {
for arg do for arg do
case "$arg" in case "$arg" in
--basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
--datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
esac
done
}
parse_manager_arguments() {
for arg do
case "$arg" in
--pid-file=*) pid_file=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; --pid-file=*) pid_file=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
esac esac
done done
...@@ -125,14 +119,16 @@ then ...@@ -125,14 +119,16 @@ then
extra_args="-e $datadir/my.cnf" extra_args="-e $datadir/my.cnf"
fi fi
parse_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server` parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server`
parse_manager_arguments `$print_defaults manager`
# #
# Set pid file if not given # Set pid file if not given
# #
if test -z "$pid_file" if test -z "$pid_file"
then then
pid_file=$datadir/`@HOSTNAME@`.pid pid_file=$datadir/mysqlmanager-`@HOSTNAME@`.pid
else else
case "$pid_file" in case "$pid_file" in
/* ) ;; /* ) ;;
...@@ -140,6 +136,9 @@ else ...@@ -140,6 +136,9 @@ else
esac esac
fi fi
user=@MYSQLD_USER@
USER_OPTION="--user=$user"
# Safeguard (relative paths, core dumps..) # Safeguard (relative paths, core dumps..)
cd $basedir cd $basedir
...@@ -147,18 +146,18 @@ case "$mode" in ...@@ -147,18 +146,18 @@ case "$mode" in
'start') 'start')
# Start daemon # Start daemon
if test -x $bindir/mysqld_safe if test -x $sbindir/mysqlmanager
then then
# Give extra arguments to mysqld with the my.cnf file. This script may # Give extra arguments to mysqlmanager with the my.cnf file. This script may
# be overwritten at next upgrade. # be overwritten at next upgrade.
$bindir/mysqld_safe --datadir=$datadir --pid-file=$pid_file >/dev/null 2>&1 & $sbindir/mysqlmanager "--pid-file=$pid_file" $USER_OPTION --run-as-service >/dev/null 2>&1 &
# Make lock for RedHat / SuSE # Make lock for RedHat / SuSE
if test -w /var/lock/subsys if test -w /var/lock/subsys
then then
touch /var/lock/subsys/mysql touch /var/lock/subsys/mysqlmanager
fi fi
else else
echo "Can't execute $bindir/mysqld_safe from dir $basedir" echo "Can't execute $sbindir/mysqlmanager from dir $basedir"
fi fi
;; ;;
...@@ -167,15 +166,15 @@ case "$mode" in ...@@ -167,15 +166,15 @@ case "$mode" in
# root password. # root password.
if test -s "$pid_file" if test -s "$pid_file"
then then
mysqld_pid=`cat $pid_file` mysqlmanager_pid=`cat $pid_file`
echo "Killing mysqld with pid $mysqld_pid" echo "Killing mysqlmanager with pid $mysqlmanager_pid"
kill $mysqld_pid kill $mysqlmanager_pid
# mysqld should remove the pid_file when it exits, so wait for it. # mysqlmanager should remove the pid_file when it exits, so wait for it.
sleep 1 sleep 1
while [ -s $pid_file -a "$flags" != aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ] while [ -s $pid_file -a "$flags" != aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ]
do do
[ -z "$flags" ] && echo $echo_n "Wait for mysqld to exit$echo_c" || echo $echo_n ".$echo_c" [ -z "$flags" ] && echo $echo_n "Wait for mysqlmanager to exit$echo_c" || echo $echo_n ".$echo_c"
flags=a$flags flags=a$flags
sleep 1 sleep 1
done done
...@@ -185,12 +184,12 @@ case "$mode" in ...@@ -185,12 +184,12 @@ case "$mode" in
then echo " done" then echo " done"
fi fi
# delete lock for RedHat / SuSE # delete lock for RedHat / SuSE
if test -f /var/lock/subsys/mysql if test -f /var/lock/subsys/mysqlmanager
then then
rm -f /var/lock/subsys/mysql rm -f /var/lock/subsys/mysqlmanager
fi fi
else else
echo "No mysqld pid file found. Looked for $pid_file." echo "No mysqlmanager pid file found. Looked for $pid_file."
fi fi
;; ;;
......
...@@ -416,8 +416,9 @@ install -s -m755 $MBD/sql/mysqld-max $RBR%{_sbindir}/mysqld-max ...@@ -416,8 +416,9 @@ install -s -m755 $MBD/sql/mysqld-max $RBR%{_sbindir}/mysqld-max
install -m644 $MBD/sql/mysqld-max.sym $RBR%{_libdir}/mysql/mysqld-max.sym install -m644 $MBD/sql/mysqld-max.sym $RBR%{_libdir}/mysql/mysqld-max.sym
install -m644 $MBD/sql/mysqld.sym $RBR%{_libdir}/mysql/mysqld.sym install -m644 $MBD/sql/mysqld.sym $RBR%{_libdir}/mysql/mysqld.sym
# Install logrotate and autostart # Install logrotate, autostart and config file
install -m644 $MBD/support-files/mysql-log-rotate $RBR%{_sysconfdir}/logrotate.d/mysql install -m644 $MBD/support-files/mysql-log-rotate $RBR%{_sysconfdir}/logrotate.d/mysql
install -m644 $MBD/support-files/my.cnf $RBR%{_sysconfdir}/my.cnf
install -m755 $MBD/support-files/mysql.server $RBR%{_sysconfdir}/init.d/mysql install -m755 $MBD/support-files/mysql.server $RBR%{_sysconfdir}/init.d/mysql
# Create a symlink "rcmysql", pointing to the init.script. SuSE users # Create a symlink "rcmysql", pointing to the init.script. SuSE users
...@@ -428,10 +429,6 @@ ln -s %{_sysconfdir}/init.d/mysql $RPM_BUILD_ROOT%{_sbindir}/rcmysql ...@@ -428,10 +429,6 @@ ln -s %{_sysconfdir}/init.d/mysql $RPM_BUILD_ROOT%{_sbindir}/rcmysql
# (safe_mysqld will be gone in MySQL 4.1) # (safe_mysqld will be gone in MySQL 4.1)
ln -sf ./mysqld_safe $RBR%{_bindir}/safe_mysqld ln -sf ./mysqld_safe $RBR%{_bindir}/safe_mysqld
# Touch the place where the my.cnf config file might be located
# Just to make sure it's in the file list and marked as a config file
touch $RBR%{_sysconfdir}/my.cnf
%pre server %pre server
# Shut down a previously installed server first # Shut down a previously installed server first
if test -x %{_sysconfdir}/init.d/mysql if test -x %{_sysconfdir}/init.d/mysql
...@@ -549,8 +546,6 @@ fi ...@@ -549,8 +546,6 @@ fi
%doc %attr(644, root, man) %{_mandir}/man1/perror.1* %doc %attr(644, root, man) %{_mandir}/man1/perror.1*
%doc %attr(644, root, man) %{_mandir}/man1/replace.1* %doc %attr(644, root, man) %{_mandir}/man1/replace.1*
%ghost %config(noreplace,missingok) %{_sysconfdir}/my.cnf
%attr(755, root, root) %{_bindir}/my_print_defaults %attr(755, root, root) %{_bindir}/my_print_defaults
%attr(755, root, root) %{_bindir}/myisamchk %attr(755, root, root) %{_bindir}/myisamchk
%attr(755, root, root) %{_bindir}/myisam_ftdump %attr(755, root, root) %{_bindir}/myisam_ftdump
...@@ -578,10 +573,12 @@ fi ...@@ -578,10 +573,12 @@ fi
%attr(755, root, root) %{_bindir}/safe_mysqld %attr(755, root, root) %{_bindir}/safe_mysqld
%attr(755, root, root) %{_sbindir}/mysqld %attr(755, root, root) %{_sbindir}/mysqld
%attr(755, root, root) %{_sbindir}/mysqlmanager
%attr(755, root, root) %{_sbindir}/rcmysql %attr(755, root, root) %{_sbindir}/rcmysql
%attr(644, root, root) %{_libdir}/mysql/mysqld.sym %attr(644, root, root) %{_libdir}/mysql/mysqld.sym
%attr(644, root, root) %config(noreplace,missingok) %{_sysconfdir}/logrotate.d/mysql %attr(644, root, root) %config(noreplace,missingok) %{_sysconfdir}/logrotate.d/mysql
%attr(644, root, root) %config(noreplace,missingok) %{_sysconfdir}/my.cnf
%attr(755, root, root) %{_sysconfdir}/init.d/mysql %attr(755, root, root) %{_sysconfdir}/init.d/mysql
%attr(755, root, root) %{_datadir}/mysql/ %attr(755, root, root) %{_datadir}/mysql/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment