mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-30 18:36:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2412 lines
		
	
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2412 lines
		
	
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright 2008-2022 Codership Oy <http://www.codership.com>
 | |
|    Copyright (c) 2008, 2022, MariaDB
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
 | |
| 
 | |
| #include "mariadb.h"
 | |
| #include "wsrep_sst.h"
 | |
| #include <inttypes.h>
 | |
| #include <ctype.h>
 | |
| #include <mysqld.h>
 | |
| #include <m_ctype.h>
 | |
| #include <strfunc.h>
 | |
| #include <sql_base.h>
 | |
| #include <sql_class.h>
 | |
| #include <set_var.h>
 | |
| #include <sql_acl.h>
 | |
| #include <sql_reload.h>
 | |
| #include <sql_parse.h>
 | |
| #include "wsrep_priv.h"
 | |
| #include "wsrep_utils.h"
 | |
| #include "wsrep_xid.h"
 | |
| #include "wsrep_thd.h"
 | |
| #include "wsrep_server_state.h"
 | |
| 
 | |
| #include <cstdio>
 | |
| #include <cstdlib>
 | |
| #include "debug_sync.h"
 | |
| #include "my_rnd.h"
 | |
| 
 | |
| #include <my_service_manager.h>
 | |
| 
 | |
| static char wsrep_defaults_file[FN_REFLEN * 2 + 10 + 30 +
 | |
|                                 sizeof(WSREP_SST_OPT_CONF) +
 | |
|                                 sizeof(WSREP_SST_OPT_CONF_SUFFIX) +
 | |
|                                 sizeof(WSREP_SST_OPT_CONF_EXTRA)]= {0};
 | |
| 
 | |
| const char* wsrep_sst_method         = WSREP_SST_DEFAULT;
 | |
| const char* wsrep_sst_receive_address= WSREP_SST_ADDRESS_AUTO;
 | |
| const char* wsrep_sst_donor          = "";
 | |
| const char* wsrep_sst_auth           = NULL;
 | |
| 
 | |
| // container for real auth string
 | |
| static const char* sst_auth_real     = NULL;
 | |
| my_bool wsrep_sst_donor_rejects_queries= FALSE;
 | |
| 
 | |
| #define WSREP_EXTEND_TIMEOUT_INTERVAL 60
 | |
| #define WSREP_TIMEDWAIT_SECONDS 30
 | |
| 
 | |
| bool sst_joiner_completed            = false;
 | |
| bool sst_donor_completed             = false;
 | |
| 
 | |
| struct sst_auth
 | |
| {
 | |
|   std::string name_;
 | |
|   std::string pswd_;
 | |
|   std::string remote_name_;
 | |
|   std::string remote_pswd_;
 | |
| };
 | |
| 
 | |
| struct sst_thread_arg
 | |
| {
 | |
|   const char*     method_;
 | |
|   const char*     cmd;
 | |
|   const sst_auth& auth_;
 | |
|   char**          env;
 | |
|   char*           ret_str;
 | |
|   int             err;
 | |
|   mysql_mutex_t   lock;
 | |
|   mysql_cond_t    cond;
 | |
| 
 | |
|   sst_thread_arg (const char* m, const char* c, const sst_auth& auth, char** e)
 | |
|     : method_(m), cmd(c), auth_(auth), env(e), ret_str(0), err(-1)
 | |
|   {
 | |
|     mysql_mutex_init(key_LOCK_wsrep_sst_thread, &lock, MY_MUTEX_INIT_FAST);
 | |
|     mysql_cond_init(key_COND_wsrep_sst_thread, &cond, NULL);
 | |
|   }
 | |
| 
 | |
|   ~sst_thread_arg()
 | |
|   {
 | |
|     mysql_cond_destroy  (&cond);
 | |
|     mysql_mutex_unlock  (&lock);
 | |
|     mysql_mutex_destroy (&lock);
 | |
|   }
 | |
| };
 | |
| 
 | |
| static void wsrep_donor_monitor_end(void)
 | |
| {
 | |
|   mysql_mutex_lock(&LOCK_wsrep_donor_monitor);
 | |
|   sst_donor_completed= true;
 | |
|   mysql_cond_signal(&COND_wsrep_donor_monitor);
 | |
|   mysql_mutex_unlock(&LOCK_wsrep_donor_monitor);
 | |
| }
 | |
| 
 | |
| static void wsrep_joiner_monitor_end(void)
 | |
| {
 | |
|   mysql_mutex_lock(&LOCK_wsrep_joiner_monitor);
 | |
|   sst_joiner_completed= true;
 | |
|   mysql_cond_signal(&COND_wsrep_joiner_monitor);
 | |
|   mysql_mutex_unlock(&LOCK_wsrep_joiner_monitor);
 | |
| }
 | |
| 
 | |
| static void* wsrep_sst_donor_monitor_thread(void *arg __attribute__((unused)))
 | |
| {
 | |
|   int ret= 0;
 | |
|   unsigned long time_waited= 0;
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_wsrep_donor_monitor);
 | |
| 
 | |
|   WSREP_INFO("Donor monitor thread started to monitor");
 | |
| 
 | |
|   wsp::thd thd;
 | |
| 
 | |
|   while (!sst_donor_completed)
 | |
|   {
 | |
|     timespec ts;
 | |
|     set_timespec(ts, WSREP_TIMEDWAIT_SECONDS);
 | |
|     time_t start_time= time(NULL);
 | |
|     ret= mysql_cond_timedwait(&COND_wsrep_donor_monitor, &LOCK_wsrep_donor_monitor, &ts);
 | |
|     time_t end_time= time(NULL);
 | |
|     time_waited+= difftime(end_time, start_time);
 | |
| 
 | |
|     if (ret == ETIMEDOUT && !sst_donor_completed)
 | |
|     {
 | |
|       WSREP_DEBUG("Donor waited %lu sec, extending systemd startup timeout as SST"
 | |
|                  "is not completed",
 | |
|                  time_waited);
 | |
|       service_manager_extend_timeout(WSREP_EXTEND_TIMEOUT_INTERVAL,
 | |
|         "WSREP state transfer (role donor) ongoing...");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   WSREP_INFO("Donor monitor thread ended with total time %lu sec", time_waited);
 | |
|   mysql_mutex_unlock(&LOCK_wsrep_donor_monitor);
 | |
| 
 | |
|   sd_notify(0, "STATUS=WSREP state transfer (role donor) completed.\n");
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static void* wsrep_sst_joiner_monitor_thread(void *arg __attribute__((unused)))
 | |
| {
 | |
|   int ret= 0;
 | |
|   unsigned long time_waited= 0;
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_wsrep_joiner_monitor);
 | |
| 
 | |
|   WSREP_INFO("Joiner monitor thread started to monitor");
 | |
| 
 | |
|   wsp::thd thd;
 | |
| 
 | |
|   while (!sst_joiner_completed)
 | |
|   {
 | |
|     timespec ts;
 | |
|     set_timespec(ts, WSREP_TIMEDWAIT_SECONDS);
 | |
|     time_t start_time= time(NULL);
 | |
|     ret= mysql_cond_timedwait(&COND_wsrep_joiner_monitor, &LOCK_wsrep_joiner_monitor, &ts);
 | |
|     time_t end_time= time(NULL);
 | |
|     time_waited+= difftime(end_time, start_time);
 | |
| 
 | |
|     if (ret == ETIMEDOUT && !sst_joiner_completed)
 | |
|     {
 | |
|       WSREP_DEBUG("Joiner waited %lu sec, extending systemd startup timeout as SST"
 | |
|                  "is not completed",
 | |
|                  time_waited);
 | |
|       service_manager_extend_timeout(WSREP_EXTEND_TIMEOUT_INTERVAL,
 | |
|         "WSREP state transfer (role joiner) ongoing...");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   WSREP_INFO("Joiner monitor thread ended with total time %lu sec", time_waited);
 | |
|   mysql_mutex_unlock(&LOCK_wsrep_joiner_monitor);
 | |
| 
 | |
|   sd_notify(0, "STATUS=WSREP state transfer (role joiner) completed.\n");
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| /* return true if character can be a part of a filename */
 | |
| static bool filename_char(int const c)
 | |
| {
 | |
|   return isalnum(c) || (c == '-') || (c == '_') || (c == '.');
 | |
| }
 | |
| 
 | |
| /* return true if character can be a part of an address string */
 | |
| static bool address_char(int const c)
 | |
| {
 | |
|   return filename_char(c) ||
 | |
|          (c == ':') || (c == '[') || (c == ']') || (c == '/');
 | |
| }
 | |
| 
 | |
| static bool check_request_str(const char* const str,
 | |
|                               bool (*check) (int c),
 | |
|                               bool log_warn = true)
 | |
| {
 | |
|   for (size_t i(0); str[i] != '\0'; ++i)
 | |
|   {
 | |
|     if (!check(str[i]))
 | |
|     {
 | |
|       if (log_warn) WSREP_WARN("Illegal character in state transfer request: %i (%c).",
 | |
|                                str[i], str[i]);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_method_check (sys_var *self, THD* thd, set_var* var)
 | |
| {
 | |
|   if ((! var->save_result.string_value.str) ||
 | |
|       (var->save_result.string_value.length == 0 ))
 | |
|   {
 | |
|     my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str,
 | |
|              var->save_result.string_value.str ?
 | |
|              var->save_result.string_value.str : "NULL");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   /* check also that method name is alphanumeric string  */
 | |
|   if (check_request_str(var->save_result.string_value.str,
 | |
|                         filename_char, false))
 | |
|   {
 | |
|     my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str,
 | |
|              var->save_result.string_value.str ?
 | |
|              var->save_result.string_value.str : "NULL");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static const char* data_home_dir;
 | |
| 
 | |
| void wsrep_set_data_home_dir(const char *data_dir)
 | |
| {
 | |
|   data_home_dir= (data_dir && *data_dir) ? data_dir : NULL;
 | |
| }
 | |
| 
 | |
| static void make_wsrep_defaults_file()
 | |
| {
 | |
|   if (!wsrep_defaults_file[0])
 | |
|   {
 | |
|     char *ptr= wsrep_defaults_file;
 | |
|     char *end= ptr + sizeof(wsrep_defaults_file);
 | |
|     if (my_defaults_file)
 | |
|       ptr= strxnmov(ptr, end - ptr,
 | |
|                     WSREP_SST_OPT_CONF, " '", my_defaults_file, "' ", NULL);
 | |
| 
 | |
|     if (my_defaults_extra_file)
 | |
|       ptr= strxnmov(ptr, end - ptr,
 | |
|                     WSREP_SST_OPT_CONF_EXTRA, " '", my_defaults_extra_file, "' ", NULL);
 | |
| 
 | |
|     if (my_defaults_group_suffix)
 | |
|       ptr= strxnmov(ptr, end - ptr,
 | |
|                     WSREP_SST_OPT_CONF_SUFFIX, " '", my_defaults_group_suffix, "' ", NULL);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| bool  wsrep_sst_receive_address_check (sys_var *self, THD* thd, set_var* var)
 | |
| {
 | |
|   if ((! var->save_result.string_value.str) ||
 | |
|       (var->save_result.string_value.length > (FN_REFLEN - 1))) // safety
 | |
|   {
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| 
 | |
| err:
 | |
|   my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str,
 | |
|            var->save_result.string_value.str ?
 | |
|            var->save_result.string_value.str : "NULL");
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_receive_address_update (sys_var *self, THD* thd,
 | |
|                                        enum_var_type type)
 | |
| {
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_auth_check (sys_var *self, THD* thd, set_var* var)
 | |
| {
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void wsrep_sst_auth_free()
 | |
| {
 | |
|   if (wsrep_sst_auth) { my_free(const_cast<char *>(wsrep_sst_auth)); }
 | |
|   if (sst_auth_real) { my_free(const_cast<char *>(sst_auth_real)); }
 | |
|   wsrep_sst_auth= NULL;
 | |
|   sst_auth_real= NULL;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_auth_set (const char* value)
 | |
| {
 | |
|   static const char* const auth_mask("********");
 | |
|   const char* v= NULL;
 | |
| 
 | |
|   if (value && *value != '\0') // empty string means unset
 | |
|   {
 | |
|     // save new value
 | |
|     v= my_strdup(PSI_INSTRUMENT_ME, value, MYF(0));
 | |
|     if (!v) return 1;
 | |
|   }
 | |
| 
 | |
|   // cleanup old values
 | |
|   wsrep_sst_auth_free();
 | |
| 
 | |
|   if (v)
 | |
|   {
 | |
|     // mask wsrep_sst_auth
 | |
|     wsrep_sst_auth = my_strdup(PSI_INSTRUMENT_ME, auth_mask, MYF(0));
 | |
|     // set sst_auth_real to actual value
 | |
|     sst_auth_real= v;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_auth_update (sys_var *self, THD* thd, enum_var_type type)
 | |
| {
 | |
|   return wsrep_sst_auth_set(wsrep_sst_auth);
 | |
| }
 | |
| 
 | |
| bool  wsrep_sst_donor_check (sys_var *self, THD* thd, set_var* var)
 | |
| {
 | |
|   if ((! var->save_result.string_value.str) ||
 | |
|       (var->save_result.string_value.length > (FN_REFLEN -1))) // safety
 | |
|   {
 | |
|     my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name.str,
 | |
|              var->save_result.string_value.str ?
 | |
|              var->save_result.string_value.str : "NULL");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| bool wsrep_sst_donor_update (sys_var *self, THD* thd, enum_var_type type)
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool wsrep_before_SE()
 | |
| {
 | |
|   return (wsrep_provider
 | |
|           && *wsrep_provider
 | |
|           && strcmp (wsrep_provider,   WSREP_NONE)
 | |
|           && strcmp (wsrep_sst_method, WSREP_SST_SKIP)
 | |
|           && strcmp (wsrep_sst_method, WSREP_SST_MYSQLDUMP));
 | |
| }
 | |
| 
 | |
| // Signal end of SST
 | |
| static bool wsrep_sst_complete (THD*                thd,
 | |
|                                 int const           rcode,
 | |
|                                 wsrep::gtid const   sst_gtid)
 | |
| {
 | |
|   Wsrep_client_service client_service(thd, thd->wsrep_cs());
 | |
|   Wsrep_server_state& server_state= Wsrep_server_state::instance();
 | |
|   enum wsrep::server_state::state state= server_state.state();
 | |
|   bool failed= false;
 | |
|   char start_pos_buf[FN_REFLEN];
 | |
|   ssize_t len= wsrep::print_to_c_str(sst_gtid, start_pos_buf, FN_REFLEN-1);
 | |
|   start_pos_buf[len]='\0';
 | |
| 
 | |
|   // Do not call sst_received if we are not in joiner or
 | |
|   // initialized state on server. This is because it
 | |
|   // assumes we are on those states. Give error if we are
 | |
|   // in incorrect state.
 | |
|   if ((state == Wsrep_server_state::s_joiner ||
 | |
|        state == Wsrep_server_state::s_initialized))
 | |
|   {
 | |
|     if (Wsrep_server_state::instance().sst_received(client_service, rcode))
 | |
|     {
 | |
|       failed= true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       WSREP_INFO("SST succeeded for position %s", start_pos_buf);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     WSREP_ERROR("SST failed for position %s initialized %d server_state %s",
 | |
|                 start_pos_buf,
 | |
|                 server_state.is_initialized(),
 | |
|                 wsrep::to_c_string(state));
 | |
|     failed= true;
 | |
|   }
 | |
| 
 | |
|   wsrep_joiner_monitor_end();
 | |
|   return failed;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   If wsrep provider is loaded, inform that the new state snapshot
 | |
|   has been received. Also update the local checkpoint.
 | |
| 
 | |
|   @param thd       [IN]
 | |
|   @param uuid      [IN]               Initial state UUID
 | |
|   @param seqno     [IN]               Initial state sequence number
 | |
|   @param state     [IN]               Always NULL, also ignored by wsrep provider (?)
 | |
|   @param state_len [IN]               Always 0, also ignored by wsrep provider (?)
 | |
|   @return true when successful, false if error
 | |
| */
 | |
| bool wsrep_sst_received (THD*                thd,
 | |
|                          const wsrep_uuid_t& uuid,
 | |
|                          wsrep_seqno_t const seqno,
 | |
|                          const void* const   state,
 | |
|                          size_t const        state_len)
 | |
| {
 | |
|   bool error= false;
 | |
|   /*
 | |
|     To keep track of whether the local uuid:seqno should be updated. Also, note
 | |
|     that local state (uuid:seqno) is updated/checkpointed only after we get an
 | |
|     OK from wsrep provider. By doing so, the values remain consistent across
 | |
|     the server & wsrep provider.
 | |
|   */
 | |
|     /*
 | |
|       TODO: Handle backwards compatibility. WSREP API v25 does not have
 | |
|       wsrep schema.
 | |
|     */
 | |
|     /*
 | |
|       Logical SST methods (mysqldump etc) don't update InnoDB sys header.
 | |
|       Reset the SE checkpoint before recovering view in order to avoid
 | |
|       sanity check failure.
 | |
|      */
 | |
|     wsrep::gtid const sst_gtid(wsrep::id(uuid.data, sizeof(uuid.data)),
 | |
|                                wsrep::seqno(seqno));
 | |
| 
 | |
|     if (!wsrep_before_SE()) {
 | |
|       wsrep_set_SE_checkpoint(wsrep::gtid::undefined(), wsrep_gtid_server.undefined());
 | |
|       wsrep_set_SE_checkpoint(sst_gtid, wsrep_gtid_server.gtid());
 | |
|     }
 | |
|     wsrep_verify_SE_checkpoint(uuid, seqno);
 | |
| 
 | |
|     /*
 | |
|       Both wsrep_init_SR() and wsrep_recover_view() may use
 | |
|       wsrep thread pool. Restore original thd context before returning.
 | |
|     */
 | |
|     if (thd) {
 | |
|       wsrep_store_threadvars(thd);
 | |
|     }
 | |
|     else {
 | |
|       set_current_thd(nullptr);
 | |
|     }
 | |
| 
 | |
|     /* During sst WSREP(thd) is not yet set for joiner. */
 | |
|     if (WSREP_ON)
 | |
|     {
 | |
|       int const rcode(seqno < 0 ? seqno : 0);
 | |
|       error= wsrep_sst_complete(thd, rcode, sst_gtid);
 | |
|     }
 | |
| 
 | |
|     return error;
 | |
| }
 | |
| 
 | |
| static int sst_scan_uuid_seqno (const char* str,
 | |
|                                 wsrep_uuid_t* uuid, wsrep_seqno_t* seqno)
 | |
| {
 | |
|   int offt= wsrep_uuid_scan (str, strlen(str), uuid);
 | |
|   errno= 0;                                     /* Reset the errno */
 | |
|   if (offt > 0 && strlen(str) > (unsigned int)offt && ':' == str[offt])
 | |
|   {
 | |
|     *seqno= strtoll (str + offt + 1, NULL, 10);
 | |
|     if (*seqno != LLONG_MAX || errno != ERANGE)
 | |
|     {
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   WSREP_ERROR("Failed to parse uuid:seqno pair: '%s'", str);
 | |
|   return -EINVAL;
 | |
| }
 | |
| 
 | |
| // get rid of trailing \n
 | |
| static char* my_fgets (char* buf, size_t buf_len, FILE* stream)
 | |
| {
 | |
|    char* ret= fgets (buf, buf_len, stream);
 | |
| 
 | |
|    if (ret)
 | |
|    {
 | |
|        size_t len= strlen(ret);
 | |
|        if (len > 0 && ret[len - 1] == '\n') ret[len - 1]= '\0';
 | |
|    }
 | |
| 
 | |
|    return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Generate "name 'value'" string.
 | |
| */
 | |
| static char* generate_name_value(const char* name, const char* value)
 | |
| {
 | |
|   size_t name_len= strlen(name);
 | |
|   size_t value_len= strlen(value);
 | |
|   char* buf=
 | |
|     (char*) my_malloc(PSI_INSTRUMENT_ME, (name_len + value_len + 5), MYF(0));
 | |
|   if (buf)
 | |
|   {
 | |
|     char* ref= buf;
 | |
|     *ref++ = ' ';
 | |
|     memcpy(ref, name, name_len * sizeof(char));
 | |
|     ref += name_len;
 | |
|     *ref++ = ' ';
 | |
|     *ref++ = '\'';
 | |
|     memcpy(ref, value, value_len * sizeof(char));
 | |
|     ref += value_len;
 | |
|     *ref++ = '\'';
 | |
|     *ref = 0;
 | |
|   }
 | |
|   return buf;
 | |
| }
 | |
| /*
 | |
|   Generate binlog option string for sst_donate_other(), sst_prepare_other().
 | |
| 
 | |
|   Returns zero on success, negative error code otherwise.
 | |
| 
 | |
|   String containing binlog name is stored in param ret if binlog is enabled
 | |
|   and GTID mode is on, otherwise empty string. Returned string should be
 | |
|   freed with my_free().
 | |
|  */
 | |
| static int generate_binlog_opt_val(char** ret)
 | |
| {
 | |
|   DBUG_ASSERT(ret);
 | |
|   *ret= NULL;
 | |
|   if (opt_bin_log)
 | |
|   {
 | |
|     assert(opt_bin_logname);
 | |
|     *ret= strcmp(opt_bin_logname, "0") ?
 | |
|       generate_name_value(WSREP_SST_OPT_BINLOG,
 | |
|                           opt_bin_logname) :
 | |
|       my_strdup(PSI_INSTRUMENT_ME, "", MYF(0));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     *ret= my_strdup(PSI_INSTRUMENT_ME, "", MYF(0));
 | |
|   }
 | |
|   if (!*ret) return -ENOMEM;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int generate_binlog_index_opt_val(char** ret)
 | |
| {
 | |
|   DBUG_ASSERT(ret);
 | |
|   *ret= NULL;
 | |
|   if (opt_binlog_index_name)
 | |
|   {
 | |
|     *ret= strcmp(opt_binlog_index_name, "0") ?
 | |
|       generate_name_value(WSREP_SST_OPT_BINLOG_INDEX,
 | |
|                           opt_binlog_index_name) :
 | |
|       my_strdup(PSI_INSTRUMENT_ME, "", MYF(0));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     *ret= my_strdup(PSI_INSTRUMENT_ME, "", MYF(0));
 | |
|   }
 | |
|   if (!*ret) return -ENOMEM;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| // report progress event
 | |
| static void sst_report_progress(int const       from,
 | |
|                                 long long const total_prev,
 | |
|                                 long long const total,
 | |
|                                 long long const complete)
 | |
| {
 | |
|   static char buf[128] = { '\0', };
 | |
|   static size_t const buf_len= sizeof(buf) - 1;
 | |
|   snprintf(buf, buf_len,
 | |
|            "{ \"from\": %d, \"to\": %d, \"total\": %lld, \"done\": %lld, "
 | |
|            "\"indefinite\": -1 }",
 | |
|            from, WSREP_MEMBER_JOINED, total_prev + total, total_prev +complete);
 | |
|   WSREP_DEBUG("REPORTING SST PROGRESS: '%s'", buf);
 | |
| }
 | |
| 
 | |
| // process "complete" event from SST script feedback
 | |
| static void sst_handle_complete(const char* const input,
 | |
|                                 long long const   total_prev,
 | |
|                                 long long*        total,
 | |
|                                 long long*        complete,
 | |
|                                 int const         from)
 | |
| {
 | |
|   long long x;
 | |
|   int n= sscanf(input, " %lld", &x);
 | |
|   if (n > 0 && x > *complete)
 | |
|   {
 | |
|     *complete= x;
 | |
|     if (*complete > *total) *total= *complete;
 | |
|     sst_report_progress(from, total_prev, *total, *complete);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // process "total" event from SST script feedback
 | |
| static void sst_handle_total(const char* const input,
 | |
|                              long long*        total_prev,
 | |
|                              long long*        total,
 | |
|                              long long*        complete,
 | |
|                              int const         from)
 | |
| {
 | |
|   long long x;
 | |
|   int n= sscanf(input, " %lld", &x);
 | |
|   if (n > 0)
 | |
|   {
 | |
|     // new stage starts, update total_prev
 | |
|     *total_prev+= *total;
 | |
|     *total= x;
 | |
|     *complete= 0;
 | |
|     sst_report_progress(from, *total_prev, *total, *complete);
 | |
|   }
 | |
| }
 | |
| 
 | |
| struct sst_thread_init
 | |
| {
 | |
|   sst_thread_init(const char* const errmsg, bool const abrt)
 | |
|   {
 | |
|     if (my_thread_init()) {
 | |
|       WSREP_ERROR("%s%s", errmsg, abrt ? " Aborting." : "");
 | |
|       if (abrt) unireg_abort(1);
 | |
|     }
 | |
|   }
 | |
|   ~sst_thread_init()
 | |
|   {
 | |
|     my_thread_end();
 | |
|   }
 | |
| };
 | |
| 
 | |
| static void* sst_joiner_thread (void* a)
 | |
| {
 | |
|   sst_thread_arg* arg= (sst_thread_arg*) a;
 | |
|   int err= 1;
 | |
| 
 | |
|   {
 | |
|     static const char magic[]= "ready";
 | |
|     static const size_t magic_len= sizeof(magic) - 1;
 | |
|     const size_t out_len= 512;
 | |
|     char out[out_len];
 | |
| 
 | |
|     WSREP_INFO("Running: '%s'", arg->cmd);
 | |
| 
 | |
|     wsp::process proc (arg->cmd, "r", arg->env);
 | |
| 
 | |
|     if (proc.from() && !proc.error())
 | |
|     {
 | |
|       const char* tmp= my_fgets (out, out_len, proc.from());
 | |
| 
 | |
|       if (!tmp || strlen(tmp) < (magic_len + 2) ||
 | |
|           strncasecmp (tmp, magic, magic_len))
 | |
|       {
 | |
|         WSREP_ERROR("Failed to read '%s <addr>' (got '%s') from: %s",
 | |
|                     magic, tmp, arg->cmd);
 | |
|         proc.wait();
 | |
|         if (proc.error()) err= proc.error();
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         err= 0;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       err= proc.error();
 | |
|       WSREP_ERROR("Failed to execute (%M): %s",
 | |
|                   err, arg->cmd);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       signal sst_prepare thread with ret code,
 | |
|       it will go on sending SST request
 | |
|     */
 | |
|     mysql_mutex_lock   (&arg->lock);
 | |
|     if (!err)
 | |
|     {
 | |
|       arg->ret_str= strdup (out + magic_len + 1);
 | |
|       if (!arg->ret_str) err= ENOMEM;
 | |
|     }
 | |
|     arg->err= -err;
 | |
|     mysql_cond_signal  (&arg->cond);
 | |
|     mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that.
 | |
| 
 | |
|     if (err) return NULL; /* lp:808417 - return immediately, don't signal
 | |
|                            * initializer thread to ensure single thread of
 | |
|                            * shutdown. */
 | |
| 
 | |
|     wsrep_uuid_t  ret_uuid = WSREP_UUID_UNDEFINED;
 | |
|     wsrep_seqno_t ret_seqno= WSREP_SEQNO_UNDEFINED;
 | |
| 
 | |
|     // current stage progress
 | |
|     long long total= 0;
 | |
|     long long complete= 0;
 | |
|     // previous stages cumulative progress
 | |
|     long long total_prev= 0;
 | |
| 
 | |
|     // in case of successful receiver start, wait for SST completion/end
 | |
|     const char* tmp= NULL;
 | |
|     err= EINVAL;
 | |
| 
 | |
|   wait_signal:
 | |
|     tmp= my_fgets (out, out_len, proc.from());
 | |
| 
 | |
|     if (tmp)
 | |
|     {
 | |
|       static const char magic_total[]= "total";
 | |
|       static const size_t total_len=strlen(magic_total);
 | |
|       static const char magic_complete[]= "complete";
 | |
|       static const size_t complete_len=strlen(magic_complete);
 | |
|       static const int from= WSREP_MEMBER_JOINER;
 | |
| 
 | |
|       if (!strncasecmp (tmp, magic_complete, complete_len))
 | |
|       {
 | |
|         sst_handle_complete(tmp + complete_len, total_prev, &total, &complete,
 | |
|                             from);
 | |
|         goto wait_signal;
 | |
|       }
 | |
|       else if (!strncasecmp (tmp, magic_total, total_len))
 | |
|       {
 | |
|         sst_handle_total(tmp + total_len, &total_prev, &total, &complete, from);
 | |
|         goto wait_signal;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       WSREP_ERROR("Failed to read uuid:seqno and wsrep_gtid_domain_id from "
 | |
|                   "joiner script.");
 | |
|       proc.wait();
 | |
|       if (proc.error()) err= proc.error();
 | |
|     }
 | |
| 
 | |
|     // this should be the final script output with GTID
 | |
|     if (tmp)
 | |
|     {
 | |
|       proc.wait();
 | |
|       // Read state ID (UUID:SEQNO) followed by wsrep_gtid_domain_id (if any).
 | |
|       unsigned long int domain_id= wsrep_gtid_domain_id;
 | |
|       const char *pos= strchr(out, ' ');
 | |
|       WSREP_DEBUG("SST state ID tmp=%s out=%s pos=%p", tmp, out, pos);
 | |
| 
 | |
|       if (!pos) {
 | |
| 
 | |
|         if (wsrep_gtid_mode)
 | |
|         {
 | |
|           // There is no wsrep_gtid_domain_id (some older version SST script?).
 | |
|           WSREP_WARN("Did not find domain ID from SST script output '%s'. "
 | |
|                      "Domain ID must be set manually to keep binlog consistent",
 | |
|                      out);
 | |
| 	  if (wsrep_gtid_domain_id)
 | |
| 	  {
 | |
| 	    WSREP_INFO("This node is configured to use wsrep_gtid_domain_id=%lu by user.",
 | |
| 		       domain_id);
 | |
| 	    wsrep_gtid_server.domain_id= (uint32)domain_id;
 | |
| 	    wsrep_gtid_domain_id= (uint32)domain_id;
 | |
| 	  }
 | |
|         }
 | |
|         err= sst_scan_uuid_seqno (out, &ret_uuid, &ret_seqno);
 | |
| 
 | |
|       } else {
 | |
|         // Scan state ID first followed by wsrep_gtid_domain_id.
 | |
|         unsigned long int domain_id;
 | |
| 
 | |
|         // Null-terminate the state-id.
 | |
|         out[pos - out]= 0;
 | |
|         err= sst_scan_uuid_seqno (out, &ret_uuid, &ret_seqno);
 | |
| 
 | |
|         if (err)
 | |
|         {
 | |
|           goto err;
 | |
|         }
 | |
|         else if (wsrep_gtid_mode)
 | |
|         {
 | |
|           errno= 0;                             /* Reset the errno */
 | |
|           domain_id= strtoul(pos + 1, NULL, 10);
 | |
|           err= errno;
 | |
| 
 | |
|           /* Check if we received a valid gtid_domain_id. */
 | |
|           if (err == EINVAL || err == ERANGE)
 | |
|           {
 | |
|             WSREP_ERROR("Failed to get donor wsrep_gtid_domain_id.");
 | |
|             err= EINVAL;
 | |
|             goto err;
 | |
|           } else {
 | |
|             wsrep_gtid_server.domain_id= (uint32) domain_id;
 | |
|             wsrep_gtid_domain_id= (uint32)domain_id;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
| err:
 | |
| 
 | |
|     wsrep::gtid ret_gtid;
 | |
| 
 | |
|     if (err)
 | |
|     {
 | |
|       ret_gtid= wsrep::gtid::undefined();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       ret_gtid= wsrep::gtid(wsrep::id(ret_uuid.data, sizeof(ret_uuid.data)),
 | |
|                             wsrep::seqno(ret_seqno));
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Tell initializer thread that SST is complete
 | |
|       For that initialize a THD
 | |
|     */
 | |
|     wsp::thd thd;
 | |
|     wsrep_sst_complete (thd.ptr, -err, ret_gtid);
 | |
|   }
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| #define WSREP_SST_AUTH_ENV        "WSREP_SST_OPT_AUTH"
 | |
| #define WSREP_SST_REMOTE_AUTH_ENV "WSREP_SST_OPT_REMOTE_AUTH"
 | |
| #define DATA_HOME_DIR_ENV         "INNODB_DATA_HOME_DIR"
 | |
| 
 | |
| static int sst_append_env_var(wsp::env&   env,
 | |
|                               const char* const var,
 | |
|                               const char* const val)
 | |
| {
 | |
|   int const env_str_size= strlen(var) + 1 /* = */
 | |
|                           + (val ? strlen(val) : 0) + 1 /* \0 */;
 | |
| 
 | |
|   wsp::string env_str(env_str_size); // for automatic cleanup on return
 | |
|   if (!env_str()) return -ENOMEM;
 | |
| 
 | |
|   int ret= snprintf(env_str(), env_str_size, "%s=%s", var, val ? val : "");
 | |
| 
 | |
|   if (ret < 0 || ret >= env_str_size)
 | |
|   {
 | |
|     WSREP_ERROR("sst_append_env_var(): snprintf(%s=%s) failed: %d",
 | |
|                 var, val, ret);
 | |
|     return (ret < 0 ? ret : -EMSGSIZE);
 | |
|   }
 | |
| 
 | |
|   env.append(env_str());
 | |
|   return -env.error();
 | |
| }
 | |
| 
 | |
| #ifdef _WIN32
 | |
| /*
 | |
|   Space, single quote, ampersand, backquote, I/O redirection
 | |
|   characters, caret, all brackets, plus, exclamation and comma
 | |
|   characters require text to be enclosed in double quotes:
 | |
| */
 | |
| #define IS_SPECIAL(c) \
 | |
|   (isspace(c) || c == '\'' || c == '&' || c == '`' || c == '|' || \
 | |
|                  c ==  '>' || c == '<' || c == ';' || c == '^' || \
 | |
|                  c ==  '[' || c == ']' || c == '{' || c == '}' || \
 | |
|                  c ==  '(' || c == ')' || c == '+' || c == '!' || \
 | |
|                  c ==  ',')
 | |
| /*
 | |
|   Inside values, equals character are interpreted as special
 | |
|   character and requires quotation:
 | |
| */
 | |
| #define IS_SPECIAL_V(c) (IS_SPECIAL(c) || c == '=')
 | |
| /*
 | |
|   Double quotation mark and percent characters require escaping:
 | |
| */
 | |
| #define IS_REQ_ESCAPING(c) (c == '""' || c == '%')
 | |
| #else
 | |
| /*
 | |
|   Space, single quote, ampersand, backquote, and I/O redirection
 | |
|   characters require text to be enclosed in double quotes. The
 | |
|   semicolon is used to separate shell commands, so it must be
 | |
|   enclosed in double quotes as well:
 | |
| */
 | |
| #define IS_SPECIAL(c) \
 | |
|   (isspace(c) || c == '\'' || c == '&' || c == '`' || c == '|' || \
 | |
|                  c ==  '>' || c == '<' || c == ';')
 | |
| /*
 | |
|   Inside values, characters are interpreted as in parameter names:
 | |
| */
 | |
| #define IS_SPECIAL_V(c) IS_SPECIAL(c)
 | |
| /*
 | |
|   Double quotation mark and backslash characters require
 | |
|   backslash prefixing, the dollar symbol is used to substitute
 | |
|   a variable value, therefore it also requires escaping:
 | |
| */
 | |
| #define IS_REQ_ESCAPING(c) (c == '"' || c == '\\' || c == '$')
 | |
| #endif
 | |
| 
 | |
| static size_t estimate_cmd_len (bool* extra_args)
 | |
| {
 | |
|   /*
 | |
|     The length of the area reserved for the control parameters
 | |
|     of the SST script (excluding the copying of the original
 | |
|     mysqld arguments):
 | |
|   */
 | |
|   size_t cmd_len= 4096;
 | |
|   bool extra= false;
 | |
|   /*
 | |
|     If mysqld was started with arguments, add them all:
 | |
|   */
 | |
|   if (orig_argc > 1)
 | |
|   {
 | |
|     for (int i = 1; i < orig_argc; i++)
 | |
|     {
 | |
|       const char* arg= orig_argv[i];
 | |
|       size_t n= strlen(arg);
 | |
|       if (n == 0) continue;
 | |
|       cmd_len += n;
 | |
|       bool quotation= false;
 | |
|       char c;
 | |
|       while ((c = *arg++) != 0)
 | |
|       {
 | |
|         if (IS_SPECIAL(c))
 | |
|         {
 | |
|           quotation= true;
 | |
|         }
 | |
|         else if (IS_REQ_ESCAPING(c))
 | |
|         {
 | |
|           cmd_len++;
 | |
| #ifdef _WIN32
 | |
|           quotation= true;
 | |
| #endif
 | |
|         }
 | |
|         /*
 | |
|           If the equals symbol is encountered, then we need to separately
 | |
|           process the right side:
 | |
|         */
 | |
|         else if (c == '=')
 | |
|         {
 | |
|           /* Perhaps we need to quote the left part of the argument: */
 | |
|           if (quotation)
 | |
|           {
 | |
|             cmd_len += 2;
 | |
|             /*
 | |
|               Reset the quotation flag, since now the status for
 | |
|               the right side of the expression will be saved here:
 | |
|             */
 | |
|             quotation= false;
 | |
|           }
 | |
|           while ((c = *arg++) != 0)
 | |
|           {
 | |
|             if (IS_SPECIAL_V(c))
 | |
|             {
 | |
|               quotation= true;
 | |
|             }
 | |
|             else if (IS_REQ_ESCAPING(c))
 | |
|             {
 | |
|               cmd_len++;
 | |
| #ifdef _WIN32
 | |
|               quotation= true;
 | |
| #endif
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       /* Perhaps we need to quote the entire argument or its right part: */
 | |
|       if (quotation)
 | |
|       {
 | |
|         cmd_len += 2;
 | |
|       }
 | |
|     }
 | |
|     extra = true;
 | |
|     cmd_len += strlen(WSREP_SST_OPT_MYSQLD);
 | |
|     /*
 | |
|       Add the separating spaces between arguments,
 | |
|       and one additional space before "--mysqld-args":
 | |
|     */
 | |
|     cmd_len += orig_argc;
 | |
|   }
 | |
|   *extra_args= extra;
 | |
|   return cmd_len;
 | |
| }
 | |
| 
 | |
| static void copy_orig_argv (char* cmd_str)
 | |
| {
 | |
|   /*
 | |
|      If mysqld was started with arguments, copy them all:
 | |
|   */
 | |
|   if (orig_argc > 1)
 | |
|   {
 | |
|     size_t n = strlen(WSREP_SST_OPT_MYSQLD);
 | |
|     *cmd_str++ = ' ';
 | |
|     memcpy(cmd_str, WSREP_SST_OPT_MYSQLD, n * sizeof(char));
 | |
|     cmd_str += n;
 | |
|     for (int i = 1; i < orig_argc; i++)
 | |
|     {
 | |
|       char* arg= orig_argv[i];
 | |
|       n = strlen(arg);
 | |
|       if (n == 0) continue;
 | |
|       *cmd_str++ = ' ';
 | |
|       bool quotation= false;
 | |
|       bool plain= true;
 | |
|       char *arg_scan= arg;
 | |
|       char c;
 | |
|       while ((c = *arg_scan++) != 0)
 | |
|       {
 | |
|         if (IS_SPECIAL(c))
 | |
|         {
 | |
|           quotation= true;
 | |
|         }
 | |
|         else if (IS_REQ_ESCAPING(c))
 | |
|         {
 | |
|           plain= false;
 | |
| #ifdef _WIN32
 | |
|           quotation= true;
 | |
| #endif
 | |
|         }
 | |
|         /*
 | |
|           If the equals symbol is encountered, then we need to separately
 | |
|           process the right side:
 | |
|         */
 | |
|         else if (c == '=')
 | |
|         {
 | |
|           /* Calculate length of the Left part of the argument: */
 | |
|           size_t m = (size_t) (arg_scan - arg) - 1;
 | |
|           if (m)
 | |
|           {
 | |
|             /* Perhaps we need to quote the left part of the argument: */
 | |
|             if (quotation)
 | |
|             {
 | |
|               *cmd_str++ = '"';
 | |
|             }
 | |
|             /*
 | |
|               If there were special characters inside, then we can use
 | |
|               the fast memcpy function:
 | |
|             */
 | |
|             if (plain)
 | |
|             {
 | |
|               memcpy(cmd_str, arg, m * sizeof(char));
 | |
|               cmd_str += m;
 | |
|               /* Left part of the argument has already been processed: */
 | |
|               n -= m;
 | |
|               arg += m;
 | |
|             }
 | |
|             /* Otherwise we need to prefix individual characters: */
 | |
|             else
 | |
|             {
 | |
|               n -= m;
 | |
|               while (m)
 | |
|               {
 | |
|                 c = *arg++;
 | |
|                 if (IS_REQ_ESCAPING(c))
 | |
|                 {
 | |
| #ifdef _WIN32
 | |
|                   *cmd_str++ = c;
 | |
| #else
 | |
|                   *cmd_str++ = '\\';
 | |
| #endif
 | |
|                 }
 | |
|                 *cmd_str++ = c;
 | |
|                 m--;
 | |
|               }
 | |
|               /*
 | |
|                 Reset the plain string flag, since now the status for
 | |
|                 the right side of the expression will be saved here:
 | |
|               */
 | |
|               plain= true;
 | |
|             }
 | |
|             /* Perhaps we need to quote the left part of the argument: */
 | |
|             if (quotation)
 | |
|             {
 | |
|               *cmd_str++ = '"';
 | |
|               /*
 | |
|                 Reset the quotation flag, since now the status for
 | |
|                 the right side of the expression will be saved here:
 | |
|               */
 | |
|               quotation= false;
 | |
|             }
 | |
|           }
 | |
|           /* Copy equals symbol: */
 | |
|           *cmd_str++ = '=';
 | |
|           arg++;
 | |
|           n--;
 | |
|           /* Let's deal with the left side of the expression: */
 | |
|           while ((c = *arg_scan++) != 0)
 | |
|           {
 | |
|             if (IS_SPECIAL_V(c))
 | |
|             {
 | |
|               quotation= true;
 | |
|             }
 | |
|             else if (IS_REQ_ESCAPING(c))
 | |
|             {
 | |
|               plain= false;
 | |
| #ifdef _WIN32
 | |
|               quotation= true;
 | |
| #endif
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (n)
 | |
|       {
 | |
|         /* Perhaps we need to quote the entire argument or its right part: */
 | |
|         if (quotation)
 | |
|         {
 | |
|           *cmd_str++ = '"';
 | |
|         }
 | |
|         /*
 | |
|           If there were no special characters inside, then we can use
 | |
|           the fast memcpy function:
 | |
|         */
 | |
|         if (plain)
 | |
|         {
 | |
|           memcpy(cmd_str, arg, n * sizeof(char));
 | |
|           cmd_str += n;
 | |
|         }
 | |
|         /* Otherwise we need to prefix individual characters: */
 | |
|         else
 | |
|         {
 | |
|           while ((c = *arg++) != 0)
 | |
|           {
 | |
|             if (IS_REQ_ESCAPING(c))
 | |
|             {
 | |
| #ifdef _WIN32
 | |
|               *cmd_str++ = c;
 | |
| #else
 | |
|               *cmd_str++ = '\\';
 | |
| #endif
 | |
|             }
 | |
|             *cmd_str++ = c;
 | |
|           }
 | |
|         }
 | |
|         /* Perhaps we need to quote the entire argument or its right part: */
 | |
|         if (quotation)
 | |
|         {
 | |
|           *cmd_str++ = '"';
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     /*
 | |
|       Add a terminating null character (not counted in the length,
 | |
|       since we've overwritten the original null character which
 | |
|       was previously added by snprintf:
 | |
|     */
 | |
|     *cmd_str = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* implementation of strchrnul for systems that don't have it */
 | |
| static const char* sst_strchrnul(const char* s, int c)
 | |
| {
 | |
|   const char* ret= strchr(s, c);
 | |
|   return ret ? ret : s + strlen(s);
 | |
| }
 | |
| 
 | |
| static ssize_t sst_prepare_other (const char*  method,
 | |
|                                   const char*  addr_in,
 | |
|                                   const char** addr_out)
 | |
| {
 | |
|   bool extra_args;
 | |
|   size_t const cmd_len= estimate_cmd_len(&extra_args);
 | |
|   wsp::string cmd_str(cmd_len);
 | |
| 
 | |
|   if (!cmd_str())
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): could not allocate cmd buffer of %zd bytes",
 | |
|                 cmd_len);
 | |
|     return -ENOMEM;
 | |
|   }
 | |
| 
 | |
|   char* binlog_opt_val= NULL;
 | |
|   char* binlog_index_opt_val= NULL;
 | |
| 
 | |
|   int ret;
 | |
|   if ((ret= generate_binlog_opt_val(&binlog_opt_val)))
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): generate_binlog_opt_val() failed: %d",
 | |
|                 ret);
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   if ((ret= generate_binlog_index_opt_val(&binlog_index_opt_val)))
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): generate_binlog_index_opt_val() failed %d",
 | |
|                 ret);
 | |
|     if (binlog_opt_val) my_free(binlog_opt_val);
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   make_wsrep_defaults_file();
 | |
| 
 | |
|   ret= snprintf (cmd_str(), cmd_len,
 | |
|                  "wsrep_sst_%s "
 | |
|                  WSREP_SST_OPT_ROLE " 'joiner' "
 | |
|                  WSREP_SST_OPT_ADDR " '%s' "
 | |
|                  WSREP_SST_OPT_DATA " '%s' "
 | |
|                  "%s"
 | |
|                  WSREP_SST_OPT_PARENT " %d "
 | |
|                  WSREP_SST_OPT_PROGRESS " %d"
 | |
|                  "%s"
 | |
|                  "%s",
 | |
|                  method, addr_in, mysql_real_data_home,
 | |
|                  wsrep_defaults_file,
 | |
|                  (int)getpid(),
 | |
|                  wsrep_debug ? 1 : 0,
 | |
|                  binlog_opt_val, binlog_index_opt_val);
 | |
| 
 | |
|   my_free(binlog_opt_val);
 | |
|   my_free(binlog_index_opt_val);
 | |
| 
 | |
|   if (ret < 0 || size_t(ret) >= cmd_len)
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): snprintf() failed: %d", ret);
 | |
|     return (ret < 0 ? ret : -EMSGSIZE);
 | |
|   }
 | |
| 
 | |
|   if (extra_args)
 | |
|     copy_orig_argv(cmd_str() + ret);
 | |
| 
 | |
|   sst_auth auth;
 | |
|   if (sst_auth_real)
 | |
|   {
 | |
|     const char* col= sst_strchrnul(sst_auth_real, ':');
 | |
|     auth.name_ = std::string(sst_auth_real, col - sst_auth_real);
 | |
|     auth.pswd_ = std::string(':' == *col ? col + 1 : "");
 | |
|   }
 | |
| 
 | |
|   wsp::env env(NULL);
 | |
|   if (env.error())
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): env. var ctor failed: %d", -env.error());
 | |
|     return -env.error();
 | |
|   }
 | |
|   if (data_home_dir)
 | |
|   {
 | |
|     if ((ret= sst_append_env_var(env, DATA_HOME_DIR_ENV, data_home_dir)))
 | |
|     {
 | |
|       WSREP_ERROR("sst_prepare_other(): appending data "
 | |
|                   "directory failed: %d", ret);
 | |
|       return ret;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pthread_t tmp, monitor;
 | |
|   sst_thread_arg arg(method, cmd_str(), auth, env());
 | |
| 
 | |
|   mysql_mutex_lock (&arg.lock);
 | |
| 
 | |
|   ret = mysql_thread_create (key_wsrep_sst_joiner_monitor, &monitor, NULL, wsrep_sst_joiner_monitor_thread, NULL);
 | |
| 
 | |
|   if (ret)
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): mysql_thread_create() failed: %d (%s)",
 | |
|                 ret, strerror(ret));
 | |
|     return -ret;
 | |
|   }
 | |
| 
 | |
|   sst_joiner_completed= false;
 | |
| 
 | |
|   ret= mysql_thread_create (key_wsrep_sst_joiner, &tmp, NULL, sst_joiner_thread, &arg);
 | |
| 
 | |
|   if (ret)
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): mysql_thread_create() failed: %d (%s)",
 | |
|                 ret, strerror(ret));
 | |
| 
 | |
|     pthread_detach(monitor);
 | |
|     return -ret;
 | |
|   }
 | |
| 
 | |
|   mysql_cond_wait (&arg.cond, &arg.lock);
 | |
| 
 | |
|   *addr_out= arg.ret_str;
 | |
| 
 | |
|   if (!arg.err)
 | |
|     ret= strlen(*addr_out);
 | |
|   else
 | |
|   {
 | |
|     assert (arg.err < 0);
 | |
|     ret= arg.err;
 | |
|   }
 | |
| 
 | |
|   pthread_detach (tmp);
 | |
|   pthread_detach (monitor);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern uint  mysqld_port;
 | |
| 
 | |
| /*! Just tells donor where to send mysqldump */
 | |
| static ssize_t sst_prepare_mysqldump (const char*  addr_in,
 | |
|                                       const char** addr_out)
 | |
| {
 | |
|   ssize_t ret= strlen (addr_in);
 | |
| 
 | |
|   if (!strrchr(addr_in, ':'))
 | |
|   {
 | |
|     ssize_t s= ret + 7;
 | |
|     char* tmp= (char*) malloc (s);
 | |
| 
 | |
|     if (tmp)
 | |
|     {
 | |
|       ret= snprintf (tmp, s, "%s:%u", addr_in, mysqld_port);
 | |
| 
 | |
|       if (ret > 0 && ret < s)
 | |
|       {
 | |
|         *addr_out= tmp;
 | |
|         return ret;
 | |
|       }
 | |
|       if (ret > 0) /* buffer too short */ ret= -EMSGSIZE;
 | |
|       free (tmp);
 | |
|     }
 | |
|     else {
 | |
|       ret= -ENOMEM;
 | |
|     }
 | |
| 
 | |
|     WSREP_ERROR ("Could not prepare state transfer request: "
 | |
|                  "adding default port failed: %zd.", ret);
 | |
|   }
 | |
|   else {
 | |
|     *addr_out= addr_in;
 | |
|   }
 | |
| 
 | |
|   pthread_t monitor;
 | |
|   ret = mysql_thread_create (key_wsrep_sst_joiner_monitor, &monitor, NULL, wsrep_sst_joiner_monitor_thread, NULL);
 | |
| 
 | |
|   if (ret)
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): mysql_thread_create() failed: %d (%s)",
 | |
|                 ret, strerror(ret));
 | |
|     return -ret;
 | |
|   }
 | |
| 
 | |
|   sst_joiner_completed= false;
 | |
|   pthread_detach (monitor);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| std::string wsrep_sst_prepare()
 | |
| {
 | |
|   const ssize_t ip_max= 256;
 | |
|   char ip_buf[ip_max];
 | |
|   const char* addr_in=  NULL;
 | |
|   const char* addr_out= NULL;
 | |
|   const char* method;
 | |
| 
 | |
|   if (!strcmp(wsrep_sst_method, WSREP_SST_SKIP))
 | |
|   {
 | |
|     return WSREP_STATE_TRANSFER_TRIVIAL;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Figure out SST receive address. Common for all SST methods.
 | |
|   */
 | |
|   wsp::Address* addr_in_parser= NULL;
 | |
| 
 | |
|   // Attempt 1: wsrep_sst_receive_address
 | |
|   if (wsrep_sst_receive_address &&
 | |
|       strcmp(wsrep_sst_receive_address, WSREP_SST_ADDRESS_AUTO))
 | |
|   {
 | |
|     addr_in_parser = new wsp::Address(wsrep_sst_receive_address);
 | |
| 
 | |
|     if (!addr_in_parser->is_valid())
 | |
|     {
 | |
|       WSREP_ERROR("Could not parse wsrep_sst_receive_address : %s",
 | |
|                   wsrep_sst_receive_address);
 | |
|       unireg_abort(1);
 | |
|     }
 | |
|   }
 | |
|   // Attempt 2: wsrep_node_address
 | |
|   else if (wsrep_node_address && *wsrep_node_address)
 | |
|   {
 | |
|     addr_in_parser = new wsp::Address(wsrep_node_address);
 | |
| 
 | |
|     if (addr_in_parser->is_valid())
 | |
|     {
 | |
|       // we must not inherit the port number from this address:
 | |
|       addr_in_parser->set_port(0);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       WSREP_ERROR("Could not parse wsrep_node_address : %s",
 | |
|                   wsrep_node_address);
 | |
|       throw wsrep::runtime_error("Failed to prepare for SST. Unrecoverable");
 | |
|     }
 | |
|   }
 | |
|   // Attempt 3: Try to get the IP from the list of available interfaces.
 | |
|   else
 | |
|   {
 | |
|     ssize_t ret= wsrep_guess_ip(ip_buf, ip_max);
 | |
| 
 | |
|     if (ret && ret < ip_max)
 | |
|     {
 | |
|       addr_in_parser = new wsp::Address(ip_buf);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       WSREP_ERROR("Failed to guess address to accept state transfer. "
 | |
|                   "wsrep_sst_receive_address must be set manually.");
 | |
|       throw wsrep::runtime_error("Could not prepare state transfer request");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   assert(addr_in_parser);
 | |
| 
 | |
|   size_t len= addr_in_parser->get_address_len();
 | |
|   bool is_ipv6= addr_in_parser->is_ipv6();
 | |
|   const char* address= addr_in_parser->get_address();
 | |
| 
 | |
|   if (len > (is_ipv6 ? ip_max - 2 : ip_max))
 | |
|   {
 | |
|     WSREP_ERROR("Address to accept state transfer is too long: '%s'",
 | |
|                 address);
 | |
|     unireg_abort(1);
 | |
|   }
 | |
| 
 | |
|   if (is_ipv6)
 | |
|   {
 | |
|     /* wsrep_sst_*.sh scripts require ipv6 addresses to be in square brackets */
 | |
|     ip_buf[0] = '[';
 | |
|     /* the length (len) already includes the null byte: */
 | |
|     memcpy(ip_buf + 1, address, len - 1);
 | |
|     ip_buf[len] = ']';
 | |
|     ip_buf[len + 1] = 0;
 | |
|     len += 2;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     memcpy(ip_buf, address, len);
 | |
|   }
 | |
| 
 | |
|   int port= addr_in_parser->get_port();
 | |
|   if (port)
 | |
|   {
 | |
|     size_t space= ip_max - len;
 | |
|     ip_buf[len - 1] = ':';
 | |
|     int ret= snprintf(ip_buf + len, ip_max - len, "%d", port);
 | |
|     if (ret <= 0 || (size_t) ret > space)
 | |
|     {
 | |
|       WSREP_ERROR("Address to accept state transfer is too long: '%s:%d'",
 | |
|                   address, port);
 | |
|       unireg_abort(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   delete addr_in_parser;
 | |
|   addr_in = ip_buf;
 | |
| 
 | |
|   ssize_t addr_len= -ENOSYS;
 | |
|   method = wsrep_sst_method;
 | |
|   if (!strcmp(method, WSREP_SST_MYSQLDUMP))
 | |
|   {
 | |
|     addr_len= sst_prepare_mysqldump (addr_in, &addr_out);
 | |
|     if (addr_len < 0)
 | |
|     {
 | |
|       throw wsrep::runtime_error("Could not prepare mysqldimp address");
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*! A heuristic workaround until we learn how to stop and start engines */
 | |
|     if (Wsrep_server_state::instance().is_initialized() &&
 | |
|         Wsrep_server_state::instance().state() == Wsrep_server_state::s_joiner)
 | |
|     {
 | |
|       if (!strcmp(method, WSREP_SST_XTRABACKUP) ||
 | |
|           !strcmp(method, WSREP_SST_XTRABACKUPV2))
 | |
|       {
 | |
|          WSREP_WARN("The %s SST method is deprecated, so it is automatically "
 | |
|                     "replaced by %s", method, WSREP_SST_MARIABACKUP);
 | |
|          method = WSREP_SST_MARIABACKUP;
 | |
|       }
 | |
|       // we already did SST at initialization, now engines are running
 | |
|       // sql_print_information() is here because the message is too long
 | |
|       // for WSREP_INFO.
 | |
|       sql_print_information ("WSREP: "
 | |
|                  "You have configured '%s' state snapshot transfer method "
 | |
|                  "which cannot be performed on a running server. "
 | |
|                  "Wsrep provider won't be able to fall back to it "
 | |
|                  "if other means of state transfer are unavailable. "
 | |
|                  "In that case you will need to restart the server.",
 | |
|                  method);
 | |
|       return "";
 | |
|     }
 | |
| 
 | |
|     addr_len = sst_prepare_other(method, addr_in, &addr_out);
 | |
|     if (addr_len < 0)
 | |
|     {
 | |
|       WSREP_ERROR("Failed to prepare for '%s' SST. Unrecoverable.",
 | |
|                    method);
 | |
|       throw wsrep::runtime_error("Failed to prepare for SST. Unrecoverable");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   std::string ret;
 | |
|   ret += method;
 | |
|   ret.push_back('\0');
 | |
|   ret += addr_out;
 | |
| 
 | |
|   const char* method_ptr(ret.data());
 | |
|   const char* addr_ptr(ret.data() + strlen(method_ptr) + 1);
 | |
|   WSREP_DEBUG("Prepared SST request: %s|%s", method_ptr, addr_ptr);
 | |
| 
 | |
|   if (addr_out != addr_in) /* malloc'ed */ free ((char*)addr_out);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| int run_sql_command(wsp::mysql& mysql, const char* query)
 | |
| {
 | |
|   int err= mysql.execute(query);
 | |
|   if (err)
 | |
|   {
 | |
|     WSREP_WARN("Error executing '%s': %d (%s)",
 | |
|                query, err, mysql.errstr());
 | |
|     return err;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int run_sql_command_thd(THD *thd, const char *query)
 | |
| {
 | |
|   int ret= 0;
 | |
| 
 | |
|   thd->reset_for_next_command();
 | |
|   thd->set_query((char*)query, strlen(query));
 | |
| 
 | |
|   Parser_state ps;
 | |
|   if (ps.init(thd, thd->query(), thd->query_length()))
 | |
|   {
 | |
|     WSREP_ERROR("SST query: %s failed", query);
 | |
|     ret= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     PSI_statement_locker *parent_locker= thd->m_statement_psi;
 | |
|     thd->set_query_id(next_query_id());
 | |
| 
 | |
|     mysql_parse(thd, thd->query(), thd->query_length(), &ps);
 | |
|     if (thd->is_error())
 | |
|     {
 | |
|       int const err= thd->get_stmt_da()->sql_errno();
 | |
|       if (err)
 | |
|       {
 | |
|         WSREP_WARN ("Error executing '%s': %d (%s)",
 | |
|                     query, err, thd->get_stmt_da()->message());
 | |
|         ret= err;
 | |
|       }
 | |
|       thd->clear_error();
 | |
|     }
 | |
|     thd->m_statement_psi= parent_locker;
 | |
|     thd->end_statement();
 | |
|     close_thread_tables(thd);
 | |
|     delete_explain_query(thd->lex);
 | |
|   }
 | |
| 
 | |
|   thd->reset_query();
 | |
|   thd->cleanup_after_query();
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| #define SST_USER_PREFIX "wsrep.sst."
 | |
| 
 | |
| static std::string
 | |
| generate_user_name(const char* const node_name)
 | |
| {
 | |
|   struct timeval tv;
 | |
|   gettimeofday(&tv, NULL);
 | |
|   time_t time_now;
 | |
|   time_now = tv.tv_sec;
 | |
|   struct tm tm_now;
 | |
|   (void)gmtime_r(&time_now, &tm_now);
 | |
|   char timestamp[32];
 | |
|   strftime(timestamp, sizeof(timestamp), "%y%m%d_%H%M%S", &tm_now);
 | |
| 
 | |
|   return (std::string(SST_USER_PREFIX) + timestamp + "_" + node_name).substr
 | |
|           (0, USERNAME_CHAR_LENGTH);
 | |
| }
 | |
| 
 | |
| void
 | |
| wsrep_sst_cleanup_user(THD* const thd)
 | |
| {
 | |
|   int err;
 | |
|   wsp::mysql mysql;
 | |
|   if ((err = mysql.errnum()) ||
 | |
|       (err = mysql.disable_replication()) ||
 | |
|       ((err = mysql.execute("DELETE FROM mysql.user WHERE user LIKE '" SST_USER_PREFIX "%';")) &&
 | |
|         err != ER_NO_SUCH_TABLE &&
 | |
|         err != ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE))
 | |
|   {
 | |
|     WSREP_WARN("Failed to clean up SST user(s): %d (%s)", err, mysql.errstr());
 | |
|   }
 | |
| }
 | |
| 
 | |
| static std::string
 | |
| generate_password(int size) {
 | |
|   // Password characters are limited, because we are using these passwords
 | |
|   // within a bash script.
 | |
|   static const std::string g_allowed_pwd_chars(
 | |
|     "qwertyuiopasdfghjklzxcvbnm1234567890"
 | |
|     "QWERTYUIOPASDFGHJKLZXCVBNM");
 | |
|   // To guarantee MySQL password requirements: upper/lowercase, numbers,
 | |
|   // special characters
 | |
|   const char* const pwd_prefix("yx9!A-");
 | |
| 
 | |
|   std::stringstream ss;
 | |
|   ss << pwd_prefix;
 | |
|   size -= strlen(pwd_prefix);
 | |
| 
 | |
|   ulong const seed1= (ulong) (my_rnd(&sql_rand) * 0xffffffff);
 | |
|   ulong const seed2= (ulong) time((time_t*) 0);
 | |
|   struct my_rnd_struct rand;
 | |
|   my_rnd_init(&rand, seed1 + (ulong)((size_t) &rand), seed2);
 | |
| 
 | |
|   while (size > 0) {
 | |
|     int ch = ((int)(my_rnd(&rand) * 0xffff)) % g_allowed_pwd_chars.size();
 | |
|     ss << g_allowed_pwd_chars[ch];
 | |
|     --size;
 | |
|   }
 | |
| 
 | |
|   return ss.str();
 | |
| }
 | |
| 
 | |
| static const char**
 | |
| sst_user_grants(const std::string& method)
 | |
| {
 | |
|   static const char* mariabackup[] = {
 | |
|     "GRANT RELOAD, PROCESS, LOCK TABLES, BINLOG MONITOR ON *.* TO '%s'@'localhost';",
 | |
|     nullptr,
 | |
|     nullptr,
 | |
|     nullptr
 | |
|   };
 | |
|   static const char* rsync[] = {
 | |
|     nullptr,
 | |
|     nullptr
 | |
|   };
 | |
|   /* We don't know which privileges might be needed for custom SST scripts,
 | |
|    * so GRANT all */
 | |
|   static const char* other[] = {
 | |
|     "GRANT ALL ON *.* TO '%s'@localhost WITH GRANT OPTION;",
 | |
|     nullptr,
 | |
|     nullptr
 | |
|   };
 | |
| 
 | |
|   if (method == "mariabackup-test") {
 | |
|     return mariabackup;
 | |
|   }
 | |
|   if (method == "rsync") {
 | |
|     assert(0); // should not attempt to create user for this SSST
 | |
|     return rsync;
 | |
|   }
 | |
| 
 | |
|   return other;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * cmds[] This array is filled with pairs of entries.
 | |
|           The first entry is the printf template for the query to be run:
 | |
|           optional %s will be filled with name and password.
 | |
|           The second entry is the string to be displayed if the query fails
 | |
|           (this can be NULL, in which case the actual query will be used)
 | |
|  */
 | |
| static int
 | |
| run_sql_commands(wsp::mysql& mysql,
 | |
|                  const char* cmds[],
 | |
|                  const std::string& name,
 | |
|                  const std::string& password)
 | |
| {
 | |
|   int ret = 0;
 | |
|   for (int index = 0; !ret && cmds[index]; index += 2)
 | |
|   {
 | |
|     int const cmd_len = 512;
 | |
|     char cmd_buf[cmd_len] = { 0,};
 | |
| 
 | |
|     ret = snprintf(cmd_buf, cmd_len, cmds[index],
 | |
|                    name.c_str(), password.c_str());
 | |
|     if (ret < 0 || ret >= cmd_len) {
 | |
|       WSREP_ERROR("run_sql_commands(): snprintf() failed: %d", ret);
 | |
|       ret = (ret < 0 ? ret : -EMSGSIZE);
 | |
|       break;
 | |
|     }
 | |
|     else {
 | |
|       WSREP_INFO("run_sql_commands(%p): '%s'",
 | |
|                   cmds, cmds[index + 1] ? cmds[index + 1] : cmd_buf);
 | |
|     }
 | |
| 
 | |
|     ret = run_sql_command(mysql, cmd_buf);
 | |
|     if (ret) break;
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Helper class to manage SST users
 | |
|  */
 | |
| class sst_user
 | |
| {
 | |
|   wsp::mysql* mysql_;
 | |
|   std::string const name_;
 | |
|   std::string const password_;
 | |
|   int err_;
 | |
| public:
 | |
|   /* ctor in case we need to create SST user */
 | |
|   sst_user(const std::string& method) :
 | |
|     mysql_(new wsp::mysql),
 | |
|     name_(generate_user_name(wsrep_node_name)),
 | |
|     password_(generate_password(32)),
 | |
|     err_(mysql_->errnum())
 | |
|   {
 | |
|     assert(method != "rsync"); // should not attempt to create user for this SST
 | |
| 
 | |
|     // This array is filled with pairs of entries
 | |
|     // The first entry is the tmeplate query to be run
 | |
|     // The second entry is the string to be displayed if the query fails
 | |
|     // (this can be NULL, in which case the actual query will be used)
 | |
|     static const char *create_sst_user[] = {
 | |
|       "DROP USER IF EXISTS '%s'@localhost;",
 | |
|       nullptr,
 | |
|       "CREATE USER '%s'@localhost IDENTIFIED BY '%s';",
 | |
|       "CREATE USER '*'@localhost IDENTIFIED BY ***** ;", // mangled password for logging
 | |
|       nullptr,
 | |
|       nullptr
 | |
|     };
 | |
| 
 | |
|     if (err_) {
 | |
|       WSREP_ERROR("sst_user::user() mysql connect failed: %d (%s)",
 | |
|                   err_, mysql_->errstr());
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     err_ = mysql_->disable_replication();
 | |
|     if (err_) {
 | |
|       WSREP_ERROR("sst_user::user() disable replication failed: %d (%s)",
 | |
|                   err_, mysql_->errstr());
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     err_ = run_sql_commands(*mysql_, create_sst_user, name_, password_);
 | |
|     if (err_) {
 | |
|       WSREP_ERROR("sst_user::user() create user failed: %d (%s)",
 | |
|                   err_, err_ < 0 ? strerror(-err_) : mysql_->errstr());
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     err_ = run_sql_commands(*mysql_, sst_user_grants(method), name_, password_);
 | |
|     if (err_) {
 | |
|       WSREP_ERROR("sst_user::user() grant privileges failed: %d (%s)",
 | |
|                   err_, err_ < 0 ? strerror(-err_) : mysql_->errstr());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* ctor in case SST user was externally supplied */
 | |
|   sst_user(const std::string& name, const std::string& password) :
 | |
|     mysql_(nullptr),
 | |
|     name_(name),
 | |
|     password_(password),
 | |
|     err_(0)
 | |
|   {}
 | |
| 
 | |
|   ~sst_user()
 | |
|   {
 | |
|     if (mysql_) {
 | |
|       /* Need to cleanup SST user */
 | |
|       static const char *cmds[] = {
 | |
|         "DROP USER IF EXISTS '%s'@localhost;",
 | |
|         nullptr,
 | |
|         nullptr,
 | |
|         nullptr
 | |
|       };
 | |
|       err_ = run_sql_commands(*mysql_, cmds, name_, password_);
 | |
|       if (err_) {
 | |
|         WSREP_ERROR("sst_user::~user() user '%s' cleanup failed: %d (%s)",
 | |
|                     name_.c_str(),
 | |
|                     err_, err_ < 0 ? strerror(-err_) : mysql_->errstr());
 | |
|       }
 | |
|       delete mysql_;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const std::string& name() const { return name_; }
 | |
|   const std::string& password() const { return password_; }
 | |
|   int err() const { return err_; }
 | |
| };
 | |
| 
 | |
| static int
 | |
| write_auth_info(FILE* const file, const char* method,
 | |
|                 const sst_auth& auth, sst_user** ret_user)
 | |
| {
 | |
|   int err;
 | |
|   sst_user* user(nullptr);
 | |
| 
 | |
|   try {
 | |
|     if (auth.name_.length() > 0) {
 | |
|       /* User supplied wsrep_sst_auth */
 | |
|       user= new sst_user(auth.name_, auth.pswd_);
 | |
|     }
 | |
|     else {
 | |
|       /* No wsrep_sst_auth, create a temporary user for SST */
 | |
|       user= new sst_user(method);
 | |
|     }
 | |
|     err= user->err();
 | |
|   }
 | |
|   catch (...) {
 | |
|     err= ENOMEM;
 | |
|   }
 | |
|   *ret_user= user;
 | |
| 
 | |
|   if (err)
 | |
|   {
 | |
|     WSREP_ERROR("Failed to create SST user: %d (%s)", err, strerror(err));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     int ret= fprintf(file,
 | |
|                      "sst_user=%s\n"
 | |
|                      "sst_password=%s\n",
 | |
|                      user->name().c_str(),
 | |
|                      user->password().c_str());
 | |
|     if (ret < 0)
 | |
|     {
 | |
|       WSREP_ERROR("write_auth_info(): fprintf(1) failed: %d", ret);
 | |
|       err= (ret < 0 ? ret : -EMSGSIZE);
 | |
|     }
 | |
| 
 | |
|     if (auth.remote_name_.length())
 | |
|     {
 | |
|       ret= fprintf(file,
 | |
|                    "sst_remote_user=%s\n"
 | |
|                    "sst_remote_password=%s\n",
 | |
|                    auth.remote_name_.c_str(),
 | |
|                    auth.remote_pswd_.c_str());
 | |
|       if (ret < 0)
 | |
|       {
 | |
|         WSREP_ERROR("write_auth_info(): fprintf(2) failed: %d", ret);
 | |
|         err= (ret < 0 ? ret : -EMSGSIZE);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return err;
 | |
| }
 | |
| 
 | |
| static void sst_reject_queries(my_bool close_conn)
 | |
| {
 | |
|   WSREP_INFO("Rejecting client queries for the duration of SST.");
 | |
|   if (TRUE == close_conn) wsrep_close_client_connections(FALSE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Create a file under data directory.
 | |
| */
 | |
| static int sst_create_file(const char *name, const char *content)
 | |
| {
 | |
|   int err= 0;
 | |
|   char *real_name;
 | |
|   char *tmp_name;
 | |
|   ssize_t len;
 | |
|   FILE *file;
 | |
| 
 | |
|   len= strlen(mysql_real_data_home) + strlen(name) + 2;
 | |
|   real_name= (char *) alloca(len);
 | |
| 
 | |
|   snprintf(real_name, (size_t) len, "%s/%s", mysql_real_data_home, name);
 | |
| 
 | |
|   tmp_name= (char *) alloca(len + 4);
 | |
|   snprintf(tmp_name, (size_t) len + 4, "%s.tmp", real_name);
 | |
| 
 | |
|   file= fopen(tmp_name, "w+");
 | |
| 
 | |
|   if (0 == file)
 | |
|   {
 | |
|     err= errno;
 | |
|     WSREP_ERROR("Failed to open '%s': %d (%s)", tmp_name, err, strerror(err));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     // Write the specified content into the file.
 | |
|     if (content != NULL)
 | |
|     {
 | |
|       fprintf(file, "%s\n", content);
 | |
|       fsync(fileno(file));
 | |
|     }
 | |
| 
 | |
|     fclose(file);
 | |
| 
 | |
|     if (rename(tmp_name, real_name) == -1)
 | |
|     {
 | |
|       err= errno;
 | |
|       WSREP_ERROR("Failed to rename '%s' to '%s': %d (%s)", tmp_name,
 | |
|                   real_name, err, strerror(err));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return err;
 | |
| }
 | |
| 
 | |
| wsrep_seqno_t wsrep_locked_seqno= WSREP_SEQNO_UNDEFINED;
 | |
| 
 | |
| static int sst_flush_tables(THD* thd)
 | |
| {
 | |
|   WSREP_INFO("Flushing tables for SST...");
 | |
| 
 | |
|   int err= 0;
 | |
|   int not_used;
 | |
|   /*
 | |
|     Files created to notify the SST script about the outcome of table flush
 | |
|     operation.
 | |
|   */
 | |
|   const char *flush_success= "tables_flushed";
 | |
|   const char *flush_error= "sst_error";
 | |
| 
 | |
|   CHARSET_INFO *current_charset= thd->variables.character_set_client;
 | |
| 
 | |
|   if (!is_supported_parser_charset(current_charset))
 | |
|   {
 | |
|       /* Do not use non-supported parser character sets */
 | |
|       WSREP_WARN("Current client character set is non-supported parser character set: %s", current_charset->cs_name.str);
 | |
|       thd->variables.character_set_client= &my_charset_latin1;
 | |
|       WSREP_WARN("For SST temporally setting character set to : %s",
 | |
|                  my_charset_latin1.cs_name.str);
 | |
|   }
 | |
| 
 | |
|   if (run_sql_command_thd(thd, "FLUSH TABLES WITH READ LOCK"))
 | |
|   {
 | |
|     err= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       Make sure logs are flushed after global read lock acquired. In case
 | |
|       reload fails, we must also release the acquired FTWRL.
 | |
|     */
 | |
|     if (reload_acl_and_cache(thd, REFRESH_ENGINE_LOG | REFRESH_BINARY_LOG,
 | |
|                              (TABLE_LIST*) 0, ¬_used))
 | |
|     {
 | |
|       thd->global_read_lock.unlock_global_read_lock(thd);
 | |
|       err= -1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   thd->variables.character_set_client= current_charset;
 | |
| 
 | |
|   if (err)
 | |
|   {
 | |
|     WSREP_ERROR("Failed to flush and lock tables");
 | |
| 
 | |
|     /*
 | |
|       The SST must be aborted as the flush tables failed. Notify this to SST
 | |
|       script by creating the error file.
 | |
|     */
 | |
|     int tmp;
 | |
|     if ((tmp= sst_create_file(flush_error, NULL))) {
 | |
|       err= tmp;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     ha_disable_internal_writes(true);
 | |
| 
 | |
|     WSREP_INFO("Tables flushed.");
 | |
| 
 | |
|     // Create a file with cluster state ID and wsrep_gtid_domain_id.
 | |
|     char content[100];
 | |
|     snprintf(content, sizeof(content), "%s:%lld %d\n", wsrep_cluster_state_uuid,
 | |
|              (long long)wsrep_locked_seqno, wsrep_gtid_server.domain_id);
 | |
|     WSREP_DEBUG("sst_flush_tables : %s:%lld %d", wsrep_cluster_state_uuid,
 | |
|                 (long long)wsrep_locked_seqno, wsrep_gtid_server.domain_id);
 | |
|     err= sst_create_file(flush_success, content);
 | |
| 
 | |
|     if (err)
 | |
|       WSREP_INFO("Creating file for flush_success failed %d",err);
 | |
| 
 | |
|     const char base_name[]= "tables_flushed";
 | |
|     ssize_t const full_len= strlen(mysql_real_data_home) + strlen(base_name)+2;
 | |
|     char *real_name= (char*) my_malloc(key_memory_WSREP, full_len, 0);
 | |
|     sprintf(real_name, "%s/%s", mysql_real_data_home, base_name);
 | |
|     char *tmp_name= (char*) my_malloc(key_memory_WSREP, full_len + 4, 0);
 | |
|     sprintf(tmp_name, "%s.tmp", real_name);
 | |
| 
 | |
|     FILE* file= fopen(tmp_name, "w+");
 | |
|     if (0 == file)
 | |
|     {
 | |
|       err= errno;
 | |
|       WSREP_ERROR("Failed to open '%s': %d (%s)", tmp_name, err,strerror(err));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       Wsrep_server_state& server_state= Wsrep_server_state::instance();
 | |
|       std::ostringstream uuid_oss;
 | |
| 
 | |
|       uuid_oss << server_state.current_view().state_id().id();
 | |
| 
 | |
|       fprintf(file, "%s:%lld %u\n",
 | |
|               uuid_oss.str().c_str(), server_state.pause_seqno().get(),
 | |
|               wsrep_gtid_server.domain_id);
 | |
|       fsync(fileno(file));
 | |
|       fclose(file);
 | |
|       if (rename(tmp_name, real_name) == -1)
 | |
|       {
 | |
|         err= errno;
 | |
|         WSREP_ERROR("Failed to rename '%s' to '%s': %d (%s)",
 | |
|                      tmp_name, real_name, err,strerror(err));
 | |
|       }
 | |
|     }
 | |
|     my_free(real_name);
 | |
|     my_free(tmp_name);
 | |
|     if (err)
 | |
|       ha_disable_internal_writes(false);
 | |
|   }
 | |
| 
 | |
|   return err;
 | |
| }
 | |
| 
 | |
| static void* sst_donor_thread (void* a)
 | |
| {
 | |
|   sst_thread_arg* arg= (sst_thread_arg*)a;
 | |
|   std::string const method(arg->method_);
 | |
|   sst_auth const auth(arg->auth_);
 | |
| 
 | |
|   WSREP_INFO("Initiating SST/IST transfer on DONOR side (%s)", arg->cmd);
 | |
| 
 | |
|   int  err= 1;
 | |
|   bool locked= false;
 | |
| 
 | |
|   const char*  out= NULL;
 | |
|   const size_t out_len= 128;
 | |
|   char         out_buf[out_len];
 | |
| 
 | |
|   wsrep_uuid_t  ret_uuid= WSREP_UUID_UNDEFINED;
 | |
|   // seqno of complete SST
 | |
|   wsrep_seqno_t ret_seqno= WSREP_SEQNO_UNDEFINED;
 | |
| 
 | |
|   wsp::process proc(arg->cmd, "rw", arg->env);
 | |
|   err= -proc.error();
 | |
| 
 | |
|   if (err)
 | |
|   {
 | |
|     WSREP_ERROR("Failed to start SST process: %d", err);
 | |
|   }
 | |
| 
 | |
|   /* Inform server about SST script startup and release TO isolation */
 | |
|   mysql_mutex_lock   (&arg->lock);
 | |
|   arg->err= -err;
 | |
|   mysql_cond_signal  (&arg->cond);
 | |
|   mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that.
 | |
|   arg= nullptr;
 | |
| 
 | |
|   wsp::thd thd;
 | |
| 
 | |
|   err = thd.err();
 | |
|   if (err)
 | |
|   {
 | |
|     WSREP_ERROR("Can't initialize donor THD: %d", err);
 | |
|   }
 | |
| 
 | |
|   sst_user* user(nullptr);
 | |
| 
 | |
|   if (proc.to() && !err)
 | |
|   {
 | |
|     /* At this point SST process is waiting for input or EOF on STDIN.
 | |
|      * Create SST user and send sensitive information to it. */
 | |
|     err= write_auth_info(proc.to(), method.c_str(), auth, &user);
 | |
|     // Close the pipe, so that the SST process gets an EOF
 | |
|     proc.close_to();
 | |
|   }
 | |
| 
 | |
|   /* This cleanups SST user on thread exit, i.e. on SST end, whether it was
 | |
|    * successfully created or not */
 | |
|   class cleanup_sst_user{
 | |
|     sst_user* const user_;
 | |
|   public:
 | |
|     cleanup_sst_user(sst_user* user) : user_(user) {}
 | |
|     ~cleanup_sst_user() {
 | |
|       WSREP_INFO("Cleaning up SST user.");
 | |
|       delete user_;
 | |
|     }
 | |
|   } cleanup_sst_user_guard(user);
 | |
| 
 | |
|   if (proc.from() && !err)
 | |
|   {
 | |
|     long long total= 0;
 | |
|     long long complete= 0;
 | |
|     // total from previous stages
 | |
|     long long total_prev= 0;
 | |
| 
 | |
| wait_signal:
 | |
|     out= my_fgets (out_buf, out_len, proc.from());
 | |
| 
 | |
|     if (out)
 | |
|     {
 | |
|       static const char magic_flush[]= "flush tables";
 | |
|       static const char magic_cont[]= "continue";
 | |
|       static const char magic_done[]= "done";
 | |
|       static const size_t done_len=strlen(magic_done);
 | |
|       static const char magic_total[]= "total";
 | |
|       static const size_t total_len=strlen(magic_total);
 | |
|       static const char magic_complete[]= "complete";
 | |
|       static const size_t complete_len=strlen(magic_complete);
 | |
|       static const int from= WSREP_MEMBER_DONOR;
 | |
| 
 | |
|       if (!strncasecmp (out, magic_complete, complete_len))
 | |
|       {
 | |
|         sst_handle_complete(out + complete_len, total_prev, &total, &complete,
 | |
|                             from);
 | |
|         goto wait_signal;
 | |
|       }
 | |
|       else if (!strncasecmp (out, magic_total, total_len))
 | |
|       {
 | |
|         sst_handle_total(out + total_len, &total_prev, &total, &complete, from);
 | |
|         goto wait_signal;
 | |
|       }
 | |
|       else if (!strcasecmp (out, magic_flush))
 | |
|       {
 | |
|         err= sst_flush_tables (thd.ptr);
 | |
| 
 | |
|         if (!err)
 | |
|         {
 | |
|           locked= true;
 | |
|           /*
 | |
|             Lets also keep statements that modify binary logs (like RESET LOGS,
 | |
|             RESET MASTER) from proceeding until the files have been transferred
 | |
|             to the joiner node.
 | |
|           */
 | |
|           if (mysql_bin_log.is_open())
 | |
|             mysql_mutex_lock(mysql_bin_log.get_log_lock());
 | |
| 
 | |
|           WSREP_INFO("Donor state reached");
 | |
| 
 | |
| #ifdef ENABLED_DEBUG_SYNC
 | |
|           DBUG_EXECUTE_IF("sync.wsrep_donor_state",
 | |
|           {
 | |
|             const char act[]=
 | |
|                              "now "
 | |
|                              "SIGNAL sync.wsrep_donor_state_reached "
 | |
|                              "WAIT_FOR signal.wsrep_donor_state";
 | |
|             assert(!debug_sync_set_action(thd.ptr,
 | |
|                                           STRING_WITH_LEN(act)));
 | |
|           };);
 | |
| #endif
 | |
| 
 | |
|           goto wait_signal;
 | |
|         }
 | |
|       }
 | |
|       else if (!strcasecmp (out, magic_cont))
 | |
|       {
 | |
|         if (locked)
 | |
|         {
 | |
|           locked= false;
 | |
|           ha_disable_internal_writes(false);
 | |
|           if (mysql_bin_log.is_open())
 | |
|             mysql_mutex_unlock(mysql_bin_log.get_log_lock());
 | |
|           thd.ptr->global_read_lock.unlock_global_read_lock(thd.ptr);
 | |
|         }
 | |
|         err=  0;
 | |
|         goto wait_signal;
 | |
|       }
 | |
|       else if (!strncasecmp (out, magic_done, done_len))
 | |
|       {
 | |
|         err= sst_scan_uuid_seqno (out + strlen(magic_done) + 1,
 | |
|                                   &ret_uuid, &ret_seqno);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         WSREP_WARN("Received unknown signal: '%s'", out);
 | |
|         /* since it is the end of the loop, we must set error code */
 | |
|         err=-EINVAL;
 | |
|         proc.wait();
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       WSREP_ERROR("Failed to read from: %s", proc.cmd());
 | |
|       proc.wait();
 | |
|     }
 | |
|     if (!err && proc.error()) err= -proc.error();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     WSREP_ERROR("Failed to execute: %s : %d (%s)",
 | |
|                 proc.cmd(), err, strerror(err));
 | |
|   }
 | |
| 
 | |
|   if (locked) // don't forget to unlock server before return
 | |
|   {
 | |
|     ha_disable_internal_writes(false);
 | |
|     if (mysql_bin_log.is_open())
 | |
|     {
 | |
|       mysql_mutex_assert_owner(mysql_bin_log.get_log_lock());
 | |
|       mysql_mutex_unlock(mysql_bin_log.get_log_lock());
 | |
|     }
 | |
|     thd.ptr->global_read_lock.unlock_global_read_lock(thd.ptr);
 | |
|   }
 | |
| 
 | |
|   wsrep::gtid gtid(wsrep::id(ret_uuid.data, sizeof(ret_uuid.data)),
 | |
|                    wsrep::seqno(err ? wsrep::seqno::undefined() :
 | |
|                                 wsrep::seqno(ret_seqno)));
 | |
| 
 | |
| #ifdef ENABLED_DEBUG_SYNC
 | |
|   DBUG_EXECUTE_IF("sync.wsrep_sst_donor_after_donation", {
 | |
|     const char act[]= "now "
 | |
|                       "SIGNAL sync.wsrep_sst_donor_after_donation_reached "
 | |
|                       "WAIT_FOR signal.wsrep_sst_donor_after_donation_continue";
 | |
|     DBUG_ASSERT(!debug_sync_set_action(thd.ptr, STRING_WITH_LEN(act)));
 | |
|   });
 | |
| #endif /* ENABLED_DEBUG_SYNC */
 | |
| 
 | |
|   Wsrep_server_state::instance().sst_sent(gtid, err);
 | |
| 
 | |
|   proc.wait();
 | |
| 
 | |
|   wsrep_donor_monitor_end();
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| /* There used to be a separate method for mysqldump SST, but now everything
 | |
|  * goes through this method. */
 | |
| static int sst_donate_other (const char*        method,
 | |
|                              const char*        addr,
 | |
|                              const wsrep::gtid& gtid,
 | |
|                              const bool         bypass,
 | |
|                              const sst_auth&    auth,
 | |
|                              char**             env) // carries auth info
 | |
| {
 | |
|   bool extra_args;
 | |
|   size_t const cmd_len= estimate_cmd_len(&extra_args);
 | |
|   wsp::string cmd_str(cmd_len);
 | |
| 
 | |
|   if (!cmd_str())
 | |
|   {
 | |
|     WSREP_ERROR("sst_donate_other(): "
 | |
|                 "could not allocate cmd buffer of %zd bytes", cmd_len);
 | |
|     return -ENOMEM;
 | |
|   }
 | |
| 
 | |
|   char* binlog_opt_val= NULL;
 | |
|   char* binlog_index_opt_val= NULL;
 | |
| 
 | |
|   int ret;
 | |
|   if ((ret= generate_binlog_opt_val(&binlog_opt_val)))
 | |
|   {
 | |
|     WSREP_ERROR("sst_donate_other(): generate_binlog_opt_val() failed: %d",ret);
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   if ((ret= generate_binlog_index_opt_val(&binlog_index_opt_val)))
 | |
|   {
 | |
|     WSREP_ERROR("sst_prepare_other(): generate_binlog_index_opt_val() failed %d",
 | |
|                 ret);
 | |
|     if (binlog_opt_val) my_free(binlog_opt_val);
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   make_wsrep_defaults_file();
 | |
| 
 | |
|   std::ostringstream uuid_oss;
 | |
|   uuid_oss << gtid.id();
 | |
|   ret= snprintf (cmd_str(), cmd_len,
 | |
|                  "wsrep_sst_%s "
 | |
|                  WSREP_SST_OPT_ROLE " 'donor' "
 | |
|                  WSREP_SST_OPT_ADDR " '%s' "
 | |
|                  WSREP_SST_OPT_LPORT " %u "
 | |
|                  WSREP_SST_OPT_SOCKET " '%s' "
 | |
|                  WSREP_SST_OPT_PROGRESS " %d "
 | |
|                  WSREP_SST_OPT_DATA " '%s' "
 | |
|                  "%s"
 | |
|                  WSREP_SST_OPT_GTID " '%s:%lld' "
 | |
|                  WSREP_SST_OPT_GTID_DOMAIN_ID " %d"
 | |
|                  "%s"
 | |
|                  "%s"
 | |
|                  "%s",
 | |
|                  method, addr, mysqld_port, mysqld_unix_port,
 | |
|                  wsrep_debug ? 1 : 0,
 | |
|                  mysql_real_data_home,
 | |
|                  wsrep_defaults_file,
 | |
|                  uuid_oss.str().c_str(), gtid.seqno().get(), wsrep_gtid_server.domain_id,
 | |
|                  binlog_opt_val, binlog_index_opt_val,
 | |
|                  bypass ? " " WSREP_SST_OPT_BYPASS : "");
 | |
| 
 | |
|   my_free(binlog_opt_val);
 | |
|   my_free(binlog_index_opt_val);
 | |
| 
 | |
|   if (ret < 0 || size_t(ret) >= cmd_len)
 | |
|   {
 | |
|     WSREP_ERROR("sst_donate_other(): snprintf() failed: %d", ret);
 | |
|     return (ret < 0 ? ret : -EMSGSIZE);
 | |
|   }
 | |
| 
 | |
|   if (extra_args)
 | |
|     copy_orig_argv(cmd_str() + ret);
 | |
| 
 | |
|   if (!bypass && wsrep_sst_donor_rejects_queries) sst_reject_queries(FALSE);
 | |
| 
 | |
|   pthread_t tmp;
 | |
|   sst_thread_arg arg(method, cmd_str(), auth, env);
 | |
| 
 | |
|   mysql_mutex_lock (&arg.lock);
 | |
| 
 | |
|   ret= mysql_thread_create (key_wsrep_sst_donor, &tmp, NULL, sst_donor_thread, &arg);
 | |
| 
 | |
|   if (ret)
 | |
|   {
 | |
|     WSREP_ERROR("sst_donate_other(): mysql_thread_create() failed: %d (%s)",
 | |
|                 ret, strerror(ret));
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   mysql_cond_wait (&arg.cond, &arg.lock);
 | |
| 
 | |
|   WSREP_INFO("sst_donor_thread signaled with %d", arg.err);
 | |
|   return arg.err;
 | |
| }
 | |
| 
 | |
| int wsrep_sst_donate(const std::string& msg,
 | |
|                      const wsrep::gtid& current_gtid,
 | |
|                      const bool         bypass)
 | |
| {
 | |
|   const char* method= msg.data();
 | |
|   size_t method_len= strlen (method);
 | |
| 
 | |
|   if (check_request_str(method, filename_char, true))
 | |
|   {
 | |
|     WSREP_ERROR("Bad SST method name. SST canceled.");
 | |
|     return WSREP_CB_FAILURE;
 | |
|   }
 | |
| 
 | |
|   const char* data= method + method_len + 1;
 | |
| 
 | |
|   /* check for auth@addr separator */
 | |
|   const char* addr= strrchr(data, '@');
 | |
|   wsp::string remote_auth;
 | |
|   if (addr)
 | |
|   {
 | |
|     remote_auth.set(strndup(data, addr - data));
 | |
|     addr++;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     // no auth part
 | |
|     addr= data;
 | |
|   }
 | |
| 
 | |
|   if (check_request_str(addr, address_char, true))
 | |
|   {
 | |
|     WSREP_ERROR("Bad SST address string. SST canceled.");
 | |
|     return WSREP_CB_FAILURE;
 | |
|   }
 | |
| 
 | |
|   /* Set up auth info (from <user>:<password> strings) */
 | |
|   sst_auth auth;
 | |
|   if (sst_auth_real)
 | |
|   {
 | |
|     /* User supplied non-trivial wsrep_sst_auth, use it */
 | |
|     const char* col= sst_strchrnul(sst_auth_real, ':');
 | |
|     auth.name_ = std::string(sst_auth_real, col - sst_auth_real);
 | |
|     auth.pswd_ = std::string(':' == *col ? col + 1 : "");
 | |
|   }
 | |
|   else if (0 == strcmp(method, "rsync"))
 | |
|   {
 | |
|     /* This dummy auth info will prevent SST account creation for rsync SST -
 | |
|      * as it does not need any ATM. */
 | |
|     auth.name_ = "rsync";
 | |
|     auth.pswd_ = "none";
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Empty auth will trigger automatic SST account creation. */
 | |
|   }
 | |
|   if (remote_auth())
 | |
|   {
 | |
|     /* wsp::string is just a dynamically allocated char* underneath
 | |
|      * so we can safely do all that arithmetics */
 | |
|     const char* col= sst_strchrnul(remote_auth(), ':');
 | |
|     auth.remote_name_ = std::string(remote_auth(), col - remote_auth());
 | |
|     auth.remote_pswd_ = std::string(':' == *col ? col + 1 : "");
 | |
|   }
 | |
| 
 | |
|   wsp::env env(NULL);
 | |
|   if (env.error())
 | |
|   {
 | |
|     WSREP_ERROR("wsrep_sst_donate_cb(): env var ctor failed: %d", -env.error());
 | |
|     return WSREP_CB_FAILURE;
 | |
|   }
 | |
| 
 | |
|   int ret;
 | |
|   if (data_home_dir)
 | |
|   {
 | |
|     if ((ret= sst_append_env_var(env, DATA_HOME_DIR_ENV, data_home_dir)))
 | |
|     {
 | |
|       WSREP_ERROR("wsrep_sst_donate_cb(): appending data "
 | |
|                   "directory failed: %d", ret);
 | |
|       return WSREP_CB_FAILURE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   sst_donor_completed= false;
 | |
|   pthread_t monitor;
 | |
| 
 | |
|   ret= mysql_thread_create (key_wsrep_sst_donor_monitor, &monitor, NULL, wsrep_sst_donor_monitor_thread, NULL);
 | |
| 
 | |
|   if (ret)
 | |
|   {
 | |
|     WSREP_ERROR("sst_donate: mysql_thread_create() failed: %d (%s)",
 | |
|                 ret, strerror(ret));
 | |
|     return WSREP_CB_FAILURE;
 | |
|   }
 | |
| 
 | |
|   ret= sst_donate_other(method, addr, current_gtid, bypass, auth, env());
 | |
| 
 | |
|   return (ret >= 0 ? WSREP_CB_SUCCESS : WSREP_CB_FAILURE);
 | |
| }
 | 
