/* Copyright (c) 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. 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, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "my_dbug.h" #include "mysql/components/services/log_builtins.h" #include "mysql/psi/mysql_file.h" #include "mysqld_error.h" #include "sql/binlog_index.h" #include "sql/current_thd.h" #include "sql/debug_sync.h" #include "sql/handler.h" #include "sql/mysqld.h" #include "sql/sql_class.h" // THD #include "strmake.h" /** Copy content of 'from' file from offset to 'to' 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 from File to copy. @param to File to copy to. @param offset Offset in 'from' file. @param end_pos End position in 'from' file up to which content should be copied; 0 means copying till the end of file @retval 0 ok @retval -1 error */ static bool copy_file(IO_CACHE *from, IO_CACHE *to, my_off_t offset, my_off_t end_pos = 0) { int bytes_read; uchar io_buf[IO_SIZE * 2]; DBUG_TRACE; unsigned int bytes_written = 0; mysql_file_seek(from->file, offset, MY_SEEK_SET, MYF(0)); while (true) { if ((bytes_read = (int)mysql_file_read(from->file, io_buf, sizeof(io_buf), MYF(MY_WME))) < 0) return true; if (DBUG_EVALUATE_IF("fault_injection_copy_part_file", 1, 0)) bytes_read = bytes_read / 2; if (!bytes_read) break; // end of file if (end_pos != 0 && (bytes_written + bytes_read > end_pos - offset)) { bytes_read = end_pos - offset - bytes_written; } bytes_written += bytes_read; if (mysql_file_write(to->file, io_buf, bytes_read, MYF(MY_WME | MY_NABP))) return true; } return false; } bool normalize_binlog_name(char *to, const char *from, bool is_relay_log) { DBUG_TRACE; bool error = false; char buff[FN_REFLEN]; char *ptr = const_cast(from); char *opt_name = is_relay_log ? opt_relay_logname : opt_bin_logname; assert(from); /* opt_name is not null and not empty and from is a relative path */ if (opt_name && opt_name[0] && from && !test_if_hard_path(from)) { // take the path from opt_name // take the filename from from char log_dirpart[FN_REFLEN], log_dirname[FN_REFLEN]; size_t log_dirpart_len, log_dirname_len; dirname_part(log_dirpart, opt_name, &log_dirpart_len); dirname_part(log_dirname, from, &log_dirname_len); /* log may be empty => relay-log or log-bin did not hold paths, just filename pattern */ if (log_dirpart_len > 0) { /* create the new path name */ if (fn_format(buff, from + log_dirname_len, log_dirpart, "", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH)) == nullptr) { error = true; goto end; } ptr = buff; } } assert(ptr); if (ptr) { size_t length = strlen(ptr); // Strips the CR+LF at the end of log name and \0-terminates it. if (length && ptr[length - 1] == '\n') { ptr[length - 1] = 0; length--; if (length && ptr[length - 1] == '\r') { ptr[length - 1] = 0; length--; } } if (!length) { error = true; goto end; } strmake(to, ptr, length); } end: return error; } int compare_log_name(const char *log_1, const char *log_2) { const char *log_1_basename = log_1 + dirname_length(log_1); const char *log_2_basename = log_2 + dirname_length(log_2); return strcmp(log_1_basename, log_2_basename); } void exec_binlog_error_action_abort(const char *err_string) { THD *thd = current_thd; /* When the code enters here it means that there was an error at higher layer and my_error function could have been invoked to let the client know what went wrong during the execution. But these errors will not let the client know that the server is going to abort. Even if we add an additional my_error function call at this point client will be able to see only the first error message that was set during the very first invocation of my_error function call. The advantage of having multiple my_error function calls are visible when the server is up and running and user issues SHOW WARNINGS or SHOW ERROR calls. In this special scenario server will be immediately aborted and user will not be able execute the above SHOW commands. Hence we clear the previous errors and push one critical error message to clients. */ if (thd) { if (thd->is_error()) thd->clear_error(); /* Send error to both client and to the server error log. */ my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(ME_FATALERROR), err_string); } LogErr(ERROR_LEVEL, ER_BINLOG_LOGGING_NOT_POSSIBLE, err_string); flush_error_log_messages(); if (thd) thd->send_statement_status(); my_abort(); } Log_info::Log_info() : index_file_offset(0), index_file_start_offset(0), pos(0), fatal(false), entry_index(0), encrypted_header_size(0), thread_id(0) { memset(log_file_name, 0, FN_REFLEN); } Binlog_index::Binlog_index(bool relay_log) : is_relay_log(relay_log) { index_file_name[0] = 0; } void Binlog_index::set_psi_keys(PSI_file_key key_file_log_index, PSI_file_key key_file_log_index_cache) { m_key_file_log_index_cache = key_file_log_index_cache; m_key_file_log_index = key_file_log_index; } bool Binlog_index::open_index_file(const char *index_file_name_arg, myf opt) { bool error = false; File index_file_nr = -1; /* First open of this class instance Create an index file that will hold all file names used for logging. Add new entries to the end of it. */ if (my_b_inited(&index_file)) goto end; fn_format(index_file_name, index_file_name_arg, mysql_data_home, ".index", opt); if (set_crash_safe_index_file_name(index_file_name_arg)) { error = true; goto end; } /* We need move crash_safe_index_file to index_file if the index_file does not exist and crash_safe_index_file exists when mysqld server restarts. */ if (my_access(index_file_name, F_OK) && !my_access(crash_safe_index_file_name, F_OK) && my_rename(crash_safe_index_file_name, index_file_name, MYF(MY_WME))) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_MOVE_TMP_TO_INDEX, "Binlog_index::open_index_file"); error = true; goto end; } if ((index_file_nr = mysql_file_open(m_key_file_log_index, index_file_name, O_RDWR | O_CREAT, MYF(MY_WME))) < 0 || mysql_file_sync(index_file_nr, MYF(MY_WME)) || init_io_cache_ext(&index_file, index_file_nr, IO_SIZE, READ_CACHE, mysql_file_seek(index_file_nr, 0L, MY_SEEK_END, MYF(0)), false, MYF(MY_WME | MY_WAIT_IF_FULL), m_key_file_log_index_cache) || 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 mysql_file_create() not mysql_file_open(). */ if (index_file_nr >= 0) mysql_file_close(index_file_nr, MYF(0)); error = true; goto end; } end: return error; } int Binlog_index::close_index_file() { int error = 0; if (my_b_inited(&index_file)) { end_io_cache(&index_file); error = mysql_file_close(index_file.file, MYF(0)); } return error; } bool Binlog_index::is_inited_index_file() { return my_b_inited(&index_file); } int Binlog_index::open_crash_safe_index_file() { int error = 0; File file = -1; DBUG_TRACE; if (!my_b_inited(&crash_safe_index_file)) { myf flags = MY_WME | MY_NABP | MY_WAIT_IF_FULL; if (is_relay_log) flags = flags | MY_REPORT_WAITING_IF_FULL; if ((file = my_open(crash_safe_index_file_name, O_RDWR | O_CREAT, MYF(MY_WME))) < 0 || init_io_cache(&crash_safe_index_file, file, IO_SIZE, WRITE_CACHE, 0, false, flags)) { error = 1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_OPEN_TEMPORARY_INDEX_FILE); } } return error; } int Binlog_index::close_crash_safe_index_file() { int error = 0; DBUG_TRACE; if (my_b_inited(&crash_safe_index_file)) { end_io_cache(&crash_safe_index_file); error = my_close(crash_safe_index_file.file, MYF(0)); } crash_safe_index_file = IO_CACHE(); return error; } int Binlog_index::set_crash_safe_index_file_name(const char *base_file_name) { int error = 0; DBUG_TRACE; if (fn_format(crash_safe_index_file_name, base_file_name, mysql_data_home, ".index_crash_safe", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH | MY_REPLACE_EXT)) == nullptr) { error = 1; LogErr(ERROR_LEVEL, ER_BINLOG_CANT_SET_TMP_INDEX_NAME); } return error; } bool Binlog_index::is_inited_crash_safe_index_file() { return my_b_inited(&crash_safe_index_file); } int Binlog_index::open_purge_index_file(bool destroy) { int error = 0; File file = -1; DBUG_TRACE; if (destroy) close_purge_index_file(); if (!my_b_inited(&purge_index_file)) { myf flags = MY_WME | MY_NABP | MY_WAIT_IF_FULL; if (is_relay_log) flags = flags | MY_REPORT_WAITING_IF_FULL; if ((file = my_open(purge_index_file_name, O_RDWR | O_CREAT, MYF(MY_WME))) < 0 || init_io_cache(&purge_index_file, file, IO_SIZE, (destroy ? WRITE_CACHE : READ_CACHE), 0, false, flags)) { error = 1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_OPEN_REGISTER_FILE); } } return error; } int Binlog_index::close_purge_index_file() { int error = 0; DBUG_TRACE; 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)); new (&purge_index_file) IO_CACHE(); return error; } int Binlog_index::end_close_purge_index_file() { int error = 0; DBUG_TRACE; if (my_b_inited(&purge_index_file)) { end_io_cache(&purge_index_file); error = my_close(purge_index_file.file, MYF(0)); } return error; } int Binlog_index::set_purge_index_file_name(const char *base_file_name) { int error = 0; DBUG_TRACE; if (fn_format( purge_index_file_name, base_file_name, mysql_data_home, ".~rec~", MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH | MY_REPLACE_EXT)) == nullptr) { error = 1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_SET_PURGE_INDEX_FILE_NAME); } return error; } bool Binlog_index::is_inited_purge_index_file() { return my_b_inited(&purge_index_file); } int Binlog_index::reinit_purge_index_file() { return reinit_io_cache(&purge_index_file, READ_CACHE, 0, false, false); } int Binlog_index::sync_purge_index_file() { int error = 0; DBUG_TRACE; if ((error = flush_io_cache(&purge_index_file)) || (error = my_sync(purge_index_file.file, MYF(MY_WME)))) return error; return error; } int Binlog_index::gets_purge_index_file(char *to, size_t max_length) { return my_b_gets(&purge_index_file, to, max_length); } int Binlog_index::error_purge_index_file() { return purge_index_file.error; } int Binlog_index::register_purge_index_entry(const char *entry) { int error = 0; DBUG_TRACE; if ((error = my_b_write(&purge_index_file, (const uchar *)entry, strlen(entry))) || (error = my_b_write(&purge_index_file, (const uchar *)"\n", 1))) return error; return error; } int Binlog_index::register_create_index_entry(const char *entry) { return register_purge_index_entry(entry); } int Binlog_index::find_log_pos(Log_info *linfo, const char *log_name) { int error = 0; char *full_fname = linfo->log_file_name; char full_log_name[FN_REFLEN], fname[FN_REFLEN]; DBUG_TRACE; full_log_name[0] = full_fname[0] = 0; if (!my_b_inited(&index_file)) { error = LOG_INFO_IO; goto end; } // extend relative paths for log_name to be searched if (log_name) { if (normalize_binlog_name(full_log_name, log_name, is_relay_log)) { error = LOG_INFO_EOF; goto end; } } DBUG_PRINT("enter", ("log_name: %s, full_log_name: %s", log_name ? log_name : "NULL", full_log_name)); /* As the file is flushed, we can't get an error here */ my_b_seek(&index_file, (my_off_t)0); for (;;) { size_t 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; } // extend relative paths and match against full path if (normalize_binlog_name(full_fname, fname, is_relay_log)) { error = LOG_INFO_EOF; break; } // if the log entry matches, null string matching anything if (!log_name || !compare_log_name(full_fname, full_log_name)) { DBUG_PRINT("info", ("Found log file entry")); linfo->index_file_start_offset = offset; linfo->index_file_offset = my_b_tell(&index_file); break; } linfo->entry_index++; } end: return error; } int Binlog_index::find_next_log(Log_info *linfo) { int error = 0; size_t length; char fname[FN_REFLEN]; char *full_fname = linfo->log_file_name; if (!my_b_inited(&index_file)) { error = LOG_INFO_IO; goto err; } /* As the file is flushed, we can't get an error here */ my_b_seek(&index_file, linfo->index_file_offset); 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; } if (fname[0] != 0) { if (normalize_binlog_name(full_fname, fname, is_relay_log)) { error = LOG_INFO_EOF; goto err; } length = strlen(full_fname); } linfo->index_file_offset = my_b_tell(&index_file); err: return error; } int Binlog_index::add_log_to_index(uchar *log_name, size_t log_name_len) { DBUG_TRACE; if (open_crash_safe_index_file()) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_OPEN_TMP_INDEX, "Binlog_index::add_log_to_index"); goto err; } if (copy_file(&index_file, &crash_safe_index_file, 0)) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_COPY_INDEX_TO_TMP, "Binlog_index::add_log_to_index"); goto err; } if (my_b_write(&crash_safe_index_file, log_name, log_name_len) || my_b_write(&crash_safe_index_file, pointer_cast("\n"), 1) || flush_io_cache(&crash_safe_index_file) || mysql_file_sync(crash_safe_index_file.file, MYF(MY_WME))) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_APPEND_LOG_TO_TMP_INDEX, log_name); goto err; } if (close_crash_safe_index_file()) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_CLOSE_TMP_INDEX, "Binlog_index::add_log_to_index"); goto err; } return 0; err: return -1; } int Binlog_index::move_crash_safe_index_file_to_index_file() { int error = 0; File fd = -1; DBUG_TRACE; int failure_trials = Binlog_index::MAX_RETRIES_FOR_DELETE_RENAME_FAILURE; bool file_rename_status = false, file_delete_status = false; THD *thd = current_thd; if (my_b_inited(&index_file)) { end_io_cache(&index_file); if (mysql_file_close(index_file.file, MYF(0)) < 0) { error = -1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_CLOSE_INDEX_FILE_WHILE_REBUILDING, index_file_name); /* Delete Crash safe index file here and recover the binlog.index state(index_file io_cache) from old binlog.index content. */ mysql_file_delete(key_file_binlog_index, crash_safe_index_file_name, MYF(0)); goto recoverable_err; } /* Sometimes an outsider can lock index files for temporary viewing purpose. For eg: MEB locks binlog.index/relaylog.index to view the content of the file. During that small period of time, deletion of the file is not possible on some platforms(Eg: Windows) Server should retry the delete operation for few times instead of panicking immediately. */ while ((file_delete_status == false) && (failure_trials > 0)) { if (DBUG_EVALUATE_IF("force_index_file_delete_failure", 1, 0)) break; DBUG_EXECUTE_IF("simulate_index_file_delete_failure", { /* This simulation causes the delete to fail */ static char first_char = index_file_name[0]; index_file_name[0] = 0; sql_print_information("Retrying delete"); if (failure_trials == 1) index_file_name[0] = first_char; };); file_delete_status = !(mysql_file_delete(key_file_binlog_index, index_file_name, MYF(MY_WME))); --failure_trials; if (!file_delete_status) { my_sleep(1000); /* Clear the error before retrying. */ if (failure_trials > 0) thd->clear_error(); } } if (!file_delete_status) { error = -1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_DELETE_INDEX_FILE_WHILE_REBUILDING, index_file_name); /* Delete Crash safe file index file here and recover the binlog.index state(index_file io_cache) from old binlog.index content. */ mysql_file_delete(key_file_binlog_index, crash_safe_index_file_name, MYF(0)); goto recoverable_err; } } DBUG_EXECUTE_IF("crash_create_before_rename_index_file", DBUG_SUICIDE();); /* Sometimes an outsider can lock index files for temporary viewing purpose. For eg: MEB locks binlog.index/relaylog.index to view the content of the file. During that small period of time, rename of the file is not possible on some platforms(Eg: Windows) Server should retry the rename operation for few times instead of panicking immediately. */ failure_trials = Binlog_index::MAX_RETRIES_FOR_DELETE_RENAME_FAILURE; while ((file_rename_status == false) && (failure_trials > 0)) { DBUG_EXECUTE_IF("simulate_crash_safe_index_file_rename_failure", { /* This simulation causes the rename to fail */ static char first_char = index_file_name[0]; index_file_name[0] = 0; sql_print_information("Retrying rename"); if (failure_trials == 1) index_file_name[0] = first_char; };); file_rename_status = !(my_rename(crash_safe_index_file_name, index_file_name, MYF(MY_WME))); --failure_trials; if (!file_rename_status) { my_sleep(1000); /* Clear the error before retrying. */ if (failure_trials > 0) thd->clear_error(); } } if (!file_rename_status) { error = -1; LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_RENAME_INDEX_FILE_WHILE_REBUILDING, index_file_name); goto fatal_err; } DBUG_EXECUTE_IF("crash_create_after_rename_index_file", DBUG_SUICIDE();); recoverable_err: if ((fd = mysql_file_open(key_file_binlog_index, index_file_name, O_RDWR | O_CREAT, MYF(MY_WME))) < 0 || mysql_file_sync(fd, MYF(MY_WME)) || init_io_cache_ext(&index_file, fd, IO_SIZE, READ_CACHE, mysql_file_seek(fd, 0L, MY_SEEK_END, MYF(0)), false, MYF(MY_WME | MY_WAIT_IF_FULL), key_file_binlog_index_cache)) { LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_OPEN_INDEX_FILE_AFTER_REBUILDING, index_file_name); goto fatal_err; } return error; fatal_err: /* This situation is very very rare to happen (unless there is some serious memory related issues like OOM) and should be treated as fatal error. Hence it is better to bring down the server without respecting 'binlog_error_action' value here. */ exec_binlog_error_action_abort( "MySQL server failed to update the " "binlog.index file's content properly. " "It might not be in sync with available " "binlogs and the binlog.index file state is in " "unrecoverable state. Aborting the server."); /* Server is aborted in the above function. This is dead code to make compiler happy. */ return error; } int Binlog_index::remove_logs_outside_range_from_index( Log_info *start_log_info, bool need_update_threads, Log_info *last_log_info) { my_off_t end_offset = 0; if (open_crash_safe_index_file()) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_OPEN_TMP_INDEX, "Binlog_index::remove_logs_outside_range_from_index"); goto err; } if (last_log_info != nullptr) { end_offset = last_log_info->index_file_offset; } if (copy_file(&index_file, &crash_safe_index_file, start_log_info->index_file_start_offset, end_offset)) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_COPY_INDEX_TO_TMP, "Binlog_index::remove_logs_outside_range_from_index"); goto err; } if (close_crash_safe_index_file()) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_CLOSE_TMP_INDEX, "Binlog_index::remove_logs_outside_range_from_index"); goto err; } DBUG_EXECUTE_IF("fault_injection_copy_part_file", DBUG_SUICIDE();); if (move_crash_safe_index_file_to_index_file()) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_MOVE_TMP_TO_INDEX, "Binlog_index::remove_logs_outside_range_from_index"); goto err; } // now update offsets in index file for running threads if (need_update_threads) adjust_linfo_offsets(start_log_info->index_file_start_offset); return 0; err: return LOG_INFO_IO; } void Binlog_index::register_log_info(Log_info *log_info) { DBUG_TRACE; log_info_set.insert(log_info); } void Binlog_index::unregister_log_info(Log_info *log_info) { DBUG_TRACE; log_info_set.erase(log_info); } int Binlog_index::log_in_use(const char *log_name) { DBUG_TRACE; int count = 0; int log_name_len = strlen(log_name) + 1; std::for_each( log_info_set.cbegin(), log_info_set.cend(), [log_name, log_name_len, &count](Log_info *log_info) { if (!strncmp(log_name, log_info->log_file_name, log_name_len)) { LogErr(WARNING_LEVEL, ER_BINLOG_FILE_BEING_READ_NOT_PURGED, log_name, log_info->thread_id); count++; } }); return count; } void Binlog_index::adjust_linfo_offsets(my_off_t purge_offset) { DBUG_TRACE; std::for_each(log_info_set.cbegin(), log_info_set.cend(), [purge_offset](Log_info *log_info) { /* 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 (log_info->index_file_offset < purge_offset) log_info->fatal = (log_info->index_file_offset != 0); else log_info->index_file_offset -= purge_offset; }); } Binlog_index_monitor::Binlog_index_monitor(bool relay_log) : m_binlog_index(relay_log), m_is_relay_log(relay_log) {} void Binlog_index_monitor::set_psi_keys(PSI_mutex_key key_LOCK_index, PSI_file_key key_file_log_index, PSI_file_key key_file_log_index_cache) { m_key_LOCK_index = key_LOCK_index; m_binlog_index.set_psi_keys(key_file_log_index, key_file_log_index_cache); } void Binlog_index_monitor::init_pthread_objects() { mysql_mutex_init(m_key_LOCK_index, &m_LOCK_index, MY_MUTEX_INIT_SLOW); } void Binlog_index_monitor::cleanup() { mysql_mutex_destroy(&m_LOCK_index); } bool Binlog_index_monitor::open_index_file(const char *index_file_name_arg, const char *log_name, PSI_file_key key_file_log, bool need_lock_index) { if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); 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; } bool error = m_binlog_index.open_index_file(index_file_name_arg, opt); if (error) { goto end; } /* 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 (m_binlog_index.set_purge_index_file_name(index_file_name_arg) || m_binlog_index.open_purge_index_file(false) || purge_index_entry(nullptr, nullptr, key_file_log, false) || m_binlog_index.close_purge_index_file() || DBUG_EVALUATE_IF("fault_injection_recovering_index", 1, 0)) { LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_SYNC_INDEX_FILE); error = true; goto end; } end: if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return error; } int Binlog_index_monitor::close_index_file(bool need_lock_index) { if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); int ret = m_binlog_index.close_index_file(); if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return ret; } bool Binlog_index_monitor::is_inited_index_file() { return m_binlog_index.is_inited_index_file(); } int Binlog_index_monitor::open_purge_index_file(bool destroy) { return m_binlog_index.open_purge_index_file(destroy); } int Binlog_index_monitor::close_purge_index_file() { return m_binlog_index.close_purge_index_file(); } int Binlog_index_monitor::end_close_purge_index_file() { return m_binlog_index.end_close_purge_index_file(); } int Binlog_index_monitor::set_purge_index_file_name( const char *base_file_name) { return m_binlog_index.set_purge_index_file_name(base_file_name); } bool Binlog_index_monitor::is_inited_purge_index_file() { return m_binlog_index.is_inited_purge_index_file(); } int Binlog_index_monitor::reinit_purge_index_file() { return m_binlog_index.reinit_purge_index_file(); } int Binlog_index_monitor::sync_purge_index_file() { return m_binlog_index.sync_purge_index_file(); } int Binlog_index_monitor::gets_purge_index_file(char *to, size_t max_length) { return m_binlog_index.gets_purge_index_file(to, max_length); } int Binlog_index_monitor::error_purge_index_file() { return m_binlog_index.error_purge_index_file(); } int Binlog_index_monitor::register_purge_index_entry(const char *entry) { return m_binlog_index.register_purge_index_entry(entry); } int Binlog_index_monitor::register_create_index_entry(const char *entry) { return m_binlog_index.register_create_index_entry(entry); } int Binlog_index_monitor::purge_index_entry(THD *thd, ulonglong *decrease_log_space, PSI_file_key key_file_log, bool need_lock_index) { MY_STAT s; int error = 0; Log_info log_info; Log_info check_log_info; DBUG_TRACE; assert(is_inited_purge_index_file()); if ((error = reinit_purge_index_file())) { LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_REINIT_REGISTER_FILE); goto err; } for (;;) { size_t length; if ((length = gets_purge_index_file(log_info.log_file_name, FN_REFLEN)) <= 1) { if ((error = error_purge_index_file())) { LogErr(ERROR_LEVEL, ER_BINLOG_FAILED_TO_READ_REGISTER_FILE, error); goto err; } /* Reached EOF */ break; } /* Get rid of the trailing '\n' */ log_info.log_file_name[length - 1] = 0; if (!mysql_file_stat(key_file_log, 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, Sql_condition::SL_WARNING, ER_LOG_PURGE_NO_FILE, ER_THD(thd, ER_LOG_PURGE_NO_FILE), log_info.log_file_name); } LogErr(INFORMATION_LEVEL, ER_CANT_STAT_FILE, log_info.log_file_name); set_my_errno(0); } else { /* Other than ENOENT are fatal */ if (thd) { push_warning_printf(thd, Sql_condition::SL_WARNING, 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 { LogErr(INFORMATION_LEVEL, ER_BINLOG_CANT_DELETE_LOG_FILE_DOES_INDEX_MATCH_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_lock_index))) { if (error != LOG_INFO_EOF) { if (thd) { push_warning_printf(thd, Sql_condition::SL_WARNING, ER_BINLOG_PURGE_FATAL_ERR, "a problem with deleting %s and " "reading the binlog index file", log_info.log_file_name); } else { LogErr(INFORMATION_LEVEL, ER_BINLOG_CANT_DELETE_FILE_AND_READ_BINLOG_INDEX, log_info.log_file_name); } goto err; } error = 0; if (!m_is_relay_log) ha_binlog_index_purge_file(current_thd, log_info.log_file_name); DBUG_PRINT("info", ("purging %s", log_info.log_file_name)); if (!mysql_file_delete(key_file_binlog, log_info.log_file_name, MYF(0))) { DBUG_EXECUTE_IF("wait_in_purge_index_entry", { const char action[] = "now SIGNAL in_purge_index_entry WAIT_FOR go_ahead_sql"; assert(!debug_sync_set_action(thd, STRING_WITH_LEN(action))); DBUG_SET("-d,wait_in_purge_index_entry"); };); if (decrease_log_space) *decrease_log_space -= s.st_size; } else { if (my_errno() == ENOENT) { if (thd) { push_warning_printf( thd, Sql_condition::SL_WARNING, ER_LOG_PURGE_NO_FILE, ER_THD(thd, ER_LOG_PURGE_NO_FILE), log_info.log_file_name); } LogErr(INFORMATION_LEVEL, ER_BINLOG_CANT_DELETE_FILE, log_info.log_file_name); set_my_errno(0); } else { if (thd) { push_warning_printf(thd, Sql_condition::SL_WARNING, 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 { LogErr(INFORMATION_LEVEL, ER_BINLOG_CANT_DELETE_LOG_FILE_DOES_INDEX_MATCH_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: return error; } int Binlog_index_monitor::move_crash_safe_index_file_to_index_file( bool need_lock_index) { if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); int error = m_binlog_index.move_crash_safe_index_file_to_index_file(); if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return error; } int Binlog_index_monitor::remove_logs_outside_range_from_index( Log_info *start_log_info, bool need_update_threads, Log_info *last_log_info) { return m_binlog_index.remove_logs_outside_range_from_index( start_log_info, need_update_threads, last_log_info); } int Binlog_index_monitor::remove_logs_outside_range_from_index( const std::string &first, bool need_update_threads, const std::string &last) { Log_info first_linfo; Log_info last_linfo; MUTEX_LOCK(g, &m_LOCK_index); int error = find_log_pos(&first_linfo, first.c_str(), false); if (error) return error; error = find_log_pos(&last_linfo, last.c_str(), false); if (error) return error; return m_binlog_index.remove_logs_outside_range_from_index( &first_linfo, need_update_threads, &last_linfo); } void Binlog_index_monitor::register_log_info(Log_info *log_info) { return m_binlog_index.register_log_info(log_info); } void Binlog_index_monitor::unregister_log_info(Log_info *log_info) { return m_binlog_index.unregister_log_info(log_info); } int Binlog_index_monitor::add_log_to_index(uchar *log_name, size_t log_name_len, bool need_lock_index) { if (m_binlog_index.add_log_to_index(log_name, log_name_len)) { goto err; } if (move_crash_safe_index_file_to_index_file(need_lock_index)) { LogErr(ERROR_LEVEL, ER_BINLOG_CANT_MOVE_TMP_TO_INDEX, "Binlog_index_monitor::add_log_to_index"); goto err; } return 0; err: return -1; } int Binlog_index_monitor::log_in_use(const char *log_name) { mysql_mutex_assert_owner(&m_LOCK_index); return m_binlog_index.log_in_use(log_name); } void Binlog_index_monitor::adjust_linfo_offsets(my_off_t purge_offset) { mysql_mutex_assert_owner(&m_LOCK_index); return m_binlog_index.adjust_linfo_offsets(purge_offset); } const char *Binlog_index_monitor::get_index_fname() const { return m_binlog_index.get_index_fname(); } int Binlog_index_monitor::find_log_pos(Log_info *linfo, const char *log_name, bool need_lock_index) { /* Mutex needed because we need to make sure the file pointer does not move from under our feet */ if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); int error = m_binlog_index.find_log_pos(linfo, log_name); if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return error; } int Binlog_index_monitor::find_next_log(Log_info *linfo, bool need_lock_index) { if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); int error = m_binlog_index.find_next_log(linfo); if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return error; } std::pair> Binlog_index_monitor::get_log_index( bool need_lock_index) { DBUG_TRACE; Log_info log_info; if (need_lock_index) mysql_mutex_lock(&m_LOCK_index); else mysql_mutex_assert_owner(&m_LOCK_index); std::list filename_list; int error = 0; for (error = find_log_pos(&log_info, nullptr, false /*need_lock_index*/); error == 0; error = find_next_log(&log_info, false /*need_lock_index*/)) { filename_list.push_back(std::string(log_info.log_file_name)); } if (need_lock_index) mysql_mutex_unlock(&m_LOCK_index); return std::make_pair(error, filename_list); } std::pair, mysql::utils::Error> Binlog_index_monitor::get_filename_list() { std::pair, mysql::utils::Error> result; auto &[filename_list, internal_error] = result; Log_info linfo; int error = 0; std::string error_message; MUTEX_LOCK(g, &m_LOCK_index); try { for (error = find_log_pos(&linfo, nullptr, false); !error; error = find_next_log(&linfo, false)) { filename_list.push_back(std::string(linfo.log_file_name)); } } catch (std::bad_alloc &) { internal_error = mysql::utils::Error("Binlog_index_monitor", __FILE__, __LINE__, "Out of memory"); } if (error != LOG_INFO_EOF) { internal_error = mysql::utils::Error("Binlog_index_monitor", __FILE__, __LINE__, "Error while reading index file"); } return result; } int Binlog_index_monitor::find_next_relay_log(char log_name[FN_REFLEN + 1]) { Log_info info; int error; char relative_path_name[FN_REFLEN + 1]; if (fn_format(relative_path_name, log_name + dirname_length(log_name), mysql_data_home, "", 0) == nullptr) return 1; mysql_mutex_lock(&m_LOCK_index); error = find_log_pos(&info, relative_path_name, false); if (error == 0) { error = find_next_log(&info, false); if (error == 0) strcpy(log_name, info.log_file_name); } mysql_mutex_unlock(&m_LOCK_index); return error; }