summaryrefslogtreecommitdiffstats
path: root/extra/mariabackup
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:24:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:24:36 +0000
commit06eaf7232e9a920468c0f8d74dcf2fe8b555501c (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /extra/mariabackup
parentInitial commit. (diff)
downloadmariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.tar.xz
mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extra/mariabackup')
-rw-r--r--extra/mariabackup/CMakeLists.txt113
-rw-r--r--extra/mariabackup/backup_copy.cc2471
-rw-r--r--extra/mariabackup/backup_copy.h43
-rw-r--r--extra/mariabackup/backup_debug.h24
-rw-r--r--extra/mariabackup/backup_mysql.cc1947
-rw-r--r--extra/mariabackup/backup_mysql.h90
-rw-r--r--extra/mariabackup/backup_wsrep.h32
-rw-r--r--extra/mariabackup/changed_page_bitmap.cc1040
-rw-r--r--extra/mariabackup/changed_page_bitmap.h85
-rw-r--r--extra/mariabackup/common.h189
-rw-r--r--extra/mariabackup/datasink.cc130
-rw-r--r--extra/mariabackup/datasink.h134
-rw-r--r--extra/mariabackup/ds_buffer.cc189
-rw-r--r--extra/mariabackup/ds_buffer.h39
-rw-r--r--extra/mariabackup/ds_compress.cc475
-rw-r--r--extra/mariabackup/ds_compress.h28
-rw-r--r--extra/mariabackup/ds_local.cc278
-rw-r--r--extra/mariabackup/ds_local.h34
-rw-r--r--extra/mariabackup/ds_stdout.cc121
-rw-r--r--extra/mariabackup/ds_stdout.h28
-rw-r--r--extra/mariabackup/ds_tmpfile.cc230
-rw-r--r--extra/mariabackup/ds_tmpfile.h30
-rw-r--r--extra/mariabackup/ds_xbstream.cc229
-rw-r--r--extra/mariabackup/ds_xbstream.h28
-rw-r--r--extra/mariabackup/fil_cur.cc522
-rw-r--r--extra/mariabackup/fil_cur.h128
-rw-r--r--extra/mariabackup/innobackupex.cc996
-rw-r--r--extra/mariabackup/innobackupex.h45
-rw-r--r--extra/mariabackup/quicklz/quicklz.c848
-rw-r--r--extra/mariabackup/quicklz/quicklz.h144
-rw-r--r--extra/mariabackup/read_filt.cc207
-rw-r--r--extra/mariabackup/read_filt.h66
-rw-r--r--extra/mariabackup/write_filt.cc236
-rw-r--r--extra/mariabackup/write_filt.h59
-rw-r--r--extra/mariabackup/wsrep.cc117
-rw-r--r--extra/mariabackup/xb_plugin.cc229
-rw-r--r--extra/mariabackup/xb_plugin.h5
-rw-r--r--extra/mariabackup/xb_regex.h49
-rw-r--r--extra/mariabackup/xbcloud.cc2722
-rw-r--r--extra/mariabackup/xbstream.cc560
-rw-r--r--extra/mariabackup/xbstream.h106
-rw-r--r--extra/mariabackup/xbstream_read.cc226
-rw-r--r--extra/mariabackup/xbstream_write.cc293
-rw-r--r--extra/mariabackup/xtrabackup.cc7083
-rw-r--r--extra/mariabackup/xtrabackup.h302
45 files changed, 22950 insertions, 0 deletions
diff --git a/extra/mariabackup/CMakeLists.txt b/extra/mariabackup/CMakeLists.txt
new file mode 100644
index 00000000..66293dac
--- /dev/null
+++ b/extra/mariabackup/CMakeLists.txt
@@ -0,0 +1,113 @@
+# Copyright (c) 2013, 2017 Percona LLC and/or its affiliates.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License 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
+
+
+OPTION(WITH_MARIABACKUP "Include mariabackup" ON)
+ADD_FEATURE_INFO(MARIABACKUP WITH_MARIABACKUP "MariaDB Backup Utility")
+
+IF(NOT WITH_MARIABACKUP)
+ RETURN()
+ENDIF()
+
+IF(NOT WIN32)
+ CHECK_SYMBOL_EXISTS(regcomp regex.h HAVE_SYSTEM_REGEX)
+ IF(HAVE_SYSTEM_REGEX)
+ ADD_DEFINITIONS(-DHAVE_SYSTEM_REGEX)
+ ENDIF()
+ENDIF()
+
+INCLUDE_DIRECTORIES(
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/sql
+ ${CMAKE_CURRENT_SOURCE_DIR}/quicklz
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+
+IF(NOT HAVE_SYSTEM_REGEX)
+ INCLUDE_DIRECTORIES(${PCRE_INCLUDES})
+ ADD_DEFINITIONS(${PCRE2_DEBIAN_HACK})
+ENDIF()
+
+
+ADD_DEFINITIONS(-UMYSQL_SERVER)
+########################################################################
+# xtrabackup binary
+########################################################################
+
+ADD_DEFINITIONS(-DPCRE_STATIC=1)
+ADD_DEFINITIONS(${SSL_DEFINES})
+
+IF(PMEM_FOUND)
+ ADD_COMPILE_FLAGS(xtrabackup.cc COMPILE_FLAGS "-DHAVE_PMEM")
+ENDIF()
+
+MYSQL_ADD_EXECUTABLE(mariadb-backup
+ xtrabackup.cc
+ innobackupex.cc
+ changed_page_bitmap.cc
+ datasink.cc
+ ds_buffer.cc
+ ds_compress.cc
+ ds_local.cc
+ ds_stdout.cc
+ ds_tmpfile.cc
+ ds_xbstream.cc
+ fil_cur.cc
+ quicklz/quicklz.c
+ read_filt.cc
+ write_filt.cc
+ wsrep.cc
+ xbstream_write.cc
+ backup_mysql.cc
+ backup_copy.cc
+ xb_plugin.cc
+ ${PROJECT_BINARY_DIR}/sql/sql_builtin.cc
+ ${PROJECT_SOURCE_DIR}/sql/net_serv.cc
+ ${PROJECT_SOURCE_DIR}/libmysqld/libmysql.c
+ COMPONENT backup
+ )
+
+# Export all symbols on Unix, for better crash callstacks
+SET_TARGET_PROPERTIES(mariadb-backup PROPERTIES ENABLE_EXPORTS TRUE)
+
+TARGET_LINK_LIBRARIES(mariadb-backup sql sql_builtins)
+IF(NOT HAVE_SYSTEM_REGEX)
+ TARGET_LINK_LIBRARIES(mariadb-backup pcre2-posix)
+ENDIF()
+
+
+########################################################################
+# mbstream binary
+########################################################################
+MYSQL_ADD_EXECUTABLE(mbstream
+ ds_buffer.cc
+ ds_local.cc
+ ds_stdout.cc
+ datasink.cc
+ xbstream.cc
+ xbstream_read.cc
+ xbstream_write.cc
+ COMPONENT backup
+ )
+
+
+TARGET_LINK_LIBRARIES(mbstream
+ mysys
+)
+ADD_DEPENDENCIES(mbstream GenError)
+
+IF(MSVC)
+ SET_TARGET_PROPERTIES(mbstream PROPERTIES LINK_FLAGS setargv.obj)
+ENDIF()
diff --git a/extra/mariabackup/backup_copy.cc b/extra/mariabackup/backup_copy.cc
new file mode 100644
index 00000000..dbf12ced
--- /dev/null
+++ b/extra/mariabackup/backup_copy.cc
@@ -0,0 +1,2471 @@
+/******************************************************
+hot backup tool for InnoDB
+(c) 2009-2015 Percona LLC and/or its affiliates
+(c) 2017 MariaDB
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <os0file.h>
+#include <my_dir.h>
+#include <ut0mem.h>
+#include <srv0start.h>
+#include <fil0fil.h>
+#include <trx0sys.h>
+#include <set>
+#include <string>
+#include <mysqld.h>
+#include <sstream>
+#include "fil_cur.h"
+#include "xtrabackup.h"
+#include "common.h"
+#include "backup_copy.h"
+#include "backup_debug.h"
+#include "backup_mysql.h"
+#include <btr0btr.h>
+#ifdef _WIN32
+#include <direct.h> /* rmdir */
+#endif
+
+#ifdef _WIN32
+#include <aclapi.h>
+#endif
+
+
+#define ROCKSDB_BACKUP_DIR "#rocksdb"
+
+/* list of files to sync for --rsync mode */
+static std::set<std::string> rsync_list;
+/* locations of tablespaces read from .isl files */
+static std::map<std::string, std::string> tablespace_locations;
+
+/* Whether LOCK BINLOG FOR BACKUP has been issued during backup */
+bool binlog_locked;
+
+static void rocksdb_create_checkpoint();
+static bool has_rocksdb_plugin();
+static void rocksdb_backup_checkpoint(ds_ctxt *ds_data);
+static void rocksdb_copy_back(ds_ctxt *ds_data);
+
+static bool is_abs_path(const char *path)
+{
+#ifdef _WIN32
+ return path[0] && path[1] == ':' && (path[2] == '/' || path[2] == '\\');
+#else
+ return path[0] == '/';
+#endif
+}
+
+/************************************************************************
+Struct represents file or directory. */
+struct datadir_node_t {
+ ulint dbpath_len;
+ char *filepath;
+ ulint filepath_len;
+ char *filepath_rel;
+ ulint filepath_rel_len;
+ bool is_empty_dir;
+ bool is_file;
+};
+
+/************************************************************************
+Holds the state needed to enumerate files in MySQL data directory. */
+struct datadir_iter_t {
+ char *datadir_path;
+ char *dbpath;
+ ulint dbpath_len;
+ char *filepath;
+ ulint filepath_len;
+ char *filepath_rel;
+ ulint filepath_rel_len;
+ pthread_mutex_t mutex;
+ os_file_dir_t dir;
+ os_file_dir_t dbdir;
+ os_file_stat_t dbinfo;
+ os_file_stat_t fileinfo;
+ dberr_t err;
+ bool is_empty_dir;
+ bool is_file;
+ bool skip_first_level;
+};
+
+
+/************************************************************************
+Represents the context of the thread processing MySQL data directory. */
+struct datadir_thread_ctxt_t {
+ datadir_iter_t *it;
+ uint n_thread;
+ uint *count;
+ pthread_mutex_t* count_mutex;
+ bool ret;
+};
+
+static bool backup_files_from_datadir(ds_ctxt_t *ds_data,
+ const char *dir_path,
+ const char *prefix);
+
+/************************************************************************
+Retirn true if character if file separator */
+bool
+is_path_separator(char c)
+{
+ return(c == FN_LIBCHAR || c == FN_LIBCHAR2);
+}
+
+
+/************************************************************************
+Fill the node struct. Memory for node need to be allocated and freed by
+the caller. It is caller responsibility to initialize node with
+datadir_node_init and cleanup the memory with datadir_node_free.
+Node can not be shared between threads. */
+static
+void
+datadir_node_fill(datadir_node_t *node, datadir_iter_t *it)
+{
+ if (node->filepath_len < it->filepath_len) {
+ free(node->filepath);
+ node->filepath = (char*)(malloc(it->filepath_len));
+ node->filepath_len = it->filepath_len;
+ }
+ if (node->filepath_rel_len < it->filepath_rel_len) {
+ free(node->filepath_rel);
+ node->filepath_rel = (char*)(malloc(it->filepath_rel_len));
+ node->filepath_rel_len = it->filepath_rel_len;
+ }
+
+ strcpy(node->filepath, it->filepath);
+ strcpy(node->filepath_rel, it->filepath_rel);
+ node->is_empty_dir = it->is_empty_dir;
+ node->is_file = it->is_file;
+}
+
+static
+void
+datadir_node_free(datadir_node_t *node)
+{
+ free(node->filepath);
+ free(node->filepath_rel);
+ memset(node, 0, sizeof(datadir_node_t));
+}
+
+static
+void
+datadir_node_init(datadir_node_t *node)
+{
+ memset(node, 0, sizeof(datadir_node_t));
+}
+
+
+/************************************************************************
+Create the MySQL data directory iterator. Memory needs to be released
+with datadir_iter_free. Position should be advanced with
+datadir_iter_next_file. Iterator can be shared between multiple
+threads. It is guaranteed that each thread receives unique file from
+data directory into its local node struct. */
+static
+datadir_iter_t *
+datadir_iter_new(const char *path, bool skip_first_level = true)
+{
+ datadir_iter_t *it;
+
+ it = static_cast<datadir_iter_t *>(malloc(sizeof(datadir_iter_t)));
+ if (!it)
+ goto error;
+ memset(it, 0, sizeof(datadir_iter_t));
+
+ pthread_mutex_init(&it->mutex, NULL);
+ it->datadir_path = strdup(path);
+
+ it->dir = os_file_opendir(it->datadir_path);
+
+ if (it->dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr)) {
+
+ goto error;
+ }
+
+ it->err = DB_SUCCESS;
+
+ it->dbpath_len = FN_REFLEN;
+ it->dbpath = static_cast<char*>(malloc(it->dbpath_len));
+
+ it->filepath_len = FN_REFLEN;
+ it->filepath = static_cast<char*>(malloc(it->filepath_len));
+
+ it->filepath_rel_len = FN_REFLEN;
+ it->filepath_rel = static_cast<char*>(malloc(it->filepath_rel_len));
+
+ it->skip_first_level = skip_first_level;
+
+ return(it);
+
+error:
+ free(it);
+
+ return(NULL);
+}
+
+static
+bool
+datadir_iter_next_database(datadir_iter_t *it)
+{
+ if (it->dbdir != NULL) {
+ if (os_file_closedir_failed(it->dbdir)) {
+ msg("Warning: could not"
+ " close database directory %s", it->dbpath);
+ it->err = DB_ERROR;
+
+ }
+ it->dbdir = NULL;
+ }
+
+ while (os_file_readdir_next_file(it->datadir_path,
+ it->dir, &it->dbinfo) == 0) {
+ ulint len;
+
+ if ((it->dbinfo.type == OS_FILE_TYPE_FILE
+ && it->skip_first_level)
+ || it->dbinfo.type == OS_FILE_TYPE_UNKNOWN) {
+
+ continue;
+ }
+
+ /* We found a symlink or a directory; try opening it to see
+ if a symlink is a directory */
+
+ len = strlen(it->datadir_path)
+ + strlen (it->dbinfo.name) + 2;
+ if (len > it->dbpath_len) {
+ it->dbpath_len = len;
+ free(it->dbpath);
+
+ it->dbpath = static_cast<char*>(
+ malloc(it->dbpath_len));
+ }
+ snprintf(it->dbpath, it->dbpath_len, "%s/%s",
+ it->datadir_path, it->dbinfo.name);
+
+ if (it->dbinfo.type == OS_FILE_TYPE_FILE) {
+ it->is_file = true;
+ return(true);
+ }
+
+ if (check_if_skip_database_by_path(it->dbpath)) {
+ msg("Skipping db: %s", it->dbpath);
+ continue;
+ }
+
+ /* We want wrong directory permissions to be a fatal error for
+ XtraBackup. */
+ it->dbdir = os_file_opendir(it->dbpath);
+
+ if (it->dir != IF_WIN(INVALID_HANDLE_VALUE, nullptr)) {
+ it->is_file = false;
+ return(true);
+ }
+
+ }
+
+ return(false);
+}
+
+/************************************************************************
+Concatenate n parts into single path */
+static
+void
+make_path_n(int n, char **path, ulint *path_len, ...)
+{
+ ulint len_needed = n + 1;
+ char *p;
+ int i;
+ va_list vl;
+
+ ut_ad(n > 0);
+
+ va_start(vl, path_len);
+ for (i = 0; i < n; i++) {
+ p = va_arg(vl, char*);
+ len_needed += strlen(p);
+ }
+ va_end(vl);
+
+ if (len_needed < *path_len) {
+ free(*path);
+ *path = static_cast<char*>(malloc(len_needed));
+ }
+
+ va_start(vl, path_len);
+ p = va_arg(vl, char*);
+ strcpy(*path, p);
+ for (i = 1; i < n; i++) {
+ size_t plen;
+ p = va_arg(vl, char*);
+ plen = strlen(*path);
+ if (!is_path_separator((*path)[plen - 1])) {
+ (*path)[plen] = FN_LIBCHAR;
+ (*path)[plen + 1] = 0;
+ }
+ strcat(*path + plen, p);
+ }
+ va_end(vl);
+}
+
+static
+bool
+datadir_iter_next_file(datadir_iter_t *it)
+{
+ if (it->is_file && it->dbpath) {
+ make_path_n(2, &it->filepath, &it->filepath_len,
+ it->datadir_path, it->dbinfo.name);
+
+ make_path_n(1, &it->filepath_rel, &it->filepath_rel_len,
+ it->dbinfo.name);
+
+ it->is_empty_dir = false;
+ it->is_file = false;
+
+ return(true);
+ }
+
+ if (!it->dbpath || !it->dbdir) {
+
+ return(false);
+ }
+
+ while (os_file_readdir_next_file(it->dbpath, it->dbdir,
+ &it->fileinfo) == 0) {
+
+ if (it->fileinfo.type == OS_FILE_TYPE_DIR) {
+
+ continue;
+ }
+
+ /* We found a symlink or a file */
+ make_path_n(3, &it->filepath, &it->filepath_len,
+ it->datadir_path, it->dbinfo.name,
+ it->fileinfo.name);
+
+ make_path_n(2, &it->filepath_rel, &it->filepath_rel_len,
+ it->dbinfo.name, it->fileinfo.name);
+
+ it->is_empty_dir = false;
+
+ return(true);
+ }
+
+ return(false);
+}
+
+static
+bool
+datadir_iter_next(datadir_iter_t *it, datadir_node_t *node)
+{
+ bool ret = true;
+
+ pthread_mutex_lock(&it->mutex);
+
+ if (datadir_iter_next_file(it)) {
+
+ datadir_node_fill(node, it);
+
+ goto done;
+ }
+
+ while (datadir_iter_next_database(it)) {
+
+ if (datadir_iter_next_file(it)) {
+
+ datadir_node_fill(node, it);
+
+ goto done;
+ }
+
+ make_path_n(2, &it->filepath, &it->filepath_len,
+ it->datadir_path, it->dbinfo.name);
+
+ make_path_n(1, &it->filepath_rel, &it->filepath_rel_len,
+ it->dbinfo.name);
+
+ it->is_empty_dir = true;
+
+ datadir_node_fill(node, it);
+
+ goto done;
+ }
+
+ /* nothing found */
+ ret = false;
+
+done:
+ pthread_mutex_unlock(&it->mutex);
+
+ return(ret);
+}
+
+/************************************************************************
+Interface to read MySQL data file sequentially. One should open file
+with datafile_open to get cursor and close the cursor with
+datafile_close. Cursor can not be shared between multiple
+threads. */
+static
+void
+datadir_iter_free(datadir_iter_t *it)
+{
+ pthread_mutex_destroy(&it->mutex);
+
+ if (it->dbdir) {
+
+ os_file_closedir(it->dbdir);
+ }
+
+ if (it->dir) {
+
+ os_file_closedir(it->dir);
+ }
+
+ free(it->dbpath);
+ free(it->filepath);
+ free(it->filepath_rel);
+ free(it->datadir_path);
+ free(it);
+}
+
+
+/************************************************************************
+Holds the state needed to copy single data file. */
+struct datafile_cur_t {
+ pfs_os_file_t file;
+ char rel_path[FN_REFLEN];
+ char abs_path[FN_REFLEN];
+ MY_STAT statinfo;
+ uint thread_n;
+ byte* buf;
+ size_t buf_size;
+ size_t buf_read;
+ size_t buf_offset;
+
+ explicit datafile_cur_t(const char* filename = NULL) :
+ file(), thread_n(0), buf(NULL), buf_size(0),
+ buf_read(0), buf_offset(0)
+ {
+ memset(rel_path, 0, sizeof rel_path);
+ if (filename) {
+ strncpy(abs_path, filename, sizeof abs_path - 1);
+ abs_path[(sizeof abs_path) - 1] = 0;
+ } else {
+ abs_path[0] = '\0';
+ }
+ rel_path[0] = '\0';
+ memset(&statinfo, 0, sizeof statinfo);
+ }
+};
+
+static
+void
+datafile_close(datafile_cur_t *cursor)
+{
+ if (cursor->file != OS_FILE_CLOSED) {
+ os_file_close(cursor->file);
+ }
+ free(cursor->buf);
+}
+
+static
+bool
+datafile_open(const char *file, datafile_cur_t *cursor, uint thread_n)
+{
+ bool success;
+
+ new (cursor) datafile_cur_t(file);
+
+ /* Get the relative path for the destination tablespace name, i.e. the
+ one that can be appended to the backup root directory. Non-system
+ tablespaces may have absolute paths for remote tablespaces in MySQL
+ 5.6+. We want to make "local" copies for the backup. */
+ strncpy(cursor->rel_path,
+ xb_get_relative_path(cursor->abs_path, FALSE),
+ (sizeof cursor->rel_path) - 1);
+ cursor->rel_path[(sizeof cursor->rel_path) - 1] = '\0';
+
+ cursor->file = os_file_create_simple_no_error_handling(
+ 0, cursor->abs_path,
+ OS_FILE_OPEN, OS_FILE_READ_ALLOW_DELETE, true, &success);
+ if (!success) {
+ /* The following call prints an error message */
+ os_file_get_last_error(TRUE);
+
+ msg(thread_n,"error: cannot open "
+ "file %s", cursor->abs_path);
+
+ return(false);
+ }
+
+ if (!my_stat(cursor->abs_path, &cursor->statinfo, 0)) {
+ msg(thread_n, "error: cannot stat %s", cursor->abs_path);
+ datafile_close(cursor);
+ return(false);
+ }
+
+ posix_fadvise(cursor->file, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ cursor->buf_size = 10 * 1024 * 1024;
+ cursor->buf = static_cast<byte *>(malloc((ulint)cursor->buf_size));
+
+ return(true);
+}
+
+
+static
+xb_fil_cur_result_t
+datafile_read(datafile_cur_t *cursor)
+{
+ ulint to_read;
+
+ xtrabackup_io_throttling();
+
+ to_read = (ulint)MY_MIN(cursor->statinfo.st_size - cursor->buf_offset,
+ cursor->buf_size);
+
+ if (to_read == 0) {
+ return(XB_FIL_CUR_EOF);
+ }
+
+ if (os_file_read(IORequestRead,
+ cursor->file, cursor->buf, cursor->buf_offset,
+ to_read, nullptr) != DB_SUCCESS) {
+ return(XB_FIL_CUR_ERROR);
+ }
+
+ posix_fadvise(cursor->file, cursor->buf_offset, to_read,
+ POSIX_FADV_DONTNEED);
+
+ cursor->buf_read = to_read;
+ cursor->buf_offset += to_read;
+
+ return(XB_FIL_CUR_SUCCESS);
+}
+
+
+
+/************************************************************************
+Check to see if a file exists.
+Takes name of the file to check.
+@return true if file exists. */
+static
+bool
+file_exists(const char *filename)
+{
+ MY_STAT stat_arg;
+
+ if (!my_stat(filename, &stat_arg, MYF(0))) {
+
+ return(false);
+ }
+
+ return(true);
+}
+
+/************************************************************************
+Trim leading slashes from absolute path so it becomes relative */
+static
+const char *
+trim_dotslash(const char *path)
+{
+ while (*path) {
+ if (is_path_separator(*path)) {
+ ++path;
+ continue;
+ }
+ if (*path == '.' && is_path_separator(path[1])) {
+ path += 2;
+ continue;
+ }
+ break;
+ }
+
+ return(path);
+}
+
+
+
+/************************************************************************
+Check if string ends with given suffix.
+@return true if string ends with given suffix. */
+bool
+ends_with(const char *str, const char *suffix)
+{
+ size_t suffix_len = strlen(suffix);
+ size_t str_len = strlen(str);
+ return(str_len >= suffix_len
+ && strcmp(str + str_len - suffix_len, suffix) == 0);
+}
+
+static bool starts_with(const char *str, const char *prefix)
+{
+ return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+/************************************************************************
+Create directories recursively.
+@return 0 if directories created successfully. */
+static
+int
+mkdirp(const char *pathname, int Flags, myf MyFlags)
+{
+ char *parent, *p;
+
+ /* make a parent directory path */
+ if (!(parent= strdup(pathname)))
+ return(-1);
+
+ for (p = parent + strlen(parent);
+ !is_path_separator(*p) && p != parent; p--) ;
+
+ *p = 0;
+
+ /* try to make parent directory */
+ if (p != parent && mkdirp(parent, Flags, MyFlags) != 0) {
+ free(parent);
+ return(-1);
+ }
+
+ /* make this one if parent has been made */
+ if (my_mkdir(pathname, Flags, MyFlags) == 0) {
+ free(parent);
+ return(0);
+ }
+
+ /* if it already exists that is fine */
+ if (errno == EEXIST) {
+ free(parent);
+ return(0);
+ }
+
+ free(parent);
+ return(-1);
+}
+
+/************************************************************************
+Return true if first and second arguments are the same path. */
+bool
+equal_paths(const char *first, const char *second)
+{
+#ifdef HAVE_REALPATH
+ char *real_first, *real_second;
+ int result;
+
+ real_first = realpath(first, 0);
+ if (real_first == NULL) {
+ return false;
+ }
+
+ real_second = realpath(second, 0);
+ if (real_second == NULL) {
+ free(real_first);
+ return false;
+ }
+
+ result = strcmp(real_first, real_second);
+ free(real_first);
+ free(real_second);
+ return result == 0;
+#else
+ return strcmp(first, second) == 0;
+#endif
+}
+
+/************************************************************************
+Check if directory exists. Optionally create directory if doesn't
+exist.
+@return true if directory exists and if it was created successfully. */
+bool
+directory_exists(const char *dir, bool create)
+{
+ os_file_dir_t os_dir;
+ MY_STAT stat_arg;
+ char errbuf[MYSYS_STRERROR_SIZE];
+
+ if (my_stat(dir, &stat_arg, MYF(0)) == NULL) {
+
+ if (!create) {
+ return(false);
+ }
+
+ if (mkdirp(dir, 0777, MYF(0)) < 0) {
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ msg("Can not create directory %s: %s", dir, errbuf);
+ return(false);
+ }
+ }
+
+ /* could be symlink */
+ os_dir = os_file_opendir(dir);
+
+ if (os_dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr)) {
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ msg("Can not open directory %s: %s", dir,
+ errbuf);
+
+ return(false);
+ }
+
+ os_file_closedir(os_dir);
+
+ return(true);
+}
+
+/************************************************************************
+Check that directory exists and it is empty. */
+static
+bool
+directory_exists_and_empty(const char *dir, const char *comment)
+{
+ os_file_dir_t os_dir;
+ dberr_t err;
+ os_file_stat_t info;
+ bool empty;
+
+ if (!directory_exists(dir, true)) {
+ return(false);
+ }
+
+ os_dir = os_file_opendir(dir);
+
+ if (os_dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr)) {
+ msg("%s can not open directory %s", comment, dir);
+ return(false);
+ }
+
+ empty = (fil_file_readdir_next_file(&err, dir, os_dir, &info) != 0);
+
+ os_file_closedir(os_dir);
+
+ if (!empty) {
+ msg("%s directory %s is not empty!", comment, dir);
+ }
+
+ return(empty);
+}
+
+
+/************************************************************************
+Check if file name ends with given set of suffixes.
+@return true if it does. */
+static
+bool
+filename_matches(const char *filename, const char **ext_list)
+{
+ const char **ext;
+
+ for (ext = ext_list; *ext; ext++) {
+ if (ends_with(filename, *ext)) {
+ return(true);
+ }
+ }
+
+ return(false);
+}
+
+
+/************************************************************************
+Copy data file for backup. Also check if it is allowed to copy by
+comparing its name to the list of known data file types and checking
+if passes the rules for partial backup.
+@return true if file backed up or skipped successfully. */
+static
+bool
+datafile_copy_backup(ds_ctxt *ds_data, const char *filepath, uint thread_n)
+{
+ const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",
+ "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",
+ NULL};
+
+ /* Get the name and the path for the tablespace. node->name always
+ contains the path (which may be absolute for remote tablespaces in
+ 5.6+). space->name contains the tablespace name in the form
+ "./database/table.ibd" (in 5.5-) or "database/table" (in 5.6+). For a
+ multi-node shared tablespace, space->name contains the name of the first
+ node, but that's irrelevant, since we only need node_name to match them
+ against filters, and the shared tablespace is always copied regardless
+ of the filters value. */
+
+ if (check_if_skip_table(filepath)) {
+ msg(thread_n,"Skipping %s.", filepath);
+ return(true);
+ }
+
+ if (filename_matches(filepath, ext_list)) {
+ return ds_data->copy_file(filepath, filepath, thread_n);
+ }
+
+ return(true);
+}
+
+
+/************************************************************************
+Same as datafile_copy_backup, but put file name into the list for
+rsync command. */
+static
+bool
+datafile_rsync_backup(const char *filepath, bool save_to_list, FILE *f)
+{
+ const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",
+ "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",
+ NULL};
+
+ /* Get the name and the path for the tablespace. node->name always
+ contains the path (which may be absolute for remote tablespaces in
+ 5.6+). space->name contains the tablespace name in the form
+ "./database/table.ibd" (in 5.5-) or "database/table" (in 5.6+). For a
+ multi-node shared tablespace, space->name contains the name of the first
+ node, but that's irrelevant, since we only need node_name to match them
+ against filters, and the shared tablespace is always copied regardless
+ of the filters value. */
+
+ if (check_if_skip_table(filepath)) {
+ return(true);
+ }
+
+ if (filename_matches(filepath, ext_list)) {
+ fprintf(f, "%s\n", filepath);
+ if (save_to_list) {
+ rsync_list.insert(filepath);
+ }
+ }
+
+ return(true);
+}
+
+bool ds_ctxt_t::backup_file_print_buf(const char *filename,
+ const char *buf, int buf_len)
+{
+ ds_file_t *dstfile = NULL;
+ MY_STAT stat; /* unused for now */
+ const char *action;
+
+ memset(&stat, 0, sizeof(stat));
+
+ stat.st_size = buf_len;
+ stat.st_mtime = my_time(0);
+
+ dstfile = ds_open(this, filename, &stat);
+ if (dstfile == NULL) {
+ msg("error: Can't open the destination stream for %s",
+ filename);
+ goto error;
+ }
+
+ action = xb_get_copy_action("Writing");
+ msg("%s %s", action, filename);
+
+ if (buf_len == -1) {
+ goto error;
+ }
+
+ if (ds_write(dstfile, buf, buf_len)) {
+ goto error;
+ }
+
+ /* close */
+ msg(" ...done");
+
+ if (ds_close(dstfile)) {
+ goto error_close;
+ }
+
+ return(true);
+
+error:
+ if (dstfile != NULL) {
+ ds_close(dstfile);
+ }
+
+error_close:
+ msg("Error: backup file failed.");
+ return(false); /*ERROR*/
+
+ return true;
+};
+
+bool
+ds_ctxt_t::backup_file_vprintf(const char *filename,
+ const char *fmt, va_list ap)
+{
+ char *buf = 0;
+ int buf_len;
+ buf_len = vasprintf(&buf, fmt, ap);
+ bool result = backup_file_print_buf(filename, buf, buf_len);
+ free(buf);
+ return result;
+}
+
+bool
+ds_ctxt_t::backup_file_printf(const char *filename, const char *fmt, ...)
+{
+ bool result;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ result = backup_file_vprintf(filename, fmt, ap);
+
+ va_end(ap);
+
+ return(result);
+}
+
+static
+bool
+run_data_threads(datadir_iter_t *it, void (*func)(datadir_thread_ctxt_t *ctxt), uint n)
+{
+ datadir_thread_ctxt_t *data_threads;
+ uint i, count;
+ pthread_mutex_t count_mutex;
+ bool ret;
+
+ data_threads = (datadir_thread_ctxt_t*)
+ malloc(sizeof(datadir_thread_ctxt_t) * n);
+
+ pthread_mutex_init(&count_mutex, NULL);
+ count = n;
+
+ for (i = 0; i < n; i++) {
+ data_threads[i].it = it;
+ data_threads[i].n_thread = i + 1;
+ data_threads[i].count = &count;
+ data_threads[i].count_mutex = &count_mutex;
+ std::thread(func, data_threads + i).detach();
+ }
+
+ /* Wait for threads to exit */
+ while (1) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ pthread_mutex_lock(&count_mutex);
+ if (count == 0) {
+ pthread_mutex_unlock(&count_mutex);
+ break;
+ }
+ pthread_mutex_unlock(&count_mutex);
+ }
+
+ pthread_mutex_destroy(&count_mutex);
+
+ ret = true;
+ for (i = 0; i < n; i++) {
+ ret = data_threads[i].ret && ret;
+ if (!data_threads[i].ret) {
+ msg("Error: thread %u failed.", i);
+ }
+ }
+
+ free(data_threads);
+
+ return(ret);
+}
+
+
+/************************************************************************
+Copy file for backup/restore.
+@return true in case of success. */
+bool
+ds_ctxt_t::copy_file(const char *src_file_path,
+ const char *dst_file_path,
+ uint thread_n)
+{
+ char dst_name[FN_REFLEN];
+ ds_file_t *dstfile = NULL;
+ datafile_cur_t cursor;
+ xb_fil_cur_result_t res;
+ DBUG_ASSERT(datasink->remove);
+ const char *dst_path =
+ (xtrabackup_copy_back || xtrabackup_move_back)?
+ dst_file_path : trim_dotslash(dst_file_path);
+
+ if (!datafile_open(src_file_path, &cursor, thread_n)) {
+ goto error_close;
+ }
+
+ strncpy(dst_name, cursor.rel_path, sizeof(dst_name));
+
+ dstfile = ds_open(this, dst_path, &cursor.statinfo);
+ if (dstfile == NULL) {
+ msg(thread_n,"error: "
+ "cannot open the destination stream for %s", dst_name);
+ goto error;
+ }
+
+ msg(thread_n, "%s %s to %s", xb_get_copy_action(), src_file_path, dstfile->path);
+
+ /* The main copy loop */
+ while ((res = datafile_read(&cursor)) == XB_FIL_CUR_SUCCESS) {
+
+ if (ds_write(dstfile, cursor.buf, cursor.buf_read)) {
+ goto error;
+ }
+ DBUG_EXECUTE_IF("copy_file_error", errno=ENOSPC;goto error;);
+ }
+
+ if (res == XB_FIL_CUR_ERROR) {
+ goto error;
+ }
+
+ /* close */
+ msg(thread_n," ...done");
+ datafile_close(&cursor);
+ if (ds_close(dstfile)) {
+ goto error_close;
+ }
+ return(true);
+
+error:
+ datafile_close(&cursor);
+ if (dstfile != NULL) {
+ datasink->remove(dstfile->path);
+ ds_close(dstfile);
+ }
+
+error_close:
+ msg(thread_n,"Error: copy_file() failed.");
+ return(false); /*ERROR*/
+}
+
+
+/************************************************************************
+Try to move file by renaming it. If source and destination are on
+different devices fall back to copy and unlink.
+@return true in case of success. */
+bool
+ds_ctxt_t::move_file(const char *src_file_path,
+ const char *dst_file_path,
+ const char *dst_dir, uint thread_n)
+{
+ char errbuf[MYSYS_STRERROR_SIZE];
+ char dst_file_path_abs[FN_REFLEN];
+ char dst_dir_abs[FN_REFLEN];
+ size_t dirname_length;
+
+ snprintf(dst_file_path_abs, sizeof(dst_file_path_abs),
+ "%s/%s", dst_dir, dst_file_path);
+
+ dirname_part(dst_dir_abs, dst_file_path_abs, &dirname_length);
+
+ if (!directory_exists(dst_dir_abs, true)) {
+ return(false);
+ }
+
+ if (file_exists(dst_file_path_abs)) {
+ msg("Error: Move file %s to %s failed: Destination "
+ "file exists", src_file_path, dst_file_path_abs);
+ return(false);
+ }
+
+ msg(thread_n,"Moving %s to %s", src_file_path, dst_file_path_abs);
+
+ if (my_rename(src_file_path, dst_file_path_abs, MYF(0)) != 0) {
+ if (my_errno == EXDEV) {
+ /* Fallback to copy/unlink */
+ if(!copy_file(src_file_path,
+ dst_file_path, thread_n))
+ return false;
+ msg(thread_n,"Removing %s", src_file_path);
+ if (unlink(src_file_path) != 0) {
+ my_strerror(errbuf, sizeof(errbuf), errno);
+ msg("Warning: unlink %s failed: %s",
+ src_file_path,
+ errbuf);
+ }
+ return true;
+ }
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ msg("Can not move file %s to %s: %s",
+ src_file_path, dst_file_path_abs,
+ errbuf);
+ return(false);
+ }
+ msg(thread_n," ...done");
+
+ return(true);
+}
+
+
+/************************************************************************
+Read link from .isl file if any and store it in the global map associated
+with given tablespace. */
+static
+void
+read_link_file(const char *ibd_filepath, const char *link_filepath)
+{
+ char *filepath= NULL;
+
+ FILE *file = fopen(link_filepath, "r+b");
+ if (file) {
+ filepath = static_cast<char*>(malloc(OS_FILE_MAX_PATH));
+
+ os_file_read_string(file, filepath, OS_FILE_MAX_PATH);
+ fclose(file);
+
+ if (size_t len = strlen(filepath)) {
+ /* Trim whitespace from end of filepath */
+ ulint lastch = len - 1;
+ while (lastch > 4 && filepath[lastch] <= 0x20) {
+ filepath[lastch--] = 0x00;
+ }
+ }
+
+ tablespace_locations[ibd_filepath] = filepath;
+ }
+ free(filepath);
+}
+
+
+/************************************************************************
+Return the location of given .ibd if it was previously read
+from .isl file.
+@return NULL or destination .ibd file path. */
+static
+const char *
+tablespace_filepath(const char *ibd_filepath)
+{
+ std::map<std::string, std::string>::iterator it;
+
+ it = tablespace_locations.find(ibd_filepath);
+
+ if (it != tablespace_locations.end()) {
+ return it->second.c_str();
+ }
+
+ return NULL;
+}
+
+
+/************************************************************************
+Copy or move file depending on current mode.
+@return true in case of success. */
+static
+bool
+copy_or_move_file(ds_ctxt *datasink0, const char *src_file_path,
+ const char *dst_file_path,
+ const char *dst_dir,
+ uint thread_n,
+ bool copy = xtrabackup_copy_back)
+{
+ ds_ctxt_t *datasink = datasink0; /* copy to datadir by default */
+ char filedir[FN_REFLEN];
+ size_t filedir_len;
+ bool ret;
+
+ /* read the link from .isl file */
+ if (ends_with(src_file_path, ".isl")) {
+ char *ibd_filepath;
+
+ ibd_filepath = strdup(src_file_path);
+ strcpy(ibd_filepath + strlen(ibd_filepath) - 3, "ibd");
+
+ read_link_file(ibd_filepath, src_file_path);
+
+ free(ibd_filepath);
+ }
+
+ /* check if there is .isl file */
+ if (ends_with(src_file_path, ".ibd")) {
+ char *link_filepath;
+ const char *filepath;
+
+ link_filepath = strdup(src_file_path);
+ strcpy(link_filepath + strlen(link_filepath) - 3, "isl");
+
+ read_link_file(src_file_path, link_filepath);
+
+ filepath = tablespace_filepath(src_file_path);
+
+ if (filepath != NULL) {
+ dirname_part(filedir, filepath, &filedir_len);
+
+ dst_file_path = filepath + filedir_len;
+ dst_dir = filedir;
+
+ if (!directory_exists(dst_dir, true)) {
+ ret = false;
+ free(link_filepath);
+ goto cleanup;
+ }
+
+ datasink = ds_create(dst_dir, DS_TYPE_LOCAL);
+ }
+
+ free(link_filepath);
+ }
+
+ ret = (copy ?
+ datasink->copy_file(src_file_path, dst_file_path, thread_n) :
+ datasink->move_file(src_file_path, dst_file_path,
+ dst_dir, thread_n));
+
+cleanup:
+
+ if (datasink != datasink0) {
+ ds_destroy(datasink);
+ }
+
+ return(ret);
+}
+
+
+
+
+static
+bool
+backup_files(ds_ctxt *ds_data, const char *from, bool prep_mode)
+{
+ char rsync_tmpfile_name[FN_REFLEN];
+ FILE *rsync_tmpfile = NULL;
+ datadir_iter_t *it;
+ datadir_node_t node;
+ bool ret = true;
+
+ if (prep_mode && !opt_rsync) {
+ return(true);
+ }
+
+ if (opt_rsync) {
+ snprintf(rsync_tmpfile_name, sizeof(rsync_tmpfile_name),
+ "%s/%s%d", opt_mysql_tmpdir,
+ "xtrabackup_rsyncfiles_pass",
+ prep_mode ? 1 : 2);
+ rsync_tmpfile = fopen(rsync_tmpfile_name, "w");
+ if (rsync_tmpfile == NULL) {
+ msg("Error: can't create file %s",
+ rsync_tmpfile_name);
+ return(false);
+ }
+ }
+
+ msg("Starting %s non-InnoDB tables and files",
+ prep_mode ? "prep copy of" : "to backup");
+
+ datadir_node_init(&node);
+ it = datadir_iter_new(from);
+
+ while (datadir_iter_next(it, &node)) {
+
+ if (!node.is_empty_dir) {
+ if (opt_rsync) {
+ ret = datafile_rsync_backup(node.filepath,
+ !prep_mode, rsync_tmpfile);
+ } else {
+ ret = datafile_copy_backup(ds_data, node.filepath, 1);
+ }
+ if (!ret) {
+ msg("Failed to copy file %s", node.filepath);
+ goto out;
+ }
+ } else if (!prep_mode) {
+ /* backup fake file into empty directory */
+ char path[FN_REFLEN];
+ snprintf(path, sizeof(path),
+ "%s/db.opt", node.filepath);
+ if (!(ret = ds_data->backup_file_printf(
+ trim_dotslash(path), "%s", ""))) {
+ msg("Failed to create file %s", path);
+ goto out;
+ }
+ }
+ }
+
+ if (opt_rsync) {
+ std::stringstream cmd;
+ int err;
+
+ if (buffer_pool_filename && file_exists(buffer_pool_filename)) {
+ fprintf(rsync_tmpfile, "%s\n", buffer_pool_filename);
+ rsync_list.insert(buffer_pool_filename);
+ }
+ if (file_exists("ib_lru_dump")) {
+ fprintf(rsync_tmpfile, "%s\n", "ib_lru_dump");
+ rsync_list.insert("ib_lru_dump");
+ }
+
+ fclose(rsync_tmpfile);
+ rsync_tmpfile = NULL;
+
+ cmd << "rsync -t . --files-from=" << rsync_tmpfile_name
+ << " " << xtrabackup_target_dir;
+
+ msg("Starting rsync as: %s", cmd.str().c_str());
+ if ((err = system(cmd.str().c_str()) && !prep_mode) != 0) {
+ msg("Error: rsync failed with error code %d", err);
+ ret = false;
+ goto out;
+ }
+ msg("rsync finished successfully.");
+
+ if (!prep_mode && !opt_no_lock) {
+ char path[FN_REFLEN];
+ char dst_path[FN_REFLEN];
+ char *newline;
+
+ /* Remove files that have been removed between first and
+ second passes. Cannot use "rsync --delete" because it
+ does not work with --files-from. */
+ snprintf(rsync_tmpfile_name, sizeof(rsync_tmpfile_name),
+ "%s/%s", opt_mysql_tmpdir,
+ "xtrabackup_rsyncfiles_pass1");
+
+ rsync_tmpfile = fopen(rsync_tmpfile_name, "r");
+ if (rsync_tmpfile == NULL) {
+ msg("Error: can't open file %s",
+ rsync_tmpfile_name);
+ ret = false;
+ goto out;
+ }
+
+ while (fgets(path, sizeof(path), rsync_tmpfile)) {
+
+ newline = strchr(path, '\n');
+ if (newline) {
+ *newline = 0;
+ }
+ if (rsync_list.count(path) < 1) {
+ snprintf(dst_path, sizeof(dst_path),
+ "%s/%s", xtrabackup_target_dir,
+ path);
+ msg("Removing %s", dst_path);
+ unlink(dst_path);
+ }
+ }
+
+ fclose(rsync_tmpfile);
+ rsync_tmpfile = NULL;
+ }
+ }
+
+ msg("Finished %s non-InnoDB tables and files",
+ prep_mode ? "a prep copy of" : "backing up");
+
+out:
+ datadir_iter_free(it);
+ datadir_node_free(&node);
+
+ if (rsync_tmpfile != NULL) {
+ fclose(rsync_tmpfile);
+ }
+
+ return(ret);
+}
+
+
+lsn_t get_current_lsn(MYSQL *connection)
+{
+ static const char lsn_prefix[] = "\nLog sequence number ";
+ lsn_t lsn = 0;
+ if (MYSQL_RES *res = xb_mysql_query(connection,
+ "SHOW ENGINE INNODB STATUS",
+ true, false)) {
+ if (MYSQL_ROW row = mysql_fetch_row(res)) {
+ const char *p= strstr(row[2], lsn_prefix);
+ DBUG_ASSERT(p);
+ if (p) {
+ p += sizeof lsn_prefix - 1;
+ lsn = lsn_t(strtoll(p, NULL, 10));
+ }
+ }
+ mysql_free_result(res);
+ }
+ return lsn;
+}
+
+lsn_t server_lsn_after_lock;
+extern void backup_wait_for_lsn(lsn_t lsn);
+/** Start --backup */
+bool backup_start(ds_ctxt *ds_data, ds_ctxt *ds_meta,
+ CorruptedPages &corrupted_pages)
+{
+ if (!opt_no_lock) {
+ if (opt_safe_slave_backup) {
+ if (!wait_for_safe_slave(mysql_connection)) {
+ return(false);
+ }
+ }
+
+ if (!backup_files(ds_data, fil_path_to_mysql_datadir, true)) {
+ return(false);
+ }
+
+ history_lock_time = time(NULL);
+
+ if (!lock_tables(mysql_connection)) {
+ return(false);
+ }
+ server_lsn_after_lock = get_current_lsn(mysql_connection);
+ }
+
+ if (!backup_files(ds_data, fil_path_to_mysql_datadir, false)) {
+ return(false);
+ }
+
+ if (!backup_files_from_datadir(ds_data, fil_path_to_mysql_datadir,
+ "aws-kms-key") ||
+ !backup_files_from_datadir(ds_data,
+ aria_log_dir_path,
+ "aria_log")) {
+ return false;
+ }
+
+ if (has_rocksdb_plugin()) {
+ rocksdb_create_checkpoint();
+ }
+
+ msg("Waiting for log copy thread to read lsn %llu", (ulonglong)server_lsn_after_lock);
+ backup_wait_for_lsn(server_lsn_after_lock);
+ DBUG_EXECUTE_FOR_KEY("sleep_after_waiting_for_lsn", {},
+ {
+ ulong milliseconds = strtoul(dbug_val, NULL, 10);
+ msg("sleep_after_waiting_for_lsn");
+ my_sleep(milliseconds*1000UL);
+ });
+
+ corrupted_pages.backup_fix_ddl(ds_data, ds_meta);
+
+ // There is no need to stop slave thread before coping non-Innodb data when
+ // --no-lock option is used because --no-lock option requires that no DDL or
+ // DML to non-transaction tables can occur.
+ if (opt_no_lock) {
+ if (opt_safe_slave_backup) {
+ if (!wait_for_safe_slave(mysql_connection)) {
+ return(false);
+ }
+ }
+ }
+
+ if (opt_slave_info) {
+ lock_binlog_maybe(mysql_connection);
+
+ if (!write_slave_info(ds_data, mysql_connection)) {
+ return(false);
+ }
+ }
+
+ /* The only reason why Galera/binlog info is written before
+ wait_for_ibbackup_log_copy_finish() is that after that call the xtrabackup
+ binary will start streamig a temporary copy of REDO log to stdout and
+ thus, any streaming from innobackupex would interfere. The only way to
+ avoid that is to have a single process, i.e. merge innobackupex and
+ xtrabackup. */
+ if (opt_galera_info) {
+ if (!write_galera_info(ds_data, mysql_connection)) {
+ return(false);
+ }
+ }
+
+ if (opt_binlog_info == BINLOG_INFO_ON) {
+
+ lock_binlog_maybe(mysql_connection);
+ write_binlog_info(ds_data, mysql_connection);
+ }
+
+ if (!opt_no_lock) {
+ msg("Executing FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS...");
+ xb_mysql_query(mysql_connection,
+ "FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS", false);
+ }
+
+ return(true);
+}
+
+/** Release resources after backup_start() */
+void backup_release()
+{
+ /* release all locks */
+ if (!opt_no_lock) {
+ unlock_all(mysql_connection);
+ history_lock_time = 0;
+ } else {
+ history_lock_time = time(NULL) - history_lock_time;
+ }
+
+ if (opt_lock_ddl_per_table) {
+ mdl_unlock_all();
+ }
+
+ if (opt_safe_slave_backup && sql_thread_started) {
+ msg("Starting slave SQL thread");
+ xb_mysql_query(mysql_connection,
+ "START SLAVE SQL_THREAD", false);
+ }
+}
+
+static const char *default_buffer_pool_file = "ib_buffer_pool";
+
+/** Finish after backup_start() and backup_release() */
+bool backup_finish(ds_ctxt *ds_data)
+{
+ /* Copy buffer pool dump or LRU dump */
+ if (!opt_rsync && opt_galera_info) {
+ if (buffer_pool_filename && file_exists(buffer_pool_filename)) {
+ ds_data->copy_file(buffer_pool_filename, default_buffer_pool_file, 0);
+ }
+ if (file_exists("ib_lru_dump")) {
+ ds_data->copy_file("ib_lru_dump", "ib_lru_dump", 0);
+ }
+ }
+
+ if (has_rocksdb_plugin()) {
+ rocksdb_backup_checkpoint(ds_data);
+ }
+
+ msg("Backup created in directory '%s'", xtrabackup_target_dir);
+ if (mysql_binlog_position != NULL) {
+ msg("MySQL binlog position: %s", mysql_binlog_position);
+ }
+ if (mysql_slave_position && opt_slave_info) {
+ msg("MySQL slave binlog position: %s",
+ mysql_slave_position);
+ }
+
+ if (!write_backup_config_file(ds_data)) {
+ return(false);
+ }
+
+ if (!write_xtrabackup_info(ds_data, mysql_connection, XTRABACKUP_INFO,
+ opt_history != 0, true)) {
+ return(false);
+ }
+
+ return(true);
+}
+
+
+/*
+ Drop all empty database directories in the base backup
+ that do not exists in the icremental backup.
+
+ This effectively re-plays all DROP DATABASE statements happened
+ in between base backup and incremental backup creation time.
+
+ Note, only checking if base_dir/db/ is empty is not enough,
+ because inc_dir/db/db.opt might have been dropped for some reasons,
+ which may also result into empty base_dir/db/.
+
+ Only the fact that at the same time:
+ - base_dir/db/ exists
+ - inc_dir/db/ does not exist
+ means that DROP DATABASE happened.
+*/
+static void
+ibx_incremental_drop_databases(const char *base_dir,
+ const char *inc_dir)
+{
+ datadir_node_t node;
+ datadir_node_init(&node);
+ datadir_iter_t *it = datadir_iter_new(base_dir);
+
+ while (datadir_iter_next(it, &node)) {
+ if (node.is_empty_dir) {
+ char path[FN_REFLEN];
+ snprintf(path, sizeof(path), "%s/%s",
+ inc_dir, node.filepath_rel);
+ if (!directory_exists(path, false)) {
+ msg("Removing %s", node.filepath);
+ rmdir(node.filepath);
+ }
+ }
+
+ }
+ datadir_iter_free(it);
+ datadir_node_free(&node);
+}
+
+
+static bool
+ibx_copy_incremental_over_full()
+{
+ const char *ext_list[] = {"frm", "isl", "MYD", "MYI", "MAD", "MAI",
+ "MRG", "TRG", "TRN", "ARM", "ARZ", "CSM", "CSV", "opt", "par",
+ NULL};
+ const char *sup_files[] = {"xtrabackup_binlog_info",
+ "xtrabackup_galera_info",
+ "xtrabackup_slave_info",
+ "xtrabackup_info",
+ "ib_lru_dump",
+ NULL};
+ datadir_iter_t *it = NULL;
+ datadir_node_t node;
+ bool ret = true;
+ char path[FN_REFLEN];
+ int i;
+ ds_ctxt *ds_data= NULL;
+
+ DBUG_ASSERT(!opt_galera_info);
+ datadir_node_init(&node);
+
+ /* If we were applying an incremental change set, we need to make
+ sure non-InnoDB files and xtrabackup_* metainfo files are copied
+ to the full backup directory. */
+
+ if (xtrabackup_incremental) {
+
+ ds_data = ds_create(xtrabackup_target_dir, DS_TYPE_LOCAL);
+
+ it = datadir_iter_new(xtrabackup_incremental_dir);
+
+ while (datadir_iter_next(it, &node)) {
+
+ /* copy only non-innodb files */
+
+ if (node.is_empty_dir
+ || !filename_matches(node.filepath, ext_list)) {
+ continue;
+ }
+
+ if (file_exists(node.filepath_rel)) {
+ unlink(node.filepath_rel);
+ }
+
+ if (!(ret = ds_data->copy_file(node.filepath,
+ node.filepath_rel, 1))) {
+ msg("Failed to copy file %s",
+ node.filepath);
+ goto cleanup;
+ }
+ }
+
+ if (!(ret = backup_files_from_datadir(ds_data,
+ xtrabackup_incremental_dir,
+ "aws-kms-key")) ||
+ !(ret = backup_files_from_datadir(ds_data,
+ xtrabackup_incremental_dir,
+ "aria_log")))
+ goto cleanup;
+
+ /* copy supplementary files */
+
+ for (i = 0; sup_files[i]; i++) {
+ snprintf(path, sizeof(path), "%s/%s",
+ xtrabackup_incremental_dir,
+ sup_files[i]);
+
+ if (file_exists(path))
+ {
+ if (file_exists(sup_files[i])) {
+ unlink(sup_files[i]);
+ }
+ ds_data->copy_file(path, sup_files[i], 0);
+ }
+ }
+
+ if (directory_exists(ROCKSDB_BACKUP_DIR, false)) {
+ if (my_rmtree(ROCKSDB_BACKUP_DIR, MYF(0))) {
+ die("Can't remove " ROCKSDB_BACKUP_DIR);
+ }
+ }
+ snprintf(path, sizeof(path), "%s/" ROCKSDB_BACKUP_DIR, xtrabackup_incremental_dir);
+ if (directory_exists(path, false)) {
+ if (my_mkdir(ROCKSDB_BACKUP_DIR, 0777, MYF(0))) {
+ die("my_mkdir failed for " ROCKSDB_BACKUP_DIR);
+ }
+ ds_data->copy_or_move_dir(path, ROCKSDB_BACKUP_DIR, true, true);
+ }
+ ibx_incremental_drop_databases(xtrabackup_target_dir,
+ xtrabackup_incremental_dir);
+ }
+
+
+cleanup:
+ if (it != NULL) {
+ datadir_iter_free(it);
+ }
+
+ if (ds_data != NULL) {
+ ds_destroy(ds_data);
+ }
+
+ datadir_node_free(&node);
+
+ return(ret);
+}
+
+bool
+ibx_cleanup_full_backup()
+{
+ const char *ext_list[] = {"delta", "meta", "ibd", NULL};
+ datadir_iter_t *it = NULL;
+ datadir_node_t node;
+ bool ret = true;
+
+ datadir_node_init(&node);
+
+ /* If we are applying an incremental change set, we need to make
+ sure non-InnoDB files are cleaned up from full backup dir before
+ we copy files from incremental dir. */
+
+ it = datadir_iter_new(xtrabackup_target_dir);
+
+ while (datadir_iter_next(it, &node)) {
+
+ if (node.is_empty_dir) {
+#ifdef _WIN32
+ DeleteFile(node.filepath);
+#else
+ rmdir(node.filepath);
+#endif
+ }
+
+ if (xtrabackup_incremental && !node.is_empty_dir
+ && !filename_matches(node.filepath, ext_list)) {
+ unlink(node.filepath);
+ }
+ }
+
+ datadir_iter_free(it);
+
+ datadir_node_free(&node);
+
+ return(ret);
+}
+
+bool
+apply_log_finish()
+{
+ if (!ibx_cleanup_full_backup()
+ || !ibx_copy_incremental_over_full()) {
+ return(false);
+ }
+
+ return(true);
+}
+
+class Copy_back_dst_dir
+{
+ std::string buf;
+
+public:
+ const char *make(const char *path)
+ {
+ if (!path || !path[0])
+ return mysql_data_home;
+ if (is_absolute_path(path))
+ return path;
+ return buf.assign(mysql_data_home).append(path).c_str();
+ }
+};
+
+
+static inline bool
+is_aria_log_dir_file(const datadir_node_t &node)
+{
+ return starts_with(node.filepath_rel, "aria_log");
+}
+
+
+bool
+copy_back_aria_logs(const char *dstdir)
+{
+ std::unique_ptr<ds_ctxt_t, void (&)(ds_ctxt_t*)>
+ ds_ctxt_aria_log_dir_path(ds_create(dstdir, DS_TYPE_LOCAL), ds_destroy);
+
+ datadir_node_t node;
+ datadir_node_init(&node);
+ datadir_iter_t *it = datadir_iter_new(".", false);
+
+ while (datadir_iter_next(it, &node))
+ {
+ if (!is_aria_log_dir_file(node))
+ continue;
+ if (!copy_or_move_file(ds_ctxt_aria_log_dir_path.get(),
+ node.filepath, node.filepath_rel,
+ dstdir, 1))
+ return false;
+ }
+ datadir_node_free(&node);
+ datadir_iter_free(it);
+ return true;
+}
+
+
+bool
+copy_back()
+{
+ bool ret = false;
+ datadir_iter_t *it = NULL;
+ datadir_node_t node;
+ const char *dst_dir;
+
+ memset(&node, 0, sizeof(node));
+
+ if (!opt_force_non_empty_dirs) {
+ if (!directory_exists_and_empty(mysql_data_home,
+ "Original data")) {
+ return(false);
+ }
+ } else {
+ if (!directory_exists(mysql_data_home, true)) {
+ return(false);
+ }
+ }
+
+#ifdef _WIN32
+ /* Initialize security descriptor for the new directories
+ to be the same as for datadir */
+ DWORD res = GetNamedSecurityInfoA(mysql_data_home,
+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
+ NULL, NULL, NULL, NULL,
+ &my_dir_security_attributes.lpSecurityDescriptor);
+ if (res != ERROR_SUCCESS) {
+ msg("Unable to read security descriptor of %s",mysql_data_home);
+ }
+#endif
+
+ if (srv_undo_dir && *srv_undo_dir
+ && !directory_exists(srv_undo_dir, true)) {
+ return(false);
+ }
+ if (innobase_data_home_dir && *innobase_data_home_dir
+ && !directory_exists(innobase_data_home_dir, true)) {
+ return(false);
+ }
+ if (srv_log_group_home_dir && *srv_log_group_home_dir
+ && !directory_exists(srv_log_group_home_dir, true)) {
+ return(false);
+ }
+
+ Copy_back_dst_dir aria_log_dir_path_dst;
+ const char *aria_log_dir_path_abs= aria_log_dir_path_dst.make(aria_log_dir_path);
+ if (aria_log_dir_path && *aria_log_dir_path
+ && !directory_exists(aria_log_dir_path_abs, true)) {
+ return false;
+ }
+
+ /* cd to backup directory */
+ if (my_setwd(xtrabackup_target_dir, MYF(MY_WME)))
+ {
+ msg("Can't my_setwd %s", xtrabackup_target_dir);
+ return(false);
+ }
+
+ if (!copy_back_aria_logs(aria_log_dir_path_abs))
+ return false;
+
+ /* parse data file path */
+
+ if (!innobase_data_file_path) {
+ innobase_data_file_path = (char*) "ibdata1:10M:autoextend";
+ }
+
+ srv_sys_space.set_path(".");
+
+ if (!srv_sys_space.parse_params(innobase_data_file_path, true)) {
+ msg("syntax error in innodb_data_file_path");
+ return(false);
+ }
+
+ srv_max_n_threads = 1000;
+
+ /* copy undo tablespaces */
+
+ Copy_back_dst_dir dst_dir_buf;
+
+ dst_dir = dst_dir_buf.make(srv_undo_dir);
+
+ ds_ctxt *ds_tmp = ds_create(dst_dir, DS_TYPE_LOCAL);
+
+ for (uint i = 1; i <= TRX_SYS_MAX_UNDO_SPACES; i++) {
+ char filename[20];
+ sprintf(filename, "undo%03u", i);
+ if (!file_exists(filename)) {
+ break;
+ }
+ if (!(ret = copy_or_move_file(ds_tmp, filename, filename,
+ dst_dir, 1))) {
+ goto cleanup;
+ }
+ }
+
+ ds_destroy(ds_tmp);
+ ds_tmp = NULL;
+
+ /* copy redo logs */
+
+ dst_dir = dst_dir_buf.make(srv_log_group_home_dir);
+
+ /* --backup generates a single ib_logfile0, which we must copy. */
+
+ ds_tmp = ds_create(dst_dir, DS_TYPE_LOCAL);
+ if (!(ret = copy_or_move_file(ds_tmp, LOG_FILE_NAME, LOG_FILE_NAME,
+ dst_dir, 1))) {
+ goto cleanup;
+ }
+ ds_destroy(ds_tmp);
+
+ /* copy innodb system tablespace(s) */
+
+ dst_dir = dst_dir_buf.make(innobase_data_home_dir);
+
+ ds_tmp = ds_create(dst_dir, DS_TYPE_LOCAL);
+
+ for (Tablespace::const_iterator iter(srv_sys_space.begin()),
+ end(srv_sys_space.end());
+ iter != end;
+ ++iter) {
+ const char *filepath = iter->filepath();
+ if (!(ret = copy_or_move_file(ds_tmp, base_name(filepath),
+ filepath, dst_dir, 1))) {
+ goto cleanup;
+ }
+ }
+
+ ds_destroy(ds_tmp);
+
+ /* copy the rest of tablespaces */
+ ds_tmp = ds_create(mysql_data_home, DS_TYPE_LOCAL);
+
+ it = datadir_iter_new(".", false);
+
+ datadir_node_init(&node);
+
+ while (datadir_iter_next(it, &node)) {
+ const char *ext_list[] = {"backup-my.cnf",
+ "xtrabackup_binary", "xtrabackup_binlog_info",
+ "xtrabackup_checkpoints", ".qp", ".pmap", ".tmp",
+ NULL};
+ const char *filename;
+ char c_tmp;
+ int i_tmp;
+
+ /* Skip aria log files */
+ if (is_aria_log_dir_file(node))
+ continue;
+
+ if (strstr(node.filepath,"/" ROCKSDB_BACKUP_DIR "/")
+#ifdef _WIN32
+ || strstr(node.filepath,"\\" ROCKSDB_BACKUP_DIR "\\")
+#endif
+ )
+ {
+ // copied at later step
+ continue;
+ }
+
+ /* create empty directories */
+ if (node.is_empty_dir) {
+ char path[FN_REFLEN];
+
+ snprintf(path, sizeof(path), "%s/%s",
+ mysql_data_home, node.filepath_rel);
+
+ msg("Creating directory %s", path);
+
+ if (mkdirp(path, 0777, MYF(0)) < 0) {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ msg("Can not create directory %s: %s",
+ path, errbuf);
+ ret = false;
+
+ goto cleanup;
+
+ }
+
+ msg(" ...done.");
+
+ continue;
+ }
+
+ filename = base_name(node.filepath);
+
+ /* skip .qp files */
+ if (filename_matches(filename, ext_list)) {
+ continue;
+ }
+
+ /* skip undo tablespaces */
+ if (sscanf(filename, "undo%d%c", &i_tmp, &c_tmp) == 1) {
+ continue;
+ }
+
+ /* skip the redo log (it was already copied) */
+ if (!strcmp(filename, LOG_FILE_NAME)) {
+ continue;
+ }
+
+ /* skip buffer pool dump */
+ if (!strcmp(filename, default_buffer_pool_file)) {
+ continue;
+ }
+
+ /* skip innodb data files */
+ for (Tablespace::const_iterator iter(srv_sys_space.begin()),
+ end(srv_sys_space.end()); iter != end; ++iter) {
+ if (!strcmp(base_name(iter->filepath()), filename)) {
+ goto next_file;
+ }
+ }
+
+ if (!(ret = copy_or_move_file(ds_tmp, node.filepath, node.filepath_rel,
+ mysql_data_home, 1))) {
+ goto cleanup;
+ }
+ next_file:
+ continue;
+ }
+
+ /* copy buffer pool dump */
+
+ if (file_exists(default_buffer_pool_file) &&
+ innobase_buffer_pool_filename) {
+ copy_or_move_file(ds_tmp, default_buffer_pool_file,
+ innobase_buffer_pool_filename,
+ mysql_data_home, 0);
+ }
+
+ rocksdb_copy_back(ds_tmp);
+
+cleanup:
+ if (it != NULL) {
+ datadir_iter_free(it);
+ }
+
+ datadir_node_free(&node);
+
+ if (ds_tmp != NULL) {
+ ds_destroy(ds_tmp);
+ }
+
+ ds_tmp = NULL;
+
+ return(ret);
+}
+
+bool
+decrypt_decompress_file(const char *filepath, uint thread_n)
+{
+ std::stringstream cmd, message;
+ char *dest_filepath = strdup(filepath);
+ bool needs_action = false;
+
+ cmd << IF_WIN("type ","cat ") << filepath;
+
+ if (opt_decompress
+ && ends_with(filepath, ".qp")) {
+ cmd << " | qpress -dio ";
+ dest_filepath[strlen(dest_filepath) - 3] = 0;
+ if (needs_action) {
+ message << " and ";
+ }
+ message << "decompressing";
+ needs_action = true;
+ }
+
+ cmd << " > " << dest_filepath;
+ message << " " << filepath;
+
+ free(dest_filepath);
+
+ if (needs_action) {
+
+ msg(thread_n,"%s\n", message.str().c_str());
+
+ if (system(cmd.str().c_str()) != 0) {
+ return(false);
+ }
+
+ if (opt_remove_original) {
+ msg(thread_n, "Removing %s", filepath);
+ if (my_delete(filepath, MYF(MY_WME)) != 0) {
+ return(false);
+ }
+ }
+ }
+
+ return(true);
+}
+
+static void decrypt_decompress_thread_func(datadir_thread_ctxt_t *ctxt)
+{
+ bool ret = true;
+ datadir_node_t node;
+
+ datadir_node_init(&node);
+
+ while (datadir_iter_next(ctxt->it, &node)) {
+
+ /* skip empty directories in backup */
+ if (node.is_empty_dir) {
+ continue;
+ }
+
+ if (!ends_with(node.filepath, ".qp")) {
+ continue;
+ }
+
+ if (!(ret = decrypt_decompress_file(node.filepath,
+ ctxt->n_thread))) {
+ goto cleanup;
+ }
+ }
+
+cleanup:
+
+ datadir_node_free(&node);
+
+ pthread_mutex_lock(ctxt->count_mutex);
+ --(*ctxt->count);
+ pthread_mutex_unlock(ctxt->count_mutex);
+
+ ctxt->ret = ret;
+}
+
+bool
+decrypt_decompress()
+{
+ bool ret;
+ datadir_iter_t *it = NULL;
+
+ srv_max_n_threads = 1000;
+
+ /* cd to backup directory */
+ if (my_setwd(xtrabackup_target_dir, MYF(MY_WME)))
+ {
+ msg("Can't my_setwd %s", xtrabackup_target_dir);
+ return(false);
+ }
+
+ /* copy the rest of tablespaces */
+ ds_ctxt *ds_tmp = ds_create(".", DS_TYPE_LOCAL);
+
+ it = datadir_iter_new(".", false);
+
+ ut_a(xtrabackup_parallel >= 0);
+
+ ret = run_data_threads(it, decrypt_decompress_thread_func,
+ xtrabackup_parallel ? xtrabackup_parallel : 1);
+
+ if (it != NULL) {
+ datadir_iter_free(it);
+ }
+
+ if (ds_tmp != NULL) {
+ ds_destroy(ds_tmp);
+ }
+
+ ds_tmp = NULL;
+
+ return(ret);
+}
+
+/*
+ Copy some files from top level datadir.
+ Do not copy the Innodb files (ibdata1, redo log files),
+ as this is done in a separate step.
+*/
+static bool backup_files_from_datadir(ds_ctxt_t *ds_data,
+ const char *dir_path,
+ const char *prefix)
+{
+ os_file_dir_t dir = os_file_opendir(dir_path);
+ if (dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr)) return false;
+
+ os_file_stat_t info;
+ bool ret = true;
+ while (os_file_readdir_next_file(dir_path, dir, &info) == 0) {
+
+ if (info.type != OS_FILE_TYPE_FILE)
+ continue;
+
+ const char *pname = strrchr(info.name, '/');
+#ifdef _WIN32
+ if (const char *last = strrchr(info.name, '\\')) {
+ if (!pname || last >pname) {
+ pname = last;
+ }
+ }
+#endif
+ if (!pname)
+ pname = info.name;
+
+ if (!starts_with(pname, prefix))
+ /* For ES exchange the above line with the following code:
+ (!xtrabackup_prepare || !xtrabackup_incremental_dir ||
+ !starts_with(pname, "aria_log")))
+ */
+ continue;
+
+ if (xtrabackup_prepare && xtrabackup_incremental_dir &&
+ file_exists(info.name))
+ unlink(info.name);
+
+ std::string full_path(dir_path);
+ full_path.append(1, '/').append(info.name);
+ if (!(ret = ds_data->copy_file(full_path.c_str() , info.name, 1)))
+ break;
+ }
+ os_file_closedir(dir);
+ return ret;
+}
+
+
+static int rocksdb_remove_checkpoint_directory()
+{
+ xb_mysql_query(mysql_connection, "set global rocksdb_remove_mariabackup_checkpoint=ON", false);
+ return 0;
+}
+
+static bool has_rocksdb_plugin()
+{
+ static bool first_time = true;
+ static bool has_plugin= false;
+ if (!first_time || !xb_backup_rocksdb)
+ return has_plugin;
+
+ const char *query = "SELECT COUNT(*) FROM information_schema.plugins WHERE plugin_name='rocksdb'";
+ MYSQL_RES* result = xb_mysql_query(mysql_connection, query, true);
+ MYSQL_ROW row = mysql_fetch_row(result);
+ if (row)
+ has_plugin = !strcmp(row[0], "1");
+ mysql_free_result(result);
+ first_time = false;
+ return has_plugin;
+}
+
+static char *trim_trailing_dir_sep(char *path)
+{
+ size_t path_len = strlen(path);
+ while (path_len)
+ {
+ char c = path[path_len - 1];
+ if (c == '/' IF_WIN(|| c == '\\', ))
+ path_len--;
+ else
+ break;
+ }
+ path[path_len] = 0;
+ return path;
+}
+
+/*
+Create a file hardlink.
+@return true on success, false on error.
+*/
+bool
+ds_ctxt_t::make_hardlink(const char *from_path, const char *to_path)
+{
+ DBUG_EXECUTE_IF("no_hardlinks", return false;);
+ char to_path_full[FN_REFLEN];
+ if (!is_abs_path(to_path))
+ {
+ fn_format(to_path_full, to_path, root, "", MYF(MY_RELATIVE_PATH));
+ }
+ else
+ {
+ strncpy(to_path_full, to_path, sizeof(to_path_full));
+ }
+#ifdef _WIN32
+ return CreateHardLink(to_path_full, from_path, NULL);
+#else
+ return !link(from_path, to_path_full);
+#endif
+}
+
+/*
+ Copies or moves a directory (non-recursively so far).
+ Helper function used to backup rocksdb checkpoint, or copy-back the
+ rocksdb files.
+
+ Has optimization that allows to use hardlinks when possible
+ (source and destination are directories on the same device)
+*/
+void
+ds_ctxt_t::copy_or_move_dir(const char *from, const char *to,
+ bool do_copy, bool allow_hardlinks)
+{
+ datadir_node_t node;
+ datadir_node_init(&node);
+ datadir_iter_t *it = datadir_iter_new(from, false);
+
+ while (datadir_iter_next(it, &node))
+ {
+ char to_path[FN_REFLEN];
+ const char *from_path = node.filepath;
+ snprintf(to_path, sizeof(to_path), "%s/%s", to, base_name(from_path));
+ bool rc = false;
+ if (do_copy && allow_hardlinks)
+ {
+ rc = make_hardlink(from_path, to_path);
+ if (rc)
+ {
+ msg("Creating hardlink from %s to %s",from_path, to_path);
+ }
+ else
+ {
+ allow_hardlinks = false;
+ }
+ }
+
+ if (!rc)
+ {
+ rc = (do_copy ?
+ copy_file(from_path, to_path, 1) :
+ move_file(from_path, node.filepath_rel,
+ to, 1));
+ }
+ if (!rc)
+ die("copy or move file failed");
+ }
+ datadir_iter_free(it);
+ datadir_node_free(&node);
+
+}
+
+/*
+ Obtain user level lock , to protect the checkpoint directory of the server
+ from being user/overwritten by different backup processes, if backups are
+ running in parallel.
+
+ This lock will be acquired before rocksdb checkpoint is created, held
+ while all files from it are being copied to their final backup destination,
+ and finally released after the checkpoint is removed.
+*/
+static void rocksdb_lock_checkpoint()
+{
+ msg("Obtaining rocksdb checkpoint lock.");
+ MYSQL_RES *res =
+ xb_mysql_query(mysql_connection, "SELECT GET_LOCK('mariabackup_rocksdb_checkpoint',3600)", true, true);
+
+ MYSQL_ROW r = mysql_fetch_row(res);
+ if (r && r[0] && strcmp(r[0], "1"))
+ {
+ msg("Could not obtain rocksdb checkpont lock.");
+ exit(EXIT_FAILURE);
+ }
+ mysql_free_result(res);
+}
+
+static void rocksdb_unlock_checkpoint()
+{
+ xb_mysql_query(mysql_connection,
+ "SELECT RELEASE_LOCK('mariabackup_rocksdb_checkpoint')", false, true);
+}
+
+
+/*
+ Create temporary checkpoint in $rocksdb_datadir/mariabackup-checkpoint
+ directory.
+ A (user-level) lock named 'mariabackup_rocksdb_checkpoint' will also be
+ acquired be this function.
+*/
+#define MARIADB_CHECKPOINT_DIR "mariabackup-checkpoint"
+static char rocksdb_checkpoint_dir[FN_REFLEN];
+
+static void rocksdb_create_checkpoint()
+{
+ MYSQL_RES *result = xb_mysql_query(mysql_connection, "SELECT @@rocksdb_datadir,@@datadir", true, true);
+ MYSQL_ROW row = mysql_fetch_row(result);
+
+ DBUG_ASSERT(row && row[0] && row[1]);
+
+ char *rocksdbdir = row[0];
+ char *datadir = row[1];
+
+ if (is_abs_path(rocksdbdir))
+ {
+ snprintf(rocksdb_checkpoint_dir, sizeof(rocksdb_checkpoint_dir),
+ "%s/" MARIADB_CHECKPOINT_DIR, trim_trailing_dir_sep(rocksdbdir));
+ }
+ else
+ {
+ snprintf(rocksdb_checkpoint_dir, sizeof(rocksdb_checkpoint_dir),
+ "%s/%s/" MARIADB_CHECKPOINT_DIR, trim_trailing_dir_sep(datadir),
+ trim_dotslash(rocksdbdir));
+ }
+ mysql_free_result(result);
+
+#ifdef _WIN32
+ for (char *p = rocksdb_checkpoint_dir; *p; p++)
+ if (*p == '\\') *p = '/';
+#endif
+
+ rocksdb_lock_checkpoint();
+
+ if (!access(rocksdb_checkpoint_dir, 0))
+ {
+ msg("Removing rocksdb checkpoint from previous backup attempt.");
+ rocksdb_remove_checkpoint_directory();
+ }
+
+ char query[FN_REFLEN + 32];
+ snprintf(query, sizeof(query), "SET GLOBAL rocksdb_create_checkpoint='%s'", rocksdb_checkpoint_dir);
+ xb_mysql_query(mysql_connection, query, false, true);
+}
+
+/*
+ Copy files from rocksdb temporary checkpoint to final destination.
+ remove temp.checkpoint directory (in server's datadir)
+ and release user level lock acquired inside rocksdb_create_checkpoint().
+*/
+static void rocksdb_backup_checkpoint(ds_ctxt *ds_data)
+{
+ msg("Backing up rocksdb files.");
+ char rocksdb_backup_dir[FN_REFLEN];
+ snprintf(rocksdb_backup_dir, sizeof(rocksdb_backup_dir), "%s/" ROCKSDB_BACKUP_DIR , xtrabackup_target_dir);
+ bool backup_to_directory = xtrabackup_backup && xtrabackup_stream_fmt == XB_STREAM_FMT_NONE;
+ if (backup_to_directory)
+ {
+ if (my_mkdir(rocksdb_backup_dir, 0777, MYF(0))){
+ die("Can't create rocksdb backup directory %s", rocksdb_backup_dir);
+ }
+ }
+ ds_data->copy_or_move_dir(rocksdb_checkpoint_dir, ROCKSDB_BACKUP_DIR, true, backup_to_directory);
+ rocksdb_remove_checkpoint_directory();
+ rocksdb_unlock_checkpoint();
+}
+
+/*
+ Copies #rocksdb directory to the $rockdb_data_dir, on copy-back
+*/
+static void rocksdb_copy_back(ds_ctxt *ds_data) {
+ if (access(ROCKSDB_BACKUP_DIR, 0))
+ return;
+ char rocksdb_home_dir[FN_REFLEN];
+ if (xb_rocksdb_datadir && is_abs_path(xb_rocksdb_datadir)) {
+ strncpy(rocksdb_home_dir, xb_rocksdb_datadir, sizeof rocksdb_home_dir - 1);
+ rocksdb_home_dir[sizeof rocksdb_home_dir - 1] = '\0';
+ } else {
+ snprintf(rocksdb_home_dir, sizeof(rocksdb_home_dir), "%s/%s", mysql_data_home,
+ xb_rocksdb_datadir?trim_dotslash(xb_rocksdb_datadir): ROCKSDB_BACKUP_DIR);
+ }
+ mkdirp(rocksdb_home_dir, 0777, MYF(0));
+ ds_data->copy_or_move_dir(ROCKSDB_BACKUP_DIR, rocksdb_home_dir, xtrabackup_copy_back, xtrabackup_copy_back);
+}
diff --git a/extra/mariabackup/backup_copy.h b/extra/mariabackup/backup_copy.h
new file mode 100644
index 00000000..b4a323f2
--- /dev/null
+++ b/extra/mariabackup/backup_copy.h
@@ -0,0 +1,43 @@
+
+#ifndef XTRABACKUP_BACKUP_COPY_H
+#define XTRABACKUP_BACKUP_COPY_H
+
+#include <my_global.h>
+#include <mysql.h>
+#include "datasink.h"
+
+/* special files */
+#define XTRABACKUP_SLAVE_INFO "xtrabackup_slave_info"
+#define XTRABACKUP_GALERA_INFO "xtrabackup_galera_info"
+#define XTRABACKUP_BINLOG_INFO "xtrabackup_binlog_info"
+#define XTRABACKUP_INFO "xtrabackup_info"
+
+extern bool binlog_locked;
+
+/************************************************************************
+Return true if first and second arguments are the same path. */
+bool
+equal_paths(const char *first, const char *second);
+
+/** Start --backup */
+bool backup_start(ds_ctxt *ds_data, ds_ctxt *ds_meta,
+ CorruptedPages &corrupted_pages);
+/** Release resources after backup_start() */
+void backup_release();
+/** Finish after backup_start() and backup_release() */
+bool backup_finish(ds_ctxt *ds_data);
+bool
+apply_log_finish();
+bool
+copy_back();
+bool
+decrypt_decompress();
+bool
+is_path_separator(char);
+bool
+directory_exists(const char *dir, bool create);
+
+lsn_t
+get_current_lsn(MYSQL *connection);
+
+#endif
diff --git a/extra/mariabackup/backup_debug.h b/extra/mariabackup/backup_debug.h
new file mode 100644
index 00000000..777b4f4a
--- /dev/null
+++ b/extra/mariabackup/backup_debug.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "my_dbug.h"
+#ifndef DBUG_OFF
+char *dbug_mariabackup_get_val(const char *event, fil_space_t::name_type key);
+/*
+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
+*/
+#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE) \
+ DBUG_EXECUTE_IF("mariabackup_inject_code", \
+ { char *dbug_val= dbug_mariabackup_get_val(EVENT, KEY); \
+ if (dbug_val) CODE })
+#else
+#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE)
+#endif
+
diff --git a/extra/mariabackup/backup_mysql.cc b/extra/mariabackup/backup_mysql.cc
new file mode 100644
index 00000000..cf8a5051
--- /dev/null
+++ b/extra/mariabackup/backup_mysql.cc
@@ -0,0 +1,1947 @@
+/******************************************************
+hot backup tool for InnoDB
+(c) 2009-2015 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.
+
+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 MYSQL_CLIENT
+
+#include <my_global.h>
+#include <mysql.h>
+#include <mysqld.h>
+#include <my_sys.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits>
+#include "common.h"
+#include "xtrabackup.h"
+#include "srv0srv.h"
+#include "mysql_version.h"
+#include "backup_copy.h"
+#include "backup_mysql.h"
+#include "mysqld.h"
+#include "xb_plugin.h"
+#include <sstream>
+#include <sql_error.h>
+#include "page0zip.h"
+
+char *tool_name;
+char tool_args[2048];
+
+ulong mysql_server_version;
+
+/* server capabilities */
+bool have_changed_page_bitmaps = false;
+bool have_backup_locks = false;
+bool have_lock_wait_timeout = false;
+bool have_galera_enabled = false;
+bool have_multi_threaded_slave = false;
+bool have_gtid_slave = false;
+
+/* Kill long selects */
+static mysql_mutex_t kill_query_thread_mutex;
+static bool kill_query_thread_running, kill_query_thread_stopping;
+static mysql_cond_t kill_query_thread_stopped;
+static mysql_cond_t kill_query_thread_stop;
+
+bool sql_thread_started = false;
+char *mysql_slave_position = NULL;
+char *mysql_binlog_position = NULL;
+char *buffer_pool_filename = NULL;
+
+/* History on server */
+time_t history_start_time;
+time_t history_end_time;
+time_t history_lock_time;
+
+MYSQL *mysql_connection;
+
+extern my_bool opt_ssl_verify_server_cert, opt_use_ssl;
+
+MYSQL *
+xb_mysql_connect()
+{
+ MYSQL *connection = mysql_init(NULL);
+ char mysql_port_str[std::numeric_limits<int>::digits10 + 3];
+
+ sprintf(mysql_port_str, "%d", opt_port);
+
+ if (connection == NULL) {
+ msg("Failed to init MariaDB struct: %s.",
+ mysql_error(connection));
+ return(NULL);
+ }
+
+#if !defined(DONT_USE_MYSQL_PWD)
+ if (!opt_password)
+ {
+ opt_password=getenv("MYSQL_PWD");
+ }
+#endif
+
+ if (!opt_secure_auth) {
+ mysql_options(connection, MYSQL_SECURE_AUTH,
+ (char *) &opt_secure_auth);
+ }
+
+ if (xb_plugin_dir && *xb_plugin_dir){
+ mysql_options(connection, MYSQL_PLUGIN_DIR, xb_plugin_dir);
+ }
+ mysql_options(connection, MYSQL_OPT_PROTOCOL, &opt_protocol);
+ mysql_options(connection,MYSQL_SET_CHARSET_NAME, "utf8");
+
+ msg("Connecting to MariaDB server host: %s, user: %s, password: %s, "
+ "port: %s, socket: %s", opt_host ? opt_host : "localhost",
+ opt_user ? opt_user : "not set",
+ opt_password ? "set" : "not set",
+ opt_port != 0 ? mysql_port_str : "not set",
+ opt_socket ? opt_socket : "not set");
+
+#ifdef HAVE_OPENSSL
+ if (opt_use_ssl && opt_protocol <= MYSQL_PROTOCOL_SOCKET)
+ {
+ mysql_ssl_set(connection, opt_ssl_key, opt_ssl_cert,
+ opt_ssl_ca, opt_ssl_capath,
+ opt_ssl_cipher);
+ mysql_options(connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl);
+ mysql_options(connection, MYSQL_OPT_SSL_CRLPATH,
+ opt_ssl_crlpath);
+ }
+ mysql_options(connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
+ (char*)&opt_ssl_verify_server_cert);
+#endif
+
+ if (!mysql_real_connect(connection,
+ opt_host ? opt_host : "localhost",
+ opt_user,
+ opt_password,
+ "" /*database*/, opt_port,
+ opt_socket, 0)) {
+ msg("Failed to connect to MariaDB server: %s.", mysql_error(connection));
+ mysql_close(connection);
+ return(NULL);
+ }
+
+ xb_mysql_query(connection, "SET SESSION wait_timeout=2147483, max_statement_time=0",
+ false, true);
+
+ return(connection);
+}
+
+/*********************************************************************//**
+Execute mysql query. */
+MYSQL_RES *
+xb_mysql_query(MYSQL *connection, const char *query, bool use_result,
+ bool die_on_error)
+{
+ MYSQL_RES *mysql_result = NULL;
+
+ if (mysql_query(connection, query)) {
+ if (die_on_error) {
+ die("failed to execute query %s: %s", query, mysql_error(connection));
+ } else {
+ msg("Error: failed to execute query %s: %s", query, mysql_error(connection));
+ }
+ return(NULL);
+ }
+
+ /* store result set on client if there is a result */
+ if (mysql_field_count(connection) > 0) {
+ if ((mysql_result = mysql_store_result(connection)) == NULL) {
+ die("failed to fetch query result %s: %s",
+ query, mysql_error(connection));
+ }
+
+ if (!use_result) {
+ mysql_free_result(mysql_result);
+ mysql_result = NULL;
+ }
+ }
+
+ return mysql_result;
+}
+
+
+struct mysql_variable {
+ const char *name;
+ char **value;
+};
+
+
+static
+void
+read_mysql_variables(MYSQL *connection, const char *query, mysql_variable *vars,
+ bool vertical_result)
+{
+ MYSQL_RES *mysql_result;
+ MYSQL_ROW row;
+ mysql_variable *var;
+
+ mysql_result = xb_mysql_query(connection, query, true);
+
+ ut_ad(!vertical_result || mysql_num_fields(mysql_result) == 2);
+
+ if (vertical_result) {
+ while ((row = mysql_fetch_row(mysql_result))) {
+ char *name = row[0];
+ char *value = row[1];
+ for (var = vars; var->name; var++) {
+ if (strcmp(var->name, name) == 0
+ && value != NULL) {
+ *(var->value) = strdup(value);
+ }
+ }
+ }
+ } else {
+ MYSQL_FIELD *field;
+
+ if ((row = mysql_fetch_row(mysql_result)) != NULL) {
+ int i = 0;
+ while ((field = mysql_fetch_field(mysql_result))
+ != NULL) {
+ char *name = field->name;
+ char *value = row[i];
+ for (var = vars; var->name; var++) {
+ if (strcmp(var->name, name) == 0
+ && value != NULL) {
+ *(var->value) = strdup(value);
+ }
+ }
+ ++i;
+ }
+ }
+ }
+
+ mysql_free_result(mysql_result);
+}
+
+
+static
+void
+free_mysql_variables(mysql_variable *vars)
+{
+ mysql_variable *var;
+
+ for (var = vars; var->name; var++) {
+ free(*(var->value));
+ }
+}
+
+
+static
+char *
+read_mysql_one_value(MYSQL *connection, const char *query,
+ uint column, uint expect_columns)
+{
+ MYSQL_RES *mysql_result;
+ MYSQL_ROW row;
+ char *result = NULL;
+
+ mysql_result = xb_mysql_query(connection, query, true);
+
+ ut_ad(mysql_num_fields(mysql_result) == expect_columns);
+
+ if ((row = mysql_fetch_row(mysql_result))) {
+ result = strdup(row[column]);
+ }
+
+ mysql_free_result(mysql_result);
+
+ return(result);
+}
+
+
+static
+char *
+read_mysql_one_value(MYSQL *mysql, const char *query)
+{
+ return read_mysql_one_value(mysql, query, 0/*offset*/, 1/*total columns*/);
+}
+
+
+static
+bool
+check_server_version(ulong version_number, const char *version_string)
+{
+ if (strstr(version_string, "MariaDB") && version_number >= 100800)
+ return true;
+
+ msg("Error: Unsupported server version: '%s'.", version_string);
+ return false;
+}
+
+/*********************************************************************//**
+Receive options important for XtraBackup from server.
+@return true on success. */
+bool get_mysql_vars(MYSQL *connection)
+{
+ char *gtid_mode_var= NULL;
+ char *version_var= NULL;
+ char *have_backup_locks_var= NULL;
+ char *log_bin_var= NULL;
+ char *lock_wait_timeout_var= NULL;
+ char *wsrep_on_var= NULL;
+ char *slave_parallel_workers_var= NULL;
+ char *gtid_slave_pos_var= NULL;
+ char *innodb_buffer_pool_filename_var= NULL;
+ char *datadir_var= NULL;
+ char *innodb_log_group_home_dir_var= NULL;
+ char *innodb_log_file_size_var= NULL;
+ char *innodb_log_files_in_group_var= NULL;
+ char *innodb_data_file_path_var= NULL;
+ char *innodb_data_home_dir_var= NULL;
+ char *innodb_undo_directory_var= NULL;
+ char *innodb_page_size_var= NULL;
+ char *innodb_undo_tablespaces_var= NULL;
+ char *aria_log_dir_path_var= NULL;
+ char *page_zip_level_var= NULL;
+ char *ignore_db_dirs= NULL;
+ char *endptr;
+ ulong server_version= mysql_get_server_version(connection);
+
+ bool ret= true;
+
+ mysql_variable mysql_vars[]= {
+ {"have_backup_locks", &have_backup_locks_var},
+ {"log_bin", &log_bin_var},
+ {"lock_wait_timeout", &lock_wait_timeout_var},
+ {"gtid_mode", &gtid_mode_var},
+ {"version", &version_var},
+ {"wsrep_on", &wsrep_on_var},
+ {"slave_parallel_workers", &slave_parallel_workers_var},
+ {"gtid_slave_pos", &gtid_slave_pos_var},
+ {"innodb_buffer_pool_filename", &innodb_buffer_pool_filename_var},
+ {"datadir", &datadir_var},
+ {"innodb_log_group_home_dir", &innodb_log_group_home_dir_var},
+ {"innodb_log_file_size", &innodb_log_file_size_var},
+ {"innodb_log_files_in_group", &innodb_log_files_in_group_var},
+ {"innodb_data_file_path", &innodb_data_file_path_var},
+ {"innodb_data_home_dir", &innodb_data_home_dir_var},
+ {"innodb_undo_directory", &innodb_undo_directory_var},
+ {"innodb_page_size", &innodb_page_size_var},
+ {"innodb_undo_tablespaces", &innodb_undo_tablespaces_var},
+ {"innodb_compression_level", &page_zip_level_var},
+ {"ignore_db_dirs", &ignore_db_dirs},
+ {"aria_log_dir_path", &aria_log_dir_path_var},
+ {NULL, NULL}};
+
+ read_mysql_variables(connection, "SHOW VARIABLES", mysql_vars, true);
+
+ if (have_backup_locks_var != NULL && !opt_no_backup_locks)
+ {
+ have_backup_locks= true;
+ }
+
+ if (opt_binlog_info == BINLOG_INFO_AUTO)
+ {
+ if (log_bin_var != NULL && !strcmp(log_bin_var, "ON"))
+ opt_binlog_info= BINLOG_INFO_ON;
+ else
+ opt_binlog_info= BINLOG_INFO_OFF;
+ }
+
+ if (lock_wait_timeout_var != NULL)
+ {
+ have_lock_wait_timeout= true;
+ }
+
+ if (wsrep_on_var != NULL)
+ {
+ have_galera_enabled= true;
+ }
+
+ /* Check server version compatibility */
+ if (!(ret= check_server_version(server_version, version_var)))
+ {
+ goto out;
+ }
+
+ mysql_server_version= server_version;
+
+ if (slave_parallel_workers_var != NULL &&
+ atoi(slave_parallel_workers_var) > 0)
+ {
+ have_multi_threaded_slave= true;
+ }
+
+ if (innodb_buffer_pool_filename_var != NULL)
+ {
+ buffer_pool_filename= strdup(innodb_buffer_pool_filename_var);
+ }
+
+ if ((gtid_mode_var && strcmp(gtid_mode_var, "ON") == 0) ||
+ (gtid_slave_pos_var && *gtid_slave_pos_var))
+ {
+ have_gtid_slave= true;
+ }
+
+ msg("Using server version %s", version_var);
+
+ if (!(ret= detect_mysql_capabilities_for_backup()))
+ {
+ goto out;
+ }
+
+ /* make sure datadir value is the same in configuration file */
+ if (check_if_param_set("datadir"))
+ {
+ if (!directory_exists(mysql_data_home, false))
+ {
+ msg("Warning: option 'datadir' points to "
+ "nonexistent directory '%s'",
+ mysql_data_home);
+ }
+ if (!directory_exists(datadir_var, false))
+ {
+ msg("Warning: MariaDB variable 'datadir' points to "
+ "nonexistent directory '%s'",
+ datadir_var);
+ }
+ if (!equal_paths(mysql_data_home, datadir_var))
+ {
+ msg("Warning: option 'datadir' has different "
+ "values:\n"
+ " '%s' in defaults file\n"
+ " '%s' in SHOW VARIABLES",
+ mysql_data_home, datadir_var);
+ }
+ }
+
+ /* get some default values is they are missing from my.cnf */
+ if (datadir_var && *datadir_var)
+ {
+ strmake(mysql_real_data_home, datadir_var, FN_REFLEN - 1);
+ mysql_data_home= mysql_real_data_home;
+ }
+
+ if (innodb_data_file_path_var && *innodb_data_file_path_var)
+ innobase_data_file_path= my_strdup(PSI_NOT_INSTRUMENTED,
+ innodb_data_file_path_var, MYF(MY_FAE));
+
+ if (innodb_data_home_dir_var)
+ innobase_data_home_dir= my_strdup(PSI_NOT_INSTRUMENTED,
+ innodb_data_home_dir_var, MYF(MY_FAE));
+
+ if (innodb_log_group_home_dir_var && *innodb_log_group_home_dir_var)
+ srv_log_group_home_dir= my_strdup(PSI_NOT_INSTRUMENTED,
+ innodb_log_group_home_dir_var,
+ MYF(MY_FAE));
+
+ if (innodb_undo_directory_var && *innodb_undo_directory_var)
+ srv_undo_dir= my_strdup(PSI_NOT_INSTRUMENTED, innodb_undo_directory_var,
+ MYF(MY_FAE));
+
+ if (innodb_log_file_size_var)
+ {
+ srv_log_file_size= strtoll(innodb_log_file_size_var, &endptr, 10);
+ ut_ad(*endptr == 0);
+ }
+
+ if (innodb_page_size_var)
+ {
+ innobase_page_size= strtoll(innodb_page_size_var, &endptr, 10);
+ ut_ad(*endptr == 0);
+ }
+
+ if (innodb_undo_tablespaces_var)
+ {
+ srv_undo_tablespaces= static_cast<uint32_t>
+ (strtoul(innodb_undo_tablespaces_var, &endptr, 10));
+ ut_ad(*endptr == 0);
+ }
+
+ if (aria_log_dir_path_var)
+ {
+ aria_log_dir_path= my_strdup(PSI_NOT_INSTRUMENTED,
+ aria_log_dir_path_var, MYF(MY_FAE));
+ }
+
+ if (page_zip_level_var != NULL)
+ {
+ page_zip_level= static_cast<uint>(strtoul(page_zip_level_var, &endptr,
+ 10));
+ ut_ad(*endptr == 0);
+ }
+
+ if (ignore_db_dirs)
+ xb_load_list_string(ignore_db_dirs, ",", register_ignore_db_dirs_filter);
+
+out:
+ free_mysql_variables(mysql_vars);
+
+ return (ret);
+}
+
+/*********************************************************************//**
+Query the server to find out what backup capabilities it supports.
+@return true on success. */
+bool
+detect_mysql_capabilities_for_backup()
+{
+ const char *query = "SELECT 'INNODB_CHANGED_PAGES', COUNT(*) FROM "
+ "INFORMATION_SCHEMA.PLUGINS "
+ "WHERE PLUGIN_NAME LIKE 'INNODB_CHANGED_PAGES'";
+ char *innodb_changed_pages = NULL;
+ mysql_variable vars[] = {
+ {"INNODB_CHANGED_PAGES", &innodb_changed_pages}, {NULL, NULL}};
+
+ if (xtrabackup_incremental) {
+
+ read_mysql_variables(mysql_connection, query, vars, true);
+
+ ut_ad(innodb_changed_pages != NULL);
+
+ have_changed_page_bitmaps = (atoi(innodb_changed_pages) == 1);
+
+ free_mysql_variables(vars);
+ }
+
+ /* do some sanity checks */
+ if (opt_galera_info && !have_galera_enabled) {
+ msg("--galera-info is specified on the command "
+ "line, but the server does not support Galera "
+ "replication. Ignoring the option.");
+ opt_galera_info = false;
+ }
+
+ if (opt_slave_info && have_multi_threaded_slave &&
+ !have_gtid_slave) {
+ msg("The --slave-info option requires GTID enabled for a "
+ "multi-threaded slave.");
+ return(false);
+ }
+
+ return(true);
+}
+
+static
+bool
+select_incremental_lsn_from_history(lsn_t *incremental_lsn)
+{
+ MYSQL_RES *mysql_result;
+ char query[1000];
+ char buf[100];
+
+ if (opt_incremental_history_name) {
+ mysql_real_escape_string(mysql_connection, buf,
+ opt_incremental_history_name,
+ (unsigned long)strlen(opt_incremental_history_name));
+ snprintf(query, sizeof(query),
+ "SELECT innodb_to_lsn "
+ "FROM " XB_HISTORY_TABLE " "
+ "WHERE name = '%s' "
+ "AND innodb_to_lsn IS NOT NULL "
+ "ORDER BY innodb_to_lsn DESC LIMIT 1",
+ buf);
+ }
+
+ if (opt_incremental_history_uuid) {
+ mysql_real_escape_string(mysql_connection, buf,
+ opt_incremental_history_uuid,
+ (unsigned long)strlen(opt_incremental_history_uuid));
+ snprintf(query, sizeof(query),
+ "SELECT innodb_to_lsn "
+ "FROM " XB_HISTORY_TABLE " "
+ "WHERE uuid = '%s' "
+ "AND innodb_to_lsn IS NOT NULL "
+ "ORDER BY innodb_to_lsn DESC LIMIT 1",
+ buf);
+ }
+
+ mysql_result = xb_mysql_query(mysql_connection, query, true);
+
+ ut_ad(mysql_num_fields(mysql_result) == 1);
+ const MYSQL_ROW row = mysql_fetch_row(mysql_result);
+ if (row) {
+ *incremental_lsn = strtoull(row[0], NULL, 10);
+ msg("Found and using lsn: " LSN_PF " for %s %s",
+ *incremental_lsn,
+ opt_incremental_history_uuid ? "uuid" : "name",
+ opt_incremental_history_uuid ?
+ opt_incremental_history_uuid :
+ opt_incremental_history_name);
+ } else {
+ msg("Error while attempting to find history record "
+ "for %s %s",
+ opt_incremental_history_uuid ? "uuid" : "name",
+ opt_incremental_history_uuid ?
+ opt_incremental_history_uuid :
+ opt_incremental_history_name);
+ }
+
+ mysql_free_result(mysql_result);
+
+ return(row != NULL);
+}
+
+static
+const char *
+eat_sql_whitespace(const char *query)
+{
+ bool comment = false;
+
+ while (*query) {
+ if (comment) {
+ if (query[0] == '*' && query[1] == '/') {
+ query += 2;
+ comment = false;
+ continue;
+ }
+ ++query;
+ continue;
+ }
+ if (query[0] == '/' && query[1] == '*') {
+ query += 2;
+ comment = true;
+ continue;
+ }
+ if (strchr("\t\n\r (", query[0])) {
+ ++query;
+ continue;
+ }
+ break;
+ }
+
+ return(query);
+}
+
+static
+bool
+is_query_from_list(const char *query, const char **list)
+{
+ const char **item;
+
+ query = eat_sql_whitespace(query);
+
+ item = list;
+ while (*item) {
+ if (strncasecmp(query, *item, strlen(*item)) == 0) {
+ return(true);
+ }
+ ++item;
+ }
+
+ return(false);
+}
+
+static
+bool
+is_query(const char *query)
+{
+ const char *query_list[] = {"insert", "update", "delete", "replace",
+ "alter", "load", "select", "do", "handler", "call", "execute",
+ "begin", NULL};
+
+ return is_query_from_list(query, query_list);
+}
+
+static
+bool
+is_select_query(const char *query)
+{
+ const char *query_list[] = {"select", NULL};
+
+ return is_query_from_list(query, query_list);
+}
+
+static
+bool
+is_update_query(const char *query)
+{
+ const char *query_list[] = {"insert", "update", "delete", "replace",
+ "alter", "load", NULL};
+
+ return is_query_from_list(query, query_list);
+}
+
+static
+bool
+have_queries_to_wait_for(MYSQL *connection, uint threshold)
+{
+ MYSQL_RES *result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST",
+ true);
+ const bool all_queries = (opt_lock_wait_query_type == QUERY_TYPE_ALL);
+ bool have_to_wait = false;
+
+ while (MYSQL_ROW row = mysql_fetch_row(result)) {
+ const char *info = row[7];
+ int duration = row[5] ? atoi(row[5]) : 0;
+ char *id = row[0];
+
+ if (info != NULL
+ && duration >= (int)threshold
+ && ((all_queries && is_query(info))
+ || is_update_query(info))) {
+ msg("Waiting for query %s (duration %d sec): %s",
+ id, duration, info);
+ have_to_wait = true;
+ break;
+ }
+ }
+
+ mysql_free_result(result);
+ return(have_to_wait);
+}
+
+static
+void
+kill_long_queries(MYSQL *connection, time_t timeout)
+{
+ char kill_stmt[100];
+
+ MYSQL_RES *result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST",
+ true);
+ const bool all_queries = (opt_kill_long_query_type == QUERY_TYPE_ALL);
+ while (MYSQL_ROW row = mysql_fetch_row(result)) {
+ const char *info = row[7];
+ long long duration = row[5]? atoll(row[5]) : 0;
+ char *id = row[0];
+
+ if (info != NULL &&
+ (time_t)duration >= timeout &&
+ ((all_queries && is_query(info)) ||
+ is_select_query(info))) {
+ msg("Killing query %s (duration %d sec): %s",
+ id, (int)duration, info);
+ snprintf(kill_stmt, sizeof(kill_stmt),
+ "KILL %s", id);
+ xb_mysql_query(connection, kill_stmt, false, false);
+ }
+ }
+
+ mysql_free_result(result);
+}
+
+static
+bool
+wait_for_no_updates(MYSQL *connection, uint timeout, uint threshold)
+{
+ time_t start_time;
+
+ start_time = time(NULL);
+
+ msg("Waiting %u seconds for queries running longer than %u seconds "
+ "to finish", timeout, threshold);
+
+ while (time(NULL) <= (time_t)(start_time + timeout)) {
+ if (!have_queries_to_wait_for(connection, threshold)) {
+ return(true);
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+
+ msg("Unable to obtain lock. Please try again later.");
+
+ return(false);
+}
+
+static void kill_query_thread()
+{
+ mysql_mutex_lock(&kill_query_thread_mutex);
+
+ msg("Kill query timeout %d seconds.", opt_kill_long_queries_timeout);
+
+ time_t start_time= time(nullptr);
+ timespec abstime;
+ set_timespec(abstime, opt_kill_long_queries_timeout);
+
+ while (!kill_query_thread_stopping)
+ if (!mysql_cond_timedwait(&kill_query_thread_stop,
+ &kill_query_thread_mutex, &abstime))
+ goto func_exit;
+
+ if (MYSQL *mysql= xb_mysql_connect())
+ {
+ do
+ {
+ kill_long_queries(mysql, time(nullptr) - start_time);
+ set_timespec(abstime, 1);
+ }
+ while (mysql_cond_timedwait(&kill_query_thread_stop,
+ &kill_query_thread_mutex, &abstime) &&
+ !kill_query_thread_stopping);
+ mysql_close(mysql);
+ }
+ else
+ msg("Error: kill query thread failed");
+
+func_exit:
+ msg("Kill query thread stopped");
+
+ kill_query_thread_running= false;
+ mysql_cond_signal(&kill_query_thread_stopped);
+ mysql_mutex_unlock(&kill_query_thread_mutex);
+}
+
+
+static void start_query_killer()
+{
+ ut_ad(!kill_query_thread_running);
+ kill_query_thread_running= true;
+ kill_query_thread_stopping= false;
+ mysql_mutex_init(0, &kill_query_thread_mutex, nullptr);
+ mysql_cond_init(0, &kill_query_thread_stop, nullptr);
+ mysql_cond_init(0, &kill_query_thread_stopped, nullptr);
+ std::thread(kill_query_thread).detach();
+}
+
+static void stop_query_killer()
+{
+ mysql_mutex_lock(&kill_query_thread_mutex);
+ kill_query_thread_stopping= true;
+ mysql_cond_signal(&kill_query_thread_stop);
+
+ do
+ mysql_cond_wait(&kill_query_thread_stopped, &kill_query_thread_mutex);
+ while (kill_query_thread_running);
+
+ mysql_cond_destroy(&kill_query_thread_stop);
+ mysql_cond_destroy(&kill_query_thread_stopped);
+ mysql_mutex_unlock(&kill_query_thread_mutex);
+ mysql_mutex_destroy(&kill_query_thread_mutex);
+}
+
+
+/*********************************************************************//**
+Function acquires either a backup tables lock, if supported
+by the server, or a global read lock (FLUSH TABLES WITH READ LOCK)
+otherwise.
+@returns true if lock acquired */
+bool lock_tables(MYSQL *connection)
+{
+ if (have_lock_wait_timeout || opt_lock_wait_timeout)
+ {
+ char buf[FN_REFLEN];
+ /* Set the maximum supported session value for
+ lock_wait_timeout if opt_lock_wait_timeout is not set to prevent
+ unnecessary timeouts when the global value is changed from the default */
+ snprintf(buf, sizeof(buf), "SET SESSION lock_wait_timeout=%u",
+ opt_lock_wait_timeout ? opt_lock_wait_timeout : 31536000);
+ xb_mysql_query(connection, buf, false);
+ }
+
+ if (have_backup_locks)
+ {
+ msg("Executing LOCK TABLES FOR BACKUP...");
+ xb_mysql_query(connection, "LOCK TABLES FOR BACKUP", false);
+ return (true);
+ }
+
+ if (opt_lock_wait_timeout)
+ {
+ if (!wait_for_no_updates(connection, opt_lock_wait_timeout,
+ opt_lock_wait_threshold))
+ {
+ return (false);
+ }
+ }
+
+ msg("Acquiring BACKUP LOCKS...");
+
+ if (opt_kill_long_queries_timeout)
+ {
+ start_query_killer();
+ }
+
+ if (have_galera_enabled)
+ {
+ xb_mysql_query(connection, "SET SESSION wsrep_sync_wait=0", false);
+ }
+
+ xb_mysql_query(connection, "BACKUP STAGE START", true);
+ DBUG_MARIABACKUP_EVENT("after_backup_stage_start", {});
+ xb_mysql_query(connection, "BACKUP STAGE BLOCK_COMMIT", true);
+ DBUG_MARIABACKUP_EVENT("after_backup_stage_block_commit", {});
+ /* Set the maximum supported session value for
+ lock_wait_timeout to prevent unnecessary timeouts when the
+ global value is changed from the default */
+ if (opt_lock_wait_timeout)
+ xb_mysql_query(connection, "SET SESSION lock_wait_timeout=31536000",
+ false);
+
+ if (opt_kill_long_queries_timeout)
+ {
+ stop_query_killer();
+ }
+
+ return (true);
+}
+
+/*********************************************************************//**
+If backup locks are used, execute LOCK BINLOG FOR BACKUP provided that we are
+not in the --no-lock mode and the lock has not been acquired already.
+@returns true if lock acquired */
+bool
+lock_binlog_maybe(MYSQL *connection)
+{
+ if (have_backup_locks && !opt_no_lock && !binlog_locked) {
+ msg("Executing LOCK BINLOG FOR BACKUP...");
+ xb_mysql_query(connection, "LOCK BINLOG FOR BACKUP", false);
+ binlog_locked = true;
+
+ return(true);
+ }
+
+ return(false);
+}
+
+
+/*********************************************************************//**
+Releases either global read lock acquired with FTWRL and the binlog
+lock acquired with LOCK BINLOG FOR BACKUP, depending on
+the locking strategy being used */
+void
+unlock_all(MYSQL *connection)
+{
+ if (opt_debug_sleep_before_unlock) {
+ msg("Debug sleep for %u seconds",
+ opt_debug_sleep_before_unlock);
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(opt_debug_sleep_before_unlock));
+ }
+
+ msg("Executing BACKUP STAGE END");
+ xb_mysql_query(connection, "BACKUP STAGE END", false);
+
+ msg("All tables unlocked");
+}
+
+
+static
+int
+get_open_temp_tables(MYSQL *connection)
+{
+ char *slave_open_temp_tables = NULL;
+ mysql_variable status[] = {
+ {"Slave_open_temp_tables", &slave_open_temp_tables},
+ {NULL, NULL}
+ };
+ int result = false;
+
+ read_mysql_variables(connection,
+ "SHOW STATUS LIKE 'slave_open_temp_tables'", status, true);
+
+ result = slave_open_temp_tables ? atoi(slave_open_temp_tables) : 0;
+
+ free_mysql_variables(status);
+
+ return(result);
+}
+
+/*********************************************************************//**
+Wait until it's safe to backup a slave. Returns immediately if
+the host isn't a slave. Currently there's only one check:
+Slave_open_temp_tables has to be zero. Dies on timeout. */
+bool
+wait_for_safe_slave(MYSQL *connection)
+{
+ char *read_master_log_pos = NULL;
+ char *slave_sql_running = NULL;
+ int n_attempts = 1;
+ const int sleep_time = 3;
+ int open_temp_tables = 0;
+ bool result = true;
+
+ mysql_variable status[] = {
+ {"Read_Master_Log_Pos", &read_master_log_pos},
+ {"Slave_SQL_Running", &slave_sql_running},
+ {NULL, NULL}
+ };
+
+ sql_thread_started = false;
+
+ read_mysql_variables(connection, "SHOW SLAVE STATUS", status, false);
+
+ if (!(read_master_log_pos && slave_sql_running)) {
+ msg("Not checking slave open temp tables for "
+ "--safe-slave-backup because host is not a slave");
+ goto cleanup;
+ }
+
+ if (strcmp(slave_sql_running, "Yes") == 0) {
+ sql_thread_started = true;
+ xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false);
+ }
+
+ if (opt_safe_slave_backup_timeout > 0) {
+ n_attempts = opt_safe_slave_backup_timeout / sleep_time;
+ }
+
+ open_temp_tables = get_open_temp_tables(connection);
+ msg("Slave open temp tables: %d", open_temp_tables);
+
+ while (open_temp_tables && n_attempts--) {
+ msg("Starting slave SQL thread, waiting %d seconds, then "
+ "checking Slave_open_temp_tables again (%d attempts "
+ "remaining)...", sleep_time, n_attempts);
+
+ xb_mysql_query(connection, "START SLAVE SQL_THREAD", false);
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
+ xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false);
+
+ open_temp_tables = get_open_temp_tables(connection);
+ msg("Slave open temp tables: %d", open_temp_tables);
+ }
+
+ /* Restart the slave if it was running at start */
+ if (open_temp_tables == 0) {
+ msg("Slave is safe to backup");
+ goto cleanup;
+ }
+
+ result = false;
+
+ if (sql_thread_started) {
+ msg("Restarting slave SQL thread.");
+ xb_mysql_query(connection, "START SLAVE SQL_THREAD", false);
+ }
+
+ msg("Slave_open_temp_tables did not become zero after "
+ "%d seconds", opt_safe_slave_backup_timeout);
+
+cleanup:
+ free_mysql_variables(status);
+
+ return(result);
+}
+
+
+class Var
+{
+ const char *m_name;
+ char *m_value;
+ /*
+ Disable copying constructors for safety, as the default binary copying
+ which would be wrong. If we ever want them, the m_value
+ member should be copied using an strdup()-alike function.
+ */
+ Var(const Var &); // Disabled
+ Var(Var &); // Disabled
+public:
+ ~Var()
+ {
+ free(m_value);
+ }
+ Var(const char *name)
+ :m_name(name),
+ m_value(NULL)
+ { }
+ // Init using a SHOW VARIABLES LIKE 'name' query
+ Var(const char *name, MYSQL *mysql)
+ :m_name(name)
+ {
+ char buf[128];
+ my_snprintf(buf, sizeof(buf), "SHOW VARIABLES LIKE '%s'", m_name);
+ m_value= read_mysql_one_value(mysql, buf, 1/*offset*/, 2/*total columns*/);
+ }
+ /*
+ Init by name from a result set.
+ If the variable name is not found in the result set metadata field names,
+ it's value stays untouched.
+ */
+ bool init(MYSQL_RES *mysql_result, MYSQL_ROW row)
+ {
+ MYSQL_FIELD *field= mysql_fetch_fields(mysql_result);
+ for (uint i= 0; i < mysql_num_fields(mysql_result); i++)
+ {
+ if (!strcmp(field[i].name, m_name))
+ {
+ free(m_value); // In case it was initialized earlier
+ m_value= row[i] ? strdup(row[i]) : NULL;
+ return false;
+ }
+ }
+ return true;
+ }
+ void replace(char from, char to)
+ {
+ ut_ad(m_value);
+ for (char *ptr= strchr(m_value, from); ptr; ptr= strchr(ptr, from))
+ *ptr= to;
+ }
+
+ const char *value() const { return m_value; }
+ bool eq_value(const char *str, size_t length) const
+ {
+ return m_value && !strncmp(m_value, str, length) && m_value[length] == '\0';
+ }
+ bool is_null_or_empty() const { return !m_value || !m_value[0]; }
+ bool print(String *to) const
+ {
+ ut_ad(m_value);
+ return to->append(m_value, strlen(m_value));
+ }
+ bool print_quoted(String *to) const
+ {
+ ut_ad(m_value);
+ return to->append('\'') || to->append(m_value, strlen(m_value)) ||
+ to->append('\'');
+ }
+ bool print_set_global(String *to) const
+ {
+ ut_ad(m_value);
+ return
+ to->append(STRING_WITH_LEN("SET GLOBAL ")) ||
+ to->append(m_name, strlen(m_name)) ||
+ to->append(STRING_WITH_LEN(" = '")) ||
+ to->append(m_value, strlen(m_value)) ||
+ to->append(STRING_WITH_LEN("';\n"));
+ }
+};
+
+
+class Show_slave_status
+{
+ Var m_mariadb_connection_name; // MariaDB: e.g. 'master1'
+ Var m_master; // e.g. 'localhost'
+ Var m_filename; // e.g. 'source-bin.000002'
+ Var m_position; // a number
+ Var m_mysql_gtid_executed; // MySQL56: e.g. single '<UUID>:1-5" or multiline
+ // '<UUID1>:1-10,\n<UUID2>:1-20\n<UUID3>:1-30'
+ Var m_mariadb_using_gtid; // MariaDB: 'No','Slave_Pos','Current_Pos'
+
+public:
+
+ Show_slave_status()
+ :m_mariadb_connection_name("Connection_name"),
+ m_master("Master_Host"),
+ m_filename("Relay_Master_Log_File"),
+ m_position("Exec_Master_Log_Pos"),
+ m_mysql_gtid_executed("Executed_Gtid_Set"),
+ m_mariadb_using_gtid("Using_Gtid")
+ { }
+
+ void init(MYSQL_RES *res, MYSQL_ROW row)
+ {
+ m_mariadb_connection_name.init(res, row);
+ m_master.init(res, row);
+ m_filename.init(res, row);
+ m_position.init(res, row);
+ m_mysql_gtid_executed.init(res, row);
+ m_mariadb_using_gtid.init(res, row);
+ // Normalize
+ if (m_mysql_gtid_executed.value())
+ m_mysql_gtid_executed.replace('\n', ' ');
+ }
+
+ static void msg_is_not_slave()
+ {
+ msg("Failed to get master binlog coordinates "
+ "from SHOW SLAVE STATUS.This means that the server is not a "
+ "replication slave. Ignoring the --slave-info option");
+ }
+
+ bool is_mariadb_using_gtid() const
+ {
+ return !m_mariadb_using_gtid.eq_value("No", 2);
+ }
+
+ static bool start_comment_chunk(String *to)
+ {
+ return to->length() ? to->append(STRING_WITH_LEN("; ")) : false;
+ }
+
+ bool print_connection_name_if_set(String *to) const
+ {
+ if (!m_mariadb_connection_name.is_null_or_empty())
+ return m_mariadb_connection_name.print_quoted(to) || to->append(' ');
+ return false;
+ }
+
+ bool print_comment_master_identity(String *comment) const
+ {
+ if (comment->append(STRING_WITH_LEN("master ")))
+ return true;
+ if (!m_mariadb_connection_name.is_null_or_empty())
+ return m_mariadb_connection_name.print_quoted(comment);
+ return comment->append(STRING_WITH_LEN("''")); // Default not named master
+ }
+
+ bool print_using_master_log_pos(String *sql, String *comment) const
+ {
+ return
+ sql->append(STRING_WITH_LEN("CHANGE MASTER ")) ||
+ print_connection_name_if_set(sql) ||
+ sql->append(STRING_WITH_LEN("TO MASTER_LOG_FILE=")) ||
+ m_filename.print_quoted(sql) ||
+ sql->append(STRING_WITH_LEN(", MASTER_LOG_POS=")) ||
+ m_position.print(sql) ||
+ sql->append(STRING_WITH_LEN(";\n")) ||
+ print_comment_master_identity(comment) ||
+ comment->append(STRING_WITH_LEN(" filename ")) ||
+ m_filename.print_quoted(comment) ||
+ comment->append(STRING_WITH_LEN(" position ")) ||
+ m_position.print_quoted(comment);
+ }
+
+ bool print_mysql56(String *sql, String *comment) const
+ {
+ /*
+ SET @@GLOBAL.gtid_purged = '2174B383-5441-11E8-B90A-C80AA9429562:1-1029, '
+ '224DA167-0C0C-11E8-8442-00059A3C7B00:1-2695';
+ CHANGE MASTER TO MASTER_AUTO_POSITION=1;
+ */
+ return
+ sql->append(STRING_WITH_LEN("SET GLOBAL gtid_purged=")) ||
+ m_mysql_gtid_executed.print_quoted(sql) ||
+ sql->append(STRING_WITH_LEN(";\n")) ||
+ sql->append(STRING_WITH_LEN("CHANGE MASTER TO MASTER_AUTO_POSITION=1;\n")) ||
+ print_comment_master_identity(comment) ||
+ comment->append(STRING_WITH_LEN(" purge list ")) ||
+ m_mysql_gtid_executed.print_quoted(comment);
+ }
+
+ bool print_mariadb10_using_gtid(String *sql, String *comment) const
+ {
+ return
+ sql->append(STRING_WITH_LEN("CHANGE MASTER ")) ||
+ print_connection_name_if_set(sql) ||
+ sql->append(STRING_WITH_LEN("TO master_use_gtid = slave_pos;\n")) ||
+ print_comment_master_identity(comment) ||
+ comment->append(STRING_WITH_LEN(" master_use_gtid = slave_pos"));
+ }
+
+ bool print(String *sql, String *comment, const Var &gtid_slave_pos) const
+ {
+ if (!m_mysql_gtid_executed.is_null_or_empty())
+ {
+ /* MySQL >= 5.6 with GTID enabled */
+ return print_mysql56(sql, comment);
+ }
+
+ if (!gtid_slave_pos.is_null_or_empty() && is_mariadb_using_gtid())
+ {
+ /* MariaDB >= 10.0 with GTID enabled */
+ return print_mariadb10_using_gtid(sql, comment);
+ }
+
+ return print_using_master_log_pos(sql, comment);
+ }
+
+ /*
+ Get master info into strings "sql" and "comment" from a MYSQL_RES.
+ @return false on success
+ @return true on error
+ */
+ static bool get_slave_info(MYSQL_RES *show_slave_info_result,
+ const Var &gtid_slave_pos,
+ String *sql, String *comment)
+ {
+ if (!gtid_slave_pos.is_null_or_empty())
+ {
+ // Print gtid_slave_pos if any of the masters really needs it.
+ while (MYSQL_ROW row= mysql_fetch_row(show_slave_info_result))
+ {
+ Show_slave_status status;
+ status.init(show_slave_info_result, row);
+ if (status.is_mariadb_using_gtid())
+ {
+ if (gtid_slave_pos.print_set_global(sql) ||
+ comment->append(STRING_WITH_LEN("gtid_slave_pos ")) ||
+ gtid_slave_pos.print_quoted(comment))
+ return true; // Error
+ break;
+ }
+ }
+ }
+
+ // Print the list of masters
+ mysql_data_seek(show_slave_info_result, 0);
+ while (MYSQL_ROW row= mysql_fetch_row(show_slave_info_result))
+ {
+ Show_slave_status status;
+ status.init(show_slave_info_result, row);
+ if (start_comment_chunk(comment) ||
+ status.print(sql, comment, gtid_slave_pos))
+ return true; // Error
+ }
+ return false; // Success
+ }
+
+ /*
+ Get master info into strings "sql" and "comment".
+ @return false on success
+ @return true on error
+ */
+ static bool get_slave_info(MYSQL *mysql, bool show_all_slave_status,
+ String *sql, String *comment)
+ {
+ bool rc= false; // Success
+ // gtid_slave_pos - MariaDB variable : e.g. "0-1-1" or "1-10-100,2-20-500"
+ Var gtid_slave_pos("gtid_slave_pos", mysql);
+ const char *query= show_all_slave_status ? "SHOW ALL SLAVES STATUS" :
+ "SHOW SLAVE STATUS";
+ MYSQL_RES *mysql_result= xb_mysql_query(mysql, query, true);
+ if (!mysql_num_rows(mysql_result))
+ {
+ msg_is_not_slave();
+ // Don't change rc, we still want to continue the backup
+ }
+ else
+ {
+ rc= get_slave_info(mysql_result, gtid_slave_pos, sql, comment);
+ }
+ mysql_free_result(mysql_result);
+ return rc;
+ }
+};
+
+
+
+/*********************************************************************//**
+Retrieves MySQL binlog position of the master server in a replication
+setup and saves it in a file. It also saves it in mysql_slave_position
+variable.
+@returns false on error
+@returns true on success
+*/
+bool
+write_slave_info(ds_ctxt *datasink, MYSQL *connection)
+{
+ String sql, comment;
+
+ if (Show_slave_status::get_slave_info(connection, true, &sql, &comment))
+ return false; // Error
+
+ if (!sql.length())
+ {
+ /*
+ SHOW [ALL] SLAVE STATUS returned no rows.
+ Don't create the file, but return success to continue the backup.
+ */
+ return true; // Success
+ }
+
+ mysql_slave_position= strdup(comment.c_ptr());
+ return datasink->backup_file_print_buf(XTRABACKUP_SLAVE_INFO,
+ sql.ptr(), sql.length());
+}
+
+
+/*********************************************************************//**
+Retrieves MySQL Galera and
+saves it in a file. It also prints it to stdout. */
+bool
+write_galera_info(ds_ctxt *datasink, MYSQL *connection)
+{
+ char *state_uuid = NULL, *state_uuid55 = NULL;
+ char *last_committed = NULL, *last_committed55 = NULL;
+ bool result;
+
+ mysql_variable status[] = {
+ {"Wsrep_local_state_uuid", &state_uuid},
+ {"wsrep_local_state_uuid", &state_uuid55},
+ {"Wsrep_last_committed", &last_committed},
+ {"wsrep_last_committed", &last_committed55},
+ {NULL, NULL}
+ };
+
+ /* When backup locks are supported by the server, we should skip
+ creating xtrabackup_galera_info file on the backup stage, because
+ wsrep_local_state_uuid and wsrep_last_committed will be inconsistent
+ without blocking commits. The state file will be created on the prepare
+ stage using the WSREP recovery procedure. */
+ if (have_backup_locks) {
+ return(true);
+ }
+
+ read_mysql_variables(connection, "SHOW STATUS", status, true);
+
+ if ((state_uuid == NULL && state_uuid55 == NULL)
+ || (last_committed == NULL && last_committed55 == NULL)) {
+ msg("Warning: failed to get master wsrep state from SHOW STATUS.");
+ result = true;
+ goto cleanup;
+ }
+
+ result = datasink->backup_file_printf(XTRABACKUP_GALERA_INFO,
+ "%s:%s\n", state_uuid ? state_uuid : state_uuid55,
+ last_committed ? last_committed : last_committed55);
+ if (result)
+ {
+ write_current_binlog_file(datasink, connection);
+ }
+
+cleanup:
+ free_mysql_variables(status);
+
+ return(result);
+}
+
+
+/*********************************************************************//**
+Flush and copy the current binary log file into the backup,
+if GTID is enabled */
+bool
+write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection)
+{
+ char *executed_gtid_set = NULL;
+ char *gtid_binlog_state = NULL;
+ char *log_bin_file = NULL;
+ char *log_bin_dir = NULL;
+ bool gtid_exists;
+ bool result = true;
+ char filepath[FN_REFLEN];
+
+ mysql_variable status[] = {
+ {"Executed_Gtid_Set", &executed_gtid_set},
+ {NULL, NULL}
+ };
+
+ mysql_variable status_after_flush[] = {
+ {"File", &log_bin_file},
+ {NULL, NULL}
+ };
+
+ mysql_variable vars[] = {
+ {"gtid_binlog_state", &gtid_binlog_state},
+ {"log_bin_basename", &log_bin_dir},
+ {NULL, NULL}
+ };
+
+ read_mysql_variables(connection, "SHOW MASTER STATUS", status, false);
+ read_mysql_variables(connection, "SHOW VARIABLES", vars, true);
+
+ gtid_exists = (executed_gtid_set && *executed_gtid_set)
+ || (gtid_binlog_state && *gtid_binlog_state);
+
+ if (gtid_exists) {
+ size_t log_bin_dir_length;
+
+ lock_binlog_maybe(connection);
+
+ xb_mysql_query(connection, "FLUSH BINARY LOGS", false);
+
+ read_mysql_variables(connection, "SHOW MASTER STATUS",
+ status_after_flush, false);
+
+ if (opt_log_bin != NULL && strchr(opt_log_bin, FN_LIBCHAR)) {
+ /* If log_bin is set, it has priority */
+ if (log_bin_dir) {
+ free(log_bin_dir);
+ }
+ log_bin_dir = strdup(opt_log_bin);
+ } else if (log_bin_dir == NULL) {
+ /* Default location is MySQL datadir */
+ log_bin_dir = strdup("./");
+ }
+
+ dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length);
+
+ /* strip final slash if it is not the only path component */
+ if (log_bin_dir_length > 1 &&
+ log_bin_dir[log_bin_dir_length - 1] == FN_LIBCHAR) {
+ log_bin_dir[log_bin_dir_length - 1] = 0;
+ }
+
+ if (log_bin_dir == NULL || log_bin_file == NULL) {
+ msg("Failed to get master binlog coordinates from "
+ "SHOW MASTER STATUS");
+ result = false;
+ goto cleanup;
+ }
+
+ snprintf(filepath, sizeof(filepath), "%s%c%s",
+ log_bin_dir, FN_LIBCHAR, log_bin_file);
+ result = datasink->copy_file(filepath, log_bin_file, 0);
+ }
+
+cleanup:
+ free_mysql_variables(status_after_flush);
+ free_mysql_variables(status);
+ free_mysql_variables(vars);
+
+ return(result);
+}
+
+
+/*********************************************************************//**
+Retrieves MySQL binlog position and
+saves it in a file. It also prints it to stdout. */
+bool
+write_binlog_info(ds_ctxt *datasink, MYSQL *connection)
+{
+ char *filename = NULL;
+ char *position = NULL;
+ char *gtid_mode = NULL;
+ char *gtid_current_pos = NULL;
+ char *gtid_executed = NULL;
+ char *gtid = NULL;
+ bool result;
+ bool mysql_gtid;
+ bool mariadb_gtid;
+
+ mysql_variable status[] = {
+ {"File", &filename},
+ {"Position", &position},
+ {"Executed_Gtid_Set", &gtid_executed},
+ {NULL, NULL}
+ };
+
+ mysql_variable vars[] = {
+ {"gtid_mode", &gtid_mode},
+ {"gtid_current_pos", &gtid_current_pos},
+ {NULL, NULL}
+ };
+
+ read_mysql_variables(connection, "SHOW MASTER STATUS", status, false);
+ read_mysql_variables(connection, "SHOW VARIABLES", vars, true);
+
+ if (filename == NULL || position == NULL) {
+ /* Do not create xtrabackup_binlog_info if binary
+ log is disabled */
+ result = true;
+ goto cleanup;
+ }
+
+ mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0));
+ mariadb_gtid = (gtid_current_pos != NULL);
+
+ gtid = (gtid_executed != NULL ? gtid_executed : gtid_current_pos);
+
+ if (mariadb_gtid || mysql_gtid) {
+ ut_a(asprintf(&mysql_binlog_position,
+ "filename '%s', position '%s', "
+ "GTID of the last change '%s'",
+ filename, position, gtid) != -1);
+ result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO,
+ "%s\t%s\t%s\n", filename, position,
+ gtid);
+ } else {
+ ut_a(asprintf(&mysql_binlog_position,
+ "filename '%s', position '%s'",
+ filename, position) != -1);
+ result = datasink->backup_file_printf(XTRABACKUP_BINLOG_INFO,
+ "%s\t%s\n", filename, position);
+ }
+
+cleanup:
+ free_mysql_variables(status);
+ free_mysql_variables(vars);
+
+ return(result);
+}
+
+struct escape_and_quote
+{
+ escape_and_quote(MYSQL *mysql, const char *str)
+ : mysql(mysql), str(str) {}
+ MYSQL * const mysql;
+ const char * const str;
+};
+
+static
+std::ostream&
+operator<<(std::ostream& s, const escape_and_quote& eq)
+{
+ if (!eq.str)
+ return s << "NULL";
+ s << '\'';
+ size_t len = strlen(eq.str);
+ char* escaped = (char *)alloca(2 * len + 1);
+ len = mysql_real_escape_string(eq.mysql, escaped, eq.str, (ulong)len);
+ s << std::string(escaped, len);
+ s << '\'';
+ return s;
+}
+
+/*********************************************************************//**
+Writes xtrabackup_info file and if backup_history is enable creates
+mysql.mariabackup_history and writes a new history record to the
+table containing all the history info particular to the just completed
+backup. */
+bool
+write_xtrabackup_info(ds_ctxt *datasink,
+ MYSQL *connection, const char * filename, bool history,
+ bool stream)
+{
+
+ bool result = true;
+ FILE *fp = NULL;
+ char *uuid = NULL;
+ char *server_version = NULL;
+ char buf_start_time[100];
+ char buf_end_time[100];
+ tm tm;
+ std::ostringstream oss;
+ const char *xb_stream_name[] = {"file", "tar", "xbstream"};
+
+ uuid = read_mysql_one_value(connection, "SELECT UUID()");
+ server_version = read_mysql_one_value(connection, "SELECT VERSION()");
+ localtime_r(&history_start_time, &tm);
+ strftime(buf_start_time, sizeof(buf_start_time),
+ "%Y-%m-%d %H:%M:%S", &tm);
+ history_end_time = time(NULL);
+ localtime_r(&history_end_time, &tm);
+ strftime(buf_end_time, sizeof(buf_end_time),
+ "%Y-%m-%d %H:%M:%S", &tm);
+ bool is_partial = (xtrabackup_tables
+ || xtrabackup_tables_file
+ || xtrabackup_databases
+ || xtrabackup_databases_file
+ || xtrabackup_tables_exclude
+ || xtrabackup_databases_exclude
+ );
+
+ char *buf = NULL;
+ int buf_len = asprintf(&buf,
+ "uuid = %s\n"
+ "name = %s\n"
+ "tool_name = %s\n"
+ "tool_command = %s\n"
+ "tool_version = %s\n"
+ "ibbackup_version = %s\n"
+ "server_version = %s\n"
+ "start_time = %s\n"
+ "end_time = %s\n"
+ "lock_time = %d\n"
+ "binlog_pos = %s\n"
+ "innodb_from_lsn = " LSN_PF "\n"
+ "innodb_to_lsn = " LSN_PF "\n"
+ "partial = %s\n"
+ "incremental = %s\n"
+ "format = %s\n"
+ "compressed = %s\n",
+ uuid, /* uuid */
+ opt_history ? opt_history : "", /* name */
+ tool_name, /* tool_name */
+ tool_args, /* tool_command */
+ MYSQL_SERVER_VERSION, /* tool_version */
+ MYSQL_SERVER_VERSION, /* ibbackup_version */
+ server_version, /* server_version */
+ buf_start_time, /* start_time */
+ buf_end_time, /* end_time */
+ (int)history_lock_time, /* lock_time */
+ mysql_binlog_position ?
+ mysql_binlog_position : "", /* binlog_pos */
+ incremental_lsn,
+ /* innodb_from_lsn */
+ metadata_to_lsn,
+ /* innodb_to_lsn */
+ is_partial? "Y" : "N",
+ xtrabackup_incremental ? "Y" : "N", /* incremental */
+ xb_stream_name[xtrabackup_stream_fmt], /* format */
+ xtrabackup_compress ? "compressed" : "N"); /* compressed */
+ if (buf_len < 0) {
+ msg("Error: cannot generate xtrabackup_info");
+ result = false;
+ goto cleanup;
+ }
+
+ if (stream) {
+ datasink->backup_file_printf(filename, "%s", buf);
+ } else {
+ fp = fopen(filename, "w");
+ if (!fp) {
+ msg("Error: cannot open %s", filename);
+ result = false;
+ goto cleanup;
+ }
+ if (fwrite(buf, buf_len, 1, fp) < 1) {
+ result = false;
+ goto cleanup;
+ }
+ }
+
+ if (!history) {
+ goto cleanup;
+ }
+
+ xb_mysql_query(connection,
+ "CREATE TABLE IF NOT EXISTS " XB_HISTORY_TABLE "("
+ "uuid VARCHAR(40) NOT NULL PRIMARY KEY,"
+ "name VARCHAR(255) DEFAULT NULL,"
+ "tool_name VARCHAR(255) DEFAULT NULL,"
+ "tool_command TEXT DEFAULT NULL,"
+ "tool_version VARCHAR(255) DEFAULT NULL,"
+ "ibbackup_version VARCHAR(255) DEFAULT NULL,"
+ "server_version VARCHAR(255) DEFAULT NULL,"
+ "start_time TIMESTAMP NULL DEFAULT NULL,"
+ "end_time TIMESTAMP NULL DEFAULT NULL,"
+ "lock_time BIGINT UNSIGNED DEFAULT NULL,"
+ "binlog_pos VARCHAR(128) DEFAULT NULL,"
+ "innodb_from_lsn BIGINT UNSIGNED DEFAULT NULL,"
+ "innodb_to_lsn BIGINT UNSIGNED DEFAULT NULL,"
+ "partial ENUM('Y', 'N') DEFAULT NULL,"
+ "incremental ENUM('Y', 'N') DEFAULT NULL,"
+ "format ENUM('file', 'tar', 'xbstream') DEFAULT NULL,"
+ "compressed ENUM('Y', 'N') DEFAULT NULL"
+ ") CHARACTER SET utf8 ENGINE=innodb", false);
+
+
+#define ESCAPE_BOOL(expr) ((expr)?"'Y'":"'N'")
+
+ oss << "insert into " XB_HISTORY_TABLE "("
+ << "uuid, name, tool_name, tool_command, tool_version,"
+ << "ibbackup_version, server_version, start_time, end_time,"
+ << "lock_time, binlog_pos, innodb_from_lsn, innodb_to_lsn,"
+ << "partial, incremental, format, compressed) "
+ << "values("
+ << escape_and_quote(connection, uuid) << ","
+ << escape_and_quote(connection, opt_history) << ","
+ << escape_and_quote(connection, tool_name) << ","
+ << escape_and_quote(connection, tool_args) << ","
+ << escape_and_quote(connection, MYSQL_SERVER_VERSION) << ","
+ << escape_and_quote(connection, MYSQL_SERVER_VERSION) << ","
+ << escape_and_quote(connection, server_version) << ","
+ << "from_unixtime(" << history_start_time << "),"
+ << "from_unixtime(" << history_end_time << "),"
+ << history_lock_time << ","
+ << escape_and_quote(connection, mysql_binlog_position) << ","
+ << incremental_lsn << ","
+ << metadata_to_lsn << ","
+ << ESCAPE_BOOL(is_partial) << ","
+ << ESCAPE_BOOL(xtrabackup_incremental)<< ","
+ << escape_and_quote(connection,xb_stream_name[xtrabackup_stream_fmt]) <<","
+ << ESCAPE_BOOL(xtrabackup_compress) << ")";
+
+ xb_mysql_query(mysql_connection, oss.str().c_str(), false);
+
+cleanup:
+
+ free(uuid);
+ free(server_version);
+ free(buf);
+ if (fp)
+ fclose(fp);
+
+ return(result);
+}
+
+extern const char *innodb_checksum_algorithm_names[];
+
+#ifdef _WIN32
+#include <algorithm>
+#endif
+
+static std::string make_local_paths(const char *data_file_path)
+{
+ if (strchr(data_file_path, '/') == 0
+#ifdef _WIN32
+ && strchr(data_file_path, '\\') == 0
+#endif
+ ){
+ return std::string(data_file_path);
+ }
+
+ std::ostringstream buf;
+
+ char *dup = strdup(innobase_data_file_path);
+ ut_a(dup);
+ char *p;
+ char * token = strtok_r(dup, ";", &p);
+ while (token) {
+ if (buf.tellp())
+ buf << ";";
+
+ char *fname = strrchr(token, '/');
+#ifdef _WIN32
+ fname = std::max(fname,strrchr(token, '\\'));
+#endif
+ if (fname)
+ buf << fname + 1;
+ else
+ buf << token;
+ token = strtok_r(NULL, ";", &p);
+ }
+ free(dup);
+ return buf.str();
+}
+
+bool write_backup_config_file(ds_ctxt *datasink)
+{
+ int rc= datasink->backup_file_printf("backup-my.cnf",
+ "# This options file was generated by innobackupex.\n\n"
+ "# The server\n"
+ "[mysqld]\n"
+ "innodb_checksum_algorithm=%s\n"
+ "innodb_data_file_path=%s\n"
+ "innodb_log_file_size=%llu\n"
+ "innodb_page_size=%lu\n"
+ "innodb_undo_directory=%s\n"
+ "innodb_undo_tablespaces=%u\n"
+ "innodb_compression_level=%u\n"
+ "%s%s\n"
+ "%s\n",
+ innodb_checksum_algorithm_names[srv_checksum_algorithm],
+ make_local_paths(innobase_data_file_path).c_str(),
+ srv_log_file_size,
+ srv_page_size,
+ srv_undo_dir,
+ srv_undo_tablespaces,
+ page_zip_level,
+ innobase_buffer_pool_filename ?
+ "innodb_buffer_pool_filename=" : "",
+ innobase_buffer_pool_filename ?
+ innobase_buffer_pool_filename : "",
+ xb_plugin_get_config());
+ return rc;
+}
+
+
+static
+char *make_argv(char *buf, size_t len, int argc, char **argv)
+{
+ size_t left= len;
+ const char *arg;
+
+ buf[0]= 0;
+ ++argv; --argc;
+ while (argc > 0 && left > 0)
+ {
+ arg = *argv;
+ if (strncmp(*argv, "--password", strlen("--password")) == 0) {
+ arg = "--password=...";
+ }
+ left-= snprintf(buf + len - left, left,
+ "%s%c", arg, argc > 1 ? ' ' : 0);
+ ++argv; --argc;
+ }
+
+ return buf;
+}
+
+void
+capture_tool_command(int argc, char **argv)
+{
+ /* capture tool name tool args */
+ tool_name = strrchr(argv[0], '/');
+ tool_name = tool_name ? tool_name + 1 : argv[0];
+
+ make_argv(tool_args, sizeof(tool_args), argc, argv);
+}
+
+
+bool
+select_history()
+{
+ if (opt_incremental_history_name || opt_incremental_history_uuid) {
+ if (!select_incremental_lsn_from_history(
+ &incremental_lsn)) {
+ return(false);
+ }
+ }
+ return(true);
+}
+
+bool
+flush_changed_page_bitmaps()
+{
+ if (xtrabackup_incremental && have_changed_page_bitmaps &&
+ !xtrabackup_incremental_force_scan) {
+ xb_mysql_query(mysql_connection,
+ "FLUSH NO_WRITE_TO_BINLOG CHANGED_PAGE_BITMAPS", false);
+ }
+ return(true);
+}
+
+
+/*********************************************************************//**
+Deallocate memory, disconnect from server, etc.
+@return true on success. */
+void
+backup_cleanup()
+{
+ free(mysql_slave_position);
+ free(mysql_binlog_position);
+ free(buffer_pool_filename);
+
+ if (mysql_connection) {
+ mysql_close(mysql_connection);
+ }
+}
+
+
+static MYSQL *mdl_con = NULL;
+
+std::map<ulint, std::string> spaceid_to_tablename;
+
+void
+mdl_lock_init()
+{
+ mdl_con = xb_mysql_connect();
+ if (!mdl_con)
+ {
+ msg("FATAL: cannot create connection for MDL locks");
+ exit(1);
+ }
+ const char *query =
+ "SELECT NAME, SPACE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE '%%/%%'";
+
+ MYSQL_RES *mysql_result = xb_mysql_query(mdl_con, query, true, true);
+ while (MYSQL_ROW row = mysql_fetch_row(mysql_result)) {
+ int err;
+ ulint id = (ulint)my_strtoll10(row[1], 0, &err);
+ spaceid_to_tablename[id] = ut_get_name(0, row[0]);
+ }
+ mysql_free_result(mysql_result);
+
+ xb_mysql_query(mdl_con, "BEGIN", false, true);
+}
+
+void
+mdl_lock_table(ulint space_id)
+{
+ if (space_id == 0)
+ return;
+
+ std::string full_table_name = spaceid_to_tablename[space_id];
+
+ DBUG_EXECUTE_IF("rename_during_mdl_lock_table",
+ if (full_table_name == "`test`.`t1`")
+ xb_mysql_query(mysql_connection, "RENAME TABLE test.t1 to test.t2", false, true);
+ );
+
+ std::ostringstream lock_query;
+ lock_query << "SELECT 1 FROM " << full_table_name << " LIMIT 0";
+ msg("Locking MDL for %s", full_table_name.c_str());
+ if (mysql_query(mdl_con, lock_query.str().c_str())) {
+ msg("Warning : locking MDL failed for space id %zu, name %s", space_id, full_table_name.c_str());
+ } else {
+ MYSQL_RES *r = mysql_store_result(mdl_con);
+ mysql_free_result(r);
+ }
+}
+
+void
+mdl_unlock_all()
+{
+ msg("Unlocking MDL for all tables");
+ xb_mysql_query(mdl_con, "COMMIT", false, true);
+ mysql_close(mdl_con);
+ spaceid_to_tablename.clear();
+}
diff --git a/extra/mariabackup/backup_mysql.h b/extra/mariabackup/backup_mysql.h
new file mode 100644
index 00000000..4b08da0b
--- /dev/null
+++ b/extra/mariabackup/backup_mysql.h
@@ -0,0 +1,90 @@
+#ifndef XTRABACKUP_BACKUP_MYSQL_H
+#define XTRABACKUP_BACKUP_MYSQL_H
+
+#include <mysql.h>
+
+/* MariaDB version */
+extern ulong mysql_server_version;
+
+/* server capabilities */
+extern bool have_changed_page_bitmaps;
+extern bool have_backup_locks;
+extern bool have_lock_wait_timeout;
+extern bool have_galera_enabled;
+extern bool have_multi_threaded_slave;
+extern bool have_gtid_slave;
+
+
+/* History on server */
+extern time_t history_start_time;
+extern time_t history_end_time;
+extern time_t history_lock_time;
+
+
+extern bool sql_thread_started;
+extern char *mysql_slave_position;
+extern char *mysql_binlog_position;
+extern char *buffer_pool_filename;
+
+/** connection to mysql server */
+extern MYSQL *mysql_connection;
+
+void
+capture_tool_command(int argc, char **argv);
+
+bool
+select_history();
+
+bool
+flush_changed_page_bitmaps();
+
+void
+backup_cleanup();
+
+bool
+get_mysql_vars(MYSQL *connection);
+
+bool
+detect_mysql_capabilities_for_backup();
+
+MYSQL *
+xb_mysql_connect();
+
+MYSQL_RES *
+xb_mysql_query(MYSQL *connection, const char *query, bool use_result,
+ bool die_on_error = true);
+
+void
+unlock_all(MYSQL *connection);
+
+bool
+write_current_binlog_file(ds_ctxt *datasink, MYSQL *connection);
+
+bool
+write_binlog_info(ds_ctxt *datasink, MYSQL *connection);
+
+bool
+write_xtrabackup_info(ds_ctxt *datasink,
+ MYSQL *connection, const char * filename, bool history,
+ bool stream);
+
+bool
+write_backup_config_file(ds_ctxt *datasink);
+
+bool
+lock_binlog_maybe(MYSQL *connection);
+
+bool
+lock_tables(MYSQL *connection);
+
+bool
+wait_for_safe_slave(MYSQL *connection);
+
+bool
+write_galera_info(ds_ctxt *datasink, MYSQL *connection);
+
+bool
+write_slave_info(ds_ctxt *datasink, MYSQL *connection);
+
+
+#endif
diff --git a/extra/mariabackup/backup_wsrep.h b/extra/mariabackup/backup_wsrep.h
new file mode 100644
index 00000000..50a8a3a5
--- /dev/null
+++ b/extra/mariabackup/backup_wsrep.h
@@ -0,0 +1,32 @@
+/******************************************************
+Percona XtraBackup: hot backup tool for InnoDB
+(c) 2009-2014 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.
+
+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
+*******************************************************/
+
+#ifndef BACKUP_WSREP_H
+#define BACKUP_WSREP_H
+
+/***********************************************************************
+Store Galera checkpoint info in the 'xtrabackup_galera_info' file, if that
+information is present in the trx system header. Otherwise, do nothing. */
+void
+xb_write_galera_info(bool incremental_prepare);
+/*==================*/
+
+#endif
diff --git a/extra/mariabackup/changed_page_bitmap.cc b/extra/mariabackup/changed_page_bitmap.cc
new file mode 100644
index 00000000..39a07a25
--- /dev/null
+++ b/extra/mariabackup/changed_page_bitmap.cc
@@ -0,0 +1,1040 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2012 Percona Inc.
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+
+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
+
+*******************************************************/
+
+/* Changed page bitmap implementation */
+
+#include "changed_page_bitmap.h"
+
+#include "common.h"
+#include "xtrabackup.h"
+#include "srv0srv.h"
+
+/* TODO: copy-pasted shared definitions from the XtraDB bitmap write code.
+Remove these on the first opportunity, i.e. single-binary XtraBackup. */
+
+/* log0online.h */
+
+/** Single bitmap file information */
+struct log_online_bitmap_file_t {
+ char name[FN_REFLEN]; /*!< Name with full path */
+ pfs_os_file_t file; /*!< Handle to opened file */
+ ib_uint64_t size; /*!< Size of the file */
+ ib_uint64_t offset; /*!< Offset of the next read,
+ or count of already-read bytes
+ */
+};
+
+/** A set of bitmap files containing some LSN range */
+struct log_online_bitmap_file_range_t {
+ size_t count; /*!< Number of files */
+ /*!< Dynamically-allocated array of info about individual files */
+ struct files_t {
+ char name[FN_REFLEN];/*!< Name of a file */
+ lsn_t start_lsn; /*!< Starting LSN of data in this
+ file */
+ ulong seq_num; /*!< Sequence number of this file */
+ } *files;
+};
+
+/* log0online.c */
+
+/** File name stem for bitmap files. */
+static const char* bmp_file_name_stem = "ib_modified_log_";
+
+/** The bitmap file block size in bytes. All writes will be multiples of this.
+ */
+enum {
+ MODIFIED_PAGE_BLOCK_SIZE = 4096
+};
+
+/** Offsets in a file bitmap block */
+enum {
+ MODIFIED_PAGE_IS_LAST_BLOCK = 0,/* 1 if last block in the current
+ write, 0 otherwise. */
+ MODIFIED_PAGE_START_LSN = 4, /* The starting tracked LSN of this and
+ other blocks in the same write */
+ MODIFIED_PAGE_END_LSN = 12, /* The ending tracked LSN of this and
+ other blocks in the same write */
+ MODIFIED_PAGE_SPACE_ID = 20, /* The space ID of tracked pages in
+ this block */
+ MODIFIED_PAGE_1ST_PAGE_ID = 24, /* The page ID of the first tracked
+ page in this block */
+ MODIFIED_PAGE_BLOCK_UNUSED_1 = 28,/* Unused in order to align the start
+ of bitmap at 8 byte boundary */
+ MODIFIED_PAGE_BLOCK_BITMAP = 32,/* Start of the bitmap itself */
+ MODIFIED_PAGE_BLOCK_UNUSED_2 = MODIFIED_PAGE_BLOCK_SIZE - 8,
+ /* Unused in order to align the end of
+ bitmap at 8 byte boundary */
+ MODIFIED_PAGE_BLOCK_CHECKSUM = MODIFIED_PAGE_BLOCK_SIZE - 4
+ /* The checksum of the current block */
+};
+
+/** Length of the bitmap data in a block */
+enum { MODIFIED_PAGE_BLOCK_BITMAP_LEN
+ = MODIFIED_PAGE_BLOCK_UNUSED_2 - MODIFIED_PAGE_BLOCK_BITMAP };
+
+/** Length of the bitmap data in a block in page ids */
+enum { MODIFIED_PAGE_BLOCK_ID_COUNT = MODIFIED_PAGE_BLOCK_BITMAP_LEN * 8 };
+
+typedef ib_uint64_t bitmap_word_t;
+
+/****************************************************************//**
+Calculate a bitmap block checksum. Algorithm borrowed from
+log_block_calc_checksum.
+@return checksum */
+UNIV_INLINE
+ulint
+log_online_calc_checksum(
+/*=====================*/
+ const byte* block); /*!<in: bitmap block */
+
+/****************************************************************//**
+Provide a comparisson function for the RB-tree tree (space,
+block_start_page) pairs. Actual implementation does not matter as
+long as the ordering is full.
+@return -1 if p1 < p2, 0 if p1 == p2, 1 if p1 > p2
+*/
+static
+int
+log_online_compare_bmp_keys(
+/*========================*/
+ const void* p1, /*!<in: 1st key to compare */
+ const void* p2) /*!<in: 2nd key to compare */
+{
+ const byte *k1 = (const byte *)p1;
+ const byte *k2 = (const byte *)p2;
+
+ ulint k1_space = mach_read_from_4(k1 + MODIFIED_PAGE_SPACE_ID);
+ ulint k2_space = mach_read_from_4(k2 + MODIFIED_PAGE_SPACE_ID);
+ if (k1_space == k2_space) {
+
+ ulint k1_start_page
+ = mach_read_from_4(k1 + MODIFIED_PAGE_1ST_PAGE_ID);
+ ulint k2_start_page
+ = mach_read_from_4(k2 + MODIFIED_PAGE_1ST_PAGE_ID);
+ return k1_start_page < k2_start_page
+ ? -1 : k1_start_page > k2_start_page ? 1 : 0;
+ }
+ return k1_space < k2_space ? -1 : 1;
+}
+
+/****************************************************************//**
+Calculate a bitmap block checksum. Algorithm borrowed from
+log_block_calc_checksum.
+@return checksum */
+UNIV_INLINE
+ulint
+log_online_calc_checksum(
+/*=====================*/
+ const byte* block) /*!<in: bitmap block */
+{
+ ulint sum;
+ ulint sh;
+ ulint i;
+
+ sum = 1;
+ sh = 0;
+
+ for (i = 0; i < MODIFIED_PAGE_BLOCK_CHECKSUM; i++) {
+
+ ulint b = block[i];
+ sum &= 0x7FFFFFFFUL;
+ sum += b;
+ sum += b << sh;
+ sh++;
+ if (sh > 24) {
+
+ sh = 0;
+ }
+ }
+
+ return sum;
+}
+
+/****************************************************************//**
+Read one bitmap data page and check it for corruption.
+
+@return TRUE if page read OK, FALSE if I/O error */
+static
+ibool
+log_online_read_bitmap_page(
+/*========================*/
+ log_online_bitmap_file_t *bitmap_file, /*!<in/out: bitmap
+ file */
+ byte *page, /*!<out: read page. Must be at
+ least MODIFIED_PAGE_BLOCK_SIZE
+ bytes long */
+ ibool *checksum_ok) /*!<out: TRUE if page
+ checksum OK */
+{
+ ulint checksum;
+ ulint actual_checksum;
+
+ ut_a(bitmap_file->size >= MODIFIED_PAGE_BLOCK_SIZE);
+ ut_a(bitmap_file->offset
+ <= bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE);
+ ut_a(bitmap_file->offset % MODIFIED_PAGE_BLOCK_SIZE == 0);
+ if (DB_SUCCESS !=
+ os_file_read(IORequestRead, bitmap_file->file, page,
+ bitmap_file->offset, MODIFIED_PAGE_BLOCK_SIZE,
+ nullptr)) {
+ /* The following call prints an error message */
+ os_file_get_last_error(TRUE);
+ msg("InnoDB: Warning: failed reading changed page bitmap "
+ "file \'%s\'", bitmap_file->name);
+ return FALSE;
+ }
+
+ bitmap_file->offset += MODIFIED_PAGE_BLOCK_SIZE;
+ ut_ad(bitmap_file->offset <= bitmap_file->size);
+
+ checksum = mach_read_from_4(page + MODIFIED_PAGE_BLOCK_CHECKSUM);
+ actual_checksum = log_online_calc_checksum(page);
+ *checksum_ok = (checksum == actual_checksum);
+
+ return TRUE;
+}
+
+/*********************************************************************//**
+Check the name of a given file if it's a changed page bitmap file and
+return file sequence and start LSN name components if it is. If is not,
+the values of output parameters are undefined.
+
+@return TRUE if a given file is a changed page bitmap file. */
+static
+ibool
+log_online_is_bitmap_file(
+/*======================*/
+ const os_file_stat_t* file_info, /*!<in: file to
+ check */
+ ulong* bitmap_file_seq_num, /*!<out: bitmap file
+ sequence number */
+ lsn_t* bitmap_file_start_lsn) /*!<out: bitmap file
+ start LSN */
+{
+ char stem[FN_REFLEN];
+
+ ut_ad (strlen(file_info->name) < OS_FILE_MAX_PATH);
+
+ return ((file_info->type == OS_FILE_TYPE_FILE
+ || file_info->type == OS_FILE_TYPE_LINK)
+ && (sscanf(file_info->name, "%[a-z_]%lu_" LSN_PF ".xdb", stem,
+ bitmap_file_seq_num, bitmap_file_start_lsn) == 3)
+ && (!strcmp(stem, bmp_file_name_stem)));
+}
+
+/*********************************************************************//**
+List the bitmap files in srv_data_home and setup their range that contains the
+specified LSN interval. This range, if non-empty, will start with a file that
+has the greatest LSN equal to or less than the start LSN and will include all
+the files up to the one with the greatest LSN less than the end LSN. Caller
+must free bitmap_files->files when done if bitmap_files set to non-NULL and
+this function returned TRUE. Field bitmap_files->count might be set to a
+larger value than the actual count of the files, and space for the unused array
+slots will be allocated but cleared to zeroes.
+
+@return TRUE if succeeded
+*/
+static
+ibool
+log_online_setup_bitmap_file_range(
+/*===============================*/
+ log_online_bitmap_file_range_t *bitmap_files, /*!<in/out: bitmap file
+ range */
+ lsn_t range_start, /*!<in: start LSN */
+ lsn_t range_end) /*!<in: end LSN */
+{
+ os_file_dir_t bitmap_dir;
+ os_file_stat_t bitmap_dir_file_info;
+ ulong first_file_seq_num = ULONG_MAX;
+ ulong last_file_seq_num = 0;
+ lsn_t first_file_start_lsn = LSN_MAX;
+
+ xb_ad(range_end >= range_start);
+
+ bitmap_files->count = 0;
+ bitmap_files->files = NULL;
+
+ /* 1st pass: size the info array */
+
+ bitmap_dir = os_file_opendir(srv_data_home);
+ if (UNIV_UNLIKELY(bitmap_dir == IF_WIN(INVALID_HANDLE_VALUE, NULL))) {
+ msg("InnoDB: Error: failed to open bitmap directory \'%s\'",
+ srv_data_home);
+ return FALSE;
+ }
+
+ while (!os_file_readdir_next_file(srv_data_home, bitmap_dir,
+ &bitmap_dir_file_info)) {
+
+ ulong file_seq_num;
+ lsn_t file_start_lsn;
+
+ if (!log_online_is_bitmap_file(&bitmap_dir_file_info,
+ &file_seq_num,
+ &file_start_lsn)
+ || file_start_lsn >= range_end) {
+
+ continue;
+ }
+
+ if (file_seq_num > last_file_seq_num) {
+
+ last_file_seq_num = file_seq_num;
+ }
+
+ if (file_start_lsn >= range_start
+ || file_start_lsn == first_file_start_lsn
+ || first_file_start_lsn > range_start) {
+
+ /* A file that falls into the range */
+
+ if (file_start_lsn < first_file_start_lsn) {
+
+ first_file_start_lsn = file_start_lsn;
+ }
+ if (file_seq_num < first_file_seq_num) {
+
+ first_file_seq_num = file_seq_num;
+ }
+ } else if (file_start_lsn > first_file_start_lsn) {
+
+ /* A file that has LSN closer to the range start
+ but smaller than it, replacing another such file */
+ first_file_start_lsn = file_start_lsn;
+ first_file_seq_num = file_seq_num;
+ }
+ }
+
+ if (UNIV_UNLIKELY(os_file_closedir_failed(bitmap_dir))) {
+ os_file_get_last_error(TRUE);
+ msg("InnoDB: Error: cannot close \'%s\'",srv_data_home);
+ return FALSE;
+ }
+
+ if (first_file_seq_num == ULONG_MAX && last_file_seq_num == 0) {
+
+ bitmap_files->count = 0;
+ return TRUE;
+ }
+
+ bitmap_files->count = last_file_seq_num - first_file_seq_num + 1;
+
+ /* 2nd pass: get the file names in the file_seq_num order */
+
+ bitmap_dir = os_file_opendir(srv_data_home);
+ if (UNIV_UNLIKELY(bitmap_dir == IF_WIN(INVALID_HANDLE_VALUE, NULL))) {
+ msg("InnoDB: Error: failed to open bitmap directory \'%s\'",
+ srv_data_home);
+ return FALSE;
+ }
+
+ bitmap_files->files =
+ static_cast<log_online_bitmap_file_range_t::files_t *>
+ (malloc(bitmap_files->count * sizeof(bitmap_files->files[0])));
+ memset(bitmap_files->files, 0,
+ bitmap_files->count * sizeof(bitmap_files->files[0]));
+
+ while (!os_file_readdir_next_file(srv_data_home, bitmap_dir,
+ &bitmap_dir_file_info)) {
+
+ ulong file_seq_num;
+ lsn_t file_start_lsn;
+ size_t array_pos;
+
+ if (!log_online_is_bitmap_file(&bitmap_dir_file_info,
+ &file_seq_num,
+ &file_start_lsn)
+ || file_start_lsn >= range_end
+ || file_start_lsn < first_file_start_lsn) {
+
+ continue;
+ }
+
+ array_pos = file_seq_num - first_file_seq_num;
+ if (UNIV_UNLIKELY(array_pos >= bitmap_files->count)) {
+
+ msg("InnoDB: Error: inconsistent bitmap file "
+ "directory");
+ os_file_closedir(bitmap_dir);
+ free(bitmap_files->files);
+ return FALSE;
+ }
+
+ if (file_seq_num > bitmap_files->files[array_pos].seq_num) {
+
+ bitmap_files->files[array_pos].seq_num = file_seq_num;
+ strncpy(bitmap_files->files[array_pos].name,
+ bitmap_dir_file_info.name, FN_REFLEN - 1);
+ bitmap_files->files[array_pos].name[FN_REFLEN - 1]
+ = '\0';
+ bitmap_files->files[array_pos].start_lsn
+ = file_start_lsn;
+ }
+ }
+
+ if (UNIV_UNLIKELY(os_file_closedir_failed(bitmap_dir))) {
+ os_file_get_last_error(TRUE);
+ msg("InnoDB: Error: cannot close \'%s\'", srv_data_home);
+ free(bitmap_files->files);
+ return FALSE;
+ }
+
+#ifdef UNIV_DEBUG
+ ut_ad(bitmap_files->files[0].seq_num == first_file_seq_num);
+
+ for (size_t i = 1; i < bitmap_files->count; i++) {
+ if (!bitmap_files->files[i].seq_num) {
+
+ break;
+ }
+ ut_ad(bitmap_files->files[i].seq_num
+ > bitmap_files->files[i - 1].seq_num);
+ ut_ad(bitmap_files->files[i].start_lsn
+ >= bitmap_files->files[i - 1].start_lsn);
+ }
+#endif
+
+ return TRUE;
+}
+
+/****************************************************************//**
+Open a bitmap file for reading.
+
+@return whether opened successfully */
+static
+bool
+log_online_open_bitmap_file_read_only(
+/*==================================*/
+ const char* name, /*!<in: bitmap file
+ name without directory,
+ which is assumed to be
+ srv_data_home */
+ log_online_bitmap_file_t* bitmap_file) /*!<out: opened bitmap
+ file */
+{
+ bool success = false;
+
+ xb_ad(name[0] != '\0');
+
+ snprintf(bitmap_file->name, FN_REFLEN, "%s%s", srv_data_home, name);
+ bitmap_file->file = os_file_create_simple_no_error_handling(
+ 0, bitmap_file->name,
+ OS_FILE_OPEN, OS_FILE_READ_ONLY, true, &success);
+ if (UNIV_UNLIKELY(!success)) {
+
+ /* Here and below assume that bitmap file names do not
+ contain apostrophes, thus no need for ut_print_filename(). */
+ msg("InnoDB: Warning: error opening the changed page "
+ "bitmap \'%s\'", bitmap_file->name);
+ return success;
+ }
+
+ bitmap_file->size = os_file_get_size(bitmap_file->file);
+ bitmap_file->offset = 0;
+
+#ifdef __linux__
+ posix_fadvise(bitmap_file->file, 0, 0, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(bitmap_file->file, 0, 0, POSIX_FADV_NOREUSE);
+#endif
+
+ return success;
+}
+
+/****************************************************************//**
+Diagnose one or both of the following situations if we read close to
+the end of bitmap file:
+1) Warn if the remainder of the file is less than one page.
+2) Error if we cannot read any more full pages but the last read page
+did not have the last-in-run flag set.
+
+@return FALSE for the error */
+static
+ibool
+log_online_diagnose_bitmap_eof(
+/*===========================*/
+ const log_online_bitmap_file_t* bitmap_file, /*!< in: bitmap file */
+ ibool last_page_in_run)/*!< in: "last page in
+ run" flag value in the
+ last read page */
+{
+ /* Check if we are too close to EOF to read a full page */
+ if ((bitmap_file->size < MODIFIED_PAGE_BLOCK_SIZE)
+ || (bitmap_file->offset
+ > bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE)) {
+
+ if (UNIV_UNLIKELY(bitmap_file->offset != bitmap_file->size)) {
+
+ /* If we are not at EOF and we have less than one page
+ to read, it's junk. This error is not fatal in
+ itself. */
+
+ msg("InnoDB: Warning: junk at the end of changed "
+ "page bitmap file \'%s\'.", bitmap_file->name);
+ }
+
+ if (UNIV_UNLIKELY(!last_page_in_run)) {
+
+ /* We are at EOF but the last read page did not finish
+ a run */
+ /* It's a "Warning" here because it's not a fatal error
+ for the whole server */
+ msg("InnoDB: Warning: changed page bitmap "
+ "file \'%s\' does not contain a complete run "
+ "at the end.", bitmap_file->name);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* End of copy-pasted definitions */
+
+/** Iterator structure over changed page bitmap */
+struct xb_page_bitmap_range_struct {
+ const xb_page_bitmap *bitmap; /* Bitmap with data */
+ ulint space_id; /* Space id for this
+ iterator */
+ ulint bit_i; /* Bit index of the iterator
+ position in the current page */
+ const ib_rbt_node_t *bitmap_node; /* Current bitmap tree node */
+ const byte *bitmap_page; /* Current bitmap page */
+ ulint current_page_id;/* Current page id */
+};
+
+/****************************************************************//**
+Print a diagnostic message on missing bitmap data for an LSN range. */
+static
+void
+xb_msg_missing_lsn_data(
+/*====================*/
+ lsn_t missing_interval_start, /*!<in: interval start */
+ lsn_t missing_interval_end) /*!<in: interval end */
+{
+ msg("mariabackup: warning: changed page data missing for LSNs between "
+ LSN_PF " and " LSN_PF, missing_interval_start,
+ missing_interval_end);
+}
+
+/****************************************************************//**
+Scan a bitmap file until data for a desired LSN or EOF is found and check that
+the page before the starting one is not corrupted to ensure that the found page
+indeed contains the very start of the desired LSN data. The caller must check
+the page LSN values to determine if the bitmap file was scanned until the data
+was found or until EOF. Page must be at least MODIFIED_PAGE_BLOCK_SIZE big.
+
+@return TRUE if the scan successful without corruption detected
+*/
+static
+ibool
+xb_find_lsn_in_bitmap_file(
+/*=======================*/
+ log_online_bitmap_file_t *bitmap_file, /*!<in/out: bitmap
+ file */
+ byte *page, /*!<in/out: last read
+ bitmap page */
+ lsn_t *page_end_lsn, /*!<out: end LSN of the
+ last read page */
+ lsn_t lsn) /*!<in: LSN to find */
+{
+ ibool last_page_ok = TRUE;
+ ibool next_to_last_page_ok = TRUE;
+
+ xb_ad (bitmap_file->size >= MODIFIED_PAGE_BLOCK_SIZE);
+
+ *page_end_lsn = 0;
+
+ while ((*page_end_lsn <= lsn)
+ && (bitmap_file->offset
+ <= bitmap_file->size - MODIFIED_PAGE_BLOCK_SIZE)) {
+
+ next_to_last_page_ok = last_page_ok;
+ if (!log_online_read_bitmap_page(bitmap_file, page,
+ &last_page_ok)) {
+
+ return FALSE;
+ }
+
+ *page_end_lsn = mach_read_from_8(page + MODIFIED_PAGE_END_LSN);
+ }
+
+ /* We check two pages here because the last read page already contains
+ the required LSN data. If the next to the last one page is corrupted,
+ then we have no way of telling if that page contained the required LSN
+ range data too */
+ return last_page_ok && next_to_last_page_ok;
+}
+
+/****************************************************************//**
+Read the disk bitmap and build the changed page bitmap tree for the
+LSN interval incremental_lsn to log_sys.next_checkpoint_lsn.
+
+@return the built bitmap tree or NULL if unable to read the full interval for
+any reason. */
+xb_page_bitmap*
+xb_page_bitmap_init(void)
+/*=====================*/
+{
+ log_online_bitmap_file_t bitmap_file;
+ lsn_t bmp_start_lsn = incremental_lsn;
+ const lsn_t bmp_end_lsn{log_sys.next_checkpoint_lsn};
+ byte page[MODIFIED_PAGE_BLOCK_SIZE];
+ lsn_t current_page_end_lsn;
+ xb_page_bitmap *result;
+ ibool last_page_in_run= FALSE;
+ log_online_bitmap_file_range_t bitmap_files;
+ size_t bmp_i;
+ ibool last_page_ok = TRUE;
+
+ if (UNIV_UNLIKELY(bmp_start_lsn > bmp_end_lsn)) {
+
+ msg("mariabackup: incremental backup LSN " LSN_PF
+ " is larger than than the last checkpoint LSN " LSN_PF
+ , bmp_start_lsn, bmp_end_lsn);
+ return NULL;
+ }
+
+ if (!log_online_setup_bitmap_file_range(&bitmap_files, bmp_start_lsn,
+ bmp_end_lsn)) {
+
+ return NULL;
+ }
+
+ /* Only accept no bitmap files returned if start LSN == end LSN */
+ if (bitmap_files.count == 0 && bmp_end_lsn != bmp_start_lsn) {
+
+ return NULL;
+ }
+
+ result = rbt_create(MODIFIED_PAGE_BLOCK_SIZE,
+ log_online_compare_bmp_keys);
+
+ if (bmp_start_lsn == bmp_end_lsn) {
+
+ /* Empty range - empty bitmap */
+ return result;
+ }
+
+ bmp_i = 0;
+
+ if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].start_lsn
+ > bmp_start_lsn)) {
+
+ /* The 1st file does not have the starting LSN data */
+ xb_msg_missing_lsn_data(bmp_start_lsn,
+ bitmap_files.files[bmp_i].start_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ /* Skip any zero-sized files at the start */
+ while ((bmp_i < bitmap_files.count - 1)
+ && (bitmap_files.files[bmp_i].start_lsn
+ == bitmap_files.files[bmp_i + 1].start_lsn)) {
+
+ bmp_i++;
+ }
+
+ /* Is the 1st bitmap file missing? */
+ if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].name[0] == '\0')) {
+
+ /* TODO: this is not the exact missing range */
+ xb_msg_missing_lsn_data(bmp_start_lsn, bmp_end_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ /* Open the 1st bitmap file */
+ if (UNIV_UNLIKELY(!log_online_open_bitmap_file_read_only(
+ bitmap_files.files[bmp_i].name,
+ &bitmap_file))) {
+
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ /* If the 1st file is truncated, no data. Not merged with the case
+ below because zero-length file indicates not a corruption but missing
+ subsequent files instead. */
+ if (UNIV_UNLIKELY(bitmap_file.size < MODIFIED_PAGE_BLOCK_SIZE)) {
+
+ xb_msg_missing_lsn_data(bmp_start_lsn, bmp_end_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ /* Find the start of the required LSN range in the file */
+ if (UNIV_UNLIKELY(!xb_find_lsn_in_bitmap_file(&bitmap_file, page,
+ &current_page_end_lsn,
+ bmp_start_lsn))) {
+
+ msg("mariabackup: Warning: changed page bitmap file "
+ "\'%s\' corrupted", bitmap_file.name);
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ last_page_in_run
+ = mach_read_from_4(page + MODIFIED_PAGE_IS_LAST_BLOCK);
+
+ if (UNIV_UNLIKELY(!log_online_diagnose_bitmap_eof(&bitmap_file,
+ last_page_in_run))) {
+
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ if (UNIV_UNLIKELY(current_page_end_lsn < bmp_start_lsn)) {
+
+ xb_msg_missing_lsn_data(current_page_end_lsn, bmp_start_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ /* 1st bitmap page found, add it to the tree. */
+ rbt_insert(result, page, page);
+
+ /* Read next pages/files until all required data is read */
+ while (last_page_ok
+ && (current_page_end_lsn < bmp_end_lsn
+ || (current_page_end_lsn == bmp_end_lsn
+ && !last_page_in_run))) {
+
+ ib_rbt_bound_t tree_search_pos;
+
+ /* If EOF, advance the file skipping over any empty files */
+ while (bitmap_file.size < MODIFIED_PAGE_BLOCK_SIZE
+ || (bitmap_file.offset
+ > bitmap_file.size - MODIFIED_PAGE_BLOCK_SIZE)) {
+
+ os_file_close(bitmap_file.file);
+
+ if (UNIV_UNLIKELY(
+ !log_online_diagnose_bitmap_eof(
+ &bitmap_file, last_page_in_run))) {
+
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ bmp_i++;
+
+ if (UNIV_UNLIKELY(bmp_i == bitmap_files.count
+ || (bitmap_files.files[bmp_i].seq_num
+ == 0))) {
+
+ xb_msg_missing_lsn_data(current_page_end_lsn,
+ bmp_end_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ /* Is the next file missing? */
+ if (UNIV_UNLIKELY(bitmap_files.files[bmp_i].name[0]
+ == '\0')) {
+
+ /* TODO: this is not the exact missing range */
+ xb_msg_missing_lsn_data(bitmap_files.files
+ [bmp_i - 1].start_lsn,
+ bmp_end_lsn);
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+
+ if (UNIV_UNLIKELY(
+ !log_online_open_bitmap_file_read_only(
+ bitmap_files.files[bmp_i].name,
+ &bitmap_file))) {
+
+ rbt_free(result);
+ free(bitmap_files.files);
+ return NULL;
+ }
+ }
+
+ if (UNIV_UNLIKELY(
+ !log_online_read_bitmap_page(&bitmap_file, page,
+ &last_page_ok))) {
+
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ if (UNIV_UNLIKELY(!last_page_ok)) {
+
+ msg("mariabackup: warning: changed page bitmap file "
+ "\'%s\' corrupted.", bitmap_file.name);
+ rbt_free(result);
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+ return NULL;
+ }
+
+ /* Merge the current page with an existing page or insert a new
+ page into the tree */
+
+ if (!rbt_search(result, &tree_search_pos, page)) {
+
+ /* Merge the bitmap pages */
+ byte *existing_page
+ = rbt_value(byte, tree_search_pos.last);
+ bitmap_word_t *bmp_word_1 = (bitmap_word_t *)
+ (existing_page + MODIFIED_PAGE_BLOCK_BITMAP);
+ bitmap_word_t *bmp_end = (bitmap_word_t *)
+ (existing_page + MODIFIED_PAGE_BLOCK_UNUSED_2);
+ bitmap_word_t *bmp_word_2 = (bitmap_word_t *)
+ (page + MODIFIED_PAGE_BLOCK_BITMAP);
+ while (bmp_word_1 < bmp_end) {
+
+ *bmp_word_1++ |= *bmp_word_2++;
+ }
+ xb_a (bmp_word_1 == bmp_end);
+ } else {
+
+ /* Add a new page */
+ rbt_add_node(result, &tree_search_pos, page);
+ }
+
+ current_page_end_lsn
+ = mach_read_from_8(page + MODIFIED_PAGE_END_LSN);
+ last_page_in_run
+ = mach_read_from_4(page + MODIFIED_PAGE_IS_LAST_BLOCK);
+ }
+
+ xb_a (current_page_end_lsn >= bmp_end_lsn);
+
+ free(bitmap_files.files);
+ os_file_close(bitmap_file.file);
+
+ return result;
+}
+
+/****************************************************************//**
+Free the bitmap tree. */
+void
+xb_page_bitmap_deinit(
+/*==================*/
+ xb_page_bitmap* bitmap) /*!<in/out: bitmap tree */
+{
+ if (bitmap) {
+
+ rbt_free(bitmap);
+ }
+}
+
+/****************************************************************//**
+Advance to the next bitmap page or setup the first bitmap page for the
+given bitmap range. Assumes that bitmap_range->bitmap_page has been
+already found/bumped by rbt_search()/rbt_next().
+
+@return FALSE if no more bitmap data for the range space ID */
+static
+ibool
+xb_page_bitmap_setup_next_page(
+/*===========================*/
+ xb_page_bitmap_range* bitmap_range) /*!<in/out: the bitmap range */
+{
+ ulint new_space_id;
+ ulint new_1st_page_id;
+
+ if (bitmap_range->bitmap_node == NULL) {
+
+ bitmap_range->current_page_id = ULINT_UNDEFINED;
+ return FALSE;
+ }
+
+ bitmap_range->bitmap_page = rbt_value(byte, bitmap_range->bitmap_node);
+
+ new_space_id = mach_read_from_4(bitmap_range->bitmap_page
+ + MODIFIED_PAGE_SPACE_ID);
+ if (new_space_id != bitmap_range->space_id) {
+
+ /* No more data for the current page id. */
+ xb_a(new_space_id > bitmap_range->space_id);
+ bitmap_range->current_page_id = ULINT_UNDEFINED;
+ return FALSE;
+ }
+
+ new_1st_page_id = mach_read_from_4(bitmap_range->bitmap_page +
+ MODIFIED_PAGE_1ST_PAGE_ID);
+ xb_a (new_1st_page_id >= bitmap_range->current_page_id
+ || bitmap_range->current_page_id == ULINT_UNDEFINED);
+
+ bitmap_range->current_page_id = new_1st_page_id;
+ bitmap_range->bit_i = 0;
+
+ return TRUE;
+}
+
+/** Find the node with the smallest key that greater than equal to search key.
+@param[in] tree red-black tree
+@param[in] key search key
+@return node with the smallest greater-than-or-equal key
+@retval NULL if none was found */
+static
+const ib_rbt_node_t*
+rbt_lower_bound(const ib_rbt_t* tree, const void* key)
+{
+ ut_ad(!tree->cmp_arg);
+ const ib_rbt_node_t* ge = NULL;
+
+ for (const ib_rbt_node_t *node = tree->root->left;
+ node != tree->nil; ) {
+ int result = tree->compare(node->value, key);
+
+ if (result < 0) {
+ node = node->right;
+ } else {
+ ge = node;
+ if (result == 0) {
+ break;
+ }
+
+ node = node->left;
+ }
+ }
+
+ return(ge);
+}
+
+/****************************************************************//**
+Set up a new bitmap range iterator over a given space id changed
+pages in a given bitmap.
+
+@return bitmap range iterator */
+xb_page_bitmap_range*
+xb_page_bitmap_range_init(
+/*======================*/
+ xb_page_bitmap* bitmap, /*!< in: bitmap to iterate over */
+ ulint space_id) /*!< in: space id */
+{
+ byte search_page[MODIFIED_PAGE_BLOCK_SIZE];
+ xb_page_bitmap_range *result
+ = static_cast<xb_page_bitmap_range *>(malloc(sizeof(*result)));
+
+ memset(result, 0, sizeof(*result));
+ result->bitmap = bitmap;
+ result->space_id = space_id;
+ result->current_page_id = ULINT_UNDEFINED;
+
+ /* Search for the 1st page for the given space id */
+ /* This also sets MODIFIED_PAGE_1ST_PAGE_ID to 0, which is what we
+ want. */
+ memset(search_page, 0, MODIFIED_PAGE_BLOCK_SIZE);
+ mach_write_to_4(search_page + MODIFIED_PAGE_SPACE_ID, space_id);
+
+ result->bitmap_node = rbt_lower_bound(result->bitmap, search_page);
+
+ xb_page_bitmap_setup_next_page(result);
+
+ return result;
+}
+
+/****************************************************************//**
+Get the value of the bitmap->range->bit_i bitmap bit
+
+@return the current bit value */
+static inline
+ibool
+is_bit_set(
+/*=======*/
+ const xb_page_bitmap_range* bitmap_range) /*!< in: bitmap
+ range */
+{
+ return ((*(((bitmap_word_t *)(bitmap_range->bitmap_page
+ + MODIFIED_PAGE_BLOCK_BITMAP))
+ + (bitmap_range->bit_i >> 6)))
+ & (1ULL << (bitmap_range->bit_i & 0x3F))) ? TRUE : FALSE;
+}
+
+/****************************************************************//**
+Get the next page id that has its bit set or cleared, i.e. equal to
+bit_value.
+
+@return page id */
+ulint
+xb_page_bitmap_range_get_next_bit(
+/*==============================*/
+ xb_page_bitmap_range* bitmap_range, /*!< in/out: bitmap range */
+ ibool bit_value) /*!< in: bit value */
+{
+ if (UNIV_UNLIKELY(bitmap_range->current_page_id
+ == ULINT_UNDEFINED)) {
+
+ return ULINT_UNDEFINED;
+ }
+
+ do {
+ while (bitmap_range->bit_i < MODIFIED_PAGE_BLOCK_ID_COUNT) {
+
+ while (is_bit_set(bitmap_range) != bit_value
+ && (bitmap_range->bit_i
+ < MODIFIED_PAGE_BLOCK_ID_COUNT)) {
+
+ bitmap_range->current_page_id++;
+ bitmap_range->bit_i++;
+ }
+
+ if (bitmap_range->bit_i
+ < MODIFIED_PAGE_BLOCK_ID_COUNT) {
+
+ ulint result = bitmap_range->current_page_id;
+ bitmap_range->current_page_id++;
+ bitmap_range->bit_i++;
+ return result;
+ }
+ }
+
+ bitmap_range->bitmap_node
+ = rbt_next(bitmap_range->bitmap,
+ bitmap_range->bitmap_node);
+
+ } while (xb_page_bitmap_setup_next_page(bitmap_range));
+
+ return ULINT_UNDEFINED;
+}
+
+/****************************************************************//**
+Free the bitmap range iterator. */
+void
+xb_page_bitmap_range_deinit(
+/*========================*/
+ xb_page_bitmap_range* bitmap_range) /*! in/out: bitmap range */
+{
+ free(bitmap_range);
+}
diff --git a/extra/mariabackup/changed_page_bitmap.h b/extra/mariabackup/changed_page_bitmap.h
new file mode 100644
index 00000000..8d504359
--- /dev/null
+++ b/extra/mariabackup/changed_page_bitmap.h
@@ -0,0 +1,85 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2012 Percona Inc.
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+
+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
+
+*******************************************************/
+
+/* Changed page bitmap interface */
+
+#ifndef XB_CHANGED_PAGE_BITMAP_H
+#define XB_CHANGED_PAGE_BITMAP_H
+
+#include <ut0rbt.h>
+#include <fil0fil.h>
+
+/* The changed page bitmap structure */
+typedef ib_rbt_t xb_page_bitmap;
+
+struct xb_page_bitmap_range_struct;
+
+/* The bitmap range iterator over one space id */
+typedef struct xb_page_bitmap_range_struct xb_page_bitmap_range;
+
+/****************************************************************//**
+Read the disk bitmap and build the changed page bitmap tree for the
+LSN interval incremental_lsn to log_sys.next_checkpoint_lsn.
+
+@return the built bitmap tree */
+xb_page_bitmap*
+xb_page_bitmap_init(void);
+/*=====================*/
+
+/****************************************************************//**
+Free the bitmap tree. */
+void
+xb_page_bitmap_deinit(
+/*==================*/
+ xb_page_bitmap* bitmap); /*!<in/out: bitmap tree */
+
+
+/****************************************************************//**
+Set up a new bitmap range iterator over a given space id changed
+pages in a given bitmap.
+
+@return bitmap range iterator */
+xb_page_bitmap_range*
+xb_page_bitmap_range_init(
+/*======================*/
+ xb_page_bitmap* bitmap, /*!< in: bitmap to iterate over */
+ ulint space_id); /*!< in: space id */
+
+/****************************************************************//**
+Get the next page id that has its bit set or cleared, i.e. equal to
+bit_value.
+
+@return page id */
+ulint
+xb_page_bitmap_range_get_next_bit(
+/*==============================*/
+ xb_page_bitmap_range* bitmap_range, /*!< in/out: bitmap range */
+ ibool bit_value); /*!< in: bit value */
+
+/****************************************************************//**
+Free the bitmap range iterator. */
+void
+xb_page_bitmap_range_deinit(
+/*========================*/
+ xb_page_bitmap_range* bitmap_range); /*! in/out: bitmap range */
+
+#endif
diff --git a/extra/mariabackup/common.h b/extra/mariabackup/common.h
new file mode 100644
index 00000000..89b189e3
--- /dev/null
+++ b/extra/mariabackup/common.h
@@ -0,0 +1,189 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Common declarations for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef XB_COMMON_H
+#define XB_COMMON_H
+
+#include <my_global.h>
+#include <mysql_version.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <my_sys.h>
+
+
+/** Determine if (i) is a user tablespace id or not. */
+# define fil_is_user_tablespace_id(i) (i != 0 \
+ && !srv_is_undo_tablespace(i))
+
+#ifdef _MSC_VER
+#define stat _stati64
+#define PATH_MAX MAX_PATH
+#endif
+
+#ifndef HAVE_VASPRINTF
+static inline int vasprintf(char **strp, const char *fmt, va_list args)
+{
+ int len;
+#ifdef _MSC_VER
+ len = _vscprintf(fmt, args);
+#else
+ len = vsnprintf(NULL, 0, fmt, args);
+#endif
+ if (len < 0)
+ {
+ return -1;
+ }
+ *strp = (char *)malloc(len + 1);
+ if (!*strp)
+ {
+ return -1;
+ }
+ vsprintf(*strp, fmt, args);
+ return len;
+}
+
+static inline int asprintf(char **strp, const char *fmt,...)
+{
+ va_list args;
+ va_start(args, fmt);
+ int len = vasprintf(strp, fmt, args);
+ va_end(args);
+ return len;
+}
+#endif
+
+#define xb_a(expr) \
+ do { \
+ if (!(expr)) { \
+ fprintf(stderr,"Assertion \"%s\" failed at %s:%lu\n", \
+ #expr, __FILE__, (ulong) __LINE__); \
+ abort(); \
+ } \
+ } while (0);
+
+#ifdef XB_DEBUG
+#define xb_ad(expr) xb_a(expr)
+#else
+#define xb_ad(expr)
+#endif
+
+#define XB_DELTA_INFO_SUFFIX ".meta"
+
+static inline int msg1(uint thread_num, const char *prefix, const char *fmt, va_list args)
+{
+ int result;
+ time_t t = time(NULL);
+ char date[100];
+ char *line;
+ strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&t));
+ result = vasprintf(&line, fmt, args);
+ if (result != -1) {
+ if (fmt && fmt[strlen(fmt)] != '\n')
+ result = fprintf(stderr, "[%02u] %s%s %s\n", thread_num, prefix, date, line);
+ else
+ result = fprintf(stderr, "[%02u] %s%s %s", thread_num, prefix, date, line);
+ free(line);
+ }
+ return result;
+}
+
+static inline ATTRIBUTE_FORMAT(printf, 2, 3) int msg(unsigned int thread_num, const char *fmt, ...)
+{
+ int result;
+ va_list args;
+ va_start(args, fmt);
+ result = msg1(thread_num,"", fmt, args);
+ va_end(args);
+ return result;
+}
+
+static inline ATTRIBUTE_FORMAT(printf, 1, 2) int msg(const char *fmt, ...)
+{
+ int result;
+ va_list args;
+ va_start(args, fmt);
+ result = msg1(0, "", fmt, args);
+ va_end(args);
+ return result;
+}
+
+static inline ATTRIBUTE_FORMAT(printf, 1,2) ATTRIBUTE_NORETURN void die(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ msg1(0, "FATAL ERROR: ", fmt, args);
+ va_end(args);
+ fflush(stderr);
+ _exit(EXIT_FAILURE);
+}
+
+
+/* Use POSIX_FADV_NORMAL when available */
+
+#ifdef POSIX_FADV_NORMAL
+# define USE_POSIX_FADVISE
+#else
+# define POSIX_FADV_NORMAL
+# define POSIX_FADV_SEQUENTIAL
+# define POSIX_FADV_DONTNEED
+# define posix_fadvise(a,b,c,d) do {} while(0)
+#endif
+
+/***********************************************************************
+Computes bit shift for a given value. If the argument is not a power
+of 2, returns 0.*/
+static inline unsigned get_bit_shift(size_t value)
+{
+ unsigned shift;
+
+ if (value == 0)
+ return 0;
+
+ for (shift = 0; !(value & 1); shift++) {
+ value >>= 1;
+ }
+ return (value >> 1) ? 0 : shift;
+}
+
+/****************************************************************************
+Read 'len' bytes from 'fd'. It is identical to my_read(..., MYF(MY_FULL_IO)),
+i.e. tries to combine partial reads into a single block of size 'len', except
+that it bails out on EOF or error, and returns the number of successfully read
+bytes instead. */
+static inline size_t
+xb_read_full(File fd, uchar *buf, size_t len)
+{
+ size_t tlen = 0;
+ size_t tbytes;
+
+ while (tlen < len) {
+ tbytes = my_read(fd, buf, len - tlen, MYF(MY_WME));
+ if (tbytes == 0 || tbytes == MY_FILE_ERROR) {
+ break;
+ }
+
+ buf += tbytes;
+ tlen += tbytes;
+ }
+
+ return tlen;
+}
+
+#endif
diff --git a/extra/mariabackup/datasink.cc b/extra/mariabackup/datasink.cc
new file mode 100644
index 00000000..a576526d
--- /dev/null
+++ b/extra/mariabackup/datasink.cc
@@ -0,0 +1,130 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Data sink interface.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include "common.h"
+#include "datasink.h"
+#include "ds_compress.h"
+#include "ds_xbstream.h"
+#include "ds_local.h"
+#include "ds_stdout.h"
+#include "ds_tmpfile.h"
+#include "ds_buffer.h"
+
+/************************************************************************
+Create a datasink of the specified type */
+ds_ctxt_t *
+ds_create(const char *root, ds_type_t type)
+{
+ datasink_t *ds;
+ ds_ctxt_t *ctxt;
+
+ switch (type) {
+ case DS_TYPE_STDOUT:
+ ds = &datasink_stdout;
+ break;
+ case DS_TYPE_LOCAL:
+ ds = &datasink_local;
+ break;
+ case DS_TYPE_XBSTREAM:
+ ds = &datasink_xbstream;
+ break;
+ case DS_TYPE_COMPRESS:
+ ds = &datasink_compress;
+ break;
+ case DS_TYPE_ENCRYPT:
+ case DS_TYPE_DECRYPT:
+ die("mariabackup does not support encrypted backups.");
+ break;
+
+ case DS_TYPE_TMPFILE:
+ ds = &datasink_tmpfile;
+ break;
+ case DS_TYPE_BUFFER:
+ ds = &datasink_buffer;
+ break;
+ default:
+ msg("Unknown datasink type: %d", type);
+ xb_ad(0);
+ return NULL;
+ }
+
+ ctxt = ds->init(root);
+ if (ctxt != NULL) {
+ ctxt->datasink = ds;
+ } else {
+ die("failed to initialize datasink.");
+ }
+
+ return ctxt;
+}
+
+/************************************************************************
+Open a datasink file */
+ds_file_t *
+ds_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat)
+{
+ ds_file_t *file;
+
+ file = ctxt->datasink->open(ctxt, path, stat);
+ if (file != NULL) {
+ file->datasink = ctxt->datasink;
+ }
+
+ return file;
+}
+
+/************************************************************************
+Write to a datasink file.
+@return 0 on success, 1 on error. */
+int
+ds_write(ds_file_t *file, const void *buf, size_t len)
+{
+ if (len == 0) {
+ return 0;
+ }
+ return file->datasink->write(file, (const uchar *)buf, len);
+}
+
+/************************************************************************
+Close a datasink file.
+@return 0 on success, 1, on error. */
+int
+ds_close(ds_file_t *file)
+{
+ return file->datasink->close(file);
+}
+
+/************************************************************************
+Destroy a datasink handle */
+void
+ds_destroy(ds_ctxt_t *ctxt)
+{
+ ctxt->datasink->deinit(ctxt);
+}
+
+/************************************************************************
+Set the destination pipe for a datasink (only makes sense for compress and
+tmpfile). */
+void ds_set_pipe(ds_ctxt_t *ctxt, ds_ctxt_t *pipe_ctxt)
+{
+ ctxt->pipe_ctxt = pipe_ctxt;
+}
diff --git a/extra/mariabackup/datasink.h b/extra/mariabackup/datasink.h
new file mode 100644
index 00000000..57468e0c
--- /dev/null
+++ b/extra/mariabackup/datasink.h
@@ -0,0 +1,134 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Data sink interface.
+
+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
+
+*******************************************************/
+
+#ifndef XB_DATASINK_H
+#define XB_DATASINK_H
+
+#include <my_global.h>
+#include <my_dir.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern char *xtrabackup_tmpdir;
+struct datasink_struct;
+typedef struct datasink_struct datasink_t;
+
+typedef struct ds_ctxt {
+ datasink_t *datasink;
+ char *root;
+ void *ptr;
+ struct ds_ctxt *pipe_ctxt;
+ /*
+ Copy file for backup/restore.
+ @return true in case of success.
+ */
+ bool copy_file(const char *src_file_path,
+ const char *dst_file_path,
+ uint thread_n);
+
+ bool move_file(const char *src_file_path,
+ const char *dst_file_path,
+ const char *dst_dir,
+ uint thread_n);
+
+ bool make_hardlink(const char *from_path, const char *to_path);
+
+ void copy_or_move_dir(const char *from, const char *to,
+ bool do_copy, bool allow_hardlinks);
+
+ bool backup_file_vprintf(const char *filename,
+ const char *fmt, va_list ap);
+
+ bool backup_file_print_buf(const char *filename,
+ const char *buf,
+ int buf_len);
+
+ bool backup_file_printf(const char *filename,
+ const char *fmt, ...)
+ ATTRIBUTE_FORMAT(printf, 2, 0);
+
+} ds_ctxt_t;
+
+typedef struct {
+ void *ptr;
+ char *path;
+ datasink_t *datasink;
+} ds_file_t;
+
+struct datasink_struct {
+ ds_ctxt_t *(*init)(const char *root);
+ ds_file_t *(*open)(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat);
+ int (*write)(ds_file_t *file, const unsigned char *buf, size_t len);
+ int (*close)(ds_file_t *file);
+ int (*remove)(const char *path);
+ void (*deinit)(ds_ctxt_t *ctxt);
+};
+
+
+static inline int dummy_remove(const char *) {
+ return 0;
+}
+
+/* Supported datasink types */
+typedef enum {
+ DS_TYPE_STDOUT,
+ DS_TYPE_LOCAL,
+ DS_TYPE_XBSTREAM,
+ DS_TYPE_COMPRESS,
+ DS_TYPE_ENCRYPT,
+ DS_TYPE_DECRYPT,
+ DS_TYPE_TMPFILE,
+ DS_TYPE_BUFFER
+} ds_type_t;
+
+/************************************************************************
+Create a datasink of the specified type */
+ds_ctxt_t *ds_create(const char *root, ds_type_t type);
+
+/************************************************************************
+Open a datasink file */
+ds_file_t *ds_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *stat);
+
+/************************************************************************
+Write to a datasink file.
+@return 0 on success, 1 on error. */
+int ds_write(ds_file_t *file, const void *buf, size_t len);
+
+/************************************************************************
+Close a datasink file.
+@return 0 on success, 1, on error. */
+int ds_close(ds_file_t *file);
+
+/************************************************************************
+Destroy a datasink handle */
+void ds_destroy(ds_ctxt_t *ctxt);
+
+/************************************************************************
+Set the destination pipe for a datasink (only makes sense for compress and
+tmpfile). */
+void ds_set_pipe(ds_ctxt_t *ctxt, ds_ctxt_t *pipe_ctxt);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* XB_DATASINK_H */
diff --git a/extra/mariabackup/ds_buffer.cc b/extra/mariabackup/ds_buffer.cc
new file mode 100644
index 00000000..d6a42095
--- /dev/null
+++ b/extra/mariabackup/ds_buffer.cc
@@ -0,0 +1,189 @@
+/******************************************************
+Copyright (c) 2012-2013 Percona LLC and/or its affiliates.
+
+buffer datasink for XtraBackup.
+
+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
+
+*******************************************************/
+
+/* Does buffered output to a destination datasink set with ds_set_pipe().
+Writes to the destination datasink are guaranteed to not be smaller than a
+specified buffer size (DS_DEFAULT_BUFFER_SIZE by default), with the only
+exception for the last write for a file. */
+
+#include <my_global.h>
+#include <my_base.h>
+#include "ds_buffer.h"
+#include "common.h"
+#include "datasink.h"
+
+#define DS_DEFAULT_BUFFER_SIZE (64 * 1024)
+
+typedef struct {
+ ds_file_t *dst_file;
+ char *buf;
+ size_t pos;
+ size_t size;
+} ds_buffer_file_t;
+
+typedef struct {
+ size_t buffer_size;
+} ds_buffer_ctxt_t;
+
+static ds_ctxt_t *buffer_init(const char *root);
+static ds_file_t *buffer_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int buffer_write(ds_file_t *file, const uchar *buf, size_t len);
+static int buffer_close(ds_file_t *file);
+static void buffer_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_buffer = {
+ &buffer_init,
+ &buffer_open,
+ &buffer_write,
+ &buffer_close,
+ &dummy_remove,
+ &buffer_deinit
+};
+
+/* Change the default buffer size */
+void ds_buffer_set_size(ds_ctxt_t *ctxt, size_t size)
+{
+ ds_buffer_ctxt_t *buffer_ctxt = (ds_buffer_ctxt_t *) ctxt->ptr;
+
+ buffer_ctxt->buffer_size = size;
+}
+
+static ds_ctxt_t *
+buffer_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+ ds_buffer_ctxt_t *buffer_ctxt;
+
+ ctxt = (ds_ctxt_t *)my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_ctxt_t) + sizeof(ds_buffer_ctxt_t), MYF(MY_FAE));
+ buffer_ctxt = (ds_buffer_ctxt_t *) (ctxt + 1);
+ buffer_ctxt->buffer_size = DS_DEFAULT_BUFFER_SIZE;
+
+ ctxt->ptr = buffer_ctxt;
+ ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static ds_file_t *
+buffer_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat)
+{
+ ds_buffer_ctxt_t *buffer_ctxt;
+ ds_ctxt_t *pipe_ctxt;
+ ds_file_t *dst_file;
+ ds_file_t *file;
+ ds_buffer_file_t *buffer_file;
+
+ pipe_ctxt = ctxt->pipe_ctxt;
+ xb_a(pipe_ctxt != NULL);
+
+ dst_file = ds_open(pipe_ctxt, path, mystat);
+ if (dst_file == NULL) {
+ die("ds_open(%s) failed", path);
+ }
+
+ buffer_ctxt = (ds_buffer_ctxt_t *) ctxt->ptr;
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_file_t) +
+ sizeof(ds_buffer_file_t) + buffer_ctxt->buffer_size,
+ MYF(MY_FAE));
+
+ buffer_file = (ds_buffer_file_t *) (file + 1);
+ buffer_file->dst_file = dst_file;
+ buffer_file->buf = (char *) (buffer_file + 1);
+ buffer_file->size = buffer_ctxt->buffer_size;
+ buffer_file->pos = 0;
+
+ file->path = dst_file->path;
+ file->ptr = buffer_file;
+
+ return file;
+}
+
+static int
+buffer_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ ds_buffer_file_t *buffer_file;
+
+ buffer_file = (ds_buffer_file_t *) file->ptr;
+
+ while (len > 0) {
+ if (buffer_file->pos + len > buffer_file->size) {
+ if (buffer_file->pos > 0) {
+ size_t bytes;
+
+ bytes = buffer_file->size - buffer_file->pos;
+ memcpy(buffer_file->buf + buffer_file->pos, buf,
+ bytes);
+
+ if (ds_write(buffer_file->dst_file,
+ buffer_file->buf,
+ buffer_file->size)) {
+ return 1;
+ }
+
+ buffer_file->pos = 0;
+
+ buf += bytes;
+ len -= bytes;
+ } else {
+ /* We don't have any buffered bytes, just write
+ the entire source buffer */
+ if (ds_write(buffer_file->dst_file, buf, len)) {
+ return 1;
+ }
+ break;
+ }
+ } else {
+ memcpy(buffer_file->buf + buffer_file->pos, buf, len);
+ buffer_file->pos += len;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int
+buffer_close(ds_file_t *file)
+{
+ ds_buffer_file_t *buffer_file;
+ int ret;
+
+ buffer_file = (ds_buffer_file_t *) file->ptr;
+ if (buffer_file->pos > 0) {
+ ds_write(buffer_file->dst_file, buffer_file->buf,
+ buffer_file->pos);
+ }
+
+ ret = ds_close(buffer_file->dst_file);
+
+ my_free(file);
+
+ return ret;
+}
+
+static void
+buffer_deinit(ds_ctxt_t *ctxt)
+{
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
diff --git a/extra/mariabackup/ds_buffer.h b/extra/mariabackup/ds_buffer.h
new file mode 100644
index 00000000..54ffd5c2
--- /dev/null
+++ b/extra/mariabackup/ds_buffer.h
@@ -0,0 +1,39 @@
+/******************************************************
+Copyright (c) 2012-2013 Percona LLC and/or its affiliates.
+
+buffer datasink for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_BUFFER_H
+#define DS_BUFFER_H
+
+#include "datasink.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern datasink_t datasink_buffer;
+
+/* Change the default buffer size */
+void ds_buffer_set_size(ds_ctxt_t *ctxt, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/extra/mariabackup/ds_compress.cc b/extra/mariabackup/ds_compress.cc
new file mode 100644
index 00000000..f7a9b7a1
--- /dev/null
+++ b/extra/mariabackup/ds_compress.cc
@@ -0,0 +1,475 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+Copyright (c) 2022, MariaDB Corporation.
+
+Compressing datasink implementation for XtraBackup.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <mysql_version.h>
+#include <my_base.h>
+#include <quicklz.h>
+#include <zlib.h>
+#include "common.h"
+#include "datasink.h"
+
+#define COMPRESS_CHUNK_SIZE ((size_t) (xtrabackup_compress_chunk_size))
+#define MY_QLZ_COMPRESS_OVERHEAD 400
+
+typedef struct {
+ pthread_t id;
+ uint num;
+ pthread_mutex_t data_mutex;
+ pthread_cond_t avail_cond;
+ pthread_cond_t data_cond;
+ pthread_cond_t done_cond;
+ pthread_t data_avail;
+ my_bool cancelled;
+ const char *from;
+ size_t from_len;
+ char *to;
+ size_t to_len;
+ qlz_state_compress state;
+ ulong adler;
+} comp_thread_ctxt_t;
+
+typedef struct {
+ comp_thread_ctxt_t *threads;
+ uint nthreads;
+} ds_compress_ctxt_t;
+
+typedef struct {
+ ds_file_t *dest_file;
+ ds_compress_ctxt_t *comp_ctxt;
+ size_t bytes_processed;
+} ds_compress_file_t;
+
+/* Compression options */
+extern char *xtrabackup_compress_alg;
+extern uint xtrabackup_compress_threads;
+extern ulonglong xtrabackup_compress_chunk_size;
+
+static ds_ctxt_t *compress_init(const char *root);
+static ds_file_t *compress_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int compress_write(ds_file_t *file, const uchar *buf, size_t len);
+static int compress_close(ds_file_t *file);
+static void compress_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_compress = {
+ &compress_init,
+ &compress_open,
+ &compress_write,
+ &compress_close,
+ &dummy_remove,
+ &compress_deinit
+};
+
+static inline int write_uint32_le(ds_file_t *file, ulong n);
+static inline int write_uint64_le(ds_file_t *file, ulonglong n);
+
+static comp_thread_ctxt_t *create_worker_threads(uint n);
+static void destroy_worker_threads(comp_thread_ctxt_t *threads, uint n);
+static void *compress_worker_thread_func(void *arg);
+
+static
+ds_ctxt_t *
+compress_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+ ds_compress_ctxt_t *compress_ctxt;
+ comp_thread_ctxt_t *threads;
+
+ /* Create and initialize the worker threads */
+ threads = create_worker_threads(xtrabackup_compress_threads);
+ if (threads == NULL) {
+ msg("compress: failed to create worker threads.");
+ return NULL;
+ }
+
+ ctxt = (ds_ctxt_t *) my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_ctxt_t) + sizeof(ds_compress_ctxt_t), MYF(MY_FAE));
+
+ compress_ctxt = (ds_compress_ctxt_t *) (ctxt + 1);
+ compress_ctxt->threads = threads;
+ compress_ctxt->nthreads = xtrabackup_compress_threads;
+
+ ctxt->ptr = compress_ctxt;
+ ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static
+ds_file_t *
+compress_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat)
+{
+ ds_compress_ctxt_t *comp_ctxt;
+ ds_ctxt_t *dest_ctxt;
+ ds_file_t *dest_file;
+ char new_name[FN_REFLEN];
+ size_t name_len;
+ ds_file_t *file;
+ ds_compress_file_t *comp_file;
+
+ xb_ad(ctxt->pipe_ctxt != NULL);
+ dest_ctxt = ctxt->pipe_ctxt;
+
+ comp_ctxt = (ds_compress_ctxt_t *) ctxt->ptr;
+
+ /* Append the .qp extension to the filename */
+ fn_format(new_name, path, "", ".qp", MYF(MY_APPEND_EXT));
+
+ dest_file = ds_open(dest_ctxt, new_name, mystat);
+ if (dest_file == NULL) {
+ return NULL;
+ }
+
+ /* Write the qpress archive header */
+ if (ds_write(dest_file, "qpress10", 8) ||
+ write_uint64_le(dest_file, COMPRESS_CHUNK_SIZE)) {
+ goto err;
+ }
+
+ /* We are going to create a one-file "flat" (i.e. with no
+ subdirectories) archive. So strip the directory part from the path and
+ remove the '.qp' suffix. */
+ fn_format(new_name, path, "", "", MYF(MY_REPLACE_DIR));
+
+ /* Write the qpress file header */
+ name_len = strlen(new_name);
+ if (ds_write(dest_file, "F", 1) ||
+ write_uint32_le(dest_file, (uint)name_len) ||
+ /* we want to write the terminating \0 as well */
+ ds_write(dest_file, new_name, name_len + 1)) {
+ goto err;
+ }
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_file_t) + sizeof(ds_compress_file_t), MYF(MY_FAE));
+ comp_file = (ds_compress_file_t *) (file + 1);
+ comp_file->dest_file = dest_file;
+ comp_file->comp_ctxt = comp_ctxt;
+ comp_file->bytes_processed = 0;
+
+ file->ptr = comp_file;
+ file->path = dest_file->path;
+
+ return file;
+
+err:
+ ds_close(dest_file);
+ return NULL;
+}
+
+static
+int
+compress_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ ds_compress_file_t *comp_file;
+ ds_compress_ctxt_t *comp_ctxt;
+ comp_thread_ctxt_t *threads;
+ comp_thread_ctxt_t *thd;
+ uint nthreads;
+ uint i;
+ const char *ptr;
+ ds_file_t *dest_file;
+
+ comp_file = (ds_compress_file_t *) file->ptr;
+ comp_ctxt = comp_file->comp_ctxt;
+ dest_file = comp_file->dest_file;
+
+ threads = comp_ctxt->threads;
+ nthreads = comp_ctxt->nthreads;
+
+ const pthread_t self = pthread_self();
+
+ ptr = (const char *) buf;
+ while (len > 0) {
+ bool wait = nthreads == 1;
+retry:
+ bool submitted = false;
+
+ /* Send data to worker threads for compression */
+ for (i = 0; i < nthreads; i++) {
+ size_t chunk_len;
+
+ thd = threads + i;
+
+ pthread_mutex_lock(&thd->data_mutex);
+ if (thd->data_avail == pthread_t(~0UL)) {
+ } else if (!wait) {
+skip:
+ pthread_mutex_unlock(&thd->data_mutex);
+ continue;
+ } else {
+ for (;;) {
+ pthread_cond_wait(&thd->avail_cond,
+ &thd->data_mutex);
+ if (thd->data_avail
+ == pthread_t(~0UL)) {
+ break;
+ }
+ goto skip;
+ }
+ }
+
+ chunk_len = (len > COMPRESS_CHUNK_SIZE) ?
+ COMPRESS_CHUNK_SIZE : len;
+ thd->from = ptr;
+ thd->from_len = chunk_len;
+
+ thd->data_avail = self;
+ pthread_cond_signal(&thd->data_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ submitted = true;
+ len -= chunk_len;
+ if (len == 0) {
+ break;
+ }
+ ptr += chunk_len;
+ }
+
+ if (!submitted) {
+ wait = true;
+ goto retry;
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ thd = threads + i;
+
+ pthread_mutex_lock(&thd->data_mutex);
+ if (thd->data_avail != self) {
+ pthread_mutex_unlock(&thd->data_mutex);
+ continue;
+ }
+
+ while (!thd->to_len) {
+ pthread_cond_wait(&thd->done_cond,
+ &thd->data_mutex);
+ }
+
+ bool fail = ds_write(dest_file, "NEWBNEWB", 8) ||
+ write_uint64_le(dest_file,
+ comp_file->bytes_processed);
+ comp_file->bytes_processed += thd->from_len;
+
+ if (!fail) {
+ fail = write_uint32_le(dest_file, thd->adler) ||
+ ds_write(dest_file, thd->to,
+ thd->to_len);
+ }
+
+ thd->to_len = 0;
+ thd->data_avail = pthread_t(~0UL);
+ pthread_cond_signal(&thd->avail_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ if (fail) {
+ msg("compress: write to the destination stream "
+ "failed.");
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static
+int
+compress_close(ds_file_t *file)
+{
+ ds_compress_file_t *comp_file;
+ ds_file_t *dest_file;
+ int rc;
+
+ comp_file = (ds_compress_file_t *) file->ptr;
+ dest_file = comp_file->dest_file;
+
+ /* Write the qpress file trailer */
+ ds_write(dest_file, "ENDSENDS", 8);
+
+ /* Supposedly the number of written bytes should be written as a
+ "recovery information" in the file trailer, but in reality qpress
+ always writes 8 zeros here. Let's do the same */
+
+ write_uint64_le(dest_file, 0);
+
+ rc = ds_close(dest_file);
+
+ my_free(file);
+
+ return rc;
+}
+
+static
+void
+compress_deinit(ds_ctxt_t *ctxt)
+{
+ ds_compress_ctxt_t *comp_ctxt;
+
+ xb_ad(ctxt->pipe_ctxt != NULL);
+
+ comp_ctxt = (ds_compress_ctxt_t *) ctxt->ptr;;
+
+ destroy_worker_threads(comp_ctxt->threads, comp_ctxt->nthreads);
+
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
+
+static inline
+int
+write_uint32_le(ds_file_t *file, ulong n)
+{
+ char tmp[4];
+
+ int4store(tmp, n);
+ return ds_write(file, tmp, sizeof(tmp));
+}
+
+static inline
+int
+write_uint64_le(ds_file_t *file, ulonglong n)
+{
+ char tmp[8];
+
+ int8store(tmp, n);
+ return ds_write(file, tmp, sizeof(tmp));
+}
+
+static
+void
+destroy_worker_thread(comp_thread_ctxt_t *thd)
+{
+ pthread_mutex_lock(&thd->data_mutex);
+ thd->cancelled = TRUE;
+ pthread_cond_signal(&thd->data_cond);
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ pthread_join(thd->id, NULL);
+
+ pthread_cond_destroy(&thd->avail_cond);
+ pthread_cond_destroy(&thd->data_cond);
+ pthread_cond_destroy(&thd->done_cond);
+ pthread_mutex_destroy(&thd->data_mutex);
+
+ my_free(thd->to);
+}
+
+static
+comp_thread_ctxt_t *
+create_worker_threads(uint n)
+{
+ comp_thread_ctxt_t *threads;
+ uint i;
+
+ threads = static_cast<comp_thread_ctxt_t*>
+ (my_malloc(PSI_NOT_INSTRUMENTED, n * sizeof *threads,
+ MYF(MY_ZEROFILL|MY_FAE)));
+
+ for (i = 0; i < n; i++) {
+ comp_thread_ctxt_t *thd = threads + i;
+
+ thd->num = i + 1;
+ thd->to = static_cast<char*>
+ (my_malloc(PSI_NOT_INSTRUMENTED,
+ COMPRESS_CHUNK_SIZE +
+ MY_QLZ_COMPRESS_OVERHEAD,
+ MYF(MY_FAE)));
+
+ /* Initialize and data mutex and condition var */
+ if (pthread_mutex_init(&thd->data_mutex, NULL) ||
+ pthread_cond_init(&thd->avail_cond, NULL) ||
+ pthread_cond_init(&thd->data_cond, NULL) ||
+ pthread_cond_init(&thd->done_cond, NULL)) {
+ goto err;
+ }
+
+ thd->data_avail = pthread_t(~0UL);
+
+ if (pthread_create(&thd->id, NULL, compress_worker_thread_func,
+ thd)) {
+ msg("compress: pthread_create() failed: "
+ "errno = %d", errno);
+ goto err;
+ }
+ }
+
+ return threads;
+
+err:
+ for (; i; i--) {
+ destroy_worker_thread(threads + i);
+ }
+
+ my_free(threads);
+ return NULL;
+}
+
+static
+void
+destroy_worker_threads(comp_thread_ctxt_t *threads, uint n)
+{
+ uint i;
+
+ for (i = 0; i < n; i++) {
+ destroy_worker_thread(threads + i);
+ }
+
+ my_free(threads);
+}
+
+static
+void *
+compress_worker_thread_func(void *arg)
+{
+ comp_thread_ctxt_t *thd = (comp_thread_ctxt_t *) arg;
+
+ pthread_mutex_lock(&thd->data_mutex);
+
+ while (1) {
+ while (!thd->cancelled
+ && (thd->to_len || thd->data_avail == pthread_t(~0UL))) {
+ pthread_cond_wait(&thd->data_cond, &thd->data_mutex);
+ }
+
+ if (thd->cancelled)
+ break;
+ thd->to_len = qlz_compress(thd->from, thd->to, thd->from_len,
+ &thd->state);
+
+ /* qpress uses 0x00010000 as the initial value, but its own
+ Adler-32 implementation treats the value differently:
+ 1. higher order bits are the sum of all bytes in the sequence
+ 2. lower order bits are the sum of resulting values at every
+ step.
+ So it's the other way around as compared to zlib's adler32().
+ That's why 0x00000001 is being passed here to be compatible
+ with qpress implementation. */
+
+ thd->adler = adler32(0x00000001, (uchar *) thd->to,
+ (uInt)thd->to_len);
+ pthread_cond_signal(&thd->done_cond);
+ }
+
+ pthread_mutex_unlock(&thd->data_mutex);
+
+ return NULL;
+}
diff --git a/extra/mariabackup/ds_compress.h b/extra/mariabackup/ds_compress.h
new file mode 100644
index 00000000..f44c1acf
--- /dev/null
+++ b/extra/mariabackup/ds_compress.h
@@ -0,0 +1,28 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Compression interface for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_COMPRESS_H
+#define DS_COMPRESS_H
+
+#include "datasink.h"
+
+extern datasink_t datasink_compress;
+
+#endif
diff --git a/extra/mariabackup/ds_local.cc b/extra/mariabackup/ds_local.cc
new file mode 100644
index 00000000..f86612b9
--- /dev/null
+++ b/extra/mariabackup/ds_local.cc
@@ -0,0 +1,278 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Local datasink implementation for XtraBackup.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <mysys_err.h>
+#include "common.h"
+#include "datasink.h"
+#include "fsp0fsp.h"
+#ifdef _WIN32
+#include <winioctl.h>
+#endif
+
+#ifdef HAVE_FALLOC_PUNCH_HOLE_AND_KEEP_SIZE
+#include <linux/falloc.h>
+#endif
+
+typedef struct {
+ File fd;
+ my_bool init_ibd_done;
+ my_bool is_ibd;
+ my_bool compressed;
+ size_t pagesize;
+} ds_local_file_t;
+
+static ds_ctxt_t *local_init(const char *root);
+static ds_file_t *local_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int local_write(ds_file_t *file, const uchar *buf, size_t len);
+static int local_close(ds_file_t *file);
+static void local_deinit(ds_ctxt_t *ctxt);
+
+static int local_remove(const char *path)
+{
+ return unlink(path);
+}
+
+extern "C" {
+datasink_t datasink_local = {
+ &local_init,
+ &local_open,
+ &local_write,
+ &local_close,
+ &local_remove,
+ &local_deinit
+};
+}
+
+static
+ds_ctxt_t *
+local_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+
+ if (my_mkdir(root, 0777, MYF(0)) < 0
+ && my_errno != EEXIST && my_errno != EISDIR)
+ {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf),my_errno);
+ my_error(EE_CANT_MKDIR, MYF(ME_BELL),
+ root, my_errno,errbuf, my_errno);
+ return NULL;
+ }
+
+ ctxt = (ds_ctxt_t *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE));
+
+ ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static
+ds_file_t *
+local_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat __attribute__((unused)))
+{
+ char fullpath[FN_REFLEN];
+ char dirpath[FN_REFLEN];
+ size_t dirpath_len;
+ size_t path_len;
+ ds_local_file_t *local_file;
+ ds_file_t *file;
+ File fd;
+
+ fn_format(fullpath, path, ctxt->root, "", MYF(MY_RELATIVE_PATH));
+
+ /* Create the directory if needed */
+ dirname_part(dirpath, fullpath, &dirpath_len);
+ if (my_mkdir(dirpath, 0777, MYF(0)) < 0 && my_errno != EEXIST) {
+ char errbuf[MYSYS_STRERROR_SIZE];
+ my_strerror(errbuf, sizeof(errbuf), my_errno);
+ my_error(EE_CANT_MKDIR, MYF(ME_BELL),
+ dirpath, my_errno, errbuf);
+ return NULL;
+ }
+
+ fd = my_create(fullpath, 0, O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
+ MYF(MY_WME));
+ if (fd < 0) {
+ return NULL;
+ }
+
+ path_len = strlen(fullpath) + 1; /* terminating '\0' */
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_file_t) +
+ sizeof(ds_local_file_t) +
+ path_len,
+ MYF(MY_FAE));
+ local_file = (ds_local_file_t *) (file + 1);
+
+ local_file->fd = fd;
+ local_file->init_ibd_done = 0;
+ local_file->is_ibd = (path_len > 5) && !strcmp(fullpath + path_len - 5, ".ibd");
+ local_file->compressed = 0;
+ local_file->pagesize = 0;
+ file->path = (char *) local_file + sizeof(ds_local_file_t);
+ memcpy(file->path, fullpath, path_len);
+
+ file->ptr = local_file;
+
+ return file;
+}
+
+/* Calculate size of data without trailing zero bytes. */
+static size_t trim_binary_zeros(uchar *buf, size_t pagesize)
+{
+ size_t i;
+ for (i = pagesize; (i > 0) && (buf[i - 1] == 0); i--) {};
+ return i;
+}
+
+
+/* Write data to the output file, and punch "holes" if needed. */
+static int write_compressed(File fd, uchar *data, size_t len, size_t pagesize)
+{
+ uchar *ptr = data;
+ for (size_t written= 0; written < len;)
+ {
+ size_t n_bytes = MY_MIN(pagesize, len - written);
+ size_t datasize= trim_binary_zeros(ptr,n_bytes);
+ if (datasize > 0) {
+ if (!my_write(fd, ptr, datasize, MYF(MY_WME | MY_NABP)))
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ else
+ return 1;
+ }
+ if (datasize < n_bytes) {
+ /* This punches a "hole" in the file. */
+ size_t hole_bytes = n_bytes - datasize;
+ my_off_t off = my_seek(fd, hole_bytes, MY_SEEK_CUR, MYF(MY_WME | MY_NABP));
+ if (off == MY_FILEPOS_ERROR)
+ return 1;
+#ifdef HAVE_FALLOC_PUNCH_HOLE_AND_KEEP_SIZE
+ /* punch holes harder for filesystems (like XFS) that
+ heuristically decide whether leave a hole after the
+ above or not based on the current access pattern
+ (which is sequential write and not at all typical for
+ what InnoDB will be doing with the file later */
+ fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ off - hole_bytes, hole_bytes);
+#endif
+ }
+ written += n_bytes;
+ ptr += n_bytes;
+ }
+ return 0;
+}
+
+
+/* Calculate Innodb tablespace specific data, when first page is written.
+ We're interested in page compression and page size.
+*/
+static void init_ibd_data(ds_local_file_t *local_file, const uchar *buf, size_t len)
+{
+ if (len < FIL_PAGE_DATA + FSP_SPACE_FLAGS) {
+ /* Weird, bail out.*/
+ return;
+ }
+
+ auto flags = mach_read_from_4(&buf[FIL_PAGE_DATA + FSP_SPACE_FLAGS]);
+ auto ssize = FSP_FLAGS_GET_PAGE_SSIZE(flags);
+ local_file->pagesize= ssize == 0 ? UNIV_PAGE_SIZE_ORIG : ((UNIV_ZIP_SIZE_MIN >> 1) << ssize);
+ local_file->compressed = fil_space_t::full_crc32(flags)
+ ? fil_space_t::is_compressed(flags)
+ : bool(FSP_FLAGS_HAS_PAGE_COMPRESSION(flags));
+
+#if defined(_WIN32) && (MYSQL_VERSION_ID > 100200)
+ /* Make compressed file sparse, on Windows.
+ In 10.1, we do not use sparse files. */
+ if (local_file->compressed) {
+ HANDLE handle= my_get_osfhandle(local_file->fd);
+ if (!DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, NULL, 0)) {
+ fprintf(stderr, "Warning: cannot make file sparse");
+ local_file->compressed = 0;
+ }
+ }
+#endif
+}
+
+
+static
+int
+local_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ uchar *b = (uchar*)buf;
+ ds_local_file_t *local_file= (ds_local_file_t *)file->ptr;
+ File fd = local_file->fd;
+
+ if (local_file->is_ibd && !local_file->init_ibd_done) {
+ init_ibd_data(local_file, b , len);
+ local_file->init_ibd_done= 1;
+ }
+
+ if (local_file->compressed) {
+ return write_compressed(fd, b, len, local_file->pagesize);
+ }
+
+ if (!my_write(fd, b , len, MYF(MY_WME | MY_NABP))) {
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ return 0;
+ }
+ return 1;
+}
+
+/* Set EOF at file's current position.*/
+static int set_eof(File fd)
+{
+#ifdef _WIN32
+ return !SetEndOfFile(my_get_osfhandle(fd));
+#elif defined(HAVE_FTRUNCATE)
+ return ftruncate(fd, my_tell(fd, MYF(MY_WME)));
+#else
+#error no ftruncate
+#endif
+}
+
+
+static
+int
+local_close(ds_file_t *file)
+{
+ ds_local_file_t *local_file= (ds_local_file_t *)file->ptr;
+ File fd = local_file->fd;
+ int ret= 0;
+
+ if (local_file->compressed) {
+ ret = set_eof(fd);
+ }
+
+ my_close(fd, MYF(MY_WME));
+ my_free(file);
+ return ret;
+}
+
+static
+void
+local_deinit(ds_ctxt_t *ctxt)
+{
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
diff --git a/extra/mariabackup/ds_local.h b/extra/mariabackup/ds_local.h
new file mode 100644
index 00000000..5555a332
--- /dev/null
+++ b/extra/mariabackup/ds_local.h
@@ -0,0 +1,34 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Local datasink interface for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_LOCAL_H
+#define DS_LOCAL_H
+
+#include "datasink.h"
+
+#ifdef __cplusplus
+extern "C"
+#else
+extern
+#endif
+
+datasink_t datasink_local;
+
+#endif
diff --git a/extra/mariabackup/ds_stdout.cc b/extra/mariabackup/ds_stdout.cc
new file mode 100644
index 00000000..a9639ff7
--- /dev/null
+++ b/extra/mariabackup/ds_stdout.cc
@@ -0,0 +1,121 @@
+/******************************************************
+Copyright (c) 2013 Percona LLC and/or its affiliates.
+
+Local datasink implementation for XtraBackup.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <mysys_err.h>
+#include "common.h"
+#include "datasink.h"
+
+typedef struct {
+ File fd;
+} ds_stdout_file_t;
+
+static ds_ctxt_t *stdout_init(const char *root);
+static ds_file_t *stdout_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int stdout_write(ds_file_t *file, const uchar *buf, size_t len);
+static int stdout_close(ds_file_t *file);
+static void stdout_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_stdout = {
+ &stdout_init,
+ &stdout_open,
+ &stdout_write,
+ &stdout_close,
+ &dummy_remove,
+ &stdout_deinit
+};
+
+static
+ds_ctxt_t *
+stdout_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+
+ ctxt = (ds_ctxt_t *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE));
+
+ ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static
+ds_file_t *
+stdout_open(ds_ctxt_t *ctxt __attribute__((unused)),
+ const char *path __attribute__((unused)),
+ MY_STAT *mystat __attribute__((unused)))
+{
+ ds_stdout_file_t *stdout_file;
+ ds_file_t *file;
+ size_t pathlen;
+ const char *fullpath = "<STDOUT>";
+
+ pathlen = strlen(fullpath) + 1;
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_file_t) +
+ sizeof(ds_stdout_file_t) + pathlen, MYF(MY_FAE));
+ stdout_file = (ds_stdout_file_t *) (file + 1);
+
+
+#ifdef _WIN32
+ setmode(fileno(stdout), _O_BINARY);
+#endif
+
+ stdout_file->fd = my_fileno(stdout);
+
+ file->path = (char *) stdout_file + sizeof(ds_stdout_file_t);
+ memcpy(file->path, fullpath, pathlen);
+
+ file->ptr = stdout_file;
+
+ return file;
+}
+
+static
+int
+stdout_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ File fd = ((ds_stdout_file_t *) file->ptr)->fd;
+
+ if (!my_write(fd, buf, len, MYF(MY_WME | MY_NABP))) {
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ return 0;
+ }
+
+ return 1;
+}
+
+static
+int
+stdout_close(ds_file_t *file)
+{
+ my_free(file);
+
+ return 1;
+}
+
+static
+void
+stdout_deinit(ds_ctxt_t *ctxt)
+{
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
diff --git a/extra/mariabackup/ds_stdout.h b/extra/mariabackup/ds_stdout.h
new file mode 100644
index 00000000..6174720d
--- /dev/null
+++ b/extra/mariabackup/ds_stdout.h
@@ -0,0 +1,28 @@
+/******************************************************
+Copyright (c) 2013 Percona LLC and/or its affiliates.
+
+Local datasink interface for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_STDOUT_H
+#define DS_STDOUT_H
+
+#include "datasink.h"
+
+extern datasink_t datasink_stdout;
+
+#endif
diff --git a/extra/mariabackup/ds_tmpfile.cc b/extra/mariabackup/ds_tmpfile.cc
new file mode 100644
index 00000000..80b9d3bb
--- /dev/null
+++ b/extra/mariabackup/ds_tmpfile.cc
@@ -0,0 +1,230 @@
+/******************************************************
+Copyright (c) 2012 Percona LLC and/or its affiliates.
+
+tmpfile datasink for XtraBackup.
+
+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
+
+*******************************************************/
+
+/* Do all writes to temporary files first, then pipe them to the specified
+datasink in a serialized way in deinit(). */
+
+#include <my_global.h>
+#include <my_base.h>
+#include "common.h"
+#include "datasink.h"
+
+typedef struct {
+ pthread_mutex_t mutex;
+ LIST *file_list;
+} ds_tmpfile_ctxt_t;
+
+typedef struct {
+ LIST list;
+ File fd;
+ char *orig_path;
+ MY_STAT mystat;
+ ds_file_t *file;
+} ds_tmp_file_t;
+
+static ds_ctxt_t *tmpfile_init(const char *root);
+static ds_file_t *tmpfile_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int tmpfile_write(ds_file_t *file, const uchar *buf, size_t len);
+static int tmpfile_close(ds_file_t *file);
+static void tmpfile_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_tmpfile = {
+ &tmpfile_init,
+ &tmpfile_open,
+ &tmpfile_write,
+ &tmpfile_close,
+ &dummy_remove,
+ &tmpfile_deinit
+};
+
+
+static ds_ctxt_t *
+tmpfile_init(const char *root)
+{
+ ds_ctxt_t *ctxt;
+ ds_tmpfile_ctxt_t *tmpfile_ctxt;
+
+ ctxt = (ds_ctxt_t *)my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_ctxt_t) + sizeof(ds_tmpfile_ctxt_t), MYF(MY_FAE));
+ tmpfile_ctxt = (ds_tmpfile_ctxt_t *) (ctxt + 1);
+ tmpfile_ctxt->file_list = NULL;
+ if (pthread_mutex_init(&tmpfile_ctxt->mutex, NULL)) {
+
+ my_free(ctxt);
+ return NULL;
+ }
+
+ ctxt->ptr = tmpfile_ctxt;
+ ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
+
+ return ctxt;
+}
+
+static ds_file_t *
+tmpfile_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat)
+{
+ ds_tmpfile_ctxt_t *tmpfile_ctxt;
+ char tmp_path[FN_REFLEN];
+ ds_tmp_file_t *tmp_file;
+ ds_file_t *file;
+ size_t path_len;
+ File fd;
+
+ /* Create a temporary file in tmpdir. The file will be automatically
+ removed on close. Code copied from mysql_tmpfile(). */
+ fd = create_temp_file(tmp_path,xtrabackup_tmpdir,
+ "xbtemp", O_BINARY | O_SEQUENTIAL,
+ MYF(MY_WME | MY_TEMPORARY));
+
+ if (fd < 0) {
+ return NULL;
+ }
+
+ path_len = strlen(path) + 1; /* terminating '\0' */
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_file_t) + sizeof(ds_tmp_file_t) + path_len, MYF(MY_FAE));
+
+ tmp_file = (ds_tmp_file_t *) (file + 1);
+ tmp_file->file = file;
+ memcpy(&tmp_file->mystat, mystat, sizeof(MY_STAT));
+ /* Save a copy of 'path', since it may not be accessible later */
+ tmp_file->orig_path = (char *) tmp_file + sizeof(ds_tmp_file_t);
+
+ tmp_file->fd = fd;
+ memcpy(tmp_file->orig_path, path, path_len);
+
+ /* Store the real temporary file name in file->path */
+ file->path = my_strdup(PSI_NOT_INSTRUMENTED, tmp_path, MYF(MY_FAE));
+ file->ptr = tmp_file;
+
+ /* Store the file object in the list to be piped later */
+ tmpfile_ctxt = (ds_tmpfile_ctxt_t *) ctxt->ptr;
+ tmp_file->list.data = tmp_file;
+
+ pthread_mutex_lock(&tmpfile_ctxt->mutex);
+ tmpfile_ctxt->file_list = list_add(tmpfile_ctxt->file_list,
+ &tmp_file->list);
+ pthread_mutex_unlock(&tmpfile_ctxt->mutex);
+
+ return file;
+}
+
+static int
+tmpfile_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ File fd = ((ds_tmp_file_t *) file->ptr)->fd;
+
+ if (!my_write(fd, buf, len, MYF(MY_WME | MY_NABP))) {
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+tmpfile_close(ds_file_t *file)
+{
+ /* Do nothing -- we will close (and thus remove) the file after piping
+ it to the destination datasink in tmpfile_deinit(). */
+
+ my_free(file->path);
+
+ return 0;
+}
+
+static void
+tmpfile_deinit(ds_ctxt_t *ctxt)
+{
+ LIST *list;
+ ds_tmpfile_ctxt_t *tmpfile_ctxt;
+ MY_STAT mystat;
+ ds_tmp_file_t *tmp_file;
+ ds_file_t *dst_file;
+ ds_ctxt_t *pipe_ctxt;
+ void *buf = NULL;
+ const size_t buf_size = 10 * 1024 * 1024;
+ size_t bytes;
+ size_t offset;
+
+ pipe_ctxt = ctxt->pipe_ctxt;
+ xb_a(pipe_ctxt != NULL);
+
+ buf = my_malloc(PSI_NOT_INSTRUMENTED, buf_size, MYF(MY_FAE));
+
+ tmpfile_ctxt = (ds_tmpfile_ctxt_t *) ctxt->ptr;
+ list = tmpfile_ctxt->file_list;
+
+ /* Walk the files in the order they have been added */
+ list = list_reverse(list);
+ while (list != NULL) {
+ tmp_file = (ds_tmp_file_t *)list->data;
+ /* Stat the file to replace size and mtime on the original
+ * mystat struct */
+ if (my_fstat(tmp_file->fd, &mystat, MYF(0))) {
+ die("my_fstat() failed.");
+ }
+ tmp_file->mystat.st_size = mystat.st_size;
+ tmp_file->mystat.st_mtime = mystat.st_mtime;
+
+ dst_file = ds_open(pipe_ctxt, tmp_file->orig_path,
+ &tmp_file->mystat);
+ if (dst_file == NULL) {
+ die("could not stream a temporary file to "
+ "'%s'", tmp_file->orig_path);
+ }
+
+ /* copy to the destination datasink */
+ posix_fadvise(tmp_file->fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+ if (my_seek(tmp_file->fd, 0, SEEK_SET, MYF(0)) ==
+ MY_FILEPOS_ERROR) {
+ die("my_seek() failed for '%s', errno = %d.",
+ tmp_file->file->path, my_errno);
+ }
+ offset = 0;
+ while ((bytes = my_read(tmp_file->fd, (unsigned char *)buf, buf_size,
+ MYF(MY_WME))) > 0) {
+ posix_fadvise(tmp_file->fd, offset, buf_size, POSIX_FADV_DONTNEED);
+ offset += buf_size;
+ if (ds_write(dst_file, buf, bytes)) {
+ die("cannot write to stream for '%s'.",
+ tmp_file->orig_path);
+ }
+ }
+ if (bytes == (size_t) -1) {
+ die("my_read failed for %s", tmp_file->orig_path);
+ }
+
+ my_close(tmp_file->fd, MYF(MY_WME));
+ ds_close(dst_file);
+
+ list = list_rest(list);
+ my_free(tmp_file->file);
+ }
+
+ pthread_mutex_destroy(&tmpfile_ctxt->mutex);
+
+ my_free(buf);
+ my_free(ctxt->root);
+ my_free(ctxt);
+}
diff --git a/extra/mariabackup/ds_tmpfile.h b/extra/mariabackup/ds_tmpfile.h
new file mode 100644
index 00000000..24fa9ad6
--- /dev/null
+++ b/extra/mariabackup/ds_tmpfile.h
@@ -0,0 +1,30 @@
+/******************************************************
+Copyright (c) 2012 Percona LLC and/or its affiliates.
+
+tmpfile datasink for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_TMPFILE_H
+#define DS_TMPFILE_H
+
+#include "datasink.h"
+
+extern datasink_t datasink_tmpfile;
+
+extern MY_TMPDIR mysql_tmpdir_list;
+
+#endif
diff --git a/extra/mariabackup/ds_xbstream.cc b/extra/mariabackup/ds_xbstream.cc
new file mode 100644
index 00000000..3bf8bd08
--- /dev/null
+++ b/extra/mariabackup/ds_xbstream.cc
@@ -0,0 +1,229 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Streaming implementation for XtraBackup.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include "common.h"
+#include "datasink.h"
+#include "xbstream.h"
+
+typedef struct {
+ xb_wstream_t *xbstream;
+ ds_file_t *dest_file;
+ pthread_mutex_t mutex;
+} ds_stream_ctxt_t;
+
+typedef struct {
+ xb_wstream_file_t *xbstream_file;
+ ds_stream_ctxt_t *stream_ctxt;
+} ds_stream_file_t;
+
+/***********************************************************************
+General streaming interface */
+
+static ds_ctxt_t *xbstream_init(const char *root);
+static ds_file_t *xbstream_open(ds_ctxt_t *ctxt, const char *path,
+ MY_STAT *mystat);
+static int xbstream_write(ds_file_t *file, const uchar *buf, size_t len);
+static int xbstream_close(ds_file_t *file);
+static void xbstream_deinit(ds_ctxt_t *ctxt);
+
+datasink_t datasink_xbstream = {
+ &xbstream_init,
+ &xbstream_open,
+ &xbstream_write,
+ &xbstream_close,
+ &dummy_remove,
+ &xbstream_deinit
+};
+
+static
+ssize_t
+my_xbstream_write_callback(xb_wstream_file_t *f __attribute__((unused)),
+ void *userdata, const void *buf, size_t len)
+{
+ ds_stream_ctxt_t *stream_ctxt;
+
+ stream_ctxt = (ds_stream_ctxt_t *) userdata;
+
+ xb_ad(stream_ctxt != NULL);
+ xb_ad(stream_ctxt->dest_file != NULL);
+
+ if (!ds_write(stream_ctxt->dest_file, buf, len)) {
+ return len;
+ }
+ return -1;
+}
+
+static
+ds_ctxt_t *
+xbstream_init(const char *root __attribute__((unused)))
+{
+ ds_ctxt_t *ctxt;
+ ds_stream_ctxt_t *stream_ctxt;
+ xb_wstream_t *xbstream;
+
+ ctxt = (ds_ctxt_t *)my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_ctxt_t) + sizeof(ds_stream_ctxt_t), MYF(MY_FAE));
+ stream_ctxt = (ds_stream_ctxt_t *)(ctxt + 1);
+
+ if (pthread_mutex_init(&stream_ctxt->mutex, NULL)) {
+ msg("xbstream_init: pthread_mutex_init() failed.");
+ goto err;
+ }
+
+ xbstream = xb_stream_write_new();
+ if (xbstream == NULL) {
+ msg("xb_stream_write_new() failed.");
+ goto err;
+ }
+ stream_ctxt->xbstream = xbstream;
+ stream_ctxt->dest_file = NULL;
+
+ ctxt->ptr = stream_ctxt;
+
+ return ctxt;
+
+err:
+ my_free(ctxt);
+ return NULL;
+}
+
+static
+ds_file_t *
+xbstream_open(ds_ctxt_t *ctxt, const char *path, MY_STAT *mystat)
+{
+ ds_file_t *file;
+ ds_stream_file_t *stream_file;
+ ds_stream_ctxt_t *stream_ctxt;
+ ds_ctxt_t *dest_ctxt;
+ xb_wstream_t *xbstream;
+ xb_wstream_file_t *xbstream_file;
+
+
+ xb_ad(ctxt->pipe_ctxt != NULL);
+ dest_ctxt = ctxt->pipe_ctxt;
+
+ stream_ctxt = (ds_stream_ctxt_t *) ctxt->ptr;
+
+ pthread_mutex_lock(&stream_ctxt->mutex);
+ if (stream_ctxt->dest_file == NULL) {
+ stream_ctxt->dest_file = ds_open(dest_ctxt, path, mystat);
+ }
+ pthread_mutex_unlock(&stream_ctxt->mutex);
+ if (stream_ctxt->dest_file == NULL) {
+ return NULL;
+ }
+
+ file = (ds_file_t *) my_malloc(PSI_NOT_INSTRUMENTED,
+ sizeof(ds_file_t) +
+ sizeof(ds_stream_file_t),
+ MYF(MY_FAE));
+ if (!file) {
+ msg("my_malloc() failed.");
+ goto err;
+ }
+ stream_file = (ds_stream_file_t *) (file + 1);
+
+ xbstream = stream_ctxt->xbstream;
+
+ xbstream_file = xb_stream_write_open(xbstream, path, mystat,
+ stream_ctxt,
+ my_xbstream_write_callback);
+
+ if (xbstream_file == NULL) {
+ msg("xb_stream_write_open() failed.");
+ goto err;
+ }
+
+ stream_file->xbstream_file = xbstream_file;
+ stream_file->stream_ctxt = stream_ctxt;
+ file->ptr = stream_file;
+ file->path = stream_ctxt->dest_file->path;
+
+ return file;
+
+err:
+ if (stream_ctxt->dest_file) {
+ ds_close(stream_ctxt->dest_file);
+ stream_ctxt->dest_file = NULL;
+ }
+ my_free(file);
+
+ return NULL;
+}
+
+static
+int
+xbstream_write(ds_file_t *file, const uchar *buf, size_t len)
+{
+ ds_stream_file_t *stream_file;
+ xb_wstream_file_t *xbstream_file;
+
+
+ stream_file = (ds_stream_file_t *) file->ptr;
+
+ xbstream_file = stream_file->xbstream_file;
+
+ if (xb_stream_write_data(xbstream_file, buf, len)) {
+ msg("xb_stream_write_data() failed.");
+ return 1;
+ }
+
+ return 0;
+}
+
+static
+int
+xbstream_close(ds_file_t *file)
+{
+ ds_stream_file_t *stream_file;
+ int rc = 0;
+
+ stream_file = (ds_stream_file_t *)file->ptr;
+
+ rc = xb_stream_write_close(stream_file->xbstream_file);
+
+ my_free(file);
+
+ return rc;
+}
+
+static
+void
+xbstream_deinit(ds_ctxt_t *ctxt)
+{
+ ds_stream_ctxt_t *stream_ctxt;
+
+ stream_ctxt = (ds_stream_ctxt_t *) ctxt->ptr;
+
+ if (xb_stream_write_done(stream_ctxt->xbstream)) {
+ msg("xb_stream_done() failed.");
+ }
+
+ if (stream_ctxt->dest_file) {
+ ds_close(stream_ctxt->dest_file);
+ stream_ctxt->dest_file = NULL;
+ }
+
+ pthread_mutex_destroy(&stream_ctxt->mutex);
+
+ my_free(ctxt);
+}
diff --git a/extra/mariabackup/ds_xbstream.h b/extra/mariabackup/ds_xbstream.h
new file mode 100644
index 00000000..acfbb33c
--- /dev/null
+++ b/extra/mariabackup/ds_xbstream.h
@@ -0,0 +1,28 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+Streaming interface for XtraBackup.
+
+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
+
+*******************************************************/
+
+#ifndef DS_XBSTREAM_H
+#define DS_XBSTREAM_H
+
+#include "datasink.h"
+
+extern datasink_t datasink_xbstream;
+
+#endif
diff --git a/extra/mariabackup/fil_cur.cc b/extra/mariabackup/fil_cur.cc
new file mode 100644
index 00000000..e0a4711a
--- /dev/null
+++ b/extra/mariabackup/fil_cur.cc
@@ -0,0 +1,522 @@
+/******************************************************
+MariaBackup: hot backup tool for InnoDB
+(c) 2009-2013 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.
+
+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
+
+*******************************************************/
+
+/* Source file cursor implementation */
+
+#include <my_global.h>
+#include <my_base.h>
+#include <fil0fil.h>
+#include <fsp0fsp.h>
+#include <srv0start.h>
+#include <trx0sys.h>
+
+#include "fil_cur.h"
+#include "fil0crypt.h"
+#include "fil0pagecompress.h"
+#include "common.h"
+#include "read_filt.h"
+#include "xtrabackup.h"
+#include "backup_debug.h"
+
+/* Size of read buffer in pages (640 pages = 10M for 16K sized pages) */
+#define XB_FIL_CUR_PAGES 640
+
+/***********************************************************************
+Extracts the relative path ("database/table.ibd") of a tablespace from a
+specified possibly absolute path.
+
+For user tablespaces both "./database/table.ibd" and
+"/remote/dir/database/table.ibd" result in "database/table.ibd".
+
+For system tablepsaces (i.e. When is_system is TRUE) both "/remote/dir/ibdata1"
+and "./ibdata1" yield "ibdata1" in the output. */
+const char *
+xb_get_relative_path(
+/*=================*/
+ const char* path, /*!< in: tablespace path (either
+ relative or absolute) */
+ ibool is_system) /*!< in: TRUE for system tablespaces,
+ i.e. when only the filename must be
+ returned. */
+{
+ const char *next;
+ const char *cur;
+ const char *prev;
+
+ prev = NULL;
+ cur = path;
+
+#ifdef _WIN32
+ while ((next = strchr(cur, '\\')) != NULL) {
+ prev = cur;
+ cur = next + 1;
+ }
+#endif
+
+ while ((next = strchr(cur, '/')) != NULL) {
+ prev = cur;
+ cur = next + 1;
+ }
+
+ if (is_system) {
+ return(cur);
+ } else {
+ return((prev == NULL) ? cur : prev);
+ }
+
+}
+
+/**********************************************************************//**
+Closes a file. */
+static
+void
+xb_fil_node_close_file(
+/*===================*/
+ fil_node_t* node) /*!< in: file node */
+{
+ ibool ret;
+
+ mysql_mutex_lock(&fil_system.mutex);
+
+ ut_ad(node);
+ ut_a(!node->being_extended);
+
+ if (node->is_open()) {
+ ret = os_file_close(node->handle);
+ ut_a(ret);
+ node->handle = OS_FILE_CLOSED;
+ }
+
+ mysql_mutex_unlock(&fil_system.mutex);
+}
+
+/************************************************************************
+Open a source file cursor and initialize the associated read filter.
+
+@return XB_FIL_CUR_SUCCESS on success, XB_FIL_CUR_SKIP if the source file must
+be skipped and XB_FIL_CUR_ERROR on error. */
+xb_fil_cur_result_t
+xb_fil_cur_open(
+ /*============*/
+ xb_fil_cur_t* cursor, /*!< out: source file cursor */
+ xb_read_filt_t* read_filter, /*!< in/out: the read filter */
+ fil_node_t* node, /*!< in: source tablespace node */
+ uint thread_n, /*!< thread number for diagnostics */
+ ulonglong max_file_size)
+{
+ bool success;
+ int err;
+ /* Initialize these first so xb_fil_cur_close() handles them correctly
+ in case of error */
+ cursor->buf = NULL;
+ cursor->node = NULL;
+ cursor->n_process_batch = 0;
+
+ cursor->space_id = node->space->id;
+
+ strncpy(cursor->abs_path, node->name, (sizeof cursor->abs_path) - 1);
+ cursor->abs_path[(sizeof cursor->abs_path) - 1] = '\0';
+
+ /* Get the relative path for the destination tablespace name, i.e. the
+ one that can be appended to the backup root directory. Non-system
+ tablespaces may have absolute paths for DATA DIRECTORY.
+ We want to make "local" copies for the backup. */
+ strncpy(cursor->rel_path,
+ xb_get_relative_path(cursor->abs_path, cursor->is_system()),
+ (sizeof cursor->rel_path) - 1);
+ cursor->rel_path[(sizeof cursor->rel_path) - 1] = '\0';
+
+ /* In the backup mode we should already have a tablespace handle created
+ by fil_ibd_load() unless it is a system
+ tablespace. Otherwise we open the file here. */
+ if (!node->is_open()) {
+ ut_ad(cursor->is_system()
+ || srv_operation == SRV_OPERATION_RESTORE_DELTA
+ || xb_close_files);
+
+ node->handle = os_file_create_simple_no_error_handling(
+ 0, node->name,
+ OS_FILE_OPEN,
+ OS_FILE_READ_ALLOW_DELETE, true, &success);
+ if (!success) {
+ /* The following call prints an error message */
+ os_file_get_last_error(TRUE);
+
+ msg(thread_n, "mariabackup: error: cannot open "
+ "tablespace %s", cursor->abs_path);
+
+ return(XB_FIL_CUR_SKIP);
+ }
+ }
+
+ ut_ad(node->is_open());
+
+ cursor->node = node;
+ cursor->file = node->handle;
+#ifdef _WIN32
+ HANDLE hDup;
+ DuplicateHandle(GetCurrentProcess(),cursor->file.m_file,
+ GetCurrentProcess(), &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ int filenr = _open_osfhandle((intptr_t)hDup, 0);
+ if (filenr < 0) {
+ err = EINVAL;
+ }
+ else {
+ err = _fstat64(filenr, &cursor->statinfo);
+ close(filenr);
+ }
+#else
+ err = fstat(cursor->file.m_file, &cursor->statinfo);
+#endif
+ if (max_file_size < (ulonglong)cursor->statinfo.st_size) {
+ cursor->statinfo.st_size = (ulonglong)max_file_size;
+ }
+ if (err) {
+ msg(thread_n, "mariabackup: error: cannot fstat %s",
+ cursor->abs_path);
+
+ xb_fil_cur_close(cursor);
+
+ return(XB_FIL_CUR_SKIP);
+ }
+
+ if (srv_file_flush_method == SRV_O_DIRECT
+ || srv_file_flush_method == SRV_O_DIRECT_NO_FSYNC) {
+
+ os_file_set_nocache(cursor->file, node->name, "OPEN");
+ }
+
+ posix_fadvise(cursor->file, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ cursor->page_size = node->space->physical_size();
+ cursor->zip_size = node->space->zip_size();
+
+ /* Allocate read buffer */
+ cursor->buf_size = XB_FIL_CUR_PAGES * cursor->page_size;
+ cursor->buf = static_cast<byte*>(aligned_malloc(cursor->buf_size,
+ srv_page_size));
+
+ cursor->buf_read = 0;
+ cursor->buf_npages = 0;
+ cursor->buf_offset = 0;
+ cursor->buf_page_no = 0;
+ cursor->thread_n = thread_n;
+
+ if (!node->space->crypt_data
+ && os_file_read(IORequestRead,
+ node->handle, cursor->buf, 0,
+ cursor->page_size, nullptr) == DB_SUCCESS) {
+ mysql_mutex_lock(&fil_system.mutex);
+ if (!node->space->crypt_data) {
+ node->space->crypt_data = fil_space_read_crypt_data(
+ node->space->zip_size(), cursor->buf);
+ }
+ mysql_mutex_unlock(&fil_system.mutex);
+ }
+
+ cursor->space_size = uint32_t(cursor->statinfo.st_size
+ / cursor->page_size);
+
+ cursor->read_filter = read_filter;
+ cursor->read_filter->init(&cursor->read_filter_ctxt, cursor,
+ node->space->id);
+
+ return(XB_FIL_CUR_SUCCESS);
+}
+
+static bool page_is_corrupted(const byte *page, ulint page_no,
+ const xb_fil_cur_t *cursor,
+ const fil_space_t *space)
+{
+ byte tmp_frame[UNIV_PAGE_SIZE_MAX];
+ byte tmp_page[UNIV_PAGE_SIZE_MAX];
+ const ulint page_size = cursor->page_size;
+ uint16_t page_type = fil_page_get_type(page);
+
+ /* We ignore the doublewrite buffer pages.*/
+ if (cursor->space_id == TRX_SYS_SPACE
+ && page_no >= FSP_EXTENT_SIZE
+ && page_no < FSP_EXTENT_SIZE * 3) {
+ return false;
+ }
+
+ /* Validate page number. */
+ if (mach_read_from_4(page + FIL_PAGE_OFFSET) != page_no
+ && cursor->space_id != TRX_SYS_SPACE) {
+ /* On pages that are not all zero, the
+ page number must match.
+
+ There may be a mismatch on tablespace ID,
+ because files may be renamed during backup.
+ We disable the page number check
+ on the system tablespace, because it may consist
+ of multiple files, and here we count the pages
+ from the start of each file.)
+
+ The first 38 and last 8 bytes are never encrypted. */
+ const ulint* p = reinterpret_cast<const ulint*>(page);
+ const ulint* const end = reinterpret_cast<const ulint*>(
+ page + page_size);
+ do {
+ if (*p++) {
+ return true;
+ }
+ } while (p != end);
+
+ /* Whole zero page is valid. */
+ return false;
+ }
+
+ if (space->full_crc32()) {
+ return buf_page_is_corrupted(true, page, space->flags);
+ }
+
+ /* Validate encrypted pages. The first page is never encrypted.
+ In the system tablespace, the first page would be written with
+ FIL_PAGE_FILE_FLUSH_LSN at shutdown, and if the LSN exceeds
+ 4,294,967,295, the mach_read_from_4() below would wrongly
+ interpret the page as encrypted. We prevent that by checking
+ page_no first. */
+ if (page_no
+ && mach_read_from_4(page + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION)
+ && (opt_encrypted_backup
+ || (space->crypt_data
+ && space->crypt_data->type != CRYPT_SCHEME_UNENCRYPTED))) {
+
+ if (!fil_space_verify_crypt_checksum(page, space->zip_size()))
+ return true;
+
+ /* Compressed encrypted need to be decrypted
+ and decompressed for verification. */
+ if (page_type != FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
+ && !opt_extended_validation)
+ return false;
+
+ memcpy(tmp_page, page, page_size);
+
+ if (!space->crypt_data
+ || space->crypt_data->type == CRYPT_SCHEME_UNENCRYPTED
+ || !fil_space_decrypt(space, tmp_frame, tmp_page)) {
+ return true;
+ }
+
+ if (page_type != FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED) {
+ return buf_page_is_corrupted(true, tmp_page,
+ space->flags);
+ }
+ }
+
+ if (page_type == FIL_PAGE_PAGE_COMPRESSED) {
+ memcpy(tmp_page, page, page_size);
+ }
+
+ if (page_type == FIL_PAGE_PAGE_COMPRESSED
+ || page_type == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED) {
+ ulint decomp = fil_page_decompress(tmp_frame, tmp_page,
+ space->flags);
+ page_type = fil_page_get_type(tmp_page);
+
+ return (!decomp
+ || (decomp != srv_page_size
+ && cursor->zip_size)
+ || page_type == FIL_PAGE_PAGE_COMPRESSED
+ || page_type == FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
+ || buf_page_is_corrupted(true, tmp_page,
+ space->flags));
+ }
+
+ return buf_page_is_corrupted(true, page, space->flags);
+}
+
+/** Reads and verifies the next block of pages from the source
+file. Positions the cursor after the last read non-corrupted page.
+@param[in,out] cursor source file cursor
+@param[out] corrupted_pages adds corrupted pages if
+opt_log_innodb_page_corruption is set
+@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF
+if there are no more pages to read and XB_FIL_CUR_ERROR on error. */
+xb_fil_cur_result_t xb_fil_cur_read(xb_fil_cur_t* cursor,
+ CorruptedPages &corrupted_pages)
+{
+ byte* page;
+ unsigned i;
+ ulint npages;
+ ulint retry_count;
+ xb_fil_cur_result_t ret;
+ ib_int64_t offset;
+ ib_int64_t to_read;
+ const ulint page_size = cursor->page_size;
+ bool defer = false;
+ xb_ad(!cursor->is_system() || page_size == srv_page_size);
+
+ cursor->read_filter->get_next_batch(&cursor->read_filter_ctxt,
+ &offset, &to_read);
+
+ if (to_read == 0LL) {
+ return(XB_FIL_CUR_EOF);
+ }
+
+reinit_buf:
+ cursor->n_process_batch++;
+ if (to_read > (ib_int64_t) cursor->buf_size) {
+ to_read = (ib_int64_t) cursor->buf_size;
+ }
+
+ xb_a(to_read > 0 && to_read <= 0xFFFFFFFFLL);
+
+ if ((to_read & ~(page_size - 1))
+ && offset + to_read == cursor->statinfo.st_size) {
+
+ if (to_read < (ib_int64_t) page_size) {
+ msg(cursor->thread_n, "Warning: junk at the end of "
+ "%s, offset = %llu, to_read = %llu",cursor->abs_path, (ulonglong) offset, (ulonglong) to_read);
+ return(XB_FIL_CUR_EOF);
+ }
+
+ to_read = (ib_int64_t) (((ulint) to_read) &
+ ~(page_size - 1));
+ }
+
+ xb_a((to_read & (page_size - 1)) == 0);
+
+ npages = (ulint) (to_read / page_size);
+
+ retry_count = 10;
+ ret = XB_FIL_CUR_SUCCESS;
+
+ fil_space_t *space = fil_space_t::get(cursor->space_id);
+
+ if (!space) {
+ return XB_FIL_CUR_ERROR;
+ }
+
+read_retry:
+ xtrabackup_io_throttling();
+
+ cursor->buf_read = 0;
+ cursor->buf_npages = 0;
+ cursor->buf_offset = offset;
+ cursor->buf_page_no = static_cast<unsigned>(offset / page_size);
+
+ if (os_file_read(IORequestRead, cursor->file, cursor->buf, offset,
+ (ulint) to_read, nullptr) != DB_SUCCESS) {
+ if (!srv_is_undo_tablespace(cursor->space_id)) {
+ ret = XB_FIL_CUR_ERROR;
+ goto func_exit;
+ }
+
+ if (cursor->buf_page_no
+ >= SRV_UNDO_TABLESPACE_SIZE_IN_PAGES) {
+ ret = XB_FIL_CUR_SKIP;
+ goto func_exit;
+ }
+
+ to_read = SRV_UNDO_TABLESPACE_SIZE_IN_PAGES * page_size;
+
+ if (cursor->n_process_batch > 1) {
+ ret = XB_FIL_CUR_ERROR;
+ goto func_exit;
+ }
+
+ space->release();
+ goto reinit_buf;
+ }
+
+ defer = UT_LIST_GET_FIRST(space->chain)->deferred;
+ /* check pages for corruption and re-read if necessary. i.e. in case of
+ partially written pages */
+ for (page = cursor->buf, i = 0; i < npages;
+ page += page_size, i++) {
+ unsigned page_no = cursor->buf_page_no + i;
+
+ if (!defer && page_is_corrupted(page, page_no, cursor, space)) {
+ retry_count--;
+
+ if (retry_count == 0) {
+ const char *ignore_corruption_warn = opt_log_innodb_page_corruption ?
+ " WARNING!!! The corruption is ignored due to"
+ " log-innodb-page-corruption option, the backup can contain"
+ " corrupted data." : "";
+ msg(cursor->thread_n,
+ "Error: failed to read page after "
+ "10 retries. File %s seems to be "
+ "corrupted.%s", cursor->abs_path, ignore_corruption_warn);
+ ut_print_buf(stderr, page, page_size);
+ if (opt_log_innodb_page_corruption) {
+ corrupted_pages.add_page(cursor->node->name,
+ {cursor->node->space->id, page_no});
+ retry_count = 1;
+ }
+ else {
+ ret = XB_FIL_CUR_ERROR;
+ break;
+ }
+ }
+ else {
+ msg(cursor->thread_n, "Database page corruption detected at page "
+ UINT32PF ", retrying...",
+ page_no);
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(100));
+ goto read_retry;
+ }
+ }
+ DBUG_EXECUTE_FOR_KEY("add_corrupted_page_for",
+ cursor->node->space->name(),
+ {
+ unsigned corrupted_page_no =
+ static_cast<unsigned>(strtoul(dbug_val, NULL, 10));
+ if (page_no == corrupted_page_no)
+ corrupted_pages.add_page(cursor->node->name,
+ {cursor->node->space->id,
+ corrupted_page_no});
+ });
+ cursor->buf_read += page_size;
+ cursor->buf_npages++;
+ }
+
+ posix_fadvise(cursor->file, offset, to_read, POSIX_FADV_DONTNEED);
+func_exit:
+ space->release();
+ return(ret);
+}
+
+/************************************************************************
+Close the source file cursor opened with xb_fil_cur_open() and its
+associated read filter. */
+void
+xb_fil_cur_close(
+/*=============*/
+ xb_fil_cur_t *cursor) /*!< in/out: source file cursor */
+{
+ if (cursor->read_filter) {
+ cursor->read_filter->deinit(&cursor->read_filter_ctxt);
+ }
+
+ aligned_free(cursor->buf);
+ cursor->buf = NULL;
+
+ if (cursor->node != NULL) {
+ xb_fil_node_close_file(cursor->node);
+ cursor->file = OS_FILE_CLOSED;
+ }
+}
diff --git a/extra/mariabackup/fil_cur.h b/extra/mariabackup/fil_cur.h
new file mode 100644
index 00000000..b7812f65
--- /dev/null
+++ b/extra/mariabackup/fil_cur.h
@@ -0,0 +1,128 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2013 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.
+
+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
+
+*******************************************************/
+
+/* Source file cursor interface */
+
+#ifndef FIL_CUR_H
+#define FIL_CUR_H
+
+#include <my_dir.h>
+#include "read_filt.h"
+#include "srv0start.h"
+#include "srv0srv.h"
+#include "xtrabackup.h"
+
+struct xb_fil_cur_t {
+ pfs_os_file_t file; /*!< source file handle */
+ fil_node_t* node; /*!< source tablespace node */
+ char rel_path[FN_REFLEN];
+ /*!< normalized file path */
+ char abs_path[FN_REFLEN];
+ /*!< absolute file path */
+ MY_STAT statinfo; /*!< information about the file */
+ ulint zip_size; /*!< compressed page size in bytes or 0
+ for uncompressed pages */
+ ulint page_size; /*!< physical page size */
+ xb_read_filt_t* read_filter; /*!< read filter */
+ xb_read_filt_ctxt_t read_filter_ctxt;
+ /*!< read filter context */
+ byte* buf; /*!< read buffer */
+ size_t buf_size; /*!< buffer size in bytes */
+ size_t buf_read; /*!< number of read bytes in buffer
+ after the last cursor read */
+ size_t buf_npages; /*!< number of pages in buffer after the
+ last cursor read */
+ ib_int64_t buf_offset; /*!< file offset of the first page in
+ buffer */
+ unsigned buf_page_no; /*!< number of the first page in
+ buffer */
+ uint thread_n; /*!< thread number for diagnostics */
+ uint32_t space_id; /*!< ID of tablespace */
+ uint32_t space_size; /*!< space size in pages */
+ uint32_t n_process_batch;/*!< Number of batch processed */
+
+ /** @return whether this is not a file-per-table tablespace */
+ bool is_system() const
+ {
+ ut_ad(space_id != SRV_TMP_SPACE_ID);
+ return(space_id == TRX_SYS_SPACE
+ || srv_is_undo_tablespace(space_id));
+ }
+};
+
+typedef enum {
+ XB_FIL_CUR_SUCCESS,
+ XB_FIL_CUR_SKIP,
+ XB_FIL_CUR_ERROR,
+ XB_FIL_CUR_EOF
+} xb_fil_cur_result_t;
+
+/************************************************************************
+Open a source file cursor and initialize the associated read filter.
+
+@return XB_FIL_CUR_SUCCESS on success, XB_FIL_CUR_SKIP if the source file must
+be skipped and XB_FIL_CUR_ERROR on error. */
+xb_fil_cur_result_t
+xb_fil_cur_open(
+/*============*/
+ xb_fil_cur_t* cursor, /*!< out: source file cursor */
+ xb_read_filt_t* read_filter, /*!< in/out: the read filter */
+ fil_node_t* node, /*!< in: source tablespace node */
+ uint thread_n, /*!< thread number for diagnostics */
+ ulonglong max_file_size = ULLONG_MAX);
+
+/** Reads and verifies the next block of pages from the source
+file. Positions the cursor after the last read non-corrupted page.
+@param[in,out] cursor source file cursor
+@param[out] corrupted_pages adds corrupted pages if
+opt_log_innodb_page_corruption is set
+@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF
+if there are no more pages to read and XB_FIL_CUR_ERROR on error. */
+xb_fil_cur_result_t xb_fil_cur_read(xb_fil_cur_t *cursor,
+ CorruptedPages &corrupted_pages);
+/************************************************************************
+Close the source file cursor opened with xb_fil_cur_open() and its
+associated read filter. */
+void
+xb_fil_cur_close(
+/*=============*/
+ xb_fil_cur_t *cursor); /*!< in/out: source file cursor */
+
+/***********************************************************************
+Extracts the relative path ("database/table.ibd") of a tablespace from a
+specified possibly absolute path.
+
+For user tablespaces both "./database/table.ibd" and
+"/remote/dir/database/table.ibd" result in "database/table.ibd".
+
+For system tablepsaces (i.e. When is_system is TRUE) both "/remote/dir/ibdata1"
+and "./ibdata1" yield "ibdata1" in the output. */
+const char *
+xb_get_relative_path(
+/*=================*/
+ const char* path, /*!< in: tablespace path (either
+ relative or absolute) */
+ ibool is_system); /*!< in: TRUE for system tablespaces,
+ i.e. when only the filename must be
+ returned. */
+
+#endif
diff --git a/extra/mariabackup/innobackupex.cc b/extra/mariabackup/innobackupex.cc
new file mode 100644
index 00000000..b925b415
--- /dev/null
+++ b/extra/mariabackup/innobackupex.cc
@@ -0,0 +1,996 @@
+/******************************************************
+hot backup tool for InnoDB
+(c) 2009-2015 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.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <stdio.h>
+#include <string.h>
+#include <mysql.h>
+#include <my_dir.h>
+#include <ut0mem.h>
+#include <os0file.h>
+#include <srv0start.h>
+#include <algorithm>
+#include <mysqld.h>
+#include <my_default.h>
+#include <my_getopt.h>
+#include <string>
+#include <sstream>
+#include <set>
+#include "common.h"
+#include "innobackupex.h"
+#include "xtrabackup.h"
+#include "xbstream.h"
+#include "fil_cur.h"
+#include "write_filt.h"
+#include "backup_copy.h"
+
+using std::min;
+using std::max;
+
+/* options */
+my_bool opt_ibx_version = FALSE;
+my_bool opt_ibx_help = FALSE;
+my_bool opt_ibx_apply_log = FALSE;
+my_bool opt_ibx_incremental = FALSE;
+my_bool opt_ibx_notimestamp = FALSE;
+
+my_bool opt_ibx_copy_back = FALSE;
+my_bool opt_ibx_move_back = FALSE;
+my_bool opt_ibx_galera_info = FALSE;
+my_bool opt_ibx_slave_info = FALSE;
+my_bool opt_ibx_no_lock = FALSE;
+my_bool opt_ibx_safe_slave_backup = FALSE;
+my_bool opt_ibx_rsync = FALSE;
+my_bool opt_ibx_force_non_empty_dirs = FALSE;
+my_bool opt_ibx_noversioncheck = FALSE;
+my_bool opt_ibx_no_backup_locks = FALSE;
+my_bool opt_ibx_decompress = FALSE;
+
+char *opt_ibx_incremental_history_name = NULL;
+char *opt_ibx_incremental_history_uuid = NULL;
+
+char *opt_ibx_user = NULL;
+const char *opt_ibx_password = NULL;
+char *opt_ibx_host = NULL;
+char *opt_ibx_defaults_group = NULL;
+char *opt_ibx_socket = NULL;
+uint opt_ibx_port = 0;
+
+ulong opt_ibx_lock_wait_query_type;
+ulong opt_ibx_kill_long_query_type;
+
+uint opt_ibx_kill_long_queries_timeout = 0;
+uint opt_ibx_lock_wait_timeout = 0;
+uint opt_ibx_lock_wait_threshold = 0;
+uint opt_ibx_debug_sleep_before_unlock = 0;
+uint opt_ibx_safe_slave_backup_timeout = 0;
+
+const char *opt_ibx_history = NULL;
+
+char *opt_ibx_include = NULL;
+char *opt_ibx_databases = NULL;
+bool ibx_partial_backup = false;
+
+char *ibx_position_arg = NULL;
+char *ibx_backup_directory = NULL;
+
+extern bool xb_opt_destroy_password;
+
+/* copy of proxied xtrabackup options */
+my_bool ibx_xb_close_files;
+const char *ibx_xtrabackup_compress_alg;
+uint ibx_xtrabackup_compress_threads;
+ulonglong ibx_xtrabackup_compress_chunk_size;
+my_bool ibx_xtrabackup_export;
+char *ibx_xtrabackup_extra_lsndir;
+char *ibx_xtrabackup_incremental_basedir;
+char *ibx_xtrabackup_incremental_dir;
+my_bool ibx_xtrabackup_incremental_force_scan;
+ulint ibx_xtrabackup_log_copy_interval;
+char *ibx_xtrabackup_incremental;
+int ibx_xtrabackup_parallel;
+char *ibx_xtrabackup_stream_str;
+char *ibx_xtrabackup_tables_file;
+long ibx_xtrabackup_throttle;
+char *ibx_opt_mysql_tmpdir;
+longlong ibx_xtrabackup_use_memory;
+
+
+static inline int ibx_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2);
+static inline int ibx_msg(const char *fmt, ...)
+{
+ int result;
+ time_t t = time(NULL);
+ char date[100];
+ char *line;
+ va_list args;
+
+ strftime(date, sizeof(date), "%y%m%d %H:%M:%S", localtime(&t));
+
+ va_start(args, fmt);
+
+ result = vasprintf(&line, fmt, args);
+
+ va_end(args);
+
+ if (result != -1) {
+ result = fprintf(stderr, "%s %s: %s",
+ date, INNOBACKUPEX_BIN_NAME, line);
+ free(line);
+ }
+
+ return result;
+}
+
+enum innobackupex_options
+{
+ OPT_APPLY_LOG = 256,
+ OPT_COPY_BACK,
+ OPT_MOVE_BACK,
+ OPT_REDO_ONLY,
+ OPT_GALERA_INFO,
+ OPT_SLAVE_INFO,
+ OPT_INCREMENTAL,
+ OPT_INCREMENTAL_HISTORY_NAME,
+ OPT_INCREMENTAL_HISTORY_UUID,
+ OPT_LOCK_WAIT_QUERY_TYPE,
+ OPT_KILL_LONG_QUERY_TYPE,
+ OPT_KILL_LONG_QUERIES_TIMEOUT,
+ OPT_LOCK_WAIT_TIMEOUT,
+ OPT_LOCK_WAIT_THRESHOLD,
+ OPT_DEBUG_SLEEP_BEFORE_UNLOCK,
+ OPT_NO_LOCK,
+ OPT_SAFE_SLAVE_BACKUP,
+ OPT_SAFE_SLAVE_BACKUP_TIMEOUT,
+ OPT_RSYNC,
+ OPT_HISTORY,
+ OPT_INCLUDE,
+ OPT_FORCE_NON_EMPTY_DIRS,
+ OPT_NO_TIMESTAMP,
+ OPT_NO_VERSION_CHECK,
+ OPT_NO_BACKUP_LOCKS,
+ OPT_DATABASES,
+ OPT_DECOMPRESS,
+
+ /* options wich are passed directly to xtrabackup */
+ OPT_CLOSE_FILES,
+ OPT_COMPACT,
+ OPT_COMPRESS,
+ OPT_COMPRESS_THREADS,
+ OPT_COMPRESS_CHUNK_SIZE,
+ OPT_EXPORT,
+ OPT_EXTRA_LSNDIR,
+ OPT_INCREMENTAL_BASEDIR,
+ OPT_INCREMENTAL_DIR,
+ OPT_INCREMENTAL_FORCE_SCAN,
+ OPT_LOG_COPY_INTERVAL,
+ OPT_PARALLEL,
+ OPT_REBUILD_INDEXES,
+ OPT_REBUILD_THREADS,
+ OPT_STREAM,
+ OPT_TABLES_FILE,
+ OPT_THROTTLE,
+ OPT_USE_MEMORY,
+ OPT_INNODB_FORCE_RECOVERY,
+};
+
+ibx_mode_t ibx_mode = IBX_MODE_BACKUP;
+
+static struct my_option ibx_long_options[] =
+{
+ {"version", 'v', "print version information",
+ (uchar *) &opt_ibx_version, (uchar *) &opt_ibx_version, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"help", '?', "This option displays a help screen and exits.",
+ (uchar *) &opt_ibx_help, (uchar *) &opt_ibx_help, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"apply-log", OPT_APPLY_LOG, "Prepare a backup in BACKUP-DIR by "
+ "applying the redo log 'ib_logfile0' and creating new redo log. "
+ "The InnoDB configuration is read from the file \"backup-my.cnf\".",
+ (uchar*) &opt_ibx_apply_log, (uchar*) &opt_ibx_apply_log,
+ 0, GET_BOOL, 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 *) &opt_ibx_copy_back, (uchar *) &opt_ibx_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 *) &opt_ibx_move_back, (uchar *) &opt_ibx_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_ibx_galera_info, (uchar *) &opt_ibx_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_ibx_slave_info, (uchar *) &opt_ibx_slave_info, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"incremental", OPT_INCREMENTAL,
+ "Create an incremental backup, rather than a full one. When this option is specified, "
+ "either --incremental-lsn or --incremental-basedir can also be given. "
+ "If neither option is given, option --incremental-basedir is used "
+ "by default, set to the first timestamped backup "
+ "directory in the backup base directory.",
+ (uchar *) &opt_ibx_incremental, (uchar *) &opt_ibx_incremental, 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_ibx_no_lock, (uchar *) &opt_ibx_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_ibx_safe_slave_backup,
+ (uchar *) &opt_ibx_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, innobackupex 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_ibx_rsync, (uchar *) &opt_ibx_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_ibx_force_non_empty_dirs,
+ (uchar *) &opt_ibx_force_non_empty_dirs,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"no-timestamp", OPT_NO_TIMESTAMP, "This option prevents creation of a "
+ "time-stamped subdirectory of the BACKUP-ROOT-DIR given on the "
+ "command line. When it is specified, the backup is done in "
+ "BACKUP-ROOT-DIR instead.",
+ (uchar *) &opt_ibx_notimestamp,
+ (uchar *) &opt_ibx_notimestamp,
+ 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_ibx_noversioncheck,
+ (uchar *) &opt_ibx_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_ibx_no_backup_locks,
+ (uchar *) &opt_ibx_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_ibx_decompress,
+ (uchar *) &opt_ibx_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_ibx_user, (uchar*) &opt_ibx_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_ibx_host, (uchar*) &opt_ibx_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_ibx_port, &opt_ibx_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},
+
+ {"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_ibx_socket, (uchar*) &opt_ibx_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. Backup 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_ibx_incremental_history_name,
+ (uchar*) &opt_ibx_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_ibx_incremental_history_uuid,
+ (uchar*) &opt_ibx_incremental_history_uuid, 0, GET_STR,
+ REQUIRED_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 innobackupex will issue the global lock. Default is all.",
+ (uchar*) &opt_ibx_lock_wait_query_type,
+ (uchar*) &opt_ibx_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_ibx_kill_long_query_type,
+ (uchar*) &opt_ibx_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},
+
+ {"include", OPT_INCLUDE,
+ "This option is a regular expression to be matched against table "
+ "names in databasename.tablename format. It is passed directly to "
+ "--tables option. See the documentation for "
+ "details.",
+ (uchar*) &opt_ibx_include,
+ (uchar*) &opt_ibx_include, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"databases", OPT_DATABASES,
+ "This option specifies the list of databases that innobackupex should "
+ "back up. The option accepts a string argument or path to file that "
+ "contains the list of databases to back up. The list is of the form "
+ "\"databasename1[.table_name1] databasename2[.table_name2] . . .\". "
+ "If this option is not specified, all databases containing MyISAM and "
+ "InnoDB tables will be backed up. Please make sure that --databases "
+ "contains all of the InnoDB databases and tables, so that all of the "
+ "innodb.frm files are also backed up. In case the list is very long, "
+ "this can be specified in a file, and the full path of the file can "
+ "be specified instead of the list. (See option --tables-file.)",
+ (uchar*) &opt_ibx_databases,
+ (uchar*) &opt_ibx_databases, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"kill-long-queries-timeout", OPT_KILL_LONG_QUERIES_TIMEOUT,
+ "This option specifies the number of seconds innobackupex waits "
+ "between starting FLUSH TABLES WITH READ LOCK and killing those "
+ "queries that block it. Default is 0 seconds, which means "
+ "innobackupex will not attempt to kill any queries.",
+ (uchar*) &opt_ibx_kill_long_queries_timeout,
+ (uchar*) &opt_ibx_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 innobackupex should wait "
+ "for queries that would block FTWRL before running it. If there are "
+ "still such queries when the timeout expires, innobackupex terminates "
+ "with an error. Default is 0, in which case innobackupex does not "
+ "wait for queries to complete and starts FTWRL immediately.",
+ (uchar*) &opt_ibx_lock_wait_timeout,
+ (uchar*) &opt_ibx_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 "
+ "innobackupex 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_ibx_lock_wait_threshold,
+ (uchar*) &opt_ibx_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_ibx_safe_slave_backup_timeout,
+ (uchar*) &opt_ibx_safe_slave_backup_timeout, 0, GET_UINT,
+ REQUIRED_ARG, 300, 0, 0, 0, 0, 0},
+
+
+ /* Following command-line options are actually handled by xtrabackup.
+ We put them here with only purpose for them to showup in
+ innobackupex --help output */
+
+ {"close_files", OPT_CLOSE_FILES, "Do not keep files opened."
+ " Use at your own risk.",
+ (uchar*) &ibx_xb_close_files, (uchar*) &ibx_xb_close_files, 0,
+ GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"compress", OPT_COMPRESS, "This option instructs backup to "
+ "compress backup copies of InnoDB data files."
+ , (uchar*) &ibx_xtrabackup_compress_alg,
+ (uchar*) &ibx_xtrabackup_compress_alg, 0,
+ GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"compress-threads", OPT_COMPRESS_THREADS,
+ "This option specifies the number of worker threads that will be used "
+ "for parallel compression.",
+ (uchar*) &ibx_xtrabackup_compress_threads,
+ (uchar*) &ibx_xtrabackup_compress_threads,
+ 0, GET_UINT, REQUIRED_ARG, 1, 1, UINT_MAX, 0, 0, 0},
+
+ {"compress-chunk-size", OPT_COMPRESS_CHUNK_SIZE, "Size of working "
+ "buffer(s) for compression threads in bytes. The default value "
+ "is 64K.", (uchar*) &ibx_xtrabackup_compress_chunk_size,
+ (uchar*) &ibx_xtrabackup_compress_chunk_size,
+ 0, GET_ULL, REQUIRED_ARG, (1 << 16), 1024, ULONGLONG_MAX, 0, 0, 0},
+
+ {"export", OPT_EXPORT, " enables exporting individual tables for import "
+ "into another server.",
+ (uchar*) &ibx_xtrabackup_export, (uchar*) &ibx_xtrabackup_export,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"extra-lsndir", OPT_EXTRA_LSNDIR, "This option specifies the "
+ "directory in which to save an extra copy of the "
+ "\"xtrabackup_checkpoints\" file. The option accepts a string "
+ "argument.",
+ (uchar*) &ibx_xtrabackup_extra_lsndir,
+ (uchar*) &ibx_xtrabackup_extra_lsndir,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"incremental-basedir", OPT_INCREMENTAL_BASEDIR, "This option "
+ "specifies the directory containing the full backup that is the base "
+ "dataset for the incremental backup. The option accepts a string "
+ "argument. It is used with the --incremental option.",
+ (uchar*) &ibx_xtrabackup_incremental_basedir,
+ (uchar*) &ibx_xtrabackup_incremental_basedir,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"incremental-dir", OPT_INCREMENTAL_DIR, "This option specifies the "
+ "directory where the incremental backup will be combined with the "
+ "full backup to make a new full backup. The option accepts a string "
+ "argument. It is used with the --incremental option.",
+ (uchar*) &ibx_xtrabackup_incremental_dir,
+ (uchar*) &ibx_xtrabackup_incremental_dir,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"incremental-force-scan", OPT_INCREMENTAL_FORCE_SCAN,
+ "Perform full scan of data files "
+ "for taking an incremental backup even if full changed page bitmap "
+ "data is available to enable the backup without the full scan.",
+ (uchar*)&ibx_xtrabackup_incremental_force_scan,
+ (uchar*)&ibx_xtrabackup_incremental_force_scan, 0, GET_BOOL, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"log-copy-interval", OPT_LOG_COPY_INTERVAL, "This option specifies "
+ "time interval between checks done by log copying thread in "
+ "milliseconds.", (uchar*) &ibx_xtrabackup_log_copy_interval,
+ (uchar*) &ibx_xtrabackup_log_copy_interval,
+ 0, GET_LONG, REQUIRED_ARG, 1000, 0, LONG_MAX, 0, 1, 0},
+
+ {"incremental-lsn", OPT_INCREMENTAL, "This option specifies the log "
+ "sequence number (LSN) to use for the incremental backup. The option "
+ "accepts a string argument. It is used with the --incremental option. "
+ "It is used instead of specifying --incremental-basedir. For "
+ "databases created by MySQL and Percona Server 5.0-series versions, "
+ "specify the LSN as two 32-bit integers in high:low format. For "
+ "databases created in 5.1 and later, specify the LSN as a single "
+ "64-bit integer.",
+ (uchar*) &ibx_xtrabackup_incremental,
+ (uchar*) &ibx_xtrabackup_incremental,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"parallel", OPT_PARALLEL, "On backup, this option specifies the "
+ "number of threads to use to back "
+ "up files concurrently. The option accepts an integer argument.",
+ (uchar*) &ibx_xtrabackup_parallel, (uchar*) &ibx_xtrabackup_parallel,
+ 0, GET_INT, REQUIRED_ARG, 1, 1, INT_MAX, 0, 0, 0},
+
+
+ {"stream", OPT_STREAM, "This option specifies the format in which to "
+ "do the streamed backup. The option accepts a string argument. The "
+ "backup will be done to STDOUT in the specified format. Currently, "
+ "the only supported formats are tar and mbstream/xbstream.",
+ (uchar*) &ibx_xtrabackup_stream_str,
+ (uchar*) &ibx_xtrabackup_stream_str, 0, GET_STR,
+ REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"tables-file", OPT_TABLES_FILE, "This option specifies the file in "
+ "which there are a list of names of the form database. The option "
+ "accepts a string argument.table, one per line.",
+ (uchar*) &ibx_xtrabackup_tables_file,
+ (uchar*) &ibx_xtrabackup_tables_file,
+ 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"throttle", OPT_THROTTLE, "This option specifies a number of I/O "
+ "operations (pairs of read+write) per second. It accepts an integer "
+ "argument.",
+ (uchar*) &ibx_xtrabackup_throttle, (uchar*) &ibx_xtrabackup_throttle,
+ 0, GET_LONG, REQUIRED_ARG, 0, 0, LONG_MAX, 0, 1, 0},
+
+ {"tmpdir", 't', "This option specifies the location where a temporary "
+ "files will be stored. If the option is not specified, the default is "
+ "to use the value of tmpdir read from the server configuration.",
+ (uchar*) &ibx_opt_mysql_tmpdir,
+ (uchar*) &ibx_opt_mysql_tmpdir, 0, GET_STR, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"use-memory", OPT_USE_MEMORY, "This option accepts a string argument "
+ "that specifies the amount of memory in bytes to use "
+ "for crash recovery while preparing a backup. Multiples are supported "
+ "providing the unit (e.g. 1MB, 1GB). It is used only with the option "
+ "--apply-log.",
+ (uchar*) &ibx_xtrabackup_use_memory,
+ (uchar*) &ibx_xtrabackup_use_memory,
+ 0, GET_LL, REQUIRED_ARG, 100*1024*1024L, 1024*1024L, LONGLONG_MAX, 0,
+ 1024*1024L, 0},
+
+ {"innodb-force-recovery", OPT_INNODB_FORCE_RECOVERY,
+ "This option starts up the embedded InnoDB instance in crash "
+ "recovery mode to ignore page corruption; should be used "
+ "with the \"--apply-log\" option, in emergencies only. The "
+ "default value is 0. Refer to \"innodb_force_recovery\" server "
+ "system variable documentation for more details.",
+ (uchar*)&xtrabackup_innodb_force_recovery,
+ (uchar*)&xtrabackup_innodb_force_recovery,
+ 0, GET_ULONG, OPT_ARG, 0, 0, SRV_FORCE_IGNORE_CORRUPT, 0, 0, 0},
+
+ { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+
+static void usage(void)
+{
+ puts("Open source backup tool\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\n");
+
+ puts("innobackupex - Non-blocking backup tool for InnoDB, XtraDB and HailDB databases\n\
+\n\
+SYNOPOSIS\n\
+\n\
+innobackupex [--compress] [--compress-threads=NUMBER-OF-THREADS] [--compress-chunk-size=CHUNK-SIZE]\n\
+ [--include=REGEXP] [--user=NAME]\n\
+ [--password=WORD] [--port=PORT] [--socket=SOCKET]\n\
+ [--no-timestamp] [--ibbackup=IBBACKUP-BINARY]\n\
+ [--slave-info] [--galera-info] [--stream=tar|mbstream|xbstream]\n\
+ [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME]\n\
+ [--databases=LIST] [--no-lock] \n\
+ [--tmpdir=DIRECTORY] [--tables-file=FILE]\n\
+ [--history=NAME]\n\
+ [--incremental] [--incremental-basedir]\n\
+ [--incremental-dir] [--incremental-force-scan] [--incremental-lsn]\n\
+ [--incremental-history-name=NAME] [--incremental-history-uuid=UUID]\n\
+ [--close-files]\n\
+ BACKUP-ROOT-DIR\n\
+\n\
+innobackupex --apply-log [--use-memory=B]\n\
+ [--defaults-file=MY.CNF]\n\
+ [--export] [--ibbackup=IBBACKUP-BINARY]\n\
+ [--innodb-force-recovery=1]\n\
+ BACKUP-DIR\n\
+\n\
+innobackupex --copy-back [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME] BACKUP-DIR\n\
+\n\
+innobackupex --move-back [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME] BACKUP-DIR\n\
+\n\
+innobackupex [--decompress]\n\
+ [--parallel=NUMBER-OF-FORKS] BACKUP-DIR\n\
+\n\
+DESCRIPTION\n\
+\n\
+The first command line above makes a hot backup of a database.\n\
+By default it creates a backup directory (named by the current date\n\
+ and time) in the given backup root directory. With the --no-timestamp\n\
+option it does not create a time-stamped backup directory, but it puts\n\
+the backup in the given directory (which must not exist). This\n\
+command makes a complete backup of all MyISAM and InnoDB tables and\n\
+indexes in all databases or in all of the databases specified with the\n\
+--databases option. The created backup contains .frm, .MRG, .MYD,\n\
+.MYI, .MAD, .MAI, .TRG, .TRN, .ARM, .ARZ, .CSM, CSV, .opt, .par, and\n\
+InnoDB data and log files. The MY.CNF options file defines the\n\
+location of the database.\n\
+\n\
+The --apply-log command prepares a backup for starting a MySQL\n\
+server on the backup. This command recovers InnoDB data files as specified\n\
+in BACKUP-DIR/backup-my.cnf using BACKUP-DIR/ib_logfile0,\n\
+and creates new InnoDB log files as specified in BACKUP-DIR/backup-my.cnf.\n\
+The BACKUP-DIR should be the path to a backup directory\n\
+\n\
+The --copy-back command copies data, index, and log files\n\
+from the backup directory back to their original locations.\n\
+The MY.CNF options file defines the original location of the database.\n\
+The BACKUP-DIR is the path to a backup directory.\n\
+\n\
+The --move-back command is similar to --copy-back with the only difference that\n\
+it moves files to their original locations rather than copies them. As this\n\
+option removes backup files, it must be used with caution. It may be useful in\n\
+cases when there is not enough free disk space to copy files.\n\
+\n\
+The --decompress command will decompress a backup made\n\
+with the --compress option. The\n\
+--parallel option will allow multiple files to be decompressed\n\
+simultaneously. In order to decompress, the qpress utility MUST be installed\n\
+and accessible within the path. This process will remove the original\n\
+compressed files and leave the results in the same location.\n\
+\n\
+On success the exit code innobackupex is 0. A non-zero exit code \n\
+indicates an error.\n");
+ printf("Usage: [%s [--defaults-file=#] --backup | %s [--defaults-file=#] --prepare] [OPTIONS]\n", my_progname, my_progname);
+ my_print_help(ibx_long_options);
+}
+
+
+static
+my_bool
+ibx_get_one_option(const struct my_option *opt,
+ const char *argument, const char *)
+{
+ switch(opt->id) {
+ case '?':
+ usage();
+ exit(0);
+ break;
+ case 'v':
+ printf("innobackupex version %s %s (%s)",
+ MYSQL_SERVER_VERSION,
+ SYSTEM_TYPE, MACHINE_TYPE);
+ exit(0);
+ break;
+ case OPT_HISTORY:
+ if (argument) {
+ opt_ibx_history = argument;
+ } else {
+ opt_ibx_history = "";
+ }
+ break;
+ case OPT_STREAM:
+ if (!strcasecmp(argument, "mbstream") ||
+ !strcasecmp(argument, "xbstream"))
+ xtrabackup_stream_fmt = XB_STREAM_FMT_XBSTREAM;
+ else {
+ ibx_msg("Invalid --stream argument: %s\n", argument);
+ return 1;
+ }
+ xtrabackup_stream = TRUE;
+ break;
+ case OPT_COMPRESS:
+ if (argument == NULL)
+ xtrabackup_compress_alg = "quicklz";
+ else if (strcasecmp(argument, "quicklz"))
+ {
+ ibx_msg("Invalid --compress argument: %s\n", argument);
+ return 1;
+ }
+ xtrabackup_compress = TRUE;
+ break;
+ case 'p':
+ opt_ibx_password= argument;
+ break;
+ }
+ return(0);
+}
+
+bool
+make_backup_dir()
+{
+ time_t t = time(NULL);
+ char buf[100];
+
+ if (!opt_ibx_notimestamp && !ibx_xtrabackup_stream_str) {
+ strftime(buf, sizeof(buf), "%Y-%m-%d_%H-%M-%S", localtime(&t));
+ ut_a(asprintf(&ibx_backup_directory, "%s/%s",
+ ibx_position_arg, buf) != -1);
+ } else {
+ ibx_backup_directory = strdup(ibx_position_arg);
+ }
+
+ if (!directory_exists(ibx_backup_directory, true)) {
+ return(false);
+ }
+
+ return(true);
+}
+
+bool
+ibx_handle_options(int *argc, char ***argv)
+{
+ int i, n_arguments;
+
+ if (handle_options(argc, argv, ibx_long_options, ibx_get_one_option)) {
+ return(false);
+ }
+
+ if (opt_ibx_apply_log) {
+ ibx_mode = IBX_MODE_APPLY_LOG;
+ } else if (opt_ibx_copy_back) {
+ ibx_mode = IBX_MODE_COPY_BACK;
+ } else if (opt_ibx_move_back) {
+ ibx_mode = IBX_MODE_MOVE_BACK;
+ } else if (opt_ibx_decompress) {
+ ibx_mode = IBX_MODE_DECRYPT_DECOMPRESS;
+ } else {
+ ibx_mode = IBX_MODE_BACKUP;
+ }
+
+ /* find and save position argument */
+ i = 0;
+ n_arguments = 0;
+ while (i < *argc) {
+ char *opt = (*argv)[i];
+
+ if (strncmp(opt, "--", 2) != 0
+ && !(strlen(opt) == 2 && opt[0] == '-')) {
+ if (ibx_position_arg != NULL
+ && ibx_position_arg != opt) {
+ ibx_msg("Error: extra argument found %s\n",
+ opt);
+ }
+ ibx_position_arg = opt;
+ ++n_arguments;
+ }
+ ++i;
+ }
+
+ *argc -= n_arguments;
+ if (n_arguments > 1) {
+ return(false);
+ }
+
+ if (ibx_position_arg == NULL) {
+ ibx_msg("Missing argument\n");
+ return(false);
+ }
+
+ /* set argv[0] to be the program name */
+ --(*argv);
+ ++(*argc);
+
+ return(true);
+}
+
+/*********************************************************************//**
+Parse command-line options, connect to MySQL server,
+detect server capabilities, etc.
+@return true on success. */
+bool
+ibx_init()
+{
+ const char *run;
+
+ /*=====================*/
+ xtrabackup_copy_back = opt_ibx_copy_back;
+ xtrabackup_move_back = opt_ibx_move_back;
+ opt_galera_info = opt_ibx_galera_info;
+ opt_slave_info = opt_ibx_slave_info;
+ opt_no_lock = opt_ibx_no_lock;
+ opt_safe_slave_backup = opt_ibx_safe_slave_backup;
+ opt_rsync = opt_ibx_rsync;
+ opt_force_non_empty_dirs = opt_ibx_force_non_empty_dirs;
+ opt_noversioncheck = opt_ibx_noversioncheck;
+ opt_no_backup_locks = opt_ibx_no_backup_locks;
+ opt_decompress = opt_ibx_decompress;
+
+ opt_incremental_history_name = opt_ibx_incremental_history_name;
+ opt_incremental_history_uuid = opt_ibx_incremental_history_uuid;
+
+ opt_user = opt_ibx_user;
+ opt_password = opt_ibx_password;
+ opt_host = opt_ibx_host;
+ opt_defaults_group = opt_ibx_defaults_group;
+ opt_socket = opt_ibx_socket;
+ opt_port = opt_ibx_port;
+
+ opt_lock_wait_query_type = opt_ibx_lock_wait_query_type;
+ opt_kill_long_query_type = opt_ibx_kill_long_query_type;
+
+ opt_kill_long_queries_timeout = opt_ibx_kill_long_queries_timeout;
+ opt_lock_wait_timeout = opt_ibx_lock_wait_timeout;
+ opt_lock_wait_threshold = opt_ibx_lock_wait_threshold;
+ opt_debug_sleep_before_unlock = opt_ibx_debug_sleep_before_unlock;
+ opt_safe_slave_backup_timeout = opt_ibx_safe_slave_backup_timeout;
+
+ opt_history = opt_ibx_history;
+
+ /* setup xtrabackup options */
+ xb_close_files = ibx_xb_close_files;
+ xtrabackup_compress_alg = ibx_xtrabackup_compress_alg;
+ xtrabackup_compress_threads = ibx_xtrabackup_compress_threads;
+ xtrabackup_compress_chunk_size = ibx_xtrabackup_compress_chunk_size;
+ xtrabackup_export = ibx_xtrabackup_export;
+ xtrabackup_extra_lsndir = ibx_xtrabackup_extra_lsndir;
+ xtrabackup_incremental_basedir = ibx_xtrabackup_incremental_basedir;
+ xtrabackup_incremental_dir = ibx_xtrabackup_incremental_dir;
+ xtrabackup_incremental_force_scan =
+ ibx_xtrabackup_incremental_force_scan;
+ xtrabackup_log_copy_interval = ibx_xtrabackup_log_copy_interval;
+ xtrabackup_incremental = ibx_xtrabackup_incremental;
+ xtrabackup_parallel = ibx_xtrabackup_parallel;
+ xtrabackup_stream_str = ibx_xtrabackup_stream_str;
+ xtrabackup_tables_file = ibx_xtrabackup_tables_file;
+ xtrabackup_throttle = ibx_xtrabackup_throttle;
+ opt_mysql_tmpdir = ibx_opt_mysql_tmpdir;
+ xtrabackup_use_memory = ibx_xtrabackup_use_memory;
+
+ if (!opt_ibx_incremental
+ && (xtrabackup_incremental
+ || xtrabackup_incremental_basedir
+ || opt_ibx_incremental_history_name
+ || opt_ibx_incremental_history_uuid)) {
+ ibx_msg("Error: --incremental-lsn, --incremental-basedir, "
+ "--incremental-history-name and "
+ "--incremental-history-uuid require the "
+ "--incremental option.\n");
+ return(false);
+ }
+
+ if (opt_ibx_databases != NULL) {
+ if (is_path_separator(*opt_ibx_databases)) {
+ xtrabackup_databases_file = opt_ibx_databases;
+ } else {
+ xtrabackup_databases = opt_ibx_databases;
+ }
+ }
+
+ /* --tables and --tables-file options are xtrabackup only */
+ ibx_partial_backup = (opt_ibx_include || opt_ibx_databases);
+
+ if (ibx_mode == IBX_MODE_BACKUP) {
+
+ if (!make_backup_dir()) {
+ return(false);
+ }
+ }
+
+ /* --binlog-info is xtrabackup only, so force
+ --binlog-info=ON. i.e. behavior before the feature had been
+ implemented */
+ opt_binlog_info = BINLOG_INFO_ON;
+
+ switch (ibx_mode) {
+ case IBX_MODE_APPLY_LOG:
+ xtrabackup_prepare = TRUE;
+ xtrabackup_target_dir = ibx_position_arg;
+ run = "apply-log";
+ break;
+ case IBX_MODE_BACKUP:
+ xtrabackup_backup = TRUE;
+ xtrabackup_target_dir = ibx_backup_directory;
+ if (opt_ibx_include != NULL) {
+ xtrabackup_tables = opt_ibx_include;
+ }
+ run = "backup";
+ break;
+ case IBX_MODE_COPY_BACK:
+ xtrabackup_copy_back = TRUE;
+ xtrabackup_target_dir = ibx_position_arg;
+ run = "copy-back";
+ break;
+ case IBX_MODE_MOVE_BACK:
+ xtrabackup_move_back = TRUE;
+ xtrabackup_target_dir = ibx_position_arg;
+ run = "move-back";
+ break;
+ case IBX_MODE_DECRYPT_DECOMPRESS:
+ xtrabackup_decrypt_decompress = TRUE;
+ xtrabackup_target_dir = ibx_position_arg;
+ run = "decompress";
+ break;
+ default:
+ ut_error;
+ }
+
+ ibx_msg("Starting the %s operation\n\n"
+ "IMPORTANT: Please check that the %s run completes "
+ "successfully.\n"
+ " At the end of a successful %s run innobackupex\n"
+ " prints \"completed OK!\".\n\n", run, run, run);
+
+
+ return(true);
+}
+
+void
+ibx_cleanup()
+{
+ free(ibx_backup_directory);
+}
diff --git a/extra/mariabackup/innobackupex.h b/extra/mariabackup/innobackupex.h
new file mode 100644
index 00000000..ba134741
--- /dev/null
+++ b/extra/mariabackup/innobackupex.h
@@ -0,0 +1,45 @@
+/******************************************************
+Copyright (c) 2011-2014 Percona LLC and/or its affiliates.
+
+Declarations for innobackupex.cc
+
+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
+
+*******************************************************/
+
+#ifndef INNOBACKUPEX_H
+#define INNOBACKUPEX_H
+
+#define INNOBACKUPEX_BIN_NAME "innobackupex"
+
+enum ibx_mode_t {
+ IBX_MODE_BACKUP,
+ IBX_MODE_APPLY_LOG,
+ IBX_MODE_COPY_BACK,
+ IBX_MODE_MOVE_BACK,
+ IBX_MODE_DECRYPT_DECOMPRESS
+};
+
+extern ibx_mode_t ibx_mode;
+
+bool
+ibx_handle_options(int *argc, char ***argv);
+
+bool
+ibx_init();
+
+void
+ibx_cleanup();
+
+#endif
diff --git a/extra/mariabackup/quicklz/quicklz.c b/extra/mariabackup/quicklz/quicklz.c
new file mode 100644
index 00000000..37421290
--- /dev/null
+++ b/extra/mariabackup/quicklz/quicklz.c
@@ -0,0 +1,848 @@
+// Fast data compression library
+// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
+// lar@quicklz.com
+//
+// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
+// released into public must be open source) or under a commercial license if such
+// has been acquired (see http://www.quicklz.com/order.html). The commercial license
+// does not cover derived or ported versions created by third parties under GPL.
+
+// 1.5.0 final
+
+#include "quicklz.h"
+
+#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0
+ #error quicklz.c and quicklz.h have different versions
+#endif
+
+#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64))
+ #define X86X64
+#endif
+
+#define MINOFFSET 2
+#define UNCONDITIONAL_MATCHLEN 6
+#define UNCOMPRESSED_END 4
+#define CWORD_LEN 4
+
+#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
+ #define OFFSET_BASE source
+ #define CAST (ui32)(size_t)
+#else
+ #define OFFSET_BASE 0
+ #define CAST
+#endif
+
+int qlz_get_setting(int setting)
+{
+ switch (setting)
+ {
+ case 0: return QLZ_COMPRESSION_LEVEL;
+ case 1: return sizeof(qlz_state_compress);
+ case 2: return sizeof(qlz_state_decompress);
+ case 3: return QLZ_STREAMING_BUFFER;
+#ifdef QLZ_MEMORY_SAFE
+ case 6: return 1;
+#else
+ case 6: return 0;
+#endif
+ case 7: return QLZ_VERSION_MAJOR;
+ case 8: return QLZ_VERSION_MINOR;
+ case 9: return QLZ_VERSION_REVISION;
+ }
+ return -1;
+}
+
+#if QLZ_COMPRESSION_LEVEL == 1
+static int same(const unsigned char *src, size_t n)
+{
+ while(n > 0 && *(src + n) == *src)
+ n--;
+ return n == 0 ? 1 : 0;
+}
+#endif
+
+static void reset_table_compress(qlz_state_compress *state)
+{
+ int i;
+ for(i = 0; i < QLZ_HASH_VALUES; i++)
+ {
+#if QLZ_COMPRESSION_LEVEL == 1
+ state->hash[i].offset = 0;
+#else
+ state->hash_counter[i] = 0;
+#endif
+ }
+}
+
+static void reset_table_decompress(qlz_state_decompress *state)
+{
+ int i;
+ (void)state;
+ (void)i;
+#if QLZ_COMPRESSION_LEVEL == 2
+ for(i = 0; i < QLZ_HASH_VALUES; i++)
+ {
+ state->hash_counter[i] = 0;
+ }
+#endif
+}
+
+static __inline ui32 hash_func(ui32 i)
+{
+#if QLZ_COMPRESSION_LEVEL == 2
+ return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1);
+#else
+ return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1);
+#endif
+}
+
+static __inline ui32 fast_read(void const *src, ui32 bytes)
+{
+#ifndef X86X64
+ unsigned char *p = (unsigned char*)src;
+ switch (bytes)
+ {
+ case 4:
+ return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
+ case 3:
+ return(*p | *(p + 1) << 8 | *(p + 2) << 16);
+ case 2:
+ return(*p | *(p + 1) << 8);
+ case 1:
+ return(*p);
+ }
+ return 0;
+#else
+ if (bytes >= 1 && bytes <= 4)
+ return *((ui32*)src);
+ else
+ return 0;
+#endif
+}
+
+static __inline ui32 hashat(const unsigned char *src)
+{
+ ui32 fetch, hash;
+ fetch = fast_read(src, 3);
+ hash = hash_func(fetch);
+ return hash;
+}
+
+static __inline void fast_write(ui32 f, void *dst, size_t bytes)
+{
+#ifndef X86X64
+ unsigned char *p = (unsigned char*)dst;
+
+ switch (bytes)
+ {
+ case 4:
+ *p = (unsigned char)f;
+ *(p + 1) = (unsigned char)(f >> 8);
+ *(p + 2) = (unsigned char)(f >> 16);
+ *(p + 3) = (unsigned char)(f >> 24);
+ return;
+ case 3:
+ *p = (unsigned char)f;
+ *(p + 1) = (unsigned char)(f >> 8);
+ *(p + 2) = (unsigned char)(f >> 16);
+ return;
+ case 2:
+ *p = (unsigned char)f;
+ *(p + 1) = (unsigned char)(f >> 8);
+ return;
+ case 1:
+ *p = (unsigned char)f;
+ return;
+ }
+#else
+ switch (bytes)
+ {
+ case 4:
+ *((ui32*)dst) = f;
+ return;
+ case 3:
+ *((ui32*)dst) = f;
+ return;
+ case 2:
+ *((ui16 *)dst) = (ui16)f;
+ return;
+ case 1:
+ *((unsigned char*)dst) = (unsigned char)f;
+ return;
+ }
+#endif
+}
+
+
+size_t qlz_size_decompressed(const char *source)
+{
+ ui32 n, r;
+ n = (((*source) & 2) == 2) ? 4 : 1;
+ r = fast_read(source + 1 + n, n);
+ r = r & (0xffffffff >> ((4 - n)*8));
+ return r;
+}
+
+size_t qlz_size_compressed(const char *source)
+{
+ ui32 n, r;
+ n = (((*source) & 2) == 2) ? 4 : 1;
+ r = fast_read(source + 1, n);
+ r = r & (0xffffffff >> ((4 - n)*8));
+ return r;
+}
+
+size_t qlz_size_header(const char *source)
+{
+ size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1;
+ return n;
+}
+
+
+static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n)
+{
+ // Caution if modifying memcpy_up! Overlap of dst and src must be special handled.
+#ifndef X86X64
+ unsigned char *end = dst + n;
+ while(dst < end)
+ {
+ *dst = *src;
+ dst++;
+ src++;
+ }
+#else
+ ui32 f = 0;
+ do
+ {
+ *(ui32 *)(dst + f) = *(ui32 *)(src + f);
+ f += MINOFFSET + 1;
+ }
+ while (f < n);
+#endif
+}
+
+static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s)
+{
+#if QLZ_COMPRESSION_LEVEL == 1
+ ui32 hash;
+ hash = hashat(s);
+ state->hash[hash].offset = s;
+ state->hash_counter[hash] = 1;
+#elif QLZ_COMPRESSION_LEVEL == 2
+ ui32 hash;
+ unsigned char c;
+ hash = hashat(s);
+ c = state->hash_counter[hash];
+ state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s;
+ c++;
+ state->hash_counter[hash] = c;
+#endif
+ (void)state;
+ (void)s;
+}
+
+#if QLZ_COMPRESSION_LEVEL <= 2
+static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max)
+{
+ while(*lh < max)
+ {
+ (*lh)++;
+ update_hash(state, *lh);
+ }
+}
+#endif
+
+static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state)
+{
+ const unsigned char *last_byte = source + size - 1;
+ const unsigned char *src = source;
+ unsigned char *cword_ptr = destination;
+ unsigned char *dst = destination + CWORD_LEN;
+ ui32 cword_val = 1U << 31;
+ const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
+ ui32 fetch = 0;
+ unsigned int lits = 0;
+
+ (void) lits;
+
+ if(src <= last_matchstart)
+ fetch = fast_read(src, 3);
+
+ while(src <= last_matchstart)
+ {
+ if ((cword_val & 1) == 1)
+ {
+ // store uncompressed if compression ratio is too low
+ if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5))
+ return 0;
+
+ fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
+
+ cword_ptr = dst;
+ dst += CWORD_LEN;
+ cword_val = 1U << 31;
+ fetch = fast_read(src, 3);
+ }
+#if QLZ_COMPRESSION_LEVEL == 1
+ {
+ const unsigned char *o;
+ ui32 hash, cached;
+
+ hash = hash_func(fetch);
+ cached = fetch ^ state->hash[hash].cache;
+ state->hash[hash].cache = fetch;
+
+ o = state->hash[hash].offset + OFFSET_BASE;
+ state->hash[hash].offset = CAST(src - OFFSET_BASE);
+
+#ifdef X86X64
+ if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
+ {
+ if(cached != 0)
+ {
+#else
+ if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6))))
+ {
+ if (*(o + 3) != *(src + 3))
+ {
+#endif
+ hash <<= 4;
+ cword_val = (cword_val >> 1) | (1U << 31);
+ fast_write((3 - 2) | hash, dst, 2);
+ src += 3;
+ dst += 2;
+ }
+ else
+ {
+ const unsigned char *old_src = src;
+ size_t matchlen;
+ hash <<= 4;
+
+ cword_val = (cword_val >> 1) | (1U << 31);
+ src += 4;
+
+ if(*(o + (src - old_src)) == *src)
+ {
+ src++;
+ if(*(o + (src - old_src)) == *src)
+ {
+ size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1;
+ size_t remaining = q > 255 ? 255 : q;
+ src++;
+ while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining)
+ src++;
+ }
+ }
+
+ matchlen = src - old_src;
+ if (matchlen < 18)
+ {
+ fast_write((ui32)(matchlen - 2) | hash, dst, 2);
+ dst += 2;
+ }
+ else
+ {
+ fast_write((ui32)(matchlen << 16) | hash, dst, 3);
+ dst += 3;
+ }
+ }
+ fetch = fast_read(src, 3);
+ lits = 0;
+ }
+ else
+ {
+ lits++;
+ *dst = *src;
+ src++;
+ dst++;
+ cword_val = (cword_val >> 1);
+#ifdef X86X64
+ fetch = fast_read(src, 3);
+#else
+ fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16);
+#endif
+ }
+ }
+#elif QLZ_COMPRESSION_LEVEL >= 2
+ {
+ const unsigned char *o, *offset2;
+ ui32 hash, matchlen, k, m, best_k = 0;
+ unsigned char c;
+ size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1);
+ (void)best_k;
+
+
+ //hash = hashat(src);
+ fetch = fast_read(src, 3);
+ hash = hash_func(fetch);
+
+ c = state->hash_counter[hash];
+
+ offset2 = state->hash[hash].offset[0];
+ if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0)
+ {
+ matchlen = 3;
+ if(*(offset2 + matchlen) == *(src + matchlen))
+ {
+ matchlen = 4;
+ while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining)
+ matchlen++;
+ }
+ }
+ else
+ matchlen = 0;
+ for(k = 1; k < QLZ_POINTERS && c > k; k++)
+ {
+ o = state->hash[hash].offset[k];
+#if QLZ_COMPRESSION_LEVEL == 3
+ if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
+#elif QLZ_COMPRESSION_LEVEL == 2
+ if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET)
+#endif
+ {
+ m = 3;
+ while(*(o + m) == *(src + m) && m < remaining)
+ m++;
+#if QLZ_COMPRESSION_LEVEL == 3
+ if ((m > matchlen) || (m == matchlen && o > offset2))
+#elif QLZ_COMPRESSION_LEVEL == 2
+ if (m > matchlen)
+#endif
+ {
+ offset2 = o;
+ matchlen = m;
+ best_k = k;
+ }
+ }
+ }
+ o = offset2;
+ state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
+ c++;
+ state->hash_counter[hash] = c;
+
+#if QLZ_COMPRESSION_LEVEL == 3
+ if(matchlen > 2 && src - o < 131071)
+ {
+ ui32 u;
+ size_t offset = src - o;
+
+ for(u = 1; u < matchlen; u++)
+ {
+ hash = hashat(src + u);
+ c = state->hash_counter[hash]++;
+ state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u;
+ }
+
+ cword_val = (cword_val >> 1) | (1U << 31);
+ src += matchlen;
+
+ if(matchlen == 3 && offset <= 63)
+ {
+ *dst = (unsigned char)(offset << 2);
+ dst++;
+ }
+ else if (matchlen == 3 && offset <= 16383)
+ {
+ ui32 f = (ui32)((offset << 2) | 1);
+ fast_write(f, dst, 2);
+ dst += 2;
+ }
+ else if (matchlen <= 18 && offset <= 1023)
+ {
+ ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2;
+ fast_write(f, dst, 2);
+ dst += 2;
+ }
+
+ else if(matchlen <= 33)
+ {
+ ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3;
+ fast_write(f, dst, 3);
+ dst += 3;
+ }
+ else
+ {
+ ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3;
+ fast_write(f, dst, 4);
+ dst += 4;
+ }
+ }
+ else
+ {
+ *dst = *src;
+ src++;
+ dst++;
+ cword_val = (cword_val >> 1);
+ }
+#elif QLZ_COMPRESSION_LEVEL == 2
+
+ if(matchlen > 2)
+ {
+ cword_val = (cword_val >> 1) | (1U << 31);
+ src += matchlen;
+
+ if (matchlen < 10)
+ {
+ ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5);
+ fast_write(f, dst, 2);
+ dst += 2;
+ }
+ else
+ {
+ ui32 f = best_k | (matchlen << 16) | (hash << 5);
+ fast_write(f, dst, 3);
+ dst += 3;
+ }
+ }
+ else
+ {
+ *dst = *src;
+ src++;
+ dst++;
+ cword_val = (cword_val >> 1);
+ }
+#endif
+ }
+#endif
+ }
+ while (src <= last_byte)
+ {
+ if ((cword_val & 1) == 1)
+ {
+ fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
+ cword_ptr = dst;
+ dst += CWORD_LEN;
+ cword_val = 1U << 31;
+ }
+#if QLZ_COMPRESSION_LEVEL < 3
+ if (src <= last_byte - 3)
+ {
+#if QLZ_COMPRESSION_LEVEL == 1
+ ui32 hash, fetch;
+ fetch = fast_read(src, 3);
+ hash = hash_func(fetch);
+ state->hash[hash].offset = CAST(src - OFFSET_BASE);
+ state->hash[hash].cache = fetch;
+#elif QLZ_COMPRESSION_LEVEL == 2
+ ui32 hash;
+ unsigned char c;
+ hash = hashat(src);
+ c = state->hash_counter[hash];
+ state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src;
+ c++;
+ state->hash_counter[hash] = c;
+#endif
+ }
+#endif
+ *dst = *src;
+ src++;
+ dst++;
+ cword_val = (cword_val >> 1);
+ }
+
+ while((cword_val & 1) != 1)
+ cword_val = (cword_val >> 1);
+
+ fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN);
+
+ // min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
+ return dst - destination < 9 ? 9 : dst - destination;
+}
+
+static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history)
+{
+ const unsigned char *src = source + qlz_size_header((const char *)source);
+ unsigned char *dst = destination;
+ const unsigned char *last_destination_byte = destination + size - 1;
+ ui32 cword_val = 1;
+ const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END;
+ unsigned char *last_hashed = destination - 1;
+ const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1;
+ static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
+
+ (void) last_source_byte;
+ (void) last_hashed;
+ (void) state;
+ (void) history;
+
+ for(;;)
+ {
+ ui32 fetch;
+
+ if (cword_val == 1)
+ {
+#ifdef QLZ_MEMORY_SAFE
+ if(src + CWORD_LEN - 1 > last_source_byte)
+ return 0;
+#endif
+ cword_val = fast_read(src, CWORD_LEN);
+ src += CWORD_LEN;
+ }
+
+#ifdef QLZ_MEMORY_SAFE
+ if(src + 4 - 1 > last_source_byte)
+ return 0;
+#endif
+
+ fetch = fast_read(src, 4);
+
+ if ((cword_val & 1) == 1)
+ {
+ ui32 matchlen;
+ const unsigned char *offset2;
+
+#if QLZ_COMPRESSION_LEVEL == 1
+ ui32 hash;
+ cword_val = cword_val >> 1;
+ hash = (fetch >> 4) & 0xfff;
+ offset2 = (const unsigned char *)(size_t)state->hash[hash].offset;
+
+ if((fetch & 0xf) != 0)
+ {
+ matchlen = (fetch & 0xf) + 2;
+ src += 2;
+ }
+ else
+ {
+ matchlen = *(src + 2);
+ src += 3;
+ }
+
+#elif QLZ_COMPRESSION_LEVEL == 2
+ ui32 hash;
+ unsigned char c;
+ cword_val = cword_val >> 1;
+ hash = (fetch >> 5) & 0x7ff;
+ c = (unsigned char)(fetch & 0x3);
+ offset2 = state->hash[hash].offset[c];
+
+ if((fetch & (28)) != 0)
+ {
+ matchlen = ((fetch >> 2) & 0x7) + 2;
+ src += 2;
+ }
+ else
+ {
+ matchlen = *(src + 2);
+ src += 3;
+ }
+
+#elif QLZ_COMPRESSION_LEVEL == 3
+ ui32 offset;
+ cword_val = cword_val >> 1;
+ if ((fetch & 3) == 0)
+ {
+ offset = (fetch & 0xff) >> 2;
+ matchlen = 3;
+ src++;
+ }
+ else if ((fetch & 2) == 0)
+ {
+ offset = (fetch & 0xffff) >> 2;
+ matchlen = 3;
+ src += 2;
+ }
+ else if ((fetch & 1) == 0)
+ {
+ offset = (fetch & 0xffff) >> 6;
+ matchlen = ((fetch >> 2) & 15) + 3;
+ src += 2;
+ }
+ else if ((fetch & 127) != 3)
+ {
+ offset = (fetch >> 7) & 0x1ffff;
+ matchlen = ((fetch >> 2) & 0x1f) + 2;
+ src += 3;
+ }
+ else
+ {
+ offset = (fetch >> 15);
+ matchlen = ((fetch >> 7) & 255) + 3;
+ src += 4;
+ }
+
+ offset2 = dst - offset;
+#endif
+
+#ifdef QLZ_MEMORY_SAFE
+ if(offset2 < history || offset2 > dst - MINOFFSET - 1)
+ return 0;
+
+ if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1))
+ return 0;
+#endif
+
+ memcpy_up(dst, offset2, matchlen);
+ dst += matchlen;
+
+#if QLZ_COMPRESSION_LEVEL <= 2
+ update_hash_upto(state, &last_hashed, dst - matchlen);
+ last_hashed = dst - 1;
+#endif
+ }
+ else
+ {
+ if (dst < last_matchstart)
+ {
+ unsigned int n = bitlut[cword_val & 0xf];
+#ifdef X86X64
+ *(ui32 *)dst = *(ui32 *)src;
+#else
+ memcpy_up(dst, src, 4);
+#endif
+ cword_val = cword_val >> n;
+ dst += n;
+ src += n;
+#if QLZ_COMPRESSION_LEVEL <= 2
+ update_hash_upto(state, &last_hashed, dst - 3);
+#endif
+ }
+ else
+ {
+ while(dst <= last_destination_byte)
+ {
+ if (cword_val == 1)
+ {
+ src += CWORD_LEN;
+ cword_val = 1U << 31;
+ }
+#ifdef QLZ_MEMORY_SAFE
+ if(src >= last_source_byte + 1)
+ return 0;
+#endif
+ *dst = *src;
+ dst++;
+ src++;
+ cword_val = cword_val >> 1;
+ }
+
+#if QLZ_COMPRESSION_LEVEL <= 2
+ update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant
+#endif
+ return size;
+ }
+
+ }
+ }
+}
+
+size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state)
+{
+ size_t r;
+ ui32 compressed;
+ size_t base;
+
+ if(size == 0 || size > 0xffffffff - 400)
+ return 0;
+
+ if(size < 216)
+ base = 3;
+ else
+ base = 9;
+
+#if QLZ_STREAMING_BUFFER > 0
+ if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER)
+#endif
+ {
+ reset_table_compress(state);
+ r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state);
+#if QLZ_STREAMING_BUFFER > 0
+ reset_table_compress(state);
+#endif
+ if(r == base)
+ {
+ memcpy(destination + base, source, size);
+ r = size + base;
+ compressed = 0;
+ }
+ else
+ {
+ compressed = 1;
+ }
+ state->stream_counter = 0;
+ }
+#if QLZ_STREAMING_BUFFER > 0
+ else
+ {
+ unsigned char *src = state->stream_buffer + state->stream_counter;
+
+ memcpy(src, source, size);
+ r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state);
+
+ if(r == base)
+ {
+ memcpy(destination + base, src, size);
+ r = size + base;
+ compressed = 0;
+ reset_table_compress(state);
+ }
+ else
+ {
+ compressed = 1;
+ }
+ state->stream_counter += size;
+ }
+#endif
+ if(base == 3)
+ {
+ *destination = (unsigned char)(0 | compressed);
+ *(destination + 1) = (unsigned char)r;
+ *(destination + 2) = (unsigned char)size;
+ }
+ else
+ {
+ *destination = (unsigned char)(2 | compressed);
+ fast_write((ui32)r, destination + 1, 4);
+ fast_write((ui32)size, destination + 5, 4);
+ }
+
+ *destination |= (QLZ_COMPRESSION_LEVEL << 2);
+ *destination |= (1 << 6);
+ *destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4);
+
+// 76543210
+// 01SSLLHC
+
+ return r;
+}
+
+size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state)
+{
+ size_t dsiz = qlz_size_decompressed(source);
+
+#if QLZ_STREAMING_BUFFER > 0
+ if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER)
+#endif
+ {
+ if((*source & 1) == 1)
+ {
+ reset_table_decompress(state);
+ dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination);
+ }
+ else
+ {
+ memcpy(destination, source + qlz_size_header(source), dsiz);
+ }
+ state->stream_counter = 0;
+ reset_table_decompress(state);
+ }
+#if QLZ_STREAMING_BUFFER > 0
+ else
+ {
+ unsigned char *dst = state->stream_buffer + state->stream_counter;
+ if((*source & 1) == 1)
+ {
+ dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer);
+ }
+ else
+ {
+ memcpy(dst, source + qlz_size_header(source), dsiz);
+ reset_table_decompress(state);
+ }
+ memcpy(destination, dst, dsiz);
+ state->stream_counter += dsiz;
+ }
+#endif
+ return dsiz;
+}
+
diff --git a/extra/mariabackup/quicklz/quicklz.h b/extra/mariabackup/quicklz/quicklz.h
new file mode 100644
index 00000000..6ffe00f3
--- /dev/null
+++ b/extra/mariabackup/quicklz/quicklz.h
@@ -0,0 +1,144 @@
+#ifndef QLZ_HEADER
+#define QLZ_HEADER
+
+// Fast data compression library
+// Copyright (C) 2006-2011 Lasse Mikkel Reinhold
+// lar@quicklz.com
+//
+// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything
+// released into public must be open source) or under a commercial license if such
+// has been acquired (see http://www.quicklz.com/order.html). The commercial license
+// does not cover derived or ported versions created by third parties under GPL.
+
+// You can edit following user settings. Data must be decompressed with the same
+// setting of QLZ_COMPRESSION_LEVEL and QLZ_STREAMING_BUFFER as it was compressed
+// (see manual). If QLZ_STREAMING_BUFFER > 0, scratch buffers must be initially
+// zeroed out (see manual). First #ifndef makes it possible to define settings from
+// the outside like the compiler command line.
+
+// 1.5.0 final
+
+#ifndef QLZ_COMPRESSION_LEVEL
+ #define QLZ_COMPRESSION_LEVEL 1
+ //#define QLZ_COMPRESSION_LEVEL 2
+ //#define QLZ_COMPRESSION_LEVEL 3
+
+ #define QLZ_STREAMING_BUFFER 0
+ //#define QLZ_STREAMING_BUFFER 100000
+ //#define QLZ_STREAMING_BUFFER 1000000
+
+ //#define QLZ_MEMORY_SAFE
+#endif
+
+#define QLZ_VERSION_MAJOR 1
+#define QLZ_VERSION_MINOR 5
+#define QLZ_VERSION_REVISION 0
+
+// Using size_t, memset() and memcpy()
+#include <string.h>
+
+// Verify compression level
+#if QLZ_COMPRESSION_LEVEL != 1 && QLZ_COMPRESSION_LEVEL != 2 && QLZ_COMPRESSION_LEVEL != 3
+#error QLZ_COMPRESSION_LEVEL must be 1, 2 or 3
+#endif
+
+typedef unsigned int ui32;
+typedef unsigned short int ui16;
+
+// Decrease QLZ_POINTERS for level 3 to increase compression speed. Do not touch any other values!
+#if QLZ_COMPRESSION_LEVEL == 1
+#define QLZ_POINTERS 1
+#define QLZ_HASH_VALUES 4096
+#elif QLZ_COMPRESSION_LEVEL == 2
+#define QLZ_POINTERS 4
+#define QLZ_HASH_VALUES 2048
+#elif QLZ_COMPRESSION_LEVEL == 3
+#define QLZ_POINTERS 16
+#define QLZ_HASH_VALUES 4096
+#endif
+
+// Detect if pointer size is 64-bit. It's not fatal if some 64-bit target is not detected because this is only for adding an optional 64-bit optimization.
+#if defined _LP64 || defined __LP64__ || defined __64BIT__ || _ADDR64 || defined _WIN64 || defined __arch64__ || __WORDSIZE == 64 || (defined __sparc && defined __sparcv9) || defined __x86_64 || defined __amd64 || defined __x86_64__ || defined _M_X64 || defined _M_IA64 || defined __ia64 || defined __IA64__
+ #define QLZ_PTR_64
+#endif
+
+// hash entry
+typedef struct
+{
+#if QLZ_COMPRESSION_LEVEL == 1
+ ui32 cache;
+#if defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0
+ unsigned int offset;
+#else
+ const unsigned char *offset;
+#endif
+#else
+ const unsigned char *offset[QLZ_POINTERS];
+#endif
+
+} qlz_hash_compress;
+
+typedef struct
+{
+#if QLZ_COMPRESSION_LEVEL == 1
+ const unsigned char *offset;
+#else
+ const unsigned char *offset[QLZ_POINTERS];
+#endif
+} qlz_hash_decompress;
+
+
+// states
+typedef struct
+{
+ #if QLZ_STREAMING_BUFFER > 0
+ unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
+ #endif
+ size_t stream_counter;
+ qlz_hash_compress hash[QLZ_HASH_VALUES];
+ unsigned char hash_counter[QLZ_HASH_VALUES];
+} qlz_state_compress;
+
+
+#if QLZ_COMPRESSION_LEVEL == 1 || QLZ_COMPRESSION_LEVEL == 2
+ typedef struct
+ {
+#if QLZ_STREAMING_BUFFER > 0
+ unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
+#endif
+ qlz_hash_decompress hash[QLZ_HASH_VALUES];
+ unsigned char hash_counter[QLZ_HASH_VALUES];
+ size_t stream_counter;
+ } qlz_state_decompress;
+#elif QLZ_COMPRESSION_LEVEL == 3
+ typedef struct
+ {
+#if QLZ_STREAMING_BUFFER > 0
+ unsigned char stream_buffer[QLZ_STREAMING_BUFFER];
+#endif
+#if QLZ_COMPRESSION_LEVEL <= 2
+ qlz_hash_decompress hash[QLZ_HASH_VALUES];
+#endif
+ size_t stream_counter;
+ } qlz_state_decompress;
+#endif
+
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+// Public functions of QuickLZ
+size_t qlz_size_decompressed(const char *source);
+size_t qlz_size_compressed(const char *source);
+size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state);
+size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state);
+int qlz_get_setting(int setting);
+size_t qlz_size_header(const char *source);
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif
+
diff --git a/extra/mariabackup/read_filt.cc b/extra/mariabackup/read_filt.cc
new file mode 100644
index 00000000..58920055
--- /dev/null
+++ b/extra/mariabackup/read_filt.cc
@@ -0,0 +1,207 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2012 Percona Inc.
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+
+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
+
+*******************************************************/
+
+/* Data file read filter implementation */
+
+#include "read_filt.h"
+#include "common.h"
+#include "fil_cur.h"
+#include "xtrabackup.h"
+
+/****************************************************************//**
+Perform read filter context initialization that is common to all read
+filters. */
+static
+void
+common_init(
+/*========*/
+ xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter context */
+ const xb_fil_cur_t* cursor) /*!<in: file cursor */
+{
+ ctxt->offset = 0;
+ ctxt->data_file_size = cursor->statinfo.st_size;
+ ctxt->buffer_capacity = cursor->buf_size;
+ ctxt->page_size = cursor->page_size;
+}
+
+/****************************************************************//**
+Initialize the pass-through read filter. */
+static
+void
+rf_pass_through_init(
+/*=================*/
+ xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter context */
+ const xb_fil_cur_t* cursor, /*!<in: file cursor */
+ ulint space_id __attribute__((unused)))
+ /*!<in: space id we are reading */
+{
+ common_init(ctxt, cursor);
+}
+
+/****************************************************************//**
+Get the next batch of pages for the pass-through read filter. */
+static
+void
+rf_pass_through_get_next_batch(
+/*===========================*/
+ xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter
+ context */
+ ib_int64_t* read_batch_start, /*!<out: starting read
+ offset in bytes for the
+ next batch of pages */
+ ib_int64_t* read_batch_len) /*!<out: length in
+ bytes of the next batch
+ of pages */
+{
+ *read_batch_start = ctxt->offset;
+ *read_batch_len = ctxt->data_file_size - ctxt->offset;
+
+ if (*read_batch_len > (ib_int64_t)ctxt->buffer_capacity) {
+ *read_batch_len = ctxt->buffer_capacity;
+ }
+
+ ctxt->offset += *read_batch_len;
+}
+
+/****************************************************************//**
+Deinitialize the pass-through read filter. */
+static
+void
+rf_pass_through_deinit(
+/*===================*/
+ xb_read_filt_ctxt_t* ctxt __attribute__((unused)))
+ /*!<in: read filter context */
+{
+}
+
+/****************************************************************//**
+Initialize the changed page bitmap-based read filter. Assumes that
+the bitmap is already set up in changed_page_bitmap. */
+static
+void
+rf_bitmap_init(
+/*===========*/
+ xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter
+ context */
+ const xb_fil_cur_t* cursor, /*!<in: read cursor */
+ ulint space_id) /*!<in: space id */
+{
+ common_init(ctxt, cursor);
+ ctxt->bitmap_range = xb_page_bitmap_range_init(changed_page_bitmap,
+ space_id);
+ ctxt->filter_batch_end = 0;
+}
+
+/****************************************************************//**
+Get the next batch of pages for the bitmap read filter. */
+static
+void
+rf_bitmap_get_next_batch(
+/*=====================*/
+ xb_read_filt_ctxt_t* ctxt, /*!<in/out: read filter
+ context */
+ ib_int64_t* read_batch_start, /*!<out: starting read
+ offset in bytes for the
+ next batch of pages */
+ ib_int64_t* read_batch_len) /*!<out: length in
+ bytes of the next batch
+ of pages */
+{
+ ulint start_page_id;
+ const ulint page_size = ctxt->page_size;
+
+ start_page_id = (ulint)(ctxt->offset / page_size);
+
+ xb_a (ctxt->offset % page_size == 0);
+
+ if (start_page_id == ctxt->filter_batch_end) {
+
+ /* Used up all the previous bitmap range, get some more */
+ ulint next_page_id;
+
+ /* Find the next changed page using the bitmap */
+ next_page_id = xb_page_bitmap_range_get_next_bit
+ (ctxt->bitmap_range, TRUE);
+
+ if (next_page_id == ULINT_UNDEFINED) {
+ *read_batch_len = 0;
+ return;
+ }
+
+ ctxt->offset = next_page_id * page_size;
+
+ /* Find the end of the current changed page block by searching
+ for the next cleared bitmap bit */
+ ctxt->filter_batch_end
+ = xb_page_bitmap_range_get_next_bit(ctxt->bitmap_range,
+ FALSE);
+ xb_a(next_page_id < ctxt->filter_batch_end);
+ }
+
+ *read_batch_start = ctxt->offset;
+ if (ctxt->filter_batch_end == ULINT_UNDEFINED) {
+ /* No more cleared bits in the bitmap, need to copy all the
+ remaining pages. */
+ *read_batch_len = ctxt->data_file_size - ctxt->offset;
+ } else {
+ *read_batch_len = ctxt->filter_batch_end * page_size
+ - ctxt->offset;
+ }
+
+ /* If the page block is larger than the buffer capacity, limit it to
+ buffer capacity. The subsequent invocations will continue returning
+ the current block in buffer-sized pieces until ctxt->filter_batch_end
+ is reached, trigerring the next bitmap query. */
+ if (*read_batch_len > (ib_int64_t)ctxt->buffer_capacity) {
+ *read_batch_len = ctxt->buffer_capacity;
+ }
+
+ ctxt->offset += *read_batch_len;
+ xb_a (ctxt->offset % page_size == 0);
+ xb_a (*read_batch_start % page_size == 0);
+ xb_a (*read_batch_len % page_size == 0);
+}
+
+/****************************************************************//**
+Deinitialize the changed page bitmap-based read filter. */
+static
+void
+rf_bitmap_deinit(
+/*=============*/
+ xb_read_filt_ctxt_t* ctxt) /*!<in/out: read filter context */
+{
+ xb_page_bitmap_range_deinit(ctxt->bitmap_range);
+}
+
+/* The pass-through read filter */
+xb_read_filt_t rf_pass_through = {
+ &rf_pass_through_init,
+ &rf_pass_through_get_next_batch,
+ &rf_pass_through_deinit
+};
+
+/* The changed page bitmap-based read filter */
+xb_read_filt_t rf_bitmap = {
+ &rf_bitmap_init,
+ &rf_bitmap_get_next_batch,
+ &rf_bitmap_deinit
+};
diff --git a/extra/mariabackup/read_filt.h b/extra/mariabackup/read_filt.h
new file mode 100644
index 00000000..51150705
--- /dev/null
+++ b/extra/mariabackup/read_filt.h
@@ -0,0 +1,66 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2012 Percona Inc.
+Originally Created 3/3/2009 Yasufumi Kinoshita
+Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
+Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
+
+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
+
+*******************************************************/
+
+/* Data file read filter interface */
+
+#ifndef XB_READ_FILT_H
+#define XB_READ_FILT_H
+
+#include "changed_page_bitmap.h"
+
+typedef uint32_t space_id_t;
+
+struct xb_fil_cur_t;
+
+/* The read filter context */
+struct xb_read_filt_ctxt_t {
+ ib_int64_t offset; /*!< current file offset */
+ ib_int64_t data_file_size; /*!< data file size */
+ size_t buffer_capacity;/*!< read buffer capacity */
+ space_id_t space_id; /*!< space id */
+ /* The following fields used only in bitmap filter */
+ /* Move these to union if any other filters are added in future */
+ xb_page_bitmap_range *bitmap_range; /*!< changed page bitmap range
+ iterator for space_id */
+ ulint page_size; /*!< page size */
+ ulint filter_batch_end;/*!< the ending page id of the
+ current changed page block in
+ the bitmap */
+ /** TODO: remove this default constructor */
+ xb_read_filt_ctxt_t() : page_size(0) {}
+};
+
+/* The read filter */
+struct xb_read_filt_t {
+ void (*init)(xb_read_filt_ctxt_t* ctxt,
+ const xb_fil_cur_t* cursor,
+ ulint space_id);
+ void (*get_next_batch)(xb_read_filt_ctxt_t* ctxt,
+ ib_int64_t* read_batch_start,
+ ib_int64_t* read_batch_len);
+ void (*deinit)(xb_read_filt_ctxt_t* ctxt);
+};
+
+extern xb_read_filt_t rf_pass_through;
+extern xb_read_filt_t rf_bitmap;
+
+#endif
diff --git a/extra/mariabackup/write_filt.cc b/extra/mariabackup/write_filt.cc
new file mode 100644
index 00000000..052cea26
--- /dev/null
+++ b/extra/mariabackup/write_filt.cc
@@ -0,0 +1,236 @@
+/******************************************************
+MariaBackup: hot backup tool for InnoDB
+(c) 2009-2013 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.
+
+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
+
+*******************************************************/
+
+/* Page write filters implementation */
+
+#include <my_global.h>
+#include <my_base.h>
+#include "common.h"
+#include "write_filt.h"
+#include "fil_cur.h"
+#include "xtrabackup.h"
+
+/************************************************************************
+Write-through page write filter. */
+static my_bool wf_wt_init(ds_ctxt *ds_meta,
+ xb_write_filt_ctxt_t *ctxt, char *dst_name,
+ xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
+static my_bool wf_wt_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile);
+
+xb_write_filt_t wf_write_through = {
+ &wf_wt_init,
+ &wf_wt_process,
+ NULL,
+ NULL
+};
+
+/************************************************************************
+Incremental page write filter. */
+static my_bool wf_incremental_init(ds_ctxt *ds_meta,
+ xb_write_filt_ctxt_t *ctxt, char *dst_name,
+ xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
+static my_bool wf_incremental_process(xb_write_filt_ctxt_t *ctxt,
+ ds_file_t *dstfile);
+static my_bool wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt,
+ ds_file_t *dstfile);
+static void wf_incremental_deinit(xb_write_filt_ctxt_t *ctxt);
+
+xb_write_filt_t wf_incremental = {
+ &wf_incremental_init,
+ &wf_incremental_process,
+ &wf_incremental_finalize,
+ &wf_incremental_deinit
+};
+
+/************************************************************************
+Initialize incremental page write filter.
+
+@return TRUE on success, FALSE on error. */
+static my_bool
+wf_incremental_init(ds_ctxt *ds_meta,
+ xb_write_filt_ctxt_t *ctxt, char *dst_name,
+ xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages)
+{
+ char meta_name[FN_REFLEN];
+ xb_wf_incremental_ctxt_t *cp =
+ &(ctxt->wf_incremental_ctxt);
+
+ ctxt->cursor = cursor;
+
+ /* allocate buffer for incremental backup (4096 pages) */
+ cp->delta_buf_size = (cursor->page_size / 4) * cursor->page_size;
+ cp->delta_buf = (unsigned char *)my_large_malloc(&cp->delta_buf_size, MYF(0));
+
+ if (!cp->delta_buf) {
+ msg(cursor->thread_n,"Can't allocate %zu bytes",
+ (size_t) cp->delta_buf_size);
+ return (FALSE);
+ }
+
+ /* write delta meta info */
+ snprintf(meta_name, sizeof(meta_name), "%s%s", dst_name,
+ XB_DELTA_INFO_SUFFIX);
+ const xb_delta_info_t info(cursor->page_size, cursor->zip_size,
+ cursor->space_id);
+ if (!xb_write_delta_metadata(ds_meta, meta_name, &info)) {
+ msg(cursor->thread_n,"Error: "
+ "failed to write meta info for %s",
+ cursor->rel_path);
+ return(FALSE);
+ }
+
+ /* change the target file name, since we are only going to write
+ delta pages */
+ strcat(dst_name, ".delta");
+
+ mach_write_to_4(cp->delta_buf, 0x78747261UL); /*"xtra"*/
+
+ cp->npages = 1;
+ cp->corrupted_pages = corrupted_pages;
+
+ return(TRUE);
+}
+
+/************************************************************************
+Run the next batch of pages through incremental page write filter.
+
+@return TRUE on success, FALSE on error. */
+static my_bool
+wf_incremental_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile)
+{
+ unsigned i;
+ xb_fil_cur_t *cursor = ctxt->cursor;
+ byte *page;
+ const ulint page_size = cursor->page_size;
+ xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);
+
+ for (i = 0, page = cursor->buf; i < cursor->buf_npages;
+ i++, page += page_size) {
+
+ if ((!cp->corrupted_pages ||
+ !cp->corrupted_pages->contains({cursor->node->space->id,
+ cursor->buf_page_no + i})) &&
+ incremental_lsn >= mach_read_from_8(page + FIL_PAGE_LSN))
+ continue;
+
+ /* Check whether TRX_SYS page has been changed */
+ if (mach_read_from_4(page + FIL_PAGE_SPACE_ID)
+ == TRX_SYS_SPACE
+ && mach_read_from_4(page + FIL_PAGE_OFFSET)
+ == TRX_SYS_PAGE_NO) {
+ msg(cursor->thread_n,
+ "--incremental backup is impossible if "
+ "the server had been restarted with "
+ "different innodb_undo_tablespaces.");
+ return false;
+ }
+
+ /* updated page */
+ if (cp->npages == page_size / 4) {
+ /* flush buffer */
+ if (ds_write(dstfile, cp->delta_buf,
+ cp->npages * page_size)) {
+ return(FALSE);
+ }
+
+ /* clear buffer */
+ memset(cp->delta_buf, 0, page_size / 4 * page_size);
+ /*"xtra"*/
+ mach_write_to_4(cp->delta_buf, 0x78747261UL);
+ cp->npages = 1;
+ }
+
+ mach_write_to_4(cp->delta_buf + cp->npages * 4,
+ cursor->buf_page_no + i);
+ memcpy(cp->delta_buf + cp->npages * page_size, page,
+ page_size);
+
+ cp->npages++;
+ }
+
+ return(TRUE);
+}
+
+/************************************************************************
+Flush the incremental page write filter's buffer.
+
+@return TRUE on success, FALSE on error. */
+static my_bool
+wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile)
+{
+ xb_fil_cur_t *cursor = ctxt->cursor;
+ const ulint page_size = cursor->page_size;
+ xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);
+
+ if (cp->npages != page_size / 4) {
+ mach_write_to_4(cp->delta_buf + cp->npages * 4, 0xFFFFFFFFUL);
+ }
+
+ /* Mark the final block */
+ mach_write_to_4(cp->delta_buf, 0x58545241UL); /*"XTRA"*/
+
+ /* flush buffer */
+ if (ds_write(dstfile, cp->delta_buf, cp->npages * page_size)) {
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
+/************************************************************************
+Free the incremental page write filter's buffer. */
+static void
+wf_incremental_deinit(xb_write_filt_ctxt_t *ctxt)
+{
+ xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);
+ my_large_free(cp->delta_buf, cp->delta_buf_size);
+}
+
+/************************************************************************
+Initialize the write-through page write filter.
+
+@return TRUE on success, FALSE on error. */
+static my_bool
+wf_wt_init(ds_ctxt *ds_meta __attribute__((unused)),
+ xb_write_filt_ctxt_t *ctxt, char *dst_name __attribute__((unused)),
+ xb_fil_cur_t *cursor, CorruptedPages *)
+{
+ ctxt->cursor = cursor;
+
+ return(TRUE);
+}
+
+/************************************************************************
+Write the next batch of pages to the destination datasink.
+
+@return TRUE on success, FALSE on error. */
+static my_bool
+wf_wt_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile)
+{
+ xb_fil_cur_t *cursor = ctxt->cursor;
+
+ if (ds_write(dstfile, cursor->buf, cursor->buf_read)) {
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
diff --git a/extra/mariabackup/write_filt.h b/extra/mariabackup/write_filt.h
new file mode 100644
index 00000000..a0ce0778
--- /dev/null
+++ b/extra/mariabackup/write_filt.h
@@ -0,0 +1,59 @@
+/******************************************************
+XtraBackup: hot backup tool for InnoDB
+(c) 2009-2013 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.
+
+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
+
+*******************************************************/
+
+/* Page write filter interface */
+
+#ifndef XB_WRITE_FILT_H
+#define XB_WRITE_FILT_H
+
+#include "fil_cur.h"
+#include "datasink.h"
+#include "xtrabackup.h"
+
+/* Incremental page filter context */
+typedef struct {
+ ulint delta_buf_size;
+ byte *delta_buf;
+ ulint npages;
+ CorruptedPages *corrupted_pages;
+} xb_wf_incremental_ctxt_t;
+
+/* Page filter context used as an opaque structure by callers */
+typedef struct {
+ xb_fil_cur_t *cursor;
+ xb_wf_incremental_ctxt_t wf_incremental_ctxt;
+} xb_write_filt_ctxt_t;
+
+
+typedef struct {
+ my_bool (*init)(ds_ctxt *ds_meta,
+ xb_write_filt_ctxt_t *ctxt, char *dst_name,
+ xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
+ my_bool (*process)(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile);
+ my_bool (*finalize)(xb_write_filt_ctxt_t *, ds_file_t *dstfile);
+ void (*deinit)(xb_write_filt_ctxt_t *);
+} xb_write_filt_t;
+
+extern xb_write_filt_t wf_write_through;
+extern xb_write_filt_t wf_incremental;
+
+#endif /* XB_WRITE_FILT_H */
diff --git a/extra/mariabackup/wsrep.cc b/extra/mariabackup/wsrep.cc
new file mode 100644
index 00000000..1b93e9ed
--- /dev/null
+++ b/extra/mariabackup/wsrep.cc
@@ -0,0 +1,117 @@
+/******************************************************
+Percona XtraBackup: hot backup tool for InnoDB
+(c) 2009-2014 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.
+
+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 2010 Codership Oy <http://www.codership.com>
+
+ 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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <handler.h>
+#include <trx0rseg.h>
+#include <mysql/service_wsrep.h>
+
+#include "common.h"
+#ifdef WITH_WSREP
+
+#include <wsrep_api.h>
+
+/*! Name of file where Galera info is stored on recovery */
+#define XB_GALERA_INFO_FILENAME "xtrabackup_galera_info"
+
+/***********************************************************************
+Store Galera checkpoint info in the 'xtrabackup_galera_info' file, if that
+information is present in the trx system header. Otherwise, do nothing. */
+void
+xb_write_galera_info(bool incremental_prepare)
+/*==================*/
+{
+ FILE* fp;
+ XID xid;
+ char uuid_str[40];
+ long long seqno;
+ MY_STAT statinfo;
+
+ /* Do not overwrite existing an existing file to be compatible with
+ servers with older server versions */
+ if (!incremental_prepare &&
+ my_stat(XB_GALERA_INFO_FILENAME, &statinfo, MYF(0)) != NULL) {
+
+ return;
+ }
+
+ xid.null();
+
+ if (!trx_rseg_read_wsrep_checkpoint(xid)) {
+
+ return;
+ }
+
+ wsrep_uuid_t uuid;
+ memcpy(uuid.data, wsrep_xid_uuid(&xid), sizeof(uuid.data));
+ if (wsrep_uuid_print(&uuid, uuid_str,
+ sizeof(uuid_str)) < 0) {
+ return;
+ }
+
+ fp = fopen(XB_GALERA_INFO_FILENAME, "w");
+ if (fp == NULL) {
+
+ die(
+ "could not create " XB_GALERA_INFO_FILENAME
+ ", errno = %d\n",
+ errno);
+ exit(EXIT_FAILURE);
+ }
+
+ seqno = wsrep_xid_seqno(&xid);
+
+ msg("mariabackup: Recovered WSREP position: %s:%lld\n",
+ uuid_str, (long long) seqno);
+
+ if (fprintf(fp, "%s:%lld", uuid_str, (long long) seqno) < 0) {
+
+ die(
+ "could not write to " XB_GALERA_INFO_FILENAME
+ ", errno = %d\n",
+ errno);;
+ }
+
+ fclose(fp);
+}
+#endif
diff --git a/extra/mariabackup/xb_plugin.cc b/extra/mariabackup/xb_plugin.cc
new file mode 100644
index 00000000..7470d376
--- /dev/null
+++ b/extra/mariabackup/xb_plugin.cc
@@ -0,0 +1,229 @@
+/* Copyright (c) 2017, 2022, MariaDB Corporation.
+
+ 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-1301, USA */
+
+#include <my_global.h>
+#include <mysqld.h>
+#include <mysql.h>
+#include <xtrabackup.h>
+#include <xb_plugin.h>
+#include <sql_plugin.h>
+#include <sstream>
+#include <vector>
+#include <common.h>
+#include <backup_mysql.h>
+#include <srv0srv.h>
+
+
+extern struct st_maria_plugin *mysql_optional_plugins[];
+extern struct st_maria_plugin *mysql_mandatory_plugins[];
+static void xb_plugin_init(int argc, char **argv);
+
+extern char *xb_plugin_load;
+extern char *xb_plugin_dir;
+
+const int PLUGIN_MAX_ARGS = 1024;
+std::vector<std::string> backup_plugins_args;
+
+const char *QUERY_PLUGIN =
+"SELECT plugin_name, plugin_library, @@plugin_dir"
+" FROM information_schema.plugins WHERE plugin_type='ENCRYPTION'"
+" OR (plugin_type = 'DAEMON' AND plugin_name LIKE 'provider\\_%')"
+" AND plugin_status='ACTIVE'";
+
+std::string xb_plugin_config;
+
+static void add_to_plugin_load_list(const char *plugin_def)
+{
+ opt_plugin_load_list_ptr->push_back(new i_string(plugin_def));
+}
+
+static char XTRABACKUP_EXE[] = "xtrabackup";
+
+/*
+ Read "plugin-load" value from backup-my.cnf during prepare phase.
+ The value is stored during backup phase.
+*/
+static std::string get_plugin_from_cnf(const char *dir)
+{
+ std::string path = dir + std::string("/backup-my.cnf");
+ FILE *f = fopen(path.c_str(), "r");
+ if (!f)
+ {
+ die("Can't open %s for reading", path.c_str());
+ }
+ char line[512];
+ std::string plugin_load;
+ while (fgets(line, sizeof(line), f))
+ {
+ if (strncmp(line, "plugin_load=", 12) == 0)
+ {
+ plugin_load = line + 12;
+ // remote \n at the end of string
+ plugin_load.resize(plugin_load.size() - 1);
+ break;
+ }
+ }
+ fclose(f);
+ return plugin_load;
+}
+
+
+void xb_plugin_backup_init(MYSQL *mysql)
+{
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+ std::ostringstream oss;
+ char *argv[PLUGIN_MAX_ARGS];
+ char show_query[1024] = "";
+ std::string plugin_load;
+ int argc;
+
+ result = xb_mysql_query(mysql, QUERY_PLUGIN, true, true);
+ while ((row = mysql_fetch_row(result)))
+ {
+ char *name= row[0];
+ char *library= row[1];
+ char *dir= row[2];
+
+ if (!plugin_load.length())
+ {
+#ifdef _WIN32
+ for (char *p = dir; *p; p++)
+ if (*p == '\\') *p = '/';
+#endif
+ strncpy(opt_plugin_dir, dir, FN_REFLEN - 1);
+ opt_plugin_dir[FN_REFLEN - 1] = '\0';
+ oss << "plugin_dir=" << '"' << dir << '"' << std::endl;
+ }
+
+ plugin_load += std::string(";") + name;
+
+ if (library)
+ {
+ /* Remove shared library suffixes, in case we'll prepare on different OS.*/
+ const char *extensions[] = { ".dll", ".so", 0 };
+ for (size_t i = 0; extensions[i]; i++)
+ {
+ const char *ext = extensions[i];
+ if (ends_with(library, ext))
+ library[strlen(library) - strlen(ext)] = 0;
+ }
+ plugin_load += std::string("=") + library;
+ }
+
+ if (strncmp(name, "provider_", 9) == 0)
+ continue;
+
+ /* Read plugin variables. */
+ snprintf(show_query, sizeof(show_query), "SHOW variables like '%s_%%'", name);
+ }
+ mysql_free_result(result);
+ if (!plugin_load.length())
+ return;
+
+ oss << "plugin_load=" << plugin_load.c_str() + 1 << std::endl;
+
+ /* Required to load the plugin later.*/
+ add_to_plugin_load_list(plugin_load.c_str() + 1);
+
+
+ if (*show_query)
+ {
+ result = xb_mysql_query(mysql, show_query, true, true);
+ while ((row = mysql_fetch_row(result)))
+ {
+ std::string arg("--");
+ arg += row[0];
+ arg += "=";
+ arg += row[1];
+ backup_plugins_args.push_back(arg);
+ oss << row[0] << "=" << row[1] << std::endl;
+ }
+
+ mysql_free_result(result);
+
+ /* Check whether to encrypt logs. */
+ result = xb_mysql_query(mysql, "select @@innodb_encrypt_log", true, true);
+ row = mysql_fetch_row(result);
+ srv_encrypt_log = (row != 0 && row[0][0] == '1');
+ oss << "innodb_encrypt_log=" << row[0] << std::endl;
+
+ mysql_free_result(result);
+ }
+
+ xb_plugin_config = oss.str();
+
+ argc = 0;
+ argv[argc++] = XTRABACKUP_EXE;
+ for(size_t i = 0; i < backup_plugins_args.size(); i++)
+ {
+ argv[argc++] = (char *)backup_plugins_args[i].c_str();
+ if (argc == PLUGIN_MAX_ARGS - 2)
+ break;
+ }
+ argv[argc] = 0;
+
+ xb_plugin_init(argc, argv);
+}
+
+const char *xb_plugin_get_config()
+{
+ return xb_plugin_config.c_str();
+}
+
+extern int finalize_encryption_plugin(st_plugin_int *plugin);
+
+
+void xb_plugin_prepare_init(int argc, char **argv, const char *dir)
+{
+ std::string plugin_load= get_plugin_from_cnf(dir ? dir : ".");
+ if (plugin_load.size())
+ {
+ msg("Loading plugins from %s", plugin_load.c_str());
+ }
+ else
+ {
+ finalize_encryption_plugin(0);
+ return;
+ }
+
+ add_to_plugin_load_list(plugin_load.c_str());
+
+ if (xb_plugin_dir)
+ {
+ strncpy(opt_plugin_dir, xb_plugin_dir, FN_REFLEN - 1);
+ opt_plugin_dir[FN_REFLEN - 1] = '\0';
+ }
+
+ char **new_argv = new char *[argc + 2];
+ new_argv[0] = XTRABACKUP_EXE;
+ memcpy(&new_argv[1], argv, argc*sizeof(char *));
+
+ xb_plugin_init(argc+1, new_argv);
+
+ delete[] new_argv;
+}
+
+static void xb_plugin_init(int argc, char **argv)
+{
+ /* Patch optional and mandatory plugins, we only need to load the one in xb_plugin_load. */
+ mysql_optional_plugins[0] = mysql_mandatory_plugins[0] = 0;
+ plugin_maturity = MariaDB_PLUGIN_MATURITY_UNKNOWN; /* mariabackup accepts all plugins */
+ msg("Loading plugins");
+ for (int i= 1; i < argc; i++)
+ msg("\t Plugin parameter : '%s'", argv[i]);
+ plugin_init(&argc, argv, PLUGIN_INIT_SKIP_PLUGIN_TABLE);
+}
+
diff --git a/extra/mariabackup/xb_plugin.h b/extra/mariabackup/xb_plugin.h
new file mode 100644
index 00000000..fea24b6b
--- /dev/null
+++ b/extra/mariabackup/xb_plugin.h
@@ -0,0 +1,5 @@
+#include <mysql.h>
+#include <string>
+extern void xb_plugin_backup_init(MYSQL *mysql);
+extern const char* xb_plugin_get_config();
+extern void xb_plugin_prepare_init(int argc, char **argv, const char *dir);
diff --git a/extra/mariabackup/xb_regex.h b/extra/mariabackup/xb_regex.h
new file mode 100644
index 00000000..8f2f0908
--- /dev/null
+++ b/extra/mariabackup/xb_regex.h
@@ -0,0 +1,49 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License 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 is required to abstract away regex(3) calls so that
+my_regex is used on Windows and native calls are used on POSIX platforms. */
+
+#ifndef XB_REGEX_H
+#define XB_REGEX_H
+
+#ifdef HAVE_SYSTEM_REGEX
+#include <regex.h>
+#else
+#define PCRE2_STATIC 1 /* Important on Windows */
+#include <pcre2posix.h>
+#endif
+
+typedef regex_t* xb_regex_t;
+
+#define xb_regex_init()
+
+#define xb_regexec(preg,string,nmatch,pmatch,eflags) \
+ regexec(preg, string, nmatch, pmatch, eflags)
+
+#define xb_regerror(errcode,preg,errbuf,errbuf_size) \
+ regerror(errcode, preg, errbuf, errbuf_size)
+
+#define xb_regcomp(preg,regex,cflags) \
+ regcomp(preg, regex, cflags)
+
+#define xb_regfree(preg) regfree(preg)
+
+#define xb_regex_end()
+
+#endif /* XB_REGEX_H */
diff --git a/extra/mariabackup/xbcloud.cc b/extra/mariabackup/xbcloud.cc
new file mode 100644
index 00000000..588a15eb
--- /dev/null
+++ b/extra/mariabackup/xbcloud.cc
@@ -0,0 +1,2722 @@
+/******************************************************
+Copyright (c) 2014 Percona LLC and/or its affiliates.
+
+The xbstream utility: serialize/deserialize files in the XBSTREAM format.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_default.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+#include <ev.h>
+#include <unistd.h>
+#include <errno.h>
+#include <gcrypt.h>
+#include <assert.h>
+#include <my_sys.h>
+#include <my_dir.h>
+#include <my_getopt.h>
+#include <algorithm>
+#include <map>
+#include <string>
+#include <jsmn.h>
+#include "xbstream.h"
+
+using std::min;
+using std::max;
+using std::map;
+using std::string;
+
+#define XBCLOUD_VERSION "1.0"
+
+#define SWIFT_MAX_URL_SIZE 8192
+#define SWIFT_MAX_HDR_SIZE 8192
+
+#define SWIFT_CHUNK_SIZE 11 * 1024 * 1024
+
+#if ((LIBCURL_VERSION_MAJOR >= 7) && (LIBCURL_VERSION_MINOR >= 16))
+#define OLD_CURL_MULTI 0
+#else
+#define OLD_CURL_MULTI 1
+#endif
+
+/*****************************************************************************/
+
+typedef struct swift_auth_info_struct swift_auth_info;
+typedef struct connection_info_struct connection_info;
+typedef struct socket_info_struct socket_info;
+typedef struct global_io_info_struct global_io_info;
+typedef struct slo_chunk_struct slo_chunk;
+typedef struct container_list_struct container_list;
+typedef struct object_info_struct object_info;
+
+struct swift_auth_info_struct {
+ char url[SWIFT_MAX_URL_SIZE];
+ char token[SWIFT_MAX_HDR_SIZE];
+};
+
+struct global_io_info_struct {
+ struct ev_loop *loop;
+ struct ev_io input_event;
+ struct ev_timer timer_event;
+ CURLM *multi;
+ int still_running;
+ int eof;
+ curl_socket_t input_fd;
+ connection_info **connections;
+ long chunk_no;
+ connection_info *current_connection;
+ const char *url;
+ const char *container;
+ const char *token;
+ const char *backup_name;
+};
+
+struct socket_info_struct {
+ curl_socket_t sockfd;
+ CURL *easy;
+ int action;
+ long timeout;
+ struct ev_io ev;
+ int evset;
+ global_io_info *global;
+};
+
+struct connection_info_struct {
+ CURL *easy;
+ global_io_info *global;
+ char *buffer;
+ size_t buffer_size;
+ size_t filled_size;
+ size_t upload_size;
+ bool chunk_uploaded;
+ bool chunk_acked;
+ char error[CURL_ERROR_SIZE];
+ struct curl_slist *slist;
+ char *name;
+ size_t name_len;
+ char hash[33];
+ size_t chunk_no;
+ bool magic_verified;
+ size_t chunk_path_len;
+ xb_chunk_type_t chunk_type;
+ size_t payload_size;
+ size_t chunk_size;
+ int retry_count;
+ bool upload_started;
+ ulong global_idx;
+};
+
+struct slo_chunk_struct {
+ char name[SWIFT_MAX_URL_SIZE];
+ char md5[33];
+ int idx;
+ size_t size;
+};
+
+struct object_info_struct {
+ char hash[33];
+ char name[SWIFT_MAX_URL_SIZE];
+ size_t bytes;
+};
+
+struct container_list_struct {
+ size_t content_length;
+ size_t content_bufsize;
+ char *content_json;
+ size_t object_count;
+ size_t idx;
+ object_info *objects;
+ bool final;
+};
+
+enum {SWIFT, S3};
+const char *storage_names[] =
+{ "SWIFT", "S3", NullS};
+
+static my_bool opt_verbose = 0;
+static ulong opt_storage = SWIFT;
+static const char *opt_swift_user = NULL;
+static const char *opt_swift_user_id = NULL;
+static const char *opt_swift_password = NULL;
+static const char *opt_swift_tenant = NULL;
+static const char *opt_swift_tenant_id = NULL;
+static const char *opt_swift_project = NULL;
+static const char *opt_swift_project_id = NULL;
+static const char *opt_swift_domain = NULL;
+static const char *opt_swift_domain_id = NULL;
+static const char *opt_swift_region = NULL;
+static const char *opt_swift_container = NULL;
+static const char *opt_swift_storage_url = NULL;
+static const char *opt_swift_auth_url = NULL;
+static const char *opt_swift_key = NULL;
+static const char *opt_swift_auth_version = NULL;
+static const char *opt_name = NULL;
+static const char *opt_cacert = NULL;
+static ulong opt_parallel = 1;
+static my_bool opt_insecure = 0;
+static enum {MODE_GET, MODE_PUT, MODE_DELETE} opt_mode;
+
+static char **file_list = NULL;
+static int file_list_size = 0;
+
+TYPELIB storage_typelib =
+{array_elements(storage_names)-1, "", storage_names, NULL};
+
+enum {
+ OPT_STORAGE = 256,
+ OPT_SWIFT_CONTAINER,
+ OPT_SWIFT_AUTH_URL,
+ OPT_SWIFT_KEY,
+ OPT_SWIFT_USER,
+ OPT_SWIFT_USER_ID,
+ OPT_SWIFT_PASSWORD,
+ OPT_SWIFT_TENANT,
+ OPT_SWIFT_TENANT_ID,
+ OPT_SWIFT_PROJECT,
+ OPT_SWIFT_PROJECT_ID,
+ OPT_SWIFT_DOMAIN,
+ OPT_SWIFT_DOMAIN_ID,
+ OPT_SWIFT_REGION,
+ OPT_SWIFT_STORAGE_URL,
+ OPT_SWIFT_AUTH_VERSION,
+ OPT_PARALLEL,
+ OPT_CACERT,
+ OPT_INSECURE,
+ OPT_VERBOSE
+};
+
+
+static struct my_option my_long_options[] =
+{
+ {"help", '?', "Display this help and exit.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"storage", OPT_STORAGE, "Specify storage type S3/SWIFT.",
+ &opt_storage, &opt_storage, &storage_typelib,
+ GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+
+ {"swift-auth-version", OPT_SWIFT_AUTH_VERSION,
+ "Swift authentication verison to use.",
+ &opt_swift_auth_version, &opt_swift_auth_version, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-container", OPT_SWIFT_CONTAINER,
+ "Swift container to store backups into.",
+ &opt_swift_container, &opt_swift_container, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-user", OPT_SWIFT_USER,
+ "Swift user name.",
+ &opt_swift_user, &opt_swift_user, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-user-id", OPT_SWIFT_USER_ID,
+ "Swift user ID.",
+ &opt_swift_user_id, &opt_swift_user_id, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-auth-url", OPT_SWIFT_AUTH_URL,
+ "Base URL of SWIFT authentication service.",
+ &opt_swift_auth_url, &opt_swift_auth_url, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-storage-url", OPT_SWIFT_STORAGE_URL,
+ "URL of object-store endpoint. Usually received from authentication "
+ "service. Specify to override this value.",
+ &opt_swift_storage_url, &opt_swift_storage_url, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-key", OPT_SWIFT_KEY,
+ "Swift key.",
+ &opt_swift_key, &opt_swift_key, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-tenant", OPT_SWIFT_TENANT,
+ "The tenant name. Both the --swift-tenant and --swift-tenant-id "
+ "options are optional, but should not be specified together.",
+ &opt_swift_tenant, &opt_swift_tenant, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-tenant-id", OPT_SWIFT_TENANT_ID,
+ "The tenant ID. Both the --swift-tenant and --swift-tenant-id "
+ "options are optional, but should not be specified together.",
+ &opt_swift_tenant_id, &opt_swift_tenant_id, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-project", OPT_SWIFT_PROJECT,
+ "The project name.",
+ &opt_swift_project, &opt_swift_project, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-project-id", OPT_SWIFT_PROJECT_ID,
+ "The project ID.",
+ &opt_swift_project_id, &opt_swift_project_id, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-domain", OPT_SWIFT_DOMAIN,
+ "The domain name.",
+ &opt_swift_domain, &opt_swift_domain, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-domain-id", OPT_SWIFT_DOMAIN_ID,
+ "The domain ID.",
+ &opt_swift_domain_id, &opt_swift_domain_id, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-password", OPT_SWIFT_PASSWORD,
+ "The password of the user.",
+ &opt_swift_password, &opt_swift_password, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"swift-region", OPT_SWIFT_REGION,
+ "The region object-store endpoint.",
+ &opt_swift_region, &opt_swift_region, 0,
+ GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"parallel", OPT_PARALLEL,
+ "Number of parallel chunk uploads.",
+ &opt_parallel, &opt_parallel, 0, GET_ULONG, REQUIRED_ARG,
+ 1, 0, 0, 0, 0, 0},
+
+ {"cacert", OPT_CACERT,
+ "CA certificate file.",
+ &opt_cacert, &opt_cacert, 0, GET_STR_ALLOC, REQUIRED_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"insecure", OPT_INSECURE,
+ "Do not verify server SSL certificate.",
+ &opt_insecure, &opt_insecure, 0, GET_BOOL, NO_ARG,
+ 0, 0, 0, 0, 0, 0},
+
+ {"verbose", OPT_VERBOSE,
+ "Turn ON cURL tracing.",
+ &opt_verbose, &opt_verbose, 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}
+};
+
+/* The values of these arguments should be masked
+ on the command line */
+static const char * const masked_args[] = {
+ "--swift-password",
+ "--swift-key",
+ "--swift-auth-url",
+ "--swift-storage-url",
+ "--swift-container",
+ "--swift-user",
+ "--swift-tenant",
+ "--swift-user-id",
+ "--swift-tenant-id",
+ 0
+};
+
+static map<string, ulonglong> file_chunk_count;
+
+static
+void
+print_version()
+{
+ printf("%s Ver %s for %s (%s)\n", my_progname, XBCLOUD_VERSION,
+ SYSTEM_TYPE, MACHINE_TYPE);
+}
+
+static
+void
+usage()
+{
+ print_version();
+ puts("Copyright (C) 2015 Percona LLC and/or its affiliates.");
+ puts("This software comes with ABSOLUTELY NO WARRANTY. "
+ "This is free software,\nand you are welcome to modify and "
+ "redistribute it under the GPL license.\n");
+
+ puts("Manage backups on Cloud services.\n");
+
+ puts("Usage: ");
+ printf(" %s -c put [OPTIONS...] <NAME> upload backup from STDIN into "
+ "the cloud service with given name.\n", my_progname);
+ printf(" %s -c get [OPTIONS...] <NAME> [FILES...] stream specified "
+ "backup or individual files from cloud service into STDOUT.\n",
+ my_progname);
+
+ puts("\nOptions:");
+ my_print_help(my_long_options);
+}
+
+static
+my_bool
+get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
+ char *argument __attribute__((unused)))
+{
+ switch (optid) {
+ case '?':
+ usage();
+ exit(0);
+ }
+
+ return(FALSE);
+}
+
+static const char *load_default_groups[]=
+ { "xbcloud", 0 };
+
+/*********************************************************************//**
+mask sensitive values on the command line */
+static
+void
+mask_args(int argc, char **argv)
+{
+ int i;
+ for (i = 0; i < argc-1; i++) {
+ int j = 0;
+ if (argv[i]) while (masked_args[j]) {
+ char *p;
+ if ((p = strstr(argv[i], masked_args[j]))) {
+ p += strlen(masked_args[j]);
+ while (*p && *p != '=') {
+ p++;
+ }
+ if (*p == '=') {
+ p++;
+ while (*p) {
+ *p++ = 'x';
+ }
+ }
+ }
+ j++;
+ }
+ }
+}
+
+static
+int parse_args(int argc, char **argv)
+{
+ const char *command;
+
+ if (argc < 2) {
+ fprintf(stderr, "Command isn't specified. "
+ "Supported commands are put and get\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ command = argv[1];
+ argc--; argv++;
+
+ if (strcasecmp(command, "put") == 0) {
+ opt_mode = MODE_PUT;
+ } else if (strcasecmp(command, "get") == 0) {
+ opt_mode = MODE_GET;
+ } else if (strcasecmp(command, "delete") == 0) {
+ opt_mode = MODE_DELETE;
+ } else {
+ fprintf(stderr, "Unknown command %s. "
+ "Supported commands are put and get\n", command);
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ load_defaults_or_exit("my", load_default_groups, &argc, &argv);
+
+ if (handle_options(&argc, &argv, my_long_options, get_one_option)) {
+ exit(EXIT_FAILURE);
+ }
+
+ /* make sure name is specified */
+ if (argc < 1) {
+ fprintf(stderr, "Backup name is required argument\n");
+ exit(EXIT_FAILURE);
+ }
+ opt_name = argv[0];
+ argc--; argv++;
+
+ /* validate arguments */
+ if (opt_storage == SWIFT) {
+ if (opt_swift_user == NULL) {
+ fprintf(stderr, "Swift user is not specified\n");
+ exit(EXIT_FAILURE);
+ }
+ if (opt_swift_container == NULL) {
+ fprintf(stderr,
+ "Swift container is not specified\n");
+ exit(EXIT_FAILURE);
+ }
+ if (opt_swift_auth_url == NULL) {
+ fprintf(stderr, "Swift auth URL is not specified\n");
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ fprintf(stderr, "Swift is only supported storage API\n");
+ }
+
+ if (argc > 0) {
+ file_list = argv;
+ file_list_size = argc;
+ }
+
+ return(0);
+}
+
+static char *hex_md5(const unsigned char *hash, char *out)
+{
+ enum { hash_len = 16 };
+ char *p;
+ int i;
+
+ for (i = 0, p = out; i < hash_len; i++, p+=2) {
+ sprintf(p, "%02x", hash[i]);
+ }
+
+ return out;
+}
+
+/* If header starts with prefix it's value will be copied into output buffer */
+static
+int get_http_header(const char *prefix, const char *buffer,
+ char *out, size_t out_size)
+{
+ const char *beg, *end;
+ size_t len, prefix_len;
+
+ prefix_len = strlen(prefix);
+
+ if (strncasecmp(buffer, prefix, prefix_len) == 0) {
+ beg = buffer + prefix_len;
+ end = strchr(beg, '\r');
+
+ len = min<size_t>(end - beg, out_size - 1);
+
+ strncpy(out, beg, len);
+
+ out[len] = 0;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static
+size_t swift_auth_header_read_cb(char *ptr, size_t size, size_t nmemb,
+ void *data)
+{
+ swift_auth_info *info = (swift_auth_info*)(data);
+
+ get_http_header("X-Storage-Url: ", ptr,
+ info->url, array_elements(info->url));
+ get_http_header("X-Auth-Token: ", ptr,
+ info->token, array_elements(info->token));
+
+ return nmemb * size;
+}
+
+/*********************************************************************//**
+Authenticate against Swift TempAuth. Fills swift_auth_info struct.
+Uses creadentials privided as global variables.
+@returns true if access is granted and token received. */
+static
+bool
+swift_temp_auth(const char *auth_url, swift_auth_info *info)
+{
+ CURL *curl;
+ CURLcode res;
+ long http_code;
+ char *hdr_buf = NULL;
+ struct curl_slist *slist = NULL;
+
+ if (opt_swift_user == NULL) {
+ fprintf(stderr, "Swift user must be specified for TempAuth.\n");
+ return(false);
+ }
+
+ if (opt_swift_key == NULL) {
+ fprintf(stderr, "Swift key must be specified for TempAuth.\n");
+ return(false);
+ }
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+
+ hdr_buf = (char *)(calloc(14 + max(strlen(opt_swift_user),
+ strlen(opt_swift_key)), 1));
+
+ if (!hdr_buf) {
+ res = CURLE_FAILED_INIT;
+ goto cleanup;
+ }
+
+ sprintf(hdr_buf, "X-Auth-User: %s", opt_swift_user);
+ slist = curl_slist_append(slist, hdr_buf);
+
+ sprintf(hdr_buf, "X-Auth-Key: %s", opt_swift_key);
+ slist = curl_slist_append(slist, hdr_buf);
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_URL, auth_url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
+ swift_auth_header_read_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, info);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr, "error: authentication failed: "
+ "curl_easy_perform(): %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 200 &&
+ http_code != 204) {
+ fprintf(stderr, "error: authentication failed "
+ "with response code: %ld\n", http_code);
+ res = CURLE_LOGIN_DENIED;
+ goto cleanup;
+ }
+ } else {
+ res = CURLE_FAILED_INIT;
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+cleanup:
+ if (hdr_buf) {
+ free(hdr_buf);
+ }
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ if (res == CURLE_OK) {
+ /* check that we received token and storage URL */
+ if (*info->url == 0) {
+ fprintf(stderr, "error: malformed response: "
+ "X-Storage-Url is missing\n");
+ return(false);
+ }
+ if (*info->token == 0) {
+ fprintf(stderr, "error: malformed response: "
+ "X-Auth-Token is missing\n");
+ return(false);
+ }
+ return(true);
+ }
+
+ return(false);
+}
+
+static
+size_t
+write_null_cb(char *buffer, size_t size, size_t nmemb, void *stream)
+{
+ return fwrite(buffer, size, nmemb, stderr);
+}
+
+
+static
+size_t
+read_null_cb(char *ptr, size_t size, size_t nmemb, void *data)
+{
+ return 0;
+}
+
+
+static
+int
+swift_create_container(swift_auth_info *info, const char *name)
+{
+ char url[SWIFT_MAX_URL_SIZE];
+ char auth_token[SWIFT_MAX_HDR_SIZE];
+ CURLcode res;
+ long http_code;
+ CURL *curl;
+ struct curl_slist *slist = NULL;
+
+ snprintf(url, array_elements(url), "%s/%s", info->url, name);
+ snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
+ info->token);
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+ slist = curl_slist_append(slist, auth_token);
+ slist = curl_slist_append(slist, "Content-Length: 0");
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_null_cb);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_null_cb);
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0L);
+ curl_easy_setopt(curl, CURLOPT_PUT, 1L);
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr,
+ "error: curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 201 && /* created */
+ http_code != 202 /* accepted (already exists) */) {
+ fprintf(stderr, "error: request failed "
+ "with response code: %ld\n", http_code);
+ res = CURLE_LOGIN_DENIED;
+ goto cleanup;
+ }
+ } else {
+ res = CURLE_FAILED_INIT;
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+cleanup:
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ return res;
+}
+
+
+/*********************************************************************//**
+Delete object with given url.
+@returns true if object deleted successfully. */
+static
+bool
+swift_delete_object(swift_auth_info *info, const char *url)
+{
+ char auth_token[SWIFT_MAX_HDR_SIZE];
+ CURLcode res;
+ long http_code;
+ CURL *curl;
+ struct curl_slist *slist = NULL;
+ bool ret = false;
+
+ snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
+ info->token);
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+ slist = curl_slist_append(slist, auth_token);
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr,
+ "error: curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code != 200 && /* OK */
+ http_code != 204 /* no content */) {
+ fprintf(stderr, "error: request failed "
+ "with response code: %ld\n", http_code);
+ goto cleanup;
+ }
+ ret = true;
+ } else {
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+cleanup:
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ return ret;
+}
+
+static int conn_upload_init(connection_info *conn);
+static void conn_buffer_updated(connection_info *conn);
+static connection_info *conn_new(global_io_info *global, ulong global_idx);
+static void conn_cleanup(connection_info *conn);
+static void conn_upload_retry(connection_info *conn);
+
+/* Check for completed transfers, and remove their easy handles */
+static void check_multi_info(global_io_info *g)
+{
+ char *eff_url;
+ CURLMsg *msg;
+ int msgs_left;
+ connection_info *conn;
+ CURL *easy;
+
+ while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
+ if (msg->msg == CURLMSG_DONE) {
+ easy = msg->easy_handle;
+ curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
+ curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL,
+ &eff_url);
+ curl_multi_remove_handle(g->multi, easy);
+ curl_easy_cleanup(easy);
+ conn->easy = NULL;
+ if (conn->chunk_acked) {
+ conn->chunk_uploaded = true;
+ fprintf(stderr, "%s is done\n", conn->hash);
+ } else {
+ fprintf(stderr, "error: chunk %zu '%s' %s "
+ "is not uploaded, but socket closed "
+ "(%zu bytes of %zu left to upload)\n",
+ conn->chunk_no,
+ conn->name,
+ conn->hash,
+ conn->chunk_size - conn->upload_size,
+ conn->chunk_size);
+ conn_upload_retry(conn);
+ }
+ }
+ }
+}
+
+/* Die if we get a bad CURLMcode somewhere */
+static void mcode_or_die(const char *where, CURLMcode code)
+{
+ if (code != CURLM_OK)
+ {
+ const char *s;
+ switch (code)
+ {
+ case CURLM_BAD_HANDLE:
+ s = "CURLM_BAD_HANDLE";
+ break;
+ case CURLM_BAD_EASY_HANDLE:
+ s = "CURLM_BAD_EASY_HANDLE";
+ break;
+ case CURLM_OUT_OF_MEMORY:
+ s = "CURLM_OUT_OF_MEMORY";
+ break;
+ case CURLM_INTERNAL_ERROR:
+ s = "CURLM_INTERNAL_ERROR";
+ break;
+ case CURLM_UNKNOWN_OPTION:
+ s = "CURLM_UNKNOWN_OPTION";
+ break;
+ case CURLM_LAST:
+ s = "CURLM_LAST";
+ break;
+ default:
+ s = "CURLM_unknown";
+ break;
+ case CURLM_BAD_SOCKET:
+ s = "CURLM_BAD_SOCKET";
+ fprintf(stderr, "error: %s returns (%d) %s\n",
+ where, code, s);
+ /* ignore this error */
+ return;
+ }
+ fprintf(stderr, "error: %s returns (%d) %s\n",
+ where, code, s);
+ assert(0);
+ }
+}
+
+/* Called by libev when we get action on a multi socket */
+static void event_cb(EV_P_ struct ev_io *w, int revents)
+{
+ global_io_info *global = (global_io_info*)(w->data);
+ CURLMcode rc;
+
+#if !(OLD_CURL_MULTI)
+ int action = (revents & EV_READ ? CURL_POLL_IN : 0) |
+ (revents & EV_WRITE ? CURL_POLL_OUT : 0);
+
+ do {
+ rc = curl_multi_socket_action(global->multi, w->fd, action,
+ &global->still_running);
+ } while (rc == CURLM_CALL_MULTI_PERFORM);
+#else
+ do {
+ rc = curl_multi_socket(global->multi, w->fd,
+ &global->still_running);
+ } while (rc == CURLM_CALL_MULTI_PERFORM);
+#endif
+ mcode_or_die("error: event_cb: curl_multi_socket_action", rc);
+ check_multi_info(global);
+ if (global->still_running <= 0) {
+ ev_timer_stop(global->loop, &global->timer_event);
+ }
+}
+
+static void remsock(curl_socket_t s, socket_info *fdp, global_io_info *global)
+{
+ if (fdp) {
+ if (fdp->evset) {
+ ev_io_stop(global->loop, &fdp->ev);
+ }
+ free(fdp);
+ }
+}
+
+static void setsock(socket_info *fdp, curl_socket_t s, CURL *easy, int action,
+ global_io_info *global)
+{
+ int kind = (action & CURL_POLL_IN ? (int)(EV_READ) : 0) |
+ (action & CURL_POLL_OUT ? (int)(EV_WRITE) : 0);
+
+ fdp->sockfd = s;
+ fdp->action = action;
+ fdp->easy = easy;
+ if (fdp->evset)
+ ev_io_stop(global->loop, &fdp->ev);
+ ev_io_init(&fdp->ev, event_cb, fdp->sockfd, kind);
+ fdp->ev.data = global;
+ fdp->evset = 1;
+ ev_io_start(global->loop, &fdp->ev);
+}
+
+static void addsock(curl_socket_t s, CURL *easy, int action,
+ global_io_info *global)
+{
+ socket_info *fdp = (socket_info *)(calloc(sizeof(socket_info), 1));
+
+ fdp->global = global;
+ setsock(fdp, s, easy, action, global);
+ curl_multi_assign(global->multi, s, fdp);
+}
+
+static int sock_cb(CURL *easy, curl_socket_t s, int what, void *cbp,
+ void *sockp)
+{
+ global_io_info *global = (global_io_info*)(cbp);
+ socket_info *fdp = (socket_info*)(sockp);
+
+ if (what == CURL_POLL_REMOVE) {
+ remsock(s, fdp, global);
+ } else {
+ if (!fdp) {
+ addsock(s, easy, what, global);
+ } else {
+ setsock(fdp, s, easy, what, global);
+ }
+ }
+ return 0;
+}
+
+/* Called by libev when our timeout expires */
+static void timer_cb(EV_P_ struct ev_timer *w, int revents)
+{
+ global_io_info *io_global = (global_io_info*)(w->data);
+ CURLMcode rc;
+
+#if !(OLD_CURL_MULTI)
+ do {
+ rc = curl_multi_socket_action(io_global->multi,
+ CURL_SOCKET_TIMEOUT, 0,
+ &io_global->still_running);
+ } while (rc == CURLM_CALL_MULTI_PERFORM);
+#else
+ do {
+ rc = curl_multi_socket_all(io_global->multi,
+ &io_global->still_running);
+ } while (rc == CURLM_CALL_MULTI_PERFORM);
+#endif
+ mcode_or_die("timer_cb: curl_multi_socket_action", rc);
+ check_multi_info(io_global);
+}
+
+static connection_info *get_current_connection(global_io_info *global)
+{
+ connection_info *conn = global->current_connection;
+ ulong i;
+
+ if (conn && conn->filled_size < conn->chunk_size)
+ return conn;
+
+ for (i = 0; i < opt_parallel; i++) {
+ conn = global->connections[i];
+ if (conn->chunk_uploaded || conn->filled_size == 0) {
+ global->current_connection = conn;
+ conn_upload_init(conn);
+ return conn;
+ }
+ }
+
+ return NULL;
+}
+
+/* This gets called whenever data is received from the input */
+static void input_cb(EV_P_ struct ev_io *w, int revents)
+{
+ global_io_info *io_global = (global_io_info *)(w->data);
+ connection_info *conn = get_current_connection(io_global);
+
+ if (conn == NULL)
+ return;
+
+ if (conn->filled_size < conn->chunk_size) {
+ if (revents & EV_READ) {
+ ssize_t nbytes = read(io_global->input_fd,
+ conn->buffer + conn->filled_size,
+ conn->chunk_size -
+ conn->filled_size);
+ if (nbytes > 0) {
+ conn->filled_size += nbytes;
+ conn_buffer_updated(conn);
+ } else if (nbytes < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ char error[200];
+ my_strerror(error, sizeof(error),
+ errno);
+ fprintf(stderr, "error: failed to read "
+ "input stream (%s)\n", error);
+ /* failed to read input */
+ exit(1);
+ }
+ } else {
+ io_global->eof = 1;
+ ev_io_stop(io_global->loop, w);
+ }
+ }
+ }
+
+ assert(conn->filled_size <= conn->chunk_size);
+}
+
+static int swift_upload_read_cb(char *ptr, size_t size, size_t nmemb,
+ void *data)
+{
+ size_t realsize;
+
+ connection_info *conn = (connection_info*)(data);
+
+ if (conn->filled_size == conn->upload_size &&
+ conn->upload_size < conn->chunk_size && !conn->global->eof) {
+ ssize_t nbytes;
+ assert(conn->global->current_connection == conn);
+ do {
+ nbytes = read(conn->global->input_fd,
+ conn->buffer + conn->filled_size,
+ conn->chunk_size - conn->filled_size);
+ } while (nbytes == -1 && errno == EAGAIN);
+ if (nbytes > 0) {
+ conn->filled_size += nbytes;
+ conn_buffer_updated(conn);
+ } else {
+ conn->global->eof = 1;
+ }
+ }
+
+ realsize = min(size * nmemb, conn->filled_size - conn->upload_size);
+
+ memcpy(ptr, conn->buffer + conn->upload_size, realsize);
+ conn->upload_size += realsize;
+
+ assert(conn->filled_size <= conn->chunk_size);
+ assert(conn->upload_size <= conn->filled_size);
+
+ return realsize;
+}
+
+static
+size_t upload_header_read_cb(char *ptr, size_t size, size_t nmemb,
+ void *data)
+{
+ connection_info *conn = (connection_info *)(data);
+ char etag[33];
+
+ if (get_http_header("Etag: ", ptr, etag, array_elements(etag))) {
+ if (strcmp(conn->hash, etag) != 0) {
+ fprintf(stderr, "error: ETag mismatch\n");
+ exit(EXIT_FAILURE);
+ }
+ fprintf(stderr, "acked chunk %s\n", etag);
+ conn->chunk_acked = true;
+ }
+
+ return nmemb * size;
+}
+
+static int conn_upload_init(connection_info *conn)
+{
+ conn->filled_size = 0;
+ conn->upload_size = 0;
+ conn->chunk_uploaded = false;
+ conn->chunk_acked = false;
+ conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN;
+ conn->magic_verified = false;
+ conn->chunk_path_len = 0;
+ conn->chunk_type = XB_CHUNK_TYPE_UNKNOWN;
+ conn->payload_size = 0;
+ conn->upload_started = false;
+ conn->retry_count = 0;
+ if (conn->name != NULL) {
+ conn->name[0] = 0;
+ }
+
+ if (conn->easy != NULL) {
+ conn->easy = 0;
+ }
+
+ if (conn->slist != NULL) {
+ curl_slist_free_all(conn->slist);
+ conn->slist = NULL;
+ }
+
+ return 0;
+}
+
+static void conn_upload_prepare(connection_info *conn)
+{
+ gcry_md_hd_t md5;
+
+ gcry_md_open(&md5, GCRY_MD_MD5, 0);
+ gcry_md_write(md5, conn->buffer, conn->chunk_size);
+ hex_md5(gcry_md_read(md5, GCRY_MD_MD5), conn->hash);
+ gcry_md_close(md5);
+}
+
+static int conn_upload_start(connection_info *conn)
+{
+ char token_header[SWIFT_MAX_HDR_SIZE];
+ char object_url[SWIFT_MAX_URL_SIZE];
+ char content_len[200], etag[200];
+ global_io_info *global;
+ CURLMcode rc;
+
+ global = conn->global;
+
+ fprintf(stderr, "uploading chunk %s/%s/%s.%020zu "
+ "(md5: %s, size: %zu)\n",
+ global->container, global->backup_name, conn->name,
+ conn->chunk_no, conn->hash, conn->chunk_size);
+
+ snprintf(object_url, array_elements(object_url), "%s/%s/%s/%s.%020zu",
+ global->url, global->container, global->backup_name,
+ conn->name, conn->chunk_no);
+
+ snprintf(content_len, sizeof(content_len), "Content-Length: %lu",
+ (ulong)(conn->chunk_size));
+
+ snprintf(etag, sizeof(etag), "ETag: %s", conn->hash);
+
+ snprintf(token_header, array_elements(token_header),
+ "X-Auth-Token: %s", global->token);
+
+ conn->slist = curl_slist_append(conn->slist, token_header);
+ conn->slist = curl_slist_append(conn->slist,
+ "Connection: keep-alive");
+ conn->slist = curl_slist_append(conn->slist,
+ "Content-Type: "
+ "application/octet-stream");
+ conn->slist = curl_slist_append(conn->slist, content_len);
+ conn->slist = curl_slist_append(conn->slist, etag);
+
+ conn->easy = curl_easy_init();
+ if (!conn->easy) {
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ return 1;
+ }
+ curl_easy_setopt(conn->easy, CURLOPT_URL, object_url);
+ curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION,
+ swift_upload_read_cb);
+ curl_easy_setopt(conn->easy, CURLOPT_READDATA, conn);
+ curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
+ curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
+ curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L);
+ curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 5L);
+ curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1024L);
+ curl_easy_setopt(conn->easy, CURLOPT_PUT, 1L);
+ curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->slist);
+ curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION,
+ upload_header_read_cb);
+ curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn);
+ curl_easy_setopt(conn->easy, CURLOPT_INFILESIZE,
+ (long) conn->chunk_size);
+ if (opt_cacert != NULL)
+ curl_easy_setopt(conn->easy, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ rc = curl_multi_add_handle(conn->global->multi, conn->easy);
+ mcode_or_die("conn_upload_init: curl_multi_add_handle", rc);
+
+#if (OLD_CURL_MULTI)
+ do {
+ rc = curl_multi_socket_all(global->multi,
+ &global->still_running);
+ } while(rc == CURLM_CALL_MULTI_PERFORM);
+#endif
+
+ conn->upload_started = true;
+
+ return 0;
+}
+
+static void conn_cleanup(connection_info *conn)
+{
+ if (conn) {
+ free(conn->name);
+ free(conn->buffer);
+ if (conn->slist) {
+ curl_slist_free_all(conn->slist);
+ conn->slist = NULL;
+ }
+ if (conn->easy) {
+ curl_easy_cleanup(conn->easy);
+ conn->easy = NULL;
+ }
+ }
+ free(conn);
+}
+
+static void conn_upload_retry(connection_info *conn)
+{
+ /* already closed by cURL */
+ conn->easy = NULL;
+
+ if (conn->slist != NULL) {
+ curl_slist_free_all(conn->slist);
+ conn->slist = NULL;
+ }
+
+ if (conn->retry_count++ > 3) {
+ fprintf(stderr, "error: retry count limit reached\n");
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(stderr, "warning: retrying to upload chunk %zu of '%s'\n",
+ conn->chunk_no, conn->name);
+
+ conn->upload_size = 0;
+
+ conn_upload_start(conn);
+}
+
+static connection_info *conn_new(global_io_info *global, ulong global_idx)
+{
+ connection_info *conn;
+
+ conn = (connection_info *)(calloc(1, sizeof(connection_info)));
+ if (conn == NULL) {
+ goto error;
+ }
+
+ conn->global = global;
+ conn->global_idx = global_idx;
+ conn->buffer_size = SWIFT_CHUNK_SIZE;
+ if ((conn->buffer = (char *)(calloc(conn->buffer_size, 1))) ==
+ NULL) {
+ goto error;
+ }
+
+ return conn;
+
+error:
+ if (conn != NULL) {
+ conn_cleanup(conn);
+ }
+
+ fprintf(stderr, "error: out of memory\n");
+ exit(EXIT_FAILURE);
+
+ return NULL;
+}
+
+/*********************************************************************//**
+Handle input buffer updates. Parse chunk header and set appropriate
+buffer size. */
+static
+void
+conn_buffer_updated(connection_info *conn)
+{
+ bool ready_for_upload = false;
+
+ /* chunk header */
+ if (!conn->magic_verified &&
+ conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN) {
+ if (strncmp(XB_STREAM_CHUNK_MAGIC, conn->buffer,
+ sizeof(XB_STREAM_CHUNK_MAGIC) - 1) != 0) {
+
+ fprintf(stderr, "Error: magic expected\n");
+ exit(EXIT_FAILURE);
+ }
+ conn->magic_verified = true;
+ conn->chunk_path_len = uint4korr(conn->buffer
+ + PATH_LENGTH_OFFSET);
+ conn->chunk_type = (xb_chunk_type_t)
+ (conn->buffer[CHUNK_TYPE_OFFSET]);
+ conn->chunk_size = CHUNK_HEADER_CONSTANT_LEN +
+ conn->chunk_path_len;
+ if (conn->chunk_type != XB_CHUNK_TYPE_EOF) {
+ conn->chunk_size += 16;
+ }
+ }
+
+ /* ordinary chunk */
+ if (conn->magic_verified &&
+ conn->payload_size == 0 &&
+ conn->chunk_type != XB_CHUNK_TYPE_EOF &&
+ conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
+ + conn->chunk_path_len + 16) {
+
+ conn->payload_size = uint8korr(conn->buffer +
+ CHUNK_HEADER_CONSTANT_LEN +
+ conn->chunk_path_len);
+
+ conn->chunk_size = conn->payload_size + 4 + 16 +
+ conn->chunk_path_len +
+ CHUNK_HEADER_CONSTANT_LEN;
+
+ if (conn->name == NULL) {
+ conn->name = (char*)(malloc(conn->chunk_path_len + 1));
+ } else if (conn->name_len < conn->chunk_path_len + 1) {
+ conn->name = (char*)(realloc(conn->name,
+ conn->chunk_path_len + 1));
+ }
+ conn->name_len = conn->chunk_path_len + 1;
+
+ memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
+ conn->chunk_path_len);
+ conn->name[conn->chunk_path_len] = 0;
+
+ if (conn->buffer_size < conn->chunk_size) {
+ conn->buffer =
+ (char *)(realloc(conn->buffer, conn->chunk_size));
+ conn->buffer_size = conn->chunk_size;
+ }
+ }
+
+ /* EOF chunk has no payload */
+ if (conn->magic_verified &&
+ conn->chunk_type == XB_CHUNK_TYPE_EOF &&
+ conn->filled_size >= CHUNK_HEADER_CONSTANT_LEN
+ + conn->chunk_path_len) {
+
+ if (conn->name == NULL) {
+ conn->name = (char*)(malloc(conn->chunk_path_len + 1));
+ } else if (conn->name_len < conn->chunk_path_len + 1) {
+ conn->name = (char*)(realloc(conn->name,
+ conn->chunk_path_len + 1));
+ }
+ conn->name_len = conn->chunk_path_len + 1;
+
+ memcpy(conn->name, conn->buffer + CHUNK_HEADER_CONSTANT_LEN,
+ conn->chunk_path_len);
+ conn->name[conn->chunk_path_len] = 0;
+ }
+
+ if (conn->filled_size > 0 && conn->filled_size == conn->chunk_size) {
+ ready_for_upload = true;
+ }
+
+ /* start upload once recieved the size of the chunk */
+ if (!conn->upload_started && ready_for_upload) {
+ conn->chunk_no = file_chunk_count[conn->name]++;
+ conn_upload_prepare(conn);
+ conn_upload_start(conn);
+ }
+}
+
+static int init_input(global_io_info *io_global)
+{
+ ev_io_init(&io_global->input_event, input_cb, STDIN_FILENO, EV_READ);
+ io_global->input_event.data = io_global;
+ ev_io_start(io_global->loop, &io_global->input_event);
+
+ return 0;
+}
+
+/* Update the event timer after curl_multi library calls */
+static int multi_timer_cb(CURLM *multi, long timeout_ms, global_io_info *global)
+{
+ ev_timer_stop(global->loop, &global->timer_event);
+ if (timeout_ms > 0) {
+ double t = timeout_ms / 1000.0;
+ ev_timer_init(&global->timer_event, timer_cb, t, 0.);
+ ev_timer_start(global->loop, &global->timer_event);
+ } else {
+ timer_cb(global->loop, &global->timer_event, 0);
+ }
+ return 0;
+}
+
+static
+int swift_upload_parts(swift_auth_info *auth, const char *container,
+ const char *name)
+{
+ global_io_info io_global;
+ ulong i;
+#if (OLD_CURL_MULTI)
+ long timeout;
+#endif
+ CURLMcode rc;
+ int n_dirty_buffers;
+
+ memset(&io_global, 0, sizeof(io_global));
+
+ io_global.loop = ev_default_loop(0);
+ init_input(&io_global);
+ io_global.multi = curl_multi_init();
+ ev_timer_init(&io_global.timer_event, timer_cb, 0., 0.);
+ io_global.timer_event.data = &io_global;
+ io_global.connections = (connection_info **)
+ (calloc(opt_parallel, sizeof(connection_info)));
+ io_global.url = auth->url;
+ io_global.container = container;
+ io_global.backup_name = name;
+ io_global.token = auth->token;
+ for (i = 0; i < opt_parallel; i++) {
+ io_global.connections[i] = conn_new(&io_global, i);
+ }
+
+ /* setup the generic multi interface options we want */
+ curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
+ curl_multi_setopt(io_global.multi, CURLMOPT_SOCKETDATA, &io_global);
+#if !(OLD_CURL_MULTI)
+ curl_multi_setopt(io_global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+ curl_multi_setopt(io_global.multi, CURLMOPT_TIMERDATA, &io_global);
+ do {
+ rc = curl_multi_socket_action(io_global.multi,
+ CURL_SOCKET_TIMEOUT, 0,
+ &io_global.still_running);
+ } while (rc == CURLM_CALL_MULTI_PERFORM);
+#else
+ curl_multi_timeout(io_global.multi, &timeout);
+ if (timeout >= 0) {
+ multi_timer_cb(io_global.multi, timeout, &io_global);
+ }
+ do {
+ rc = curl_multi_socket_all(io_global.multi, &io_global.still_running);
+ } while(rc == CURLM_CALL_MULTI_PERFORM);
+#endif
+
+ ev_loop(io_global.loop, 0);
+ check_multi_info(&io_global);
+ curl_multi_cleanup(io_global.multi);
+
+ n_dirty_buffers = 0;
+ for (i = 0; i < opt_parallel; i++) {
+ connection_info *conn = io_global.connections[i];
+ if (conn && conn->upload_size != conn->filled_size) {
+ fprintf(stderr, "error: upload failed: %lu bytes left "
+ "in the buffer %s (uploaded = %d)\n",
+ (ulong)(conn->filled_size - conn->upload_size),
+ conn->name, conn->chunk_uploaded);
+ ++n_dirty_buffers;
+ }
+ }
+
+ for (i = 0; i < opt_parallel; i++) {
+ if (io_global.connections[i] != NULL) {
+ conn_cleanup(io_global.connections[i]);
+ }
+ }
+ free(io_global.connections);
+
+ if (n_dirty_buffers > 0) {
+ return(EXIT_FAILURE);
+ }
+
+ return 0;
+}
+
+struct download_buffer_info {
+ off_t offset;
+ size_t size;
+ size_t result_len;
+ char *buf;
+ curl_read_callback custom_header_callback;
+ void *custom_header_callback_data;
+};
+
+/*********************************************************************//**
+Callback to parse header of GET request on swift contaier. */
+static
+size_t fetch_buffer_header_cb(char *ptr, size_t size, size_t nmemb,
+ void *data)
+{
+ download_buffer_info *buffer_info = (download_buffer_info*)(data);
+ size_t buf_size;
+ char content_length_str[100];
+ char *endptr;
+
+ if (get_http_header("Content-Length: ", ptr,
+ content_length_str, sizeof(content_length_str))) {
+
+ buf_size = strtoull(content_length_str, &endptr, 10);
+
+ if (buffer_info->buf == NULL) {
+ buffer_info->buf = (char*)(malloc(buf_size));
+ buffer_info->size = buf_size;
+ }
+
+ if (buf_size > buffer_info->size) {
+ buffer_info->buf = (char*)
+ (realloc(buffer_info->buf, buf_size));
+ buffer_info->size = buf_size;
+ }
+
+ buffer_info->result_len = buf_size;
+ }
+
+ if (buffer_info->custom_header_callback) {
+ buffer_info->custom_header_callback(ptr, size, nmemb,
+ buffer_info->custom_header_callback_data);
+ }
+
+ return nmemb * size;
+}
+
+/*********************************************************************//**
+Write contents into string buffer */
+static
+size_t
+fetch_buffer_cb(char *buffer, size_t size, size_t nmemb, void *out_buffer)
+{
+ download_buffer_info *buffer_info = (download_buffer_info*)(out_buffer);
+
+ assert(buffer_info->size >= buffer_info->offset + size * nmemb);
+
+ memcpy(buffer_info->buf + buffer_info->offset, buffer, size * nmemb);
+ buffer_info->offset += size * nmemb;
+
+ return size * nmemb;
+}
+
+
+/*********************************************************************//**
+Downloads contents of URL into buffer. Caller is responsible for
+deallocating the buffer.
+@return pointer to a buffer or NULL */
+static
+char *
+swift_fetch_into_buffer(swift_auth_info *auth, const char *url,
+ char **buf, size_t *buf_size, size_t *result_len,
+ curl_read_callback header_callback,
+ void *header_callback_data)
+{
+ char auth_token[SWIFT_MAX_HDR_SIZE];
+ download_buffer_info buffer_info;
+ struct curl_slist *slist = NULL;
+ long http_code;
+ CURL *curl;
+ CURLcode res;
+
+ memset(&buffer_info, 0, sizeof(buffer_info));
+ buffer_info.buf = *buf;
+ buffer_info.size = *buf_size;
+ buffer_info.custom_header_callback = header_callback;
+ buffer_info.custom_header_callback_data = header_callback_data;
+
+ snprintf(auth_token, array_elements(auth_token), "X-Auth-Token: %s",
+ auth->token);
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+ slist = curl_slist_append(slist, auth_token);
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer_info);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
+ fetch_buffer_header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA,
+ &buffer_info);
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr,
+ "error: curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code < 200 || http_code >= 300) {
+ fprintf(stderr, "error: request failed "
+ "with response code: %ld\n", http_code);
+ res = CURLE_LOGIN_DENIED;
+ goto cleanup;
+ }
+ } else {
+ res = CURLE_FAILED_INIT;
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+cleanup:
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ if (res == CURLE_OK) {
+ *buf = buffer_info.buf;
+ *buf_size = buffer_info.size;
+ *result_len = buffer_info.result_len;
+ return(buffer_info.buf);
+ }
+
+ free(buffer_info.buf);
+ *buf = NULL;
+ *buf_size = 0;
+ *result_len = 0;
+
+ return(NULL);
+}
+
+static
+container_list *
+container_list_new()
+{
+ container_list *list =
+ (container_list *)(calloc(1, sizeof(container_list)));
+
+ list->object_count = 1000;
+ list->objects = (object_info*)
+ (calloc(list->object_count, sizeof(object_info)));
+
+ if (list->objects == NULL) {
+ fprintf(stderr, "error: out of memory\n");
+ free(list);
+ return(NULL);
+ }
+
+ return(list);
+}
+
+static
+void
+container_list_free(container_list *list)
+{
+ free(list->content_json);
+ free(list->objects);
+ free(list);
+}
+
+static
+void
+container_list_add_object(container_list *list, const char *name,
+ const char *hash, size_t bytes)
+{
+ const size_t object_count_step = 1000;
+
+ if (list->idx >= list->object_count) {
+ list->objects = (object_info*)
+ realloc(list->objects,
+ (list->object_count + object_count_step) *
+ sizeof(object_info));
+ memset(list->objects + list->object_count, 0,
+ object_count_step * sizeof(object_info));
+ list->object_count += object_count_step;
+ }
+ assert(list->idx <= list->object_count);
+ safe_strcpy(list->objects[list->idx].name,
+ sizeof(list->objects[list->idx].name), name);
+ safe_strcpy(list->objects[list->idx].hash,
+ sizeof(list->objects[list->idx].hash), hash);
+
+ list->objects[list->idx].bytes = bytes;
+ ++list->idx;
+}
+
+
+/*********************************************************************//**
+Tokenize json string. Return array of tokens. Caller is responsoble for
+deallocating the array. */
+jsmntok_t *
+json_tokenise(char *json, size_t len, int initial_tokens)
+{
+ jsmn_parser parser;
+ jsmn_init(&parser);
+
+ unsigned int n = initial_tokens;
+ jsmntok_t *tokens = (jsmntok_t *)(malloc(sizeof(jsmntok_t) * n));
+
+ int ret = jsmn_parse(&parser, json, len, tokens, n);
+
+ while (ret == JSMN_ERROR_NOMEM)
+ {
+ n = n * 2 + 1;
+ tokens = (jsmntok_t*)(realloc(tokens, sizeof(jsmntok_t) * n));
+ ret = jsmn_parse(&parser, json, len, tokens, n);
+ }
+
+ if (ret == JSMN_ERROR_INVAL) {
+ fprintf(stderr, "error: invalid JSON string\n");
+
+ }
+ if (ret == JSMN_ERROR_PART) {
+ fprintf(stderr, "error: truncated JSON string\n");
+ }
+
+ return tokens;
+}
+
+/*********************************************************************//**
+Return true if token representation equal to given string. */
+static
+bool
+json_token_eq(const char *buf, jsmntok_t *t, const char *s)
+{
+ size_t len = strlen(s);
+
+ assert(t->end > t->start);
+
+ return((size_t)(t->end - t->start) == len &&
+ (strncmp(buf + t->start, s, len) == 0));
+}
+
+/*********************************************************************//**
+Copy given token as string. */
+static
+bool
+json_token_str(const char *buf, jsmntok_t *t, char *out, int out_size)
+{
+ size_t len = min(t->end - t->start, out_size - 1);
+
+ memcpy(out, buf + t->start, len);
+ out[len] = 0;
+
+ return(true);
+}
+
+/*********************************************************************//**
+Parse SWIFT container list response and fill output array with values
+sorted by object name. */
+static
+bool
+swift_parse_container_list(container_list *list)
+{
+ enum {MAX_DEPTH=20};
+ enum label_t {NONE, OBJECT};
+
+ char name[SWIFT_MAX_URL_SIZE];
+ char hash[33];
+ char bytes[30];
+ char *response = list->content_json;
+
+ struct stack_t {
+ jsmntok_t *t;
+ int n_items;
+ label_t label;
+ };
+
+ stack_t stack[MAX_DEPTH];
+ jsmntok_t *tokens;
+ int level;
+ size_t count = 0;
+
+ tokens = json_tokenise(list->content_json, list->content_length, 200);
+
+ stack[0].t = &tokens[0];
+ stack[0].label = NONE;
+ stack[0].n_items = 1;
+ level = 0;
+
+ for (size_t i = 0, j = 1; j > 0; i++, j--) {
+ jsmntok_t *t = &tokens[i];
+
+ assert(t->start != -1 && t->end != -1);
+ assert(level >= 0);
+
+ --stack[level].n_items;
+
+ switch (t->type) {
+ case JSMN_ARRAY:
+ case JSMN_OBJECT:
+ if (level < MAX_DEPTH - 1) {
+ level++;
+ }
+ stack[level].t = t;
+ stack[level].label = NONE;
+ if (t->type == JSMN_ARRAY) {
+ stack[level].n_items = t->size;
+ j += t->size;
+ } else {
+ stack[level].n_items = t->size * 2;
+ j += t->size * 2;
+ }
+ break;
+ case JSMN_PRIMITIVE:
+ case JSMN_STRING:
+ if (stack[level].t->type == JSMN_OBJECT &&
+ stack[level].n_items % 2 == 1) {
+ /* key */
+ if (json_token_eq(response, t, "name")) {
+ json_token_str(response, &tokens[i + 1],
+ name, sizeof(name));
+ }
+ if (json_token_eq(response, t, "hash")) {
+ json_token_str(response, &tokens[i + 1],
+ hash, sizeof(hash));
+ }
+ if (json_token_eq(response, t, "bytes")) {
+ json_token_str(response, &tokens[i + 1],
+ bytes, sizeof(bytes));
+ }
+ }
+ break;
+ }
+
+ while (stack[level].n_items == 0 && level > 0) {
+ if (stack[level].t->type == JSMN_OBJECT
+ && level == 2) {
+ char *endptr;
+ container_list_add_object(list, name, hash,
+ strtoull(bytes, &endptr, 10));
+ ++count;
+ }
+ --level;
+ }
+ }
+
+ if (count == 0) {
+ list->final = true;
+ }
+
+ free(tokens);
+
+ return(true);
+}
+
+/*********************************************************************//**
+List swift container with given name. Return list of objects sorted by
+object name. */
+static
+container_list *
+swift_list(swift_auth_info *auth, const char *container, const char *path)
+{
+ container_list *list;
+ char url[SWIFT_MAX_URL_SIZE];
+
+ list = container_list_new();
+
+ while (!list->final) {
+
+ /* download the list in json format */
+ snprintf(url, array_elements(url),
+ "%s/%s?format=json&limit=1000%s%s%s%s",
+ auth->url, container, path ? "&prefix=" : "",
+ path ? path : "", list->idx > 0 ? "&marker=" : "",
+ list->idx > 0 ?
+ list->objects[list->idx - 1].name : "");
+
+ list->content_json = swift_fetch_into_buffer(auth, url,
+ &list->content_json, &list->content_bufsize,
+ &list->content_length, NULL, NULL);
+
+ if (list->content_json == NULL) {
+ container_list_free(list);
+ return(NULL);
+ }
+
+ /* parse downloaded list */
+ if (!swift_parse_container_list(list)) {
+ fprintf(stderr, "error: unable to parse "
+ "container list\n");
+ container_list_free(list);
+ return(NULL);
+ }
+ }
+
+ return(list);
+}
+
+
+/*********************************************************************//**
+Return true if chunk is a part of backup with given name. */
+static
+bool
+chunk_belongs_to(const char *chunk_name, const char *backup_name)
+{
+ size_t backup_name_len = strlen(backup_name);
+
+ return((strlen(chunk_name) > backup_name_len)
+ && (chunk_name[backup_name_len] == '/')
+ && strncmp(chunk_name, backup_name, backup_name_len) == 0);
+}
+
+/*********************************************************************//**
+Return true if chunk is in given list. */
+static
+bool
+chunk_in_list(const char *chunk_name, char **list, int list_size)
+{
+ size_t chunk_name_len;
+
+ if (list_size == 0) {
+ return(true);
+ }
+
+ chunk_name_len = strlen(chunk_name);
+ if (chunk_name_len < 20) {
+ return(false);
+ }
+
+ for (int i = 0; i < list_size; i++) {
+ size_t item_len = strlen(list[i]);
+
+ if ((strncmp(chunk_name - item_len + chunk_name_len - 21,
+ list[i], item_len) == 0)
+ && (chunk_name[chunk_name_len - 21] == '.')
+ && (chunk_name[chunk_name_len - item_len - 22] == '/')) {
+ return(true);
+ }
+ }
+
+ return(false);
+}
+
+static
+int swift_download(swift_auth_info *auth, const char *container,
+ const char *name)
+{
+ container_list *list;
+ char *buf = NULL;
+ size_t buf_size = 0;
+ size_t result_len = 0;
+
+ if ((list = swift_list(auth, container, name)) == NULL) {
+ return(CURLE_FAILED_INIT);
+ }
+
+ for (size_t i = 0; i < list->idx; i++) {
+ const char *chunk_name = list->objects[i].name;
+
+ if (chunk_belongs_to(chunk_name, name)
+ && chunk_in_list(chunk_name, file_list, file_list_size)) {
+ char url[SWIFT_MAX_URL_SIZE];
+
+ snprintf(url, sizeof(url), "%s/%s/%s",
+ auth->url, container, chunk_name);
+
+ if ((buf = swift_fetch_into_buffer(
+ auth, url, &buf, &buf_size, &result_len,
+ NULL, NULL)) == NULL) {
+ fprintf(stderr, "error: failed to download "
+ "chunk %s\n", chunk_name);
+ container_list_free(list);
+ return(CURLE_FAILED_INIT);
+ }
+
+ fwrite(buf, 1, result_len, stdout);
+ }
+ }
+
+ free(buf);
+
+ container_list_free(list);
+
+ return(CURLE_OK);
+}
+
+
+/*********************************************************************//**
+Delete backup with given name from given container.
+@return true if backup deleted successfully */
+static
+bool swift_delete(swift_auth_info *auth, const char *container,
+ const char *name)
+{
+ container_list *list;
+
+ if ((list = swift_list(auth, container, name)) == NULL) {
+ return(CURLE_FAILED_INIT);
+ }
+
+ for (size_t i = 0; i < list->object_count; i++) {
+ const char *chunk_name = list->objects[i].name;
+
+ if (chunk_belongs_to(chunk_name, name)) {
+ char url[SWIFT_MAX_URL_SIZE];
+
+ snprintf(url, sizeof(url), "%s/%s/%s",
+ auth->url, container, chunk_name);
+
+ fprintf(stderr, "delete %s\n", chunk_name);
+ if (!swift_delete_object(auth, url)) {
+ fprintf(stderr, "error: failed to delete "
+ "chunk %s\n", chunk_name);
+ container_list_free(list);
+ return(CURLE_FAILED_INIT);
+ }
+ }
+ }
+
+ container_list_free(list);
+
+ return(CURLE_OK);
+}
+
+/*********************************************************************//**
+Check if backup with given name exists.
+@return true if backup exists */
+static
+bool swift_backup_exists(swift_auth_info *auth, const char *container,
+ const char *backup_name)
+{
+ container_list *list;
+
+ if ((list = swift_list(auth, container, backup_name)) == NULL) {
+ fprintf(stderr, "error: unable to list container %s\n",
+ container);
+ exit(EXIT_FAILURE);
+ }
+
+ for (size_t i = 0; i < list->object_count; i++) {
+ if (chunk_belongs_to(list->objects[i].name, backup_name)) {
+ container_list_free(list);
+ return(true);
+ }
+ }
+
+ container_list_free(list);
+
+ return(false);
+}
+
+/*********************************************************************//**
+Fills auth_info with response from keystone response.
+@return true is response parsed successfully */
+static
+bool
+swift_parse_keystone_response_v2(char *response, size_t response_length,
+ swift_auth_info *auth_info)
+{
+ enum {MAX_DEPTH=20};
+ enum label_t {NONE, ACCESS, CATALOG, ENDPOINTS, TOKEN};
+
+ char filtered_url[SWIFT_MAX_URL_SIZE];
+ char public_url[SWIFT_MAX_URL_SIZE];
+ char region[SWIFT_MAX_URL_SIZE];
+ char id[SWIFT_MAX_URL_SIZE];
+ char token_id[SWIFT_MAX_URL_SIZE];
+ char type[SWIFT_MAX_URL_SIZE];
+
+ struct stack_t {
+ jsmntok_t *t;
+ int n_items;
+ label_t label;
+ };
+
+ stack_t stack[MAX_DEPTH];
+ jsmntok_t *tokens;
+ int level;
+
+ tokens = json_tokenise(response, response_length, 200);
+
+ stack[0].t = &tokens[0];
+ stack[0].label = NONE;
+ stack[0].n_items = 1;
+ level = 0;
+
+ for (size_t i = 0, j = 1; j > 0; i++, j--) {
+ jsmntok_t *t = &tokens[i];
+
+ assert(t->start != -1 && t->end != -1);
+ assert(level >= 0);
+
+ --stack[level].n_items;
+
+ switch (t->type) {
+ case JSMN_ARRAY:
+ case JSMN_OBJECT:
+ if (level < MAX_DEPTH - 1) {
+ level++;
+ }
+ stack[level].t = t;
+ stack[level].label = NONE;
+ if (t->type == JSMN_ARRAY) {
+ stack[level].n_items = t->size;
+ j += t->size;
+ } else {
+ stack[level].n_items = t->size * 2;
+ j += t->size * 2;
+ }
+ break;
+ case JSMN_PRIMITIVE:
+ case JSMN_STRING:
+ if (stack[level].t->type == JSMN_OBJECT &&
+ stack[level].n_items % 2 == 1) {
+ /* key */
+ if (json_token_eq(response, t, "access")) {
+ stack[level].label = ACCESS;
+ }
+ if (json_token_eq(response, t,
+ "serviceCatalog")) {
+ stack[level].label = CATALOG;
+ }
+ if (json_token_eq(response, t, "endpoints")) {
+ stack[level].label = ENDPOINTS;
+ }
+ if (json_token_eq(response, t, "token")) {
+ stack[level].label = TOKEN;
+ }
+ if (json_token_eq(response, t, "id")) {
+ json_token_str(response, &tokens[i + 1],
+ id, sizeof(id));
+ }
+ if (json_token_eq(response, t, "id")
+ && stack[level - 1].label == TOKEN) {
+ json_token_str(response, &tokens[i + 1],
+ token_id, sizeof(token_id));
+ }
+ if (json_token_eq(response, t, "region")) {
+ json_token_str(response, &tokens[i + 1],
+ region, sizeof(region));
+ }
+ if (json_token_eq(response, t, "publicURL")) {
+ json_token_str(response, &tokens[i + 1],
+ public_url, sizeof(public_url));
+ }
+ if (json_token_eq(response, t, "type")) {
+ json_token_str(response, &tokens[i + 1],
+ type, sizeof(type));
+ }
+ }
+ break;
+ }
+
+ while (stack[level].n_items == 0 && level > 0) {
+ if (stack[level].t->type == JSMN_OBJECT
+ && level == 6
+ && stack[level - 1].t->type == JSMN_ARRAY
+ && stack[level - 2].label == ENDPOINTS) {
+ if (opt_swift_region == NULL
+ || strcmp(opt_swift_region, region) == 0) {
+ strncpy(filtered_url, public_url,
+ sizeof(filtered_url));
+ }
+ }
+ if (stack[level].t->type == JSMN_OBJECT &&
+ level == 4 &&
+ stack[level - 1].t->type == JSMN_ARRAY &&
+ stack[level - 2].label == CATALOG) {
+ if (strcmp(type, "object-store") == 0) {
+ strncpy(auth_info->url, filtered_url,
+ sizeof(auth_info->url));
+ }
+ }
+ --level;
+ }
+ }
+
+ free(tokens);
+
+ strncpy(auth_info->token, token_id, sizeof(auth_info->token));
+
+ assert(level == 0);
+
+ if (*auth_info->token == 0) {
+ fprintf(stderr, "error: can not receive token from response\n");
+ return(false);
+ }
+
+ if (*auth_info->url == 0) {
+ fprintf(stderr, "error: can not get URL from response\n");
+ return(false);
+ }
+
+ return(true);
+}
+
+/*********************************************************************//**
+Authenticate against Swift TempAuth. Fills swift_auth_info struct.
+Uses creadentials privided as global variables.
+@returns true if access is granted and token received. */
+static
+bool
+swift_keystone_auth_v2(const char *auth_url, swift_auth_info *info)
+{
+ char tenant_arg[SWIFT_MAX_URL_SIZE];
+ char payload[SWIFT_MAX_URL_SIZE];
+ struct curl_slist *slist = NULL;
+ download_buffer_info buf_info;
+ long http_code;
+ CURLcode res;
+ CURL *curl;
+ bool auth_res = false;
+
+ memset(&buf_info, 0, sizeof(buf_info));
+
+ if (opt_swift_user == NULL) {
+ fprintf(stderr, "error: both --swift-user is required "
+ "for keystone authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_password == NULL) {
+ fprintf(stderr, "error: both --swift-password is required "
+ "for keystone authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_tenant != NULL && opt_swift_tenant_id != NULL) {
+ fprintf(stderr, "error: both --swift-tenant and "
+ "--swift-tenant-id specified for keystone "
+ "authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_tenant != NULL) {
+ snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
+ "tenantName", opt_swift_tenant);
+ } else if (opt_swift_tenant_id != NULL) {
+ snprintf(tenant_arg, sizeof(tenant_arg), ",\"%s\":\"%s\"",
+ "tenantId", opt_swift_tenant_id);
+ } else {
+ *tenant_arg = 0;
+ }
+
+ snprintf(payload, sizeof(payload), "{\"auth\": "
+ "{\"passwordCredentials\": {\"username\":\"%s\","
+ "\"password\":\"%s\"}%s}}",
+ opt_swift_user, opt_swift_password, tenant_arg);
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+
+ slist = curl_slist_append(slist,
+ "Content-Type: application/json");
+ slist = curl_slist_append(slist,
+ "Accept: application/json");
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl, CURLOPT_URL, auth_url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
+ fetch_buffer_header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA,
+ &buf_info);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr,
+ "error: curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code < 200 || http_code >= 300) {
+ fprintf(stderr, "error: request failed "
+ "with response code: %ld\n", http_code);
+ res = CURLE_LOGIN_DENIED;
+ goto cleanup;
+ }
+ } else {
+ res = CURLE_FAILED_INIT;
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+ if (!swift_parse_keystone_response_v2(buf_info.buf,
+ buf_info.size, info)) {
+ goto cleanup;
+ }
+
+ auth_res = true;
+
+cleanup:
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ free(buf_info.buf);
+
+ return(auth_res);
+}
+
+
+/*********************************************************************//**
+Fills auth_info with response from keystone response.
+@return true is response parsed successfully */
+static
+bool
+swift_parse_keystone_response_v3(char *response, size_t response_length,
+ swift_auth_info *auth_info)
+{
+ enum {MAX_DEPTH=20};
+ enum label_t {NONE, TOKEN, CATALOG, ENDPOINTS};
+
+ char url[SWIFT_MAX_URL_SIZE];
+ char filtered_url[SWIFT_MAX_URL_SIZE];
+ char region[SWIFT_MAX_URL_SIZE];
+ char interface[SWIFT_MAX_URL_SIZE];
+ char type[SWIFT_MAX_URL_SIZE];
+
+ struct stack_t {
+ jsmntok_t *t;
+ int n_items;
+ label_t label;
+ };
+
+ stack_t stack[MAX_DEPTH];
+ jsmntok_t *tokens;
+ int level;
+
+ tokens = json_tokenise(response, response_length, 200);
+
+ stack[0].t = &tokens[0];
+ stack[0].label = NONE;
+ stack[0].n_items = 1;
+ level = 0;
+
+ for (size_t i = 0, j = 1; j > 0; i++, j--) {
+ jsmntok_t *t = &tokens[i];
+
+ assert(t->start != -1 && t->end != -1);
+ assert(level >= 0);
+
+ --stack[level].n_items;
+
+ switch (t->type) {
+ case JSMN_ARRAY:
+ case JSMN_OBJECT:
+ if (level < MAX_DEPTH - 1) {
+ level++;
+ }
+ stack[level].t = t;
+ stack[level].label = NONE;
+ if (t->type == JSMN_ARRAY) {
+ stack[level].n_items = t->size;
+ j += t->size;
+ } else {
+ stack[level].n_items = t->size * 2;
+ j += t->size * 2;
+ }
+ break;
+ case JSMN_PRIMITIVE:
+ case JSMN_STRING:
+ if (stack[level].t->type == JSMN_OBJECT &&
+ stack[level].n_items % 2 == 1) {
+ /* key */
+ if (json_token_eq(response, t, "token")) {
+ stack[level].label = TOKEN;
+ fprintf(stderr, "token\n");
+ }
+ if (json_token_eq(response, t,
+ "catalog")) {
+ stack[level].label = CATALOG;
+ fprintf(stderr, "catalog\n");
+ }
+ if (json_token_eq(response, t, "endpoints")) {
+ stack[level].label = ENDPOINTS;
+ }
+ if (json_token_eq(response, t, "region")) {
+ json_token_str(response, &tokens[i + 1],
+ region, sizeof(region));
+ }
+ if (json_token_eq(response, t, "url")) {
+ json_token_str(response, &tokens[i + 1],
+ url, sizeof(url));
+ }
+ if (json_token_eq(response, t, "interface")) {
+ json_token_str(response, &tokens[i + 1],
+ interface, sizeof(interface));
+ }
+ if (json_token_eq(response, t, "type")) {
+ json_token_str(response, &tokens[i + 1],
+ type, sizeof(type));
+ }
+ }
+ break;
+ }
+
+ while (stack[level].n_items == 0 && level > 0) {
+ if (stack[level].t->type == JSMN_OBJECT
+ && level == 6
+ && stack[level - 1].t->type == JSMN_ARRAY
+ && stack[level - 2].label == ENDPOINTS) {
+ if ((opt_swift_region == NULL
+ || strcmp(opt_swift_region, region) == 0)
+ && strcmp(interface, "public") == 0) {
+ strncpy(filtered_url, url,
+ sizeof(filtered_url));
+ }
+ }
+ if (stack[level].t->type == JSMN_OBJECT &&
+ level == 4 &&
+ stack[level - 1].t->type == JSMN_ARRAY &&
+ stack[level - 2].label == CATALOG) {
+ if (strcmp(type, "object-store") == 0) {
+ strncpy(auth_info->url, filtered_url,
+ sizeof(auth_info->url));
+ }
+ }
+ --level;
+ }
+ }
+
+ free(tokens);
+
+ assert(level == 0);
+
+ if (*auth_info->url == 0) {
+ fprintf(stderr, "error: can not get URL from response\n");
+ return(false);
+ }
+
+ return(true);
+}
+
+/*********************************************************************//**
+Captures X-Subject-Token header. */
+static
+size_t keystone_v3_header_cb(char *ptr, size_t size, size_t nmemb, void *data)
+{
+ swift_auth_info *info = (swift_auth_info*)(data);
+
+ get_http_header("X-Subject-Token: ", ptr,
+ info->token, array_elements(info->token));
+
+ return nmemb * size;
+}
+
+/*********************************************************************//**
+Authenticate against Swift TempAuth. Fills swift_auth_info struct.
+Uses creadentials privided as global variables.
+@returns true if access is granted and token received. */
+static
+bool
+swift_keystone_auth_v3(const char *auth_url, swift_auth_info *info)
+{
+ char scope[SWIFT_MAX_URL_SIZE];
+ char domain[SWIFT_MAX_URL_SIZE];
+ char payload[SWIFT_MAX_URL_SIZE];
+ struct curl_slist *slist = NULL;
+ download_buffer_info buf_info;
+ long http_code;
+ CURLcode res;
+ CURL *curl;
+ bool auth_res = false;
+
+ memset(&buf_info, 0, sizeof(buf_info));
+ buf_info.custom_header_callback = keystone_v3_header_cb;
+ buf_info.custom_header_callback_data = info;
+
+ if (opt_swift_user == NULL) {
+ fprintf(stderr, "error: both --swift-user is required "
+ "for keystone authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_password == NULL) {
+ fprintf(stderr, "error: both --swift-password is required "
+ "for keystone authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_project_id != NULL && opt_swift_project != NULL) {
+ fprintf(stderr, "error: both --swift-project and "
+ "--swift-project-id specified for keystone "
+ "authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_domain_id != NULL && opt_swift_domain != NULL) {
+ fprintf(stderr, "error: both --swift-domain and "
+ "--swift-domain-id specified for keystone "
+ "authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_project_id != NULL && opt_swift_domain != NULL) {
+ fprintf(stderr, "error: both --swift-project-id and "
+ "--swift-domain specified for keystone "
+ "authentication.\n");
+ return(false);
+ }
+
+ if (opt_swift_project_id != NULL && opt_swift_domain_id != NULL) {
+ fprintf(stderr, "error: both --swift-project-id and "
+ "--swift-domain-id specified for keystone "
+ "authentication.\n");
+ return(false);
+ }
+
+ scope[0] = 0; domain[0] = 0;
+
+ if (opt_swift_domain != NULL) {
+ snprintf(domain, sizeof(domain),
+ ",{\"domain\":{\"name\":\"%s\"}}",
+ opt_swift_domain);
+ } else if (opt_swift_domain_id != NULL) {
+ snprintf(domain, sizeof(domain),
+ ",{\"domain\":{\"id\":\"%s\"}}",
+ opt_swift_domain_id);
+ }
+
+ if (opt_swift_project_id != NULL) {
+ snprintf(scope, sizeof(scope),
+ ",\"scope\":{\"project\":{\"id\":\"%s\"}}",
+ opt_swift_project_id);
+ } else if (opt_swift_project != NULL) {
+ snprintf(scope, sizeof(scope),
+ ",\"scope\":{\"project\":{\"name\":\"%s\"%s}}",
+ opt_swift_project, domain);
+ }
+
+ snprintf(payload, sizeof(payload), "{\"auth\":{\"identity\":"
+ "{\"methods\":[\"password\"],\"password\":{\"user\":"
+ "{\"name\":\"%s\",\"password\":\"%s\"%s}}}%s}}",
+ opt_swift_user, opt_swift_password,
+ *scope ? "" : ",\"domain\":{\"id\":\"default\"}",
+ scope);
+
+ curl = curl_easy_init();
+
+ if (curl != NULL) {
+
+ slist = curl_slist_append(slist,
+ "Content-Type: application/json");
+ slist = curl_slist_append(slist,
+ "Accept: application/json");
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, opt_verbose);
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl, CURLOPT_URL, auth_url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fetch_buffer_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf_info);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,
+ fetch_buffer_header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA,
+ &buf_info);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+
+ if (opt_cacert != NULL)
+ curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cacert);
+ if (opt_insecure)
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+ res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ fprintf(stderr,
+ "error: curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ goto cleanup;
+ }
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+ if (http_code < 200 || http_code >= 300) {
+ fprintf(stderr, "error: request failed "
+ "with response code: %ld\n", http_code);
+ res = CURLE_LOGIN_DENIED;
+ goto cleanup;
+ }
+ } else {
+ res = CURLE_FAILED_INIT;
+ fprintf(stderr, "error: curl_easy_init() failed\n");
+ goto cleanup;
+ }
+
+ if (!swift_parse_keystone_response_v3(buf_info.buf,
+ buf_info.size, info)) {
+ goto cleanup;
+ }
+
+ auth_res = true;
+
+cleanup:
+ if (slist) {
+ curl_slist_free_all(slist);
+ }
+ if (curl) {
+ curl_easy_cleanup(curl);
+ }
+
+ free(buf_info.buf);
+
+ return(auth_res);
+}
+
+int main(int argc, char **argv)
+{
+ swift_auth_info info;
+ char auth_url[SWIFT_MAX_URL_SIZE];
+
+ MY_INIT(argv[0]);
+
+ /* handle_options in parse_args is destructive so
+ * we make a copy of our argument pointers so we can
+ * mask the sensitive values afterwards */
+ char **mask_argv = (char **)malloc(sizeof(char *) * (argc - 1));
+ memcpy(mask_argv, argv + 1, sizeof(char *) * (argc - 1));
+
+ if (parse_args(argc, argv)) {
+ return(EXIT_FAILURE);
+ }
+
+ mask_args(argc, mask_argv); /* mask args on cmdline */
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+ if (opt_swift_auth_version == NULL || *opt_swift_auth_version == '1') {
+ /* TempAuth */
+ snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sauth/v%s/",
+ opt_swift_auth_url, opt_swift_auth_version ?
+ opt_swift_auth_version : "1.0");
+
+ if (!swift_temp_auth(auth_url, &info)) {
+ fprintf(stderr, "error: failed to authenticate\n");
+ return(EXIT_FAILURE);
+ }
+
+ } else if (*opt_swift_auth_version == '2') {
+ /* Keystone v2 */
+ snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/tokens",
+ opt_swift_auth_url, opt_swift_auth_version);
+
+ if (!swift_keystone_auth_v2(auth_url, &info)) {
+ fprintf(stderr, "error: failed to authenticate\n");
+ return(EXIT_FAILURE);
+ }
+
+ } else if (*opt_swift_auth_version == '3') {
+ /* Keystone v3 */
+ snprintf(auth_url, SWIFT_MAX_URL_SIZE, "%sv%s/auth/tokens",
+ opt_swift_auth_url, opt_swift_auth_version);
+
+ if (!swift_keystone_auth_v3(auth_url, &info)) {
+ fprintf(stderr, "error: failed to authenticate\n");
+ exit(EXIT_FAILURE);
+ }
+
+ }
+
+ if (opt_swift_storage_url != NULL) {
+ snprintf(info.url, sizeof(info.url), "%s",
+ opt_swift_storage_url);
+ }
+
+ fprintf(stderr, "Object store URL: %s\n", info.url);
+
+ if (opt_mode == MODE_PUT) {
+
+ if (swift_create_container(&info, opt_swift_container) != 0) {
+ fprintf(stderr, "error: failed to create "
+ "container %s\n",
+ opt_swift_container);
+ return(EXIT_FAILURE);
+ }
+
+ if (swift_backup_exists(&info, opt_swift_container, opt_name)) {
+ fprintf(stderr, "error: backup named '%s' "
+ "already exists!\n",
+ opt_name);
+ return(EXIT_FAILURE);
+ }
+
+ if (swift_upload_parts(&info, opt_swift_container,
+ opt_name) != 0) {
+ fprintf(stderr, "error: upload failed\n");
+ return(EXIT_FAILURE);
+ }
+
+ } else if (opt_mode == MODE_GET) {
+
+ if (swift_download(&info, opt_swift_container, opt_name)
+ != CURLE_OK) {
+ fprintf(stderr, "error: download failed\n");
+ return(EXIT_FAILURE);
+ }
+
+ } else if (opt_mode == MODE_DELETE) {
+
+ if (swift_delete(&info, opt_swift_container, opt_name)
+ != CURLE_OK) {
+ fprintf(stderr, "error: delete failed\n");
+ return(EXIT_FAILURE);
+ }
+
+ } else {
+ fprintf(stderr, "Unknown command supplied.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ curl_global_cleanup();
+
+ return(EXIT_SUCCESS);
+}
diff --git a/extra/mariabackup/xbstream.cc b/extra/mariabackup/xbstream.cc
new file mode 100644
index 00000000..6306806b
--- /dev/null
+++ b/extra/mariabackup/xbstream.cc
@@ -0,0 +1,560 @@
+/******************************************************
+Copyright (c) 2011-2013 Percona LLC and/or its affiliates.
+
+The xbstream utility: serialize/deserialize files in the XBSTREAM format.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <my_getopt.h>
+#include <hash.h>
+#include <my_pthread.h>
+#include "common.h"
+#include "xbstream.h"
+#include "datasink.h"
+
+#define XBSTREAM_VERSION "1.0"
+#define XBSTREAM_BUFFER_SIZE (10 * 1024 * 1024UL)
+
+#define START_FILE_HASH_SIZE 16
+
+typedef enum {
+ RUN_MODE_NONE,
+ RUN_MODE_CREATE,
+ RUN_MODE_EXTRACT
+} run_mode_t;
+
+/* Need the following definitions to avoid linking with ds_*.o and their link
+dependencies */
+datasink_t datasink_archive;
+datasink_t datasink_xbstream;
+datasink_t datasink_compress;
+datasink_t datasink_tmpfile;
+
+static run_mode_t opt_mode;
+static char * opt_directory = NULL;
+static my_bool opt_verbose = 0;
+static int opt_parallel = 1;
+
+static struct my_option my_long_options[] =
+{
+ {"help", '?', "Display this help and exit.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"create", 'c', "Stream the specified files to the standard output.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"extract", 'x', "Extract to disk files from the stream on the "
+ "standard input.",
+ 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"directory", 'C', "Change the current directory to the specified one "
+ "before streaming or extracting.", &opt_directory, &opt_directory, 0,
+ GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+ {"verbose", 'v', "Print verbose output.", &opt_verbose, &opt_verbose,
+ 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"parallel", 'p', "Number of worker threads for reading / writing.",
+ &opt_parallel, &opt_parallel, 0, GET_INT, REQUIRED_ARG,
+ 1, 1, INT_MAX, 0, 0, 0},
+
+ {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+typedef struct {
+ HASH *filehash;
+ xb_rstream_t *stream;
+ ds_ctxt_t *ds_ctxt;
+ pthread_mutex_t *mutex;
+} extract_ctxt_t;
+
+typedef struct {
+ char *path;
+ uint pathlen;
+ my_off_t offset;
+ ds_file_t *file;
+ pthread_mutex_t mutex;
+} file_entry_t;
+
+static int get_options(int *argc, char ***argv);
+static int mode_create(int argc, char **argv);
+static int mode_extract(int n_threads, int argc, char **argv);
+static my_bool get_one_option(const struct my_option *opt,
+ const char *argument, const char *filename);
+
+int
+main(int argc, char **argv)
+{
+ MY_INIT(argv[0]);
+
+ if (get_options(&argc, &argv)) {
+ goto err;
+ }
+
+ if (opt_mode == RUN_MODE_NONE) {
+ msg("%s: either -c or -x must be specified.", my_progname);
+ goto err;
+ }
+
+ /* Change the current directory if -C is specified */
+ if (opt_directory && my_setwd(opt_directory, MYF(MY_WME))) {
+ goto err;
+ }
+
+ if (opt_mode == RUN_MODE_CREATE && mode_create(argc, argv)) {
+ goto err;
+ } else if (opt_mode == RUN_MODE_EXTRACT &&
+ mode_extract(opt_parallel, argc, argv)) {
+ goto err;
+ }
+
+ my_cleanup_options(my_long_options);
+
+ my_end(0);
+
+ return EXIT_SUCCESS;
+err:
+ my_cleanup_options(my_long_options);
+
+ my_end(0);
+
+ exit(EXIT_FAILURE);
+}
+
+static
+int
+get_options(int *argc, char ***argv)
+{
+ int ho_error;
+
+ if ((ho_error= handle_options(argc, argv, my_long_options,
+ get_one_option)))
+ {
+ exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
+
+static
+void
+print_version(void)
+{
+ printf("%s Ver %s for %s (%s)\n", my_progname, XBSTREAM_VERSION,
+ SYSTEM_TYPE, MACHINE_TYPE);
+}
+
+static
+void
+usage(void)
+{
+ print_version();
+ puts("Copyright (C) 2011-2013 Percona LLC and/or its affiliates.");
+ puts("This software comes with ABSOLUTELY NO WARRANTY. "
+ "This is free software,\nand you are welcome to modify and "
+ "redistribute it under the GPL license.\n");
+
+ puts("Serialize/deserialize files in the XBSTREAM format.\n");
+
+ puts("Usage: ");
+ printf(" %s -c [OPTIONS...] FILES... # stream specified files to "
+ "standard output.\n", my_progname);
+ printf(" %s -x [OPTIONS...] # extract files from the stream"
+ "on the standard input.\n", my_progname);
+
+ puts("\nOptions:");
+ my_print_help(my_long_options);
+}
+
+static
+int
+set_run_mode(run_mode_t mode)
+{
+ if (opt_mode != RUN_MODE_NONE) {
+ msg("%s: can't set specify both -c and -x.", my_progname);
+ return 1;
+ }
+
+ opt_mode = mode;
+
+ return 0;
+}
+
+static
+my_bool
+get_one_option(const struct my_option *opt, const char *, const char *)
+{
+ switch (opt->id) {
+ case 'c':
+ if (set_run_mode(RUN_MODE_CREATE)) {
+ return TRUE;
+ }
+ break;
+ case 'x':
+ if (set_run_mode(RUN_MODE_EXTRACT)) {
+ return TRUE;
+ }
+ break;
+ case '?':
+ usage();
+ exit(0);
+ }
+
+ return FALSE;
+}
+
+static
+int
+stream_one_file(File file, xb_wstream_file_t *xbfile)
+{
+ uchar *buf;
+ ssize_t bytes;
+ my_off_t offset;
+
+ posix_fadvise(file, 0, 0, POSIX_FADV_SEQUENTIAL);
+ offset = my_tell(file, MYF(MY_WME));
+
+ buf = (uchar*)(my_malloc(PSI_NOT_INSTRUMENTED, XBSTREAM_BUFFER_SIZE, MYF(MY_FAE)));
+
+ while ((bytes = (ssize_t)my_read(file, buf, XBSTREAM_BUFFER_SIZE,
+ MYF(MY_WME))) > 0) {
+ if (xb_stream_write_data(xbfile, buf, bytes)) {
+ msg("%s: xb_stream_write_data() failed.",
+ my_progname);
+ my_free(buf);
+ return 1;
+ }
+ posix_fadvise(file, offset, XBSTREAM_BUFFER_SIZE,
+ POSIX_FADV_DONTNEED);
+ offset += XBSTREAM_BUFFER_SIZE;
+
+ }
+
+ my_free(buf);
+
+ if (bytes < 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static
+int
+mode_create(int argc, char **argv)
+{
+ int i;
+ MY_STAT mystat;
+ xb_wstream_t *stream;
+
+ if (argc < 1) {
+ msg("%s: no files are specified.", my_progname);
+ return 1;
+ }
+
+ stream = xb_stream_write_new();
+ if (stream == NULL) {
+ msg("%s: xb_stream_write_new() failed.", my_progname);
+ return 1;
+ }
+
+ for (i = 0; i < argc; i++) {
+ char *filepath = argv[i];
+ File src_file;
+ xb_wstream_file_t *file;
+
+ if (my_stat(filepath, &mystat, MYF(MY_WME)) == NULL) {
+ goto err;
+ }
+ if (!MY_S_ISREG(mystat.st_mode)) {
+ msg("%s: %s is not a regular file, exiting.",
+ my_progname, filepath);
+ goto err;
+ }
+
+ if ((src_file = my_open(filepath, O_RDONLY, MYF(MY_WME))) < 0) {
+ msg("%s: failed to open %s.", my_progname, filepath);
+ goto err;
+ }
+
+ file = xb_stream_write_open(stream, filepath, &mystat, NULL, NULL);
+ if (file == NULL) {
+ goto err;
+ }
+
+ if (opt_verbose) {
+ msg("%s", filepath);
+ }
+
+ if (stream_one_file(src_file, file) ||
+ xb_stream_write_close(file) ||
+ my_close(src_file, MYF(MY_WME))) {
+ goto err;
+ }
+ }
+
+ xb_stream_write_done(stream);
+
+ return 0;
+err:
+ xb_stream_write_done(stream);
+
+ return 1;
+}
+
+static
+file_entry_t *
+file_entry_new(extract_ctxt_t *ctxt, const char *path, uint pathlen)
+{
+ file_entry_t *entry;
+ ds_file_t *file;
+
+ entry = (file_entry_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(file_entry_t),
+ MYF(MY_WME | MY_ZEROFILL));
+ if (entry == NULL) {
+ return NULL;
+ }
+
+ entry->path = my_strndup(PSI_NOT_INSTRUMENTED, path, pathlen, MYF(MY_WME));
+ if (entry->path == NULL) {
+ goto err;
+ }
+ entry->pathlen = pathlen;
+
+ file = ds_open(ctxt->ds_ctxt, path, NULL);
+
+ if (file == NULL) {
+ msg("%s: failed to create file.", my_progname);
+ goto err;
+ }
+
+ if (opt_verbose) {
+ msg("%s", entry->path);
+ }
+
+ entry->file = file;
+
+ pthread_mutex_init(&entry->mutex, NULL);
+
+ return entry;
+
+err:
+ if (entry->path != NULL) {
+ my_free(entry->path);
+ }
+ my_free(entry);
+
+ return NULL;
+}
+
+static
+uchar *
+get_file_entry_key(file_entry_t *entry, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length = entry->pathlen;
+ return (uchar *) entry->path;
+}
+
+static
+void
+file_entry_free(file_entry_t *entry)
+{
+ pthread_mutex_destroy(&entry->mutex);
+ ds_close(entry->file);
+ my_free(entry->path);
+ my_free(entry);
+}
+
+static
+void *
+extract_worker_thread_func(void *arg)
+{
+ xb_rstream_chunk_t chunk;
+ file_entry_t *entry;
+ xb_rstream_result_t res;
+
+ extract_ctxt_t *ctxt = (extract_ctxt_t *) arg;
+
+ my_thread_init();
+
+ memset(&chunk, 0, sizeof(chunk));
+
+ while (1) {
+
+ pthread_mutex_lock(ctxt->mutex);
+ res = xb_stream_read_chunk(ctxt->stream, &chunk);
+
+ if (res != XB_STREAM_READ_CHUNK) {
+ pthread_mutex_unlock(ctxt->mutex);
+ break;
+ }
+
+ /* If unknown type and ignorable flag is set, skip this chunk */
+ if (chunk.type == XB_CHUNK_TYPE_UNKNOWN && \
+ !(chunk.flags & XB_STREAM_FLAG_IGNORABLE)) {
+ pthread_mutex_unlock(ctxt->mutex);
+ continue;
+ }
+
+ /* See if we already have this file open */
+ entry = (file_entry_t *) my_hash_search(ctxt->filehash,
+ (uchar *) chunk.path,
+ chunk.pathlen);
+
+ if (entry == NULL) {
+ entry = file_entry_new(ctxt,
+ chunk.path,
+ chunk.pathlen);
+ if (entry == NULL) {
+ pthread_mutex_unlock(ctxt->mutex);
+ break;
+ }
+ if (my_hash_insert(ctxt->filehash, (uchar *) entry)) {
+ msg("%s: my_hash_insert() failed.",
+ my_progname);
+ pthread_mutex_unlock(ctxt->mutex);
+ break;
+ }
+ }
+
+ pthread_mutex_lock(&entry->mutex);
+
+ pthread_mutex_unlock(ctxt->mutex);
+
+ res = xb_stream_validate_checksum(&chunk);
+
+ if (res != XB_STREAM_READ_CHUNK) {
+ pthread_mutex_unlock(&entry->mutex);
+ break;
+ }
+
+ if (chunk.type == XB_CHUNK_TYPE_EOF) {
+ pthread_mutex_unlock(&entry->mutex);
+ pthread_mutex_lock(ctxt->mutex);
+ my_hash_delete(ctxt->filehash, (uchar *) entry);
+ pthread_mutex_unlock(ctxt->mutex);
+
+ continue;
+ }
+
+ if (entry->offset != chunk.offset) {
+ msg("%s: out-of-order chunk: real offset = 0x%llx, "
+ "expected offset = 0x%llx", my_progname,
+ chunk.offset, entry->offset);
+ pthread_mutex_unlock(&entry->mutex);
+ res = XB_STREAM_READ_ERROR;
+ break;
+ }
+
+ if (ds_write(entry->file, chunk.data, chunk.length)) {
+ msg("%s: my_write() failed.", my_progname);
+ pthread_mutex_unlock(&entry->mutex);
+ res = XB_STREAM_READ_ERROR;
+ break;
+ }
+
+ entry->offset += chunk.length;
+
+ pthread_mutex_unlock(&entry->mutex);
+ }
+
+ if (chunk.data)
+ my_free(chunk.data);
+
+ my_thread_end();
+
+ return (void *)(res);
+}
+
+
+static
+int
+mode_extract(int n_threads, int argc __attribute__((unused)),
+ char **argv __attribute__((unused)))
+{
+ xb_rstream_t *stream = NULL;
+ HASH filehash;
+ ds_ctxt_t *ds_ctxt = NULL;
+ extract_ctxt_t ctxt;
+ int i;
+ pthread_t *tids = NULL;
+ void **retvals = NULL;
+ pthread_mutex_t mutex;
+ int ret = 0;
+
+ if (my_hash_init(PSI_NOT_INSTRUMENTED, &filehash, &my_charset_bin,
+ START_FILE_HASH_SIZE, 0, 0, (my_hash_get_key) get_file_entry_key,
+ (my_hash_free_key) file_entry_free, MYF(0))) {
+ msg("%s: failed to initialize file hash.", my_progname);
+ return 1;
+ }
+
+ if (pthread_mutex_init(&mutex, NULL)) {
+ msg("%s: failed to initialize mutex.", my_progname);
+ my_hash_free(&filehash);
+ return 1;
+ }
+
+ /* If --directory is specified, it is already set as CWD by now. */
+ ds_ctxt = ds_create(".", DS_TYPE_LOCAL);
+ if (ds_ctxt == NULL) {
+ ret = 1;
+ goto exit;
+ }
+
+
+ stream = xb_stream_read_new();
+ if (stream == NULL) {
+ msg("%s: xb_stream_read_new() failed.", my_progname);
+ pthread_mutex_destroy(&mutex);
+ ret = 1;
+ goto exit;
+ }
+
+ ctxt.stream = stream;
+ ctxt.filehash = &filehash;
+ ctxt.ds_ctxt = ds_ctxt;
+ ctxt.mutex = &mutex;
+
+ tids = (pthread_t *)calloc(n_threads, sizeof(pthread_t));
+ retvals = (void **)calloc(n_threads, sizeof(void*));
+
+ for (i = 0; i < n_threads; i++)
+ pthread_create(tids + i, NULL, extract_worker_thread_func,
+ &ctxt);
+
+ for (i = 0; i < n_threads; i++)
+ pthread_join(tids[i], retvals + i);
+
+ for (i = 0; i < n_threads; i++) {
+ if ((size_t)retvals[i] == XB_STREAM_READ_ERROR) {
+ ret = 1;
+ goto exit;
+ }
+ }
+
+exit:
+ pthread_mutex_destroy(&mutex);
+
+ free(tids);
+ free(retvals);
+
+ my_hash_free(&filehash);
+ if (ds_ctxt != NULL) {
+ ds_destroy(ds_ctxt);
+ }
+ xb_stream_read_done(stream);
+
+ return ret;
+}
diff --git a/extra/mariabackup/xbstream.h b/extra/mariabackup/xbstream.h
new file mode 100644
index 00000000..1b36ec24
--- /dev/null
+++ b/extra/mariabackup/xbstream.h
@@ -0,0 +1,106 @@
+/******************************************************
+Copyright (c) 2011-2017 Percona LLC and/or its affiliates.
+
+The xbstream format interface.
+
+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
+
+*******************************************************/
+
+#ifndef XBSTREAM_H
+#define XBSTREAM_H
+
+#include <my_base.h>
+
+/* Magic value in a chunk header */
+#define XB_STREAM_CHUNK_MAGIC "XBSTCK01"
+
+/* Chunk flags */
+/* Chunk can be ignored if unknown version/format */
+#define XB_STREAM_FLAG_IGNORABLE 0x01
+
+/* Magic + flags + type + path len */
+#define CHUNK_HEADER_CONSTANT_LEN ((sizeof(XB_STREAM_CHUNK_MAGIC) - 1) + \
+ 1 + 1 + 4)
+#define CHUNK_TYPE_OFFSET (sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1)
+#define PATH_LENGTH_OFFSET (sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1)
+
+typedef struct xb_wstream_struct xb_wstream_t;
+
+typedef struct xb_wstream_file_struct xb_wstream_file_t;
+
+typedef enum {
+ XB_STREAM_FMT_NONE,
+ XB_STREAM_FMT_XBSTREAM
+} xb_stream_fmt_t;
+
+/************************************************************************
+Write interface. */
+
+typedef ssize_t xb_stream_write_callback(xb_wstream_file_t *file,
+ void *userdata,
+ const void *buf, size_t len);
+
+xb_wstream_t *xb_stream_write_new(void);
+
+xb_wstream_file_t *xb_stream_write_open(xb_wstream_t *stream, const char *path,
+ MY_STAT *mystat, void *userdata,
+ xb_stream_write_callback *onwrite);
+
+int xb_stream_write_data(xb_wstream_file_t *file, const void *buf, size_t len);
+
+int xb_stream_write_close(xb_wstream_file_t *file);
+
+int xb_stream_write_done(xb_wstream_t *stream);
+
+/************************************************************************
+Read interface. */
+
+typedef enum {
+ XB_STREAM_READ_CHUNK,
+ XB_STREAM_READ_EOF,
+ XB_STREAM_READ_ERROR
+} xb_rstream_result_t;
+
+typedef enum {
+ XB_CHUNK_TYPE_UNKNOWN = '\0',
+ XB_CHUNK_TYPE_PAYLOAD = 'P',
+ XB_CHUNK_TYPE_EOF = 'E'
+} xb_chunk_type_t;
+
+typedef struct xb_rstream_struct xb_rstream_t;
+
+typedef struct {
+ uchar flags;
+ xb_chunk_type_t type;
+ uint pathlen;
+ char path[FN_REFLEN];
+ size_t length;
+ my_off_t offset;
+ my_off_t checksum_offset;
+ void *data;
+ ulong checksum;
+ size_t buflen;
+} xb_rstream_chunk_t;
+
+xb_rstream_t *xb_stream_read_new(void);
+
+xb_rstream_result_t xb_stream_read_chunk(xb_rstream_t *stream,
+ xb_rstream_chunk_t *chunk);
+
+int xb_stream_read_done(xb_rstream_t *stream);
+
+xb_rstream_result_t xb_stream_validate_checksum(xb_rstream_chunk_t *chunk);
+
+#endif
diff --git a/extra/mariabackup/xbstream_read.cc b/extra/mariabackup/xbstream_read.cc
new file mode 100644
index 00000000..b54a9815
--- /dev/null
+++ b/extra/mariabackup/xbstream_read.cc
@@ -0,0 +1,226 @@
+/******************************************************
+Copyright (c) 2011-2017 Percona LLC and/or its affiliates.
+
+The xbstream format reader implementation.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <zlib.h>
+#include "common.h"
+#include "xbstream.h"
+
+/* Allocate 1 MB for the payload buffer initially */
+#define INIT_BUFFER_LEN (1024 * 1024)
+
+#ifndef MY_OFF_T_MAX
+#define MY_OFF_T_MAX (~(my_off_t)0UL)
+#endif
+
+struct xb_rstream_struct {
+ my_off_t offset;
+ File fd;
+};
+
+xb_rstream_t *
+xb_stream_read_new(void)
+{
+ xb_rstream_t *stream;
+
+ stream = (xb_rstream_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(xb_rstream_t), MYF(MY_FAE));
+
+#ifdef _WIN32
+ setmode(fileno(stdin), _O_BINARY);
+#endif
+
+ stream->fd = my_fileno(stdin);
+ stream->offset = 0;
+
+ return stream;
+}
+
+static inline
+xb_chunk_type_t
+validate_chunk_type(uchar code)
+{
+ switch ((xb_chunk_type_t) code) {
+ case XB_CHUNK_TYPE_PAYLOAD:
+ case XB_CHUNK_TYPE_EOF:
+ return (xb_chunk_type_t) code;
+ default:
+ return XB_CHUNK_TYPE_UNKNOWN;
+ }
+}
+
+xb_rstream_result_t
+xb_stream_validate_checksum(xb_rstream_chunk_t *chunk)
+{
+ ulong checksum;
+ checksum = my_checksum(0, chunk->data, chunk->length);
+ if (checksum != chunk->checksum) {
+ msg("xb_stream_read_chunk(): invalid checksum at offset "
+ "0x%llx: expected 0x%lx, read 0x%lx.",
+ (ulonglong) chunk->checksum_offset, chunk->checksum,
+ checksum);
+ return XB_STREAM_READ_ERROR;
+ }
+
+ return XB_STREAM_READ_CHUNK;
+}
+
+#define F_READ(buf,len) \
+ do { \
+ if (xb_read_full(fd, (uchar *)buf, len) < len) { \
+ msg("xb_stream_read_chunk(): my_read() failed."); \
+ goto err; \
+ } \
+ } while (0)
+
+xb_rstream_result_t
+xb_stream_read_chunk(xb_rstream_t *stream, xb_rstream_chunk_t *chunk)
+{
+ uchar tmpbuf[16];
+ uchar *ptr = tmpbuf;
+ uint pathlen;
+ size_t tbytes;
+ ulonglong ullval;
+ File fd = stream->fd;
+
+ xb_ad(sizeof(tmpbuf) >= CHUNK_HEADER_CONSTANT_LEN);
+
+ /* This is the only place where we expect EOF, so read with
+ xb_read_full() rather than F_READ() */
+ tbytes = xb_read_full(fd, ptr, CHUNK_HEADER_CONSTANT_LEN);
+ if (tbytes == 0) {
+ return XB_STREAM_READ_EOF;
+ } else if (tbytes < CHUNK_HEADER_CONSTANT_LEN) {
+ msg("xb_stream_read_chunk(): unexpected end of stream at "
+ "offset 0x%llx.", stream->offset);
+ goto err;
+ }
+
+ ptr = tmpbuf;
+
+ /* Chunk magic value */
+ if (memcmp(tmpbuf, XB_STREAM_CHUNK_MAGIC, 8)) {
+ msg("xb_stream_read_chunk(): wrong chunk magic at offset "
+ "0x%llx.", (ulonglong) stream->offset);
+ goto err;
+ }
+ ptr += 8;
+ stream->offset += 8;
+
+ /* Chunk flags */
+ chunk->flags = *ptr++;
+ stream->offset++;
+
+ /* Chunk type, ignore unknown ones if ignorable flag is set */
+ chunk->type = validate_chunk_type(*ptr);
+ if (chunk->type == XB_CHUNK_TYPE_UNKNOWN &&
+ !(chunk->flags & XB_STREAM_FLAG_IGNORABLE)) {
+ msg("xb_stream_read_chunk(): unknown chunk type 0x%lu at "
+ "offset 0x%llx.", (ulong) *ptr,
+ (ulonglong) stream->offset);
+ goto err;
+ }
+ ptr++;
+ stream->offset++;
+
+ /* Path length */
+ pathlen = uint4korr(ptr);
+ if (pathlen >= FN_REFLEN) {
+ msg("xb_stream_read_chunk(): path length (%lu) is too large at "
+ "offset 0x%llx.", (ulong) pathlen, stream->offset);
+ goto err;
+ }
+ chunk->pathlen = pathlen;
+ stream->offset +=4;
+
+ xb_ad((ptr + 4 - tmpbuf) == CHUNK_HEADER_CONSTANT_LEN);
+
+ /* Path */
+ if (chunk->pathlen > 0) {
+ F_READ((uchar *) chunk->path, pathlen);
+ stream->offset += pathlen;
+ }
+ chunk->path[pathlen] = '\0';
+
+ if (chunk->type == XB_CHUNK_TYPE_EOF) {
+ return XB_STREAM_READ_CHUNK;
+ }
+
+ /* Payload length */
+ F_READ(tmpbuf, 16);
+ ullval = uint8korr(tmpbuf);
+ if (ullval > (ulonglong) SIZE_T_MAX) {
+ msg("xb_stream_read_chunk(): chunk length is too large at "
+ "offset 0x%llx: 0x%llx.", (ulonglong) stream->offset,
+ ullval);
+ goto err;
+ }
+ chunk->length = (size_t) ullval;
+ stream->offset += 8;
+
+ /* Payload offset */
+ ullval = uint8korr(tmpbuf + 8);
+ if (ullval > (ulonglong) MY_OFF_T_MAX) {
+ msg("xb_stream_read_chunk(): chunk offset is too large at "
+ "offset 0x%llx: 0x%llx.", (ulonglong) stream->offset,
+ ullval);
+ goto err;
+ }
+ chunk->offset = (my_off_t) ullval;
+ stream->offset += 8;
+
+ /* Reallocate the buffer if needed */
+ if (chunk->length > chunk->buflen) {
+ chunk->data = my_realloc(PSI_NOT_INSTRUMENTED, chunk->data, chunk->length,
+ MYF(MY_WME | MY_ALLOW_ZERO_PTR));
+ if (chunk->data == NULL) {
+ msg("xb_stream_read_chunk(): failed to increase buffer "
+ "to %lu bytes.", (ulong) chunk->length);
+ goto err;
+ }
+ chunk->buflen = chunk->length;
+ }
+
+ /* Checksum */
+ F_READ(tmpbuf, 4);
+ chunk->checksum = uint4korr(tmpbuf);
+ chunk->checksum_offset = stream->offset;
+
+ /* Payload */
+ if (chunk->length > 0) {
+ F_READ(chunk->data, chunk->length);
+ stream->offset += chunk->length;
+ }
+
+ stream->offset += 4;
+
+ return XB_STREAM_READ_CHUNK;
+
+err:
+ return XB_STREAM_READ_ERROR;
+}
+
+int
+xb_stream_read_done(xb_rstream_t *stream)
+{
+ my_free(stream);
+
+ return 0;
+}
diff --git a/extra/mariabackup/xbstream_write.cc b/extra/mariabackup/xbstream_write.cc
new file mode 100644
index 00000000..5801e867
--- /dev/null
+++ b/extra/mariabackup/xbstream_write.cc
@@ -0,0 +1,293 @@
+/******************************************************
+Copyright (c) 2011-2017 Percona LLC and/or its affiliates.
+
+The xbstream format writer implementation.
+
+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
+
+*******************************************************/
+
+#include <my_global.h>
+#include <my_base.h>
+#include <zlib.h>
+#include "common.h"
+#include "xbstream.h"
+
+/* Group writes smaller than this into a single chunk */
+#define XB_STREAM_MIN_CHUNK_SIZE (10 * 1024 * 1024)
+
+struct xb_wstream_struct {
+ pthread_mutex_t mutex;
+};
+
+struct xb_wstream_file_struct {
+ xb_wstream_t *stream;
+ char *path;
+ size_t path_len;
+ char chunk[XB_STREAM_MIN_CHUNK_SIZE];
+ char *chunk_ptr;
+ size_t chunk_free;
+ my_off_t offset;
+ void *userdata;
+ xb_stream_write_callback *write;
+};
+
+static int xb_stream_flush(xb_wstream_file_t *file);
+static int xb_stream_write_chunk(xb_wstream_file_t *file,
+ const void *buf, size_t len);
+static int xb_stream_write_eof(xb_wstream_file_t *file);
+
+static
+ssize_t
+xb_stream_default_write_callback(xb_wstream_file_t *file __attribute__((unused)),
+ void *userdata __attribute__((unused)),
+ const void *buf, size_t len)
+{
+ if (my_write(my_fileno(stdout), (const uchar *)buf, len, MYF(MY_WME | MY_NABP)))
+ return -1;
+ return len;
+}
+
+xb_wstream_t *
+xb_stream_write_new(void)
+{
+ xb_wstream_t *stream;
+
+ stream = (xb_wstream_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(xb_wstream_t), MYF(MY_FAE));
+ pthread_mutex_init(&stream->mutex, NULL);
+
+ return stream;;
+}
+
+xb_wstream_file_t *
+xb_stream_write_open(xb_wstream_t *stream, const char *path,
+ MY_STAT *mystat __attribute__((unused)),
+ void *userdata,
+ xb_stream_write_callback *onwrite)
+{
+ xb_wstream_file_t *file;
+ size_t path_len;
+
+ path_len = strlen(path);
+
+ if (path_len > FN_REFLEN) {
+ msg("xb_stream_write_open(): file path is too long.");
+ return NULL;
+ }
+
+ file = (xb_wstream_file_t *) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(xb_wstream_file_t) +
+ path_len + 1, MYF(MY_FAE));
+
+ file->path = (char *) (file + 1);
+#ifdef _WIN32
+ /* Normalize path on Windows, so we can restore elsewhere.*/
+ {
+ int i;
+ for (i = 0; ; i++) {
+ file->path[i] = (path[i] == '\\') ? '/' : path[i];
+ if (!path[i])
+ break;
+ }
+ }
+#else
+ memcpy(file->path, path, path_len + 1);
+#endif
+ file->path_len = path_len;
+
+ file->stream = stream;
+ file->offset = 0;
+ file->chunk_ptr = file->chunk;
+ file->chunk_free = XB_STREAM_MIN_CHUNK_SIZE;
+ if (onwrite) {
+#ifdef _WIN32
+ setmode(fileno(stdout), _O_BINARY);
+#endif
+ file->userdata = userdata;
+ file->write = onwrite;
+ } else {
+ file->userdata = NULL;
+ file->write = xb_stream_default_write_callback;
+ }
+
+ return file;
+}
+
+int
+xb_stream_write_data(xb_wstream_file_t *file, const void *buf, size_t len)
+{
+ if (len < file->chunk_free) {
+ memcpy(file->chunk_ptr, buf, len);
+ file->chunk_ptr += len;
+ file->chunk_free -= len;
+
+ return 0;
+ }
+
+ if (xb_stream_flush(file))
+ return 1;
+
+ return xb_stream_write_chunk(file, buf, len);
+}
+
+int
+xb_stream_write_close(xb_wstream_file_t *file)
+{
+ if (xb_stream_flush(file) ||
+ xb_stream_write_eof(file)) {
+ my_free(file);
+ return 1;
+ }
+
+ my_free(file);
+
+ return 0;
+}
+
+int
+xb_stream_write_done(xb_wstream_t *stream)
+{
+ pthread_mutex_destroy(&stream->mutex);
+
+ my_free(stream);
+
+ return 0;
+}
+
+static
+int
+xb_stream_flush(xb_wstream_file_t *file)
+{
+ if (file->chunk_ptr == file->chunk) {
+ return 0;
+ }
+
+ if (xb_stream_write_chunk(file, file->chunk,
+ file->chunk_ptr - file->chunk)) {
+ return 1;
+ }
+
+ file->chunk_ptr = file->chunk;
+ file->chunk_free = XB_STREAM_MIN_CHUNK_SIZE;
+
+ return 0;
+}
+
+static
+int
+xb_stream_write_chunk(xb_wstream_file_t *file, const void *buf, size_t len)
+{
+ /* Chunk magic + flags + chunk type + path_len + path + len + offset +
+ checksum */
+ uchar tmpbuf[sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1 + 4 +
+ FN_REFLEN + 8 + 8 + 4];
+ uchar *ptr;
+ xb_wstream_t *stream = file->stream;
+ ulong checksum;
+
+ /* Write xbstream header */
+ ptr = tmpbuf;
+
+ /* Chunk magic */
+ memcpy(ptr, XB_STREAM_CHUNK_MAGIC, sizeof(XB_STREAM_CHUNK_MAGIC) - 1);
+ ptr += sizeof(XB_STREAM_CHUNK_MAGIC) - 1;
+
+ *ptr++ = 0; /* Chunk flags */
+
+ *ptr++ = (uchar) XB_CHUNK_TYPE_PAYLOAD; /* Chunk type */
+
+ int4store(ptr, file->path_len); /* Path length */
+ ptr += 4;
+
+ memcpy(ptr, file->path, file->path_len); /* Path */
+ ptr += file->path_len;
+
+ int8store(ptr, len); /* Payload length */
+ ptr += 8;
+
+ checksum = my_checksum(0, buf, len);
+
+ pthread_mutex_lock(&stream->mutex);
+
+ int8store(ptr, file->offset); /* Payload offset */
+ ptr += 8;
+
+ int4store(ptr, checksum);
+ ptr += 4;
+
+ xb_ad(ptr <= tmpbuf + sizeof(tmpbuf));
+
+ if (file->write(file, file->userdata, tmpbuf, ptr-tmpbuf) == -1)
+ goto err;
+
+
+ if (file->write(file, file->userdata, buf, len) == -1) /* Payload */
+ goto err;
+
+ file->offset+= len;
+
+ pthread_mutex_unlock(&stream->mutex);
+
+ return 0;
+
+err:
+
+ pthread_mutex_unlock(&stream->mutex);
+
+ return 1;
+}
+
+static
+int
+xb_stream_write_eof(xb_wstream_file_t *file)
+{
+ /* Chunk magic + flags + chunk type + path_len + path */
+ uchar tmpbuf[sizeof(XB_STREAM_CHUNK_MAGIC) - 1 + 1 + 1 + 4 +
+ FN_REFLEN];
+ uchar *ptr;
+ xb_wstream_t *stream = file->stream;
+
+ pthread_mutex_lock(&stream->mutex);
+
+ /* Write xbstream header */
+ ptr = tmpbuf;
+
+ /* Chunk magic */
+ memcpy(ptr, XB_STREAM_CHUNK_MAGIC, sizeof(XB_STREAM_CHUNK_MAGIC) - 1);
+ ptr += sizeof(XB_STREAM_CHUNK_MAGIC) - 1;
+
+ *ptr++ = 0; /* Chunk flags */
+
+ *ptr++ = (uchar) XB_CHUNK_TYPE_EOF; /* Chunk type */
+
+ int4store(ptr, file->path_len); /* Path length */
+ ptr += 4;
+
+ memcpy(ptr, file->path, file->path_len); /* Path */
+ ptr += file->path_len;
+
+ xb_ad(ptr <= tmpbuf + sizeof(tmpbuf));
+
+ if (file->write(file, file->userdata, tmpbuf,
+ (ulonglong) (ptr - tmpbuf)) == -1)
+ goto err;
+
+ pthread_mutex_unlock(&stream->mutex);
+
+ return 0;
+err:
+
+ pthread_mutex_unlock(&stream->mutex);
+
+ return 1;
+}
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
diff --git a/extra/mariabackup/xtrabackup.h b/extra/mariabackup/xtrabackup.h
new file mode 100644
index 00000000..53784a3f
--- /dev/null
+++ b/extra/mariabackup/xtrabackup.h
@@ -0,0 +1,302 @@
+/******************************************************
+Copyright (c) 2011-2015 Percona LLC and/or its affiliates.
+
+Declarations for xtrabackup.cc
+
+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
+
+*******************************************************/
+
+#ifndef XB_XTRABACKUP_H
+#define XB_XTRABACKUP_H
+
+#include <my_getopt.h>
+#include "datasink.h"
+#include "xbstream.h"
+#include "changed_page_bitmap.h"
+#include <set>
+
+#define XB_TOOL_NAME "mariadb-backup"
+#define XB_HISTORY_TABLE "mysql.mariadb_backup_history"
+
+struct xb_delta_info_t
+{
+ xb_delta_info_t(ulint page_size, ulint zip_size, uint32_t space_id)
+ : page_size(page_size), zip_size(zip_size), space_id(space_id) {}
+
+ ulint page_size;
+ ulint zip_size;
+ uint32_t space_id;
+};
+
+class CorruptedPages
+{
+public:
+ CorruptedPages();
+ ~CorruptedPages();
+ void add_page(const char *file_name, page_id_t page_id);
+ bool contains(page_id_t page_id) const;
+ void drop_space(uint32_t space_id);
+ void rename_space(uint32_t space_id, const std::string &new_name);
+ bool print_to_file(ds_ctxt *ds_data, const char *file_name) const;
+ void read_from_file(const char *file_name);
+ bool empty() const;
+ void zero_out_free_pages();
+
+ void backup_fix_ddl(ds_ctxt *ds_data, ds_ctxt *ds_meta);
+
+private:
+ void add_page_no_lock(const char *space_name, page_id_t page_id,
+ bool convert_space_name);
+ struct space_info_t {
+ std::string space_name;
+ std::set<uint32_t> pages;
+ };
+ typedef std::map<uint32_t, space_info_t> container_t;
+ mutable pthread_mutex_t m_mutex;
+ container_t m_spaces;
+};
+
+
+/* value of the --incremental option */
+extern lsn_t incremental_lsn;
+
+extern char *xtrabackup_target_dir;
+extern char *xtrabackup_incremental_dir;
+extern char *xtrabackup_incremental_basedir;
+extern char *innobase_data_home_dir;
+extern char *innobase_buffer_pool_filename;
+extern char *aria_log_dir_path;
+extern char *xb_plugin_dir;
+extern char *xb_rocksdb_datadir;
+extern my_bool xb_backup_rocksdb;
+
+extern uint opt_protocol;
+
+extern xb_page_bitmap *changed_page_bitmap;
+
+extern char *xtrabackup_incremental;
+extern my_bool xtrabackup_incremental_force_scan;
+
+extern lsn_t metadata_to_lsn;
+
+extern xb_stream_fmt_t xtrabackup_stream_fmt;
+extern ibool xtrabackup_stream;
+
+extern char *xtrabackup_tables;
+extern char *xtrabackup_tables_file;
+extern char *xtrabackup_databases;
+extern char *xtrabackup_databases_file;
+extern char *xtrabackup_tables_exclude;
+extern char *xtrabackup_databases_exclude;
+
+extern uint xtrabackup_compress;
+
+extern my_bool xtrabackup_backup;
+extern my_bool xtrabackup_prepare;
+extern my_bool xtrabackup_copy_back;
+extern my_bool xtrabackup_move_back;
+extern my_bool xtrabackup_decrypt_decompress;
+
+extern char *innobase_data_file_path;
+extern longlong innobase_page_size;
+
+extern int xtrabackup_parallel;
+
+extern my_bool xb_close_files;
+extern const char *xtrabackup_compress_alg;
+
+extern uint xtrabackup_compress_threads;
+extern ulonglong xtrabackup_compress_chunk_size;
+
+extern my_bool xtrabackup_export;
+extern char *xtrabackup_extra_lsndir;
+extern ulint xtrabackup_log_copy_interval;
+extern char *xtrabackup_stream_str;
+extern long xtrabackup_throttle;
+extern longlong xtrabackup_use_memory;
+
+extern my_bool opt_galera_info;
+extern my_bool opt_slave_info;
+extern my_bool opt_no_lock;
+extern my_bool opt_safe_slave_backup;
+extern my_bool opt_rsync;
+extern my_bool opt_force_non_empty_dirs;
+extern my_bool opt_noversioncheck;
+extern my_bool opt_no_backup_locks;
+extern my_bool opt_decompress;
+extern my_bool opt_remove_original;
+extern my_bool opt_extended_validation;
+extern my_bool opt_encrypted_backup;
+extern my_bool opt_lock_ddl_per_table;
+extern my_bool opt_log_innodb_page_corruption;
+
+extern char *opt_incremental_history_name;
+extern char *opt_incremental_history_uuid;
+
+extern char *opt_user;
+extern const char *opt_password;
+extern char *opt_host;
+extern char *opt_defaults_group;
+extern char *opt_socket;
+extern uint opt_port;
+extern char *opt_log_bin;
+
+extern const char *query_type_names[];
+
+enum query_type_t {QUERY_TYPE_ALL, QUERY_TYPE_UPDATE,
+ QUERY_TYPE_SELECT};
+
+extern TYPELIB query_type_typelib;
+
+extern ulong opt_lock_wait_query_type;
+extern ulong opt_kill_long_query_type;
+
+extern uint opt_kill_long_queries_timeout;
+extern uint opt_lock_wait_timeout;
+extern uint opt_lock_wait_threshold;
+extern uint opt_debug_sleep_before_unlock;
+extern uint opt_safe_slave_backup_timeout;
+
+extern const char *opt_history;
+
+enum binlog_info_enum { BINLOG_INFO_OFF, BINLOG_INFO_ON,
+ BINLOG_INFO_AUTO};
+
+extern ulong opt_binlog_info;
+
+extern ulong xtrabackup_innodb_force_recovery;
+
+void xtrabackup_io_throttling(void);
+my_bool xb_write_delta_metadata(ds_ctxt *ds_meta,
+ const char *filename,
+ const xb_delta_info_t *info);
+
+/************************************************************************
+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 */
+
+
+/************************************************************************
+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. */
+);
+
+/************************************************************************
+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);
+
+#if defined(HAVE_OPENSSL)
+extern my_bool opt_ssl_verify_server_cert;
+#endif
+
+
+my_bool
+xb_get_one_option(int optid,
+ const struct my_option *opt __attribute__((unused)),
+ char *argument);
+
+const char*
+xb_get_copy_action(const char *dflt = "Copying");
+
+void mdl_lock_init();
+void mdl_lock_table(ulint space_id);
+void mdl_unlock_all();
+bool ends_with(const char *str, const char *suffix);
+
+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);
+void register_ignore_db_dirs_filter(const char *name);
+
+#ifdef _WIN32
+typedef HANDLE os_file_dir_t; /*!< directory stream */
+/** 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
+@retval INVALID_HANDLE_VALUE on error */
+HANDLE os_file_opendir(const char *dirname);
+# define os_file_closedir(dir) static_cast<void>(FindClose(dir))
+# define os_file_closedir_failed(dir) !FindClose(dir)
+#else
+typedef DIR* os_file_dir_t;
+# define os_file_opendir(dirname) opendir(dirname)
+# define os_file_closedir(dir) static_cast<void>(closedir(dir))
+# define os_file_closedir_failed(dir) closedir(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);
+
+/***********************************************************************//**
+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 */
+
+#ifndef DBUG_OFF
+#include <fil0fil.h>
+extern void dbug_mariabackup_event(const char *event,
+ const fil_space_t::name_type key);
+
+#define DBUG_MARIABACKUP_EVENT(A, B) \
+ DBUG_EXECUTE_IF("mariabackup_events", dbug_mariabackup_event(A, B);)
+#else
+#define DBUG_MARIABACKUP_EVENT(A, B) /* empty */
+#endif // DBUG_OFF
+
+#endif /* XB_XTRABACKUP_H */