diff --git a/client/client_priv.h b/client/client_priv.h index 99584b3783b5..9cf0f663914a 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -200,6 +200,7 @@ enum options_client { OPT_INNODB_STATS_ON_METADATA, OPT_DUMP_FBOBJ_STATS, OPT_MYSQLBINLOG_SKIP_EMPTY_TRANS, + OPT_PRINT_GTIDS, /* Add new option above this */ OPT_MAX_CLIENT_OPTION }; diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 87636a9df576..39df548f67d7 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -781,6 +781,7 @@ Gtid_set *gtid_set_excluded = nullptr; static uint opt_zstd_compress_level = default_zstd_compression_level; static char *opt_compress_algorithm = nullptr; Gtid_set *gtid_set_stop = nullptr; +static bool opt_print_gtids = false; static bool opt_print_table_metadata; @@ -1159,12 +1160,13 @@ static bool shall_stop_gtids(Log_event *ev) { according to the include-gtids, exclude-gtids and skip-gtids options. - @param ev Pointer to the event to be checked. + @param[in] ev Pointer to the event to be checked. + @param[out] cached_gtid Store the gtid here. @return true if the event should be filtered out, false, otherwise. */ -static bool shall_skip_gtids(const Log_event *ev) { +static bool shall_skip_gtids(const Log_event *ev, Gtid *cached_gtid) { bool filtered = false; switch (ev->get_type_code()) { @@ -1172,6 +1174,9 @@ static bool shall_skip_gtids(const Log_event *ev) { case binary_log::ANONYMOUS_GTID_LOG_EVENT: { Gtid_log_event *gtid = const_cast(down_cast(ev)); + if (gtid->get_sidno(true) > 0 && gtid->get_gno() != 0) { + cached_gtid->set(gtid->get_sidno(true), gtid->get_gno()); + } if (opt_include_gtids_str != nullptr) { filtered = filtered || !gtid_set_included->contains_gtid( gtid->get_sidno(true), gtid->get_gno()); @@ -1187,6 +1192,13 @@ static bool shall_skip_gtids(const Log_event *ev) { /* Skip previous gtids if --skip-gtids is set. */ case binary_log::PREVIOUS_GTIDS_LOG_EVENT: filtered = opt_skip_gtids; + if (opt_print_gtids) { + const Previous_gtids_log_event *pgev = + down_cast(ev); + global_sid_lock->rdlock(); + pgev->add_to_set(gtid_set_excluded); + global_sid_lock->unlock(); + } break; /* @@ -1497,6 +1509,21 @@ void handle_last_rows_query_event(bool print, last_rows_query_event.event_pos = 0; } +// Helper for next function +static bool encounter_gtid(Gtid const &cached_gtid) { + global_sid_lock->rdlock(); + if (!cached_gtid.is_empty()) { + if (gtid_set_excluded->ensure_sidno(cached_gtid.sidno) != + RETURN_STATUS_OK) { + global_sid_lock->unlock(); + return true; + } + gtid_set_excluded->_add_gtid(cached_gtid); + } + global_sid_lock->unlock(); + return false; +} + /** Print the given event, and either delete it or delegate the deletion to someone else. @@ -1528,6 +1555,7 @@ static Exit_status process_event(PRINT_EVENT_INFO *print_event_info, DBUG_TRACE; Exit_status retval = OK_CONTINUE; IO_CACHE *const head = &print_event_info->head_cache; + static Gtid cached_gtid; /* Format events are not concerned by --offset and such, we always need to @@ -1583,7 +1611,7 @@ static Exit_status process_event(PRINT_EVENT_INFO *print_event_info, DBUG_PRINT("debug", ("event_type: %s", ev->get_type_str())); - if (shall_skip_gtids(ev)) goto end; + if (shall_skip_gtids(ev, &cached_gtid)) goto end; switch (ev_type) { case binary_log::TRANSACTION_PAYLOAD_EVENT: @@ -1671,6 +1699,7 @@ static Exit_status process_event(PRINT_EVENT_INFO *print_event_info, cur_database = ""; if (skip) break; } + if (opt_print_gtids && encounter_gtid(cached_gtid)) goto err; } else if (starts_group) { in_transaction = true; @@ -1745,6 +1774,9 @@ static Exit_status process_event(PRINT_EVENT_INFO *print_event_info, output of Append_block_log_event::print is only a comment. */ ev->print(result_file, print_event_info); + + if (opt_print_gtids && encounter_gtid(cached_gtid)) goto err; + if (head->error == -1) goto err; if ((retval = load_processor.process((Append_block_log_event *)ev)) != OK_CONTINUE) @@ -2193,6 +2225,9 @@ static struct my_option my_long_options[] = { "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", &port, &port, nullptr, GET_INT, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr}, + {"print_gtids", OPT_PRINT_GTIDS, "Print encountered gtid set to stderr.", + &opt_print_gtids, &opt_print_gtids, 0, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, + 0, nullptr}, {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe, memory).", nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr}, @@ -3995,6 +4030,23 @@ int main(int argc, char **argv) { fprintf(result_file, "/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;\n"); } + // print encountered gtids to stderr + if (opt_print_gtids) { + global_sid_lock->rdlock(); + char *encountered_gtids = nullptr; + gtid_set_excluded->to_string(&encountered_gtids); + if (encountered_gtids) { + // Replace new lines with spaces. Easy for parsing the output. + for (char *ptr = encountered_gtids; *ptr; ++ptr) { + if (*ptr == '\n') *ptr = ' '; + } + // NO_LINT_DEBUG + fprintf(stderr, "Executed gtids: %s\n", encountered_gtids); + my_free(encountered_gtids); + } + global_sid_lock->unlock(); + } + /* We should unset the RBR_EXEC_MODE since the user may concatenate output of multiple runs of mysqlbinlog, all of which may not run in idempotent mode. diff --git a/mysql-test/r/mysqlbinlog.result b/mysql-test/r/mysqlbinlog.result index b4873efcdd27..a4073ab4ed6a 100644 --- a/mysql-test/r/mysqlbinlog.result +++ b/mysql-test/r/mysqlbinlog.result @@ -1030,6 +1030,7 @@ DROP TABLE t1; shell> mysqlbinlog std_data/corrupt-relay-bin.000624 > var/tmp/bug31793.sql set timestamp= default; FLUSH LOGS; +Executed gtids: Bug#31611 Security risk with BINLOG statement SET BINLOG_FORMAT='ROW'; Warnings: diff --git a/mysql-test/r/mysqlbinlog_gtid.result b/mysql-test/r/mysqlbinlog_gtid.result index 5d2d5c822212..e0f7f778ba2f 100644 --- a/mysql-test/r/mysqlbinlog_gtid.result +++ b/mysql-test/r/mysqlbinlog_gtid.result @@ -428,6 +428,10 @@ include/mysqlbinlog.inc include/mysqlbinlog.inc ** --stop-gtid without --index-file nor --read-from-remote-master ** include/mysqlbinlog.inc +WARNING: --short-form is deprecated and will be removed in a future version + +Executed gtids: uuid:1-5 + PURGE BINARY LOGS TO 'master-bin.000002'; include/mysqlbinlog.inc # The proper term is pseudo_replica_mode, but we use this compatibility alias diff --git a/mysql-test/t/mysqlbinlog.test b/mysql-test/t/mysqlbinlog.test index eeeaf63fe19a..1053080b90d8 100644 --- a/mysql-test/t/mysqlbinlog.test +++ b/mysql-test/t/mysqlbinlog.test @@ -288,6 +288,9 @@ let $master_binlog_file= query_get_value(SHOW MASTER STATUS, File, 1); --exec $MYSQL_BINLOG --disable-force-if-open $MYSQLD_DATADIR/$master_binlog_file >/dev/null 2>/dev/null --exec $MYSQL_BINLOG --force-if-open $MYSQLD_DATADIR/$master_binlog_file >/dev/null 2>/dev/null +# Test --print-gtids with gtids disabled +--exec $MYSQL_BINLOG --force-if-open --print-gtids $MYSQLD_DATADIR/$master_binlog_file 2>&1 1>&- + --echo Bug#31611 Security risk with BINLOG statement SET BINLOG_FORMAT='ROW'; diff --git a/mysql-test/t/mysqlbinlog_gtid.test b/mysql-test/t/mysqlbinlog_gtid.test index 3242fbd8ae37..6f7635e8eed7 100644 --- a/mysql-test/t/mysqlbinlog_gtid.test +++ b/mysql-test/t/mysqlbinlog_gtid.test @@ -80,6 +80,10 @@ FLUSH LOGS; -- let $mysqlbinlog_allow_error= 0 +# Printing encountered gtids +-- replace_result $MASTER_UUID uuid +-- exec $MYSQL_BINLOG --force-if-open --short_form --index-file=$MYSQLD_DATADIR/master-bin.index --start-gtid=$MASTER_UUID:3 --print-gtids 2>&1 >&- +-- echo PURGE BINARY LOGS TO 'master-bin.000002';