summaryrefslogtreecommitdiffstats
path: root/extra/mariabackup/xtrabackup.cc
diff options
context:
space:
mode:
Diffstat (limited to 'extra/mariabackup/xtrabackup.cc')
-rw-r--r--extra/mariabackup/xtrabackup.cc7083
1 files changed, 7083 insertions, 0 deletions
diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc
new file mode 100644
index 00000000..9e359257
--- /dev/null
+++ b/extra/mariabackup/xtrabackup.cc
@@ -0,0 +1,7083 @@
+/******************************************************
+MariaBackup: hot backup tool for InnoDB
+(c) 2009-2017 Percona LLC and/or its affiliates
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+(c) 2017, 2022, MariaDB Corporation.
+Portions written by Marko Mäkelä.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License.
+
+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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+*******************************************************
+
+This file incorporates work covered by the following copyright and
+permission notice:
+
+Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. 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 as published by the Free Software
+Foundation; version 2 of the License.
+
+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 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
+Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+*******************************************************/
+
+//#define XTRABACKUP_TARGET_IS_PLUGIN
+
+#include <my_global.h>
+#include <my_config.h>
+#include <unireg.h>
+#include <mysql_version.h>
+#include <my_base.h>
+#include <my_getopt.h>
+#include <mysql_com.h>
+#include <my_default.h>
+#include <scope.h>
+#include <sql_class.h>
+
+#include <fcntl.h>
+#include <string.h>
+
+#ifdef __linux__
+# include <sys/prctl.h>
+# include <sys/resource.h>
+#endif
+
+#ifdef __APPLE__
+# include "libproc.h"
+#endif
+
+#ifdef __FreeBSD__
+# include <sys/sysctl.h>
+#endif
+
+
+#include <btr0sea.h>
+#include <lock0lock.h>
+#include <log0recv.h>
+#include <log0crypt.h>
+#include <row0mysql.h>
+#include <row0quiesce.h>
+#include <srv0start.h>
+#include "trx0sys.h"
+#include <buf0dblwr.h>
+#include <buf0flu.h>
+#include "ha_innodb.h"
+
+#include <list>
+#include <sstream>
+#include <set>
+#include <fstream>
+#include <mysql.h>
+
+#define G_PTR uchar*
+
+#include "common.h"
+#include "datasink.h"
+
+#include "xb_regex.h"
+#include "fil_cur.h"
+#include "write_filt.h"
+#include "xtrabackup.h"
+#include "ds_buffer.h"
+#include "ds_tmpfile.h"
+#include "xbstream.h"
+#include "changed_page_bitmap.h"
+#include "read_filt.h"
+#include "backup_wsrep.h"
+#include "innobackupex.h"
+#include "backup_mysql.h"
+#include "backup_copy.h"
+#include "backup_mysql.h"
+#include "xb_plugin.h"
+#include <sql_plugin.h>
+#include <srv0srv.h>
+#include <log.h>
+#include <derror.h>
+#include <thr_timer.h>
+#include "backup_debug.h"
+
+#define MB_CORRUPTED_PAGES_FILE "innodb_corrupted_pages"
+
+// disable server's systemd notification code
+extern "C" {
+int sd_notify() { return 0; }
+int sd_notifyf() { return 0; }
+}
+
+int sys_var_init();
+
+/* === xtrabackup specific options === */
+char xtrabackup_real_target_dir[FN_REFLEN] = "./xtrabackup_backupfiles/";
+char *xtrabackup_target_dir= xtrabackup_real_target_dir;
+static my_bool xtrabackup_version;
+static my_bool verbose;
+my_bool xtrabackup_backup;
+my_bool xtrabackup_prepare;
+my_bool xtrabackup_copy_back;
+my_bool xtrabackup_move_back;
+my_bool xtrabackup_decrypt_decompress;
+my_bool xtrabackup_print_param;
+my_bool xtrabackup_mysqld_args;
+my_bool xtrabackup_help;
+
+my_bool xtrabackup_export;
+
+longlong xtrabackup_use_memory;
+
+uint opt_protocol;
+long xtrabackup_throttle; /* 0:unlimited */
+static lint io_ticket;
+static mysql_cond_t wait_throttle;
+static mysql_cond_t log_copying_stop;
+
+char *xtrabackup_incremental;
+lsn_t incremental_lsn;
+lsn_t incremental_to_lsn;
+lsn_t incremental_last_lsn;
+xb_page_bitmap *changed_page_bitmap;
+
+char *xtrabackup_incremental_basedir; /* for --backup */
+char *xtrabackup_extra_lsndir; /* for --backup with --extra-lsndir */
+char *xtrabackup_incremental_dir; /* for --prepare */
+
+char xtrabackup_real_incremental_basedir[FN_REFLEN];
+char xtrabackup_real_extra_lsndir[FN_REFLEN];
+char xtrabackup_real_incremental_dir[FN_REFLEN];
+
+
+char *xtrabackup_tmpdir;
+
+char *xtrabackup_tables;
+char *xtrabackup_tables_file;
+char *xtrabackup_tables_exclude;
+char *xb_rocksdb_datadir;
+my_bool xb_backup_rocksdb = 1;
+
+typedef std::list<regex_t> regex_list_t;
+static regex_list_t regex_include_list;
+static regex_list_t regex_exclude_list;
+
+static hash_table_t tables_include_hash;
+static hash_table_t tables_exclude_hash;
+
+char *xtrabackup_databases = NULL;
+char *xtrabackup_databases_file = NULL;
+char *xtrabackup_databases_exclude = NULL;
+static hash_table_t databases_include_hash;
+static hash_table_t databases_exclude_hash;
+
+static hash_table_t inc_dir_tables_hash;
+
+struct xb_filter_entry_t{
+ char* name;
+ ibool has_tables;
+ xb_filter_entry_t *name_hash;
+};
+
+/** whether log_copying_thread() is active; protected by recv_sys.mutex */
+static bool log_copying_running;
+
+int xtrabackup_parallel;
+
+char *xtrabackup_stream_str = NULL;
+xb_stream_fmt_t xtrabackup_stream_fmt = XB_STREAM_FMT_NONE;
+ibool xtrabackup_stream = FALSE;
+
+const char *xtrabackup_compress_alg = NULL;
+uint xtrabackup_compress = FALSE;
+uint xtrabackup_compress_threads;
+ulonglong xtrabackup_compress_chunk_size = 0;
+
+/* sleep interval beetween log copy iterations in log copying thread
+in milliseconds (default is 1 second) */
+ulint xtrabackup_log_copy_interval = 1000;
+static ulong max_buf_pool_modified_pct;
+
+/* Ignored option (--log) for MySQL option compatibility */
+static char* log_ignored_opt;
+
+
+extern my_bool opt_use_ssl;
+extern char *opt_tls_version;
+my_bool opt_ssl_verify_server_cert;
+my_bool opt_extended_validation;
+my_bool opt_encrypted_backup;
+
+/* === metadata of backup === */
+#define XTRABACKUP_METADATA_FILENAME "xtrabackup_checkpoints"
+char metadata_type[30] = ""; /*[full-backuped|log-applied|incremental]*/
+static lsn_t metadata_from_lsn;
+lsn_t metadata_to_lsn;
+static lsn_t metadata_last_lsn;
+
+static ds_file_t* dst_log_file;
+
+static char mysql_data_home_buff[2];
+
+const char *defaults_group = "mysqld";
+
+/* === static parameters in ha_innodb.cc */
+
+#define HA_INNOBASE_ROWS_IN_TABLE 10000 /* to get optimization right */
+#define HA_INNOBASE_RANGE_COUNT 100
+
+/* The default values for the following, type long or longlong, start-up
+parameters are declared in mysqld.cc: */
+
+long innobase_buffer_pool_awe_mem_mb = 0;
+long innobase_file_io_threads = 4;
+ulong innobase_read_io_threads = 4;
+ulong innobase_write_io_threads = 4;
+
+/** Store the failed read of undo tablespace ids. Protected by
+recv_sys.mutex. */
+static std::set<uint32_t> fail_undo_ids;
+
+longlong innobase_page_size = (1LL << 14); /* 16KB */
+char* innobase_buffer_pool_filename = NULL;
+
+/* The default values for the following char* start-up parameters
+are determined in innobase_init below: */
+
+static char* innobase_ignored_opt;
+char* innobase_data_home_dir;
+char* innobase_data_file_path;
+
+char *aria_log_dir_path;
+
+my_bool xtrabackup_incremental_force_scan = FALSE;
+
+/*
+ * Ignore corrupt pages (disabled by default; used
+ * by "innobackupex" as a command line argument).
+ */
+ulong xtrabackup_innodb_force_recovery = 0;
+
+/* The flushed lsn which is read from data files */
+lsn_t flushed_lsn= 0;
+
+ulong xb_open_files_limit= 0;
+char *xb_plugin_dir;
+char *xb_plugin_load;
+my_bool xb_close_files;
+
+
+class Datasink_free_list
+{
+protected:
+ /*
+ Simple datasink creation tracking...
+ add datasinks in the reverse order you want them destroyed.
+ */
+#define XTRABACKUP_MAX_DATASINKS 10
+ ds_ctxt_t *m_datasinks_to_destroy[XTRABACKUP_MAX_DATASINKS];
+ uint m_actual_datasinks_to_destroy;
+public:
+ Datasink_free_list()
+ :m_actual_datasinks_to_destroy(0)
+ { }
+
+ void add_datasink_to_destroy(ds_ctxt_t *ds)
+ {
+ xb_ad(m_actual_datasinks_to_destroy < XTRABACKUP_MAX_DATASINKS);
+ m_datasinks_to_destroy[m_actual_datasinks_to_destroy] = ds;
+ m_actual_datasinks_to_destroy++;
+ }
+
+ /*
+ Destroy datasinks.
+ Destruction is done in the specific order to not violate their order in the
+ pipeline so that each datasink is able to flush data down the pipeline.
+ */
+ void destroy()
+ {
+ for (uint i= m_actual_datasinks_to_destroy; i > 0; i--)
+ {
+ ds_destroy(m_datasinks_to_destroy[i - 1]);
+ m_datasinks_to_destroy[i - 1] = NULL;
+ }
+ }
+};
+
+
+class Backup_datasinks: public Datasink_free_list
+{
+public:
+ ds_ctxt_t *m_data;
+ ds_ctxt_t *m_meta;
+ ds_ctxt_t *m_redo;
+
+ Backup_datasinks()
+ :m_data(NULL),
+ m_meta(NULL),
+ m_redo(NULL)
+ { }
+ void init();
+ void destroy()
+ {
+ Datasink_free_list::destroy();
+ *this= Backup_datasinks();
+ }
+ bool backup_low();
+};
+
+
+static bool innobackupex_mode = false;
+
+/* String buffer used by --print-param to accumulate server options as they are
+parsed from the defaults file */
+static std::ostringstream print_param_str;
+
+/* Set of specified parameters */
+std::set<std::string> param_set;
+
+static ulonglong global_max_value;
+
+extern "C" sig_handler handle_fatal_signal(int sig);
+extern LOGGER logger;
+
+my_bool opt_galera_info = FALSE;
+my_bool opt_slave_info = FALSE;
+my_bool opt_no_lock = FALSE;
+my_bool opt_safe_slave_backup = FALSE;
+my_bool opt_rsync = FALSE;
+my_bool opt_force_non_empty_dirs = FALSE;
+my_bool opt_noversioncheck = FALSE;
+my_bool opt_no_backup_locks = FALSE;
+my_bool opt_decompress = FALSE;
+my_bool opt_remove_original;
+my_bool opt_log_innodb_page_corruption;
+
+my_bool opt_lock_ddl_per_table = FALSE;
+static my_bool opt_check_privileges;
+
+extern const char *innodb_checksum_algorithm_names[];
+extern TYPELIB innodb_checksum_algorithm_typelib;
+extern const char *innodb_flush_method_names[];
+extern TYPELIB innodb_flush_method_typelib;
+
+static const char *binlog_info_values[] = {"off", "lockless", "on", "auto",
+ NullS};
+static TYPELIB binlog_info_typelib = {array_elements(binlog_info_values)-1, "",
+ binlog_info_values, NULL};
+ulong opt_binlog_info;
+
+char *opt_incremental_history_name;
+char *opt_incremental_history_uuid;
+
+char *opt_user;
+const char *opt_password;
+char *opt_host;
+char *opt_defaults_group;
+char *opt_socket;
+uint opt_port;
+char *opt_log_bin;
+
+const char *query_type_names[] = { "ALL", "UPDATE", "SELECT", NullS};
+
+TYPELIB query_type_typelib= {array_elements(query_type_names) - 1, "",
+ query_type_names, NULL};
+
+ulong opt_lock_wait_query_type;
+ulong opt_kill_long_query_type;
+
+uint opt_kill_long_queries_timeout = 0;
+uint opt_lock_wait_timeout = 0;
+uint opt_lock_wait_threshold = 0;
+uint opt_debug_sleep_before_unlock = 0;
+uint opt_safe_slave_backup_timeout = 0;
+
+const char *opt_history = NULL;
+
+
+char mariabackup_exe[FN_REFLEN];
+char orig_argv1[FN_REFLEN];
+
+pthread_cond_t scanned_lsn_cond;
+
+/** Store the deferred tablespace name during --backup */
+static std::set<std::string> defer_space_names;
+
+typedef std::map<space_id_t,std::string> space_id_to_name_t;
+
+struct ddl_tracker_t {
+ /** Tablspaces with their ID and name, as they were copied to backup.*/
+ space_id_to_name_t tables_in_backup;
+ /** Drop operations found in redo log. */
+ std::set<space_id_t> drops;
+ /* For DDL operation found in redo log, */
+ space_id_to_name_t id_to_name;
+ /** Deferred tablespaces with their ID and name which was
+ found in redo log of DDL operations */
+ space_id_to_name_t deferred_tables;
+
+ /** Insert the deferred tablespace id with the name */
+ void insert_defer_id(space_id_t space_id, std::string name)
+ {
+ auto it= defer_space_names.find(name);
+ if (it != defer_space_names.end())
+ {
+ deferred_tables[space_id]= name;
+ defer_space_names.erase(it);
+ }
+ }
+
+ /** Rename the deferred tablespace with new name */
+ void rename_defer(space_id_t space_id, std::string old_name,
+ std::string new_name)
+ {
+ if (deferred_tables.find(space_id) != deferred_tables.end())
+ deferred_tables[space_id] = new_name;
+ auto defer_end= defer_space_names.end();
+ auto defer= defer_space_names.find(old_name);
+ if (defer == defer_end)
+ defer= defer_space_names.find(new_name);
+
+ if (defer != defer_end)
+ {
+ deferred_tables[space_id]= new_name;
+ defer_space_names.erase(defer);
+ }
+ }
+
+ /** Delete the deferred tablespace */
+ void delete_defer(space_id_t space_id, std::string name)
+ {
+ deferred_tables.erase(space_id);
+ defer_space_names.erase(name);
+ }
+};
+
+static ddl_tracker_t ddl_tracker;
+
+/** Store the space ids of truncated undo log tablespaces. Protected
+by recv_sys.mutex */
+static std::set<uint32_t> undo_trunc_ids;
+
+/** Stores the space ids of page0 INIT_PAGE redo records. It is
+used to indicate whether the given deferred tablespace can
+be reconstructed. */
+static std::set<space_id_t> first_page_init_ids;
+
+// Convert non-null terminated filename to space name
+static std::string filename_to_spacename(const void *filename, size_t len);
+
+CorruptedPages::CorruptedPages() { ut_a(!pthread_mutex_init(&m_mutex, NULL)); }
+
+CorruptedPages::~CorruptedPages() { ut_a(!pthread_mutex_destroy(&m_mutex)); }
+
+void CorruptedPages::add_page_no_lock(const char *space_name,
+ page_id_t page_id,
+ bool convert_space_name)
+{
+ space_info_t &space_info = m_spaces[page_id.space()];
+ if (space_info.space_name.empty())
+ space_info.space_name= convert_space_name
+ ? filename_to_spacename(space_name, strlen(space_name))
+ : space_name;
+ (void)space_info.pages.insert(page_id.page_no());
+}
+
+void CorruptedPages::add_page(const char *file_name, page_id_t page_id)
+{
+ pthread_mutex_lock(&m_mutex);
+ add_page_no_lock(file_name, page_id, true);
+ pthread_mutex_unlock(&m_mutex);
+}
+
+bool CorruptedPages::contains(page_id_t page_id) const
+{
+ bool result = false;
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ container_t::const_iterator space_it= m_spaces.find(page_id.space());
+ if (space_it != m_spaces.end())
+ result = space_it->second.pages.count(page_id.page_no());
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+ return result;
+}
+
+void CorruptedPages::drop_space(uint32_t space_id)
+{
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ m_spaces.erase(space_id);
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+}
+
+void CorruptedPages::rename_space(uint32_t space_id,
+ const std::string &new_name)
+{
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ container_t::iterator space_it = m_spaces.find(space_id);
+ if (space_it != m_spaces.end())
+ space_it->second.space_name = new_name;
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+}
+
+bool CorruptedPages::print_to_file(ds_ctxt *ds_data,
+ const char *filename) const
+{
+ std::ostringstream out;
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ if (!m_spaces.size())
+ {
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+ return true;
+ }
+ for (container_t::const_iterator space_it=
+ m_spaces.begin();
+ space_it != m_spaces.end(); ++space_it)
+ {
+ out << space_it->second.space_name << " " << space_it->first << "\n";
+ bool first_page_no= true;
+ for (std::set<unsigned>::const_iterator page_it=
+ space_it->second.pages.begin();
+ page_it != space_it->second.pages.end(); ++page_it)
+ if (first_page_no)
+ {
+ out << *page_it;
+ first_page_no= false;
+ }
+ else
+ out << " " << *page_it;
+ out << "\n";
+ }
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+ if (ds_data)
+ return ds_data->backup_file_print_buf(filename, out.str().c_str(),
+ static_cast<int>(out.str().size()));
+ std::ofstream outfile;
+ outfile.open(filename);
+ if (!outfile.is_open())
+ die("Can't open %s, error number: %d, error message: %s", filename, errno,
+ strerror(errno));
+ outfile << out.str();
+ return true;
+}
+
+void CorruptedPages::read_from_file(const char *file_name)
+{
+ MY_STAT mystat;
+ if (!my_stat(file_name, &mystat, MYF(0)))
+ return;
+ std::ifstream infile;
+ infile.open(file_name);
+ if (!infile.is_open())
+ die("Can't open %s, error number: %d, error message: %s", file_name, errno,
+ strerror(errno));
+ std::string line;
+ std::string space_name;
+ uint32_t space_id;
+ ulint line_number= 0;
+ while (std::getline(infile, line))
+ {
+ ++line_number;
+ std::istringstream iss(line);
+ if (line_number & 1) {
+ if (!(iss >> space_name))
+ die("Can't parse space name from corrupted pages file at "
+ "line " ULINTPF,
+ line_number);
+ if (!(iss >> space_id))
+ die("Can't parse space id from corrupted pages file at line " ULINTPF,
+ line_number);
+ }
+ else
+ {
+ std::istringstream iss(line);
+ unsigned page_no;
+ while ((iss >> page_no))
+ add_page_no_lock(space_name.c_str(), {space_id, page_no}, false);
+ if (!iss.eof())
+ die("Corrupted pages file parse error on line number " ULINTPF,
+ line_number);
+ }
+ }
+}
+
+bool CorruptedPages::empty() const
+{
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ bool result= !m_spaces.size();
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+ return result;
+}
+
+static void xb_load_single_table_tablespace(const std::string &space_name,
+ bool set_size,
+ uint32_t defer_space_id=0);
+static void xb_data_files_close();
+static fil_space_t* fil_space_get_by_name(const char* name);
+
+void CorruptedPages::zero_out_free_pages()
+{
+ container_t non_free_pages;
+ byte *zero_page=
+ static_cast<byte *>(aligned_malloc(srv_page_size, srv_page_size));
+ memset(zero_page, 0, srv_page_size);
+
+ ut_a(!pthread_mutex_lock(&m_mutex));
+ for (container_t::const_iterator space_it= m_spaces.begin();
+ space_it != m_spaces.end(); ++space_it)
+ {
+ uint32_t space_id = space_it->first;
+ const std::string &space_name = space_it->second.space_name;
+ // There is no need to close tablespaces explixitly as they will be closed
+ // in innodb_shutdown().
+ xb_load_single_table_tablespace(space_name, false);
+ fil_space_t *space = fil_space_t::get(space_id);
+ if (!space)
+ die("Can't find space object for space name %s to check corrupted page",
+ space_name.c_str());
+ for (std::set<unsigned>::const_iterator page_it=
+ space_it->second.pages.begin();
+ page_it != space_it->second.pages.end(); ++page_it)
+ {
+ if (fseg_page_is_allocated(space, *page_it))
+ {
+ space_info_t &space_info = non_free_pages[space_id];
+ space_info.pages.insert(*page_it);
+ if (space_info.space_name.empty())
+ space_info.space_name = space_name;
+ msg("Error: corrupted page " UINT32PF
+ " of tablespace %s can not be fixed",
+ *page_it, space_name.c_str());
+ }
+ else
+ {
+ space->reacquire();
+ auto err= space
+ ->io(IORequest(IORequest::PUNCH_RANGE),
+ *page_it * srv_page_size, srv_page_size, zero_page)
+ .err;
+ if (err != DB_SUCCESS)
+ die("Can't zero out corrupted page " UINT32PF " of tablespace %s",
+ *page_it, space_name.c_str());
+ msg("Corrupted page " UINT32PF
+ " of tablespace %s was successfully fixed.",
+ *page_it, space_name.c_str());
+ }
+ }
+ space->flush<true>();
+ space->release();
+ }
+ m_spaces.swap(non_free_pages);
+ ut_a(!pthread_mutex_unlock(&m_mutex));
+ aligned_free(zero_page);
+}
+
+typedef void (*process_single_tablespace_func_t)(const char *dirname,
+ const char *filname,
+ bool is_remote,
+ bool skip_node_page0,
+ uint32_t defer_space_id);
+static dberr_t enumerate_ibd_files(process_single_tablespace_func_t callback);
+
+/* ======== Datafiles iterator ======== */
+struct datafiles_iter_t {
+ space_list_t::iterator space = fil_system.space_list.end();
+ fil_node_t *node = nullptr;
+ bool started = false;
+ std::mutex mutex;
+};
+
+/* ======== Datafiles iterator ======== */
+static
+fil_node_t *
+datafiles_iter_next(datafiles_iter_t *it)
+{
+ fil_node_t *new_node;
+
+ std::lock_guard<std::mutex> _(it->mutex);
+
+ if (it->node == NULL) {
+ if (it->started)
+ goto end;
+ it->started = TRUE;
+ } else {
+ it->node = UT_LIST_GET_NEXT(chain, it->node);
+ if (it->node != NULL)
+ goto end;
+ }
+
+ it->space = (it->space == fil_system.space_list.end()) ?
+ fil_system.space_list.begin() :
+ std::next(it->space);
+
+ while (it->space != fil_system.space_list.end() &&
+ (it->space->purpose != FIL_TYPE_TABLESPACE ||
+ UT_LIST_GET_LEN(it->space->chain) == 0))
+ ++it->space;
+ if (it->space == fil_system.space_list.end())
+ goto end;
+
+ it->node = UT_LIST_GET_FIRST(it->space->chain);
+
+end:
+ new_node = it->node;
+
+ return new_node;
+}
+
+#ifndef DBUG_OFF
+struct dbug_thread_param_t
+{
+ MYSQL *con;
+ const char *query;
+ int expect_err;
+ int expect_errno;
+};
+
+
+/* Thread procedure used in dbug_start_query_thread. */
+static void *dbug_execute_in_new_connection(void *arg)
+{
+ mysql_thread_init();
+ dbug_thread_param_t *par= static_cast<dbug_thread_param_t*>(arg);
+ int err = mysql_query(par->con, par->query);
+ int err_no = mysql_errno(par->con);
+ if(par->expect_err != err)
+ {
+ msg("FATAL: dbug_execute_in_new_connection : mysql_query '%s' returns %d, instead of expected %d",
+ par->query, err, par->expect_err);
+ _exit(1);
+ }
+ if (err && par->expect_errno && par->expect_errno != err_no)
+ {
+ msg("FATAL: dbug_execute_in_new_connection: mysql_query '%s' returns mysql_errno %d, instead of expected %d",
+ par->query, err_no, par->expect_errno);
+ _exit(1);
+ }
+ mysql_close(par->con);
+ mysql_thread_end();
+ delete par;
+ return nullptr;
+}
+
+static pthread_t dbug_alter_thread;
+
+/*
+Execute query from a new connection, in own thread.
+
+@param query - query to be executed
+@param wait_state - if not NULL, wait until query from new connection
+ reaches this state (value of column State in I_S.PROCESSLIST)
+@param expected_err - if 0, query is supposed to finish successfully,
+ otherwise query should return error.
+@param expected_errno - if not 0, and query finished with error,
+ expected mysql_errno()
+*/
+static void dbug_start_query_thread(
+ const char *query,
+ const char *wait_state,
+ int expected_err,
+ int expected_errno)
+
+{
+ dbug_thread_param_t *par = new dbug_thread_param_t;
+ par->query = query;
+ par->expect_err = expected_err;
+ par->expect_errno = expected_errno;
+ par->con = xb_mysql_connect();
+
+ mysql_thread_create(0, &dbug_alter_thread, nullptr,
+ dbug_execute_in_new_connection, par);
+
+ if (!wait_state)
+ return;
+
+ char q[256];
+ snprintf(q, sizeof(q),
+ "SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST where ID=%lu"
+ " AND Command='Query' AND State='%s'",
+ mysql_thread_id(par->con), wait_state);
+ for (;;) {
+ MYSQL_RES *result = xb_mysql_query(mysql_connection,q, true, true);
+ bool exists = mysql_fetch_row(result) != NULL;
+ mysql_free_result(result);
+ if (exists) {
+ goto end;
+ }
+ msg("Waiting for query '%s' on connection %lu to "
+ " reach state '%s'", query, mysql_thread_id(par->con),
+ wait_state);
+ my_sleep(1000);
+ }
+end:
+ msg("query '%s' on connection %lu reached state '%s'", query,
+ mysql_thread_id(par->con), wait_state);
+}
+#endif
+
+void mdl_lock_all()
+{
+ mdl_lock_init();
+ datafiles_iter_t it;
+
+ while (fil_node_t *node= datafiles_iter_next(&it))
+ {
+ const auto id= node->space->id;
+ if (const char *name= (fil_is_user_tablespace_id(id) &&
+ node->space->chain.start)
+ ? node->space->chain.start->name : nullptr)
+ if (check_if_skip_table(filename_to_spacename(name,
+ strlen(name)).c_str()))
+ continue;
+ mdl_lock_table(id);
+ }
+}
+
+
+// Convert non-null terminated filename to space name
+// Note that in 10.6 the filename may be an undo file name
+static std::string filename_to_spacename(const void *filename, size_t len)
+{
+ char f[FN_REFLEN];
+ char *p= 0, *table, *db;
+ DBUG_ASSERT(len < FN_REFLEN);
+
+ strmake(f, (const char*) filename, len);
+
+#ifdef _WIN32
+ for (size_t i = 0; i < len; i++)
+ {
+ if (f[i] == '\\')
+ f[i] = '/';
+ }
+#endif
+
+ /* Remove extension, if exists */
+ if (!(p= strrchr(f, '.')))
+ goto err;
+ *p= 0;
+
+ /* Find table name */
+ if (!(table= strrchr(f, '/')))
+ goto err;
+ *table = 0;
+
+ /* Find database name */
+ db= strrchr(f, '/');
+ *table = '/';
+ if (!db)
+ goto err;
+ {
+ std::string s(db+1);
+ return s;
+ }
+
+err:
+ /* Not a database/table. Return original (converted) name */
+ if (p)
+ *p= '.'; // Restore removed extension
+ std::string s(f);
+ return s;
+}
+
+/** Report an operation to create, delete, or rename a file during backup.
+@param[in] space_id tablespace identifier
+@param[in] type redo log file operation type
+@param[in] name file name (not NUL-terminated)
+@param[in] len length of name, in bytes
+@param[in] new_name new file name (NULL if not rename)
+@param[in] new_len length of new_name, in bytes (0 if NULL) */
+static void backup_file_op(uint32_t space_id, int type,
+ const byte* name, ulint len,
+ const byte* new_name, ulint new_len)
+{
+
+ ut_ad(name);
+ ut_ad(len);
+ ut_ad(!new_name == !new_len);
+ mysql_mutex_assert_owner(&recv_sys.mutex);
+
+ switch(type) {
+ case FILE_CREATE:
+ {
+ std::string space_name = filename_to_spacename(name, len);
+ ddl_tracker.id_to_name[space_id] = space_name;
+ ddl_tracker.delete_defer(space_id, space_name);
+ msg("DDL tracking : create %u \"%.*s\"", space_id, int(len), name);
+ }
+ break;
+ case FILE_MODIFY:
+ ddl_tracker.insert_defer_id(
+ space_id, filename_to_spacename(name, len));
+ break;
+ case FILE_RENAME:
+ {
+ std::string new_space_name = filename_to_spacename(
+ new_name, new_len);
+ std::string old_space_name = filename_to_spacename(
+ name, len);
+ ddl_tracker.id_to_name[space_id] = new_space_name;
+ ddl_tracker.rename_defer(space_id, old_space_name,
+ new_space_name);
+ msg("DDL tracking : rename %u \"%.*s\",\"%.*s\"",
+ space_id, int(len), name, int(new_len), new_name);
+ }
+ break;
+ case FILE_DELETE:
+ ddl_tracker.drops.insert(space_id);
+ ddl_tracker.delete_defer(
+ space_id, filename_to_spacename(name, len));
+ msg("DDL tracking : delete %u \"%.*s\"", space_id, int(len), name);
+ break;
+ default:
+ ut_ad(0);
+ break;
+ }
+}
+
+
+/*
+ This callback is called if DDL operation is detected,
+ at the end of backup
+
+ Normally, DDL operations are blocked due to FTWRL,
+ but in rare cases of --no-lock, they are not.
+
+ We will abort backup in this case.
+*/
+static void backup_file_op_fail(uint32_t space_id, int type,
+ const byte* name, ulint len,
+ const byte* new_name, ulint new_len)
+{
+ bool fail = false;
+ switch(type) {
+ case FILE_CREATE:
+ msg("DDL tracking : create %u \"%.*s\"", space_id, int(len), name);
+ fail = !check_if_skip_table(
+ filename_to_spacename(name, len).c_str());
+ break;
+ case FILE_MODIFY:
+ break;
+ case FILE_RENAME:
+ msg("DDL tracking : rename %u \"%.*s\",\"%.*s\"",
+ space_id, int(len), name, int(new_len), new_name);
+ fail = !check_if_skip_table(
+ filename_to_spacename(name, len).c_str())
+ || !check_if_skip_table(
+ filename_to_spacename(new_name, new_len).c_str());
+ break;
+ case FILE_DELETE:
+ fail = !check_if_skip_table(
+ filename_to_spacename(name, len).c_str());
+ msg("DDL tracking : delete %u \"%.*s\"", space_id, int(len), name);
+ break;
+ default:
+ ut_ad(0);
+ break;
+ }
+
+ if (fail) {
+ ut_a(opt_no_lock);
+ die("DDL operation detected in the late phase of backup."
+ "Backup is inconsistent. Remove --no-lock option to fix.");
+ }
+}
+
+static void backup_undo_trunc(uint32_t space_id)
+{
+ undo_trunc_ids.insert(space_id);
+}
+
+/* Function to store the space id of page0 INIT_PAGE
+@param space_id space id which has page0 init page */
+static void backup_first_page_op(space_id_t space_id)
+{
+ first_page_init_ids.insert(space_id);
+}
+
+/*
+ Retrieve default data directory, to be used with --copy-back.
+
+ On Windows, default datadir is ..\data, relative to the
+ directory where mariabackup.exe is located(usually "bin")
+
+ Elsewhere, the compiled-in constant MYSQL_DATADIR is used.
+*/
+static char *get_default_datadir() {
+ static char ddir[] = MYSQL_DATADIR;
+#ifdef _WIN32
+ static char buf[MAX_PATH];
+ DWORD size = (DWORD)sizeof(buf) - 1;
+ if (GetModuleFileName(NULL, buf, size) <= size)
+ {
+ char *p;
+ if ((p = strrchr(buf, '\\')))
+ {
+ *p = 0;
+ if ((p = strrchr(buf, '\\')))
+ {
+ strncpy(p + 1, "data", buf + MAX_PATH - p);
+ return buf;
+ }
+ }
+ }
+#endif
+ return ddir;
+}
+
+
+/* ======== Date copying thread context ======== */
+
+typedef struct {
+ datafiles_iter_t *it;
+ uint num;
+ uint *count;
+ pthread_mutex_t* count_mutex;
+ CorruptedPages *corrupted_pages;
+ Backup_datasinks *datasinks;
+} data_thread_ctxt_t;
+
+/* ======== for option and variables ======== */
+#include <../../client/client_priv.h>
+
+enum options_xtrabackup
+{
+ OPT_XTRA_TARGET_DIR= 1000, /* make sure it is larger
+ than OPT_MAX_CLIENT_OPTION */
+ OPT_XTRA_BACKUP,
+ OPT_XTRA_PREPARE,
+ OPT_XTRA_EXPORT,
+ OPT_XTRA_PRINT_PARAM,
+ OPT_XTRA_USE_MEMORY,
+ OPT_XTRA_THROTTLE,
+ OPT_XTRA_LOG_COPY_INTERVAL,
+ OPT_XTRA_INCREMENTAL,
+ OPT_XTRA_INCREMENTAL_BASEDIR,
+ OPT_XTRA_EXTRA_LSNDIR,
+ OPT_XTRA_INCREMENTAL_DIR,
+ OPT_XTRA_TABLES,
+ OPT_XTRA_TABLES_FILE,
+ OPT_XTRA_DATABASES,
+ OPT_XTRA_DATABASES_FILE,
+ OPT_XTRA_PARALLEL,
+ OPT_XTRA_EXTENDED_VALIDATION,
+ OPT_XTRA_ENCRYPTED_BACKUP,
+ OPT_XTRA_STREAM,
+ OPT_XTRA_COMPRESS,
+ OPT_XTRA_COMPRESS_THREADS,
+ OPT_XTRA_COMPRESS_CHUNK_SIZE,
+ OPT_LOG,
+ OPT_INNODB,
+ OPT_INNODB_DATA_FILE_PATH,
+ OPT_INNODB_DATA_HOME_DIR,
+ OPT_INNODB_ADAPTIVE_HASH_INDEX,
+ OPT_INNODB_DOUBLEWRITE,
+ OPT_INNODB_FILE_PER_TABLE,
+ OPT_INNODB_FLUSH_METHOD,
+ OPT_INNODB_LOG_GROUP_HOME_DIR,
+ OPT_INNODB_MAX_DIRTY_PAGES_PCT,
+ OPT_INNODB_MAX_PURGE_LAG,
+ OPT_INNODB_STATUS_FILE,
+ OPT_INNODB_AUTOEXTEND_INCREMENT,
+ OPT_INNODB_BUFFER_POOL_SIZE,
+ OPT_INNODB_COMMIT_CONCURRENCY,
+ OPT_INNODB_CONCURRENCY_TICKETS,
+ OPT_INNODB_FILE_IO_THREADS,
+ OPT_INNODB_IO_CAPACITY,
+ OPT_INNODB_READ_IO_THREADS,
+ OPT_INNODB_WRITE_IO_THREADS,
+ OPT_INNODB_USE_NATIVE_AIO,
+ OPT_INNODB_PAGE_SIZE,
+ OPT_INNODB_BUFFER_POOL_FILENAME,
+ OPT_INNODB_LOCK_WAIT_TIMEOUT,
+ OPT_INNODB_LOG_BUFFER_SIZE,
+#if defined __linux__ || defined _WIN32
+ OPT_INNODB_LOG_FILE_BUFFERING,
+#endif
+ OPT_INNODB_LOG_FILE_SIZE,
+ OPT_INNODB_OPEN_FILES,
+ OPT_XTRA_DEBUG_SYNC,
+ OPT_INNODB_CHECKSUM_ALGORITHM,
+ OPT_INNODB_UNDO_DIRECTORY,
+ OPT_INNODB_UNDO_TABLESPACES,
+ OPT_XTRA_INCREMENTAL_FORCE_SCAN,
+ OPT_DEFAULTS_GROUP,
+ OPT_CLOSE_FILES,
+ OPT_CORE_FILE,
+
+ OPT_COPY_BACK,
+ OPT_MOVE_BACK,
+ OPT_GALERA_INFO,
+ OPT_SLAVE_INFO,
+ OPT_NO_LOCK,
+ OPT_SAFE_SLAVE_BACKUP,
+ OPT_RSYNC,
+ OPT_FORCE_NON_EMPTY_DIRS,
+ OPT_NO_VERSION_CHECK,
+ OPT_NO_BACKUP_LOCKS,
+ OPT_DECOMPRESS,
+ OPT_INCREMENTAL_HISTORY_NAME,
+ OPT_INCREMENTAL_HISTORY_UUID,
+ OPT_REMOVE_ORIGINAL,
+ OPT_LOCK_WAIT_QUERY_TYPE,
+ OPT_KILL_LONG_QUERY_TYPE,
+ OPT_HISTORY,
+ OPT_KILL_LONG_QUERIES_TIMEOUT,
+ OPT_LOCK_WAIT_TIMEOUT,
+ OPT_LOCK_WAIT_THRESHOLD,
+ OPT_DEBUG_SLEEP_BEFORE_UNLOCK,
+ OPT_SAFE_SLAVE_BACKUP_TIMEOUT,
+ OPT_BINLOG_INFO,
+ OPT_XB_SECURE_AUTH,
+
+ OPT_XTRA_TABLES_EXCLUDE,
+ OPT_XTRA_DATABASES_EXCLUDE,
+ OPT_PROTOCOL,
+ OPT_INNODB_COMPRESSION_LEVEL,
+ OPT_LOCK_DDL_PER_TABLE,
+ OPT_ROCKSDB_DATADIR,
+ OPT_BACKUP_ROCKSDB,
+ OPT_XTRA_CHECK_PRIVILEGES,
+ OPT_XTRA_MYSQLD_ARGS,
+ OPT_XB_IGNORE_INNODB_PAGE_CORRUPTION,
+ OPT_INNODB_FORCE_RECOVERY,
+ OPT_ARIA_LOG_DIR_PATH
+};
+
+struct my_option xb_client_options[]= {
+ {"verbose", 'V', "display verbose output", (G_PTR *) &verbose,
+ (G_PTR *) &verbose, 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
+ {"version", 'v', "print version information",
+ (G_PTR *) &xtrabackup_version, (G_PTR *) &xtrabackup_version, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"target-dir", OPT_XTRA_TARGET_DIR, "destination directory",
+ (G_PTR *) &xtrabackup_target_dir, (G_PTR *) &xtrabackup_target_dir, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"backup", OPT_XTRA_BACKUP, "take backup to target-dir",
+ (G_PTR *) &xtrabackup_backup, (G_PTR *) &xtrabackup_backup, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"prepare", OPT_XTRA_PREPARE,
+ "prepare a backup for starting mysql server on the backup.",
+ (G_PTR *) &xtrabackup_prepare, (G_PTR *) &xtrabackup_prepare, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"export", OPT_XTRA_EXPORT,
+ "create files to import to another database when prepare.",
+ (G_PTR *) &xtrabackup_export, (G_PTR *) &xtrabackup_export, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"print-param", OPT_XTRA_PRINT_PARAM,
+ "print parameter of mysqld needed for copyback.",
+ (G_PTR *) &xtrabackup_print_param, (G_PTR *) &xtrabackup_print_param, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"use-memory", OPT_XTRA_USE_MEMORY,
+ "The value is used in place of innodb_buffer_pool_size. "
+ "This option is only relevant when the --prepare option is specified.",
+ (G_PTR *) &xtrabackup_use_memory, (G_PTR *) &xtrabackup_use_memory, 0,
+ GET_LL, REQUIRED_ARG, 100 * 1024 * 1024L, 1024 * 1024L, LONGLONG_MAX, 0,
+ 1024 * 1024L, 0},
+ {"throttle", OPT_XTRA_THROTTLE,
+ "limit count of IO operations (pairs of read&write) per second to IOS "
+ "values (for '--backup')",
+ (G_PTR *) &xtrabackup_throttle, (G_PTR *) &xtrabackup_throttle, 0,
+ GET_LONG, REQUIRED_ARG, 0, 0, LONG_MAX, 0, 1, 0},
+ {"log", OPT_LOG, "Ignored option for MySQL option compatibility",
+ (G_PTR *) &log_ignored_opt, (G_PTR *) &log_ignored_opt, 0, GET_STR,
+ OPT_ARG, 0, 0, 0, 0, 0, 0},
+ {"log-copy-interval", OPT_XTRA_LOG_COPY_INTERVAL,
+ "time interval between checks done by log copying thread in milliseconds "
+ "(default is 1 second).",
+ (G_PTR *) &xtrabackup_log_copy_interval,
+ (G_PTR *) &xtrabackup_log_copy_interval, 0, GET_LONG, REQUIRED_ARG, 1000,
+ 0, LONG_MAX, 0, 1, 0},
+ {"extra-lsndir", OPT_XTRA_EXTRA_LSNDIR,
+ "(for --backup): save an extra copy of the xtrabackup_checkpoints file "
+ "in this directory.",
+ (G_PTR *) &xtrabackup_extra_lsndir, (G_PTR *) &xtrabackup_extra_lsndir, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"incremental-lsn", OPT_XTRA_INCREMENTAL,
+ "(for --backup): copy only .ibd pages newer than specified LSN "
+ "'high:low'. ##ATTENTION##: If a wrong LSN value is specified, it is "
+ "impossible to diagnose this, causing the backup to be unusable. Be "
+ "careful!",
+ (G_PTR *) &xtrabackup_incremental, (G_PTR *) &xtrabackup_incremental, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"incremental-basedir", OPT_XTRA_INCREMENTAL_BASEDIR,
+ "(for --backup): copy only .ibd pages newer than backup at specified "
+ "directory.",
+ (G_PTR *) &xtrabackup_incremental_basedir,
+ (G_PTR *) &xtrabackup_incremental_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+ {"incremental-dir", OPT_XTRA_INCREMENTAL_DIR,
+ "(for --prepare): apply .delta files and logfile in the specified "
+ "directory.",
+ (G_PTR *) &xtrabackup_incremental_dir,
+ (G_PTR *) &xtrabackup_incremental_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0,
+ 0, 0, 0},
+ {"tables", OPT_XTRA_TABLES, "filtering by regexp for table names.",
+ (G_PTR *) &xtrabackup_tables, (G_PTR *) &xtrabackup_tables, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"tables_file", OPT_XTRA_TABLES_FILE,
+ "filtering by list of the exact database.table name in the file.",
+ (G_PTR *) &xtrabackup_tables_file, (G_PTR *) &xtrabackup_tables_file, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"databases", OPT_XTRA_DATABASES, "filtering by list of databases.",
+ (G_PTR *) &xtrabackup_databases, (G_PTR *) &xtrabackup_databases, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"databases_file", OPT_XTRA_DATABASES_FILE,
+ "filtering by list of databases in the file.",
+ (G_PTR *) &xtrabackup_databases_file,
+ (G_PTR *) &xtrabackup_databases_file, 0, GET_STR, REQUIRED_ARG, 0, 0, 0,
+ 0, 0, 0},
+ {"tables-exclude", OPT_XTRA_TABLES_EXCLUDE,
+ "filtering by regexp for table names. "
+ "Operates the same way as --tables, but matched names are excluded from "
+ "backup. "
+ "Note that this option has a higher priority than --tables.",
+ (G_PTR *) &xtrabackup_tables_exclude,
+ (G_PTR *) &xtrabackup_tables_exclude, 0, GET_STR, REQUIRED_ARG, 0, 0, 0,
+ 0, 0, 0},
+ {"databases-exclude", OPT_XTRA_DATABASES_EXCLUDE,
+ "Excluding databases based on name, "
+ "Operates the same way as --databases, but matched names are excluded "
+ "from backup. "
+ "Note that this option has a higher priority than --databases.",
+ (G_PTR *) &xtrabackup_databases_exclude,
+ (G_PTR *) &xtrabackup_databases_exclude, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"stream", OPT_XTRA_STREAM,
+ "Stream all backup files to the standard output "
+ "in the specified format."
+ "Supported format is 'mbstream' or 'xbstream'.",
+ (G_PTR *) &xtrabackup_stream_str, (G_PTR *) &xtrabackup_stream_str, 0,
+ GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"compress", OPT_XTRA_COMPRESS,
+ "Compress individual backup files using the "
+ "specified compression algorithm. Currently the only supported algorithm "
+ "is 'quicklz'. It is also the default algorithm, i.e. the one used when "
+ "--compress is used without an argument.",
+ (G_PTR *) &xtrabackup_compress_alg, (G_PTR *) &xtrabackup_compress_alg, 0,
+ GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"compress-threads", OPT_XTRA_COMPRESS_THREADS,
+ "Number of threads for parallel data compression. The default value is "
+ "1.",
+ (G_PTR *) &xtrabackup_compress_threads,
+ (G_PTR *) &xtrabackup_compress_threads, 0, GET_UINT, REQUIRED_ARG, 1, 1,
+ UINT_MAX, 0, 0, 0},
+
+ {"compress-chunk-size", OPT_XTRA_COMPRESS_CHUNK_SIZE,
+ "Size of working buffer(s) for compression threads in bytes. The default "
+ "value is 64K.",
+ (G_PTR *) &xtrabackup_compress_chunk_size,
+ (G_PTR *) &xtrabackup_compress_chunk_size, 0, GET_ULL, REQUIRED_ARG,
+ (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0},
+
+ {"incremental-force-scan", OPT_XTRA_INCREMENTAL_FORCE_SCAN,
+ "Perform a full-scan incremental backup even in the presence of changed "
+ "page bitmap data",
+ (G_PTR *) &xtrabackup_incremental_force_scan,
+ (G_PTR *) &xtrabackup_incremental_force_scan, 0, GET_BOOL, NO_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"close_files", OPT_CLOSE_FILES,
+ "do not keep files opened. Use at your own "
+ "risk.",
+ (G_PTR *) &xb_close_files, (G_PTR *) &xb_close_files, 0, GET_BOOL, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"core-file", OPT_CORE_FILE, "Write core on fatal signals", 0, 0, 0,
+ GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"copy-back", OPT_COPY_BACK,
+ "Copy all the files in a previously made "
+ "backup from the backup directory to their original locations.",
+ (uchar *) &xtrabackup_copy_back, (uchar *) &xtrabackup_copy_back, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"move-back", OPT_MOVE_BACK,
+ "Move all the files in a previously made "
+ "backup from the backup directory to the actual datadir location. "
+ "Use with caution, as it removes backup files.",
+ (uchar *) &xtrabackup_move_back, (uchar *) &xtrabackup_move_back, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"galera-info", OPT_GALERA_INFO,
+ "This options creates the "
+ "xtrabackup_galera_info file which contains the local node state at "
+ "the time of the backup. Option should be used when performing the "
+ "backup of MariaDB Galera Cluster. Has no effect when backup locks "
+ "are used to create the backup.",
+ (uchar *) &opt_galera_info, (uchar *) &opt_galera_info, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"slave-info", OPT_SLAVE_INFO,
+ "This option is useful when backing "
+ "up a replication slave server. It prints the binary log position "
+ "and name of the master server. It also writes this information to "
+ "the \"xtrabackup_slave_info\" file as a \"CHANGE MASTER\" command. "
+ "A new slave for this master can be set up by starting a slave server "
+ "on this backup and issuing a \"CHANGE MASTER\" command with the "
+ "binary log position saved in the \"xtrabackup_slave_info\" file.",
+ (uchar *) &opt_slave_info, (uchar *) &opt_slave_info, 0, GET_BOOL, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"no-lock", OPT_NO_LOCK,
+ "Use this option to disable table lock "
+ "with \"FLUSH TABLES WITH READ LOCK\". Use it only if ALL your "
+ "tables are InnoDB and you DO NOT CARE about the binary log "
+ "position of the backup. This option shouldn't be used if there "
+ "are any DDL statements being executed or if any updates are "
+ "happening on non-InnoDB tables (this includes the system MyISAM "
+ "tables in the mysql database), otherwise it could lead to an "
+ "inconsistent backup. If you are considering to use --no-lock "
+ "because your backups are failing to acquire the lock, this could "
+ "be because of incoming replication events preventing the lock "
+ "from succeeding. Please try using --safe-slave-backup to "
+ "momentarily stop the replication slave thread, this may help "
+ "the backup to succeed and you then don't need to resort to "
+ "using this option.",
+ (uchar *) &opt_no_lock, (uchar *) &opt_no_lock, 0, GET_BOOL, NO_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"safe-slave-backup", OPT_SAFE_SLAVE_BACKUP,
+ "Stop slave SQL thread "
+ "and wait to start backup until Slave_open_temp_tables in "
+ "\"SHOW STATUS\" is zero. If there are no open temporary tables, "
+ "the backup will take place, otherwise the SQL thread will be "
+ "started and stopped until there are no open temporary tables. "
+ "The backup will fail if Slave_open_temp_tables does not become "
+ "zero after --safe-slave-backup-timeout seconds. The slave SQL "
+ "thread will be restarted when the backup finishes.",
+ (uchar *) &opt_safe_slave_backup, (uchar *) &opt_safe_slave_backup, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"rsync", OPT_RSYNC,
+ "Uses the rsync utility to optimize local file "
+ "transfers. When this option is specified, " XB_TOOL_NAME " uses rsync "
+ "to copy all non-InnoDB files instead of spawning a separate cp for "
+ "each file, which can be much faster for servers with a large number "
+ "of databases or tables. This option cannot be used together with "
+ "--stream.",
+ (uchar *) &opt_rsync, (uchar *) &opt_rsync, 0, GET_BOOL, NO_ARG, 0, 0, 0,
+ 0, 0, 0},
+
+ {"force-non-empty-directories", OPT_FORCE_NON_EMPTY_DIRS,
+ "This "
+ "option, when specified, makes --copy-back or --move-back transfer "
+ "files to non-empty directories. Note that no existing files will be "
+ "overwritten. If --copy-back or --move-back has to copy a file from "
+ "the backup directory which already exists in the destination "
+ "directory, it will still fail with an error.",
+ (uchar *) &opt_force_non_empty_dirs, (uchar *) &opt_force_non_empty_dirs,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"no-version-check", OPT_NO_VERSION_CHECK,
+ "This option disables the "
+ "version check which is enabled by the --version-check option.",
+ (uchar *) &opt_noversioncheck, (uchar *) &opt_noversioncheck, 0, GET_BOOL,
+ NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"no-backup-locks", OPT_NO_BACKUP_LOCKS,
+ "This option controls if "
+ "backup locks should be used instead of FLUSH TABLES WITH READ LOCK "
+ "on the backup stage. The option has no effect when backup locks are "
+ "not supported by the server. This option is enabled by default, "
+ "disable with --no-backup-locks.",
+ (uchar *) &opt_no_backup_locks, (uchar *) &opt_no_backup_locks, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"decompress", OPT_DECOMPRESS,
+ "Decompresses all files with the .qp "
+ "extension in a backup previously made with the --compress option.",
+ (uchar *) &opt_decompress, (uchar *) &opt_decompress, 0, GET_BOOL, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"user", 'u',
+ "This option specifies the MySQL username used "
+ "when connecting to the server, if that's not the current user. "
+ "The option accepts a string argument. See mysql --help for details.",
+ (uchar *) &opt_user, (uchar *) &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"host", 'H',
+ "This option specifies the host to use when "
+ "connecting to the database server with TCP/IP. The option accepts "
+ "a string argument. See mysql --help for details.",
+ (uchar *) &opt_host, (uchar *) &opt_host, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"port", 'P',
+ "This option specifies the port to use when "
+ "connecting to the database server with TCP/IP. The option accepts "
+ "a string argument. See mysql --help for details.",
+ &opt_port, &opt_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"password", 'p',
+ "This option specifies the password to use "
+ "when connecting to the database. It accepts a string argument. "
+ "See mysql --help for details.",
+ 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"protocol", OPT_PROTOCOL,
+ "The protocol to use for connection (tcp, socket, pipe, memory).", 0, 0,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"socket", 'S',
+ "This option specifies the socket to use when "
+ "connecting to the local database server with a UNIX domain socket. "
+ "The option accepts a string argument. See mysql --help for details.",
+ (uchar *) &opt_socket, (uchar *) &opt_socket, 0, GET_STR, REQUIRED_ARG, 0,
+ 0, 0, 0, 0, 0},
+
+ {"incremental-history-name", OPT_INCREMENTAL_HISTORY_NAME,
+ "This option specifies the name of the backup series stored in the "
+ XB_HISTORY_TABLE " history record to base an "
+ "incremental backup on. Xtrabackup will search the history table "
+ "looking for the most recent (highest innodb_to_lsn), successful "
+ "backup in the series and take the to_lsn value to use as the "
+ "starting lsn for the incremental backup. This will be mutually "
+ "exclusive with --incremental-history-uuid, --incremental-basedir "
+ "and --incremental-lsn. If no valid lsn can be found (no series by "
+ "that name, no successful backups by that name), an error will be returned."
+ " It is used with the --incremental option.",
+ (uchar *) &opt_incremental_history_name,
+ (uchar *) &opt_incremental_history_name, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"incremental-history-uuid", OPT_INCREMENTAL_HISTORY_UUID,
+ "This option specifies the UUID of the specific history record "
+ "stored in the " XB_HISTORY_TABLE " table to base an "
+ "incremental backup on. --incremental-history-name, "
+ "--incremental-basedir and --incremental-lsn. If no valid lsn can be "
+ "found (no success record with that uuid), an error will be returned."
+ " It is used with the --incremental option.",
+ (uchar *) &opt_incremental_history_uuid,
+ (uchar *) &opt_incremental_history_uuid, 0, GET_STR, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"remove-original", OPT_REMOVE_ORIGINAL,
+ "Remove .qp files after decompression.", (uchar *) &opt_remove_original,
+ (uchar *) &opt_remove_original, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"ftwrl-wait-query-type", OPT_LOCK_WAIT_QUERY_TYPE,
+ "This option specifies which types of queries are allowed to complete "
+ "before " XB_TOOL_NAME " will issue the global lock. Default is all.",
+ (uchar *) &opt_lock_wait_query_type, (uchar *) &opt_lock_wait_query_type,
+ &query_type_typelib, GET_ENUM, REQUIRED_ARG, QUERY_TYPE_ALL, 0, 0, 0, 0,
+ 0},
+
+ {"kill-long-query-type", OPT_KILL_LONG_QUERY_TYPE,
+ "This option specifies which types of queries should be killed to "
+ "unblock the global lock. Default is \"all\".",
+ (uchar *) &opt_kill_long_query_type, (uchar *) &opt_kill_long_query_type,
+ &query_type_typelib, GET_ENUM, REQUIRED_ARG, QUERY_TYPE_SELECT, 0, 0, 0,
+ 0, 0},
+
+ {"history", OPT_HISTORY,
+ "This option enables the tracking of backup history in the "
+ XB_HISTORY_TABLE " table. An optional history "
+ "series name may be specified that will be placed with the history "
+ "record for the current backup being taken.",
+ NULL, NULL, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"kill-long-queries-timeout", OPT_KILL_LONG_QUERIES_TIMEOUT,
+ "This option specifies the number of seconds " XB_TOOL_NAME " waits "
+ "between starting FLUSH TABLES WITH READ LOCK and killing those "
+ "queries that block it. Default is 0 seconds, which means "
+ XB_TOOL_NAME " will not attempt to kill any queries.",
+ (uchar *) &opt_kill_long_queries_timeout,
+ (uchar *) &opt_kill_long_queries_timeout, 0, GET_UINT, REQUIRED_ARG, 0, 0,
+ 0, 0, 0, 0},
+
+ {"ftwrl-wait-timeout", OPT_LOCK_WAIT_TIMEOUT,
+ "This option specifies time in seconds that " XB_TOOL_NAME " should wait "
+ "for queries that would block FTWRL before running it. If there are "
+ "still such queries when the timeout expires, " XB_TOOL_NAME " terminates "
+ "with an error. Default is 0, in which case " XB_TOOL_NAME " does not "
+ "wait for queries to complete and starts FTWRL immediately.",
+ (uchar *) &opt_lock_wait_timeout, (uchar *) &opt_lock_wait_timeout, 0,
+ GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"ftwrl-wait-threshold", OPT_LOCK_WAIT_THRESHOLD,
+ "This option specifies the query run time threshold which is used by "
+ XB_TOOL_NAME " to detect long-running queries with a non-zero value "
+ "of --ftwrl-wait-timeout. FTWRL is not started until such "
+ "long-running queries exist. This option has no effect if "
+ "--ftwrl-wait-timeout is 0. Default value is 60 seconds.",
+ (uchar *) &opt_lock_wait_threshold, (uchar *) &opt_lock_wait_threshold, 0,
+ GET_UINT, REQUIRED_ARG, 60, 0, 0, 0, 0, 0},
+
+
+ {"safe-slave-backup-timeout", OPT_SAFE_SLAVE_BACKUP_TIMEOUT,
+ "How many seconds --safe-slave-backup should wait for "
+ "Slave_open_temp_tables to become zero. (default 300)",
+ (uchar *) &opt_safe_slave_backup_timeout,
+ (uchar *) &opt_safe_slave_backup_timeout, 0, GET_UINT, REQUIRED_ARG, 300,
+ 0, 0, 0, 0, 0},
+
+ {"binlog-info", OPT_BINLOG_INFO,
+ "This option controls how backup should retrieve server's binary log "
+ "coordinates corresponding to the backup. Possible values are OFF, ON, "
+ "LOCKLESS and AUTO.",
+ &opt_binlog_info, &opt_binlog_info, &binlog_info_typelib, GET_ENUM,
+ OPT_ARG, BINLOG_INFO_AUTO, 0, 0, 0, 0, 0},
+
+ {"secure-auth", OPT_XB_SECURE_AUTH,
+ "Refuse client connecting to server if it"
+ " uses old (pre-4.1.1) protocol.",
+ &opt_secure_auth, &opt_secure_auth, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0,
+ 0},
+
+ {"log-innodb-page-corruption", OPT_XB_IGNORE_INNODB_PAGE_CORRUPTION,
+ "Continue backup if innodb corrupted pages are found. The pages are "
+ "logged in " MB_CORRUPTED_PAGES_FILE
+ " and backup is finished with error. "
+ "--prepare will try to fix corrupted pages. If " MB_CORRUPTED_PAGES_FILE
+ " exists after --prepare in base backup directory, backup still contains "
+ "corrupted pages and can not be considered as consistent.",
+ &opt_log_innodb_page_corruption, &opt_log_innodb_page_corruption, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+#define MYSQL_CLIENT
+#include "sslopt-longopts.h"
+#undef MYSQL_CLIENT
+ {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}};
+
+uint xb_client_options_count = array_elements(xb_client_options);
+
+#ifndef DBUG_OFF
+/** Parameters to DBUG */
+static const char *dbug_option;
+#endif
+
+#ifdef HAVE_URING
+extern const char *io_uring_may_be_unsafe;
+bool innodb_use_native_aio_default();
+#endif
+
+struct my_option xb_server_options[] =
+{
+ {"datadir", 'h', "Path to the database root.", (G_PTR*) &mysql_data_home,
+ (G_PTR*) &mysql_data_home, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"tmpdir", 't',
+ "Path for temporary files. Several paths may be specified, separated by a "
+#if defined(_WIN32)
+ "semicolon (;)"
+#else
+ "colon (:)"
+#endif
+ ", in this case they are used in a round-robin fashion.",
+ (G_PTR*) &opt_mysql_tmpdir,
+ (G_PTR*) &opt_mysql_tmpdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"parallel", OPT_XTRA_PARALLEL,
+ "Number of threads to use for parallel datafiles transfer. "
+ "The default value is 1.",
+ (G_PTR*) &xtrabackup_parallel, (G_PTR*) &xtrabackup_parallel, 0, GET_INT,
+ REQUIRED_ARG, 1, 1, INT_MAX, 0, 0, 0},
+
+ {"extended_validation", OPT_XTRA_EXTENDED_VALIDATION,
+ "Enable extended validation for Innodb data pages during backup phase. "
+ "Will slow down backup considerably, in case encryption is used. "
+ "May fail if tables are created during the backup.",
+ (G_PTR*)&opt_extended_validation,
+ (G_PTR*)&opt_extended_validation,
+ 0, GET_BOOL, NO_ARG, FALSE, 0, 0, 0, 0, 0},
+
+ {"encrypted_backup", OPT_XTRA_ENCRYPTED_BACKUP,
+ "In --backup, assume that nonzero key_version implies that the page"
+ " is encrypted. Use --backup --skip-encrypted-backup to allow"
+ " copying unencrypted that were originally created before MySQL 5.1.48.",
+ (G_PTR*)&opt_encrypted_backup,
+ (G_PTR*)&opt_encrypted_backup,
+ 0, GET_BOOL, NO_ARG, TRUE, 0, 0, 0, 0, 0},
+
+ {"log", OPT_LOG, "Ignored option for MySQL option compatibility",
+ (G_PTR*) &log_ignored_opt, (G_PTR*) &log_ignored_opt, 0,
+ GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"log_bin", OPT_LOG, "Base name for the log sequence",
+ &opt_log_bin, &opt_log_bin, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"innodb", OPT_INNODB, "Ignored option for MySQL option compatibility",
+ (G_PTR*) &innobase_ignored_opt, (G_PTR*) &innobase_ignored_opt, 0,
+ GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+#ifdef BTR_CUR_HASH_ADAPT
+ {"innodb_adaptive_hash_index", OPT_INNODB_ADAPTIVE_HASH_INDEX,
+ "Enable InnoDB adaptive hash index (disabled by default).",
+ &btr_search_enabled,
+ &btr_search_enabled,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+#endif /* BTR_CUR_HASH_ADAPT */
+ {"innodb_autoextend_increment", OPT_INNODB_AUTOEXTEND_INCREMENT,
+ "Data file autoextend increment in megabytes",
+ (G_PTR*) &sys_tablespace_auto_extend_increment,
+ (G_PTR*) &sys_tablespace_auto_extend_increment,
+ 0, GET_UINT, REQUIRED_ARG, 8, 1, 1000, 0, 1, 0},
+ {"innodb_data_file_path", OPT_INNODB_DATA_FILE_PATH,
+ "Path to individual files and their sizes.", &innobase_data_file_path,
+ &innobase_data_file_path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"innodb_data_home_dir", OPT_INNODB_DATA_HOME_DIR,
+ "The common part for InnoDB table spaces.", &innobase_data_home_dir,
+ &innobase_data_home_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"innodb_doublewrite", OPT_INNODB_DOUBLEWRITE,
+ "Enable InnoDB doublewrite buffer during --prepare.",
+ (G_PTR*) &srv_use_doublewrite_buf,
+ (G_PTR*) &srv_use_doublewrite_buf, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"innodb_io_capacity", OPT_INNODB_IO_CAPACITY,
+ "Number of IOPs the server can do. Tunes the background IO rate",
+ (G_PTR*) &srv_io_capacity, (G_PTR*) &srv_io_capacity,
+ 0, GET_ULONG, OPT_ARG, 200, 100, ~0UL, 0, 0, 0},
+ {"innodb_file_io_threads", OPT_INNODB_FILE_IO_THREADS,
+ "Number of file I/O threads in InnoDB.", (G_PTR*) &innobase_file_io_threads,
+ (G_PTR*) &innobase_file_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 4, 64, 0,
+ 1, 0},
+ {"innodb_read_io_threads", OPT_INNODB_READ_IO_THREADS,
+ "Number of background read I/O threads in InnoDB.", (G_PTR*) &innobase_read_io_threads,
+ (G_PTR*) &innobase_read_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 1, 64, 0,
+ 1, 0},
+ {"innodb_write_io_threads", OPT_INNODB_WRITE_IO_THREADS,
+ "Number of background write I/O threads in InnoDB.", (G_PTR*) &innobase_write_io_threads,
+ (G_PTR*) &innobase_write_io_threads, 0, GET_LONG, REQUIRED_ARG, 4, 1, 64, 0,
+ 1, 0},
+ {"innodb_file_per_table", OPT_INNODB_FILE_PER_TABLE,
+ "Stores each InnoDB table to an .ibd file in the database dir.",
+ (G_PTR*) &srv_file_per_table,
+ (G_PTR*) &srv_file_per_table, 0, GET_BOOL, NO_ARG,
+ FALSE, 0, 0, 0, 0, 0},
+
+ {"innodb_flush_method", OPT_INNODB_FLUSH_METHOD,
+ "With which method to flush data.",
+ &srv_file_flush_method, &srv_file_flush_method,
+ &innodb_flush_method_typelib, GET_ENUM, REQUIRED_ARG,
+ IF_WIN(SRV_ALL_O_DIRECT_FSYNC, SRV_O_DIRECT), 0, 0, 0, 0, 0},
+
+ {"innodb_log_buffer_size", OPT_INNODB_LOG_BUFFER_SIZE,
+ "Redo log buffer size in bytes.",
+ (G_PTR*) &log_sys.buf_size, (G_PTR*) &log_sys.buf_size, 0,
+ IF_WIN(GET_ULL,GET_ULONG), REQUIRED_ARG, 2U << 20,
+ 2U << 20, SIZE_T_MAX, 0, 4096, 0},
+#if defined __linux__ || defined _WIN32
+ {"innodb_log_file_buffering", OPT_INNODB_LOG_FILE_BUFFERING,
+ "Whether the file system cache for ib_logfile0 is enabled during --backup",
+ (G_PTR*) &log_sys.log_buffered,
+ (G_PTR*) &log_sys.log_buffered, 0, GET_BOOL, NO_ARG,
+ TRUE, 0, 0, 0, 0, 0},
+#endif
+ {"innodb_log_file_size", OPT_INNODB_LOG_FILE_SIZE,
+ "Ignored for mysqld option compatibility",
+ (G_PTR*) &srv_log_file_size, (G_PTR*) &srv_log_file_size, 0,
+ GET_ULL, REQUIRED_ARG, 96 << 20, 4 << 20,
+ std::numeric_limits<ulonglong>::max(), 0, 4096, 0},
+ {"innodb_log_group_home_dir", OPT_INNODB_LOG_GROUP_HOME_DIR,
+ "Path to InnoDB log files.", &srv_log_group_home_dir,
+ &srv_log_group_home_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"innodb_max_dirty_pages_pct", OPT_INNODB_MAX_DIRTY_PAGES_PCT,
+ "Percentage of dirty pages allowed in bufferpool.", (G_PTR*) &srv_max_buf_pool_modified_pct,
+ (G_PTR*) &srv_max_buf_pool_modified_pct, 0, GET_ULONG, REQUIRED_ARG, 90, 0, 100, 0, 0, 0},
+ {"innodb_use_native_aio", OPT_INNODB_USE_NATIVE_AIO,
+ "Use native AIO if supported on this platform.",
+ (G_PTR*) &srv_use_native_aio,
+ (G_PTR*) &srv_use_native_aio, 0, GET_BOOL, NO_ARG,
+#ifdef HAVE_URING
+ innodb_use_native_aio_default(),
+#else
+ TRUE,
+#endif
+ 0, 0, 0, 0, 0},
+ {"innodb_page_size", OPT_INNODB_PAGE_SIZE,
+ "The universal page size of the database.",
+ (G_PTR*) &innobase_page_size, (G_PTR*) &innobase_page_size, 0,
+ /* Use GET_LL to support numeric suffixes in 5.6 */
+ GET_LL, REQUIRED_ARG,
+ (1LL << 14), (1LL << 12), (1LL << UNIV_PAGE_SIZE_SHIFT_MAX), 0, 1L, 0},
+ {"innodb_buffer_pool_filename", OPT_INNODB_BUFFER_POOL_FILENAME,
+ "Ignored for mysqld option compatibility",
+ (G_PTR*) &innobase_buffer_pool_filename,
+ (G_PTR*) &innobase_buffer_pool_filename,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+#ifndef DBUG_OFF /* unfortunately "debug" collides with existing options */
+ {"dbug", '#', "Built in DBUG debugger.",
+ &dbug_option, &dbug_option, 0, GET_STR, OPT_ARG,
+ 0, 0, 0, 0, 0, 0},
+#endif
+
+ {"innodb_checksum_algorithm", OPT_INNODB_CHECKSUM_ALGORITHM,
+ "The algorithm InnoDB uses for page checksumming. [CRC32, STRICT_CRC32, "
+ "FULL_CRC32, STRICT_FULL_CRC32]", &srv_checksum_algorithm,
+ &srv_checksum_algorithm, &innodb_checksum_algorithm_typelib, GET_ENUM,
+ REQUIRED_ARG, SRV_CHECKSUM_ALGORITHM_CRC32, 0, 0, 0, 0, 0},
+
+ {"innodb_undo_directory", OPT_INNODB_UNDO_DIRECTORY,
+ "Directory where undo tablespace files live, this path can be absolute.",
+ &srv_undo_dir, &srv_undo_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0,
+ 0},
+
+ {"innodb_undo_tablespaces", OPT_INNODB_UNDO_TABLESPACES,
+ "Number of undo tablespaces to use.",
+ (G_PTR*)&srv_undo_tablespaces, (G_PTR*)&srv_undo_tablespaces,
+ 0, GET_UINT, REQUIRED_ARG, 0, 0, 126, 0, 1, 0},
+
+ {"innodb_compression_level", OPT_INNODB_COMPRESSION_LEVEL,
+ "Compression level used for zlib compression.",
+ (G_PTR*)&page_zip_level, (G_PTR*)&page_zip_level,
+ 0, GET_UINT, REQUIRED_ARG, 6, 0, 9, 0, 0, 0},
+
+ {"defaults_group", OPT_DEFAULTS_GROUP, "defaults group in config file (default \"mysqld\").",
+ (G_PTR*) &defaults_group, (G_PTR*) &defaults_group,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"plugin-dir", OPT_PLUGIN_DIR,
+ "Server plugin directory. Used to load plugins during 'prepare' phase."
+ "Has no effect in the 'backup' phase (plugin directory during backup is the same as server's)",
+ &xb_plugin_dir, &xb_plugin_dir,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+
+ {"aria_log_dir_path", OPT_ARIA_LOG_DIR_PATH,
+ "Path to individual files and their sizes.",
+ &aria_log_dir_path, &aria_log_dir_path,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"open_files_limit", OPT_OPEN_FILES_LIMIT, "the maximum number of file "
+ "descriptors to reserve with setrlimit().",
+ (G_PTR*) &xb_open_files_limit, (G_PTR*) &xb_open_files_limit, 0, GET_ULONG,
+ REQUIRED_ARG, 0, 0, UINT_MAX, 0, 1, 0},
+
+ {"lock-ddl-per-table", OPT_LOCK_DDL_PER_TABLE, "Lock DDL for each table "
+ "before backup starts to copy it and until the backup is completed.",
+ (uchar*) &opt_lock_ddl_per_table, (uchar*) &opt_lock_ddl_per_table, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"rocksdb-datadir", OPT_ROCKSDB_DATADIR, "RocksDB data directory."
+ "This option is only used with --copy-back or --move-back option",
+ &xb_rocksdb_datadir, &xb_rocksdb_datadir,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
+
+ {"rocksdb-backup", OPT_BACKUP_ROCKSDB, "Backup rocksdb data, if rocksdb plugin is installed."
+ "Used only with --backup option. Can be useful for partial backups, to exclude all rocksdb data",
+ &xb_backup_rocksdb, &xb_backup_rocksdb,
+ 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 },
+
+ {"check-privileges", OPT_XTRA_CHECK_PRIVILEGES, "Check database user "
+ "privileges fro the backup user",
+ &opt_check_privileges, &opt_check_privileges,
+ 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 },
+
+ {"innodb_force_recovery", OPT_INNODB_FORCE_RECOVERY,
+ "(for --prepare): Crash recovery mode (ignores "
+ "page corruption; for emergencies only).",
+ (G_PTR*)&srv_force_recovery,
+ (G_PTR*)&srv_force_recovery,
+ 0, GET_ULONG, OPT_ARG, 0, 0, SRV_FORCE_IGNORE_CORRUPT, 0, 0, 0},
+
+ {"mysqld-args", OPT_XTRA_MYSQLD_ARGS,
+ "All arguments that follow this argument are considered as server "
+ "options, and if some of them are not supported by mariabackup, they "
+ "will be ignored.",
+ (G_PTR *) &xtrabackup_mysqld_args, (G_PTR *) &xtrabackup_mysqld_args, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"help", '?',
+ "Display this help and exit.",
+ (G_PTR *) &xtrabackup_help, (G_PTR *) &xtrabackup_help, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+uint xb_server_options_count = array_elements(xb_server_options);
+
+
+static std::set<std::string> tables_for_export;
+
+static void append_export_table(const char *dbname, const char *tablename,
+ bool is_remote, bool skip_node_page0,
+ uint32_t defer_space_id)
+{
+ if(dbname && tablename && !is_remote)
+ {
+ char buf[3*FN_REFLEN];
+ snprintf(buf,sizeof(buf),"%s/%s",dbname, tablename);
+ // trim .ibd
+ char *p=strrchr(buf, '.');
+ if (p) *p=0;
+
+ std::string name=ut_get_name(0, buf);
+ /* Strip partition name comment from table name, if any */
+ if (ends_with(name.c_str(), "*/"))
+ {
+ size_t pos= name.rfind("/*");
+ if (pos != std::string::npos)
+ name.resize(pos);
+ }
+ tables_for_export.insert(name);
+ }
+}
+
+
+#define BOOTSTRAP_FILENAME "mariabackup_prepare_for_export.sql"
+
+static int create_bootstrap_file()
+{
+ FILE *f= fopen(BOOTSTRAP_FILENAME,"wb");
+ if(!f)
+ return -1;
+
+ fputs("SET NAMES UTF8;\n",f);
+ enumerate_ibd_files(append_export_table);
+ for (std::set<std::string>::iterator it = tables_for_export.begin();
+ it != tables_for_export.end(); it++)
+ {
+ const char *tab = it->c_str();
+ fprintf(f,
+ "BEGIN NOT ATOMIC "
+ "DECLARE CONTINUE HANDLER FOR NOT FOUND,SQLEXCEPTION BEGIN END;"
+ "FLUSH TABLES %s FOR EXPORT;"
+ "END;\n"
+ "UNLOCK TABLES;\n",
+ tab);
+ }
+ fclose(f);
+ return 0;
+}
+
+static int prepare_export()
+{
+ int err= -1;
+
+ char cmdline[2*FN_REFLEN];
+ FILE *outf;
+
+ if (create_bootstrap_file())
+ return -1;
+
+ // Process defaults-file , it can have some --lc-language stuff,
+ // which is* unfortunately* still necessary to get mysqld up
+ if (strncmp(orig_argv1,"--defaults-file=", 16) == 0)
+ {
+ snprintf(cmdline, sizeof cmdline,
+ IF_WIN("\"","") "\"%s\" --mysqld \"%s\""
+ " --defaults-extra-file=./backup-my.cnf --defaults-group-suffix=%s --datadir=."
+ " --innodb --innodb-fast-shutdown=0 --loose-partition"
+ " --innodb-buffer-pool-size=%llu"
+ " --console --skip-log-error --skip-log-bin --bootstrap %s< "
+ BOOTSTRAP_FILENAME IF_WIN("\"",""),
+ mariabackup_exe,
+ orig_argv1, (my_defaults_group_suffix?my_defaults_group_suffix:""),
+ xtrabackup_use_memory,
+ (srv_force_recovery ? "--innodb-force-recovery=1 " : ""));
+ }
+ else
+ {
+ snprintf(cmdline, sizeof cmdline,
+ IF_WIN("\"","") "\"%s\" --mysqld"
+ " --defaults-file=./backup-my.cnf --defaults-group-suffix=%s --datadir=."
+ " --innodb --innodb-fast-shutdown=0 --loose-partition"
+ " --innodb-buffer-pool-size=%llu"
+ " --console --log-error= --skip-log-bin --bootstrap %s< "
+ BOOTSTRAP_FILENAME IF_WIN("\"",""),
+ mariabackup_exe,
+ (my_defaults_group_suffix?my_defaults_group_suffix:""),
+ xtrabackup_use_memory,
+ (srv_force_recovery ? "--innodb-force-recovery=1 " : ""));
+ }
+
+ msg("Prepare export : executing %s\n", cmdline);
+ fflush(stderr);
+
+ outf= popen(cmdline,"r");
+ if (!outf)
+ goto end;
+
+ char outline[FN_REFLEN];
+ while (fgets(outline, FN_REFLEN - 1, outf))
+ fprintf(stderr,"%s",outline);
+
+ err = pclose(outf);
+end:
+ unlink(BOOTSTRAP_FILENAME);
+ return err;
+}
+
+static const char *xb_client_default_groups[]= {
+ "client", "client-server", "client-mariadb", 0, 0, 0};
+
+static const char *backup_default_groups[]= {
+ "xtrabackup", "mariabackup", "mariadb-backup", 0, 0, 0};
+
+static void print_version(void)
+{
+ fprintf(stderr, "%s based on MariaDB server %s %s (%s)\n",
+ my_progname, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
+}
+
+static void concatenate_default_groups(std::vector<const char*> &backup_load_groups, const char **default_groups)
+{
+ for ( ; *default_groups ; default_groups++)
+ backup_load_groups.push_back(*default_groups);
+}
+
+static void usage(void)
+{
+ puts("Open source backup tool for InnoDB and XtraDB\n\
+\n\
+Copyright (C) 2009-2015 Percona LLC and/or its affiliates.\n\
+Portions Copyright (C) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved.\n\
+\n\
+This program is free software; you can redistribute it and/or\n\
+modify it under the terms of the GNU General Public License\n\
+as published by the Free Software Foundation version 2\n\
+of the License.\n\
+\n\
+This program is distributed in the hope that it will be useful,\n\
+but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
+GNU General Public License for more details.\n\
+\n\
+You can download full text of the license on http://www.gnu.org/licenses/gpl-2.0.txt\n");
+
+ printf("Usage: %s [--defaults-file=#] [--backup | --prepare | --copy-back | --move-back] [OPTIONS]\n",my_progname);
+ std::vector<const char*> backup_load_default_groups;
+ concatenate_default_groups(backup_load_default_groups, backup_default_groups);
+ concatenate_default_groups(backup_load_default_groups, load_default_groups);
+ backup_load_default_groups.push_back(nullptr);
+ print_defaults("my", &backup_load_default_groups[0]);
+ my_print_help(xb_client_options);
+ my_print_help(xb_server_options);
+ my_print_variables(xb_server_options);
+ my_print_variables(xb_client_options);
+}
+
+#define ADD_PRINT_PARAM_OPT(value) \
+ { \
+ print_param_str << opt->name << "=" << value << "\n"; \
+ param_set.insert(opt->name); \
+ }
+
+/************************************************************************
+Check if parameter is set in defaults file or via command line argument
+@return true if parameter is set. */
+bool
+check_if_param_set(const char *param)
+{
+ return param_set.find(param) != param_set.end();
+}
+
+my_bool
+xb_get_one_option(const struct my_option *opt,
+ const char *argument, const char *)
+{
+ switch(opt->id) {
+ case 'h':
+ strmake(mysql_real_data_home,argument, FN_REFLEN - 1);
+ mysql_data_home= mysql_real_data_home;
+
+ ADD_PRINT_PARAM_OPT(mysql_real_data_home);
+ break;
+
+ case 't':
+
+ ADD_PRINT_PARAM_OPT(opt_mysql_tmpdir);
+ break;
+
+ case OPT_INNODB_DATA_HOME_DIR:
+
+ ADD_PRINT_PARAM_OPT(innobase_data_home_dir);
+ break;
+
+ case OPT_INNODB_DATA_FILE_PATH:
+
+ ADD_PRINT_PARAM_OPT(innobase_data_file_path);
+ break;
+
+ case OPT_INNODB_LOG_GROUP_HOME_DIR:
+
+ ADD_PRINT_PARAM_OPT(srv_log_group_home_dir);
+ break;
+
+ case OPT_INNODB_FLUSH_METHOD:
+#ifdef _WIN32
+ /* From: storage/innobase/handler/ha_innodb.cc:innodb_init_params */
+ switch (srv_file_flush_method) {
+ case SRV_ALL_O_DIRECT_FSYNC + 1 /* "async_unbuffered"="unbuffered" */:
+ srv_file_flush_method= SRV_ALL_O_DIRECT_FSYNC;
+ break;
+ case SRV_ALL_O_DIRECT_FSYNC + 2 /* "normal"="fsync" */:
+ srv_file_flush_method= SRV_FSYNC;
+ break;
+ }
+#endif
+ ut_a(srv_file_flush_method
+ <= IF_WIN(SRV_ALL_O_DIRECT_FSYNC, SRV_O_DIRECT_NO_FSYNC));
+ ADD_PRINT_PARAM_OPT(innodb_flush_method_names[srv_file_flush_method]);
+ break;
+
+ case OPT_INNODB_PAGE_SIZE:
+
+ ADD_PRINT_PARAM_OPT(innobase_page_size);
+ break;
+
+ case OPT_INNODB_UNDO_DIRECTORY:
+
+ ADD_PRINT_PARAM_OPT(srv_undo_dir);
+ break;
+
+ case OPT_INNODB_UNDO_TABLESPACES:
+
+ ADD_PRINT_PARAM_OPT(srv_undo_tablespaces);
+ break;
+
+ case OPT_INNODB_CHECKSUM_ALGORITHM:
+
+ ut_a(srv_checksum_algorithm <= SRV_CHECKSUM_ALGORITHM_STRICT_FULL_CRC32);
+
+ ADD_PRINT_PARAM_OPT(innodb_checksum_algorithm_names[srv_checksum_algorithm]);
+ break;
+
+ case OPT_INNODB_COMPRESSION_LEVEL:
+ ADD_PRINT_PARAM_OPT(page_zip_level);
+ break;
+
+ case OPT_INNODB_BUFFER_POOL_FILENAME:
+
+ ADD_PRINT_PARAM_OPT(innobase_buffer_pool_filename);
+ break;
+
+ case OPT_INNODB_FORCE_RECOVERY:
+
+ if (srv_force_recovery) {
+ ADD_PRINT_PARAM_OPT(srv_force_recovery);
+ }
+ break;
+
+ case OPT_ARIA_LOG_DIR_PATH:
+ ADD_PRINT_PARAM_OPT(aria_log_dir_path);
+ break;
+
+ case OPT_XTRA_TARGET_DIR:
+ strmake(xtrabackup_real_target_dir,argument, sizeof(xtrabackup_real_target_dir)-1);
+ xtrabackup_target_dir= xtrabackup_real_target_dir;
+ break;
+ case OPT_XTRA_STREAM:
+ if (!strcasecmp(argument, "mbstream") ||
+ !strcasecmp(argument, "xbstream"))
+ xtrabackup_stream_fmt = XB_STREAM_FMT_XBSTREAM;
+ else
+ {
+ msg("Invalid --stream argument: %s", argument);
+ return 1;
+ }
+ xtrabackup_stream = TRUE;
+ break;
+ case OPT_XTRA_COMPRESS:
+ if (argument == NULL)
+ xtrabackup_compress_alg = "quicklz";
+ else if (strcasecmp(argument, "quicklz"))
+ {
+ msg("Invalid --compress argument: %s", argument);
+ return 1;
+ }
+ xtrabackup_compress = TRUE;
+ break;
+ case OPT_DECOMPRESS:
+ opt_decompress = TRUE;
+ xtrabackup_decrypt_decompress = true;
+ break;
+ case (int) OPT_CORE_FILE:
+ test_flags |= TEST_CORE_ON_SIGNAL;
+ break;
+ case OPT_HISTORY:
+ if (argument) {
+ opt_history = argument;
+ } else {
+ opt_history = "";
+ }
+ break;
+ case 'p':
+ opt_password = argument;
+ break;
+ case OPT_PROTOCOL:
+ if (argument)
+ {
+ if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib,
+ opt->name)) <= 0)
+ {
+ sf_leaking_memory= 1; /* no memory leak reports here */
+ exit(1);
+ }
+ }
+ break;
+#define MYSQL_CLIENT
+#include "sslopt-case.h"
+#undef MYSQL_CLIENT
+
+ case '?':
+ usage();
+ exit(EXIT_SUCCESS);
+ break;
+ case 'v':
+ print_version();
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static bool innodb_init_param()
+{
+ srv_is_being_started = TRUE;
+ /* === some variables from mysqld === */
+ memset((G_PTR) &mysql_tmpdir_list, 0, sizeof(mysql_tmpdir_list));
+
+ if (init_tmpdir(&mysql_tmpdir_list, opt_mysql_tmpdir)) {
+ msg("init_tmpdir() failed");
+ return true;
+ }
+ xtrabackup_tmpdir = my_tmpdir(&mysql_tmpdir_list);
+ /* dummy for initialize all_charsets[] */
+ get_charset_name(0);
+
+ srv_page_size = 0;
+ srv_page_size_shift = 0;
+#ifdef BTR_CUR_HASH_ADAPT
+ btr_ahi_parts = 1;
+#endif /* BTR_CUR_HASH_ADAPT */
+
+ if (innobase_page_size != (1LL << 14)) {
+ size_t n_shift = get_bit_shift(size_t(innobase_page_size));
+
+ if (n_shift >= 12 && n_shift <= UNIV_PAGE_SIZE_SHIFT_MAX) {
+ srv_page_size_shift = uint32_t(n_shift);
+ srv_page_size = 1U << n_shift;
+ msg("InnoDB: The universal page size of the "
+ "database is set to %lu.", srv_page_size);
+ } else {
+ msg("invalid value of "
+ "innobase_page_size: %lld", innobase_page_size);
+ goto error;
+ }
+ } else {
+ srv_page_size_shift = 14;
+ srv_page_size = 1U << 14;
+ }
+
+ /* Check that values don't overflow on 32-bit systems. */
+ if (sizeof(ulint) == 4) {
+ if (xtrabackup_use_memory > UINT_MAX32) {
+ msg("mariabackup: use-memory can't be over 4GB"
+ " on 32-bit systems");
+ }
+ }
+
+ static char default_path[2] = { FN_CURLIB, 0 };
+ fil_path_to_mysql_datadir = default_path;
+
+ /* Set InnoDB initialization parameters according to the values
+ read from MySQL .cnf file */
+
+ if (xtrabackup_backup) {
+ msg("mariabackup: using the following InnoDB configuration:");
+ } else {
+ msg("mariabackup: using the following InnoDB configuration "
+ "for recovery:");
+ }
+
+ /*--------------- Data files -------------------------*/
+
+ /* The default dir for data files is the datadir of MySQL */
+
+ srv_data_home = (xtrabackup_backup && innobase_data_home_dir
+ ? innobase_data_home_dir : default_path);
+ msg("innodb_data_home_dir = %s", srv_data_home);
+
+ /* Set default InnoDB data file size to 10 MB and let it be
+ auto-extending. Thus users can use InnoDB in >= 4.0 without having
+ to specify any startup options. */
+
+ if (!innobase_data_file_path) {
+ innobase_data_file_path = (char*) "ibdata1:10M:autoextend";
+ }
+ msg("innodb_data_file_path = %s",
+ innobase_data_file_path);
+
+ srv_sys_space.set_space_id(TRX_SYS_SPACE);
+ srv_sys_space.set_path(srv_data_home);
+ switch (srv_checksum_algorithm) {
+ case SRV_CHECKSUM_ALGORITHM_FULL_CRC32:
+ case SRV_CHECKSUM_ALGORITHM_STRICT_FULL_CRC32:
+ srv_sys_space.set_flags(FSP_FLAGS_FCRC32_MASK_MARKER
+ | FSP_FLAGS_FCRC32_PAGE_SSIZE());
+ break;
+ default:
+ srv_sys_space.set_flags(FSP_FLAGS_PAGE_SSIZE());
+ }
+
+ if (!srv_sys_space.parse_params(innobase_data_file_path, true)) {
+ goto error;
+ }
+
+ srv_sys_space.normalize_size();
+ srv_lock_table_size = 5 * (srv_buf_pool_size >> srv_page_size_shift);
+
+ /* -------------- Log files ---------------------------*/
+
+ /* The default dir for log files is the datadir of MySQL */
+
+ if (!(xtrabackup_backup && srv_log_group_home_dir)) {
+ srv_log_group_home_dir = default_path;
+ }
+ if (xtrabackup_prepare && xtrabackup_incremental_dir) {
+ srv_log_group_home_dir = xtrabackup_incremental_dir;
+ }
+ msg("innodb_log_group_home_dir = %s",
+ srv_log_group_home_dir);
+
+ if (strchr(srv_log_group_home_dir, ';')) {
+ msg("syntax error in innodb_log_group_home_dir, ");
+ goto error;
+ }
+
+ srv_adaptive_flushing = FALSE;
+
+ /* We set srv_pool_size here in units of 1 kB. InnoDB internally
+ changes the value so that it becomes the number of database pages. */
+
+ srv_buf_pool_size = (ulint) xtrabackup_use_memory;
+ srv_buf_pool_chunk_unit = srv_buf_pool_size;
+
+ srv_n_read_io_threads = (uint) innobase_read_io_threads;
+ srv_n_write_io_threads = (uint) innobase_write_io_threads;
+
+ srv_max_n_open_files = ULINT_UNDEFINED - 5;
+
+ srv_print_verbose_log = verbose ? 2 : 1;
+
+ /* Store the default charset-collation number of this MySQL
+ installation */
+
+ /* We cannot treat characterset here for now!! */
+ data_mysql_default_charset_coll = (ulint)default_charset_info->number;
+
+ ut_ad(DATA_MYSQL_BINARY_CHARSET_COLL == my_charset_bin.number);
+
+#ifdef _WIN32
+ srv_use_native_aio = TRUE;
+
+#elif defined(LINUX_NATIVE_AIO)
+
+ if (srv_use_native_aio) {
+ msg("InnoDB: Using Linux native AIO");
+ }
+#elif defined(HAVE_URING)
+ if (!srv_use_native_aio) {
+ } else if (io_uring_may_be_unsafe) {
+ msg("InnoDB: Using liburing on this kernel %s may cause hangs;"
+ " see https://jira.mariadb.org/browse/MDEV-26674",
+ io_uring_may_be_unsafe);
+ } else {
+ msg("InnoDB: Using liburing");
+ }
+#else
+ /* Currently native AIO is supported only on windows and linux
+ and that also when the support is compiled in. In all other
+ cases, we ignore the setting of innodb_use_native_aio. */
+ srv_use_native_aio = FALSE;
+
+#endif
+
+ /* Assign the default value to srv_undo_dir if it's not specified, as
+ my_getopt does not support default values for string options. We also
+ ignore the option and override innodb_undo_directory on --prepare,
+ because separate undo tablespaces are copied to the root backup
+ directory. */
+
+ if (!srv_undo_dir || !xtrabackup_backup) {
+ srv_undo_dir = (char*) ".";
+ }
+
+ compile_time_assert(SRV_FORCE_IGNORE_CORRUPT == 1);
+
+ /*
+ * This option can be read both from the command line, and the
+ * defaults file. The assignment should account for both cases,
+ * and for "--innobackupex". Since the command line argument is
+ * parsed after the defaults file, it takes precedence.
+ */
+ if (xtrabackup_innodb_force_recovery) {
+ srv_force_recovery = xtrabackup_innodb_force_recovery;
+ }
+
+ if (srv_force_recovery >= SRV_FORCE_IGNORE_CORRUPT) {
+ if (!xtrabackup_prepare) {
+ msg("mariabackup: The option \"innodb_force_recovery\""
+ " should only be used with \"%s\".",
+ (innobackupex_mode ? "--apply-log" : "--prepare"));
+ goto error;
+ } else {
+ msg("innodb_force_recovery = %lu", srv_force_recovery);
+ }
+ }
+
+#ifdef _WIN32
+ srv_use_native_aio = TRUE;
+#endif
+ return false;
+
+error:
+ msg("mariabackup: innodb_init_param(): Error occurred.\n");
+ return true;
+}
+
+static byte log_hdr_buf[log_t::START_OFFSET + SIZE_OF_FILE_CHECKPOINT];
+
+/** Initialize an InnoDB log file header in log_hdr_buf[] */
+static void log_hdr_init()
+{
+ memset(log_hdr_buf, 0, sizeof log_hdr_buf);
+ mach_write_to_4(LOG_HEADER_FORMAT + log_hdr_buf, log_t::FORMAT_10_8);
+ mach_write_to_8(LOG_HEADER_START_LSN + log_hdr_buf,
+ log_sys.next_checkpoint_lsn);
+ snprintf(reinterpret_cast<char*>(LOG_HEADER_CREATOR + log_hdr_buf),
+ 16, "Backup %u.%u.%u",
+ MYSQL_VERSION_ID / 10000, MYSQL_VERSION_ID / 100 % 100,
+ MYSQL_VERSION_ID % 100);
+ if (log_sys.is_encrypted())
+ log_crypt_write_header(log_hdr_buf + LOG_HEADER_CREATOR_END);
+ mach_write_to_4(508 + log_hdr_buf, my_crc32c(0, log_hdr_buf, 508));
+ mach_write_to_8(log_hdr_buf + 0x1000, log_sys.next_checkpoint_lsn);
+ mach_write_to_8(log_hdr_buf + 0x1008, recv_sys.lsn);
+ mach_write_to_4(log_hdr_buf + 0x103c,
+ my_crc32c(0, log_hdr_buf + 0x1000, 60));
+}
+
+static bool innodb_init()
+{
+ bool create_new_db= false;
+
+ srv_max_io_capacity= srv_io_capacity >= SRV_MAX_IO_CAPACITY_LIMIT / 2
+ ? SRV_MAX_IO_CAPACITY_LIMIT : std::max(2 * srv_io_capacity, 2000UL);
+
+ /* Check if the data files exist or not. */
+ dberr_t err= srv_sys_space.check_file_spec(&create_new_db, 5U << 20);
+
+ if (create_new_db)
+ {
+ msg("mariadb-backup: InnoDB files do not exist");
+ return true;
+ }
+
+ if (err == DB_SUCCESS)
+ err= srv_start(false);
+
+ if (err != DB_SUCCESS)
+ {
+ msg("mariadb-backup: srv_start() returned %d (%s).", err, ut_strerr(err));
+ return true;
+ }
+
+ ut_ad(srv_force_recovery <= SRV_FORCE_IGNORE_CORRUPT);
+ ut_ad(recv_no_log_write);
+ buf_flush_sync();
+ recv_sys.debug_free();
+ ut_ad(!os_aio_pending_reads());
+ ut_d(mysql_mutex_lock(&buf_pool.flush_list_mutex));
+ ut_ad(!buf_pool.get_oldest_modification(0));
+ ut_d(mysql_mutex_unlock(&buf_pool.flush_list_mutex));
+ /* os_aio_pending_writes() may hold here if some write_io_callback()
+ did not release the slot yet. However, the page write itself must
+ have completed, because the buf_pool.flush_list is empty. In debug
+ builds, we wait for this to happen, hoping to get a hung process if
+ this assumption does not hold. */
+ ut_d(os_aio_wait_until_no_pending_writes(false));
+ log_sys.close_file();
+
+ if (xtrabackup_incremental)
+ /* Reset the ib_logfile0 in --target-dir, not --incremental-dir. */
+ srv_log_group_home_dir= xtrabackup_target_dir;
+
+ bool ret;
+ const std::string ib_logfile0{get_log_file_path()};
+ os_file_delete_if_exists_func(ib_logfile0.c_str(), nullptr);
+ os_file_t file= os_file_create_func(ib_logfile0.c_str(),
+ OS_FILE_CREATE, OS_FILE_NORMAL,
+ OS_DATA_FILE_NO_O_DIRECT, false, &ret);
+ if (!ret)
+ {
+ invalid_log:
+ msg("mariadb-backup: Cannot create %s", ib_logfile0.c_str());
+ return true;
+ }
+
+ recv_sys.lsn= log_sys.next_checkpoint_lsn=
+ log_sys.get_lsn() - SIZE_OF_FILE_CHECKPOINT;
+ log_sys.set_latest_format(false); // not encrypted
+ log_hdr_init();
+ byte *b= &log_hdr_buf[log_t::START_OFFSET];
+ b[0]= FILE_CHECKPOINT | 10;
+ mach_write_to_8(b + 3, recv_sys.lsn);
+ b[11]= 1;
+ mach_write_to_4(b + 12, my_crc32c(0, b, 11));
+ static_assert(12 + 4 == SIZE_OF_FILE_CHECKPOINT, "compatibility");
+
+#ifdef _WIN32
+ DWORD len;
+ ret= WriteFile(file, log_hdr_buf, sizeof log_hdr_buf,
+ &len, nullptr) && len == sizeof log_hdr_buf;
+#else
+ ret= sizeof log_hdr_buf == write(file, log_hdr_buf, sizeof log_hdr_buf);
+#endif
+ if (!os_file_close_func(file) || !ret)
+ goto invalid_log;
+ return false;
+}
+
+/* ================= common ================= */
+
+/***********************************************************************
+Read backup meta info.
+@return TRUE on success, FALSE on failure. */
+static
+my_bool
+xtrabackup_read_metadata(char *filename)
+{
+ FILE *fp;
+ my_bool r = TRUE;
+
+ fp = fopen(filename,"r");
+ if(!fp) {
+ msg("Error: cannot open %s", filename);
+ return(FALSE);
+ }
+
+ if (fscanf(fp, "backup_type = %29s\n", metadata_type)
+ != 1) {
+ r = FALSE;
+ goto end;
+ }
+ /* Use UINT64PF instead of LSN_PF here, as we have to maintain the file
+ format. */
+ if (fscanf(fp, "from_lsn = " UINT64PF "\n", &metadata_from_lsn)
+ != 1) {
+ r = FALSE;
+ goto end;
+ }
+ if (fscanf(fp, "to_lsn = " UINT64PF "\n", &metadata_to_lsn)
+ != 1) {
+ r = FALSE;
+ goto end;
+ }
+ if (fscanf(fp, "last_lsn = " UINT64PF "\n", &metadata_last_lsn)
+ != 1) {
+ metadata_last_lsn = 0;
+ }
+ /* Optional fields */
+
+end:
+ fclose(fp);
+
+ return(r);
+}
+
+/***********************************************************************
+Print backup meta info to a specified buffer. */
+static
+void
+xtrabackup_print_metadata(char *buf, size_t buf_len)
+{
+ /* Use UINT64PF instead of LSN_PF here, as we have to maintain the file
+ format. */
+ snprintf(buf, buf_len,
+ "backup_type = %s\n"
+ "from_lsn = " UINT64PF "\n"
+ "to_lsn = " UINT64PF "\n"
+ "last_lsn = " UINT64PF "\n",
+ metadata_type,
+ metadata_from_lsn,
+ metadata_to_lsn,
+ metadata_last_lsn);
+}
+
+/***********************************************************************
+Stream backup meta info to a specified datasink.
+@return TRUE on success, FALSE on failure. */
+static
+my_bool
+xtrabackup_stream_metadata(ds_ctxt_t *ds_ctxt)
+{
+ char buf[1024];
+ size_t len;
+ ds_file_t *stream;
+ MY_STAT mystat;
+ my_bool rc = TRUE;
+
+ xtrabackup_print_metadata(buf, sizeof(buf));
+
+ len = strlen(buf);
+
+ mystat.st_size = len;
+ mystat.st_mtime = my_time(0);
+
+ stream = ds_open(ds_ctxt, XTRABACKUP_METADATA_FILENAME, &mystat);
+ if (stream == NULL) {
+ msg("Error: cannot open output stream for %s", XTRABACKUP_METADATA_FILENAME);
+ return(FALSE);
+ }
+
+ if (ds_write(stream, buf, len)) {
+ rc = FALSE;
+ }
+
+ if (ds_close(stream)) {
+ rc = FALSE;
+ }
+
+ return(rc);
+}
+
+/***********************************************************************
+Write backup meta info to a specified file.
+@return TRUE on success, FALSE on failure. */
+static
+my_bool
+xtrabackup_write_metadata(const char *filepath)
+{
+ char buf[1024];
+ size_t len;
+ FILE *fp;
+
+ xtrabackup_print_metadata(buf, sizeof(buf));
+
+ len = strlen(buf);
+
+ fp = fopen(filepath, "w");
+ if(!fp) {
+ msg("Error: cannot open %s", filepath);
+ return(FALSE);
+ }
+ if (fwrite(buf, len, 1, fp) < 1) {
+ fclose(fp);
+ return(FALSE);
+ }
+
+ fclose(fp);
+
+ return(TRUE);
+}
+
+/***********************************************************************
+Read meta info for an incremental delta.
+@return TRUE on success, FALSE on failure. */
+static my_bool
+xb_read_delta_metadata(const char *filepath, xb_delta_info_t *info)
+{
+ FILE* fp;
+ char key[51];
+ char value[51];
+ my_bool r = TRUE;
+
+ /* set defaults */
+ ulint page_size = ULINT_UNDEFINED, zip_size = 0;
+ info->space_id = UINT32_MAX;
+
+ fp = fopen(filepath, "r");
+ if (!fp) {
+ /* Meta files for incremental deltas are optional */
+ return(TRUE);
+ }
+
+ while (!feof(fp)) {
+ if (fscanf(fp, "%50s = %50s\n", key, value) == 2) {
+ if (strcmp(key, "page_size") == 0) {
+ page_size = strtoul(value, NULL, 10);
+ } else if (strcmp(key, "zip_size") == 0) {
+ zip_size = strtoul(value, NULL, 10);
+ } else if (strcmp(key, "space_id") == 0) {
+ info->space_id = static_cast<uint32_t>
+ (strtoul(value, NULL, 10));
+ }
+ }
+ }
+
+ fclose(fp);
+
+ if (page_size == ULINT_UNDEFINED) {
+ msg("page_size is required in %s", filepath);
+ r = FALSE;
+ } else {
+ info->page_size = page_size;
+ info->zip_size = zip_size;
+ }
+
+ if (info->space_id == UINT32_MAX) {
+ msg("mariabackup: Warning: This backup was taken with XtraBackup 2.0.1 "
+ "or earlier, some DDL operations between full and incremental "
+ "backups may be handled incorrectly");
+ }
+
+ return(r);
+}
+
+/***********************************************************************
+Write meta info for an incremental delta.
+@return TRUE on success, FALSE on failure. */
+my_bool
+xb_write_delta_metadata(ds_ctxt *ds_meta,
+ const char *filename, const xb_delta_info_t *info)
+{
+ ds_file_t *f;
+ char buf[64];
+ my_bool ret;
+ size_t len;
+ MY_STAT mystat;
+
+ snprintf(buf, sizeof(buf),
+ "page_size = " ULINTPF "\n"
+ "zip_size = " ULINTPF " \n"
+ "space_id = %u\n",
+ info->page_size,
+ info->zip_size,
+ info->space_id);
+ len = strlen(buf);
+
+ mystat.st_size = len;
+ mystat.st_mtime = my_time(0);
+
+ f = ds_open(ds_meta, filename, &mystat);
+ if (f == NULL) {
+ msg("Error: Can't open output stream for %s",filename);
+ return(FALSE);
+ }
+
+ ret = (ds_write(f, buf, len) == 0);
+
+ if (ds_close(f)) {
+ ret = FALSE;
+ }
+
+ return(ret);
+}
+
+/* ================= backup ================= */
+void xtrabackup_io_throttling()
+{
+ if (!xtrabackup_backup || !xtrabackup_throttle)
+ return;
+
+ mysql_mutex_lock(&recv_sys.mutex);
+ if (io_ticket-- < 0)
+ mysql_cond_wait(&wait_throttle, &recv_sys.mutex);
+ mysql_mutex_unlock(&recv_sys.mutex);
+}
+
+static
+my_bool regex_list_check_match(
+ const regex_list_t& list,
+ const char* name)
+{
+ regmatch_t tables_regmatch[1];
+ for (regex_list_t::const_iterator i = list.begin(), end = list.end();
+ i != end; ++i) {
+ const regex_t& regex = *i;
+ int regres = regexec(&regex, name, 1, tables_regmatch, 0);
+
+ if (regres != REG_NOMATCH) {
+ return(TRUE);
+ }
+ }
+ return(FALSE);
+}
+
+static
+my_bool
+find_filter_in_hashtable(
+ const char* name,
+ hash_table_t* table,
+ xb_filter_entry_t** result
+)
+{
+ xb_filter_entry_t* found = NULL;
+ const ulint fold = my_crc32c(0, name, strlen(name));
+ HASH_SEARCH(name_hash, table, fold,
+ xb_filter_entry_t*,
+ found, (void) 0,
+ !strcmp(found->name, name));
+
+ if (found && result) {
+ *result = found;
+ }
+ return (found != NULL);
+}
+
+/************************************************************************
+Checks if a given table name matches any of specifications given in
+regex_list or tables_hash.
+
+@return TRUE on match or both regex_list and tables_hash are empty.*/
+static my_bool
+check_if_table_matches_filters(const char *name,
+ const regex_list_t& regex_list,
+ hash_table_t* tables_hash)
+{
+ if (regex_list.empty() && !tables_hash->array) {
+ return(FALSE);
+ }
+
+ if (regex_list_check_match(regex_list, name)) {
+ return(TRUE);
+ }
+
+ return tables_hash->array &&
+ find_filter_in_hashtable(name, tables_hash, NULL);
+}
+
+enum skip_database_check_result {
+ DATABASE_SKIP,
+ DATABASE_SKIP_SOME_TABLES,
+ DATABASE_DONT_SKIP,
+ DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED,
+};
+
+/************************************************************************
+Checks if a database specified by name should be skipped from backup based on
+the --databases, --databases_file or --databases_exclude options.
+
+@return TRUE if entire database should be skipped,
+ FALSE otherwise.
+*/
+static
+skip_database_check_result
+check_if_skip_database(
+ const char* name /*!< in: path to the database */
+)
+{
+ /* There are some filters for databases, check them */
+ xb_filter_entry_t* database = NULL;
+
+ if (databases_exclude_hash.array &&
+ find_filter_in_hashtable(name, &databases_exclude_hash,
+ &database) &&
+ (!database->has_tables || !databases_include_hash.array)) {
+ /* Database is found and there are no tables specified,
+ skip entire db. */
+ return DATABASE_SKIP;
+ }
+
+ if (databases_include_hash.array) {
+ if (!find_filter_in_hashtable(name, &databases_include_hash,
+ &database)) {
+ /* Database isn't found, skip the database */
+ return DATABASE_SKIP;
+ } else if (database->has_tables) {
+ return DATABASE_SKIP_SOME_TABLES;
+ } else {
+ return DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED;
+ }
+ }
+
+ return DATABASE_DONT_SKIP;
+}
+
+/************************************************************************
+Checks if a database specified by path should be skipped from backup based on
+the --databases, --databases_file or --databases_exclude options.
+
+@return TRUE if the table should be skipped. */
+my_bool
+check_if_skip_database_by_path(
+ const char* path /*!< in: path to the db directory. */
+)
+{
+ if (!databases_include_hash.array && !databases_exclude_hash.array) {
+ return(FALSE);
+ }
+
+ const char* db_name = strrchr(path, '/');
+#ifdef _WIN32
+ if (const char* last = strrchr(path, '\\')) {
+ if (!db_name || last > db_name) {
+ db_name = last;
+ }
+ }
+#endif
+
+ if (db_name == NULL) {
+ db_name = path;
+ } else {
+ ++db_name;
+ }
+
+ return check_if_skip_database(db_name) == DATABASE_SKIP;
+}
+
+/************************************************************************
+Checks if a table specified as a name in the form "database/name" (InnoDB 5.6)
+or "./database/name.ibd" (InnoDB 5.5-) should be skipped from backup based on
+the --tables or --tables-file options.
+
+@return TRUE if the table should be skipped. */
+my_bool
+check_if_skip_table(
+/******************/
+ const char* name) /*!< in: path to the table */
+{
+ char buf[FN_REFLEN];
+ const char *dbname, *tbname;
+ const char *ptr;
+ char *eptr;
+
+
+ dbname = NULL;
+ tbname = name;
+ for (;;) {
+ ptr= strchr(tbname, '/');
+#ifdef _WIN32
+ if (!ptr) {
+ ptr= strchr(tbname,'\\');
+ }
+#endif
+ if (!ptr) {
+ break;
+ }
+ dbname = tbname;
+ tbname = ptr + 1;
+ }
+
+ if (strncmp(tbname, tmp_file_prefix, tmp_file_prefix_length) == 0) {
+ return TRUE;
+ }
+
+ if (regex_exclude_list.empty() &&
+ regex_include_list.empty() &&
+ !tables_include_hash.array &&
+ !tables_exclude_hash.array &&
+ !databases_include_hash.array &&
+ !databases_exclude_hash.array) {
+ return(FALSE);
+ }
+
+ if (dbname == NULL) {
+ return(FALSE);
+ }
+
+ strncpy(buf, dbname, FN_REFLEN - 1);
+ buf[FN_REFLEN - 1] = '\0';
+ buf[tbname - 1 - dbname] = '\0';
+
+ const skip_database_check_result skip_database =
+ check_if_skip_database(buf);
+ if (skip_database == DATABASE_SKIP) {
+ return (TRUE);
+ }
+
+ buf[tbname - 1 - dbname] = '.';
+
+ /* Check if there's a suffix in the table name. If so, truncate it. We
+ rely on the fact that a dot cannot be a part of a table name (it is
+ encoded by the server with the @NNNN syntax). */
+ if ((eptr = strchr(&buf[tbname - dbname], '.')) != NULL) {
+
+ *eptr = '\0';
+ }
+
+ /* For partitioned tables first try to match against the regexp
+ without truncating the #P#... suffix so we can backup individual
+ partitions with regexps like '^test[.]t#P#p5' */
+ if (check_if_table_matches_filters(buf, regex_exclude_list,
+ &tables_exclude_hash)) {
+ return(TRUE);
+ }
+ if (check_if_table_matches_filters(buf, regex_include_list,
+ &tables_include_hash)) {
+ return(FALSE);
+ }
+ if ((eptr = strstr(buf, "#P#")) != NULL) {
+ *eptr = 0;
+
+ if (check_if_table_matches_filters(buf, regex_exclude_list,
+ &tables_exclude_hash)) {
+ return (TRUE);
+ }
+ if (check_if_table_matches_filters(buf, regex_include_list,
+ &tables_include_hash)) {
+ return(FALSE);
+ }
+ }
+
+ if (skip_database == DATABASE_DONT_SKIP_UNLESS_EXPLICITLY_EXCLUDED) {
+ /* Database is in include-list, and qualified name wasn't
+ found in any of exclusion filters.*/
+ return (FALSE);
+ }
+
+ if (skip_database == DATABASE_SKIP_SOME_TABLES ||
+ !regex_include_list.empty() ||
+ tables_include_hash.array) {
+
+ /* Include lists are present, but qualified name
+ failed to match any.*/
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+const char*
+xb_get_copy_action(const char *dflt)
+{
+ const char *action;
+
+ if (xtrabackup_stream) {
+ if (xtrabackup_compress) {
+ action = "Compressing and streaming";
+ } else {
+ action = "Streaming";
+ }
+ } else {
+ if (xtrabackup_compress) {
+ action = "Compressing";
+ } else {
+ action = dflt;
+ }
+ }
+
+ return(action);
+}
+
+
+/** Copy innodb data file to the specified destination.
+
+@param[in] node file node of a tablespace
+@param[in] thread_n thread id, used in the text of diagnostic messages
+@param[in] dest_name destination file name
+@param[in] write_filter write filter to copy data, can be pass-through filter
+for full backup, pages filter for incremental backup, etc.
+
+@return FALSE on success and TRUE on error */
+static my_bool xtrabackup_copy_datafile(ds_ctxt *ds_data,
+ ds_ctxt *ds_meta,
+ fil_node_t *node, uint thread_n,
+ const char *dest_name,
+ const xb_write_filt_t &write_filter,
+ CorruptedPages &corrupted_pages)
+{
+ char dst_name[FN_REFLEN];
+ ds_file_t *dstfile = NULL;
+ xb_fil_cur_t cursor;
+ xb_fil_cur_result_t res;
+ xb_write_filt_ctxt_t write_filt_ctxt;
+ const char *action;
+ xb_read_filt_t *read_filter;
+ my_bool rc = FALSE;
+
+ if (fil_is_user_tablespace_id(node->space->id)
+ && check_if_skip_table(filename_to_spacename(node->name,
+ strlen(node->name)).
+ c_str())) {
+ msg(thread_n, "Skipping %s.", node->name);
+ return(FALSE);
+ }
+
+ memset(&write_filt_ctxt, 0, sizeof(xb_write_filt_ctxt_t));
+
+ bool was_dropped;
+ mysql_mutex_lock(&recv_sys.mutex);
+ was_dropped = (ddl_tracker.drops.find(node->space->id) != ddl_tracker.drops.end());
+ mysql_mutex_unlock(&recv_sys.mutex);
+ if (was_dropped) {
+ if (node->is_open()) {
+ mysql_mutex_lock(&fil_system.mutex);
+ node->close();
+ mysql_mutex_unlock(&fil_system.mutex);
+ }
+ goto skip;
+ }
+
+ if (!changed_page_bitmap) {
+ read_filter = &rf_pass_through;
+ }
+ else {
+ read_filter = &rf_bitmap;
+ }
+
+ res = xb_fil_cur_open(&cursor, read_filter, node, thread_n, ULLONG_MAX);
+ if (res == XB_FIL_CUR_SKIP) {
+ goto skip;
+ } else if (res == XB_FIL_CUR_ERROR) {
+ goto error;
+ }
+
+ strncpy(dst_name, dest_name ? dest_name : cursor.rel_path,
+ sizeof dst_name - 1);
+ dst_name[sizeof dst_name - 1] = '\0';
+
+ ut_a(write_filter.process != NULL);
+
+ if (write_filter.init != NULL &&
+ !write_filter.init(ds_meta, &write_filt_ctxt, dst_name, &cursor,
+ opt_log_innodb_page_corruption ? &corrupted_pages : NULL)) {
+ msg (thread_n, "mariabackup: error: failed to initialize page write filter.");
+ goto error;
+ }
+
+ dstfile = ds_open(ds_data, dst_name, &cursor.statinfo);
+ if (dstfile == NULL) {
+ msg(thread_n,"mariabackup: error: can't open the destination stream for %s", dst_name);
+ goto error;
+ }
+
+ action = xb_get_copy_action();
+
+ if (xtrabackup_stream) {
+ msg(thread_n, "%s %s", action, node->name);
+ } else {
+ msg(thread_n, "%s %s to %s", action, node->name,
+ dstfile->path);
+ }
+
+ /* The main copy loop */
+ while (1) {
+ res = xb_fil_cur_read(&cursor, corrupted_pages);
+ if (res == XB_FIL_CUR_ERROR) {
+ goto error;
+ }
+
+ if (res == XB_FIL_CUR_EOF) {
+ break;
+ }
+
+ if (!write_filter.process(&write_filt_ctxt, dstfile)) {
+ goto error;
+ }
+
+ if (res == XB_FIL_CUR_SKIP) {
+ mysql_mutex_lock(&recv_sys.mutex);
+ fail_undo_ids.insert(
+ static_cast<uint32_t>(cursor.space_id));
+ mysql_mutex_unlock(&recv_sys.mutex);
+ break;
+ }
+ }
+
+ if (write_filter.finalize
+ && !write_filter.finalize(&write_filt_ctxt, dstfile)) {
+ goto error;
+ } else {
+ const fil_space_t::name_type name = node->space->name();
+
+ mysql_mutex_lock(&recv_sys.mutex);
+ ddl_tracker.tables_in_backup.emplace(node->space->id,
+ std::string(name.data(),
+ name.size()));
+ mysql_mutex_unlock(&recv_sys.mutex);
+ }
+
+ /* close */
+ msg(thread_n," ...done");
+ xb_fil_cur_close(&cursor);
+ if (ds_close(dstfile)) {
+ rc = TRUE;
+ }
+ if (write_filter.deinit) {
+ write_filter.deinit(&write_filt_ctxt);
+ }
+ return(rc);
+
+error:
+ xb_fil_cur_close(&cursor);
+ if (dstfile != NULL) {
+ ds_close(dstfile);
+ }
+ if (write_filter.deinit) {
+ write_filter.deinit(&write_filt_ctxt);;
+ }
+ msg(thread_n, "mariabackup: xtrabackup_copy_datafile() failed.");
+ return(TRUE); /*ERROR*/
+
+skip:
+
+ if (dstfile != NULL) {
+ ds_close(dstfile);
+ }
+ if (write_filter.deinit) {
+ write_filter.deinit(&write_filt_ctxt);
+ }
+ msg(thread_n,"Warning: We assume the table was dropped during xtrabackup execution and ignore the tablespace %s", node->name);
+ return(FALSE);
+}
+
+/** Copy redo log until the current end of the log is reached
+@return whether the operation failed */
+static bool xtrabackup_copy_logfile()
+{
+ mysql_mutex_assert_owner(&recv_sys.mutex);
+ DBUG_EXECUTE_IF("log_checksum_mismatch", return false;);
+
+ ut_a(dst_log_file);
+ ut_ad(recv_sys.is_initialised());
+ const size_t sequence_offset{log_sys.is_encrypted() ? 8U + 5U : 5U};
+ const size_t block_size_1{log_sys.get_block_size() - 1};
+
+ ut_ad(!log_sys.is_pmem());
+
+ {
+ recv_sys.offset= size_t(recv_sys.lsn - log_sys.get_first_lsn()) &
+ block_size_1;
+ recv_sys.len= 0;
+ }
+
+ for (unsigned retry_count{0};;)
+ {
+ recv_sys_t::parse_mtr_result r;
+ size_t start_offset{recv_sys.offset};
+
+ {
+ {
+ auto source_offset=
+ log_sys.calc_lsn_offset(recv_sys.lsn + recv_sys.len -
+ recv_sys.offset);
+ source_offset&= ~block_size_1;
+ size_t size{log_sys.buf_size - recv_sys.len};
+ if (UNIV_UNLIKELY(source_offset + size > log_sys.file_size))
+ {
+ const size_t first{size_t(log_sys.file_size - source_offset)};
+ ut_ad(first <= log_sys.buf_size);
+ log_sys.log.read(source_offset, {log_sys.buf, first});
+ size-= first;
+ if (log_sys.START_OFFSET + size > source_offset)
+ size= size_t(source_offset - log_sys.START_OFFSET);
+ if (size)
+ log_sys.log.read(log_sys.START_OFFSET,
+ {log_sys.buf + first, size});
+ size+= first;
+ }
+ else
+ log_sys.log.read(source_offset, {log_sys.buf, size});
+ recv_sys.len= size;
+ }
+
+ if (log_sys.buf[recv_sys.offset] <= 1)
+ break;
+
+ if (recv_sys.parse_mtr<false>(false) == recv_sys_t::OK)
+ {
+ do
+ {
+ /* Set the sequence bit (the backed-up log will not wrap around) */
+ byte *seq= &log_sys.buf[recv_sys.offset - sequence_offset];
+ ut_ad(*seq == log_sys.get_sequence_bit(recv_sys.lsn -
+ sequence_offset));
+ *seq= 1;
+ }
+ while ((r= recv_sys.parse_mtr<false>(false)) == recv_sys_t::OK);
+
+ if (ds_write(dst_log_file, log_sys.buf + start_offset,
+ recv_sys.offset - start_offset))
+ {
+ msg("Error: write to ib_logfile0 failed");
+ return true;
+ }
+ else
+ {
+ const auto ofs= recv_sys.offset & ~block_size_1;
+ memmove_aligned<64>(log_sys.buf, log_sys.buf + ofs,
+ recv_sys.len - ofs);
+ recv_sys.len-= ofs;
+ recv_sys.offset&= block_size_1;
+ }
+
+ pthread_cond_broadcast(&scanned_lsn_cond);
+
+ if (r == recv_sys_t::GOT_EOF)
+ break;
+
+ if (recv_sys.offset < log_sys.get_block_size())
+ break;
+
+ if (xtrabackup_throttle && io_ticket-- < 0)
+ mysql_cond_wait(&wait_throttle, &recv_sys.mutex);
+
+ retry_count= 0;
+ continue;
+ }
+ else
+ {
+ recv_sys.len= recv_sys.offset & ~block_size_1;
+ if (retry_count == 100)
+ break;
+
+ mysql_mutex_unlock(&recv_sys.mutex);
+ if (!retry_count++)
+ msg("Retrying read of log at LSN=" LSN_PF, recv_sys.lsn);
+ my_sleep(1000);
+ }
+ }
+ mysql_mutex_lock(&recv_sys.mutex);
+ }
+
+ if (verbose)
+ msg(">> log scanned up to (" LSN_PF ")", recv_sys.lsn);
+ return false;
+}
+
+/**
+Wait until redo log copying thread processes given lsn
+*/
+void backup_wait_for_lsn(lsn_t lsn)
+{
+ mysql_mutex_lock(&recv_sys.mutex);
+ for (lsn_t last_lsn{recv_sys.lsn}; last_lsn < lsn; )
+ {
+ timespec abstime;
+ set_timespec(abstime, 5);
+ if (my_cond_timedwait(&scanned_lsn_cond, &recv_sys.mutex.m_mutex,
+ &abstime) &&
+ last_lsn == recv_sys.lsn)
+ die("Was only able to copy log from " LSN_PF " to " LSN_PF
+ ", not " LSN_PF "; try increasing innodb_log_file_size",
+ log_sys.next_checkpoint_lsn, last_lsn, lsn);
+ last_lsn= recv_sys.lsn;
+ }
+ mysql_mutex_unlock(&recv_sys.mutex);
+}
+
+extern lsn_t server_lsn_after_lock;
+
+static void log_copying_thread()
+{
+ my_thread_init();
+ mysql_mutex_lock(&recv_sys.mutex);
+ while (!xtrabackup_copy_logfile() &&
+ (!metadata_to_lsn || metadata_to_lsn > recv_sys.lsn))
+ {
+ timespec abstime;
+ set_timespec_nsec(abstime, 1000000ULL * xtrabackup_log_copy_interval);
+ mysql_cond_timedwait(&log_copying_stop, &recv_sys.mutex, &abstime);
+ }
+ log_copying_running= false;
+ mysql_mutex_unlock(&recv_sys.mutex);
+ my_thread_end();
+}
+
+/** whether io_watching_thread() is active; protected by recv_sys.mutex */
+static bool have_io_watching_thread;
+
+/* io throttle watching (rough) */
+static void io_watching_thread()
+{
+ my_thread_init();
+ /* currently, for --backup only */
+ ut_ad(xtrabackup_backup);
+
+ mysql_mutex_lock(&recv_sys.mutex);
+ ut_ad(have_io_watching_thread);
+
+ while (log_copying_running && !metadata_to_lsn)
+ {
+ timespec abstime;
+ set_timespec(abstime, 1);
+ mysql_cond_timedwait(&log_copying_stop, &recv_sys.mutex, &abstime);
+ io_ticket= xtrabackup_throttle;
+ mysql_cond_broadcast(&wait_throttle);
+ }
+
+ /* stop io throttle */
+ xtrabackup_throttle= 0;
+ have_io_watching_thread= false;
+ mysql_cond_broadcast(&wait_throttle);
+ mysql_mutex_unlock(&recv_sys.mutex);
+ my_thread_end();
+}
+
+#ifndef DBUG_OFF
+char *dbug_mariabackup_get_val(const char *event,
+ const fil_space_t::name_type key)
+{
+ char envvar[FN_REFLEN];
+ strncpy(envvar, event, sizeof envvar - 1);
+ envvar[(sizeof envvar) - 1] = '\0';
+
+ if (key.size() && key.size() + strlen(envvar) < (sizeof envvar) - 2)
+ {
+ strcat(envvar, "_");
+ strncat(envvar, key.data(), key.size());
+ if (char *slash= strchr(envvar, '/'))
+ *slash= '_';
+ }
+
+ char *val = getenv(envvar);
+ return val && *val ? val : nullptr;
+}
+
+/*
+In debug mode, execute SQL statement that was passed via environment.
+To use this facility, you need to
+
+1. Add code DBUG_EXECUTE_MARIABACKUP_EVENT("my_event_name", key););
+ to the code. key is usually a table name
+2. Set environment variable my_event_name_$key SQL statement you want to execute
+ when event occurs, in DBUG_EXECUTE_IF from above.
+ In mtr , you can set environment via 'let' statement (do not use $ as the first char
+ for the variable)
+3. start mariabackup with --dbug=+d,debug_mariabackup_events
+*/
+void dbug_mariabackup_event(const char *event,
+ const fil_space_t::name_type key)
+{
+ char *sql = dbug_mariabackup_get_val(event, key);
+ if (sql && *sql) {
+ msg("dbug_mariabackup_event : executing '%s'", sql);
+ xb_mysql_query(mysql_connection, sql, false, true);
+ }
+}
+#endif // DBUG_OFF
+
+/** Datafiles copying thread.*/
+static void data_copy_thread_func(data_thread_ctxt_t *ctxt) /* thread context */
+{
+ uint num = ctxt->num;
+ fil_node_t* node;
+ ut_ad(ctxt->corrupted_pages);
+
+ /*
+ Initialize mysys thread-specific memory so we can
+ use mysys functions in this thread.
+ */
+ my_thread_init();
+
+ while ((node = datafiles_iter_next(ctxt->it)) != NULL) {
+ DBUG_MARIABACKUP_EVENT("before_copy", node->space->name());
+ DBUG_EXECUTE_FOR_KEY("wait_innodb_redo_before_copy",
+ node->space->name(),
+ backup_wait_for_lsn(get_current_lsn(mysql_connection)););
+ /* copy the datafile */
+ if (xtrabackup_copy_datafile(ctxt->datasinks->m_data,
+ ctxt->datasinks->m_meta, node, num, NULL,
+ xtrabackup_incremental ? wf_incremental : wf_write_through,
+ *ctxt->corrupted_pages))
+ die("failed to copy datafile.");
+
+ DBUG_MARIABACKUP_EVENT("after_copy", node->space->name());
+ }
+
+ pthread_mutex_lock(ctxt->count_mutex);
+ (*ctxt->count)--;
+ pthread_mutex_unlock(ctxt->count_mutex);
+
+ my_thread_end();
+}
+
+/************************************************************************
+Initialize the appropriate datasink(s). Both local backups and streaming in the
+'xbstream' format allow parallel writes so we can write directly.
+
+Otherwise (i.e. when streaming in the 'tar' format) we need 2 separate datasinks
+for the data stream (and don't allow parallel data copying) and for metainfo
+files (including ib_logfile0). The second datasink writes to temporary
+files first, and then streams them in a serialized way when closed. */
+void Backup_datasinks::init()
+{
+ /* Start building out the pipelines from the terminus back */
+ if (xtrabackup_stream) {
+ /* All streaming goes to stdout */
+ m_data = m_meta = m_redo = ds_create(xtrabackup_target_dir,
+ DS_TYPE_STDOUT);
+ } else {
+ /* Local filesystem */
+ m_data = m_meta = m_redo = ds_create(xtrabackup_target_dir,
+ DS_TYPE_LOCAL);
+ }
+
+ /* Track it for destruction */
+ add_datasink_to_destroy(m_data);
+
+ /* Stream formatting */
+ if (xtrabackup_stream) {
+ ds_ctxt_t *ds;
+
+ ut_a(xtrabackup_stream_fmt == XB_STREAM_FMT_XBSTREAM);
+ ds = ds_create(xtrabackup_target_dir, DS_TYPE_XBSTREAM);
+
+ add_datasink_to_destroy(ds);
+
+ ds_set_pipe(ds, m_data);
+ m_data = ds;
+
+
+ m_redo = m_meta = m_data;
+ }
+
+ /* Compression for m_data and m_redo */
+ if (xtrabackup_compress) {
+ ds_ctxt_t *ds;
+
+ /* Use a 1 MB buffer for compressed output stream */
+ ds = ds_create(xtrabackup_target_dir, DS_TYPE_BUFFER);
+ ds_buffer_set_size(ds, 1024 * 1024);
+ add_datasink_to_destroy(ds);
+ ds_set_pipe(ds, m_data);
+ if (m_data != m_redo) {
+ m_data = ds;
+ ds = ds_create(xtrabackup_target_dir, DS_TYPE_BUFFER);
+ ds_buffer_set_size(ds, 1024 * 1024);
+ add_datasink_to_destroy(ds);
+ ds_set_pipe(ds, m_redo);
+ m_redo = ds;
+ } else {
+ m_redo = m_data = ds;
+ }
+
+ ds = ds_create(xtrabackup_target_dir, DS_TYPE_COMPRESS);
+ add_datasink_to_destroy(ds);
+ ds_set_pipe(ds, m_data);
+ if (m_data != m_redo) {
+ m_data = ds;
+ ds = ds_create(xtrabackup_target_dir, DS_TYPE_COMPRESS);
+ add_datasink_to_destroy(ds);
+ ds_set_pipe(ds, m_redo);
+ m_redo = ds;
+ } else {
+ m_redo = m_data = ds;
+ }
+ }
+}
+
+#define SRV_MAX_N_PENDING_SYNC_IOS 100
+
+/** Initialize the tablespace cache subsystem. */
+static
+void
+xb_fil_io_init()
+{
+ fil_system.create(srv_file_per_table ? 50000 : 5000);
+ fil_system.freeze_space_list = 1;
+ fil_system.space_id_reuse_warned = true;
+}
+
+/** Load tablespace.
+
+@param[in] dirname directory name of the tablespace to open
+@param[in] filname file name of the tablespece to open
+@param[in] is_remote true if tablespace file is .isl
+@param[in] skip_node_page0 true if we don't need to read node page 0. Otherwise
+node page0 will be read, and it's size and free pages limit
+will be set from page 0, what is neccessary for checking and fixing corrupted
+pages.
+@param[in] defer_space_id use the space id to create space object
+when there is deferred tablespace
+*/
+static void xb_load_single_table_tablespace(const char *dirname,
+ const char *filname,
+ bool is_remote,
+ bool skip_node_page0,
+ uint32_t defer_space_id)
+{
+ ut_ad(srv_operation == SRV_OPERATION_BACKUP
+ || srv_operation == SRV_OPERATION_RESTORE_DELTA
+ || srv_operation == SRV_OPERATION_RESTORE
+ || srv_operation == SRV_OPERATION_BACKUP_NO_DEFER);
+ /* Ignore .isl files on XtraBackup recovery. All tablespaces must be
+ local. */
+ if (is_remote && srv_operation == SRV_OPERATION_RESTORE_DELTA) {
+ return;
+ }
+ if (check_if_skip_table(filname)) {
+ return;
+ }
+
+ /* The name ends in .ibd or .isl;
+ try opening the file */
+ char* name;
+ size_t dirlen = dirname == NULL ? 0 : strlen(dirname);
+ size_t namelen = strlen(filname);
+ ulint pathlen = dirname == NULL ? namelen + 1: dirlen + namelen + 2;
+ dberr_t err;
+ fil_space_t *space;
+ bool defer = false;
+
+ name = static_cast<char*>(ut_malloc_nokey(pathlen));
+
+ if (dirname != NULL) {
+ snprintf(name, pathlen, "%s/%s", dirname, filname);
+ name[pathlen - 5] = 0;
+ } else {
+ snprintf(name, pathlen, "%s", filname);
+ name[pathlen - 5] = 0;
+ }
+
+ const fil_space_t::name_type n{name, pathlen - 5};
+ Datafile *file;
+
+ if (is_remote) {
+ RemoteDatafile* rf = new RemoteDatafile();
+ if (!rf->open_link_file(n)) {
+ die("Can't open datafile %s", name);
+ }
+ file = rf;
+ } else {
+ file = new Datafile();
+ file->make_filepath(".", n, IBD);
+ }
+
+ if (file->open_read_only(true) != DB_SUCCESS) {
+ die("Can't open datafile %s", name);
+ }
+
+ for (int i = 0; i < 10; i++) {
+ file->m_defer = false;
+ err = file->validate_first_page();
+
+ if (file->m_defer) {
+ if (defer_space_id) {
+ defer = true;
+ file->set_space_id(defer_space_id);
+ file->set_flags(FSP_FLAGS_PAGE_SSIZE());
+ err = DB_SUCCESS;
+ break;
+ }
+ } else if (err != DB_CORRUPTION) {
+ break;
+ }
+
+ my_sleep(1000);
+ }
+
+ if (!defer && file->m_defer) {
+ const char *file_path = file->filepath();
+ defer_space_names.insert(
+ filename_to_spacename(
+ file_path, strlen(file_path)));
+ delete file;
+ ut_free(name);
+ return;
+ }
+
+ bool is_empty_file = file->exists() && file->is_empty_file();
+
+ if (err == DB_SUCCESS && file->space_id() != SRV_TMP_SPACE_ID) {
+ mysql_mutex_lock(&fil_system.mutex);
+ space = fil_space_t::create(
+ file->space_id(), file->flags(),
+ FIL_TYPE_TABLESPACE, nullptr/* TODO: crypt_data */,
+ FIL_ENCRYPTION_DEFAULT,
+ file->handle() != OS_FILE_CLOSED);
+ ut_ad(space);
+ fil_node_t* node= space->add(
+ file->filepath(),
+ skip_node_page0 ? file->detach() : pfs_os_file_t(),
+ 0, false, false);
+ node->deferred= defer;
+ if (!space->read_page0())
+ err = DB_CANNOT_OPEN_FILE;
+ mysql_mutex_unlock(&fil_system.mutex);
+
+ if (srv_operation == SRV_OPERATION_RESTORE_DELTA
+ || xb_close_files) {
+ space->close();
+ }
+ }
+
+ delete file;
+
+ if (err != DB_SUCCESS && xtrabackup_backup && !is_empty_file) {
+ die("Failed to validate first page of the file %s, error %d",name, (int)err);
+ }
+
+ ut_free(name);
+}
+
+static void xb_load_single_table_tablespace(const std::string &space_name,
+ bool skip_node_page0,
+ uint32_t defer_space_id)
+{
+ std::string name(space_name);
+ bool is_remote= access((name + ".ibd").c_str(), R_OK) != 0;
+ const char *extension= is_remote ? ".isl" : ".ibd";
+ name.append(extension);
+ char buf[FN_REFLEN];
+ strncpy(buf, name.c_str(), sizeof buf - 1);
+ buf[sizeof buf - 1]= '\0';
+ const char *dbname= buf;
+ char *p= strchr(buf, '/');
+ if (!p)
+ die("Unexpected tablespace %s filename %s", space_name.c_str(),
+ name.c_str());
+ *p= 0;
+ const char *tablename= p + 1;
+ xb_load_single_table_tablespace(dbname, tablename, is_remote,
+ skip_node_page0, defer_space_id);
+}
+
+#ifdef _WIN32
+/**
+The os_file_opendir() function opens a directory stream corresponding to the
+directory named by the dirname argument. The directory stream is positioned
+at the first entry. In both Unix and Windows we automatically skip the '.'
+and '..' items at the start of the directory listing.
+@param[in] dirname directory name; it must not contain a trailing
+ '\' or '/'
+@return directory stream, NULL if error */
+os_file_dir_t os_file_opendir(const char *dirname)
+{
+ char path[OS_FILE_MAX_PATH + 3];
+
+ ut_a(strlen(dirname) < OS_FILE_MAX_PATH);
+
+ strcpy(path, dirname);
+ strcpy(path + strlen(path), "\\*");
+
+ /* Note that in Windows opening the 'directory stream' also retrieves
+ the first entry in the directory. Since it is '.', that is no problem,
+ as we will skip over the '.' and '..' entries anyway. */
+
+ LPWIN32_FIND_DATA lpFindFileData= static_cast<LPWIN32_FIND_DATA>
+ (ut_malloc_nokey(sizeof(WIN32_FIND_DATA)));
+ os_file_dir_t dir= FindFirstFile((LPCTSTR) path, lpFindFileData);
+ ut_free(lpFindFileData);
+
+ return dir;
+}
+#endif
+
+/** This function returns information of the next file in the directory. We jump
+over the '.' and '..' entries in the directory.
+@param[in] dirname directory name or path
+@param[in] dir directory stream
+@param[out] info buffer where the info is returned
+@return 0 if ok, -1 if error, 1 if at the end of the directory */
+int
+os_file_readdir_next_file(
+ const char* dirname,
+ os_file_dir_t dir,
+ os_file_stat_t* info)
+{
+#ifdef _WIN32
+ BOOL ret;
+ int status;
+ WIN32_FIND_DATA find_data;
+
+next_file:
+ ret = FindNextFile(dir, &find_data);
+
+ if (ret > 0) {
+
+ const char* name;
+
+ name = static_cast<const char*>(find_data.cFileName);
+
+ ut_a(strlen(name) < OS_FILE_MAX_PATH);
+
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
+
+ goto next_file;
+ }
+
+ strcpy(info->name, name);
+
+ info->size = find_data.nFileSizeHigh;
+ info->size <<= 32;
+ info->size |= find_data.nFileSizeLow;
+
+ if (find_data.dwFileAttributes
+ & FILE_ATTRIBUTE_REPARSE_POINT) {
+
+ /* TODO: test Windows symlinks */
+ /* TODO: MySQL has apparently its own symlink
+ implementation in Windows, dbname.sym can
+ redirect a database directory:
+ REFMAN "windows-symbolic-links.html" */
+
+ info->type = OS_FILE_TYPE_LINK;
+
+ } else if (find_data.dwFileAttributes
+ & FILE_ATTRIBUTE_DIRECTORY) {
+
+ info->type = OS_FILE_TYPE_DIR;
+
+ } else {
+
+ /* It is probably safest to assume that all other
+ file types are normal. Better to check them rather
+ than blindly skip them. */
+
+ info->type = OS_FILE_TYPE_FILE;
+ }
+
+ status = 0;
+
+ } else {
+ DWORD err = GetLastError();
+ if (err == ERROR_NO_MORE_FILES) {
+ status = 1;
+ } else {
+ msg("FindNextFile in %s returned %lu", dirname, err);
+ status = -1;
+ }
+ }
+
+ return(status);
+#else
+ struct dirent* ent;
+ char* full_path;
+ int ret;
+ struct stat statinfo;
+
+next_file:
+
+ ent = readdir(dir);
+
+ if (ent == NULL) {
+
+ return(1);
+ }
+
+ ut_a(strlen(ent->d_name) < OS_FILE_MAX_PATH);
+
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
+
+ goto next_file;
+ }
+
+ strcpy(info->name, ent->d_name);
+
+ full_path = static_cast<char*>(
+ ut_malloc_nokey(strlen(dirname) + strlen(ent->d_name) + 10));
+ if (!full_path) {
+ return -1;
+ }
+
+ sprintf(full_path, "%s/%s", dirname, ent->d_name);
+
+ ret = stat(full_path, &statinfo);
+
+ if (ret) {
+
+ if (errno == ENOENT) {
+ /* readdir() returned a file that does not exist,
+ it must have been deleted in the meantime. Do what
+ would have happened if the file was deleted before
+ readdir() - ignore and go to the next entry.
+ If this is the last entry then info->name will still
+ contain the name of the deleted file when this
+ function returns, but this is not an issue since the
+ caller shouldn't be looking at info when end of
+ directory is returned. */
+
+ ut_free(full_path);
+
+ goto next_file;
+ }
+
+ msg("stat %s: Got error %d", full_path, errno);
+
+ ut_free(full_path);
+
+ return(-1);
+ }
+
+ info->size = statinfo.st_size;
+
+ if (S_ISDIR(statinfo.st_mode)) {
+ info->type = OS_FILE_TYPE_DIR;
+ } else if (S_ISLNK(statinfo.st_mode)) {
+ info->type = OS_FILE_TYPE_LINK;
+ } else if (S_ISREG(statinfo.st_mode)) {
+ info->type = OS_FILE_TYPE_FILE;
+ } else {
+ info->type = OS_FILE_TYPE_UNKNOWN;
+ }
+
+ ut_free(full_path);
+ return(0);
+#endif
+}
+
+/***********************************************************************//**
+A fault-tolerant function that tries to read the next file name in the
+directory. We retry 100 times if os_file_readdir_next_file() returns -1. The
+idea is to read as much good data as we can and jump over bad data.
+@return 0 if ok, -1 if error even after the retries, 1 if at the end
+of the directory */
+int
+fil_file_readdir_next_file(
+/*=======================*/
+ dberr_t* err, /*!< out: this is set to DB_ERROR if an error
+ was encountered, otherwise not changed */
+ const char* dirname,/*!< in: directory name or path */
+ os_file_dir_t dir, /*!< in: directory stream */
+ os_file_stat_t* info) /*!< in/out: buffer where the
+ info is returned */
+{
+ for (ulint i = 0; i < 100; i++) {
+ int ret = os_file_readdir_next_file(dirname, dir, info);
+
+ if (ret != -1) {
+
+ return(ret);
+ }
+
+ ib::error() << "os_file_readdir_next_file() returned -1 in"
+ " directory " << dirname
+ << ", crash recovery may have failed"
+ " for some .ibd files!";
+
+ *err = DB_ERROR;
+ }
+
+ return(-1);
+}
+
+/** Scan the database directories under the MySQL datadir, looking for
+.ibd files and determining the space id in each of them.
+@return DB_SUCCESS or error number */
+
+static dberr_t enumerate_ibd_files(process_single_tablespace_func_t callback)
+{
+ int ret;
+ char* dbpath = NULL;
+ ulint dbpath_len = 100;
+ os_file_dir_t dir;
+ os_file_dir_t dbdir;
+ os_file_stat_t dbinfo;
+ os_file_stat_t fileinfo;
+ dberr_t err = DB_SUCCESS;
+ size_t len;
+
+ /* The datadir of MySQL is always the default directory of mysqld */
+
+ dir = os_file_opendir(fil_path_to_mysql_datadir);
+
+ if (UNIV_UNLIKELY(dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr))) {
+ msg("cannot open dir %s", fil_path_to_mysql_datadir);
+ return(DB_ERROR);
+ }
+
+ dbpath = static_cast<char*>(ut_malloc_nokey(dbpath_len));
+
+ /* Scan all directories under the datadir. They are the database
+ directories of MySQL. */
+
+ ret = fil_file_readdir_next_file(&err, fil_path_to_mysql_datadir, dir,
+ &dbinfo);
+ while (ret == 0) {
+
+ /* General tablespaces are always at the first level of the
+ data home dir */
+ if (dbinfo.type != OS_FILE_TYPE_FILE) {
+ const bool is_isl = ends_with(dbinfo.name, ".isl");
+ if (is_isl || ends_with(dbinfo.name,".ibd")) {
+ (*callback)(nullptr, dbinfo.name, is_isl,
+ false, 0);
+ }
+ }
+
+ if (dbinfo.type == OS_FILE_TYPE_FILE
+ || dbinfo.type == OS_FILE_TYPE_UNKNOWN) {
+
+ goto next_datadir_item;
+ }
+
+ /* We found a symlink or a directory; try opening it to see
+ if a symlink is a directory */
+
+ len = strlen(fil_path_to_mysql_datadir)
+ + strlen (dbinfo.name) + 2;
+ if (len > dbpath_len) {
+ dbpath_len = len;
+
+ if (dbpath) {
+ ut_free(dbpath);
+ }
+
+ dbpath = static_cast<char*>(ut_malloc_nokey(dbpath_len));
+ }
+ snprintf(dbpath, dbpath_len,
+ "%s/%s", fil_path_to_mysql_datadir, dbinfo.name);
+
+ if (check_if_skip_database_by_path(dbpath)) {
+ fprintf(stderr, "Skipping db: %s\n", dbpath);
+ goto next_datadir_item;
+ }
+
+ dbdir = os_file_opendir(dbpath);
+
+ if (UNIV_UNLIKELY(dbdir != IF_WIN(INVALID_HANDLE_VALUE,NULL))){
+ /* We found a database directory; loop through it,
+ looking for possible .ibd files in it */
+
+ for (ret = fil_file_readdir_next_file(&err, dbpath,
+ dbdir,
+ &fileinfo);
+ ret == 0;
+ ret = fil_file_readdir_next_file(&err, dbpath,
+ dbdir,
+ &fileinfo)) {
+ if (fileinfo.type == OS_FILE_TYPE_DIR) {
+ continue;
+ }
+
+ /* We found a symlink or a file */
+ if (strlen(fileinfo.name) > 4) {
+ bool is_isl= false;
+ if (ends_with(fileinfo.name, ".ibd") || ((is_isl = ends_with(fileinfo.name, ".isl"))))
+ (*callback)(dbinfo.name, fileinfo.name, is_isl, false, 0);
+ }
+ }
+
+ if (os_file_closedir_failed(dbdir)) {
+ fprintf(stderr, "InnoDB: Warning: could not"
+ " close database directory %s\n",
+ dbpath);
+
+ err = DB_ERROR;
+ }
+
+ } else {
+ msg("Can't open dir %s", dbpath);
+ err = DB_ERROR;
+ break;
+
+ }
+
+next_datadir_item:
+ ret = fil_file_readdir_next_file(&err,
+ fil_path_to_mysql_datadir,
+ dir, &dbinfo);
+ }
+
+ ut_free(dbpath);
+
+ if (os_file_closedir_failed(dir)) {
+ fprintf(stderr,
+ "InnoDB: Error: could not close MariaDB datadir\n");
+ return(DB_ERROR);
+ }
+
+ return(err);
+}
+
+/** Close all undo tablespaces while applying incremental delta */
+static void xb_close_undo_tablespaces()
+{
+ if (srv_undo_space_id_start == 0)
+ return;
+ for (uint32_t space_id= srv_undo_space_id_start;
+ space_id < srv_undo_space_id_start + srv_undo_tablespaces_open;
+ space_id++)
+ {
+ fil_space_t *space= fil_space_get(space_id);
+ ut_ad(space);
+ space->close();
+ }
+}
+
+/****************************************************************************
+Populates the tablespace memory cache by scanning for and opening data files.
+@returns DB_SUCCESS or error code.*/
+static
+dberr_t
+xb_load_tablespaces()
+{
+ bool create_new_db;
+ dberr_t err;
+ ulint sum_of_new_sizes;
+
+ ut_ad(srv_operation == SRV_OPERATION_BACKUP
+ || srv_operation == SRV_OPERATION_RESTORE_DELTA);
+
+ err = srv_sys_space.check_file_spec(&create_new_db, 0);
+
+ /* create_new_db must not be true. */
+ if (err != DB_SUCCESS || create_new_db) {
+ msg("Could not find data files at the specified datadir");
+ return(DB_ERROR);
+ }
+
+ for (int i= 0; i < 10; i++) {
+ err = srv_sys_space.open_or_create(false, false, &sum_of_new_sizes);
+ if (err == DB_PAGE_CORRUPTED || err == DB_CORRUPTION) {
+ my_sleep(1000);
+ }
+ else
+ break;
+ }
+
+ if (err != DB_SUCCESS) {
+ msg("Could not open data files.\n");
+ return(err);
+ }
+
+ /* Add separate undo tablespaces to fil_system */
+ err = srv_undo_tablespaces_init(false, nullptr);
+
+ if (err != DB_SUCCESS) {
+ return(err);
+ }
+
+ /* It is important to call xb_load_single_table_tablespaces() after
+ srv_undo_tablespaces_init(), because fil_is_user_tablespace_id() *
+ relies on srv_undo_tablespaces_open to be properly initialized */
+
+ msg("mariabackup: Generating a list of tablespaces");
+
+ err = enumerate_ibd_files(xb_load_single_table_tablespace);
+ if (err != DB_SUCCESS) {
+ return(err);
+ }
+
+ if (srv_operation == SRV_OPERATION_RESTORE_DELTA) {
+ xb_close_undo_tablespaces();
+ }
+
+ DBUG_MARIABACKUP_EVENT("after_load_tablespaces", {});
+ return(DB_SUCCESS);
+}
+
+/** Destroy the tablespace memory cache. */
+static void xb_data_files_close()
+{
+ fil_space_t::close_all();
+ buf_dblwr.close();
+}
+
+/***********************************************************************
+Allocate and initialize the entry for databases and tables filtering
+hash tables. If memory allocation is not successful, terminate program.
+@return pointer to the created entry. */
+static
+xb_filter_entry_t *
+xb_new_filter_entry(
+/*================*/
+ const char* name) /*!< in: name of table/database */
+{
+ xb_filter_entry_t *entry;
+ ulint namelen = strlen(name);
+
+ ut_a(namelen <= NAME_LEN * 2 + 1);
+
+ entry = static_cast<xb_filter_entry_t *>
+ (malloc(sizeof(xb_filter_entry_t) + namelen + 1));
+ memset(entry, '\0', sizeof(xb_filter_entry_t) + namelen + 1);
+ entry->name = ((char*)entry) + sizeof(xb_filter_entry_t);
+ strcpy(entry->name, name);
+ entry->has_tables = FALSE;
+
+ return entry;
+}
+
+/***********************************************************************
+Add entry to hash table. If hash table is NULL, allocate and initialize
+new hash table */
+static
+xb_filter_entry_t*
+xb_add_filter(
+ const char* name, /*!< in: name of table/database */
+ hash_table_t* hash) /*!< in/out: hash to insert into */
+{
+ xb_filter_entry_t* entry = xb_new_filter_entry(name);
+
+ if (UNIV_UNLIKELY(!hash->array)) {
+ hash->create(1000);
+ }
+ const ulint fold = my_crc32c(0, entry->name, strlen(entry->name));
+ HASH_INSERT(xb_filter_entry_t, name_hash, hash, fold, entry);
+ return entry;
+}
+
+/***********************************************************************
+Validate name of table or database. If name is invalid, program will
+be finished with error code */
+static
+void
+xb_validate_name(
+/*=============*/
+ const char* name, /*!< in: name */
+ size_t len) /*!< in: length of name */
+{
+ const char* p;
+
+ /* perform only basic validation. validate length and
+ path symbols */
+ if (len > NAME_LEN) {
+ die("name `%s` is too long.", name);
+ }
+ p = strpbrk(name, "/\\~");
+ if (p && (uint) (p - name) < NAME_LEN) {
+ die("name `%s` is not valid.", name);
+ }
+}
+
+/***********************************************************************
+Register new filter entry which can be either database
+or table name. */
+static
+void
+xb_register_filter_entry(
+/*=====================*/
+ const char* name, /*!< in: name */
+ hash_table_t* databases_hash,
+ hash_table_t* tables_hash
+ )
+{
+ const char* p;
+ size_t namelen;
+ xb_filter_entry_t* db_entry = NULL;
+
+ namelen = strlen(name);
+ if ((p = strchr(name, '.')) != NULL) {
+ char dbname[NAME_LEN + 1];
+
+ xb_validate_name(name, p - name);
+ xb_validate_name(p + 1, namelen - (p - name));
+
+ strncpy(dbname, name, p - name);
+ dbname[p - name] = 0;
+
+ if (databases_hash && databases_hash->array) {
+ const ulint fold = my_crc32c(0, dbname, p - name);
+ HASH_SEARCH(name_hash, databases_hash,
+ fold,
+ xb_filter_entry_t*,
+ db_entry, (void) 0,
+ !strcmp(db_entry->name, dbname));
+ }
+ if (!db_entry) {
+ db_entry = xb_add_filter(dbname, databases_hash);
+ }
+ db_entry->has_tables = TRUE;
+ xb_add_filter(name, tables_hash);
+ } else {
+ xb_validate_name(name, namelen);
+
+ xb_add_filter(name, databases_hash);
+ }
+}
+
+static
+void
+xb_register_include_filter_entry(
+ const char* name
+)
+{
+ xb_register_filter_entry(name, &databases_include_hash,
+ &tables_include_hash);
+}
+
+static
+void
+xb_register_exclude_filter_entry(
+ const char* name
+)
+{
+ xb_register_filter_entry(name, &databases_exclude_hash,
+ &tables_exclude_hash);
+}
+
+void register_ignore_db_dirs_filter(const char *name)
+{
+ xb_add_filter(name, &databases_exclude_hash);
+}
+
+/***********************************************************************
+Register new table for the filter. */
+static
+void
+xb_register_table(
+/*==============*/
+ const char* name) /*!< in: name of table */
+{
+ if (strchr(name, '.') == NULL) {
+ die("`%s` is not fully qualified name.", name);
+ }
+
+ xb_register_include_filter_entry(name);
+}
+
+static
+void
+xb_add_regex_to_list(
+ const char* regex, /*!< in: regex */
+ const char* error_context, /*!< in: context to error message */
+ regex_list_t* list) /*! in: list to put new regex to */
+{
+ char errbuf[100];
+ int ret;
+
+ regex_t compiled_regex;
+ ret = regcomp(&compiled_regex, regex, REG_EXTENDED);
+
+ if (ret != 0) {
+ regerror(ret, &compiled_regex, errbuf, sizeof(errbuf));
+ msg("mariabackup: error: %s regcomp(%s): %s",
+ error_context, regex, errbuf);
+ exit(EXIT_FAILURE);
+ }
+
+ list->push_back(compiled_regex);
+}
+
+/***********************************************************************
+Register new regex for the include filter. */
+static
+void
+xb_register_include_regex(
+/*==============*/
+ const char* regex) /*!< in: regex */
+{
+ xb_add_regex_to_list(regex, "tables", &regex_include_list);
+}
+
+/***********************************************************************
+Register new regex for the exclude filter. */
+static
+void
+xb_register_exclude_regex(
+/*==============*/
+ const char* regex) /*!< in: regex */
+{
+ xb_add_regex_to_list(regex, "tables-exclude", &regex_exclude_list);
+}
+
+typedef void (*insert_entry_func_t)(const char*);
+
+/* Scan string and load filter entries from it.
+@param[in] list string representing a list
+@param[in] delimiters delimiters of entries
+@param[in] ins callback to add entry */
+void xb_load_list_string(char *list, const char *delimiters,
+ insert_entry_func_t ins)
+{
+ char *p;
+ char *saveptr;
+
+ p= strtok_r(list, delimiters, &saveptr);
+ while (p)
+ {
+
+ ins(p);
+
+ p= strtok_r(NULL, delimiters, &saveptr);
+ }
+}
+
+/***********************************************************************
+Scan file and load filter entries from it. */
+static
+void
+xb_load_list_file(
+/*==============*/
+ const char* filename, /*!< in: name of file */
+ insert_entry_func_t ins) /*!< in: callback to add entry */
+{
+ char name_buf[NAME_LEN*2+2];
+ FILE* fp;
+
+ /* read and store the filenames */
+ fp = fopen(filename, "r");
+ if (!fp) {
+ die("Can't open %s",
+ filename);
+ }
+ while (fgets(name_buf, sizeof(name_buf), fp) != NULL) {
+ char* p = strchr(name_buf, '\n');
+ if (p) {
+ *p = '\0';
+ } else {
+ die("`%s...` name is too long", name_buf);
+ }
+
+ ins(name_buf);
+ }
+
+ fclose(fp);
+}
+
+
+static
+void
+xb_filters_init()
+{
+ if (xtrabackup_databases) {
+ xb_load_list_string(xtrabackup_databases, " \t",
+ xb_register_include_filter_entry);
+ }
+
+ if (xtrabackup_databases_file) {
+ xb_load_list_file(xtrabackup_databases_file,
+ xb_register_include_filter_entry);
+ }
+
+ if (xtrabackup_databases_exclude) {
+ xb_load_list_string(xtrabackup_databases_exclude, " \t",
+ xb_register_exclude_filter_entry);
+ }
+
+ if (xtrabackup_tables) {
+ xb_load_list_string(xtrabackup_tables, ",",
+ xb_register_include_regex);
+ }
+
+ if (xtrabackup_tables_file) {
+ xb_load_list_file(xtrabackup_tables_file, xb_register_table);
+ }
+
+ if (xtrabackup_tables_exclude) {
+ xb_load_list_string(xtrabackup_tables_exclude, ",",
+ xb_register_exclude_regex);
+ }
+}
+
+static
+void
+xb_filter_hash_free(hash_table_t* hash)
+{
+ ulint i;
+
+ /* free the hash elements */
+ for (i = 0; i < hash->n_cells; i++) {
+ xb_filter_entry_t* table;
+
+ table = static_cast<xb_filter_entry_t *>
+ (HASH_GET_FIRST(hash, i));
+
+ while (table) {
+ xb_filter_entry_t* prev_table = table;
+
+ table = static_cast<xb_filter_entry_t *>
+ (HASH_GET_NEXT(name_hash, prev_table));
+ const ulint fold = my_crc32c(0, prev_table->name,
+ strlen(prev_table->name));
+ HASH_DELETE(xb_filter_entry_t, name_hash, hash,
+ fold, prev_table);
+ free(prev_table);
+ }
+ }
+
+ hash->free();
+}
+
+static void xb_regex_list_free(regex_list_t* list)
+{
+ while (list->size() > 0) {
+ xb_regfree(&list->front());
+ list->pop_front();
+ }
+}
+
+/************************************************************************
+Destroy table filters for partial backup. */
+static
+void
+xb_filters_free()
+{
+ xb_regex_list_free(&regex_include_list);
+ xb_regex_list_free(&regex_exclude_list);
+
+ if (tables_include_hash.array) {
+ xb_filter_hash_free(&tables_include_hash);
+ }
+
+ if (tables_exclude_hash.array) {
+ xb_filter_hash_free(&tables_exclude_hash);
+ }
+
+ if (databases_include_hash.array) {
+ xb_filter_hash_free(&databases_include_hash);
+ }
+
+ if (databases_exclude_hash.array) {
+ xb_filter_hash_free(&databases_exclude_hash);
+ }
+}
+
+#ifdef RLIMIT_NOFILE
+/**
+Set the open files limit. Based on set_max_open_files().
+@param max_file_limit requested open files limit
+@return the resulting open files limit. May be less or more than the requested
+value. */
+static ulong xb_set_max_open_files(rlim_t max_file_limit)
+{
+ struct rlimit rlimit;
+ rlim_t old_cur;
+
+ if (getrlimit(RLIMIT_NOFILE, &rlimit)) {
+
+ goto end;
+ }
+
+ old_cur = rlimit.rlim_cur;
+
+ if (rlimit.rlim_cur == RLIM_INFINITY) {
+
+ rlimit.rlim_cur = max_file_limit;
+ }
+
+ if (rlimit.rlim_cur >= max_file_limit) {
+
+ max_file_limit = rlimit.rlim_cur;
+ goto end;
+ }
+
+ rlimit.rlim_cur = rlimit.rlim_max = max_file_limit;
+
+ if (setrlimit(RLIMIT_NOFILE, &rlimit)) {
+ /* Use original value */
+ max_file_limit = static_cast<ulong>(old_cur);
+ } else {
+
+ rlimit.rlim_cur = 0; /* Safety if next call fails */
+
+ (void) getrlimit(RLIMIT_NOFILE, &rlimit);
+
+ if (rlimit.rlim_cur) {
+
+ /* If call didn't fail */
+ max_file_limit = rlimit.rlim_cur;
+ }
+ }
+
+end:
+ return static_cast<ulong>(max_file_limit);
+}
+#else
+# define xb_set_max_open_files(x) 0UL
+#endif
+
+static void stop_backup_threads()
+{
+ mysql_cond_broadcast(&log_copying_stop);
+
+ if (log_copying_running || have_io_watching_thread)
+ {
+ mysql_mutex_unlock(&recv_sys.mutex);
+ fputs("mariabackup: Stopping log copying thread", stderr);
+ fflush(stderr);
+ mysql_mutex_lock(&recv_sys.mutex);
+ while (log_copying_running || have_io_watching_thread)
+ {
+ mysql_cond_broadcast(&log_copying_stop);
+ mysql_mutex_unlock(&recv_sys.mutex);
+ putc('.', stderr);
+ fflush(stderr);
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ mysql_mutex_lock(&recv_sys.mutex);
+ }
+ putc('\n', stderr);
+ }
+
+ mysql_cond_destroy(&log_copying_stop);
+}
+
+/** Implement the core of --backup
+@return whether the operation succeeded */
+bool Backup_datasinks::backup_low()
+{
+ mysql_mutex_lock(&recv_sys.mutex);
+ ut_ad(!metadata_to_lsn);
+
+ /* read the latest checkpoint lsn */
+ {
+ const lsn_t lsn = recv_sys.lsn;
+ if (recv_sys.find_checkpoint() == DB_SUCCESS
+ && log_sys.is_latest()) {
+ metadata_to_lsn = log_sys.next_checkpoint_lsn;
+ msg("mariabackup: The latest check point"
+ " (for incremental): '" LSN_PF "'",
+ metadata_to_lsn);
+ } else {
+ msg("Error: recv_sys.find_checkpoint() failed.");
+ }
+
+ recv_sys.lsn = lsn;
+ stop_backup_threads();
+ }
+
+ if (metadata_to_lsn && xtrabackup_copy_logfile()) {
+ mysql_mutex_unlock(&recv_sys.mutex);
+ ds_close(dst_log_file);
+ dst_log_file = NULL;
+ return false;
+ }
+
+ mysql_mutex_unlock(&recv_sys.mutex);
+
+ if (ds_close(dst_log_file) || !metadata_to_lsn) {
+ dst_log_file = NULL;
+ return false;
+ }
+
+ dst_log_file = NULL;
+
+ std::vector<uint32_t> failed_ids;
+ std::set_difference(
+ fail_undo_ids.begin(), fail_undo_ids.end(),
+ undo_trunc_ids.begin(), undo_trunc_ids.end(),
+ std::inserter(failed_ids, failed_ids.begin()));
+
+ for (uint32_t id : failed_ids) {
+ msg("mariabackup: Failed to read undo log "
+ "tablespace space id %d and there is no undo "
+ "tablespace truncation redo record.",
+ id);
+ }
+
+ if (failed_ids.size() > 0) {
+ return false;
+ }
+
+ if (!xtrabackup_incremental) {
+ safe_strcpy(metadata_type, sizeof(metadata_type),
+ "full-backuped");
+ metadata_from_lsn = 0;
+ } else {
+ safe_strcpy(metadata_type, sizeof(metadata_type),
+ "incremental");
+ metadata_from_lsn = incremental_lsn;
+ }
+ metadata_last_lsn = recv_sys.lsn;
+
+ if (!xtrabackup_stream_metadata(m_meta)) {
+ msg("Error: failed to stream metadata.");
+ return false;
+ }
+ if (xtrabackup_extra_lsndir) {
+ char filename[FN_REFLEN];
+
+ sprintf(filename, "%s/%s", xtrabackup_extra_lsndir,
+ XTRABACKUP_METADATA_FILENAME);
+ if (!xtrabackup_write_metadata(filename)) {
+ msg("Error: failed to write metadata "
+ "to '%s'.", filename);
+ return false;
+ }
+ sprintf(filename, "%s/%s", xtrabackup_extra_lsndir,
+ XTRABACKUP_INFO);
+ if (!write_xtrabackup_info(m_data,
+ mysql_connection, filename, false, false)) {
+ msg("Error: failed to write info "
+ "to '%s'.", filename);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/** Implement --backup
+@return whether the operation succeeded */
+static bool xtrabackup_backup_func()
+{
+ MY_STAT stat_info;
+ uint i;
+ uint count;
+ pthread_mutex_t count_mutex;
+ CorruptedPages corrupted_pages;
+ data_thread_ctxt_t *data_threads;
+ Backup_datasinks backup_datasinks;
+ pthread_cond_init(&scanned_lsn_cond, NULL);
+
+#ifdef USE_POSIX_FADVISE
+ msg("uses posix_fadvise().");
+#endif
+
+ /* cd to datadir */
+
+ if (my_setwd(mysql_real_data_home,MYF(MY_WME)))
+ {
+ msg("my_setwd() failed , %s", mysql_real_data_home);
+ return(false);
+ }
+ msg("cd to %s", mysql_real_data_home);
+ xb_plugin_backup_init(mysql_connection);
+ msg("open files limit requested %lu, set to %lu",
+ xb_open_files_limit,
+ xb_set_max_open_files(xb_open_files_limit));
+
+ mysql_data_home= mysql_data_home_buff;
+ mysql_data_home[0]=FN_CURLIB; // all paths are relative from here
+ mysql_data_home[1]=0;
+
+ srv_n_purge_threads = 1;
+ srv_read_only_mode = TRUE;
+
+ srv_operation = SRV_OPERATION_BACKUP;
+ log_file_op = backup_file_op;
+ undo_space_trunc = backup_undo_trunc;
+ first_page_init = backup_first_page_op;
+ metadata_to_lsn = 0;
+
+ /* initialize components */
+ if(innodb_init_param()) {
+fail:
+ if (log_copying_running) {
+ mysql_mutex_lock(&recv_sys.mutex);
+ metadata_to_lsn = 1;
+ stop_backup_threads();
+ mysql_mutex_unlock(&recv_sys.mutex);
+ }
+
+ log_file_op = NULL;
+ undo_space_trunc = NULL;
+ first_page_init = NULL;
+ if (dst_log_file) {
+ ds_close(dst_log_file);
+ dst_log_file = NULL;
+ }
+ if (fil_system.is_initialised()) {
+ innodb_shutdown();
+ }
+ return(false);
+ }
+
+ if (srv_buf_pool_size >= 1000 * 1024 * 1024) {
+ /* Here we still have srv_pool_size counted
+ in kilobytes (in 4.0 this was in bytes)
+ srv_boot() converts the value to
+ pages; if buffer pool is less than 1000 MB,
+ assume fewer threads. */
+ srv_max_n_threads = 50000;
+
+ } else if (srv_buf_pool_size >= 8 * 1024 * 1024) {
+
+ srv_max_n_threads = 10000;
+ } else {
+ srv_max_n_threads = 1000; /* saves several MB of memory,
+ especially in 64-bit
+ computers */
+ }
+ srv_thread_pool_init();
+ /* Reset the system variables in the recovery module. */
+ trx_pool_init();
+ recv_sys.create();
+
+ xb_filters_init();
+
+ xb_fil_io_init();
+
+ if (os_aio_init()) {
+ msg("Error: cannot initialize AIO subsystem");
+ goto fail;
+ }
+
+ if (!log_sys.create()) {
+ goto fail;
+ }
+ /* get current checkpoint_lsn */
+ {
+ mysql_mutex_lock(&recv_sys.mutex);
+
+ dberr_t err = recv_sys.find_checkpoint();
+
+ if (err != DB_SUCCESS) {
+ msg("Error: cannot read redo log header");
+ } else if (!log_sys.is_latest()) {
+ msg("Error: cannot process redo log before "
+ "MariaDB 10.8");
+ err = DB_ERROR;
+ } else {
+ recv_needed_recovery = true;
+ }
+ mysql_mutex_unlock(&recv_sys.mutex);
+
+ if (err != DB_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ /* create extra LSN dir if it does not exist. */
+ if (xtrabackup_extra_lsndir
+ &&!my_stat(xtrabackup_extra_lsndir,&stat_info,MYF(0))
+ && (my_mkdir(xtrabackup_extra_lsndir,0777,MYF(0)) < 0)) {
+ msg("Error: cannot mkdir %d: %s\n",
+ my_errno, xtrabackup_extra_lsndir);
+ goto fail;
+ }
+
+ /* create target dir if not exist */
+ if (!xtrabackup_stream_str && !my_stat(xtrabackup_target_dir,&stat_info,MYF(0))
+ && (my_mkdir(xtrabackup_target_dir,0777,MYF(0)) < 0)){
+ msg("Error: cannot mkdir %d: %s\n",
+ my_errno, xtrabackup_target_dir);
+ goto fail;
+ }
+
+ backup_datasinks.init();
+
+ if (!select_history()) {
+ goto fail;
+ }
+
+ /* open the log file */
+ memset(&stat_info, 0, sizeof(MY_STAT));
+ dst_log_file = ds_open(backup_datasinks.m_redo, LOG_FILE_NAME, &stat_info);
+ if (dst_log_file == NULL) {
+ msg("Error: failed to open the target stream for '%s'.",
+ LOG_FILE_NAME);
+ goto fail;
+ }
+
+ /* label it */
+ recv_sys.file_checkpoint = log_sys.next_checkpoint_lsn;
+ log_hdr_init();
+ /* Write log header*/
+ if (ds_write(dst_log_file, log_hdr_buf, 12288)) {
+ msg("error: write to logfile failed");
+ goto fail;
+ }
+ log_copying_running = true;
+
+ mysql_cond_init(0, &log_copying_stop, nullptr);
+
+ /* start io throttle */
+ if (xtrabackup_throttle) {
+ io_ticket = xtrabackup_throttle;
+ have_io_watching_thread = true;
+ mysql_cond_init(0, &wait_throttle, nullptr);
+ std::thread(io_watching_thread).detach();
+ }
+
+ /* Populate fil_system with tablespaces to copy */
+ if (dberr_t err = xb_load_tablespaces()) {
+ msg("merror: xb_load_tablespaces() failed with"
+ " error %s.", ut_strerr(err));
+ log_copying_running = false;
+ goto fail;
+ }
+
+ /* copy log file by current position */
+
+ mysql_mutex_lock(&recv_sys.mutex);
+ recv_sys.lsn = log_sys.next_checkpoint_lsn;
+
+ const bool log_copy_failed = xtrabackup_copy_logfile();
+
+ mysql_mutex_unlock(&recv_sys.mutex);
+
+ if (log_copy_failed) {
+ log_copying_running = false;
+ goto fail;
+ }
+
+ DBUG_MARIABACKUP_EVENT("before_innodb_log_copy_thread_started", {});
+
+ std::thread(log_copying_thread).detach();
+
+ /* FLUSH CHANGED_PAGE_BITMAPS call */
+ if (!flush_changed_page_bitmaps()) {
+ goto fail;
+ }
+
+ ut_a(xtrabackup_parallel > 0);
+
+ if (xtrabackup_parallel > 1) {
+ msg("mariabackup: Starting %u threads for parallel data "
+ "files transfer", xtrabackup_parallel);
+ }
+
+ if (opt_lock_ddl_per_table) {
+ mdl_lock_all();
+
+ DBUG_EXECUTE_IF("check_mdl_lock_works",
+ dbug_start_query_thread("ALTER TABLE test.t ADD COLUMN mdl_lock_column int",
+ "Waiting for table metadata lock", 0, 0););
+ }
+
+ datafiles_iter_t it;
+
+ /* Create data copying threads */
+ data_threads = (data_thread_ctxt_t *)
+ malloc(sizeof(data_thread_ctxt_t) * xtrabackup_parallel);
+ count = xtrabackup_parallel;
+ pthread_mutex_init(&count_mutex, NULL);
+
+ for (i = 0; i < (uint) xtrabackup_parallel; i++) {
+ data_threads[i].it = &it;
+ data_threads[i].num = i+1;
+ data_threads[i].count = &count;
+ data_threads[i].count_mutex = &count_mutex;
+ data_threads[i].corrupted_pages = &corrupted_pages;
+ data_threads[i].datasinks= &backup_datasinks;
+ std::thread(data_copy_thread_func, data_threads + i).detach();
+ }
+
+ /* Wait for threads to exit */
+ while (1) {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ pthread_mutex_lock(&count_mutex);
+ bool stop = count == 0;
+ pthread_mutex_unlock(&count_mutex);
+ if (stop) {
+ break;
+ }
+ }
+
+ pthread_mutex_destroy(&count_mutex);
+ free(data_threads);
+
+ DBUG_ASSERT(backup_datasinks.m_data);
+ DBUG_ASSERT(backup_datasinks.m_meta);
+ bool ok = backup_start(backup_datasinks.m_data,
+ backup_datasinks.m_meta, corrupted_pages);
+
+ if (ok) {
+ ok = backup_datasinks.backup_low();
+
+ backup_release();
+
+ DBUG_EXECUTE_IF("check_mdl_lock_works",
+ pthread_join(dbug_alter_thread, nullptr););
+
+ if (ok) {
+ backup_finish(backup_datasinks.m_data);
+ }
+ }
+
+ if (opt_log_innodb_page_corruption)
+ ok = corrupted_pages.print_to_file(backup_datasinks.m_data,
+ MB_CORRUPTED_PAGES_FILE);
+
+ if (!ok) {
+ goto fail;
+ }
+
+ if (changed_page_bitmap) {
+ xb_page_bitmap_deinit(changed_page_bitmap);
+ }
+ backup_datasinks.destroy();
+
+ msg("Redo log (from LSN " LSN_PF " to " LSN_PF ") was copied.",
+ log_sys.next_checkpoint_lsn, recv_sys.lsn);
+ xb_filters_free();
+
+ xb_data_files_close();
+
+ /* Make sure that the latest checkpoint was included */
+ if (metadata_to_lsn > recv_sys.lsn) {
+ msg("Error: failed to copy enough redo log ("
+ "LSN=" LSN_PF "; checkpoint LSN=" LSN_PF ").",
+ recv_sys.lsn, metadata_to_lsn);
+ goto fail;
+ }
+
+ innodb_shutdown();
+ log_file_op = NULL;
+ undo_space_trunc = NULL;
+ first_page_init = NULL;
+ pthread_cond_destroy(&scanned_lsn_cond);
+ if (!corrupted_pages.empty()) {
+ ut_ad(opt_log_innodb_page_corruption);
+ msg("Error: corrupted innodb pages are found and logged to "
+ MB_CORRUPTED_PAGES_FILE " file");
+ }
+ return(true);
+}
+
+
+/**
+This function handles DDL changes at the end of backup, under protection of
+FTWRL. This ensures consistent backup in presence of DDL.
+
+- New tables, that were created during backup, are now copied into backup.
+ Also, tablespaces with optimized (no redo loggin DDL) are re-copied into
+ backup. This tablespaces will get the extension ".new" in the backup
+
+- Tables that were renamed during backup, are marked as renamed
+ For these, file <old_name>.ren will be created.
+ The content of the file is the new tablespace name.
+
+- Tables that were deleted during backup, are marked as deleted
+ For these , an empty file <name>.del will be created
+
+ It is the responsibility of the prepare phase to deal with .new, .ren, and .del
+ files.
+*/
+void CorruptedPages::backup_fix_ddl(ds_ctxt *ds_data, ds_ctxt *ds_meta)
+{
+ std::set<std::string> dropped_tables;
+ std::map<std::string, std::string> renamed_tables;
+ space_id_to_name_t new_tables;
+
+ /* Disable further DDL on backed up tables (only needed for --no-lock).*/
+ mysql_mutex_lock(&recv_sys.mutex);
+ log_file_op = backup_file_op_fail;
+ mysql_mutex_unlock(&recv_sys.mutex);
+
+ DBUG_MARIABACKUP_EVENT("backup_fix_ddl", {});
+
+ for (space_id_to_name_t::iterator iter = ddl_tracker.tables_in_backup.begin();
+ iter != ddl_tracker.tables_in_backup.end();
+ iter++) {
+
+ const std::string name = iter->second;
+ uint32_t id = iter->first;
+
+ if (ddl_tracker.drops.find(id) != ddl_tracker.drops.end()) {
+ dropped_tables.insert(name);
+ drop_space(id);
+ continue;
+ }
+
+ if (ddl_tracker.id_to_name.find(id) == ddl_tracker.id_to_name.end()) {
+ continue;
+ }
+
+ /* tablespace was affected by DDL. */
+ const std::string new_name = ddl_tracker.id_to_name[id];
+ if (new_name != name) {
+ renamed_tables[name] = new_name;
+ if (opt_log_innodb_page_corruption)
+ rename_space(id, new_name);
+ }
+ }
+
+ /* Find tables that were created during backup (and not removed).*/
+ for(space_id_to_name_t::iterator iter = ddl_tracker.id_to_name.begin();
+ iter != ddl_tracker.id_to_name.end();
+ iter++) {
+
+ uint32_t id = iter->first;
+ std::string name = iter->second;
+
+ if (ddl_tracker.tables_in_backup.find(id) != ddl_tracker.tables_in_backup.end()) {
+ /* already processed above */
+ continue;
+ }
+
+ if (ddl_tracker.drops.find(id) == ddl_tracker.drops.end()
+ && ddl_tracker.deferred_tables.find(id)
+ == ddl_tracker.deferred_tables.end()) {
+ dropped_tables.erase(name);
+ new_tables[id] = name;
+ if (opt_log_innodb_page_corruption)
+ drop_space(id);
+ }
+ }
+
+ // Mark tablespaces for rename
+ for (std::map<std::string, std::string>::iterator iter = renamed_tables.begin();
+ iter != renamed_tables.end(); ++iter) {
+ const std::string old_name = iter->first;
+ std::string new_name = iter->second;
+ DBUG_ASSERT(ds_data);
+ ds_data->backup_file_printf((old_name + ".ren").c_str(), "%s", new_name.c_str());
+ }
+
+ // Mark tablespaces for drop
+ for (std::set<std::string>::iterator iter = dropped_tables.begin();
+ iter != dropped_tables.end();
+ iter++) {
+ const std::string name(*iter);
+ ds_data->backup_file_printf((name + ".del").c_str(), "%s", "");
+ }
+
+ // Load and copy new tables.
+ // Close all datanodes first, reload only new tables.
+ std::vector<fil_node_t *> all_nodes;
+ datafiles_iter_t it;
+ while (fil_node_t *node = datafiles_iter_next(&it)) {
+ all_nodes.push_back(node);
+ }
+ for (size_t i = 0; i < all_nodes.size(); i++) {
+ fil_node_t *n = all_nodes[i];
+ if (n->space->id == 0)
+ continue;
+ if (n->is_open()) {
+ mysql_mutex_lock(&fil_system.mutex);
+ n->close();
+ mysql_mutex_unlock(&fil_system.mutex);
+ }
+ fil_space_free(n->space->id, false);
+ }
+
+ DBUG_EXECUTE_IF("check_mdl_lock_works", DBUG_ASSERT(new_tables.size() == 0););
+
+ srv_operation = SRV_OPERATION_BACKUP_NO_DEFER;
+
+ /* Mariabackup detected the FILE_MODIFY or FILE_RENAME
+ for the deferred tablespace. So it needs to read the
+ tablespace again if innodb doesn't have page0 initialization
+ redo log for it */
+ for (space_id_to_name_t::iterator iter =
+ ddl_tracker.deferred_tables.begin();
+ iter != ddl_tracker.deferred_tables.end();
+ iter++) {
+ if (check_if_skip_table(iter->second.c_str())) {
+ continue;
+ }
+
+ if (first_page_init_ids.find(iter->first)
+ != first_page_init_ids.end()) {
+ new_tables[iter->first] = iter->second.c_str();
+ continue;
+ }
+
+ xb_load_single_table_tablespace(iter->second, false);
+ }
+
+ /* Mariabackup doesn't detect any FILE_OP for the deferred
+ tablespace. There is a possiblity that page0 could've
+ been corrupted persistently in the disk */
+ for (auto space_name: defer_space_names) {
+ if (!check_if_skip_table(space_name.c_str())) {
+ xb_load_single_table_tablespace(
+ space_name, false);
+ }
+ }
+
+ srv_operation = SRV_OPERATION_BACKUP;
+
+ for (const auto &t : new_tables) {
+ if (!check_if_skip_table(t.second.c_str())) {
+ xb_load_single_table_tablespace(t.second, false,
+ t.first);
+ }
+ }
+
+ datafiles_iter_t it2;
+
+ while (fil_node_t *node = datafiles_iter_next(&it2)) {
+ if (!fil_is_user_tablespace_id(node->space->id))
+ continue;
+ std::string dest_name= filename_to_spacename(
+ node->name, strlen(node->name));
+ dest_name.append(".new");
+
+ xtrabackup_copy_datafile(ds_data, ds_meta,
+ node, 0, dest_name.c_str(),
+ wf_write_through, *this);
+ }
+}
+
+/* ================= prepare ================= */
+
+/***********************************************************************
+Generates path to the meta file path from a given path to an incremental .delta
+by replacing trailing ".delta" with ".meta", or returns error if 'delta_path'
+does not end with the ".delta" character sequence.
+@return TRUE on success, FALSE on error. */
+static
+ibool
+get_meta_path(
+ const char *delta_path, /* in: path to a .delta file */
+ char *meta_path) /* out: path to the corresponding .meta
+ file */
+{
+ size_t len = strlen(delta_path);
+
+ if (len <= 6 || strcmp(delta_path + len - 6, ".delta")) {
+ return FALSE;
+ }
+ memcpy(meta_path, delta_path, len - 6);
+ strcpy(meta_path + len - 6, XB_DELTA_INFO_SUFFIX);
+
+ return TRUE;
+}
+
+/****************************************************************//**
+Create a new tablespace on disk and return the handle to its opened
+file. Code adopted from fil_create_new_single_table_tablespace with
+the main difference that only disk file is created without updating
+the InnoDB in-memory dictionary data structures.
+
+@return true on success, false on error. */
+static
+bool
+xb_space_create_file(
+/*==================*/
+ const char* path, /*!<in: path to tablespace */
+ uint32_t space_id, /*!<in: space id */
+ uint32_t flags, /*!<in: tablespace flags */
+ pfs_os_file_t* file) /*!<out: file handle */
+{
+ bool ret;
+
+ *file = os_file_create_simple_no_error_handling(
+ 0, path, OS_FILE_CREATE, OS_FILE_READ_WRITE, false, &ret);
+ if (!ret) {
+ msg("Can't create file %s", path);
+ return ret;
+ }
+
+ ret = os_file_set_size(path, *file,
+ FIL_IBD_FILE_INITIAL_SIZE
+ << srv_page_size_shift);
+ if (!ret) {
+ msg("mariabackup: cannot set size for file %s", path);
+ os_file_close(*file);
+ os_file_delete(0, path);
+ return ret;
+ }
+
+ return TRUE;
+}
+
+static fil_space_t* fil_space_get_by_name(const char* name)
+{
+ mysql_mutex_assert_owner(&fil_system.mutex);
+ for (fil_space_t &space : fil_system.space_list)
+ if (space.chain.start)
+ if (const char *str= strstr(space.chain.start->name, name))
+ if (!strcmp(str + strlen(name), ".ibd") &&
+ (str == space.chain.start->name ||
+ IF_WIN(str[-1] == '\\' ||,) str[-1] == '/'))
+ return &space;
+ return nullptr;
+}
+
+/***********************************************************************
+Searches for matching tablespace file for given .delta file and space_id
+in given directory. When matching tablespace found, renames it to match the
+name of .delta file. If there was a tablespace with matching name and
+mismatching ID, renames it to xtrabackup_tmp_#ID.ibd. If there was no
+matching file, creates a new tablespace.
+@return file handle of matched or created file */
+static
+pfs_os_file_t
+xb_delta_open_matching_space(
+ const char* dbname, /* in: path to destination database dir */
+ const char* name, /* in: name of delta file (without .delta) */
+ const xb_delta_info_t& info,
+ char* real_name, /* out: full path of destination file */
+ size_t real_name_len, /* out: buffer size for real_name */
+ bool* success) /* out: indicates error. true = success */
+{
+ char dest_dir[FN_REFLEN];
+ char dest_space_name[FN_REFLEN];
+ fil_space_t* fil_space;
+ pfs_os_file_t file;
+ xb_filter_entry_t* table;
+
+ ut_a(dbname != NULL ||
+ !fil_is_user_tablespace_id(info.space_id) ||
+ info.space_id == UINT32_MAX);
+
+ *success = false;
+
+ if (dbname) {
+ snprintf(dest_dir, FN_REFLEN, "%s/%s",
+ xtrabackup_target_dir, dbname);
+ snprintf(dest_space_name, FN_REFLEN, "%s/%s", dbname, name);
+ } else {
+ snprintf(dest_dir, FN_REFLEN, "%s", xtrabackup_target_dir);
+ snprintf(dest_space_name, FN_REFLEN, "%s", name);
+ }
+
+ snprintf(real_name, real_name_len,
+ "%s/%s",
+ xtrabackup_target_dir, dest_space_name);
+ /* Truncate ".ibd" */
+ dest_space_name[strlen(dest_space_name) - 4] = '\0';
+
+ /* Create the database directory if it doesn't exist yet */
+ if (!os_file_create_directory(dest_dir, FALSE)) {
+ msg("mariabackup: error: cannot create dir %s", dest_dir);
+ return file;
+ }
+
+ if (!info.space_id && fil_system.sys_space) {
+ fil_node_t *node
+ = UT_LIST_GET_FIRST(fil_system.sys_space->chain);
+ for (; node; node = UT_LIST_GET_NEXT(chain, node)) {
+ if (!strcmp(node->name, real_name)) {
+ break;
+ }
+ }
+ if (node && node->handle != OS_FILE_CLOSED) {
+ *success = true;
+ return node->handle;
+ }
+ msg("mariabackup: Cannot find file %s\n", real_name);
+ return OS_FILE_CLOSED;
+ }
+
+ mysql_mutex_lock(&recv_sys.mutex);
+ if (!fil_is_user_tablespace_id(info.space_id)) {
+found:
+ /* open the file and return its handle */
+
+ file = os_file_create_simple_no_error_handling(
+ 0, real_name,
+ OS_FILE_OPEN, OS_FILE_READ_WRITE, false, success);
+
+ if (!*success) {
+ msg("mariabackup: Cannot open file %s\n", real_name);
+ }
+exit:
+ mysql_mutex_unlock(&recv_sys.mutex);
+ return file;
+ }
+
+ const size_t len = strlen(dest_space_name);
+ /* remember space name for further reference */
+ table = static_cast<xb_filter_entry_t *>
+ (malloc(sizeof(xb_filter_entry_t) +
+ len + 1));
+
+ table->name = ((char*)table) + sizeof(xb_filter_entry_t);
+ memcpy(table->name, dest_space_name, len + 1);
+ const ulint fold = my_crc32c(0, dest_space_name, len);
+ HASH_INSERT(xb_filter_entry_t, name_hash, &inc_dir_tables_hash,
+ fold, table);
+
+ mysql_mutex_lock(&fil_system.mutex);
+ fil_space = fil_space_get_by_name(dest_space_name);
+ mysql_mutex_unlock(&fil_system.mutex);
+
+ if (fil_space != NULL) {
+ if (fil_space->id == info.space_id
+ || info.space_id == UINT32_MAX) {
+ /* we found matching space */
+ goto found;
+ } else {
+
+ char tmpname[FN_REFLEN];
+
+ snprintf(tmpname, FN_REFLEN, "%s/xtrabackup_tmp_#%u",
+ dbname, fil_space->id);
+
+ msg("mariabackup: Renaming %s to %s.ibd",
+ fil_space->chain.start->name, tmpname);
+
+ if (fil_space->rename(tmpname, false) != DB_SUCCESS) {
+ msg("mariabackup: Cannot rename %s to %s",
+ fil_space->chain.start->name, tmpname);
+ goto exit;
+ }
+ }
+ }
+
+ if (info.space_id == UINT32_MAX)
+ {
+ die("Can't handle DDL operation on tablespace "
+ "%s\n", dest_space_name);
+ }
+ mysql_mutex_lock(&fil_system.mutex);
+ fil_space = fil_space_get_by_id(info.space_id);
+ mysql_mutex_unlock(&fil_system.mutex);
+ if (fil_space != NULL) {
+ char tmpname[FN_REFLEN];
+
+ snprintf(tmpname, sizeof tmpname, "%s.ibd", dest_space_name);
+
+ msg("mariabackup: Renaming %s to %s",
+ fil_space->chain.start->name, tmpname);
+
+ if (fil_space->rename(tmpname, false) != DB_SUCCESS) {
+ msg("mariabackup: Cannot rename %s to %s",
+ fil_space->chain.start->name, tmpname);
+ goto exit;
+ }
+
+ goto found;
+ }
+
+ /* No matching space found. create the new one. */
+ const uint32_t flags = info.zip_size
+ ? get_bit_shift(info.page_size
+ >> (UNIV_ZIP_SIZE_SHIFT_MIN - 1))
+ << FSP_FLAGS_POS_ZIP_SSIZE
+ | FSP_FLAGS_MASK_POST_ANTELOPE
+ | FSP_FLAGS_MASK_ATOMIC_BLOBS
+ | (srv_page_size == UNIV_PAGE_SIZE_ORIG
+ ? 0
+ : get_bit_shift(srv_page_size
+ >> (UNIV_ZIP_SIZE_SHIFT_MIN - 1))
+ << FSP_FLAGS_POS_PAGE_SSIZE)
+ : FSP_FLAGS_PAGE_SSIZE();
+ ut_ad(fil_space_t::zip_size(flags) == info.zip_size);
+ ut_ad(fil_space_t::physical_size(flags) == info.page_size);
+
+ mysql_mutex_lock(&fil_system.mutex);
+ fil_space_t* space = fil_space_t::create(info.space_id, flags,
+ FIL_TYPE_TABLESPACE, 0,
+ FIL_ENCRYPTION_DEFAULT, true);
+ mysql_mutex_unlock(&fil_system.mutex);
+ if (space) {
+ *success = xb_space_create_file(real_name, info.space_id,
+ flags, &file);
+ } else {
+ msg("Can't create tablespace %s\n", dest_space_name);
+ }
+
+ goto exit;
+}
+
+/************************************************************************
+Applies a given .delta file to the corresponding data file.
+@return TRUE on success */
+static
+ibool
+xtrabackup_apply_delta(
+ const char* dirname, /* in: dir name of incremental */
+ const char* dbname, /* in: database name (ibdata: NULL) */
+ const char* filename, /* in: file name (not a path),
+ including the .delta extension */
+ void* /*data*/)
+{
+ pfs_os_file_t src_file;
+ pfs_os_file_t dst_file;
+ char src_path[FN_REFLEN];
+ char dst_path[FN_REFLEN];
+ char meta_path[FN_REFLEN];
+ char space_name[FN_REFLEN];
+ bool success;
+
+ ibool last_buffer = FALSE;
+ ulint page_in_buffer;
+ ulint incremental_buffers = 0;
+
+ xb_delta_info_t info(srv_page_size, 0, SRV_TMP_SPACE_ID);
+ ulint page_size;
+ ulint page_size_shift;
+ byte* incremental_buffer = NULL;
+
+ size_t offset;
+
+ ut_a(xtrabackup_incremental);
+
+ if (dbname) {
+ snprintf(src_path, sizeof(src_path), "%s/%s/%s",
+ dirname, dbname, filename);
+ snprintf(dst_path, sizeof(dst_path), "%s/%s/%s",
+ xtrabackup_real_target_dir, dbname, filename);
+ } else {
+ snprintf(src_path, sizeof(src_path), "%s/%s",
+ dirname, filename);
+ snprintf(dst_path, sizeof(dst_path), "%s/%s",
+ xtrabackup_real_target_dir, filename);
+ }
+ dst_path[strlen(dst_path) - 6] = '\0';
+
+ strncpy(space_name, filename, FN_REFLEN - 1);
+ space_name[FN_REFLEN - 1] = '\0';
+ space_name[strlen(space_name) - 6] = 0;
+
+ if (!get_meta_path(src_path, meta_path)) {
+ goto error;
+ }
+
+ if (!xb_read_delta_metadata(meta_path, &info)) {
+ goto error;
+ }
+
+ page_size = info.page_size;
+ page_size_shift = get_bit_shift(page_size);
+ msg("page size for %s is %zu bytes",
+ src_path, page_size);
+ if (page_size_shift < 10 ||
+ page_size_shift > UNIV_PAGE_SIZE_SHIFT_MAX) {
+ msg("error: invalid value of page_size "
+ "(%zu bytes) read from %s", page_size, meta_path);
+ goto error;
+ }
+
+ src_file = os_file_create_simple_no_error_handling(
+ 0, src_path,
+ OS_FILE_OPEN, OS_FILE_READ_WRITE, false, &success);
+ if (!success) {
+ os_file_get_last_error(TRUE);
+ msg("error: can't open %s", src_path);
+ goto error;
+ }
+
+ posix_fadvise(src_file, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ dst_file = xb_delta_open_matching_space(
+ dbname, space_name, info,
+ dst_path, sizeof(dst_path), &success);
+ if (!success) {
+ msg("error: can't open %s", dst_path);
+ goto error;
+ }
+
+ posix_fadvise(dst_file, 0, 0, POSIX_FADV_DONTNEED);
+
+ /* allocate buffer for incremental backup (4096 pages) */
+ incremental_buffer = static_cast<byte *>
+ (aligned_malloc(page_size / 4 * page_size, page_size));
+
+ msg("Applying %s to %s...", src_path, dst_path);
+
+ while (!last_buffer) {
+ ulint cluster_header;
+
+ /* read to buffer */
+ /* first block of block cluster */
+ offset = ((incremental_buffers * (page_size / 4))
+ << page_size_shift);
+ if (os_file_read(IORequestRead, src_file,
+ incremental_buffer, offset, page_size,
+ nullptr)
+ != DB_SUCCESS) {
+ goto error;
+ }
+
+ cluster_header = mach_read_from_4(incremental_buffer);
+ switch(cluster_header) {
+ case 0x78747261UL: /*"xtra"*/
+ break;
+ case 0x58545241UL: /*"XTRA"*/
+ last_buffer = TRUE;
+ break;
+ default:
+ msg("error: %s seems not "
+ ".delta file.", src_path);
+ goto error;
+ }
+
+ /* FIXME: If the .delta modifies FSP_SIZE on page 0,
+ extend the file to that size. */
+
+ for (page_in_buffer = 1; page_in_buffer < page_size / 4;
+ page_in_buffer++) {
+ if (mach_read_from_4(incremental_buffer + page_in_buffer * 4)
+ == 0xFFFFFFFFUL)
+ break;
+ }
+
+ ut_a(last_buffer || page_in_buffer == page_size / 4);
+
+ /* read whole of the cluster */
+ if (os_file_read(IORequestRead, src_file,
+ incremental_buffer,
+ offset, page_in_buffer * page_size, nullptr)
+ != DB_SUCCESS) {
+ goto error;
+ }
+
+ posix_fadvise(src_file, offset, page_in_buffer * page_size,
+ POSIX_FADV_DONTNEED);
+
+ for (page_in_buffer = 1; page_in_buffer < page_size / 4;
+ page_in_buffer++) {
+ ulint offset_on_page;
+
+ offset_on_page = mach_read_from_4(incremental_buffer + page_in_buffer * 4);
+
+ if (offset_on_page == 0xFFFFFFFFUL)
+ break;
+
+ uchar *buf = incremental_buffer + page_in_buffer * page_size;
+ const os_offset_t off = os_offset_t(offset_on_page)*page_size;
+
+ if (off == 0) {
+ /* Read tablespace size from page 0,
+ and extend the file to specified size.*/
+ os_offset_t n_pages = mach_read_from_4(
+ buf + FSP_HEADER_OFFSET + FSP_SIZE);
+ if (mach_read_from_4(buf
+ + FIL_PAGE_SPACE_ID)) {
+ if (!os_file_set_size(
+ dst_path, dst_file,
+ n_pages * page_size))
+ goto error;
+ } else if (fil_space_t* space
+ = fil_system.sys_space) {
+ /* The system tablespace can
+ consist of multiple files. The
+ first one has full tablespace
+ size in page 0, but only the last
+ file should be extended. */
+ fil_node_t* n = UT_LIST_GET_FIRST(
+ space->chain);
+ bool fail = !strcmp(n->name, dst_path)
+ && !fil_space_extend(
+ space, uint32_t(n_pages));
+ if (fail) goto error;
+ }
+ }
+
+ if (os_file_write(IORequestWrite,
+ dst_path, dst_file, buf, off,
+ page_size) != DB_SUCCESS) {
+ goto error;
+ }
+ }
+
+ /* Free file system buffer cache after the batch was written. */
+#ifdef __linux__
+ os_file_flush_func(dst_file);
+#endif
+ posix_fadvise(dst_file, 0, 0, POSIX_FADV_DONTNEED);
+
+
+ incremental_buffers++;
+ }
+
+ aligned_free(incremental_buffer);
+ if (src_file != OS_FILE_CLOSED) {
+ os_file_close(src_file);
+ os_file_delete(0,src_path);
+ }
+ if (dst_file != OS_FILE_CLOSED && info.space_id)
+ os_file_close(dst_file);
+ return TRUE;
+
+error:
+ aligned_free(incremental_buffer);
+ if (src_file != OS_FILE_CLOSED)
+ os_file_close(src_file);
+ if (dst_file != OS_FILE_CLOSED && info.space_id)
+ os_file_close(dst_file);
+ msg("Error: xtrabackup_apply_delta(): "
+ "failed to apply %s to %s.\n", src_path, dst_path);
+ return FALSE;
+}
+
+
+std::string change_extension(std::string filename, std::string new_ext) {
+ DBUG_ASSERT(new_ext.size() == 3);
+ std::string new_name(filename);
+ new_name.resize(new_name.size() - new_ext.size());
+ new_name.append(new_ext);
+ return new_name;
+}
+
+
+static void rename_file(const char *from,const char *to) {
+ msg("Renaming %s to %s\n", from, to);
+ if (my_rename(from, to, MY_WME)) {
+ die("Can't rename %s to %s errno %d", from, to, errno);
+ }
+}
+
+static void rename_file(const std::string& from, const std::string &to) {
+ rename_file(from.c_str(), to.c_str());
+}
+/************************************************************************
+Callback to handle datadir entry. Function of this type will be called
+for each entry which matches the mask by xb_process_datadir.
+@return should return TRUE on success */
+typedef ibool (*handle_datadir_entry_func_t)(
+/*=========================================*/
+ const char* data_home_dir, /*!<in: path to datadir */
+ const char* db_name, /*!<in: database name */
+ const char* file_name, /*!<in: file name with suffix */
+ void* arg); /*!<in: caller-provided data */
+
+/** Rename, and replace destination file, if exists */
+static void rename_force(const char *from, const char *to) {
+ if (access(to, R_OK) == 0) {
+ msg("Removing %s", to);
+ if (my_delete(to, MYF(MY_WME))) {
+ msg("Can't remove %s, errno %d", to, errno);
+ exit(EXIT_FAILURE);
+ }
+ }
+ rename_file(from,to);
+}
+
+
+/** During prepare phase, rename ".new" files, that were created in
+backup_fix_ddl() and backup_optimized_ddl_op(), to ".ibd". In the case of
+incremental backup, i.e. of arg argument is set, move ".new" files to
+destination directory and rename them to ".ibd", remove existing ".ibd.delta"
+and ".idb.meta" files in incremental directory to avoid applying delta to
+".ibd" file.
+
+@param[in] data_home_dir path to datadir
+@param[in] db_name database name
+@param[in] file_name file name with suffix
+@param[in] arg destination path, used in incremental backup to notify, that
+*.new file must be moved to destibation directory
+
+@return true */
+static ibool prepare_handle_new_files(const char *data_home_dir,
+ const char *db_name,
+ const char *file_name, void *arg)
+{
+ const char *dest_dir = static_cast<const char *>(arg);
+ std::string src_path = std::string(data_home_dir) + '/' + std::string(db_name) + '/';
+ /* Copy "*.new" files from incremental to base dir for incremental backup */
+ std::string dest_path=
+ dest_dir ? std::string(dest_dir) + '/' + std::string(db_name) +
+ '/' : src_path;
+
+ /*
+ A CREATE DATABASE could have happened during the base mariabackup run.
+ In case if the current table file (e.g. `t1.new`) is from such
+ a new database, the database directory may not exist yet in
+ the base backup directory. Let's make sure to check if the directory
+ exists (and create if needed).
+ */
+ if (!directory_exists(dest_path.c_str(), true/*create if not exists*/))
+ return FALSE;
+ src_path+= file_name;
+ dest_path+= file_name;
+
+ size_t index = dest_path.find(".new");
+ DBUG_ASSERT(index != std::string::npos);
+ dest_path.replace(index, strlen(".ibd"), ".ibd");
+ rename_force(src_path.c_str(),dest_path.c_str());
+
+ if (dest_dir) {
+ /* remove delta and meta files to avoid delta applying for new file */
+ index = src_path.find(".new");
+ DBUG_ASSERT(index != std::string::npos);
+ src_path.replace(index, std::string::npos, ".ibd.delta");
+ if (access(src_path.c_str(), R_OK) == 0) {
+ msg("Removing %s", src_path.c_str());
+ if (my_delete(src_path.c_str(), MYF(MY_WME)))
+ die("Can't remove %s, errno %d", src_path.c_str(), errno);
+ }
+ src_path.replace(index, std::string::npos, ".ibd.meta");
+ if (access(src_path.c_str(), R_OK) == 0) {
+ msg("Removing %s", src_path.c_str());
+ if (my_delete(src_path.c_str(), MYF(MY_WME)))
+ die("Can't remove %s, errno %d", src_path.c_str(), errno);
+ }
+
+ /* add table name to the container to avoid it's deletion at the end of
+ prepare */
+ std::string table_name = std::string(db_name) + '/'
+ + std::string(file_name, file_name + strlen(file_name) - strlen(".new"));
+ xb_filter_entry_t *table = static_cast<xb_filter_entry_t *>
+ (malloc(sizeof(xb_filter_entry_t) + table_name.size() + 1));
+ table->name = ((char*)table) + sizeof(xb_filter_entry_t);
+ strcpy(table->name, table_name.c_str());
+ const ulint fold = my_crc32c(0, table->name,
+ table_name.size());
+ HASH_INSERT(xb_filter_entry_t, name_hash, &inc_dir_tables_hash,
+ fold, table);
+ }
+
+ return TRUE;
+}
+
+/************************************************************************
+Callback to handle datadir entry. Deletes entry if it has no matching
+fil_space in fil_system directory.
+@return FALSE if delete attempt was unsuccessful */
+static
+ibool
+rm_if_not_found(
+ const char* data_home_dir, /*!<in: path to datadir */
+ const char* db_name, /*!<in: database name */
+ const char* file_name, /*!<in: file name with suffix */
+ void* arg __attribute__((unused)))
+{
+ char name[FN_REFLEN];
+ xb_filter_entry_t* table;
+
+ snprintf(name, FN_REFLEN, "%s/%s", db_name, file_name);
+ /* Truncate ".ibd" */
+ const size_t len = strlen(name) - 4;
+ name[len] = '\0';
+ const ulint fold = my_crc32c(0, name, len);
+
+ HASH_SEARCH(name_hash, &inc_dir_tables_hash, fold,
+ xb_filter_entry_t*,
+ table, (void) 0,
+ !strcmp(table->name, name));
+
+ if (!table) {
+ snprintf(name, FN_REFLEN, "%s/%s/%s", data_home_dir,
+ db_name, file_name);
+ return os_file_delete(0, name);
+ }
+
+ return(TRUE);
+}
+
+/** Function enumerates files in datadir (provided by path) which are matched
+by provided suffix. For each entry callback is called.
+
+@param[in] path datadir path
+@param[in] suffix suffix to match against
+@param[in] func callback
+@param[in] func_arg arguments for the above callback
+
+@return FALSE if callback for some entry returned FALSE */
+static ibool xb_process_datadir(const char *path, const char *suffix,
+ handle_datadir_entry_func_t func,
+ void *func_arg = NULL)
+{
+ ulint ret;
+ char dbpath[OS_FILE_MAX_PATH+2];
+ os_file_dir_t dir;
+ os_file_dir_t dbdir;
+ os_file_stat_t dbinfo;
+ os_file_stat_t fileinfo;
+ ulint suffix_len;
+ dberr_t err = DB_SUCCESS;
+ static char current_dir[2];
+
+ current_dir[0] = FN_CURLIB;
+ current_dir[1] = 0;
+ srv_data_home = current_dir;
+
+ suffix_len = strlen(suffix);
+
+ /* datafile */
+ dbdir = os_file_opendir(path);
+ if (UNIV_UNLIKELY(dbdir != IF_WIN(INVALID_HANDLE_VALUE, nullptr))) {
+ ret = fil_file_readdir_next_file(&err, path, dbdir, &fileinfo);
+ while (ret == 0) {
+ if (fileinfo.type == OS_FILE_TYPE_DIR) {
+ goto next_file_item_1;
+ }
+
+ if (strlen(fileinfo.name) > suffix_len
+ && 0 == strcmp(fileinfo.name +
+ strlen(fileinfo.name) - suffix_len,
+ suffix)) {
+ if (!func(
+ path, NULL,
+ fileinfo.name, func_arg))
+ {
+ os_file_closedir(dbdir);
+ return(FALSE);
+ }
+ }
+next_file_item_1:
+ ret = fil_file_readdir_next_file(&err,
+ path, dbdir,
+ &fileinfo);
+ }
+
+ os_file_closedir(dbdir);
+ } else {
+ msg("Can't open dir %s", path);
+ }
+
+ /* single table tablespaces */
+ dir = os_file_opendir(path);
+
+ if (UNIV_UNLIKELY(dbdir == IF_WIN(INVALID_HANDLE_VALUE, nullptr))) {
+ msg("Can't open dir %s", path);
+ return TRUE;
+ }
+
+ ret = fil_file_readdir_next_file(&err, path, dir, &dbinfo);
+ while (ret == 0) {
+ if (dbinfo.type == OS_FILE_TYPE_FILE
+ || dbinfo.type == OS_FILE_TYPE_UNKNOWN) {
+
+ goto next_datadir_item;
+ }
+
+ snprintf(dbpath, sizeof(dbpath), "%.*s/%.*s",
+ OS_FILE_MAX_PATH/2-1,
+ path,
+ OS_FILE_MAX_PATH/2-1,
+ dbinfo.name);
+
+ dbdir = os_file_opendir(dbpath);
+
+ if (dbdir != IF_WIN(INVALID_HANDLE_VALUE, nullptr)) {
+ ret = fil_file_readdir_next_file(&err, dbpath, dbdir,
+ &fileinfo);
+ while (ret == 0) {
+
+ if (fileinfo.type == OS_FILE_TYPE_DIR) {
+
+ goto next_file_item_2;
+ }
+
+ if (strlen(fileinfo.name) > suffix_len
+ && 0 == strcmp(fileinfo.name +
+ strlen(fileinfo.name) -
+ suffix_len,
+ suffix)) {
+ /* The name ends in suffix; process
+ the file */
+ if (!func(
+ path,
+ dbinfo.name,
+ fileinfo.name, func_arg))
+ {
+ os_file_closedir(dbdir);
+ os_file_closedir(dir);
+ return(FALSE);
+ }
+ }
+next_file_item_2:
+ ret = fil_file_readdir_next_file(&err,
+ dbpath, dbdir,
+ &fileinfo);
+ }
+
+ os_file_closedir(dbdir);
+ }
+next_datadir_item:
+ ret = fil_file_readdir_next_file(&err,
+ path,
+ dir, &dbinfo);
+ }
+
+ os_file_closedir(dir);
+
+ return(TRUE);
+}
+
+/************************************************************************
+Applies all .delta files from incremental_dir to the full backup.
+@return TRUE on success. */
+static
+ibool
+xtrabackup_apply_deltas()
+{
+ return xb_process_datadir(xtrabackup_incremental_dir, ".delta",
+ xtrabackup_apply_delta);
+}
+
+
+static
+void
+innodb_free_param()
+{
+ srv_sys_space.shutdown();
+ free_tmpdir(&mysql_tmpdir_list);
+}
+
+
+/** Check if file exists*/
+static bool file_exists(std::string name)
+{
+ return access(name.c_str(), R_OK) == 0 ;
+}
+
+/** Read file content into STL string */
+static std::string read_file_as_string(const std::string file) {
+ char content[FN_REFLEN];
+ FILE *f = fopen(file.c_str(), "r");
+ if (!f) {
+ msg("Can not open %s", file.c_str());
+ }
+ size_t len = fread(content, 1, FN_REFLEN, f);
+ fclose(f);
+ return std::string(content, len);
+}
+
+/** Delete file- Provide verbose diagnostics and exit, if operation fails. */
+static void delete_file(const std::string& file, bool if_exists = false) {
+ if (if_exists && !file_exists(file))
+ return;
+ if (my_delete(file.c_str(), MYF(MY_WME))) {
+ die("Can't remove %s, errno %d", file.c_str(), errno);
+ }
+}
+
+/**
+Rename tablespace during prepare.
+Backup in its end phase may generate some .ren files, recording
+tablespaces that should be renamed in --prepare.
+*/
+static void rename_table_in_prepare(const std::string &datadir, const std::string& from , const std::string& to,
+ const char *extension=0) {
+ if (!extension) {
+ static const char *extensions_nonincremental[] = { ".ibd", 0 };
+ static const char *extensions_incremental[] = { ".ibd.delta", ".ibd.meta", 0 };
+ const char **extensions = xtrabackup_incremental_dir ?
+ extensions_incremental : extensions_nonincremental;
+ for (size_t i = 0; extensions[i]; i++) {
+ rename_table_in_prepare(datadir, from, to, extensions[i]);
+ }
+ return;
+ }
+ std::string src = std::string(datadir) + "/" + from + extension;
+ std::string dest = std::string(datadir) + "/" + to + extension;
+ std::string ren2, tmp;
+ if (file_exists(dest)) {
+ ren2= std::string(datadir) + "/" + to + ".ren";
+ if (!file_exists(ren2)) {
+ die("ERROR : File %s was not found, but expected during rename processing\n", ren2.c_str());
+ }
+ tmp = to + "#";
+ rename_table_in_prepare(datadir, to, tmp);
+ }
+ rename_file(src, dest);
+ if (ren2.size()) {
+ // Make sure the temp. renamed file is processed.
+ std::string to2 = read_file_as_string(ren2);
+ rename_table_in_prepare(datadir, tmp, to2);
+ delete_file(ren2);
+ }
+}
+
+static ibool prepare_handle_ren_files(const char *datadir, const char *db, const char *filename, void *) {
+
+ std::string ren_file = std::string(datadir) + "/" + db + "/" + filename;
+ if (!file_exists(ren_file))
+ return TRUE;
+
+ std::string to = read_file_as_string(ren_file);
+ std::string source_space_name = std::string(db) + "/" + filename;
+ source_space_name.resize(source_space_name.size() - 4); // remove extension
+
+ rename_table_in_prepare(datadir, source_space_name.c_str(), to.c_str());
+ delete_file(ren_file);
+ return TRUE;
+}
+
+/* Remove tablespaces during backup, based on */
+static ibool prepare_handle_del_files(const char *datadir, const char *db, const char *filename, void *) {
+ std::string del_file = std::string(datadir) + "/" + db + "/" + filename;
+ std::string path(del_file);
+ path.resize(path.size() - 4); // remove extension;
+ if (xtrabackup_incremental) {
+ delete_file(path + ".ibd.delta", true);
+ delete_file(path + ".ibd.meta", true);
+ }
+ else {
+ delete_file(path + ".ibd", true);
+ }
+ delete_file(del_file);
+ return TRUE;
+}
+
+/** Implement --prepare
+@return whether the operation succeeded */
+static bool xtrabackup_prepare_func(char** argv)
+{
+ CorruptedPages corrupted_pages;
+ char metadata_path[FN_REFLEN];
+
+ /* cd to target-dir */
+
+ if (my_setwd(xtrabackup_real_target_dir,MYF(MY_WME)))
+ {
+ msg("can't my_setwd %s", xtrabackup_real_target_dir);
+ return(false);
+ }
+ msg("cd to %s", xtrabackup_real_target_dir);
+
+ fil_path_to_mysql_datadir = ".";
+
+ ut_ad(xtrabackup_incremental == xtrabackup_incremental_dir);
+ if (xtrabackup_incremental)
+ inc_dir_tables_hash.create(1000);
+
+ msg("open files limit requested %u, set to %lu",
+ (uint) xb_open_files_limit,
+ xb_set_max_open_files(xb_open_files_limit));
+
+ /* Fix DDL for prepare. Process .del,.ren, and .new files.
+ The order in which files are processed, is important
+ (see MDEV-18185, MDEV-18201)
+ */
+ xb_process_datadir(xtrabackup_incremental_dir ? xtrabackup_incremental_dir : ".",
+ ".del", prepare_handle_del_files);
+ xb_process_datadir(xtrabackup_incremental_dir? xtrabackup_incremental_dir:".",
+ ".ren", prepare_handle_ren_files);
+ if (xtrabackup_incremental_dir) {
+ xb_process_datadir(xtrabackup_incremental_dir, ".new.meta", prepare_handle_new_files);
+ xb_process_datadir(xtrabackup_incremental_dir, ".new.delta", prepare_handle_new_files);
+ xb_process_datadir(xtrabackup_incremental_dir, ".new",
+ prepare_handle_new_files, (void *)".");
+ }
+ else {
+ xb_process_datadir(".", ".new", prepare_handle_new_files);
+ }
+
+ int argc; for (argc = 0; argv[argc]; argc++) {}
+ xb_plugin_prepare_init(argc, argv, xtrabackup_incremental_dir);
+
+ xtrabackup_target_dir= mysql_data_home_buff;
+ xtrabackup_target_dir[0]=FN_CURLIB; // all paths are relative from here
+ xtrabackup_target_dir[1]=0;
+ const lsn_t target_lsn = xtrabackup_incremental
+ ? incremental_to_lsn : metadata_to_lsn;
+
+ /*
+ read metadata of target
+ */
+ sprintf(metadata_path, "%s/%s", xtrabackup_target_dir,
+ XTRABACKUP_METADATA_FILENAME);
+
+ if (!xtrabackup_read_metadata(metadata_path)) {
+ msg("Error: failed to read metadata from '%s'\n",
+ metadata_path);
+ return(false);
+ }
+
+ if (!strcmp(metadata_type, "full-backuped")) {
+ if (xtrabackup_incremental) {
+ msg("error: applying incremental backup "
+ "needs a prepared target.");
+ return(false);
+ }
+ msg("This target seems to be not prepared yet.");
+ } else if (!strcmp(metadata_type, "log-applied")) {
+ msg("This target seems to be already prepared.");
+ } else {
+ msg("This target does not have correct metadata.");
+ return(false);
+ }
+
+ bool ok = !xtrabackup_incremental
+ || metadata_to_lsn == incremental_lsn;
+ if (!ok) {
+ msg("error: This incremental backup seems "
+ "not to be proper for the target. Check 'to_lsn' of the target and "
+ "'from_lsn' of the incremental.");
+ return(false);
+ }
+
+ srv_max_n_threads = 1000;
+ srv_n_purge_threads = 1;
+
+ xb_filters_init();
+
+ srv_log_group_home_dir = NULL;
+
+ if (xtrabackup_incremental) {
+ srv_operation = SRV_OPERATION_RESTORE_DELTA;
+
+ if (innodb_init_param()) {
+error:
+ ok = false;
+ goto cleanup;
+ }
+
+ recv_sys.create();
+ if (!log_sys.create()) {
+ goto error;
+ }
+ recv_sys.recovery_on = true;
+
+ xb_fil_io_init();
+ if (dberr_t err = xb_load_tablespaces()) {
+ msg("mariabackup: error: xb_data_files_init() failed "
+ "with error %s\n", ut_strerr(err));
+ goto error;
+ }
+
+ ok = fil_system.sys_space->open(false)
+ && xtrabackup_apply_deltas();
+
+ xb_data_files_close();
+
+ if (ok) {
+ /* Cleanup datadir from tablespaces deleted
+ between full and incremental backups */
+
+ xb_process_datadir("./", ".ibd", rm_if_not_found);
+ }
+
+ xb_filter_hash_free(&inc_dir_tables_hash);
+
+ fil_system.close();
+ innodb_free_param();
+ log_sys.close();
+ if (!ok) goto cleanup;
+ }
+
+ srv_operation = xtrabackup_export
+ ? SRV_OPERATION_RESTORE_EXPORT : SRV_OPERATION_RESTORE;
+
+ if (innodb_init_param()) {
+ goto error;
+ }
+
+ fil_system.freeze_space_list = 0;
+
+ msg("Starting InnoDB instance for recovery.");
+
+ msg("mariabackup: Using %lld bytes for buffer pool "
+ "(set by --use-memory parameter)", xtrabackup_use_memory);
+
+ srv_max_buf_pool_modified_pct = (double)max_buf_pool_modified_pct;
+
+ if (srv_max_dirty_pages_pct_lwm > srv_max_buf_pool_modified_pct) {
+ srv_max_dirty_pages_pct_lwm = srv_max_buf_pool_modified_pct;
+ }
+
+ if (innodb_init()) {
+ goto error;
+ }
+
+ ut_ad(!fil_system.freeze_space_list);
+
+ corrupted_pages.read_from_file(MB_CORRUPTED_PAGES_FILE);
+ if (xtrabackup_incremental)
+ {
+ char inc_filename[FN_REFLEN];
+ sprintf(inc_filename, "%s/%s", xtrabackup_incremental_dir,
+ MB_CORRUPTED_PAGES_FILE);
+ corrupted_pages.read_from_file(inc_filename);
+ }
+ if (!corrupted_pages.empty())
+ corrupted_pages.zero_out_free_pages();
+ if (corrupted_pages.empty())
+ {
+ if (!xtrabackup_incremental && unlink(MB_CORRUPTED_PAGES_FILE) &&
+ errno != ENOENT)
+ {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), errno);
+ die("Error: unlink %s failed: %s", MB_CORRUPTED_PAGES_FILE,
+ errbuf);
+ }
+ }
+ else
+ corrupted_pages.print_to_file(NULL, MB_CORRUPTED_PAGES_FILE);
+
+ if (ok) {
+ msg("Last binlog file %s, position %lld",
+ trx_sys.recovered_binlog_filename,
+ longlong(trx_sys.recovered_binlog_offset));
+ }
+
+ /* Check whether the log is applied enough or not. */
+ if (recv_sys.lsn && recv_sys.lsn < target_lsn) {
+ msg("mariabackup: error: "
+ "The log was only applied up to LSN " LSN_PF
+ ", instead of " LSN_PF, recv_sys.lsn, target_lsn);
+ ok = false;
+ }
+#ifdef WITH_WSREP
+ else if (ok) xb_write_galera_info(xtrabackup_incremental);
+#endif
+
+ innodb_shutdown();
+
+ innodb_free_param();
+
+ /* output to metadata file */
+ if (ok) {
+ char filename[FN_REFLEN];
+
+ safe_strcpy(metadata_type, sizeof(metadata_type),
+ "log-applied");
+
+ if(xtrabackup_incremental
+ && metadata_to_lsn < incremental_to_lsn)
+ {
+ metadata_to_lsn = incremental_to_lsn;
+ metadata_last_lsn = incremental_last_lsn;
+ }
+
+ sprintf(filename, "%s/%s", xtrabackup_target_dir, XTRABACKUP_METADATA_FILENAME);
+ if (!xtrabackup_write_metadata(filename)) {
+
+ msg("mariabackup: Error: failed to write metadata "
+ "to '%s'", filename);
+ ok = false;
+ } else if (xtrabackup_extra_lsndir) {
+ sprintf(filename, "%s/%s", xtrabackup_extra_lsndir, XTRABACKUP_METADATA_FILENAME);
+ if (!xtrabackup_write_metadata(filename)) {
+ msg("mariabackup: Error: failed to write "
+ "metadata to '%s'", filename);
+ ok = false;
+ }
+ }
+ }
+
+ if (ok) ok = apply_log_finish();
+
+ if (ok && xtrabackup_export)
+ ok= (prepare_export() == 0);
+
+cleanup:
+ xb_filters_free();
+ return ok && !ib::error::was_logged() && corrupted_pages.empty();
+}
+
+/**************************************************************************
+Append group name to xb_load_default_groups list. */
+static
+void
+append_defaults_group(const char *group, const char *default_groups[],
+ size_t default_groups_size)
+{
+ uint i;
+ bool appended = false;
+ for (i = 0; i < default_groups_size - 1; i++) {
+ if (default_groups[i] == NULL) {
+ default_groups[i] = group;
+ appended = true;
+ break;
+ }
+ }
+ ut_a(appended);
+}
+
+static const char*
+normalize_privilege_target_name(const char* name)
+{
+ if (strcmp(name, "*") == 0) {
+ return "\\*";
+ }
+ else {
+ /* should have no regex special characters. */
+ ut_ad(strpbrk(name, ".()[]*+?") == 0);
+ }
+ return name;
+}
+
+/******************************************************************//**
+Check if specific privilege is granted.
+Uses regexp magic to check if requested privilege is granted for given
+database.table or database.* or *.*
+or if user has 'ALL PRIVILEGES' granted.
+@return true if requested privilege is granted, false otherwise. */
+static bool
+has_privilege(const std::list<std::string> &granted,
+ const char* required,
+ const char* db_name,
+ const char* table_name)
+{
+ char buffer[1000];
+ regex_t priv_re;
+ regmatch_t tables_regmatch[1];
+ bool result = false;
+
+ db_name = normalize_privilege_target_name(db_name);
+ table_name = normalize_privilege_target_name(table_name);
+
+ int written = snprintf(buffer, sizeof(buffer),
+ "GRANT .*(%s)|(ALL PRIVILEGES).* ON (\\*|`%s`)\\.(\\*|`%s`)",
+ required, db_name, table_name);
+ if (written < 0 || written == sizeof(buffer)
+ || regcomp(&priv_re, buffer, REG_EXTENDED)) {
+ die("regcomp() failed for '%s'", buffer);
+ }
+
+ typedef std::list<std::string>::const_iterator string_iter;
+ for (string_iter i = granted.begin(), e = granted.end(); i != e; ++i) {
+ int res = regexec(&priv_re, i->c_str(),
+ 1, tables_regmatch, 0);
+
+ if (res != REG_NOMATCH) {
+ result = true;
+ break;
+ }
+ }
+
+ xb_regfree(&priv_re);
+ return result;
+}
+
+enum {
+ PRIVILEGE_OK = 0,
+ PRIVILEGE_WARNING = 1,
+ PRIVILEGE_ERROR = 2,
+};
+
+/******************************************************************//**
+Check if specific privilege is granted.
+Prints error message if required privilege is missing.
+@return PRIVILEGE_OK if requested privilege is granted, error otherwise. */
+static
+int check_privilege(
+ const std::list<std::string> &granted_priv, /* in: list of
+ granted privileges*/
+ const char* required, /* in: required privilege name */
+ const char* target_database, /* in: required privilege target
+ database name */
+ const char* target_table, /* in: required privilege target
+ table name */
+ int error = PRIVILEGE_ERROR) /* in: return value if privilege
+ is not granted */
+{
+ if (!has_privilege(granted_priv,
+ required, target_database, target_table)) {
+ msg("%s: missing required privilege %s on %s.%s",
+ (error == PRIVILEGE_ERROR ? "Error" : "Warning"),
+ required, target_database, target_table);
+ return error;
+ }
+ return PRIVILEGE_OK;
+}
+
+
+/**
+Check DB user privileges according to the intended actions.
+
+Fetches DB user privileges, determines intended actions based on
+command-line arguments and prints missing privileges.
+@return whether all the necessary privileges are granted */
+static bool check_all_privileges()
+{
+ if (!mysql_connection) {
+ /* Not connected, no queries is going to be executed. */
+ return true;
+ }
+
+ /* Fetch effective privileges. */
+ std::list<std::string> granted_privileges;
+ MYSQL_RES* result = xb_mysql_query(mysql_connection, "SHOW GRANTS",
+ true);
+ while (MYSQL_ROW row = mysql_fetch_row(result)) {
+ granted_privileges.push_back(*row);
+ }
+ mysql_free_result(result);
+
+ int check_result = PRIVILEGE_OK;
+
+ /* FLUSH TABLES WITH READ LOCK */
+ if (!opt_no_lock)
+ {
+ check_result |= check_privilege(
+ granted_privileges,
+ "RELOAD", "*", "*");
+ check_result |= check_privilege(
+ granted_privileges,
+ "PROCESS", "*", "*");
+ }
+
+ /* KILL ... */
+ if (!opt_no_lock && (opt_kill_long_queries_timeout || opt_kill_long_query_type)) {
+ check_result |= check_privilege(
+ granted_privileges,
+ "CONNECTION ADMIN", "*", "*",
+ PRIVILEGE_WARNING);
+ }
+
+ /* START SLAVE SQL_THREAD */
+ /* STOP SLAVE SQL_THREAD */
+ if (opt_safe_slave_backup) {
+ check_result |= check_privilege(
+ granted_privileges,
+ "REPLICATION SLAVE ADMIN", "*", "*",
+ PRIVILEGE_WARNING);
+ }
+
+ /* SHOW MASTER STATUS */
+ /* SHOW SLAVE STATUS */
+ if (opt_galera_info || opt_slave_info
+ || opt_safe_slave_backup) {
+ check_result |= check_privilege(granted_privileges,
+ "SLAVE MONITOR", "*", "*",
+ PRIVILEGE_WARNING);
+ }
+
+ if (check_result & PRIVILEGE_ERROR) {
+ msg("Current privileges, as reported by 'SHOW GRANTS': ");
+ int n=1;
+ for (std::list<std::string>::const_iterator it = granted_privileges.begin();
+ it != granted_privileges.end();
+ it++,n++) {
+ msg(" %d.%s", n, it->c_str());
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool
+xb_init()
+{
+ const char *mixed_options[4] = {NULL, NULL, NULL, NULL};
+ int n_mixed_options;
+
+ /* sanity checks */
+
+ if (opt_slave_info
+ && opt_no_lock
+ && !opt_safe_slave_backup) {
+ msg("Error: --slave-info is used with --no-lock but "
+ "without --safe-slave-backup. The binlog position "
+ "cannot be consistent with the backup data.");
+ return(false);
+ }
+
+ if (xtrabackup_backup && opt_rsync)
+ {
+ if (xtrabackup_stream_fmt)
+ {
+ msg("Error: --rsync doesn't work with --stream\n");
+ return(false);
+ }
+ bool have_rsync = IF_WIN(false, (system("rsync --version > /dev/null 2>&1") == 0));
+ if (!have_rsync)
+ {
+ msg("Error: rsync executable not found, cannot run backup with --rsync\n");
+ return false;
+ }
+ }
+
+ n_mixed_options = 0;
+
+ if (opt_decompress) {
+ mixed_options[n_mixed_options++] = "--decompress";
+ }
+
+ if (xtrabackup_copy_back) {
+ mixed_options[n_mixed_options++] = "--copy-back";
+ }
+
+ if (xtrabackup_move_back) {
+ mixed_options[n_mixed_options++] = "--move-back";
+ }
+
+ if (xtrabackup_prepare) {
+ mixed_options[n_mixed_options++] = "--apply-log";
+ }
+
+ if (n_mixed_options > 1) {
+ msg("Error: %s and %s are mutually exclusive\n",
+ mixed_options[0], mixed_options[1]);
+ return(false);
+ }
+
+ if (xtrabackup_backup) {
+ if ((mysql_connection = xb_mysql_connect()) == NULL) {
+ return(false);
+ }
+
+ if (!get_mysql_vars(mysql_connection)) {
+ return(false);
+ }
+
+ if (opt_check_privileges && !check_all_privileges()) {
+ return(false);
+ }
+ history_start_time = time(NULL);
+ }
+
+ return(true);
+}
+
+
+extern void init_signals(void);
+
+#include <sql_locale.h>
+
+
+void setup_error_messages()
+{
+ my_default_lc_messages = &my_locale_en_US;
+ if (init_errmessage())
+ die("could not initialize error messages");
+}
+
+/** Handle mariabackup options. The options are handled with the following
+order:
+
+1) Load server groups and process server options, ignore unknown options
+2) Load client groups and process client options, ignore unknown options
+3) Load backup groups and process client-server options, exit on unknown option
+4) Process --mysqld-args options, ignore unknown options
+
+@param[in] argc arguments count
+@param[in] argv arguments array
+@param[out] argv_server server options including loaded from server groups
+@param[out] argv_client client options including loaded from client groups
+@param[out] argv_backup backup options including loaded from backup groups */
+void handle_options(int argc, char **argv, char ***argv_server,
+ char ***argv_client, char ***argv_backup)
+{
+ /* Setup some variables for Innodb.*/
+ srv_operation = SRV_OPERATION_RESTORE;
+
+ files_charset_info = &my_charset_utf8mb3_general_ci;
+
+
+ setup_error_messages();
+ sys_var_init();
+ plugin_mutex_init();
+ mysql_prlock_init(key_rwlock_LOCK_system_variables_hash, &LOCK_system_variables_hash);
+ opt_stack_trace = 1;
+ test_flags |= TEST_SIGINT;
+ init_signals();
+#ifndef _WIN32
+ /* Exit process on SIGINT. */
+ my_sigset(SIGINT, SIG_DFL);
+#endif
+
+ sf_leaking_memory = 1; /* don't report memory leaks on early exist */
+
+ int i;
+ int ho_error;
+
+ char* target_dir = NULL;
+ bool prepare = false;
+
+ char conf_file[FN_REFLEN];
+
+ // array_elements() will not work for load_defaults, as it is defined
+ // as external symbol, so let's use dynamic array to have ability to
+ // add new server default groups
+ std::vector<const char *> server_default_groups;
+
+ for (const char **default_group= load_default_groups; *default_group;
+ ++default_group)
+ server_default_groups.push_back(*default_group);
+
+ std::vector<char *> mysqld_args;
+ std::vector<char *> mariabackup_args;
+ mysqld_args.push_back(argv[0]);
+ mariabackup_args.push_back(argv[0]);
+
+ /* scan options for group and config file to load defaults from */
+ for (i= 1; i < argc; i++)
+ {
+ char *optend= strcend(argv[i], '=');
+ if (mysqld_args.size() > 1 ||
+ strncmp(argv[i], "--mysqld-args", optend - argv[i]) == 0)
+ {
+ mysqld_args.push_back(argv[i]);
+ continue;
+ }
+ else
+ mariabackup_args.push_back(argv[i]);
+
+ if (strncmp(argv[i], "--defaults-group", optend - argv[i]) == 0)
+ {
+ defaults_group= optend + 1;
+ server_default_groups.push_back(defaults_group);
+ }
+ else if (strncmp(argv[i], "--login-path", optend - argv[i]) == 0)
+ {
+ append_defaults_group(optend + 1, xb_client_default_groups,
+ array_elements(xb_client_default_groups));
+ }
+ else if (!strncmp(argv[i], "--prepare", optend - argv[i]))
+ {
+ prepare= true;
+ }
+ else if (!strncmp(argv[i], "--apply-log", optend - argv[i]))
+ {
+ prepare= true;
+ }
+ else if (!strncmp(argv[i], "--incremental-dir", optend - argv[i]) &&
+ *optend)
+ {
+ target_dir= optend + 1;
+ }
+ else if (!strncmp(argv[i], "--target-dir", optend - argv[i]) &&
+ *optend && !target_dir)
+ {
+ target_dir= optend + 1;
+ }
+ else if (!*optend && argv[i][0] != '-' && !target_dir)
+ {
+ target_dir= argv[i];
+ }
+ }
+
+ server_default_groups.push_back(NULL);
+ snprintf(conf_file, sizeof(conf_file), "my");
+
+ if (prepare && target_dir) {
+ snprintf(conf_file, sizeof(conf_file),
+ "%s/backup-my.cnf", target_dir);
+ if (!strncmp(argv[1], "--defaults-file=", 16)) {
+ /* Remove defaults-file*/
+ for (int i = 2; ; i++) {
+ if ((argv[i-1]= argv[i]) == 0)
+ break;
+ }
+ argc--;
+ }
+ }
+
+ mariabackup_args.push_back(nullptr);
+ *argv_client= *argv_server= *argv_backup= &mariabackup_args[0];
+ int argc_backup= static_cast<int>(mariabackup_args.size() - 1);
+ int argc_client= argc_backup;
+ int argc_server= argc_backup;
+
+ /* 1) Load server groups and process server options, ignore unknown
+ options */
+
+ load_defaults_or_exit(conf_file, &server_default_groups[0],
+ &argc_server, argv_server);
+
+ int n;
+ for (n = 0; (*argv_server)[n]; n++) {};
+ argc_server = n;
+
+ print_param_str <<
+ "# This MySQL options file was generated by XtraBackup.\n"
+ "[" << defaults_group << "]\n";
+
+ /* We want xtrabackup to ignore unknown options, because it only
+ recognizes a small subset of server variables */
+ my_getopt_skip_unknown = TRUE;
+
+ /* Reset u_max_value for all options, as we don't want the
+ --maximum-... modifier to set the actual option values */
+ for (my_option *optp= xb_server_options; optp->name; optp++) {
+ optp->u_max_value = (G_PTR *) &global_max_value;
+ }
+
+ /* Throw a descriptive error if --defaults-file or --defaults-extra-file
+ is not the first command line argument */
+ for (int i = 2 ; i < argc ; i++) {
+ char *optend = strcend((argv)[i], '=');
+
+ if (optend - argv[i] == 15 &&
+ !strncmp(argv[i], "--defaults-file", optend - argv[i])) {
+ die("--defaults-file must be specified first on the command line");
+ }
+ if (optend - argv[i] == 21 &&
+ !strncmp(argv[i], "--defaults-extra-file",
+ optend - argv[i])) {
+ die("--defaults-extra-file must be specified first on the command line");
+ }
+ }
+
+ if (argc_server > 0
+ && (ho_error=handle_options(&argc_server, argv_server,
+ xb_server_options, xb_get_one_option)))
+ exit(ho_error);
+
+ /* 2) Load client groups and process client options, ignore unknown
+ options */
+
+ load_defaults_or_exit(conf_file, xb_client_default_groups,
+ &argc_client, argv_client);
+
+ for (n = 0; (*argv_client)[n]; n++) {};
+ argc_client = n;
+
+ if (innobackupex_mode && argc_client > 0) {
+ if (!ibx_handle_options(&argc_client, argv_client)) {
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (argc_client > 0
+ && (ho_error=handle_options(&argc_client, argv_client,
+ xb_client_options, xb_get_one_option)))
+ exit(ho_error);
+
+ /* 3) Load backup groups and process client-server options, exit on
+ unknown option */
+
+ load_defaults_or_exit(conf_file, backup_default_groups, &argc_backup,
+ argv_backup);
+ for (n= 0; (*argv_backup)[n]; n++)
+ {
+ };
+ argc_backup= n;
+
+ my_handle_options_init_variables = FALSE;
+
+ if (argc_backup > 0 &&
+ (ho_error= handle_options(&argc_backup, argv_backup,
+ xb_server_options, xb_get_one_option)))
+ exit(ho_error);
+
+ /* Add back the program name handle_options removes */
+ ++argc_backup;
+ --(*argv_backup);
+
+ if (innobackupex_mode && argc_backup > 0 &&
+ !ibx_handle_options(&argc_backup, argv_backup))
+ exit(EXIT_FAILURE);
+
+ my_getopt_skip_unknown = FALSE;
+
+ if (argc_backup > 0 &&
+ (ho_error= handle_options(&argc_backup, argv_backup,
+ xb_client_options, xb_get_one_option)))
+ exit(ho_error);
+
+ if (opt_password)
+ {
+ char *argument= (char*) opt_password;
+ char *start= (char*) opt_password;
+ opt_password= my_strdup(PSI_NOT_INSTRUMENTED, opt_password,
+ MYF(MY_FAE));
+ while (*argument)
+ *argument++= 'x'; // Destroy argument
+ if (*start)
+ start[1]= 0;
+ }
+
+ /* 4) Process --mysqld-args options, ignore unknown options */
+
+ my_getopt_skip_unknown = TRUE;
+
+ int argc_mysqld = static_cast<int>(mysqld_args.size());
+ if (argc_mysqld > 1)
+ {
+ char **argv_mysqld= &mysqld_args[0];
+ if ((ho_error= handle_options(&argc_mysqld, &argv_mysqld,
+ xb_server_options, xb_get_one_option)))
+ exit(ho_error);
+ }
+
+ my_handle_options_init_variables = TRUE;
+
+ /* Reject command line arguments that don't look like options, i.e. are
+ not of the form '-X' (single-character options) or '--option' (long
+ options) */
+ for (int i = 0 ; i < argc_backup ; i++) {
+ const char * const opt = (*argv_backup)[i];
+
+ if (strncmp(opt, "--", 2) &&
+ !(strlen(opt) == 2 && opt[0] == '-')) {
+ bool server_option = true;
+
+ for (int j = 0; j < argc_backup; j++) {
+ if (opt == (*argv_backup)[j]) {
+ server_option = false;
+ break;
+ }
+ }
+
+ if (!server_option) {
+ msg("mariabackup: Error:"
+ " unknown argument: '%s'", opt);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+}
+
+static int main_low(char** argv);
+static int get_exepath(char *buf, size_t size, const char *argv0);
+
+/* ================= main =================== */
+int main(int argc, char **argv)
+{
+ char **server_defaults;
+ char **client_defaults;
+ char **backup_defaults;
+
+ my_getopt_prefix_matching= 0;
+
+ if (get_exepath(mariabackup_exe,FN_REFLEN, argv[0]))
+ strncpy(mariabackup_exe,argv[0], FN_REFLEN-1);
+
+
+ if (argc > 1 )
+ {
+ /* In "prepare export", we need to start mysqld
+ Since it is not always be installed on the machine,
+ we start "mariabackup --mysqld", which acts as mysqld
+ */
+ if (strcmp(argv[1], "--mysqld") == 0)
+ {
+ srv_operation= SRV_OPERATION_EXPORT_RESTORED;
+ extern int mysqld_main(int argc, char **argv);
+ argc--;
+ argv++;
+ argv[0]+=2;
+ return mysqld_main(argc, argv);
+ }
+ if(strcmp(argv[1], "--innobackupex") == 0)
+ {
+ argv++;
+ argc--;
+ innobackupex_mode = true;
+ }
+ }
+
+ if (argc > 1)
+ strncpy(orig_argv1,argv[1],sizeof(orig_argv1) -1);
+
+ init_signals();
+ MY_INIT(argv[0]);
+
+ xb_regex_init();
+
+ capture_tool_command(argc, argv);
+
+ if (mysql_server_init(-1, NULL, NULL))
+ {
+ die("mysql_server_init() failed");
+ }
+
+ system_charset_info = &my_charset_utf8mb3_general_ci;
+ key_map_full.set_all();
+
+ logger.init_base();
+ logger.set_handlers(LOG_NONE, LOG_NONE);
+ mysql_mutex_init(key_LOCK_error_log, &LOCK_error_log,
+ MY_MUTEX_INIT_FAST);
+
+ handle_options(argc, argv, &server_defaults, &client_defaults,
+ &backup_defaults);
+
+#ifndef DBUG_OFF
+ if (dbug_option) {
+ DBUG_SET_INITIAL(dbug_option);
+ DBUG_SET(dbug_option);
+ }
+#endif
+ /* Main functions for library */
+ init_thr_timer(5);
+
+ int status = main_low(server_defaults);
+
+ end_thr_timer();
+ backup_cleanup();
+
+ if (innobackupex_mode) {
+ ibx_cleanup();
+ }
+
+ free_defaults(server_defaults);
+ free_defaults(client_defaults);
+ free_defaults(backup_defaults);
+
+#ifndef DBUG_OFF
+ if (dbug_option) {
+ DBUG_END();
+ }
+#endif
+
+ logger.cleanup_base();
+ cleanup_errmsgs();
+ free_error_messages();
+ mysql_mutex_destroy(&LOCK_error_log);
+
+ if (status == EXIT_SUCCESS) {
+ msg("completed OK!");
+ }
+
+ return status;
+}
+
+static int main_low(char** argv)
+{
+ if (innobackupex_mode) {
+ if (!ibx_init()) {
+ return(EXIT_FAILURE);
+ }
+ }
+
+ if (!xtrabackup_print_param && !xtrabackup_prepare
+ && !strcmp(mysql_data_home, "./")) {
+ if (!xtrabackup_print_param)
+ usage();
+ msg("mariabackup: Error: Please set parameter 'datadir'");
+ return(EXIT_FAILURE);
+ }
+
+ /* Expand target-dir, incremental-basedir, etc. */
+
+ char cwd[FN_REFLEN];
+ my_getwd(cwd, sizeof(cwd), MYF(0));
+
+ my_load_path(xtrabackup_real_target_dir,
+ xtrabackup_target_dir, cwd);
+ unpack_dirname(xtrabackup_real_target_dir,
+ xtrabackup_real_target_dir);
+ xtrabackup_target_dir= xtrabackup_real_target_dir;
+
+ if (xtrabackup_incremental_basedir) {
+ my_load_path(xtrabackup_real_incremental_basedir,
+ xtrabackup_incremental_basedir, cwd);
+ unpack_dirname(xtrabackup_real_incremental_basedir,
+ xtrabackup_real_incremental_basedir);
+ xtrabackup_incremental_basedir =
+ xtrabackup_real_incremental_basedir;
+ }
+
+ if (xtrabackup_incremental_dir) {
+ my_load_path(xtrabackup_real_incremental_dir,
+ xtrabackup_incremental_dir, cwd);
+ unpack_dirname(xtrabackup_real_incremental_dir,
+ xtrabackup_real_incremental_dir);
+ xtrabackup_incremental_dir = xtrabackup_real_incremental_dir;
+ }
+
+ if (xtrabackup_extra_lsndir) {
+ my_load_path(xtrabackup_real_extra_lsndir,
+ xtrabackup_extra_lsndir, cwd);
+ unpack_dirname(xtrabackup_real_extra_lsndir,
+ xtrabackup_real_extra_lsndir);
+ xtrabackup_extra_lsndir = xtrabackup_real_extra_lsndir;
+ }
+
+ /* get default temporary directory */
+ if (!opt_mysql_tmpdir || !opt_mysql_tmpdir[0]) {
+ opt_mysql_tmpdir = getenv("TMPDIR");
+#if defined(_WIN32)
+ if (!opt_mysql_tmpdir) {
+ opt_mysql_tmpdir = getenv("TEMP");
+ }
+ if (!opt_mysql_tmpdir) {
+ opt_mysql_tmpdir = getenv("TMP");
+ }
+#endif
+ if (!opt_mysql_tmpdir || !opt_mysql_tmpdir[0]) {
+ opt_mysql_tmpdir = const_cast<char*>(DEFAULT_TMPDIR);
+ }
+ }
+
+ /* temporary setting of enough size */
+ srv_page_size_shift = UNIV_PAGE_SIZE_SHIFT_MAX;
+ srv_page_size = UNIV_PAGE_SIZE_MAX;
+ if (xtrabackup_backup && xtrabackup_incremental) {
+ /* direct specification is only for --backup */
+ /* and the lsn is prior to the other option */
+
+ char* endchar;
+ int error = 0;
+ incremental_lsn = strtoll(xtrabackup_incremental, &endchar, 10);
+ if (*endchar != '\0')
+ error = 1;
+
+ if (error) {
+ msg("mariabackup: value '%s' may be wrong format for "
+ "incremental option.", xtrabackup_incremental);
+ return(EXIT_FAILURE);
+ }
+ } else if (xtrabackup_backup && xtrabackup_incremental_basedir) {
+ char filename[FN_REFLEN];
+
+ sprintf(filename, "%s/%s", xtrabackup_incremental_basedir, XTRABACKUP_METADATA_FILENAME);
+
+ if (!xtrabackup_read_metadata(filename)) {
+ msg("mariabackup: error: failed to read metadata from "
+ "%s", filename);
+ return(EXIT_FAILURE);
+ }
+
+ incremental_lsn = metadata_to_lsn;
+ xtrabackup_incremental = xtrabackup_incremental_basedir; //dummy
+ } else if (xtrabackup_prepare && xtrabackup_incremental_dir) {
+ char filename[FN_REFLEN];
+
+ sprintf(filename, "%s/%s", xtrabackup_incremental_dir, XTRABACKUP_METADATA_FILENAME);
+
+ if (!xtrabackup_read_metadata(filename)) {
+ msg("mariabackup: error: failed to read metadata from "
+ "%s", filename);
+ return(EXIT_FAILURE);
+ }
+
+ incremental_lsn = metadata_from_lsn;
+ incremental_to_lsn = metadata_to_lsn;
+ incremental_last_lsn = metadata_last_lsn;
+ xtrabackup_incremental = xtrabackup_incremental_dir; //dummy
+
+ } else if (opt_incremental_history_name) {
+ xtrabackup_incremental = opt_incremental_history_name;
+ } else if (opt_incremental_history_uuid) {
+ xtrabackup_incremental = opt_incremental_history_uuid;
+ } else {
+ xtrabackup_incremental = NULL;
+ }
+
+ if (xtrabackup_stream && !xtrabackup_backup) {
+ msg("Warning: --stream parameter is ignored, it only works together with --backup.");
+ }
+
+ if (!xb_init()) {
+ return(EXIT_FAILURE);
+ }
+
+ /* --print-param */
+ if (xtrabackup_print_param) {
+ printf("%s", print_param_str.str().c_str());
+ return(EXIT_SUCCESS);
+ }
+
+ print_version();
+ if (xtrabackup_incremental) {
+ msg("incremental backup from " LSN_PF " is enabled.",
+ incremental_lsn);
+ }
+
+ if (xtrabackup_export && !srv_file_per_table) {
+ msg("mariabackup: auto-enabling --innodb-file-per-table due to "
+ "the --export option");
+ srv_file_per_table = TRUE;
+ }
+
+ /* cannot execute both for now */
+ {
+ int num = 0;
+
+ if (xtrabackup_backup) num++;
+ if (xtrabackup_prepare) num++;
+ if (xtrabackup_copy_back) num++;
+ if (xtrabackup_move_back) num++;
+ if (xtrabackup_decrypt_decompress) num++;
+ if (num != 1) { /* !XOR (for now) */
+ usage();
+ return(EXIT_FAILURE);
+ }
+ }
+
+ ut_ad(!field_ref_zero);
+ if (auto b = aligned_malloc(UNIV_PAGE_SIZE_MAX, 4096)) {
+ field_ref_zero = static_cast<byte*>(
+ memset_aligned<4096>(b, 0, UNIV_PAGE_SIZE_MAX));
+ } else {
+ msg("Can't allocate memory for field_ref_zero");
+ return EXIT_FAILURE;
+ }
+
+ auto _ = make_scope_exit([]() {
+ aligned_free(const_cast<byte*>(field_ref_zero));
+ field_ref_zero = nullptr;
+ });
+
+ /* --backup */
+ if (xtrabackup_backup && !xtrabackup_backup_func()) {
+ return(EXIT_FAILURE);
+ }
+
+ /* --prepare */
+ if (xtrabackup_prepare
+ && !xtrabackup_prepare_func(argv)) {
+ return(EXIT_FAILURE);
+ }
+
+ if (xtrabackup_copy_back || xtrabackup_move_back) {
+ if (!check_if_param_set("datadir")) {
+ mysql_data_home = get_default_datadir();
+ }
+ if (!copy_back())
+ return(EXIT_FAILURE);
+ }
+
+ if (xtrabackup_decrypt_decompress && !decrypt_decompress()) {
+ return(EXIT_FAILURE);
+ }
+
+ return(EXIT_SUCCESS);
+}
+
+
+static int get_exepath(char *buf, size_t size, const char *argv0)
+{
+#ifdef _WIN32
+ DWORD ret = GetModuleFileNameA(NULL, buf, (DWORD)size);
+ if (ret > 0)
+ return 0;
+#elif defined(__linux__)
+ ssize_t ret = readlink("/proc/self/exe", buf, size-1);
+ if(ret > 0)
+ return 0;
+#elif defined(__APPLE__)
+ size_t ret = proc_pidpath(getpid(), buf, static_cast<uint32_t>(size));
+ if (ret > 0) {
+ buf[ret] = 0;
+ return 0;
+ }
+#elif defined(__FreeBSD__)
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+ if (sysctl(mib, 4, buf, &size, NULL, 0) == 0) {
+ return 0;
+ }
+#endif
+
+ return my_realpath(buf, argv0, 0);
+}
+
+
+#if defined (__SANITIZE_ADDRESS__) && defined (__linux__)
+/* Avoid LeakSanitizer's false positives. */
+const char* __asan_default_options()
+{
+ return "detect_leaks=0";
+}
+#endif