/* Copyright (c) 2018, 2021, Alibaba 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, version 2.0, as published by the Free Software Foundation. This program is also distributed 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/PolarDB-X Engine hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL/PolarDB-X Engine. 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 */ /** @file fil/fil0purge.cc Implementation of data file purge operation. Created 1/11/2019 Galaxy SQL *******************************************************/ #include "fil0purge.h" #include "fil0fil.h" #include "os0file.h" #include "row0mysql.h" #include "srv0file.h" #include "ut0mutex.h" /* Global file purge system */ File_purge *file_purge_sys = nullptr; /** Constructor */ File_purge::File_purge(ulint thread_id, time_t start_time) : m_thread_id(thread_id), m_start_time(start_time), m_id(0), m_dir(nullptr) { mutex_create(LATCH_ID_FILE_PURGE_LIST, &m_mutex); UT_LIST_INIT(m_list, &file_purge_node_t::LIST); } File_purge::~File_purge() { /** Clear the file nodes, those will be purged again when DDL log recovery after reboot */ for (file_purge_node_t *node = UT_LIST_GET_FIRST(m_list); node != nullptr; node = UT_LIST_GET_FIRST(m_list)) { if (node->m_file_path) ut_free(node->m_file_path); if (node->m_original_path) ut_free(node->m_original_path); UT_LIST_REMOVE(m_list, node); ut_free(node); } mutex_free(&m_mutex); } void File_purge::lock() { mutex_enter(&m_mutex); } void File_purge::unlock() { mutex_exit(&m_mutex); } /** Iterate the file nodes in list. @param[in] first Whether it is first retrieve from list @param[in] last Last iterated file node @retval node The file purge node in list */ file_purge_node_t *File_purge::iterate_node(bool first, file_purge_node_t *last) { DBUG_ENTER("File_purge::iterate_node"); if (first) { ut_ad(last == nullptr); DBUG_RETURN(UT_LIST_GET_FIRST(m_list)); } else { ut_ad(last != nullptr); DBUG_RETURN(UT_LIST_GET_NEXT(LIST, last)); } } /* Get next unique id number */ ulint File_purge::next_id() { ulint number; number = m_id++; return number; } /** Add file into purge list @param[in] id Log DDL record id @param[int] path The temporary file name @param[int] original_path The original file name @retval false Success @retval true Failure */ bool File_purge::add_file(ulint id, const char *path, const char *original_path) { bool success = false; os_offset_t file_size; pfs_os_file_t handle = os_file_create_simple_no_error_handling( innodb_data_file_key, path, OS_FILE_OPEN, OS_FILE_READ_WRITE, FALSE, &success); if (!success) { ib::error(ER_IB_MSG_392) << "Cannot open temp data file for read-write: '" << path << "'" << " when add file into purge list"; /** Temp file maybe has been purged */ ut_free(const_cast(path)); log_ddl->remove_by_id(id); return true; } file_size = os_file_get_size(handle); os_file_close(handle); file_purge_node_t *node = static_cast( ut_malloc_nokey(sizeof(file_purge_node_t))); node->m_log_ddl_id = id; node->m_file_path = const_cast(path); node->m_start_time = ut_time(); /* Original path will be null when ddl log recovery */ node->m_original_path = original_path ? mem_strdup(original_path) : nullptr; node->m_original_size = file_size; /** Current size will change when purge file, and is not protected by mutex. */ node->m_current_size = file_size; if (srv_print_data_file_purge_process) ib::info(ER_IB_MSG_FILE_PURGE) << "File purge add file : " << id << ";" << path; mutex_enter(&m_mutex); UT_LIST_ADD_LAST(m_list, node); mutex_exit(&m_mutex); /* Wake up background thread */ srv_wakeup_file_purge_thread(); return false; } /** Purge the first file node by purge max size. @param[in] size Purge max size (bytes) @param[in] force Whether unlink file directly First return in std::pair @retval -1 Error @retval 0 Success & no file purge @retval >0 How many purged operation Second return in std::pair @retval size Truncated file size */ std::pair File_purge::purge_file(ulint size, bool force) { bool success = false; uint truncated = 0; file_purge_node_t *node = nullptr; pfs_os_file_t handle; ulint truncated_size = 0; mutex_enter(&m_mutex); node = UT_LIST_GET_FIRST(m_list); mutex_exit(&m_mutex); if (node) { handle = os_file_create_simple_no_error_handling( innodb_data_file_key, node->m_file_path, OS_FILE_OPEN, OS_FILE_READ_WRITE, FALSE, &success); if (!success) { ulint err = os_file_get_last_error(true); ib::error(ER_IB_MSG_392) << "Cannot open temp data file for read-write: '" << node->m_file_path << "'" << " when purge file from list"; /* Maybe delete by DBA, so remove file directly */ if (err == OS_FILE_NOT_FOUND) remove_file(node); return std::make_pair(-1, 0); } if (srv_print_data_file_purge_process) ib::info(ER_IB_MSG_FILE_PURGE) << "File purge purge file : " << node->m_file_path; os_offset_t file_size = os_file_get_size(handle); if (!force && file_size > size) { ut_ad(file_size == node->m_current_size); /* Truncate predefined size once */ os_file_truncate(node->m_file_path, handle, file_size - size); os_file_close(handle); truncated++; node->m_current_size = (file_size - size); truncated_size += size; } else { /* Direct delete the file when file_size < predefined size */ os_file_close(handle); os_file_delete(innodb_data_file_key, node->m_file_path); remove_file(node); truncated++; truncated_size += file_size; } } else { return std::make_pair(0, 0); } return std::make_pair(truncated, truncated_size); } /** The data file list length @retval >=0 */ ulint File_purge::length() { ulint len; mutex_enter(&m_mutex); len = UT_LIST_GET_LEN(m_list); mutex_exit(&m_mutex); return len; } /** Purge all the data file cached in list. @param[in] size Purge max size (bytes) @param[in] force Purge little by little or unlink directly. */ void File_purge::purge_all(ulint size, bool force) { while (length() > 0) { purge_file(size, force); } } /** Remove the file node from list @param[in] node File purge node pointer */ void File_purge::remove_file(file_purge_node_t *node) { ulint log_id = node->m_log_ddl_id; mutex_enter(&m_mutex); UT_LIST_REMOVE(m_list, node); mutex_exit(&m_mutex); #ifdef UNIV_DEBUG bool exist; os_file_type_t type; os_file_status(node->m_file_path, &exist, &type); ut_ad(exist == false); #endif if (srv_print_data_file_purge_process) ib::info(ER_IB_MSG_FILE_PURGE) << "File purge remove file : " << node->m_file_path; ut_free(node->m_file_path); ut_free(node->m_original_path); ut_free(node); log_ddl->remove_by_id(log_id); } /** Generate a unique temporary file name. @param[in] filepath Original file name @retval file name Generated file name */ char *File_purge::generate_file(const char *filepath) { std::string new_path; new_path.assign(get_dir()); std::string temp_filename; temp_filename.assign(PREFIX); temp_filename.append(std::to_string((ulong)m_start_time)); temp_filename.append("_"); temp_filename.append(std::to_string(next_id())); char *new_file = Fil_path::make(new_path, temp_filename, NO_EXT, false); if (srv_print_data_file_purge_process) ib::info(ER_IB_MSG_FILE_PURGE) << "File purge generate file : " << filepath << ";" << new_file; return new_file; } /** Drop a single-table tablespace and rename the data file as temporary file. This deletes the fil_space_t if found and rename the file on disk. @param[in] space_id Tablespace id @param[in] filepath File path of tablespace to delete @retval error code */ dberr_t row_purge_single_table_tablespace(space_id_t space_id, const char *filepath) { dberr_t err = DB_SUCCESS; uint64_t log_id; char *new_filepath = file_purge_sys->generate_file(filepath); log_ddl->write_purge_file_log(&log_id, file_purge_sys->get_thread_id(), new_filepath); if (srv_print_data_file_purge_process) ib::info(ER_IB_MSG_FILE_PURGE) << "File purge write log : " << log_id << ";" << new_filepath; if (!fil_space_exists_in_mem(space_id, nullptr, true, false, NULL, 0)) { if (fil_purge_file(filepath, new_filepath)) { ib::info(ER_IB_MSG_989) << "Purge data file " << filepath; } } else { // purge file TODO BUF_REMOVE_FLUSH_NO_WRITE -> BUF_REMOVE_NONE err = fil_delete_tablespace(space_id, BUF_REMOVE_FLUSH_NO_WRITE, new_filepath); } file_purge_sys->add_file(log_id, new_filepath, filepath); return err; } /** Rename the ibd data file and delete the releted files @param[in] old_filepath The original data file @param[in] new_filepath The new data file @retval TRUE Success @retval FALSE Failure */ bool fil_purge_file(const char *old_filepath, const char *new_filepath) { bool success = true; bool exist; os_file_type_t type; /* If old file has been renamed or dropped, just skip it */ os_file_status(old_filepath, &exist, &type); if (exist && (!(success = os_file_rename(innodb_data_file_key, old_filepath, new_filepath)))) { success = os_file_delete_if_exists(innodb_data_file_key, old_filepath, nullptr); } char *cfg_filepath = Fil_path::make_cfg(old_filepath); if (cfg_filepath != nullptr) { os_file_delete_if_exists(innodb_data_file_key, cfg_filepath, nullptr); ut_free(cfg_filepath); } char *cfp_filepath = Fil_path::make_cfp(old_filepath); if (cfp_filepath != nullptr) { os_file_delete_if_exists(innodb_data_file_key, cfp_filepath, nullptr); ut_free(cfp_filepath); } return (success); } /** Drop or purge single table tablespace @param[in] space_id tablespace id @param[in] filepath tablespace data file path @retval DB_SUCCESS or error */ dberr_t row_drop_or_purge_single_table_tablespace(space_id_t space_id, const char *filepath) { if (srv_data_file_purge) return row_purge_single_table_tablespace(space_id, filepath); else return row_drop_tablespace(space_id, filepath); }