mirror of https://github.com/MariaDB/server
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							4241 lines
						
					
					
						
							135 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							4241 lines
						
					
					
						
							135 KiB
						
					
					
				| /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. | |
|    Copyright (c) 2008, 2019, MariaDB Corporation | |
|  | |
|    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 St, Fifth Floor, Boston, MA 02110-1301  USA */ | |
| 
 | |
| #include <my_global.h> | |
| #include "sql_priv.h" | |
| #include "unireg.h" | |
| #include "sql_base.h" | |
| #include "sql_parse.h"                          // check_access | |
| #ifdef HAVE_REPLICATION | |
|  | |
| #include "rpl_mi.h" | |
| #include "rpl_rli.h" | |
| #include "sql_repl.h" | |
| #include "sql_acl.h"                            // SUPER_ACL | |
| #include "log_event.h" | |
| #include "rpl_filter.h" | |
| #include <my_dir.h> | |
| #include "rpl_handler.h" | |
| #include "debug_sync.h" | |
|  | |
| 
 | |
| enum enum_gtid_until_state { | |
|   GTID_UNTIL_NOT_DONE, | |
|   GTID_UNTIL_STOP_AFTER_STANDALONE, | |
|   GTID_UNTIL_STOP_AFTER_TRANSACTION | |
| }; | |
| 
 | |
| 
 | |
| int max_binlog_dump_events = 0; // unlimited | |
| my_bool opt_sporadic_binlog_dump_fail = 0; | |
| #ifndef DBUG_OFF | |
| static int binlog_dump_count = 0; | |
| #endif | |
|  | |
| extern TYPELIB binlog_checksum_typelib; | |
| 
 | |
| 
 | |
| static int | |
| fake_event_header(String* packet, Log_event_type event_type, ulong extra_len, | |
|                   my_bool *do_checksum, ha_checksum *crc, const char** errmsg, | |
|                   uint8 checksum_alg_arg, uint32 end_pos) | |
| { | |
|   char header[LOG_EVENT_HEADER_LEN]; | |
|   ulong event_len; | |
| 
 | |
|   *do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF && | |
|     checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF; | |
| 
 | |
|   /* | |
|     'when' (the timestamp) is set to 0 so that slave could distinguish between | |
|     real and fake Rotate events (if necessary) | |
|   */ | |
|   memset(header, 0, 4); | |
|   header[EVENT_TYPE_OFFSET] = (uchar)event_type; | |
|   event_len=  LOG_EVENT_HEADER_LEN + extra_len + | |
|     (*do_checksum ? BINLOG_CHECKSUM_LEN : 0); | |
|   int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); | |
|   int4store(header + EVENT_LEN_OFFSET, event_len); | |
|   int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F); | |
|   // TODO: check what problems this may cause and fix them | |
|   int4store(header + LOG_POS_OFFSET, end_pos); | |
|   if (packet->append(header, sizeof(header))) | |
|   { | |
|     *errmsg= "Failed due to out-of-memory writing event"; | |
|     return -1; | |
|   } | |
|   if (*do_checksum) | |
|   { | |
|     *crc= my_checksum(0, (uchar*)header, sizeof(header)); | |
|   } | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| static int | |
| fake_event_footer(String *packet, my_bool do_checksum, ha_checksum crc, const char **errmsg) | |
| { | |
|   if (do_checksum) | |
|   { | |
|     char b[BINLOG_CHECKSUM_LEN]; | |
|     int4store(b, crc); | |
|     if (packet->append(b, sizeof(b))) | |
|     { | |
|       *errmsg= "Failed due to out-of-memory writing event checksum"; | |
|       return -1; | |
|     } | |
|   } | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| static int | |
| fake_event_write(NET *net, String *packet, const char **errmsg) | |
| { | |
|   if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) | |
|   { | |
|     *errmsg = "failed on my_net_write()"; | |
|     return -1; | |
|   } | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Helper structure, used to pass miscellaneous info from mysql_binlog_send() | |
|   into the helper functions that it calls. | |
| */ | |
| struct binlog_send_info { | |
|   rpl_binlog_state until_binlog_state; | |
|   slave_connection_state gtid_state; | |
|   THD *thd; | |
|   NET *net; | |
|   String *packet; | |
|   char *log_file_name; | |
|   slave_connection_state *until_gtid_state; | |
|   Format_description_log_event *fdev; | |
|   int mariadb_slave_capability; | |
|   enum_gtid_skip_type gtid_skip_group; | |
|   enum_gtid_until_state gtid_until_group; | |
|   ushort flags; | |
|   uint8 current_checksum_alg; | |
|   bool slave_gtid_strict_mode; | |
|   bool send_fake_gtid_list; | |
|   bool slave_gtid_ignore_duplicates; | |
|   bool using_gtid_state; | |
| 
 | |
|   binlog_send_info(THD *thd_arg, String *packet_arg, ushort flags_arg, char *lfn) | |
|     : thd(thd_arg), net(&thd_arg->net), packet(packet_arg), | |
|       log_file_name(lfn), until_gtid_state(NULL), fdev(NULL), | |
|       gtid_skip_group(GTID_SKIP_NOT), gtid_until_group(GTID_UNTIL_NOT_DONE), | |
|       flags(flags_arg), current_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), | |
|       slave_gtid_strict_mode(false), send_fake_gtid_list(false), | |
|       slave_gtid_ignore_duplicates(false) | |
|   { } | |
| }; | |
| 
 | |
| /* | |
|     fake_rotate_event() builds a fake (=which does not exist physically in any | |
|     binlog) Rotate event, which contains the name of the binlog we are going to | |
|     send to the slave (because the slave may not know it if it just asked for | |
|     MASTER_LOG_FILE='', MASTER_LOG_POS=4). | |
|     < 4.0.14, fake_rotate_event() was called only if the requested pos was 4. | |
|     After this version we always call it, so that a 3.23.58 slave can rely on | |
|     it to detect if the master is 4.0 (and stop) (the _fake_ Rotate event has | |
|     zeros in the good positions which, by chance, make it possible for the 3.23 | |
|     slave to detect that this event is unexpected) (this is luck which happens | |
|     because the master and slave disagree on the size of the header of | |
|     Log_event). | |
|  | |
|     Relying on the event length of the Rotate event instead of these | |
|     well-placed zeros was not possible as Rotate events have a variable-length | |
|     part. | |
| */ | |
| 
 | |
| static int fake_rotate_event(binlog_send_info *info, ulonglong position, | |
|                              const char** errmsg, uint8 checksum_alg_arg) | |
| { | |
|   DBUG_ENTER("fake_rotate_event"); | |
|   char buf[ROTATE_HEADER_LEN+100]; | |
|   my_bool do_checksum; | |
|   int err; | |
|   char* p = info->log_file_name+dirname_length(info->log_file_name); | |
|   uint ident_len = (uint) strlen(p); | |
|   String *packet= info->packet; | |
|   ha_checksum crc; | |
| 
 | |
|   if ((err= fake_event_header(packet, ROTATE_EVENT, | |
|                               ident_len + ROTATE_HEADER_LEN, &do_checksum, &crc, | |
|                               errmsg, checksum_alg_arg, 0))) | |
|     DBUG_RETURN(err); | |
| 
 | |
|   int8store(buf+R_POS_OFFSET,position); | |
|   packet->append(buf, ROTATE_HEADER_LEN); | |
|   packet->append(p, ident_len); | |
| 
 | |
|   if (do_checksum) | |
|   { | |
|     crc= my_checksum(crc, (uchar*)buf, ROTATE_HEADER_LEN); | |
|     crc= my_checksum(crc, (uchar*)p, ident_len); | |
|   } | |
| 
 | |
|   if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) || | |
|       (err= fake_event_write(info->net, packet, errmsg))) | |
|     DBUG_RETURN(err); | |
| 
 | |
|   DBUG_RETURN(0); | |
| } | |
| 
 | |
| 
 | |
| static int fake_gtid_list_event(binlog_send_info *info, | |
|                                 Gtid_list_log_event *glev, const char** errmsg, | |
|                                 uint32 current_pos) | |
| { | |
|   my_bool do_checksum; | |
|   int err; | |
|   ha_checksum crc; | |
|   char buf[128]; | |
|   String str(buf, sizeof(buf), system_charset_info); | |
|   String* packet= info->packet; | |
| 
 | |
|   str.length(0); | |
|   if (glev->to_packet(&str)) | |
|   { | |
|     *errmsg= "Failed due to out-of-memory writing Gtid_list event"; | |
|     return -1; | |
|   } | |
|   if ((err= fake_event_header(packet, GTID_LIST_EVENT, | |
|                               str.length(), &do_checksum, &crc, | |
|                               errmsg, info->current_checksum_alg, current_pos))) | |
|     return err; | |
| 
 | |
|   packet->append(str); | |
|   if (do_checksum) | |
|   { | |
|     crc= my_checksum(crc, (uchar*)str.ptr(), str.length()); | |
|   } | |
| 
 | |
|   if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) || | |
|       (err= fake_event_write(info->net, packet, errmsg))) | |
|     return err; | |
| 
 | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Reset thread transmit packet buffer for event sending | |
|  | |
|   This function allocates header bytes for event transmission, and | |
|   should be called before store the event data to the packet buffer. | |
| */ | |
| static int reset_transmit_packet(THD *thd, ushort flags, | |
|                                  ulong *ev_offset, const char **errmsg) | |
| { | |
|   int ret= 0; | |
|   String *packet= &thd->packet; | |
| 
 | |
|   /* reserve and set default header */ | |
|   packet->length(0); | |
|   packet->set("\0", 1, &my_charset_bin); | |
| 
 | |
|   if (RUN_HOOK(binlog_transmit, reserve_header, (thd, flags, packet))) | |
|   { | |
|     *errmsg= "Failed to run hook 'reserve_header'"; | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     ret= 1; | |
|   } | |
|   *ev_offset= packet->length(); | |
|   return ret; | |
| } | |
| 
 | |
| static int send_file(THD *thd) | |
| { | |
|   NET* net = &thd->net; | |
|   int fd = -1, error = 1; | |
|   size_t bytes; | |
|   char fname[FN_REFLEN+1]; | |
|   const char *errmsg = 0; | |
|   int old_timeout; | |
|   unsigned long packet_len; | |
|   uchar buf[IO_SIZE];				// It's safe to alloc this | |
|   DBUG_ENTER("send_file"); | |
| 
 | |
|   /* | |
|     The client might be slow loading the data, give him wait_timeout to do | |
|     the job | |
|   */ | |
|   old_timeout= net->read_timeout; | |
|   my_net_set_read_timeout(net, thd->variables.net_wait_timeout); | |
| 
 | |
|   /* | |
|     We need net_flush here because the client will not know it needs to send | |
|     us the file name until it has processed the load event entry | |
|   */ | |
|   if (net_flush(net) || (packet_len = my_net_read(net)) == packet_error) | |
|   { | |
|     errmsg = "while reading file name"; | |
|     goto err; | |
|   } | |
| 
 | |
|   // terminate with \0 for fn_format | |
|   *((char*)net->read_pos +  packet_len) = 0; | |
|   fn_format(fname, (char*) net->read_pos + 1, "", "", 4); | |
|   // this is needed to make replicate-ignore-db | |
|   if (!strcmp(fname,"/dev/null")) | |
|     goto end; | |
| 
 | |
|   if ((fd= mysql_file_open(key_file_send_file, | |
|                            fname, O_RDONLY, MYF(0))) < 0) | |
|   { | |
|     errmsg = "on open of file"; | |
|     goto err; | |
|   } | |
| 
 | |
|   while ((long) (bytes= mysql_file_read(fd, buf, IO_SIZE, MYF(0))) > 0) | |
|   { | |
|     if (my_net_write(net, buf, bytes)) | |
|     { | |
|       errmsg = "while writing data to client"; | |
|       goto err; | |
|     } | |
|   } | |
| 
 | |
|  end: | |
|   if (my_net_write(net, (uchar*) "", 0) || net_flush(net) || | |
|       (my_net_read(net) == packet_error)) | |
|   { | |
|     errmsg = "while negotiating file transfer close"; | |
|     goto err; | |
|   } | |
|   error = 0; | |
| 
 | |
|  err: | |
|   my_net_set_read_timeout(net, old_timeout); | |
|   if (fd >= 0) | |
|     mysql_file_close(fd, MYF(0)); | |
|   if (errmsg) | |
|   { | |
|     sql_print_error("Failed in send_file() %s", errmsg); | |
|     DBUG_PRINT("error", ("%s", errmsg)); | |
|   } | |
|   DBUG_RETURN(error); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|    Internal to mysql_binlog_send() routine that recalculates checksum for | |
|    a FD event (asserted) that needs additional arranment prior sending to slave. | |
| */ | |
| inline void fix_checksum(String *packet, ulong ev_offset) | |
| { | |
|   /* recalculate the crc for this event */ | |
|   uint data_len = uint4korr(packet->ptr() + ev_offset + EVENT_LEN_OFFSET); | |
|   ha_checksum crc= my_checksum(0L, NULL, 0); | |
|   DBUG_ASSERT(data_len ==  | |
|               LOG_EVENT_MINIMAL_HEADER_LEN + FORMAT_DESCRIPTION_HEADER_LEN + | |
|               BINLOG_CHECKSUM_ALG_DESC_LEN + BINLOG_CHECKSUM_LEN); | |
|   crc= my_checksum(crc, (uchar *)packet->ptr() + ev_offset, data_len - | |
|                    BINLOG_CHECKSUM_LEN); | |
|   int4store(packet->ptr() + ev_offset + data_len - BINLOG_CHECKSUM_LEN, crc); | |
| } | |
| 
 | |
| 
 | |
| static user_var_entry * get_binlog_checksum_uservar(THD * thd) | |
| { | |
|   LEX_STRING name=  { C_STRING_WITH_LEN("master_binlog_checksum")}; | |
|   user_var_entry *entry=  | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry; | |
| } | |
| 
 | |
| /** | |
|   Function for calling in mysql_binlog_send | |
|   to check if slave initiated checksum-handshake. | |
|  | |
|   @param[in]    thd  THD to access a user variable | |
|  | |
|   @return        TRUE if handshake took place, FALSE otherwise | |
| */ | |
| 
 | |
| static bool is_slave_checksum_aware(THD * thd) | |
| { | |
|   DBUG_ENTER("is_slave_checksum_aware"); | |
|   user_var_entry *entry= get_binlog_checksum_uservar(thd); | |
|   DBUG_RETURN(entry? true  : false); | |
| } | |
| 
 | |
| /** | |
|   Function for calling in mysql_binlog_send | |
|   to get the value of @@binlog_checksum of the master at | |
|   time of checksum-handshake. | |
|  | |
|   The value tells the master whether to compute or not, and the slave | |
|   to verify or not the first artificial Rotate event's checksum. | |
|  | |
|   @param[in]    thd  THD to access a user variable | |
|  | |
|   @return       value of @@binlog_checksum alg according to | |
|                 @c enum enum_binlog_checksum_alg | |
| */ | |
| 
 | |
| static uint8 get_binlog_checksum_value_at_connect(THD * thd) | |
| { | |
|   uint8 ret; | |
| 
 | |
|   DBUG_ENTER("get_binlog_checksum_value_at_connect"); | |
|   user_var_entry *entry= get_binlog_checksum_uservar(thd); | |
|   if (!entry) | |
|   { | |
|     ret= BINLOG_CHECKSUM_ALG_UNDEF; | |
|   } | |
|   else | |
|   { | |
|     DBUG_ASSERT(entry->type == STRING_RESULT); | |
|     String str; | |
|     uint dummy_errors; | |
|     str.copy(entry->value, entry->length, &my_charset_bin, &my_charset_bin, | |
|              &dummy_errors); | |
|     ret= (uint8) find_type ((char*) str.ptr(), &binlog_checksum_typelib, 1) - 1; | |
|     DBUG_ASSERT(ret <= BINLOG_CHECKSUM_ALG_CRC32); // while it's just on CRC32 alg | |
|   } | |
|   DBUG_RETURN(ret); | |
| } | |
| 
 | |
| /* | |
|   Adjust the position pointer in the binary log file for all running slaves | |
|  | |
|   SYNOPSIS | |
|     adjust_linfo_offsets() | |
|     purge_offset	Number of bytes removed from start of log index file | |
|  | |
|   NOTES | |
|     - This is called when doing a PURGE when we delete lines from the | |
|       index log file | |
|  | |
|   REQUIREMENTS | |
|     - Before calling this function, we have to ensure that no threads are | |
|       using any binary log file before purge_offset.a | |
|  | |
|   TODO | |
|     - Inform the slave threads that they should sync the position | |
|       in the binary log file with flush_relay_log_info. | |
|       Now they sync is done for next read. | |
| */ | |
| 
 | |
| void adjust_linfo_offsets(my_off_t purge_offset) | |
| { | |
|   THD *tmp; | |
| 
 | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   I_List_iterator<THD> it(threads); | |
| 
 | |
|   while ((tmp=it++)) | |
|   { | |
|     LOG_INFO* linfo; | |
|     if ((linfo = tmp->current_linfo)) | |
|     { | |
|       mysql_mutex_lock(&linfo->lock); | |
|       /* | |
| 	Index file offset can be less that purge offset only if | |
| 	we just started reading the index file. In that case | |
| 	we have nothing to adjust | |
|       */ | |
|       if (linfo->index_file_offset < purge_offset) | |
| 	linfo->fatal = (linfo->index_file_offset != 0); | |
|       else | |
| 	linfo->index_file_offset -= purge_offset; | |
|       mysql_mutex_unlock(&linfo->lock); | |
|     } | |
|   } | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
| } | |
| 
 | |
| 
 | |
| bool log_in_use(const char* log_name) | |
| { | |
|   size_t log_name_len = strlen(log_name) + 1; | |
|   THD *tmp; | |
|   bool result = 0; | |
| 
 | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   I_List_iterator<THD> it(threads); | |
| 
 | |
|   while ((tmp=it++)) | |
|   { | |
|     LOG_INFO* linfo; | |
|     if ((linfo = tmp->current_linfo)) | |
|     { | |
|       mysql_mutex_lock(&linfo->lock); | |
|       result = !strncmp(log_name, linfo->log_file_name, log_name_len); | |
|       mysql_mutex_unlock(&linfo->lock); | |
|       if (result) | |
| 	break; | |
|     } | |
|   } | |
| 
 | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
|   return result; | |
| } | |
| 
 | |
| bool purge_error_message(THD* thd, int res) | |
| { | |
|   uint errcode; | |
| 
 | |
|   if ((errcode= purge_log_get_error_code(res)) != 0) | |
|   { | |
|     my_message(errcode, ER(errcode), MYF(0)); | |
|     return TRUE; | |
|   } | |
|   my_ok(thd); | |
|   return FALSE; | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a PURGE BINARY LOGS TO <log> command. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @param to_log Name of the last log to purge. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE failure | |
| */ | |
| bool purge_master_logs(THD* thd, const char* to_log) | |
| { | |
|   char search_file_name[FN_REFLEN]; | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_ok(thd); | |
|     return FALSE; | |
|   } | |
| 
 | |
|   mysql_bin_log.make_log_name(search_file_name, to_log); | |
|   return purge_error_message(thd, | |
| 			     mysql_bin_log.purge_logs(search_file_name, 0, 1, | |
| 						      1, NULL)); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a PURGE BINARY LOGS BEFORE <date> command. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @param purge_time Date before which logs should be purged. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE failure | |
| */ | |
| bool purge_master_logs_before_date(THD* thd, time_t purge_time) | |
| { | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_ok(thd); | |
|     return 0; | |
|   } | |
|   return purge_error_message(thd, | |
|                              mysql_bin_log.purge_logs_before_date(purge_time)); | |
| } | |
| 
 | |
| int test_for_non_eof_log_read_errors(int error, const char **errmsg) | |
| { | |
|   if (error == LOG_READ_EOF) | |
|     return 0; | |
|   my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|   switch (error) { | |
|   case LOG_READ_BOGUS: | |
|     *errmsg = "bogus data in log event"; | |
|     break; | |
|   case LOG_READ_TOO_LARGE: | |
|     *errmsg = "log event entry exceeded max_allowed_packet; \ | |
| Increase max_allowed_packet on master"; | |
|     break; | |
|   case LOG_READ_IO: | |
|     *errmsg = "I/O error reading log event"; | |
|     break; | |
|   case LOG_READ_MEM: | |
|     *errmsg = "memory allocation failed reading log event"; | |
|     break; | |
|   case LOG_READ_TRUNC: | |
|     *errmsg = "binlog truncated in the middle of event; consider out of disk space on master"; | |
|     break; | |
|   case LOG_READ_CHECKSUM_FAILURE: | |
|     *errmsg = "event read from binlog did not pass crc check"; | |
|     break; | |
|   default: | |
|     *errmsg = "unknown error reading log event on the master"; | |
|     break; | |
|   } | |
|   return error; | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   An auxiliary function for calling in mysql_binlog_send | |
|   to initialize the heartbeat timeout in waiting for a binlogged event. | |
|  | |
|   @param[in]    thd  THD to access a user variable | |
|  | |
|   @return        heartbeat period an ulonglong of nanoseconds | |
|                  or zero if heartbeat was not demanded by slave | |
| */  | |
| static ulonglong get_heartbeat_period(THD * thd) | |
| { | |
|   bool null_value; | |
|   LEX_STRING name=  { C_STRING_WITH_LEN("master_heartbeat_period")}; | |
|   user_var_entry *entry=  | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry? entry->val_int(&null_value) : 0; | |
| } | |
| 
 | |
| /* | |
|   Lookup the capabilities of the slave, which it announces by setting a value | |
|   MARIA_SLAVE_CAPABILITY_XXX in @mariadb_slave_capability. | |
|  | |
|   Older MariaDB slaves, and other MySQL slaves, do not set | |
|   @mariadb_slave_capability, corresponding to a capability of | |
|   MARIA_SLAVE_CAPABILITY_UNKNOWN (0). | |
| */ | |
| static int | |
| get_mariadb_slave_capability(THD *thd) | |
| { | |
|   bool null_value; | |
|   const LEX_STRING name= { C_STRING_WITH_LEN("mariadb_slave_capability") }; | |
|   const user_var_entry *entry= | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry ? | |
|     (int)(entry->val_int(&null_value)) : MARIA_SLAVE_CAPABILITY_UNKNOWN; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Get the value of the @slave_connect_state user variable into the supplied | |
|   String (this is the GTID connect state requested by the connecting slave). | |
|  | |
|   Returns false if error (ie. slave did not set the variable and does not | |
|   want to use GTID to set start position), true if success. | |
| */ | |
| static bool | |
| get_slave_connect_state(THD *thd, String *out_str) | |
| { | |
|   bool null_value; | |
| 
 | |
|   const LEX_STRING name= { C_STRING_WITH_LEN("slave_connect_state") }; | |
|   user_var_entry *entry= | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry && entry->val_str(&null_value, out_str, 0) && !null_value; | |
| } | |
| 
 | |
| 
 | |
| static bool | |
| get_slave_gtid_strict_mode(THD *thd) | |
| { | |
|   bool null_value; | |
| 
 | |
|   const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_strict_mode") }; | |
|   user_var_entry *entry= | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry && entry->val_int(&null_value) && !null_value; | |
| } | |
| 
 | |
| 
 | |
| static bool | |
| get_slave_gtid_ignore_duplicates(THD *thd) | |
| { | |
|   bool null_value; | |
| 
 | |
|   const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_ignore_duplicates") }; | |
|   user_var_entry *entry= | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                      name.length); | |
|   return entry && entry->val_int(&null_value) && !null_value; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Get the value of the @slave_until_gtid user variable into the supplied | |
|   String (this is the GTID position specified for START SLAVE UNTIL | |
|   master_gtid_pos='xxx'). | |
|  | |
|   Returns false if error (ie. slave did not set the variable and is not doing | |
|   START SLAVE UNTIL mater_gtid_pos='xxx'), true if success. | |
| */ | |
| static bool | |
| get_slave_until_gtid(THD *thd, String *out_str) | |
| { | |
|   bool null_value; | |
| 
 | |
|   const LEX_STRING name= { C_STRING_WITH_LEN("slave_until_gtid") }; | |
|   user_var_entry *entry= | |
|     (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, | |
|                                   name.length); | |
|   return entry && entry->val_str(&null_value, out_str, 0) && !null_value; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Function prepares and sends repliation heartbeat event. | |
|  | |
|   @param net                net object of THD | |
|   @param packet             buffer to store the heartbeat instance | |
|   @param event_coordinates  binlog file name and position of the last | |
|                             real event master sent from binlog | |
|  | |
|   @note  | |
|     Among three essential pieces of heartbeat data Log_event::when | |
|     is computed locally. | |
|     The  error to send is serious and should force terminating | |
|     the dump thread. | |
| */ | |
| static int send_heartbeat_event(NET* net, String* packet, | |
|                                 const struct event_coordinates *coord, | |
|                                 uint8 checksum_alg_arg) | |
| { | |
|   DBUG_ENTER("send_heartbeat_event"); | |
|   char header[LOG_EVENT_HEADER_LEN]; | |
|   my_bool do_checksum= checksum_alg_arg != BINLOG_CHECKSUM_ALG_OFF && | |
|     checksum_alg_arg != BINLOG_CHECKSUM_ALG_UNDEF; | |
|   /* | |
|     'when' (the timestamp) is set to 0 so that slave could distinguish between | |
|     real and fake Rotate events (if necessary) | |
|   */ | |
|   memset(header, 0, 4);  // when | |
|  | |
|   header[EVENT_TYPE_OFFSET] = HEARTBEAT_LOG_EVENT; | |
| 
 | |
|   char* p= coord->file_name + dirname_length(coord->file_name); | |
| 
 | |
|   uint ident_len = strlen(p); | |
|   ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + | |
|     (do_checksum ? BINLOG_CHECKSUM_LEN : 0); | |
|   int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); | |
|   int4store(header + EVENT_LEN_OFFSET, event_len); | |
|   int2store(header + FLAGS_OFFSET, 0); | |
| 
 | |
|   int4store(header + LOG_POS_OFFSET, coord->pos);  // log_pos | |
|  | |
|   packet->append(header, sizeof(header)); | |
|   packet->append(p, ident_len);             // log_file_name | |
|  | |
|   if (do_checksum) | |
|   { | |
|     char b[BINLOG_CHECKSUM_LEN]; | |
|     ha_checksum crc; | |
|     crc= my_checksum(0, (uchar*) header, sizeof(header)); | |
|     crc= my_checksum(crc, (uchar*) p, ident_len); | |
|     int4store(b, crc); | |
|     packet->append(b, sizeof(b)); | |
|   } | |
| 
 | |
|   if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) || | |
|       net_flush(net)) | |
|   { | |
|     DBUG_RETURN(-1); | |
|   } | |
|   DBUG_RETURN(0); | |
| } | |
| 
 | |
| 
 | |
| struct binlog_file_entry | |
| { | |
|   binlog_file_entry *next; | |
|   char *name; | |
| }; | |
| 
 | |
| static binlog_file_entry * | |
| get_binlog_list(MEM_ROOT *memroot) | |
| { | |
|   IO_CACHE *index_file; | |
|   char fname[FN_REFLEN]; | |
|   size_t length; | |
|   binlog_file_entry *current_list= NULL, *e; | |
|   DBUG_ENTER("get_binlog_list"); | |
| 
 | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_error(ER_NO_BINARY_LOGGING, MYF(0)); | |
|     DBUG_RETURN(NULL); | |
|   } | |
| 
 | |
|   mysql_bin_log.lock_index(); | |
|   index_file=mysql_bin_log.get_index_file(); | |
|   reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0); | |
| 
 | |
|   /* The file ends with EOF or empty line */ | |
|   while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1) | |
|   { | |
|     --length;                                   /* Remove the newline */ | |
|     if (!(e= (binlog_file_entry *)alloc_root(memroot, sizeof(*e))) || | |
|         !(e->name= strmake_root(memroot, fname, length))) | |
|     { | |
|       mysql_bin_log.unlock_index(); | |
|       my_error(ER_OUTOFMEMORY, MYF(0), length + 1 + sizeof(*e)); | |
|       DBUG_RETURN(NULL); | |
|     } | |
|     e->next= current_list; | |
|     current_list= e; | |
|   } | |
|   mysql_bin_log.unlock_index(); | |
| 
 | |
|   DBUG_RETURN(current_list); | |
| } | |
| 
 | |
| /* | |
|   Find the Gtid_list_log_event at the start of a binlog. | |
|  | |
|   NULL for ok, non-NULL error message for error. | |
|  | |
|   If ok, then the event is returned in *out_gtid_list. This can be NULL if we | |
|   get back to binlogs written by old server version without GTID support. If | |
|   so, it means we have reached the point to start from, as no GTID events can | |
|   exist in earlier binlogs. | |
| */ | |
| static const char * | |
| get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) | |
| { | |
|   Format_description_log_event init_fdle(BINLOG_VERSION); | |
|   Format_description_log_event *fdle; | |
|   Log_event *ev; | |
|   const char *errormsg = NULL; | |
| 
 | |
|   *out_gtid_list= NULL; | |
| 
 | |
|   if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle, | |
|                                       opt_master_verify_checksum)) || | |
|       ev->get_type_code() != FORMAT_DESCRIPTION_EVENT) | |
|   { | |
|     if (ev) | |
|       delete ev; | |
|     return "Could not read format description log event while looking for " | |
|       "GTID position in binlog"; | |
|   } | |
| 
 | |
|   fdle= static_cast<Format_description_log_event *>(ev); | |
| 
 | |
|   for (;;) | |
|   { | |
|     Log_event_type typ; | |
| 
 | |
|     ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum); | |
|     if (!ev) | |
|     { | |
|       errormsg= "Could not read GTID list event while looking for GTID " | |
|         "position in binlog"; | |
|       break; | |
|     } | |
|     typ= ev->get_type_code(); | |
|     if (typ == GTID_LIST_EVENT) | |
|       break;                                    /* Done, found it */ | |
|     delete ev; | |
|     if (typ == ROTATE_EVENT || typ == STOP_EVENT || | |
|         typ == FORMAT_DESCRIPTION_EVENT) | |
|       continue;                                 /* Continue looking */ | |
| 
 | |
|     /* We did not find any Gtid_list_log_event, must be old binlog. */ | |
|     ev= NULL; | |
|     break; | |
|   } | |
| 
 | |
|   delete fdle; | |
|   *out_gtid_list= static_cast<Gtid_list_log_event *>(ev); | |
|   return errormsg; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Check if every GTID requested by the slave is contained in this (or a later) | |
|   binlog file. Return true if so, false if not. | |
|  | |
|   We do the check with a single scan of the list of GTIDs, avoiding the need | |
|   to build an in-memory hash or stuff like that. | |
|  | |
|   We need to check that slave did not request GTID D-S-N1, when the | |
|   Gtid_list_log_event for this binlog file has D-S-N2 with N2 >= N1. | |
|   (Because this means that requested GTID is in an earlier binlog). | |
|   However, if the Gtid_list_log_event indicates that D-S-N1 is the very last | |
|   GTID for domain D in prior binlog files, then it is ok to start from the | |
|   very start of this binlog file. This special case is important, as it | |
|   allows to purge old logs even if some domain is unused for long. | |
|  | |
|   In addition, we need to check that we do not have a GTID D-S-N3 in the | |
|   Gtid_list_log_event where D is not present in the requested slave state at | |
|   all. Since if D is not in requested slave state, it means that slave needs | |
|   to start at the very first GTID in domain D. | |
| */ | |
| static bool | |
| contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) | |
| { | |
|   uint32 i; | |
| 
 | |
|   for (i= 0; i < glev->count; ++i) | |
|   { | |
|     uint32 gl_domain_id= glev->list[i].domain_id; | |
|     const rpl_gtid *gtid= st->find(gl_domain_id); | |
|     if (!gtid) | |
|     { | |
|       /* | |
|         The slave needs to start from the very beginning of this domain, which | |
|         is in an earlier binlog file. So we need to search back further. | |
|       */ | |
|       return false; | |
|     } | |
|     if (gtid->server_id == glev->list[i].server_id && | |
|         gtid->seq_no <= glev->list[i].seq_no) | |
|     { | |
|       /* | |
|         The slave needs to start after gtid, but it is contained in an earlier | |
|         binlog file. So we need to search back further, unless it was the very | |
|         last gtid logged for the domain in earlier binlog files. | |
|       */ | |
|       if (gtid->seq_no < glev->list[i].seq_no) | |
|         return false; | |
| 
 | |
|       /* | |
|         The slave requested D-S-N1, which happens to be the last GTID logged | |
|         in prior binlog files with same domain id D and server id S. | |
|  | |
|         The Gtid_list is kept sorted on domain_id, with the last GTID in each | |
|         domain_id group being the last one logged. So if this is the last GTID | |
|         within the domain_id group, then it is ok to start from the very | |
|         beginning of this group, per the special case explained in comment at | |
|         the start of this function. If not, then we need to search back further. | |
|       */ | |
|       if (i+1 < glev->count && gl_domain_id == glev->list[i+1].domain_id) | |
|         return false; | |
|     } | |
|   } | |
| 
 | |
|   return true; | |
| } | |
| 
 | |
| 
 | |
| static void | |
| give_error_start_pos_missing_in_binlog(int *err, const char **errormsg, | |
|                                        rpl_gtid *error_gtid) | |
| { | |
|   rpl_gtid binlog_gtid; | |
| 
 | |
|   if (mysql_bin_log.lookup_domain_in_binlog_state(error_gtid->domain_id, | |
|                                                   &binlog_gtid) && | |
|       binlog_gtid.seq_no >= error_gtid->seq_no) | |
|   { | |
|     *errormsg= "Requested slave GTID state not found in binlog. The slave has " | |
|       "probably diverged due to executing erroneous transactions"; | |
|     *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2; | |
|   } | |
|   else | |
|   { | |
|     *errormsg= "Requested slave GTID state not found in binlog"; | |
|     *err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; | |
|   } | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Check the start GTID state requested by the slave against our binlog state. | |
|  | |
|   Give an error if the slave requests something that we do not have in our | |
|   binlog. | |
| */ | |
| 
 | |
| static int | |
| check_slave_start_position(binlog_send_info *info, const char **errormsg, | |
|                            rpl_gtid *error_gtid) | |
| { | |
|   uint32 i; | |
|   int err; | |
|   slave_connection_state::entry **delete_list= NULL; | |
|   uint32 delete_idx= 0; | |
|   slave_connection_state *st= &info->gtid_state; | |
| 
 | |
|   if (rpl_load_gtid_slave_state(info->thd)) | |
|   { | |
|     *errormsg= "Failed to load replication slave GTID state"; | |
|     err= ER_CANNOT_LOAD_SLAVE_GTID_STATE; | |
|     goto end; | |
|   } | |
| 
 | |
|   for (i= 0; i < st->hash.records; ++i) | |
|   { | |
|     slave_connection_state::entry *slave_gtid_entry= | |
|       (slave_connection_state::entry *)my_hash_element(&st->hash, i); | |
|     rpl_gtid *slave_gtid= &slave_gtid_entry->gtid; | |
|     rpl_gtid master_gtid; | |
|     rpl_gtid master_replication_gtid; | |
|     rpl_gtid start_gtid; | |
|     bool start_at_own_slave_pos= | |
|       rpl_global_gtid_slave_state->domain_to_gtid(slave_gtid->domain_id, | |
|                                                   &master_replication_gtid) && | |
|       slave_gtid->server_id == master_replication_gtid.server_id && | |
|       slave_gtid->seq_no == master_replication_gtid.seq_no; | |
| 
 | |
|     if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, | |
|                                            slave_gtid->server_id, | |
|                                            &master_gtid) && | |
|         master_gtid.seq_no >= slave_gtid->seq_no) | |
|     { | |
|       /* | |
|         If connecting slave requests to start at the GTID we last applied when | |
|         we were ourselves a slave, then this GTID may not exist in our binlog | |
|         (in case of --log-slave-updates=0). So set the flag to disable the | |
|         error about missing GTID in the binlog in this case. | |
|       */ | |
|       if (start_at_own_slave_pos) | |
|         slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS; | |
|       continue; | |
|     } | |
| 
 | |
|     if (!start_at_own_slave_pos) | |
|     { | |
|       rpl_gtid domain_gtid; | |
|       slave_connection_state *until_gtid_state= info->until_gtid_state; | |
|       rpl_gtid *until_gtid; | |
| 
 | |
|       if (!mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, | |
|                                                        &domain_gtid)) | |
|       { | |
|         /* | |
|           We do not have anything in this domain, neither in the binlog nor | |
|           in the slave state. So we are probably one master in a multi-master | |
|           setup, and this domain is served by a different master. | |
|  | |
|           But set a flag so that if we then ever _do_ happen to encounter | |
|           anything in this domain, then we will re-check that the requested | |
|           slave position exists, and give the error at that time if not. | |
|         */ | |
|         slave_gtid_entry->flags|= slave_connection_state::START_ON_EMPTY_DOMAIN; | |
|         continue; | |
|       } | |
| 
 | |
|       if (info->slave_gtid_ignore_duplicates && | |
|           domain_gtid.seq_no < slave_gtid->seq_no) | |
|       { | |
|         /* | |
|           When --gtid-ignore-duplicates, it is ok for the slave to request | |
|           something that we do not have (yet) - they might already have gotten | |
|           it through another path in a multi-path replication hierarchy. | |
|         */ | |
|         continue; | |
|       } | |
| 
 | |
|       if (until_gtid_state && | |
|           ( !(until_gtid= until_gtid_state->find(slave_gtid->domain_id)) || | |
|             (mysql_bin_log.find_in_binlog_state(until_gtid->domain_id, | |
|                                                 until_gtid->server_id, | |
|                                                 &master_gtid) && | |
|              master_gtid.seq_no >= until_gtid->seq_no))) | |
|       { | |
|         /* | |
|           The slave requested to start from a position that is not (yet) in | |
|           our binlog, but it also specified an UNTIL condition that _is_ in | |
|           our binlog (or a missing UNTIL, which means stop at the very | |
|           beginning). So the stop position is before the start position, and | |
|           we just delete the entry from the UNTIL hash to mark that this | |
|           domain has already reached the UNTIL condition. | |
|         */ | |
|         if(until_gtid) | |
|           until_gtid_state->remove(until_gtid); | |
|         continue; | |
|       } | |
| 
 | |
|       *error_gtid= *slave_gtid; | |
|       give_error_start_pos_missing_in_binlog(&err, errormsg, error_gtid); | |
|       goto end; | |
|     } | |
| 
 | |
|     /* | |
|       Ok, so connecting slave asked to start at a GTID that we do not have in | |
|       our binlog, but it was in fact the last GTID we applied earlier, when we | |
|       were acting as a replication slave. | |
|  | |
|       So this means that we were running as a replication slave without | |
|       --log-slave-updates, but now we switched to be a master. It is worth it | |
|       to handle this special case, as it allows users to run a simple | |
|       master -> slave without --log-slave-updates, and then exchange slave and | |
|       master, as long as they make sure the slave is caught up before switching. | |
|     */ | |
| 
 | |
|     /* | |
|       First check if we logged something ourselves as a master after being a | |
|       slave. This will be seen as a GTID with our own server_id and bigger | |
|       seq_no than what is in the slave state. | |
|  | |
|       If we did not log anything ourselves, then start the connecting slave | |
|       replicating from the current binlog end position, which in this case | |
|       corresponds to our replication slave state and hence what the connecting | |
|       slave is requesting. | |
|     */ | |
|     if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, | |
|                                            global_system_variables.server_id, | |
|                                            &start_gtid) && | |
|         start_gtid.seq_no > slave_gtid->seq_no) | |
|     { | |
|       /* | |
|         Start replication within this domain at the first GTID that we logged | |
|         ourselves after becoming a master. | |
|  | |
|         Remember that this starting point is in fact a "fake" GTID which may | |
|         not exists in the binlog, so that we do not complain about it in | |
|         --gtid-strict-mode. | |
|       */ | |
|       slave_gtid->server_id= global_system_variables.server_id; | |
|       slave_gtid_entry->flags|= slave_connection_state::START_OWN_SLAVE_POS; | |
|     } | |
|     else if (mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, | |
|                                                          &start_gtid)) | |
|     { | |
|       slave_gtid->server_id= start_gtid.server_id; | |
|       slave_gtid->seq_no= start_gtid.seq_no; | |
|     } | |
|     else | |
|     { | |
|       /* | |
|         We do not have _anything_ in our own binlog for this domain.  Just | |
|         delete the entry in the slave connection state, then it will pick up | |
|         anything new that arrives. | |
|  | |
|         We just queue up the deletion and do it later, after the loop, so that | |
|         we do not mess up the iteration over the hash. | |
|       */ | |
|       if (!delete_list) | |
|       { | |
|         if (!(delete_list= (slave_connection_state::entry **) | |
|               my_malloc(sizeof(*delete_list) * st->hash.records, MYF(MY_WME)))) | |
|         { | |
|           *errormsg= "Out of memory while checking slave start position"; | |
|           err= ER_OUT_OF_RESOURCES; | |
|           goto end; | |
|         } | |
|       } | |
|       delete_list[delete_idx++]= slave_gtid_entry; | |
|     } | |
|   } | |
| 
 | |
|   /* Do any delayed deletes from the hash. */ | |
|   if (delete_list) | |
|   { | |
|     for (i= 0; i < delete_idx; ++i) | |
|       st->remove(&(delete_list[i]->gtid)); | |
|   } | |
|   err= 0; | |
| 
 | |
| end: | |
|   if (delete_list) | |
|     my_free(delete_list); | |
|   return err; | |
| } | |
| 
 | |
| /* | |
|   Find the name of the binlog file to start reading for a slave that connects | |
|   using GTID state. | |
|  | |
|   Returns the file name in out_name, which must be of size at least FN_REFLEN. | |
|  | |
|   Returns NULL on ok, error message on error. | |
|  | |
|   In case of non-error return, the returned binlog file is guaranteed to | |
|   contain the first event to be transmitted to the slave for every domain | |
|   present in our binlogs. It is still necessary to skip all GTIDs up to | |
|   and including the GTID requested by slave within each domain. | |
|  | |
|   However, as a special case, if the event to be sent to the slave is the very | |
|   first event (within that domain) in the returned binlog, then nothing should | |
|   be skipped, so that domain is deleted from the passed in slave connection | |
|   state. | |
|  | |
|   This is necessary in case the slave requests a GTID within a replication | |
|   domain that has long been inactive. The binlog file containing that GTID may | |
|   have been long since purged. However, as long as no GTIDs after that have | |
|   been purged, we have the GTID requested by slave in the Gtid_list_log_event | |
|   of the latest binlog. So we can start from there, as long as we delete the | |
|   corresponding entry in the slave state so we do not wrongly skip any events | |
|   that might turn up if that domain becomes active again, vainly looking for | |
|   the requested GTID that was already purged. | |
| */ | |
| static const char * | |
| gtid_find_binlog_file(slave_connection_state *state, char *out_name, | |
|                       slave_connection_state *until_gtid_state) | |
| { | |
|   MEM_ROOT memroot; | |
|   binlog_file_entry *list; | |
|   Gtid_list_log_event *glev= NULL; | |
|   const char *errormsg= NULL; | |
|   char buf[FN_REFLEN]; | |
| 
 | |
|   init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0, | |
|                   MYF(MY_THREAD_SPECIFIC)); | |
|   if (!(list= get_binlog_list(&memroot))) | |
|   { | |
|     errormsg= "Out of memory while looking for GTID position in binlog"; | |
|     goto end; | |
|   } | |
| 
 | |
|   while (list) | |
|   { | |
|     File file; | |
|     IO_CACHE cache; | |
| 
 | |
|     if (!list->next) | |
|     { | |
|       /* | |
|         It should be safe to read the currently used binlog, as we will only | |
|         read the header part that is already written. | |
|  | |
|         But if that does not work on windows, then we will need to cache the | |
|         event somewhere in memory I suppose - that could work too. | |
|       */ | |
|     } | |
|     /* | |
|       Read the Gtid_list_log_event at the start of the binlog file to | |
|       get the binlog state. | |
|     */ | |
|     if (normalize_binlog_name(buf, list->name, false)) | |
|     { | |
|       errormsg= "Failed to determine binlog file name while looking for " | |
|         "GTID position in binlog"; | |
|       goto end; | |
|     } | |
|     bzero((char*) &cache, sizeof(cache)); | |
|     if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1) | |
|       goto end; | |
|     errormsg= get_gtid_list_event(&cache, &glev); | |
|     end_io_cache(&cache); | |
|     mysql_file_close(file, MYF(MY_WME)); | |
|     if (errormsg) | |
|       goto end; | |
| 
 | |
|     if (!glev || contains_all_slave_gtid(state, glev)) | |
|     { | |
|       strmake(out_name, buf, FN_REFLEN); | |
| 
 | |
|       if (glev) | |
|       { | |
|         uint32 i; | |
| 
 | |
|         /* | |
|           As a special case, we allow to start from binlog file N if the | |
|           requested GTID is the last event (in the corresponding domain) in | |
|           binlog file (N-1), but then we need to remove that GTID from the slave | |
|           state, rather than skipping events waiting for it to turn up. | |
|  | |
|           If slave is doing START SLAVE UNTIL, check for any UNTIL conditions | |
|           that are already included in a previous binlog file. Delete any such | |
|           from the UNTIL hash, to mark that such domains have already reached | |
|           their UNTIL condition. | |
|         */ | |
|         for (i= 0; i < glev->count; ++i) | |
|         { | |
|           const rpl_gtid *gtid= state->find(glev->list[i].domain_id); | |
|           if (!gtid) | |
|           { | |
|             /* | |
|               Contains_all_slave_gtid() returns false if there is any domain in | |
|               Gtid_list_event which is not in the requested slave position. | |
|  | |
|               We may delete a domain from the slave state inside this loop, but | |
|               we only do this when it is the very last GTID logged for that | |
|               domain in earlier binlogs, and then we can not encounter it in any | |
|               further GTIDs in the Gtid_list. | |
|             */ | |
|             DBUG_ASSERT(0); | |
|           } else if (gtid->server_id == glev->list[i].server_id && | |
|                      gtid->seq_no == glev->list[i].seq_no) | |
|           { | |
|             /* | |
|               The slave requested to start from the very beginning of this | |
|               domain in this binlog file. So delete the entry from the state, | |
|               we do not need to skip anything. | |
|             */ | |
|             state->remove(gtid); | |
|           } | |
| 
 | |
|           if (until_gtid_state && | |
|               (gtid= until_gtid_state->find(glev->list[i].domain_id)) && | |
|               gtid->server_id == glev->list[i].server_id && | |
|               gtid->seq_no <= glev->list[i].seq_no) | |
|           { | |
|             /* | |
|               We've already reached the stop position in UNTIL for this domain, | |
|               since it is before the start position. | |
|             */ | |
|             until_gtid_state->remove(gtid); | |
|           } | |
|         } | |
|       } | |
| 
 | |
|       goto end; | |
|     } | |
|     delete glev; | |
|     glev= NULL; | |
|     list= list->next; | |
|   } | |
| 
 | |
|   /* We reached the end without finding anything. */ | |
|   errormsg= "Could not find GTID state requested by slave in any binlog " | |
|     "files. Probably the slave state is too old and required binlog files " | |
|     "have been purged."; | |
| 
 | |
| end: | |
|   if (glev) | |
|     delete glev; | |
| 
 | |
|   free_root(&memroot, MYF(0)); | |
|   return errormsg; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Given an old-style binlog position with file name and file offset, find the | |
|   corresponding gtid position. If the offset is not at an event boundary, give | |
|   an error. | |
|  | |
|   Return NULL on ok, error message string on error. | |
|  | |
|   ToDo: Improve the performance of this by using binlog index files. | |
| */ | |
| static const char * | |
| gtid_state_from_pos(const char *name, uint32 offset, | |
|                     slave_connection_state *gtid_state) | |
| { | |
|   IO_CACHE cache; | |
|   File file; | |
|   const char *errormsg= NULL; | |
|   bool found_gtid_list_event= false; | |
|   bool found_format_description_event= false; | |
|   bool valid_pos= false; | |
|   uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; | |
|   int err; | |
|   String packet; | |
|   Format_description_log_event *fdev= NULL; | |
| 
 | |
|   if (gtid_state->load((const rpl_gtid *)NULL, 0)) | |
|   { | |
|     errormsg= "Internal error (out of memory?) initializing slave state " | |
|       "while scanning binlog to find start position"; | |
|     return errormsg; | |
|   } | |
| 
 | |
|   if ((file= open_binlog(&cache, name, &errormsg)) == (File)-1) | |
|     return errormsg; | |
| 
 | |
|   if (!(fdev= new Format_description_log_event(3))) | |
|   { | |
|     errormsg= "Out of memory initializing format_description event " | |
|       "while scanning binlog to find start position"; | |
|     goto end; | |
|   } | |
| 
 | |
|   /* | |
|     First we need to find the initial GTID_LIST_EVENT. We need this even | |
|     if the offset is at the very start of the binlog file. | |
|  | |
|     But if we do not find any GTID_LIST_EVENT, then this is an old binlog | |
|     with no GTID information, so we return empty GTID state. | |
|   */ | |
|   for (;;) | |
|   { | |
|     Log_event_type typ; | |
|     uint32 cur_pos; | |
| 
 | |
|     cur_pos= (uint32)my_b_tell(&cache); | |
|     if (cur_pos == offset) | |
|       valid_pos= true; | |
|     if (found_format_description_event && found_gtid_list_event && | |
|         cur_pos >= offset) | |
|       break; | |
| 
 | |
|     packet.length(0); | |
|     err= Log_event::read_log_event(&cache, &packet, NULL, | |
|                                    current_checksum_alg); | |
|     if (err) | |
|     { | |
|       errormsg= "Could not read binlog while searching for slave start " | |
|         "position on master"; | |
|       goto end; | |
|     } | |
|     /* | |
|       The cast to uchar is needed to avoid a signed char being converted to a | |
|       negative number. | |
|     */ | |
|     typ= (Log_event_type)(uchar)packet[EVENT_TYPE_OFFSET]; | |
|     if (typ == FORMAT_DESCRIPTION_EVENT) | |
|     { | |
|       Format_description_log_event *tmp; | |
| 
 | |
|       if (found_format_description_event) | |
|       { | |
|         errormsg= "Duplicate format description log event found while " | |
|           "searching for old-style position in binlog"; | |
|         goto end; | |
|       } | |
| 
 | |
|       current_checksum_alg= get_checksum_alg(packet.ptr(), packet.length()); | |
|       found_format_description_event= true; | |
|       if (!(tmp= new Format_description_log_event(packet.ptr(), packet.length(), | |
|                                                   fdev))) | |
|       { | |
|         errormsg= "Corrupt Format_description event found or out-of-memory " | |
|           "while searching for old-style position in binlog"; | |
|         goto end; | |
|       } | |
|       delete fdev; | |
|       fdev= tmp; | |
|     } | |
|     else if (typ != FORMAT_DESCRIPTION_EVENT && !found_format_description_event) | |
|     { | |
|       errormsg= "Did not find format description log event while searching " | |
|         "for old-style position in binlog"; | |
|       goto end; | |
|     } | |
|     else if (typ == ROTATE_EVENT || typ == STOP_EVENT || | |
|              typ == BINLOG_CHECKPOINT_EVENT) | |
|       continue;                                 /* Continue looking */ | |
|     else if (typ == GTID_LIST_EVENT) | |
|     { | |
|       rpl_gtid *gtid_list; | |
|       bool status; | |
|       uint32 list_len; | |
| 
 | |
|       if (found_gtid_list_event) | |
|       { | |
|         errormsg= "Found duplicate Gtid_list_log_event while scanning binlog " | |
|           "to find slave start position"; | |
|         goto end; | |
|       } | |
|       status= Gtid_list_log_event::peek(packet.ptr(), packet.length(), | |
|                                         current_checksum_alg, | |
|                                         >id_list, &list_len, fdev); | |
|       if (status) | |
|       { | |
|         errormsg= "Error reading Gtid_list_log_event while searching " | |
|           "for old-style position in binlog"; | |
|         goto end; | |
|       } | |
|       err= gtid_state->load(gtid_list, list_len); | |
|       my_free(gtid_list); | |
|       if (err) | |
|       { | |
|         errormsg= "Internal error (out of memory?) initialising slave state " | |
|           "while scanning binlog to find start position"; | |
|         goto end; | |
|       } | |
|       found_gtid_list_event= true; | |
|     } | |
|     else if (!found_gtid_list_event) | |
|     { | |
|       /* We did not find any Gtid_list_log_event, must be old binlog. */ | |
|       goto end; | |
|     } | |
|     else if (typ == GTID_EVENT) | |
|     { | |
|       rpl_gtid gtid; | |
|       uchar flags2; | |
|       if (Gtid_log_event::peek(packet.ptr(), packet.length(), | |
|                                current_checksum_alg, >id.domain_id, | |
|                                >id.server_id, >id.seq_no, &flags2, fdev)) | |
|       { | |
|         errormsg= "Corrupt gtid_log_event found while scanning binlog to find " | |
|           "initial slave position"; | |
|         goto end; | |
|       } | |
|       if (gtid_state->update(>id)) | |
|       { | |
|         errormsg= "Internal error (out of memory?) updating slave state while " | |
|           "scanning binlog to find start position"; | |
|         goto end; | |
|       } | |
|     } | |
|   } | |
| 
 | |
|   if (!valid_pos) | |
|   { | |
|     errormsg= "Slave requested incorrect position in master binlog. " | |
|       "Requested position %u in file '%s', but this position does not " | |
|       "correspond to the location of any binlog event."; | |
|   } | |
| 
 | |
| end: | |
|   delete fdev; | |
|   end_io_cache(&cache); | |
|   mysql_file_close(file, MYF(MY_WME)); | |
| 
 | |
|   return errormsg; | |
| } | |
| 
 | |
| 
 | |
| int | |
| gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) | |
| { | |
|   slave_connection_state gtid_state; | |
|   const char *lookup_name; | |
|   char name_buf[FN_REFLEN]; | |
|   LOG_INFO linfo; | |
| 
 | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_error(ER_NO_BINARY_LOGGING, MYF(0)); | |
|     return 1; | |
|   } | |
| 
 | |
|   if (in_name && in_name[0]) | |
|   { | |
|     mysql_bin_log.make_log_name(name_buf, in_name); | |
|     lookup_name= name_buf; | |
|   } | |
|   else | |
|     lookup_name= NULL; | |
|   linfo.index_file_offset= 0; | |
|   if (mysql_bin_log.find_log_pos(&linfo, lookup_name, 1)) | |
|     return 1; | |
| 
 | |
|   if (pos < 4) | |
|     pos= 4; | |
| 
 | |
|   if (gtid_state_from_pos(linfo.log_file_name, pos, >id_state) || | |
|       gtid_state.to_string(out_str)) | |
|     return 1; | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| static bool | |
| is_until_reached(binlog_send_info *info, ulong *ev_offset, | |
|                  Log_event_type event_type, const char **errmsg, | |
|                  uint32 current_pos) | |
| { | |
|   switch (info->gtid_until_group) | |
|   { | |
|   case GTID_UNTIL_NOT_DONE: | |
|     return false; | |
|   case GTID_UNTIL_STOP_AFTER_STANDALONE: | |
|     if (Log_event::is_part_of_group(event_type)) | |
|       return false; | |
|     break; | |
|   case GTID_UNTIL_STOP_AFTER_TRANSACTION: | |
|     if (event_type != XID_EVENT && | |
|         (event_type != QUERY_EVENT || | |
|          !Query_log_event::peek_is_commit_rollback | |
|                (info->packet->ptr()+*ev_offset, | |
|                 info->packet->length()-*ev_offset, | |
|                 info->current_checksum_alg))) | |
|       return false; | |
|     break; | |
|   } | |
| 
 | |
|   /* | |
|     The last event group has been sent, now the START SLAVE UNTIL condition | |
|     has been reached. | |
|  | |
|     Send a last fake Gtid_list_log_event with a flag set to mark that we | |
|     stop due to UNTIL condition. | |
|   */ | |
|   if (reset_transmit_packet(info->thd, info->flags, ev_offset, errmsg)) | |
|     return true; | |
|   Gtid_list_log_event glev(&info->until_binlog_state, | |
|                            Gtid_list_log_event::FLAG_UNTIL_REACHED); | |
|   if (fake_gtid_list_event(info, &glev, errmsg, current_pos)) | |
|     return true; | |
|   *errmsg= NULL; | |
|   return true; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Helper function for mysql_binlog_send() to write an event down the slave | |
|   connection. | |
|  | |
|   Returns NULL on success, error message string on error. | |
| */ | |
| static const char * | |
| send_event_to_slave(binlog_send_info *info, Log_event_type event_type, | |
|                     IO_CACHE *log, ulong ev_offset, rpl_gtid *error_gtid) | |
| { | |
|   my_off_t pos; | |
|   String* const packet= info->packet; | |
|   size_t len= packet->length(); | |
|   int mariadb_slave_capability= info->mariadb_slave_capability; | |
|   uint8 current_checksum_alg= info->current_checksum_alg; | |
|   slave_connection_state *gtid_state= &info->gtid_state; | |
|   slave_connection_state *until_gtid_state= info->until_gtid_state; | |
| 
 | |
|   if (event_type == GTID_LIST_EVENT && | |
|       info->using_gtid_state && until_gtid_state) | |
|   { | |
|     rpl_gtid *gtid_list; | |
|     uint32 list_len; | |
|     bool err; | |
| 
 | |
|     if (ev_offset > len || | |
|         Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, | |
|                                   current_checksum_alg, | |
|                                   >id_list, &list_len, info->fdev)) | |
|     { | |
|       my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|       return "Failed to read Gtid_list_log_event: corrupt binlog"; | |
|     } | |
|     err= info->until_binlog_state.load(gtid_list, list_len); | |
|     my_free(gtid_list); | |
|     if (err) | |
|     { | |
|       my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|       return "Failed in internal GTID book-keeping: Out of memory"; | |
|     } | |
|   } | |
| 
 | |
|   /* Skip GTID event groups until we reach slave position within a domain_id. */ | |
|   if (event_type == GTID_EVENT && info->using_gtid_state) | |
|   { | |
|     uchar flags2; | |
|     slave_connection_state::entry *gtid_entry; | |
|     rpl_gtid *gtid; | |
| 
 | |
|     if (gtid_state->count() > 0 || until_gtid_state) | |
|     { | |
|       rpl_gtid event_gtid; | |
| 
 | |
|       if (ev_offset > len || | |
|           Gtid_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, | |
|                                current_checksum_alg, | |
|                                &event_gtid.domain_id, &event_gtid.server_id, | |
|                                &event_gtid.seq_no, &flags2, info->fdev)) | |
|       { | |
|         my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|         return "Failed to read Gtid_log_event: corrupt binlog"; | |
|       } | |
| 
 | |
|       DBUG_EXECUTE_IF("gtid_force_reconnect_at_10_1_100", | |
|         { | |
|           rpl_gtid *dbug_gtid; | |
|           if ((dbug_gtid= info->until_binlog_state.find_nolock(10,1)) && | |
|               dbug_gtid->seq_no == 100) | |
|           { | |
|             DBUG_SET("-d,gtid_force_reconnect_at_10_1_100"); | |
|             DBUG_SET_INITIAL("-d,gtid_force_reconnect_at_10_1_100"); | |
|             my_errno= ER_UNKNOWN_ERROR; | |
|             return "DBUG-injected forced reconnect"; | |
|           } | |
|         }); | |
| 
 | |
|       if (info->until_binlog_state.update_nolock(&event_gtid, false)) | |
|       { | |
|         my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|         return "Failed in internal GTID book-keeping: Out of memory"; | |
|       } | |
| 
 | |
|       if (gtid_state->count() > 0) | |
|       { | |
|         gtid_entry= gtid_state->find_entry(event_gtid.domain_id); | |
|         if (gtid_entry != NULL) | |
|         { | |
|           gtid= >id_entry->gtid; | |
|           if (gtid_entry->flags & slave_connection_state::START_ON_EMPTY_DOMAIN) | |
|           { | |
|             rpl_gtid master_gtid; | |
|             if (!mysql_bin_log.find_in_binlog_state(gtid->domain_id, | |
|                                                     gtid->server_id, | |
|                                                     &master_gtid) || | |
|                 master_gtid.seq_no < gtid->seq_no) | |
|             { | |
|               int err; | |
|               const char *errormsg; | |
|               *error_gtid= *gtid; | |
|               give_error_start_pos_missing_in_binlog(&err, &errormsg, error_gtid); | |
|               my_errno= err; | |
|               return errormsg; | |
|             } | |
|             gtid_entry->flags&= ~(uint32)slave_connection_state::START_ON_EMPTY_DOMAIN; | |
|           } | |
| 
 | |
|           /* Skip this event group if we have not yet reached slave start pos. */ | |
|           if (event_gtid.server_id != gtid->server_id || | |
|               event_gtid.seq_no <= gtid->seq_no) | |
|             info->gtid_skip_group= (flags2 & Gtid_log_event::FL_STANDALONE ? | |
|                                 GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); | |
|           if (event_gtid.server_id == gtid->server_id && | |
|               event_gtid.seq_no >= gtid->seq_no) | |
|           { | |
|             if (info->slave_gtid_strict_mode && | |
|                 event_gtid.seq_no > gtid->seq_no && | |
|                 !(gtid_entry->flags & slave_connection_state::START_OWN_SLAVE_POS)) | |
|             { | |
|               /* | |
|                 In strict mode, it is an error if the slave requests to start | |
|                 in a "hole" in the master's binlog: a GTID that does not | |
|                 exist, even though both the prior and subsequent seq_no exists | |
|                 for same domain_id and server_id. | |
|               */ | |
|               my_errno= ER_GTID_START_FROM_BINLOG_HOLE; | |
|               *error_gtid= *gtid; | |
|               return "The binlog on the master is missing the GTID requested " | |
|                 "by the slave (even though both a prior and a subsequent " | |
|                 "sequence number does exist), and GTID strict mode is enabled."; | |
|             } | |
| 
 | |
|             /* | |
|               Send a fake Gtid_list event to the slave. | |
|               This allows the slave to update its current binlog position | |
|               so MASTER_POS_WAIT() and MASTER_GTID_WAIT() can work. | |
|               The fake event will be sent at the end of this event group. | |
|             */ | |
|             info->send_fake_gtid_list= true; | |
| 
 | |
|             /* | |
|               Delete this entry if we have reached slave start position (so we | |
|               will not skip subsequent events and won't have to look them up | |
|               and check). | |
|             */ | |
|             gtid_state->remove(gtid); | |
|           } | |
|         } | |
|       } | |
| 
 | |
|       if (until_gtid_state) | |
|       { | |
|         gtid= until_gtid_state->find(event_gtid.domain_id); | |
|         if (gtid == NULL) | |
|         { | |
|           /* | |
|             This domain already reached the START SLAVE UNTIL stop condition, | |
|             so skip this event group. | |
|           */ | |
|           info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? | |
|                               GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); | |
|         } | |
|         else if (event_gtid.server_id == gtid->server_id && | |
|                  event_gtid.seq_no >= gtid->seq_no) | |
|         { | |
|           /* | |
|             We have reached the stop condition. | |
|             Delete this domain_id from the hash, so we will skip all further | |
|             events in this domain and eventually stop when all domains are | |
|             done. | |
|           */ | |
|           uint64 until_seq_no= gtid->seq_no; | |
|           until_gtid_state->remove(gtid); | |
|           if (until_gtid_state->count() == 0) | |
|             info->gtid_until_group= (flags2 & Gtid_log_event::FL_STANDALONE ? | |
|                                      GTID_UNTIL_STOP_AFTER_STANDALONE : | |
|                                      GTID_UNTIL_STOP_AFTER_TRANSACTION); | |
|           if (event_gtid.seq_no > until_seq_no) | |
|           { | |
|             /* | |
|               The GTID in START SLAVE UNTIL condition is missing in our binlog. | |
|               This should normally not happen (user error), but since we can be | |
|               sure that we are now beyond the position that the UNTIL condition | |
|               should be in, we can just stop now. And we also need to skip this | |
|               event group (as it is beyond the UNTIL condition). | |
|             */ | |
|             info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? | |
|                                 GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); | |
|           } | |
|         } | |
|       } | |
|     } | |
|   } | |
| 
 | |
|   /* | |
|     Skip event group if we have not yet reached the correct slave GTID position. | |
|  | |
|     Note that slave that understands GTID can also tolerate holes, so there is | |
|     no need to supply dummy event. | |
|   */ | |
|   switch (info->gtid_skip_group) | |
|   { | |
|   case GTID_SKIP_STANDALONE: | |
|     if (!Log_event::is_part_of_group(event_type)) | |
|       info->gtid_skip_group= GTID_SKIP_NOT; | |
|     return NULL; | |
|   case GTID_SKIP_TRANSACTION: | |
|     if (event_type == XID_EVENT || | |
|         (event_type == QUERY_EVENT && | |
|          Query_log_event::peek_is_commit_rollback(packet->ptr() + ev_offset, | |
|                                                   len - ev_offset, | |
|                                                   current_checksum_alg))) | |
|       info->gtid_skip_group= GTID_SKIP_NOT; | |
|     return NULL; | |
|   case GTID_SKIP_NOT: | |
|     break; | |
|   } | |
| 
 | |
|   /* Do not send annotate_rows events unless slave requested it. */ | |
|   if (event_type == ANNOTATE_ROWS_EVENT && | |
|       !(info->flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) | |
|   { | |
|     if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES) | |
|     { | |
|       /* This slave can tolerate events omitted from the binlog stream. */ | |
|       return NULL; | |
|     } | |
|     else if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_ANNOTATE) | |
|     { | |
|       /* | |
|         The slave did not request ANNOTATE_ROWS_EVENT (it does not need them as | |
|         it will not log them in its own binary log). However, it understands the | |
|         event and will just ignore it, and it would break if we omitted it, | |
|         leaving a hole in the binlog stream. So just send the event as-is. | |
|       */ | |
|     } | |
|     else | |
|     { | |
|       /* | |
|         The slave does not understand ANNOTATE_ROWS_EVENT. | |
|  | |
|         Older MariaDB slaves (and MySQL slaves) will break replication if there | |
|         are holes in the binlog stream (they will miscompute the binlog offset | |
|         and request the wrong position when reconnecting). | |
|  | |
|         So replace the event with a dummy event of the same size that will be | |
|         a no-operation on the slave. | |
|       */ | |
|       if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) | |
|       { | |
|         my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|         return "Failed to replace row annotate event with dummy: too small event."; | |
|       } | |
|     } | |
|   } | |
| 
 | |
|   /* | |
|     Replace GTID events with old-style BEGIN events for slaves that do not | |
|     understand global transaction IDs. For stand-alone events, where there is | |
|     no terminating COMMIT query event, omit the GTID event or replace it with | |
|     a dummy event, as appropriate. | |
|   */ | |
|   if (event_type == GTID_EVENT && | |
|       mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID) | |
|   { | |
|     bool need_dummy= | |
|       mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES; | |
|     bool err= Gtid_log_event::make_compatible_event(packet, &need_dummy, | |
|                                                     ev_offset, | |
|                                                     current_checksum_alg); | |
|     if (err) | |
|     { | |
|       my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|       return "Failed to replace GTID event with backwards-compatible event: " | |
|              "currupt event."; | |
|     } | |
|     if (!need_dummy) | |
|       return NULL; | |
|   } | |
| 
 | |
|   /* | |
|     Do not send binlog checkpoint or gtid list events to a slave that does not | |
|     understand it. | |
|   */ | |
|   if ((unlikely(event_type == BINLOG_CHECKPOINT_EVENT) && | |
|        mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT) || | |
|       (unlikely(event_type == GTID_LIST_EVENT) && | |
|        mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID)) | |
|   { | |
|     if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES) | |
|     { | |
|       /* This slave can tolerate events omitted from the binlog stream. */ | |
|       return NULL; | |
|     } | |
|     else | |
|     { | |
|       /* | |
|         The slave does not understand BINLOG_CHECKPOINT_EVENT. Send a dummy | |
|         event instead, with same length so slave does not get confused about | |
|         binlog positions. | |
|       */ | |
|       if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) | |
|       { | |
|         my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|         return "Failed to replace binlog checkpoint or gtid list event with " | |
|                "dummy: too small event."; | |
|       } | |
|     } | |
|   } | |
| 
 | |
|   /* | |
|     Skip events with the @@skip_replication flag set, if slave requested | |
|     skipping of such events. | |
|   */ | |
|   if (info->thd->variables.option_bits & OPTION_SKIP_REPLICATION) | |
|   { | |
|     /* | |
|       The first byte of the packet is a '\0' to distinguish it from an error | |
|       packet. So the actual event starts at offset +1. | |
|     */ | |
|     uint16 event_flags= uint2korr(&((*packet)[FLAGS_OFFSET+1])); | |
|     if (event_flags & LOG_EVENT_SKIP_REPLICATION_F) | |
|       return NULL; | |
|   } | |
| 
 | |
|   THD_STAGE_INFO(info->thd, stage_sending_binlog_event_to_slave); | |
| 
 | |
|   pos= my_b_tell(log); | |
|   if (RUN_HOOK(binlog_transmit, before_send_event, | |
|                (info->thd, info->flags, packet, info->log_file_name, pos))) | |
|   { | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     return "run 'before_send_event' hook failed"; | |
|   } | |
| 
 | |
|   if (my_net_write(info->net, (uchar*) packet->ptr(), len)) | |
|   { | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     return "Failed on my_net_write()"; | |
|   } | |
| 
 | |
|   DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] )); | |
|   if (event_type == LOAD_EVENT) | |
|   { | |
|     if (send_file(info->thd)) | |
|     { | |
|       my_errno= ER_UNKNOWN_ERROR; | |
|       return "failed in send_file()"; | |
|     } | |
|   } | |
| 
 | |
|   if (RUN_HOOK(binlog_transmit, after_send_event, | |
|                (info->thd, info->flags, packet))) | |
|   { | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     return "Failed to run hook 'after_send_event'"; | |
|   } | |
| 
 | |
|   return NULL;    /* Success */ | |
| } | |
| 
 | |
| 
 | |
| void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, | |
| 		       ushort flags) | |
| { | |
|   LOG_INFO linfo; | |
|   char *log_file_name = linfo.log_file_name; | |
|   char search_file_name[FN_REFLEN], *name; | |
| 
 | |
|   ulong ev_offset; | |
| 
 | |
|   IO_CACHE log; | |
|   File file = -1; | |
|   String* const packet= &thd->packet; | |
|   int error; | |
|   const char *errmsg = "Unknown error", *tmp_msg; | |
|   char error_text[MAX_SLAVE_ERRMSG]; // to be send to slave via my_message() | |
|   mysql_mutex_t *log_lock; | |
|   mysql_cond_t *log_cond; | |
|   char str_buf[128]; | |
|   String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info); | |
|   char str_buf2[128]; | |
|   String slave_until_gtid_str(str_buf2, sizeof(str_buf2), system_charset_info); | |
|   slave_connection_state until_gtid_state_obj; | |
|   rpl_gtid error_gtid; | |
|   binlog_send_info info(thd, packet, flags, log_file_name); | |
|   bool has_transmit_started= false; | |
| 
 | |
|   int old_max_allowed_packet= thd->variables.max_allowed_packet; | |
| 
 | |
| #ifndef DBUG_OFF | |
|   int left_events = max_binlog_dump_events; | |
|   uint dbug_reconnect_counter= 0; | |
| #endif | |
|   DBUG_ENTER("mysql_binlog_send"); | |
|   DBUG_PRINT("enter",("log_ident: '%s'  pos: %ld", log_ident, (long) pos)); | |
| 
 | |
|   bzero((char*) &log,sizeof(log)); | |
|   bzero(&error_gtid, sizeof(error_gtid)); | |
|   /*  | |
|      heartbeat_period from @master_heartbeat_period user variable | |
|   */ | |
|   ulonglong heartbeat_period= get_heartbeat_period(thd); | |
|   struct timespec heartbeat_buf; | |
|   struct timespec *heartbeat_ts= NULL; | |
|   const LOG_POS_COORD start_coord= { log_ident, pos }, | |
|     *p_start_coord= &start_coord; | |
|   LOG_POS_COORD coord_buf= { log_file_name, BIN_LOG_HEADER_SIZE }, | |
|     *p_coord= &coord_buf; | |
|   if (heartbeat_period != 0) | |
|   { | |
|     heartbeat_ts= &heartbeat_buf; | |
|     set_timespec_nsec(*heartbeat_ts, 0); | |
|   } | |
|   info.mariadb_slave_capability= get_mariadb_slave_capability(thd); | |
| 
 | |
|   connect_gtid_state.length(0); | |
|   info.using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); | |
|   DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", info.using_gtid_state= false;); | |
|   if (info.using_gtid_state) | |
|   { | |
|     info.slave_gtid_strict_mode= get_slave_gtid_strict_mode(thd); | |
|     info.slave_gtid_ignore_duplicates= get_slave_gtid_ignore_duplicates(thd); | |
|     if(get_slave_until_gtid(thd, &slave_until_gtid_str)) | |
|       info.until_gtid_state= &until_gtid_state_obj; | |
|   } | |
| 
 | |
|   DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events", | |
|     { | |
|       DBUG_SET("-d,binlog_force_reconnect_after_22_events"); | |
|       DBUG_SET_INITIAL("-d,binlog_force_reconnect_after_22_events"); | |
|       dbug_reconnect_counter= 22; | |
|     }); | |
| 
 | |
|   /* | |
|     We want to corrupt the first event, in Log_event::read_log_event(). | |
|     But we do not want the corruption to happen early, eg. when client does | |
|     BINLOG_GTID_POS(). So test case sets a DBUG trigger which causes us to | |
|     set the real DBUG injection here. | |
|   */ | |
|   DBUG_EXECUTE_IF("corrupt_read_log_event2_set", | |
|                   { | |
|                     DBUG_SET("-d,corrupt_read_log_event2_set"); | |
|                     DBUG_SET("+d,corrupt_read_log_event2"); | |
|                   }); | |
| 
 | |
| #ifndef DBUG_OFF | |
|   if (opt_sporadic_binlog_dump_fail && (binlog_dump_count++ % 2)) | |
|   { | |
|     errmsg = "Master failed COM_BINLOG_DUMP to test if slave can recover"; | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     goto err; | |
|   } | |
| #endif | |
|  | |
|   if (!(info.fdev= new Format_description_log_event(3))) | |
|   { | |
|     errmsg= "Out of memory initializing format_description event"; | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
| 
 | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     errmsg = "Binary log is not open"; | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
|   if (!server_id_supplied) | |
|   { | |
|     errmsg = "Misconfigured master - server id was not set"; | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
| 
 | |
|   name=search_file_name; | |
|   if (info.using_gtid_state) | |
|   { | |
|     if (info.gtid_state.load(connect_gtid_state.c_ptr_quick(), | |
|                              connect_gtid_state.length())) | |
|     { | |
|       errmsg= "Out of memory or malformed slave request when obtaining start " | |
|         "position from GTID state"; | |
|       my_errno= ER_UNKNOWN_ERROR; | |
|       goto err; | |
|     } | |
|     if (info.until_gtid_state && | |
|         info.until_gtid_state->load(slave_until_gtid_str.c_ptr_quick(), | |
|                                     slave_until_gtid_str.length())) | |
|     { | |
|       errmsg= "Out of memory or malformed slave request when obtaining UNTIL " | |
|         "position sent from slave"; | |
|       my_errno= ER_UNKNOWN_ERROR; | |
|       goto err; | |
|     } | |
|     if ((error= check_slave_start_position(&info, &errmsg, &error_gtid))) | |
|     { | |
|       my_errno= error; | |
|       goto err; | |
|     } | |
|     if ((errmsg= gtid_find_binlog_file(&info.gtid_state, search_file_name, | |
|                                        info.until_gtid_state))) | |
|     { | |
|       my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|       goto err; | |
|     } | |
|     pos= 4; | |
|   } | |
|   else | |
|   { | |
|     if (log_ident[0]) | |
|       mysql_bin_log.make_log_name(search_file_name, log_ident); | |
|     else | |
|       name=0;					// Find first log | |
|   } | |
| 
 | |
|   linfo.index_file_offset = 0; | |
| 
 | |
|   if (mysql_bin_log.find_log_pos(&linfo, name, 1)) | |
|   { | |
|     errmsg = "Could not find first log file name in binary log index file"; | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
| 
 | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   thd->current_linfo = &linfo; | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
| 
 | |
|   if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0) | |
|   { | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
|   if (pos < BIN_LOG_HEADER_SIZE || pos > my_b_filelength(&log)) | |
|   { | |
|     errmsg= "Client requested master to start replication from \ | |
| impossible position"; | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
| 
 | |
|   if (global_system_variables.log_warnings > 1) | |
|     sql_print_information("Start binlog_dump to slave_server(%lu), pos(%s, %lu)", | |
|                           thd->variables.server_id, log_ident, (ulong)pos); | |
|   if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos))) | |
|   { | |
|     errmsg= "Failed to run hook 'transmit_start'"; | |
|     my_errno= ER_UNKNOWN_ERROR; | |
|     goto err; | |
|   } | |
|   has_transmit_started= true; | |
| 
 | |
|   /* reset transmit packet for the fake rotate event below */ | |
|   if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|     goto err; | |
| 
 | |
|   /* | |
|     Tell the client about the log name with a fake Rotate event; | |
|     this is needed even if we also send a Format_description_log_event | |
|     just after, because that event does not contain the binlog's name. | |
|     Note that as this Rotate event is sent before | |
|     Format_description_log_event, the slave cannot have any info to | |
|     understand this event's format, so the header len of | |
|     Rotate_log_event is FROZEN (so in 5.0 it will have a header shorter | |
|     than other events except FORMAT_DESCRIPTION_EVENT). | |
|     Before 4.0.14 we called fake_rotate_event below only if (pos == | |
|     BIN_LOG_HEADER_SIZE), because if this is false then the slave | |
|     already knows the binlog's name. | |
|     Since, we always call fake_rotate_event; if the slave already knew | |
|     the log's name (ex: CHANGE MASTER TO MASTER_LOG_FILE=...) this is | |
|     useless but does not harm much. It is nice for 3.23 (>=.58) slaves | |
|     which test Rotate events to see if the master is 4.0 (then they | |
|     choose to stop because they can't replicate 4.0); by always calling | |
|     fake_rotate_event we are sure that 3.23.58 and newer will detect the | |
|     problem as soon as replication starts (BUG#198). | |
|     Always calling fake_rotate_event makes sending of normal | |
|     (=from-binlog) Rotate events a priori unneeded, but it is not so | |
|     simple: the 2 Rotate events are not equivalent, the normal one is | |
|     before the Stop event, the fake one is after. If we don't send the | |
|     normal one, then the Stop event will be interpreted (by existing 4.0 | |
|     slaves) as "the master stopped", which is wrong. So for safety, | |
|     given that we want minimum modification of 4.0, we send the normal | |
|     and fake Rotates. | |
|   */ | |
|   if (fake_rotate_event(&info, pos, &errmsg, | |
|                         get_binlog_checksum_value_at_connect(thd))) | |
|   { | |
|     /* | |
|        This error code is not perfect, as fake_rotate_event() does not | |
|        read anything from the binlog; if it fails it's because of an | |
|        error in my_net_write(), fortunately it will say so in errmsg. | |
|     */ | |
|     my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|     goto err; | |
|   } | |
| 
 | |
|   /* | |
|     Adding MAX_LOG_EVENT_HEADER_LEN, since a binlog event can become | |
|     this larger than the corresponding packet (query) sent  | |
|     from client to master. | |
|   */ | |
|   thd->variables.max_allowed_packet= MAX_MAX_ALLOWED_PACKET; | |
| 
 | |
|   /* | |
|     We can set log_lock now, it does not move (it's a member of | |
|     mysql_bin_log, and it's already inited, and it will be destroyed | |
|     only at shutdown). | |
|   */ | |
|   p_coord->pos= pos; // the first hb matches the slave's last seen value | |
|   log_lock= mysql_bin_log.get_log_lock(); | |
|   log_cond= mysql_bin_log.get_log_cond(); | |
|   if (pos > BIN_LOG_HEADER_SIZE) | |
|   { | |
|     /* reset transmit packet for the event read from binary log | |
|        file */ | |
|     if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|       goto err; | |
| 
 | |
|      /* | |
|        Try to find a Format_description_log_event at the beginning of | |
|        the binlog | |
|      */ | |
|     if (!(error = Log_event::read_log_event(&log, packet, log_lock, 0))) | |
|     {  | |
|        /* | |
|          The packet has offsets equal to the normal offsets in a | |
|          binlog event + ev_offset (the first ev_offset characters are | |
|          the header (default \0)). | |
|        */ | |
|        DBUG_PRINT("info", | |
|                   ("Looked for a Format_description_log_event, found event type %d", | |
|                    (*packet)[EVENT_TYPE_OFFSET+ev_offset])); | |
|        if ((*packet)[EVENT_TYPE_OFFSET+ev_offset] == FORMAT_DESCRIPTION_EVENT) | |
|        { | |
|          Format_description_log_event *tmp; | |
| 
 | |
|          info.current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, | |
|                                                      packet->length() - ev_offset); | |
|          DBUG_ASSERT(info.current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || | |
|                      info.current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || | |
|                      info.current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); | |
|          if (!is_slave_checksum_aware(thd) && | |
|              info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && | |
|              info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) | |
|          { | |
|            my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|            errmsg= "Slave can not handle replication events with the checksum " | |
|              "that master is configured to log"; | |
|            sql_print_warning("Master is configured to log replication events " | |
|                              "with checksum, but will not send such events to " | |
|                              "slaves that cannot process them"); | |
|            goto err; | |
|          } | |
| 
 | |
|          if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset, | |
|                                                      packet->length()-ev_offset, | |
|                                                      info.fdev))) | |
|          { | |
|            my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|            errmsg= "Corrupt Format_description event found or out-of-memory"; | |
|            goto err; | |
|          } | |
|          delete info.fdev; | |
|          info.fdev= tmp; | |
| 
 | |
|          (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; | |
|          /* | |
|            mark that this event with "log_pos=0", so the slave | |
|            should not increment master's binlog position | |
|            (rli->group_master_log_pos) | |
|          */ | |
|          int4store((char*) packet->ptr()+LOG_POS_OFFSET+ev_offset, 0); | |
|          /* | |
|            if reconnect master sends FD event with `created' as 0 | |
|            to avoid destroying temp tables. | |
|           */ | |
|          int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+ | |
|                    ST_CREATED_OFFSET+ev_offset, (ulong) 0); | |
| 
 | |
| 	 /* fix the checksum due to latest changes in header */ | |
| 	 if (info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && | |
|              info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) | |
|            fix_checksum(packet, ev_offset); | |
| 
 | |
|          /* send it */ | |
|          if (my_net_write(info.net, (uchar*) packet->ptr(), packet->length())) | |
|          { | |
|            errmsg = "Failed on my_net_write()"; | |
|            my_errno= ER_UNKNOWN_ERROR; | |
|            goto err; | |
|          } | |
| 
 | |
|          /* | |
|            No need to save this event. We are only doing simple reads | |
|            (no real parsing of the events) so we don't need it. And so | |
|            we don't need the artificial Format_description_log_event of | |
|            3.23&4.x. | |
|          */ | |
|        } | |
|      } | |
|      else | |
|      { | |
|        if (test_for_non_eof_log_read_errors(error, &errmsg)) | |
|          goto err; | |
|        /* | |
|          It's EOF, nothing to do, go on reading next events, the | |
|          Format_description_log_event will be found naturally if it is written. | |
|        */ | |
|      } | |
|   } /* end of if (pos > BIN_LOG_HEADER_SIZE); */ | |
|   else | |
|   { | |
|     /* The Format_description_log_event event will be found naturally. */ | |
|   } | |
| 
 | |
|   /* | |
|     Handle the case of START SLAVE UNTIL with an UNTIL condition already | |
|     fulfilled at the start position. | |
|  | |
|     We will send one event, the format_description, and then stop. | |
|   */ | |
|   if (info.until_gtid_state && info.until_gtid_state->count() == 0) | |
|     info.gtid_until_group= GTID_UNTIL_STOP_AFTER_STANDALONE; | |
| 
 | |
|   /* seek to the requested position, to start the requested dump */ | |
|   my_b_seek(&log, pos);			// Seek will done on next read | |
|  | |
|   while (!info.net->error && info.net->vio != 0 && !thd->killed) | |
|   { | |
|     Log_event_type event_type= UNKNOWN_EVENT; | |
|     killed_state killed; | |
| 
 | |
|     /* reset the transmit packet for the event read from binary log | |
|        file */ | |
|     if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|       goto err; | |
| 
 | |
|     bool is_active_binlog= false; | |
|     while (!(killed= thd->killed) && | |
|            !(error = Log_event::read_log_event(&log, packet, log_lock, | |
|                                               info.current_checksum_alg, | |
|                                               log_file_name, | |
|                                               &is_active_binlog))) | |
|     { | |
| #ifndef DBUG_OFF | |
|       if (max_binlog_dump_events && !left_events--) | |
|       { | |
| 	net_flush(info.net); | |
| 	errmsg = "Debugging binlog dump abort"; | |
| 	my_errno= ER_UNKNOWN_ERROR; | |
| 	goto err; | |
|       } | |
| #endif | |
|       /* | |
|         log's filename does not change while it's active | |
|       */ | |
|       p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET); | |
| 
 | |
|       event_type= | |
|         (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); | |
| #ifdef ENABLED_DEBUG_SYNC | |
|       DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", | |
|                       { | |
|                         if (event_type == XID_EVENT) | |
|                         { | |
|                           net_flush(info.net); | |
|                           const char act[]= | |
|                             "now " | |
|                             "wait_for signal.continue"; | |
|                           DBUG_ASSERT(debug_sync_service); | |
|                           DBUG_ASSERT(!debug_sync_set_action(thd, | |
|                                                              STRING_WITH_LEN(act))); | |
|                           const char act2[]= | |
|                             "now " | |
|                             "signal signal.continued"; | |
|                           DBUG_ASSERT(!debug_sync_set_action(current_thd, | |
|                                                              STRING_WITH_LEN(act2))); | |
|                         } | |
|                       }); | |
| #endif | |
|       if (event_type == FORMAT_DESCRIPTION_EVENT) | |
|       { | |
|         Format_description_log_event *tmp; | |
| 
 | |
|         info.current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, | |
|                                                packet->length() - ev_offset); | |
|         DBUG_ASSERT(info.current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || | |
|                     info.current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || | |
|                     info.current_checksum_alg == BINLOG_CHECKSUM_ALG_CRC32); | |
|         if (!is_slave_checksum_aware(thd) && | |
|             info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && | |
|             info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) | |
|         { | |
|           my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|           errmsg= "Slave can not handle replication events with the checksum " | |
|             "that master is configured to log"; | |
|           sql_print_warning("Master is configured to log replication events " | |
|                             "with checksum, but will not send such events to " | |
|                             "slaves that cannot process them"); | |
|           goto err; | |
|         } | |
| 
 | |
|         if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset, | |
|                                                     packet->length()-ev_offset, | |
|                                                     info.fdev))) | |
|         { | |
|           my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|           errmsg= "Corrupt Format_description event found or out-of-memory"; | |
|           goto err; | |
|         } | |
|         delete info.fdev; | |
|         info.fdev= tmp; | |
| 
 | |
|         (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; | |
| 
 | |
|         if (info.using_gtid_state) | |
|         { | |
|           /* | |
|             If this event has the field `created' set, then it will cause the | |
|             slave to delete all active temporary tables. This must not happen | |
|             if the slave received any later GTIDs in a previous connect, as | |
|             those GTIDs might have created new temporary tables that are still | |
|             needed. | |
|  | |
|             So here, we check if the starting GTID position was already | |
|             reached before this format description event. If not, we clear the | |
|             `created' flag to preserve temporary tables on the slave. (If the | |
|             slave connects at a position past this event, it means that it | |
|             already received and handled it in a previous connect). | |
|           */ | |
|           if (!info.gtid_state.is_pos_reached()) | |
|           { | |
|             int4store((char*) packet->ptr()+LOG_EVENT_MINIMAL_HEADER_LEN+ | |
|                       ST_CREATED_OFFSET+ev_offset, (ulong) 0); | |
|             if (info.current_checksum_alg != BINLOG_CHECKSUM_ALG_OFF && | |
|                 info.current_checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF) | |
|               fix_checksum(packet, ev_offset); | |
|           } | |
|         } | |
|       } | |
| 
 | |
| #ifndef DBUG_OFF | |
|       if (dbug_reconnect_counter > 0) | |
|       { | |
|         --dbug_reconnect_counter; | |
|         if (dbug_reconnect_counter == 0) | |
|         { | |
|           errmsg= "DBUG-injected forced reconnect"; | |
|           my_errno= ER_UNKNOWN_ERROR; | |
|           goto err; | |
|         } | |
|       } | |
| #endif | |
|  | |
|       if ((tmp_msg= send_event_to_slave(&info, event_type, &log, | |
|                                         ev_offset, &error_gtid))) | |
|       { | |
|         errmsg= tmp_msg; | |
|         goto err; | |
|       } | |
|       if (unlikely(info.send_fake_gtid_list) && | |
|           info.gtid_skip_group == GTID_SKIP_NOT) | |
|       { | |
|         Gtid_list_log_event glev(&info.until_binlog_state, 0); | |
| 
 | |
|         if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg) || | |
|             fake_gtid_list_event(&info, &glev, &errmsg, my_b_tell(&log))) | |
|         { | |
|           my_errno= ER_UNKNOWN_ERROR; | |
|           goto err; | |
|         } | |
|         info.send_fake_gtid_list= false; | |
|       } | |
|       if (info.until_gtid_state && | |
|           is_until_reached(&info, &ev_offset, event_type, &errmsg, | |
|                            my_b_tell(&log))) | |
|       { | |
|         if (errmsg) | |
|         { | |
|           my_errno= ER_UNKNOWN_ERROR; | |
|           goto err; | |
|         } | |
|         goto end; | |
|       } | |
| 
 | |
|       DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", | |
|                       { | |
|                         if (event_type == XID_EVENT) | |
|                         { | |
|                           net_flush(info.net); | |
|                         } | |
|                       }); | |
| 
 | |
|       /* reset transmit packet for next loop */ | |
|       if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|         goto err; | |
|     } | |
|     if (killed) | |
|       goto end; | |
| 
 | |
|     DBUG_EXECUTE_IF("wait_after_binlog_EOF", | |
|                     { | |
|                       const char act[]= "now wait_for signal.rotate_finished"; | |
|                       DBUG_ASSERT(!debug_sync_set_action(current_thd, | |
|                                                          STRING_WITH_LEN(act))); | |
|                     };); | |
| 
 | |
|     /* | |
|       TODO: now that we are logging the offset, check to make sure | |
|       the recorded offset and the actual match. | |
|       Guilhem 2003-06: this is not true if this master is a slave | |
|       <4.0.15 running with --log-slave-updates, because then log_pos may | |
|       be the offset in the-master-of-this-master's binlog. | |
|     */ | |
|     if (test_for_non_eof_log_read_errors(error, &errmsg)) | |
|       goto err; | |
| 
 | |
|     /* | |
|       We should only move to the next binlog when the last read event | |
|       came from a already deactivated binlog. | |
|      */ | |
|     if (!(flags & BINLOG_DUMP_NON_BLOCK) && is_active_binlog) | |
|     { | |
|       /* | |
| 	Block until there is more data in the log | |
|       */ | |
|       if (net_flush(info.net)) | |
|       { | |
| 	errmsg = "failed on net_flush()"; | |
| 	my_errno= ER_UNKNOWN_ERROR; | |
| 	goto err; | |
|       } | |
| 
 | |
|       /* | |
| 	We may have missed the update broadcast from the log | |
| 	that has just happened, let's try to catch it if it did. | |
| 	If we did not miss anything, we just wait for other threads | |
| 	to signal us. | |
|       */ | |
|       { | |
| 	log.error=0; | |
| 	bool read_packet = 0; | |
| 
 | |
| #ifndef DBUG_OFF | |
| 	if (max_binlog_dump_events && !left_events--) | |
| 	{ | |
| 	  errmsg = "Debugging binlog dump abort"; | |
| 	  my_errno= ER_UNKNOWN_ERROR; | |
| 	  goto err; | |
| 	} | |
| #endif | |
|  | |
|         /* reset the transmit packet for the event read from binary log | |
|            file */ | |
|         if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|           goto err; | |
|          | |
| 	/* | |
| 	  No one will update the log while we are reading | |
| 	  now, but we'll be quick and just read one record | |
|  | |
| 	  TODO: | |
|           Add an counter that is incremented for each time we update the | |
|           binary log.  We can avoid the following read if the counter | |
|           has not been updated since last read. | |
| 	*/ | |
| 
 | |
|         mysql_mutex_lock(log_lock); | |
|         switch (error= Log_event::read_log_event(&log, packet, (mysql_mutex_t*) 0, | |
|                                                  info.current_checksum_alg)) { | |
| 	case 0: | |
| 	  /* we read successfully, so we'll need to send it to the slave */ | |
|           mysql_mutex_unlock(log_lock); | |
| 	  read_packet = 1; | |
|           p_coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET); | |
|           event_type= | |
|             (Log_event_type)((uchar)(*packet)[LOG_EVENT_OFFSET+ev_offset]); | |
| 	  break; | |
| 
 | |
| 	case LOG_READ_EOF: | |
|         { | |
|           int ret; | |
|           ulong signal_cnt; | |
| 	  DBUG_PRINT("wait",("waiting for data in binary log")); | |
|           /* For mysqlbinlog (mysqlbinlog.server_id==0). */ | |
| 	  if (thd->variables.server_id==0) | |
| 	  { | |
|             mysql_mutex_unlock(log_lock); | |
| 	    goto end; | |
| 	  } | |
| 
 | |
| #ifndef DBUG_OFF | |
|           ulong hb_info_counter= 0; | |
| #endif | |
|           PSI_stage_info old_stage; | |
|           signal_cnt= mysql_bin_log.signal_cnt; | |
|           do  | |
|           { | |
|             if (heartbeat_period != 0) | |
|             { | |
|               DBUG_ASSERT(heartbeat_ts); | |
|               set_timespec_nsec(*heartbeat_ts, heartbeat_period); | |
|             } | |
|             thd->ENTER_COND(log_cond, log_lock, | |
|                             &stage_master_has_sent_all_binlog_to_slave, | |
|                             &old_stage); | |
|             if (thd->killed) | |
|               break; | |
|             ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts); | |
|             DBUG_ASSERT(ret == 0 || (heartbeat_period != 0)); | |
|             if (ret == ETIMEDOUT || ret == ETIME) | |
|             { | |
| #ifndef DBUG_OFF | |
|               if (hb_info_counter < 3) | |
|               { | |
|                 sql_print_information("master sends heartbeat message"); | |
|                 hb_info_counter++; | |
|                 if (hb_info_counter == 3) | |
|                   sql_print_information("the rest of heartbeat info skipped ..."); | |
|               } | |
| #endif | |
|               /* reset transmit packet for the heartbeat event */ | |
|               if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|               { | |
|                 thd->EXIT_COND(&old_stage); | |
|                 goto err; | |
|               } | |
|               if (send_heartbeat_event(info.net, packet, p_coord, | |
|                                        info.current_checksum_alg)) | |
|               { | |
|                 errmsg = "Failed on my_net_write()"; | |
|                 my_errno= ER_UNKNOWN_ERROR; | |
|                 thd->EXIT_COND(&old_stage); | |
|                 goto err; | |
|               } | |
|             } | |
|             else | |
|             { | |
|               DBUG_PRINT("wait",("binary log received update or a broadcast signal caught")); | |
|             } | |
|           } while (signal_cnt == mysql_bin_log.signal_cnt); | |
|           thd->EXIT_COND(&old_stage); | |
|         } | |
|         break; | |
|              | |
|         default: | |
|           mysql_mutex_unlock(log_lock); | |
|           test_for_non_eof_log_read_errors(error, &errmsg); | |
|           goto err; | |
| 	} | |
| 
 | |
|         if (read_packet) | |
|         { | |
|           if ((tmp_msg= send_event_to_slave(&info, event_type, &log, | |
|                                             ev_offset, &error_gtid))) | |
|           { | |
|             errmsg= tmp_msg; | |
|             goto err; | |
|           } | |
|           if (unlikely(info.send_fake_gtid_list) | |
|               && info.gtid_skip_group == GTID_SKIP_NOT) | |
|           { | |
|             Gtid_list_log_event glev(&info.until_binlog_state, 0); | |
| 
 | |
|             if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg) || | |
|                 fake_gtid_list_event(&info, &glev, &errmsg, my_b_tell(&log))) | |
|             { | |
|               my_errno= ER_UNKNOWN_ERROR; | |
|               goto err; | |
|             } | |
|             info.send_fake_gtid_list= false; | |
|           } | |
|           if (info.until_gtid_state && | |
|               is_until_reached(&info, &ev_offset, event_type, &errmsg, | |
|                                my_b_tell(&log))) | |
|           { | |
|             if (errmsg) | |
|             { | |
|               my_errno= ER_UNKNOWN_ERROR; | |
|               goto err; | |
|             } | |
|             goto end; | |
|           } | |
|         } | |
| 
 | |
| 	log.error=0; | |
|       } | |
|     } | |
|     else | |
|     { | |
|       bool loop_breaker = 0; | |
|       /* need this to break out of the for loop from switch */ | |
| 
 | |
|       THD_STAGE_INFO(thd, stage_finished_reading_one_binlog_switching_to_next_binlog); | |
|       switch (mysql_bin_log.find_next_log(&linfo, 1)) { | |
|       case 0: | |
| 	break; | |
|       case LOG_INFO_EOF: | |
|         if (mysql_bin_log.is_active(log_file_name)) | |
|         { | |
|           loop_breaker = (flags & BINLOG_DUMP_NON_BLOCK); | |
|           break; | |
|         } | |
|         /* fall through */ | |
|       default: | |
| 	errmsg = "could not find next log"; | |
| 	my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
| 	goto err; | |
|       } | |
| 
 | |
|       if (loop_breaker) | |
|         break; | |
| 
 | |
|       end_io_cache(&log); | |
|       mysql_file_close(file, MYF(MY_WME)); | |
| 
 | |
|       /* reset transmit packet for the possible fake rotate event */ | |
|       if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg)) | |
|         goto err; | |
|        | |
|       /* | |
|         Call fake_rotate_event() in case the previous log (the one which | |
|         we have just finished reading) did not contain a Rotate event | |
|         (for example (I don't know any other example) the previous log | |
|         was the last one before the master was shutdown & restarted). | |
|         This way we tell the slave about the new log's name and | |
|         position.  If the binlog is 5.0, the next event we are going to | |
|         read and send is Format_description_log_event. | |
|       */ | |
|       if ((file=open_binlog(&log, log_file_name, &errmsg)) < 0 || | |
| 	  fake_rotate_event(&info, BIN_LOG_HEADER_SIZE, &errmsg, | |
|                             info.current_checksum_alg)) | |
|       { | |
| 	my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
| 	goto err; | |
|       } | |
| 
 | |
|       p_coord->file_name= log_file_name; // reset to the next | |
|     } | |
|   } | |
| 
 | |
| end: | |
|   end_io_cache(&log); | |
|   mysql_file_close(file, MYF(MY_WME)); | |
| 
 | |
|   if (has_transmit_started) | |
|     RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags)); | |
|   my_eof(thd); | |
|   THD_STAGE_INFO(thd, stage_waiting_to_finalize_termination); | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   thd->current_linfo = 0; | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
|   thd->variables.max_allowed_packet= old_max_allowed_packet; | |
|   delete info.fdev; | |
|   DBUG_VOID_RETURN; | |
| 
 | |
| err: | |
|   THD_STAGE_INFO(thd, stage_waiting_to_finalize_termination); | |
|   if (my_errno == ER_MASTER_FATAL_ERROR_READING_BINLOG && my_b_inited(&log)) | |
|   { | |
|     /*  | |
|        detailing the fatal error message with coordinates  | |
|        of the last position read. | |
|     */ | |
|     my_snprintf(error_text, sizeof(error_text), | |
|                 "%s; the first event '%s' at %lld, " | |
|                 "the last event read from '%s' at %lld, " | |
|                 "the last byte read from '%s' at %lld.", | |
|                 errmsg, | |
|                 my_basename(p_start_coord->file_name), p_start_coord->pos, | |
|                 my_basename(p_coord->file_name), p_coord->pos, | |
|                 my_basename(log_file_name), my_b_tell(&log)); | |
|   } | |
|   else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG) | |
|   { | |
|     my_snprintf(error_text, sizeof(error_text), | |
|                 "Error: connecting slave requested to start from GTID " | |
|                 "%u-%u-%llu, which is not in the master's binlog", | |
|                 error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no); | |
|     /* Use this error code so slave will know not to try reconnect. */ | |
|     my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|   } | |
|   else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2) | |
|   { | |
|     my_snprintf(error_text, sizeof(error_text), | |
|                 "Error: connecting slave requested to start from GTID " | |
|                 "%u-%u-%llu, which is not in the master's binlog. Since the " | |
|                 "master's binlog contains GTIDs with higher sequence numbers, " | |
|                 "it probably means that the slave has diverged due to " | |
|                 "executing extra erroneous transactions", | |
|                 error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no); | |
|     /* Use this error code so slave will know not to try reconnect. */ | |
|     my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|   } | |
|   else if (my_errno == ER_GTID_START_FROM_BINLOG_HOLE) | |
|   { | |
|     my_snprintf(error_text, sizeof(error_text), | |
|                 "The binlog on the master is missing the GTID %u-%u-%llu " | |
|                 "requested by the slave (even though both a prior and a " | |
|                 "subsequent sequence number does exist), and GTID strict mode " | |
|                 "is enabled", | |
|                 error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no); | |
|     /* Use this error code so slave will know not to try reconnect. */ | |
|     my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|   } | |
|   else if (my_errno == ER_CANNOT_LOAD_SLAVE_GTID_STATE) | |
|   { | |
|     my_snprintf(error_text, sizeof(error_text), | |
|                 "Failed to load replication slave GTID state from table %s.%s", | |
|                 "mysql", rpl_gtid_slave_state_table_name.str); | |
|     my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; | |
|   } | |
|   else | |
|     strcpy(error_text, errmsg); | |
|   end_io_cache(&log); | |
|   if (has_transmit_started) | |
|     RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags)); | |
|   /* | |
|     Exclude  iteration through thread list | |
|     this is needed for purge_logs() - it will iterate through | |
|     thread list and update thd->current_linfo->index_file_offset | |
|     this mutex will make sure that it never tried to update our linfo | |
|     after we return from this stack frame | |
|   */ | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   thd->current_linfo = 0; | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
|   if (file >= 0) | |
|     mysql_file_close(file, MYF(MY_WME)); | |
|   thd->variables.max_allowed_packet= old_max_allowed_packet; | |
|   delete info.fdev; | |
| 
 | |
|   my_message(my_errno, error_text, MYF(0)); | |
|   DBUG_VOID_RETURN; | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a START SLAVE statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @param mi Pointer to Master_info object for the slave's IO thread. | |
|  | |
|   @param net_report If true, saves the exit status into thd->stmt_da. | |
|  | |
|   @retval 0 success | |
|   @retval 1 error | |
|   @retval -1 fatal error | |
| */ | |
| 
 | |
| int start_slave(THD* thd , Master_info* mi,  bool net_report) | |
| { | |
|   int slave_errno= 0; | |
|   int thread_mask; | |
|   char master_info_file_tmp[FN_REFLEN]; | |
|   char relay_log_info_file_tmp[FN_REFLEN]; | |
|   DBUG_ENTER("start_slave"); | |
| 
 | |
|   if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0)) | |
|     DBUG_RETURN(-1); | |
| 
 | |
|   create_logfile_name_with_suffix(master_info_file_tmp, | |
|                                   sizeof(master_info_file_tmp), | |
|                                   master_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
|   create_logfile_name_with_suffix(relay_log_info_file_tmp, | |
|                                   sizeof(relay_log_info_file_tmp), | |
|                                   relay_log_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
| 
 | |
|   mi->lock_slave_threads(); | |
|   if (mi->killed) | |
|   { | |
|     /* connection was deleted while we waited for lock_slave_threads */ | |
|     mi->unlock_slave_threads(); | |
|     my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, | |
|              mi->connection_name.str); | |
|     DBUG_RETURN(-1); | |
|   } | |
| 
 | |
|   // Get a mask of _stopped_ threads | |
|   init_thread_mask(&thread_mask,mi,1 /* inverse */); | |
| 
 | |
|   if (thd->lex->mi.gtid_pos_str.str) | |
|   { | |
|     if (thread_mask != (SLAVE_IO|SLAVE_SQL)) | |
|     { | |
|       slave_errno= ER_SLAVE_WAS_RUNNING; | |
|       goto err; | |
|     } | |
|     if (thd->lex->slave_thd_opt) | |
|     { | |
|       slave_errno= ER_BAD_SLAVE_UNTIL_COND; | |
|       goto err; | |
|     } | |
|     if (mi->using_gtid == Master_info::USE_GTID_NO) | |
|     { | |
|       slave_errno= ER_UNTIL_REQUIRES_USING_GTID; | |
|       goto err; | |
|     } | |
|   } | |
| 
 | |
|   /* | |
|     Below we will start all stopped threads.  But if the user wants to | |
|     start only one thread, do as if the other thread was running (as we | |
|     don't wan't to touch the other thread), so set the bit to 0 for the | |
|     other thread | |
|   */ | |
|   if (thd->lex->slave_thd_opt) | |
|     thread_mask&= thd->lex->slave_thd_opt; | |
|   if (thread_mask) //some threads are stopped, start them | |
|   { | |
|     if (init_master_info(mi,master_info_file_tmp,relay_log_info_file_tmp, 0, | |
| 			 thread_mask)) | |
|       slave_errno=ER_MASTER_INFO; | |
|     else if (!server_id_supplied) | |
|     { | |
|       slave_errno= ER_BAD_SLAVE; net_report= 0; | |
|       my_message(slave_errno, "Misconfigured slave: server_id was not set; Fix in config file", | |
|                    MYF(0)); | |
|     } | |
|     else if (!*mi->host) | |
|     { | |
|       slave_errno= ER_BAD_SLAVE; net_report= 0; | |
|       my_message(slave_errno, "Misconfigured slave: MASTER_HOST was not set; Fix in config file or with CHANGE MASTER TO", | |
|                  MYF(0)); | |
|     } | |
|     else | |
|     { | |
|       /* | |
|         If we will start SQL thread we will care about UNTIL options If | |
|         not and they are specified we will ignore them and warn user | |
|         about this fact. | |
|       */ | |
|       if (thread_mask & SLAVE_SQL) | |
|       { | |
|         mysql_mutex_lock(&mi->rli.data_lock); | |
| 
 | |
|         if (thd->lex->mi.pos) | |
|         { | |
|           if (thd->lex->mi.relay_log_pos) | |
|             slave_errno=ER_BAD_SLAVE_UNTIL_COND; | |
|           mi->rli.until_condition= Relay_log_info::UNTIL_MASTER_POS; | |
|           mi->rli.until_log_pos= thd->lex->mi.pos; | |
|           /* | |
|              We don't check thd->lex->mi.log_file_name for NULL here | |
|              since it is checked in sql_yacc.yy | |
|           */ | |
|           strmake_buf(mi->rli.until_log_name, thd->lex->mi.log_file_name); | |
|         } | |
|         else if (thd->lex->mi.relay_log_pos) | |
|         { | |
|           if (thd->lex->mi.pos) | |
|             slave_errno=ER_BAD_SLAVE_UNTIL_COND; | |
|           mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS; | |
|           mi->rli.until_log_pos= thd->lex->mi.relay_log_pos; | |
|           strmake_buf(mi->rli.until_log_name, thd->lex->mi.relay_log_name); | |
|         } | |
|         else if (thd->lex->mi.gtid_pos_str.str) | |
|         { | |
|           if (mi->rli.until_gtid_pos.load(thd->lex->mi.gtid_pos_str.str, | |
|                                           thd->lex->mi.gtid_pos_str.length)) | |
|           { | |
|             slave_errno= ER_INCORRECT_GTID_STATE; | |
|             mysql_mutex_unlock(&mi->rli.data_lock); | |
|             goto err; | |
|           } | |
|           mi->rli.until_condition= Relay_log_info::UNTIL_GTID; | |
|         } | |
|         else | |
|           mi->rli.clear_until_condition(); | |
| 
 | |
|         if (mi->rli.until_condition == Relay_log_info::UNTIL_MASTER_POS || | |
|             mi->rli.until_condition == Relay_log_info::UNTIL_RELAY_POS) | |
|         { | |
|           /* Preparing members for effective until condition checking */ | |
|           const char *p= fn_ext(mi->rli.until_log_name); | |
|           char *p_end; | |
|           if (*p) | |
|           { | |
|             //p points to '.' | |
|             mi->rli.until_log_name_extension= strtoul(++p,&p_end, 10); | |
|             /* | |
|               p_end points to the first invalid character. If it equals | |
|               to p, no digits were found, error. If it contains '\0' it | |
|               means  conversion went ok. | |
|             */ | |
|             if (p_end==p || *p_end) | |
|               slave_errno=ER_BAD_SLAVE_UNTIL_COND; | |
|           } | |
|           else | |
|             slave_errno=ER_BAD_SLAVE_UNTIL_COND; | |
| 
 | |
|           /* mark the cached result of the UNTIL comparison as "undefined" */ | |
|           mi->rli.until_log_names_cmp_result= | |
|             Relay_log_info::UNTIL_LOG_NAMES_CMP_UNKNOWN; | |
|         } | |
| 
 | |
|         if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE) | |
|         { | |
|           /* Issuing warning then started without --skip-slave-start */ | |
|           if (!opt_skip_slave_start) | |
|             push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, | |
|                          ER_MISSING_SKIP_SLAVE, | |
|                          ER(ER_MISSING_SKIP_SLAVE)); | |
|         } | |
| 
 | |
|         mysql_mutex_unlock(&mi->rli.data_lock); | |
|       } | |
|       else if (thd->lex->mi.pos || thd->lex->mi.relay_log_pos) | |
|         push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED, | |
|                      ER(ER_UNTIL_COND_IGNORED)); | |
| 
 | |
|       if (!slave_errno) | |
|         slave_errno = start_slave_threads(1, | |
|                                           1 /* wait for start */, | |
|                                           mi, | |
|                                           master_info_file_tmp, | |
|                                           relay_log_info_file_tmp, | |
|                                           thread_mask); | |
|     } | |
|   } | |
|   else | |
|   { | |
|     /* no error if all threads are already started, only a warning */ | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING, | |
|                  ER(ER_SLAVE_WAS_RUNNING)); | |
|   } | |
| 
 | |
| err: | |
|   mi->unlock_slave_threads(); | |
| 
 | |
|   if (slave_errno) | |
|   { | |
|     if (net_report) | |
|       my_error(slave_errno, MYF(0), | |
|                (int) mi->connection_name.length, | |
|                mi->connection_name.str); | |
|     DBUG_RETURN(slave_errno == ER_BAD_SLAVE ? -1 : 1); | |
|   } | |
| 
 | |
|   DBUG_RETURN(0); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a STOP SLAVE statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @param mi Pointer to Master_info object for the slave's IO thread. | |
|  | |
|   @param net_report If true, saves the exit status into thd->stmt_da. | |
|  | |
|   @retval 0 success | |
|   @retval 1 error | |
|   @retval -1 error | |
| */ | |
| 
 | |
| int stop_slave(THD* thd, Master_info* mi, bool net_report ) | |
| { | |
|   int slave_errno; | |
|   DBUG_ENTER("stop_slave"); | |
|   DBUG_PRINT("enter",("Connection: %s", mi->connection_name.str)); | |
| 
 | |
|   if (check_access(thd, SUPER_ACL, any_db, NULL, NULL, 0, 0)) | |
|     DBUG_RETURN(-1); | |
|   THD_STAGE_INFO(thd, stage_killing_slave); | |
|   int thread_mask; | |
|   mi->lock_slave_threads(); | |
|   /* | |
|     Get a mask of _running_ threads. | |
|     We don't have to test for mi->killed as the thread_mask will take care | |
|     of checking if threads exists | |
|   */ | |
|   init_thread_mask(&thread_mask,mi,0 /* not inverse*/); | |
|   /* | |
|     Below we will stop all running threads. | |
|     But if the user wants to stop only one thread, do as if the other thread | |
|     was stopped (as we don't wan't to touch the other thread), so set the | |
|     bit to 0 for the other thread | |
|   */ | |
|   if (thd->lex->slave_thd_opt) | |
|     thread_mask &= thd->lex->slave_thd_opt; | |
| 
 | |
|   if (thread_mask) | |
|   { | |
|     slave_errno= terminate_slave_threads(mi,thread_mask, 0 /* get lock */); | |
|   } | |
|   else | |
|   { | |
|     //no error if both threads are already stopped, only a warning | |
|     slave_errno= 0; | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING, | |
|                  ER(ER_SLAVE_WAS_NOT_RUNNING)); | |
|   } | |
| 
 | |
|   mi->unlock_slave_threads(); | |
| 
 | |
|   if (slave_errno) | |
|   { | |
|     if (net_report) | |
|       my_message(slave_errno, ER(slave_errno), MYF(0)); | |
|     DBUG_RETURN(1); | |
|   } | |
| 
 | |
|   DBUG_RETURN(0); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a RESET SLAVE statement. | |
|  | |
|   @param thd Pointer to THD object of the client thread executing the | |
|   statement. | |
|  | |
|   @param mi Pointer to Master_info object for the slave. | |
|  | |
|   @retval 0 success | |
|   @retval 1 error | |
| */ | |
| int reset_slave(THD *thd, Master_info* mi) | |
| { | |
|   MY_STAT stat_area; | |
|   char fname[FN_REFLEN]; | |
|   int thread_mask= 0, error= 0; | |
|   uint sql_errno=ER_UNKNOWN_ERROR; | |
|   const char* errmsg= "Unknown error occurred while reseting slave"; | |
|   char master_info_file_tmp[FN_REFLEN]; | |
|   char relay_log_info_file_tmp[FN_REFLEN]; | |
|   DBUG_ENTER("reset_slave"); | |
| 
 | |
|   mi->lock_slave_threads(); | |
|   if (mi->killed) | |
|   { | |
|     /* connection was deleted while we waited for lock_slave_threads */ | |
|     mi->unlock_slave_threads(); | |
|     my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, | |
|              mi->connection_name.str); | |
|     DBUG_RETURN(-1); | |
|   } | |
| 
 | |
|   init_thread_mask(&thread_mask,mi,0 /* not inverse */); | |
|   if (thread_mask) // We refuse if any slave thread is running | |
|   { | |
|     mi->unlock_slave_threads(); | |
|     my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length, | |
|              mi->connection_name.str); | |
|     DBUG_RETURN(ER_SLAVE_MUST_STOP); | |
|   } | |
| 
 | |
|   ha_reset_slave(thd); | |
| 
 | |
|   // delete relay logs, clear relay log coordinates | |
|   if ((error= purge_relay_logs(&mi->rli, thd, | |
| 			       1 /* just reset */, | |
| 			       &errmsg))) | |
|   { | |
|     sql_errno= ER_RELAY_LOG_FAIL; | |
|     goto err; | |
|   } | |
| 
 | |
|   /* Clear master's log coordinates and associated information */ | |
|   mi->clear_in_memory_info(thd->lex->reset_slave_info.all); | |
| 
 | |
|   /* | |
|      Reset errors (the idea is that we forget about the | |
|      old master). | |
|   */ | |
|   mi->clear_error(); | |
|   mi->rli.clear_error(); | |
|   mi->rli.clear_until_condition(); | |
|   mi->rli.slave_skip_counter= 0; | |
| 
 | |
|   // close master_info_file, relay_log_info_file, set mi->inited=rli->inited=0 | |
|   end_master_info(mi); | |
| 
 | |
|   end_relay_log_info(&mi->rli); | |
|   // and delete these two files | |
|   create_logfile_name_with_suffix(master_info_file_tmp, | |
|                                   sizeof(master_info_file_tmp), | |
|                                   master_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
|   create_logfile_name_with_suffix(relay_log_info_file_tmp, | |
|                                   sizeof(relay_log_info_file_tmp), | |
|                                   relay_log_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
| 
 | |
|   fn_format(fname, master_info_file_tmp, mysql_data_home, "", 4+32); | |
|   if (mysql_file_stat(key_file_master_info, fname, &stat_area, MYF(0)) && | |
|       mysql_file_delete(key_file_master_info, fname, MYF(MY_WME))) | |
|   { | |
|     error=1; | |
|     goto err; | |
|   } | |
|   else if (global_system_variables.log_warnings > 1) | |
|     sql_print_information("Deleted Master_info file '%s'.", fname); | |
| 
 | |
|   // delete relay_log_info_file | |
|   fn_format(fname, relay_log_info_file_tmp, mysql_data_home, "", 4+32); | |
|   if (mysql_file_stat(key_file_relay_log_info, fname, &stat_area, MYF(0)) && | |
|       mysql_file_delete(key_file_relay_log_info, fname, MYF(MY_WME))) | |
|   { | |
|     error=1; | |
|     goto err; | |
|   } | |
|   else if (global_system_variables.log_warnings > 1) | |
|     sql_print_information("Deleted Master_info file '%s'.", fname); | |
| 
 | |
|   RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi)); | |
| err: | |
|   mi->unlock_slave_threads(); | |
|   if (error) | |
|     my_error(sql_errno, MYF(0), errmsg); | |
|   DBUG_RETURN(error); | |
| } | |
| 
 | |
| /* | |
|  | |
|   Kill all Binlog_dump threads which previously talked to the same slave | |
|   ("same" means with the same server id). Indeed, if the slave stops, if the | |
|   Binlog_dump thread is waiting (mysql_cond_wait) for binlog update, then it | |
|   will keep existing until a query is written to the binlog. If the master is | |
|   idle, then this could last long, and if the slave reconnects, we could have 2 | |
|   Binlog_dump threads in SHOW PROCESSLIST, until a query is written to the | |
|   binlog. To avoid this, when the slave reconnects and sends COM_BINLOG_DUMP, | |
|   the master kills any existing thread with the slave's server id (if this id | |
|   is not zero; it will be true for real slaves, but false for mysqlbinlog when | |
|   it sends COM_BINLOG_DUMP to get a remote binlog dump). | |
|  | |
|   SYNOPSIS | |
|     kill_zombie_dump_threads() | |
|     slave_server_id     the slave's server id | |
|  | |
| */ | |
| 
 | |
| 
 | |
| void kill_zombie_dump_threads(uint32 slave_server_id) | |
| { | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   I_List_iterator<THD> it(threads); | |
|   THD *tmp; | |
| 
 | |
|   while ((tmp=it++)) | |
|   { | |
|     if (tmp->get_command() == COM_BINLOG_DUMP && | |
|        tmp->variables.server_id == slave_server_id) | |
|     { | |
|       mysql_mutex_lock(&tmp->LOCK_thd_data);    // Lock from delete | |
|       break; | |
|     } | |
|   } | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
|   if (tmp) | |
|   { | |
|     /* | |
|       Here we do not call kill_one_thread() as | |
|       it will be slow because it will iterate through the list | |
|       again. We just to do kill the thread ourselves. | |
|     */ | |
|     tmp->awake(KILL_QUERY); | |
|     mysql_mutex_unlock(&tmp->LOCK_thd_data); | |
|   } | |
| } | |
| 
 | |
| /** | |
|    Get value for a string parameter with error checking | |
|  | |
|    Note that in case of error the original string should not be updated! | |
|  | |
|    @ret 0 ok | |
|    @ret 1 error | |
| */ | |
| 
 | |
| static bool get_string_parameter(char *to, const char *from, size_t length, | |
|                                  const char *name, CHARSET_INFO *cs) | |
| { | |
|   if (from)                                     // Empty paramaters allowed | |
|   { | |
|     size_t from_length= strlen(from); | |
|     uint from_numchars= cs->cset->numchars(cs, from, from + from_length); | |
|     if (from_numchars > length / cs->mbmaxlen) | |
|     { | |
|       my_error(ER_WRONG_STRING_LENGTH, MYF(0), from, name, length / cs->mbmaxlen); | |
|       return 1; | |
|     } | |
|     memcpy(to, from, from_length+1); | |
|   } | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a CHANGE MASTER statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @param mi Pointer to Master_info object belonging to the slave's IO | |
|   thread. | |
|  | |
|   @param master_info_added Out parameter saying if the Master_info *mi was | |
|   added to the global list of masters. This is useful in error conditions | |
|   to know if caller should free Master_info *mi. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE error | |
| */ | |
| bool change_master(THD* thd, Master_info* mi, bool *master_info_added) | |
| { | |
|   int thread_mask; | |
|   const char* errmsg= 0; | |
|   bool need_relay_log_purge= 1; | |
|   bool ret= FALSE; | |
|   char saved_host[HOSTNAME_LENGTH + 1]; | |
|   uint saved_port; | |
|   char saved_log_name[FN_REFLEN]; | |
|   Master_info::enum_using_gtid saved_using_gtid; | |
|   char master_info_file_tmp[FN_REFLEN]; | |
|   char relay_log_info_file_tmp[FN_REFLEN]; | |
|   my_off_t saved_log_pos; | |
|   LEX_MASTER_INFO* lex_mi= &thd->lex->mi; | |
|   DBUG_ENTER("change_master"); | |
| 
 | |
|   DBUG_ASSERT(master_info_index); | |
|   mysql_mutex_assert_owner(&LOCK_active_mi); | |
| 
 | |
|   *master_info_added= false; | |
|   /*  | |
|     We need to check if there is an empty master_host. Otherwise | |
|     change master succeeds, a master.info file is created containing  | |
|     empty master_host string and when issuing: start slave; an error | |
|     is thrown stating that the server is not configured as slave. | |
|     (See BUG#28796). | |
|   */ | |
|   if (lex_mi->host && !*lex_mi->host)  | |
|   { | |
|     my_error(ER_WRONG_ARGUMENTS, MYF(0), "MASTER_HOST"); | |
|     DBUG_RETURN(TRUE); | |
|   } | |
|   if (master_info_index->check_duplicate_master_info(&lex_mi->connection_name, | |
|                                                      lex_mi->host, | |
|                                                      lex_mi->port)) | |
|     DBUG_RETURN(TRUE); | |
| 
 | |
|   mi->lock_slave_threads(); | |
|   if (mi->killed) | |
|   { | |
|     /* connection was deleted while we waited for lock_slave_threads */ | |
|     mi->unlock_slave_threads(); | |
|     my_error(WARN_NO_MASTER_INFO, mi->connection_name.length, | |
|              mi->connection_name.str); | |
|     DBUG_RETURN(TRUE); | |
|   } | |
| 
 | |
|   init_thread_mask(&thread_mask,mi,0 /*not inverse*/); | |
|   if (thread_mask) // We refuse if any slave thread is running | |
|   { | |
|     my_error(ER_SLAVE_MUST_STOP, MYF(0), (int) mi->connection_name.length, | |
|              mi->connection_name.str); | |
|     ret= TRUE; | |
|     goto err; | |
|   } | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_changing_master); | |
| 
 | |
|   create_logfile_name_with_suffix(master_info_file_tmp, | |
|                                   sizeof(master_info_file_tmp), | |
|                                   master_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
|   create_logfile_name_with_suffix(relay_log_info_file_tmp, | |
|                                   sizeof(relay_log_info_file_tmp), | |
|                                   relay_log_info_file, 0, | |
|                                   &mi->cmp_connection_name); | |
| 
 | |
|   /* if new Master_info doesn't exists, add it */ | |
|   if (!master_info_index->get_master_info(&mi->connection_name, | |
|                                           Sql_condition::WARN_LEVEL_NOTE)) | |
|   { | |
|     if (master_info_index->add_master_info(mi, TRUE)) | |
|     { | |
|       my_error(ER_MASTER_INFO, MYF(0), | |
|                (int) lex_mi->connection_name.length, | |
|                lex_mi->connection_name.str); | |
|       ret= TRUE; | |
|       goto err; | |
|     } | |
|     *master_info_added= true; | |
|   } | |
|   if (global_system_variables.log_warnings > 1) | |
|     sql_print_information("Master connection name: '%.*s'  " | |
|                           "Master_info_file: '%s'  " | |
|                           "Relay_info_file: '%s'", | |
|                           (int) mi->connection_name.length, | |
|                           mi->connection_name.str, | |
|                           master_info_file_tmp, relay_log_info_file_tmp); | |
| 
 | |
|   if (init_master_info(mi, master_info_file_tmp, relay_log_info_file_tmp, 0, | |
| 		       thread_mask)) | |
|   { | |
|     my_error(ER_MASTER_INFO, MYF(0), | |
|              (int) lex_mi->connection_name.length, | |
|              lex_mi->connection_name.str); | |
|     ret= TRUE; | |
|     goto err; | |
|   } | |
| 
 | |
|   /* | |
|     Data lock not needed since we have already stopped the running threads, | |
|     and we have the hold on the run locks which will keep all threads that | |
|     could possibly modify the data structures from running | |
|   */ | |
| 
 | |
|   /* | |
|     Before processing the command, save the previous state. | |
|   */ | |
|   strmake_buf(saved_host, mi->host); | |
|   saved_port= mi->port; | |
|   strmake_buf(saved_log_name, mi->master_log_name); | |
|   saved_log_pos= mi->master_log_pos; | |
|   saved_using_gtid= mi->using_gtid; | |
| 
 | |
|   /* | |
|     If the user specified host or port without binlog or position, | |
|     reset binlog's name to FIRST and position to 4. | |
|   */ | |
| 
 | |
|   if ((lex_mi->host || lex_mi->port) && !lex_mi->log_file_name && !lex_mi->pos) | |
|   { | |
|     mi->master_log_name[0] = 0; | |
|     mi->master_log_pos= BIN_LOG_HEADER_SIZE; | |
|   } | |
| 
 | |
|   if (lex_mi->log_file_name) | |
|     strmake_buf(mi->master_log_name, lex_mi->log_file_name); | |
|   if (lex_mi->pos) | |
|   { | |
|     mi->master_log_pos= lex_mi->pos; | |
|   } | |
|   DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); | |
| 
 | |
|   if (get_string_parameter(mi->host, lex_mi->host, sizeof(mi->host)-1, | |
|                            "MASTER_HOST", system_charset_info) || | |
|       get_string_parameter(mi->user, lex_mi->user, sizeof(mi->user)-1, | |
|                            "MASTER_USER", system_charset_info) || | |
|       get_string_parameter(mi->password, lex_mi->password, | |
|                            sizeof(mi->password)-1, "MASTER_PASSWORD", | |
|                            &my_charset_bin)) | |
|   { | |
|     ret= TRUE; | |
|     goto err; | |
|   } | |
| 
 | |
|   if (lex_mi->port) | |
|     mi->port = lex_mi->port; | |
|   if (lex_mi->connect_retry) | |
|     mi->connect_retry = lex_mi->connect_retry; | |
|   if (lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED) | |
|     mi->heartbeat_period = lex_mi->heartbeat_period; | |
|   else | |
|     mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, | |
|                                       (slave_net_timeout/2.0)); | |
|   mi->received_heartbeats= 0; // counter lives until master is CHANGEd | |
|   /* | |
|     reset the last time server_id list if the current CHANGE MASTER  | |
|     is mentioning IGNORE_SERVER_IDS= (...) | |
|   */ | |
|   if (lex_mi->repl_ignore_server_ids_opt == LEX_MASTER_INFO::LEX_MI_ENABLE) | |
|     reset_dynamic(&mi->ignore_server_ids); | |
|   for (uint i= 0; i < lex_mi->repl_ignore_server_ids.elements; i++) | |
|   { | |
|     ulong s_id; | |
|     get_dynamic(&lex_mi->repl_ignore_server_ids, (uchar*) &s_id, i); | |
|     if (s_id == global_system_variables.server_id && replicate_same_server_id) | |
|     { | |
|       my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast<int>(s_id)); | |
|       ret= TRUE; | |
|       goto err; | |
|     } | |
|     else | |
|     { | |
|       if (bsearch((const ulong *) &s_id, | |
|                   mi->ignore_server_ids.buffer, | |
|                   mi->ignore_server_ids.elements, sizeof(ulong), | |
|                   (int (*) (const void*, const void*)) | |
|                   change_master_server_id_cmp) == NULL) | |
|         insert_dynamic(&mi->ignore_server_ids, (uchar*) &s_id); | |
|     } | |
|   } | |
|   sort_dynamic(&mi->ignore_server_ids, (qsort_cmp) change_master_server_id_cmp); | |
| 
 | |
|   if (lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED) | |
|     mi->ssl= (lex_mi->ssl == LEX_MASTER_INFO::LEX_MI_ENABLE); | |
| 
 | |
|   if (lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED) | |
|     mi->ssl_verify_server_cert= | |
|       (lex_mi->ssl_verify_server_cert == LEX_MASTER_INFO::LEX_MI_ENABLE); | |
| 
 | |
|   if (lex_mi->ssl_ca) | |
|     strmake_buf(mi->ssl_ca, lex_mi->ssl_ca); | |
|   if (lex_mi->ssl_capath) | |
|     strmake_buf(mi->ssl_capath, lex_mi->ssl_capath); | |
|   if (lex_mi->ssl_cert) | |
|     strmake_buf(mi->ssl_cert, lex_mi->ssl_cert); | |
|   if (lex_mi->ssl_cipher) | |
|     strmake_buf(mi->ssl_cipher, lex_mi->ssl_cipher); | |
|   if (lex_mi->ssl_key) | |
|     strmake_buf(mi->ssl_key, lex_mi->ssl_key); | |
|   if (lex_mi->ssl_crl) | |
|     strmake_buf(mi->ssl_crl, lex_mi->ssl_crl); | |
|   if (lex_mi->ssl_crlpath) | |
|     strmake_buf(mi->ssl_crlpath, lex_mi->ssl_crlpath); | |
| 
 | |
| #ifndef HAVE_OPENSSL | |
|   if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath || | |
|       lex_mi->ssl_cert || lex_mi->ssl_cipher || lex_mi->ssl_key || | |
|       lex_mi->ssl_verify_server_cert || lex_mi->ssl_crl || lex_mi->ssl_crlpath) | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, | |
|                  ER_SLAVE_IGNORED_SSL_PARAMS, ER(ER_SLAVE_IGNORED_SSL_PARAMS)); | |
| #endif | |
|  | |
|   if (lex_mi->relay_log_name) | |
|   { | |
|     need_relay_log_purge= 0; | |
|     char relay_log_name[FN_REFLEN]; | |
|     mi->rli.relay_log.make_log_name(relay_log_name, lex_mi->relay_log_name); | |
|     strmake_buf(mi->rli.group_relay_log_name, relay_log_name); | |
|     strmake_buf(mi->rli.event_relay_log_name, relay_log_name); | |
|   } | |
| 
 | |
|   if (lex_mi->relay_log_pos) | |
|   { | |
|     need_relay_log_purge= 0; | |
|     mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; | |
|   } | |
| 
 | |
|   if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_SLAVE_POS) | |
|     mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; | |
|   else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_CURRENT_POS) | |
|     mi->using_gtid= Master_info::USE_GTID_CURRENT_POS; | |
|   else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_NO || | |
|            lex_mi->log_file_name || lex_mi->pos || | |
|            lex_mi->relay_log_name || lex_mi->relay_log_pos) | |
|     mi->using_gtid= Master_info::USE_GTID_NO; | |
| 
 | |
|   /* | |
|     If user did specify neither host nor port nor any log name nor any log | |
|     pos, i.e. he specified only user/password/master_connect_retry, he probably | |
|     wants replication to resume from where it had left, i.e. from the | |
|     coordinates of the **SQL** thread (imagine the case where the I/O is ahead | |
|     of the SQL; restarting from the coordinates of the I/O would lose some | |
|     events which is probably unwanted when you are just doing minor changes | |
|     like changing master_connect_retry). | |
|     A side-effect is that if only the I/O thread was started, this thread may | |
|     restart from ''/4 after the CHANGE MASTER. That's a minor problem (it is a | |
|     much more unlikely situation than the one we are fixing here). | |
|     Note: coordinates of the SQL thread must be read here, before the | |
|     'if (need_relay_log_purge)' block which resets them. | |
|   */ | |
|   if (!lex_mi->host && !lex_mi->port && | |
|       !lex_mi->log_file_name && !lex_mi->pos && | |
|       need_relay_log_purge) | |
|    { | |
|      /* | |
|        Sometimes mi->rli.master_log_pos == 0 (it happens when the SQL thread is | |
|        not initialized), so we use a MY_MAX(). | |
|        What happens to mi->rli.master_log_pos during the initialization stages | |
|        of replication is not 100% clear, so we guard against problems using | |
|        MY_MAX(). | |
|       */ | |
|      mi->master_log_pos = MY_MAX(BIN_LOG_HEADER_SIZE, | |
| 			      mi->rli.group_master_log_pos); | |
|      strmake_buf(mi->master_log_name, mi->rli.group_master_log_name); | |
|   } | |
| 
 | |
|   /* | |
|     Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never | |
|     a slave before). | |
|   */ | |
|   if (flush_master_info(mi, FALSE, FALSE)) | |
|   { | |
|     my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file"); | |
|     ret= TRUE; | |
|     goto err; | |
|   } | |
|   if (need_relay_log_purge) | |
|   { | |
|     THD_STAGE_INFO(thd, stage_purging_old_relay_logs); | |
|     if (purge_relay_logs(&mi->rli, thd, | |
| 			 0 /* not only reset, but also reinit */, | |
| 			 &errmsg)) | |
|     { | |
|       my_error(ER_RELAY_LOG_FAIL, MYF(0), errmsg); | |
|       ret= TRUE; | |
|       goto err; | |
|     } | |
|   } | |
|   else | |
|   { | |
|     const char* msg; | |
|     /* Relay log is already initialized */ | |
|     if (init_relay_log_pos(&mi->rli, | |
| 			   mi->rli.group_relay_log_name, | |
| 			   mi->rli.group_relay_log_pos, | |
| 			   0 /*no data lock*/, | |
| 			   &msg, 0)) | |
|     { | |
|       my_error(ER_RELAY_LOG_INIT, MYF(0), msg); | |
|       ret= TRUE; | |
|       goto err; | |
|     } | |
|   } | |
|   /* | |
|     Coordinates in rli were spoilt by the 'if (need_relay_log_purge)' block, | |
|     so restore them to good values. If we left them to ''/0, that would work; | |
|     but that would fail in the case of 2 successive CHANGE MASTER (without a | |
|     START SLAVE in between): because first one would set the coords in mi to | |
|     the good values of those in rli, the set those in rli to ''/0, then | |
|     second CHANGE MASTER would set the coords in mi to those of rli, i.e. to | |
|     ''/0: we have lost all copies of the original good coordinates. | |
|     That's why we always save good coords in rli. | |
|   */ | |
|   mi->rli.group_master_log_pos= mi->master_log_pos; | |
|   DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); | |
|   strmake_buf(mi->rli.group_master_log_name,mi->master_log_name); | |
| 
 | |
|   if (!mi->rli.group_master_log_name[0]) // uninitialized case | |
|     mi->rli.group_master_log_pos=0; | |
| 
 | |
|   mysql_mutex_lock(&mi->rli.data_lock); | |
|   mi->rli.abort_pos_wait++; /* for MASTER_POS_WAIT() to abort */ | |
|   /* Clear the errors, for a clean start */ | |
|   mi->rli.clear_error(); | |
|   mi->rli.clear_until_condition(); | |
|   mi->rli.slave_skip_counter= 0; | |
| 
 | |
|   sql_print_information("'CHANGE MASTER TO executed'. " | |
|     "Previous state master_host='%s', master_port='%u', master_log_file='%s', " | |
|     "master_log_pos='%ld'. " | |
|     "New state master_host='%s', master_port='%u', master_log_file='%s', " | |
|     "master_log_pos='%ld'.", saved_host, saved_port, saved_log_name, | |
|     (ulong) saved_log_pos, mi->host, mi->port, mi->master_log_name, | |
|     (ulong) mi->master_log_pos); | |
|   if (saved_using_gtid != Master_info::USE_GTID_NO || | |
|       mi->using_gtid != Master_info::USE_GTID_NO) | |
|     sql_print_information("Previous Using_Gtid=%s. New Using_Gtid=%s", | |
|                           mi->using_gtid_astext(saved_using_gtid), | |
|                           mi->using_gtid_astext(mi->using_gtid)); | |
| 
 | |
|   /* | |
|     If we don't write new coordinates to disk now, then old will remain in | |
|     relay-log.info until START SLAVE is issued; but if mysqld is shutdown | |
|     before START SLAVE, then old will remain in relay-log.info, and will be the | |
|     in-memory value at restart (thus causing errors, as the old relay log does | |
|     not exist anymore). | |
|   */ | |
|   if (flush_relay_log_info(&mi->rli)) | |
|     ret= 1; | |
|   mysql_cond_broadcast(&mi->data_cond); | |
|   mysql_mutex_unlock(&mi->rli.data_lock); | |
| 
 | |
| err: | |
|   mi->unlock_slave_threads(); | |
|   if (ret == FALSE) | |
|     my_ok(thd); | |
|   DBUG_RETURN(ret); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a RESET MASTER statement. | |
|  | |
|   @param thd Pointer to THD object of the client thread executing the | |
|   statement. | |
|  | |
|   @retval 0 success | |
|   @retval 1 error | |
| */ | |
| int reset_master(THD* thd, rpl_gtid *init_state, uint32 init_state_len) | |
| { | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_message(ER_FLUSH_MASTER_BINLOG_CLOSED, | |
|                ER(ER_FLUSH_MASTER_BINLOG_CLOSED), MYF(ME_BELL+ME_WAITTANG)); | |
|     return 1; | |
|   } | |
| 
 | |
|   if (mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len)) | |
|     return 1; | |
|   RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */)); | |
|   return 0; | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a SHOW BINLOG EVENTS statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE failure | |
| */ | |
| bool mysql_show_binlog_events(THD* thd) | |
| { | |
|   Protocol *protocol= thd->protocol; | |
|   List<Item> field_list; | |
|   const char *errmsg = 0; | |
|   bool ret = TRUE; | |
|   IO_CACHE log; | |
|   File file = -1; | |
|   MYSQL_BIN_LOG *binary_log= NULL; | |
|   int old_max_allowed_packet= thd->variables.max_allowed_packet; | |
|   Master_info *mi= 0; | |
|   LOG_INFO linfo; | |
|   DBUG_ENTER("mysql_show_binlog_events"); | |
| 
 | |
|   Log_event::init_show_field_list(&field_list); | |
|   if (protocol->send_result_set_metadata(&field_list, | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) | |
|     DBUG_RETURN(TRUE); | |
| 
 | |
|   DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS || | |
|               thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS); | |
| 
 | |
|   /* select which binary log to use: binlog or relay */ | |
|   if ( thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS ) | |
|   { | |
|     /* | |
|       Wait for handlers to insert any pending information | |
|       into the binlog.  For e.g. ndb which updates the binlog asynchronously | |
|       this is needed so that the uses sees all its own commands in the binlog | |
|     */ | |
|     ha_binlog_wait(thd); | |
| 
 | |
|     binary_log= &mysql_bin_log; | |
|   } | |
|   else  /* showing relay log contents */ | |
|   { | |
|     if (!(mi= get_master_info(&thd->variables.default_master_connection, | |
|                               Sql_condition::WARN_LEVEL_ERROR))) | |
|     { | |
|       DBUG_RETURN(TRUE); | |
|     } | |
|     binary_log= &(mi->rli.relay_log); | |
|   } | |
| 
 | |
|   Format_description_log_event *description_event= new | |
|     Format_description_log_event(3); /* MySQL 4.0 by default */ | |
| 
 | |
|   if (binary_log->is_open()) | |
|   { | |
|     LEX_MASTER_INFO *lex_mi= &thd->lex->mi; | |
|     SELECT_LEX_UNIT *unit= &thd->lex->unit; | |
|     ha_rows event_count, limit_start, limit_end; | |
|     my_off_t pos = MY_MAX(BIN_LOG_HEADER_SIZE, lex_mi->pos); // user-friendly | |
|     char search_file_name[FN_REFLEN], *name; | |
|     const char *log_file_name = lex_mi->log_file_name; | |
|     mysql_mutex_t *log_lock = binary_log->get_log_lock(); | |
|     Log_event* ev; | |
| 
 | |
|     if (mi) | |
|     { | |
|       /* We can unlock the mutex as we have a lock on the file */ | |
|       mi->release(); | |
|       mi= 0; | |
|     } | |
| 
 | |
|     unit->set_limit(thd->lex->current_select); | |
|     limit_start= unit->offset_limit_cnt; | |
|     limit_end= unit->select_limit_cnt; | |
| 
 | |
|     name= search_file_name; | |
|     if (log_file_name) | |
|       binary_log->make_log_name(search_file_name, log_file_name); | |
|     else | |
|       name=0;					// Find first log | |
|  | |
|     linfo.index_file_offset = 0; | |
| 
 | |
|     if (binary_log->find_log_pos(&linfo, name, 1)) | |
|     { | |
|       errmsg = "Could not find target log"; | |
|       goto err; | |
|     } | |
| 
 | |
|     /* These locks is here to enable syncronization with log_in_use() */ | |
|     mysql_mutex_lock(&LOCK_thread_count); | |
|     thd->current_linfo = &linfo; | |
|     mysql_mutex_unlock(&LOCK_thread_count); | |
| 
 | |
|     if ((file=open_binlog(&log, linfo.log_file_name, &errmsg)) < 0) | |
|       goto err; | |
| 
 | |
|     /* | |
|       to account binlog event header size | |
|     */ | |
|     thd->variables.max_allowed_packet += MAX_LOG_EVENT_HEADER; | |
| 
 | |
|     mysql_mutex_lock(log_lock); | |
| 
 | |
|     /* | |
|       open_binlog() sought to position 4. | |
|       Read the first event in case it's a Format_description_log_event, to | |
|       know the format. If there's no such event, we are 3.23 or 4.x. This | |
|       code, like before, can't read 3.23 binlogs. | |
|       This code will fail on a mixed relay log (one which has Format_desc then | |
|       Rotate then Format_desc). | |
|     */ | |
|     ev= Log_event::read_log_event(&log, (mysql_mutex_t*)0, description_event, | |
|                                    opt_master_verify_checksum); | |
|     if (ev) | |
|     { | |
|       if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) | |
|       { | |
|         delete description_event; | |
|         description_event= (Format_description_log_event*) ev; | |
|       } | |
|       else | |
|         delete ev; | |
|     } | |
| 
 | |
|     my_b_seek(&log, pos); | |
| 
 | |
|     if (!description_event->is_valid()) | |
|     { | |
|       errmsg="Invalid Format_description event; could be out of memory"; | |
|       goto err; | |
|     } | |
| 
 | |
|     for (event_count = 0; | |
|          (ev = Log_event::read_log_event(&log, (mysql_mutex_t*) 0, | |
|                                          description_event, | |
|                                          opt_master_verify_checksum)); ) | |
|     { | |
|       if (ev->get_type_code() == FORMAT_DESCRIPTION_EVENT) | |
|         description_event->checksum_alg= ev->checksum_alg; | |
| 
 | |
|       if (event_count >= limit_start && | |
| 	  ev->net_send(thd, protocol, linfo.log_file_name, pos)) | |
|       { | |
| 	errmsg = "Net error"; | |
| 	delete ev; | |
|         mysql_mutex_unlock(log_lock); | |
| 	goto err; | |
|       } | |
| 
 | |
|       pos = my_b_tell(&log); | |
|       delete ev; | |
| 
 | |
|       if (++event_count >= limit_end) | |
| 	break; | |
|     } | |
| 
 | |
|     if (event_count < limit_end && log.error) | |
|     { | |
|       errmsg = "Wrong offset or I/O error"; | |
|       mysql_mutex_unlock(log_lock); | |
|       goto err; | |
|     } | |
| 
 | |
|     mysql_mutex_unlock(log_lock); | |
|   } | |
|   else if (mi) | |
|     mi->release(); | |
| 
 | |
|   // Check that linfo is still on the function scope. | |
|   DEBUG_SYNC(thd, "after_show_binlog_events"); | |
| 
 | |
|   ret= FALSE; | |
| 
 | |
| err: | |
|   delete description_event; | |
|   if (file >= 0) | |
|   { | |
|     end_io_cache(&log); | |
|     mysql_file_close(file, MYF(MY_WME)); | |
|   } | |
| 
 | |
|   if (errmsg) | |
|     my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), | |
|              "SHOW BINLOG EVENTS", errmsg); | |
|   else | |
|     my_eof(thd); | |
| 
 | |
|   /* These locks is here to enable syncronization with log_in_use() */ | |
|   mysql_mutex_lock(&LOCK_thread_count); | |
|   thd->current_linfo= 0; | |
|   mysql_mutex_unlock(&LOCK_thread_count); | |
|   thd->variables.max_allowed_packet= old_max_allowed_packet; | |
|   DBUG_RETURN(ret); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a SHOW MASTER STATUS statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE failure | |
| */ | |
| bool show_binlog_info(THD* thd) | |
| { | |
|   Protocol *protocol= thd->protocol; | |
|   DBUG_ENTER("show_binlog_info"); | |
|   List<Item> field_list; | |
|   field_list.push_back(new Item_empty_string("File", FN_REFLEN)); | |
|   field_list.push_back(new Item_return_int("Position",20, | |
| 					   MYSQL_TYPE_LONGLONG)); | |
|   field_list.push_back(new Item_empty_string("Binlog_Do_DB",255)); | |
|   field_list.push_back(new Item_empty_string("Binlog_Ignore_DB",255)); | |
| 
 | |
|   if (protocol->send_result_set_metadata(&field_list, | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) | |
|     DBUG_RETURN(TRUE); | |
|   protocol->prepare_for_resend(); | |
| 
 | |
|   if (mysql_bin_log.is_open()) | |
|   { | |
|     LOG_INFO li; | |
|     mysql_bin_log.get_current_log(&li); | |
|     int dir_len = dirname_length(li.log_file_name); | |
|     protocol->store(li.log_file_name + dir_len, &my_charset_bin); | |
|     protocol->store((ulonglong) li.pos); | |
|     protocol->store(binlog_filter->get_do_db()); | |
|     protocol->store(binlog_filter->get_ignore_db()); | |
|     if (protocol->write()) | |
|       DBUG_RETURN(TRUE); | |
|   } | |
|   my_eof(thd); | |
|   DBUG_RETURN(FALSE); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|   Execute a SHOW BINARY LOGS statement. | |
|  | |
|   @param thd Pointer to THD object for the client thread executing the | |
|   statement. | |
|  | |
|   @retval FALSE success | |
|   @retval TRUE failure | |
| */ | |
| bool show_binlogs(THD* thd) | |
| { | |
|   IO_CACHE *index_file; | |
|   LOG_INFO cur; | |
|   File file; | |
|   char fname[FN_REFLEN]; | |
|   List<Item> field_list; | |
|   uint length; | |
|   int cur_dir_len; | |
|   Protocol *protocol= thd->protocol; | |
|   DBUG_ENTER("show_binlogs"); | |
| 
 | |
|   if (!mysql_bin_log.is_open()) | |
|   { | |
|     my_error(ER_NO_BINARY_LOGGING, MYF(0)); | |
|     DBUG_RETURN(TRUE); | |
|   } | |
| 
 | |
|   field_list.push_back(new Item_empty_string("Log_name", 255)); | |
|   field_list.push_back(new Item_return_int("File_size", 20, | |
|                                            MYSQL_TYPE_LONGLONG)); | |
|   if (protocol->send_result_set_metadata(&field_list, | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) | |
|     DBUG_RETURN(TRUE); | |
|    | |
|   mysql_mutex_lock(mysql_bin_log.get_log_lock()); | |
|   mysql_bin_log.lock_index(); | |
|   index_file=mysql_bin_log.get_index_file(); | |
|    | |
|   mysql_bin_log.raw_get_current_log(&cur); // dont take mutex | |
|   mysql_mutex_unlock(mysql_bin_log.get_log_lock()); // lockdep, OK | |
|    | |
|   cur_dir_len= dirname_length(cur.log_file_name); | |
| 
 | |
|   reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0); | |
| 
 | |
|   /* The file ends with EOF or empty line */ | |
|   while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1) | |
|   { | |
|     int dir_len; | |
|     ulonglong file_length= 0;                   // Length if open fails | |
|     fname[--length] = '\0';                     // remove the newline | |
|  | |
|     protocol->prepare_for_resend(); | |
|     dir_len= dirname_length(fname); | |
|     length-= dir_len; | |
|     protocol->store(fname + dir_len, length, &my_charset_bin); | |
| 
 | |
|     if (!(strncmp(fname+dir_len, cur.log_file_name+cur_dir_len, length))) | |
|       file_length= cur.pos;  /* The active log, use the active position */ | |
|     else | |
|     { | |
|       /* this is an old log, open it and find the size */ | |
|       if ((file= mysql_file_open(key_file_binlog, | |
|                                  fname, O_RDONLY | O_SHARE | O_BINARY, | |
|                                  MYF(0))) >= 0) | |
|       { | |
|         file_length= (ulonglong) mysql_file_seek(file, 0L, MY_SEEK_END, MYF(0)); | |
|         mysql_file_close(file, MYF(0)); | |
|       } | |
|     } | |
|     protocol->store(file_length); | |
|     if (protocol->write()) | |
|       goto err; | |
|   } | |
|   if(index_file->error == -1) | |
|     goto err; | |
|   mysql_bin_log.unlock_index(); | |
|   my_eof(thd); | |
|   DBUG_RETURN(FALSE); | |
| 
 | |
| err: | |
|   mysql_bin_log.unlock_index(); | |
|   DBUG_RETURN(TRUE); | |
| } | |
| 
 | |
| /** | |
|    Load data's io cache specific hook to be executed | |
|    before a chunk of data is being read into the cache's buffer | |
|    The fuction instantianates and writes into the binlog | |
|    replication events along LOAD DATA processing. | |
|     | |
|    @param file  pointer to io-cache | |
|    @retval 0 success | |
|    @retval 1 failure | |
| */ | |
| int log_loaded_block(IO_CACHE* file) | |
| { | |
|   DBUG_ENTER("log_loaded_block"); | |
|   LOAD_FILE_INFO *lf_info; | |
|   uint block_len; | |
|   /* buffer contains position where we started last read */ | |
|   uchar* buffer= (uchar*) my_b_get_buffer_start(file); | |
|   uint max_event_size= current_thd->variables.max_allowed_packet; | |
|   lf_info= (LOAD_FILE_INFO*) file->arg; | |
|   if (lf_info->thd->is_current_stmt_binlog_format_row()) | |
|     DBUG_RETURN(0); | |
|   if (lf_info->last_pos_in_file != HA_POS_ERROR && | |
|       lf_info->last_pos_in_file >= my_b_get_pos_in_file(file)) | |
|     DBUG_RETURN(0); | |
|    | |
|   for (block_len= (uint) (my_b_get_bytes_in_buffer(file)); block_len > 0; | |
|        buffer += MY_MIN(block_len, max_event_size), | |
|        block_len -= MY_MIN(block_len, max_event_size)) | |
|   { | |
|     lf_info->last_pos_in_file= my_b_get_pos_in_file(file); | |
|     if (lf_info->wrote_create_file) | |
|     { | |
|       Append_block_log_event a(lf_info->thd, lf_info->thd->db, buffer, | |
|                                MY_MIN(block_len, max_event_size), | |
|                                lf_info->log_delayed); | |
|       if (mysql_bin_log.write(&a)) | |
|         DBUG_RETURN(1); | |
|     } | |
|     else | |
|     { | |
|       Begin_load_query_log_event b(lf_info->thd, lf_info->thd->db, | |
|                                    buffer, | |
|                                    MY_MIN(block_len, max_event_size), | |
|                                    lf_info->log_delayed); | |
|       if (mysql_bin_log.write(&b)) | |
|         DBUG_RETURN(1); | |
|       lf_info->wrote_create_file= 1; | |
|     } | |
|   } | |
|   DBUG_RETURN(0); | |
| } | |
| 
 | |
| 
 | |
| /** | |
|    Initialise the slave replication state from the mysql.gtid_slave_pos table. | |
|  | |
|    This is called each time an SQL thread starts, but the data is only actually | |
|    loaded on the first call. | |
|  | |
|    The slave state is the last GTID applied on the slave within each | |
|    replication domain. | |
|  | |
|    To avoid row lock contention, there are multiple rows for each domain_id. | |
|    The one containing the current slave state is the one with the maximal | |
|    sub_id value, within each domain_id. | |
|  | |
|     CREATE TABLE mysql.gtid_slave_pos ( | |
|       domain_id INT UNSIGNED NOT NULL, | |
|       sub_id BIGINT UNSIGNED NOT NULL, | |
|       server_id INT UNSIGNED NOT NULL, | |
|       seq_no BIGINT UNSIGNED NOT NULL, | |
|       PRIMARY KEY (domain_id, sub_id)) | |
| */ | |
| 
 | |
| void | |
| rpl_init_gtid_slave_state() | |
| { | |
|   rpl_global_gtid_slave_state= new rpl_slave_state; | |
| } | |
| 
 | |
| 
 | |
| void | |
| rpl_deinit_gtid_slave_state() | |
| { | |
|   delete rpl_global_gtid_slave_state; | |
| } | |
| 
 | |
| 
 | |
| void | |
| rpl_init_gtid_waiting() | |
| { | |
|   rpl_global_gtid_waiting.init(); | |
| } | |
| 
 | |
| 
 | |
| void | |
| rpl_deinit_gtid_waiting() | |
| { | |
|   rpl_global_gtid_waiting.destroy(); | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Format the current GTID state as a string, for returning the value of | |
|   @@global.gtid_slave_pos. | |
|  | |
|   If the flag use_binlog is true, then the contents of the binary log (if | |
|   enabled) is merged into the current GTID state (@@global.gtid_current_pos). | |
| */ | |
| int | |
| rpl_append_gtid_state(String *dest, bool use_binlog) | |
| { | |
|   int err; | |
|   rpl_gtid *gtid_list= NULL; | |
|   uint32 num_gtids= 0; | |
| 
 | |
|   if (use_binlog && opt_bin_log && | |
|       (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) | |
|     return err; | |
| 
 | |
|   err= rpl_global_gtid_slave_state->tostring(dest, gtid_list, num_gtids); | |
|   my_free(gtid_list); | |
| 
 | |
|   return err; | |
| } | |
| 
 | |
| 
 | |
| /* | |
|   Load the current GTID position into a slave_connection_state, for use when | |
|   connecting to a master server with GTID. | |
|  | |
|   If the flag use_binlog is true, then the contents of the binary log (if | |
|   enabled) is merged into the current GTID state (master_use_gtid=current_pos). | |
| */ | |
| int | |
| rpl_load_gtid_state(slave_connection_state *state, bool use_binlog) | |
| { | |
|   int err; | |
|   rpl_gtid *gtid_list= NULL; | |
|   uint32 num_gtids= 0; | |
| 
 | |
|   if (use_binlog && opt_bin_log && | |
|       (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) | |
|     return err; | |
| 
 | |
|   err= state->load(rpl_global_gtid_slave_state, gtid_list, num_gtids); | |
|   my_free(gtid_list); | |
| 
 | |
|   return err; | |
| } | |
| 
 | |
| 
 | |
| bool | |
| rpl_gtid_pos_check(THD *thd, char *str, size_t len) | |
| { | |
|   slave_connection_state tmp_slave_state; | |
|   bool gave_conflict_warning= false, gave_missing_warning= false; | |
| 
 | |
|   /* Check that we can parse the supplied string. */ | |
|   if (tmp_slave_state.load(str, len)) | |
|     return true; | |
| 
 | |
|   /* | |
|     Check our own binlog for any of our own transactions that are newer | |
|     than the GTID state the user is requesting. Any such transactions would | |
|     result in an out-of-order binlog, which could break anyone replicating | |
|     with us as master. | |
|  | |
|     So give an error if this is found, requesting the user to do a | |
|     RESET MASTER (to clean up the binlog) if they really want this. | |
|   */ | |
|   if (mysql_bin_log.is_open()) | |
|   { | |
|     rpl_gtid *binlog_gtid_list= NULL; | |
|     uint32 num_binlog_gtids= 0; | |
|     uint32 i; | |
| 
 | |
|     if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list, | |
|                                                 &num_binlog_gtids)) | |
|     { | |
|       my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); | |
|       return true; | |
|     } | |
|     for (i= 0; i < num_binlog_gtids; ++i) | |
|     { | |
|       rpl_gtid *binlog_gtid= &binlog_gtid_list[i]; | |
|       rpl_gtid *slave_gtid; | |
|       if (binlog_gtid->server_id != global_system_variables.server_id) | |
|         continue; | |
|       if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) | |
|       { | |
|         if (opt_gtid_strict_mode) | |
|         { | |
|           my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), | |
|                    binlog_gtid->domain_id, binlog_gtid->domain_id, | |
|                    binlog_gtid->server_id, binlog_gtid->seq_no); | |
|           break; | |
|         } | |
|         else if (!gave_missing_warning) | |
|         { | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, | |
|                               ER_MASTER_GTID_POS_MISSING_DOMAIN, | |
|                               ER(ER_MASTER_GTID_POS_MISSING_DOMAIN), | |
|                               binlog_gtid->domain_id, binlog_gtid->domain_id, | |
|                               binlog_gtid->server_id, binlog_gtid->seq_no); | |
|           gave_missing_warning= true; | |
|         } | |
|       } | |
|       else if (slave_gtid->seq_no < binlog_gtid->seq_no) | |
|       { | |
|         if (opt_gtid_strict_mode) | |
|         { | |
|           my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), | |
|                    slave_gtid->domain_id, slave_gtid->server_id, | |
|                    slave_gtid->seq_no, binlog_gtid->domain_id, | |
|                    binlog_gtid->server_id, binlog_gtid->seq_no); | |
|           break; | |
|         } | |
|         else if (!gave_conflict_warning) | |
|         { | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, | |
|                               ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, | |
|                               ER(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG), | |
|                               slave_gtid->domain_id, slave_gtid->server_id, | |
|                               slave_gtid->seq_no, binlog_gtid->domain_id, | |
|                               binlog_gtid->server_id, binlog_gtid->seq_no); | |
|           gave_conflict_warning= true; | |
|         } | |
|       } | |
|     } | |
|     my_free(binlog_gtid_list); | |
|     if (i != num_binlog_gtids) | |
|       return true; | |
|   } | |
| 
 | |
|   return false; | |
| } | |
| 
 | |
| 
 | |
| bool | |
| rpl_gtid_pos_update(THD *thd, char *str, size_t len) | |
| { | |
|   if (rpl_global_gtid_slave_state->load(thd, str, len, true, true)) | |
|   { | |
|     my_error(ER_FAILED_GTID_STATE_INIT, MYF(0)); | |
|     return true; | |
|   } | |
|   else | |
|     return false; | |
| } | |
| 
 | |
| 
 | |
| #endif /* HAVE_REPLICATION */
 |