/************************************************************************ Mysql Enterprise Backup Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. *************************************************************************/ #include "backup_page_tracker.h" #include "backup_comp_constants.h" #if defined _MSC_VER #include #endif #include #include #include #include "m_string.h" #include "my_io.h" #include "mysql/plugin.h" #include "mysqld_error.h" // defined in mysqlbackup component definition extern char *mysqlbackup_backup_id; // page track system variables uchar *Backup_page_tracker::m_changed_pages_buf = nullptr; static std::string changed_pages_file; bool Backup_page_tracker::m_receive_changed_page_data = false; std::list Backup_page_tracker::m_udf_list; bool Backup_page_tracker::backup_id_update() { // stop if any ongoing transfer if (Backup_page_tracker::m_receive_changed_page_data) Backup_page_tracker::m_receive_changed_page_data = false; // delete the existing page tracker file remove(changed_pages_file.c_str()); return true; } /** Make a list of the UDFs exposed by mysqlbackup page_tracking. @return None */ void Backup_page_tracker::initialize_udf_list() { m_udf_list.push_back(new udf_data_t( Backup_comp_constants::udf_set_page_tracking, INT_RESULT, reinterpret_cast(set_page_tracking), reinterpret_cast(set_page_tracking_init), reinterpret_cast(set_page_tracking_deinit))); m_udf_list.push_back(new udf_data_t( Backup_comp_constants::udf_get_start_lsn, INT_RESULT, reinterpret_cast(page_track_get_start_lsn), reinterpret_cast(page_track_get_start_lsn_init), reinterpret_cast(page_track_get_start_lsn_deinit))); m_udf_list.push_back(new udf_data_t( Backup_comp_constants::udf_get_changed_page_count, INT_RESULT, reinterpret_cast(page_track_get_changed_page_count), reinterpret_cast(page_track_get_changed_page_count_init), reinterpret_cast( page_track_get_changed_page_count_deinit))); m_udf_list.push_back(new udf_data_t( Backup_comp_constants::udf_get_changed_pages, INT_RESULT, reinterpret_cast(page_track_get_changed_pages), reinterpret_cast(page_track_get_changed_pages_init), reinterpret_cast(page_track_get_changed_pages_deinit))); } /** Register backup page-track UDFs @return Status of UDF registration @retval 0 success @retval non-zero failure */ mysql_service_status_t Backup_page_tracker::register_udfs() { initialize_udf_list(); std::list success_list; for (auto udf : m_udf_list) { if (mysql_service_udf_registration->udf_register( udf->m_name.c_str(), udf->m_return_type, udf->m_function, udf->m_init_function, udf->m_deinit_function)) { LogErr(ERROR_LEVEL, ER_MYSQLBACKUP_MSG, std::string(udf->m_name + " registration failed.").c_str()); // un-register already successful UDFs unregister_udfs(success_list); return (1); } // add the udf into success list success_list.push_back(udf); } return (0); } /** Un-Register a given list of UDFs @return Status of UDF un-registration @retval 0 success @retval non-zero failure */ mysql_service_status_t Backup_page_tracker::unregister_udfs( std::list list) { int was_present; std::list fail_list; for (auto udf : list) { if (mysql_service_udf_registration->udf_unregister(udf->m_name.c_str(), &was_present) || !was_present) { LogErr(ERROR_LEVEL, ER_MYSQLBACKUP_MSG, std::string(udf->m_name + " un-register failed").c_str()); fail_list.push_back(udf); } delete (udf); } if (!fail_list.empty()) return (1); return (0); } /** Un-Register backup page-track UDFs @return Status of UDF un-registration @retval 0 success @retval non-zero failure */ mysql_service_status_t Backup_page_tracker::unregister_udfs() { return (unregister_udfs(m_udf_list)); } /** Callback function for initialization of UDF "mysqlbackup_page_track_set". @return Status of initialization @retval false on success @retval true on failure */ bool Backup_page_tracker::set_page_tracking_init(UDF_INIT *, UDF_ARGS *, char *) { return (false); } /** Callback method for initialization of UDF "mysqlbackup_page_track_set". @return None */ void Backup_page_tracker::set_page_tracking_deinit( UDF_INIT *initid MY_ATTRIBUTE((unused))) {} /** UDF for "mysqlbackup_page_track_set" See include/mysql/udf_registration_types.h @return lsn at which page-tracking is started/stopped. */ long long Backup_page_tracker::set_page_tracking(UDF_INIT *, UDF_ARGS *args, unsigned char *, unsigned char *) { MYSQL_THD thd; if (mysql_service_mysql_current_thread_reader->get(&thd)) { return (-1); } if (args->arg_count != 1 || args->arg_type[0] != INT_RESULT) { return (-1); } int retval = 0; uint64_t lsn = 0; retval = mysql_service_mysql_page_track->start(thd, PAGE_TRACK_SE_INNODB, &lsn); if (retval) return (-1 * retval); // try stop only if page-track is ongoing if (!(*((long long *)args->args[0])) && (lsn != 0)) { retval = mysql_service_mysql_page_track->stop(thd, PAGE_TRACK_SE_INNODB, &lsn); if (retval) return (-1 * retval); } return lsn; } /** Callback function for initialization of UDF "mysqlbackup_page_track_get_start_lsn" @return Status of initialization @retval false on success @retval true on failure */ bool Backup_page_tracker::page_track_get_start_lsn_init(UDF_INIT *, UDF_ARGS *, char *) { return (false); } /** Callback method for initialization of UDF "mysqlbackup_page_track_get_start_lsn" @return None */ void Backup_page_tracker::page_track_get_start_lsn_deinit( UDF_INIT *initid MY_ATTRIBUTE((unused))) {} /** UDF for "mysqlbackup_page_track_get_start_lsn" See include/mysql/udf_registration_types.h @return lsn at which page-tracking is started/stopped. */ long long Backup_page_tracker::page_track_get_start_lsn(UDF_INIT *, UDF_ARGS *args, unsigned char *, unsigned char *) { MYSQL_THD thd; if (mysql_service_mysql_current_thread_reader->get(&thd)) { return (-1); } if (args->arg_count != 0) { return (-1); } uint64_t first_start_lsn, last_start_lsn; // ignore the return value mysql_service_mysql_page_track->get_status(thd, PAGE_TRACK_SE_INNODB, &first_start_lsn, &last_start_lsn); return first_start_lsn; } /** Callback function for initialization of UDF "mysqlbackup_page_track_get_changed_page_count". @return Status of initialization @retval false on success @retval true on failure */ bool Backup_page_tracker::page_track_get_changed_page_count_init(UDF_INIT *, UDF_ARGS *, char *) { return false; } /** Callback method for initialization of UDF "mysqlbackup_page_track_get_changed_page_count". @return None */ void Backup_page_tracker::page_track_get_changed_page_count_deinit( UDF_INIT *initid MY_ATTRIBUTE((unused))) {} /** UDF for "mysqlbackup_page_track_get_changed_page_count" See include/mysql/udf_registration_types.h @return lsn at which page-tracking is started/stopped. */ long long Backup_page_tracker::page_track_get_changed_page_count( UDF_INIT *, UDF_ARGS *args, unsigned char *, unsigned char *) { MYSQL_THD thd; if (mysql_service_mysql_current_thread_reader->get(&thd)) { return (-1); } if (args->arg_count != 2 || args->arg_type[0] != INT_RESULT || args->arg_type[1] != INT_RESULT) { return (-1); } uint64_t changed_page_count = 0; // get the values form the agrs passed to UDF uint64_t start_lsn = *((long long *)args->args[0]); uint64_t stop_lsn = *((long long *)args->args[1]); int status = mysql_service_mysql_page_track->get_num_page_ids( thd, PAGE_TRACK_SE_INNODB, &start_lsn, &stop_lsn, &changed_page_count); if (status) return (-1 * status); return (changed_page_count); } /** Callback function for initialization of UDF "mysqlbackup_page_track_get_changed_pages". @return Status of initialization @retval false on success @retval true on failure */ bool Backup_page_tracker::page_track_get_changed_pages_init(UDF_INIT *, UDF_ARGS *, char *) { m_changed_pages_buf = (uchar *)malloc(CHANGED_PAGES_BUFFER_SIZE); return false; } /** Callback method for initialization of UDF "mysqlbackup_page_track_get_changed_pages". @return None */ void Backup_page_tracker::page_track_get_changed_pages_deinit( UDF_INIT *initid MY_ATTRIBUTE((unused))) { free(m_changed_pages_buf); } /** UDF for "mysqlbackup_page_track_get_changed_pages" See include/mysql/udf_registration_types.h @returns an int status */ long long Backup_page_tracker::page_track_get_changed_pages(UDF_INIT *, UDF_ARGS *args, unsigned char *, unsigned char *) { MYSQL_THD thd; if (mysql_service_mysql_current_thread_reader->get(&thd)) { return (-1); } if (args->arg_count != 2 || args->arg_type[0] != INT_RESULT || args->arg_type[1] != INT_RESULT) { return (-1); } if (!mysqlbackup_backup_id) { return (-1); } // Not expecting anything other than digits in the backupid. // Make sure no elements of a relative path are there if the // above rule is relaxed std::string backupid = mysqlbackup_backup_id; if (!std::all_of(backupid.begin(), backupid.end(), ::isdigit)) return 1; char mysqlbackup_backupdir[1023]; void *p = &mysqlbackup_backupdir; size_t var_len = 1023; mysql_service_component_sys_variable_register->get_variable( "mysql_server", "datadir", (void **)&p, &var_len); if (var_len == 0) return 2; std::string changed_pages_file_dir = mysqlbackup_backupdir + Backup_comp_constants::backup_scratch_dir; #if defined _MSC_VER _mkdir(changed_pages_file_dir.c_str()); #else if (!mkdir(changed_pages_file_dir.c_str(), 0777)) { // no error if already exists } #endif changed_pages_file = changed_pages_file_dir + FN_LIBCHAR + backupid + Backup_comp_constants::change_file_extension; // if file already exists return error FILE *fd = fopen(changed_pages_file.c_str(), "r"); if (fd) { fclose(fd); return (-1); } // get the values form the agrs passed to UDF uint64_t start_lsn = *((long long *)args->args[0]); uint64_t stop_lsn = *((long long *)args->args[1]); Backup_page_tracker::m_receive_changed_page_data = true; int status = mysql_service_mysql_page_track->get_page_ids( thd, PAGE_TRACK_SE_INNODB, &start_lsn, &stop_lsn, Backup_page_tracker::m_changed_pages_buf, CHANGED_PAGES_BUFFER_SIZE, page_track_callback, nullptr); Backup_page_tracker::m_receive_changed_page_data = false; return (status); } /** Callback method from InnoDB page-tracking to return the changed pages. @param[in] opaque_thd Current thread context. @param[in] buffer Buffer filled with 8 byte page ids. @param[in] buffer_length Total buffer length @param[in] page_count Number of pages in the buffer @param[in] context User data pointer passed to the InnoDB service @return Status if the call-back was successful in handling the data @retval 0 success @retval non-zero failure */ int page_track_callback(MYSQL_THD opaque_thd MY_ATTRIBUTE((unused)), const uchar *buffer, size_t buffer_length MY_ATTRIBUTE((unused)), int page_count, void *context MY_ATTRIBUTE((unused))) { // append to the disk file in binary mode FILE *fd = fopen(changed_pages_file.c_str(), "ab"); if (!fd) { LogEvent() .type(LOG_TYPE_ERROR) .prio(ERROR_LEVEL) .lookup(ER_MYSQLBACKUP_MSG, "[page-track] File open failed."); return (1); } size_t data_size = page_count * Backup_comp_constants::page_number_size; size_t write_count = fwrite(buffer, sizeof(char), data_size, fd); fclose(fd); // write failed if (write_count != data_size) { LogEvent() .type(LOG_TYPE_ERROR) .prio(ERROR_LEVEL) .lookup(ER_MYSQLBACKUP_MSG, "[page-track] Writing to file failed."); return (1); } // on-going backup interrupted, stop receiving the changed page data if (!Backup_page_tracker::m_receive_changed_page_data) return (2); // interupt an on going transfer else return (0); }