1 /* Copyright (c) 2010, 2021, Oracle and/or its affiliates.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software Foundation,
21    51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22 
23 
24 #ifdef HAVE_REPLICATION
25 #include "rpl_master.h"
26 
27 #include "hash.h"                               // HASH
28 #include "m_string.h"                           // strmake
29 #include "auth_common.h"                        // check_global_access
30 #include "binlog.h"                             // mysql_bin_log
31 #include "debug_sync.h"                         // DEBUG_SYNC
32 #include "log.h"                                // sql_print_information
33 #include "mysqld_thd_manager.h"                 // Global_THD_manager
34 #include "rpl_binlog_sender.h"                  // Binlog_sender
35 #include "rpl_filter.h"                         // binlog_filter
36 #include "rpl_handler.h"                        // RUN_HOOK
37 #include "sql_class.h"                          // THD
38 #include "rpl_group_replication.h"              // is_group_replication_running
39 #include "mutex_lock.h"                         // Mutex_lock
40 #include "pfs_file_provider.h"
41 #include "mysql/psi/mysql_file.h"
42 
43 
44 int max_binlog_dump_events = 0; // unlimited
45 my_bool opt_sporadic_binlog_dump_fail = 0;
46 
47 #define SLAVE_LIST_CHUNK 128
48 #define SLAVE_ERRMSG_SIZE (FN_REFLEN+64)
49 HASH slave_list;
50 extern TYPELIB binlog_checksum_typelib;
51 
52 
53 #define get_object(p, obj, msg) \
54 {\
55   uint len; \
56   if (p >= p_end) \
57   { \
58     my_error(ER_MALFORMED_PACKET, MYF(0)); \
59     my_free(si); \
60     return 1; \
61   } \
62   len= (uint)*p++;  \
63   if (p + len > p_end || len >= sizeof(obj)) \
64   {\
65     errmsg= msg;\
66     goto err; \
67   }\
68   strmake(obj,(char*) p,len); \
69   p+= len; \
70 }\
71 
72 extern "C" uint32
slave_list_key(SLAVE_INFO * si,size_t * len,my_bool not_used MY_ATTRIBUTE ((unused)))73 *slave_list_key(SLAVE_INFO* si, size_t *len,
74 		my_bool not_used MY_ATTRIBUTE((unused)))
75 {
76   *len = 4;
77   return &si->server_id;
78 }
79 
slave_info_free(void * s)80 extern "C" void slave_info_free(void *s)
81 {
82   my_free(s);
83 }
84 
85 #ifdef HAVE_PSI_INTERFACE
86 static PSI_mutex_key key_LOCK_slave_list;
87 
88 static PSI_mutex_info all_slave_list_mutexes[]=
89 {
90   { &key_LOCK_slave_list, "LOCK_slave_list", PSI_FLAG_GLOBAL}
91 };
92 
init_all_slave_list_mutexes(void)93 static void init_all_slave_list_mutexes(void)
94 {
95   int count;
96 
97   count= array_elements(all_slave_list_mutexes);
98   mysql_mutex_register("sql", all_slave_list_mutexes, count);
99 }
100 #endif /* HAVE_PSI_INTERFACE */
101 
init_slave_list()102 void init_slave_list()
103 {
104 #ifdef HAVE_PSI_INTERFACE
105   init_all_slave_list_mutexes();
106 #endif
107 
108   my_hash_init(&slave_list, system_charset_info, SLAVE_LIST_CHUNK, 0, 0,
109                (my_hash_get_key) slave_list_key,
110                (my_hash_free_key) slave_info_free, 0,
111                key_memory_SLAVE_INFO);
112   mysql_mutex_init(key_LOCK_slave_list, &LOCK_slave_list, MY_MUTEX_INIT_FAST);
113 }
114 
end_slave_list()115 void end_slave_list()
116 {
117   /* No protection by a mutex needed as we are only called at shutdown */
118   if (my_hash_inited(&slave_list))
119   {
120     my_hash_free(&slave_list);
121     mysql_mutex_destroy(&LOCK_slave_list);
122   }
123 }
124 
125 /**
126   Register slave in 'slave_list' hash table.
127 
128   @return
129     0	ok
130   @return
131     1	Error.   Error message sent to client
132 */
133 
register_slave(THD * thd,uchar * packet,size_t packet_length)134 int register_slave(THD* thd, uchar* packet, size_t packet_length)
135 {
136   int res;
137   SLAVE_INFO *si;
138   uchar *p= packet, *p_end= packet + packet_length;
139   const char *errmsg= "Wrong parameters to function register_slave";
140 
141   if (check_access(thd, REPL_SLAVE_ACL, any_db, NULL, NULL, 0, 0))
142     return 1;
143   if (!(si = (SLAVE_INFO*)my_malloc(key_memory_SLAVE_INFO,
144                                     sizeof(SLAVE_INFO), MYF(MY_WME))))
145     goto err2;
146 
147   /* 4 bytes for the server id */
148   if (p + 4 > p_end)
149   {
150     my_error(ER_MALFORMED_PACKET, MYF(0));
151     my_free(si);
152     return 1;
153   }
154 
155   thd->server_id= si->server_id= uint4korr(p);
156   p+= 4;
157   get_object(p,si->host, "Failed to register slave: too long 'report-host'");
158   get_object(p,si->user, "Failed to register slave: too long 'report-user'");
159   get_object(p,si->password, "Failed to register slave; too long 'report-password'");
160   if (p+10 > p_end)
161     goto err;
162   si->port= uint2korr(p);
163   p += 2;
164   /*
165      We need to by pass the bytes used in the fake rpl_recovery_rank
166      variable. It was removed in patch for BUG#13963. But this would
167      make a server with that patch unable to connect to an old master.
168      See: BUG#49259
169   */
170   p += 4;
171   if (!(si->master_id= uint4korr(p)))
172     si->master_id= server_id;
173   si->thd= thd;
174 
175   mysql_mutex_lock(&LOCK_slave_list);
176   unregister_slave(thd, false, false/*need_lock_slave_list=false*/);
177   res= my_hash_insert(&slave_list, (uchar*) si);
178   mysql_mutex_unlock(&LOCK_slave_list);
179   return res;
180 
181 err:
182   my_free(si);
183   my_message(ER_UNKNOWN_ERROR, errmsg, MYF(0)); /* purecov: inspected */
184 err2:
185   return 1;
186 }
187 
unregister_slave(THD * thd,bool only_mine,bool need_lock_slave_list)188 void unregister_slave(THD* thd, bool only_mine, bool need_lock_slave_list)
189 {
190   if (thd->server_id && my_hash_inited(&slave_list))
191   {
192     if (need_lock_slave_list)
193       mysql_mutex_lock(&LOCK_slave_list);
194     else
195       mysql_mutex_assert_owner(&LOCK_slave_list);
196 
197     SLAVE_INFO* old_si;
198     if ((old_si = (SLAVE_INFO*)my_hash_search(&slave_list,
199                                               (uchar*)&thd->server_id, 4)) &&
200 	(!only_mine || old_si->thd == thd))
201     my_hash_delete(&slave_list, (uchar*)old_si);
202 
203     if (need_lock_slave_list)
204       mysql_mutex_unlock(&LOCK_slave_list);
205   }
206 }
207 
208 
209 /**
210   Execute a SHOW SLAVE HOSTS statement.
211 
212   @param thd Pointer to THD object for the client thread executing the
213   statement.
214 
215   @retval FALSE success
216   @retval TRUE failure
217 */
show_slave_hosts(THD * thd)218 bool show_slave_hosts(THD* thd)
219 {
220   List<Item> field_list;
221   Protocol *protocol= thd->get_protocol();
222   DBUG_ENTER("show_slave_hosts");
223 
224   field_list.push_back(new Item_return_int("Server_id", 10,
225 					   MYSQL_TYPE_LONG));
226   field_list.push_back(new Item_empty_string("Host", 20));
227   if (opt_show_slave_auth_info)
228   {
229     field_list.push_back(new Item_empty_string("User",20));
230     field_list.push_back(new Item_empty_string("Password",20));
231   }
232   field_list.push_back(new Item_return_int("Port", 7, MYSQL_TYPE_LONG));
233   field_list.push_back(new Item_return_int("Master_id", 10,
234 					   MYSQL_TYPE_LONG));
235   field_list.push_back(new Item_empty_string("Slave_UUID", UUID_LENGTH));
236 
237   if (thd->send_result_metadata(&field_list,
238                                 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
239     DBUG_RETURN(TRUE);
240 
241   mysql_mutex_lock(&LOCK_slave_list);
242 
243   for (uint i = 0; i < slave_list.records; ++i)
244   {
245     SLAVE_INFO* si = (SLAVE_INFO*) my_hash_element(&slave_list, i);
246     protocol->start_row();
247     protocol->store((uint32) si->server_id);
248     protocol->store(si->host, &my_charset_bin);
249     if (opt_show_slave_auth_info)
250     {
251       protocol->store(si->user, &my_charset_bin);
252       protocol->store(si->password, &my_charset_bin);
253     }
254     protocol->store((uint32) si->port);
255     protocol->store((uint32) si->master_id);
256 
257     /* get slave's UUID */
258     String slave_uuid;
259     if (get_slave_uuid(si->thd, &slave_uuid))
260       protocol->store(slave_uuid.c_ptr_safe(), &my_charset_bin);
261     if (protocol->end_row())
262     {
263       mysql_mutex_unlock(&LOCK_slave_list);
264       DBUG_RETURN(TRUE);
265     }
266   }
267   mysql_mutex_unlock(&LOCK_slave_list);
268   my_eof(thd);
269   DBUG_RETURN(FALSE);
270 }
271 
272 /**
273   If there are less than BYTES bytes left to read in the packet,
274   report error.
275 */
276 #define CHECK_PACKET_SIZE(BYTES)                                        \
277   do {                                                                  \
278     if (packet_bytes_todo < BYTES)                                      \
279       goto error_malformed_packet;                                      \
280   } while (0)
281 
282 /**
283   Auxiliary macro used to define READ_INT and READ_STRING.
284 
285   Check that there are at least BYTES more bytes to read, then read
286   the bytes using the given DECODER, then advance the reading
287   position.
288 */
289 #define READ(DECODE, BYTES)                                             \
290   do {                                                                  \
291     CHECK_PACKET_SIZE(BYTES);                                           \
292     DECODE;                                                             \
293     packet_position+= BYTES;                                            \
294     packet_bytes_todo-= BYTES;                                          \
295   } while (0)
296 
297 
298 /**
299   Check that there are at least BYTES more bytes to read, then read
300   the bytes and decode them into the given integer VAR, then advance
301   the reading position.
302 */
303 #define READ_INT(VAR, BYTES)                                            \
304   READ(VAR= uint ## BYTES ## korr(packet_position), BYTES)
305 
306 /**
307   Check that there are at least BYTES more bytes to read and that
308   BYTES+1 is not greater than BUFFER_SIZE, then read the bytes into
309   the given variable VAR, then advance the reading position.
310 */
311 #define READ_STRING(VAR, BYTES, BUFFER_SIZE)                            \
312   do {                                                                  \
313     if (BUFFER_SIZE <= BYTES)                                           \
314       goto error_malformed_packet;                                      \
315     READ(memcpy(VAR, packet_position, BYTES), BYTES);                   \
316     VAR[BYTES]= '\0';                                                   \
317   } while (0)
318 
319 
com_binlog_dump(THD * thd,char * packet,size_t packet_length)320 bool com_binlog_dump(THD *thd, char *packet, size_t packet_length)
321 {
322   DBUG_ENTER("com_binlog_dump");
323   ulong pos;
324   ushort flags= 0;
325   const uchar* packet_position= (uchar *) packet;
326   size_t packet_bytes_todo= packet_length;
327 
328   assert(!thd->status_var_aggregated);
329   thd->status_var.com_other++;
330   thd->enable_slow_log= opt_log_slow_admin_statements;
331   if (check_global_access(thd, REPL_SLAVE_ACL))
332     DBUG_RETURN(false);
333 
334   /*
335     4 bytes is too little, but changing the protocol would break
336     compatibility.  This has been fixed in the new protocol. @see
337     com_binlog_dump_gtid().
338   */
339   READ_INT(pos, 4);
340   READ_INT(flags, 2);
341   READ_INT(thd->server_id, 4);
342 
343   DBUG_PRINT("info", ("pos=%lu flags=%d server_id=%d", pos, flags, thd->server_id));
344 
345   kill_zombie_dump_threads(thd);
346 
347   query_logger.general_log_print(thd, thd->get_command(), "Log: '%s'  Pos: %ld",
348                                  packet + 10, (long) pos);
349   mysql_binlog_send(thd, thd->mem_strdup(packet + 10), (my_off_t) pos, NULL, flags);
350 
351   unregister_slave(thd, true, true/*need_lock_slave_list=true*/);
352   /*  fake COM_QUIT -- if we get here, the thread needs to terminate */
353   DBUG_RETURN(true);
354 
355 error_malformed_packet:
356   my_error(ER_MALFORMED_PACKET, MYF(0));
357   DBUG_RETURN(true);
358 }
359 
360 
com_binlog_dump_gtid(THD * thd,char * packet,size_t packet_length)361 bool com_binlog_dump_gtid(THD *thd, char *packet, size_t packet_length)
362 {
363   DBUG_ENTER("com_binlog_dump_gtid");
364   /*
365     Before going GA, we need to make this protocol extensible without
366     breaking compatitibilty. /Alfranio.
367   */
368   ushort flags= 0;
369   uint32 data_size= 0;
370   uint64 pos= 0;
371   char name[FN_REFLEN + 1];
372   uint32 name_size= 0;
373   char* gtid_string= NULL;
374   const uchar* packet_position= (uchar *) packet;
375   size_t packet_bytes_todo= packet_length;
376   Sid_map sid_map(NULL/*no sid_lock because this is a completely local object*/);
377   Gtid_set slave_gtid_executed(&sid_map);
378 
379   assert(!thd->status_var_aggregated);
380   thd->status_var.com_other++;
381   thd->enable_slow_log= opt_log_slow_admin_statements;
382   if (check_global_access(thd, REPL_SLAVE_ACL))
383     DBUG_RETURN(false);
384 
385   READ_INT(flags,2);
386   READ_INT(thd->server_id, 4);
387   READ_INT(name_size, 4);
388   READ_STRING(name, name_size, sizeof(name));
389   READ_INT(pos, 8);
390   DBUG_PRINT("info", ("pos=%llu flags=%d server_id=%d", pos, flags, thd->server_id));
391   READ_INT(data_size, 4);
392   CHECK_PACKET_SIZE(data_size);
393   if (slave_gtid_executed.add_gtid_encoding(packet_position, data_size) !=
394       RETURN_STATUS_OK)
395     DBUG_RETURN(true);
396   slave_gtid_executed.to_string(&gtid_string);
397   DBUG_PRINT("info", ("Slave %d requested to read %s at position %llu gtid set "
398                       "'%s'.", thd->server_id, name, pos, gtid_string));
399 
400   kill_zombie_dump_threads(thd);
401   query_logger.general_log_print(thd, thd->get_command(),
402                                  "Log: '%s' Pos: %llu GTIDs: '%s'",
403                                  name, pos, gtid_string);
404   my_free(gtid_string);
405   mysql_binlog_send(thd, name, (my_off_t) pos, &slave_gtid_executed, flags);
406 
407   unregister_slave(thd, true, true/*need_lock_slave_list=true*/);
408   /*  fake COM_QUIT -- if we get here, the thread needs to terminate */
409   DBUG_RETURN(true);
410 
411 error_malformed_packet:
412   my_error(ER_MALFORMED_PACKET, MYF(0));
413   DBUG_RETURN(true);
414 }
415 
mysql_binlog_send(THD * thd,char * log_ident,my_off_t pos,Gtid_set * slave_gtid_executed,uint32 flags)416 void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
417                        Gtid_set* slave_gtid_executed, uint32 flags)
418 {
419   Binlog_sender sender(thd, log_ident, pos, slave_gtid_executed, flags);
420 
421   sender.run();
422 }
423 
424 /**
425   An auxiliary function extracts slave UUID.
426 
427   @param[in]    thd  THD to access a user variable
428   @param[out]   value String to return UUID value.
429 
430   @return       if success value is returned else NULL is returned.
431 */
get_slave_uuid(THD * thd,String * value)432 String *get_slave_uuid(THD *thd, String *value)
433 {
434   uchar name[]= "slave_uuid";
435 
436   if (value == NULL)
437     return NULL;
438 
439   /* Protects thd->user_vars. */
440   Mutex_lock lock_guard(&thd->LOCK_thd_data);
441 
442   user_var_entry *entry=
443     (user_var_entry*) my_hash_search(&thd->user_vars, name, sizeof(name)-1);
444   if (entry && entry->length() > 0)
445   {
446     value->copy(entry->ptr(), entry->length(), NULL);
447     return value;
448   }
449   return NULL;
450 }
451 
452 /**
453   Callback function used by kill_zombie_dump_threads() function to
454   to find zombie dump thread from the thd list.
455 
456   @note It acquires LOCK_thd_data mutex when it finds matching thd.
457   It is the responsibility of the caller to release this mutex.
458 */
459 class Find_zombie_dump_thread : public Find_THD_Impl
460 {
461 public:
Find_zombie_dump_thread(String value)462   Find_zombie_dump_thread(String value): m_slave_uuid(value) {}
operator ()(THD * thd)463   virtual bool operator()(THD *thd)
464   {
465     THD *cur_thd= current_thd;
466     if (thd != cur_thd && (thd->get_command() == COM_BINLOG_DUMP ||
467                                thd->get_command() == COM_BINLOG_DUMP_GTID))
468     {
469       String tmp_uuid;
470       bool is_zombie_thread= false;
471       get_slave_uuid(thd, &tmp_uuid);
472       if (m_slave_uuid.length())
473       {
474         is_zombie_thread= (tmp_uuid.length() &&
475                            !strncmp(m_slave_uuid.c_ptr(),
476                                     tmp_uuid.c_ptr(), UUID_LENGTH));
477       }
478       else
479       {
480         /*
481           Check if it is a 5.5 slave's dump thread i.e., server_id should be
482           same && dump thread should not contain 'UUID'.
483         */
484         is_zombie_thread= ((thd->server_id == cur_thd->server_id) &&
485                            !tmp_uuid.length());
486       }
487       if (is_zombie_thread)
488       {
489         mysql_mutex_lock(&thd->LOCK_thd_data);
490         return true;
491       }
492     }
493     return false;
494   }
495 private:
496   String m_slave_uuid;
497 };
498 
499 /*
500 
501   Kill all Binlog_dump threads which previously talked to the same slave
502   ("same" means with the same UUID(for slave versions >= 5.6) or same server id
503   (for slave versions < 5.6). Indeed, if the slave stops, if the
504   Binlog_dump thread is waiting (mysql_cond_wait) for binlog update, then it
505   will keep existing until a query is written to the binlog. If the master is
506   idle, then this could last long, and if the slave reconnects, we could have 2
507   Binlog_dump threads in SHOW PROCESSLIST, until a query is written to the
508   binlog. To avoid this, when the slave reconnects and sends COM_BINLOG_DUMP,
509   the master kills any existing thread with the slave's UUID/server id (if this id is
510   not zero; it will be true for real slaves, but false for mysqlbinlog when it
511   sends COM_BINLOG_DUMP to get a remote binlog dump).
512 
513   SYNOPSIS
514     kill_zombie_dump_threads()
515     @param thd newly connected dump thread object
516 
517 */
518 
kill_zombie_dump_threads(THD * thd)519 void kill_zombie_dump_threads(THD *thd)
520 {
521   String slave_uuid;
522   get_slave_uuid(thd, &slave_uuid);
523   if (slave_uuid.length() == 0 && thd->server_id == 0)
524     return;
525 
526   Find_zombie_dump_thread find_zombie_dump_thread(slave_uuid);
527   THD *tmp= Global_THD_manager::get_instance()->
528                                 find_thd(&find_zombie_dump_thread);
529   if (tmp)
530   {
531     /*
532       Here we do not call kill_one_thread() as
533       it will be slow because it will iterate through the list
534       again. We just to do kill the thread ourselves.
535     */
536     if (log_warnings > 1)
537     {
538       if (slave_uuid.length())
539       {
540         sql_print_information("While initializing dump thread for slave with "
541                               "UUID <%s>, found a zombie dump thread with the "
542                               "same UUID. Master is killing the zombie dump "
543                               "thread(%u).", slave_uuid.c_ptr(),
544                               tmp->thread_id());
545       }
546       else
547       {
548         sql_print_information("While initializing dump thread for slave with "
549                               "server_id <%u>, found a zombie dump thread with the "
550                               "same server_id. Master is killing the zombie dump "
551                               "thread(%u).", thd->server_id,
552                               tmp->thread_id());
553       }
554     }
555     tmp->duplicate_slave_id= true;
556     tmp->awake(THD::KILL_QUERY);
557     mysql_mutex_unlock(&tmp->LOCK_thd_data);
558   }
559 }
560 
561 /**
562   Execute a RESET MASTER statement.
563 
564   @param thd Pointer to THD object of the client thread executing the
565   statement.
566 
567   @retval false success
568   @retval true error
569 */
reset_master(THD * thd)570 bool reset_master(THD* thd)
571 {
572   bool ret= false;
573 
574   /*
575     RESET MASTER command should ignore 'read-only' and 'super_read_only'
576     options so that it can update 'mysql.gtid_executed' replication repository
577     table.
578 
579     Please note that skip_readonly_check flag should be set even when binary log
580     is not enabled, as RESET MASTER command will clear 'gtid_executed' table.
581   */
582   thd->set_skip_readonly_check();
583   if (is_group_replication_running())
584   {
585     my_error(ER_CANT_RESET_MASTER, MYF(0), "Group Replication is running");
586     return true;
587   }
588 
589   if (mysql_bin_log.is_open())
590   {
591     /*
592       mysql_bin_log.reset_logs will delete the binary logs *and* clear
593       gtid_state.  It is important to do both these operations from
594       within reset_logs, since the operations can then use the same
595       lock.  I.e., if we would remove the call to gtid_state->clear
596       from reset_logs and call gtid_state->clear explicitly from this
597       function instead, it would be possible for a concurrent thread
598       to commit between the point where the binary log was removed and
599       the point where the gtid_executed table is cleared. This would
600       lead to an inconsistent state.
601     */
602     ret= mysql_bin_log.reset_logs(thd);
603   }
604   else
605   {
606     global_sid_lock->wrlock();
607     ret= (gtid_state->clear(thd) != 0);
608     global_sid_lock->unlock();
609   }
610 
611   /*
612     Only run after_reset_master hook, when all reset operations preceding this
613     have succeeded.
614   */
615   if (!ret)
616     (void) RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */));
617   return ret;
618  }
619 
620 
621 /**
622   Execute a SHOW MASTER STATUS statement.
623 
624   @param thd Pointer to THD object for the client thread executing the
625   statement.
626 
627   @retval false success
628   @retval true failure
629 */
show_master_status(THD * thd)630 bool show_master_status(THD* thd)
631 {
632   Protocol *protocol= thd->get_protocol();
633   char* gtid_set_buffer= NULL;
634   int gtid_set_size= 0;
635   List<Item> field_list;
636 
637   DBUG_ENTER("show_binlog_info");
638 
639   global_sid_lock->wrlock();
640   const Gtid_set* gtid_set= gtid_state->get_executed_gtids();
641   if ((gtid_set_size= gtid_set->to_string(&gtid_set_buffer)) < 0)
642   {
643     global_sid_lock->unlock();
644     my_eof(thd);
645     my_free(gtid_set_buffer);
646     DBUG_RETURN(true);
647   }
648   global_sid_lock->unlock();
649 
650   field_list.push_back(new Item_empty_string("File", FN_REFLEN));
651   field_list.push_back(new Item_return_int("Position",20,
652 					   MYSQL_TYPE_LONGLONG));
653   field_list.push_back(new Item_empty_string("Binlog_Do_DB",255));
654   field_list.push_back(new Item_empty_string("Binlog_Ignore_DB",255));
655   field_list.push_back(new Item_empty_string("Executed_Gtid_Set",
656                                              gtid_set_size));
657 
658   if (thd->send_result_metadata(&field_list,
659                                 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
660   {
661     my_free(gtid_set_buffer);
662     DBUG_RETURN(true);
663   }
664   protocol->start_row();
665 
666   if (mysql_bin_log.is_open())
667   {
668     LOG_INFO li;
669     mysql_bin_log.get_current_log(&li);
670     size_t dir_len = dirname_length(li.log_file_name);
671     protocol->store(li.log_file_name + dir_len, &my_charset_bin);
672     protocol->store((ulonglong) li.pos);
673     store(protocol, binlog_filter->get_do_db());
674     store(protocol, binlog_filter->get_ignore_db());
675     protocol->store(gtid_set_buffer, &my_charset_bin);
676     if (protocol->end_row())
677     {
678       my_free(gtid_set_buffer);
679       DBUG_RETURN(true);
680     }
681   }
682   my_eof(thd);
683   my_free(gtid_set_buffer);
684   DBUG_RETURN(false);
685 }
686 
687 
688 /**
689   Execute a SHOW BINARY LOGS statement.
690 
691   @param thd Pointer to THD object for the client thread executing the
692   statement.
693 
694   @retval FALSE success
695   @retval TRUE failure
696 */
show_binlogs(THD * thd)697 bool show_binlogs(THD* thd)
698 {
699   IO_CACHE *index_file;
700   LOG_INFO cur;
701   File file;
702   char fname[FN_REFLEN];
703   List<Item> field_list;
704   size_t length;
705   size_t cur_dir_len;
706   Protocol *protocol= thd->get_protocol();
707   DBUG_ENTER("show_binlogs");
708 
709   if (!mysql_bin_log.is_open())
710   {
711     my_error(ER_NO_BINARY_LOGGING, MYF(0));
712     DBUG_RETURN(TRUE);
713   }
714 
715   field_list.push_back(new Item_empty_string("Log_name", 255));
716   field_list.push_back(new Item_return_int("File_size", 20,
717                                            MYSQL_TYPE_LONGLONG));
718     if (thd->send_result_metadata(&field_list, Protocol::SEND_NUM_ROWS |
719                                     Protocol::SEND_EOF))
720     DBUG_RETURN(TRUE);
721 
722   mysql_mutex_lock(mysql_bin_log.get_log_lock());
723   DEBUG_SYNC(thd, "show_binlogs_after_lock_log_before_lock_index");
724   mysql_bin_log.lock_index();
725   index_file=mysql_bin_log.get_index_file();
726 
727   mysql_bin_log.raw_get_current_log(&cur); // dont take mutex
728   mysql_mutex_unlock(mysql_bin_log.get_log_lock()); // lockdep, OK
729 
730   cur_dir_len= dirname_length(cur.log_file_name);
731 
732   if (reinit_io_cache(index_file, READ_CACHE, (my_off_t)0, 0, 0))
733     goto err;
734 
735   /* The file ends with EOF or empty line */
736   while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1)
737   {
738     size_t dir_len;
739     ulonglong file_length= 0;                   // Length if open fails
740     fname[--length] = '\0';                     // remove the newline
741 
742     protocol->start_row();
743     dir_len= dirname_length(fname);
744     length-= dir_len;
745     protocol->store(fname + dir_len, length, &my_charset_bin);
746 
747     if (!(strncmp(fname+dir_len, cur.log_file_name+cur_dir_len, length)))
748       file_length= cur.pos;  /* The active log, use the active position */
749     else
750     {
751       /* this is an old log, open it and find the size */
752       if ((file= mysql_file_open(key_file_binlog,
753                                  fname, O_RDONLY | O_SHARE | O_BINARY,
754                                  MYF(0))) >= 0)
755       {
756         file_length= (ulonglong) mysql_file_seek(file, 0L, MY_SEEK_END, MYF(0));
757         mysql_file_close(file, MYF(0));
758       }
759     }
760     protocol->store(file_length);
761     if (protocol->end_row())
762     {
763       DBUG_PRINT("info", ("stopping dump thread because protocol->write failed at line %d", __LINE__));
764       goto err;
765     }
766   }
767   if(index_file->error == -1)
768     goto err;
769   mysql_bin_log.unlock_index();
770   my_eof(thd);
771   DBUG_RETURN(FALSE);
772 
773 err:
774   mysql_bin_log.unlock_index();
775   DBUG_RETURN(TRUE);
776 }
777 
778 #endif /* HAVE_REPLICATION */
779