#At file:///media/sdb2/hezx/work/mysql/bzrwork/rap/5.1-rep%2B2/ based on revid:li-bing.song@stripped
3185 He Zhenxing 2010-04-26
WL#3662 Refactoring: Replication Modules
Splitting replication code into libraries for binlog, slave
and master.
- add binlog.[cc|h], move BINLOG code from log.cc to binlog.[cc|h]
- split sql_repl.[cc|h] into sql_master.[cc|h] and sql_slave.[cc|h]
- add mysql_show_relaylog_events
- modified Makefile.am/CMakeLists.txt to build the libraries
@ libmysqld/CMakeLists.txt
Add binlog.cc
@ libmysqld/Makefile.am
Add binlog.cc
@ sql/Makefile.am
Add binlog, master, slave libraries
@ sql/log.cc
Move binlog related code to binlog.cc
@ sql/log.h
Move binlog related code to binlog.cc
@ sql/sql_parse.cc
Add function mysql_show_relaylog_events
@ sql/sql_repl.cc
Split sql_repl.cc into sql_master.cc and sql_slave.cc
@ sql/sql_repl.h
Split sql_repl.h into sql_master.h and sql_slave.h
D sql/sql_repl.cc
A sql/binlog.cc
A sql/binlog.h
A sql/sql_master.cc
A sql/sql_master.h
A sql/sql_slave.cc
A sql/sql_slave.h
M libmysqld/CMakeLists.txt
M libmysqld/Makefile.am
M sql/CMakeLists.txt
M sql/Makefile.am
M sql/log.cc
M sql/log.h
M sql/slave.h
M sql/sql_class.h
M sql/sql_db.cc
M sql/sql_parse.cc
M sql/sql_repl.h
=== modified file 'libmysqld/CMakeLists.txt'
--- a/libmysqld/CMakeLists.txt 2009-11-06 16:35:04 +0000
+++ b/libmysqld/CMakeLists.txt 2010-04-26 05:01:18 +0000
@@ -141,7 +141,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libm
../sql/time.cc ../sql/tztime.cc ../sql/uniques.cc ../sql/unireg.cc
../sql/partition_info.cc ../sql/sql_connect.cc
../sql/scheduler.cc ../sql/event_parse_data.cc
- ../sql/rpl_handler.cc
+ ../sql/rpl_handler.cc ../sql/binlog.cc
${GEN_SOURCES}
${LIB_SOURCES})
=== modified file 'libmysqld/Makefile.am'
--- a/libmysqld/Makefile.am 2009-12-15 20:21:00 +0000
+++ b/libmysqld/Makefile.am 2010-04-26 05:01:18 +0000
@@ -78,7 +78,7 @@ sqlsources = derror.cc field.cc field_co
sql_tablespace.cc \
rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc \
- rpl_handler.cc
+ rpl_handler.cc binlog.cc
libmysqld_int_a_SOURCES= $(libmysqld_sources)
nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt 2009-11-06 16:35:04 +0000
+++ b/sql/CMakeLists.txt 2010-04-26 05:01:18 +0000
@@ -87,6 +87,14 @@ SET (SQL_SOURCE
${PROJECT_SOURCE_DIR}/sql/lex_hash.h)
ADD_LIBRARY(sql ${SQL_SOURCE})
+SET (BINLOG_SOURCE log_event.cc log_event_old.cc rpl_tblmap.cc binlog.cc sql_binlog.cc
+ rpl_filter.cc rpl_record.cc rpl_record_old.cc rpl_utility.cc)
+ADD_LIBRARY(binlog ${BINLOG_SOURCE})
+SET (MASTER_SOURCE sql_master.cc rpl_injector.cc repl_failsafe.cc)
+ADD_LIBRARY(master ${MASTER_SOURCE})
+SET (SLAVE_SOURCE sql_slave.cc slave.cc rpl_mi.cc rpl_rli.cc rpl_reporting.cc)
+ADD_LIBRARY(slave ${SLAVE_SOURCE})
+
IF (NOT EXISTS cmake_dummy.cc)
FILE (WRITE cmake_dummy.cc "")
ENDIF (NOT EXISTS cmake_dummy.cc)
@@ -95,7 +103,8 @@ ADD_EXECUTABLE(mysqld cmake_dummy.cc)
SET_TARGET_PROPERTIES(mysqld PROPERTIES OUTPUT_NAME mysqld${MYSQLD_EXE_SUFFIX})
SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE)
-SET (MYSQLD_CORE_LIBS mysys zlib dbug strings yassl taocrypt vio regex sql)
+SET (MYSQLD_CORE_LIBS mysys zlib dbug strings yassl taocrypt vio regex
+ binlog master slave sql)
TARGET_LINK_LIBRARIES(mysqld ${MYSQLD_CORE_LIBS} ${MYSQLD_STATIC_ENGINE_LIBS})
TARGET_LINK_LIBRARIES(mysqld ws2_32.lib)
=== modified file 'sql/Makefile.am'
--- a/sql/Makefile.am 2009-11-06 16:35:04 +0000
+++ b/sql/Makefile.am 2010-04-26 05:01:18 +0000
@@ -32,14 +32,18 @@ bin_PROGRAMS = mysql_tzinfo_to_sql
noinst_LTLIBRARIES= libndb.la \
udf_example.la
+pkglib_LTLIBRARIES= libbinlog.la libmaster.la libslave.la
+
SUPPORTING_LIBS = $(top_builddir)/vio/libvio.a \
$(top_builddir)/mysys/libmysys.a \
$(top_builddir)/dbug/libdbug.a \
$(top_builddir)/regex/libregex.a \
$(top_builddir)/strings/libmystrings.a
-mysqld_DEPENDENCIES= @mysql_plugin_libs@ $(SUPPORTING_LIBS) libndb.la
+mysqld_DEPENDENCIES= @mysql_plugin_libs@ $(SUPPORTING_LIBS) libndb.la \
+ libbinlog.la libmaster.la libslave.la
LDADD = $(SUPPORTING_LIBS) @ZLIB_LIBS@ @NDB_SCI_LIBS@
mysqld_LDADD = libndb.la \
+ libbinlog.la libmaster.la libslave.la \
@MYSQLD_EXTRA_LDFLAGS@ \
@pstack_libs@ \
@mysql_plugin_libs@ \
@@ -78,7 +82,8 @@ noinst_HEADERS = item.h item_func.h item
event_data_objects.h event_scheduler.h \
sql_partition.h partition_info.h partition_element.h \
contributors.h sql_servers.h \
- rpl_handler.h replication.h
+ rpl_handler.h replication.h \
+ binlog.h sql_master.h sql_slave.h rpl_plugin.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -99,8 +104,6 @@ mysqld_SOURCES = sql_lex.cc sql_handler.
procedure.cc sql_test.cc \
log.cc init.cc derror.cc sql_acl.cc \
unireg.cc des_key_file.cc \
- log_event.cc rpl_record.cc \
- log_event_old.cc rpl_record_old.cc \
discover.cc time.cc opt_range.cc opt_sum.cc \
records.cc filesort.cc handler.cc \
ha_partition.cc \
@@ -108,12 +111,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.
sql_db.cc sql_table.cc sql_rename.cc sql_crypt.cc \
sql_load.cc mf_iocache.cc field_conv.cc sql_show.cc \
sql_udf.cc sql_analyse.cc sql_analyse.h sql_cache.cc \
- slave.cc sql_repl.cc rpl_filter.cc rpl_tblmap.cc \
- rpl_utility.cc rpl_injector.cc rpl_rli.cc rpl_mi.cc \
- rpl_reporting.cc \
sql_union.cc sql_derived.cc \
sql_client.cc \
- repl_failsafe.h repl_failsafe.cc \
sql_olap.cc sql_view.cc \
gstream.cc spatial.cc sql_help.cc sql_cursor.cc \
tztime.cc my_decimal.cc\
@@ -121,11 +120,20 @@ mysqld_SOURCES = sql_lex.cc sql_handler.
sp_cache.cc parse_file.cc sql_trigger.cc \
event_scheduler.cc event_data_objects.cc \
event_queue.cc event_db_repository.cc events.cc \
- sql_plugin.cc sql_binlog.cc \
+ sql_plugin.cc \
sql_builtin.cc sql_tablespace.cc partition_info.cc \
sql_servers.cc event_parse_data.cc \
rpl_handler.cc
+libbinlog_la_SOURCES = log_event.cc log_event_old.cc \
+ binlog.cc sql_binlog.cc \
+ rpl_filter.cc rpl_tblmap.cc \
+ rpl_record.cc rpl_record_old.cc \
+ rpl_utility.cc
+libmaster_la_SOURCES = sql_master.cc rpl_injector.cc repl_failsafe.cc
+libslave_la_SOURCES = sql_slave.cc slave.cc rpl_mi.cc rpl_rli.cc \
+ rpl_reporting.cc
+
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c
libndb_la_CPPFLAGS= @ndbcluster_includes@
=== added file 'sql/binlog.cc'
--- a/sql/binlog.cc 1970-01-01 00:00:00 +0000
+++ b/sql/binlog.cc 2010-04-26 05:01:18 +0000
@@ -0,0 +1,3835 @@
+/* Copyright (C) 2000-2006 MySQL AB & Sasha
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include "mysql_priv.h"
+#include <my_dir.h>
+#include <mysql/plugin.h>
+#include "binlog.h"
+#include "rpl_filter.h"
+#include "rpl_handler.h"
+
+#define MY_OFF_T_UNDEF (~(my_off_t)0UL)
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+handlerton *binlog_hton;
+
+MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period);
+
+static int binlog_init(void *p);
+static int binlog_close_connection(handlerton *hton, THD *thd);
+static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv);
+static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv);
+static int binlog_commit(handlerton *hton, THD *thd, bool all);
+static int binlog_rollback(handlerton *hton, THD *thd, bool all);
+static int binlog_prepare(handlerton *hton, THD *thd, bool all);
+
+/*
+ Helper classes to store non-transactional and transactional data
+ before copying it to the binary log.
+*/
+class binlog_cache_data
+{
+public:
+ binlog_cache_data(): m_pending(0), before_stmt_pos(MY_OFF_T_UNDEF),
+ incident(FALSE)
+ {
+ cache_log.end_of_file= max_binlog_cache_size;
+ }
+
+ ~binlog_cache_data()
+ {
+ DBUG_ASSERT(empty());
+ close_cached_file(&cache_log);
+ }
+
+ bool empty() const
+ {
+ return pending() == NULL && my_b_tell(&cache_log) == 0;
+ }
+
+ Rows_log_event *pending() const
+ {
+ return m_pending;
+ }
+
+ void set_pending(Rows_log_event *const pending)
+ {
+ m_pending= pending;
+ }
+
+ void set_incident(void)
+ {
+ incident= TRUE;
+ }
+
+ bool has_incident(void)
+ {
+ return(incident);
+ }
+
+ void reset()
+ {
+ truncate(0);
+ incident= FALSE;
+ before_stmt_pos= MY_OFF_T_UNDEF;
+ cache_log.end_of_file= max_binlog_cache_size;
+ DBUG_ASSERT(empty());
+ }
+
+ my_off_t get_byte_position() const
+ {
+ return my_b_tell(&cache_log);
+ }
+
+ my_off_t get_prev_position()
+ {
+ return(before_stmt_pos);
+ }
+
+ void set_prev_position(my_off_t pos)
+ {
+ before_stmt_pos= pos;
+ }
+
+ void restore_prev_position()
+ {
+ truncate(before_stmt_pos);
+ }
+
+ void restore_savepoint(my_off_t pos)
+ {
+ truncate(pos);
+ if (pos < before_stmt_pos)
+ before_stmt_pos= MY_OFF_T_UNDEF;
+ }
+
+ /*
+ Cache to store data before copying it to the binary log.
+ */
+ IO_CACHE cache_log;
+
+private:
+ /*
+ Pending binrows event. This event is the event where the rows are currently
+ written.
+ */
+ Rows_log_event *m_pending;
+
+ /*
+ Binlog position before the start of the current statement.
+ */
+ my_off_t before_stmt_pos;
+
+ /*
+ This indicates that some events did not get into the cache and most likely
+ it is corrupted.
+ */
+ bool incident;
+
+ /*
+ It truncates the cache to a certain position. This includes deleting the
+ pending event.
+ */
+ void truncate(my_off_t pos)
+ {
+ DBUG_PRINT("info", ("truncating to position %lu", (ulong) pos));
+ if (pending())
+ {
+ delete pending();
+ set_pending(0);
+ }
+ reinit_io_cache(&cache_log, WRITE_CACHE, pos, 0, 0);
+ cache_log.end_of_file= max_binlog_cache_size;
+ }
+
+ binlog_cache_data& operator=(const binlog_cache_data& info);
+ binlog_cache_data(const binlog_cache_data& info);
+};
+
+class binlog_cache_mngr {
+public:
+ binlog_cache_mngr() {}
+
+ void reset_cache(binlog_cache_data* cache_data)
+ {
+ cache_data->reset();
+ }
+
+ binlog_cache_data* get_binlog_cache_data(bool is_transactional)
+ {
+ return (is_transactional ? &trx_cache : &stmt_cache);
+ }
+
+ IO_CACHE* get_binlog_cache_log(bool is_transactional)
+ {
+ return (is_transactional ? &trx_cache.cache_log : &stmt_cache.cache_log);
+ }
+
+ binlog_cache_data stmt_cache;
+
+ binlog_cache_data trx_cache;
+
+private:
+
+ binlog_cache_mngr& operator=(const binlog_cache_mngr& info);
+ binlog_cache_mngr(const binlog_cache_mngr& info);
+};
+
+
+/*
+ Helper class to hold a mutex for the duration of the
+ block.
+
+ Eliminates the need for explicit unlocking of mutexes on, e.g.,
+ error returns. On passing a null pointer, the sentry will not do
+ anything.
+ */
+class Mutex_sentry
+{
+public:
+ Mutex_sentry(pthread_mutex_t *mutex)
+ : m_mutex(mutex)
+ {
+ if (m_mutex)
+ pthread_mutex_lock(mutex);
+ }
+
+ ~Mutex_sentry()
+ {
+ if (m_mutex)
+ pthread_mutex_unlock(m_mutex);
+#ifndef DBUG_OFF
+ m_mutex= 0;
+#endif
+ }
+
+private:
+ pthread_mutex_t *m_mutex;
+
+ // It's not allowed to copy this object in any way
+ Mutex_sentry(Mutex_sentry const&);
+ void operator=(Mutex_sentry const&);
+};
+
+
+/**
+ This function checks if a transactional talbe was updated by the
+ current transaction.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a transactional table was updated, @c false otherwise.
+*/
+bool
+trans_has_updated_trans_table(const THD* thd)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ return (cache_mngr ? my_b_tell (&cache_mngr->trx_cache.cache_log) : 0);
+}
+
+/**
+ This function checks if a transactional talbe was updated by the
+ current statement.
+
+ @param thd The client thread that executed the current statement.
+ @return
+ @c true if a transactional table was updated, @c false otherwise.
+*/
+bool
+stmt_has_updated_trans_table(const THD *thd)
+{
+ Ha_trx_info *ha_info;
+
+ for (ha_info= thd->transaction.stmt.ha_list; ha_info; ha_info= ha_info->next())
+ {
+ if (ha_info->is_trx_read_write() && ha_info->ht() != binlog_hton)
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+/**
+ This function checks if either a trx-cache or a non-trx-cache should
+ be used. If @c bin_log_direct_non_trans_update is active, the cache
+ to be used depends on the flag @c is_transactional.
+
+ Otherswise, we use the trx-cache if either the @c is_transactional
+ is true or the trx-cache is not empty.
+
+ @param thd The client thread.
+ @param is_transactional The changes are related to a trx-table.
+ @return
+ @c true if a trx-cache should be used, @c false otherwise.
+*/
+bool use_trans_cache(const THD* thd, bool is_transactional)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ return
+ (thd->variables.binlog_direct_non_trans_update ? is_transactional :
+ (cache_mngr->trx_cache.empty() && !is_transactional ? FALSE : TRUE));
+}
+
+bool log_in_use(const char* log_name)
+{
+ size_t log_name_len = strlen(log_name) + 1;
+ THD *tmp;
+ bool result = 0;
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+
+ while ((tmp=it++))
+ {
+ LOG_INFO* linfo;
+ if ((linfo = tmp->current_linfo))
+ {
+ pthread_mutex_lock(&linfo->lock);
+ result = !bcmp((uchar*) log_name, (uchar*) linfo->log_file_name,
+ log_name_len);
+ pthread_mutex_unlock(&linfo->lock);
+ if (result)
+ break;
+ }
+ }
+
+ pthread_mutex_unlock(&LOCK_thread_count);
+ return result;
+}
+
+
+#ifdef HAVE_REPLICATION
+static bool copy_up_file_and_fill(IO_CACHE *index_file, my_off_t offset)
+{
+ int bytes_read;
+ my_off_t init_offset= offset;
+ File file= index_file->file;
+ uchar io_buf[IO_SIZE*2];
+ DBUG_ENTER("copy_up_file_and_fill");
+
+ for (;; offset+= bytes_read)
+ {
+ (void) my_seek(file, offset, MY_SEEK_SET, MYF(0));
+ if ((bytes_read= (int) my_read(file, io_buf, sizeof(io_buf), MYF(MY_WME)))
+ < 0)
+ goto err;
+ if (!bytes_read)
+ break; // end of file
+ (void) my_seek(file, offset-init_offset, MY_SEEK_SET, MYF(0));
+ if (my_write(file, io_buf, bytes_read, MYF(MY_WME | MY_NABP)))
+ goto err;
+ }
+ /* The following will either truncate the file or fill the end with \n' */
+ if (my_chsize(file, offset - init_offset, '\n', MYF(MY_WME)) ||
+ my_sync(file, MYF(MY_WME)))
+ goto err;
+
+ /* Reset data in old index cache */
+ reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 1);
+ DBUG_RETURN(0);
+
+err:
+ DBUG_RETURN(1);
+}
+#endif /* HAVE_REPLICATION */
+
+/*
+ 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;
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+
+ while ((tmp=it++))
+ {
+ LOG_INFO* linfo;
+ if ((linfo = tmp->current_linfo))
+ {
+ pthread_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;
+ pthread_mutex_unlock(&linfo->lock);
+ }
+ }
+ pthread_mutex_unlock(&LOCK_thread_count);
+}
+
+
+/*
+ Save position of binary log transaction cache.
+
+ SYNPOSIS
+ binlog_trans_log_savepos()
+
+ thd The thread to take the binlog data from
+ pos Pointer to variable where the position will be stored
+
+ DESCRIPTION
+
+ Save the current position in the binary log transaction cache into
+ the variable pointed to by 'pos'
+*/
+static void
+binlog_trans_log_savepos(THD *thd, my_off_t *pos)
+{
+ DBUG_ENTER("binlog_trans_log_savepos");
+ DBUG_ASSERT(pos != NULL);
+ if (thd_get_ha_data(thd, binlog_hton) == NULL)
+ thd->binlog_setup_trx_data();
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ DBUG_ASSERT(mysql_bin_log.is_open());
+ *pos= cache_mngr->trx_cache.get_byte_position();
+ DBUG_PRINT("return", ("*pos: %lu", (ulong) *pos));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Truncate the binary log transaction cache.
+
+ SYNPOSIS
+ binlog_trans_log_truncate()
+
+ thd The thread to take the binlog data from
+ pos Position to truncate to
+
+ DESCRIPTION
+
+ Truncate the binary log to the given position. Will not change
+ anything else.
+
+ */
+static void
+binlog_trans_log_truncate(THD *thd, my_off_t pos)
+{
+ DBUG_ENTER("binlog_trans_log_truncate");
+ DBUG_PRINT("enter", ("pos: %lu", (ulong) pos));
+
+ DBUG_ASSERT(thd_get_ha_data(thd, binlog_hton) != NULL);
+ /* Only true if binlog_trans_log_savepos() wasn't called before */
+ DBUG_ASSERT(pos != ~(my_off_t) 0);
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ cache_mngr->trx_cache.restore_savepoint(pos);
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ this function is mostly a placeholder.
+ conceptually, binlog initialization (now mostly done in MYSQL_BIN_LOG::open)
+ should be moved here.
+*/
+
+int binlog_init(void *p)
+{
+ binlog_hton= (handlerton *)p;
+ binlog_hton->state=opt_bin_log ? SHOW_OPTION_YES : SHOW_OPTION_NO;
+ binlog_hton->db_type=DB_TYPE_BINLOG;
+ binlog_hton->savepoint_offset= sizeof(my_off_t);
+ binlog_hton->close_connection= binlog_close_connection;
+ binlog_hton->savepoint_set= binlog_savepoint_set;
+ binlog_hton->savepoint_rollback= binlog_savepoint_rollback;
+ binlog_hton->commit= binlog_commit;
+ binlog_hton->rollback= binlog_rollback;
+ binlog_hton->prepare= binlog_prepare;
+ binlog_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN;
+ return 0;
+}
+
+static int binlog_close_connection(handlerton *hton, THD *thd)
+{
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ DBUG_ASSERT(cache_mngr->trx_cache.empty() && cache_mngr->stmt_cache.empty());
+ thd_set_ha_data(thd, binlog_hton, NULL);
+ cache_mngr->~binlog_cache_mngr();
+ my_free((uchar*)cache_mngr, MYF(0));
+ return 0;
+}
+
+/**
+ This function flushes a transactional cache upon commit/rollback.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache data to be flushed
+ @param end_ev The end event either commit/rollback.
+
+ @return
+ nonzero if an error pops up when flushing the transactional cache.
+*/
+static int
+binlog_flush_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr,
+ Log_event *end_ev)
+{
+ DBUG_ENTER("binlog_flush_trx_cache");
+ int error=0;
+ IO_CACHE *cache_log= &cache_mngr->trx_cache.cache_log;
+
+ /*
+ This function handles transactional changes and as such
+ this flag equals to true.
+ */
+ bool const is_transactional= TRUE;
+
+ if (thd->binlog_flush_pending_rows_event(TRUE, is_transactional))
+ DBUG_RETURN(1);
+ /*
+ Doing a commit or a rollback including non-transactional tables,
+ i.e., ending a transaction where we might write the transaction
+ cache to the binary log.
+
+ We can always end the statement when ending a transaction since
+ transactions are not allowed inside stored functions. If they
+ were, we would have to ensure that we're not ending a statement
+ inside a stored function.
+ */
+ error= mysql_bin_log.write(thd, &cache_mngr->trx_cache.cache_log, end_ev,
+ cache_mngr->trx_cache.has_incident());
+ cache_mngr->reset_cache(&cache_mngr->trx_cache);
+
+ /*
+ We need to step the table map version after writing the
+ transaction cache to disk.
+ */
+ mysql_bin_log.update_table_map_version();
+ statistic_increment(binlog_cache_use, &LOCK_status);
+ if (cache_log->disk_writes != 0)
+ {
+ statistic_increment(binlog_cache_disk_use, &LOCK_status);
+ cache_log->disk_writes= 0;
+ }
+
+ DBUG_ASSERT(cache_mngr->trx_cache.empty());
+ DBUG_RETURN(error);
+}
+
+/**
+ This function truncates the transactional cache upon committing or rolling
+ back either a transaction or a statement.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache data to be flushed
+ @param all @c true means truncate the transaction, otherwise the
+ statement must be truncated.
+
+ @return
+ nonzero if an error pops up when truncating the transactional cache.
+*/
+static int
+binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all)
+{
+ DBUG_ENTER("binlog_truncate_trx_cache");
+ int error=0;
+ /*
+ This function handles transactional changes and as such this flag
+ equals to true.
+ */
+ bool const is_transactional= TRUE;
+
+ DBUG_PRINT("info", ("thd->options={ %s%s}, transaction: %s",
+ FLAGSTR(thd->options, OPTION_NOT_AUTOCOMMIT),
+ FLAGSTR(thd->options, OPTION_BEGIN),
+ all ? "all" : "stmt"));
+ /*
+ If rolling back an entire transaction or a single statement not
+ inside a transaction, we reset the transaction cache.
+ */
+ thd->binlog_remove_pending_rows_event(TRUE, is_transactional);
+ if (all || !thd->in_multi_stmt_transaction())
+ {
+ if (cache_mngr->trx_cache.has_incident())
+ error= mysql_bin_log.write_incident(thd, TRUE);
+
+ cache_mngr->reset_cache(&cache_mngr->trx_cache);
+
+ thd->clear_binlog_table_maps();
+ }
+ /*
+ If rolling back a statement in a transaction, we truncate the
+ transaction cache to remove the statement.
+ */
+ else
+ cache_mngr->trx_cache.restore_prev_position();
+
+ /*
+ We need to step the table map version on a rollback to ensure that a new
+ table map event is generated instead of the one that was written to the
+ thrown-away transaction cache.
+ */
+ mysql_bin_log.update_table_map_version();
+
+ DBUG_ASSERT(thd->binlog_get_pending_rows_event(is_transactional) == NULL);
+ DBUG_RETURN(error);
+}
+
+static int binlog_prepare(handlerton *hton, THD *thd, bool all)
+{
+ /*
+ do nothing.
+ just pretend we can do 2pc, so that MySQL won't
+ switch to 1pc.
+ real work will be done in MYSQL_BIN_LOG::log_xid()
+ */
+ return 0;
+}
+
+/**
+ This function flushes the non-transactional to the binary log upon
+ committing or rolling back a statement.
+
+ @param thd The thread whose transaction should be flushed
+ @param cache_mngr Pointer to the cache data to be flushed
+
+ @return
+ nonzero if an error pops up when flushing the non-transactional cache.
+*/
+static int
+binlog_flush_stmt_cache(THD *thd, binlog_cache_mngr *cache_mngr)
+{
+ int error= 0;
+ DBUG_ENTER("binlog_flush_stmt_cache");
+ /*
+ If we are flushing the statement cache, it means that the changes get
+ through otherwise the cache is empty and this routine should not be called.
+ */
+ DBUG_ASSERT(cache_mngr->stmt_cache.has_incident() == FALSE);
+ /*
+ This function handles non-transactional changes and as such this flag equals
+ to false.
+ */
+ bool const is_transactional= FALSE;
+ IO_CACHE *cache_log= &cache_mngr->stmt_cache.cache_log;
+ thd->binlog_flush_pending_rows_event(TRUE, is_transactional);
+ Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, FALSE, TRUE, 0);
+ if ((error= mysql_bin_log.write(thd, cache_log, &qev,
+ cache_mngr->stmt_cache.has_incident())))
+ DBUG_RETURN(error);
+ cache_mngr->reset_cache(&cache_mngr->stmt_cache);
+
+ /*
+ We need to step the table map version after writing the
+ transaction cache to disk.
+ */
+ mysql_bin_log.update_table_map_version();
+ statistic_increment(binlog_cache_use, &LOCK_status);
+ if (cache_log->disk_writes != 0)
+ {
+ statistic_increment(binlog_cache_disk_use, &LOCK_status);
+ cache_log->disk_writes= 0;
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ This function is called once after each statement.
+
+ It has the responsibility to flush the caches to the binary log on commits.
+
+ @param hton The binlog handlerton.
+ @param thd The client thread that executes the transaction.
+ @param all This is @c true if this is a real transaction commit, and
+ @false otherwise.
+
+ @see handlerton::commit
+*/
+static int binlog_commit(handlerton *hton, THD *thd, bool all)
+{
+ int error= 0;
+ DBUG_ENTER("binlog_commit");
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ bool const in_transaction= thd->in_multi_stmt_transaction();
+
+ DBUG_PRINT("debug",
+ ("all: %d, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
+ all,
+ YESNO(in_transaction),
+ YESNO(thd->transaction.all.modified_non_trans_table),
+ YESNO(thd->transaction.stmt.modified_non_trans_table)));
+
+ if (!cache_mngr->stmt_cache.empty())
+ {
+ binlog_flush_stmt_cache(thd, cache_mngr);
+ }
+
+ if (cache_mngr->trx_cache.empty())
+ {
+ /*
+ we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
+ */
+ cache_mngr->reset_cache(&cache_mngr->trx_cache);
+ DBUG_RETURN(0);
+ }
+
+ /*
+ We commit the transaction if:
+ - We are not in a transaction and committing a statement, or
+ - We are in a transaction and a full transaction is committed.
+ Otherwise, we accumulate the changes.
+ */
+ if (!in_transaction || all)
+ {
+ Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, FALSE, TRUE, 0);
+ error= binlog_flush_trx_cache(thd, cache_mngr, &qev);
+ }
+
+ /*
+ This is part of the stmt rollback.
+ */
+ if (!all)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
+ DBUG_RETURN(error);
+}
+
+/**
+ This function is called when a transaction or a statement is rolled back.
+
+ @param hton The binlog handlerton.
+ @param thd The client thread that executes the transaction.
+ @param all This is @c true if this is a real transaction rollback, and
+ @false otherwise.
+
+ @see handlerton::rollback
+*/
+static int binlog_rollback(handlerton *hton, THD *thd, bool all)
+{
+ DBUG_ENTER("binlog_rollback");
+ int error=0;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_PRINT("debug", ("all: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
+ YESNO(all),
+ YESNO(thd->transaction.all.modified_non_trans_table),
+ YESNO(thd->transaction.stmt.modified_non_trans_table)));
+
+ /*
+ If an incident event is set we do not flush the content of the statement
+ cache because it may be corrupted.
+ */
+ if (cache_mngr->stmt_cache.has_incident())
+ {
+ mysql_bin_log.write_incident(thd, TRUE);
+ cache_mngr->reset_cache(&cache_mngr->stmt_cache);
+ }
+ else if (!cache_mngr->stmt_cache.empty())
+ {
+ binlog_flush_stmt_cache(thd, cache_mngr);
+ }
+
+ if (cache_mngr->trx_cache.empty())
+ {
+ /*
+ we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
+ */
+ cache_mngr->reset_cache(&cache_mngr->trx_cache);
+ DBUG_RETURN(0);
+ }
+
+ if (mysql_bin_log.check_write_error(thd))
+ {
+ /*
+ "all == true" means that a "rollback statement" triggered the error and
+ this function was called. However, this must not happen as a rollback
+ is written directly to the binary log. And in auto-commit mode, a single
+ statement that is rolled back has the flag all == false.
+ */
+ DBUG_ASSERT(!all);
+ /*
+ We reach this point if the effect of a statement did not properly get into
+ a cache and need to be rolled back.
+ */
+ error= binlog_truncate_trx_cache(thd, cache_mngr, all);
+ }
+ else
+ {
+ /*
+ We flush the cache wrapped in a beging/rollback if:
+ . aborting a transcation that modified a non-transactional table or;
+ . aborting a statement that modified both transactional and
+ non-transctional tables but which is not in the boundaries of any
+ transaction;
+ . the OPTION_KEEP_LOG is activate.
+ */
+ if (thd->variables.binlog_format == BINLOG_FORMAT_STMT &&
+ ((all && thd->transaction.all.modified_non_trans_table) ||
+ (!all && thd->transaction.stmt.modified_non_trans_table &&
+ !thd->in_multi_stmt_transaction()) ||
+ (thd->options & OPTION_KEEP_LOG)))
+ {
+ Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, FALSE, TRUE, 0);
+ error= binlog_flush_trx_cache(thd, cache_mngr, &qev);
+ }
+ /*
+ Otherwise, we simply truncate the cache as there is no change on
+ non-transactional tables as follows.
+ */
+ else if (all || (!all &&
+ (!thd->transaction.stmt.modified_non_trans_table ||
+ !thd->in_multi_stmt_transaction() ||
+ thd->variables.binlog_format != BINLOG_FORMAT_STMT)))
+ error= binlog_truncate_trx_cache(thd, cache_mngr, all);
+ }
+
+ /*
+ This is part of the stmt rollback.
+ */
+ if (!all)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
+ DBUG_RETURN(error);
+}
+
+/*
+ These functions are placed in this file since they need access to
+ binlog_hton, which has internal linkage.
+*/
+
+int THD::binlog_setup_trx_data()
+{
+ DBUG_ENTER("THD::binlog_setup_trx_data");
+ binlog_cache_mngr *cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ if (cache_mngr)
+ DBUG_RETURN(0); // Already set up
+
+ cache_mngr= (binlog_cache_mngr*) my_malloc(sizeof(binlog_cache_mngr), MYF(MY_ZEROFILL));
+ if (!cache_mngr ||
+ open_cached_file(&cache_mngr->stmt_cache.cache_log, mysql_tmpdir,
+ LOG_PREFIX, binlog_cache_size, MYF(MY_WME)) ||
+ open_cached_file(&cache_mngr->trx_cache.cache_log, mysql_tmpdir,
+ LOG_PREFIX, binlog_cache_size, MYF(MY_WME)))
+ {
+ my_free((uchar*)cache_mngr, MYF(MY_ALLOW_ZERO_PTR));
+ DBUG_RETURN(1); // Didn't manage to set it up
+ }
+ thd_set_ha_data(this, binlog_hton, cache_mngr);
+
+ cache_mngr= new (thd_get_ha_data(this, binlog_hton)) binlog_cache_mngr;
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Function to start a statement and optionally a transaction for the
+ binary log.
+
+ SYNOPSIS
+ binlog_start_trans_and_stmt()
+
+ DESCRIPTION
+
+ This function does three things:
+ - Start a transaction if not in autocommit mode or if a BEGIN
+ statement has been seen.
+
+ - Start a statement transaction to allow us to truncate the cache.
+
+ - Save the currrent binlog position so that we can roll back the
+ statement by truncating the cache.
+
+ We only update the saved position if the old one was undefined,
+ the reason is that there are some cases (e.g., for CREATE-SELECT)
+ where the position is saved twice (e.g., both in
+ select_create::prepare() and THD::binlog_write_table_map()) , but
+ we should use the first. This means that calls to this function
+ can be used to start the statement before the first table map
+ event, to include some extra events.
+ */
+
+void
+THD::binlog_start_trans_and_stmt()
+{
+ binlog_cache_mngr *cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+ DBUG_ENTER("binlog_start_trans_and_stmt");
+ DBUG_PRINT("enter", ("cache_mngr: %p cache_mngr->trx_cache.get_prev_position(): %lu",
+ cache_mngr,
+ (cache_mngr ? (ulong) cache_mngr->trx_cache.get_prev_position() :
+ (ulong) 0)));
+
+ if (cache_mngr == NULL ||
+ cache_mngr->trx_cache.get_prev_position() == MY_OFF_T_UNDEF)
+ {
+ this->binlog_set_stmt_begin();
+ if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
+ trans_register_ha(this, TRUE, binlog_hton);
+ trans_register_ha(this, FALSE, binlog_hton);
+ /*
+ Mark statement transaction as read/write. We never start
+ a binary log transaction and keep it read-only,
+ therefore it's best to mark the transaction read/write just
+ at the same time we start it.
+ Not necessary to mark the normal transaction read/write
+ since the statement-level flag will be propagated automatically
+ inside ha_commit_trans.
+ */
+ ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
+ }
+ DBUG_VOID_RETURN;
+}
+
+void THD::binlog_set_stmt_begin() {
+ binlog_cache_mngr *cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ /*
+ The call to binlog_trans_log_savepos() might create the cache_mngr
+ structure, if it didn't exist before, so we save the position
+ into an auto variable and then write it into the transaction
+ data for the binary log (i.e., cache_mngr).
+ */
+ my_off_t pos= 0;
+ binlog_trans_log_savepos(this, &pos);
+ cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+ cache_mngr->trx_cache.set_prev_position(pos);
+}
+
+
+/**
+ This function writes a table map to the binary log.
+ Note that in order to keep the signature uniform with related methods,
+ we use a redundant parameter to indicate whether a transactional table
+ was changed or not.
+
+ @param table a pointer to the table.
+ @param is_transactional @c true indicates a transactional table,
+ otherwise @c false a non-transactional.
+ @return
+ nonzero if an error pops up when writing the table map event.
+*/
+int THD::binlog_write_table_map(TABLE *table, bool is_transactional)
+{
+ int error;
+ DBUG_ENTER("THD::binlog_write_table_map");
+ DBUG_PRINT("enter", ("table: 0x%lx (%s: #%lu)",
+ (long) table, table->s->table_name.str,
+ table->s->table_map_id));
+
+ /* Pre-conditions */
+ DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
+ DBUG_ASSERT(table->s->table_map_id != ULONG_MAX);
+
+ Table_map_log_event
+ the_event(this, table, table->s->table_map_id, is_transactional);
+
+ if (binlog_table_maps == 0)
+ binlog_start_trans_and_stmt();
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ IO_CACHE *file=
+ cache_mngr->get_binlog_cache_log(use_trans_cache(this, is_transactional));
+ if ((error= the_event.write(file)))
+ DBUG_RETURN(error);
+
+ binlog_table_maps++;
+ table->s->table_map_version= mysql_bin_log.table_map_version();
+ DBUG_RETURN(0);
+}
+
+/**
+ This function retrieves a pending row event from a cache which is
+ specified through the parameter @c is_transactional. Respectively, when it
+ is @c true, the pending event is returned from the transactional cache.
+ Otherwise from the non-transactional cache.
+
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+ @return
+ The row event if any.
+*/
+Rows_log_event*
+THD::binlog_get_pending_rows_event(bool is_transactional) const
+{
+ Rows_log_event* rows= NULL;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ /*
+ This is less than ideal, but here's the story: If there is no cache_mngr,
+ prepare_pending_rows_event() has never been called (since the cache_mngr
+ is set up there). In that case, we just return NULL.
+ */
+ if (cache_mngr)
+ {
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
+
+ rows= cache_data->pending();
+ }
+ return (rows);
+}
+
+/**
+ This function stores a pending row event into a cache which is specified
+ through the parameter @c is_transactional. Respectively, when it is @c
+ true, the pending event is stored into the transactional cache. Otherwise
+ into the non-transactional cache.
+
+ @param evt a pointer to the row event.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+void
+THD::binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional)
+{
+ if (thd_get_ha_data(this, binlog_hton) == NULL)
+ binlog_setup_trx_data();
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
+
+ cache_data->set_pending(ev);
+}
+
+void MYSQL_BIN_LOG::set_write_error(THD *thd)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::set_write_error");
+
+ write_error= 1;
+
+ if (check_write_error(thd))
+ DBUG_VOID_RETURN;
+
+ if (my_errno == EFBIG)
+ my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(MY_WME));
+ else
+ my_error(ER_ERROR_ON_WRITE, MYF(MY_WME), name, errno);
+
+ DBUG_VOID_RETURN;
+}
+
+bool MYSQL_BIN_LOG::check_write_error(THD *thd)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::check_write_error");
+
+ bool checked= FALSE;
+
+ if (!thd->is_error())
+ DBUG_RETURN(checked);
+
+ switch (thd->main_da.sql_errno())
+ {
+ case ER_TRANS_CACHE_FULL:
+ case ER_ERROR_ON_WRITE:
+ case ER_BINLOG_LOGGING_IMPOSSIBLE:
+ checked= TRUE;
+ break;
+ }
+
+ DBUG_RETURN(checked);
+}
+
+/**
+ @note
+ How do we handle this (unlikely but legal) case:
+ @verbatim
+ [transaction] + [update to non-trans table] + [rollback to savepoint] ?
+ @endverbatim
+ The problem occurs when a savepoint is before the update to the
+ non-transactional table. Then when there's a rollback to the savepoint, if we
+ simply truncate the binlog cache, we lose the part of the binlog cache where
+ the update is. If we want to not lose it, we need to write the SAVEPOINT
+ command and the ROLLBACK TO SAVEPOINT command to the binlog cache. The latter
+ is easy: it's just write at the end of the binlog cache, but the former
+ should be *inserted* to the place where the user called SAVEPOINT. The
+ solution is that when the user calls SAVEPOINT, we write it to the binlog
+ cache (so no need to later insert it). As transactions are never intermixed
+ in the binary log (i.e. they are serialized), we won't have conflicts with
+ savepoint names when using mysqlbinlog or in the slave SQL thread.
+ Then when ROLLBACK TO SAVEPOINT is called, if we updated some
+ non-transactional table, we don't truncate the binlog cache but instead write
+ ROLLBACK TO SAVEPOINT to it; otherwise we truncate the binlog cache (which
+ will chop the SAVEPOINT command from the binlog cache, which is good as in
+ that case there is no need to have it in the binlog).
+*/
+
+static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv)
+{
+ DBUG_ENTER("binlog_savepoint_set");
+
+ binlog_trans_log_savepos(thd, (my_off_t*) sv);
+ /* Write it to the binary log */
+
+ int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED);
+ int const error=
+ thd->binlog_query(THD::STMT_QUERY_TYPE,
+ thd->query(), thd->query_length(), TRUE, FALSE, FALSE,
+ errcode);
+ DBUG_RETURN(error);
+}
+
+static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
+{
+ DBUG_ENTER("binlog_savepoint_rollback");
+
+ /*
+ Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some
+ non-transactional table. Otherwise, truncate the binlog cache starting
+ from the SAVEPOINT command.
+ */
+ if (unlikely(thd->transaction.all.modified_non_trans_table ||
+ (thd->options & OPTION_KEEP_LOG)))
+ {
+ int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED);
+ int error=
+ thd->binlog_query(THD::STMT_QUERY_TYPE,
+ thd->query(), thd->query_length(), TRUE, FALSE, FALSE,
+ errcode);
+ DBUG_RETURN(error);
+ }
+ binlog_trans_log_truncate(thd, *(my_off_t*)sv);
+ DBUG_RETURN(0);
+}
+
+
+int check_binlog_magic(IO_CACHE* log, const char** errmsg)
+{
+ char magic[4];
+ DBUG_ASSERT(my_b_tell(log) == 0);
+
+ if (my_b_read(log, (uchar*) magic, sizeof(magic)))
+ {
+ *errmsg = "I/O error reading the header from the binary log";
+ sql_print_error("%s, errno=%d, io cache code=%d", *errmsg, my_errno,
+ log->error);
+ return 1;
+ }
+ if (memcmp(magic, BINLOG_MAGIC, sizeof(magic)))
+ {
+ *errmsg = "Binlog has bad magic number; It's not a binary log file that can be used by this version of MySQL";
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef HAVE_REPLICATION
+/*
+ Helper function for SHOW BINLOG/RELAYLOG EVENTS
+ */
+bool show_binlog_events(THD* thd, MYSQL_BIN_LOG *binary_log)
+{
+ Protocol *protocol= thd->protocol;
+ List<Item> field_list;
+ const char *errmsg = 0;
+ bool ret = TRUE;
+ IO_CACHE log;
+ File file = -1;
+ DBUG_ENTER("mysql_show_binlog_events");
+
+ DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS ||
+ thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS);
+
+ Log_event::init_show_field_list(&field_list);
+ if (protocol->send_fields(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ 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 = 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;
+ pthread_mutex_t *log_lock = binary_log->get_log_lock();
+ LOG_INFO linfo;
+ Log_event* ev;
+
+ 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;
+ }
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = &linfo;
+ pthread_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;
+
+ pthread_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,(pthread_mutex_t*)0,description_event);
+ 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,(pthread_mutex_t*) 0,
+ description_event)); )
+ {
+ if (event_count >= limit_start &&
+ ev->net_send(protocol, linfo.log_file_name, pos))
+ {
+ errmsg = "Net error";
+ delete ev;
+ pthread_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";
+ pthread_mutex_unlock(log_lock);
+ goto err;
+ }
+
+ pthread_mutex_unlock(log_lock);
+ }
+
+ ret= FALSE;
+
+err:
+ delete description_event;
+ if (file >= 0)
+ {
+ end_io_cache(&log);
+ (void) my_close(file, MYF(MY_WME));
+ }
+
+ if (errmsg)
+ my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
+ "SHOW BINLOG EVENTS", errmsg);
+ else
+ my_eof(thd);
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ pthread_mutex_unlock(&LOCK_thread_count);
+ DBUG_RETURN(ret);
+}
+#endif /* HAVE_REPLICATION */
+
+File open_binlog(IO_CACHE *log, const char *log_file_name, const char **errmsg)
+{
+ File file;
+ DBUG_ENTER("open_binlog");
+
+ if ((file = my_open(log_file_name, O_RDONLY | O_BINARY | O_SHARE,
+ MYF(MY_WME))) < 0)
+ {
+ sql_print_error("Failed to open log (file '%s', errno %d)",
+ log_file_name, my_errno);
+ *errmsg = "Could not open log file";
+ goto err;
+ }
+ if (init_io_cache(log, file, IO_SIZE*2, READ_CACHE, 0, 0,
+ MYF(MY_WME|MY_DONT_CHECK_FILESIZE)))
+ {
+ sql_print_error("Failed to create a cache on log (file '%s')",
+ log_file_name);
+ *errmsg = "Could not open log file";
+ goto err;
+ }
+ if (check_binlog_magic(log,errmsg))
+ goto err;
+ DBUG_RETURN(file);
+
+err:
+ if (file >= 0)
+ {
+ my_close(file,MYF(0));
+ end_io_cache(log);
+ }
+ DBUG_RETURN(-1);
+}
+
+MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period)
+ :bytes_written(0), prepared_xids(0), file_id(1), open_count(1),
+ need_start_event(TRUE), m_table_map_version(0),
+ sync_period_ptr(sync_period),
+ is_relay_log(0), signal_cnt(0),
+ description_event_for_exec(0), description_event_for_queue(0)
+{
+ /*
+ We don't want to initialize locks here as such initialization depends on
+ safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
+ called only in main(). Doing initialization here would make it happen
+ before main().
+ */
+ index_file_name[0] = 0;
+ bzero((char*) &index_file, sizeof(index_file));
+ bzero((char*) &purge_index_file, sizeof(purge_index_file));
+}
+
+/* this is called only once */
+
+void MYSQL_BIN_LOG::cleanup()
+{
+ DBUG_ENTER("cleanup");
+ if (inited)
+ {
+ inited= 0;
+ close(LOG_CLOSE_INDEX|LOG_CLOSE_STOP_EVENT);
+ delete description_event_for_queue;
+ delete description_event_for_exec;
+ (void) pthread_mutex_destroy(&LOCK_log);
+ (void) pthread_mutex_destroy(&LOCK_index);
+ (void) pthread_cond_destroy(&update_cond);
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/* Init binlog-specific vars */
+void MYSQL_BIN_LOG::init(bool no_auto_events_arg, ulong max_size_arg)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::init");
+ no_auto_events= no_auto_events_arg;
+ max_size= max_size_arg;
+ DBUG_PRINT("info",("max_size: %lu", max_size));
+ DBUG_VOID_RETURN;
+}
+
+
+void MYSQL_BIN_LOG::init_pthread_objects()
+{
+ DBUG_ASSERT(inited == 0);
+ inited= 1;
+ (void) pthread_mutex_init(&LOCK_log, MY_MUTEX_INIT_SLOW);
+ (void) pthread_mutex_init(&LOCK_index, MY_MUTEX_INIT_SLOW);
+ (void) pthread_cond_init(&update_cond, 0);
+}
+
+
+bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
+ const char *log_name, bool need_mutex)
+{
+ File index_file_nr= -1;
+ DBUG_ASSERT(!my_b_inited(&index_file));
+
+ /*
+ First open of this class instance
+ Create an index file that will hold all file names uses for logging.
+ Add new entries to the end of it.
+ */
+ myf opt= MY_UNPACK_FILENAME;
+ if (!index_file_name_arg)
+ {
+ index_file_name_arg= log_name; // Use same basename for index file
+ opt= MY_UNPACK_FILENAME | MY_REPLACE_EXT;
+ }
+ fn_format(index_file_name, index_file_name_arg, mysql_data_home,
+ ".index", opt);
+ if ((index_file_nr= my_open(index_file_name,
+ O_RDWR | O_CREAT | O_BINARY ,
+ MYF(MY_WME))) < 0 ||
+ my_sync(index_file_nr, MYF(MY_WME)) ||
+ init_io_cache(&index_file, index_file_nr,
+ IO_SIZE, WRITE_CACHE,
+ my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)),
+ 0, MYF(MY_WME | MY_WAIT_IF_FULL)) ||
+ DBUG_EVALUATE_IF("fault_injection_openning_index", 1, 0))
+ {
+ /*
+ TODO: all operations creating/deleting the index file or a log, should
+ call my_sync_dir() or my_sync_dir_by_file() to be durable.
+ TODO: file creation should be done with my_create() not my_open().
+ */
+ if (index_file_nr >= 0)
+ my_close(index_file_nr,MYF(0));
+ return TRUE;
+ }
+
+#ifdef HAVE_REPLICATION
+ /*
+ Sync the index by purging any binary log file that is not registered.
+ In other words, either purge binary log files that were removed from
+ the index but not purged from the file system due to a crash or purge
+ any binary log file that was created but not register in the index
+ due to a crash.
+ */
+
+ if (set_purge_index_file_name(index_file_name_arg) ||
+ open_purge_index_file(FALSE) ||
+ purge_index_entry(NULL, NULL, need_mutex) ||
+ close_purge_index_file() ||
+ DBUG_EVALUATE_IF("fault_injection_recovering_index", 1, 0))
+ {
+ sql_print_error("MYSQL_BIN_LOG::open_index_file failed to sync the index "
+ "file.");
+ return TRUE;
+ }
+#endif
+
+ return FALSE;
+}
+
+
+/**
+ Open a (new) binlog file.
+
+ - Open the log file and the index file. Register the new
+ file name in it
+ - When calling this when the file is in use, you must have a locks
+ on LOCK_log and LOCK_index.
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool MYSQL_BIN_LOG::open(const char *log_name,
+ enum_log_type log_type_arg,
+ const char *new_name,
+ enum cache_type io_cache_type_arg,
+ bool no_auto_events_arg,
+ ulong max_size_arg,
+ bool null_created_arg,
+ bool need_mutex)
+{
+ File file= -1;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::open");
+ DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg));
+
+ if (init_and_set_log_file_name(log_name, new_name, log_type_arg,
+ io_cache_type_arg))
+ {
+ sql_print_error("MSYQL_BIN_LOG::open failed to generate new file name.");
+ DBUG_RETURN(1);
+ }
+
+#ifdef HAVE_REPLICATION
+ if (open_purge_index_file(TRUE) ||
+ register_create_index_entry(log_file_name) ||
+ sync_purge_index_file() ||
+ DBUG_EVALUATE_IF("fault_injection_registering_index", 1, 0))
+ {
+ sql_print_error("MSYQL_BIN_LOG::open failed to sync the index file.");
+ DBUG_RETURN(1);
+ }
+ DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", abort(););
+#endif
+
+ write_error= 0;
+
+ /* open the main log file */
+ if (MYSQL_LOG::open(log_name, log_type_arg, new_name,
+ io_cache_type_arg))
+ {
+#ifdef HAVE_REPLICATION
+ close_purge_index_file();
+#endif
+ DBUG_RETURN(1); /* all warnings issued */
+ }
+
+ init(no_auto_events_arg, max_size_arg);
+
+ open_count++;
+
+ DBUG_ASSERT(log_type == LOG_BIN);
+
+ {
+ bool write_file_name_to_index_file=0;
+
+ if (!my_b_filelength(&log_file))
+ {
+ /*
+ The binary log file was empty (probably newly created)
+ This is the normal case and happens when the user doesn't specify
+ an extension for the binary log files.
+ In this case we write a standard header to it.
+ */
+ if (my_b_safe_write(&log_file, (uchar*) BINLOG_MAGIC,
+ BIN_LOG_HEADER_SIZE))
+ goto err;
+ bytes_written+= BIN_LOG_HEADER_SIZE;
+ write_file_name_to_index_file= 1;
+ }
+
+ if (need_start_event && !no_auto_events)
+ {
+ /*
+ In 4.x we set need_start_event=0 here, but in 5.0 we want a Start event
+ even if this is not the very first binlog.
+ */
+ Format_description_log_event s(BINLOG_VERSION);
+ /*
+ don't set LOG_EVENT_BINLOG_IN_USE_F for SEQ_READ_APPEND io_cache
+ as we won't be able to reset it later
+ */
+ if (io_cache_type == WRITE_CACHE)
+ s.flags|= LOG_EVENT_BINLOG_IN_USE_F;
+ if (!s.is_valid())
+ goto err;
+ s.dont_set_created= null_created_arg;
+ if (s.write(&log_file))
+ goto err;
+ bytes_written+= s.data_written;
+ }
+ if (description_event_for_queue &&
+ description_event_for_queue->binlog_version>=4)
+ {
+ /*
+ This is a relay log written to by the I/O slave thread.
+ Write the event so that others can later know the format of this relay
+ log.
+ Note that this event is very close to the original event from the
+ master (it has binlog version of the master, event types of the
+ master), so this is suitable to parse the next relay log's event. It
+ has been produced by
+ Format_description_log_event::Format_description_log_event(char* buf,).
+ Why don't we want to write the description_event_for_queue if this
+ event is for format<4 (3.23 or 4.x): this is because in that case, the
+ description_event_for_queue describes the data received from the
+ master, but not the data written to the relay log (*conversion*),
+ which is in format 4 (slave's).
+ */
+ /*
+ Set 'created' to 0, so that in next relay logs this event does not
+ trigger cleaning actions on the slave in
+ Format_description_log_event::apply_event_impl().
+ */
+ description_event_for_queue->created= 0;
+ /* Don't set log_pos in event header */
+ description_event_for_queue->set_artificial_event();
+
+ if (description_event_for_queue->write(&log_file))
+ goto err;
+ bytes_written+= description_event_for_queue->data_written;
+ }
+ if (flush_io_cache(&log_file) ||
+ my_sync(log_file.file, MYF(MY_WME)))
+ goto err;
+
+ if (write_file_name_to_index_file)
+ {
+#ifdef HAVE_REPLICATION
+ DBUG_EXECUTE_IF("crash_create_critical_before_update_index", abort(););
+#endif
+
+ DBUG_ASSERT(my_b_inited(&index_file) != 0);
+ reinit_io_cache(&index_file, WRITE_CACHE,
+ my_b_filelength(&index_file), 0, 0);
+ /*
+ As this is a new log file, we write the file name to the index
+ file. As every time we write to the index file, we sync it.
+ */
+ if (DBUG_EVALUATE_IF("fault_injection_updating_index", 1, 0) ||
+ my_b_write(&index_file, (uchar*) log_file_name,
+ strlen(log_file_name)) ||
+ my_b_write(&index_file, (uchar*) "\n", 1) ||
+ flush_io_cache(&index_file) ||
+ my_sync(index_file.file, MYF(MY_WME)))
+ goto err;
+
+#ifdef HAVE_REPLICATION
+ DBUG_EXECUTE_IF("crash_create_after_update_index", abort(););
+#endif
+ }
+ }
+ log_state= LOG_OPENED;
+
+#ifdef HAVE_REPLICATION
+ close_purge_index_file();
+#endif
+
+ DBUG_RETURN(0);
+
+err:
+#ifdef HAVE_REPLICATION
+ if (is_inited_purge_index_file())
+ purge_index_entry(NULL, NULL, need_mutex);
+ close_purge_index_file();
+#endif
+ sql_print_error("Could not use %s for logging (error %d). \
+Turning logging off for the whole duration of the MySQL server process. \
+To turn it on again: fix the cause, \
+shutdown the MySQL server and restart it.", name, errno);
+ if (file >= 0)
+ my_close(file,MYF(0));
+ end_io_cache(&log_file);
+ end_io_cache(&index_file);
+ safeFree(name);
+ log_state= LOG_CLOSED;
+ DBUG_RETURN(1);
+}
+
+
+int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo)
+{
+ pthread_mutex_lock(&LOCK_log);
+ int ret = raw_get_current_log(linfo);
+ pthread_mutex_unlock(&LOCK_log);
+ return ret;
+}
+
+int MYSQL_BIN_LOG::raw_get_current_log(LOG_INFO* linfo)
+{
+ strmake(linfo->log_file_name, log_file_name, sizeof(linfo->log_file_name)-1);
+ linfo->pos = my_b_tell(&log_file);
+ return 0;
+}
+
+/**
+ Find the position in the log-index-file for the given log name.
+
+ @param linfo Store here the found log file name and position to
+ the NEXT log file name in the index file.
+ @param log_name Filename to find in the index file.
+ Is a null pointer if we want to read the first entry
+ @param need_lock Set this to 1 if the parent doesn't already have a
+ lock on LOCK_index
+
+ @note
+ On systems without the truncate function the file will end with one or
+ more empty lines. These will be ignored when reading the file.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name,
+ bool need_lock)
+{
+ int error= 0;
+ char *fname= linfo->log_file_name;
+ uint log_name_len= log_name ? (uint) strlen(log_name) : 0;
+ DBUG_ENTER("find_log_pos");
+ DBUG_PRINT("enter",("log_name: %s", log_name ? log_name : "NULL"));
+
+ /*
+ Mutex needed because we need to make sure the file pointer does not
+ move from under our feet
+ */
+ if (need_lock)
+ pthread_mutex_lock(&LOCK_index);
+ safe_mutex_assert_owner(&LOCK_index);
+
+ /* As the file is flushed, we can't get an error here */
+ (void) reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+
+ for (;;)
+ {
+ uint length;
+ my_off_t offset= my_b_tell(&index_file);
+
+ DBUG_EXECUTE_IF("simulate_find_log_pos_error",
+ error= LOG_INFO_EOF; break;);
+ /* If we get 0 or 1 characters, this is the end of the file */
+ if ((length= my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
+ {
+ /* Did not find the given entry; Return not found or error */
+ error= !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
+ break;
+ }
+
+ // if the log entry matches, null string matching anything
+ if (!log_name ||
+ (log_name_len == length-1 && fname[log_name_len] == '\n' &&
+ !memcmp(fname, log_name, log_name_len)))
+ {
+ DBUG_PRINT("info",("Found log file entry"));
+ fname[length-1]=0; // remove last \n
+ linfo->index_file_start_offset= offset;
+ linfo->index_file_offset = my_b_tell(&index_file);
+ break;
+ }
+ }
+
+ if (need_lock)
+ pthread_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Find the position in the log-index-file for the given log name.
+
+ @param
+ linfo Store here the next log file name and position to
+ the file name after that.
+ @param
+ need_lock Set this to 1 if the parent doesn't already have a
+ lock on LOCK_index
+
+ @note
+ - Before calling this function, one has to call find_log_pos()
+ to set up 'linfo'
+ - Mutex needed because we need to make sure the file pointer does not move
+ from under our feet
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+int MYSQL_BIN_LOG::find_next_log(LOG_INFO* linfo, bool need_lock)
+{
+ int error= 0;
+ uint length;
+ char *fname= linfo->log_file_name;
+
+ if (need_lock)
+ pthread_mutex_lock(&LOCK_index);
+ safe_mutex_assert_owner(&LOCK_index);
+
+ /* As the file is flushed, we can't get an error here */
+ (void) reinit_io_cache(&index_file, READ_CACHE, linfo->index_file_offset, 0,
+ 0);
+
+ linfo->index_file_start_offset= linfo->index_file_offset;
+ if ((length=my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
+ {
+ error = !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
+ goto err;
+ }
+ fname[length-1]=0; // kill \n
+ linfo->index_file_offset = my_b_tell(&index_file);
+
+err:
+ if (need_lock)
+ pthread_mutex_unlock(&LOCK_index);
+ return error;
+}
+
+
+/**
+ Delete all logs refered to in the index file.
+ Start writing to a new log file.
+
+ The new index file will only contain this file.
+
+ @param thd Thread
+
+ @note
+ If not called from slave thread, write start event to new log
+
+ @retval
+ 0 ok
+ @retval
+ 1 error
+*/
+
+bool MYSQL_BIN_LOG::reset_logs(THD* thd)
+{
+ LOG_INFO linfo;
+ bool error=0;
+ int err;
+ const char* save_name;
+ DBUG_ENTER("reset_logs");
+
+ ha_reset_logs(thd);
+ /*
+ We need to get both locks to be sure that no one is trying to
+ write to the index log file.
+ */
+ pthread_mutex_lock(&LOCK_log);
+ pthread_mutex_lock(&LOCK_index);
+
+ /*
+ The following mutex is needed to ensure that no threads call
+ 'delete thd' as we would then risk missing a 'rollback' from this
+ thread. If the transaction involved MyISAM tables, it should go
+ into binlog even on rollback.
+ */
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+
+ /* Save variables so that we can reopen the log */
+ save_name=name;
+ name=0; // Protect against free
+ close(LOG_CLOSE_TO_BE_OPENED);
+
+ /*
+ First delete all old log files and then update the index file.
+ As we first delete the log files and do not use sort of logging,
+ a crash may lead to an inconsistent state where the index has
+ references to non-existent files.
+
+ We need to invert the steps and use the purge_index_file methods
+ in order to make the operation safe.
+ */
+ if ((err= find_log_pos(&linfo, NullS, 0)) != 0)
+ {
+ uint errcode= purge_log_get_error_code(err);
+ sql_print_error("Failed to locate old binlog or relay log files");
+ my_message(errcode, ER(errcode), MYF(0));
+ error= 1;
+ goto err;
+ }
+
+ for (;;)
+ {
+ if ((error= my_delete_allow_opened(linfo.log_file_name, MYF(0))) != 0)
+ {
+ if (my_errno == ENOENT)
+ {
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ linfo.log_file_name);
+ sql_print_information("Failed to delete file '%s'",
+ linfo.log_file_name);
+ my_errno= 0;
+ error= 0;
+ }
+ else
+ {
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ linfo.log_file_name);
+ error= 1;
+ goto err;
+ }
+ }
+ if (find_next_log(&linfo, 0))
+ break;
+ }
+
+ /* Start logging with a new file */
+ close(LOG_CLOSE_INDEX);
+ if ((error= my_delete_allow_opened(index_file_name, MYF(0)))) // Reset (open will update)
+ {
+ if (my_errno == ENOENT)
+ {
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ index_file_name);
+ sql_print_information("Failed to delete file '%s'",
+ index_file_name);
+ my_errno= 0;
+ error= 0;
+ }
+ else
+ {
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ index_file_name);
+ error= 1;
+ goto err;
+ }
+ }
+ if (!thd->slave_thread)
+ need_start_event=1;
+ if (!open_index_file(index_file_name, 0, FALSE))
+ open(save_name, log_type, 0, io_cache_type, no_auto_events, max_size, 0, FALSE);
+ my_free((uchar*) save_name, MYF(0));
+
+err:
+ if (error == 1)
+ name= const_cast<char*>(save_name);
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+ pthread_mutex_unlock(&LOCK_index);
+ pthread_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Delete relay log files prior to rli->group_relay_log_name
+ (i.e. all logs which are not involved in a non-finished group
+ (transaction)), remove them from the index file and start on next
+ relay log.
+
+ IMPLEMENTATION
+ - Protects index file with LOCK_index
+ - Delete relevant relay log files
+ - Copy all file names after these ones to the front of the index file
+ - If the OS has truncate, truncate the file, else fill it with \n'
+ - Read the next file name from the index file and store in rli->linfo
+
+ @param rli Relay log information
+ @param included If false, all relay logs that are strictly before
+ rli->group_relay_log_name are deleted ; if true, the
+ latter is deleted too (i.e. all relay logs
+ read by the SQL slave thread are deleted).
+
+ @note
+ - This is only called from the slave-execute thread when it has read
+ all commands from a relay log and want to switch to a new relay log.
+ - When this happens, we can be in an active transaction as
+ a transaction can span over two relay logs
+ (although it is always written as a single block to the master's binary
+ log, hence cannot span over two master's binary logs).
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF End of log-index-file found
+ @retval
+ LOG_INFO_SEEK Could not allocate IO cache
+ @retval
+ LOG_INFO_IO Got IO error while reading file
+*/
+
+#ifdef HAVE_REPLICATION
+
+int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included)
+{
+ int error;
+ char *to_purge_if_included= NULL;
+ DBUG_ENTER("purge_first_log");
+
+ DBUG_ASSERT(is_open());
+ DBUG_ASSERT(rli->slave_running == 1);
+ DBUG_ASSERT(!strcmp(rli->linfo.log_file_name,rli->event_relay_log_name));
+
+ pthread_mutex_lock(&LOCK_index);
+ to_purge_if_included= my_strdup(rli->group_relay_log_name, MYF(0));
+
+ /*
+ Read the next log file name from the index file and pass it back to
+ the caller.
+ */
+ if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)) ||
+ (error=find_next_log(&rli->linfo, 0)))
+ {
+ char buff[22];
+ sql_print_error("next log error: %d offset: %s log: %s included: %d",
+ error,
+ llstr(rli->linfo.index_file_offset,buff),
+ rli->event_relay_log_name,
+ included);
+ goto err;
+ }
+
+ /*
+ Reset rli's coordinates to the current log.
+ */
+ rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE;
+ strmake(rli->event_relay_log_name,rli->linfo.log_file_name,
+ sizeof(rli->event_relay_log_name)-1);
+
+ /*
+ If we removed the rli->group_relay_log_name file,
+ we must update the rli->group* coordinates, otherwise do not touch it as the
+ group's execution is not finished (e.g. COMMIT not executed)
+ */
+ if (included)
+ {
+ rli->group_relay_log_pos = BIN_LOG_HEADER_SIZE;
+ strmake(rli->group_relay_log_name,rli->linfo.log_file_name,
+ sizeof(rli->group_relay_log_name)-1);
+ rli->notify_group_relay_log_name_update();
+ }
+
+ /* Store where we are in the new file for the execution thread */
+ flush_relay_log_info(rli);
+
+ DBUG_EXECUTE_IF("crash_before_purge_logs", abort(););
+
+ pthread_mutex_lock(&rli->log_space_lock);
+ rli->relay_log.purge_logs(to_purge_if_included, included,
+ 0, 0, &rli->log_space_total);
+ // Tell the I/O thread to take the relay_log_space_limit into account
+ rli->ignore_log_space_limit= 0;
+ pthread_mutex_unlock(&rli->log_space_lock);
+
+ /*
+ Ok to broadcast after the critical region as there is no risk of
+ the mutex being destroyed by this thread later - this helps save
+ context switches
+ */
+ pthread_cond_broadcast(&rli->log_space_cond);
+
+ /*
+ * Need to update the log pos because purge logs has been called
+ * after fetching initially the log pos at the begining of the method.
+ */
+ if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)))
+ {
+ char buff[22];
+ sql_print_error("next log error: %d offset: %s log: %s included: %d",
+ error,
+ llstr(rli->linfo.index_file_offset,buff),
+ rli->group_relay_log_name,
+ included);
+ goto err;
+ }
+
+ /* If included was passed, rli->linfo should be the first entry. */
+ DBUG_ASSERT(!included || rli->linfo.index_file_start_offset == 0);
+
+err:
+ my_free(to_purge_if_included, MYF(0));
+ pthread_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+/**
+ Update log index_file.
+*/
+
+int MYSQL_BIN_LOG::update_log_index(LOG_INFO* log_info, bool need_update_threads)
+{
+ if (copy_up_file_and_fill(&index_file, log_info->index_file_start_offset))
+ return LOG_INFO_IO;
+
+ // now update offsets in index file for running threads
+ if (need_update_threads)
+ adjust_linfo_offsets(log_info->index_file_start_offset);
+ return 0;
+}
+
+/**
+ Remove all logs before the given log from disk and from the index file.
+
+ @param to_log Delete all log file name before this file.
+ @param included If true, to_log is deleted too.
+ @param need_mutex
+ @param need_update_threads If we want to update the log coordinates of
+ all threads. False for relay logs, true otherwise.
+ @param freed_log_space If not null, decrement this variable of
+ the amount of log space freed
+
+ @note
+ If any of the logs before the deleted one is in use,
+ only purge logs up to this one.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_EOF to_log not found
+ LOG_INFO_EMFILE too many files opened
+ LOG_INFO_FATAL if any other than ENOENT error from
+ my_stat() or my_delete()
+*/
+
+int MYSQL_BIN_LOG::purge_logs(const char *to_log,
+ bool included,
+ bool need_mutex,
+ bool need_update_threads,
+ ulonglong *decrease_log_space)
+{
+ int error= 0;
+ bool exit_loop= 0;
+ LOG_INFO log_info;
+ THD *thd= current_thd;
+ DBUG_ENTER("purge_logs");
+ DBUG_PRINT("info",("to_log= %s",to_log));
+
+ if (need_mutex)
+ pthread_mutex_lock(&LOCK_index);
+ if ((error=find_log_pos(&log_info, to_log, 0 /*no mutex*/)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs was called with file %s not "
+ "listed in the index.", to_log);
+ goto err;
+ }
+
+ if ((error= open_purge_index_file(TRUE)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs failed to sync the index file.");
+ goto err;
+ }
+
+ /*
+ File name exists in index file; delete until we find this file
+ or a file that is used.
+ */
+ if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
+ goto err;
+ while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) &&
+ !is_active(log_info.log_file_name) &&
+ !log_in_use(log_info.log_file_name))
+ {
+ if ((error= register_purge_index_entry(log_info.log_file_name)))
+ {
+ sql_print_error("MYSQL_BIN_LOG::purge_logs failed to copy %s to register file.",
+ log_info.log_file_name);
+ goto err;
+ }
+
+ if (find_next_log(&log_info, 0) || exit_loop)
+ break;
+ }
+
+ DBUG_EXECUTE_IF("crash_purge_before_update_index", abort(););
+
+ if ((error= sync_purge_index_file()))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to flush register file.");
+ goto err;
+ }
+
+ /* We know how many files to delete. Update index file. */
+ if ((error=update_log_index(&log_info, need_update_threads)))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to update the index file");
+ goto err;
+ }
+
+ DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", abort(););
+
+err:
+ /* Read each entry from purge_index_file and delete the file. */
+ if (is_inited_purge_index_file() &&
+ (error= purge_index_entry(thd, decrease_log_space, FALSE)))
+ sql_print_error("MSYQL_BIN_LOG::purge_logs failed to process registered files"
+ " that would be purged.");
+ close_purge_index_file();
+
+ DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", abort(););
+
+ if (need_mutex)
+ pthread_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::set_purge_index_file_name(const char *base_file_name)
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::set_purge_index_file_name");
+ if (fn_format(purge_index_file_name, base_file_name, mysql_data_home,
+ ".~rec~", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH |
+ MY_REPLACE_EXT)) == NULL)
+ {
+ error= 1;
+ sql_print_error("MYSQL_BIN_LOG::set_purge_index_file_name failed to set "
+ "file name.");
+ }
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::open_purge_index_file(bool destroy)
+{
+ int error= 0;
+ File file= -1;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::open_purge_index_file");
+
+ if (destroy)
+ close_purge_index_file();
+
+ if (!my_b_inited(&purge_index_file))
+ {
+ if ((file= my_open(purge_index_file_name, O_RDWR | O_CREAT | O_BINARY,
+ MYF(MY_WME | ME_WAITTANG))) < 0 ||
+ init_io_cache(&purge_index_file, file, IO_SIZE,
+ (destroy ? WRITE_CACHE : READ_CACHE),
+ 0, 0, MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)))
+ {
+ error= 1;
+ sql_print_error("MYSQL_BIN_LOG::open_purge_index_file failed to open register "
+ " file.");
+ }
+ }
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::close_purge_index_file()
+{
+ int error= 0;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::close_purge_index_file");
+
+ if (my_b_inited(&purge_index_file))
+ {
+ end_io_cache(&purge_index_file);
+ error= my_close(purge_index_file.file, MYF(0));
+ }
+ my_delete(purge_index_file_name, MYF(0));
+ bzero((char*) &purge_index_file, sizeof(purge_index_file));
+
+ DBUG_RETURN(error);
+}
+
+bool MYSQL_BIN_LOG::is_inited_purge_index_file()
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::is_inited_purge_index_file");
+ DBUG_RETURN (my_b_inited(&purge_index_file));
+}
+
+int MYSQL_BIN_LOG::sync_purge_index_file()
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::sync_purge_index_file");
+
+ if ((error= flush_io_cache(&purge_index_file)) ||
+ (error= my_sync(purge_index_file.file, MYF(MY_WME))))
+ DBUG_RETURN(error);
+
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::register_purge_index_entry(const char *entry)
+{
+ int error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::register_purge_index_entry");
+
+ if ((error=my_b_write(&purge_index_file, (const uchar*)entry, strlen(entry))) ||
+ (error=my_b_write(&purge_index_file, (const uchar*)"\n", 1)))
+ DBUG_RETURN (error);
+
+ DBUG_RETURN(error);
+}
+
+int MYSQL_BIN_LOG::register_create_index_entry(const char *entry)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::register_create_index_entry");
+ DBUG_RETURN(register_purge_index_entry(entry));
+}
+
+int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *decrease_log_space,
+ bool need_mutex)
+{
+ MY_STAT s;
+ int error= 0;
+ LOG_INFO log_info;
+ LOG_INFO check_log_info;
+
+ DBUG_ENTER("MYSQL_BIN_LOG:purge_index_entry");
+
+ DBUG_ASSERT(my_b_inited(&purge_index_file));
+
+ if ((error=reinit_io_cache(&purge_index_file, READ_CACHE, 0, 0, 0)))
+ {
+ sql_print_error("MSYQL_BIN_LOG::purge_index_entry failed to reinit register file "
+ "for read");
+ goto err;
+ }
+
+ for (;;)
+ {
+ uint length;
+
+ if ((length=my_b_gets(&purge_index_file, log_info.log_file_name,
+ FN_REFLEN)) <= 1)
+ {
+ if (purge_index_file.error)
+ {
+ error= purge_index_file.error;
+ sql_print_error("MSYQL_BIN_LOG::purge_index_entry error %d reading from "
+ "register file.", error);
+ goto err;
+ }
+
+ /* Reached EOF */
+ break;
+ }
+
+ /* Get rid of the trailing '\n' */
+ log_info.log_file_name[length-1]= 0;
+
+ if (!my_stat(log_info.log_file_name, &s, MYF(0)))
+ {
+ if (my_errno == ENOENT)
+ {
+ /*
+ It's not fatal if we can't stat a log file that does not exist;
+ If we could not stat, we won't delete.
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ log_info.log_file_name);
+ }
+ sql_print_information("Failed to execute my_stat on file '%s'",
+ log_info.log_file_name);
+ my_errno= 0;
+ }
+ else
+ {
+ /*
+ Other than ENOENT are fatal
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with getting info on being purged %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete log file '%s'; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ else
+ {
+ if ((error= find_log_pos(&check_log_info, log_info.log_file_name, need_mutex)))
+ {
+ if (error != LOG_INFO_EOF)
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s and "
+ "reading the binlog index file",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete file '%s' and "
+ "read the binlog index file",
+ log_info.log_file_name);
+ }
+ goto err;
+ }
+
+ error= 0;
+ if (!need_mutex)
+ {
+ /*
+ This is to avoid triggering an error in NDB.
+ */
+ ha_binlog_index_purge_file(current_thd, log_info.log_file_name);
+ }
+
+ DBUG_PRINT("info",("purging %s",log_info.log_file_name));
+ if (!my_delete(log_info.log_file_name, MYF(0)))
+ {
+ if (decrease_log_space)
+ *decrease_log_space-= s.st_size;
+ }
+ else
+ {
+ if (my_errno == ENOENT)
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
+ log_info.log_file_name);
+ }
+ sql_print_information("Failed to delete file '%s'",
+ log_info.log_file_name);
+ my_errno= 0;
+ }
+ else
+ {
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with deleting %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete file '%s'; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ if (my_errno == EMFILE)
+ {
+ DBUG_PRINT("info",
+ ("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno));
+ error= LOG_INFO_EMFILE;
+ goto err;
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ }
+ }
+ }
+
+err:
+ DBUG_RETURN(error);
+}
+
+/**
+ Remove all logs before the given file date from disk and from the
+ index file.
+
+ @param thd Thread pointer
+ @param purge_time Delete all log files before given date.
+
+ @note
+ If any of the logs before the deleted one is in use,
+ only purge logs up to this one.
+
+ @retval
+ 0 ok
+ @retval
+ LOG_INFO_PURGE_NO_ROTATE Binary file that can't be rotated
+ LOG_INFO_FATAL if any other than ENOENT error from
+ my_stat() or my_delete()
+*/
+
+int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time)
+{
+ int error;
+ char to_log[FN_REFLEN];
+ LOG_INFO log_info;
+ MY_STAT stat_area;
+ THD *thd= current_thd;
+
+ DBUG_ENTER("purge_logs_before_date");
+
+ pthread_mutex_lock(&LOCK_index);
+ to_log[0]= 0;
+
+ if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
+ goto err;
+
+ while (strcmp(log_file_name, log_info.log_file_name) &&
+ !is_active(log_info.log_file_name) &&
+ !log_in_use(log_info.log_file_name))
+ {
+ if (!my_stat(log_info.log_file_name, &stat_area, MYF(0)))
+ {
+ if (my_errno == ENOENT)
+ {
+ /*
+ It's not fatal if we can't stat a log file that does not exist.
+ */
+ my_errno= 0;
+ }
+ else
+ {
+ /*
+ Other than ENOENT are fatal
+ */
+ if (thd)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_BINLOG_PURGE_FATAL_ERR,
+ "a problem with getting info on being purged %s; "
+ "consider examining correspondence "
+ "of your binlog index file "
+ "to the actual binlog files",
+ log_info.log_file_name);
+ }
+ else
+ {
+ sql_print_information("Failed to delete log file '%s'",
+ log_info.log_file_name);
+ }
+ error= LOG_INFO_FATAL;
+ goto err;
+ }
+ }
+ else
+ {
+ if (stat_area.st_mtime < purge_time)
+ strmake(to_log,
+ log_info.log_file_name,
+ sizeof(log_info.log_file_name) - 1);
+ else
+ break;
+ }
+ if (find_next_log(&log_info, 0))
+ break;
+ }
+
+ error= (to_log[0] ? purge_logs(to_log, 1, 0, 1, (ulonglong *) 0) : 0);
+
+err:
+ pthread_mutex_unlock(&LOCK_index);
+ DBUG_RETURN(error);
+}
+#endif /* HAVE_REPLICATION */
+
+
+/**
+ Create a new log file name.
+
+ @param buf buf of at least FN_REFLEN where new name is stored
+
+ @note
+ If file name will be longer then FN_REFLEN it will be truncated
+*/
+
+void MYSQL_BIN_LOG::make_log_name(char* buf, const char* log_ident)
+{
+ uint dir_len = dirname_length(log_file_name);
+ if (dir_len >= FN_REFLEN)
+ dir_len=FN_REFLEN-1;
+ strnmov(buf, log_file_name, dir_len);
+ strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len -1);
+}
+
+
+/**
+ Check if we are writing/reading to the given log file.
+*/
+
+bool MYSQL_BIN_LOG::is_active(const char *log_file_name_arg)
+{
+ return !strcmp(log_file_name, log_file_name_arg);
+}
+
+
+/*
+ Wrappers around new_file_impl to avoid using argument
+ to control locking. The argument 1) less readable 2) breaks
+ incapsulation 3) allows external access to the class without
+ a lock (which is not possible with private new_file_without_locking
+ method).
+*/
+
+void MYSQL_BIN_LOG::new_file()
+{
+ new_file_impl(1);
+}
+
+
+void MYSQL_BIN_LOG::new_file_without_locking()
+{
+ new_file_impl(0);
+}
+
+
+/**
+ Start writing to a new log file or reopen the old file.
+
+ @param need_lock Set to 1 if caller has not locked LOCK_log
+
+ @note
+ The new file name is stored last in the index file
+*/
+
+void MYSQL_BIN_LOG::new_file_impl(bool need_lock)
+{
+ char new_name[FN_REFLEN], *new_name_ptr, *old_name;
+
+ DBUG_ENTER("MYSQL_BIN_LOG::new_file_impl");
+ if (!is_open())
+ {
+ DBUG_PRINT("info",("log is closed"));
+ DBUG_VOID_RETURN;
+ }
+
+ if (need_lock)
+ pthread_mutex_lock(&LOCK_log);
+ pthread_mutex_lock(&LOCK_index);
+
+ safe_mutex_assert_owner(&LOCK_log);
+ safe_mutex_assert_owner(&LOCK_index);
+
+ /*
+ if binlog is used as tc log, be sure all xids are "unlogged",
+ so that on recover we only need to scan one - latest - binlog file
+ for prepared xids. As this is expected to be a rare event,
+ simple wait strategy is enough. We're locking LOCK_log to be sure no
+ new Xid_log_event's are added to the log (and prepared_xids is not
+ increased), and waiting on COND_prep_xids for late threads to
+ catch up.
+ */
+ if (prepared_xids)
+ {
+ tc_log_page_waits++;
+ pthread_mutex_lock(&LOCK_prep_xids);
+ while (prepared_xids) {
+ DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids));
+ pthread_cond_wait(&COND_prep_xids, &LOCK_prep_xids);
+ }
+ pthread_mutex_unlock(&LOCK_prep_xids);
+ }
+
+ /* Reuse old name if not binlog and not update log */
+ new_name_ptr= name;
+
+ /*
+ If user hasn't specified an extension, generate a new log name
+ We have to do this here and not in open as we want to store the
+ new file name in the current binary log file.
+ */
+ if (generate_new_name(new_name, name))
+ goto end;
+ new_name_ptr=new_name;
+
+ if (log_type == LOG_BIN)
+ {
+ if (!no_auto_events)
+ {
+ /*
+ We log the whole file name for log file as the user may decide
+ to change base names at some point.
+ */
+ Rotate_log_event r(new_name+dirname_length(new_name),
+ 0, LOG_EVENT_OFFSET, is_relay_log ? Rotate_log_event::RELAY_LOG : 0);
+ r.write(&log_file);
+ bytes_written += r.data_written;
+ }
+ /*
+ Update needs to be signalled even if there is no rotate event
+ log rotation should give the waiting thread a signal to
+ discover EOF and move on to the next log.
+ */
+ signal_update();
+ }
+ old_name=name;
+ name=0; // Don't free name
+ close(LOG_CLOSE_TO_BE_OPENED | LOG_CLOSE_INDEX);
+
+ /*
+ Note that at this point, log_state != LOG_CLOSED (important for is_open()).
+ */
+
+ /*
+ new_file() is only used for rotation (in FLUSH LOGS or because size >
+ max_binlog_size or max_relay_log_size).
+ If this is a binary log, the Format_description_log_event at the beginning of
+ the new file should have created=0 (to distinguish with the
+ Format_description_log_event written at server startup, which should
+ trigger temp tables deletion on slaves.
+ */
+
+ /* reopen index binlog file, BUG#34582 */
+ if (!open_index_file(index_file_name, 0, FALSE))
+ open(old_name, log_type, new_name_ptr,
+ io_cache_type, no_auto_events, max_size, 1, FALSE);
+ my_free(old_name,MYF(0));
+
+end:
+ if (need_lock)
+ pthread_mutex_unlock(&LOCK_log);
+ pthread_mutex_unlock(&LOCK_index);
+
+ DBUG_VOID_RETURN;
+}
+
+
+bool MYSQL_BIN_LOG::append(Log_event* ev)
+{
+ bool error = 0;
+ pthread_mutex_lock(&LOCK_log);
+ DBUG_ENTER("MYSQL_BIN_LOG::append");
+
+ DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
+ /*
+ Log_event::write() is smart enough to use my_b_write() or
+ my_b_append() depending on the kind of cache we have.
+ */
+ if (ev->write(&log_file))
+ {
+ error=1;
+ goto err;
+ }
+ bytes_written+= ev->data_written;
+ DBUG_PRINT("info",("max_size: %lu",max_size));
+ if (flush_and_sync(0))
+ goto err;
+ if ((uint) my_b_append_tell(&log_file) > max_size)
+ new_file_without_locking();
+
+err:
+ pthread_mutex_unlock(&LOCK_log);
+ signal_update(); // Safe as we don't call close
+ DBUG_RETURN(error);
+}
+
+
+bool MYSQL_BIN_LOG::appendv(const char* buf, uint len,...)
+{
+ bool error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::appendv");
+ va_list(args);
+ va_start(args,len);
+
+ DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
+
+ safe_mutex_assert_owner(&LOCK_log);
+ do
+ {
+ if (my_b_append(&log_file,(uchar*) buf,len))
+ {
+ error= 1;
+ goto err;
+ }
+ bytes_written += len;
+ } while ((buf=va_arg(args,const char*)) && (len=va_arg(args,uint)));
+ DBUG_PRINT("info",("max_size: %lu",max_size));
+ if (flush_and_sync(0))
+ goto err;
+ if ((uint) my_b_append_tell(&log_file) > max_size)
+ new_file_without_locking();
+
+err:
+ if (!error)
+ signal_update();
+ DBUG_RETURN(error);
+}
+
+bool MYSQL_BIN_LOG::flush_and_sync(bool *synced)
+{
+ int err=0, fd=log_file.file;
+ if (synced)
+ *synced= 0;
+ safe_mutex_assert_owner(&LOCK_log);
+ if (flush_io_cache(&log_file))
+ return 1;
+ uint sync_period= get_sync_period();
+ if (sync_period && ++sync_counter >= sync_period)
+ {
+ sync_counter= 0;
+ err=my_sync(fd, MYF(MY_WME));
+ if (synced)
+ *synced= 1;
+ }
+ return err;
+}
+
+void MYSQL_BIN_LOG::start_union_events(THD *thd, query_id_t query_id_param)
+{
+ DBUG_ASSERT(!thd->binlog_evt_union.do_union);
+ thd->binlog_evt_union.do_union= TRUE;
+ thd->binlog_evt_union.unioned_events= FALSE;
+ thd->binlog_evt_union.unioned_events_trans= FALSE;
+ thd->binlog_evt_union.first_query_id= query_id_param;
+}
+
+void MYSQL_BIN_LOG::stop_union_events(THD *thd)
+{
+ DBUG_ASSERT(thd->binlog_evt_union.do_union);
+ thd->binlog_evt_union.do_union= FALSE;
+}
+
+bool MYSQL_BIN_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
+{
+ return (thd->binlog_evt_union.do_union &&
+ query_id_param >= thd->binlog_evt_union.first_query_id);
+}
+
+/**
+ This function removes the pending rows event, discarding any outstanding
+ rows. If there is no pending rows event available, this is effectively a
+ no-op.
+
+ @param thd a pointer to the user thread.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+int
+MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::remove_pending_rows_event");
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
+
+ if (Rows_log_event* pending= cache_data->pending())
+ {
+ delete pending;
+ cache_data->set_pending(NULL);
+ }
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Moves the last bunch of rows from the pending Rows event to a cache (either
+ transactional cache if is_transaction is @c true, or the non-transactional
+ cache otherwise. Sets a new pending event.
+
+ @param thd a pointer to the user thread.
+ @param evt a pointer to the row event.
+ @param is_transactional @c true indicates a transactional cache,
+ otherwise @c false a non-transactional.
+*/
+int
+MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd,
+ Rows_log_event* event,
+ bool is_transactional)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::flush_and_set_pending_rows_event(event)");
+ DBUG_ASSERT(mysql_bin_log.is_open());
+ DBUG_PRINT("enter", ("event: 0x%lx", (long) event));
+
+ int error= 0;
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ DBUG_ASSERT(cache_mngr);
+
+ binlog_cache_data *cache_data=
+ cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
+
+ DBUG_PRINT("info", ("cache_mngr->pending(): 0x%lx", (long) cache_data->pending()));
+
+ if (Rows_log_event* pending= cache_data->pending())
+ {
+ IO_CACHE *file= &cache_data->cache_log;
+
+ /*
+ Write pending event to the cache.
+ */
+ if (pending->write(file))
+ {
+ set_write_error(thd);
+ if (check_write_error(thd) && cache_data &&
+ thd->transaction.stmt.modified_non_trans_table)
+ cache_data->set_incident();
+ DBUG_RETURN(1);
+ }
+
+ /*
+ We step the table map version if we are writing an event
+ representing the end of a statement.
+
+ In an ideal world, we could avoid stepping the table map version,
+ since we could then reuse the table map that was written earlier
+ in the cache. This does not work since STMT_END_F implies closing
+ all table mappings on the slave side.
+
+ TODO: Find a solution so that table maps does not have to be
+ written several times within a transaction.
+ */
+ if (pending->get_flags(Rows_log_event::STMT_END_F))
+ ++m_table_map_version;
+
+ delete pending;
+ }
+
+ thd->binlog_set_pending_rows_event(event, is_transactional);
+
+ DBUG_RETURN(error);
+}
+
+/**
+ Write an event to the binary log.
+*/
+
+bool MYSQL_BIN_LOG::write(Log_event *event_info)
+{
+ THD *thd= event_info->thd;
+ bool error= 1;
+ DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)");
+ binlog_cache_data *cache_data= 0;
+
+ if (thd->binlog_evt_union.do_union)
+ {
+ /*
+ In Stored function; Remember that function call caused an update.
+ We will log the function call to the binary log on function exit
+ */
+ thd->binlog_evt_union.unioned_events= TRUE;
+ thd->binlog_evt_union.unioned_events_trans |=
+ event_info->use_trans_cache();
+ DBUG_RETURN(0);
+ }
+
+ /*
+ We only end the statement if we are in a top-level statement. If
+ we are inside a stored function, we do not end the statement since
+ this will close all tables on the slave.
+ */
+ bool const end_stmt=
+ thd->prelocked_mode && thd->lex->requires_prelocking();
+ if (thd->binlog_flush_pending_rows_event(end_stmt,
+ event_info->use_trans_cache()))
+ DBUG_RETURN(error);
+
+ /*
+ In most cases this is only called if 'is_open()' is true; in fact this is
+ mostly called if is_open() *was* true a few instructions before, but it
+ could have changed since.
+ */
+ if (likely(is_open()))
+ {
+#ifdef HAVE_REPLICATION
+ /*
+ In the future we need to add to the following if tests like
+ "do the involved tables match (to be implemented)
+ binlog_[wild_]{do|ignore}_table?" (WL#1049)"
+ */
+ const char *local_db= event_info->get_db();
+ if ((thd && !(thd->options & OPTION_BIN_LOG)) ||
+ (!binlog_filter->db_ok(local_db)))
+ DBUG_RETURN(0);
+#endif /* HAVE_REPLICATION */
+
+#if defined(USING_TRANSACTIONS)
+ IO_CACHE *file= NULL;
+
+ if (event_info->use_direct_logging())
+ {
+ file= &log_file;
+ pthread_mutex_lock(&LOCK_log);
+ }
+ else
+ {
+ if (thd->binlog_setup_trx_data())
+ goto err;
+
+ binlog_cache_mngr *const cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+
+ bool is_trans_cache= use_trans_cache(thd, event_info->use_trans_cache());
+ file= cache_mngr->get_binlog_cache_log(is_trans_cache);
+ cache_data= cache_mngr->get_binlog_cache_data(is_trans_cache);
+
+ thd->binlog_start_trans_and_stmt();
+ }
+#endif /* USING_TRANSACTIONS */
+ DBUG_PRINT("info",("event type: %d",event_info->get_type_code()));
+
+ /*
+ No check for auto events flag here - this write method should
+ never be called if auto-events are enabled.
+
+ Write first log events which describe the 'run environment'
+ of the SQL command. If row-based binlogging, Insert_id, Rand
+ and other kind of "setting context" events are not needed.
+ */
+ if (thd)
+ {
+ if (!thd->is_current_stmt_binlog_format_row())
+ {
+ if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT,
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog);
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
+ {
+ DBUG_PRINT("info",("number of auto_inc intervals: %u",
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.
+ nb_elements()));
+ Intvar_log_event e(thd, (uchar) INSERT_ID_EVENT,
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.
+ minimum());
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->rand_used)
+ {
+ Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2);
+ if (e.write(file))
+ goto err;
+ }
+ if (thd->user_var_events.elements)
+ {
+ for (uint i= 0; i < thd->user_var_events.elements; i++)
+ {
+ BINLOG_USER_VAR_EVENT *user_var_event;
+ get_dynamic(&thd->user_var_events,(uchar*) &user_var_event, i);
+
+ /* setting flags for user var log event */
+ uchar flags= User_var_log_event::UNDEF_F;
+ if (user_var_event->unsigned_flag)
+ flags|= User_var_log_event::UNSIGNED_F;
+
+ User_var_log_event e(thd, user_var_event->user_var_event->name.str,
+ user_var_event->user_var_event->name.length,
+ user_var_event->value,
+ user_var_event->length,
+ user_var_event->type,
+ user_var_event->charset_number,
+ flags);
+ if (e.write(file))
+ goto err;
+ }
+ }
+ }
+ }
+
+ /*
+ Write the event.
+ */
+ if (event_info->write(file) ||
+ DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0))
+ goto err;
+
+ error= 0;
+
+err:
+ if (event_info->use_direct_logging())
+ {
+ if (!error)
+ {
+ bool synced;
+ if ((error= flush_and_sync(&synced)))
+ goto unlock;
+
+ if ((error= RUN_HOOK(binlog_storage, after_flush,
+ (thd, log_file_name, file->pos_in_file, synced))))
+ {
+ sql_print_error("Failed to run 'after_flush' hooks");
+ goto unlock;
+ }
+ signal_update();
+ rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
+ }
+unlock:
+ pthread_mutex_unlock(&LOCK_log);
+ }
+
+ if (error)
+ {
+ set_write_error(thd);
+ if (check_write_error(thd) && cache_data &&
+ thd->transaction.stmt.modified_non_trans_table)
+ cache_data->set_incident();
+ }
+ }
+
+ if (event_info->flags & LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F)
+ ++m_table_map_version;
+
+ DBUG_RETURN(error);
+}
+
+void MYSQL_BIN_LOG::rotate_and_purge(uint flags)
+{
+#ifdef HAVE_REPLICATION
+ bool check_purge= false;
+#endif
+ if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED))
+ pthread_mutex_lock(&LOCK_log);
+ if ((flags & RP_FORCE_ROTATE) ||
+ (my_b_tell(&log_file) >= (my_off_t) max_size))
+ {
+ new_file_without_locking();
+#ifdef HAVE_REPLICATION
+ check_purge= true;
+#endif
+ }
+ if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED))
+ pthread_mutex_unlock(&LOCK_log);
+
+#ifdef HAVE_REPLICATION
+ /*
+ NOTE: Run purge_logs wo/ holding LOCK_log
+ as it otherwise will deadlock in ndbcluster_binlog_index_purge_file
+ */
+ if (check_purge && expire_logs_days)
+ {
+ time_t purge_time= my_time(0) - expire_logs_days*24*60*60;
+ if (purge_time >= 0)
+ purge_logs_before_date(purge_time);
+ }
+#endif
+}
+
+uint MYSQL_BIN_LOG::next_file_id()
+{
+ uint res;
+ pthread_mutex_lock(&LOCK_log);
+ res = file_id++;
+ pthread_mutex_unlock(&LOCK_log);
+ return res;
+}
+
+
+/*
+ Write the contents of a cache to the binary log.
+
+ SYNOPSIS
+ write_cache()
+ cache Cache to write to the binary log
+ lock_log True if the LOCK_log mutex should be aquired, false otherwise
+ sync_log True if the log should be flushed and synced
+
+ DESCRIPTION
+ Write the contents of the cache to the binary log. The cache will
+ be reset as a READ_CACHE to be able to read the contents from it.
+ */
+
+int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache, bool lock_log, bool sync_log)
+{
+ Mutex_sentry sentry(lock_log ? &LOCK_log : NULL);
+
+ if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0))
+ return ER_ERROR_ON_WRITE;
+ uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs;
+ long val;
+ uchar header[LOG_EVENT_HEADER_LEN];
+
+ /*
+ The events in the buffer have incorrect end_log_pos data
+ (relative to beginning of group rather than absolute),
+ so we'll recalculate them in situ so the binlog is always
+ correct, even in the middle of a group. This is possible
+ because we now know the start position of the group (the
+ offset of this cache in the log, if you will); all we need
+ to do is to find all event-headers, and add the position of
+ the group to the end_log_pos of each event. This is pretty
+ straight forward, except that we read the cache in segments,
+ so an event-header might end up on the cache-border and get
+ split.
+ */
+
+ group= (uint)my_b_tell(&log_file);
+ hdr_offs= carry= 0;
+
+ do
+ {
+ /*
+ if we only got a partial header in the last iteration,
+ get the other half now and process a full header.
+ */
+ if (unlikely(carry > 0))
+ {
+ DBUG_ASSERT(carry < LOG_EVENT_HEADER_LEN);
+
+ /* assemble both halves */
+ memcpy(&header[carry], (char *)cache->read_pos, LOG_EVENT_HEADER_LEN - carry);
+
+ /* fix end_log_pos */
+ val= uint4korr(&header[LOG_POS_OFFSET]) + group;
+ int4store(&header[LOG_POS_OFFSET], val);
+
+ /* write the first half of the split header */
+ if (my_b_write(&log_file, header, carry))
+ return ER_ERROR_ON_WRITE;
+
+ /*
+ copy fixed second half of header to cache so the correct
+ version will be written later.
+ */
+ memcpy((char *)cache->read_pos, &header[carry], LOG_EVENT_HEADER_LEN - carry);
+
+ /* next event header at ... */
+ hdr_offs = uint4korr(&header[EVENT_LEN_OFFSET]) - carry;
+
+ carry= 0;
+ }
+
+ /* if there is anything to write, process it. */
+
+ if (likely(length > 0))
+ {
+ /*
+ process all event-headers in this (partial) cache.
+ if next header is beyond current read-buffer,
+ we'll get it later (though not necessarily in the
+ very next iteration, just "eventually").
+ */
+
+ while (hdr_offs < length)
+ {
+ /*
+ partial header only? save what we can get, process once
+ we get the rest.
+ */
+
+ if (hdr_offs + LOG_EVENT_HEADER_LEN > length)
+ {
+ carry= length - hdr_offs;
+ memcpy(header, (char *)cache->read_pos + hdr_offs, carry);
+ length= hdr_offs;
+ }
+ else
+ {
+ /* we've got a full event-header, and it came in one piece */
+
+ uchar *log_pos= (uchar *)cache->read_pos + hdr_offs + LOG_POS_OFFSET;
+
+ /* fix end_log_pos */
+ val= uint4korr(log_pos) + group;
+ int4store(log_pos, val);
+
+ /* next event header at ... */
+ log_pos= (uchar *)cache->read_pos + hdr_offs + EVENT_LEN_OFFSET;
+ hdr_offs += uint4korr(log_pos);
+
+ }
+ }
+
+ /*
+ Adjust hdr_offs. Note that it may still point beyond the segment
+ read in the next iteration; if the current event is very long,
+ it may take a couple of read-iterations (and subsequent adjustments
+ of hdr_offs) for it to point into the then-current segment.
+ If we have a split header (!carry), hdr_offs will be set at the
+ beginning of the next iteration, overwriting the value we set here:
+ */
+ hdr_offs -= length;
+ }
+
+ /* Write data to the binary log file */
+ if (my_b_write(&log_file, cache->read_pos, length))
+ return ER_ERROR_ON_WRITE;
+ cache->read_pos=cache->read_end; // Mark buffer used up
+ } while ((length= my_b_fill(cache)));
+
+ DBUG_ASSERT(carry == 0);
+
+ if (sync_log)
+ return flush_and_sync(0);
+
+ return 0; // All OK
+}
+
+/*
+ Helper function to get the error code of the query to be binlogged.
+ */
+int query_error_code(THD *thd, bool not_killed)
+{
+ int error;
+
+ if (not_killed)
+ {
+ error= thd->is_error() ? thd->main_da.sql_errno() : 0;
+
+ /* thd->main_da.sql_errno() might be ER_SERVER_SHUTDOWN or
+ ER_QUERY_INTERRUPTED, So here we need to make sure that error
+ is not set to these errors when specified not_killed by the
+ caller.
+ */
+ if (error == ER_SERVER_SHUTDOWN || error == ER_QUERY_INTERRUPTED)
+ error= 0;
+ }
+ else
+ {
+ /* killed status for DELAYED INSERT thread should never be used */
+ DBUG_ASSERT(!(thd->system_thread & SYSTEM_THREAD_DELAYED_INSERT));
+ error= thd->killed_errno();
+ }
+
+ return error;
+}
+
+bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock)
+{
+ uint error= 0;
+ DBUG_ENTER("MYSQL_BIN_LOG::write_incident");
+ LEX_STRING const write_error_msg=
+ { C_STRING_WITH_LEN("error writing to the binary log") };
+ Incident incident= INCIDENT_LOST_EVENTS;
+ Incident_log_event ev(thd, incident, write_error_msg);
+ if (lock)
+ pthread_mutex_lock(&LOCK_log);
+ error= ev.write(&log_file);
+ if (lock)
+ {
+ if (!error && !(error= flush_and_sync(0)))
+ {
+ signal_update();
+ rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
+ }
+ pthread_mutex_unlock(&LOCK_log);
+ }
+ DBUG_RETURN(error);
+}
+
+/**
+ Write a cached log entry to the binary log.
+ - To support transaction over replication, we wrap the transaction
+ with BEGIN/COMMIT or BEGIN/ROLLBACK in the binary log.
+ We want to write a BEGIN/ROLLBACK block when a non-transactional table
+ was updated in a transaction which was rolled back. This is to ensure
+ that the same updates are run on the slave.
+
+ @param thd
+ @param cache The cache to copy to the binlog
+ @param commit_event The commit event to print after writing the
+ contents of the cache.
+ @param incident Defines if an incident event should be created to
+ notify that some non-transactional changes did
+ not get into the binlog.
+
+ @note
+ We only come here if there is something in the cache.
+ @note
+ The thing in the cache is always a complete transaction.
+ @note
+ 'cache' needs to be reinitialized after this functions returns.
+*/
+
+bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event,
+ bool incident)
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::write(THD *, IO_CACHE *, Log_event *)");
+ VOID(pthread_mutex_lock(&LOCK_log));
+
+ DBUG_ASSERT(is_open());
+ if (likely(is_open())) // Should always be true
+ {
+ /*
+ We only bother to write to the binary log if there is anything
+ to write.
+ */
+ if (my_b_tell(cache) > 0)
+ {
+ /*
+ Log "BEGIN" at the beginning of every transaction. Here, a
+ transaction is either a BEGIN..COMMIT block or a single
+ statement in autocommit mode.
+ */
+ Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), TRUE, FALSE, TRUE, 0);
+ if (qinfo.write(&log_file))
+ goto err;
+ DBUG_EXECUTE_IF("crash_before_writing_xid",
+ {
+ if ((write_error= write_cache(cache, false, true)))
+ DBUG_PRINT("info", ("error writing binlog cache: %d",
+ write_error));
+ DBUG_PRINT("info", ("crashing before writing xid"));
+ abort();
+ });
+
+ if ((write_error= write_cache(cache, false, false)))
+ goto err;
+
+ if (commit_event && commit_event->write(&log_file))
+ goto err;
+
+ if (incident && write_incident(thd, FALSE))
+ goto err;
+
+ bool synced= 0;
+ if (flush_and_sync(&synced))
+ goto err;
+ DBUG_EXECUTE_IF("half_binlogged_transaction", abort(););
+ if (cache->error) // Error on read
+ {
+ sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno);
+ write_error=1; // Don't give more errors
+ goto err;
+ }
+
+ if (RUN_HOOK(binlog_storage, after_flush,
+ (thd, log_file_name, log_file.pos_in_file, synced)))
+ {
+ sql_print_error("Failed to run 'after_flush' hooks");
+ write_error=1;
+ goto err;
+ }
+
+ signal_update();
+ }
+
+ /*
+ if commit_event is Xid_log_event, increase the number of
+ prepared_xids (it's decreasd in ::unlog()). Binlog cannot be rotated
+ if there're prepared xids in it - see the comment in new_file() for
+ an explanation.
+ If the commit_event is not Xid_log_event (then it's a Query_log_event)
+ rotate binlog, if necessary.
+ */
+ if (commit_event && commit_event->get_type_code() == XID_EVENT)
+ {
+ pthread_mutex_lock(&LOCK_prep_xids);
+ prepared_xids++;
+ pthread_mutex_unlock(&LOCK_prep_xids);
+ }
+ else
+ rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
+ }
+ VOID(pthread_mutex_unlock(&LOCK_log));
+
+ DBUG_RETURN(0);
+
+err:
+ if (!write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
+ }
+ VOID(pthread_mutex_unlock(&LOCK_log));
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Wait until we get a signal that the relay log has been updated.
+
+ @param thd Thread variable
+
+ @note
+ One must have a lock on LOCK_log before calling this function.
+ This lock will be released before return! That's required by
+ THD::enter_cond() (see NOTES in sql_class.h).
+*/
+
+void MYSQL_BIN_LOG::wait_for_update_relay_log(THD* thd)
+{
+ const char *old_msg;
+ DBUG_ENTER("wait_for_update_relay_log");
+
+ old_msg= thd->enter_cond(&update_cond, &LOCK_log,
+ "Slave has read all relay log; "
+ "waiting for the slave I/O "
+ "thread to update it" );
+ pthread_cond_wait(&update_cond, &LOCK_log);
+ thd->exit_cond(old_msg);
+ DBUG_VOID_RETURN;
+}
+
+/**
+ Wait until we get a signal that the binary log has been updated.
+ Applies to master only.
+
+ NOTES
+ @param[in] thd a THD struct
+ @param[in] timeout a pointer to a timespec;
+ NULL means to wait w/o timeout.
+ @retval 0 if got signalled on update
+ @retval non-0 if wait timeout elapsed
+ @note
+ LOCK_log must be taken before calling this function.
+ LOCK_log is being released while the thread is waiting.
+ LOCK_log is released by the caller.
+*/
+
+int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd,
+ const struct timespec *timeout)
+{
+ int ret= 0;
+ const char* old_msg = thd->proc_info;
+ DBUG_ENTER("wait_for_update_bin_log");
+ old_msg= thd->enter_cond(&update_cond, &LOCK_log,
+ "Master has sent all binlog to slave; "
+ "waiting for binlog to be updated");
+ if (!timeout)
+ pthread_cond_wait(&update_cond, &LOCK_log);
+ else
+ ret= pthread_cond_timedwait(&update_cond, &LOCK_log,
+ const_cast<struct timespec *>(timeout));
+ DBUG_RETURN(ret);
+}
+
+
+/**
+ Close the log file.
+
+ @param exiting Bitmask for one or more of the following bits:
+ - LOG_CLOSE_INDEX : if we should close the index file
+ - LOG_CLOSE_TO_BE_OPENED : if we intend to call open
+ at once after close.
+ - LOG_CLOSE_STOP_EVENT : write a 'stop' event to the log
+
+ @note
+ One can do an open on the object at once after doing a close.
+ The internal structures are not freed until cleanup() is called
+*/
+
+void MYSQL_BIN_LOG::close(uint exiting)
+{ // One can't set log_type here!
+ DBUG_ENTER("MYSQL_BIN_LOG::close");
+ DBUG_PRINT("enter",("exiting: %d", (int) exiting));
+ if (log_state == LOG_OPENED)
+ {
+#ifdef HAVE_REPLICATION
+ if (log_type == LOG_BIN && !no_auto_events &&
+ (exiting & LOG_CLOSE_STOP_EVENT))
+ {
+ Stop_log_event s;
+ s.write(&log_file);
+ bytes_written+= s.data_written;
+ signal_update();
+ }
+#endif /* HAVE_REPLICATION */
+
+ /* don't pwrite in a file opened with O_APPEND - it doesn't work */
+ if (log_file.type == WRITE_CACHE && log_type == LOG_BIN)
+ {
+ my_off_t offset= BIN_LOG_HEADER_SIZE + FLAGS_OFFSET;
+ my_off_t org_position= my_tell(log_file.file, MYF(0));
+ uchar flags= 0; // clearing LOG_EVENT_BINLOG_IN_USE_F
+ my_pwrite(log_file.file, &flags, 1, offset, MYF(0));
+ /*
+ Restore position so that anything we have in the IO_cache is written
+ to the correct position.
+ We need the seek here, as my_pwrite() is not guaranteed to keep the
+ original position on system that doesn't support pwrite().
+ */
+ my_seek(log_file.file, org_position, MY_SEEK_SET, MYF(0));
+ }
+
+ /* this will cleanup IO_CACHE, sync and close the file */
+ MYSQL_LOG::close(exiting);
+ }
+
+ /*
+ The following test is needed even if is_open() is not set, as we may have
+ called a not complete close earlier and the index file is still open.
+ */
+
+ if ((exiting & LOG_CLOSE_INDEX) && my_b_inited(&index_file))
+ {
+ end_io_cache(&index_file);
+ if (my_close(index_file.file, MYF(0)) < 0 && ! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), index_file_name, errno);
+ }
+ }
+ log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
+ safeFree(name);
+ DBUG_VOID_RETURN;
+}
+
+
+void MYSQL_BIN_LOG::set_max_size(ulong max_size_arg)
+{
+ /*
+ We need to take locks, otherwise this may happen:
+ new_file() is called, calls open(old_max_size), then before open() starts,
+ set_max_size() sets max_size to max_size_arg, then open() starts and
+ uses the old_max_size argument, so max_size_arg has been overwritten and
+ it's like if the SET command was never run.
+ */
+ DBUG_ENTER("MYSQL_BIN_LOG::set_max_size");
+ pthread_mutex_lock(&LOCK_log);
+ if (is_open())
+ max_size= max_size_arg;
+ pthread_mutex_unlock(&LOCK_log);
+ DBUG_VOID_RETURN;
+}
+
+void MYSQL_BIN_LOG::signal_update()
+{
+ DBUG_ENTER("MYSQL_BIN_LOG::signal_update");
+ signal_cnt++;
+ pthread_cond_broadcast(&update_cond);
+ DBUG_VOID_RETURN;
+}
+
+/****** transaction coordinator log for 2pc - binlog() based solution ******/
+#define TC_LOG_BINLOG MYSQL_BIN_LOG
+
+/**
+ @todo
+ keep in-memory list of prepared transactions
+ (add to list in log(), remove on unlog())
+ and copy it to the new binlog if rotated
+ but let's check the behaviour of tc_log_page_waits first!
+*/
+
+int TC_LOG_BINLOG::open(const char *opt_name)
+{
+ LOG_INFO log_info;
+ int error= 1;
+
+ DBUG_ASSERT(total_ha_2pc > 1);
+ DBUG_ASSERT(opt_name && opt_name[0]);
+
+ pthread_mutex_init(&LOCK_prep_xids, MY_MUTEX_INIT_FAST);
+ pthread_cond_init (&COND_prep_xids, 0);
+
+ if (!my_b_inited(&index_file))
+ {
+ /* There was a failure to open the index file, can't open the binlog */
+ cleanup();
+ return 1;
+ }
+
+ if (using_heuristic_recover())
+ {
+ /* generate a new binlog to mask a corrupted one */
+ open(opt_name, LOG_BIN, 0, WRITE_CACHE, 0, max_binlog_size, 0, TRUE);
+ cleanup();
+ return 1;
+ }
+
+ if ((error= find_log_pos(&log_info, NullS, 1)))
+ {
+ if (error != LOG_INFO_EOF)
+ sql_print_error("find_log_pos() failed (error: %d)", error);
+ else
+ error= 0;
+ goto err;
+ }
+
+ {
+ const char *errmsg;
+ IO_CACHE log;
+ File file;
+ Log_event *ev=0;
+ Format_description_log_event fdle(BINLOG_VERSION);
+ char log_name[FN_REFLEN];
+
+ if (! fdle.is_valid())
+ goto err;
+
+ do
+ {
+ strmake(log_name, log_info.log_file_name, sizeof(log_name)-1);
+ } while (!(error= find_next_log(&log_info, 1)));
+
+ if (error != LOG_INFO_EOF)
+ {
+ sql_print_error("find_log_pos() failed (error: %d)", error);
+ goto err;
+ }
+
+ if ((file= open_binlog(&log, log_name, &errmsg)) < 0)
+ {
+ sql_print_error("%s", errmsg);
+ goto err;
+ }
+
+ if ((ev= Log_event::read_log_event(&log, 0, &fdle)) &&
+ ev->get_type_code() == FORMAT_DESCRIPTION_EVENT &&
+ ev->flags & LOG_EVENT_BINLOG_IN_USE_F)
+ {
+ sql_print_information("Recovering after a crash using %s", opt_name);
+ error= recover(&log, (Format_description_log_event *)ev);
+ }
+ else
+ error=0;
+
+ delete ev;
+ end_io_cache(&log);
+ my_close(file, MYF(MY_WME));
+
+ if (error)
+ goto err;
+ }
+
+err:
+ return error;
+}
+
+/** This is called on shutdown, after ha_panic. */
+void TC_LOG_BINLOG::close()
+{
+ DBUG_ASSERT(prepared_xids==0);
+ pthread_mutex_destroy(&LOCK_prep_xids);
+ pthread_cond_destroy (&COND_prep_xids);
+}
+
+/**
+ @todo
+ group commit
+
+ @retval
+ 0 error
+ @retval
+ 1 success
+*/
+int TC_LOG_BINLOG::log_xid(THD *thd, my_xid xid)
+{
+ DBUG_ENTER("TC_LOG_BINLOG::log");
+ Xid_log_event xle(thd, xid);
+ binlog_cache_mngr *cache_mngr=
+ (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+ /*
+ We always commit the entire transaction when writing an XID. Also
+ note that the return value is inverted.
+ */
+ DBUG_RETURN(!binlog_flush_stmt_cache(thd, cache_mngr) &&
+ !binlog_flush_trx_cache(thd, cache_mngr, &xle));
+}
+
+void TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid)
+{
+ pthread_mutex_lock(&LOCK_prep_xids);
+ DBUG_ASSERT(prepared_xids > 0);
+ if (--prepared_xids == 0) {
+ DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids));
+ pthread_cond_signal(&COND_prep_xids);
+ }
+ pthread_mutex_unlock(&LOCK_prep_xids);
+ rotate_and_purge(0); // as ::write() did not rotate
+}
+
+int TC_LOG_BINLOG::recover(IO_CACHE *log, Format_description_log_event *fdle)
+{
+ Log_event *ev;
+ HASH xids;
+ MEM_ROOT mem_root;
+
+ if (! fdle->is_valid() ||
+ hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0,
+ sizeof(my_xid), 0, 0, MYF(0)))
+ goto err1;
+
+ init_alloc_root(&mem_root, TC_LOG_PAGE_SIZE, TC_LOG_PAGE_SIZE);
+
+ fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error
+
+ while ((ev= Log_event::read_log_event(log,0,fdle)) && ev->is_valid())
+ {
+ if (ev->get_type_code() == XID_EVENT)
+ {
+ Xid_log_event *xev=(Xid_log_event *)ev;
+ uchar *x= (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid,
+ sizeof(xev->xid));
+ if (!x || my_hash_insert(&xids, x))
+ goto err2;
+ }
+ delete ev;
+ }
+
+ if (ha_recover(&xids))
+ goto err2;
+
+ free_root(&mem_root, MYF(0));
+ hash_free(&xids);
+ return 0;
+
+err2:
+ free_root(&mem_root, MYF(0));
+ hash_free(&xids);
+err1:
+ sql_print_error("Crash recovery failed. Either correct the problem "
+ "(if it's, for example, out of memory error) and restart, "
+ "or delete (or rename) binary log and start mysqld with "
+ "--tc-heuristic-recover={commit|rollback}");
+ return 1;
+}
+
+#ifdef INNODB_COMPATIBILITY_HOOKS
+/**
+ Get the file name of the MySQL binlog.
+ @return the name of the binlog file
+*/
+extern "C"
+const char* mysql_bin_log_file_name(void)
+{
+ return mysql_bin_log.get_log_fname();
+}
+/**
+ Get the current position of the MySQL binlog.
+ @return byte offset from the beginning of the binlog
+*/
+extern "C"
+ulonglong mysql_bin_log_file_pos(void)
+{
+ return (ulonglong) mysql_bin_log.get_log_file()->pos_in_file;
+}
+#endif /* INNODB_COMPATIBILITY_HOOKS */
+
+
+struct st_mysql_storage_engine binlog_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+mysql_declare_plugin(binlog)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &binlog_storage_engine,
+ "binlog",
+ "MySQL AB",
+ "This is a pseudo storage engine to represent the binlog in a transaction",
+ PLUGIN_LICENSE_GPL,
+ binlog_init, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0100 /* 1.0 */,
+ NULL, /* status variables */
+ NULL, /* system variables */
+ NULL /* config options */
+}
+mysql_declare_plugin_end;
=== added file 'sql/binlog.h'
--- a/sql/binlog.h 1970-01-01 00:00:00 +0000
+++ b/sql/binlog.h 2010-04-26 05:01:18 +0000
@@ -0,0 +1,270 @@
+/* Copyright (C) 2000-2006 MySQL AB & Sasha
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifndef BINLOG_H
+#define BINLOG_H
+
+#include "log.h"
+
+class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
+{
+ private:
+ /* LOCK_log and LOCK_index are inited by init_pthread_objects() */
+ pthread_mutex_t LOCK_index;
+ pthread_mutex_t LOCK_prep_xids;
+ pthread_cond_t COND_prep_xids;
+ pthread_cond_t update_cond;
+ ulonglong bytes_written;
+ IO_CACHE index_file;
+ char index_file_name[FN_REFLEN];
+ /*
+ purge_file is a temp file used in purge_logs so that the index file
+ can be updated before deleting files from disk, yielding better crash
+ recovery. It is created on demand the first time purge_logs is called
+ and then reused for subsequent calls. It is cleaned up in cleanup().
+ */
+ IO_CACHE purge_index_file;
+ char purge_index_file_name[FN_REFLEN];
+ /*
+ The max size before rotation (usable only if log_type == LOG_BIN: binary
+ logs and relay logs).
+ For a binlog, max_size should be max_binlog_size.
+ For a relay log, it should be max_relay_log_size if this is non-zero,
+ max_binlog_size otherwise.
+ max_size is set in init(), and dynamically changed (when one does SET
+ GLOBAL MAX_BINLOG_SIZE|MAX_RELAY_LOG_SIZE) by fix_max_binlog_size and
+ fix_max_relay_log_size).
+ */
+ ulong max_size;
+ long prepared_xids; /* for tc log - number of xids to remember */
+ // current file sequence number for load data infile binary logging
+ uint file_id;
+ uint open_count; // For replication
+ int readers_count;
+ bool need_start_event;
+ /*
+ no_auto_events means we don't want any of these automatic events :
+ Start/Rotate/Stop. That is, in 4.x when we rotate a relay log, we don't
+ want a Rotate_log event to be written to the relay log. When we start a
+ relay log etc. So in 4.x this is 1 for relay logs, 0 for binlogs.
+ In 5.0 it's 0 for relay logs too!
+ */
+ bool no_auto_events;
+
+ ulonglong m_table_map_version;
+
+ /* pointer to the sync period variable, for binlog this will be
+ sync_binlog_period, for relay log this will be
+ sync_relay_log_period
+ */
+ uint *sync_period_ptr;
+ uint sync_counter;
+
+ inline uint get_sync_period()
+ {
+ return *sync_period_ptr;
+ }
+
+ int write_to_file(IO_CACHE *cache);
+ /*
+ This is used to start writing to a new log file. The difference from
+ new_file() is locking. new_file_without_locking() does not acquire
+ LOCK_log.
+ */
+ void new_file_without_locking();
+ void new_file_impl(bool need_lock);
+
+public:
+ MYSQL_LOG::generate_name;
+ MYSQL_LOG::is_open;
+
+ /* This is relay log */
+ bool is_relay_log;
+ ulong signal_cnt; // update of the counter is checked by heartbeat
+ /*
+ These describe the log's format. This is used only for relay logs.
+ _for_exec is used by the SQL thread, _for_queue by the I/O thread. It's
+ necessary to have 2 distinct objects, because the I/O thread may be reading
+ events in a different format from what the SQL thread is reading (consider
+ the case of a master which has been upgraded from 5.0 to 5.1 without doing
+ RESET MASTER, or from 4.x to 5.0).
+ */
+ Format_description_log_event *description_event_for_exec,
+ *description_event_for_queue;
+
+ MYSQL_BIN_LOG(uint *sync_period);
+ /*
+ note that there's no destructor ~MYSQL_BIN_LOG() !
+ The reason is that we don't want it to be automatically called
+ on exit() - but only during the correct shutdown process
+ */
+
+ int open(const char *opt_name);
+ void close();
+ int log_xid(THD *thd, my_xid xid);
+ void unlog(ulong cookie, my_xid xid);
+ int recover(IO_CACHE *log, Format_description_log_event *fdle);
+#if !defined(MYSQL_CLIENT)
+ bool is_table_mapped(TABLE *table) const
+ {
+ return table->s->table_map_version == table_map_version();
+ }
+
+ ulonglong table_map_version() const { return m_table_map_version; }
+ void update_table_map_version() { ++m_table_map_version; }
+
+ int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
+ bool is_transactional);
+ int remove_pending_rows_event(THD *thd, bool is_transactional);
+
+#endif /* !defined(MYSQL_CLIENT) */
+ void reset_bytes_written()
+ {
+ bytes_written = 0;
+ }
+ void harvest_bytes_written(ulonglong* counter)
+ {
+#ifndef DBUG_OFF
+ char buf1[22],buf2[22];
+#endif
+ DBUG_ENTER("harvest_bytes_written");
+ (*counter)+=bytes_written;
+ DBUG_PRINT("info",("counter: %s bytes_written: %s", llstr(*counter,buf1),
+ llstr(bytes_written,buf2)));
+ bytes_written=0;
+ DBUG_VOID_RETURN;
+ }
+ void set_max_size(ulong max_size_arg);
+ void signal_update();
+ void wait_for_update_relay_log(THD* thd);
+ int wait_for_update_bin_log(THD* thd, const struct timespec * timeout);
+ void set_need_start_event() { need_start_event = 1; }
+ void init(bool no_auto_events_arg, ulong max_size);
+ void init_pthread_objects();
+ void cleanup();
+ bool open(const char *log_name,
+ enum_log_type log_type,
+ const char *new_name,
+ enum cache_type io_cache_type_arg,
+ bool no_auto_events_arg, ulong max_size,
+ bool null_created,
+ bool need_mutex);
+ bool open_index_file(const char *index_file_name_arg,
+ const char *log_name, bool need_mutex);
+ /* Use this to start writing a new log file */
+ void new_file();
+
+ bool write(Log_event* event_info);
+ bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident);
+ bool write_incident(THD *thd, bool lock);
+
+ int write_cache(IO_CACHE *cache, bool lock_log, bool flush_and_sync);
+ void set_write_error(THD *thd);
+ bool check_write_error(THD *thd);
+
+ void start_union_events(THD *thd, query_id_t query_id_param);
+ void stop_union_events(THD *thd);
+ bool is_query_in_union(THD *thd, query_id_t query_id_param);
+
+ /*
+ v stands for vector
+ invoked as appendv(buf1,len1,buf2,len2,...,bufn,lenn,0)
+ */
+ bool appendv(const char* buf,uint len,...);
+ bool append(Log_event* ev);
+
+ void make_log_name(char* buf, const char* log_ident);
+ bool is_active(const char* log_file_name);
+ int update_log_index(LOG_INFO* linfo, bool need_update_threads);
+ void rotate_and_purge(uint flags);
+ /**
+ Flush binlog cache and synchronize to disk.
+
+ This function flushes events in binlog cache to binary log file,
+ it will do synchronizing according to the setting of system
+ variable 'sync_binlog'. If file is synchronized, @c synced will
+ be set to 1, otherwise 0.
+
+ @param[out] synced if not NULL, set to 1 if file is synchronized, otherwise 0
+
+ @retval 0 Success
+ @retval other Failure
+ */
+ bool flush_and_sync(bool *synced);
+ int purge_logs(const char *to_log, bool included,
+ bool need_mutex, bool need_update_threads,
+ ulonglong *decrease_log_space);
+ int purge_logs_before_date(time_t purge_time);
+ int purge_first_log(Relay_log_info* rli, bool included);
+ int set_purge_index_file_name(const char *base_file_name);
+ int open_purge_index_file(bool destroy);
+ bool is_inited_purge_index_file();
+ int close_purge_index_file();
+ int clean_purge_index_file();
+ int sync_purge_index_file();
+ int register_purge_index_entry(const char* entry);
+ int register_create_index_entry(const char* entry);
+ int purge_index_entry(THD *thd, ulonglong *decrease_log_space,
+ bool need_mutex);
+ bool reset_logs(THD* thd);
+ void close(uint exiting);
+
+ // iterating through the log index file
+ int find_log_pos(LOG_INFO* linfo, const char* log_name,
+ bool need_mutex);
+ int find_next_log(LOG_INFO* linfo, bool need_mutex);
+ int get_current_log(LOG_INFO* linfo);
+ int raw_get_current_log(LOG_INFO* linfo);
+ uint next_file_id();
+ inline char* get_index_fname() { return index_file_name;}
+ inline char* get_log_fname() { return log_file_name; }
+ inline char* get_name() { return name; }
+ inline pthread_mutex_t* get_log_lock() { return &LOCK_log; }
+ inline IO_CACHE* get_log_file() { return &log_file; }
+
+ inline void lock_index() { pthread_mutex_lock(&LOCK_index);}
+ inline void unlock_index() { pthread_mutex_unlock(&LOCK_index);}
+ inline IO_CACHE *get_index_file() { return &index_file;}
+ inline uint32 get_open_count() { return open_count; }
+};
+
+enum enum_binlog_format {
+ /*
+ statement-based except for cases where only row-based can work (UUID()
+ etc):
+ */
+ BINLOG_FORMAT_MIXED= 0,
+ BINLOG_FORMAT_STMT= 1, // statement-based
+ BINLOG_FORMAT_ROW= 2, // row_based
+/*
+ This value is last, after the end of binlog_format_typelib: it has no
+ corresponding cell in this typelib. We use this value to be able to know if
+ the user has explicitely specified a binlog format at startup or not.
+*/
+ BINLOG_FORMAT_UNSPEC= 3
+};
+extern TYPELIB binlog_format_typelib;
+
+int query_error_code(THD *thd, bool not_killed);
+
+/* Helper function for SHOW BINLOG/RELAYLOG EVENTS */
+bool show_binlog_events(THD* thd, MYSQL_BIN_LOG *binary_log);
+
+bool rpl_master_has_bug(const Relay_log_info *rli,
+ uint bug_id, bool report,
+ bool (*pred)(const void *), const void *param);
+bool rpl_master_erroneous_autoinc(THD* thd);
+
+#endif /* BINLOG_H */
=== modified file 'sql/log.cc'
--- a/sql/log.cc 2010-03-17 11:31:50 +0000
+++ b/sql/log.cc 2010-04-26 05:01:18 +0000
@@ -44,23 +44,11 @@
#define MAX_LOG_BUFFER_SIZE 1024
#define MAX_USER_HOST_SIZE 512
#define MAX_TIME_SIZE 32
-#define MY_OFF_T_UNDEF (~(my_off_t)0UL)
-
-#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
LOGGER logger;
-MYSQL_BIN_LOG mysql_bin_log(&sync_binlog_period);
-
static bool test_if_number(const char *str,
ulong *res, bool allow_wildcards);
-static int binlog_init(void *p);
-static int binlog_close_connection(handlerton *hton, THD *thd);
-static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv);
-static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv);
-static int binlog_commit(handlerton *hton, THD *thd, bool all);
-static int binlog_rollback(handlerton *hton, THD *thd, bool all);
-static int binlog_prepare(handlerton *hton, THD *thd, bool all);
/**
purge logs, master and slave sides both, related error code
@@ -141,195 +129,6 @@ char *make_default_log_name(char *buff,c
MYF(MY_UNPACK_FILENAME|MY_REPLACE_EXT));
}
-/*
- Helper class to hold a mutex for the duration of the
- block.
-
- Eliminates the need for explicit unlocking of mutexes on, e.g.,
- error returns. On passing a null pointer, the sentry will not do
- anything.
- */
-class Mutex_sentry
-{
-public:
- Mutex_sentry(pthread_mutex_t *mutex)
- : m_mutex(mutex)
- {
- if (m_mutex)
- pthread_mutex_lock(mutex);
- }
-
- ~Mutex_sentry()
- {
- if (m_mutex)
- pthread_mutex_unlock(m_mutex);
-#ifndef DBUG_OFF
- m_mutex= 0;
-#endif
- }
-
-private:
- pthread_mutex_t *m_mutex;
-
- // It's not allowed to copy this object in any way
- Mutex_sentry(Mutex_sentry const&);
- void operator=(Mutex_sentry const&);
-};
-
-/*
- Helper classes to store non-transactional and transactional data
- before copying it to the binary log.
-*/
-class binlog_cache_data
-{
-public:
- binlog_cache_data(): m_pending(0), before_stmt_pos(MY_OFF_T_UNDEF),
- incident(FALSE)
- {
- cache_log.end_of_file= max_binlog_cache_size;
- }
-
- ~binlog_cache_data()
- {
- DBUG_ASSERT(empty());
- close_cached_file(&cache_log);
- }
-
- bool empty() const
- {
- return pending() == NULL && my_b_tell(&cache_log) == 0;
- }
-
- Rows_log_event *pending() const
- {
- return m_pending;
- }
-
- void set_pending(Rows_log_event *const pending)
- {
- m_pending= pending;
- }
-
- void set_incident(void)
- {
- incident= TRUE;
- }
-
- bool has_incident(void)
- {
- return(incident);
- }
-
- void reset()
- {
- truncate(0);
- incident= FALSE;
- before_stmt_pos= MY_OFF_T_UNDEF;
- cache_log.end_of_file= max_binlog_cache_size;
- DBUG_ASSERT(empty());
- }
-
- my_off_t get_byte_position() const
- {
- return my_b_tell(&cache_log);
- }
-
- my_off_t get_prev_position()
- {
- return(before_stmt_pos);
- }
-
- void set_prev_position(my_off_t pos)
- {
- before_stmt_pos= pos;
- }
-
- void restore_prev_position()
- {
- truncate(before_stmt_pos);
- }
-
- void restore_savepoint(my_off_t pos)
- {
- truncate(pos);
- if (pos < before_stmt_pos)
- before_stmt_pos= MY_OFF_T_UNDEF;
- }
-
- /*
- Cache to store data before copying it to the binary log.
- */
- IO_CACHE cache_log;
-
-private:
- /*
- Pending binrows event. This event is the event where the rows are currently
- written.
- */
- Rows_log_event *m_pending;
-
- /*
- Binlog position before the start of the current statement.
- */
- my_off_t before_stmt_pos;
-
- /*
- This indicates that some events did not get into the cache and most likely
- it is corrupted.
- */
- bool incident;
-
- /*
- It truncates the cache to a certain position. This includes deleting the
- pending event.
- */
- void truncate(my_off_t pos)
- {
- DBUG_PRINT("info", ("truncating to position %lu", (ulong) pos));
- if (pending())
- {
- delete pending();
- set_pending(0);
- }
- reinit_io_cache(&cache_log, WRITE_CACHE, pos, 0, 0);
- cache_log.end_of_file= max_binlog_cache_size;
- }
-
- binlog_cache_data& operator=(const binlog_cache_data& info);
- binlog_cache_data(const binlog_cache_data& info);
-};
-
-class binlog_cache_mngr {
-public:
- binlog_cache_mngr() {}
-
- void reset_cache(binlog_cache_data* cache_data)
- {
- cache_data->reset();
- }
-
- binlog_cache_data* get_binlog_cache_data(bool is_transactional)
- {
- return (is_transactional ? &trx_cache : &stmt_cache);
- }
-
- IO_CACHE* get_binlog_cache_log(bool is_transactional)
- {
- return (is_transactional ? &trx_cache.cache_log : &stmt_cache.cache_log);
- }
-
- binlog_cache_data stmt_cache;
-
- binlog_cache_data trx_cache;
-
-private:
-
- binlog_cache_mngr& operator=(const binlog_cache_mngr& info);
- binlog_cache_mngr(const binlog_cache_mngr& info);
-};
-
-handlerton *binlog_hton;
-
bool LOGGER::is_log_table_enabled(uint log_table_type)
{
switch (log_table_type) {
@@ -1381,3923 +1180,776 @@ int LOGGER::set_handlers(uint error_log_
return 0;
}
- /*
- Save position of binary log transaction cache.
+#ifdef __NT__
+static int eventSource = 0;
- SYNPOSIS
- binlog_trans_log_savepos()
+static void setup_windows_event_source()
+{
+ HKEY hRegKey= NULL;
+ DWORD dwError= 0;
+ TCHAR szPath[MAX_PATH];
+ DWORD dwTypes;
- thd The thread to take the binlog data from
- pos Pointer to variable where the position will be stored
+ if (eventSource) // Ensure that we are only called once
+ return;
+ eventSource= 1;
- DESCRIPTION
+ // Create the event source registry key
+ dwError= RegCreateKey(HKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\MySQL",
+ &hRegKey);
- Save the current position in the binary log transaction cache into
- the variable pointed to by 'pos'
- */
-
-static void
-binlog_trans_log_savepos(THD *thd, my_off_t *pos)
-{
- DBUG_ENTER("binlog_trans_log_savepos");
- DBUG_ASSERT(pos != NULL);
- if (thd_get_ha_data(thd, binlog_hton) == NULL)
- thd->binlog_setup_trx_data();
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
- DBUG_ASSERT(mysql_bin_log.is_open());
- *pos= cache_mngr->trx_cache.get_byte_position();
- DBUG_PRINT("return", ("*pos: %lu", (ulong) *pos));
- DBUG_VOID_RETURN;
-}
+ /* Name of the PE module that contains the message resource */
+ GetModuleFileName(NULL, szPath, MAX_PATH);
+ /* Register EventMessageFile */
+ dwError = RegSetValueEx(hRegKey, "EventMessageFile", 0, REG_EXPAND_SZ,
+ (PBYTE) szPath, (DWORD) (strlen(szPath) + 1));
-/*
- Truncate the binary log transaction cache.
+ /* Register supported event types */
+ dwTypes= (EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
+ EVENTLOG_INFORMATION_TYPE);
+ dwError= RegSetValueEx(hRegKey, "TypesSupported", 0, REG_DWORD,
+ (LPBYTE) &dwTypes, sizeof dwTypes);
- SYNPOSIS
- binlog_trans_log_truncate()
+ RegCloseKey(hRegKey);
+}
- thd The thread to take the binlog data from
- pos Position to truncate to
+#endif /* __NT__ */
- DESCRIPTION
- Truncate the binary log to the given position. Will not change
- anything else.
+/**
+ Find a unique filename for 'filename.#'.
- */
-static void
-binlog_trans_log_truncate(THD *thd, my_off_t pos)
-{
- DBUG_ENTER("binlog_trans_log_truncate");
- DBUG_PRINT("enter", ("pos: %lu", (ulong) pos));
-
- DBUG_ASSERT(thd_get_ha_data(thd, binlog_hton) != NULL);
- /* Only true if binlog_trans_log_savepos() wasn't called before */
- DBUG_ASSERT(pos != ~(my_off_t) 0);
-
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
- cache_mngr->trx_cache.restore_savepoint(pos);
- DBUG_VOID_RETURN;
-}
+ Set '#' to the number next to the maximum found in the most
+ recent log file extension.
+ This function will return nonzero if: (i) the generated name
+ exceeds FN_REFLEN; (ii) if the number of extensions is exhausted;
+ or (iii) some other error happened while examining the filesystem.
-/*
- this function is mostly a placeholder.
- conceptually, binlog initialization (now mostly done in MYSQL_BIN_LOG::open)
- should be moved here.
+ @return
+ nonzero if not possible to get unique filename.
*/
-int binlog_init(void *p)
-{
- binlog_hton= (handlerton *)p;
- binlog_hton->state=opt_bin_log ? SHOW_OPTION_YES : SHOW_OPTION_NO;
- binlog_hton->db_type=DB_TYPE_BINLOG;
- binlog_hton->savepoint_offset= sizeof(my_off_t);
- binlog_hton->close_connection= binlog_close_connection;
- binlog_hton->savepoint_set= binlog_savepoint_set;
- binlog_hton->savepoint_rollback= binlog_savepoint_rollback;
- binlog_hton->commit= binlog_commit;
- binlog_hton->rollback= binlog_rollback;
- binlog_hton->prepare= binlog_prepare;
- binlog_hton->flags= HTON_NOT_USER_SELECTABLE | HTON_HIDDEN;
- return 0;
-}
-
-static int binlog_close_connection(handlerton *hton, THD *thd)
+static int find_uniq_filename(char *name)
{
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
- DBUG_ASSERT(cache_mngr->trx_cache.empty() && cache_mngr->stmt_cache.empty());
- thd_set_ha_data(thd, binlog_hton, NULL);
- cache_mngr->~binlog_cache_mngr();
- my_free((uchar*)cache_mngr, MYF(0));
- return 0;
-}
-
-/**
- This function flushes a transactional cache upon commit/rollback.
-
- @param thd The thread whose transaction should be flushed
- @param cache_mngr Pointer to the cache data to be flushed
- @param end_ev The end event either commit/rollback.
+ uint i;
+ char buff[FN_REFLEN], ext_buf[FN_REFLEN];
+ struct st_my_dir *dir_info;
+ reg1 struct fileinfo *file_info;
+ ulong max_found= 0, next= 0, number= 0;
+ size_t buf_length, length;
+ char *start, *end;
+ int error= 0;
+ DBUG_ENTER("find_uniq_filename");
- @return
- nonzero if an error pops up when flushing the transactional cache.
-*/
-static int
-binlog_flush_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr,
- Log_event *end_ev)
-{
- DBUG_ENTER("binlog_flush_trx_cache");
- int error=0;
- IO_CACHE *cache_log= &cache_mngr->trx_cache.cache_log;
+ length= dirname_part(buff, name, &buf_length);
+ start= name + length;
+ end= strend(start);
- /*
- This function handles transactional changes and as such
- this flag equals to true.
- */
- bool const is_transactional= TRUE;
+ *end='.';
+ length= (size_t) (end - start + 1);
- if (thd->binlog_flush_pending_rows_event(TRUE, is_transactional))
+ if (!(dir_info= my_dir(buff,MYF(MY_DONT_SORT))))
+ { // This shouldn't happen
+ strmov(end,".1"); // use name+1
DBUG_RETURN(1);
- /*
- Doing a commit or a rollback including non-transactional tables,
- i.e., ending a transaction where we might write the transaction
- cache to the binary log.
-
- We can always end the statement when ending a transaction since
- transactions are not allowed inside stored functions. If they
- were, we would have to ensure that we're not ending a statement
- inside a stored function.
- */
- error= mysql_bin_log.write(thd, &cache_mngr->trx_cache.cache_log, end_ev,
- cache_mngr->trx_cache.has_incident());
- cache_mngr->reset_cache(&cache_mngr->trx_cache);
-
- /*
- We need to step the table map version after writing the
- transaction cache to disk.
- */
- mysql_bin_log.update_table_map_version();
- statistic_increment(binlog_cache_use, &LOCK_status);
- if (cache_log->disk_writes != 0)
+ }
+ file_info= dir_info->dir_entry;
+ for (i= dir_info->number_off_files ; i-- ; file_info++)
{
- statistic_increment(binlog_cache_disk_use, &LOCK_status);
- cache_log->disk_writes= 0;
+ if (bcmp((uchar*) file_info->name, (uchar*) start, length) == 0 &&
+ test_if_number(file_info->name+length, &number,0))
+ {
+ set_if_bigger(max_found,(ulong) number);
+ }
}
+ my_dirend(dir_info);
- DBUG_ASSERT(cache_mngr->trx_cache.empty());
- DBUG_RETURN(error);
-}
-
-/**
- This function truncates the transactional cache upon committing or rolling
- back either a transaction or a statement.
-
- @param thd The thread whose transaction should be flushed
- @param cache_mngr Pointer to the cache data to be flushed
- @param all @c true means truncate the transaction, otherwise the
- statement must be truncated.
-
- @return
- nonzero if an error pops up when truncating the transactional cache.
-*/
-static int
-binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all)
-{
- DBUG_ENTER("binlog_truncate_trx_cache");
- int error=0;
- /*
- This function handles transactional changes and as such this flag
- equals to true.
- */
- bool const is_transactional= TRUE;
-
- DBUG_PRINT("info", ("thd->options={ %s%s}, transaction: %s",
- FLAGSTR(thd->options, OPTION_NOT_AUTOCOMMIT),
- FLAGSTR(thd->options, OPTION_BEGIN),
- all ? "all" : "stmt"));
- /*
- If rolling back an entire transaction or a single statement not
- inside a transaction, we reset the transaction cache.
- */
- thd->binlog_remove_pending_rows_event(TRUE, is_transactional);
- if (all || !thd->in_multi_stmt_transaction())
+ /* check if reached the maximum possible extension number */
+ if ((max_found == MAX_LOG_UNIQUE_FN_EXT))
{
- if (cache_mngr->trx_cache.has_incident())
- error= mysql_bin_log.write_incident(thd, TRUE);
+ sql_print_error("Log filename extension number exhausted: %06lu. \
+Please fix this by archiving old logs and \
+updating the index files.", max_found);
+ error= 1;
+ goto end;
+ }
- cache_mngr->reset_cache(&cache_mngr->trx_cache);
+ next= max_found + 1;
+ sprintf(ext_buf, "%06lu", next);
+ *end++='.';
- thd->clear_binlog_table_maps();
+ /*
+ Check if the generated extension size + the file name exceeds the
+ buffer size used. If one did not check this, then the filename might be
+ truncated, resulting in error.
+ */
+ if (((strlen(ext_buf) + (end - name)) >= FN_REFLEN))
+ {
+ sql_print_error("Log filename too large: %s%s (%lu). \
+Please fix this by archiving old logs and updating the \
+index files.", name, ext_buf, (strlen(ext_buf) + (end - name)));
+ error= 1;
+ goto end;
}
- /*
- If rolling back a statement in a transaction, we truncate the
- transaction cache to remove the statement.
- */
- else
- cache_mngr->trx_cache.restore_prev_position();
- /*
- We need to step the table map version on a rollback to ensure that a new
- table map event is generated instead of the one that was written to the
- thrown-away transaction cache.
- */
- mysql_bin_log.update_table_map_version();
+ sprintf(end, "%06lu", next);
+
+ /* print warning if reaching the end of available extensions. */
+ if ((next > (MAX_LOG_UNIQUE_FN_EXT - LOG_WARN_UNIQUE_FN_EXT_LEFT)))
+ sql_print_warning("Next log extension: %lu. \
+Remaining log filename extensions: %lu. \
+Please consider archiving some logs.", next, (MAX_LOG_UNIQUE_FN_EXT - next));
- DBUG_ASSERT(thd->binlog_get_pending_rows_event(is_transactional) == NULL);
+end:
DBUG_RETURN(error);
}
-static int binlog_prepare(handlerton *hton, THD *thd, bool all)
+
+void MYSQL_LOG::init(enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg)
{
- /*
- do nothing.
- just pretend we can do 2pc, so that MySQL won't
- switch to 1pc.
- real work will be done in MYSQL_BIN_LOG::log_xid()
- */
- return 0;
+ DBUG_ENTER("MYSQL_LOG::init");
+ log_type= log_type_arg;
+ io_cache_type= io_cache_type_arg;
+ DBUG_PRINT("info",("log_type: %d", log_type));
+ DBUG_VOID_RETURN;
}
-/**
- This function flushes the non-transactional to the binary log upon
- committing or rolling back a statement.
-
- @param thd The thread whose transaction should be flushed
- @param cache_mngr Pointer to the cache data to be flushed
- @return
- nonzero if an error pops up when flushing the non-transactional cache.
-*/
-static int
-binlog_flush_stmt_cache(THD *thd, binlog_cache_mngr *cache_mngr)
+bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name,
+ const char *new_name,
+ enum_log_type log_type_arg,
+ enum cache_type io_cache_type_arg)
{
- int error= 0;
- DBUG_ENTER("binlog_flush_stmt_cache");
- /*
- If we are flushing the statement cache, it means that the changes get
- through otherwise the cache is empty and this routine should not be called.
- */
- DBUG_ASSERT(cache_mngr->stmt_cache.has_incident() == FALSE);
- /*
- This function handles non-transactional changes and as such this flag equals
- to false.
- */
- bool const is_transactional= FALSE;
- IO_CACHE *cache_log= &cache_mngr->stmt_cache.cache_log;
- thd->binlog_flush_pending_rows_event(TRUE, is_transactional);
- Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, FALSE, TRUE, 0);
- if ((error= mysql_bin_log.write(thd, cache_log, &qev,
- cache_mngr->stmt_cache.has_incident())))
- DBUG_RETURN(error);
- cache_mngr->reset_cache(&cache_mngr->stmt_cache);
+ init(log_type_arg, io_cache_type_arg);
- /*
- We need to step the table map version after writing the
- transaction cache to disk.
- */
- mysql_bin_log.update_table_map_version();
- statistic_increment(binlog_cache_use, &LOCK_status);
- if (cache_log->disk_writes != 0)
- {
- statistic_increment(binlog_cache_disk_use, &LOCK_status);
- cache_log->disk_writes= 0;
- }
- DBUG_RETURN(error);
+ if (new_name && !strmov(log_file_name, new_name))
+ return TRUE;
+ else if (!new_name && generate_new_name(log_file_name, log_name))
+ return TRUE;
+
+ return FALSE;
}
-/**
- This function is called once after each statement.
- It has the responsibility to flush the caches to the binary log on commits.
+/*
+ Open a (new) log file.
+
+ SYNOPSIS
+ open()
+
+ log_name The name of the log to open
+ log_type_arg The type of the log. E.g. LOG_NORMAL
+ new_name The new name for the logfile. This is only needed
+ when the method is used to open the binlog file.
+ io_cache_type_arg The type of the IO_CACHE to use for this log file
- @param hton The binlog handlerton.
- @param thd The client thread that executes the transaction.
- @param all This is @c true if this is a real transaction commit, and
- @false otherwise.
+ DESCRIPTION
+ Open the logfile, init IO_CACHE and write startup messages
+ (in case of general and slow query logs).
- @see handlerton::commit
+ RETURN VALUES
+ 0 ok
+ 1 error
*/
-static int binlog_commit(handlerton *hton, THD *thd, bool all)
+
+bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
+ const char *new_name, enum cache_type io_cache_type_arg)
{
- int error= 0;
- DBUG_ENTER("binlog_commit");
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
- bool const in_transaction= thd->in_multi_stmt_transaction();
+ char buff[FN_REFLEN];
+ File file= -1;
+ int open_flags= O_CREAT | O_BINARY;
+ DBUG_ENTER("MYSQL_LOG::open");
+ DBUG_PRINT("enter", ("log_type: %d", (int) log_type_arg));
- DBUG_PRINT("debug",
- ("all: %d, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
- all,
- YESNO(in_transaction),
- YESNO(thd->transaction.all.modified_non_trans_table),
- YESNO(thd->transaction.stmt.modified_non_trans_table)));
+ write_error= 0;
- if (!cache_mngr->stmt_cache.empty())
+ if (!(name= my_strdup(log_name, MYF(MY_WME))))
{
- binlog_flush_stmt_cache(thd, cache_mngr);
+ name= (char *)log_name; // for the error message
+ goto err;
}
- if (cache_mngr->trx_cache.empty())
- {
- /*
- we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
- */
- cache_mngr->reset_cache(&cache_mngr->trx_cache);
- DBUG_RETURN(0);
- }
+ if (init_and_set_log_file_name(name, new_name,
+ log_type_arg, io_cache_type_arg))
+ goto err;
- /*
- We commit the transaction if:
- - We are not in a transaction and committing a statement, or
- - We are in a transaction and a full transaction is committed.
- Otherwise, we accumulate the changes.
- */
- if (!in_transaction || all)
+ if (io_cache_type == SEQ_READ_APPEND)
+ open_flags |= O_RDWR | O_APPEND;
+ else
+ open_flags |= O_WRONLY | (log_type == LOG_BIN ? 0 : O_APPEND);
+
+ db[0]= 0;
+
+ if ((file= my_open(log_file_name, open_flags,
+ MYF(MY_WME | ME_WAITTANG))) < 0 ||
+ init_io_cache(&log_file, file, IO_SIZE, io_cache_type,
+ my_tell(file, MYF(MY_WME)), 0,
+ MYF(MY_WME | MY_NABP |
+ ((log_type == LOG_BIN) ? MY_WAIT_IF_FULL : 0))))
+ goto err;
+
+ if (log_type == LOG_NORMAL)
{
- Query_log_event qev(thd, STRING_WITH_LEN("COMMIT"), TRUE, FALSE, TRUE, 0);
- error= binlog_flush_trx_cache(thd, cache_mngr, &qev);
+ char *end;
+ int len=my_snprintf(buff, sizeof(buff), "%s, Version: %s (%s). "
+#ifdef EMBEDDED_LIBRARY
+ "embedded library\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT
+#elif __NT__
+ "started with:\nTCP Port: %d, Named Pipe: %s\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT,
+ mysqld_port, mysqld_unix_port
+#else
+ "started with:\nTcp port: %d Unix socket: %s\n",
+ my_progname, server_version, MYSQL_COMPILATION_COMMENT,
+ mysqld_port, mysqld_unix_port
+#endif
+ );
+ end= strnmov(buff + len, "Time Id Command Argument\n",
+ sizeof(buff) - len);
+ if (my_b_write(&log_file, (uchar*) buff, (uint) (end-buff)) ||
+ flush_io_cache(&log_file))
+ goto err;
}
+ log_state= LOG_OPENED;
+ DBUG_RETURN(0);
+
+err:
+ sql_print_error("Could not use %s for logging (error %d). \
+Turning logging off for the whole duration of the MySQL server process. \
+To turn it on again: fix the cause, \
+shutdown the MySQL server and restart it.", name, errno);
+ if (file >= 0)
+ my_close(file, MYF(0));
+ end_io_cache(&log_file);
+ safeFree(name);
+ log_state= LOG_CLOSED;
+ DBUG_RETURN(1);
+}
+
+MYSQL_LOG::MYSQL_LOG()
+ : name(0), write_error(FALSE), inited(FALSE), log_type(LOG_UNKNOWN),
+ log_state(LOG_CLOSED)
+{
/*
- This is part of the stmt rollback.
+ We don't want to initialize LOCK_Log here as such initialization depends on
+ safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
+ called only in main(). Doing initialization here would make it happen
+ before main().
*/
- if (!all)
- cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
- DBUG_RETURN(error);
+ bzero((char*) &log_file, sizeof(log_file));
}
-/**
- This function is called when a transaction or a statement is rolled back.
+void MYSQL_LOG::init_pthread_objects()
+{
+ DBUG_ASSERT(inited == 0);
+ inited= 1;
+ (void) pthread_mutex_init(&LOCK_log, MY_MUTEX_INIT_SLOW);
+}
+
+/*
+ Close the log file
- @param hton The binlog handlerton.
- @param thd The client thread that executes the transaction.
- @param all This is @c true if this is a real transaction rollback, and
- @false otherwise.
+ SYNOPSIS
+ close()
+ exiting Bitmask. For the slow and general logs the only used bit is
+ LOG_CLOSE_TO_BE_OPENED. This is used if we intend to call
+ open at once after close.
- @see handlerton::rollback
+ NOTES
+ One can do an open on the object at once after doing a close.
+ The internal structures are not freed until cleanup() is called
*/
-static int binlog_rollback(handlerton *hton, THD *thd, bool all)
-{
- DBUG_ENTER("binlog_rollback");
- int error=0;
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- DBUG_PRINT("debug", ("all: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s",
- YESNO(all),
- YESNO(thd->transaction.all.modified_non_trans_table),
- YESNO(thd->transaction.stmt.modified_non_trans_table)));
- /*
- If an incident event is set we do not flush the content of the statement
- cache because it may be corrupted.
- */
- if (cache_mngr->stmt_cache.has_incident())
- {
- mysql_bin_log.write_incident(thd, TRUE);
- cache_mngr->reset_cache(&cache_mngr->stmt_cache);
- }
- else if (!cache_mngr->stmt_cache.empty())
+void MYSQL_LOG::close(uint exiting)
+{ // One can't set log_type here!
+ DBUG_ENTER("MYSQL_LOG::close");
+ DBUG_PRINT("enter",("exiting: %d", (int) exiting));
+ if (log_state == LOG_OPENED)
{
- binlog_flush_stmt_cache(thd, cache_mngr);
- }
+ end_io_cache(&log_file);
- if (cache_mngr->trx_cache.empty())
- {
- /*
- we're here because cache_log was flushed in MYSQL_BIN_LOG::log_xid()
- */
- cache_mngr->reset_cache(&cache_mngr->trx_cache);
- DBUG_RETURN(0);
- }
+ if (my_sync(log_file.file, MYF(MY_WME)) && ! write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
+ }
- if (mysql_bin_log.check_write_error(thd))
- {
- /*
- "all == true" means that a "rollback statement" triggered the error and
- this function was called. However, this must not happen as a rollback
- is written directly to the binary log. And in auto-commit mode, a single
- statement that is rolled back has the flag all == false.
- */
- DBUG_ASSERT(!all);
- /*
- We reach this point if the effect of a statement did not properly get into
- a cache and need to be rolled back.
- */
- error= binlog_truncate_trx_cache(thd, cache_mngr, all);
- }
- else
- {
- /*
- We flush the cache wrapped in a beging/rollback if:
- . aborting a transcation that modified a non-transactional table or;
- . aborting a statement that modified both transactional and
- non-transctional tables but which is not in the boundaries of any
- transaction;
- . the OPTION_KEEP_LOG is activate.
- */
- if (thd->variables.binlog_format == BINLOG_FORMAT_STMT &&
- ((all && thd->transaction.all.modified_non_trans_table) ||
- (!all && thd->transaction.stmt.modified_non_trans_table &&
- !thd->in_multi_stmt_transaction()) ||
- (thd->options & OPTION_KEEP_LOG)))
+ if (my_close(log_file.file, MYF(MY_WME)) && ! write_error)
{
- Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, FALSE, TRUE, 0);
- error= binlog_flush_trx_cache(thd, cache_mngr, &qev);
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
}
- /*
- Otherwise, we simply truncate the cache as there is no change on
- non-transactional tables as follows.
- */
- else if (all || (!all &&
- (!thd->transaction.stmt.modified_non_trans_table ||
- !thd->in_multi_stmt_transaction() ||
- thd->variables.binlog_format != BINLOG_FORMAT_STMT)))
- error= binlog_truncate_trx_cache(thd, cache_mngr, all);
}
- /*
- This is part of the stmt rollback.
- */
- if (!all)
- cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
- DBUG_RETURN(error);
+ log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
+ safeFree(name);
+ DBUG_VOID_RETURN;
}
-void MYSQL_BIN_LOG::set_write_error(THD *thd)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::set_write_error");
-
- write_error= 1;
-
- if (check_write_error(thd))
- DBUG_VOID_RETURN;
-
- if (my_errno == EFBIG)
- my_message(ER_TRANS_CACHE_FULL, ER(ER_TRANS_CACHE_FULL), MYF(MY_WME));
- else
- my_error(ER_ERROR_ON_WRITE, MYF(MY_WME), name, errno);
+/** This is called only once. */
+void MYSQL_LOG::cleanup()
+{
+ DBUG_ENTER("cleanup");
+ if (inited)
+ {
+ inited= 0;
+ (void) pthread_mutex_destroy(&LOCK_log);
+ close(0);
+ }
DBUG_VOID_RETURN;
}
-bool MYSQL_BIN_LOG::check_write_error(THD *thd)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::check_write_error");
-
- bool checked= FALSE;
- if (!thd->is_error())
- DBUG_RETURN(checked);
-
- switch (thd->main_da.sql_errno())
+int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name)
+{
+ fn_format(new_name, log_name, mysql_data_home, "", 4);
+ if (log_type == LOG_BIN)
{
- case ER_TRANS_CACHE_FULL:
- case ER_ERROR_ON_WRITE:
- case ER_BINLOG_LOGGING_IMPOSSIBLE:
- checked= TRUE;
- break;
+ if (!fn_ext(log_name)[0])
+ {
+ if (find_uniq_filename(new_name))
+ {
+ /*
+ This should be treated as error once propagation of error further
+ up in the stack gets proper handling.
+ */
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_NO_UNIQUE_LOGFILE, ER(ER_NO_UNIQUE_LOGFILE),
+ log_name);
+ sql_print_error(ER(ER_NO_UNIQUE_LOGFILE), log_name);
+ return 1;
+ }
+ }
}
-
- DBUG_RETURN(checked);
+ return 0;
}
-/**
- @note
- How do we handle this (unlikely but legal) case:
- @verbatim
- [transaction] + [update to non-trans table] + [rollback to savepoint] ?
- @endverbatim
- The problem occurs when a savepoint is before the update to the
- non-transactional table. Then when there's a rollback to the savepoint, if we
- simply truncate the binlog cache, we lose the part of the binlog cache where
- the update is. If we want to not lose it, we need to write the SAVEPOINT
- command and the ROLLBACK TO SAVEPOINT command to the binlog cache. The latter
- is easy: it's just write at the end of the binlog cache, but the former
- should be *inserted* to the place where the user called SAVEPOINT. The
- solution is that when the user calls SAVEPOINT, we write it to the binlog
- cache (so no need to later insert it). As transactions are never intermixed
- in the binary log (i.e. they are serialized), we won't have conflicts with
- savepoint names when using mysqlbinlog or in the slave SQL thread.
- Then when ROLLBACK TO SAVEPOINT is called, if we updated some
- non-transactional table, we don't truncate the binlog cache but instead write
- ROLLBACK TO SAVEPOINT to it; otherwise we truncate the binlog cache (which
- will chop the SAVEPOINT command from the binlog cache, which is good as in
- that case there is no need to have it in the binlog).
-*/
-static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv)
-{
- DBUG_ENTER("binlog_savepoint_set");
+/*
+ Reopen the log file
- binlog_trans_log_savepos(thd, (my_off_t*) sv);
- /* Write it to the binary log */
+ SYNOPSIS
+ reopen_file()
+
+ DESCRIPTION
+ Reopen the log file. The method is used during FLUSH LOGS
+ and locks LOCK_log mutex
+*/
- int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED);
- int const error=
- thd->binlog_query(THD::STMT_QUERY_TYPE,
- thd->query(), thd->query_length(), TRUE, FALSE, FALSE,
- errcode);
- DBUG_RETURN(error);
-}
-static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
+void MYSQL_QUERY_LOG::reopen_file()
{
- DBUG_ENTER("binlog_savepoint_rollback");
+ char *save_name;
- /*
- Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some
- non-transactional table. Otherwise, truncate the binlog cache starting
- from the SAVEPOINT command.
- */
- if (unlikely(thd->transaction.all.modified_non_trans_table ||
- (thd->options & OPTION_KEEP_LOG)))
+ DBUG_ENTER("MYSQL_LOG::reopen_file");
+ if (!is_open())
{
- int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED);
- int error=
- thd->binlog_query(THD::STMT_QUERY_TYPE,
- thd->query(), thd->query_length(), TRUE, FALSE, FALSE,
- errcode);
- DBUG_RETURN(error);
+ DBUG_PRINT("info",("log is closed"));
+ DBUG_VOID_RETURN;
}
- binlog_trans_log_truncate(thd, *(my_off_t*)sv);
- DBUG_RETURN(0);
-}
+ pthread_mutex_lock(&LOCK_log);
-int check_binlog_magic(IO_CACHE* log, const char** errmsg)
-{
- char magic[4];
- DBUG_ASSERT(my_b_tell(log) == 0);
+ save_name= name;
+ name= 0; // Don't free name
+ close(LOG_CLOSE_TO_BE_OPENED);
- if (my_b_read(log, (uchar*) magic, sizeof(magic)))
- {
- *errmsg = "I/O error reading the header from the binary log";
- sql_print_error("%s, errno=%d, io cache code=%d", *errmsg, my_errno,
- log->error);
- return 1;
- }
- if (memcmp(magic, BINLOG_MAGIC, sizeof(magic)))
- {
- *errmsg = "Binlog has bad magic number; It's not a binary log file that can be used by this version of MySQL";
- return 1;
- }
- return 0;
-}
+ /*
+ Note that at this point, log_state != LOG_CLOSED (important for is_open()).
+ */
+ open(save_name, log_type, 0, io_cache_type);
+ my_free(save_name, MYF(0));
-File open_binlog(IO_CACHE *log, const char *log_file_name, const char **errmsg)
-{
- File file;
- DBUG_ENTER("open_binlog");
-
- if ((file = my_open(log_file_name, O_RDONLY | O_BINARY | O_SHARE,
- MYF(MY_WME))) < 0)
- {
- sql_print_error("Failed to open log (file '%s', errno %d)",
- log_file_name, my_errno);
- *errmsg = "Could not open log file";
- goto err;
- }
- if (init_io_cache(log, file, IO_SIZE*2, READ_CACHE, 0, 0,
- MYF(MY_WME|MY_DONT_CHECK_FILESIZE)))
- {
- sql_print_error("Failed to create a cache on log (file '%s')",
- log_file_name);
- *errmsg = "Could not open log file";
- goto err;
- }
- if (check_binlog_magic(log,errmsg))
- goto err;
- DBUG_RETURN(file);
-
-err:
- if (file >= 0)
- {
- my_close(file,MYF(0));
- end_io_cache(log);
- }
- DBUG_RETURN(-1);
-}
-
-#ifdef __NT__
-static int eventSource = 0;
-
-static void setup_windows_event_source()
-{
- HKEY hRegKey= NULL;
- DWORD dwError= 0;
- TCHAR szPath[MAX_PATH];
- DWORD dwTypes;
-
- if (eventSource) // Ensure that we are only called once
- return;
- eventSource= 1;
-
- // Create the event source registry key
- dwError= RegCreateKey(HKEY_LOCAL_MACHINE,
- "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\MySQL",
- &hRegKey);
-
- /* Name of the PE module that contains the message resource */
- GetModuleFileName(NULL, szPath, MAX_PATH);
-
- /* Register EventMessageFile */
- dwError = RegSetValueEx(hRegKey, "EventMessageFile", 0, REG_EXPAND_SZ,
- (PBYTE) szPath, (DWORD) (strlen(szPath) + 1));
-
- /* Register supported event types */
- dwTypes= (EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
- EVENTLOG_INFORMATION_TYPE);
- dwError= RegSetValueEx(hRegKey, "TypesSupported", 0, REG_DWORD,
- (LPBYTE) &dwTypes, sizeof dwTypes);
-
- RegCloseKey(hRegKey);
-}
-
-#endif /* __NT__ */
-
-
-/**
- Find a unique filename for 'filename.#'.
-
- Set '#' to the number next to the maximum found in the most
- recent log file extension.
-
- This function will return nonzero if: (i) the generated name
- exceeds FN_REFLEN; (ii) if the number of extensions is exhausted;
- or (iii) some other error happened while examining the filesystem.
-
- @return
- nonzero if not possible to get unique filename.
-*/
-
-static int find_uniq_filename(char *name)
-{
- uint i;
- char buff[FN_REFLEN], ext_buf[FN_REFLEN];
- struct st_my_dir *dir_info;
- reg1 struct fileinfo *file_info;
- ulong max_found= 0, next= 0, number= 0;
- size_t buf_length, length;
- char *start, *end;
- int error= 0;
- DBUG_ENTER("find_uniq_filename");
-
- length= dirname_part(buff, name, &buf_length);
- start= name + length;
- end= strend(start);
-
- *end='.';
- length= (size_t) (end - start + 1);
-
- if (!(dir_info= my_dir(buff,MYF(MY_DONT_SORT))))
- { // This shouldn't happen
- strmov(end,".1"); // use name+1
- DBUG_RETURN(1);
- }
- file_info= dir_info->dir_entry;
- for (i= dir_info->number_off_files ; i-- ; file_info++)
- {
- if (bcmp((uchar*) file_info->name, (uchar*) start, length) == 0 &&
- test_if_number(file_info->name+length, &number,0))
- {
- set_if_bigger(max_found,(ulong) number);
- }
- }
- my_dirend(dir_info);
-
- /* check if reached the maximum possible extension number */
- if ((max_found == MAX_LOG_UNIQUE_FN_EXT))
- {
- sql_print_error("Log filename extension number exhausted: %06lu. \
-Please fix this by archiving old logs and \
-updating the index files.", max_found);
- error= 1;
- goto end;
- }
-
- next= max_found + 1;
- sprintf(ext_buf, "%06lu", next);
- *end++='.';
-
- /*
- Check if the generated extension size + the file name exceeds the
- buffer size used. If one did not check this, then the filename might be
- truncated, resulting in error.
- */
- if (((strlen(ext_buf) + (end - name)) >= FN_REFLEN))
- {
- sql_print_error("Log filename too large: %s%s (%lu). \
-Please fix this by archiving old logs and updating the \
-index files.", name, ext_buf, (strlen(ext_buf) + (end - name)));
- error= 1;
- goto end;
- }
-
- sprintf(end, "%06lu", next);
-
- /* print warning if reaching the end of available extensions. */
- if ((next > (MAX_LOG_UNIQUE_FN_EXT - LOG_WARN_UNIQUE_FN_EXT_LEFT)))
- sql_print_warning("Next log extension: %lu. \
-Remaining log filename extensions: %lu. \
-Please consider archiving some logs.", next, (MAX_LOG_UNIQUE_FN_EXT - next));
-
-end:
- DBUG_RETURN(error);
-}
-
-
-void MYSQL_LOG::init(enum_log_type log_type_arg,
- enum cache_type io_cache_type_arg)
-{
- DBUG_ENTER("MYSQL_LOG::init");
- log_type= log_type_arg;
- io_cache_type= io_cache_type_arg;
- DBUG_PRINT("info",("log_type: %d", log_type));
- DBUG_VOID_RETURN;
-}
-
-
-bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name,
- const char *new_name,
- enum_log_type log_type_arg,
- enum cache_type io_cache_type_arg)
-{
- init(log_type_arg, io_cache_type_arg);
-
- if (new_name && !strmov(log_file_name, new_name))
- return TRUE;
- else if (!new_name && generate_new_name(log_file_name, log_name))
- return TRUE;
-
- return FALSE;
-}
-
-
-/*
- Open a (new) log file.
-
- SYNOPSIS
- open()
-
- log_name The name of the log to open
- log_type_arg The type of the log. E.g. LOG_NORMAL
- new_name The new name for the logfile. This is only needed
- when the method is used to open the binlog file.
- io_cache_type_arg The type of the IO_CACHE to use for this log file
-
- DESCRIPTION
- Open the logfile, init IO_CACHE and write startup messages
- (in case of general and slow query logs).
-
- RETURN VALUES
- 0 ok
- 1 error
-*/
-
-bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg,
- const char *new_name, enum cache_type io_cache_type_arg)
-{
- char buff[FN_REFLEN];
- File file= -1;
- int open_flags= O_CREAT | O_BINARY;
- DBUG_ENTER("MYSQL_LOG::open");
- DBUG_PRINT("enter", ("log_type: %d", (int) log_type_arg));
-
- write_error= 0;
-
- if (!(name= my_strdup(log_name, MYF(MY_WME))))
- {
- name= (char *)log_name; // for the error message
- goto err;
- }
-
- if (init_and_set_log_file_name(name, new_name,
- log_type_arg, io_cache_type_arg))
- goto err;
-
- if (io_cache_type == SEQ_READ_APPEND)
- open_flags |= O_RDWR | O_APPEND;
- else
- open_flags |= O_WRONLY | (log_type == LOG_BIN ? 0 : O_APPEND);
-
- db[0]= 0;
-
- if ((file= my_open(log_file_name, open_flags,
- MYF(MY_WME | ME_WAITTANG))) < 0 ||
- init_io_cache(&log_file, file, IO_SIZE, io_cache_type,
- my_tell(file, MYF(MY_WME)), 0,
- MYF(MY_WME | MY_NABP |
- ((log_type == LOG_BIN) ? MY_WAIT_IF_FULL : 0))))
- goto err;
-
- if (log_type == LOG_NORMAL)
- {
- char *end;
- int len=my_snprintf(buff, sizeof(buff), "%s, Version: %s (%s). "
-#ifdef EMBEDDED_LIBRARY
- "embedded library\n",
- my_progname, server_version, MYSQL_COMPILATION_COMMENT
-#elif __NT__
- "started with:\nTCP Port: %d, Named Pipe: %s\n",
- my_progname, server_version, MYSQL_COMPILATION_COMMENT,
- mysqld_port, mysqld_unix_port
-#else
- "started with:\nTcp port: %d Unix socket: %s\n",
- my_progname, server_version, MYSQL_COMPILATION_COMMENT,
- mysqld_port, mysqld_unix_port
-#endif
- );
- end= strnmov(buff + len, "Time Id Command Argument\n",
- sizeof(buff) - len);
- if (my_b_write(&log_file, (uchar*) buff, (uint) (end-buff)) ||
- flush_io_cache(&log_file))
- goto err;
- }
-
- log_state= LOG_OPENED;
- DBUG_RETURN(0);
-
-err:
- sql_print_error("Could not use %s for logging (error %d). \
-Turning logging off for the whole duration of the MySQL server process. \
-To turn it on again: fix the cause, \
-shutdown the MySQL server and restart it.", name, errno);
- if (file >= 0)
- my_close(file, MYF(0));
- end_io_cache(&log_file);
- safeFree(name);
- log_state= LOG_CLOSED;
- DBUG_RETURN(1);
-}
-
-MYSQL_LOG::MYSQL_LOG()
- : name(0), write_error(FALSE), inited(FALSE), log_type(LOG_UNKNOWN),
- log_state(LOG_CLOSED)
-{
- /*
- We don't want to initialize LOCK_Log here as such initialization depends on
- safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
- called only in main(). Doing initialization here would make it happen
- before main().
- */
- bzero((char*) &log_file, sizeof(log_file));
-}
-
-void MYSQL_LOG::init_pthread_objects()
-{
- DBUG_ASSERT(inited == 0);
- inited= 1;
- (void) pthread_mutex_init(&LOCK_log, MY_MUTEX_INIT_SLOW);
-}
-
-/*
- Close the log file
-
- SYNOPSIS
- close()
- exiting Bitmask. For the slow and general logs the only used bit is
- LOG_CLOSE_TO_BE_OPENED. This is used if we intend to call
- open at once after close.
-
- NOTES
- One can do an open on the object at once after doing a close.
- The internal structures are not freed until cleanup() is called
-*/
-
-void MYSQL_LOG::close(uint exiting)
-{ // One can't set log_type here!
- DBUG_ENTER("MYSQL_LOG::close");
- DBUG_PRINT("enter",("exiting: %d", (int) exiting));
- if (log_state == LOG_OPENED)
- {
- end_io_cache(&log_file);
-
- if (my_sync(log_file.file, MYF(MY_WME)) && ! write_error)
- {
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
- }
-
- if (my_close(log_file.file, MYF(MY_WME)) && ! write_error)
- {
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
- }
- }
-
- log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
- safeFree(name);
- DBUG_VOID_RETURN;
-}
-
-/** This is called only once. */
-
-void MYSQL_LOG::cleanup()
-{
- DBUG_ENTER("cleanup");
- if (inited)
- {
- inited= 0;
- (void) pthread_mutex_destroy(&LOCK_log);
- close(0);
- }
- DBUG_VOID_RETURN;
-}
-
-
-int MYSQL_LOG::generate_new_name(char *new_name, const char *log_name)
-{
- fn_format(new_name, log_name, mysql_data_home, "", 4);
- if (log_type == LOG_BIN)
- {
- if (!fn_ext(log_name)[0])
- {
- if (find_uniq_filename(new_name))
- {
- /*
- This should be treated as error once propagation of error further
- up in the stack gets proper handling.
- */
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_NO_UNIQUE_LOGFILE, ER(ER_NO_UNIQUE_LOGFILE),
- log_name);
- sql_print_error(ER(ER_NO_UNIQUE_LOGFILE), log_name);
- return 1;
- }
- }
- }
- return 0;
-}
-
-
-/*
- Reopen the log file
-
- SYNOPSIS
- reopen_file()
-
- DESCRIPTION
- Reopen the log file. The method is used during FLUSH LOGS
- and locks LOCK_log mutex
-*/
-
-
-void MYSQL_QUERY_LOG::reopen_file()
-{
- char *save_name;
-
- DBUG_ENTER("MYSQL_LOG::reopen_file");
- if (!is_open())
- {
- DBUG_PRINT("info",("log is closed"));
- DBUG_VOID_RETURN;
- }
-
- pthread_mutex_lock(&LOCK_log);
-
- save_name= name;
- name= 0; // Don't free name
- close(LOG_CLOSE_TO_BE_OPENED);
-
- /*
- Note that at this point, log_state != LOG_CLOSED (important for is_open()).
- */
-
- open(save_name, log_type, 0, io_cache_type);
- my_free(save_name, MYF(0));
-
- pthread_mutex_unlock(&LOCK_log);
-
- DBUG_VOID_RETURN;
-}
-
-
-/*
- Write a command to traditional general log file
-
- SYNOPSIS
- write()
-
- event_time command start timestamp
- user_host the pointer to the string with user@host info
- user_host_len length of the user_host string. this is computed once
- and passed to all general log event handlers
- thread_id Id of the thread, issued a query
- command_type the type of the command being logged
- command_type_len the length of the string above
- sql_text the very text of the query being executed
- sql_text_len the length of sql_text string
-
- DESCRIPTION
-
- Log given command to to normal (not rotable) log file
-
- RETURN
- FASE - OK
- TRUE - error occured
-*/
-
-bool MYSQL_QUERY_LOG::write(time_t event_time, const char *user_host,
- uint user_host_len, int thread_id,
- const char *command_type, uint command_type_len,
- const char *sql_text, uint sql_text_len)
-{
- char buff[32];
- uint length= 0;
- char local_time_buff[MAX_TIME_SIZE];
- struct tm start;
- uint time_buff_len= 0;
-
- (void) pthread_mutex_lock(&LOCK_log);
-
- /* Test if someone closed between the is_open test and lock */
- if (is_open())
- {
- /* for testing output of timestamp and thread id */
- DBUG_EXECUTE_IF("reset_log_last_time", last_time= 0;);
-
- /* Note that my_b_write() assumes it knows the length for this */
- if (event_time != last_time)
- {
- last_time= event_time;
-
- localtime_r(&event_time, &start);
-
- time_buff_len= my_snprintf(local_time_buff, MAX_TIME_SIZE,
- "%02d%02d%02d %2d:%02d:%02d\t",
- start.tm_year % 100, start.tm_mon + 1,
- start.tm_mday, start.tm_hour,
- start.tm_min, start.tm_sec);
-
- if (my_b_write(&log_file, (uchar*) local_time_buff, time_buff_len))
- goto err;
- }
- else
- if (my_b_write(&log_file, (uchar*) "\t\t" ,2) < 0)
- goto err;
-
- /* command_type, thread_id */
- length= my_snprintf(buff, 32, "%5ld ", (long) thread_id);
-
- if (my_b_write(&log_file, (uchar*) buff, length))
- goto err;
-
- if (my_b_write(&log_file, (uchar*) command_type, command_type_len))
- goto err;
-
- if (my_b_write(&log_file, (uchar*) "\t", 1))
- goto err;
-
- /* sql_text */
- if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len))
- goto err;
-
- if (my_b_write(&log_file, (uchar*) "\n", 1) ||
- flush_io_cache(&log_file))
- goto err;
- }
-
- (void) pthread_mutex_unlock(&LOCK_log);
- return FALSE;
-err:
-
- if (!write_error)
- {
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
- }
- (void) pthread_mutex_unlock(&LOCK_log);
- return TRUE;
-}
-
-
-/*
- Log a query to the traditional slow log file
-
- SYNOPSIS
- write()
-
- thd THD of the query
- current_time current timestamp
- query_start_arg command start timestamp
- user_host the pointer to the string with user@host info
- user_host_len length of the user_host string. this is computed once
- and passed to all general log event handlers
- query_utime Amount of time the query took to execute (in microseconds)
- lock_utime Amount of time the query was locked (in microseconds)
- is_command The flag, which determines, whether the sql_text is a
- query or an administrator command.
- sql_text the very text of the query or administrator command
- processed
- sql_text_len the length of sql_text string
-
- DESCRIPTION
-
- Log a query to the slow log file.
-
- RETURN
- FALSE - OK
- TRUE - error occured
-*/
-
-bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time,
- time_t query_start_arg, const char *user_host,
- uint user_host_len, ulonglong query_utime,
- ulonglong lock_utime, bool is_command,
- const char *sql_text, uint sql_text_len)
-{
- bool error= 0;
- DBUG_ENTER("MYSQL_QUERY_LOG::write");
-
- (void) pthread_mutex_lock(&LOCK_log);
-
- if (!is_open())
- {
- (void) pthread_mutex_unlock(&LOCK_log);
- DBUG_RETURN(0);
- }
-
- if (is_open())
- { // Safety agains reopen
- int tmp_errno= 0;
- char buff[80], *end;
- char query_time_buff[22+7], lock_time_buff[22+7];
- uint buff_len;
- end= buff;
-
- if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
- {
- if (current_time != last_time)
- {
- last_time= current_time;
- struct tm start;
- localtime_r(¤t_time, &start);
-
- buff_len= my_snprintf(buff, sizeof buff,
- "# Time: %02d%02d%02d %2d:%02d:%02d\n",
- start.tm_year % 100, start.tm_mon + 1,
- start.tm_mday, start.tm_hour,
- start.tm_min, start.tm_sec);
-
- /* Note that my_b_write() assumes it knows the length for this */
- if (my_b_write(&log_file, (uchar*) buff, buff_len))
- tmp_errno= errno;
- }
- const uchar uh[]= "# User@Host: ";
- if (my_b_write(&log_file, uh, sizeof(uh) - 1))
- tmp_errno= errno;
- if (my_b_write(&log_file, (uchar*) user_host, user_host_len))
- tmp_errno= errno;
- if (my_b_write(&log_file, (uchar*) "\n", 1))
- tmp_errno= errno;
- }
- /* For slow query log */
- sprintf(query_time_buff, "%.6f", ulonglong2double(query_utime)/1000000.0);
- sprintf(lock_time_buff, "%.6f", ulonglong2double(lock_utime)/1000000.0);
- if (my_b_printf(&log_file,
- "# Query_time: %s Lock_time: %s"
- " Rows_sent: %lu Rows_examined: %lu\n",
- query_time_buff, lock_time_buff,
- (ulong) thd->sent_row_count,
- (ulong) thd->examined_row_count) == (uint) -1)
- tmp_errno= errno;
- if (thd->db && strcmp(thd->db, db))
- { // Database changed
- if (my_b_printf(&log_file,"use %s;\n",thd->db) == (uint) -1)
- tmp_errno= errno;
- strmov(db,thd->db);
- }
- if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
- {
- end=strmov(end, ",last_insert_id=");
- end=longlong10_to_str((longlong)
- thd->first_successful_insert_id_in_prev_stmt_for_binlog,
- end, -10);
- }
- // Save value if we do an insert.
- if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
- {
- if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
- {
- end=strmov(end,",insert_id=");
- end=longlong10_to_str((longlong)
- thd->auto_inc_intervals_in_cur_stmt_for_binlog.minimum(),
- end, -10);
- }
- }
-
- /*
- This info used to show up randomly, depending on whether the query
- checked the query start time or not. now we always write current
- timestamp to the slow log
- */
- end= strmov(end, ",timestamp=");
- end= int10_to_str((long) current_time, end, 10);
-
- if (end != buff)
- {
- *end++=';';
- *end='\n';
- if (my_b_write(&log_file, (uchar*) "SET ", 4) ||
- my_b_write(&log_file, (uchar*) buff + 1, (uint) (end-buff)))
- tmp_errno= errno;
- }
- if (is_command)
- {
- end= strxmov(buff, "# administrator command: ", NullS);
- buff_len= (ulong) (end - buff);
- my_b_write(&log_file, (uchar*) buff, buff_len);
- }
- if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len) ||
- my_b_write(&log_file, (uchar*) ";\n",2) ||
- flush_io_cache(&log_file))
- tmp_errno= errno;
- if (tmp_errno)
- {
- error= 1;
- if (! write_error)
- {
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), name, error);
- }
- }
- }
- (void) pthread_mutex_unlock(&LOCK_log);
- DBUG_RETURN(error);
-}
-
-
-/**
- @todo
- The following should be using fn_format(); We just need to
- first change fn_format() to cut the file name if it's too long.
-*/
-const char *MYSQL_LOG::generate_name(const char *log_name,
- const char *suffix,
- bool strip_ext, char *buff)
-{
- if (!log_name || !log_name[0])
- {
- strmake(buff, pidfile_name, FN_REFLEN - strlen(suffix) - 1);
- return (const char *)
- fn_format(buff, buff, "", suffix, MYF(MY_REPLACE_EXT|MY_REPLACE_DIR));
- }
- // get rid of extension if the log is binary to avoid problems
- if (strip_ext)
- {
- char *p= fn_ext(log_name);
- uint length= (uint) (p - log_name);
- strmake(buff, log_name, min(length, FN_REFLEN-1));
- return (const char*)buff;
- }
- return log_name;
-}
-
-
-
-MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period)
- :bytes_written(0), prepared_xids(0), file_id(1), open_count(1),
- need_start_event(TRUE), m_table_map_version(0),
- sync_period_ptr(sync_period),
- is_relay_log(0), signal_cnt(0),
- description_event_for_exec(0), description_event_for_queue(0)
-{
- /*
- We don't want to initialize locks here as such initialization depends on
- safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
- called only in main(). Doing initialization here would make it happen
- before main().
- */
- index_file_name[0] = 0;
- bzero((char*) &index_file, sizeof(index_file));
- bzero((char*) &purge_index_file, sizeof(purge_index_file));
-}
-
-/* this is called only once */
-
-void MYSQL_BIN_LOG::cleanup()
-{
- DBUG_ENTER("cleanup");
- if (inited)
- {
- inited= 0;
- close(LOG_CLOSE_INDEX|LOG_CLOSE_STOP_EVENT);
- delete description_event_for_queue;
- delete description_event_for_exec;
- (void) pthread_mutex_destroy(&LOCK_log);
- (void) pthread_mutex_destroy(&LOCK_index);
- (void) pthread_cond_destroy(&update_cond);
- }
- DBUG_VOID_RETURN;
-}
-
-
-/* Init binlog-specific vars */
-void MYSQL_BIN_LOG::init(bool no_auto_events_arg, ulong max_size_arg)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::init");
- no_auto_events= no_auto_events_arg;
- max_size= max_size_arg;
- DBUG_PRINT("info",("max_size: %lu", max_size));
- DBUG_VOID_RETURN;
-}
-
-
-void MYSQL_BIN_LOG::init_pthread_objects()
-{
- DBUG_ASSERT(inited == 0);
- inited= 1;
- (void) pthread_mutex_init(&LOCK_log, MY_MUTEX_INIT_SLOW);
- (void) pthread_mutex_init(&LOCK_index, MY_MUTEX_INIT_SLOW);
- (void) pthread_cond_init(&update_cond, 0);
-}
-
-
-bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
- const char *log_name, bool need_mutex)
-{
- File index_file_nr= -1;
- DBUG_ASSERT(!my_b_inited(&index_file));
-
- /*
- First open of this class instance
- Create an index file that will hold all file names uses for logging.
- Add new entries to the end of it.
- */
- myf opt= MY_UNPACK_FILENAME;
- if (!index_file_name_arg)
- {
- index_file_name_arg= log_name; // Use same basename for index file
- opt= MY_UNPACK_FILENAME | MY_REPLACE_EXT;
- }
- fn_format(index_file_name, index_file_name_arg, mysql_data_home,
- ".index", opt);
- if ((index_file_nr= my_open(index_file_name,
- O_RDWR | O_CREAT | O_BINARY ,
- MYF(MY_WME))) < 0 ||
- my_sync(index_file_nr, MYF(MY_WME)) ||
- init_io_cache(&index_file, index_file_nr,
- IO_SIZE, WRITE_CACHE,
- my_seek(index_file_nr,0L,MY_SEEK_END,MYF(0)),
- 0, MYF(MY_WME | MY_WAIT_IF_FULL)) ||
- DBUG_EVALUATE_IF("fault_injection_openning_index", 1, 0))
- {
- /*
- TODO: all operations creating/deleting the index file or a log, should
- call my_sync_dir() or my_sync_dir_by_file() to be durable.
- TODO: file creation should be done with my_create() not my_open().
- */
- if (index_file_nr >= 0)
- my_close(index_file_nr,MYF(0));
- return TRUE;
- }
-
-#ifdef HAVE_REPLICATION
- /*
- Sync the index by purging any binary log file that is not registered.
- In other words, either purge binary log files that were removed from
- the index but not purged from the file system due to a crash or purge
- any binary log file that was created but not register in the index
- due to a crash.
- */
-
- if (set_purge_index_file_name(index_file_name_arg) ||
- open_purge_index_file(FALSE) ||
- purge_index_entry(NULL, NULL, need_mutex) ||
- close_purge_index_file() ||
- DBUG_EVALUATE_IF("fault_injection_recovering_index", 1, 0))
- {
- sql_print_error("MYSQL_BIN_LOG::open_index_file failed to sync the index "
- "file.");
- return TRUE;
- }
-#endif
-
- return FALSE;
-}
-
-
-/**
- Open a (new) binlog file.
-
- - Open the log file and the index file. Register the new
- file name in it
- - When calling this when the file is in use, you must have a locks
- on LOCK_log and LOCK_index.
-
- @retval
- 0 ok
- @retval
- 1 error
-*/
-
-bool MYSQL_BIN_LOG::open(const char *log_name,
- enum_log_type log_type_arg,
- const char *new_name,
- enum cache_type io_cache_type_arg,
- bool no_auto_events_arg,
- ulong max_size_arg,
- bool null_created_arg,
- bool need_mutex)
-{
- File file= -1;
-
- DBUG_ENTER("MYSQL_BIN_LOG::open");
- DBUG_PRINT("enter",("log_type: %d",(int) log_type_arg));
-
- if (init_and_set_log_file_name(log_name, new_name, log_type_arg,
- io_cache_type_arg))
- {
- sql_print_error("MSYQL_BIN_LOG::open failed to generate new file name.");
- DBUG_RETURN(1);
- }
-
-#ifdef HAVE_REPLICATION
- if (open_purge_index_file(TRUE) ||
- register_create_index_entry(log_file_name) ||
- sync_purge_index_file() ||
- DBUG_EVALUATE_IF("fault_injection_registering_index", 1, 0))
- {
- sql_print_error("MSYQL_BIN_LOG::open failed to sync the index file.");
- DBUG_RETURN(1);
- }
- DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", abort(););
-#endif
-
- write_error= 0;
-
- /* open the main log file */
- if (MYSQL_LOG::open(log_name, log_type_arg, new_name,
- io_cache_type_arg))
- {
-#ifdef HAVE_REPLICATION
- close_purge_index_file();
-#endif
- DBUG_RETURN(1); /* all warnings issued */
- }
-
- init(no_auto_events_arg, max_size_arg);
-
- open_count++;
-
- DBUG_ASSERT(log_type == LOG_BIN);
-
- {
- bool write_file_name_to_index_file=0;
-
- if (!my_b_filelength(&log_file))
- {
- /*
- The binary log file was empty (probably newly created)
- This is the normal case and happens when the user doesn't specify
- an extension for the binary log files.
- In this case we write a standard header to it.
- */
- if (my_b_safe_write(&log_file, (uchar*) BINLOG_MAGIC,
- BIN_LOG_HEADER_SIZE))
- goto err;
- bytes_written+= BIN_LOG_HEADER_SIZE;
- write_file_name_to_index_file= 1;
- }
-
- if (need_start_event && !no_auto_events)
- {
- /*
- In 4.x we set need_start_event=0 here, but in 5.0 we want a Start event
- even if this is not the very first binlog.
- */
- Format_description_log_event s(BINLOG_VERSION);
- /*
- don't set LOG_EVENT_BINLOG_IN_USE_F for SEQ_READ_APPEND io_cache
- as we won't be able to reset it later
- */
- if (io_cache_type == WRITE_CACHE)
- s.flags|= LOG_EVENT_BINLOG_IN_USE_F;
- if (!s.is_valid())
- goto err;
- s.dont_set_created= null_created_arg;
- if (s.write(&log_file))
- goto err;
- bytes_written+= s.data_written;
- }
- if (description_event_for_queue &&
- description_event_for_queue->binlog_version>=4)
- {
- /*
- This is a relay log written to by the I/O slave thread.
- Write the event so that others can later know the format of this relay
- log.
- Note that this event is very close to the original event from the
- master (it has binlog version of the master, event types of the
- master), so this is suitable to parse the next relay log's event. It
- has been produced by
- Format_description_log_event::Format_description_log_event(char* buf,).
- Why don't we want to write the description_event_for_queue if this
- event is for format<4 (3.23 or 4.x): this is because in that case, the
- description_event_for_queue describes the data received from the
- master, but not the data written to the relay log (*conversion*),
- which is in format 4 (slave's).
- */
- /*
- Set 'created' to 0, so that in next relay logs this event does not
- trigger cleaning actions on the slave in
- Format_description_log_event::apply_event_impl().
- */
- description_event_for_queue->created= 0;
- /* Don't set log_pos in event header */
- description_event_for_queue->set_artificial_event();
-
- if (description_event_for_queue->write(&log_file))
- goto err;
- bytes_written+= description_event_for_queue->data_written;
- }
- if (flush_io_cache(&log_file) ||
- my_sync(log_file.file, MYF(MY_WME)))
- goto err;
-
- if (write_file_name_to_index_file)
- {
-#ifdef HAVE_REPLICATION
- DBUG_EXECUTE_IF("crash_create_critical_before_update_index", abort(););
-#endif
-
- DBUG_ASSERT(my_b_inited(&index_file) != 0);
- reinit_io_cache(&index_file, WRITE_CACHE,
- my_b_filelength(&index_file), 0, 0);
- /*
- As this is a new log file, we write the file name to the index
- file. As every time we write to the index file, we sync it.
- */
- if (DBUG_EVALUATE_IF("fault_injection_updating_index", 1, 0) ||
- my_b_write(&index_file, (uchar*) log_file_name,
- strlen(log_file_name)) ||
- my_b_write(&index_file, (uchar*) "\n", 1) ||
- flush_io_cache(&index_file) ||
- my_sync(index_file.file, MYF(MY_WME)))
- goto err;
-
-#ifdef HAVE_REPLICATION
- DBUG_EXECUTE_IF("crash_create_after_update_index", abort(););
-#endif
- }
- }
- log_state= LOG_OPENED;
-
-#ifdef HAVE_REPLICATION
- close_purge_index_file();
-#endif
-
- DBUG_RETURN(0);
-
-err:
-#ifdef HAVE_REPLICATION
- if (is_inited_purge_index_file())
- purge_index_entry(NULL, NULL, need_mutex);
- close_purge_index_file();
-#endif
- sql_print_error("Could not use %s for logging (error %d). \
-Turning logging off for the whole duration of the MySQL server process. \
-To turn it on again: fix the cause, \
-shutdown the MySQL server and restart it.", name, errno);
- if (file >= 0)
- my_close(file,MYF(0));
- end_io_cache(&log_file);
- end_io_cache(&index_file);
- safeFree(name);
- log_state= LOG_CLOSED;
- DBUG_RETURN(1);
-}
-
-
-int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo)
-{
- pthread_mutex_lock(&LOCK_log);
- int ret = raw_get_current_log(linfo);
- pthread_mutex_unlock(&LOCK_log);
- return ret;
-}
-
-int MYSQL_BIN_LOG::raw_get_current_log(LOG_INFO* linfo)
-{
- strmake(linfo->log_file_name, log_file_name, sizeof(linfo->log_file_name)-1);
- linfo->pos = my_b_tell(&log_file);
- return 0;
-}
-
-/**
- Move all data up in a file in an filename index file.
-
- We do the copy outside of the IO_CACHE as the cache buffers would just
- make things slower and more complicated.
- In most cases the copy loop should only do one read.
-
- @param index_file File to move
- @param offset Move everything from here to beginning
-
- @note
- File will be truncated to be 'offset' shorter or filled up with newlines
-
- @retval
- 0 ok
-*/
-
-#ifdef HAVE_REPLICATION
-
-static bool copy_up_file_and_fill(IO_CACHE *index_file, my_off_t offset)
-{
- int bytes_read;
- my_off_t init_offset= offset;
- File file= index_file->file;
- uchar io_buf[IO_SIZE*2];
- DBUG_ENTER("copy_up_file_and_fill");
-
- for (;; offset+= bytes_read)
- {
- (void) my_seek(file, offset, MY_SEEK_SET, MYF(0));
- if ((bytes_read= (int) my_read(file, io_buf, sizeof(io_buf), MYF(MY_WME)))
- < 0)
- goto err;
- if (!bytes_read)
- break; // end of file
- (void) my_seek(file, offset-init_offset, MY_SEEK_SET, MYF(0));
- if (my_write(file, io_buf, bytes_read, MYF(MY_WME | MY_NABP)))
- goto err;
- }
- /* The following will either truncate the file or fill the end with \n' */
- if (my_chsize(file, offset - init_offset, '\n', MYF(MY_WME)) ||
- my_sync(file, MYF(MY_WME)))
- goto err;
-
- /* Reset data in old index cache */
- reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 1);
- DBUG_RETURN(0);
-
-err:
- DBUG_RETURN(1);
-}
-
-#endif /* HAVE_REPLICATION */
-
-/**
- Find the position in the log-index-file for the given log name.
-
- @param linfo Store here the found log file name and position to
- the NEXT log file name in the index file.
- @param log_name Filename to find in the index file.
- Is a null pointer if we want to read the first entry
- @param need_lock Set this to 1 if the parent doesn't already have a
- lock on LOCK_index
-
- @note
- On systems without the truncate function the file will end with one or
- more empty lines. These will be ignored when reading the file.
-
- @retval
- 0 ok
- @retval
- LOG_INFO_EOF End of log-index-file found
- @retval
- LOG_INFO_IO Got IO error while reading file
-*/
-
-int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name,
- bool need_lock)
-{
- int error= 0;
- char *fname= linfo->log_file_name;
- uint log_name_len= log_name ? (uint) strlen(log_name) : 0;
- DBUG_ENTER("find_log_pos");
- DBUG_PRINT("enter",("log_name: %s", log_name ? log_name : "NULL"));
-
- /*
- Mutex needed because we need to make sure the file pointer does not
- move from under our feet
- */
- if (need_lock)
- pthread_mutex_lock(&LOCK_index);
- safe_mutex_assert_owner(&LOCK_index);
-
- /* As the file is flushed, we can't get an error here */
- (void) reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0);
-
- for (;;)
- {
- uint length;
- my_off_t offset= my_b_tell(&index_file);
-
- DBUG_EXECUTE_IF("simulate_find_log_pos_error",
- error= LOG_INFO_EOF; break;);
- /* If we get 0 or 1 characters, this is the end of the file */
- if ((length= my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
- {
- /* Did not find the given entry; Return not found or error */
- error= !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
- break;
- }
-
- // if the log entry matches, null string matching anything
- if (!log_name ||
- (log_name_len == length-1 && fname[log_name_len] == '\n' &&
- !memcmp(fname, log_name, log_name_len)))
- {
- DBUG_PRINT("info",("Found log file entry"));
- fname[length-1]=0; // remove last \n
- linfo->index_file_start_offset= offset;
- linfo->index_file_offset = my_b_tell(&index_file);
- break;
- }
- }
-
- if (need_lock)
- pthread_mutex_unlock(&LOCK_index);
- DBUG_RETURN(error);
-}
-
-
-/**
- Find the position in the log-index-file for the given log name.
-
- @param
- linfo Store here the next log file name and position to
- the file name after that.
- @param
- need_lock Set this to 1 if the parent doesn't already have a
- lock on LOCK_index
-
- @note
- - Before calling this function, one has to call find_log_pos()
- to set up 'linfo'
- - Mutex needed because we need to make sure the file pointer does not move
- from under our feet
-
- @retval
- 0 ok
- @retval
- LOG_INFO_EOF End of log-index-file found
- @retval
- LOG_INFO_IO Got IO error while reading file
-*/
-
-int MYSQL_BIN_LOG::find_next_log(LOG_INFO* linfo, bool need_lock)
-{
- int error= 0;
- uint length;
- char *fname= linfo->log_file_name;
-
- if (need_lock)
- pthread_mutex_lock(&LOCK_index);
- safe_mutex_assert_owner(&LOCK_index);
-
- /* As the file is flushed, we can't get an error here */
- (void) reinit_io_cache(&index_file, READ_CACHE, linfo->index_file_offset, 0,
- 0);
-
- linfo->index_file_start_offset= linfo->index_file_offset;
- if ((length=my_b_gets(&index_file, fname, FN_REFLEN)) <= 1)
- {
- error = !index_file.error ? LOG_INFO_EOF : LOG_INFO_IO;
- goto err;
- }
- fname[length-1]=0; // kill \n
- linfo->index_file_offset = my_b_tell(&index_file);
-
-err:
- if (need_lock)
- pthread_mutex_unlock(&LOCK_index);
- return error;
-}
-
-
-/**
- Delete all logs refered to in the index file.
- Start writing to a new log file.
-
- The new index file will only contain this file.
-
- @param thd Thread
-
- @note
- If not called from slave thread, write start event to new log
-
- @retval
- 0 ok
- @retval
- 1 error
-*/
-
-bool MYSQL_BIN_LOG::reset_logs(THD* thd)
-{
- LOG_INFO linfo;
- bool error=0;
- int err;
- const char* save_name;
- DBUG_ENTER("reset_logs");
-
- ha_reset_logs(thd);
- /*
- We need to get both locks to be sure that no one is trying to
- write to the index log file.
- */
- pthread_mutex_lock(&LOCK_log);
- pthread_mutex_lock(&LOCK_index);
-
- /*
- The following mutex is needed to ensure that no threads call
- 'delete thd' as we would then risk missing a 'rollback' from this
- thread. If the transaction involved MyISAM tables, it should go
- into binlog even on rollback.
- */
- VOID(pthread_mutex_lock(&LOCK_thread_count));
-
- /* Save variables so that we can reopen the log */
- save_name=name;
- name=0; // Protect against free
- close(LOG_CLOSE_TO_BE_OPENED);
-
- /*
- First delete all old log files and then update the index file.
- As we first delete the log files and do not use sort of logging,
- a crash may lead to an inconsistent state where the index has
- references to non-existent files.
-
- We need to invert the steps and use the purge_index_file methods
- in order to make the operation safe.
- */
- if ((err= find_log_pos(&linfo, NullS, 0)) != 0)
- {
- uint errcode= purge_log_get_error_code(err);
- sql_print_error("Failed to locate old binlog or relay log files");
- my_message(errcode, ER(errcode), MYF(0));
- error= 1;
- goto err;
- }
-
- for (;;)
- {
- if ((error= my_delete_allow_opened(linfo.log_file_name, MYF(0))) != 0)
- {
- if (my_errno == ENOENT)
- {
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
- linfo.log_file_name);
- sql_print_information("Failed to delete file '%s'",
- linfo.log_file_name);
- my_errno= 0;
- error= 0;
- }
- else
- {
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with deleting %s; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- linfo.log_file_name);
- error= 1;
- goto err;
- }
- }
- if (find_next_log(&linfo, 0))
- break;
- }
-
- /* Start logging with a new file */
- close(LOG_CLOSE_INDEX);
- if ((error= my_delete_allow_opened(index_file_name, MYF(0)))) // Reset (open will update)
- {
- if (my_errno == ENOENT)
- {
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
- index_file_name);
- sql_print_information("Failed to delete file '%s'",
- index_file_name);
- my_errno= 0;
- error= 0;
- }
- else
- {
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with deleting %s; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- index_file_name);
- error= 1;
- goto err;
- }
- }
- if (!thd->slave_thread)
- need_start_event=1;
- if (!open_index_file(index_file_name, 0, FALSE))
- open(save_name, log_type, 0, io_cache_type, no_auto_events, max_size, 0, FALSE);
- my_free((uchar*) save_name, MYF(0));
-
-err:
- if (error == 1)
- name= const_cast<char*>(save_name);
- VOID(pthread_mutex_unlock(&LOCK_thread_count));
- pthread_mutex_unlock(&LOCK_index);
- pthread_mutex_unlock(&LOCK_log);
- DBUG_RETURN(error);
-}
-
-
-/**
- Delete relay log files prior to rli->group_relay_log_name
- (i.e. all logs which are not involved in a non-finished group
- (transaction)), remove them from the index file and start on next
- relay log.
-
- IMPLEMENTATION
- - Protects index file with LOCK_index
- - Delete relevant relay log files
- - Copy all file names after these ones to the front of the index file
- - If the OS has truncate, truncate the file, else fill it with \n'
- - Read the next file name from the index file and store in rli->linfo
-
- @param rli Relay log information
- @param included If false, all relay logs that are strictly before
- rli->group_relay_log_name are deleted ; if true, the
- latter is deleted too (i.e. all relay logs
- read by the SQL slave thread are deleted).
-
- @note
- - This is only called from the slave-execute thread when it has read
- all commands from a relay log and want to switch to a new relay log.
- - When this happens, we can be in an active transaction as
- a transaction can span over two relay logs
- (although it is always written as a single block to the master's binary
- log, hence cannot span over two master's binary logs).
-
- @retval
- 0 ok
- @retval
- LOG_INFO_EOF End of log-index-file found
- @retval
- LOG_INFO_SEEK Could not allocate IO cache
- @retval
- LOG_INFO_IO Got IO error while reading file
-*/
-
-#ifdef HAVE_REPLICATION
-
-int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included)
-{
- int error;
- char *to_purge_if_included= NULL;
- DBUG_ENTER("purge_first_log");
-
- DBUG_ASSERT(is_open());
- DBUG_ASSERT(rli->slave_running == 1);
- DBUG_ASSERT(!strcmp(rli->linfo.log_file_name,rli->event_relay_log_name));
-
- pthread_mutex_lock(&LOCK_index);
- to_purge_if_included= my_strdup(rli->group_relay_log_name, MYF(0));
-
- /*
- Read the next log file name from the index file and pass it back to
- the caller.
- */
- if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)) ||
- (error=find_next_log(&rli->linfo, 0)))
- {
- char buff[22];
- sql_print_error("next log error: %d offset: %s log: %s included: %d",
- error,
- llstr(rli->linfo.index_file_offset,buff),
- rli->event_relay_log_name,
- included);
- goto err;
- }
-
- /*
- Reset rli's coordinates to the current log.
- */
- rli->event_relay_log_pos= BIN_LOG_HEADER_SIZE;
- strmake(rli->event_relay_log_name,rli->linfo.log_file_name,
- sizeof(rli->event_relay_log_name)-1);
-
- /*
- If we removed the rli->group_relay_log_name file,
- we must update the rli->group* coordinates, otherwise do not touch it as the
- group's execution is not finished (e.g. COMMIT not executed)
- */
- if (included)
- {
- rli->group_relay_log_pos = BIN_LOG_HEADER_SIZE;
- strmake(rli->group_relay_log_name,rli->linfo.log_file_name,
- sizeof(rli->group_relay_log_name)-1);
- rli->notify_group_relay_log_name_update();
- }
-
- /* Store where we are in the new file for the execution thread */
- flush_relay_log_info(rli);
-
- DBUG_EXECUTE_IF("crash_before_purge_logs", abort(););
-
- pthread_mutex_lock(&rli->log_space_lock);
- rli->relay_log.purge_logs(to_purge_if_included, included,
- 0, 0, &rli->log_space_total);
- // Tell the I/O thread to take the relay_log_space_limit into account
- rli->ignore_log_space_limit= 0;
- pthread_mutex_unlock(&rli->log_space_lock);
-
- /*
- Ok to broadcast after the critical region as there is no risk of
- the mutex being destroyed by this thread later - this helps save
- context switches
- */
- pthread_cond_broadcast(&rli->log_space_cond);
-
- /*
- * Need to update the log pos because purge logs has been called
- * after fetching initially the log pos at the begining of the method.
- */
- if((error=find_log_pos(&rli->linfo, rli->event_relay_log_name, 0)))
- {
- char buff[22];
- sql_print_error("next log error: %d offset: %s log: %s included: %d",
- error,
- llstr(rli->linfo.index_file_offset,buff),
- rli->group_relay_log_name,
- included);
- goto err;
- }
-
- /* If included was passed, rli->linfo should be the first entry. */
- DBUG_ASSERT(!included || rli->linfo.index_file_start_offset == 0);
-
-err:
- my_free(to_purge_if_included, MYF(0));
- pthread_mutex_unlock(&LOCK_index);
- DBUG_RETURN(error);
-}
-
-/**
- Update log index_file.
-*/
-
-int MYSQL_BIN_LOG::update_log_index(LOG_INFO* log_info, bool need_update_threads)
-{
- if (copy_up_file_and_fill(&index_file, log_info->index_file_start_offset))
- return LOG_INFO_IO;
-
- // now update offsets in index file for running threads
- if (need_update_threads)
- adjust_linfo_offsets(log_info->index_file_start_offset);
- return 0;
-}
-
-/**
- Remove all logs before the given log from disk and from the index file.
-
- @param to_log Delete all log file name before this file.
- @param included If true, to_log is deleted too.
- @param need_mutex
- @param need_update_threads If we want to update the log coordinates of
- all threads. False for relay logs, true otherwise.
- @param freed_log_space If not null, decrement this variable of
- the amount of log space freed
-
- @note
- If any of the logs before the deleted one is in use,
- only purge logs up to this one.
-
- @retval
- 0 ok
- @retval
- LOG_INFO_EOF to_log not found
- LOG_INFO_EMFILE too many files opened
- LOG_INFO_FATAL if any other than ENOENT error from
- my_stat() or my_delete()
-*/
-
-int MYSQL_BIN_LOG::purge_logs(const char *to_log,
- bool included,
- bool need_mutex,
- bool need_update_threads,
- ulonglong *decrease_log_space)
-{
- int error= 0;
- bool exit_loop= 0;
- LOG_INFO log_info;
- THD *thd= current_thd;
- DBUG_ENTER("purge_logs");
- DBUG_PRINT("info",("to_log= %s",to_log));
-
- if (need_mutex)
- pthread_mutex_lock(&LOCK_index);
- if ((error=find_log_pos(&log_info, to_log, 0 /*no mutex*/)))
- {
- sql_print_error("MYSQL_BIN_LOG::purge_logs was called with file %s not "
- "listed in the index.", to_log);
- goto err;
- }
-
- if ((error= open_purge_index_file(TRUE)))
- {
- sql_print_error("MYSQL_BIN_LOG::purge_logs failed to sync the index file.");
- goto err;
- }
-
- /*
- File name exists in index file; delete until we find this file
- or a file that is used.
- */
- if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
- goto err;
- while ((strcmp(to_log,log_info.log_file_name) || (exit_loop=included)) &&
- !is_active(log_info.log_file_name) &&
- !log_in_use(log_info.log_file_name))
- {
- if ((error= register_purge_index_entry(log_info.log_file_name)))
- {
- sql_print_error("MYSQL_BIN_LOG::purge_logs failed to copy %s to register file.",
- log_info.log_file_name);
- goto err;
- }
-
- if (find_next_log(&log_info, 0) || exit_loop)
- break;
- }
-
- DBUG_EXECUTE_IF("crash_purge_before_update_index", abort(););
-
- if ((error= sync_purge_index_file()))
- {
- sql_print_error("MSYQL_BIN_LOG::purge_logs failed to flush register file.");
- goto err;
- }
-
- /* We know how many files to delete. Update index file. */
- if ((error=update_log_index(&log_info, need_update_threads)))
- {
- sql_print_error("MSYQL_BIN_LOG::purge_logs failed to update the index file");
- goto err;
- }
-
- DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", abort(););
-
-err:
- /* Read each entry from purge_index_file and delete the file. */
- if (is_inited_purge_index_file() &&
- (error= purge_index_entry(thd, decrease_log_space, FALSE)))
- sql_print_error("MSYQL_BIN_LOG::purge_logs failed to process registered files"
- " that would be purged.");
- close_purge_index_file();
-
- DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", abort(););
-
- if (need_mutex)
- pthread_mutex_unlock(&LOCK_index);
- DBUG_RETURN(error);
-}
-
-int MYSQL_BIN_LOG::set_purge_index_file_name(const char *base_file_name)
-{
- int error= 0;
- DBUG_ENTER("MYSQL_BIN_LOG::set_purge_index_file_name");
- if (fn_format(purge_index_file_name, base_file_name, mysql_data_home,
- ".~rec~", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH |
- MY_REPLACE_EXT)) == NULL)
- {
- error= 1;
- sql_print_error("MYSQL_BIN_LOG::set_purge_index_file_name failed to set "
- "file name.");
- }
- DBUG_RETURN(error);
-}
-
-int MYSQL_BIN_LOG::open_purge_index_file(bool destroy)
-{
- int error= 0;
- File file= -1;
-
- DBUG_ENTER("MYSQL_BIN_LOG::open_purge_index_file");
-
- if (destroy)
- close_purge_index_file();
-
- if (!my_b_inited(&purge_index_file))
- {
- if ((file= my_open(purge_index_file_name, O_RDWR | O_CREAT | O_BINARY,
- MYF(MY_WME | ME_WAITTANG))) < 0 ||
- init_io_cache(&purge_index_file, file, IO_SIZE,
- (destroy ? WRITE_CACHE : READ_CACHE),
- 0, 0, MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)))
- {
- error= 1;
- sql_print_error("MYSQL_BIN_LOG::open_purge_index_file failed to open register "
- " file.");
- }
- }
- DBUG_RETURN(error);
-}
-
-int MYSQL_BIN_LOG::close_purge_index_file()
-{
- int error= 0;
-
- DBUG_ENTER("MYSQL_BIN_LOG::close_purge_index_file");
-
- if (my_b_inited(&purge_index_file))
- {
- end_io_cache(&purge_index_file);
- error= my_close(purge_index_file.file, MYF(0));
- }
- my_delete(purge_index_file_name, MYF(0));
- bzero((char*) &purge_index_file, sizeof(purge_index_file));
-
- DBUG_RETURN(error);
-}
-
-bool MYSQL_BIN_LOG::is_inited_purge_index_file()
-{
- DBUG_ENTER("MYSQL_BIN_LOG::is_inited_purge_index_file");
- DBUG_RETURN (my_b_inited(&purge_index_file));
-}
-
-int MYSQL_BIN_LOG::sync_purge_index_file()
-{
- int error= 0;
- DBUG_ENTER("MYSQL_BIN_LOG::sync_purge_index_file");
-
- if ((error= flush_io_cache(&purge_index_file)) ||
- (error= my_sync(purge_index_file.file, MYF(MY_WME))))
- DBUG_RETURN(error);
-
- DBUG_RETURN(error);
-}
-
-int MYSQL_BIN_LOG::register_purge_index_entry(const char *entry)
-{
- int error= 0;
- DBUG_ENTER("MYSQL_BIN_LOG::register_purge_index_entry");
-
- if ((error=my_b_write(&purge_index_file, (const uchar*)entry, strlen(entry))) ||
- (error=my_b_write(&purge_index_file, (const uchar*)"\n", 1)))
- DBUG_RETURN (error);
-
- DBUG_RETURN(error);
-}
-
-int MYSQL_BIN_LOG::register_create_index_entry(const char *entry)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::register_create_index_entry");
- DBUG_RETURN(register_purge_index_entry(entry));
-}
-
-int MYSQL_BIN_LOG::purge_index_entry(THD *thd, ulonglong *decrease_log_space,
- bool need_mutex)
-{
- MY_STAT s;
- int error= 0;
- LOG_INFO log_info;
- LOG_INFO check_log_info;
-
- DBUG_ENTER("MYSQL_BIN_LOG:purge_index_entry");
-
- DBUG_ASSERT(my_b_inited(&purge_index_file));
-
- if ((error=reinit_io_cache(&purge_index_file, READ_CACHE, 0, 0, 0)))
- {
- sql_print_error("MSYQL_BIN_LOG::purge_index_entry failed to reinit register file "
- "for read");
- goto err;
- }
-
- for (;;)
- {
- uint length;
-
- if ((length=my_b_gets(&purge_index_file, log_info.log_file_name,
- FN_REFLEN)) <= 1)
- {
- if (purge_index_file.error)
- {
- error= purge_index_file.error;
- sql_print_error("MSYQL_BIN_LOG::purge_index_entry error %d reading from "
- "register file.", error);
- goto err;
- }
-
- /* Reached EOF */
- break;
- }
-
- /* Get rid of the trailing '\n' */
- log_info.log_file_name[length-1]= 0;
-
- if (!my_stat(log_info.log_file_name, &s, MYF(0)))
- {
- if (my_errno == ENOENT)
- {
- /*
- It's not fatal if we can't stat a log file that does not exist;
- If we could not stat, we won't delete.
- */
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
- log_info.log_file_name);
- }
- sql_print_information("Failed to execute my_stat on file '%s'",
- log_info.log_file_name);
- my_errno= 0;
- }
- else
- {
- /*
- Other than ENOENT are fatal
- */
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with getting info on being purged %s; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- log_info.log_file_name);
- }
- else
- {
- sql_print_information("Failed to delete log file '%s'; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- log_info.log_file_name);
- }
- error= LOG_INFO_FATAL;
- goto err;
- }
- }
- else
- {
- if ((error= find_log_pos(&check_log_info, log_info.log_file_name, need_mutex)))
- {
- if (error != LOG_INFO_EOF)
- {
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with deleting %s and "
- "reading the binlog index file",
- log_info.log_file_name);
- }
- else
- {
- sql_print_information("Failed to delete file '%s' and "
- "read the binlog index file",
- log_info.log_file_name);
- }
- goto err;
- }
-
- error= 0;
- if (!need_mutex)
- {
- /*
- This is to avoid triggering an error in NDB.
- */
- ha_binlog_index_purge_file(current_thd, log_info.log_file_name);
- }
-
- DBUG_PRINT("info",("purging %s",log_info.log_file_name));
- if (!my_delete(log_info.log_file_name, MYF(0)))
- {
- if (decrease_log_space)
- *decrease_log_space-= s.st_size;
- }
- else
- {
- if (my_errno == ENOENT)
- {
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_LOG_PURGE_NO_FILE, ER(ER_LOG_PURGE_NO_FILE),
- log_info.log_file_name);
- }
- sql_print_information("Failed to delete file '%s'",
- log_info.log_file_name);
- my_errno= 0;
- }
- else
- {
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with deleting %s; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- log_info.log_file_name);
- }
- else
- {
- sql_print_information("Failed to delete file '%s'; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- log_info.log_file_name);
- }
- if (my_errno == EMFILE)
- {
- DBUG_PRINT("info",
- ("my_errno: %d, set ret = LOG_INFO_EMFILE", my_errno));
- error= LOG_INFO_EMFILE;
- goto err;
- }
- error= LOG_INFO_FATAL;
- goto err;
- }
- }
- }
- }
- }
-
-err:
- DBUG_RETURN(error);
-}
-
-/**
- Remove all logs before the given file date from disk and from the
- index file.
-
- @param thd Thread pointer
- @param purge_time Delete all log files before given date.
-
- @note
- If any of the logs before the deleted one is in use,
- only purge logs up to this one.
-
- @retval
- 0 ok
- @retval
- LOG_INFO_PURGE_NO_ROTATE Binary file that can't be rotated
- LOG_INFO_FATAL if any other than ENOENT error from
- my_stat() or my_delete()
-*/
-
-int MYSQL_BIN_LOG::purge_logs_before_date(time_t purge_time)
-{
- int error;
- char to_log[FN_REFLEN];
- LOG_INFO log_info;
- MY_STAT stat_area;
- THD *thd= current_thd;
-
- DBUG_ENTER("purge_logs_before_date");
-
- pthread_mutex_lock(&LOCK_index);
- to_log[0]= 0;
-
- if ((error=find_log_pos(&log_info, NullS, 0 /*no mutex*/)))
- goto err;
-
- while (strcmp(log_file_name, log_info.log_file_name) &&
- !is_active(log_info.log_file_name) &&
- !log_in_use(log_info.log_file_name))
- {
- if (!my_stat(log_info.log_file_name, &stat_area, MYF(0)))
- {
- if (my_errno == ENOENT)
- {
- /*
- It's not fatal if we can't stat a log file that does not exist.
- */
- my_errno= 0;
- }
- else
- {
- /*
- Other than ENOENT are fatal
- */
- if (thd)
- {
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BINLOG_PURGE_FATAL_ERR,
- "a problem with getting info on being purged %s; "
- "consider examining correspondence "
- "of your binlog index file "
- "to the actual binlog files",
- log_info.log_file_name);
- }
- else
- {
- sql_print_information("Failed to delete log file '%s'",
- log_info.log_file_name);
- }
- error= LOG_INFO_FATAL;
- goto err;
- }
- }
- else
- {
- if (stat_area.st_mtime < purge_time)
- strmake(to_log,
- log_info.log_file_name,
- sizeof(log_info.log_file_name) - 1);
- else
- break;
- }
- if (find_next_log(&log_info, 0))
- break;
- }
-
- error= (to_log[0] ? purge_logs(to_log, 1, 0, 1, (ulonglong *) 0) : 0);
-
-err:
- pthread_mutex_unlock(&LOCK_index);
- DBUG_RETURN(error);
-}
-#endif /* HAVE_REPLICATION */
-
-
-/**
- Create a new log file name.
-
- @param buf buf of at least FN_REFLEN where new name is stored
-
- @note
- If file name will be longer then FN_REFLEN it will be truncated
-*/
-
-void MYSQL_BIN_LOG::make_log_name(char* buf, const char* log_ident)
-{
- uint dir_len = dirname_length(log_file_name);
- if (dir_len >= FN_REFLEN)
- dir_len=FN_REFLEN-1;
- strnmov(buf, log_file_name, dir_len);
- strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len -1);
-}
-
-
-/**
- Check if we are writing/reading to the given log file.
-*/
-
-bool MYSQL_BIN_LOG::is_active(const char *log_file_name_arg)
-{
- return !strcmp(log_file_name, log_file_name_arg);
-}
-
-
-/*
- Wrappers around new_file_impl to avoid using argument
- to control locking. The argument 1) less readable 2) breaks
- incapsulation 3) allows external access to the class without
- a lock (which is not possible with private new_file_without_locking
- method).
-*/
-
-void MYSQL_BIN_LOG::new_file()
-{
- new_file_impl(1);
-}
-
-
-void MYSQL_BIN_LOG::new_file_without_locking()
-{
- new_file_impl(0);
-}
-
-
-/**
- Start writing to a new log file or reopen the old file.
-
- @param need_lock Set to 1 if caller has not locked LOCK_log
-
- @note
- The new file name is stored last in the index file
-*/
-
-void MYSQL_BIN_LOG::new_file_impl(bool need_lock)
-{
- char new_name[FN_REFLEN], *new_name_ptr, *old_name;
-
- DBUG_ENTER("MYSQL_BIN_LOG::new_file_impl");
- if (!is_open())
- {
- DBUG_PRINT("info",("log is closed"));
- DBUG_VOID_RETURN;
- }
-
- if (need_lock)
- pthread_mutex_lock(&LOCK_log);
- pthread_mutex_lock(&LOCK_index);
-
- safe_mutex_assert_owner(&LOCK_log);
- safe_mutex_assert_owner(&LOCK_index);
-
- /*
- if binlog is used as tc log, be sure all xids are "unlogged",
- so that on recover we only need to scan one - latest - binlog file
- for prepared xids. As this is expected to be a rare event,
- simple wait strategy is enough. We're locking LOCK_log to be sure no
- new Xid_log_event's are added to the log (and prepared_xids is not
- increased), and waiting on COND_prep_xids for late threads to
- catch up.
- */
- if (prepared_xids)
- {
- tc_log_page_waits++;
- pthread_mutex_lock(&LOCK_prep_xids);
- while (prepared_xids) {
- DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids));
- pthread_cond_wait(&COND_prep_xids, &LOCK_prep_xids);
- }
- pthread_mutex_unlock(&LOCK_prep_xids);
- }
-
- /* Reuse old name if not binlog and not update log */
- new_name_ptr= name;
-
- /*
- If user hasn't specified an extension, generate a new log name
- We have to do this here and not in open as we want to store the
- new file name in the current binary log file.
- */
- if (generate_new_name(new_name, name))
- goto end;
- new_name_ptr=new_name;
-
- if (log_type == LOG_BIN)
- {
- if (!no_auto_events)
- {
- /*
- We log the whole file name for log file as the user may decide
- to change base names at some point.
- */
- Rotate_log_event r(new_name+dirname_length(new_name),
- 0, LOG_EVENT_OFFSET, is_relay_log ? Rotate_log_event::RELAY_LOG : 0);
- r.write(&log_file);
- bytes_written += r.data_written;
- }
- /*
- Update needs to be signalled even if there is no rotate event
- log rotation should give the waiting thread a signal to
- discover EOF and move on to the next log.
- */
- signal_update();
- }
- old_name=name;
- name=0; // Don't free name
- close(LOG_CLOSE_TO_BE_OPENED | LOG_CLOSE_INDEX);
-
- /*
- Note that at this point, log_state != LOG_CLOSED (important for is_open()).
- */
-
- /*
- new_file() is only used for rotation (in FLUSH LOGS or because size >
- max_binlog_size or max_relay_log_size).
- If this is a binary log, the Format_description_log_event at the beginning of
- the new file should have created=0 (to distinguish with the
- Format_description_log_event written at server startup, which should
- trigger temp tables deletion on slaves.
- */
-
- /* reopen index binlog file, BUG#34582 */
- if (!open_index_file(index_file_name, 0, FALSE))
- open(old_name, log_type, new_name_ptr,
- io_cache_type, no_auto_events, max_size, 1, FALSE);
- my_free(old_name,MYF(0));
-
-end:
- if (need_lock)
- pthread_mutex_unlock(&LOCK_log);
- pthread_mutex_unlock(&LOCK_index);
-
- DBUG_VOID_RETURN;
-}
-
-
-bool MYSQL_BIN_LOG::append(Log_event* ev)
-{
- bool error = 0;
- pthread_mutex_lock(&LOCK_log);
- DBUG_ENTER("MYSQL_BIN_LOG::append");
-
- DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
- /*
- Log_event::write() is smart enough to use my_b_write() or
- my_b_append() depending on the kind of cache we have.
- */
- if (ev->write(&log_file))
- {
- error=1;
- goto err;
- }
- bytes_written+= ev->data_written;
- DBUG_PRINT("info",("max_size: %lu",max_size));
- if (flush_and_sync(0))
- goto err;
- if ((uint) my_b_append_tell(&log_file) > max_size)
- new_file_without_locking();
-
-err:
- pthread_mutex_unlock(&LOCK_log);
- signal_update(); // Safe as we don't call close
- DBUG_RETURN(error);
-}
-
-
-bool MYSQL_BIN_LOG::appendv(const char* buf, uint len,...)
-{
- bool error= 0;
- DBUG_ENTER("MYSQL_BIN_LOG::appendv");
- va_list(args);
- va_start(args,len);
-
- DBUG_ASSERT(log_file.type == SEQ_READ_APPEND);
-
- safe_mutex_assert_owner(&LOCK_log);
- do
- {
- if (my_b_append(&log_file,(uchar*) buf,len))
- {
- error= 1;
- goto err;
- }
- bytes_written += len;
- } while ((buf=va_arg(args,const char*)) && (len=va_arg(args,uint)));
- DBUG_PRINT("info",("max_size: %lu",max_size));
- if (flush_and_sync(0))
- goto err;
- if ((uint) my_b_append_tell(&log_file) > max_size)
- new_file_without_locking();
-
-err:
- if (!error)
- signal_update();
- DBUG_RETURN(error);
-}
-
-bool MYSQL_BIN_LOG::flush_and_sync(bool *synced)
-{
- int err=0, fd=log_file.file;
- if (synced)
- *synced= 0;
- safe_mutex_assert_owner(&LOCK_log);
- if (flush_io_cache(&log_file))
- return 1;
- uint sync_period= get_sync_period();
- if (sync_period && ++sync_counter >= sync_period)
- {
- sync_counter= 0;
- err=my_sync(fd, MYF(MY_WME));
- if (synced)
- *synced= 1;
- }
- return err;
-}
-
-void MYSQL_BIN_LOG::start_union_events(THD *thd, query_id_t query_id_param)
-{
- DBUG_ASSERT(!thd->binlog_evt_union.do_union);
- thd->binlog_evt_union.do_union= TRUE;
- thd->binlog_evt_union.unioned_events= FALSE;
- thd->binlog_evt_union.unioned_events_trans= FALSE;
- thd->binlog_evt_union.first_query_id= query_id_param;
-}
-
-void MYSQL_BIN_LOG::stop_union_events(THD *thd)
-{
- DBUG_ASSERT(thd->binlog_evt_union.do_union);
- thd->binlog_evt_union.do_union= FALSE;
-}
-
-bool MYSQL_BIN_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
-{
- return (thd->binlog_evt_union.do_union &&
- query_id_param >= thd->binlog_evt_union.first_query_id);
-}
-
-/**
- This function checks if a transactional talbe was updated by the
- current transaction.
-
- @param thd The client thread that executed the current statement.
- @return
- @c true if a transactional table was updated, @c false otherwise.
-*/
-bool
-trans_has_updated_trans_table(const THD* thd)
-{
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- return (cache_mngr ? my_b_tell (&cache_mngr->trx_cache.cache_log) : 0);
-}
-
-/**
- This function checks if a transactional talbe was updated by the
- current statement.
-
- @param thd The client thread that executed the current statement.
- @return
- @c true if a transactional table was updated, @c false otherwise.
-*/
-bool
-stmt_has_updated_trans_table(const THD *thd)
-{
- Ha_trx_info *ha_info;
-
- for (ha_info= thd->transaction.stmt.ha_list; ha_info; ha_info= ha_info->next())
- {
- if (ha_info->is_trx_read_write() && ha_info->ht() != binlog_hton)
- return (TRUE);
- }
- return (FALSE);
-}
-
-/**
- This function checks if either a trx-cache or a non-trx-cache should
- be used. If @c bin_log_direct_non_trans_update is active, the cache
- to be used depends on the flag @c is_transactional.
-
- Otherswise, we use the trx-cache if either the @c is_transactional
- is true or the trx-cache is not empty.
-
- @param thd The client thread.
- @param is_transactional The changes are related to a trx-table.
- @return
- @c true if a trx-cache should be used, @c false otherwise.
-*/
-bool use_trans_cache(const THD* thd, bool is_transactional)
-{
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- return
- (thd->variables.binlog_direct_non_trans_update ? is_transactional :
- (cache_mngr->trx_cache.empty() && !is_transactional ? FALSE : TRUE));
-}
-
-/*
- These functions are placed in this file since they need access to
- binlog_hton, which has internal linkage.
-*/
-
-int THD::binlog_setup_trx_data()
-{
- DBUG_ENTER("THD::binlog_setup_trx_data");
- binlog_cache_mngr *cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
- if (cache_mngr)
- DBUG_RETURN(0); // Already set up
-
- cache_mngr= (binlog_cache_mngr*) my_malloc(sizeof(binlog_cache_mngr), MYF(MY_ZEROFILL));
- if (!cache_mngr ||
- open_cached_file(&cache_mngr->stmt_cache.cache_log, mysql_tmpdir,
- LOG_PREFIX, binlog_cache_size, MYF(MY_WME)) ||
- open_cached_file(&cache_mngr->trx_cache.cache_log, mysql_tmpdir,
- LOG_PREFIX, binlog_cache_size, MYF(MY_WME)))
- {
- my_free((uchar*)cache_mngr, MYF(MY_ALLOW_ZERO_PTR));
- DBUG_RETURN(1); // Didn't manage to set it up
- }
- thd_set_ha_data(this, binlog_hton, cache_mngr);
-
- cache_mngr= new (thd_get_ha_data(this, binlog_hton)) binlog_cache_mngr;
-
- DBUG_RETURN(0);
-}
-
-/*
- Function to start a statement and optionally a transaction for the
- binary log.
-
- SYNOPSIS
- binlog_start_trans_and_stmt()
-
- DESCRIPTION
-
- This function does three things:
- - Start a transaction if not in autocommit mode or if a BEGIN
- statement has been seen.
-
- - Start a statement transaction to allow us to truncate the cache.
-
- - Save the currrent binlog position so that we can roll back the
- statement by truncating the cache.
-
- We only update the saved position if the old one was undefined,
- the reason is that there are some cases (e.g., for CREATE-SELECT)
- where the position is saved twice (e.g., both in
- select_create::prepare() and THD::binlog_write_table_map()) , but
- we should use the first. This means that calls to this function
- can be used to start the statement before the first table map
- event, to include some extra events.
- */
-
-void
-THD::binlog_start_trans_and_stmt()
-{
- binlog_cache_mngr *cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
- DBUG_ENTER("binlog_start_trans_and_stmt");
- DBUG_PRINT("enter", ("cache_mngr: %p cache_mngr->trx_cache.get_prev_position(): %lu",
- cache_mngr,
- (cache_mngr ? (ulong) cache_mngr->trx_cache.get_prev_position() :
- (ulong) 0)));
-
- if (cache_mngr == NULL ||
- cache_mngr->trx_cache.get_prev_position() == MY_OFF_T_UNDEF)
- {
- this->binlog_set_stmt_begin();
- if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
- trans_register_ha(this, TRUE, binlog_hton);
- trans_register_ha(this, FALSE, binlog_hton);
- /*
- Mark statement transaction as read/write. We never start
- a binary log transaction and keep it read-only,
- therefore it's best to mark the transaction read/write just
- at the same time we start it.
- Not necessary to mark the normal transaction read/write
- since the statement-level flag will be propagated automatically
- inside ha_commit_trans.
- */
- ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
- }
- DBUG_VOID_RETURN;
-}
-
-void THD::binlog_set_stmt_begin() {
- binlog_cache_mngr *cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
- /*
- The call to binlog_trans_log_savepos() might create the cache_mngr
- structure, if it didn't exist before, so we save the position
- into an auto variable and then write it into the transaction
- data for the binary log (i.e., cache_mngr).
- */
- my_off_t pos= 0;
- binlog_trans_log_savepos(this, &pos);
- cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
- cache_mngr->trx_cache.set_prev_position(pos);
-}
-
-
-/**
- This function writes a table map to the binary log.
- Note that in order to keep the signature uniform with related methods,
- we use a redundant parameter to indicate whether a transactional table
- was changed or not.
-
- @param table a pointer to the table.
- @param is_transactional @c true indicates a transactional table,
- otherwise @c false a non-transactional.
- @return
- nonzero if an error pops up when writing the table map event.
-*/
-int THD::binlog_write_table_map(TABLE *table, bool is_transactional)
-{
- int error;
- DBUG_ENTER("THD::binlog_write_table_map");
- DBUG_PRINT("enter", ("table: 0x%lx (%s: #%lu)",
- (long) table, table->s->table_name.str,
- table->s->table_map_id));
-
- /* Pre-conditions */
- DBUG_ASSERT(is_current_stmt_binlog_format_row() && mysql_bin_log.is_open());
- DBUG_ASSERT(table->s->table_map_id != ULONG_MAX);
-
- Table_map_log_event
- the_event(this, table, table->s->table_map_id, is_transactional);
-
- if (binlog_table_maps == 0)
- binlog_start_trans_and_stmt();
-
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
- IO_CACHE *file=
- cache_mngr->get_binlog_cache_log(use_trans_cache(this, is_transactional));
- if ((error= the_event.write(file)))
- DBUG_RETURN(error);
-
- binlog_table_maps++;
- table->s->table_map_version= mysql_bin_log.table_map_version();
- DBUG_RETURN(0);
-}
-
-/**
- This function retrieves a pending row event from a cache which is
- specified through the parameter @c is_transactional. Respectively, when it
- is @c true, the pending event is returned from the transactional cache.
- Otherwise from the non-transactional cache.
-
- @param is_transactional @c true indicates a transactional cache,
- otherwise @c false a non-transactional.
- @return
- The row event if any.
-*/
-Rows_log_event*
-THD::binlog_get_pending_rows_event(bool is_transactional) const
-{
- Rows_log_event* rows= NULL;
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
- /*
- This is less than ideal, but here's the story: If there is no cache_mngr,
- prepare_pending_rows_event() has never been called (since the cache_mngr
- is set up there). In that case, we just return NULL.
- */
- if (cache_mngr)
- {
- binlog_cache_data *cache_data=
- cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
-
- rows= cache_data->pending();
- }
- return (rows);
-}
-
-/**
- This function stores a pending row event into a cache which is specified
- through the parameter @c is_transactional. Respectively, when it is @c
- true, the pending event is stored into the transactional cache. Otherwise
- into the non-transactional cache.
-
- @param evt a pointer to the row event.
- @param is_transactional @c true indicates a transactional cache,
- otherwise @c false a non-transactional.
-*/
-void
-THD::binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional)
-{
- if (thd_get_ha_data(this, binlog_hton) == NULL)
- binlog_setup_trx_data();
-
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
- DBUG_ASSERT(cache_mngr);
-
- binlog_cache_data *cache_data=
- cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
-
- cache_data->set_pending(ev);
-}
-
-
-/**
- This function removes the pending rows event, discarding any outstanding
- rows. If there is no pending rows event available, this is effectively a
- no-op.
-
- @param thd a pointer to the user thread.
- @param is_transactional @c true indicates a transactional cache,
- otherwise @c false a non-transactional.
-*/
-int
-MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::remove_pending_rows_event");
-
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- DBUG_ASSERT(cache_mngr);
-
- binlog_cache_data *cache_data=
- cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
-
- if (Rows_log_event* pending= cache_data->pending())
- {
- delete pending;
- cache_data->set_pending(NULL);
- }
-
- DBUG_RETURN(0);
-}
-
-/*
- Moves the last bunch of rows from the pending Rows event to a cache (either
- transactional cache if is_transaction is @c true, or the non-transactional
- cache otherwise. Sets a new pending event.
-
- @param thd a pointer to the user thread.
- @param evt a pointer to the row event.
- @param is_transactional @c true indicates a transactional cache,
- otherwise @c false a non-transactional.
-*/
-int
-MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd,
- Rows_log_event* event,
- bool is_transactional)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::flush_and_set_pending_rows_event(event)");
- DBUG_ASSERT(mysql_bin_log.is_open());
- DBUG_PRINT("enter", ("event: 0x%lx", (long) event));
-
- int error= 0;
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- DBUG_ASSERT(cache_mngr);
-
- binlog_cache_data *cache_data=
- cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
-
- DBUG_PRINT("info", ("cache_mngr->pending(): 0x%lx", (long) cache_data->pending()));
-
- if (Rows_log_event* pending= cache_data->pending())
- {
- IO_CACHE *file= &cache_data->cache_log;
-
- /*
- Write pending event to the cache.
- */
- if (pending->write(file))
- {
- set_write_error(thd);
- if (check_write_error(thd) && cache_data &&
- thd->transaction.stmt.modified_non_trans_table)
- cache_data->set_incident();
- DBUG_RETURN(1);
- }
-
- /*
- We step the table map version if we are writing an event
- representing the end of a statement.
-
- In an ideal world, we could avoid stepping the table map version,
- since we could then reuse the table map that was written earlier
- in the cache. This does not work since STMT_END_F implies closing
- all table mappings on the slave side.
-
- TODO: Find a solution so that table maps does not have to be
- written several times within a transaction.
- */
- if (pending->get_flags(Rows_log_event::STMT_END_F))
- ++m_table_map_version;
-
- delete pending;
- }
-
- thd->binlog_set_pending_rows_event(event, is_transactional);
-
- DBUG_RETURN(error);
-}
-
-/**
- Write an event to the binary log.
-*/
-
-bool MYSQL_BIN_LOG::write(Log_event *event_info)
-{
- THD *thd= event_info->thd;
- bool error= 1;
- DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)");
- binlog_cache_data *cache_data= 0;
-
- if (thd->binlog_evt_union.do_union)
- {
- /*
- In Stored function; Remember that function call caused an update.
- We will log the function call to the binary log on function exit
- */
- thd->binlog_evt_union.unioned_events= TRUE;
- thd->binlog_evt_union.unioned_events_trans |=
- event_info->use_trans_cache();
- DBUG_RETURN(0);
- }
-
- /*
- We only end the statement if we are in a top-level statement. If
- we are inside a stored function, we do not end the statement since
- this will close all tables on the slave.
- */
- bool const end_stmt=
- thd->prelocked_mode && thd->lex->requires_prelocking();
- if (thd->binlog_flush_pending_rows_event(end_stmt,
- event_info->use_trans_cache()))
- DBUG_RETURN(error);
-
- /*
- In most cases this is only called if 'is_open()' is true; in fact this is
- mostly called if is_open() *was* true a few instructions before, but it
- could have changed since.
- */
- if (likely(is_open()))
- {
-#ifdef HAVE_REPLICATION
- /*
- In the future we need to add to the following if tests like
- "do the involved tables match (to be implemented)
- binlog_[wild_]{do|ignore}_table?" (WL#1049)"
- */
- const char *local_db= event_info->get_db();
- if ((thd && !(thd->options & OPTION_BIN_LOG)) ||
- (!binlog_filter->db_ok(local_db)))
- DBUG_RETURN(0);
-#endif /* HAVE_REPLICATION */
-
-#if defined(USING_TRANSACTIONS)
- IO_CACHE *file= NULL;
-
- if (event_info->use_direct_logging())
- {
- file= &log_file;
- pthread_mutex_lock(&LOCK_log);
- }
- else
- {
- if (thd->binlog_setup_trx_data())
- goto err;
-
- binlog_cache_mngr *const cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
- bool is_trans_cache= use_trans_cache(thd, event_info->use_trans_cache());
- file= cache_mngr->get_binlog_cache_log(is_trans_cache);
- cache_data= cache_mngr->get_binlog_cache_data(is_trans_cache);
-
- thd->binlog_start_trans_and_stmt();
- }
-#endif /* USING_TRANSACTIONS */
- DBUG_PRINT("info",("event type: %d",event_info->get_type_code()));
-
- /*
- No check for auto events flag here - this write method should
- never be called if auto-events are enabled.
-
- Write first log events which describe the 'run environment'
- of the SQL command. If row-based binlogging, Insert_id, Rand
- and other kind of "setting context" events are not needed.
- */
- if (thd)
- {
- if (!thd->is_current_stmt_binlog_format_row())
- {
- if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
- {
- Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT,
- thd->first_successful_insert_id_in_prev_stmt_for_binlog);
- if (e.write(file))
- goto err;
- }
- if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
- {
- DBUG_PRINT("info",("number of auto_inc intervals: %u",
- thd->auto_inc_intervals_in_cur_stmt_for_binlog.
- nb_elements()));
- Intvar_log_event e(thd, (uchar) INSERT_ID_EVENT,
- thd->auto_inc_intervals_in_cur_stmt_for_binlog.
- minimum());
- if (e.write(file))
- goto err;
- }
- if (thd->rand_used)
- {
- Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2);
- if (e.write(file))
- goto err;
- }
- if (thd->user_var_events.elements)
- {
- for (uint i= 0; i < thd->user_var_events.elements; i++)
- {
- BINLOG_USER_VAR_EVENT *user_var_event;
- get_dynamic(&thd->user_var_events,(uchar*) &user_var_event, i);
-
- /* setting flags for user var log event */
- uchar flags= User_var_log_event::UNDEF_F;
- if (user_var_event->unsigned_flag)
- flags|= User_var_log_event::UNSIGNED_F;
-
- User_var_log_event e(thd, user_var_event->user_var_event->name.str,
- user_var_event->user_var_event->name.length,
- user_var_event->value,
- user_var_event->length,
- user_var_event->type,
- user_var_event->charset_number,
- flags);
- if (e.write(file))
- goto err;
- }
- }
- }
- }
-
- /*
- Write the event.
- */
- if (event_info->write(file) ||
- DBUG_EVALUATE_IF("injecting_fault_writing", 1, 0))
- goto err;
-
- error= 0;
-
-err:
- if (event_info->use_direct_logging())
- {
- if (!error)
- {
- bool synced;
- if ((error= flush_and_sync(&synced)))
- goto unlock;
-
- if ((error= RUN_HOOK(binlog_storage, after_flush,
- (thd, log_file_name, file->pos_in_file, synced))))
- {
- sql_print_error("Failed to run 'after_flush' hooks");
- goto unlock;
- }
- signal_update();
- rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
- }
-unlock:
- pthread_mutex_unlock(&LOCK_log);
- }
-
- if (error)
- {
- set_write_error(thd);
- if (check_write_error(thd) && cache_data &&
- thd->transaction.stmt.modified_non_trans_table)
- cache_data->set_incident();
- }
- }
-
- if (event_info->flags & LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F)
- ++m_table_map_version;
-
- DBUG_RETURN(error);
-}
-
-
-int error_log_print(enum loglevel level, const char *format,
- va_list args)
-{
- return logger.error_log_print(level, format, args);
-}
-
-
-bool slow_log_print(THD *thd, const char *query, uint query_length,
- ulonglong current_utime)
-{
- return logger.slow_log_print(thd, query, query_length, current_utime);
-}
-
-
-bool LOGGER::log_command(THD *thd, enum enum_server_command command)
-{
-#ifndef NO_EMBEDDED_ACCESS_CHECKS
- Security_context *sctx= thd->security_ctx;
-#endif
- /*
- Log command if we have at least one log event handler enabled and want
- to log this king of commands
- */
- if (*general_log_handler_list && (what_to_log & (1L << (uint) command)))
- {
- if ((thd->options & OPTION_LOG_OFF)
-#ifndef NO_EMBEDDED_ACCESS_CHECKS
- && (sctx->master_access & SUPER_ACL)
-#endif
- )
- {
- /* No logging */
- return FALSE;
- }
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-
-bool general_log_print(THD *thd, enum enum_server_command command,
- const char *format, ...)
-{
- va_list args;
- uint error= 0;
-
- /* Print the message to the buffer if we want to log this king of commands */
- if (! logger.log_command(thd, command))
- return FALSE;
-
- va_start(args, format);
- error= logger.general_log_print(thd, command, format, args);
- va_end(args);
-
- return error;
-}
-
-bool general_log_write(THD *thd, enum enum_server_command command,
- const char *query, uint query_length)
-{
- /* Write the message to the log if we want to log this king of commands */
- if (logger.log_command(thd, command))
- return logger.general_log_write(thd, command, query, query_length);
-
- return FALSE;
-}
-
-void MYSQL_BIN_LOG::rotate_and_purge(uint flags)
-{
-#ifdef HAVE_REPLICATION
- bool check_purge= false;
-#endif
- if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED))
- pthread_mutex_lock(&LOCK_log);
- if ((flags & RP_FORCE_ROTATE) ||
- (my_b_tell(&log_file) >= (my_off_t) max_size))
- {
- new_file_without_locking();
-#ifdef HAVE_REPLICATION
- check_purge= true;
-#endif
- }
- if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED))
- pthread_mutex_unlock(&LOCK_log);
-
-#ifdef HAVE_REPLICATION
- /*
- NOTE: Run purge_logs wo/ holding LOCK_log
- as it otherwise will deadlock in ndbcluster_binlog_index_purge_file
- */
- if (check_purge && expire_logs_days)
- {
- time_t purge_time= my_time(0) - expire_logs_days*24*60*60;
- if (purge_time >= 0)
- purge_logs_before_date(purge_time);
- }
-#endif
-}
-
-uint MYSQL_BIN_LOG::next_file_id()
-{
- uint res;
- pthread_mutex_lock(&LOCK_log);
- res = file_id++;
pthread_mutex_unlock(&LOCK_log);
- return res;
+
+ DBUG_VOID_RETURN;
}
/*
- Write the contents of a cache to the binary log.
+ Write a command to traditional general log file
SYNOPSIS
- write_cache()
- cache Cache to write to the binary log
- lock_log True if the LOCK_log mutex should be aquired, false otherwise
- sync_log True if the log should be flushed and synced
+ write()
+
+ event_time command start timestamp
+ user_host the pointer to the string with user@host info
+ user_host_len length of the user_host string. this is computed once
+ and passed to all general log event handlers
+ thread_id Id of the thread, issued a query
+ command_type the type of the command being logged
+ command_type_len the length of the string above
+ sql_text the very text of the query being executed
+ sql_text_len the length of sql_text string
DESCRIPTION
- Write the contents of the cache to the binary log. The cache will
- be reset as a READ_CACHE to be able to read the contents from it.
- */
-
-int MYSQL_BIN_LOG::write_cache(IO_CACHE *cache, bool lock_log, bool sync_log)
-{
- Mutex_sentry sentry(lock_log ? &LOCK_log : NULL);
-
- if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0))
- return ER_ERROR_ON_WRITE;
- uint length= my_b_bytes_in_cache(cache), group, carry, hdr_offs;
- long val;
- uchar header[LOG_EVENT_HEADER_LEN];
- /*
- The events in the buffer have incorrect end_log_pos data
- (relative to beginning of group rather than absolute),
- so we'll recalculate them in situ so the binlog is always
- correct, even in the middle of a group. This is possible
- because we now know the start position of the group (the
- offset of this cache in the log, if you will); all we need
- to do is to find all event-headers, and add the position of
- the group to the end_log_pos of each event. This is pretty
- straight forward, except that we read the cache in segments,
- so an event-header might end up on the cache-border and get
- split.
- */
+ Log given command to to normal (not rotable) log file
- group= (uint)my_b_tell(&log_file);
- hdr_offs= carry= 0;
+ RETURN
+ FASE - OK
+ TRUE - error occured
+*/
- do
+bool MYSQL_QUERY_LOG::write(time_t event_time, const char *user_host,
+ uint user_host_len, int thread_id,
+ const char *command_type, uint command_type_len,
+ const char *sql_text, uint sql_text_len)
+{
+ char buff[32];
+ uint length= 0;
+ char local_time_buff[MAX_TIME_SIZE];
+ struct tm start;
+ uint time_buff_len= 0;
+
+ (void) pthread_mutex_lock(&LOCK_log);
+
+ /* Test if someone closed between the is_open test and lock */
+ if (is_open())
{
- /*
- if we only got a partial header in the last iteration,
- get the other half now and process a full header.
- */
- if (unlikely(carry > 0))
- {
- DBUG_ASSERT(carry < LOG_EVENT_HEADER_LEN);
+ /* for testing output of timestamp and thread id */
+ DBUG_EXECUTE_IF("reset_log_last_time", last_time= 0;);
- /* assemble both halves */
- memcpy(&header[carry], (char *)cache->read_pos, LOG_EVENT_HEADER_LEN - carry);
+ /* Note that my_b_write() assumes it knows the length for this */
+ if (event_time != last_time)
+ {
+ last_time= event_time;
- /* fix end_log_pos */
- val= uint4korr(&header[LOG_POS_OFFSET]) + group;
- int4store(&header[LOG_POS_OFFSET], val);
+ localtime_r(&event_time, &start);
- /* write the first half of the split header */
- if (my_b_write(&log_file, header, carry))
- return ER_ERROR_ON_WRITE;
+ time_buff_len= my_snprintf(local_time_buff, MAX_TIME_SIZE,
+ "%02d%02d%02d %2d:%02d:%02d\t",
+ start.tm_year % 100, start.tm_mon + 1,
+ start.tm_mday, start.tm_hour,
+ start.tm_min, start.tm_sec);
- /*
- copy fixed second half of header to cache so the correct
- version will be written later.
- */
- memcpy((char *)cache->read_pos, &header[carry], LOG_EVENT_HEADER_LEN - carry);
+ if (my_b_write(&log_file, (uchar*) local_time_buff, time_buff_len))
+ goto err;
+ }
+ else
+ if (my_b_write(&log_file, (uchar*) "\t\t" ,2) < 0)
+ goto err;
- /* next event header at ... */
- hdr_offs = uint4korr(&header[EVENT_LEN_OFFSET]) - carry;
+ /* command_type, thread_id */
+ length= my_snprintf(buff, 32, "%5ld ", (long) thread_id);
- carry= 0;
- }
+ if (my_b_write(&log_file, (uchar*) buff, length))
+ goto err;
- /* if there is anything to write, process it. */
+ if (my_b_write(&log_file, (uchar*) command_type, command_type_len))
+ goto err;
- if (likely(length > 0))
- {
- /*
- process all event-headers in this (partial) cache.
- if next header is beyond current read-buffer,
- we'll get it later (though not necessarily in the
- very next iteration, just "eventually").
- */
+ if (my_b_write(&log_file, (uchar*) "\t", 1))
+ goto err;
- while (hdr_offs < length)
- {
- /*
- partial header only? save what we can get, process once
- we get the rest.
- */
+ /* sql_text */
+ if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len))
+ goto err;
- if (hdr_offs + LOG_EVENT_HEADER_LEN > length)
- {
- carry= length - hdr_offs;
- memcpy(header, (char *)cache->read_pos + hdr_offs, carry);
- length= hdr_offs;
- }
- else
- {
- /* we've got a full event-header, and it came in one piece */
-
- uchar *log_pos= (uchar *)cache->read_pos + hdr_offs + LOG_POS_OFFSET;
-
- /* fix end_log_pos */
- val= uint4korr(log_pos) + group;
- int4store(log_pos, val);
-
- /* next event header at ... */
- log_pos= (uchar *)cache->read_pos + hdr_offs + EVENT_LEN_OFFSET;
- hdr_offs += uint4korr(log_pos);
+ if (my_b_write(&log_file, (uchar*) "\n", 1) ||
+ flush_io_cache(&log_file))
+ goto err;
+ }
- }
- }
+ (void) pthread_mutex_unlock(&LOCK_log);
+ return FALSE;
+err:
- /*
- Adjust hdr_offs. Note that it may still point beyond the segment
- read in the next iteration; if the current event is very long,
- it may take a couple of read-iterations (and subsequent adjustments
- of hdr_offs) for it to point into the then-current segment.
- If we have a split header (!carry), hdr_offs will be set at the
- beginning of the next iteration, overwriting the value we set here:
- */
- hdr_offs -= length;
- }
+ if (!write_error)
+ {
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
+ }
+ (void) pthread_mutex_unlock(&LOCK_log);
+ return TRUE;
+}
- /* Write data to the binary log file */
- if (my_b_write(&log_file, cache->read_pos, length))
- return ER_ERROR_ON_WRITE;
- cache->read_pos=cache->read_end; // Mark buffer used up
- } while ((length= my_b_fill(cache)));
- DBUG_ASSERT(carry == 0);
+/*
+ Log a query to the traditional slow log file
- if (sync_log)
- return flush_and_sync(0);
+ SYNOPSIS
+ write()
- return 0; // All OK
-}
+ thd THD of the query
+ current_time current timestamp
+ query_start_arg command start timestamp
+ user_host the pointer to the string with user@host info
+ user_host_len length of the user_host string. this is computed once
+ and passed to all general log event handlers
+ query_utime Amount of time the query took to execute (in microseconds)
+ lock_utime Amount of time the query was locked (in microseconds)
+ is_command The flag, which determines, whether the sql_text is a
+ query or an administrator command.
+ sql_text the very text of the query or administrator command
+ processed
+ sql_text_len the length of sql_text string
-/*
- Helper function to get the error code of the query to be binlogged.
- */
-int query_error_code(THD *thd, bool not_killed)
-{
- int error;
-
- if (not_killed)
- {
- error= thd->is_error() ? thd->main_da.sql_errno() : 0;
+ DESCRIPTION
- /* thd->main_da.sql_errno() might be ER_SERVER_SHUTDOWN or
- ER_QUERY_INTERRUPTED, So here we need to make sure that error
- is not set to these errors when specified not_killed by the
- caller.
- */
- if (error == ER_SERVER_SHUTDOWN || error == ER_QUERY_INTERRUPTED)
- error= 0;
- }
- else
- {
- /* killed status for DELAYED INSERT thread should never be used */
- DBUG_ASSERT(!(thd->system_thread & SYSTEM_THREAD_DELAYED_INSERT));
- error= thd->killed_errno();
- }
+ Log a query to the slow log file.
- return error;
-}
+ RETURN
+ FALSE - OK
+ TRUE - error occured
+*/
-bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock)
+bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time,
+ time_t query_start_arg, const char *user_host,
+ uint user_host_len, ulonglong query_utime,
+ ulonglong lock_utime, bool is_command,
+ const char *sql_text, uint sql_text_len)
{
- uint error= 0;
- DBUG_ENTER("MYSQL_BIN_LOG::write_incident");
- LEX_STRING const write_error_msg=
- { C_STRING_WITH_LEN("error writing to the binary log") };
- Incident incident= INCIDENT_LOST_EVENTS;
- Incident_log_event ev(thd, incident, write_error_msg);
- if (lock)
- pthread_mutex_lock(&LOCK_log);
- error= ev.write(&log_file);
- if (lock)
+ bool error= 0;
+ DBUG_ENTER("MYSQL_QUERY_LOG::write");
+
+ (void) pthread_mutex_lock(&LOCK_log);
+
+ if (!is_open())
{
- if (!error && !(error= flush_and_sync(0)))
- {
- signal_update();
- rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
- }
- pthread_mutex_unlock(&LOCK_log);
+ (void) pthread_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(0);
}
- DBUG_RETURN(error);
-}
-/**
- Write a cached log entry to the binary log.
- - To support transaction over replication, we wrap the transaction
- with BEGIN/COMMIT or BEGIN/ROLLBACK in the binary log.
- We want to write a BEGIN/ROLLBACK block when a non-transactional table
- was updated in a transaction which was rolled back. This is to ensure
- that the same updates are run on the slave.
-
- @param thd
- @param cache The cache to copy to the binlog
- @param commit_event The commit event to print after writing the
- contents of the cache.
- @param incident Defines if an incident event should be created to
- notify that some non-transactional changes did
- not get into the binlog.
+ if (is_open())
+ { // Safety agains reopen
+ int tmp_errno= 0;
+ char buff[80], *end;
+ char query_time_buff[22+7], lock_time_buff[22+7];
+ uint buff_len;
+ end= buff;
- @note
- We only come here if there is something in the cache.
- @note
- The thing in the cache is always a complete transaction.
- @note
- 'cache' needs to be reinitialized after this functions returns.
-*/
+ if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
+ {
+ if (current_time != last_time)
+ {
+ last_time= current_time;
+ struct tm start;
+ localtime_r(¤t_time, &start);
-bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event,
- bool incident)
-{
- DBUG_ENTER("MYSQL_BIN_LOG::write(THD *, IO_CACHE *, Log_event *)");
- VOID(pthread_mutex_lock(&LOCK_log));
+ buff_len= my_snprintf(buff, sizeof buff,
+ "# Time: %02d%02d%02d %2d:%02d:%02d\n",
+ start.tm_year % 100, start.tm_mon + 1,
+ start.tm_mday, start.tm_hour,
+ start.tm_min, start.tm_sec);
- DBUG_ASSERT(is_open());
- if (likely(is_open())) // Should always be true
- {
- /*
- We only bother to write to the binary log if there is anything
- to write.
- */
- if (my_b_tell(cache) > 0)
+ /* Note that my_b_write() assumes it knows the length for this */
+ if (my_b_write(&log_file, (uchar*) buff, buff_len))
+ tmp_errno= errno;
+ }
+ const uchar uh[]= "# User@Host: ";
+ if (my_b_write(&log_file, uh, sizeof(uh) - 1))
+ tmp_errno= errno;
+ if (my_b_write(&log_file, (uchar*) user_host, user_host_len))
+ tmp_errno= errno;
+ if (my_b_write(&log_file, (uchar*) "\n", 1))
+ tmp_errno= errno;
+ }
+ /* For slow query log */
+ sprintf(query_time_buff, "%.6f", ulonglong2double(query_utime)/1000000.0);
+ sprintf(lock_time_buff, "%.6f", ulonglong2double(lock_utime)/1000000.0);
+ if (my_b_printf(&log_file,
+ "# Query_time: %s Lock_time: %s"
+ " Rows_sent: %lu Rows_examined: %lu\n",
+ query_time_buff, lock_time_buff,
+ (ulong) thd->sent_row_count,
+ (ulong) thd->examined_row_count) == (uint) -1)
+ tmp_errno= errno;
+ if (thd->db && strcmp(thd->db, db))
+ { // Database changed
+ if (my_b_printf(&log_file,"use %s;\n",thd->db) == (uint) -1)
+ tmp_errno= errno;
+ strmov(db,thd->db);
+ }
+ if (thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt)
+ {
+ end=strmov(end, ",last_insert_id=");
+ end=longlong10_to_str((longlong)
+ thd->first_successful_insert_id_in_prev_stmt_for_binlog,
+ end, -10);
+ }
+ // Save value if we do an insert.
+ if (thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements() > 0)
{
- /*
- Log "BEGIN" at the beginning of every transaction. Here, a
- transaction is either a BEGIN..COMMIT block or a single
- statement in autocommit mode.
- */
- Query_log_event qinfo(thd, STRING_WITH_LEN("BEGIN"), TRUE, FALSE, TRUE, 0);
- if (qinfo.write(&log_file))
- goto err;
- DBUG_EXECUTE_IF("crash_before_writing_xid",
- {
- if ((write_error= write_cache(cache, false, true)))
- DBUG_PRINT("info", ("error writing binlog cache: %d",
- write_error));
- DBUG_PRINT("info", ("crashing before writing xid"));
- abort();
- });
-
- if ((write_error= write_cache(cache, false, false)))
- goto err;
-
- if (commit_event && commit_event->write(&log_file))
- goto err;
-
- if (incident && write_incident(thd, FALSE))
- goto err;
-
- bool synced= 0;
- if (flush_and_sync(&synced))
- goto err;
- DBUG_EXECUTE_IF("half_binlogged_transaction", abort(););
- if (cache->error) // Error on read
+ if (!(specialflag & SPECIAL_SHORT_LOG_FORMAT))
{
- sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno);
- write_error=1; // Don't give more errors
- goto err;
+ end=strmov(end,",insert_id=");
+ end=longlong10_to_str((longlong)
+ thd->auto_inc_intervals_in_cur_stmt_for_binlog.minimum(),
+ end, -10);
}
+ }
+
+ /*
+ This info used to show up randomly, depending on whether the query
+ checked the query start time or not. now we always write current
+ timestamp to the slow log
+ */
+ end= strmov(end, ",timestamp=");
+ end= int10_to_str((long) current_time, end, 10);
- if (RUN_HOOK(binlog_storage, after_flush,
- (thd, log_file_name, log_file.pos_in_file, synced)))
+ if (end != buff)
+ {
+ *end++=';';
+ *end='\n';
+ if (my_b_write(&log_file, (uchar*) "SET ", 4) ||
+ my_b_write(&log_file, (uchar*) buff + 1, (uint) (end-buff)))
+ tmp_errno= errno;
+ }
+ if (is_command)
+ {
+ end= strxmov(buff, "# administrator command: ", NullS);
+ buff_len= (ulong) (end - buff);
+ my_b_write(&log_file, (uchar*) buff, buff_len);
+ }
+ if (my_b_write(&log_file, (uchar*) sql_text, sql_text_len) ||
+ my_b_write(&log_file, (uchar*) ";\n",2) ||
+ flush_io_cache(&log_file))
+ tmp_errno= errno;
+ if (tmp_errno)
+ {
+ error= 1;
+ if (! write_error)
{
- sql_print_error("Failed to run 'after_flush' hooks");
- write_error=1;
- goto err;
+ write_error= 1;
+ sql_print_error(ER(ER_ERROR_ON_WRITE), name, error);
}
-
- signal_update();
- }
-
- /*
- if commit_event is Xid_log_event, increase the number of
- prepared_xids (it's decreasd in ::unlog()). Binlog cannot be rotated
- if there're prepared xids in it - see the comment in new_file() for
- an explanation.
- If the commit_event is not Xid_log_event (then it's a Query_log_event)
- rotate binlog, if necessary.
- */
- if (commit_event && commit_event->get_type_code() == XID_EVENT)
- {
- pthread_mutex_lock(&LOCK_prep_xids);
- prepared_xids++;
- pthread_mutex_unlock(&LOCK_prep_xids);
}
- else
- rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED);
}
- VOID(pthread_mutex_unlock(&LOCK_log));
+ (void) pthread_mutex_unlock(&LOCK_log);
+ DBUG_RETURN(error);
+}
- DBUG_RETURN(0);
-err:
- if (!write_error)
+/**
+ @todo
+ The following should be using fn_format(); We just need to
+ first change fn_format() to cut the file name if it's too long.
+*/
+const char *MYSQL_LOG::generate_name(const char *log_name,
+ const char *suffix,
+ bool strip_ext, char *buff)
+{
+ if (!log_name || !log_name[0])
{
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), name, errno);
+ strmake(buff, pidfile_name, FN_REFLEN - strlen(suffix) - 1);
+ return (const char *)
+ fn_format(buff, buff, "", suffix, MYF(MY_REPLACE_EXT|MY_REPLACE_DIR));
}
- VOID(pthread_mutex_unlock(&LOCK_log));
- DBUG_RETURN(1);
+ // get rid of extension if the log is binary to avoid problems
+ if (strip_ext)
+ {
+ char *p= fn_ext(log_name);
+ uint length= (uint) (p - log_name);
+ strmake(buff, log_name, min(length, FN_REFLEN-1));
+ return (const char*)buff;
+ }
+ return log_name;
}
-/**
- Wait until we get a signal that the relay log has been updated.
- @param thd Thread variable
- @note
- One must have a lock on LOCK_log before calling this function.
- This lock will be released before return! That's required by
- THD::enter_cond() (see NOTES in sql_class.h).
-*/
+/**
+ Move all data up in a file in an filename index file.
-void MYSQL_BIN_LOG::wait_for_update_relay_log(THD* thd)
-{
- const char *old_msg;
- DBUG_ENTER("wait_for_update_relay_log");
+ We do the copy outside of the IO_CACHE as the cache buffers would just
+ make things slower and more complicated.
+ In most cases the copy loop should only do one read.
- old_msg= thd->enter_cond(&update_cond, &LOCK_log,
- "Slave has read all relay log; "
- "waiting for the slave I/O "
- "thread to update it" );
- pthread_cond_wait(&update_cond, &LOCK_log);
- thd->exit_cond(old_msg);
- DBUG_VOID_RETURN;
-}
+ @param index_file File to move
+ @param offset Move everything from here to beginning
-/**
- Wait until we get a signal that the binary log has been updated.
- Applies to master only.
-
- NOTES
- @param[in] thd a THD struct
- @param[in] timeout a pointer to a timespec;
- NULL means to wait w/o timeout.
- @retval 0 if got signalled on update
- @retval non-0 if wait timeout elapsed
@note
- LOCK_log must be taken before calling this function.
- LOCK_log is being released while the thread is waiting.
- LOCK_log is released by the caller.
+ File will be truncated to be 'offset' shorter or filled up with newlines
+
+ @retval
+ 0 ok
*/
-int MYSQL_BIN_LOG::wait_for_update_bin_log(THD* thd,
- const struct timespec *timeout)
-{
- int ret= 0;
- const char* old_msg = thd->proc_info;
- DBUG_ENTER("wait_for_update_bin_log");
- old_msg= thd->enter_cond(&update_cond, &LOCK_log,
- "Master has sent all binlog to slave; "
- "waiting for binlog to be updated");
- if (!timeout)
- pthread_cond_wait(&update_cond, &LOCK_log);
- else
- ret= pthread_cond_timedwait(&update_cond, &LOCK_log,
- const_cast<struct timespec *>(timeout));
- DBUG_RETURN(ret);
-}
-/**
- Close the log file.
- @param exiting Bitmask for one or more of the following bits:
- - LOG_CLOSE_INDEX : if we should close the index file
- - LOG_CLOSE_TO_BE_OPENED : if we intend to call open
- at once after close.
- - LOG_CLOSE_STOP_EVENT : write a 'stop' event to the log
- @note
- One can do an open on the object at once after doing a close.
- The internal structures are not freed until cleanup() is called
-*/
+int error_log_print(enum loglevel level, const char *format,
+ va_list args)
+{
+ return logger.error_log_print(level, format, args);
+}
-void MYSQL_BIN_LOG::close(uint exiting)
-{ // One can't set log_type here!
- DBUG_ENTER("MYSQL_BIN_LOG::close");
- DBUG_PRINT("enter",("exiting: %d", (int) exiting));
- if (log_state == LOG_OPENED)
- {
-#ifdef HAVE_REPLICATION
- if (log_type == LOG_BIN && !no_auto_events &&
- (exiting & LOG_CLOSE_STOP_EVENT))
- {
- Stop_log_event s;
- s.write(&log_file);
- bytes_written+= s.data_written;
- signal_update();
- }
-#endif /* HAVE_REPLICATION */
- /* don't pwrite in a file opened with O_APPEND - it doesn't work */
- if (log_file.type == WRITE_CACHE && log_type == LOG_BIN)
- {
- my_off_t offset= BIN_LOG_HEADER_SIZE + FLAGS_OFFSET;
- my_off_t org_position= my_tell(log_file.file, MYF(0));
- uchar flags= 0; // clearing LOG_EVENT_BINLOG_IN_USE_F
- my_pwrite(log_file.file, &flags, 1, offset, MYF(0));
- /*
- Restore position so that anything we have in the IO_cache is written
- to the correct position.
- We need the seek here, as my_pwrite() is not guaranteed to keep the
- original position on system that doesn't support pwrite().
- */
- my_seek(log_file.file, org_position, MY_SEEK_SET, MYF(0));
- }
+bool slow_log_print(THD *thd, const char *query, uint query_length,
+ ulonglong current_utime)
+{
+ return logger.slow_log_print(thd, query, query_length, current_utime);
+}
- /* this will cleanup IO_CACHE, sync and close the file */
- MYSQL_LOG::close(exiting);
- }
+bool LOGGER::log_command(THD *thd, enum enum_server_command command)
+{
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *sctx= thd->security_ctx;
+#endif
/*
- The following test is needed even if is_open() is not set, as we may have
- called a not complete close earlier and the index file is still open.
+ Log command if we have at least one log event handler enabled and want
+ to log this king of commands
*/
-
- if ((exiting & LOG_CLOSE_INDEX) && my_b_inited(&index_file))
+ if (*general_log_handler_list && (what_to_log & (1L << (uint) command)))
{
- end_io_cache(&index_file);
- if (my_close(index_file.file, MYF(0)) < 0 && ! write_error)
+ if ((thd->options & OPTION_LOG_OFF)
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ && (sctx->master_access & SUPER_ACL)
+#endif
+ )
{
- write_error= 1;
- sql_print_error(ER(ER_ERROR_ON_WRITE), index_file_name, errno);
+ /* No logging */
+ return FALSE;
}
+
+ return TRUE;
}
- log_state= (exiting & LOG_CLOSE_TO_BE_OPENED) ? LOG_TO_BE_OPENED : LOG_CLOSED;
- safeFree(name);
- DBUG_VOID_RETURN;
+
+ return FALSE;
}
-void MYSQL_BIN_LOG::set_max_size(ulong max_size_arg)
+bool general_log_print(THD *thd, enum enum_server_command command,
+ const char *format, ...)
{
- /*
- We need to take locks, otherwise this may happen:
- new_file() is called, calls open(old_max_size), then before open() starts,
- set_max_size() sets max_size to max_size_arg, then open() starts and
- uses the old_max_size argument, so max_size_arg has been overwritten and
- it's like if the SET command was never run.
- */
- DBUG_ENTER("MYSQL_BIN_LOG::set_max_size");
- pthread_mutex_lock(&LOCK_log);
- if (is_open())
- max_size= max_size_arg;
- pthread_mutex_unlock(&LOCK_log);
- DBUG_VOID_RETURN;
+ va_list args;
+ uint error= 0;
+
+ /* Print the message to the buffer if we want to log this king of commands */
+ if (! logger.log_command(thd, command))
+ return FALSE;
+
+ va_start(args, format);
+ error= logger.general_log_print(thd, command, format, args);
+ va_end(args);
+
+ return error;
+}
+
+bool general_log_write(THD *thd, enum enum_server_command command,
+ const char *query, uint query_length)
+{
+ /* Write the message to the log if we want to log this king of commands */
+ if (logger.log_command(thd, command))
+ return logger.general_log_write(thd, command, query, query_length);
+
+ return FALSE;
}
+
/**
Check if a string is a valid number.
@@ -5417,14 +2069,6 @@ bool flush_error_log()
return result;
}
-void MYSQL_BIN_LOG::signal_update()
-{
- DBUG_ENTER("MYSQL_BIN_LOG::signal_update");
- signal_cnt++;
- pthread_cond_broadcast(&update_cond);
- DBUG_VOID_RETURN;
-}
-
#ifdef __NT__
static void print_buffer_to_nt_eventlog(enum loglevel level, char *buff,
size_t length, size_t buffLen)
@@ -6058,230 +2702,3 @@ int TC_LOG::using_heuristic_recover()
return 1;
}
-/****** transaction coordinator log for 2pc - binlog() based solution ******/
-#define TC_LOG_BINLOG MYSQL_BIN_LOG
-
-/**
- @todo
- keep in-memory list of prepared transactions
- (add to list in log(), remove on unlog())
- and copy it to the new binlog if rotated
- but let's check the behaviour of tc_log_page_waits first!
-*/
-
-int TC_LOG_BINLOG::open(const char *opt_name)
-{
- LOG_INFO log_info;
- int error= 1;
-
- DBUG_ASSERT(total_ha_2pc > 1);
- DBUG_ASSERT(opt_name && opt_name[0]);
-
- pthread_mutex_init(&LOCK_prep_xids, MY_MUTEX_INIT_FAST);
- pthread_cond_init (&COND_prep_xids, 0);
-
- if (!my_b_inited(&index_file))
- {
- /* There was a failure to open the index file, can't open the binlog */
- cleanup();
- return 1;
- }
-
- if (using_heuristic_recover())
- {
- /* generate a new binlog to mask a corrupted one */
- open(opt_name, LOG_BIN, 0, WRITE_CACHE, 0, max_binlog_size, 0, TRUE);
- cleanup();
- return 1;
- }
-
- if ((error= find_log_pos(&log_info, NullS, 1)))
- {
- if (error != LOG_INFO_EOF)
- sql_print_error("find_log_pos() failed (error: %d)", error);
- else
- error= 0;
- goto err;
- }
-
- {
- const char *errmsg;
- IO_CACHE log;
- File file;
- Log_event *ev=0;
- Format_description_log_event fdle(BINLOG_VERSION);
- char log_name[FN_REFLEN];
-
- if (! fdle.is_valid())
- goto err;
-
- do
- {
- strmake(log_name, log_info.log_file_name, sizeof(log_name)-1);
- } while (!(error= find_next_log(&log_info, 1)));
-
- if (error != LOG_INFO_EOF)
- {
- sql_print_error("find_log_pos() failed (error: %d)", error);
- goto err;
- }
-
- if ((file= open_binlog(&log, log_name, &errmsg)) < 0)
- {
- sql_print_error("%s", errmsg);
- goto err;
- }
-
- if ((ev= Log_event::read_log_event(&log, 0, &fdle)) &&
- ev->get_type_code() == FORMAT_DESCRIPTION_EVENT &&
- ev->flags & LOG_EVENT_BINLOG_IN_USE_F)
- {
- sql_print_information("Recovering after a crash using %s", opt_name);
- error= recover(&log, (Format_description_log_event *)ev);
- }
- else
- error=0;
-
- delete ev;
- end_io_cache(&log);
- my_close(file, MYF(MY_WME));
-
- if (error)
- goto err;
- }
-
-err:
- return error;
-}
-
-/** This is called on shutdown, after ha_panic. */
-void TC_LOG_BINLOG::close()
-{
- DBUG_ASSERT(prepared_xids==0);
- pthread_mutex_destroy(&LOCK_prep_xids);
- pthread_cond_destroy (&COND_prep_xids);
-}
-
-/**
- @todo
- group commit
-
- @retval
- 0 error
- @retval
- 1 success
-*/
-int TC_LOG_BINLOG::log_xid(THD *thd, my_xid xid)
-{
- DBUG_ENTER("TC_LOG_BINLOG::log");
- Xid_log_event xle(thd, xid);
- binlog_cache_mngr *cache_mngr=
- (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
- /*
- We always commit the entire transaction when writing an XID. Also
- note that the return value is inverted.
- */
- DBUG_RETURN(!binlog_flush_stmt_cache(thd, cache_mngr) &&
- !binlog_flush_trx_cache(thd, cache_mngr, &xle));
-}
-
-void TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid)
-{
- pthread_mutex_lock(&LOCK_prep_xids);
- DBUG_ASSERT(prepared_xids > 0);
- if (--prepared_xids == 0) {
- DBUG_PRINT("info", ("prepared_xids=%lu", prepared_xids));
- pthread_cond_signal(&COND_prep_xids);
- }
- pthread_mutex_unlock(&LOCK_prep_xids);
- rotate_and_purge(0); // as ::write() did not rotate
-}
-
-int TC_LOG_BINLOG::recover(IO_CACHE *log, Format_description_log_event *fdle)
-{
- Log_event *ev;
- HASH xids;
- MEM_ROOT mem_root;
-
- if (! fdle->is_valid() ||
- hash_init(&xids, &my_charset_bin, TC_LOG_PAGE_SIZE/3, 0,
- sizeof(my_xid), 0, 0, MYF(0)))
- goto err1;
-
- init_alloc_root(&mem_root, TC_LOG_PAGE_SIZE, TC_LOG_PAGE_SIZE);
-
- fdle->flags&= ~LOG_EVENT_BINLOG_IN_USE_F; // abort on the first error
-
- while ((ev= Log_event::read_log_event(log,0,fdle)) && ev->is_valid())
- {
- if (ev->get_type_code() == XID_EVENT)
- {
- Xid_log_event *xev=(Xid_log_event *)ev;
- uchar *x= (uchar *) memdup_root(&mem_root, (uchar*) &xev->xid,
- sizeof(xev->xid));
- if (!x || my_hash_insert(&xids, x))
- goto err2;
- }
- delete ev;
- }
-
- if (ha_recover(&xids))
- goto err2;
-
- free_root(&mem_root, MYF(0));
- hash_free(&xids);
- return 0;
-
-err2:
- free_root(&mem_root, MYF(0));
- hash_free(&xids);
-err1:
- sql_print_error("Crash recovery failed. Either correct the problem "
- "(if it's, for example, out of memory error) and restart, "
- "or delete (or rename) binary log and start mysqld with "
- "--tc-heuristic-recover={commit|rollback}");
- return 1;
-}
-
-
-#ifdef INNODB_COMPATIBILITY_HOOKS
-/**
- Get the file name of the MySQL binlog.
- @return the name of the binlog file
-*/
-extern "C"
-const char* mysql_bin_log_file_name(void)
-{
- return mysql_bin_log.get_log_fname();
-}
-/**
- Get the current position of the MySQL binlog.
- @return byte offset from the beginning of the binlog
-*/
-extern "C"
-ulonglong mysql_bin_log_file_pos(void)
-{
- return (ulonglong) mysql_bin_log.get_log_file()->pos_in_file;
-}
-#endif /* INNODB_COMPATIBILITY_HOOKS */
-
-
-struct st_mysql_storage_engine binlog_storage_engine=
-{ MYSQL_HANDLERTON_INTERFACE_VERSION };
-
-mysql_declare_plugin(binlog)
-{
- MYSQL_STORAGE_ENGINE_PLUGIN,
- &binlog_storage_engine,
- "binlog",
- "MySQL AB",
- "This is a pseudo storage engine to represent the binlog in a transaction",
- PLUGIN_LICENSE_GPL,
- binlog_init, /* Plugin Init */
- NULL, /* Plugin Deinit */
- 0x0100 /* 1.0 */,
- NULL, /* status variables */
- NULL, /* system variables */
- NULL /* config options */
-}
-mysql_declare_plugin_end;
=== modified file 'sql/log.h'
--- a/sql/log.h 2010-02-13 09:42:41 +0000
+++ b/sql/log.h 2010-04-26 05:01:18 +0000
@@ -244,228 +244,6 @@ private:
time_t last_time;
};
-class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
-{
- private:
- /* LOCK_log and LOCK_index are inited by init_pthread_objects() */
- pthread_mutex_t LOCK_index;
- pthread_mutex_t LOCK_prep_xids;
- pthread_cond_t COND_prep_xids;
- pthread_cond_t update_cond;
- ulonglong bytes_written;
- IO_CACHE index_file;
- char index_file_name[FN_REFLEN];
- /*
- purge_file is a temp file used in purge_logs so that the index file
- can be updated before deleting files from disk, yielding better crash
- recovery. It is created on demand the first time purge_logs is called
- and then reused for subsequent calls. It is cleaned up in cleanup().
- */
- IO_CACHE purge_index_file;
- char purge_index_file_name[FN_REFLEN];
- /*
- The max size before rotation (usable only if log_type == LOG_BIN: binary
- logs and relay logs).
- For a binlog, max_size should be max_binlog_size.
- For a relay log, it should be max_relay_log_size if this is non-zero,
- max_binlog_size otherwise.
- max_size is set in init(), and dynamically changed (when one does SET
- GLOBAL MAX_BINLOG_SIZE|MAX_RELAY_LOG_SIZE) by fix_max_binlog_size and
- fix_max_relay_log_size).
- */
- ulong max_size;
- long prepared_xids; /* for tc log - number of xids to remember */
- // current file sequence number for load data infile binary logging
- uint file_id;
- uint open_count; // For replication
- int readers_count;
- bool need_start_event;
- /*
- no_auto_events means we don't want any of these automatic events :
- Start/Rotate/Stop. That is, in 4.x when we rotate a relay log, we don't
- want a Rotate_log event to be written to the relay log. When we start a
- relay log etc. So in 4.x this is 1 for relay logs, 0 for binlogs.
- In 5.0 it's 0 for relay logs too!
- */
- bool no_auto_events;
-
- ulonglong m_table_map_version;
-
- /* pointer to the sync period variable, for binlog this will be
- sync_binlog_period, for relay log this will be
- sync_relay_log_period
- */
- uint *sync_period_ptr;
- uint sync_counter;
-
- inline uint get_sync_period()
- {
- return *sync_period_ptr;
- }
-
- int write_to_file(IO_CACHE *cache);
- /*
- This is used to start writing to a new log file. The difference from
- new_file() is locking. new_file_without_locking() does not acquire
- LOCK_log.
- */
- void new_file_without_locking();
- void new_file_impl(bool need_lock);
-
-public:
- MYSQL_LOG::generate_name;
- MYSQL_LOG::is_open;
-
- /* This is relay log */
- bool is_relay_log;
- ulong signal_cnt; // update of the counter is checked by heartbeat
- /*
- These describe the log's format. This is used only for relay logs.
- _for_exec is used by the SQL thread, _for_queue by the I/O thread. It's
- necessary to have 2 distinct objects, because the I/O thread may be reading
- events in a different format from what the SQL thread is reading (consider
- the case of a master which has been upgraded from 5.0 to 5.1 without doing
- RESET MASTER, or from 4.x to 5.0).
- */
- Format_description_log_event *description_event_for_exec,
- *description_event_for_queue;
-
- MYSQL_BIN_LOG(uint *sync_period);
- /*
- note that there's no destructor ~MYSQL_BIN_LOG() !
- The reason is that we don't want it to be automatically called
- on exit() - but only during the correct shutdown process
- */
-
- int open(const char *opt_name);
- void close();
- int log_xid(THD *thd, my_xid xid);
- void unlog(ulong cookie, my_xid xid);
- int recover(IO_CACHE *log, Format_description_log_event *fdle);
-#if !defined(MYSQL_CLIENT)
- bool is_table_mapped(TABLE *table) const
- {
- return table->s->table_map_version == table_map_version();
- }
-
- ulonglong table_map_version() const { return m_table_map_version; }
- void update_table_map_version() { ++m_table_map_version; }
-
- int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
- bool is_transactional);
- int remove_pending_rows_event(THD *thd, bool is_transactional);
-
-#endif /* !defined(MYSQL_CLIENT) */
- void reset_bytes_written()
- {
- bytes_written = 0;
- }
- void harvest_bytes_written(ulonglong* counter)
- {
-#ifndef DBUG_OFF
- char buf1[22],buf2[22];
-#endif
- DBUG_ENTER("harvest_bytes_written");
- (*counter)+=bytes_written;
- DBUG_PRINT("info",("counter: %s bytes_written: %s", llstr(*counter,buf1),
- llstr(bytes_written,buf2)));
- bytes_written=0;
- DBUG_VOID_RETURN;
- }
- void set_max_size(ulong max_size_arg);
- void signal_update();
- void wait_for_update_relay_log(THD* thd);
- int wait_for_update_bin_log(THD* thd, const struct timespec * timeout);
- void set_need_start_event() { need_start_event = 1; }
- void init(bool no_auto_events_arg, ulong max_size);
- void init_pthread_objects();
- void cleanup();
- bool open(const char *log_name,
- enum_log_type log_type,
- const char *new_name,
- enum cache_type io_cache_type_arg,
- bool no_auto_events_arg, ulong max_size,
- bool null_created,
- bool need_mutex);
- bool open_index_file(const char *index_file_name_arg,
- const char *log_name, bool need_mutex);
- /* Use this to start writing a new log file */
- void new_file();
-
- bool write(Log_event* event_info);
- bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident);
- bool write_incident(THD *thd, bool lock);
-
- int write_cache(IO_CACHE *cache, bool lock_log, bool flush_and_sync);
- void set_write_error(THD *thd);
- bool check_write_error(THD *thd);
-
- void start_union_events(THD *thd, query_id_t query_id_param);
- void stop_union_events(THD *thd);
- bool is_query_in_union(THD *thd, query_id_t query_id_param);
-
- /*
- v stands for vector
- invoked as appendv(buf1,len1,buf2,len2,...,bufn,lenn,0)
- */
- bool appendv(const char* buf,uint len,...);
- bool append(Log_event* ev);
-
- void make_log_name(char* buf, const char* log_ident);
- bool is_active(const char* log_file_name);
- int update_log_index(LOG_INFO* linfo, bool need_update_threads);
- void rotate_and_purge(uint flags);
- /**
- Flush binlog cache and synchronize to disk.
-
- This function flushes events in binlog cache to binary log file,
- it will do synchronizing according to the setting of system
- variable 'sync_binlog'. If file is synchronized, @c synced will
- be set to 1, otherwise 0.
-
- @param[out] synced if not NULL, set to 1 if file is synchronized, otherwise 0
-
- @retval 0 Success
- @retval other Failure
- */
- bool flush_and_sync(bool *synced);
- int purge_logs(const char *to_log, bool included,
- bool need_mutex, bool need_update_threads,
- ulonglong *decrease_log_space);
- int purge_logs_before_date(time_t purge_time);
- int purge_first_log(Relay_log_info* rli, bool included);
- int set_purge_index_file_name(const char *base_file_name);
- int open_purge_index_file(bool destroy);
- bool is_inited_purge_index_file();
- int close_purge_index_file();
- int clean_purge_index_file();
- int sync_purge_index_file();
- int register_purge_index_entry(const char* entry);
- int register_create_index_entry(const char* entry);
- int purge_index_entry(THD *thd, ulonglong *decrease_log_space,
- bool need_mutex);
- bool reset_logs(THD* thd);
- void close(uint exiting);
-
- // iterating through the log index file
- int find_log_pos(LOG_INFO* linfo, const char* log_name,
- bool need_mutex);
- int find_next_log(LOG_INFO* linfo, bool need_mutex);
- int get_current_log(LOG_INFO* linfo);
- int raw_get_current_log(LOG_INFO* linfo);
- uint next_file_id();
- inline char* get_index_fname() { return index_file_name;}
- inline char* get_log_fname() { return log_file_name; }
- inline char* get_name() { return name; }
- inline pthread_mutex_t* get_log_lock() { return &LOCK_log; }
- inline IO_CACHE* get_log_file() { return &log_file; }
-
- inline void lock_index() { pthread_mutex_lock(&LOCK_index);}
- inline void unlock_index() { pthread_mutex_unlock(&LOCK_index);}
- inline IO_CACHE *get_index_file() { return &index_file;}
- inline uint32 get_open_count() { return open_count; }
-};
-
class Log_event_handler
{
public:
@@ -629,24 +407,6 @@ public:
}
};
-enum enum_binlog_format {
- /*
- statement-based except for cases where only row-based can work (UUID()
- etc):
- */
- BINLOG_FORMAT_MIXED= 0,
- BINLOG_FORMAT_STMT= 1, // statement-based
- BINLOG_FORMAT_ROW= 2, // row_based
-/*
- This value is last, after the end of binlog_format_typelib: it has no
- corresponding cell in this typelib. We use this value to be able to know if
- the user has explicitely specified a binlog format at startup or not.
-*/
- BINLOG_FORMAT_UNSPEC= 3
-};
-extern TYPELIB binlog_format_typelib;
-
-int query_error_code(THD *thd, bool not_killed);
uint purge_log_get_error_code(int res);
#endif /* LOG_H */
=== modified file 'sql/slave.h'
--- a/sql/slave.h 2010-02-12 23:30:44 +0000
+++ b/sql/slave.h 2010-04-26 05:01:18 +0000
@@ -36,6 +36,7 @@
#ifdef HAVE_REPLICATION
#include "log.h"
+#include "binlog.h"
#include "my_list.h"
#include "rpl_filter.h"
#include "rpl_tblmap.h"
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2010-03-17 11:31:50 +0000
+++ b/sql/sql_class.h 2010-04-26 05:01:18 +0000
@@ -24,6 +24,7 @@
#endif
#include "log.h"
+#include "binlog.h"
#include "rpl_tblmap.h"
#include "replication.h"
=== modified file 'sql/sql_db.cc'
--- a/sql/sql_db.cc 2010-02-12 23:30:44 +0000
+++ b/sql/sql_db.cc 2010-04-26 05:01:18 +0000
@@ -23,6 +23,7 @@
#include <my_dir.h>
#include <m_ctype.h>
#include "log.h"
+#include "binlog.h"
#ifdef __WIN__
#include <direct.h>
#endif
=== added file 'sql/sql_master.cc'
--- a/sql/sql_master.cc 1970-01-01 00:00:00 +0000
+++ b/sql/sql_master.cc 2010-04-26 05:01:18 +0000
@@ -0,0 +1,1191 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include "sql_master.h"
+
+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
+
+/*
+ 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(NET* net, String* packet, char* log_file_name,
+ ulonglong position, const char** errmsg)
+{
+ DBUG_ENTER("fake_rotate_event");
+ char header[LOG_EVENT_HEADER_LEN], buf[ROTATE_HEADER_LEN+100];
+ /*
+ '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] = ROTATE_EVENT;
+
+ char* p = log_file_name+dirname_length(log_file_name);
+ uint ident_len = (uint) strlen(p);
+ ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN;
+ int4store(header + SERVER_ID_OFFSET, 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, 0);
+
+ packet->append(header, sizeof(header));
+ int8store(buf+R_POS_OFFSET,position);
+ packet->append(buf, ROTATE_HEADER_LEN);
+ packet->append(p,ident_len);
+ if (my_net_write(net, (uchar*) packet->ptr(), packet->length()))
+ {
+ *errmsg = "failed on my_net_write()";
+ DBUG_RETURN(-1);
+ }
+ DBUG_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 = my_open(fname, O_RDONLY, MYF(0))) < 0)
+ {
+ errmsg = "on open of file";
+ goto err;
+ }
+
+ while ((long) (bytes= my_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)
+ (void) my_close(fd, MYF(0));
+ if (errmsg)
+ {
+ sql_print_error("Failed in send_file() %s", errmsg);
+ DBUG_PRINT("error", ("%s", errmsg));
+ }
+ DBUG_RETURN(error);
+}
+
+
+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";
+ 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)
+{
+ my_bool null_value;
+ LEX_STRING name= { C_STRING_WITH_LEN("master_heartbeat_period")};
+ user_var_entry *entry=
+ (user_var_entry*) hash_search(&thd->user_vars, (uchar*) name.str,
+ name.length);
+ return entry? entry->val_int(&null_value) : 0;
+}
+
+/*
+ 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)
+{
+ DBUG_ENTER("send_heartbeat_event");
+ char header[LOG_EVENT_HEADER_LEN];
+ /*
+ '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;
+ int4store(header + SERVER_ID_OFFSET, 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 (my_net_write(net, (uchar*) packet->ptr(), packet->length()) ||
+ net_flush(net))
+ {
+ DBUG_RETURN(-1);
+ }
+ DBUG_RETURN(0);
+}
+
+/*
+ TODO: Clean up loop to only have one call to send_file()
+*/
+
+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* packet = &thd->packet;
+ int error;
+ const char *errmsg = "Unknown error";
+ NET* net = &thd->net;
+ pthread_mutex_t *log_lock;
+ bool binlog_can_be_corrupted= FALSE;
+#ifndef DBUG_OFF
+ int left_events = max_binlog_dump_events;
+#endif
+ DBUG_ENTER("mysql_binlog_send");
+ DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos));
+
+ bzero((char*) &log,sizeof(log));
+ /*
+ heartbeat_period from @master_heartbeat_period user variable
+ */
+ ulonglong heartbeat_period= get_heartbeat_period(thd);
+ struct timespec heartbeat_buf;
+ struct event_coordinates coord_buf;
+ struct timespec *heartbeat_ts= NULL;
+ struct event_coordinates *coord= NULL;
+ if (heartbeat_period != LL(0))
+ {
+ heartbeat_ts= &heartbeat_buf;
+ set_timespec_nsec(*heartbeat_ts, 0);
+ coord= &coord_buf;
+ coord->file_name= log_file_name; // initialization basing on what slave remembers
+ coord->pos= pos;
+ }
+ sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)",
+ thd->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;
+ }
+
+#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 (!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 (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;
+ }
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = &linfo;
+ pthread_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;
+ }
+
+ /* 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(net, packet, log_file_name, pos, &errmsg))
+ {
+ /*
+ 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_LOG_EVENT_HEADER;
+
+ /*
+ 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).
+ */
+ log_lock = mysql_bin_log.get_log_lock();
+ 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)))
+ {
+ /*
+ 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)
+ {
+ binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] &
+ LOG_EVENT_BINLOG_IN_USE_F);
+ (*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);
+ /* send it */
+ if (my_net_write(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. */
+ }
+
+ /* seek to the requested position, to start the requested dump */
+ my_b_seek(&log, pos); // Seek will done on next read
+
+ while (!net->error && net->vio != 0 && !thd->killed)
+ {
+ Log_event_type event_type= UNKNOWN_EVENT;
+
+ /* reset the transmit packet for the event read from binary log
+ file */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+ while (!(error = Log_event::read_log_event(&log, packet, log_lock)))
+ {
+#ifndef DBUG_OFF
+ if (max_binlog_dump_events && !left_events--)
+ {
+ net_flush(net);
+ errmsg = "Debugging binlog dump abort";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+#endif
+ /*
+ log's filename does not change while it's active
+ */
+ if (coord)
+ coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
+
+ event_type= (Log_event_type)((*packet)[LOG_EVENT_OFFSET+ev_offset]);
+ if (event_type == FORMAT_DESCRIPTION_EVENT)
+ {
+ binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] &
+ LOG_EVENT_BINLOG_IN_USE_F);
+ (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F;
+ }
+ else if (event_type == STOP_EVENT)
+ binlog_can_be_corrupted= FALSE;
+
+ pos = my_b_tell(&log);
+ if (RUN_HOOK(binlog_transmit, before_send_event,
+ (thd, flags, packet, log_file_name, pos)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ errmsg= "run 'before_send_event' hook failed";
+ goto err;
+ }
+
+ if (my_net_write(net, (uchar*) packet->ptr(), packet->length()))
+ {
+ errmsg = "Failed on my_net_write()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+
+ DBUG_PRINT("info", ("log event code %d", event_type));
+ if (event_type == LOAD_EVENT)
+ {
+ if (send_file(thd))
+ {
+ errmsg = "failed in send_file()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ }
+
+ if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet)))
+ {
+ errmsg= "Failed to run hook 'after_send_event'";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+
+ /* reset transmit packet for next loop */
+ if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
+ goto err;
+ }
+
+ /*
+ here we were reading binlog that was not closed properly (as a result
+ of a crash ?). treat any corruption as EOF
+ */
+ if (binlog_can_be_corrupted && error != LOG_READ_MEM)
+ error=LOG_READ_EOF;
+ /*
+ 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;
+
+ if (!(flags & BINLOG_DUMP_NON_BLOCK) &&
+ mysql_bin_log.is_active(log_file_name))
+ {
+ /*
+ Block until there is more data in the log
+ */
+ if (net_flush(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.
+ */
+
+ pthread_mutex_lock(log_lock);
+ switch (error= Log_event::read_log_event(&log, packet, (pthread_mutex_t*) 0)) {
+ case 0:
+ /* we read successfully, so we'll need to send it to the slave */
+ pthread_mutex_unlock(log_lock);
+ read_packet = 1;
+ if (coord)
+ coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
+ event_type= (Log_event_type)((*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"));
+ if (thd->server_id==0) // for mysqlbinlog (mysqlbinlog.server_id==0)
+ {
+ pthread_mutex_unlock(log_lock);
+ goto end;
+ }
+
+#ifndef DBUG_OFF
+ ulong hb_info_counter= 0;
+#endif
+ signal_cnt= mysql_bin_log.signal_cnt;
+ do
+ {
+ if (coord)
+ {
+ DBUG_ASSERT(heartbeat_ts && heartbeat_period != LL(0));
+ set_timespec_nsec(*heartbeat_ts, heartbeat_period);
+ }
+ ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts);
+ DBUG_ASSERT(ret == 0 || heartbeat_period != LL(0) && coord != NULL);
+ 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))
+ goto err;
+ if (send_heartbeat_event(net, packet, coord))
+ {
+ errmsg = "Failed on my_net_write()";
+ my_errno= ER_UNKNOWN_ERROR;
+ pthread_mutex_unlock(log_lock);
+ goto err;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("wait",("binary log received update or a broadcast signal caught"));
+ }
+ } while (signal_cnt == mysql_bin_log.signal_cnt && !thd->killed);
+ pthread_mutex_unlock(log_lock);
+ }
+ break;
+
+ default:
+ pthread_mutex_unlock(log_lock);
+ test_for_non_eof_log_read_errors(error, &errmsg);
+ goto err;
+ }
+
+ if (read_packet)
+ {
+ thd_proc_info(thd, "Sending binlog event to slave");
+ pos = my_b_tell(&log);
+ if (RUN_HOOK(binlog_transmit, before_send_event,
+ (thd, flags, packet, log_file_name, pos)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ errmsg= "run 'before_send_event' hook failed";
+ goto err;
+ }
+
+ if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) )
+ {
+ errmsg = "Failed on my_net_write()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+
+ if (event_type == LOAD_EVENT)
+ {
+ if (send_file(thd))
+ {
+ errmsg = "failed in send_file()";
+ my_errno= ER_UNKNOWN_ERROR;
+ goto err;
+ }
+ }
+
+ if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet)))
+ {
+ my_errno= ER_UNKNOWN_ERROR;
+ errmsg= "Failed to run hook 'after_send_event'";
+ goto err;
+ }
+ }
+
+ log.error=0;
+ }
+ }
+ else
+ {
+ bool loop_breaker = 0;
+ /* need this to break out of the for loop from switch */
+
+ thd_proc_info(thd, "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;
+ }
+ 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);
+ (void) my_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(net, packet, log_file_name, BIN_LOG_HEADER_SIZE,
+ &errmsg))
+ {
+ my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
+ goto err;
+ }
+
+ if (coord)
+ coord->file_name= log_file_name; // reset to the next
+ }
+ }
+
+end:
+ end_io_cache(&log);
+ (void)my_close(file, MYF(MY_WME));
+
+ RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags));
+ my_eof(thd);
+ thd_proc_info(thd, "Waiting to finalize termination");
+ pthread_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ pthread_mutex_unlock(&LOCK_thread_count);
+ DBUG_VOID_RETURN;
+
+err:
+ thd_proc_info(thd, "Waiting to finalize termination");
+ end_io_cache(&log);
+ 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
+ */
+ pthread_mutex_lock(&LOCK_thread_count);
+ thd->current_linfo = 0;
+ pthread_mutex_unlock(&LOCK_thread_count);
+ if (file >= 0)
+ (void) my_close(file, MYF(MY_WME));
+
+ my_message(my_errno, errmsg, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+/*
+ 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 (pthread_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)
+{
+ pthread_mutex_lock(&LOCK_thread_count);
+ I_List_iterator<THD> it(threads);
+ THD *tmp;
+
+ while ((tmp=it++))
+ {
+ if (tmp->command == COM_BINLOG_DUMP &&
+ tmp->server_id == slave_server_id)
+ {
+ pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
+ break;
+ }
+ }
+ pthread_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(THD::KILL_QUERY);
+ pthread_mutex_unlock(&tmp->LOCK_thd_data);
+ }
+}
+
+/**
+ 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)
+{
+ 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) ||
+/* RUN_HOOK(rpl_master, reset, (thd)) || */
+ RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */)))
+ return 1;
+ 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)
+{
+ DBUG_ENTER("mysql_show_relaylog_events");
+ DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS);
+ ha_binlog_wait(thd);
+ DBUG_RETURN(show_binlog_events(thd, &mysql_bin_log));
+}
+
+/**
+ 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_fields(&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_message(ER_NO_BINARY_LOGGING, ER(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_fields(&field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
+ DBUG_RETURN(TRUE);
+
+ pthread_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
+ pthread_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= my_open(fname, O_RDONLY | O_SHARE | O_BINARY,
+ MYF(0))) >= 0)
+ {
+ file_length= (ulonglong) my_seek(file, 0L, MY_SEEK_END, MYF(0));
+ my_close(file, MYF(0));
+ }
+ }
+ protocol->store(file_length);
+ if (protocol->write())
+ 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 += min(block_len, max_event_size),
+ block_len -= 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,
+ 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,
+ 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_SYNC_POINT("debug_lock.created_file_event",10);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+class sys_var_sync_binlog_period :public sys_var_long_ptr
+{
+public:
+ sys_var_sync_binlog_period(sys_var_chain *chain, const char *name_arg,
+ ulong *value_ptr)
+ :sys_var_long_ptr(chain, name_arg,value_ptr) {}
+ bool update(THD *thd, set_var *var);
+};
+
+static sys_var_chain vars = { NULL, NULL };
+static sys_var_uint_ptr sys_sync_binlog_period(&vars, "sync_binlog",
+ &sync_binlog_period);
+
+int init_master_sys_vars()
+{
+ if (mysql_add_sys_var_chain(vars.first, my_long_options))
+ {
+ /* should not happen */
+ fprintf(stderr, "failed to initialize replication master system variables");
+ unireg_abort(1);
+ }
+ return 0;
+}
=== added file 'sql/sql_master.h'
--- a/sql/sql_master.h 1970-01-01 00:00:00 +0000
+++ b/sql/sql_master.h 2010-04-26 05:01:18 +0000
@@ -0,0 +1,62 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#ifndef RPL_MASTER_H
+#define RPL_MASTER_H
+
+#include <mysql_priv.h>
+#include <my_global.h>
+#include <my_pthread.h>
+#include "binlog.h"
+#include "rpl_filter.h"
+#include "rpl_handler.h"
+
+#ifdef HAVE_REPLICATION
+typedef struct st_slave_info
+{
+ uint32 server_id;
+ uint32 rpl_recovery_rank, master_id;
+ char host[HOSTNAME_LENGTH+1];
+ char user[USERNAME_LENGTH+1];
+ char password[MAX_PASSWORD_LENGTH+1];
+ uint16 port;
+ THD* thd;
+} SLAVE_INFO;
+
+extern int max_binlog_dump_events;
+extern my_bool opt_sporadic_binlog_dump_fail;
+
+bool mysql_show_binlog_events(THD* thd);
+int reset_master(THD* thd);
+bool purge_master_logs(THD* thd, const char* to_log);
+bool purge_master_logs_before_date(THD* thd, time_t purge_time);
+bool log_in_use(const char* log_name);
+void adjust_linfo_offsets(my_off_t purge_offset);
+bool show_binlogs(THD* thd);
+void kill_zombie_dump_threads(uint32 slave_server_id);
+
+typedef struct st_load_file_info
+{
+ THD* thd;
+ my_off_t last_pos_in_file;
+ bool wrote_create_file, log_delayed;
+} LOAD_FILE_INFO;
+
+int log_loaded_block(IO_CACHE* file);
+int init_master_sys_vars();
+
+#endif /* HAVE_REPLICATION */
+#endif /* RPL_MASTER_H */
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2010-03-03 14:43:35 +0000
+++ b/sql/sql_parse.cc 2010-04-26 05:01:18 +0000
@@ -2260,7 +2260,13 @@ mysql_execute_command(THD *thd)
res = show_slave_hosts(thd);
break;
}
- case SQLCOM_SHOW_RELAYLOG_EVENTS: /* fall through */
+ case SQLCOM_SHOW_RELAYLOG_EVENTS:
+ {
+ if (check_global_access(thd, REPL_SLAVE_ACL))
+ goto error;
+ res = mysql_show_relaylog_events(thd);
+ break;
+ }
case SQLCOM_SHOW_BINLOG_EVENTS:
{
if (check_global_access(thd, REPL_SLAVE_ACL))
=== removed file 'sql/sql_repl.cc'
--- a/sql/sql_repl.cc 2010-03-03 14:43:35 +0000
+++ b/sql/sql_repl.cc 1970-01-01 00:00:00 +0000
@@ -1,2134 +0,0 @@
-/* Copyright (C) 2000-2006 MySQL AB & Sasha
-
- 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
-
-#include "mysql_priv.h"
-#ifdef HAVE_REPLICATION
-
-#include "rpl_mi.h"
-#include "sql_repl.h"
-#include "log_event.h"
-#include "rpl_filter.h"
-#include <my_dir.h>
-#include "rpl_handler.h"
-
-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
-
-/*
- 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(NET* net, String* packet, char* log_file_name,
- ulonglong position, const char** errmsg)
-{
- DBUG_ENTER("fake_rotate_event");
- char header[LOG_EVENT_HEADER_LEN], buf[ROTATE_HEADER_LEN+100];
- /*
- '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] = ROTATE_EVENT;
-
- char* p = log_file_name+dirname_length(log_file_name);
- uint ident_len = (uint) strlen(p);
- ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN;
- int4store(header + SERVER_ID_OFFSET, 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, 0);
-
- packet->append(header, sizeof(header));
- int8store(buf+R_POS_OFFSET,position);
- packet->append(buf, ROTATE_HEADER_LEN);
- packet->append(p,ident_len);
- if (my_net_write(net, (uchar*) packet->ptr(), packet->length()))
- {
- *errmsg = "failed on my_net_write()";
- DBUG_RETURN(-1);
- }
- DBUG_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 = my_open(fname, O_RDONLY, MYF(0))) < 0)
- {
- errmsg = "on open of file";
- goto err;
- }
-
- while ((long) (bytes= my_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)
- (void) my_close(fd, MYF(0));
- if (errmsg)
- {
- sql_print_error("Failed in send_file() %s", errmsg);
- DBUG_PRINT("error", ("%s", errmsg));
- }
- DBUG_RETURN(error);
-}
-
-
-/*
- 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;
-
- pthread_mutex_lock(&LOCK_thread_count);
- I_List_iterator<THD> it(threads);
-
- while ((tmp=it++))
- {
- LOG_INFO* linfo;
- if ((linfo = tmp->current_linfo))
- {
- pthread_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;
- pthread_mutex_unlock(&linfo->lock);
- }
- }
- pthread_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;
-
- pthread_mutex_lock(&LOCK_thread_count);
- I_List_iterator<THD> it(threads);
-
- while ((tmp=it++))
- {
- LOG_INFO* linfo;
- if ((linfo = tmp->current_linfo))
- {
- pthread_mutex_lock(&linfo->lock);
- result = !bcmp((uchar*) log_name, (uchar*) linfo->log_file_name,
- log_name_len);
- pthread_mutex_unlock(&linfo->lock);
- if (result)
- break;
- }
- }
-
- pthread_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";
- 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)
-{
- my_bool null_value;
- LEX_STRING name= { C_STRING_WITH_LEN("master_heartbeat_period")};
- user_var_entry *entry=
- (user_var_entry*) hash_search(&thd->user_vars, (uchar*) name.str,
- name.length);
- return entry? entry->val_int(&null_value) : 0;
-}
-
-/*
- 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)
-{
- DBUG_ENTER("send_heartbeat_event");
- char header[LOG_EVENT_HEADER_LEN];
- /*
- '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;
- int4store(header + SERVER_ID_OFFSET, 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 (my_net_write(net, (uchar*) packet->ptr(), packet->length()) ||
- net_flush(net))
- {
- DBUG_RETURN(-1);
- }
- DBUG_RETURN(0);
-}
-
-/*
- TODO: Clean up loop to only have one call to send_file()
-*/
-
-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* packet = &thd->packet;
- int error;
- const char *errmsg = "Unknown error";
- NET* net = &thd->net;
- pthread_mutex_t *log_lock;
- bool binlog_can_be_corrupted= FALSE;
-#ifndef DBUG_OFF
- int left_events = max_binlog_dump_events;
-#endif
- DBUG_ENTER("mysql_binlog_send");
- DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos));
-
- bzero((char*) &log,sizeof(log));
- /*
- heartbeat_period from @master_heartbeat_period user variable
- */
- ulonglong heartbeat_period= get_heartbeat_period(thd);
- struct timespec heartbeat_buf;
- struct event_coordinates coord_buf;
- struct timespec *heartbeat_ts= NULL;
- struct event_coordinates *coord= NULL;
- if (heartbeat_period != LL(0))
- {
- heartbeat_ts= &heartbeat_buf;
- set_timespec_nsec(*heartbeat_ts, 0);
- coord= &coord_buf;
- coord->file_name= log_file_name; // initialization basing on what slave remembers
- coord->pos= pos;
- }
- sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)",
- thd->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;
- }
-
-#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 (!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 (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;
- }
-
- pthread_mutex_lock(&LOCK_thread_count);
- thd->current_linfo = &linfo;
- pthread_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;
- }
-
- /* 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(net, packet, log_file_name, pos, &errmsg))
- {
- /*
- 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_LOG_EVENT_HEADER;
-
- /*
- 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).
- */
- log_lock = mysql_bin_log.get_log_lock();
- 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)))
- {
- /*
- 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)
- {
- binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] &
- LOG_EVENT_BINLOG_IN_USE_F);
- (*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);
- /* send it */
- if (my_net_write(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. */
- }
-
- /* seek to the requested position, to start the requested dump */
- my_b_seek(&log, pos); // Seek will done on next read
-
- while (!net->error && net->vio != 0 && !thd->killed)
- {
- Log_event_type event_type= UNKNOWN_EVENT;
-
- /* reset the transmit packet for the event read from binary log
- file */
- if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
- goto err;
- while (!(error = Log_event::read_log_event(&log, packet, log_lock)))
- {
-#ifndef DBUG_OFF
- if (max_binlog_dump_events && !left_events--)
- {
- net_flush(net);
- errmsg = "Debugging binlog dump abort";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
-#endif
- /*
- log's filename does not change while it's active
- */
- if (coord)
- coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
-
- event_type= (Log_event_type)((*packet)[LOG_EVENT_OFFSET+ev_offset]);
- if (event_type == FORMAT_DESCRIPTION_EVENT)
- {
- binlog_can_be_corrupted= test((*packet)[FLAGS_OFFSET+ev_offset] &
- LOG_EVENT_BINLOG_IN_USE_F);
- (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F;
- }
- else if (event_type == STOP_EVENT)
- binlog_can_be_corrupted= FALSE;
-
- pos = my_b_tell(&log);
- if (RUN_HOOK(binlog_transmit, before_send_event,
- (thd, flags, packet, log_file_name, pos)))
- {
- my_errno= ER_UNKNOWN_ERROR;
- errmsg= "run 'before_send_event' hook failed";
- goto err;
- }
-
- if (my_net_write(net, (uchar*) packet->ptr(), packet->length()))
- {
- errmsg = "Failed on my_net_write()";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
-
- DBUG_PRINT("info", ("log event code %d", event_type));
- if (event_type == LOAD_EVENT)
- {
- if (send_file(thd))
- {
- errmsg = "failed in send_file()";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
- }
-
- if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet)))
- {
- errmsg= "Failed to run hook 'after_send_event'";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
-
- /* reset transmit packet for next loop */
- if (reset_transmit_packet(thd, flags, &ev_offset, &errmsg))
- goto err;
- }
-
- /*
- here we were reading binlog that was not closed properly (as a result
- of a crash ?). treat any corruption as EOF
- */
- if (binlog_can_be_corrupted && error != LOG_READ_MEM)
- error=LOG_READ_EOF;
- /*
- 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;
-
- if (!(flags & BINLOG_DUMP_NON_BLOCK) &&
- mysql_bin_log.is_active(log_file_name))
- {
- /*
- Block until there is more data in the log
- */
- if (net_flush(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.
- */
-
- pthread_mutex_lock(log_lock);
- switch (error= Log_event::read_log_event(&log, packet, (pthread_mutex_t*) 0)) {
- case 0:
- /* we read successfully, so we'll need to send it to the slave */
- pthread_mutex_unlock(log_lock);
- read_packet = 1;
- if (coord)
- coord->pos= uint4korr(packet->ptr() + ev_offset + LOG_POS_OFFSET);
- event_type= (Log_event_type)((*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"));
- if (thd->server_id==0) // for mysqlbinlog (mysqlbinlog.server_id==0)
- {
- pthread_mutex_unlock(log_lock);
- goto end;
- }
-
-#ifndef DBUG_OFF
- ulong hb_info_counter= 0;
-#endif
- signal_cnt= mysql_bin_log.signal_cnt;
- do
- {
- if (coord)
- {
- DBUG_ASSERT(heartbeat_ts && heartbeat_period != LL(0));
- set_timespec_nsec(*heartbeat_ts, heartbeat_period);
- }
- ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts);
- DBUG_ASSERT(ret == 0 || heartbeat_period != LL(0) && coord != NULL);
- 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))
- goto err;
- if (send_heartbeat_event(net, packet, coord))
- {
- errmsg = "Failed on my_net_write()";
- my_errno= ER_UNKNOWN_ERROR;
- pthread_mutex_unlock(log_lock);
- goto err;
- }
- }
- else
- {
- DBUG_PRINT("wait",("binary log received update or a broadcast signal caught"));
- }
- } while (signal_cnt == mysql_bin_log.signal_cnt && !thd->killed);
- pthread_mutex_unlock(log_lock);
- }
- break;
-
- default:
- pthread_mutex_unlock(log_lock);
- test_for_non_eof_log_read_errors(error, &errmsg);
- goto err;
- }
-
- if (read_packet)
- {
- thd_proc_info(thd, "Sending binlog event to slave");
- pos = my_b_tell(&log);
- if (RUN_HOOK(binlog_transmit, before_send_event,
- (thd, flags, packet, log_file_name, pos)))
- {
- my_errno= ER_UNKNOWN_ERROR;
- errmsg= "run 'before_send_event' hook failed";
- goto err;
- }
-
- if (my_net_write(net, (uchar*) packet->ptr(), packet->length()) )
- {
- errmsg = "Failed on my_net_write()";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
-
- if (event_type == LOAD_EVENT)
- {
- if (send_file(thd))
- {
- errmsg = "failed in send_file()";
- my_errno= ER_UNKNOWN_ERROR;
- goto err;
- }
- }
-
- if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet)))
- {
- my_errno= ER_UNKNOWN_ERROR;
- errmsg= "Failed to run hook 'after_send_event'";
- goto err;
- }
- }
-
- log.error=0;
- }
- }
- else
- {
- bool loop_breaker = 0;
- /* need this to break out of the for loop from switch */
-
- thd_proc_info(thd, "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;
- }
- 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);
- (void) my_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(net, packet, log_file_name, BIN_LOG_HEADER_SIZE,
- &errmsg))
- {
- my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
- goto err;
- }
-
- if (coord)
- coord->file_name= log_file_name; // reset to the next
- }
- }
-
-end:
- end_io_cache(&log);
- (void)my_close(file, MYF(MY_WME));
-
- RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags));
- my_eof(thd);
- thd_proc_info(thd, "Waiting to finalize termination");
- pthread_mutex_lock(&LOCK_thread_count);
- thd->current_linfo = 0;
- pthread_mutex_unlock(&LOCK_thread_count);
- DBUG_VOID_RETURN;
-
-err:
- thd_proc_info(thd, "Waiting to finalize termination");
- end_io_cache(&log);
- 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
- */
- pthread_mutex_lock(&LOCK_thread_count);
- thd->current_linfo = 0;
- pthread_mutex_unlock(&LOCK_thread_count);
- if (file >= 0)
- (void) my_close(file, MYF(MY_WME));
-
- my_message(my_errno, errmsg, 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->main_da.
-
- @retval 0 success
- @retval 1 error
-*/
-int start_slave(THD* thd , Master_info* mi, bool net_report)
-{
- int slave_errno= 0;
- int thread_mask;
- DBUG_ENTER("start_slave");
-
- if (check_access(thd, SUPER_ACL, any_db,0,0,0,0))
- DBUG_RETURN(1);
- lock_slave_threads(mi); // this allows us to cleanly read slave_running
- // Get a mask of _stopped_ threads
- init_thread_mask(&thread_mask,mi,1 /* inverse */);
- /*
- 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,relay_log_info_file, 0,
- thread_mask))
- slave_errno=ER_MASTER_INFO;
- else if (server_id_supplied && *mi->host)
- {
- /*
- 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)
- {
- pthread_mutex_lock(&mi->rli.data_lock);
-
- if (thd->lex->mi.pos)
- {
- 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(mi->rli.until_log_name, thd->lex->mi.log_file_name,
- sizeof(mi->rli.until_log_name)-1);
- }
- else if (thd->lex->mi.relay_log_pos)
- {
- mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS;
- mi->rli.until_log_pos= thd->lex->mi.relay_log_pos;
- strmake(mi->rli.until_log_name, thd->lex->mi.relay_log_name,
- sizeof(mi->rli.until_log_name)-1);
- }
- else
- mi->rli.clear_until_condition();
-
- if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE)
- {
- /* 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;
-
- /* Issuing warning then started without --skip-slave-start */
- if (!opt_skip_slave_start)
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_MISSING_SKIP_SLAVE,
- ER(ER_MISSING_SKIP_SLAVE));
- }
-
- pthread_mutex_unlock(&mi->rli.data_lock);
- }
- else if (thd->lex->mi.pos || thd->lex->mi.relay_log_pos)
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED,
- ER(ER_UNTIL_COND_IGNORED));
-
- if (!slave_errno)
- slave_errno = start_slave_threads(0 /*no mutex */,
- 1 /* wait for start */,
- mi,
- master_info_file,relay_log_info_file,
- thread_mask);
- }
- else
- slave_errno = ER_BAD_SLAVE;
- }
- else
- {
- /* no error if all threads are already started, only a warning */
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING,
- ER(ER_SLAVE_WAS_RUNNING));
- }
-
- unlock_slave_threads(mi);
-
- if (slave_errno)
- {
- if (net_report)
- my_message(slave_errno, ER(slave_errno), MYF(0));
- DBUG_RETURN(1);
- }
- else if (net_report)
- my_ok(thd);
-
- 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->main_da.
-
- @retval 0 success
- @retval 1 error
-*/
-int stop_slave(THD* thd, Master_info* mi, bool net_report )
-{
- DBUG_ENTER("stop_slave");
-
- int slave_errno;
- if (!thd)
- thd = current_thd;
-
- if (check_access(thd, SUPER_ACL, any_db,0,0,0,0))
- DBUG_RETURN(1);
- thd_proc_info(thd, "Killing slave");
- int thread_mask;
- lock_slave_threads(mi);
- // Get a mask of _running_ threads
- 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,
- 1 /*skip lock */);
- }
- else
- {
- //no error if both threads are already stopped, only a warning
- slave_errno= 0;
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING,
- ER(ER_SLAVE_WAS_NOT_RUNNING));
- }
- unlock_slave_threads(mi);
- thd_proc_info(thd, 0);
-
- if (slave_errno)
- {
- if (net_report)
- my_message(slave_errno, ER(slave_errno), MYF(0));
- DBUG_RETURN(1);
- }
- else if (net_report)
- my_ok(thd);
-
- 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 occured while reseting slave";
- DBUG_ENTER("reset_slave");
-
- lock_slave_threads(mi);
- init_thread_mask(&thread_mask,mi,0 /* not inverse */);
- if (thread_mask) // We refuse if any slave thread is running
- {
- sql_errno= ER_SLAVE_MUST_STOP;
- error=1;
- goto err;
- }
-
- 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 */
- init_master_log_pos(mi);
- /*
- Reset errors (the idea is that we forget about the
- old master).
- */
- mi->clear_error();
- mi->rli.clear_error();
- mi->rli.clear_until_condition();
-
- // close master_info_file, relay_log_info_file, set mi->inited=rli->inited=0
- end_master_info(mi);
- // and delete these two files
- fn_format(fname, master_info_file, mysql_data_home, "", 4+32);
- if (my_stat(fname, &stat_area, MYF(0)) && my_delete(fname, MYF(MY_WME)))
- {
- error=1;
- goto err;
- }
- // delete relay_log_info_file
- fn_format(fname, relay_log_info_file, mysql_data_home, "", 4+32);
- if (my_stat(fname, &stat_area, MYF(0)) && my_delete(fname, MYF(MY_WME)))
- {
- error=1;
- goto err;
- }
-
- RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi));
-err:
- unlock_slave_threads(mi);
- 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 (pthread_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)
-{
- pthread_mutex_lock(&LOCK_thread_count);
- I_List_iterator<THD> it(threads);
- THD *tmp;
-
- while ((tmp=it++))
- {
- if (tmp->command == COM_BINLOG_DUMP &&
- tmp->server_id == slave_server_id)
- {
- pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
- break;
- }
- }
- pthread_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(THD::KILL_QUERY);
- pthread_mutex_unlock(&tmp->LOCK_thd_data);
- }
-}
-
-
-/**
- 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.
-
- @retval FALSE success
- @retval TRUE error
-*/
-bool change_master(THD* thd, Master_info* mi)
-{
- int thread_mask;
- const char* errmsg= 0;
- bool need_relay_log_purge= 1;
- bool ret= FALSE;
- DBUG_ENTER("change_master");
-
- lock_slave_threads(mi);
- init_thread_mask(&thread_mask,mi,0 /*not inverse*/);
- LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
- if (thread_mask) // We refuse if any slave thread is running
- {
- my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0));
- ret= TRUE;
- goto err;
- }
-
- thd_proc_info(thd, "Changing master");
- /*
- 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");
- unlock_slave_threads(mi);
- DBUG_RETURN(TRUE);
- }
- // TODO: see if needs re-write
- if (init_master_info(mi, master_info_file, relay_log_info_file, 0,
- thread_mask))
- {
- my_message(ER_MASTER_INFO, ER(ER_MASTER_INFO), MYF(0));
- 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
- */
-
- /*
- 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(mi->master_log_name, lex_mi->log_file_name,
- sizeof(mi->master_log_name)-1);
- if (lex_mi->pos)
- {
- mi->master_log_pos= lex_mi->pos;
- }
- DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
-
- if (lex_mi->host)
- strmake(mi->host, lex_mi->host, sizeof(mi->host)-1);
- if (lex_mi->user)
- strmake(mi->user, lex_mi->user, sizeof(mi->user)-1);
- if (lex_mi->password)
- strmake(mi->password, lex_mi->password, sizeof(mi->password)-1);
- 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) min(SLAVE_MAX_HEARTBEAT_PERIOD,
- (slave_net_timeout/2.0));
- mi->received_heartbeats= LL(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 == ::server_id && replicate_same_server_id)
- {
- my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), 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(mi->ssl_ca, lex_mi->ssl_ca, sizeof(mi->ssl_ca)-1);
- if (lex_mi->ssl_capath)
- strmake(mi->ssl_capath, lex_mi->ssl_capath, sizeof(mi->ssl_capath)-1);
- if (lex_mi->ssl_cert)
- strmake(mi->ssl_cert, lex_mi->ssl_cert, sizeof(mi->ssl_cert)-1);
- if (lex_mi->ssl_cipher)
- strmake(mi->ssl_cipher, lex_mi->ssl_cipher, sizeof(mi->ssl_cipher)-1);
- if (lex_mi->ssl_key)
- strmake(mi->ssl_key, lex_mi->ssl_key, sizeof(mi->ssl_key)-1);
-#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 )
- push_warning(thd, MYSQL_ERROR::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(mi->rli.group_relay_log_name, relay_log_name,
- sizeof(mi->rli.group_relay_log_name)-1);
- strmake(mi->rli.event_relay_log_name, relay_log_name,
- sizeof(mi->rli.event_relay_log_name)-1);
- }
-
- 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 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 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
- max().
- */
- mi->master_log_pos = max(BIN_LOG_HEADER_SIZE,
- mi->rli.group_master_log_pos);
- strmake(mi->master_log_name, mi->rli.group_master_log_name,
- sizeof(mi->master_log_name)-1);
- }
- /*
- Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never
- a slave before).
- */
- if (flush_master_info(mi, 0))
- {
- my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file");
- ret= TRUE;
- goto err;
- }
- if (need_relay_log_purge)
- {
- relay_log_purge= 1;
- thd_proc_info(thd, "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_purge= 0;
- /* 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(mi->rli.group_master_log_name,mi->master_log_name,
- sizeof(mi->rli.group_master_log_name)-1);
-
- if (!mi->rli.group_master_log_name[0]) // uninitialized case
- mi->rli.group_master_log_pos=0;
-
- pthread_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();
- /*
- 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).
- */
- flush_relay_log_info(&mi->rli);
- pthread_cond_broadcast(&mi->data_cond);
- pthread_mutex_unlock(&mi->rli.data_lock);
-
-err:
- unlock_slave_threads(mi);
- thd_proc_info(thd, 0);
- if (ret == FALSE)
- my_ok(thd);
- delete_dynamic(&lex_mi->repl_ignore_server_ids); //freeing of parser-time alloc
- 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)
-{
- 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))
- return 1;
- RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */));
- return 0;
-}
-
-int cmp_master_pos(const char* log_file_name1, ulonglong log_pos1,
- const char* log_file_name2, ulonglong log_pos2)
-{
- int res;
- size_t log_file_name1_len= strlen(log_file_name1);
- size_t log_file_name2_len= strlen(log_file_name2);
-
- // We assume that both log names match up to '.'
- if (log_file_name1_len == log_file_name2_len)
- {
- if ((res= strcmp(log_file_name1, log_file_name2)))
- return res;
- return (log_pos1 < log_pos2) ? -1 : (log_pos1 == log_pos2) ? 0 : 1;
- }
- return ((log_file_name1_len < log_file_name2_len) ? -1 : 1);
-}
-
-
-/**
- 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;
- DBUG_ENTER("mysql_show_binlog_events");
-
- Log_event::init_show_field_list(&field_list);
- if (protocol->send_fields(&field_list,
- Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
- DBUG_RETURN(TRUE);
-
- Format_description_log_event *description_event= new
- Format_description_log_event(3); /* MySQL 4.0 by default */
-
- DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS ||
- thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS);
-
- /* select wich 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 (!active_mi)
- DBUG_RETURN(TRUE);
-
- binary_log= &(active_mi->rli.relay_log);
- }
-
- 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 = 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;
- pthread_mutex_t *log_lock = binary_log->get_log_lock();
- LOG_INFO linfo;
- Log_event* ev;
-
- 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;
- }
-
- pthread_mutex_lock(&LOCK_thread_count);
- thd->current_linfo = &linfo;
- pthread_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;
-
- pthread_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,(pthread_mutex_t*)0,description_event);
- 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,(pthread_mutex_t*) 0,
- description_event)); )
- {
- if (event_count >= limit_start &&
- ev->net_send(protocol, linfo.log_file_name, pos))
- {
- errmsg = "Net error";
- delete ev;
- pthread_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";
- pthread_mutex_unlock(log_lock);
- goto err;
- }
-
- pthread_mutex_unlock(log_lock);
- }
-
- ret= FALSE;
-
-err:
- delete description_event;
- if (file >= 0)
- {
- end_io_cache(&log);
- (void) my_close(file, MYF(MY_WME));
- }
-
- if (errmsg)
- my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
- "SHOW BINLOG EVENTS", errmsg);
- else
- my_eof(thd);
-
- pthread_mutex_lock(&LOCK_thread_count);
- thd->current_linfo = 0;
- pthread_mutex_unlock(&LOCK_thread_count);
- 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_fields(&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_message(ER_NO_BINARY_LOGGING, ER(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_fields(&field_list,
- Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
- DBUG_RETURN(TRUE);
-
- pthread_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
- pthread_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= my_open(fname, O_RDONLY | O_SHARE | O_BINARY,
- MYF(0))) >= 0)
- {
- file_length= (ulonglong) my_seek(file, 0L, MY_SEEK_END, MYF(0));
- my_close(file, MYF(0));
- }
- }
- protocol->store(file_length);
- if (protocol->write())
- 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 += min(block_len, max_event_size),
- block_len -= 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,
- 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,
- 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_SYNC_POINT("debug_lock.created_file_event",10);
- }
- }
- DBUG_RETURN(0);
-}
-
-/*
- Replication System Variables
-*/
-
-class sys_var_slave_skip_counter :public sys_var
-{
-public:
- sys_var_slave_skip_counter(sys_var_chain *chain, const char *name_arg)
- :sys_var(name_arg)
- { chain_sys_var(chain); }
- bool check(THD *thd, set_var *var);
- bool update(THD *thd, set_var *var);
- bool check_type(enum_var_type type) { return type != OPT_GLOBAL; }
- /*
- We can't retrieve the value of this, so we don't have to define
- type() or value_ptr()
- */
-};
-
-class sys_var_sync_binlog_period :public sys_var_long_ptr
-{
-public:
- sys_var_sync_binlog_period(sys_var_chain *chain, const char *name_arg,
- ulong *value_ptr)
- :sys_var_long_ptr(chain, name_arg,value_ptr) {}
- bool update(THD *thd, set_var *var);
-};
-
-static void fix_slave_net_timeout(THD *thd, enum_var_type type)
-{
- DBUG_ENTER("fix_slave_net_timeout");
-#ifdef HAVE_REPLICATION
- pthread_mutex_lock(&LOCK_active_mi);
- DBUG_PRINT("info",("slave_net_timeout=%lu mi->heartbeat_period=%.3f",
- slave_net_timeout,
- (active_mi? active_mi->heartbeat_period : 0.0)));
- if (active_mi && slave_net_timeout < active_mi->heartbeat_period)
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE,
- "The currect value for master_heartbeat_period"
- " exceeds the new value of `slave_net_timeout' sec."
- " A sensible value for the period should be"
- " less than the timeout.");
- pthread_mutex_unlock(&LOCK_active_mi);
-#endif
- DBUG_VOID_RETURN;
-}
-
-static sys_var_chain vars = { NULL, NULL };
-
-static sys_var_const sys_log_slave_updates(&vars, "log_slave_updates",
- OPT_GLOBAL, SHOW_MY_BOOL,
- (uchar*) &opt_log_slave_updates);
-static sys_var_const sys_relay_log(&vars, "relay_log",
- OPT_GLOBAL, SHOW_CHAR_PTR,
- (uchar*) &opt_relay_logname);
-static sys_var_const sys_relay_log_index(&vars, "relay_log_index",
- OPT_GLOBAL, SHOW_CHAR_PTR,
- (uchar*) &opt_relaylog_index_name);
-static sys_var_const sys_relay_log_info_file(&vars, "relay_log_info_file",
- OPT_GLOBAL, SHOW_CHAR_PTR,
- (uchar*) &relay_log_info_file);
-static sys_var_bool_ptr sys_relay_log_purge(&vars, "relay_log_purge",
- &relay_log_purge);
-static sys_var_bool_ptr sys_relay_log_recovery(&vars, "relay_log_recovery",
- &relay_log_recovery);
-static sys_var_uint_ptr sys_sync_binlog_period(&vars, "sync_binlog",
- &sync_binlog_period);
-static sys_var_uint_ptr sys_sync_relaylog_period(&vars, "sync_relay_log",
- &sync_relaylog_period);
-static sys_var_uint_ptr sys_sync_relayloginfo_period(&vars, "sync_relay_log_info",
- &sync_relayloginfo_period);
-static sys_var_uint_ptr sys_sync_masterinfo_period(&vars, "sync_master_info",
- &sync_masterinfo_period);
-static sys_var_const sys_relay_log_space_limit(&vars,
- "relay_log_space_limit",
- OPT_GLOBAL, SHOW_LONGLONG,
- (uchar*)
- &relay_log_space_limit);
-static sys_var_const sys_slave_load_tmpdir(&vars, "slave_load_tmpdir",
- OPT_GLOBAL, SHOW_CHAR_PTR,
- (uchar*) &slave_load_tmpdir);
-static sys_var_long_ptr sys_slave_net_timeout(&vars, "slave_net_timeout",
- &slave_net_timeout,
- fix_slave_net_timeout);
-static sys_var_const sys_slave_skip_errors(&vars, "slave_skip_errors",
- OPT_GLOBAL, SHOW_CHAR,
- (uchar*) slave_skip_error_names);
-static sys_var_long_ptr sys_slave_trans_retries(&vars, "slave_transaction_retries",
- &slave_trans_retries);
-static sys_var_slave_skip_counter sys_slave_skip_counter(&vars, "sql_slave_skip_counter");
-
-
-bool sys_var_slave_skip_counter::check(THD *thd, set_var *var)
-{
- int result= 0;
- pthread_mutex_lock(&LOCK_active_mi);
- pthread_mutex_lock(&active_mi->rli.run_lock);
- if (active_mi->rli.slave_running)
- {
- my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0));
- result=1;
- }
- pthread_mutex_unlock(&active_mi->rli.run_lock);
- pthread_mutex_unlock(&LOCK_active_mi);
- var->save_result.ulong_value= (ulong) var->value->val_int();
- return result;
-}
-
-
-bool sys_var_slave_skip_counter::update(THD *thd, set_var *var)
-{
- pthread_mutex_lock(&LOCK_active_mi);
- pthread_mutex_lock(&active_mi->rli.run_lock);
- /*
- The following test should normally never be true as we test this
- in the check function; To be safe against multiple
- SQL_SLAVE_SKIP_COUNTER request, we do the check anyway
- */
- if (!active_mi->rli.slave_running)
- {
- pthread_mutex_lock(&active_mi->rli.data_lock);
- active_mi->rli.slave_skip_counter= var->save_result.ulong_value;
- pthread_mutex_unlock(&active_mi->rli.data_lock);
- }
- pthread_mutex_unlock(&active_mi->rli.run_lock);
- pthread_mutex_unlock(&LOCK_active_mi);
- return 0;
-}
-
-
-int init_replication_sys_vars()
-{
- if (mysql_add_sys_var_chain(vars.first, my_long_options))
- {
- /* should not happen */
- fprintf(stderr, "failed to initialize replication system variables");
- unireg_abort(1);
- }
- return 0;
-}
-
-
-#endif /* HAVE_REPLICATION */
=== modified file 'sql/sql_repl.h'
--- a/sql/sql_repl.h 2009-09-23 21:32:31 +0000
+++ b/sql/sql_repl.h 2010-04-26 05:01:18 +0000
@@ -16,55 +16,16 @@
#ifndef SQL_REPL_INCLUDED
#define SQL_REPL_INCLUDED
-#include "rpl_filter.h"
-
#ifdef HAVE_REPLICATION
-#include "slave.h"
-
-typedef struct st_slave_info
-{
- uint32 server_id;
- uint32 rpl_recovery_rank, master_id;
- char host[HOSTNAME_LENGTH+1];
- char user[USERNAME_LENGTH+1];
- char password[MAX_PASSWORD_LENGTH+1];
- uint16 port;
- THD* thd;
-} SLAVE_INFO;
-extern my_bool opt_show_slave_auth_info;
-extern char *master_host, *master_info_file;
-extern bool server_id_supplied;
+#include "sql_slave.h"
+#include "sql_master.h"
-extern int max_binlog_dump_events;
-extern my_bool opt_sporadic_binlog_dump_fail;
-
-int start_slave(THD* thd, Master_info* mi, bool net_report);
-int stop_slave(THD* thd, Master_info* mi, bool net_report);
-bool change_master(THD* thd, Master_info* mi);
-bool mysql_show_binlog_events(THD* thd);
-int cmp_master_pos(const char* log_file_name1, ulonglong log_pos1,
- const char* log_file_name2, ulonglong log_pos2);
-int reset_slave(THD *thd, Master_info* mi);
-int reset_master(THD* thd);
-bool purge_master_logs(THD* thd, const char* to_log);
-bool purge_master_logs_before_date(THD* thd, time_t purge_time);
-bool log_in_use(const char* log_name);
-void adjust_linfo_offsets(my_off_t purge_offset);
-bool show_binlogs(THD* thd);
-extern int init_master_info(Master_info* mi);
-void kill_zombie_dump_threads(uint32 slave_server_id);
-int check_binlog_magic(IO_CACHE* log, const char** errmsg);
-
-typedef struct st_load_file_info
+inline int init_replication_sys_vars()
{
- THD* thd;
- my_off_t last_pos_in_file;
- bool wrote_create_file, log_delayed;
-} LOAD_FILE_INFO;
-
-int log_loaded_block(IO_CACHE* file);
-int init_replication_sys_vars();
+ return (init_master_sys_vars() ||
+ init_slave_sys_vars());
+}
#endif /* HAVE_REPLICATION */
=== added file 'sql/sql_slave.cc'
--- a/sql/sql_slave.cc 1970-01-01 00:00:00 +0000
+++ b/sql/sql_slave.cc 2010-04-26 05:01:18 +0000
@@ -0,0 +1,738 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include "sql_slave.h"
+
+#ifdef HAVE_REPLICATION
+
+/**
+ 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->main_da.
+
+ @retval 0 success
+ @retval 1 error
+*/
+int start_slave(THD* thd , Master_info* mi, bool net_report)
+{
+ int slave_errno= 0;
+ int thread_mask;
+ DBUG_ENTER("start_slave");
+
+ if (check_access(thd, SUPER_ACL, any_db,0,0,0,0))
+ DBUG_RETURN(1);
+ lock_slave_threads(mi); // this allows us to cleanly read slave_running
+ // Get a mask of _stopped_ threads
+ init_thread_mask(&thread_mask,mi,1 /* inverse */);
+ /*
+ 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,relay_log_info_file, 0,
+ thread_mask))
+ slave_errno=ER_MASTER_INFO;
+ else if (server_id_supplied && *mi->host)
+ {
+ /*
+ 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)
+ {
+ pthread_mutex_lock(&mi->rli.data_lock);
+
+ if (thd->lex->mi.pos)
+ {
+ 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(mi->rli.until_log_name, thd->lex->mi.log_file_name,
+ sizeof(mi->rli.until_log_name)-1);
+ }
+ else if (thd->lex->mi.relay_log_pos)
+ {
+ mi->rli.until_condition= Relay_log_info::UNTIL_RELAY_POS;
+ mi->rli.until_log_pos= thd->lex->mi.relay_log_pos;
+ strmake(mi->rli.until_log_name, thd->lex->mi.relay_log_name,
+ sizeof(mi->rli.until_log_name)-1);
+ }
+ else
+ mi->rli.clear_until_condition();
+
+ if (mi->rli.until_condition != Relay_log_info::UNTIL_NONE)
+ {
+ /* 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;
+
+ /* Issuing warning then started without --skip-slave-start */
+ if (!opt_skip_slave_start)
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_MISSING_SKIP_SLAVE,
+ ER(ER_MISSING_SKIP_SLAVE));
+ }
+
+ pthread_mutex_unlock(&mi->rli.data_lock);
+ }
+ else if (thd->lex->mi.pos || thd->lex->mi.relay_log_pos)
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_UNTIL_COND_IGNORED,
+ ER(ER_UNTIL_COND_IGNORED));
+
+ if (!slave_errno)
+ slave_errno = start_slave_threads(0 /*no mutex */,
+ 1 /* wait for start */,
+ mi,
+ master_info_file,relay_log_info_file,
+ thread_mask);
+ }
+ else
+ slave_errno = ER_BAD_SLAVE;
+ }
+ else
+ {
+ /* no error if all threads are already started, only a warning */
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_RUNNING,
+ ER(ER_SLAVE_WAS_RUNNING));
+ }
+
+ unlock_slave_threads(mi);
+
+ if (slave_errno)
+ {
+ if (net_report)
+ my_message(slave_errno, ER(slave_errno), MYF(0));
+ DBUG_RETURN(1);
+ }
+ else if (net_report)
+ my_ok(thd);
+
+ 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->main_da.
+
+ @retval 0 success
+ @retval 1 error
+*/
+int stop_slave(THD* thd, Master_info* mi, bool net_report )
+{
+ DBUG_ENTER("stop_slave");
+
+ int slave_errno;
+ if (!thd)
+ thd = current_thd;
+
+ if (check_access(thd, SUPER_ACL, any_db,0,0,0,0))
+ DBUG_RETURN(1);
+ thd_proc_info(thd, "Killing slave");
+ int thread_mask;
+ lock_slave_threads(mi);
+ // Get a mask of _running_ threads
+ 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,
+ 1 /*skip lock */);
+ }
+ else
+ {
+ //no error if both threads are already stopped, only a warning
+ slave_errno= 0;
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SLAVE_WAS_NOT_RUNNING,
+ ER(ER_SLAVE_WAS_NOT_RUNNING));
+ }
+ unlock_slave_threads(mi);
+ thd_proc_info(thd, 0);
+
+ if (slave_errno)
+ {
+ if (net_report)
+ my_message(slave_errno, ER(slave_errno), MYF(0));
+ DBUG_RETURN(1);
+ }
+ else if (net_report)
+ my_ok(thd);
+
+ 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 occured while reseting slave";
+ DBUG_ENTER("reset_slave");
+
+ lock_slave_threads(mi);
+ init_thread_mask(&thread_mask,mi,0 /* not inverse */);
+ if (thread_mask) // We refuse if any slave thread is running
+ {
+ sql_errno= ER_SLAVE_MUST_STOP;
+ error=1;
+ goto err;
+ }
+
+ 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 */
+ init_master_log_pos(mi);
+ /*
+ Reset errors (the idea is that we forget about the
+ old master).
+ */
+ mi->clear_error();
+ mi->rli.clear_error();
+ mi->rli.clear_until_condition();
+
+ // close master_info_file, relay_log_info_file, set mi->inited=rli->inited=0
+ end_master_info(mi);
+ // and delete these two files
+ fn_format(fname, master_info_file, mysql_data_home, "", 4+32);
+ if (my_stat(fname, &stat_area, MYF(0)) && my_delete(fname, MYF(MY_WME)))
+ {
+ error=1;
+ goto err;
+ }
+ // delete relay_log_info_file
+ fn_format(fname, relay_log_info_file, mysql_data_home, "", 4+32);
+ if (my_stat(fname, &stat_area, MYF(0)) && my_delete(fname, MYF(MY_WME)))
+ {
+ error=1;
+ goto err;
+ }
+
+ RUN_HOOK(binlog_relay_io, after_reset_slave, (thd, mi));
+err:
+ unlock_slave_threads(mi);
+ if (error)
+ my_error(sql_errno, MYF(0), errmsg);
+ DBUG_RETURN(error);
+}
+
+/**
+ 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.
+
+ @retval FALSE success
+ @retval TRUE error
+*/
+bool change_master(THD* thd, Master_info* mi)
+{
+ int thread_mask;
+ const char* errmsg= 0;
+ bool need_relay_log_purge= 1;
+ bool ret= FALSE;
+ DBUG_ENTER("change_master");
+
+ lock_slave_threads(mi);
+ init_thread_mask(&thread_mask,mi,0 /*not inverse*/);
+ LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
+ if (thread_mask) // We refuse if any slave thread is running
+ {
+ my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0));
+ ret= TRUE;
+ goto err;
+ }
+
+ thd_proc_info(thd, "Changing master");
+ /*
+ 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");
+ unlock_slave_threads(mi);
+ DBUG_RETURN(TRUE);
+ }
+ // TODO: see if needs re-write
+ if (init_master_info(mi, master_info_file, relay_log_info_file, 0,
+ thread_mask))
+ {
+ my_message(ER_MASTER_INFO, ER(ER_MASTER_INFO), MYF(0));
+ 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
+ */
+
+ /*
+ 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(mi->master_log_name, lex_mi->log_file_name,
+ sizeof(mi->master_log_name)-1);
+ if (lex_mi->pos)
+ {
+ mi->master_log_pos= lex_mi->pos;
+ }
+ DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos));
+
+ if (lex_mi->host)
+ strmake(mi->host, lex_mi->host, sizeof(mi->host)-1);
+ if (lex_mi->user)
+ strmake(mi->user, lex_mi->user, sizeof(mi->user)-1);
+ if (lex_mi->password)
+ strmake(mi->password, lex_mi->password, sizeof(mi->password)-1);
+ 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) min(SLAVE_MAX_HEARTBEAT_PERIOD,
+ (slave_net_timeout/2.0));
+ mi->received_heartbeats= LL(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 == ::server_id && replicate_same_server_id)
+ {
+ my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), 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(mi->ssl_ca, lex_mi->ssl_ca, sizeof(mi->ssl_ca)-1);
+ if (lex_mi->ssl_capath)
+ strmake(mi->ssl_capath, lex_mi->ssl_capath, sizeof(mi->ssl_capath)-1);
+ if (lex_mi->ssl_cert)
+ strmake(mi->ssl_cert, lex_mi->ssl_cert, sizeof(mi->ssl_cert)-1);
+ if (lex_mi->ssl_cipher)
+ strmake(mi->ssl_cipher, lex_mi->ssl_cipher, sizeof(mi->ssl_cipher)-1);
+ if (lex_mi->ssl_key)
+ strmake(mi->ssl_key, lex_mi->ssl_key, sizeof(mi->ssl_key)-1);
+#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 )
+ push_warning(thd, MYSQL_ERROR::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(mi->rli.group_relay_log_name, relay_log_name,
+ sizeof(mi->rli.group_relay_log_name)-1);
+ strmake(mi->rli.event_relay_log_name, relay_log_name,
+ sizeof(mi->rli.event_relay_log_name)-1);
+ }
+
+ 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 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 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
+ max().
+ */
+ mi->master_log_pos = max(BIN_LOG_HEADER_SIZE,
+ mi->rli.group_master_log_pos);
+ strmake(mi->master_log_name, mi->rli.group_master_log_name,
+ sizeof(mi->master_log_name)-1);
+ }
+ /*
+ Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never
+ a slave before).
+ */
+ if (flush_master_info(mi, 0))
+ {
+ my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file");
+ ret= TRUE;
+ goto err;
+ }
+ if (need_relay_log_purge)
+ {
+ relay_log_purge= 1;
+ thd_proc_info(thd, "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_purge= 0;
+ /* 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(mi->rli.group_master_log_name,mi->master_log_name,
+ sizeof(mi->rli.group_master_log_name)-1);
+
+ if (!mi->rli.group_master_log_name[0]) // uninitialized case
+ mi->rli.group_master_log_pos=0;
+
+ pthread_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();
+ /*
+ 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).
+ */
+ flush_relay_log_info(&mi->rli);
+ pthread_cond_broadcast(&mi->data_cond);
+ pthread_mutex_unlock(&mi->rli.data_lock);
+
+err:
+ unlock_slave_threads(mi);
+ thd_proc_info(thd, 0);
+ if (ret == FALSE)
+ my_ok(thd);
+ delete_dynamic(&lex_mi->repl_ignore_server_ids); //freeing of parser-time alloc
+ DBUG_RETURN(ret);
+}
+
+int cmp_master_pos(const char* log_file_name1, ulonglong log_pos1,
+ const char* log_file_name2, ulonglong log_pos2)
+{
+ int res;
+ size_t log_file_name1_len= strlen(log_file_name1);
+ size_t log_file_name2_len= strlen(log_file_name2);
+
+ // We assume that both log names match up to '.'
+ if (log_file_name1_len == log_file_name2_len)
+ {
+ if ((res= strcmp(log_file_name1, log_file_name2)))
+ return res;
+ return (log_pos1 < log_pos2) ? -1 : (log_pos1 == log_pos2) ? 0 : 1;
+ }
+ return ((log_file_name1_len < log_file_name2_len) ? -1 : 1);
+}
+
+/**
+ Execute a SHOW RELAYLOG EVENTS statement.
+
+ @param thd Pointer to THD object for the client thread executing the
+ statement.
+
+ @retval FALSE success
+ @retval TRUE failure
+*/
+bool mysql_show_relaylog_events(THD* thd)
+{
+ DBUG_ENTER("mysql_show_relaylog_events");
+ DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS);
+ if (!active_mi)
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(show_binlog_events(thd, &(active_mi->rli.relay_log)));
+}
+
+/*
+ Replication System Variables
+*/
+
+class sys_var_slave_skip_counter :public sys_var
+{
+public:
+ sys_var_slave_skip_counter(sys_var_chain *chain, const char *name_arg)
+ :sys_var(name_arg)
+ { chain_sys_var(chain); }
+ bool check(THD *thd, set_var *var);
+ bool update(THD *thd, set_var *var);
+ bool check_type(enum_var_type type) { return type != OPT_GLOBAL; }
+ /*
+ We can't retrieve the value of this, so we don't have to define
+ type() or value_ptr()
+ */
+};
+
+static void fix_slave_net_timeout(THD *thd, enum_var_type type)
+{
+ DBUG_ENTER("fix_slave_net_timeout");
+#ifdef HAVE_REPLICATION
+ pthread_mutex_lock(&LOCK_active_mi);
+ DBUG_PRINT("info",("slave_net_timeout=%lu mi->heartbeat_period=%.3f",
+ slave_net_timeout,
+ (active_mi? active_mi->heartbeat_period : 0.0)));
+ if (active_mi && slave_net_timeout < active_mi->heartbeat_period)
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE,
+ "The currect value for master_heartbeat_period"
+ " exceeds the new value of `slave_net_timeout' sec."
+ " A sensible value for the period should be"
+ " less than the timeout.");
+ pthread_mutex_unlock(&LOCK_active_mi);
+#endif
+ DBUG_VOID_RETURN;
+}
+
+static sys_var_chain vars = { NULL, NULL };
+static sys_var_const sys_log_slave_updates(&vars, "log_slave_updates",
+ OPT_GLOBAL, SHOW_MY_BOOL,
+ (uchar*) &opt_log_slave_updates);
+static sys_var_const sys_relay_log(&vars, "relay_log",
+ OPT_GLOBAL, SHOW_CHAR_PTR,
+ (uchar*) &opt_relay_logname);
+static sys_var_const sys_relay_log_index(&vars, "relay_log_index",
+ OPT_GLOBAL, SHOW_CHAR_PTR,
+ (uchar*) &opt_relaylog_index_name);
+static sys_var_const sys_relay_log_info_file(&vars, "relay_log_info_file",
+ OPT_GLOBAL, SHOW_CHAR_PTR,
+ (uchar*) &relay_log_info_file);
+static sys_var_bool_ptr sys_relay_log_purge(&vars, "relay_log_purge",
+ &relay_log_purge);
+static sys_var_bool_ptr sys_relay_log_recovery(&vars, "relay_log_recovery",
+ &relay_log_recovery);
+static sys_var_uint_ptr sys_sync_relaylog_period(&vars, "sync_relay_log",
+ &sync_relaylog_period);
+static sys_var_uint_ptr sys_sync_relayloginfo_period(&vars, "sync_relay_log_info",
+ &sync_relayloginfo_period);
+static sys_var_uint_ptr sys_sync_masterinfo_period(&vars, "sync_master_info",
+ &sync_masterinfo_period);
+static sys_var_const sys_relay_log_space_limit(&vars,
+ "relay_log_space_limit",
+ OPT_GLOBAL, SHOW_LONGLONG,
+ (uchar*)
+ &relay_log_space_limit);
+static sys_var_const sys_slave_load_tmpdir(&vars, "slave_load_tmpdir",
+ OPT_GLOBAL, SHOW_CHAR_PTR,
+ (uchar*) &slave_load_tmpdir);
+static sys_var_long_ptr sys_slave_net_timeout(&vars, "slave_net_timeout",
+ &slave_net_timeout,
+ fix_slave_net_timeout);
+static sys_var_const sys_slave_skip_errors(&vars, "slave_skip_errors",
+ OPT_GLOBAL, SHOW_CHAR,
+ (uchar*) slave_skip_error_names);
+static sys_var_long_ptr sys_slave_trans_retries(&vars, "slave_transaction_retries",
+ &slave_trans_retries);
+static sys_var_slave_skip_counter sys_slave_skip_counter(&vars, "sql_slave_skip_counter");
+
+bool sys_var_slave_skip_counter::check(THD *thd, set_var *var)
+{
+ int result= 0;
+ pthread_mutex_lock(&LOCK_active_mi);
+ pthread_mutex_lock(&active_mi->rli.run_lock);
+ if (active_mi->rli.slave_running)
+ {
+ my_message(ER_SLAVE_MUST_STOP, ER(ER_SLAVE_MUST_STOP), MYF(0));
+ result=1;
+ }
+ pthread_mutex_unlock(&active_mi->rli.run_lock);
+ pthread_mutex_unlock(&LOCK_active_mi);
+ var->save_result.ulong_value= (ulong) var->value->val_int();
+ return result;
+}
+
+
+bool sys_var_slave_skip_counter::update(THD *thd, set_var *var)
+{
+ pthread_mutex_lock(&LOCK_active_mi);
+ pthread_mutex_lock(&active_mi->rli.run_lock);
+ /*
+ The following test should normally never be true as we test this
+ in the check function; To be safe against multiple
+ SQL_SLAVE_SKIP_COUNTER request, we do the check anyway
+ */
+ if (!active_mi->rli.slave_running)
+ {
+ pthread_mutex_lock(&active_mi->rli.data_lock);
+ active_mi->rli.slave_skip_counter= var->save_result.ulong_value;
+ pthread_mutex_unlock(&active_mi->rli.data_lock);
+ }
+ pthread_mutex_unlock(&active_mi->rli.run_lock);
+ pthread_mutex_unlock(&LOCK_active_mi);
+ return 0;
+}
+
+int init_slave_sys_vars()
+{
+ if (mysql_add_sys_var_chain(vars.first, my_long_options))
+ {
+ /* should not happen */
+ fprintf(stderr, "failed to initialize replication slave system variables");
+ unireg_abort(1);
+ }
+ return 0;
+}
+#endif /* HAVE_REPLICATION */
=== added file 'sql/sql_slave.h'
--- a/sql/sql_slave.h 1970-01-01 00:00:00 +0000
+++ b/sql/sql_slave.h 2010-04-26 05:01:18 +0000
@@ -0,0 +1,48 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#ifndef RPL_SLAVE_H
+#define RPL_SLAVE_H
+
+#include <mysql_priv.h>
+#include <my_global.h>
+#include <my_pthread.h>
+#include <my_dir.h>
+#include "binlog.h"
+#include "slave.h"
+#include "rpl_rli.h"
+#include "rpl_mi.h"
+#include "rpl_filter.h"
+#include "rpl_handler.h"
+
+#ifdef HAVE_REPLICATION
+
+extern my_bool opt_show_slave_auth_info;
+extern char *master_host, *master_info_file;
+
+int start_slave(THD* thd, Master_info* mi, bool net_report);
+int stop_slave(THD* thd, Master_info* mi, bool net_report);
+bool change_master(THD* thd, Master_info* mi);
+int cmp_master_pos(const char* log_file_name1, ulonglong log_pos1,
+ const char* log_file_name2, ulonglong log_pos2);
+int reset_slave(THD *thd, Master_info* mi);
+extern int init_master_info(Master_info* mi);
+int check_binlog_magic(IO_CACHE* log, const char** errmsg);
+bool mysql_show_relaylog_events(THD* thd);
+int init_slave_sys_vars();
+
+#endif /* HAVE_REPLICATION */
+#endif /* RPL_SLAVE_H */
Attachment: [text/bzr-bundle] bzr/zhenxing.he@sun.com-20100426050118-hq4k7hsa1xfxwy7n.bundle
| Thread |
|---|
| • bzr commit into mysql-5.1-rep+2 branch (zhenxing.he:3185) WL#3662 | He Zhenxing | 26 Apr |