diff options
78 files changed, 5807 insertions, 1777 deletions
diff --git a/client/get_password.c b/client/get_password.c index 1b7b4e65a9f..b643b760718 100644 --- a/client/get_password.c +++ b/client/get_password.c @@ -64,7 +64,7 @@ /* were just going to fake it here and get input from the keyboard */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { char to[80]; char *pos=to,*end=to+sizeof(to)-1; @@ -150,7 +150,7 @@ static void get_password(char *to,uint length,int fd,bool echo) #endif /* ! HAVE_GETPASS */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { #ifdef HAVE_GETPASS char *passbuff; diff --git a/include/m_string.h b/include/m_string.h index e73f5c11487..2fddf4a83dd 100644 --- a/include/m_string.h +++ b/include/m_string.h @@ -246,4 +246,16 @@ extern int my_snprintf(char* to, size_t n, const char* fmt, ...); #if defined(__cplusplus) } #endif + +/* LEX_STRING */ + +typedef struct LEX_STRING +{ + char *str; + uint length; +}; + +#define STRING_WITH_LEN(X) (X), ((uint) (sizeof(X) - 1)) +#define C_STRING_WITH_SIZE(X) ((char *) (X)), ((uint) (sizeof(X) - 1)) + #endif diff --git a/include/my_sys.h b/include/my_sys.h index 51883e8d6f9..50bd4702a29 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -76,6 +76,10 @@ extern int NEAR my_errno; /* Last error in mysys */ #define MY_CHECK_ERROR 1 /* Params to my_end; Check open-close */ #define MY_GIVE_INFO 2 /* Give time info about process*/ +#define MY_REMOVE_NONE 0 /* Params for modify_defaults_file */ +#define MY_REMOVE_OPTION 1 +#define MY_REMOVE_SECTION 2 + #define ME_HIGHBYTE 8 /* Shift for colours */ #define ME_NOCUR 1 /* Don't use curses message */ #define ME_OLDWIN 2 /* Use old window */ diff --git a/include/mysql_com.h b/include/mysql_com.h index 2af0fb86906..bff5fcc47d2 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -423,17 +423,11 @@ char *octet2hex(char *to, const char *str, unsigned int len); /* end of password.c */ -char *get_tty_password(char *opt_message); +char *get_tty_password(const char *opt_message); const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); /* Some other useful functions */ -my_bool my_init(void); -extern int modify_defaults_file(const char *file_location, const char *option, - const char *option_value, - const char *section_name, int remove_option); -int load_defaults(const char *conf_file, const char **groups, - int *argc, char ***argv); my_bool my_thread_init(void); void my_thread_end(void); diff --git a/libmysql/get_password.c b/libmysql/get_password.c index a48cb6d7a6e..4c251677a66 100644 --- a/libmysql/get_password.c +++ b/libmysql/get_password.c @@ -75,7 +75,7 @@ #define _cputs(A) putstring(A) #endif -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { char to[80]; char *pos=to,*end=to+sizeof(to)-1; @@ -159,7 +159,7 @@ static void get_password(char *to,uint length,int fd,bool echo) #endif /* ! HAVE_GETPASS */ -char *get_tty_password(char *opt_message) +char *get_tty_password(const char *opt_message) { #ifdef HAVE_GETPASS char *passbuff; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 40b3954b533..5e2c408b0f5 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -1212,8 +1212,11 @@ sub environment_setup () { $ENV{'NDBCLUSTER_PORT'}= $opt_ndbcluster_port; $ENV{'NDBCLUSTER_PORT_SLAVE'}=$opt_ndbcluster_port_slave; + $ENV{'IM_EXE'}= $exe_im; $ENV{'IM_PATH_PID'}= $instance_manager->{path_pid}; $ENV{'IM_PORT'}= $instance_manager->{port}; + $ENV{'IM_DEFAULTS_PATH'}= $instance_manager->{defaults_file}; + $ENV{'IM_PASSWORD_PATH'}= $instance_manager->{password_file}; $ENV{'IM_MYSQLD1_SOCK'}= $instance_manager->{instances}->[0]->{path_sock}; $ENV{'IM_MYSQLD1_PORT'}= $instance_manager->{instances}->[0]->{port}; diff --git a/mysql-test/r/im_cmd_line.result b/mysql-test/r/im_cmd_line.result new file mode 100644 index 00000000000..5b289549a3f --- /dev/null +++ b/mysql-test/r/im_cmd_line.result @@ -0,0 +1,40 @@ +--> Listing users... +im_admin + +==> Adding user 'testuser'... + +--> IM password file: +testuser:*0D3CED9BEC10A777AEC23CCC353A8C08A633045E +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +--> EOF + +--> Printing out line for 'testuser'... +testuser:*0D3CED9BEC10A777AEC23CCC353A8C08A633045E + +--> Listing users... +im_admin +testuser + +==> Changing the password of 'testuser'... + +--> IM password file: +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +testuser:*39C549BDECFBA8AFC3CE6B948C9359A0ECE08DE2 +--> EOF + +--> Printing out line for 'testuser'... +testuser:*39C549BDECFBA8AFC3CE6B948C9359A0ECE08DE2 + +--> Listing users... +testuser +im_admin + +==> Dropping user 'testuser'... + +--> IM password file: +im_admin:*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295 +--> EOF + +--> Listing users... +im_admin + diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index d0a76b450fe..29c9ea2047d 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -1,5 +1,5 @@ SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline Killing the process... diff --git a/mysql-test/r/im_instance_conf.result b/mysql-test/r/im_instance_conf.result new file mode 100644 index 00000000000..3ea1e2bd824 --- /dev/null +++ b/mysql-test/r/im_instance_conf.result @@ -0,0 +1,196 @@ +-------------------------------------------------------------------- +server_id =1 +server_id =2 +-------------------------------------------------------------------- +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline + +---> connection: mysql1_con +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 + +---> connection: default +CREATE INSTANCE mysqld3; +SHOW INSTANCES; +instance_name state +mysqld3 offline +mysqld2 offline +mysqld1 online +-------------------------------------------------------------------- +server_id =1 +server_id =2 +-------------------------------------------------------------------- +CREATE INSTANCE mysqld1; +ERROR HY000: Instance already exists +CREATE INSTANCE mysqld2; +ERROR HY000: Instance already exists +CREATE INSTANCE mysqld3; +ERROR HY000: Instance already exists +-------------------------------------------------------------------- +nonguarded +-------------------------------------------------------------------- +CREATE INSTANCE mysqld4 nonguarded; +SHOW INSTANCES; +instance_name state +mysqld3 offline +mysqld4 offline +mysqld1 online +mysqld2 offline +-------------------------------------------------------------------- +nonguarded +nonguarded +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld5 test-A = 000, test-B = test; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld4 offline +mysqld5 offline +mysqld2 offline +mysqld3 offline +-------------------------------------------------------------------- +test-A=000 +-------------------------------------------------------------------- +test-B=test +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld6 test-C1 = 10 , test-C2 = 02 ; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +-------------------------------------------------------------------- +test-C1=10 +-------------------------------------------------------------------- +test-C2=02 +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld7 test-D = test-D-value ; +ERROR 42000: 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 +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +CREATE INSTANCE mysqld8 test-E 0 ; +ERROR 42000: 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 +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +CREATE INSTANCE mysqld8 test-F = ; +ERROR 42000: 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 +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE mysqld9 test-1=" hello world ", test-2=' '; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +CREATE INSTANCE mysqld9a test-3='\b\babc\sdef'; +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld9a offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +CREATE INSTANCE mysqld9b test-4='abc\tdef', test-5='abc\ndef'; +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld9a offline +mysqld5 offline +mysqld6 offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +CREATE INSTANCE mysqld9c test-6="abc\rdef", test-7="abc\\def"; +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld6 offline +mysqld5 offline +mysqld9c offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +mysqld9a offline +CREATE INSTANCE mysqld10 test-bad=' \ '; +ERROR 42000: 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 +SHOW INSTANCES; +instance_name state +mysqld9b offline +mysqld6 offline +mysqld5 offline +mysqld9c offline +mysqld3 offline +mysqld4 offline +mysqld9 offline +mysqld2 offline +mysqld1 online +mysqld9a offline +-------------------------------------------------------------------- +test-1= hello world +-------------------------------------------------------------------- +test-2= +-------------------------------------------------------------------- +test-3=abc def +-------------------------------------------------------------------- +test-4=abc def +-------------------------------------------------------------------- +test-5=abc +-------------------------------------------------------------------- +test-6=abc
def +-------------------------------------------------------------------- +test-7=abc\def +-------------------------------------------------------------------- +-------------------------------------------------------------------- +CREATE INSTANCE qqq1; +ERROR HY000: Malformed instance name. diff --git a/mysql-test/r/im_life_cycle.result b/mysql-test/r/im_life_cycle.result index e208ccb9f00..876fbb38eee 100644 --- a/mysql-test/r/im_life_cycle.result +++ b/mysql-test/r/im_life_cycle.result @@ -1,69 +1,93 @@ + +-------------------------------------------------------------------- +-- 1.1.1. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline -SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION -SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 offline VERSION_NUMBER VERSION + +-------------------------------------------------------------------- +-- 1.1.2. +-------------------------------------------------------------------- START INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 online -SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION -SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 online VERSION_NUMBER VERSION SHOW VARIABLES LIKE 'port'; Variable_name Value -port IM_MYSQLD1_PORT +port IM_MYSQLD2_PORT + +-------------------------------------------------------------------- +-- 1.1.3. +-------------------------------------------------------------------- STOP INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline SHOW INSTANCE STATUS mysqld1; -instance_name status version_number version -mysqld1 online VERSION_NUMBER VERSION +instance_name state version_number version mysqld_compatible +mysqld1 online VERSION_NUMBER VERSION no SHOW INSTANCE STATUS mysqld2; -instance_name status version_number version -mysqld2 offline VERSION_NUMBER VERSION +instance_name state version_number version mysqld_compatible +mysqld2 offline VERSION_NUMBER VERSION no + +-------------------------------------------------------------------- +-- 1.1.4. +-------------------------------------------------------------------- START INSTANCE mysqld3; ERROR HY000: Bad instance name. Check that the instance with such a name exists START INSTANCE mysqld1; ERROR HY000: The instance is already started + +-------------------------------------------------------------------- +-- 1.1.5. +-------------------------------------------------------------------- STOP INSTANCE mysqld3; ERROR HY000: Bad instance name. Check that the instance with such a name exists + +-------------------------------------------------------------------- +-- 1.1.6. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline Killing the process... Sleeping... Success: the process was restarted. + +-------------------------------------------------------------------- +-- 1.1.7. +-------------------------------------------------------------------- SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline START INSTANCE mysqld2; SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 online Killing the process... Sleeping... Success: the process was killed. SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline + +-------------------------------------------------------------------- +-- 1.1.8. +-------------------------------------------------------------------- SHOW INSTANCE STATUS; ERROR 42000: 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 + +-------------------------------------------------------------------- +-- BUG#12813 +-------------------------------------------------------------------- START INSTANCE mysqld1,mysqld2,mysqld3; ERROR 42000: 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 STOP INSTANCE mysqld1,mysqld2,mysqld3; diff --git a/mysql-test/r/im_options.result b/mysql-test/r/im_options.result new file mode 100644 index 00000000000..ed5b2ee5091 --- /dev/null +++ b/mysql-test/r/im_options.result @@ -0,0 +1,150 @@ +-------------------------------------------------------------------- +server_id =1 +server_id =2 +-------------------------------------------------------------------- +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 +SHOW INSTANCES; +instance_name state +mysqld1 starting +mysqld2 offline +UNSET mysqld1.server_id; +ERROR HY000: The instance is active. Stop the instance first +SET mysqld1.server_id = 11; +ERROR HY000: The instance is active. Stop the instance first +CREATE INSTANCE mysqld3 datadir = '/'; +START INSTANCE mysqld3; +UNSET mysqld3.server_id; +ERROR HY000: The instance is active. Stop the instance first +SET mysqld3.server_id = 11; +ERROR HY000: The instance is active. Stop the instance first +STOP INSTANCE mysqld3; +SHOW INSTANCE STATUS mysqld3; +instance_name state version_number version mysqld_compatible +mysqld3 offline VERSION_NUMBER VERSION no +UNSET mysqld2.server_id; +UNSET mysqld2.server_id; +SHOW INSTANCE OPTIONS mysqld2; +option_name value +instance_name option_value +socket option_value +pid-file option_value +port option_value +datadir option_value +log option_value +log-error option_value +log-slow-queries option_value +language option_value +character-sets-dir option_value +basedir option_value +skip-stack-trace option_value +skip-innodb option_value +skip-bdb option_value +skip-ndbcluster option_value +nonguarded option_value +log-output option_value +SET mysqld2.server_id = 2; +SET mysqld2.server_id = 2; +SHOW INSTANCE OPTIONS mysqld2; +option_name value +instance_name option_value +socket option_value +pid-file option_value +port option_value +datadir option_value +log option_value +log-error option_value +log-slow-queries option_value +language option_value +character-sets-dir option_value +basedir option_value +skip-stack-trace option_value +skip-innodb option_value +skip-bdb option_value +skip-ndbcluster option_value +nonguarded option_value +log-output option_value +server_id option_value +UNSET mysqld2.server_id = 11; +ERROR 42000: 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 +SET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc = 0010, mysqld3.ddd = 0020; +-------------------------------------------------------------------- +aaa +-------------------------------------------------------------------- +bbb +-------------------------------------------------------------------- +ccc=0010 +-------------------------------------------------------------------- +ddd=0020 +-------------------------------------------------------------------- +UNSET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc, mysqld3.ddd; +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +SET mysqld2.aaa, mysqld3.bbb, mysqld.ccc = 0010; +ERROR HY000: Bad instance name. Check that the instance with such a name exists +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +SET mysqld2.aaa, mysqld3.bbb, mysqld1.ccc = 0010; +ERROR HY000: The instance is active. Stop the instance first +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +UNSET mysqld2.server_id, mysqld3.server_id, mysqld.ccc; +ERROR HY000: Bad instance name. Check that the instance with such a name exists +-------------------------------------------------------------------- +server_id =1 +server_id=2 +-------------------------------------------------------------------- +UNSET mysqld2.server_id, mysqld3.server_id, mysqld1.ccc; +ERROR HY000: The instance is active. Stop the instance first +-------------------------------------------------------------------- +server_id =1 +server_id=2 +-------------------------------------------------------------------- +DROP INSTANCE mysqld3; +SET mysqld2.server_id=222; +SET mysqld2.server_id = 222; +SET mysqld2.server_id = 222 ; +SET mysqld2 . server_id = 222 ; +SET mysqld2 . server_id = 222 , mysqld2 . aaa , mysqld2 . bbb ; +-------------------------------------------------------------------- +server_id =1 +server_id=222 +-------------------------------------------------------------------- +aaa +-------------------------------------------------------------------- +bbb +-------------------------------------------------------------------- +UNSET mysqld2 . aaa , mysqld2 . bbb ; +-------------------------------------------------------------------- +server_id =1 +server_id=222 +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- +server_id =1 +server_id=222 +-------------------------------------------------------------------- +SHOW VARIABLES LIKE 'server_id'; +Variable_name Value +server_id 1 +SHOW INSTANCES; +instance_name state +mysqld1 online +mysqld2 offline +FLUSH INSTANCES; +ERROR HY000: At least one instance is active. Stop all instances first +STOP INSTANCE mysqld1; +SHOW INSTANCES; +instance_name state +mysqld1 offline +mysqld2 offline +FLUSH INSTANCES; diff --git a/mysql-test/r/im_options_set.result b/mysql-test/r/im_options_set.result deleted file mode 100644 index 0d2fa699fc7..00000000000 --- a/mysql-test/r/im_options_set.result +++ /dev/null @@ -1,20 +0,0 @@ -server_id =1 -server_id =2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -SET mysqld1.server_id = 11; -server_id =11 -server_id =2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -SET mysqld2.server_id = 12; -server_id =11 -server_id =12 -FLUSH INSTANCES; -server_id =11 -server_id =12 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 diff --git a/mysql-test/r/im_options_unset.result b/mysql-test/r/im_options_unset.result deleted file mode 100644 index 834152c35d2..00000000000 --- a/mysql-test/r/im_options_unset.result +++ /dev/null @@ -1,15 +0,0 @@ -server_id =1 -server_id =2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -UNSET mysqld1.server_id; -server_id =2 -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 -UNSET mysqld2.server_id; -FLUSH INSTANCES; -SHOW VARIABLES LIKE 'server_id'; -Variable_name Value -server_id 1 diff --git a/mysql-test/r/im_utils.result b/mysql-test/r/im_utils.result index 504b2efe4af..ae8e03bf8ea 100644 --- a/mysql-test/r/im_utils.result +++ b/mysql-test/r/im_utils.result @@ -1,11 +1,10 @@ SHOW INSTANCES; -instance_name status +instance_name state mysqld1 online mysqld2 offline SHOW INSTANCE OPTIONS mysqld1; option_name value instance_name VALUE -mysqld-path VALUE socket VALUE pid-file VALUE port VALUE @@ -25,8 +24,6 @@ log-output VALUE SHOW INSTANCE OPTIONS mysqld2; option_name value instance_name VALUE -mysqld-path VALUE -nonguarded VALUE socket VALUE pid-file VALUE port VALUE @@ -42,6 +39,7 @@ skip-stack-trace VALUE skip-innodb VALUE skip-bdb VALUE skip-ndbcluster VALUE +nonguarded VALUE log-output VALUE START INSTANCE mysqld2; STOP INSTANCE mysqld2; diff --git a/mysql-test/t/im_cmd_line.imtest b/mysql-test/t/im_cmd_line.imtest new file mode 100644 index 00000000000..00e8351535e --- /dev/null +++ b/mysql-test/t/im_cmd_line.imtest @@ -0,0 +1,68 @@ +########################################################################### +# +# Tests for user-management command-line options. +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### + +# List users so we are sure about starting conditions. + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Add a new user. + +--echo ==> Adding user 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --add-user --username=testuser --password=abc 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Printing out line for 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --passwd --username=testuser --password=abc | tail -1 +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Edit user's attributes. + +--echo ==> Changing the password of 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --edit-user --username=testuser --password=xyz 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Printing out line for 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --passwd --username=testuser --password=xyz | tail -1 +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo + +# Drop user. + +--echo ==> Dropping user 'testuser'... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --drop-user --username=testuser 2>&1 >/dev/null +--echo + +--echo --> IM password file: +--exec cat $IM_PASSWORD_PATH +--echo --> EOF +--echo + +--echo --> Listing users... +--exec $IM_EXE --defaults-file="$IM_DEFAULTS_PATH" --list-users 2>&1 >/dev/null +--echo diff --git a/mysql-test/t/im_daemon_life_cycle-im.opt b/mysql-test/t/im_daemon_life_cycle-im.opt index 21c01191e4c..3a45c7a41f7 100644 --- a/mysql-test/t/im_daemon_life_cycle-im.opt +++ b/mysql-test/t/im_daemon_life_cycle-im.opt @@ -1,2 +1,3 @@ --run-as-service --log=$MYSQLTEST_VARDIR/log/im.log +--monitoring-interval=1 diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index 87388d7c1e6..d173ce2a6e2 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -10,6 +10,9 @@ ########################################################################### +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted diff --git a/mysql-test/t/im_instance_conf-im.opt b/mysql-test/t/im_instance_conf-im.opt new file mode 100644 index 00000000000..34b74ce0c95 --- /dev/null +++ b/mysql-test/t/im_instance_conf-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_instance_conf.imtest b/mysql-test/t/im_instance_conf.imtest new file mode 100644 index 00000000000..17703fdd303 --- /dev/null +++ b/mysql-test/t/im_instance_conf.imtest @@ -0,0 +1,228 @@ +########################################################################### +# +# This test suite checks the following statements: +# - CREATE INSTANCE <instance_name> [option1[=option1_value], ...]; +# - DROP INSTANCE <instance_name>; +# +# For CREATE INSTANCE we check that: +# - CREATE INSTANCE succeeds for non-existing instance; +# - CREATE INSTANCE fails for existing instance; +# - CREATE INSTANCE can get additional options with and w/o values; +# - CREATE INSTANCE parses options and handles grammar errors correctly. +# Check that strings with spaces are handled correctly, unknown (for +# mysqld) options should also be handled; +# - CREATE INSTANCE updates both config file and internal configuration cache; +# - CREATE INSTANCE allows to create instances only with properly formed +# names (mysqld*); +# +# For DROP INSTANCE we check that: +# - DROP INSTANCE succeeds for existing instance; +# - DROP INSTANCE fails for non-existing instance; +# - DROP INSTANCE fails for active instance. +# - DROP INSTANCE updates both config file and internal configuration cache; +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### +# +# Check starting conditions. +# +########################################################################### + +# Check that the configuration file contains only instances that we expect. + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# Check that mysqld1 is reported as running. + +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + +SHOW INSTANCES; + +# Check that the expected mysqld instance is actually run (check that we can +# connect and execute something). + +--echo +--echo ---> connection: mysql1_con +--connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--disconnect mysql1_con + +--echo +--echo ---> connection: default +--connection default + +########################################################################### +# +# CREATE INSTANCE tests. +# +########################################################################### + +# Check that CREATE INSTANCE succeeds for non-existing instance and also check +# that both config file and internal configuration cache have been updated. + +CREATE INSTANCE mysqld3; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# Check that CREATE INSTANCE fails for existing instance. Let's all three +# existing instances (running one, stopped one and just created one). Just in +# case... + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld1; + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld2; + +--error 3012 # ER_CREATE_EXISTING_INSTANCE +CREATE INSTANCE mysqld3; + +# Check that CREATE INSTANCE can get additional options with and w/o values. +# Ensure that config file is updated properly. + +# - without values; + +--echo -------------------------------------------------------------------- +--exec grep nonguarded $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld4 nonguarded; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep nonguarded $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# - with value; + +--echo -------------------------------------------------------------------- +--exec grep test-A $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-B $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld5 test-A = 000, test-B = test; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-A $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-B $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# Check that CREATE INSTANCE parses options and handles grammar errors +# correctly. Check that strings with spaces are handled correctly, +# unknown (for mysqld) options should also be handled. + +# - check handling of extra spaces; + +--echo -------------------------------------------------------------------- +--exec grep test-C $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld6 test-C1 = 10 , test-C2 = 02 ; + +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-C1 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-C2 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +# - check handling of grammar error; + +--echo -------------------------------------------------------------------- +--exec grep test-D $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-E $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld7 test-D = test-D-value ; +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld8 test-E 0 ; +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld8 test-F = ; +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-D $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-E $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check parsing of string option values + +--echo -------------------------------------------------------------------- +--exec grep test-1 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-2 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-3 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep test-4 $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +CREATE INSTANCE mysqld9 test-1=" hello world ", test-2=' '; +SHOW INSTANCES; + +CREATE INSTANCE mysqld9a test-3='\b\babc\sdef'; +# test-3='abc def' +SHOW INSTANCES; + +CREATE INSTANCE mysqld9b test-4='abc\tdef', test-5='abc\ndef'; +SHOW INSTANCES; + +CREATE INSTANCE mysqld9c test-6="abc\rdef", test-7="abc\\def"; +# test-6=abc +SHOW INSTANCES; + +--error ER_SYNTAX_ERROR +CREATE INSTANCE mysqld10 test-bad=' \ '; +SHOW INSTANCES; + +--echo -------------------------------------------------------------------- +--exec grep test-1 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-2 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-3 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-4 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-5 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-6 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-7 $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- +--exec grep test-bad $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + + +# Check that CREATE INSTANCE allows to create instances only with properly +# formed names (mysqld*). + +--error 3014 # ER_MALFORMED_INSTANCE_NAME +CREATE INSTANCE qqq1; + diff --git a/mysql-test/t/im_life_cycle-im.opt b/mysql-test/t/im_life_cycle-im.opt new file mode 100644 index 00000000000..34b74ce0c95 --- /dev/null +++ b/mysql-test/t/im_life_cycle-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_life_cycle.imtest b/mysql-test/t/im_life_cycle.imtest index 246843a022b..d71cdc86624 100644 --- a/mysql-test/t/im_life_cycle.imtest +++ b/mysql-test/t/im_life_cycle.imtest @@ -17,11 +17,15 @@ # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.1. +--echo -------------------------------------------------------------------- + +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld1; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld2; ########################################################################### # @@ -33,20 +37,22 @@ SHOW INSTANCE STATUS mysqld2; # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.2. +--echo -------------------------------------------------------------------- + START INSTANCE mysqld2; -# FIXME +# FIXME: START INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to start instance. SHOW INSTANCES; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld1; ---replace_column 3 VERSION_NUMBER 4 VERSION -SHOW INSTANCE STATUS mysqld2; ---connect (mysql_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) +--connect (mysql_con,localhost,root,,mysql,$IM_MYSQLD2_PORT,$IM_MYSQLD2_SOCK) --connection mysql_con ---replace_result $IM_MYSQLD1_PORT IM_MYSQLD1_PORT +--replace_result $IM_MYSQLD2_PORT IM_MYSQLD2_PORT SHOW VARIABLES LIKE 'port'; --connection default @@ -61,9 +67,15 @@ SHOW VARIABLES LIKE 'port'; # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.3. +--echo -------------------------------------------------------------------- + STOP INSTANCE mysqld2; -# FIXME +# FIXME: STOP INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to stop instance. SHOW INSTANCES; --replace_column 3 VERSION_NUMBER 4 VERSION @@ -81,16 +93,17 @@ SHOW INSTANCE STATUS mysqld2; # ########################################################################### ---error 3000 +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.4. +--echo -------------------------------------------------------------------- + +--error 3000 # ER_BAD_INSTANCE_NAME START INSTANCE mysqld3; ---error 3002 +--error 3002 # ER_INSTANCE_ALREADY_STARTED START INSTANCE mysqld1; -# FIXME TODO -# BUG#12813: START/STOP INSTANCE commands accept a list as argument -# START INSTANCE mysqld1, mysqld2; - ########################################################################### # # 1.1.5. Check that Instance Manager reports correct errors for 'STOP INSTANCE' @@ -101,39 +114,54 @@ START INSTANCE mysqld1; # ########################################################################### ---error 3000 +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.5. +--echo -------------------------------------------------------------------- + +--error 3000 # ER_BAD_INSTANCE_NAME STOP INSTANCE mysqld3; # TODO: IM should be fixed. # BUG#12673: Instance Manager allows to stop the instance many times -# --error 3002 +# --error 3002 # ER_INSTANCE_ALREADY_STARTED # STOP INSTANCE mysqld2; -# FIXME TODO -# BUG#12813: START/STOP INSTANCE commands accept a list as argument -# STOP INSTANCE mysqld1, mysqld2; - ########################################################################### # # 1.1.6. Check that Instance Manager is able to restart guarded instances. # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.6. +--echo -------------------------------------------------------------------- + SHOW INSTANCES; --exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD1_PATH_PID restarted +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + ########################################################################### # # 1.1.7. Check that Instance Manager does not restart non-guarded instance. # ########################################################################### +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.7. +--echo -------------------------------------------------------------------- + SHOW INSTANCES; START INSTANCE mysqld2; -# FIXME +# FIXME: START INSTANCE should be synchronous. --sleep 3 +# should be longer than monitoring interval and enough to start instance. SHOW INSTANCES; @@ -147,7 +175,13 @@ SHOW INSTANCES; # incomplete SHOW INSTANCE STATUS command. # ########################################################################### ---error 1149 + +--echo +--echo -------------------------------------------------------------------- +--echo -- 1.1.8. +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR SHOW INSTANCE STATUS; # @@ -159,8 +193,13 @@ SHOW INSTANCE STATUS; # a list as argument. # ---error 1149 +--echo +--echo -------------------------------------------------------------------- +--echo -- BUG#12813 +--echo -------------------------------------------------------------------- + +--error ER_SYNTAX_ERROR START INSTANCE mysqld1,mysqld2,mysqld3; ---error 1149 +--error ER_SYNTAX_ERROR STOP INSTANCE mysqld1,mysqld2,mysqld3; diff --git a/mysql-test/t/im_options.imtest b/mysql-test/t/im_options.imtest new file mode 100644 index 00000000000..cd905416cda --- /dev/null +++ b/mysql-test/t/im_options.imtest @@ -0,0 +1,268 @@ +########################################################################### +# +# This test suite checks the following statements: +# - SET <instance id>.<option name> = <option value>; +# - UNSET <instance id>.<option name> = <option value>; +# - FLUSH INSTANCES; +# +# For SET/UNSET we check that: +# - SET ignores spaces correctly; +# - UNSET does not allow option-value part (= <option value>); +# - SET/UNSET can be applied several times w/o error; +# - SET/UNSET is allowed only for stopped instances; +# - SET/UNSET updates both the configuration cache in IM and +# the configuration file; +# +# For FLUSH INSTANCES we check that: +# - FLUSH INSTANCES is allowed only when all instances are stopped; +# +# According to the IM implementation details, we should play at least with the +# following options: +# - server_id +# - port +# - nonguarded + +# Let's test SET statement on the option 'server_id'. It's expected that +# originally the instances have the following server ids and states: +# - mysqld1: server_id: 1; running (online) +# - mysqld2: server_id: 2; stopped (offline) +# +########################################################################### + +--source include/im_check_os.inc + +########################################################################### +# +# Check starting conditions. +# +########################################################################### + +# - check the configuration file; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# - check the running instances. + +--connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) + +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--connection default + +# - check the internal cache. + +SHOW INSTANCES; + +########################################################################### +# +# Check that SET/UNSET is allowed only for stopped instances. +# +########################################################################### + +# - check that SET/UNSET is denied for running instances; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld1.server_id; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld1.server_id = 11; + +# - check that SET/UNSET is denied for active instances: +# - create dummy misconfigured instance; +# - start it; +# - try to set/unset options; + +CREATE INSTANCE mysqld3 datadir = '/'; +START INSTANCE mysqld3; + +# FIXME: START INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + +# NOTE: We can not analyze state of the instance here -- it can be Failed or +# Starting because Instance Manager is trying to start the misconfigured +# instance several times. + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld3.server_id; + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld3.server_id = 11; + +STOP INSTANCE mysqld3; + +# FIXME: STOP INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to stop instance. + +--replace_column 3 VERSION_NUMBER 4 VERSION +SHOW INSTANCE STATUS mysqld3; + +# - check that SET/UNSET succeed for stopped instances; +# - check that SET/UNSET can be applied multiple times; + +UNSET mysqld2.server_id; +UNSET mysqld2.server_id; + +--replace_column 2 option_value +SHOW INSTANCE OPTIONS mysqld2; + +SET mysqld2.server_id = 2; +SET mysqld2.server_id = 2; + +--replace_column 2 option_value +SHOW INSTANCE OPTIONS mysqld2; + +# - check that UNSET does not allow option-value part (= <option value>); + +--error ER_SYNTAX_ERROR +UNSET mysqld2.server_id = 11; + +# - check that SET/UNSET working properly with multiple options; + +SET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc = 0010, mysqld3.ddd = 0020; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep ddd $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +UNSET mysqld2.aaa, mysqld3.bbb, mysqld2.ccc, mysqld3.ddd; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ddd $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check that if some instance name is invalid or the active is active, +# whole SET-statement will not be executed; + +--error 3000 # ER_BAD_INSTANCE_NAME +SET mysqld2.aaa, mysqld3.bbb, mysqld.ccc = 0010; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +--error 3015 # ER_INSTANCE_IS_ACTIVE +SET mysqld2.aaa, mysqld3.bbb, mysqld1.ccc = 0010; + +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep ccc $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +# - check that if some instance name is invalid or the active is active, +# whole UNSET-statement will not be executed; + +--error 3000 # ER_BAD_INSTANCE_NAME +UNSET mysqld2.server_id, mysqld3.server_id, mysqld.ccc; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +--error 3015 # ER_INSTANCE_IS_ACTIVE +UNSET mysqld2.server_id, mysqld3.server_id, mysqld1.ccc; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf; +--echo -------------------------------------------------------------------- + +DROP INSTANCE mysqld3; + +# - check that spaces are handled correctly; + +SET mysqld2.server_id=222; +SET mysqld2.server_id = 222; +SET mysqld2.server_id = 222 ; +SET mysqld2 . server_id = 222 ; +SET mysqld2 . server_id = 222 , mysqld2 . aaa , mysqld2 . bbb ; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +UNSET mysqld2 . aaa , mysqld2 . bbb ; + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- +--exec grep aaa $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- +--exec grep bbb $MYSQLTEST_VARDIR/im.cnf || true; +--echo -------------------------------------------------------------------- + +########################################################################### +# +# Check that SET/UNSET updates both the configuration cache in IM and +# the configuration file. +# +########################################################################### + +# - check that the configuration file has been updated (i.e. contains +# server_id=SERVER_ID for mysqld2); + +--echo -------------------------------------------------------------------- +--exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; +--echo -------------------------------------------------------------------- + +# - (for mysqld1) check that the running instance has not been affected: +# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' +# returns zero; + +--connection mysql1_con + +SHOW VARIABLES LIKE 'server_id'; + +--connection default + +# - check that internal cache of Instance Manager has been affected; +# TODO: we should check only server_id option here. + +# SHOW INSTANCE OPTIONS mysqld2; + +########################################################################### +# +# Check that FLUSH INSTANCES is allowed only when all instances are stopped. +# +########################################################################### + +SHOW INSTANCES; + +--error 3016 # ER_THERE_IS_ACTIVE_INSTACE +FLUSH INSTANCES; + +STOP INSTANCE mysqld1; +# FIXME: STOP INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to stop instance. + +SHOW INSTANCES; + +FLUSH INSTANCES; diff --git a/mysql-test/t/im_options_set.imtest b/mysql-test/t/im_options_set.imtest deleted file mode 100644 index a9b64861f99..00000000000 --- a/mysql-test/t/im_options_set.imtest +++ /dev/null @@ -1,142 +0,0 @@ -########################################################################### -# -# This file contains test for (3) test suite. -# -# Consult WL#2789 for more information. -# -########################################################################### - -# -# Check the options-management commands: -# - SET; -# - FLUSH INSTANCES; -# -# Let's test the commands on the option 'server_id'. It's expected that -# originally the instances have the following server ids: -# - mysqld1: 1 -# - mysqld2: 2 -# -# 1. SET <instance_id>.server_id= SERVER_ID); where SERVER_ID is 11 or 12. -# 1.1. check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); -# 1.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero; -# 1.3. check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# -# 2. FLUSH INSTANCES; -# 2.1. check that the configuration file has not been updated; -# 2.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero value; -# 2.3. check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> contains 'server_id=SERVER_ID' line). -# -# 3. Restore options. -# - -########################################################################### - ---source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 1. SET <instance_id>.server_id= SERVER_ID); where SERVER_ID is 11 or 12. -# -########################################################################### - -# * mysqld1 - -SET mysqld1.server_id = 11; - -# - check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; - -# * mysqld2 - -SET mysqld2.server_id = 12; - -# - check that the configuration file has been updated (i.e. contains -# server_id=SERVER_ID for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check that internal cache of Instance Manager has not been affected -# (i.e. SHOW INSTANCE OPTIONS <instance> does not contain updated value). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 2. FLUSH INSTANCES; -# -########################################################################### - -FLUSH INSTANCES; - -# - check that the configuration file has not been updated; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns zero value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> contains 'server_id=' line). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; diff --git a/mysql-test/t/im_options_unset.imtest b/mysql-test/t/im_options_unset.imtest deleted file mode 100644 index 40629805d45..00000000000 --- a/mysql-test/t/im_options_unset.imtest +++ /dev/null @@ -1,150 +0,0 @@ -########################################################################### -# -# This file contains test for (3) test suite. -# -# Consult WL#2789 for more information. -# -########################################################################### - -# -# Check the options-management commands: -# - UNSET; -# - FLUSH INSTANCES; -# -# Let's test the commands on the option 'server_id'. It's expected that -# originally the instances have the following server ids: -# - mysqld1: 1 -# - mysqld2: 2 -# -# The test case: -# -# 1. UNSET <instance_id>.server_id; -# -# Do the step for both instances. -# -# 1.1. check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); -# 1.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; -# 1.3. check that internal cache of Instance Manager is not affected (i.e. -# SHOW INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# -# 2. FLUSH INSTANCES; -# -# Do the step for both instances. -# -# 2.1. check that the configuration file has not been updated (i.e. does not -# contain 'server_id=' for the instance); -# 2.2. (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; -# 2.3. check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> does not contain 'server_id=' line). -# - -########################################################################### - ---source include/im_check_os.inc - -########################################################################### -# -# 0. Check starting conditions. -# -########################################################################### - -# - check the configuration file; - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check the running instances. - ---connect (mysql1_con,localhost,root,,mysql,$IM_MYSQLD1_PORT,$IM_MYSQLD1_SOCK) - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check the internal cache. -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 1. UNSET <instance_id>.server_id; -# -########################################################################### - -# * mysqld1 - -UNSET mysqld1.server_id; - -# - check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf ; - -# - check that the running instance has not been affected: connect to the -# instance and check that 'SHOW VARIABLES LIKE 'server_id'' returns non-zero -# value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager is not affected (i.e. SHOW -# INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; - -# * mysqld2 - -UNSET mysqld2.server_id; - -# - check that the configuration file has been updated (i.e. does not -# contain 'server_id=' line for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf || true; - -# - check that internal cache of Instance Manager is not affected (i.e. SHOW -# INSTANCE OPTIONS <instance> contains non-zero value for server_id); -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld2; - -########################################################################### -# -# 2. FLUSH INSTANCES; -# -########################################################################### - -FLUSH INSTANCES; - -# - check that the configuration file has not been updated (i.e. does not -# contain 'server_id=' for the instance); - ---exec grep server_id $MYSQLTEST_VARDIR/im.cnf || true; - -# - (for mysqld1) check that the running instance has not been affected: -# connect to the instance and check that 'SHOW VARIABLES LIKE 'server_id'' -# returns non-zero value; - ---connection mysql1_con - -SHOW VARIABLES LIKE 'server_id'; - ---connection default - -# - check that internal cache of Instance Manager has been updated (i.e. -# SHOW INSTANCE OPTIONS <instance> does not contain 'server_id=' line). -# TODO: we should check only server_id option here. - -# SHOW INSTANCE OPTIONS mysqld1; -# SHOW INSTANCE OPTIONS mysqld2; diff --git a/mysql-test/t/im_utils-im.opt b/mysql-test/t/im_utils-im.opt new file mode 100644 index 00000000000..34b74ce0c95 --- /dev/null +++ b/mysql-test/t/im_utils-im.opt @@ -0,0 +1 @@ +--monitoring-interval=1 diff --git a/mysql-test/t/im_utils.imtest b/mysql-test/t/im_utils.imtest index dc6fb93c4ff..8e8d475cfee 100644 --- a/mysql-test/t/im_utils.imtest +++ b/mysql-test/t/im_utils.imtest @@ -17,6 +17,9 @@ # - the second instance is offline; # +--sleep 3 +# should be longer than monitoring interval and enough to start instance. + SHOW INSTANCES; # @@ -41,8 +44,9 @@ SHOW INSTANCE OPTIONS mysqld2; START INSTANCE mysqld2; -# FIXME --- sleep 3 +# FIXME: START INSTANCE should be synchronous. +--sleep 3 +# should be longer than monitoring interval and enough to start instance. STOP INSTANCE mysqld2; diff --git a/mysys/default.c b/mysys/default.c index 580bcc19eca..3a80d7b37b9 100644 --- a/mysys/default.c +++ b/mysys/default.c @@ -244,7 +244,8 @@ err: handle_option_ctx 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) + prepared to be used in argv (has -- prefix). If it + is NULL, we are handling a new group (section). DESCRIPTION This handler checks whether a group is one of the listed and adds an option @@ -263,6 +264,9 @@ static int handle_default_option(void *in_ctx, const char *group_name, char *tmp; struct handle_option_ctx *ctx= (struct handle_option_ctx *) in_ctx; + if (!option) + return 0; + if (find_type((char *)group_name, ctx->group, 3)) { if (!(tmp= alloc_root(ctx->alloc, (uint) strlen(option) + 1))) @@ -719,6 +723,10 @@ static int search_default_file_with_ext(Process_option_func opt_handler, end[0]=0; strnmov(curr_gr, ptr, min((uint) (end-ptr)+1, 4096)); + + /* signal that a new group is found */ + opt_handler(handler_ctx, curr_gr, NULL); + continue; } if (!found_group) diff --git a/mysys/default_modify.c b/mysys/default_modify.c index 0f58b8a930c..8dbcac699ea 100644 --- a/mysys/default_modify.c +++ b/mysys/default_modify.c @@ -40,11 +40,13 @@ static char *add_option(char *dst, const char *option_value, SYNOPSYS modify_defaults_file() file_location The location of configuration file to edit - option option to look for - option value The value of the option we would like to set - section_name the name of the section - remove_option This is true if we want to remove the option. - False otherwise. + option The name of the option to look for (can be NULL) + option value The value of the option we would like to set (can be NULL) + section_name The name of the section (must be NOT NULL) + remove_option This defines what we want to remove: + - MY_REMOVE_NONE -- nothing to remove; + - MY_REMOVE_OPTION -- remove the specified option; + - MY_REMOVE_SECTION -- remove the specified section; IMPLEMENTATION We open the option file first, then read the file line-by-line, looking for the section we need. At the same time we put these lines @@ -67,7 +69,9 @@ int modify_defaults_file(const char *file_location, const char *option, FILE *cnf_file; MY_STAT file_stat; char linebuff[BUFF_SIZE], *src_ptr, *dst_ptr, *file_buffer; - uint opt_len, optval_len, sect_len, nr_newlines= 0, buffer_size; + uint opt_len= 0; + uint optval_len= 0; + uint sect_len, nr_newlines= 0, buffer_size; my_bool in_section= FALSE, opt_applied= 0; uint reserve_extended; uint new_opt_len; @@ -81,8 +85,11 @@ int modify_defaults_file(const char *file_location, const char *option, if (my_fstat(fileno(cnf_file), &file_stat, MYF(0))) goto malloc_err; - opt_len= (uint) strlen(option); - optval_len= (uint) strlen(option_value); + if (option && option_value) + { + opt_len= (uint) strlen(option); + optval_len= (uint) strlen(option_value); + } new_opt_len= opt_len + 1 + optval_len + NEWLINE_LEN; @@ -119,8 +126,8 @@ int modify_defaults_file(const char *file_location, const char *option, continue; } - /* correct the option */ - if (in_section && !strncmp(src_ptr, option, opt_len) && + /* correct the option (if requested) */ + if (option && in_section && !strncmp(src_ptr, option, opt_len) && (*(src_ptr + opt_len) == '=' || my_isspace(&my_charset_latin1, *(src_ptr + opt_len)) || *(src_ptr + opt_len) == '\0')) @@ -143,7 +150,12 @@ int modify_defaults_file(const char *file_location, const char *option, } else { - /* If going to new group and we have option to apply, do it now */ + /* + If we are going to the new group and have an option to apply, do + it now. If we are removing a single option or the whole section + this will only trigger opt_applied flag. + */ + if (in_section && !opt_applied && *src_ptr == '[') { dst_ptr= add_option(dst_ptr, option_value, option, remove_option); @@ -153,7 +165,10 @@ int modify_defaults_file(const char *file_location, const char *option, for (; nr_newlines; nr_newlines--) dst_ptr= strmov(dst_ptr, NEWLINE); - dst_ptr= strmov(dst_ptr, linebuff); + + /* Skip the section if MY_REMOVE_SECTION was given */ + if (!in_section || remove_option != MY_REMOVE_SECTION) + dst_ptr= strmov(dst_ptr, linebuff); } /* Look for a section */ if (*src_ptr == '[') @@ -167,18 +182,31 @@ int modify_defaults_file(const char *file_location, const char *option, {} if (*src_ptr != ']') + { + in_section= FALSE; continue; /* Missing closing parenthesis. Assume this was no group */ + } + + if (remove_option == MY_REMOVE_SECTION) + dst_ptr= dst_ptr - strlen(linebuff); + in_section= TRUE; } else in_section= FALSE; /* mark that this section is of no interest to us */ } } - /* File ended. */ - if (!opt_applied && !remove_option && in_section) + + /* + File ended. Apply an option or set opt_applied flag (in case of + MY_REMOVE_SECTION) so that the changes are saved. Do not do anything + if we are removing non-existent option. + */ + + if (!opt_applied && in_section && (remove_option != MY_REMOVE_OPTION)) { /* New option still remains to apply at the end */ - if (*(dst_ptr - 1) != '\n') + if (!remove_option && *(dst_ptr - 1) != '\n') dst_ptr= strmov(dst_ptr, NEWLINE); dst_ptr= add_option(dst_ptr, option_value, option, remove_option); opt_applied= 1; diff --git a/server-tools/instance-manager/IMService.cpp b/server-tools/instance-manager/IMService.cpp index b7ea8e7eb81..3093147bccf 100644 --- a/server-tools/instance-manager/IMService.cpp +++ b/server-tools/instance-manager/IMService.cpp @@ -20,7 +20,7 @@ IMService::~IMService(void) void IMService::Stop() { ReportStatus(SERVICE_STOP_PENDING); - + // stop the IM work raise(SIGTERM); } @@ -32,7 +32,7 @@ void IMService::Run(DWORD argc, LPTSTR *argv) Options o; o.load(argc, argv); - + // init goes here ReportStatus((DWORD)SERVICE_RUNNING); @@ -46,13 +46,13 @@ void IMService::Log(const char *msg) log_info(msg); } -int HandleServiceOptions(Options options) +int HandleServiceOptions() { int ret_val= 0; IMService winService; - if (options.install_as_service) + if (Options::Service::install_as_service) { if (winService.IsInstalled()) log_info("Service is already installed"); @@ -64,7 +64,7 @@ int HandleServiceOptions(Options options) ret_val= 1; } } - else if (options.remove_service) + else if (Options::Service::remove_service) { if (! winService.IsInstalled()) log_info("Service is not installed"); @@ -77,6 +77,19 @@ int HandleServiceOptions(Options options) } } else - ret_val= !winService.Init(); + { + log_info("Initializing Instance Manager service..."); + + if (!winService.Init()) + { + log_info("Service failed to initialize."); + fprintf(stderr, + "The service should be started by Windows Service Manager.\n" + "The MySQL Manager should be started with '--standalone'\n" + "to run from command line."); + ret_val= 1; + } + } + return ret_val; } diff --git a/server-tools/instance-manager/IMService.h b/server-tools/instance-manager/IMService.h index cad38bebdaf..94d59c2af31 100644 --- a/server-tools/instance-manager/IMService.h +++ b/server-tools/instance-manager/IMService.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #pragma once #include "windowsservice.h" @@ -12,3 +30,5 @@ protected: void Stop(); void Run(DWORD argc, LPTSTR *argv); }; + +extern int HandleServiceOptions(); diff --git a/server-tools/instance-manager/Makefile.am b/server-tools/instance-manager/Makefile.am index 462d7984aa4..59f057648ed 100644 --- a/server-tools/instance-manager/Makefile.am +++ b/server-tools/instance-manager/Makefile.am @@ -76,7 +76,10 @@ mysqlmanager_SOURCES= command.cc command.h mysqlmanager.cc \ guardian.cc guardian.h \ parse_output.cc parse_output.h \ mysql_manager_error.h \ - portability.h + portability.h \ + exit_codes.h \ + user_management_commands.h \ + user_management_commands.cc mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ liboptions.la \ @@ -90,6 +93,9 @@ mysqlmanager_LDADD= @CLIENT_EXTRA_LDFLAGS@ \ EXTRA_DIST = WindowsService.cpp WindowsService.h IMService.cpp \ IMService.h cmakelists.txt +AM_CFLAGS = -Werror +AM_CXXFLAGS = -Werror + tags: ctags -R *.h *.cc diff --git a/server-tools/instance-manager/WindowsService.cpp b/server-tools/instance-manager/WindowsService.cpp index 192045b7a4c..8a36a2f2fdd 100644 --- a/server-tools/instance-manager/WindowsService.cpp +++ b/server-tools/instance-manager/WindowsService.cpp @@ -7,9 +7,9 @@ static WindowsService *gService; WindowsService::WindowsService(void) : statusCheckpoint(0), serviceName(NULL), - inited(false), + inited(FALSE), dwAcceptedControls(SERVICE_ACCEPT_STOP), - debugging(false) + debugging(FALSE) { gService= this; status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; @@ -22,11 +22,12 @@ WindowsService::~WindowsService(void) BOOL WindowsService::Install() { - bool ret_val= false; + bool ret_val= FALSE; SC_HANDLE newService; SC_HANDLE scm; - if (IsInstalled()) return true; + if (IsInstalled()) + return TRUE; // determine the name of the currently executing file char szFilePath[_MAX_PATH]; @@ -34,7 +35,7 @@ BOOL WindowsService::Install() // open a connection to the SCM if (!(scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE))) - return false; + return FALSE; newService= CreateService(scm, serviceName, displayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, @@ -45,7 +46,7 @@ BOOL WindowsService::Install() if (newService) { CloseServiceHandle(newService); - ret_val= true; + ret_val= TRUE; } CloseServiceHandle(scm); @@ -56,34 +57,35 @@ BOOL WindowsService::Init() { assert(serviceName != NULL); - if (inited) return true; + if (inited) + return TRUE; SERVICE_TABLE_ENTRY stb[] = { { (LPSTR)serviceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, { NULL, NULL } }; - inited= true; + inited= TRUE; return StartServiceCtrlDispatcher(stb); //register with the Service Manager } BOOL WindowsService::Remove() { - bool ret_val= false; + bool ret_val= FALSE; - if (! IsInstalled()) - return true; + if (!IsInstalled()) + return TRUE; // open a connection to the SCM SC_HANDLE scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE); - if (! scm) - return false; + if (!scm) + return FALSE; SC_HANDLE service= OpenService(scm, serviceName, DELETE); if (service) { if (DeleteService(service)) - ret_val= true; + ret_val= TRUE; DWORD dw= ::GetLastError(); CloseServiceHandle(service); } @@ -116,7 +118,8 @@ void WindowsService::SetAcceptedControls(DWORD acceptedControls) BOOL WindowsService::ReportStatus(DWORD currentState, DWORD waitHint, DWORD dwError) { - if(debugging) return TRUE; + if (debugging) + return TRUE; if(currentState == SERVICE_START_PENDING) status.dwControlsAccepted= 0; diff --git a/server-tools/instance-manager/WindowsService.h b/server-tools/instance-manager/WindowsService.h index 1a034ce1351..3af7cdf39a7 100644 --- a/server-tools/instance-manager/WindowsService.h +++ b/server-tools/instance-manager/WindowsService.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #pragma once class WindowsService diff --git a/server-tools/instance-manager/command.h b/server-tools/instance-manager/command.h index b84cc6a8e9e..f31ea404867 100644 --- a/server-tools/instance-manager/command.h +++ b/server-tools/instance-manager/command.h @@ -22,10 +22,12 @@ #pragma interface #endif -/* Class responsible for allocation of im commands. */ +/* Class responsible for allocation of IM commands. */ class Instance_map; +struct st_net; + /* Command - entry point for any command. GangOf4: 'Command' design pattern @@ -37,8 +39,18 @@ public: Command(Instance_map *instance_map_arg= 0); virtual ~Command(); - /* method of executing: */ - virtual int execute(struct st_net *net, ulong connection_id) = 0; + /* + This operation incapsulates behaviour of the command. + + SYNOPSYS + net The network connection to the client. + connection_id Client connection ID + + RETURN + 0 On success + non 0 On error. Client error code is returned. + */ + virtual int execute(st_net *net, ulong connection_id) = 0; protected: Instance_map *instance_map; diff --git a/server-tools/instance-manager/commands.cc b/server-tools/instance-manager/commands.cc index 7b999f61503..83ed9cf6536 100644 --- a/server-tools/instance-manager/commands.cc +++ b/server-tools/instance-manager/commands.cc @@ -14,36 +14,54 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + #include "commands.h" +#include <my_global.h> +#include <m_string.h> +#include <m_ctype.h> +#include <mysql.h> +#include <my_dir.h> + +#include "buffer.h" +#include "guardian.h" #include "instance_map.h" +#include "log.h" +#include "manager.h" #include "messages.h" #include "mysqld_error.h" #include "mysql_manager_error.h" -#include "protocol.h" -#include "buffer.h" #include "options.h" +#include "priv.h" +#include "protocol.h" -#include <m_string.h> -#include <m_ctype.h> -#include <mysql.h> -#include <my_dir.h> + +/* + modify_defaults_to_im_error -- a map of error codes of + mysys::modify_defaults_file() into Instance Manager error codes. +*/ + +static const int modify_defaults_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, + ER_ACCESS_OPTION_FILE }; /* - Add a string to a buffer + Add a string to a buffer. SYNOPSYS put_to_buff() buff buffer to add the string str string to add - uint offset in the buff to add a string + position offset in the buff to add a string DESCRIPTION Function to add a string to the buffer. It is different from - store_to_protocol_packet, which is used in the protocol.cc. The last - one also stores the length of the string in a special way. + store_to_protocol_packet, which is used in the protocol.cc. + The last one also stores the length of the string in a special way. This is required for MySQL client/server protocol support only. RETURN @@ -51,7 +69,6 @@ 1 - error occured */ - static inline int put_to_buff(Buffer *buff, const char *str, uint *position) { uint len= strlen(str); @@ -88,749 +105,1615 @@ static int parse_version_number(const char *version_str, char *version, } -/* implementation for Show_instances: */ +/************************************************************************** + Implementation of Instance_name. +**************************************************************************/ +Instance_name::Instance_name(const LEX_STRING *name) +{ + str.str= str_buffer; + str.length= name->length; -/* - The method sends a list of instances in the instance map to the client. + if (str.length > MAX_INSTANCE_NAME_SIZE - 1) + str.length= MAX_INSTANCE_NAME_SIZE - 1; - SYNOPSYS - Show_instances::execute() - net The network connection to the client. - connection_id Client connection ID + strmake(str.str, name->str, str.length); +} - RETURN - 0 - ok - 1 - error occured +/************************************************************************** + Implementation of Show_instances. +**************************************************************************/ + +/* + Implementation of SHOW INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instances::execute(struct st_net *net, ulong connection_id) +int Show_instances::execute(st_net *net, ulong connection_id) { - Buffer send_buff; /* buffer for packets */ - LIST name, status; - NAME_WITH_LENGTH name_field, status_field; + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net))) + return err_code; + + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instances::write_header(st_net *net) +{ + LIST name, state; + LEX_STRING name_field, state_field; LIST *field_list; - uint position=0; - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - field_list= list_add(NULL, &status); + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + field_list= list_add(NULL, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + + +int Show_instances::write_data(st_net *net) +{ + my_bool err_status= FALSE; + + Instance *instance; + Instance_map::Iterator iterator(instance_map); + instance_map->guardian->lock(); + instance_map->lock(); + + while ((instance= iterator.next())) { - Instance *instance; - Instance_map::Iterator iterator(instance_map); + Buffer send_buf; /* buffer for packets */ + uint pos= 0; + + const char *instance_name= instance->options.instance_name.str; + const char *state_name= instance_map->get_instance_state_name(instance); - instance_map->lock(); - while ((instance= iterator.next())) + if (store_to_protocol_packet(&send_buf, instance_name, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + my_net_write(net, send_buf.buffer, pos)) { - position= 0; - store_to_protocol_packet(&send_buff, instance->options.instance_name, - &position); - if (instance->is_running()) - store_to_protocol_packet(&send_buff, (char*) "online", &position); - else - store_to_protocol_packet(&send_buff, (char*) "offline", &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + err_status= TRUE; + break; } - instance_map->unlock(); } - if (send_eof(net)) - goto err; - if (net_flush(net)) - goto err; - return 0; -err: - return ER_OUT_OF_RESOURCES; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return err_status ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Flush_instances: */ +/************************************************************************** + Implementation of Flush_instances. +**************************************************************************/ + +/* + Implementation of FLUSH INSTANCES statement. + + Possible error codes: + ER_OUT_OF_RESOURCES Not enough resources to complete the operation + ER_THERE_IS_ACTIVE_INSTACE If there is an active instance +*/ -int Flush_instances::execute(struct st_net *net, ulong connection_id) +int Flush_instances::execute(st_net *net, ulong connection_id) { - if (instance_map->flush_instances() || - net_send_ok(net, connection_id, NULL)) + instance_map->guardian->lock(); + instance_map->lock(); + + if (instance_map->is_there_active_instance()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); + return ER_THERE_IS_ACTIVE_INSTACE; + } + + if (instance_map->flush_instances()) + { + instance_map->unlock(); + instance_map->guardian->unlock(); return ER_OUT_OF_RESOURCES; + } - return 0; + instance_map->unlock(); + instance_map->guardian->unlock(); + + return net_send_ok(net, connection_id, NULL) ? ER_OUT_OF_RESOURCES : 0; } -/* implementation for Show_instance_status: */ +/************************************************************************** + Implementation of Abstract_instance_cmd. +**************************************************************************/ -Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Abstract_instance_cmd::Abstract_instance_cmd( + Instance_map *instance_map_arg, const LEX_STRING *instance_name_arg) + :Command(instance_map_arg), + instance_name(instance_name_arg) { - Instance *instance; + /* + MT-NOTE: we can not make a search for Instance object here, + because it can dissappear after releasing the lock. + */ +} + + +int Abstract_instance_cmd::execute(st_net *net, ulong connection_id) +{ + int err_code; + + instance_map->lock(); + + { + Instance *instance= instance_map->find(get_instance_name()); + + if (!instance) + { + instance_map->unlock(); + return ER_BAD_INSTANCE_NAME; + } + + err_code= execute_impl(net, instance); + } - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + instance_map->unlock(); + + if (!err_code) + err_code= send_ok_response(net, connection_id); + + return err_code; } -/* - The method sends a table with a status of requested instance to the client. +/************************************************************************** + Implementation of Show_instance_status. +**************************************************************************/ - SYNOPSYS - Show_instance_status::do_command() - net The network connection to the client. - instance_name The name of the instance. +Show_instance_status::Show_instance_status(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} - RETURN - 0 - ok - 1 - error occured + +/* + Implementation of SHOW INSTANCE STATUS statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ +int Show_instance_status::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; -int Show_instance_status::execute(struct st_net *net, - ulong connection_id) + return 0; +} + + +int Show_instance_status::send_ok_response(st_net *net, ulong connection_id) { - enum { MAX_VERSION_LENGTH= 40 }; - Buffer send_buff; /* buffer for packets */ - LIST name, status, version, version_number; + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_status::write_header(st_net *net) +{ + LIST name, state, version, version_number, mysqld_compatible; LIST *field_list; - NAME_WITH_LENGTH name_field, status_field, version_field, - version_number_field; - uint position=0; + LEX_STRING name_field, state_field, version_field, + version_number_field, mysqld_compatible_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "instance_name"; + name_field.str= (char *) "instance_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - status_field.name= (char*) "status"; - status_field.length= DEFAULT_FIELD_LENGTH; - status.data= &status_field; - version_field.name= (char*) "version"; + + state_field.str= (char *) "state"; + state_field.length= DEFAULT_FIELD_LENGTH; + state.data= &state_field; + + version_field.str= (char *) "version"; version_field.length= MAX_VERSION_LENGTH; version.data= &version_field; - version_number_field.name= (char*) "version_number"; + + version_number_field.str= (char *) "version_number"; version_number_field.length= MAX_VERSION_LENGTH; version_number.data= &version_number_field; - field_list= list_add(NULL, &version); + + mysqld_compatible_field.str= (char *) "mysqld_compatible"; + mysqld_compatible_field.length= DEFAULT_FIELD_LENGTH; + mysqld_compatible.data= &mysqld_compatible_field; + + field_list= list_add(NULL, &mysqld_compatible); + field_list= list_add(field_list, &version); field_list= list_add(field_list, &version_number); - field_list= list_add(field_list, &status); + field_list= list_add(field_list, &state); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - { - 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); +int Show_instance_status::write_data(st_net *net, Instance *instance) +{ + Buffer send_buf; /* buffer for packets */ + char version_num_buf[MAX_VERSION_LENGTH]; + uint pos= 0; - if (instance->options.mysqld_version) - { - char parsed_version[MAX_VERSION_LENGTH]; + const char *state_name; + const char *version_tag= "unknown"; + const char *version_num= "unknown"; + const char *mysqld_compatible_status; - parse_version_number(instance->options.mysqld_version, parsed_version, - sizeof(parsed_version)); - store_to_protocol_packet(&send_buff, parsed_version, &position); + instance_map->guardian->lock(); + state_name= instance_map->get_instance_state_name(instance); + mysqld_compatible_status= instance->is_mysqld_compatible() ? "yes" : "no"; + instance_map->guardian->unlock(); - store_to_protocol_packet(&send_buff, instance->options.mysqld_version, - &position); - } - else - { - store_to_protocol_packet(&send_buff, (char*) "unknown", &position); - store_to_protocol_packet(&send_buff, (char*) "unknown", &position); - } + if (instance->options.mysqld_version) + { + if (parse_version_number(instance->options.mysqld_version, version_num_buf, + sizeof(version_num_buf))) + return ER_OUT_OF_RESOURCES; - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + version_num= version_num_buf; + version_tag= instance->options.mysqld_version; } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buf, get_instance_name()->str, &pos) || + store_to_protocol_packet(&send_buf, state_name, &pos) || + store_to_protocol_packet(&send_buf, version_num, &pos) || + store_to_protocol_packet(&send_buf, version_tag, &pos) || + store_to_protocol_packet(&send_buf, mysqld_compatible_status, &pos) || + my_net_write(net, send_buf.buffer, (uint) pos)) + { + return ER_OUT_OF_RESOURCES; + } return 0; +} + + +/************************************************************************** + Implementation of Show_instance_options. +**************************************************************************/ -err: - return ER_OUT_OF_RESOURCES; +Show_instance_options::Show_instance_options( + Instance_map *instance_map_arg, const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ } -/* Implementation for Show_instance_options */ +/* + Implementation of SHOW INSTANCE OPTIONS statement. -Show_instance_options::Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len): - Command(instance_map_arg) + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Show_instance_options::execute_impl(st_net *net, Instance *instance) { - Instance *instance; + int err_code; - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; } -int Show_instance_options::execute(struct st_net *net, ulong connection_id) +int Show_instance_options::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_options::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name, option; LIST *field_list; - NAME_WITH_LENGTH name_field, option_field; - uint position=0; + LEX_STRING name_field, option_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "option_name"; + name_field.str= (char *) "option_name"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - option_field.name= (char*) "value"; + + option_field.str= (char *) "value"; option_field.length= DEFAULT_FIELD_LENGTH; option.data= &option_field; + field_list= list_add(NULL, &option); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} + +int Show_instance_options::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + uint pos= 0; + + if (store_to_protocol_packet(&send_buff, "instance_name", &pos) || + store_to_protocol_packet(&send_buff, get_instance_name()->str, &pos) || + my_net_write(net, send_buff.buffer, pos)) { - Instance *instance; - - if (!(instance= instance_map->find(instance_name, strlen(instance_name)))) - goto err; - store_to_protocol_packet(&send_buff, (char*) "instance_name", &position); - store_to_protocol_packet(&send_buff, (char*) instance_name, &position); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - if ((instance->options.mysqld_path)) - { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "mysqld-path", &position); - store_to_protocol_packet(&send_buff, - (char*) instance->options.mysqld_path, - &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + return ER_OUT_OF_RESOURCES; + } + + /* Loop through the options. */ + + for (int i= 0; i < instance->options.get_num_options(); i++) + { + Named_value option= instance->options.get_option(i); + const char *option_value= option.get_value()[0] ? option.get_value() : ""; + + pos= 0; - if ((instance->options.nonguarded)) + if (store_to_protocol_packet(&send_buff, option.get_name(), &pos) || + store_to_protocol_packet(&send_buff, option_value, &pos) || + my_net_write(net, send_buff.buffer, pos)) { - position= 0; - store_to_protocol_packet(&send_buff, (char*) "nonguarded", &position); - store_to_protocol_packet(&send_buff, "", &position); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + return ER_OUT_OF_RESOURCES; } + } + + return 0; +} + - /* loop through the options stored in DYNAMIC_ARRAY */ - for (uint i= 0; i < instance->options.options_array.elements; i++) +/************************************************************************** + Implementation of Start_instance. +**************************************************************************/ + +Start_instance::Start_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} + + +/* + Implementation of START INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Start_instance::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= instance->start())) + return err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->guard(instance); + + return 0; +} + + +int Start_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance started")) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation of Stop_instance. +**************************************************************************/ + +Stop_instance::Stop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) +{ +} + + +/* + Implementation of STOP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Stop_instance::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if (!(instance->options.nonguarded)) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + return 0; +} + + +int Stop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation for Create_instance. +**************************************************************************/ + +Create_instance::Create_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Command(instance_map_arg), + instance_name(instance_name_arg) +{ +} + + +/* + This operation initializes Create_instance object. + + SYNOPSYS + text [IN/OUT] a pointer to the text containing instance options. + + RETURN + FALSE On success. + TRUE On error. +*/ + +bool Create_instance::init(const char **text) +{ + return options.init() || parse_args(text); +} + + +/* + This operation parses CREATE INSTANCE options. + + SYNOPSYS + text [IN/OUT] a pointer to the text containing instance options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Create_instance::parse_args(const char **text) +{ + uint len; + + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return FALSE; /* OK: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + /* Looking for equal sign. */ + + skip_spaces(text); + + if (**text == '=') { - char *tmp_option, *option_value; - get_dynamic(&(instance->options.options_array), (gptr) &tmp_option, i); - option_value= strchr(tmp_option, '='); - /* split the option string into two parts if it has a value */ + ++(*text); /* Skip an equal sign. */ - position= 0; - if (option_value != NULL) + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ + + if (**text != '\'' && **text != '"') { - *option_value= 0; - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, option_value + 1, &position); - /* join name and the value into the same option again */ - *option_value= '='; + /* Option value is a simple token. */ + + LEX_STRING option_value; + + get_word(text, &option_value.length, ALPHANUM); + + if (option_value.length == 0) + return TRUE; /* internal parser error. */ + + option_value.str= (char *) *text; + *text+= option_value.length; + + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ } else { - store_to_protocol_packet(&send_buff, tmp_option + 2, &position); - store_to_protocol_packet(&send_buff, "", &position); + /* Option value is a string. */ + + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ + + *text+= len; } + } + + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_value_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); - if (send_buff.is_error() || - my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + if (options.add_element(&option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); } +} - if (send_eof(net) || net_flush(net)) - goto err; - return 0; +/* + Implementation of CREATE INSTANCE statement. -err: - return ER_OUT_OF_RESOURCES; + Possible error codes: + ER_MALFORMED_INSTANCE_NAME Instance name is malformed + ER_CREATE_EXISTING_INSTANCE There is an instance with the given name + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Create_instance::execute(st_net *net, ulong connection_id) +{ + int err_code; + + /* Check that the name is valid and there is no instance with such name. */ + + if (!Instance::is_name_valid(get_instance_name())) + return ER_MALFORMED_INSTANCE_NAME; + + /* + NOTE: In order to prevent race condition, we should perform all operations + on under acquired lock. + */ + + instance_map->lock(); + + if (instance_map->find(get_instance_name())) + { + instance_map->unlock(); + return ER_CREATE_EXISTING_INSTANCE; + } + + if ((err_code= instance_map->create_instance(get_instance_name(), &options))) + { + instance_map->unlock(); + return err_code; + } + + if ((err_code= create_instance_in_file(get_instance_name(), &options))) + { + Instance *instance= instance_map->find(get_instance_name()); + + if (instance) + instance_map->remove_instance(instance); /* instance is deleted here. */ + + instance_map->unlock(); + return err_code; + } + + /* That's all. */ + + instance_map->unlock(); + + /* Send the result. */ + + if (net_send_ok(net, connection_id, NULL)) + return ER_OUT_OF_RESOURCES; + + return 0; } -/* Implementation for Start_instance */ +/************************************************************************** + Implementation for Drop_instance. +**************************************************************************/ -Start_instance::Start_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) +Drop_instance::Drop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) { - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; } -int Start_instance::execute(struct st_net *net, ulong connection_id) +/* + Implementation of DROP INSTANCE statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_DROP_ACTIVE_INSTANCE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Drop_instance::execute_impl(st_net *net, Instance *instance) { - uint err_code; - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ - else - { - if ((err_code= instance->start())) - return err_code; + int err_code; + + /* Check that the instance is offline. */ - if (!(instance->options.nonguarded)) - instance_map->guardian->guard(instance); + if (instance_map->guardian->is_active(instance)) + return ER_DROP_ACTIVE_INSTANCE; - net_send_ok(net, connection_id, "Instance started"); - return 0; + err_code= modify_defaults_file(Options::Main::config_file, NULL, NULL, + get_instance_name()->str, MY_REMOVE_SECTION); + DBUG_ASSERT(err_code >= 0 && err_code <= 2); + + if (err_code) + { + log_error("Can not remove instance '%s' from defaults file (%s). " + "Original error code: %d.", + (const char *) get_instance_name()->str, + (const char *) Options::Main::config_file, + (int) err_code); } + + if (err_code) + return modify_defaults_to_im_error[err_code]; + + /* Remove instance from the instance map hash and Guardian's list. */ + + if (!instance->options.nonguarded) + instance_map->guardian->stop_guard(instance); + + if ((err_code= instance->stop())) + return err_code; + + instance_map->remove_instance(instance); + + return 0; } -/* implementation for Show_instance_log: */ +int Drop_instance::send_ok_response(st_net *net, ulong connection_id) +{ + if (net_send_ok(net, connection_id, "Instance dropped")) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +/************************************************************************** + Implementation for Show_instance_log. +**************************************************************************/ Show_instance_log::Show_instance_log(Instance_map *instance_map_arg, - const char *name, uint len, + const LEX_STRING *instance_name_arg, Log_type log_type_arg, - const char *size_arg, - const char *offset_arg) - :Command(instance_map_arg) + uint size_arg, uint offset_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg), + log_type(log_type_arg), + size(size_arg), + offset(offset_arg) { - Instance *instance; +} + + +/* + Implementation of SHOW INSTANCE LOG statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OFFSET_ERROR We were requested to read negative number of + bytes from the log + ER_NO_SUCH_LOG The specified type of log is not available for + the given instance + ER_GUESS_LOGFILE IM wasn't able to figure out the log + placement, while it is enabled. Probably user + should specify the path to the logfile + explicitly. + ER_OPEN_LOGFILE Cannot open the logfile + ER_READ_FILE Cannot read the logfile + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Show_instance_log::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= check_params(instance))) + return err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} - 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; +int Show_instance_log::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; } +int Show_instance_log::check_params(Instance *instance) +{ + const char *logpath= instance->options.logs[log_type]; -/* - Open the logfile, read requested part of the log and send the info - to the client. + /* Cannot read negative number of bytes. */ - SYNOPSYS - Show_instance_log::execute() - net The network connection to the client. - connection_id Client connection ID + if (offset > size) + return ER_OFFSET_ERROR; - DESCRIPTION + /* Instance has no such log. */ - Send a table with the content of the log requested. The function also - deals with errro handling, to be verbose. + if (logpath == NULL) + return ER_NO_SUCH_LOG; + + if (*logpath == '\0') + return ER_GUESS_LOGFILE; + + return 0; +} - 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) +int Show_instance_log::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name; LIST *field_list; - NAME_WITH_LENGTH name_field; - uint position= 0; + LEX_STRING name_field; + + /* Create list of the fields to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Log"; + name_field.str= (char *) "Log"; name_field.length= DEFAULT_FIELD_LENGTH; - name.data= &name_field; - field_list= list_add(NULL, &name); - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + name.data= &name_field; - /* cannot read negative number of bytes */ - if (offset > size) - return ER_OFFSET_ERROR; + field_list= list_add(NULL, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - { - Instance *instance; - const char *logpath; - File fd; - if ((instance= instance_map->find(instance_name, - strlen(instance_name))) == NULL) - goto err; +int Show_instance_log::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + uint pos= 0; - logpath= instance->options.logs[log_type]; + const char *logpath= instance->options.logs[log_type]; + File fd; - /* Instance has no such log */ - if (logpath == NULL) - return ER_NO_SUCH_LOG; + size_t buff_size; + int read_len; - if (*logpath == '\0') - return ER_GUESS_LOGFILE; + MY_STAT file_stat; + Buffer read_buff; + if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) <= 0) + return ER_OPEN_LOGFILE; - if ((fd= my_open(logpath, O_RDONLY | O_BINARY, MYF(MY_WME))) >= 0) - { - size_t buff_size; - int read_len; - /* calculate buffer size */ - MY_STAT file_stat; - Buffer read_buff; + /* my_fstat doesn't use the flag parameter */ + if (my_fstat(fd, &file_stat, MYF(0))) + { + close(fd); + return ER_OUT_OF_RESOURCES; + } - /* my_fstat doesn't use the flag parameter */ - if (my_fstat(fd, &file_stat, MYF(0))) - goto err; + /* calculate buffer size */ + buff_size= (size - offset); - buff_size= (size - offset); + read_buff.reserve(0, buff_size); - read_buff.reserve(0, buff_size); + /* read in one chunk */ + read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); - /* read in one chunk */ - read_len= (int)my_seek(fd, file_stat.st_size - size, MY_SEEK_SET, MYF(0)); + if ((read_len= my_read(fd, (byte*) read_buff.buffer, + buff_size, MYF(0))) < 0) + { + close(fd); + return ER_READ_FILE; + } - if ((read_len= my_read(fd, (byte*) read_buff.buffer, - buff_size, MYF(0))) < 0) - return ER_READ_FILE; - store_to_protocol_packet(&send_buff, read_buff.buffer, - &position, read_len); - close(fd); - } - else - return ER_OPEN_LOGFILE; + close(fd); - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; + if (store_to_protocol_packet(&send_buff, read_buff.buffer, &pos, read_len) || + my_net_write(net, send_buff.buffer, pos)) + { + return ER_OUT_OF_RESOURCES; } - if (send_eof(net) || net_flush(net)) - goto err; - return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* implementation for Show_instance_log_files: */ +/************************************************************************** + Implementation of Show_instance_log_files. +**************************************************************************/ Show_instance_log_files::Show_instance_log_files - (Instance_map *instance_map_arg, const char *name, uint len) - :Command(instance_map_arg) + (Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg) + :Abstract_instance_cmd(instance_map_arg, instance_name_arg) { - Instance *instance; - - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - instance_name= instance->options.instance_name; - else - instance_name= NULL; } /* - The method sends a table with a list of log files - used by the instance. - - SYNOPSYS - Show_instance_log_files::execute() - net The network connection to the client. - connection_id The ID of the client connection + Implementation of SHOW INSTANCE LOG FILES statement. - RETURN - ER_BAD_INSTANCE_NAME The instance name specified is not valid - ER_OUT_OF_RESOURCES some error occured - 0 - ok + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_OUT_OF_RESOURCES Not enough resources to complete the operation */ -int Show_instance_log_files::execute(struct st_net *net, ulong connection_id) +int Show_instance_log_files::execute_impl(st_net *net, Instance *instance) +{ + int err_code; + + if ((err_code= write_header(net)) || + (err_code= write_data(net, instance))) + return err_code; + + return 0; +} + + +int Show_instance_log_files::send_ok_response(st_net *net, ulong connection_id) +{ + if (send_eof(net) || net_flush(net)) + return ER_OUT_OF_RESOURCES; + + return 0; +} + + +int Show_instance_log_files::write_header(st_net *net) { - Buffer send_buff; /* buffer for packets */ LIST name, path, size; LIST *field_list; - NAME_WITH_LENGTH name_field, path_field, size_field; - uint position= 0; + LEX_STRING name_field, path_field, size_field; - if (!instance_name) - return ER_BAD_INSTANCE_NAME; + /* Create list of the fileds to be passed to send_fields(). */ - /* create list of the fileds to be passed to send_fields */ - name_field.name= (char*) "Logfile"; + name_field.str= (char *) "Logfile"; name_field.length= DEFAULT_FIELD_LENGTH; name.data= &name_field; - path_field.name= (char*) "Path"; + + path_field.str= (char *) "Path"; path_field.length= DEFAULT_FIELD_LENGTH; path.data= &path_field; - size_field.name= (char*) "File size"; + + size_field.str= (char *) "File size"; size_field.length= DEFAULT_FIELD_LENGTH; size.data= &size_field; + field_list= list_add(NULL, &size); field_list= list_add(field_list, &path); field_list= list_add(field_list, &name); - send_fields(net, field_list); + return send_fields(net, field_list) ? ER_OUT_OF_RESOURCES : 0; +} - Instance *instance; - if ((instance= instance_map-> - find(instance_name, strlen(instance_name))) == NULL) - goto err; +int Show_instance_log_files::write_data(st_net *net, Instance *instance) +{ + Buffer send_buff; /* buffer for packets */ + /* + We have alike structure in instance_options.cc. We use such to be able + to loop through the options, which we need to handle in some common way. + */ + struct log_files_st + { + const char *name; + const char *value; + } logs[]= { + {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, + {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, + {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, + {NULL, NULL} + }; + struct log_files_st *log_files; + + for (log_files= logs; log_files->name; log_files++) + { + if (!log_files->value) + continue; + + struct stat file_stat; /* - We have alike structure in instance_options.cc. We use such to be able - to loop through the options, which we need to handle in some common way. + Save some more space for the log file names. In fact all + we need is strlen("GENERAL_LOG") + 1 */ - struct log_files_st - { - const char *name; - const char *value; - } logs[]= - { - {"ERROR LOG", instance->options.logs[IM_LOG_ERROR]}, - {"GENERAL LOG", instance->options.logs[IM_LOG_GENERAL]}, - {"SLOW LOG", instance->options.logs[IM_LOG_SLOW]}, - {NULL, NULL} - }; - struct log_files_st *log_files; - - for (log_files= logs; log_files->name; log_files++) + enum { LOG_NAME_BUFFER_SIZE= 20 }; + char buff[LOG_NAME_BUFFER_SIZE]; + + uint pos= 0; + + const char *log_path= ""; + const char *log_size= "0"; + + if (!stat(log_files->value, &file_stat) && + MY_S_ISREG(file_stat.st_mode)) { - if (log_files->value != NULL) - { - struct stat file_stat; - /* - Save some more space for the log file names. In fact all - we need is srtlen("GENERAL_LOG") + 1 - */ - enum { LOG_NAME_BUFFER_SIZE= 20 }; - char buff[LOG_NAME_BUFFER_SIZE]; - - position= 0; - /* store the type of the log in the send buffer */ - store_to_protocol_packet(&send_buff, log_files->name, &position); - if (stat(log_files->value, &file_stat)) - { - store_to_protocol_packet(&send_buff, "", &position); - store_to_protocol_packet(&send_buff, (char*) "0", &position); - } - else if (MY_S_ISREG(file_stat.st_mode)) - { - store_to_protocol_packet(&send_buff, - (char*) log_files->value, - &position); - int10_to_str(file_stat.st_size, buff, 10); - store_to_protocol_packet(&send_buff, (char*) buff, &position); - } - - if (my_net_write(net, send_buff.buffer, (uint) position)) - goto err; - } + int10_to_str(file_stat.st_size, buff, 10); + + log_path= log_files->value; + log_size= buff; } - } - if (send_eof(net) || net_flush(net)) - goto err; + if (store_to_protocol_packet(&send_buff, log_files->name, &pos) || + store_to_protocol_packet(&send_buff, log_path, &pos) || + store_to_protocol_packet(&send_buff, log_size, &pos) || + my_net_write(net, send_buff.buffer, pos)) + return ER_OUT_OF_RESOURCES; + } return 0; - -err: - return ER_OUT_OF_RESOURCES; } -/* implementation for SET instance_name.option=option_value: */ +/************************************************************************** + Implementation of Abstract_option_cmd. +**************************************************************************/ + +/* + Instance_options_list -- a data class representing a list of options for + some instance. +*/ -Set_option::Set_option(Instance_map *instance_map_arg, - const char *name, uint len, - const char *option_arg, uint option_len_arg, - const char *option_value_arg, uint option_value_len_arg) - :Command(instance_map_arg) +class Instance_options_list { - Instance *instance; +public: + Instance_options_list(const LEX_STRING *instance_name_arg); - /* we make a search here, since we don't want to store the name */ - if ((instance= instance_map->find(name, len))) - { - instance_name= instance->options.instance_name; +public: + bool init(); - /* add prefix for add_option */ - if ((option_len_arg < MAX_OPTION_LEN - 1) || - (option_value_len_arg < MAX_OPTION_LEN - 1)) - { - strmake(option, option_arg, option_len_arg); - strmake(option_value, option_value_arg, option_value_len_arg); - } - else - { - option[0]= 0; - option_value[0]= 0; - } - instance_name_len= len; - } - else + const LEX_STRING *get_instance_name() const { - instance_name= NULL; - instance_name_len= 0; + return instance_name.get_str(); } + +public: + /* + This member is set and used only in Abstract_option_cmd::execute_impl(). + Normally it is not used (and should not). + + The problem is that construction and execution of commands are made not + in one transaction (not under one lock session). So, we can not initialize + instance in constructor and use it in execution. + */ + Instance *instance; + + Named_value_arr options; + +private: + Instance_name instance_name; +}; + + +/**************************************************************************/ + +Instance_options_list::Instance_options_list( + const LEX_STRING *instance_name_arg) + :instance(NULL), + instance_name(instance_name_arg) +{ } -/* - The method sends a table with a list of log files - used by the instance. +bool Instance_options_list::init() +{ + return options.init(); +} - SYNOPSYS - Set_option::correct_file() - skip Skip the option, being searched while writing the result file. - That is, to delete it. - DESCRIPTION +/**************************************************************************/ + +C_MODE_START + +static byte* get_item_key(const byte* item, uint* len, + my_bool __attribute__((unused)) t) +{ + const Instance_options_list *lst= (const Instance_options_list *) item; + *len= lst->get_instance_name()->length; + return (byte *) lst->get_instance_name()->str; +} + +static void delete_item(void *item) +{ + delete (Instance_options_list *) item; +} + +C_MODE_END + + +/**************************************************************************/ + +Abstract_option_cmd::Abstract_option_cmd(Instance_map *instance_map_arg) + :Command(instance_map_arg), + initialized(FALSE) +{ +} + + +Abstract_option_cmd::~Abstract_option_cmd() +{ + if (initialized) + hash_free(&instance_options_map); +} + + +bool Abstract_option_cmd::add_option(const LEX_STRING *instance_name, + Named_value *option) +{ + Instance_options_list *lst= get_instance_options_list(instance_name); + if (!lst) + return TRUE; + + lst->options.add_element(option); + + return FALSE; +} + + +bool Abstract_option_cmd::init(const char **text) +{ + static const int INITIAL_HASH_SIZE= 16; + + if (hash_init(&instance_options_map, default_charset_info, + INITIAL_HASH_SIZE, 0, 0, get_item_key, delete_item, 0)) + return TRUE; + + if (parse_args(text)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +/* Correct the option file. The "skip" option is used to remove the found option. + SYNOPSYS + Abstract_option_cmd::correct_file() + skip Skip the option, being searched while writing the result file. + That is, to delete it. + RETURN - ER_OUT_OF_RESOURCES out of resources + 0 Success + ER_OUT_OF_RESOURCES Not enough resources to complete the operation ER_ACCESS_OPTION_FILE Cannot access the option file - 0 - ok */ -int Set_option::correct_file(int skip) +int Abstract_option_cmd::correct_file(Instance *instance, Named_value *option, + bool skip) +{ + int err_code= modify_defaults_file(Options::Main::config_file, + option->get_name(), + option->get_value(), + instance->get_name()->str, + skip); + + DBUG_ASSERT(err_code >= 0 && err_code <= 2); + + if (err_code) + { + log_error("Can not modify option (%s) in defaults file (%s). " + "Original error code: %d.", + (const char *) option->get_name(), + (const char *) Options::Main::config_file, + (int) err_code); + } + + return modify_defaults_to_im_error[err_code]; +} + + +/* + Implementation of SET statement. + + Possible error codes: + ER_BAD_INSTANCE_NAME The instance with the given name does not exist + ER_INCOMPATIBLE_OPTION The specified option can not be set for + mysqld-compatible instance + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ + +int Abstract_option_cmd::execute(st_net *net, ulong connection_id) +{ + int err_code; + + instance_map->lock(); + + err_code= execute_impl(net, connection_id); + + instance_map->unlock(); + + return err_code; +} + + +Instance_options_list * +Abstract_option_cmd::get_instance_options_list(const LEX_STRING *instance_name) { - static const int mysys_to_im_error[]= { 0, ER_OUT_OF_RESOURCES, - ER_ACCESS_OPTION_FILE }; - int error; + Instance_options_list *lst= + (Instance_options_list *) hash_search(&instance_options_map, + (byte *) instance_name->str, + instance_name->length); + + if (!lst) + { + lst= new Instance_options_list(instance_name); - error= modify_defaults_file(Options::config_file, option, - option_value, instance_name, skip); - DBUG_ASSERT(error >= 0 && error <= 2); + if (!lst) + return NULL; - return mysys_to_im_error[error]; + if (lst->init() || my_hash_insert(&instance_options_map, (byte *) lst)) + { + delete lst; + return NULL; + } + } + + return lst; +} + + +int Abstract_option_cmd::execute_impl(st_net *net, ulong connection_id) +{ + int err_code; + + /* Check that all the specified instances exist and are offline. */ + + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + lst->instance= instance_map->find(lst->get_instance_name()); + + if (!lst->instance) + return ER_BAD_INSTANCE_NAME; + + if (instance_map->guardian->is_active(lst->instance)) + return ER_INSTANCE_IS_ACTIVE; + } + + /* Perform command-specific (SET/UNSET) actions. */ + + for (uint i= 0; i < instance_options_map.records; ++i) + { + Instance_options_list *lst= + (Instance_options_list *) hash_element(&instance_options_map, i); + + for (int j= 0; j < lst->options.get_size(); ++j) + { + Named_value option= lst->options.get_element(j); + err_code= process_option(lst->instance, &option); + + if (err_code) + break; + } + + if (err_code) + break; + } + + if (err_code == 0) + net_send_ok(net, connection_id, NULL); + + return err_code; +} + + +/************************************************************************** + Implementation of Set_option. +**************************************************************************/ + +Set_option::Set_option(Instance_map *instance_map_arg) + :Abstract_option_cmd(instance_map_arg) +{ } /* - The method sets an option in the the default config file (/etc/my.cnf). + This operation parses SET options. SYNOPSYS - Set_option::do_command() - net The network connection to the client. + text [IN/OUT] a pointer to the text containing options. RETURN - 0 - ok - 1 - error occured + FALSE On success. + TRUE On syntax error. */ -int Set_option::do_command(struct st_net *net) +bool Set_option::parse_args(const char **text) { - int error; + uint len; - /* we must hold the instance_map mutex while changing config file */ - instance_map->lock(); - error= correct_file(FALSE); - instance_map->unlock(); + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str= NULL; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + /* Looking for equal sign. */ + + skip_spaces(text); + + if (**text == '=') + { + ++(*text); /* Skip an equal sign. */ + + /* Looking for option value. */ + + skip_spaces(text); + + if (!**text) + return TRUE; /* Syntax error: EOS when option value expected. */ + + if (**text != '\'' && **text != '"') + { + /* Option value is a simple token. */ + + LEX_STRING option_value; + + get_word(text, &option_value.length, ALPHANUM); + + if (option_value.length == 0) + return TRUE; /* internal parser error. */ + + option_value.str= (char *) *text; + *text+= option_value.length; - return error; + if (!(option_value_str= Named_value::alloc_str(&option_value))) + return TRUE; /* out of memory during parsing. */ + } + else + { + /* Option value is a string. */ + + if (parse_option_value(*text, &len, &option_value_str)) + return TRUE; /* Syntax error: invalid string specification. */ + + *text+= len; + } + } + + if (!option_value_str) + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + return TRUE; /* out of memory during parsing. */ + } + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + { + Named_value::free_str(&option_name_str); + return TRUE; /* out of memory during parsing. */ + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } } -int Set_option::execute(struct st_net *net, ulong connection_id) +int Set_option::process_option(Instance *instance, Named_value *option) { - if (instance_name != NULL) + /* Check that the option is valid. */ + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option->get_name())) { - int val; + log_error("Error: IM-option (%s) can not be used " + "in the configuration of mysqld-compatible instance (%s).", + (const char *) option->get_name(), + (const char *) instance->get_name()->str); + return ER_INCOMPATIBLE_OPTION; + } - val= do_command(net); + /* Update the configuration file. */ - if (val == 0) - net_send_ok(net, connection_id, NULL); + int err_code= correct_file(instance, option, FALSE); - return val; - } + if (err_code) + return err_code; + + /* Update the internal cache. */ - return ER_BAD_INSTANCE_NAME; + if (instance->options.set_option(option)) + return ER_OUT_OF_RESOURCES; + + return 0; } -/* the only function from Unset_option we need to Implement */ +/************************************************************************** + Implementation of Unset_option. +**************************************************************************/ -int Unset_option::do_command(struct st_net *net) +Unset_option::Unset_option(Instance_map *instance_map_arg) + :Abstract_option_cmd(instance_map_arg) { - return correct_file(TRUE); } -/* Implementation for Stop_instance: */ +/* + This operation parses UNSET options. -Stop_instance::Stop_instance(Instance_map *instance_map_arg, - const char *name, uint len) - :Command(instance_map_arg) + SYNOPSYS + text [IN/OUT] a pointer to the text containing options. + + RETURN + FALSE On success. + TRUE On syntax error. +*/ + +bool Unset_option::parse_args(const char **text) { - /* 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; + uint len; + + /* Check if we have something (and trim leading spaces). */ + + get_word(text, &len, NONSPACE); + + if (len == 0) + return TRUE; /* Syntax error: no option. */ + + /* Main parsing loop. */ + + while (TRUE) + { + LEX_STRING instance_name; + LEX_STRING option_name; + char *option_name_str; + char *option_value_str; + + /* Looking for instance name. */ + + get_word(text, &instance_name.length, ALPHANUM); + + if (instance_name.length == 0) + return TRUE; /* Syntax error: instance name expected. */ + + instance_name.str= (char *) *text; + *text+= instance_name.length; + + skip_spaces(text); + + /* Check the the delimiter is a dot. */ + + if (**text != '.') + return TRUE; /* Syntax error: dot expected. */ + + ++(*text); /* Skip a dot. */ + + /* Looking for option name. */ + + get_word(text, &option_name.length, OPTION_NAME); + + if (option_name.length == 0) + return TRUE; /* Syntax error: option name expected. */ + + option_name.str= (char *) *text; + *text+= option_name.length; + + if (!(option_name_str= Named_value::alloc_str(&option_name))) + return TRUE; /* out of memory during parsing. */ + + { + LEX_STRING empty_str= { C_STRING_WITH_SIZE("") }; + + if (!(option_value_str= Named_value::alloc_str(&empty_str))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } + } + + { + Named_value option(option_name_str, option_value_str); + + if (add_option(&instance_name, &option)) + { + option.free(); + return TRUE; /* out of memory during parsing. */ + } + } + + skip_spaces(text); + + if (!**text) + return FALSE; /* OK: end of options. */ + + if (**text != ',') + return TRUE; /* Syntax error: comma expected. */ + + ++(*text); /* Skip a comma. */ + } } -int Stop_instance::execute(struct st_net *net, ulong connection_id) -{ - uint err_code; +/* + Implementation of UNSET statement. - if (instance == 0) - return ER_BAD_INSTANCE_NAME; /* haven't found an instance */ + Possible error codes: + ER_BAD_INSTANCE_NAME The instance name specified is not valid + ER_INSTANCE_IS_ACTIVE The specified instance is active + ER_OUT_OF_RESOURCES Not enough resources to complete the operation +*/ - if (!(instance->options.nonguarded)) - instance_map->guardian->stop_guard(instance); +int Unset_option::process_option(Instance *instance, Named_value *option) +{ + /* Update the configuration file. */ - if ((err_code= instance->stop())) + int err_code= correct_file(instance, option, TRUE); + + if (err_code) return err_code; - net_send_ok(net, connection_id, NULL); + /* Update the internal cache. */ + + instance->options.unset_option(option->get_name()); + return 0; } -int Syntax_error::execute(struct st_net *net, ulong connection_id) +/************************************************************************** + Implementation of Syntax_error. +**************************************************************************/ + +int Syntax_error::execute(st_net *net, ulong connection_id) { return ER_SYNTAX_ERROR; } diff --git a/server-tools/instance-manager/commands.h b/server-tools/instance-manager/commands.h index bfd38d34889..92008e00bfa 100644 --- a/server-tools/instance-manager/commands.h +++ b/server-tools/instance-manager/commands.h @@ -16,10 +16,20 @@ 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> + #include "command.h" #include "instance.h" #include "parse.h" +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +struct LEX_STRING; + /* Print all instances of this instance manager. Grammar: SHOW ISTANCES @@ -31,12 +41,16 @@ public: Show_instances(Instance_map *instance_map_arg): Command(instance_map_arg) {} - int execute(struct st_net *net, ulong connection_id); + int execute(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net); }; /* - Reread configuration file and refresh instance map. + Reread configuration file and refresh internal cache. Grammar: FLUSH INSTANCES */ @@ -46,7 +60,43 @@ public: Flush_instances(Instance_map *instance_map_arg): Command(instance_map_arg) {} - int execute(struct st_net *net, ulong connection_id); + int execute(st_net *net, ulong connection_id); +}; + + +/* + Abstract class for Instance-specific commands. +*/ + +class Abstract_instance_cmd : public Command +{ +public: + Abstract_instance_cmd(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +public: + virtual int execute(st_net *net, ulong connection_id); + +protected: + /* MT-NOTE: this operation is called under acquired Instance_map's lock. */ + virtual int execute_impl(st_net *net, Instance *instance) = 0; + + /* + This operation is invoked on successful return of execute_impl() and is + intended to send closing data. + + MT-NOTE: this operation is called under released Instance_map's lock. + */ + virtual int send_ok_response(st_net *net, ulong connection_id) = 0; + +protected: + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + Instance_name instance_name; }; @@ -55,31 +105,40 @@ public: Grammar: SHOW ISTANCE STATUS <instance_name> */ -class Show_instance_status : public Command +class Show_instance_status : public Abstract_instance_cmd { 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; + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Print options if chosen instance. + Print options of chosen instance. Grammar: SHOW INSTANCE OPTIONS <instance_name> */ -class Show_instance_options : public Command +class Show_instance_options : public Abstract_instance_cmd { public: - Show_instance_options(Instance_map *instance_map_arg, - const char *name, uint len); + const LEX_STRING *instance_name_arg); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; @@ -88,14 +147,15 @@ public: Grammar: START INSTANCE <instance_name> */ -class Start_instance : public Command +class Start_instance : public Abstract_instance_cmd { public: - Start_instance(Instance_map *instance_map_arg, const char *name, uint len); + Start_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; - Instance *instance; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; @@ -104,33 +164,95 @@ public: Grammar: STOP INSTANCE <instance_name> */ -class Stop_instance : public Command +class Stop_instance : public Abstract_instance_cmd { public: - Stop_instance(Instance_map *instance_map_arg, const char *name, uint len); + Stop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); - Instance *instance; - int execute(struct st_net *net, ulong connection_id); - const char *instance_name; +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); }; /* - Print requested part of the log + Create an instance. + Grammar: CREATE INSTANCE <instance_name> [<options>] +*/ + +class Create_instance : public Command +{ +public: + Create_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +public: + bool init(const char **text); + +protected: + virtual int execute(st_net *net, ulong connection_id); + + inline const LEX_STRING *get_instance_name() const + { + return instance_name.get_str(); + } + +private: + bool parse_args(const char **text); + +private: + Instance_name instance_name; + + Named_value_arr options; +}; + + +/* + Drop an instance. + Grammar: DROP INSTANCE <instance_name> + + Operation is permitted only if the instance is stopped. On successful + completion the instance section is removed from config file and the instance + is removed from the instance map. +*/ + +class Drop_instance : public Abstract_instance_cmd +{ +public: + Drop_instance(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); +}; + + +/* + Print requested part of the log. Grammar: - SHOW <instance_name> log {ERROR | SLOW | GENERAL} size[, offset_from_end] + SHOW <instance_name> LOG {ERROR | SLOW | GENERAL} size[, offset_from_end] */ -class Show_instance_log : public Command +class Show_instance_log : public Abstract_instance_cmd { public: + Show_instance_log(Instance_map *instance_map_arg, + const LEX_STRING *instance_name_arg, + Log_type log_type_arg, uint size_arg, uint offset_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int check_params(Instance *instance); + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); - Show_instance_log(Instance_map *instance_map_arg, const char *name, - uint len, Log_type log_type_arg, const char *size_arg, - const char *offset_arg); - int execute(struct st_net *net, ulong connection_id); +private: Log_type log_type; - const char *instance_name; uint size; uint offset; }; @@ -141,75 +263,112 @@ public: Grammar: SHOW <instance_name> LOG FILES */ -class Show_instance_log_files : public Command +class Show_instance_log_files : public Abstract_instance_cmd { public: - Show_instance_log_files(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; + const LEX_STRING *instance_name_arg); + +protected: + virtual int execute_impl(st_net *net, Instance *instance); + virtual int send_ok_response(st_net *net, ulong connection_id); + +private: + int write_header(st_net *net); + int write_data(st_net *net, Instance *instance); }; /* - Syntax error command. This command is issued if parser reported a syntax - error. We need it to distinguish the parse error and the situation when - parser internal error occured. E.g. parsing failed because we hadn't had - enought memory. In the latter case parse_command() should return an error. + Abstract class for option-management commands. */ -class Syntax_error : public Command +class Instance_options_list; + +class Abstract_option_cmd : public Command { public: - int execute(struct st_net *net, ulong connection_id); + ~Abstract_option_cmd(); + +public: + bool add_option(const LEX_STRING *instance_name, Named_value *option); + +public: + bool init(const char **text); + + virtual int execute(st_net *net, ulong connection_id); + +protected: + Abstract_option_cmd(Instance_map *instance_map_arg); + + int correct_file(Instance *instance, Named_value *option, bool skip); + +protected: + virtual bool parse_args(const char **text) = 0; + virtual int process_option(Instance *instance, Named_value *option) = 0; + +private: + Instance_options_list * + get_instance_options_list(const LEX_STRING *instance_name); + + int execute_impl(st_net *net, ulong connection_id); + +private: + HASH instance_options_map; + bool initialized; }; + /* Set an option for the instance. - Grammar: SET instance_name.option=option_value + Grammar: SET instance_name.option[=option_value][, ...] */ -class Set_option : public Command +class Set_option : public Abstract_option_cmd { public: - Set_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len); - /* - the following function is virtual to let Unset_option to use - */ - virtual int do_command(struct st_net *net); - int execute(struct st_net *net, ulong connection_id); + Set_option(Instance_map *instance_map_arg); + protected: - int correct_file(int skip); -public: - const char *instance_name; - uint instance_name_len; - /* buffer for the option */ - enum { MAX_OPTION_LEN= 1024 }; - char option[MAX_OPTION_LEN]; - char option_value[MAX_OPTION_LEN]; + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; /* - Remove option of the instance from config file - Grammar: UNSET instance_name.option + Remove option of the instance. + Grammar: UNSET instance_name.option[, ...] */ -class Unset_option: public Set_option +class Unset_option: public Abstract_option_cmd { public: - Unset_option(Instance_map *instance_map_arg, const char *name, uint len, - const char *option_arg, uint option_len, - const char *option_value_arg, uint option_value_len): - Set_option(instance_map_arg, name, len, option_arg, option_len, - option_value_arg, option_value_len) - {} - int do_command(struct st_net *net); + Unset_option(Instance_map *instance_map_arg); + +protected: + virtual bool parse_args(const char **text); + virtual int process_option(Instance *instance, Named_value *option); }; +/* + Syntax error command. + + This command is issued if parser reported a syntax error. We need it to + distinguish between syntax error and internal parser error. E.g. parsing + failed because we hadn't had enought memory. In the latter case the parser + just returns NULL. +*/ + +class Syntax_error : public Command +{ +public: + /* This is just to avoid compiler warning. */ + Syntax_error() :Command(NULL) + {} + +public: + int execute(st_net *net, ulong connection_id); +}; + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_COMMANDS_H */ diff --git a/server-tools/instance-manager/exit_codes.h b/server-tools/instance-manager/exit_codes.h new file mode 100644 index 00000000000..560ce30b7aa --- /dev/null +++ b/server-tools/instance-manager/exit_codes.h @@ -0,0 +1,41 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H + +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + This file contains a list of exit codes, which are used when Instance + Manager is working in user-management mode. +*/ + +const int ERR_OK = 0; + +const int ERR_OUT_OF_MEMORY = 1; +const int ERR_INVALID_USAGE = 2; +const int ERR_INTERNAL_ERROR = 3; +const int ERR_IO_ERROR = 4; +const int ERR_PASSWORD_FILE_CORRUPTED = 5; +const int ERR_PASSWORD_FILE_DOES_NOT_EXIST = 6; + +const int ERR_CAN_NOT_READ_USER_NAME = 10; +const int ERR_CAN_NOT_READ_PASSWORD = 11; +const int ERR_USER_ALREADY_EXISTS = 12; +const int ERR_USER_NOT_FOUND = 13; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_EXIT_CODES_H diff --git a/server-tools/instance-manager/guardian.cc b/server-tools/instance-manager/guardian.cc index 3be672cd71c..7a9ff3e8367 100644 --- a/server-tools/instance-manager/guardian.cc +++ b/server-tools/instance-manager/guardian.cc @@ -21,16 +21,14 @@ #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> +#include "instance.h" +#include "instance_map.h" +#include "log.h" +#include "mysql_manager_error.h" pthread_handler_t guardian(void *arg) @@ -40,6 +38,37 @@ pthread_handler_t guardian(void *arg) return 0; } + +const char * +Guardian_thread::get_instance_state_name(enum_instance_state state) +{ + switch (state) { + case NOT_STARTED: + return "offline"; + + case STARTING: + return "starting"; + + case STARTED: + return "online"; + + case JUST_CRASHED: + return "failed"; + + case CRASHED: + return "crashed"; + + case CRASHED_AND_ABANDONED: + return "abandoned"; + + case STOPPING: + return "stopping"; + } + + return NULL; /* just to ignore compiler warning. */ +} + + Guardian_thread::Guardian_thread(Thread_registry &thread_registry_arg, Instance_map *instance_map_arg, uint monitoring_interval_arg) : @@ -89,10 +118,17 @@ void Guardian_thread::process_instance(Instance *instance, if (current_node->state == STOPPING) { /* this brach is executed during shutdown */ - if (instance->options.shutdown_delay_val) + if (instance->options.shutdown_delay) + { + /* + NOTE: it is important to check shutdown_delay here, but use + shutdown_delay_val. The idea is that if the option is unset, + shutdown_delay will be NULL, but shutdown_delay_val will not be reset. + */ waitchild= instance->options.shutdown_delay_val; + } - /* this returns true if and only if an instance was stopped for sure */ + /* this returns TRUE if and only if an instance was stopped for sure */ if (instance->is_crashed()) *guarded_instances= list_delete(*guarded_instances, node); else if ( (uint) (current_time - current_node->last_checked) > waitchild) @@ -159,7 +195,11 @@ void Guardian_thread::process_instance(Instance *instance, instance->options.instance_name); } else + { + log_info("guardian: cannot start instance %s. Abandoning attempts " + "to (re)start it", instance->options.instance_name); current_node->state= CRASHED_AND_ABANDONED; + } } break; case CRASHED_AND_ABANDONED: @@ -242,7 +282,9 @@ int Guardian_thread::is_stopped() SYNOPSYS Guardian_thread::init() - NOTE: One should always lock guardian before calling this routine. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian_thread; + - Instance_map; RETURN 0 - ok @@ -261,12 +303,11 @@ int Guardian_thread::init() while ((instance= iterator.next())) { - if (!(instance->options.nonguarded)) - if (guard(instance, TRUE)) /* do not lock guardian */ - { - instance_map->unlock(); - return 1; - } + if (instance->options.nonguarded) + continue; + + if (guard(instance, TRUE)) /* do not lock guardian */ + return 1; } return 0; @@ -334,24 +375,14 @@ 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; - } + node= find_instance_node(instance); + + if (node != NULL) + guarded_instances= list_delete(guarded_instances, node); + pthread_mutex_unlock(&LOCK_guardian); + /* if there is nothing to delete it is also fine */ return 0; } @@ -420,7 +451,7 @@ int Guardian_thread::stop_instances(bool stop_instances_arg) void Guardian_thread::lock() { - pthread_mutex_lock(&LOCK_guardian); + pthread_mutex_lock(&LOCK_guardian); } @@ -428,3 +459,41 @@ void Guardian_thread::unlock() { pthread_mutex_unlock(&LOCK_guardian); } + + +LIST *Guardian_thread::find_instance_node(Instance *instance) +{ + LIST *node= guarded_instances; + + while (node != NULL) + { + /* + We compare only pointers, as we always use pointers from the + instance_map's MEM_ROOT. + */ + if (((GUARD_NODE *) node->data)->instance == instance) + return node; + + node= node->next; + } + + return NULL; +} + + +bool Guardian_thread::is_active(Instance *instance) +{ + bool guarded; + + lock(); + + guarded= find_instance_node(instance) != NULL; + + /* is_running() can take a long time, so let's unlock mutex first. */ + unlock(); + + if (guarded) + return true; + + return instance->is_running(); +} diff --git a/server-tools/instance-manager/guardian.h b/server-tools/instance-manager/guardian.h index 16b4c373c91..6d3a2b222d7 100644 --- a/server-tools/instance-manager/guardian.h +++ b/server-tools/instance-manager/guardian.h @@ -17,11 +17,11 @@ 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> +#include "thread_registry.h" + #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif @@ -79,6 +79,8 @@ public: time_t last_checked; }; + /* Return client state name. */ + static const char *get_instance_state_name(enum_instance_state state); Guardian_thread(Thread_registry &thread_registry_arg, Instance_map *instance_map_arg, @@ -94,11 +96,28 @@ public: int guard(Instance *instance, bool nolock= FALSE); /* Stop instance protection */ int stop_guard(Instance *instance); - /* Returns true if guardian thread is stopped */ + /* Returns TRUE if guardian thread is stopped */ int is_stopped(); void lock(); void unlock(); + /* + Return an internal list node for the given instance if the instance is + managed by Guardian. Otherwise, return NULL. + + MT-NOTE: must be called under acquired lock. + */ + LIST *find_instance_node(Instance *instance); + + /* The operation is used to check if the instance is active or not. */ + bool is_active(Instance *instance); + + /* + Return state of the given instance list node. The pointer must specify + a valid list node. + */ + inline enum_instance_state get_instance_state(LIST *instance_node); + public: pthread_cond_t COND_guardian; @@ -108,6 +127,7 @@ private: /* check instance state and act accordingly */ void process_instance(Instance *instance, GUARD_NODE *current_node, LIST **guarded_instances, LIST *elem); + int stopped; private: @@ -115,9 +135,15 @@ private: 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; }; + +inline Guardian_thread::enum_instance_state +Guardian_thread::get_instance_state(LIST *instance_node) +{ + return ((GUARD_NODE *) instance_node->data)->state; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_GUARDIAN_H */ diff --git a/server-tools/instance-manager/instance.cc b/server-tools/instance-manager/instance.cc index 39381b457ab..456052bf7e5 100644 --- a/server-tools/instance-manager/instance.cc +++ b/server-tools/instance-manager/instance.cc @@ -20,18 +20,27 @@ #include "instance.h" -#include "mysql_manager_error.h" -#include "log.h" -#include "instance_map.h" -#include "priv.h" -#include "portability.h" +#include <my_global.h> +#include <mysql.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif -#include <my_sys.h> -#include <signal.h> -#include <m_string.h> -#include <mysql.h> + +#include "guardian.h" +#include "instance_map.h" +#include "log.h" +#include "mysql_manager_error.h" +#include "portability.h" +#include "priv.h" + + +const LEX_STRING +Instance::DFLT_INSTANCE_NAME= { C_STRING_WITH_SIZE("mysqld") }; + +static const char * const INSTANCE_NAME_PREFIX= Instance::DFLT_INSTANCE_NAME.str; +static const int INSTANCE_NAME_PREFIX_LEN= Instance::DFLT_INSTANCE_NAME.length; static void start_and_monitor_instance(Instance_options *old_instance_options, @@ -152,7 +161,7 @@ static int start_process(Instance_options *instance_options, switch (*pi) { case 0: /* never happens on QNX */ - execv(instance_options->mysqld_path, instance_options->argv); + execv(instance_options->mysqld_path.str, instance_options->argv); /* exec never returns */ exit(1); case -1: @@ -180,7 +189,7 @@ static int start_process(Instance_options *instance_options, char *cmdline= new char[cmdlen]; if (cmdline == NULL) return 1; - + cmdline[0]= 0; for (int i= 0; instance_options->argv[i] != 0; i++) { @@ -232,9 +241,7 @@ static int start_process(Instance_options *instance_options, 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_name instance_name(&old_instance_options->instance_name); Instance *current_instance; My_process_info process_info; @@ -248,11 +255,8 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, Save the instance name in the case if Instance object we are using is destroyed. (E.g. by "FLUSH INSTANCES") */ - strmake(instance_name_buff, old_instance_options->instance_name, - MAX_INSTANCE_NAME_LEN - 1); - instance_name_len= old_instance_options->instance_name_len; - log_info("starting instance %s", instance_name_buff); + log_info("starting instance %s", (const char *) instance_name.get_c_str()); if (start_process(old_instance_options, &process_info)) { @@ -266,15 +270,36 @@ static void start_and_monitor_instance(Instance_options *old_instance_options, /* don't check for return value */ wait_process(&process_info); - current_instance= instance_map->find(instance_name_buff, instance_name_len); + instance_map->lock(); + + current_instance= instance_map->find(instance_name.get_str()); if (current_instance) current_instance->set_crash_flag_n_wake_all(); + instance_map->unlock(); + return; } +bool Instance::is_name_valid(const LEX_STRING *name) +{ + const char *name_suffix= name->str + INSTANCE_NAME_PREFIX_LEN; + + if (strncmp(name->str, INSTANCE_NAME_PREFIX, INSTANCE_NAME_PREFIX_LEN) != 0) + return FALSE; + + return *name_suffix == 0 || my_isdigit(default_charset_info, *name_suffix); +} + + +bool Instance::is_mysqld_compatible_name(const LEX_STRING *name) +{ + return strcmp(name->str, INSTANCE_NAME_PREFIX) == 0; +} + + Instance_map *Instance::get_map() { return instance_map; @@ -309,11 +334,11 @@ int Instance::start() { /* clear crash flag */ pthread_mutex_lock(&LOCK_instance); - crashed= 0; + crashed= FALSE; pthread_mutex_unlock(&LOCK_instance); - if (!is_running()) + if (configured && !is_running()) { remove_pid(); @@ -339,8 +364,8 @@ int Instance::start() return 0; } - /* the instance is started already */ - return ER_INSTANCE_ALREADY_STARTED; + /* The instance is started already or misconfigured. */ + return configured ? ER_INSTANCE_ALREADY_STARTED : ER_INSTANCE_MISCONFIGURED; } /* @@ -363,7 +388,7 @@ void Instance::set_crash_flag_n_wake_all() { /* set instance state to crashed */ pthread_mutex_lock(&LOCK_instance); - crashed= 1; + crashed= TRUE; pthread_mutex_unlock(&LOCK_instance); /* @@ -378,7 +403,7 @@ void Instance::set_crash_flag_n_wake_all() -Instance::Instance(): crashed(0) +Instance::Instance(): crashed(FALSE), configured(FALSE) { pthread_mutex_init(&LOCK_instance, 0); pthread_cond_init(&COND_instance_stopped, 0); @@ -392,9 +417,9 @@ Instance::~Instance() } -int Instance::is_crashed() +bool Instance::is_crashed() { - int val; + bool val; pthread_mutex_lock(&LOCK_instance); val= crashed; pthread_mutex_unlock(&LOCK_instance); @@ -413,10 +438,17 @@ bool Instance::is_running() bool return_val; if (options.mysqld_port) + { + /* + NOTE: it is important to check mysqld_port here, but use + mysqld_port_val. The idea is that if the option is unset, mysqld_port + will be NULL, but mysqld_port_val will not be reset. + */ port= options.mysqld_port_val; + } if (options.mysqld_socket) - socket= strchr(options.mysqld_socket, '=') + 1; + socket= options.mysqld_socket; /* no port was specified => instance falled back to default value */ if (!options.mysqld_port && !options.mysqld_socket) @@ -469,8 +501,15 @@ int Instance::stop() struct timespec timeout; uint waitchild= (uint) DEFAULT_SHUTDOWN_DELAY; - if (options.shutdown_delay_val) + if (options.shutdown_delay) + { + /* + NOTE: it is important to check shutdown_delay here, but use + shutdown_delay_val. The idea is that if the option is unset, + shutdown_delay will be NULL, but shutdown_delay_val will not be reset. + */ waitchild= options.shutdown_delay_val; + } kill_instance(SIGTERM); /* sleep on condition to wait for SIGCHLD */ @@ -588,20 +627,33 @@ void Instance::kill_instance(int signum) } /* - We execute this function to initialize instance parameters. - Return value: 0 - ok. 1 - unable to init DYNAMIC_ARRAY. + Initialize instance parameters. + + SYNOPSYS + Instance::init() + name_arg name of the instance + + RETURN: + 0 ok + !0 error */ -int Instance::init(const char *name_arg) +int Instance::init(const LEX_STRING *name_arg) { + mysqld_compatible= is_mysqld_compatible_name(name_arg); + return options.init(name_arg); } int Instance::complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, - uint instance_type) + const char *mysqld_path) { instance_map= instance_map_arg; - return options.complete_initialization(mysqld_path, instance_type); + configured= !options.complete_initialization(mysqld_path); + return 0; + /* + TODO: return actual status (from + Instance_options::complete_initialization()) here. + */ } diff --git a/server-tools/instance-manager/instance.h b/server-tools/instance-manager/instance.h index adb66991685..1f06cabebf7 100644 --- a/server-tools/instance-manager/instance.h +++ b/server-tools/instance-manager/instance.h @@ -17,7 +17,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> +#include <m_string.h> + #include "instance_options.h" +#include "priv.h" #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface @@ -25,31 +28,120 @@ class Instance_map; + +/* + Instance_name -- the class represents instance name -- a string of length + less than MAX_INSTANCE_NAME_SIZE. + + Generally, this is just a string with self-memory-management and should be + eliminated in the future. +*/ + +class Instance_name +{ +public: + Instance_name(const LEX_STRING *name); + +public: + inline const LEX_STRING *get_str() const + { + return &str; + } + + inline const char *get_c_str() const + { + return str.str; + } + + inline uint get_length() const + { + return str.length; + } + +private: + LEX_STRING str; + char str_buffer[MAX_INSTANCE_NAME_SIZE]; +}; + + class Instance { public: + /* + The following two constants defines name of the default mysqld-instance + ("mysqld"). + */ + static const LEX_STRING DFLT_INSTANCE_NAME; + +public: + /* + The operation is intended to check whether string is a well-formed + instance name or not. + */ + static bool is_name_valid(const LEX_STRING *name); + + /* + The operation is intended to check if the given instance name is + mysqld-compatible or not. + */ + static bool is_mysqld_compatible_name(const LEX_STRING *name); + +public: Instance(); ~Instance(); - int init(const char *name); + int init(const LEX_STRING *name_arg); int complete_initialization(Instance_map *instance_map_arg, - const char *mysqld_path, uint instance_type); + const char *mysqld_path); bool is_running(); int start(); int stop(); /* send a signal to the instance */ void kill_instance(int signo); - int is_crashed(); + bool is_crashed(); void set_crash_flag_n_wake_all(); Instance_map *get_map(); + /* + The operation is intended to check if the instance is mysqld-compatible + or not. + */ + inline bool is_mysqld_compatible() const; + + /* + The operation is intended to check if the instance is configured properly + or not. Misconfigured instances are not managed. + */ + inline bool is_configured() const; + + inline const LEX_STRING *get_name() const; + public: enum { DEFAULT_SHUTDOWN_DELAY= 35 }; Instance_options options; private: - int crashed; + /* This attributes is a flag, specifies if the instance has been crashed. */ + bool crashed; + + /* + This attribute specifies if the instance is configured properly or not. + Misconfigured instances are not managed. + */ + bool configured; + + /* + This attribute specifies whether the instance is mysqld-compatible or not. + Mysqld-compatible instances can contain only mysqld-specific options. + At the moment an instance is mysqld-compatible if its name is "mysqld". + + The idea is that [mysqld] section should contain only mysqld-specific + options (no Instance Manager-specific options) to be readable by mysqld + program. + */ + bool mysqld_compatible; + /* Mutex protecting the instance. Currently we use it to avoid the double start of the instance. This happens when the instance is starting @@ -66,4 +158,22 @@ private: void remove_pid(); }; + +inline bool Instance::is_mysqld_compatible() const +{ + return mysqld_compatible; +} + + +inline bool Instance::is_configured() const +{ + return configured; +} + + +inline const LEX_STRING *Instance::get_name() const +{ + return &options.instance_name; +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_H */ diff --git a/server-tools/instance-manager/instance_map.cc b/server-tools/instance-manager/instance_map.cc index 543c9c31bfa..abe8f6e7dab 100644 --- a/server-tools/instance-manager/instance_map.cc +++ b/server-tools/instance-manager/instance_map.cc @@ -20,14 +20,20 @@ #include "instance_map.h" +#include <my_global.h> +#include <m_ctype.h> +#include <mysql_com.h> +#include <m_string.h> + #include "buffer.h" +#include "guardian.h" #include "instance.h" #include "log.h" +#include "manager.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" #include "options.h" - -#include <m_ctype.h> -#include <mysql_com.h> -#include <m_string.h> +#include "priv.h" /* Note: As we are going to suppost different types of connections, @@ -45,8 +51,8 @@ static byte* get_instance_key(const byte* u, uint* len, my_bool __attribute__((unused)) t) { const Instance *instance= (const Instance *) u; - *len= instance->options.instance_name_len; - return (byte *) instance->options.instance_name; + *len= instance->options.instance_name.length; + return (byte *) instance->options.instance_name.str; } static void delete_instance(void *u) @@ -79,16 +85,60 @@ static void delete_instance(void *u) static int process_option(void *ctx, const char *group, const char *option) { - Instance_map *map= NULL; + Instance_map *map= (Instance_map*) ctx; + LEX_STRING group_str; + + group_str.str= (char *) group; + group_str.length= strlen(group); - map = (Instance_map*) ctx; - return map->process_one_option(group, option); + return map->process_one_option(&group_str, option); } C_MODE_END /* + Parse option string. + + SYNOPSIS + parse_option() + option_str [IN] option string (e.g. "--name=value") + option_name_buf [OUT] parsed name of the option. + Must be of (MAX_OPTION_LEN + 1) size. + option_value_buf [OUT] parsed value of the option. + Must be of (MAX_OPTION_LEN + 1) size. + + DESCRIPTION + This is an auxiliary function and should not be used externally. It is + intended to parse whole option string into option name and option value. +*/ + +static void parse_option(const char *option_str, + char *option_name_buf, + char *option_value_buf) +{ + char *eq_pos; + const char *ptr= option_str; + + while (*ptr == '-') + ++ptr; + + strmake(option_name_buf, ptr, MAX_OPTION_LEN + 1); + + eq_pos= strchr(ptr, '='); + if (eq_pos) + { + option_name_buf[eq_pos - ptr]= 0; + strmake(option_value_buf, eq_pos + 1, MAX_OPTION_LEN + 1); + } + else + { + option_value_buf[0]= 0; + } +} + + +/* Process one option from the configuration file. SYNOPSIS @@ -103,34 +153,64 @@ C_MODE_END of the instance map object. */ -int Instance_map::process_one_option(const char *group, const char *option) +int Instance_map::process_one_option(const LEX_STRING *group, + const char *option) { Instance *instance= NULL; - static const char prefix[]= { 'm', 'y', 's', 'q', 'l', 'd' }; - if (strncmp(group, prefix, sizeof prefix) == 0 && - ((my_isdigit(default_charset_info, group[sizeof prefix])) - || group[sizeof(prefix)] == '\0')) + if (!Instance::is_name_valid(group)) + { + /* + Current section name is not a valid instance name. + We should skip it w/o error. + */ + return 0; + } + + if (!(instance= (Instance *) hash_search(&hash, (byte *) group->str, + group->length))) + { + if (!(instance= new Instance())) + return 1; + + if (instance->init(group) || add_instance(instance)) { - if (!(instance= (Instance *) hash_search(&hash, (byte *) group, - strlen(group)))) - { - if (!(instance= new Instance)) - goto err; - if (instance->init(group) || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - } - - if (instance->options.add_option(option)) - goto err; /* the instance'll be deleted when we destroy the map */ + delete instance; + return 1; } - return 0; + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) group->str); -err_instance: - delete instance; -err: - return 1; + log_info("mysqld instance '%s' has been added successfully.", + (const char *) group->str); + } + + if (option) + { + char option_name[MAX_OPTION_LEN + 1]; + char option_value[MAX_OPTION_LEN + 1]; + + parse_option(option, option_name, option_value); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option_name)) + { + log_info("Warning: configuration of mysqld-compatible instance '%s' " + "contains IM-specific option '%s'. " + "This breaks backward compatibility for the configuration file.", + (const char *) group->str, + (const char *) option_name); + } + + Named_value option(option_name, option_value); + + if (instance->options.set_option(&option)) + return 1; /* the instance'll be deleted when we destroy the map */ + } + + return 0; } @@ -181,7 +261,7 @@ void Instance_map::unlock() - pass on the new map to the guardian thread: it will start all instances that are marked `guarded' and not yet started. Note, as the check whether an instance is started is currently - very simple (returns true if there is a MySQL server running + very simple (returns TRUE if there is a MySQL server running at the given port), this function has some peculiar side-effects: * if the port number of a running instance was changed, the @@ -194,9 +274,9 @@ void Instance_map::unlock() In order to avoid such side effects one should never call FLUSH INSTANCES without prior stop of all running instances. - TODO - FLUSH INSTANCES should return an error if it's called - while there is a running instance. + NOTE: The operation should be invoked with the following locks acquired: + - Guardian_thread; + - Instance_map; */ int Instance_map::flush_instances() @@ -209,67 +289,169 @@ int Instance_map::flush_instances() guardian (2) reload the instance map (3) reinitialize the guardian with new instances. */ - guardian->lock(); - pthread_mutex_lock(&LOCK_instance_map); hash_free(&hash); hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_instance_key, delete_instance, 0); + rc= load(); guardian->init(); - pthread_mutex_unlock(&LOCK_instance_map); - guardian->unlock(); return rc; } -Instance * -Instance_map::find(const char *name, uint name_len) +bool Instance_map::is_there_active_instance() { Instance *instance; - pthread_mutex_lock(&LOCK_instance_map); - instance= (Instance *) hash_search(&hash, (byte *) name, name_len); - pthread_mutex_unlock(&LOCK_instance_map); - return instance; + Iterator iterator(this); + + while ((instance= iterator.next())) + { + if (guardian->find_instance_node(instance) != NULL || + instance->is_running()) + { + return TRUE; + } + } + + return FALSE; } -int Instance_map::complete_initialization() +int Instance_map::add_instance(Instance *instance) { - Instance *instance; - uint i= 0; + return my_hash_insert(&hash, (byte *) instance); +} - if (hash.records == 0) /* no instances found */ - { - if ((instance= new Instance) == 0) - goto err; +int Instance_map::remove_instance(Instance *instance) +{ + return hash_delete(&hash, (byte *) instance); +} - if (instance->init("mysqld") || my_hash_insert(&hash, (byte *) instance)) - goto err_instance; - /* - After an instance have been added to the instance_map, - hash_free should handle it's deletion => goto err, not - err_instance. - */ - if (instance->complete_initialization(this, mysqld_path, - DEFAULT_SINGLE_INSTANCE)) - goto err; +int Instance_map::create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + Instance *instance= new Instance(); + + if (!instance) + { + log_error("Error: can not initialize (name: '%s').", + (const char *) instance_name->str); + return ER_OUT_OF_RESOURCES; } - else - while (i < hash.records) + + if (instance->init(instance_name)) + { + log_error("Error: can not initialize (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + Named_value option= options->get_element(i); + + if (instance->is_mysqld_compatible() && + Instance_options::is_option_im_specific(option.get_name())) { - instance= (Instance *) hash_element(&hash, i); - if (instance->complete_initialization(this, mysqld_path, USUAL_INSTANCE)) - goto err; - i++; + log_error("Error: IM-option (%s) can not be used " + "in configuration of mysqld-compatible instance (%s).", + (const char *) option.get_name(), + (const char *) instance_name->str); + delete instance; + return ER_INCOMPATIBLE_OPTION; } + instance->options.set_option(&option); + } + + if (instance->is_mysqld_compatible()) + log_info("Warning: instance name '%s' is mysqld-compatible.", + (const char *) instance_name->str); + + if (instance->complete_initialization(this, mysqld_path)) + { + log_error("Error: can not complete initialization of instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + /* TODO: return more appropriate error code in this case. */ + } + + if (add_instance(instance)) + { + log_error("Error: can not register instance (name: '%s').", + (const char *) instance_name->str); + delete instance; + return ER_OUT_OF_RESOURCES; + } + return 0; -err_instance: - delete instance; -err: - return 1; +} + + +Instance * Instance_map::find(const LEX_STRING *name) +{ + return (Instance *) hash_search(&hash, (byte *) name->str, name->length); +} + + +bool Instance_map::complete_initialization() +{ + bool mysqld_found; + + /* Complete initialization of all registered instances. */ + + for (uint i= 0; i < hash.records; ++i) + { + Instance *instance= (Instance *) hash_element(&hash, i); + + if (instance->complete_initialization(this, mysqld_path)) + return TRUE; + } + + /* That's all if we are runnning in an ordinary mode. */ + + if (!Options::Main::mysqld_safe_compatible) + return FALSE; + + /* In mysqld-compatible mode we must ensure that there 'mysqld' instance. */ + + mysqld_found= find(&Instance::DFLT_INSTANCE_NAME) != NULL; + + if (mysqld_found) + return FALSE; + + if (create_instance(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + log_error("Error: could not create default instance."); + return TRUE; + } + + switch (create_instance_in_file(&Instance::DFLT_INSTANCE_NAME, NULL)) + { + case 0: + case ER_CONF_FILE_DOES_NOT_EXIST: + /* + Continue if the instance has been added to the config file + successfully, or the config file just does not exist. + */ + break; + + default: + log_error("Error: could not add default instance to the config file."); + + Instance *instance= find(&Instance::DFLT_INSTANCE_NAME); + + if (instance) + remove_instance(instance); /* instance is deleted here. */ + + return TRUE; + } + + return FALSE; } @@ -297,10 +479,10 @@ int Instance_map::load() name and start looking for files named "my.cnf.cnf" in all default dirs. Which is not what we want. */ - if (Options::is_forced_default_file) + if (Options::Main::is_forced_default_file) { snprintf(defaults_file_arg, FN_REFLEN, "--defaults-file=%s", - Options::config_file); + Options::Main::config_file); argv_options[1]= defaults_file_arg; argv_options[2]= '\0'; @@ -314,15 +496,12 @@ int Instance_map::load() If the routine failed, we'll simply fallback to defaults in complete_initialization(). */ - if (my_search_option_files(Options::config_file, &argc, + if (my_search_option_files(Options::Main::config_file, &argc, (char ***) &argv, &args_used, process_option, (void*) this)) log_info("Falling back to compiled-in defaults"); - if (complete_initialization()) - return 1; - - return 0; + return complete_initialization(); } @@ -343,3 +522,105 @@ Instance *Instance_map::Iterator::next() return NULL; } + +const char *Instance_map::get_instance_state_name(Instance *instance) +{ + LIST *instance_node; + + if (!instance->is_configured()) + return "misconfigured"; + + if ((instance_node= guardian->find_instance_node(instance)) != NULL) + { + /* The instance is managed by Guardian: we can report precise state. */ + + return Guardian_thread::get_instance_state_name( + guardian->get_instance_state(instance_node)); + } + + /* The instance is not managed by Guardian: we can report status only. */ + + return instance->is_running() ? "online" : "offline"; +} + + +/* + Create a new configuration section for mysqld-instance in the config file. + + SYNOPSYS + create_instance_in_file() + instance_name mysqld-instance name + options options for the new mysqld-instance + + RETURN + 0 On success + ER_CONF_FILE_DOES_NOT_EXIST If config file does not exist + ER_ACCESS_OPTION_FILE If config file is not writable or some I/O + error ocurred during writing configuration +*/ + +int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options) +{ + File cnf_file; + + if (my_access(Options::Main::config_file, W_OK)) + { + log_error("Error: configuration file (%s) does not exist.", + (const char *) Options::Main::config_file); + return ER_CONF_FILE_DOES_NOT_EXIST; + } + + cnf_file= my_open(Options::Main::config_file, O_WRONLY | O_APPEND, MYF(0)); + + if (cnf_file <= 0) + { + log_error("Error: can not open configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + return ER_ACCESS_OPTION_FILE; + } + + if (my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"[", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)instance_name->str, instance_name->length, + MYF(MY_NABP)) || + my_write(cnf_file, (byte*)"]", 1, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Error: can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + + for (int i= 0; options && i < options->get_size(); ++i) + { + char option_str[MAX_OPTION_STR_LEN]; + char *ptr; + int option_str_len; + Named_value option= options->get_element(i); + + ptr= strxnmov(option_str, MAX_OPTION_LEN + 1, option.get_name(), NullS); + + if (option.get_value()[0]) + ptr= strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + option_str_len= ptr - option_str; + + if (my_write(cnf_file, (byte*)option_str, option_str_len, MYF(MY_NABP)) || + my_write(cnf_file, (byte*)NEWLINE, NEWLINE_LEN, MYF(MY_NABP))) + { + log_error("Error: can not write to configuration file (%s): %s.", + (const char *) Options::Main::config_file, + (const char *) strerror(errno)); + my_close(cnf_file, MYF(0)); + return ER_ACCESS_OPTION_FILE; + } + } + + my_close(cnf_file, MYF(0)); + + return 0; +} diff --git a/server-tools/instance-manager/instance_map.h b/server-tools/instance-manager/instance_map.h index d3de42f4d80..04e617aebe4 100644 --- a/server-tools/instance-manager/instance_map.h +++ b/server-tools/instance-manager/instance_map.h @@ -17,10 +17,6 @@ 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> @@ -28,10 +24,18 @@ #pragma interface #endif +struct LEX_STRING; + +class Guardian_thread; class Instance; +class Named_value_arr; + extern int load_all_groups(char ***groups, const char *filename); extern void free_groups(char **groups); +extern int create_instance_in_file(const LEX_STRING *instance_name, + const Named_value_arr *options); + /* Instance_map - stores all existing instances @@ -56,22 +60,64 @@ public: }; friend class Iterator; public: - /* returns a pointer to the instance or NULL, if there is no such instance */ - Instance *find(const char *name, uint name_len); + /* + Return a pointer to the instance or NULL, if there is no such instance. + MT-NOTE: must be called under acquired lock. + */ + Instance *find(const LEX_STRING *name); + /* Clear the configuration cache and reload the configuration file. */ int flush_instances(); + + /* The operation is used to check if there is an active instance or not. */ + bool is_there_active_instance(); + void lock(); void unlock(); + int init(); + /* Process a given option and assign it to appropricate instance. This is required for the option handler, passed to my_search_option_files(). */ - int process_one_option(const char *group, const char *option); + int process_one_option(const LEX_STRING *group, const char *option); + + /* + Add an instance into the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int add_instance(Instance *instance); + + /* + Remove instance from the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int remove_instance(Instance *instance); + + /* + Create a new instance and register it in the internal hash. + + MT-NOTE: the operation must be called under acquired lock. + */ + int create_instance(const LEX_STRING *instance_name, + const Named_value_arr *options); Instance_map(const char *default_mysqld_path_arg); ~Instance_map(); + /* + Retrieve client state name of the given instance. + + MT-NOTE: the options must be called under acquired locks of the following + objects: + - Instance_map; + - Guardian_thread; + */ + const char *get_instance_state_name(Instance *instance); + public: const char *mysqld_path; Guardian_thread *guardian; @@ -80,7 +126,7 @@ private: /* loads options from config files */ int load(); /* inits instances argv's after all options have been loaded */ - int complete_initialization(); + bool complete_initialization(); private: enum { START_HASH_SIZE = 16 }; pthread_mutex_t LOCK_instance_map; diff --git a/server-tools/instance-manager/instance_options.cc b/server-tools/instance-manager/instance_options.cc index 06a6e1ebc63..394efaf0568 100644 --- a/server-tools/instance-manager/instance_options.cc +++ b/server-tools/instance-manager/instance_options.cc @@ -20,27 +20,24 @@ #include "instance_options.h" -#include "parse_output.h" -#include "buffer.h" - +#include <my_global.h> #include <my_sys.h> -#include <signal.h> #include <m_string.h> -#ifdef __WIN__ -#define NEWLINE_LEN 2 -#else -#define NEWLINE_LEN 1 -#endif +#include <signal.h> + +#include "buffer.h" +#include "instance.h" +#include "log.h" +#include "parse_output.h" +#include "priv.h" /* Create "mysqld ..." command in the buffer */ static inline int create_mysqld_command(Buffer *buf, - const char *mysqld_path_str, - uint mysqld_path_len, - const char *option, - uint option_len) + const LEX_STRING *mysqld_path, + const LEX_STRING *option) { int position= 0; @@ -49,13 +46,13 @@ static inline int create_mysqld_command(Buffer *buf, #ifdef __WIN__ buf->append(position++, "\"", 1); #endif - buf->append(position, mysqld_path_str, mysqld_path_len); - position+= mysqld_path_len; + buf->append(position, mysqld_path->str, mysqld_path->length); + position+= mysqld_path->length; #ifdef __WIN__ buf->append(position++, "\"", 1); #endif /* here the '\0' character is copied from the option string */ - buf->append(position, option, option_len); + buf->append(position, option->str, option->length + 1); return buf->is_error(); } @@ -63,6 +60,39 @@ static inline int create_mysqld_command(Buffer *buf, } +bool Instance_options::is_option_im_specific(const char *option_name) +{ + static const char *IM_SPECIFIC_OPTIONS[] = + { + "nonguarded", + "mysqld-path", + "shutdown-delay", + NULL + }; + + for (int i= 0; IM_SPECIFIC_OPTIONS[i]; ++i) + { + if (!strcmp(option_name, IM_SPECIFIC_OPTIONS[i])) + return TRUE; + } + + return FALSE; +} + + +Instance_options::Instance_options() + :mysqld_version(NULL), mysqld_socket(NULL), mysqld_datadir(NULL), + mysqld_pid_file(NULL), mysqld_port(NULL), mysqld_port_val(0), + nonguarded(NULL), shutdown_delay(NULL), shutdown_delay_val(0), + filled_default_options(0) +{ + mysqld_path.str= NULL; + mysqld_path.length= 0; + + memset(logs, 0, sizeof(logs)); +} + + /* Get compiled-in value of default_option @@ -86,13 +116,13 @@ int Instance_options::get_default_option(char *result, size_t result_len, const char *option_name) { int rc= 1; - char verbose_option[]= " --no-defaults --verbose --help"; + LEX_STRING verbose_option= + { C_STRING_WITH_SIZE(" --no-defaults --verbose --help") }; - /* reserve space fot the path + option + final '\0' */ - Buffer cmd(mysqld_path_len + sizeof(verbose_option)); + /* reserve space for the path + option + final '\0' */ + Buffer cmd(mysqld_path.length + verbose_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - verbose_option, sizeof(verbose_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &verbose_option)) goto err; /* +2 eats first "--" from the option string (E.g. "--datadir") */ @@ -120,21 +150,19 @@ err: int Instance_options::fill_instance_version() { - enum { MAX_VERSION_STRING_LENGTH= 160 }; - char result[MAX_VERSION_STRING_LENGTH]; - char version_option[]= " --no-defaults --version"; + char result[MAX_VERSION_LENGTH]; + LEX_STRING version_option= + { C_STRING_WITH_SIZE(" --no-defaults --version") }; int rc= 1; - Buffer cmd(mysqld_path_len + sizeof(version_option)); + Buffer cmd(mysqld_path.length + version_option.length + 1); - if (create_mysqld_command(&cmd, mysqld_path, mysqld_path_len, - version_option, sizeof(version_option))) + if (create_mysqld_command(&cmd, &mysqld_path, &version_option)) goto err; - bzero(result, MAX_VERSION_STRING_LENGTH); + bzero(result, MAX_VERSION_LENGTH); - rc= parse_output_and_get_value(cmd.buffer, "Ver", - result, MAX_VERSION_STRING_LENGTH, - GET_LINE); + rc= parse_output_and_get_value(cmd.buffer, "Ver", result, + MAX_VERSION_LENGTH, GET_LINE); if (*result != '\0') { @@ -145,6 +173,7 @@ int Instance_options::fill_instance_version() start= result; while (my_isspace(default_charset_info, *start)) ++start; + mysqld_version= strdup_root(&alloc, start); } err: @@ -200,8 +229,7 @@ int Instance_options::fill_log_options() else { /* below is safe, as --datadir always has a value */ - strmake(datadir, - strchr(mysqld_datadir, '=') + 1, MAX_LOG_OPTION_LENGTH - 1); + strmake(datadir, mysqld_datadir, MAX_LOG_OPTION_LENGTH - 1); } if (gethostname(hostname,sizeof(hostname)-1) < 0) @@ -287,7 +315,6 @@ err: int Instance_options::get_pid_filename(char *result) { - const char *pid_file= mysqld_pid_file; char datadir[MAX_PATH_LEN]; if (mysqld_datadir == NULL) @@ -297,14 +324,10 @@ int Instance_options::get_pid_filename(char *result) return 1; } else - strxnmov(datadir, MAX_PATH_LEN - 1, strchr(mysqld_datadir, '=') + 1, - "/", NullS); - - DBUG_ASSERT(mysqld_pid_file); - pid_file= strchr(pid_file, '=') + 1; + strxnmov(datadir, MAX_PATH_LEN - 1, mysqld_datadir, "/", NullS); /* get the full path to the pidfile */ - my_load_path(result, pid_file, datadir); + my_load_path(result, mysqld_pid_file, datadir); return 0; } @@ -333,23 +356,22 @@ pid_t Instance_options::get_pid() } -int Instance_options::complete_initialization(const char *default_path, - uint instance_type) +int Instance_options::complete_initialization(const char *default_path) { const char *tmp; char *end; - if (!mysqld_path && !(mysqld_path= strdup_root(&alloc, default_path))) + if (!mysqld_path.str && !(mysqld_path.str= strdup_root(&alloc, default_path))) goto err; // 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= convert_dirname((char*)mysqld_path.str, mysqld_path.str, NullS); end[-1]= 0; - mysqld_path_len= strlen(mysqld_path); + mysqld_path.length= strlen(mysqld_path.str); if (mysqld_port) - mysqld_port_val= atoi(strchr(mysqld_port, '=') + 1); + mysqld_port_val= atoi(mysqld_port); if (shutdown_delay) shutdown_delay_val= atoi(shutdown_delay); @@ -357,7 +379,7 @@ int Instance_options::complete_initialization(const char *default_path, if (!(tmp= strdup_root(&alloc, "--no-defaults"))) goto err; - if (!(mysqld_pid_file)) + if (!mysqld_pid_file) { char pidfilename[MAX_PATH_LEN]; char hostname[MAX_PATH_LEN]; @@ -366,26 +388,27 @@ int Instance_options::complete_initialization(const char *default_path, If we created only one istance [mysqld], because no config. files were found, we would like to model mysqld pid file values. */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", hostname, - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, hostname, ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - "-", hostname, ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, "-", + hostname, ".pid", NullS); } else { - if (instance_type & DEFAULT_SINGLE_INSTANCE) - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", "mysql", - ".pid", NullS); + if (Instance::is_mysqld_compatible_name(&instance_name)) + strxnmov(pidfilename, MAX_PATH_LEN - 1, "mysql", ".pid", NullS); else - strxnmov(pidfilename, MAX_PATH_LEN - 1, "--pid-file=", instance_name, - ".pid", NullS); + strxnmov(pidfilename, MAX_PATH_LEN - 1, instance_name.str, ".pid", + NullS); } - add_option(pidfilename); + Named_value option((char *) "pid-file", pidfilename); + + set_option(&option); } if (get_pid_filename(pid_file_with_path)) @@ -393,20 +416,37 @@ int Instance_options::complete_initialization(const char *default_path, /* we need to reserve space for the final zero + possible default options */ if (!(argv= (char**) - alloc_root(&alloc, (options_array.elements + 1 + alloc_root(&alloc, (get_num_options() + 1 + MAX_NUMBER_OF_DEFAULT_OPTIONS) * sizeof(char*)))) goto err; + filled_default_options= 0; /* the path must be first in the argv */ - if (add_to_argv(mysqld_path)) + if (add_to_argv(mysqld_path.str)) 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; + int arg_idx= filled_default_options; + for (int opt_idx= 0; opt_idx < get_num_options(); ++opt_idx) + { + char option_str[MAX_OPTION_STR_LEN]; + Named_value option= get_option(opt_idx); + + if (is_option_im_specific(option.get_name())) + continue; + + char *ptr= strxnmov(option_str, MAX_OPTION_LEN + 3, "--", option.get_name(), + NullS); + + if (option.get_value()[0]) + strxnmov(ptr, MAX_OPTION_LEN + 2, "=", option.get_value(), NullS); + + argv[arg_idx++]= strdup_root(&alloc, option_str); + } + + argv[arg_idx]= 0; if (fill_log_options() || fill_instance_version()) goto err; @@ -418,75 +458,91 @@ err: } -/* - Assigns given value to the appropriate option from the class. +bool Instance_options::set_option(Named_value *option) +{ + bool err_status; + int idx= find_option(option->get_name()); + char *option_name_str; + char *option_value_str; - SYNOPSYS - add_option() - option string with the option prefixed by -- + if (!(option_name_str= Named_value::alloc_str(option->get_name()))) + return TRUE; - DESCRIPTION + if (!(option_value_str= Named_value::alloc_str(option->get_value()))) + { + Named_value::free_str(&option_name_str); + return TRUE; + } - The method is called from the option handling routine. + Named_value option_copy(option_name_str, option_value_str); - RETURN - 0 - ok - 1 - error occured -*/ + if (idx < 0) + err_status= options.add_element(&option_copy); + else + err_status= options.replace_element(idx, &option_copy); -int Instance_options::add_option(const char* option) + if (!err_status) + update_var(option_copy.get_name(), option_copy.get_value()); + else + option_copy.free(); + + return err_status; +} + + +void Instance_options::unset_option(const char *option_name) { - char *tmp; - enum { SAVE_VALUE= 1, SAVE_WHOLE, SAVE_WHOLE_AND_ADD }; - struct selected_options_st + int idx= find_option(option_name); + + if (idx < 0) + return; /* the option has not been set. */ + + options.remove_element(idx); + + update_var(option_name, NULL); +} + + +void Instance_options::update_var(const char *option_name, + const char *option_value) +{ + struct options_st { const char *name; - uint length; - const char **value; - uint type; - } options[]= + uint name_len; + const char **var; + } options_def[]= { - {"--socket=", 9, &mysqld_socket, SAVE_WHOLE_AND_ADD}, - {"--port=", 7, &mysqld_port, SAVE_WHOLE_AND_ADD}, - {"--datadir=", 10, &mysqld_datadir, SAVE_WHOLE_AND_ADD}, - {"--bind-address=", 15, &mysqld_bind_address, SAVE_WHOLE_AND_ADD}, - {"--pid-file=", 11, &mysqld_pid_file, SAVE_WHOLE_AND_ADD}, - {"--mysqld-path=", 14, &mysqld_path, SAVE_VALUE}, - {"--nonguarded", 9, &nonguarded, SAVE_WHOLE}, - {"--shutdown_delay", 9, &shutdown_delay, SAVE_VALUE}, - {NULL, 0, NULL, 0} + {"socket", 6, &mysqld_socket}, + {"port", 4, &mysqld_port}, + {"datadir", 7, &mysqld_datadir}, + {"pid-file", 8, &mysqld_pid_file}, + {"nonguarded", 10, &nonguarded}, + {"mysqld-path", 11, (const char **) &mysqld_path.str}, + {"shutdown-delay", 14, &shutdown_delay}, + {NULL, 0, NULL} }; - struct selected_options_st *selected_options; - if (!(tmp= strdup_root(&alloc, option))) - goto err; + for (options_st *opt= options_def; opt->name; ++opt) + { + if (!strncmp(opt->name, option_name, opt->name_len)) + { + *(opt->var)= option_value; + break; + } + } +} - for (selected_options= options; selected_options->name; selected_options++) - { - if (strncmp(tmp, selected_options->name, selected_options->length) == 0) - switch (selected_options->type) { - case SAVE_WHOLE_AND_ADD: - *(selected_options->value)= tmp; - insert_dynamic(&options_array,(gptr) &tmp); - return 0; - case SAVE_VALUE: - *(selected_options->value)= strchr(tmp, '=') + 1; - return 0; - case SAVE_WHOLE: - *(selected_options->value)= tmp; - return 0; - default: - break; - } - } - - /* if we haven't returned earlier we should just save the option */ - insert_dynamic(&options_array,(gptr) &tmp); - return 0; +int Instance_options::find_option(const char *option_name) +{ + for (int i= 0; i < get_num_options(); i++) + { + if (!strcmp(get_option(i).get_name(), option_name)) + return i; + } -err: - return 1; + return -1; } @@ -504,7 +560,10 @@ int Instance_options::add_to_argv(const char* option) void Instance_options::print_argv() { int i; - printf("printing out an instance %s argv:\n", instance_name); + + printf("printing out an instance %s argv:\n", + (const char *) instance_name.str); + for (i=0; argv[i] != NULL; i++) printf("argv: %s\n", argv[i]); } @@ -515,17 +574,17 @@ void Instance_options::print_argv() Return value: 0 - ok. 1 - unable to allocate memory. */ -int Instance_options::init(const char *instance_name_arg) +int Instance_options::init(const LEX_STRING *instance_name_arg) { - instance_name_len= strlen(instance_name_arg); + instance_name.length= instance_name_arg->length; init_alloc_root(&alloc, MEM_ROOT_BLOCK_SIZE, 0); - if (my_init_dynamic_array(&options_array, sizeof(char*), 0, 32)) + if (options.init()) goto err; - if (!(instance_name= strmake_root(&alloc, (char*) instance_name_arg, - instance_name_len))) + if (!(instance_name.str= strmake_root(&alloc, instance_name_arg->str, + instance_name_arg->length))) goto err; return 0; @@ -538,6 +597,4 @@ err: 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 index dae1c2695d1..16e548f7324 100644 --- a/server-tools/instance-manager/instance_options.h +++ b/server-tools/instance-manager/instance_options.h @@ -18,8 +18,8 @@ #include <my_global.h> #include <my_sys.h> + #include "parse.h" -#include "portability.h" #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface @@ -35,24 +35,26 @@ 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), nonguarded(0), shutdown_delay(0), - shutdown_delay_val(0), filled_default_options(0) - {} + /* The operation is used to check if the option is IM-specific or not. */ + static bool is_option_im_specific(const char *option_name); + +public: + Instance_options(); ~Instance_options(); /* fills in argv */ - int complete_initialization(const char *default_path, uint instance_type); + int complete_initialization(const char *default_path); - int add_option(const char* option); - int init(const char *instance_name_arg); + bool set_option(Named_value *option); + void unset_option(const char *option_name); + + inline int get_num_options() const; + inline Named_value get_option(int idx) const; + +public: + int init(const LEX_STRING *instance_name_arg); pid_t get_pid(); int get_pid_filename(char *result); int unlink_pidfile(); @@ -65,7 +67,6 @@ public: */ enum { MAX_PATH_LEN= 512 }; enum { MAX_NUMBER_OF_DEFAULT_OPTIONS= 2 }; - enum { MEM_ROOT_BLOCK_SIZE= 512 }; char pid_file_with_path[MAX_PATH_LEN]; char **argv; /* @@ -76,31 +77,44 @@ public: /* We need the some options, so we store them as a separate pointers */ const char *mysqld_socket; const char *mysqld_datadir; - const char *mysqld_bind_address; const char *mysqld_pid_file; const char *mysqld_port; uint mysqld_port_val; - const char *instance_name; - uint instance_name_len; - const char *mysqld_path; - uint mysqld_path_len; + LEX_STRING instance_name; + LEX_STRING mysqld_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 add_to_argv(const char *option); int get_default_option(char *result, size_t result_len, const char *option_name); + + void update_var(const char *option_name, const char *option_value); + int find_option(const char *option_name); + private: uint filled_default_options; MEM_ROOT alloc; + + Named_value_arr options; }; + +inline int Instance_options::get_num_options() const +{ + return options.get_size(); +} + + +inline Named_value Instance_options::get_option(int idx) const +{ + return options.get_element(idx); +} + #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_INSTANCE_OPTIONS_H */ diff --git a/server-tools/instance-manager/listener.cc b/server-tools/instance-manager/listener.cc index 500b25bec03..da9338e28c5 100644 --- a/server-tools/instance-manager/listener.cc +++ b/server-tools/instance-manager/listener.cc @@ -19,21 +19,23 @@ #endif #include "listener.h" -#include "priv.h" -#include <m_string.h> + +#include <my_global.h> #include <mysql.h> #include <violite.h> + +#include <sys/stat.h> #ifndef __WIN__ #include <sys/un.h> #endif -#include <sys/stat.h> -#include "thread_registry.h" -#include "options.h" #include "instance_map.h" #include "log.h" #include "mysql_connection.h" +#include "options.h" #include "portability.h" +#include "priv.h" +#include "thread_registry.h" /* @@ -62,8 +64,7 @@ private: Listener_thread::Listener_thread(const Listener_thread_args &args) : - Listener_thread_args(args.thread_registry, args.options, args.user_map, - args.instance_map) + Listener_thread_args(args.thread_registry, args.user_map, args.instance_map) ,total_connection_count(0) ,thread_info(pthread_self()) ,num_sockets(0) @@ -234,14 +235,16 @@ int Listener_thread::create_tcp_socket() bzero(&ip_socket_address, sizeof(ip_socket_address)); ulong im_bind_addr; - if (options.bind_address != 0) + if (Options::Main::bind_address != 0) { - if ((im_bind_addr= (ulong) inet_addr(options.bind_address)) == INADDR_NONE) + im_bind_addr= (ulong) inet_addr(Options::Main::bind_address); + + if (im_bind_addr == INADDR_NONE) im_bind_addr= htonl(INADDR_ANY); } else im_bind_addr= htonl(INADDR_ANY); - uint im_port= options.port_number; + uint im_port= Options::Main::port_number; ip_socket_address.sin_family= AF_INET; ip_socket_address.sin_addr.s_addr= im_bind_addr; @@ -295,7 +298,7 @@ create_unix_socket(struct sockaddr_un &unix_socket_address) bzero(&unix_socket_address, sizeof(unix_socket_address)); unix_socket_address.sun_family= AF_UNIX; - strmake(unix_socket_address.sun_path, options.socket_file_name, + strmake(unix_socket_address.sun_path, Options::Main::socket_file_name, sizeof(unix_socket_address.sun_path)); unlink(unix_socket_address.sun_path); // in case we have stale socket file diff --git a/server-tools/instance-manager/listener.h b/server-tools/instance-manager/listener.h index 28ccbf91731..c28ab0649d7 100644 --- a/server-tools/instance-manager/listener.h +++ b/server-tools/instance-manager/listener.h @@ -27,23 +27,19 @@ 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) {} diff --git a/server-tools/instance-manager/log.cc b/server-tools/instance-manager/log.cc index 3f54bc0649a..94eaf0f7747 100644 --- a/server-tools/instance-manager/log.cc +++ b/server-tools/instance-manager/log.cc @@ -14,14 +14,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> - #include "log.h" -#include "portability.h" -#include <stdarg.h> + +#include <my_global.h> #include <m_string.h> #include <my_sys.h> +#include <stdarg.h> + /* TODO: - add flexible header support @@ -71,7 +71,7 @@ static inline void log(FILE *file, const char *format, va_list args) { int size= sizeof(buff_stack) * 2; buff_msg= (char*) my_malloc(size, MYF(0)); - while (true) + while (TRUE) { if (buff_msg == 0) { diff --git a/server-tools/instance-manager/manager.cc b/server-tools/instance-manager/manager.cc index 95f9029f648..21e02c0b023 100644 --- a/server-tools/instance-manager/manager.cc +++ b/server-tools/instance-manager/manager.cc @@ -14,39 +14,55 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "manager.h" -#include "priv.h" -#include "thread_registry.h" -#include "listener.h" -#include "instance_map.h" -#include "options.h" -#include "user_map.h" -#include "log.h" -#include "guardian.h" - -#include <my_sys.h> +#include <my_global.h> #include <m_string.h> -#include <signal.h> +#include <my_sys.h> #include <thr_alarm.h> + +#include <signal.h> #ifndef __WIN__ #include <sys/wait.h> #endif +#include "exit_codes.h" +#include "guardian.h" +#include "instance_map.h" +#include "listener.h" +#include "log.h" +#include "options.h" +#include "priv.h" +#include "thread_registry.h" +#include "user_map.h" + static int create_pid_file(const char *pid_file_name) { - if (FILE *pid_file= my_fopen(pid_file_name, - O_WRONLY | O_CREAT | O_BINARY, MYF(0))) + FILE *pid_file; + + if (!(pid_file= my_fopen(pid_file_name, O_WRONLY | O_CREAT | O_BINARY, + MYF(0)))) + { + log_error("Error: can not create pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; + } + + if (fprintf(pid_file, "%d\n", (int) getpid()) <= 0) { - fprintf(pid_file, "%d\n", (int) getpid()); - my_fclose(pid_file, MYF(0)); - return 0; + log_error("Error: can not write to pid file '%s': %s (errno: %d)", + (const char *) pid_file_name, + (const char *) strerror(errno), + (int) errno); + return 1; } - log_error("can't create pid file %s: errno=%d, %s", - pid_file_name, errno, strerror(errno)); - return 1; + + my_fclose(pid_file, MYF(0)); + + return 0; } #ifndef __WIN__ @@ -82,14 +98,14 @@ bool have_signal; void onsignal(int signo) { - have_signal= true; + have_signal= TRUE; } void set_signals(sigset_t *set) { signal(SIGINT, onsignal); signal(SIGTERM, onsignal); - have_signal= false; + have_signal= FALSE; } int my_sigwait(const sigset_t *set, int *sig) @@ -109,10 +125,15 @@ int my_sigwait(const sigset_t *set, int *sig) listener thread, write pid file and enter into signal handling. See also comments in mysqlmanager.cc to picture general Instance Manager architecture. + + TODO: how about returning error status. */ -void manager(const Options &options) +void manager() { + int err_code; + const char *err_msg; + Thread_registry thread_registry; /* All objects created in the manager() function live as long as @@ -121,26 +142,55 @@ void manager(const Options &options) */ User_map user_map; - Instance_map instance_map(options.default_mysqld_path); + Instance_map instance_map(Options::Main::default_mysqld_path); Guardian_thread guardian_thread(thread_registry, &instance_map, - options.monitoring_interval); + Options::Main::monitoring_interval); - Listener_thread_args listener_args(thread_registry, options, user_map, - instance_map); + Listener_thread_args listener_args(thread_registry, user_map, instance_map); manager_pid= getpid(); instance_map.guardian= &guardian_thread; - if (instance_map.init() || user_map.init()) + /* Initialize instance map. */ + + if (instance_map.init()) + { + log_error("Error: can not initialize instance list: out of memory."); return; + } + + /* Initialize user map and load password file. */ - if (user_map.load(options.password_file_name)) + if (user_map.init()) + { + log_error("Error: can not initialize user list: out of memory."); return; + } + + if ((err_code= user_map.load(Options::Main::password_file_name, &err_msg))) + { + if (err_code == ERR_PASSWORD_FILE_DOES_NOT_EXIST && + Options::Main::mysqld_safe_compatible) + { + /* + The password file does not exist, but we are running in + mysqld_safe-compatible mode. Continue, but complain in log. + */ + + log_error("Warning: password file does not exist, " + "nobody will be able to connect to Instance Manager."); + } + else + { + log_error("Error: %s.", (const char *) err_msg); + return; + } + } /* write pid file */ - if (create_pid_file(options.pid_file_name)) - return; + if (create_pid_file(Options::Main::pid_file_name)) + return; /* necessary logging has been already done. */ sigset_t mask; set_signals(&mask); @@ -198,7 +248,15 @@ void manager(const Options &options) shutdown_complete= FALSE; - if (instance_map.flush_instances()) + instance_map.guardian->lock(); + instance_map.lock(); + + int flush_instances_status= instance_map.flush_instances(); + + instance_map.unlock(); + instance_map.guardian->unlock(); + + if (flush_instances_status) { log_error("Cannot init instances repository. This might be caused by " "the wrong config file options. For instance, missing mysqld " @@ -240,7 +298,7 @@ void manager(const Options &options) { if (!guardian_thread.is_stopped()) { - bool stop_instances= true; + bool stop_instances= TRUE; guardian_thread.request_shutdown(stop_instances); pthread_cond_signal(&guardian_thread.COND_guardian); } @@ -254,7 +312,7 @@ void manager(const Options &options) err: /* delete the pid file */ - my_delete(options.pid_file_name, MYF(0)); + my_delete(Options::Main::pid_file_name, MYF(0)); #ifndef __WIN__ /* free alarm structures */ @@ -262,4 +320,3 @@ err: /* 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 index 12ed6b3b1ff..d350b5f51f2 100644 --- a/server-tools/instance-manager/manager.h +++ b/server-tools/instance-manager/manager.h @@ -16,8 +16,6 @@ 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); +void manager(); #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_MANAGER_H diff --git a/server-tools/instance-manager/messages.cc b/server-tools/instance-manager/messages.cc index a9b00b9e01f..af35af1e7b0 100644 --- a/server-tools/instance-manager/messages.cc +++ b/server-tools/instance-manager/messages.cc @@ -14,15 +14,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> #include "messages.h" +#include <my_global.h> +#include <mysql_com.h> + #include "mysqld_error.h" #include "mysql_manager_error.h" -#include <mysql_com.h> -#include <assert.h> - static const char *mysqld_error_message(unsigned sql_errno) { @@ -70,6 +69,23 @@ static const char *mysqld_error_message(unsigned sql_errno) "in the instance options"; case ER_ACCESS_OPTION_FILE: return "Cannot open the option file to edit. Check permissions"; + case ER_DROP_ACTIVE_INSTANCE: + return "Cannot drop an active instance. You should stop it first"; + case ER_CREATE_EXISTING_INSTANCE: + return "Instance already exists"; + case ER_INSTANCE_MISCONFIGURED: + return "Instance is misconfigured. Cannot start it"; + case ER_MALFORMED_INSTANCE_NAME: + return "Malformed instance name."; + case ER_INSTANCE_IS_ACTIVE: + return "The instance is active. Stop the instance first"; + case ER_THERE_IS_ACTIVE_INSTACE: + return "At least one instance is active. Stop all instances first"; + case ER_INCOMPATIBLE_OPTION: + return "Instance Manager-specific options are prohibited from being used " + "in the configuration of mysqld-compatible instances"; + case ER_CONF_FILE_DOES_NOT_EXIST: + return "Configuration file does not exist"; default: DBUG_ASSERT(0); return 0; diff --git a/server-tools/instance-manager/mysql_connection.cc b/server-tools/instance-manager/mysql_connection.cc index dcd1807701f..17cda3af704 100644 --- a/server-tools/instance-manager/mysql_connection.cc +++ b/server-tools/instance-manager/mysql_connection.cc @@ -20,22 +20,24 @@ #include "mysql_connection.h" -#include "priv.h" -#include "mysql_manager_error.h" -#include "mysqld_error.h" -#include "thread_registry.h" +#include <m_string.h> +#include <m_string.h> +#include <my_global.h> +#include <mysql_com.h> +#include <mysql.h> +#include <my_sys.h> +#include <violite.h> + +#include "command.h" #include "log.h" -#include "user_map.h" -#include "protocol.h" #include "messages.h" -#include "command.h" +#include "mysqld_error.h" +#include "mysql_manager_error.h" #include "parse.h" - -#include <mysql.h> -#include <violite.h> -#include <mysql_com.h> -#include <m_string.h> -#include <my_sys.h> +#include "priv.h" +#include "protocol.h" +#include "thread_registry.h" +#include "user_map.h" Mysql_connection_thread_args::Mysql_connection_thread_args( @@ -56,7 +58,7 @@ Mysql_connection_thread_args::Mysql_connection_thread_args( 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, + 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(). */ @@ -190,8 +192,6 @@ void Mysql_connection_thread::run() 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 @@ -202,8 +202,8 @@ int Mysql_connection_thread::check_connection() char *pos= buff; ulong server_flags; - memcpy(pos, mysqlmanager_version, mysqlmanager_version_length + 1); - pos+= mysqlmanager_version_length + 1; + memcpy(pos, mysqlmanager_version.str, mysqlmanager_version.length + 1); + pos+= mysqlmanager_version.length + 1; int4store((uchar*) pos, connection_id); pos+= 4; @@ -271,12 +271,14 @@ int Mysql_connection_thread::check_connection() const char *user= pos; const char *password= strend(user)+1; ulong password_len= *password++; + LEX_STRING user_name= { (char *) user, password - user - 2 }; + if (password_len != SCRAMBLE_LENGTH) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; } - if (user_map.authenticate(user, password-user-2, password, scramble)) + if (user_map.authenticate(&user_name, password, scramble)) { net_send_error(&net, ER_ACCESS_DENIED_ERROR); return 1; @@ -312,7 +314,7 @@ int Mysql_connection_thread::do_command() 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", + log_info("connection %d: packet_length=%d, command=%d", connection_id, packet_length, command); return dispatch_command(command, packet + 1, packet_length - 1); } @@ -336,7 +338,7 @@ int Mysql_connection_thread::dispatch_command(enum enum_server_command command, if (Command *command= parse_command(&instance_map, packet)) { int res= 0; - log_info("query for connection %d successefully parsed",connection_id); + log_info("query for connection %d successfully parsed",connection_id); res= command->execute(&net, connection_id); delete command; if (!res) @@ -380,5 +382,5 @@ pthread_handler_t mysql_connection(void *arg) } /* - vim: fdm=marker + vim: fdm=marker */ diff --git a/server-tools/instance-manager/mysql_manager_error.h b/server-tools/instance-manager/mysql_manager_error.h index ff782923a8e..373c7d798a0 100644 --- a/server-tools/instance-manager/mysql_manager_error.h +++ b/server-tools/instance-manager/mysql_manager_error.h @@ -29,5 +29,13 @@ #define ER_ACCESS_OPTION_FILE 3008 #define ER_OFFSET_ERROR 3009 #define ER_READ_FILE 3010 +#define ER_DROP_ACTIVE_INSTANCE 3011 +#define ER_CREATE_EXISTING_INSTANCE 3012 +#define ER_INSTANCE_MISCONFIGURED 3013 +#define ER_MALFORMED_INSTANCE_NAME 3014 +#define ER_INSTANCE_IS_ACTIVE 3015 +#define ER_THERE_IS_ACTIVE_INSTACE 3016 +#define ER_INCOMPATIBLE_OPTION 3017 +#define ER_CONF_FILE_DOES_NOT_EXIST 3018 #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_MYSQL_MANAGER_ERROR_H */ diff --git a/server-tools/instance-manager/mysqlmanager.cc b/server-tools/instance-manager/mysqlmanager.cc index d0b2cf2666c..c8be1842258 100644 --- a/server-tools/instance-manager/mysqlmanager.cc +++ b/server-tools/instance-manager/mysqlmanager.cc @@ -15,25 +15,29 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <my_global.h> -#include "manager.h" - -#include "options.h" -#include "log.h" - #include <my_sys.h> + #include <string.h> #include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> + #ifndef __WIN__ #include <pwd.h> #include <grp.h> #include <sys/wait.h> #endif -#include <sys/types.h> -#include <sys/stat.h> + +#include "log.h" +#include "manager.h" +#include "options.h" +#include "user_management_commands.h" + #ifdef __WIN__ #include "windowsservice.h" #endif + /* Few notes about Instance Manager architecture: Instance Manager consisits of two processes: the angel process, and the @@ -59,13 +63,12 @@ */ static void init_environment(char *progname); + #ifndef __WIN__ static void daemonize(const char *log_file_name); -static void angel(const Options &options); +static void angel(); static struct passwd *check_user(const char *user); static int set_user(const char *user, struct passwd *user_info); -#else -int HandleServiceOptions(Options options); #endif @@ -81,41 +84,61 @@ int main(int argc, char *argv[]) { int return_value= 1; init_environment(argv[0]); - Options options; - if (options.load(argc, argv)) - goto err; + if ((return_value= Options::load(argc, argv))) + goto main_end; + + if (Options::User_management::cmd) + { + return_value= Options::User_management::cmd->execute(); + + goto main_end; + } #ifndef __WIN__ + struct passwd *user_info; - if ((user_info= check_user(options.user))) + if ((user_info= check_user(Options::Daemon::user))) { - if (set_user(options.user, user_info)) - goto err; + if (set_user(Options::Daemon::user, user_info)) + { + return_value= 1; + goto main_end; + } } - if (options.run_as_service) + if (Options::Daemon::run_as_service) { /* forks, and returns only in child */ - daemonize(options.log_file_name); + daemonize(Options::Daemon::log_file_name); /* forks again, and returns only in child: parent becomes angel */ - angel(options); + angel(); } + + manager(); + #else - if (!options.stand_alone) + + if (!Options::Service::stand_alone) { - if (HandleServiceOptions(options)) - goto err; + if (HandleServiceOptions()) + { + return_value= 1; + goto main_end; + } } else + { + manager(); + } + #endif - manager(options); return_value= 0; -err: - options.cleanup(); +main_end: + Options::cleanup(); my_end(0); return return_value; } @@ -200,7 +223,7 @@ static void init_environment(char *progname) MY_INIT(progname); log_init(); umask(0117); - srand(time(0)); + srand((unsigned int) time(0)); } @@ -298,7 +321,7 @@ void terminate(int signo) Angel process will exit silently if mysqlmanager exits normally. */ -static void angel(const Options &options) +static void angel() { /* install signal handlers */ sigset_t zeromask; // to sigsuspend in parent diff --git a/server-tools/instance-manager/options.cc b/server-tools/instance-manager/options.cc index 85fea76c079..ab40ba96dba 100644 --- a/server-tools/instance-manager/options.cc +++ b/server-tools/instance-manager/options.cc @@ -20,45 +20,88 @@ #include "options.h" -#include "priv.h" -#include "portability.h" +#include <my_global.h> #include <my_sys.h> #include <my_getopt.h> -#include <m_string.h> #include <mysql_com.h> +#include "exit_codes.h" +#include "log.h" +#include "portability.h" +#include "priv.h" +#include "user_management_commands.h" + #define QUOTE2(x) #x #define QUOTE(x) QUOTE2(x) #ifdef __WIN__ -char Options::install_as_service; -char Options::remove_service; -char Options::stand_alone; -char windows_config_file[FN_REFLEN]; -char default_password_file_name[FN_REFLEN]; -char default_log_file_name[FN_REFLEN]; -const char *Options::config_file= windows_config_file; -#else -char Options::run_as_service; -const char *Options::user= 0; /* No default value */ -const char *default_password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); -const char *default_log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); -const char *Options::config_file= QUOTE(DEFAULT_CONFIG_FILE); + +/* Define holders for default values. */ + +static char win_dflt_config_file_name[FN_REFLEN]; +static char win_dflt_password_file_name[FN_REFLEN]; +static char win_dflt_pid_file_name[FN_REFLEN]; +static char win_dflt_socket_file_name[FN_REFLEN]; + +static char win_dflt_mysqld_path[FN_REFLEN]; + +/* Define and initialize Windows-specific options. */ + +my_bool Options::Service::install_as_service; +my_bool Options::Service::remove_service; +my_bool Options::Service::stand_alone; + +const char *Options::Main::config_file= win_dflt_config_file_name; +const char *Options::Main::password_file_name= win_dflt_password_file_name; +const char *Options::Main::pid_file_name= win_dflt_pid_file_name; +const char *Options::Main::socket_file_name= win_dflt_socket_file_name; + +const char *Options::Main::default_mysqld_path= win_dflt_mysqld_path; + +static int setup_windows_defaults(); + +#else /* UNIX */ + +/* Define and initialize UNIX-specific options. */ + +my_bool Options::Daemon::run_as_service= FALSE; +const char *Options::Daemon::log_file_name= QUOTE(DEFAULT_LOG_FILE_NAME); +const char *Options::Daemon::user= NULL; /* No default value */ + +const char *Options::Main::config_file= QUOTE(DEFAULT_CONFIG_FILE); +const char * +Options::Main::password_file_name= QUOTE(DEFAULT_PASSWORD_FILE_NAME); +const char *Options::Main::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); +const char *Options::Main::socket_file_name= QUOTE(DEFAULT_SOCKET_FILE_NAME); + +const char *Options::Main::default_mysqld_path= QUOTE(DEFAULT_MYSQLD_PATH); + #endif -const char *Options::log_file_name= default_log_file_name; -const char *Options::pid_file_name= QUOTE(DEFAULT_PID_FILE_NAME); -const char *Options::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 */ + +/* Remember if the config file was forced. */ + +bool Options::Main::is_forced_default_file= FALSE; + +/* Define and initialize common options. */ + +const char *Options::Main::bind_address= NULL; /* No default value */ +uint Options::Main::monitoring_interval= DEFAULT_MONITORING_INTERVAL; +uint Options::Main::port_number= DEFAULT_PORT; +my_bool Options::Main::mysqld_safe_compatible= FALSE; + +/* Options::User_management */ + +char *Options::User_management::user_name= NULL; +char *Options::User_management::password= NULL; + +User_management_cmd *Options::User_management::cmd= NULL; + +/* Private members. */ + char **Options::saved_argv= NULL; -/* Remember if the config file was forced */ -bool Options::is_forced_default_file= 0; + #ifndef DBUG_OFF -const char *Options::default_dbug_option= "d:t:i:O,im.trace"; +const char *Options::Debug::config_str= "d:t:i:O,im.trace"; #endif /* @@ -67,23 +110,33 @@ const char *Options::default_dbug_option= "d:t:i:O,im.trace"; */ enum options { + OPT_PASSWD= 'P', + OPT_USERNAME= 'u', + OPT_PASSWORD= 'p', OPT_LOG= 256, OPT_PID_FILE, OPT_SOCKET, OPT_PASSWORD_FILE, OPT_MYSQLD_PATH, -#ifndef __WIN__ - OPT_RUN_AS_SERVICE, - OPT_USER, -#else +#ifdef __WIN__ OPT_INSTALL_SERVICE, OPT_REMOVE_SERVICE, OPT_STAND_ALONE, +#else + OPT_RUN_AS_SERVICE, + OPT_USER, #endif OPT_MONITORING_INTERVAL, OPT_PORT, OPT_WAIT_TIMEOUT, - OPT_BIND_ADDRESS + OPT_BIND_ADDRESS, + OPT_ADD_USER, + OPT_DROP_USER, + OPT_EDIT_USER, + OPT_CLEAN_PASSWORD_FILE, + OPT_CHECK_PASSWORD_FILE, + OPT_LIST_USERS, + OPT_MYSQLD_SAFE_COMPATIBLE }; static struct my_option my_long_options[] = @@ -91,94 +144,151 @@ 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 }, + { "add-user", OPT_ADD_USER, + "Add a user to the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "bind-address", OPT_BIND_ADDRESS, "Bind address to use for connection.", - (gptr *) &Options::bind_address, (gptr *) &Options::bind_address, + (gptr *) &Options::Main::bind_address, + (gptr *) &Options::Main::bind_address, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "check-password-file", OPT_CHECK_PASSWORD_FILE, + "Check the password file for consistency", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "clean-password-file", OPT_CLEAN_PASSWORD_FILE, + "Clean the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + #ifndef DBUG_OFF {"debug", '#', "Debug log.", - (gptr*) &Options::default_dbug_option, (gptr*) &Options::default_dbug_option, + (gptr *) &Options::Debug::config_str, + (gptr *) &Options::Debug::config_str, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, #endif { "default-mysqld-path", OPT_MYSQLD_PATH, "Where to look for MySQL" " Server binary.", - (gptr *) &Options::default_mysqld_path, - (gptr *) &Options::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, + (gptr *) &Options::Main::default_mysqld_path, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 }, + + { "drop-user", OPT_DROP_USER, + "Drop existing user from the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "edit-user", OPT_EDIT_USER, + "Edit existing user in the password file", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + #ifdef __WIN__ { "install", OPT_INSTALL_SERVICE, "Install as system service.", - (gptr *) &Options::install_as_service, (gptr*) &Options::install_as_service, + (gptr *) &Options::Service::install_as_service, + (gptr *) &Options::Service::install_as_service, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, #endif + { "list-users", OPT_LIST_USERS, + "Print out a list of registered users", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + +#ifndef __WIN__ { "log", OPT_LOG, "Path to log file. Used only with --run-as-service.", - (gptr *) &Options::log_file_name, (gptr *) &Options::log_file_name, + (gptr *) &Options::Daemon::log_file_name, + (gptr *) &Options::Daemon::log_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, +#endif { "monitoring-interval", OPT_MONITORING_INTERVAL, "Interval to monitor" " instances in seconds.", - (gptr *) &Options::monitoring_interval, - (gptr *) &Options::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, + (gptr *) &Options::Main::monitoring_interval, 0, GET_UINT, REQUIRED_ARG, DEFAULT_MONITORING_INTERVAL, 0, 0, 0, 0, 0 }, - { "passwd", 'P', "Prepare entry for passwd file and exit.", 0, 0, 0, - GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + { "mysqld-safe-compatible", OPT_MYSQLD_SAFE_COMPATIBLE, + "Start Instance Manager in mysqld_safe compatible manner", + (gptr *) &Options::Main::mysqld_safe_compatible, + (gptr *) &Options::Main::mysqld_safe_compatible, + 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, + + { "passwd", OPT_PASSWD, + "Prepare an entry for the password file and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, + + { "password", OPT_PASSWORD, "Password to update the password file", + (gptr *) &Options::User_management::password, + (gptr *) &Options::User_management::password, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - { "password-file", OPT_PASSWORD_FILE, "Look for Instance Manager users" - " and passwords here.", - (gptr *) &Options::password_file_name, - (gptr *) &Options::password_file_name, + { "password-file", OPT_PASSWORD_FILE, + "Look for Instance Manager users and passwords here.", + (gptr *) &Options::Main::password_file_name, + (gptr *) &Options::Main::password_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "pid-file", OPT_PID_FILE, "Pid file to use.", - (gptr *) &Options::pid_file_name, (gptr *) &Options::pid_file_name, + (gptr *) &Options::Main::pid_file_name, + (gptr *) &Options::Main::pid_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "port", OPT_PORT, "Port number to use for connections", - (gptr *) &Options::port_number, (gptr *) &Options::port_number, + (gptr *) &Options::Main::port_number, + (gptr *) &Options::Main::port_number, 0, GET_UINT, REQUIRED_ARG, DEFAULT_PORT, 0, 0, 0, 0, 0 }, #ifdef __WIN__ { "remove", OPT_REMOVE_SERVICE, "Remove system service.", - (gptr *)&Options::remove_service, (gptr*) &Options::remove_service, + (gptr *) &Options::Service::remove_service, + (gptr *) &Options::Service::remove_service, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, #else { "run-as-service", OPT_RUN_AS_SERVICE, - "Daemonize and start angel process.", (gptr *) &Options::run_as_service, + "Daemonize and start angel process.", + (gptr *) &Options::Daemon::run_as_service, 0, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0 }, #endif { "socket", OPT_SOCKET, "Socket file to use for connection.", - (gptr *) &Options::socket_file_name, (gptr *) &Options::socket_file_name, + (gptr *) &Options::Main::socket_file_name, + (gptr *) &Options::Main::socket_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #ifdef __WIN__ { "standalone", OPT_STAND_ALONE, "Run the application in stand alone mode.", - (gptr *)&Options::stand_alone, (gptr*) &Options::stand_alone, + (gptr *) &Options::Service::stand_alone, + (gptr *) &Options::Service::stand_alone, 0, GET_BOOL, NO_ARG, 0, 0, 1, 0, 0, 0}, #else { "user", OPT_USER, "Username to start mysqlmanager", - (gptr *) &Options::user, - (gptr *) &Options::user, + (gptr *) &Options::Daemon::user, + (gptr *) &Options::Daemon::user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif + { "username", OPT_USERNAME, + "Username to update the password file", + (gptr *) &Options::User_management::user_name, + (gptr *) &Options::User_management::user_name, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + { "version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "wait-timeout", OPT_WAIT_TIMEOUT, "The number of seconds IM waits " "for activity on a connection before closing it.", - (gptr *) &net_read_timeout, (gptr *) &net_read_timeout, 0, GET_ULONG, - REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, + (gptr *) &net_read_timeout, + (gptr *) &net_read_timeout, + 0, GET_ULONG, REQUIRED_ARG, NET_WAIT_TIMEOUT, 1, LONG_TIMEOUT, 0, 1, 0 }, { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0 } }; static void version() { - printf("%s Ver %s for %s on %s\n", my_progname, mysqlmanager_version, + printf("%s Ver %s for %s on %s\n", my_progname, + (const char *) mysqlmanager_version.str, SYSTEM_TYPE, MACHINE_TYPE); } @@ -206,37 +316,6 @@ static void usage() } -static void passwd() -{ - char user[1024], *p; - const char *pw1, *pw2; - char pw1msg[]= "Enter password: "; - char pw2msg[]= "Re-type password: "; - char crypted_pw[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; - - fprintf(stderr, "Creating record for new user.\n"); - fprintf(stderr, "Enter user name: "); - if (!fgets(user, sizeof(user), stdin)) - { - fprintf(stderr, "Unable to read user.\n"); - return; - } - if ((p= strchr(user, '\n'))) *p= 0; - - pw1= get_tty_password(pw1msg); - pw2= get_tty_password(pw2msg); - - if (strcmp(pw1, pw2)) - { - fprintf(stderr, "Sorry, passwords do not match.\n"); - return; - } - - make_scrambled_password(crypted_pw, pw1); - printf("%s:%s\n", user, crypted_pw); -} - - C_MODE_START static my_bool @@ -248,16 +327,52 @@ get_one_option(int optid, case 'V': version(); exit(0); - case 'P': - passwd(); - exit(0); + case OPT_PASSWD: + case OPT_ADD_USER: + case OPT_DROP_USER: + case OPT_EDIT_USER: + case OPT_CLEAN_PASSWORD_FILE: + case OPT_CHECK_PASSWORD_FILE: + case OPT_LIST_USERS: + if (Options::User_management::cmd) + { + fprintf(stderr, "Error: only one password-management command " + "can be specified at a time.\n"); + exit(ERR_INVALID_USAGE); + } + + switch (optid) { + case OPT_PASSWD: + Options::User_management::cmd= new Passwd_cmd(); + break; + case OPT_ADD_USER: + Options::User_management::cmd= new Add_user_cmd(); + break; + case OPT_DROP_USER: + Options::User_management::cmd= new Drop_user_cmd(); + break; + case OPT_EDIT_USER: + Options::User_management::cmd= new Edit_user_cmd(); + break; + case OPT_CLEAN_PASSWORD_FILE: + Options::User_management::cmd= new Clean_db_cmd(); + break; + case OPT_CHECK_PASSWORD_FILE: + Options::User_management::cmd= new Check_db_cmd(); + break; + case OPT_LIST_USERS: + Options::User_management::cmd= new List_users_cmd(); + break; + } + + break; case '?': usage(); exit(0); case '#': #ifndef DBUG_OFF - DBUG_SET(argument ? argument : Options::default_dbug_option); - DBUG_SET_INITIAL(argument ? argument : Options::default_dbug_option); + DBUG_SET(argument ? argument : Options::Debug::config_str); + DBUG_SET_INITIAL(argument ? argument : Options::Debug::config_str); #endif break; } @@ -283,8 +398,8 @@ int Options::load(int argc, char **argv) { if (is_prefix(argv[1], "--defaults-file=")) { - Options::config_file= strchr(argv[1], '=') + 1; - Options::is_forced_default_file= 1; + Main::config_file= strchr(argv[1], '=') + 1; + Main::is_forced_default_file= TRUE; } if (is_prefix(argv[1], "--defaults-extra-file=") || is_prefix(argv[1], "--no-defaults")) @@ -293,59 +408,92 @@ int Options::load(int argc, char **argv) fprintf(stderr, "The --defaults-extra-file and --no-defaults options" " are not supported by\n" "Instance Manager. Program aborted.\n"); - goto err; + return ERR_INVALID_USAGE; } } #ifdef __WIN__ if (setup_windows_defaults()) - goto err; + { + fprintf(stderr, "Internal error: could not setup default values.\n"); + return ERR_OUT_OF_MEMORY; + } #endif + /* load_defaults will reset saved_argv with a new allocated list */ saved_argv= argv; /* config-file options are prepended to command-line ones */ - load_defaults(config_file, default_groups, &argc, - &saved_argv); - if ((handle_options(&argc, &saved_argv, my_long_options, - get_one_option)) != 0) - goto err; + log_info("Loading config file '%s'...", + (const char *) Main::config_file); - return 0; + load_defaults(Main::config_file, default_groups, &argc, &saved_argv); + + if ((handle_options(&argc, &saved_argv, my_long_options, get_one_option))) + return ERR_INVALID_USAGE; + + if (!User_management::cmd && + (User_management::user_name || User_management::password)) + { + fprintf(stderr, + "--username and/or --password options have been specified, " + "but no password-management command has been given.\n"); + return ERR_INVALID_USAGE; + } -err: - return 1; + return 0; } void Options::cleanup() { - /* free_defaults returns nothing */ - if (Options::saved_argv != NULL) - free_defaults(Options::saved_argv); + if (saved_argv) + free_defaults(saved_argv); + + delete User_management::cmd; } #ifdef __WIN__ -int Options::setup_windows_defaults() +static int setup_windows_defaults() { - if (!GetModuleFileName(NULL, default_password_file_name, - sizeof(default_password_file_name))) - return 1; - char *filename= strstr(default_password_file_name, ".exe"); - strcpy(filename, ".passwd"); - - if (!GetModuleFileName(NULL, default_log_file_name, - sizeof(default_log_file_name))) + char module_full_name[FN_REFLEN]; + char dir_name[FN_REFLEN]; + char base_name[FN_REFLEN]; + char im_name[FN_REFLEN]; + char *base_name_ptr; + char *ptr; + + /* Determine dirname and basename. */ + + if (!GetModuleFileName(NULL, module_full_name, sizeof (module_full_name)) || + !GetFullPathName(module_full_name, sizeof (dir_name), dir_name, + &base_name_ptr)) + { return 1; - filename= strstr(default_log_file_name, ".exe"); - strcpy(filename, ".log"); + } + + strmake(base_name, base_name_ptr, FN_REFLEN); + *base_name_ptr= 0; + + strmake(im_name, base_name, FN_REFLEN); + ptr= strrchr(im_name, '.'); + + if (!ptr) + return 1; + + *ptr= 0; + + /* Initialize the defaults. */ + + strxmov(win_dflt_config_file_name, dir_name, DFLT_CONFIG_FILE_NAME, NullS); + strxmov(win_dflt_mysqld_path, dir_name, DFLT_MYSQLD_PATH, NullS); + strxmov(win_dflt_password_file_name, dir_name, im_name, DFLT_PASSWD_FILE_EXT, + NullS); + strxmov(win_dflt_pid_file_name, dir_name, im_name, DFLT_PID_FILE_EXT, NullS); + strxmov(win_dflt_socket_file_name, dir_name, im_name, DFLT_SOCKET_FILE_EXT, + NullS); - if (!GetModuleFileName(NULL, windows_config_file, - sizeof(windows_config_file))) - return 1; - char *slash= strrchr(windows_config_file, '\\'); - strcpy(slash, "\\my.ini"); return 0; } diff --git a/server-tools/instance-manager/options.h b/server-tools/instance-manager/options.h index 06818288606..898114c45da 100644 --- a/server-tools/instance-manager/options.h +++ b/server-tools/instance-manager/options.h @@ -17,49 +17,86 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - Options - all possible options for the instance manager grouped in one - struct. + Options - all possible command-line options for the Instance Manager grouped + in one struct. */ + #include <my_global.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +class User_management_cmd; + struct Options { -#ifdef __WIN__ - static char install_as_service; - static char remove_service; - static char stand_alone; -#else - static char run_as_service; /* handle_options doesn't support bool */ - static const char *user; -#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; + /* + NOTE: handle_options() expects value of my_bool type for GET_BOOL + accessor (i.e. bool must not be used). + */ - /* argv pointer returned by load_defaults() to be used by free_defaults() */ - static char **saved_argv; + struct User_management + { + static User_management_cmd *cmd; + + static char *user_name; + static char *password; + }; + + struct Main + { + /* this is not an option parsed by handle_options(). */ + static bool is_forced_default_file; + + static const char *pid_file_name; + static const char *socket_file_name; + static const char *password_file_name; + static const char *default_mysqld_path; + static uint monitoring_interval; + static uint port_number; + static const char *bind_address; + static const char *config_file; + static my_bool mysqld_safe_compatible; + }; #ifndef DBUG_OFF - static const char *default_dbug_option; + struct Debug + { + static const char *config_str; + }; #endif - int load(int argc, char **argv); - void cleanup(); -#ifdef __WIN__ - int setup_windows_defaults(); +#ifndef __WIN__ + + struct Daemon + { + static my_bool run_as_service; + static const char *log_file_name; + static const char *user; + }; + +#else + + struct Service + { + static my_bool install_as_service; + static my_bool remove_service; + static my_bool stand_alone; + }; + #endif + +public: + static int load(int argc, char **argv); + static void cleanup(); + +private: + Options(); /* Deny instantiation of this class. */ + +private: + /* argv pointer returned by load_defaults() to be used by free_defaults() */ + static char **saved_argv; }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_OPTIONS_H diff --git a/server-tools/instance-manager/parse.cc b/server-tools/instance-manager/parse.cc index 14b3db16b45..4e931488fce 100644 --- a/server-tools/instance-manager/parse.cc +++ b/server-tools/instance-manager/parse.cc @@ -17,12 +17,12 @@ #include "parse.h" #include "commands.h" -#include <string.h> - enum Token { - TOK_ERROR= 0, /* Encodes the "ERROR" word, it doesn't indicate error. */ + TOK_CREATE= 0, + TOK_DROP, + TOK_ERROR, /* Encodes the "ERROR" word, it doesn't indicate error. */ TOK_FILES, TOK_FLUSH, TOK_GENERAL, @@ -50,6 +50,8 @@ struct tokens_st static struct tokens_st tokens[]= { + {6, "CREATE"}, + {4, "DROP"}, {5, "ERROR"}, {5, "FILES"}, {5, "FLUSH"}, @@ -67,6 +69,37 @@ static struct tokens_st tokens[]= { {5, "UNSET"} }; +/************************************************************************/ + +Named_value_arr::Named_value_arr() : + initialized(FALSE) +{ +} + + +bool Named_value_arr::init() +{ + if (my_init_dynamic_array(&arr, sizeof(Named_value), 0, 32)) + return TRUE; + + initialized= TRUE; + + return FALSE; +} + + +Named_value_arr::~Named_value_arr() +{ + if (!initialized) + return; + + for (int i= 0; i < get_size(); ++i) + get_element(i).free(); + + delete_dynamic(&arr); +} + +/************************************************************************/ /* Returns token no if word corresponds to some token, otherwise returns @@ -104,53 +137,200 @@ Token shift_token(const char **text, uint *word_len) } -int get_text_id(const char **text, uint *word_len, const char **id) +int get_text_id(const char **text, LEX_STRING *token) { - get_word(text, word_len); - if (*word_len == 0) + get_word(text, &token->length); + if (token->length == 0) return 1; - *id= *text; + token->str= (char *) *text; return 0; } +static bool parse_long(const LEX_STRING *token, long *value) +{ + int err_code; + char *end_ptr= token->str + token->length; + + *value= my_strtoll10(token->str, &end_ptr, &err_code); + + return err_code != 0; +} + + +bool parse_option_value(const char *text, uint *text_len, char **value) +{ + char beginning_quote; + const char *text_start_ptr; + char *v; + bool escape_mode= FALSE; + + if (!*text || (*text != '\'' && *text != '"')) + return TRUE; /* syntax error: string expected. */ + + beginning_quote= *text; + + ++text; /* skip the beginning quote. */ + + text_start_ptr= text; + + if (!(v= Named_value::alloc_str(text))) + return TRUE; + + *value= v; + + while (TRUE) + { + if (!*text) + { + Named_value::free_str(value); + return TRUE; /* syntax error: missing terminating ' character. */ + } + + if (*text == '\n' || *text == '\r') + { + Named_value::free_str(value); + return TRUE; /* syntax error: option value should be a single line. */ + } + + if (!escape_mode && *text == beginning_quote) + break; + + if (escape_mode) + { + switch (*text) + { + case 'b': /* \b -- backspace */ + if (v > *value) + --v; + break; + + case 't': /* \t -- tab */ + *v= '\t'; + ++v; + break; + + case 'n': /* \n -- newline */ + *v= '\n'; + ++v; + break; + + case 'r': /* \r -- carriage return */ + *v= '\r'; + ++v; + break; + + case '\\': /* \\ -- back slash */ + *v= '\\'; + ++v; + break; + + case 's': /* \s -- space */ + *v= ' '; + ++v; + break; + + default: /* Unknown escape sequence. Treat as error. */ + Named_value::free_str(value); + return TRUE; + } + + escape_mode= FALSE; + } + else + { + if (*text == '\\') + { + escape_mode= TRUE; + } + else + { + *v= *text; + ++v; + } + } + + ++text; + } + + *v= 0; + + /* "2" below stands for beginning and ending quotes. */ + *text_len= text - text_start_ptr + 2; + + return FALSE; +} + + +void skip_spaces(const char **text) +{ + while (**text && my_isspace(default_charset_info, **text)) + ++(*text); +} + + Command *parse_command(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; + LEX_STRING instance_name; 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: + case TOK_CREATE: + case TOK_DROP: if (shift_token(&text, &word_len) != TOK_INSTANCE) goto syntax_error; get_word(&text, &word_len); if (word_len == 0) goto syntax_error; - instance_name= text; - instance_name_len= word_len; + instance_name.str= (char *) text; + instance_name.length= word_len; text+= word_len; - /* it should be the end of command */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; - if (tok1 == TOK_START) - command= new Start_instance(map, instance_name, instance_name_len); + if (tok1 == TOK_CREATE) + { + Create_instance *cmd= new Create_instance(map, &instance_name); + + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ + + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } + + command= cmd; + } else - command= new Stop_instance(map, instance_name, instance_name_len); + { + /* it should be the end of command */ + get_word(&text, &word_len, NONSPACE); + if (word_len) + goto syntax_error; + } + + switch (tok1) { + case TOK_START: + command= new Start_instance(map, &instance_name); + break; + case TOK_STOP: + command= new Stop_instance(map, &instance_name); + break; + case TOK_CREATE: + ; /* command already initialized. */ + break; + case TOK_DROP: + command= new Drop_instance(map, &instance_name); + break; + default: /* this is impossible, but nevertheless... */ + DBUG_ASSERT(0); + } break; case TOK_FLUSH: if (shift_token(&text, &word_len) != TOK_INSTANCES) @@ -163,53 +343,28 @@ Command *parse_command(Instance_map *map, const char *text) command= new Flush_instances(map); break; case TOK_UNSET: - skip= true; case TOK_SET: + { + Abstract_option_cmd *cmd; - if (get_text_id(&text, &instance_name_len, &instance_name)) - goto syntax_error; - text+= instance_name_len; - - /* the next token should be a dot */ - get_word(&text, &word_len); - if (*text != '.') - goto syntax_error; - text++; + if (tok1 == TOK_SET) + cmd= new Set_option(map); + else + cmd= new Unset_option(map); - get_word(&text, &option_len, NONSPACE); - option= text; - if ((tmp= strchr(text, '=')) != NULL) - option_len= tmp - text; - text+= option_len; + if (!cmd) + return NULL; /* Report ER_OUT_OF_RESOURCES. */ - get_word(&text, &word_len); - if (*text == '=') - { - text++; /* skip '=' */ - get_word(&text, &option_value_len, NONSPACE); - option_value= text; - text+= option_value_len; - } - else - { - option_value= ""; - option_value_len= 0; - } + if (cmd->init(&text)) + { + delete cmd; + goto syntax_error; + } - /* should be empty */ - get_word(&text, &word_len, NONSPACE); - if (word_len) - goto syntax_error; + command= cmd; - if (skip) - command= new Unset_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - else - command= new Set_option(map, instance_name, instance_name_len, - option, option_len, option_value, - option_value_len); - break; + break; + } case TOK_SHOW: switch (shift_token(&text, &word_len)) { case TOK_INSTANCES: @@ -222,30 +377,35 @@ Command *parse_command(Instance_map *map, const char *text) switch (Token tok2= shift_token(&text, &word_len)) { case TOK_OPTIONS: case TOK_STATUS: - if (get_text_id(&text, &instance_name_len, &instance_name)) + if (get_text_id(&text, &instance_name)) goto syntax_error; - text+= instance_name_len; + text+= instance_name.length; /* check that this is the end of the command */ get_word(&text, &word_len, NONSPACE); if (word_len) goto syntax_error; if (tok2 == TOK_STATUS) - command= new Show_instance_status(map, instance_name, - instance_name_len); + command= new Show_instance_status(map, &instance_name); else - command= new Show_instance_options(map, instance_name, - instance_name_len); + command= new Show_instance_options(map, &instance_name); break; default: goto syntax_error; } break; default: - instance_name= text - word_len; - instance_name_len= word_len; - if (instance_name_len) + instance_name.str= (char *) text - word_len; + instance_name.length= word_len; + if (instance_name.length) { Log_type log_type; + + long log_size; + LEX_STRING log_size_str; + + long log_offset= 0; + LEX_STRING log_offset_str= { NULL, 0 }; + switch (shift_token(&text, &word_len)) { case TOK_LOG: switch (Token tok3= shift_token(&text, &word_len)) { @@ -254,8 +414,7 @@ Command *parse_command(Instance_map *map, const char *text) /* check that this is the end of the command */ if (word_len) goto syntax_error; - command= new Show_instance_log_files(map, instance_name, - instance_name_len); + command= new Show_instance_log_files(map, &instance_name); break; case TOK_ERROR: case TOK_GENERAL: @@ -275,12 +434,14 @@ Command *parse_command(Instance_map *map, const char *text) goto syntax_error; } /* get the size of the log we want to retrieve */ - if (get_text_id(&text, &word_len, &log_size)) + if (get_text_id(&text, &log_size_str)) goto syntax_error; - text+= word_len; + text+= log_size_str.length; + /* this parameter is required */ - if (!word_len) + if (!log_size_str.length) goto syntax_error; + /* the next token should be comma, or nothing */ get_word(&text, &word_len); switch (*text) { @@ -290,23 +451,41 @@ Command *parse_command(Instance_map *map, const char *text) get_word(&text, &word_len); if (!word_len) goto syntax_error; + log_offset_str.str= (char *) text; + log_offset_str.length= word_len; text+= word_len; - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, text); get_word(&text, &word_len, NONSPACE); /* check that this is the end of the command */ if (word_len) goto syntax_error; break; case '\0': - command= new Show_instance_log(map, instance_name, - instance_name_len, log_type, - log_size, NULL); break; /* this is ok */ default: + goto syntax_error; + } + + /* Parse size parameter. */ + + if (parse_long(&log_size_str, &log_size)) + goto syntax_error; + + if (log_size <= 0) goto syntax_error; + + /* Parse offset parameter (if specified). */ + + if (log_offset_str.length) + { + if (parse_long(&log_offset_str, &log_offset)) + goto syntax_error; + + if (log_offset <= 0) + goto syntax_error; } + + command= new Show_instance_log(map, &instance_name, + log_type, log_size, log_offset); break; default: goto syntax_error; diff --git a/server-tools/instance-manager/parse.h b/server-tools/instance-manager/parse.h index 3da53e3a61e..ae29c7eb64a 100644 --- a/server-tools/instance-manager/parse.h +++ b/server-tools/instance-manager/parse.h @@ -18,6 +18,7 @@ #include <my_global.h> #include <my_sys.h> +#include <m_string.h> class Command; class Instance_map; @@ -29,10 +30,148 @@ enum Log_type IM_LOG_SLOW }; -Command *parse_command(Instance_map *instance_map, const char *text); +Command *parse_command(Instance_map *map, const char *text); + +bool parse_option_value(const char *text, uint *text_len, char **value); + +void skip_spaces(const char **text); /* define kinds of the word seek method */ -enum { ALPHANUM= 1, NONSPACE }; +enum enum_seek_method { ALPHANUM= 1, NONSPACE, OPTION_NAME }; + +/************************************************************************/ + +class Named_value +{ +public: + /* + The purpose of these methods is just to have one method for + allocating/deallocating memory for strings for Named_value. + */ + + static inline char *alloc_str(const LEX_STRING *str); + static inline char *alloc_str(const char *str); + static inline void free_str(char **str); + +public: + inline Named_value(); + inline Named_value(char *name_arg, char *value_arg); + + inline char *get_name(); + inline char *get_value(); + + inline void free(); + +private: + char *name; + char *value; +}; + +inline char *Named_value::alloc_str(const LEX_STRING *str) +{ + return my_strndup((const byte *) str->str, str->length, MYF(0)); +} + +inline char *Named_value::alloc_str(const char *str) +{ + return my_strdup(str, MYF(0)); +} + +inline void Named_value::free_str(char **str) +{ + my_free(*str, MYF(MY_ALLOW_ZERO_PTR)); + *str= NULL; +} + +inline Named_value::Named_value() + :name(NULL), value(NULL) +{ } + +inline Named_value::Named_value(char *name_arg, char *value_arg) + :name(name_arg), value(value_arg) +{ } + +inline char *Named_value::get_name() +{ + return name; +} + +inline char *Named_value::get_value() +{ + return value; +} + +void Named_value::free() +{ + free_str(&name); + free_str(&value); +} + +/************************************************************************/ + +class Named_value_arr +{ +public: + Named_value_arr(); + ~Named_value_arr(); + + bool init(); + + inline int get_size() const; + inline Named_value get_element(int idx) const; + inline void remove_element(int idx); + inline bool add_element(Named_value *option); + inline bool replace_element(int idx, Named_value *option); + +private: + bool initialized; + DYNAMIC_ARRAY arr; +}; + + +inline int Named_value_arr::get_size() const +{ + return arr.elements; +} + + +inline Named_value Named_value_arr::get_element(int idx) const +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + Named_value option; + get_dynamic((DYNAMIC_ARRAY *) &arr, (gptr) &option, idx); + + return option; +} + + +inline void Named_value_arr::remove_element(int idx) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + delete_dynamic_element(&arr, idx); +} + + +inline bool Named_value_arr::add_element(Named_value *option) +{ + return insert_dynamic(&arr, (gptr) option); +} + + +inline bool Named_value_arr::replace_element(int idx, Named_value *option) +{ + DBUG_ASSERT(0 <= idx && (uint) idx < arr.elements); + + get_element(idx).free(); + + return set_dynamic(&arr, (gptr) option, idx); +} + +/************************************************************************/ /* tries to find next word in the text @@ -41,7 +180,7 @@ enum { ALPHANUM= 1, NONSPACE }; */ inline void get_word(const char **text, uint *word_len, - int seek_method= ALPHANUM) + enum_seek_method seek_method= ALPHANUM) { const char *word_end; @@ -51,13 +190,23 @@ inline void get_word(const char **text, uint *word_len, word_end= *text; - if (seek_method == ALPHANUM) + switch (seek_method) { + case ALPHANUM: while (my_isalnum(default_charset_info, *word_end)) ++word_end; - else + break; + case NONSPACE: while (!my_isspace(default_charset_info, *word_end) && (*word_end != '\0')) ++word_end; + break; + case OPTION_NAME: + while (my_isalnum(default_charset_info, *word_end) || + *word_end == '-' || + *word_end == '_') + ++word_end; + break; + } *word_len= word_end - *text; } diff --git a/server-tools/instance-manager/parse_output.cc b/server-tools/instance-manager/parse_output.cc index 64bb6a6485f..643a50625a1 100644 --- a/server-tools/instance-manager/parse_output.cc +++ b/server-tools/instance-manager/parse_output.cc @@ -14,13 +14,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <my_global.h> -#include "parse.h" #include "parse_output.h" -#include <stdio.h> +#include <my_global.h> #include <my_sys.h> #include <m_string.h> + +#include <stdio.h> + +#include "parse.h" #include "portability.h" diff --git a/server-tools/instance-manager/parse_output.h b/server-tools/instance-manager/parse_output.h index 6a84fabbf17..b86363a4452 100644 --- a/server-tools/instance-manager/parse_output.h +++ b/server-tools/instance-manager/parse_output.h @@ -16,6 +16,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> + #define GET_VALUE 1 #define GET_LINE 2 diff --git a/server-tools/instance-manager/portability.h b/server-tools/instance-manager/portability.h index 1a3be5705e3..a76cff58893 100644 --- a/server-tools/instance-manager/portability.h +++ b/server-tools/instance-manager/portability.h @@ -16,11 +16,25 @@ /*TODO: fix this */ #define PROTOCOL_VERSION 10 +#define DFLT_CONFIG_FILE_NAME "my.ini" +#define DFLT_MYSQLD_PATH "mysqld" +#define DFLT_PASSWD_FILE_EXT ".passwd" +#define DFLT_PID_FILE_EXT ".pid" +#define DFLT_SOCKET_FILE_EXT ".sock" + typedef int pid_t; #undef popen #define popen(A,B) _popen(A,B) +#define NEWLINE "\r\n" +#define NEWLINE_LEN 2 + +#else /* ! __WIN__ */ + +#define NEWLINE "\n" +#define NEWLINE_LEN 1 + #endif /* __WIN__ */ #endif /* INCLUDES_MYSQL_INSTANCE_MANAGER_PORTABILITY_H */ diff --git a/server-tools/instance-manager/priv.cc b/server-tools/instance-manager/priv.cc index d2d6a3f636c..d3cc52ec638 100644 --- a/server-tools/instance-manager/priv.cc +++ b/server-tools/instance-manager/priv.cc @@ -14,10 +14,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "priv.h" + #include <my_global.h> #include <mysql_com.h> -#include "priv.h" -#include "portability.h" #if defined(__ia64__) || defined(__ia64) /* @@ -43,9 +43,7 @@ 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 LEX_STRING mysqlmanager_version= { C_STRING_WITH_SIZE("1.0-beta") }; const unsigned char protocol_version= PROTOCOL_VERSION; diff --git a/server-tools/instance-manager/priv.h b/server-tools/instance-manager/priv.h index 52d7aa1d23d..0b393c17ac2 100644 --- a/server-tools/instance-manager/priv.h +++ b/server-tools/instance-manager/priv.h @@ -16,13 +16,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <my_global.h> +#include <m_string.h> +#include <my_pthread.h> + #include <sys/types.h> -#ifdef __WIN__ -#include "portability.h" -#else + +#ifndef __WIN__ #include <unistd.h> #endif -#include "my_pthread.h" + +#include "portability.h" /* IM-wide platform-independent defines */ #define SERVER_DEFAULT_PORT 3306 @@ -31,6 +35,21 @@ /* three-week timeout should be enough */ #define LONG_TIMEOUT ((ulong) 3600L*24L*21L) +const int MEM_ROOT_BLOCK_SIZE= 512; + +/* The maximal length of option name and option value. */ +const int MAX_OPTION_LEN= 1024; + +/* + The maximal length of whole option string: + --<option name>=<option value> +*/ +const int MAX_OPTION_STR_LEN= 2 + MAX_OPTION_LEN + 1 + MAX_OPTION_LEN + 1; + +const int MAX_VERSION_LENGTH= 160; + +const int MAX_INSTANCE_NAME_SIZE= FN_REFLEN; + /* the pid of the manager process (of the signal thread on the LinuxThreads) */ extern pid_t manager_pid; @@ -42,8 +61,7 @@ extern pid_t manager_pid; extern bool linuxthreads; #endif -extern const char mysqlmanager_version[]; -extern const int mysqlmanager_version_length; +extern const LEX_STRING mysqlmanager_version; /* MySQL client-server protocol version: substituted from configure */ extern const unsigned char protocol_version; diff --git a/server-tools/instance-manager/protocol.cc b/server-tools/instance-manager/protocol.cc index 73e07f993ae..4a8c4d0b88d 100644 --- a/server-tools/instance-manager/protocol.cc +++ b/server-tools/instance-manager/protocol.cc @@ -163,7 +163,7 @@ int send_fields(struct st_net *net, LIST *fields) Buffer send_buff; char small_buff[4]; uint position= 0; - NAME_WITH_LENGTH *field; + LEX_STRING *field; /* send the number of fileds */ net_store_length(small_buff, (uint) list_length(fields)); @@ -173,7 +173,7 @@ int send_fields(struct st_net *net, LIST *fields) while (tmp) { position= 0; - field= (NAME_WITH_LENGTH *) tmp->data; + field= (LEX_STRING *) tmp->data; store_to_protocol_packet(&send_buff, (char*) "", &position); /* catalog name */ @@ -184,9 +184,9 @@ int send_fields(struct st_net *net, LIST *fields) store_to_protocol_packet(&send_buff, (char*) "", &position); /* table name alias */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name */ + field->str, &position); /* column name */ store_to_protocol_packet(&send_buff, - field->name, &position); /* column name alias */ + field->str, &position); /* column name alias */ send_buff.reserve(position, 12); if (send_buff.is_error()) goto err; diff --git a/server-tools/instance-manager/protocol.h b/server-tools/instance-manager/protocol.h index f38eac6b079..2c84ad394b4 100644 --- a/server-tools/instance-manager/protocol.h +++ b/server-tools/instance-manager/protocol.h @@ -20,11 +20,6 @@ #include <my_list.h> -typedef struct field { - char *name; - uint length; -} NAME_WITH_LENGTH; - /* default field length to be used in various field-realted functions */ enum { DEFAULT_FIELD_LENGTH= 20 }; diff --git a/server-tools/instance-manager/thread_registry.cc b/server-tools/instance-manager/thread_registry.cc index 0091d713a91..a424860548d 100644 --- a/server-tools/instance-manager/thread_registry.cc +++ b/server-tools/instance-manager/thread_registry.cc @@ -20,11 +20,12 @@ #include "thread_registry.h" -#include "log.h" +#include <my_global.h> +#include <thr_alarm.h> -#include <assert.h> #include <signal.h> -#include <thr_alarm.h> + +#include "log.h" #ifndef __WIN__ @@ -52,7 +53,7 @@ Thread_info::Thread_info(pthread_t thread_id_arg) : */ Thread_registry::Thread_registry() : - shutdown_in_progress(false) + shutdown_in_progress(FALSE) ,sigwait_thread_pid(pthread_self()) { pthread_mutex_init(&LOCK_thread_registry, 0); @@ -186,7 +187,7 @@ void Thread_registry::deliver_shutdown() set_timespec(shutdown_time, 1); pthread_mutex_lock(&LOCK_thread_registry); - shutdown_in_progress= true; + shutdown_in_progress= TRUE; #ifndef __WIN__ /* to stop reading from the network we need to flush alarm queue */ diff --git a/server-tools/instance-manager/user_management_commands.cc b/server-tools/instance-manager/user_management_commands.cc new file mode 100644 index 00000000000..8332d2d38de --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.cc @@ -0,0 +1,408 @@ +#if defined(__GNUC__) && defined(USE_PRAGMA_IMPLEMENTATION) +#pragma implementation +#endif + +#include "user_management_commands.h" + +#include "exit_codes.h" +#include "options.h" +#include "user_map.h" + +/************************************************************************* + Module-specific (internal) functions. +*************************************************************************/ + +/* + The function returns user name. The user name is retrieved from command-line + options (if specified) or from console. + + NOTE + This function must not be used in user-management command implementations. + Use get_user_name() instead. + + SYNOPSYS + get_user_name_impl() + + RETURN + NULL on error + valid pointer on success +*/ + +static char *get_user_name_impl() +{ + static char user_name_buf[1024]; + char *ptr; + + if (Options::User_management::user_name) + return Options::User_management::user_name; + + printf("Enter user name: "); + fflush(stdout); + + if (!fgets(user_name_buf, sizeof (user_name_buf), stdin)) + return NULL; + + if ((ptr= strchr(user_name_buf, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_name_buf, '\r'))) + *ptr= 0; + + return user_name_buf; +} + + +/* + The function is intended to provide user name for user-management + operations. It also checks that length of the specified user name is correct + (not empty, not exceeds USERNAME_LENGTH). Report to stderr if something is + wrong. + + SYNOPSYS + get_user_name() + user_name [OUT] on success contains user name + + RETURN + TRUE on error + FALSE on success +*/ + +static bool get_user_name(LEX_STRING *user_name) +{ + char *user_name_str= get_user_name_impl(); + + if (!user_name_str) + { + fprintf(stderr, "Error: unable to read user name from stdin.\n"); + return TRUE; + } + + user_name->str= user_name_str; + user_name->length= strlen(user_name->str); + + if (user_name->length == 0) + { + fprintf(stderr, "Error: user name can not be empty.\n"); + return TRUE; + } + + if (user_name->length > USERNAME_LENGTH) + { + fprintf(stderr, "Error: user name must not exceed %d characters.\n", + (int) USERNAME_LENGTH); + return TRUE; + } + + return FALSE; +} + + +/* + The function is intended to provide password for user-management operations. + The password is retrieved from command-line options (if specified) or from + console. + + SYNOPSYS + get_password() + + RETURN + NULL on error + valid pointer on success +*/ + +static const char *get_password() +{ + if (Options::User_management::password) + return Options::User_management::password; + + const char *passwd1= get_tty_password("Enter password: "); + const char *passwd2= get_tty_password("Re-type password: "); + + if (strcmp(passwd1, passwd2)) + { + fprintf(stderr, "Error: passwords do not match.\n"); + return 0; + } + + return passwd1; +} + + +/* + Load password file into user map. + + SYNOPSYS + load_password_file() + user_map target user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int load_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if (user_map->init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + if ((err_code= user_map->load(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + + +/* + Save user map into password file. + + SYNOPSYS + save_password_file() + user_map user map + + RETURN + See exit_codes.h for possible values. +*/ + +static int save_password_file(User_map *user_map) +{ + int err_code; + const char *err_msg; + + if ((err_code= user_map->save(Options::Main::password_file_name, &err_msg))) + fprintf(stderr, "Error: %s.\n", (const char *) err_msg); + + return err_code; +} + +/************************************************************************* + Passwd_cmd +*************************************************************************/ + +int Passwd_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + + printf("Creating record for new user.\n"); + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + { + User user(&user_name, password); + + printf("%s:%s\n", + (const char *) user.user, + (const char *) user.scrambled_password); + } + + return ERR_OK; +} + + +/************************************************************************* + Add_user_cmd +*************************************************************************/ + +int Add_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + char scrambled_password_buf[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; + + User_map user_map; + User *new_user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Check that the user does not exist. */ + + if (user_map.find_user(&user_name)) + { + fprintf(stderr, "Error: user '%s' already exists.\n", + (const char *) user_name.str); + return ERR_USER_ALREADY_EXISTS; + } + + /* Add the user. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + if (!(new_user= new User(&user_name, password))) + return ERR_OUT_OF_MEMORY; + + if (user_map.add_user(new_user)) + { + delete new_user; + return ERR_OUT_OF_MEMORY; + } + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Drop_user_cmd +*************************************************************************/ + +int Drop_user_cmd::execute() +{ + LEX_STRING user_name; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Remove the user (ignore possible errors). */ + + user_map.remove_user(user); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Edit_user_cmd +*************************************************************************/ + +int Edit_user_cmd::execute() +{ + LEX_STRING user_name; + const char *password; + char scrambled_password_buf[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; + + User_map user_map; + User *user; + + int err_code; + + if (get_user_name(&user_name)) + return ERR_CAN_NOT_READ_USER_NAME; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map)) != ERR_OK) + return err_code; + + /* Find the user. */ + + user= user_map.find_user(&user_name); + + if (!user) + { + fprintf(stderr, "Error: user '%s' does not exist.\n", + (const char *) user_name.str); + return ERR_USER_NOT_FOUND; + } + + /* Modify user's password. */ + + if (!(password= get_password())) + return ERR_CAN_NOT_READ_PASSWORD; + + user->set_password(password); + + /* Save the password file. */ + + return save_password_file(&user_map); +} + + +/************************************************************************* + Clean_db_cmd +*************************************************************************/ + +int Clean_db_cmd::execute() +{ + User_map user_map; + + if (user_map.init()) + { + fprintf(stderr, "Error: can not initialize user map.\n"); + return ERR_OUT_OF_MEMORY; + } + + return save_password_file(&user_map); +} + + +/************************************************************************* + Check_db_cmd +*************************************************************************/ + +int Check_db_cmd::execute() +{ + User_map user_map; + + return load_password_file(&user_map); +} + + +/************************************************************************* + List_users_cmd +*************************************************************************/ + +int List_users_cmd::execute() +{ + User_map user_map; + + int err_code; + + /* Load the password file. */ + + if ((err_code= load_password_file(&user_map))) + return err_code; + + /* Print out registered users. */ + + { + User_map::Iterator it(&user_map); + User *user; + + while ((user= it.next())) + fprintf(stderr, "%s\n", (const char *) user->user); + } + + return ERR_OK; +} diff --git a/server-tools/instance-manager/user_management_commands.h b/server-tools/instance-manager/user_management_commands.h new file mode 100644 index 00000000000..4bf3546f0a6 --- /dev/null +++ b/server-tools/instance-manager/user_management_commands.h @@ -0,0 +1,167 @@ +#ifndef INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H +#define INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H + +/* + Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + This header contains declarations of classes inteded to support + user-management commands (such as add user, get list of users, etc). + + The general idea is to have one interface (pure abstract class) for such a + command. Each concrete user-management command is implemented in concrete + class, derived from the common interface. +*/ + +#if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) +#pragma interface +#endif + +/************************************************************************* + User_management_cmd -- base class for User-management commands. +*************************************************************************/ + +class User_management_cmd +{ +public: + User_management_cmd() + { } + + virtual ~User_management_cmd() + { } + +public: + /* + Executes user-management command. + + SYNOPSYS + execute() + + RETURN + See exit_codes.h for possible values. + */ + + virtual int execute() = 0; +}; + + +/************************************************************************* + Passwd_cmd: support for --passwd command-line option. +*************************************************************************/ + +class Passwd_cmd : public User_management_cmd +{ +public: + Passwd_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Add_user_cmd: support for --add-user command-line option. +*************************************************************************/ + +class Add_user_cmd : public User_management_cmd +{ +public: + Add_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Drop_user_cmd: support for --drop-user command-line option. +*************************************************************************/ + +class Drop_user_cmd : public User_management_cmd +{ +public: + Drop_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Edit_user_cmd: support for --edit-user command-line option. +*************************************************************************/ + +class Edit_user_cmd : public User_management_cmd +{ +public: + Edit_user_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Clean_db_cmd: support for --clean-db command-line option. +*************************************************************************/ + +class Clean_db_cmd : public User_management_cmd +{ +public: + Clean_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + Check_db_cmd: support for --check-db command-line option. +*************************************************************************/ + +class Check_db_cmd : public User_management_cmd +{ +public: + Check_db_cmd() + { } + +public: + virtual int execute(); +}; + + +/************************************************************************* + List_users_cmd: support for --list-users command-line option. +*************************************************************************/ + +class List_users_cmd : public User_management_cmd +{ +public: + List_users_cmd() + { } + +public: + virtual int execute(); +}; + +#endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MANAGEMENT_CMD_H diff --git a/server-tools/instance-manager/user_map.cc b/server-tools/instance-manager/user_map.cc index 9cb15307131..2f957be426c 100644 --- a/server-tools/instance-manager/user_map.cc +++ b/server-tools/instance-manager/user_map.cc @@ -19,32 +19,31 @@ #endif #include "user_map.h" - -#include <mysql_com.h> -#include <m_string.h> - +#include "exit_codes.h" #include "log.h" -struct User +User::User(const LEX_STRING *user_name_arg, const char *password) { - char user[USERNAME_LENGTH + 1]; - uint8 user_length; - uint8 salt[SCRAMBLE_LENGTH]; - int init(const char *line); -}; + user_length= strmake(user, user_name_arg->str, USERNAME_LENGTH + 1) - user; + set_password(password); +} int User::init(const char *line) { const char *name_begin, *name_end, *password; - int line_ending_len= 1; + int password_length; if (line[0] == '\'' || line[0] == '"') { name_begin= line + 1; name_end= strchr(name_begin, line[0]); if (name_end == 0 || name_end[1] != ':') - goto err; + { + log_info("Error: invalid format (unmatched quote) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 2; } else @@ -52,33 +51,47 @@ int User::init(const char *line) name_begin= line; name_end= strchr(name_begin, ':'); if (name_end == 0) - goto err; + { + log_info("Error: invalid format (no delimiter) of user line (%s).", + (const char *) line); + return 1; + } password= name_end + 1; } + user_length= name_end - name_begin; if (user_length > USERNAME_LENGTH) - goto err; - - /* - assume that newline characater is present - we support reading password files that end in \n or \r\n on - either platform. - */ - if (password[strlen(password)-2] == '\r') - line_ending_len= 2; - if (strlen(password) != (uint) (SCRAMBLED_PASSWORD_CHAR_LENGTH + - line_ending_len)) - goto err; + { + log_info("Error: user name is too long (%d). Max length: %d. " + "User line: '%s'.", + (int) user_length, + (int) USERNAME_LENGTH, + (const char *) line); + return 1; + } + + password_length= strlen(password); + if (password_length > SCRAMBLED_PASSWORD_CHAR_LENGTH) + { + log_info("Error: password is too long (%d). Max length: %d. ", + "User line: '%s'.", + (int) password_length, + (int) SCRAMBLED_PASSWORD_CHAR_LENGTH, + (const char *) line); + return 1; + } memcpy(user, name_begin, user_length); user[user_length]= 0; + + memcpy(scrambled_password, password, password_length); + scrambled_password[password_length]= 0; + get_salt_from_password(salt, password); - log_info("loaded user %s", user); + + log_info("loaded user '%s'.", user); return 0; -err: - log_error("error parsing user and password at line %s", line); - return 1; } @@ -101,30 +114,70 @@ static void delete_user(void *u) C_MODE_END +void User_map::Iterator::reset() +{ + cur_idx= 0; +} + + +User *User_map::Iterator::next() +{ + if (cur_idx < user_map->hash.records) + return (User *) hash_element(&user_map->hash, cur_idx++); + + return NULL; +} + + int User_map::init() { enum { START_HASH_SIZE= 16 }; if (hash_init(&hash, default_charset_info, START_HASH_SIZE, 0, 0, get_user_key, delete_user, 0)) return 1; + + initialized= TRUE; + return 0; } +User_map::User_map() + :initialized(FALSE) +{ +} + + User_map::~User_map() { - hash_free(&hash); + if (initialized) + hash_free(&hash); } /* - Load all users from the password file. Must be called once right after - construction. - In case of failure, puts error message to the log file and returns 1 + Load password database. + + SYNOPSYS + load() + password_file_name [IN] password file path + err_msg [OUT] error message + + DESCRIPTION + Load all users from the password file. Must be called once right after + construction. In case of failure, puts error message to the log file and + returns specific error code. + + RETURN + 0 on success + !0 on error */ -int User_map::load(const char *password_file_name) +int User_map::load(const char *password_file_name, const char **err_msg) { + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + FILE *file; char line[USERNAME_LENGTH + SCRAMBLED_PASSWORD_CHAR_LENGTH + 2 + /* for possible quotes */ @@ -134,33 +187,172 @@ int User_map::load(const char *password_file_name) User *user; int rc= 1; + if (my_access(password_file_name, F_OK) != 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) does not exist", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_DOES_NOT_EXIST; + } + if ((file= my_fopen(password_file_name, O_RDONLY | O_BINARY, MYF(0))) == 0) { - /* Probably the password file wasn't specified. Try to leave without it */ - log_info("[WARNING] can't open password file %s: errno=%d, %s", password_file_name, - errno, strerror(errno)); - return 0; + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; } + log_info("loading the password database..."); + while (fgets(line, sizeof(line), file)) { + char *user_line= line; + + /* + We need to skip EOL-symbols also from the beginning of the line, because + if the previous line was ended by \n\r sequence, we get \r in our line. + */ + + while (user_line[0] == '\r' || user_line[0] == '\n') + ++user_line; + + /* Skip EOL-symbols in the end of the line. */ + + { + char *ptr; + + if ((ptr= strchr(user_line, '\n'))) + *ptr= 0; + + if ((ptr= strchr(user_line, '\r'))) + *ptr= 0; + } + /* skip comments and empty lines */ - if (line[0] == '#' || line[0] == '\n' && - (line[1] == '\0' || line[1] == '\r')) + if (!user_line[0] || user_line[0] == '#') continue; + if ((user= new User) == 0) - goto done; - if (user->init(line) || my_hash_insert(&hash, (byte *) user)) - goto err_init_user; + { + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } + + if (user->init(user_line)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "password file (%s) corrupted", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_PASSWORD_FILE_CORRUPTED; + } + + if (my_hash_insert(&hash, (byte *) user)) + { + delete user; + my_fclose(file, MYF(0)); + + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "out of memory while parsing password file (%s)", + (const char *) password_file_name); + *err_msg= err_msg_buf; + } + + return ERR_OUT_OF_MEMORY; + } } - if (feof(file)) - rc= 0; - goto done; -err_init_user: - delete user; -done: + + log_info("the password database loaded successfully."); + my_fclose(file, MYF(0)); - return rc; + + if (err_msg) + *err_msg= NULL; + + return ERR_OK; +} + + +int User_map::save(const char *password_file_name, const char **err_msg) +{ + static const int ERR_MSG_BUF_SIZE = 255; + static char err_msg_buf[ERR_MSG_BUF_SIZE]; + + FILE *file; + + if ((file= my_fopen(password_file_name, O_WRONLY | O_TRUNC | O_BINARY, + MYF(0))) == 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not open password file (%s) for writing: %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + return ERR_IO_ERROR; + } + + { + User_map::Iterator it(this); + User *user; + + while ((user= it.next())) + { + if (fprintf(file, "%s:%s\n", (const char *) user->user, + (const char *) user->scrambled_password) < 0) + { + if (err_msg) + { + snprintf(err_msg_buf, ERR_MSG_BUF_SIZE, + "can not write to password file (%s): %s", + (const char *) password_file_name, + (const char *) strerror(errno)); + *err_msg= err_msg_buf; + } + + my_fclose(file, MYF(0)); + + return ERR_IO_ERROR; + } + } + } + + my_fclose(file, MYF(0)); + + return ERR_OK; } @@ -172,13 +364,33 @@ done: 2 - user not found */ -int User_map::authenticate(const char *user_name, uint length, +int User_map::authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const { - const User *user= (const User *) hash_search((HASH *) &hash, - (byte *) user_name, length); - if (user) - return check_scramble(scrambled_password, scramble, user->salt); - return 2; + const User *user= find_user(user_name); + return user ? check_scramble(scrambled_password, scramble, user->salt) : 2; +} + + +User *User_map::find_user(const LEX_STRING *user_name) +{ + return (User*) hash_search(&hash, (byte*) user_name->str, user_name->length); +} + +const User *User_map::find_user(const LEX_STRING *user_name) const +{ + return const_cast<User_map *> (this)->find_user(user_name); +} + + +bool User_map::add_user(User *user) +{ + return my_hash_insert(&hash, (byte*) user) == 0 ? FALSE : TRUE; +} + + +bool User_map::remove_user(User *user) +{ + return hash_delete(&hash, (byte*) user) == 0 ? FALSE : TRUE; } diff --git a/server-tools/instance-manager/user_map.h b/server-tools/instance-manager/user_map.h index 4134017dd9b..de207c11e65 100644 --- a/server-tools/instance-manager/user_map.h +++ b/server-tools/instance-manager/user_map.h @@ -18,14 +18,35 @@ #include <my_global.h> - #include <my_sys.h> +#include <mysql_com.h> +#include <m_string.h> #include <hash.h> #if defined(__GNUC__) && defined(USE_PRAGMA_INTERFACE) #pragma interface #endif +struct User +{ + User() + {} + + User(const LEX_STRING *user_name_arg, const char *password); + + int init(const char *line); + + inline void set_password(const char *password) + { + make_scrambled_password(scrambled_password, password); + } + + char user[USERNAME_LENGTH + 1]; + char scrambled_password[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1]; + uint8 user_length; + uint8 salt[SCRAMBLE_LENGTH]; +}; + /* User_map -- all users and passwords */ @@ -33,15 +54,51 @@ class User_map { public: + /* User_map iterator */ + + class Iterator + { + public: + Iterator(User_map *user_map_arg) : + cur_idx(0), user_map(user_map_arg) + { } + + public: + void reset(); + + User *next(); + + private: + User_map *user_map; + uint cur_idx; + }; + +public: + User_map(); ~User_map(); int init(); - int load(const char *password_file_name); - int authenticate(const char *user_name, uint length, + int load(const char *password_file_name, const char **err_msg); + int save(const char *password_file_name, const char **err_msg); + int authenticate(const LEX_STRING *user_name, const char *scrambled_password, const char *scramble) const; + + const User *find_user(const LEX_STRING *user_name) const; + User *find_user(const LEX_STRING *user_name); + + bool add_user(User *user); + bool remove_user(User *user); + +private: + User_map(const User_map &); + User_map &operator =(const User_map &); + private: HASH hash; + bool initialized; + + friend class Iterator; }; #endif // INCLUDES_MYSQL_INSTANCE_MANAGER_USER_MAP_H diff --git a/sql/sp.cc b/sql/sp.cc index 7fc388860d1..77d91be28b9 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -409,15 +409,13 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp, ulong old_sql_mode= thd->variables.sql_mode; ha_rows old_select_limit= thd->variables.select_limit; sp_rcontext *old_spcont= thd->spcont; - + char definer_user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT definer_user_name(definer_user_name_holder, - USERNAME_LENGTH); + LEX_STRING definer_user_name= { definer_user_name_holder, USERNAME_LENGTH }; char definer_host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT definer_host_name(definer_host_name_holder, - HOSTNAME_LENGTH); - + LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH }; + int ret; thd->variables.sql_mode= sql_mode; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 47d4326d780..2707cb76281 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1850,10 +1850,10 @@ void sp_head::set_definer(const char *definer, uint definerlen) { char user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT user_name(user_name_holder, USERNAME_LENGTH); + LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH }; char host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT host_name(host_name_holder, HOSTNAME_LENGTH); + LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH }; parse_user(definer, definerlen, user_name.str, &user_name.length, host_name.str, &host_name.length); diff --git a/sql/spatial.cc b/sql/spatial.cc index e91653f79d5..9f1f05aa18f 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -34,8 +34,11 @@ static Geometry::Class_info **ci_collection_end= Geometry::Class_info::Class_info(const char *name, int type_id, void(*create_func)(void *)): - m_name(name, strlen(name)), m_type_id(type_id), m_create_func(create_func) + m_type_id(type_id), m_create_func(create_func) { + m_name.str= (char *) name; + m_name.length= strlen(name); + ci_collection[type_id]= this; } diff --git a/sql/spatial.h b/sql/spatial.h index a6f74a1ada0..36949ff5014 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -200,7 +200,7 @@ public: class Class_info { public: - LEX_STRING_WITH_INIT m_name; + LEX_STRING m_name; int m_type_id; void (*m_create_func)(void *); Class_info(const char *name, int type_id, void(*create_func)(void *)); diff --git a/sql/sql_string.h b/sql/sql_string.h index 0659f684afe..ddae6368228 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -24,8 +24,6 @@ #define NOT_FIXED_DEC 31 #endif -#define STRING_WITH_LEN(X) ((const char*) X), ((uint) (sizeof(X) - 1)) - class String; int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index b2b6b115f7d..60a64f9fc1f 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1412,8 +1412,8 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, } if (table.triggers) { - LEX_STRING_WITH_INIT old_table_name(old_table, strlen(old_table)); - LEX_STRING_WITH_INIT new_table_name(new_table, strlen(new_table)); + LEX_STRING old_table_name= { (char *) STRING_WITH_LEN(old_table) }; + LEX_STRING new_table_name= { (char *) STRING_WITH_LEN(new_table) }; /* Since triggers should be in the same schema as their subject tables moving table with them between two schemas raises too many questions. diff --git a/sql/structs.h b/sql/structs.h index e369d8ed7e8..dc835db1e99 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -20,22 +20,6 @@ struct st_table; class Field; -typedef struct st_lex_string -{ - char *str; - uint length; -} LEX_STRING; - -typedef struct st_lex_string_with_init :public st_lex_string -{ - st_lex_string_with_init(const char *str_arg, uint length_arg) - { - str= (char*) str_arg; - length= length_arg; - } -} LEX_STRING_WITH_INIT; - - typedef struct st_date_time_format { uchar positions[8]; char time_separator; /* Separator between hour and minute */ diff --git a/support-files/mysql.server.sh b/support-files/mysql.server.sh index bf17375c0eb..bd16297f8e7 100644 --- a/support-files/mysql.server.sh +++ b/support-files/mysql.server.sh @@ -265,7 +265,10 @@ case "$mode" in then # Give extra arguments to mysqld with the my.cnf file. This script may # be overwritten at next upgrade. - $manager --user=$user --pid-file=$pid_file >/dev/null 2>&1 & + "$manager" \ + --mysqld-safe-compatible \ + --user="$user" \ + --pid-file="$pid_file" >/dev/null 2>&1 & wait_for_pid created # Make lock for RedHat / SuSE |