From 8c497cf56507e8f89b1b44ffbaaceeb2db493d38 Mon Sep 17 00:00:00 2001 From: Hemant Dangi Date: Tue, 1 Sep 2026 18:24:46 +0530 Subject: [PATCH] MDEV-40501: Assertion `info->type == READ_CACHE || info->type == WRITE_CACHE' failed in reinit_io_cache upon CHANGE MASTER Issue: CHANGE MASTER ... FOR CHANNEL with a channel name within MAX_CONNECTION_NAME can still overflow the OS file name limit once escaped into the relay log file name. The failed open then leaves the relay log's index IO_CACHE uninitialized, but the CHANGE MASTER error-cleanup path unconditionally calls reset_logs() on it, hitting the assertion in reinit_io_cache(). Solution: Guard reinit_io_cache() in find_log_pos() with my_b_inited() so an unopened index file returns a clean error instead of asserting. Raise a proper client-visible error from Relay_log_info::init() when the relay log fails to open, guarding the other error paths that raise a more generic error so they don't double-set the diagnostics area. Use MY_SAFE_PATH in open_index_file() so an over-length name fails deterministically instead of silently falling back to a mangled one. --- ...rpl_change_master_long_channel_name.result | 17 ++++++++ .../rpl_change_master_long_channel_name.test | 41 +++++++++++++++++++ sql/log.cc | 17 +++++--- sql/rpl_rli.cc | 1 + sql/sql_repl.cc | 13 ++++-- 5 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_change_master_long_channel_name.result create mode 100644 mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test diff --git a/mysql-test/suite/rpl/r/rpl_change_master_long_channel_name.result b/mysql-test/suite/rpl/r/rpl_change_master_long_channel_name.result new file mode 100644 index 0000000000000..dbaf1db78bd41 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_change_master_long_channel_name.result @@ -0,0 +1,17 @@ +include/master-slave.inc +[connection master] +connection slave; +CHANGE MASTER TO MASTER_DELAY=1 FOR CHANNEL '################################################'; +ERROR HY000: Can't open file: 'slave-relay-bin-@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@0023@002' (errno: 36 "File name too long") +connection master; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +connection slave; +connection slave; +include/diff_tables.inc [master:t1, slave:t1] +CHANGE MASTER 'new_channel' TO MASTER_HOST='127.0.0.1', MASTER_PORT=1, MASTER_USER='root'; +RESET SLAVE 'new_channel' ALL; +connection master; +DROP TABLE t1; +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test b/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test new file mode 100644 index 0000000000000..c2f9c23565ded --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test @@ -0,0 +1,41 @@ +# +# CHANGE MASTER ... FOR CHANNEL with a name that overflows the relay +# log file name once escaped should return an error, not crash. +# +# STEPS: +# 1. Issue CHANGE MASTER ... FOR CHANNEL with a channel name that is +# within MAX_CONNECTION_NAME, but expands past the file name +# limit once escaped into the relay log file name. +# 2. Verify the statement fails with a clean error, not a crash. +# 3. Verify the server is still usable: a normal CHANGE MASTER for a +# new, short-named channel succeeds. +# +--source include/master-slave.inc + +--connection slave +--let $channel= `SELECT REPEAT('#', 48)` +--error ER_CANT_OPEN_FILE +eval CHANGE MASTER TO MASTER_DELAY=1 FOR CHANNEL '$channel'; + +--connection master +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +--sync_slave_with_master + +--connection slave +--let $diff_tables= master:t1, slave:t1 +--source include/diff_tables.inc + +CHANGE MASTER 'new_channel' TO MASTER_HOST='127.0.0.1', MASTER_PORT=1, MASTER_USER='root'; +RESET SLAVE 'new_channel' ALL; + +--disable_query_log +call mtr.add_suppression("Failed when trying to open logs"); +call mtr.add_suppression("Failed to locate old binlog or relay log files"); +--enable_query_log + +--connection master +DROP TABLE t1; +--sync_slave_with_master + +--source include/rpl_end.inc diff --git a/sql/log.cc b/sql/log.cc index 00544f71b17cc..46bca04beb00c 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -3871,15 +3871,15 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg, 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; + myf opt= MY_UNPACK_FILENAME | MY_SAFE_PATH; if (!index_file_name_arg) { index_file_name_arg= log_name; // Use same basename for index file - opt= MY_UNPACK_FILENAME | MY_REPLACE_EXT; + opt= MY_UNPACK_FILENAME | MY_REPLACE_EXT | MY_SAFE_PATH; } - fn_format(index_file_name, index_file_name_arg, mysql_data_home, - ".index", opt); - if ((index_file_nr= mysql_file_open(m_key_file_log_index, + if (!fn_format(index_file_name, index_file_name_arg, mysql_data_home, + ".index", opt) || + (index_file_nr= mysql_file_open(m_key_file_log_index, index_file_name, O_RDWR | O_CREAT | O_BINARY | O_CLOEXEC, MYF(MY_WME))) < 0 || @@ -4475,6 +4475,13 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name, DBUG_PRINT("enter", ("log_name: %s, full_log_name: %s", log_name ? log_name : "NULL", full_log_name)); + /* index_file is not open if the log/index file failed to be created */ + if (!my_b_inited(&index_file)) + { + error= LOG_INFO_EOF; + goto end; + } + /* 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); diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 05a8b5948e306..0983cd31dca30 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -244,6 +244,7 @@ a file name for --relay-log-index option", opt_relaylog_index_name); { mysql_mutex_unlock(log_lock); mysql_mutex_unlock(&data_lock); + my_error(ER_CANT_OPEN_FILE, MYF(0), ln, my_errno); sql_print_error("Failed when trying to open logs for '%s' in Relay_log_info::init(). Error: %M", ln, my_errno); DBUG_RETURN(1); } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 34f84a0c32e43..6ede7f4f5ffd2 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3487,7 +3487,9 @@ int start_slave(THD* thd , Master_info* mi, bool net_report) if (slave_errno) { - if (net_report) + /* A more specific error may already have been raised (e.g. by + Relay_log_info::init() when it fails to open the relay log). */ + if (net_report && !thd->is_error()) my_error(slave_errno, MYF(0), (int) mi->connection_name.length, mi->connection_name.str); @@ -3921,9 +3923,12 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) if (init_master_info(mi, master_info_file_tmp, relay_log_info_file_tmp, 0, thread_mask)) { - my_error(ER_MASTER_INFO, MYF(0), - (int) lex_mi->connection_name.length, - lex_mi->connection_name.str); + /* A more specific error may already have been raised (e.g. by + Relay_log_info::init() when it fails to open the relay log). */ + if (!thd->is_error()) + my_error(ER_MASTER_INFO, MYF(0), + (int) lex_mi->connection_name.length, + lex_mi->connection_name.str); ret= TRUE; goto err; }