mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
5d9ebef41e
recognize *#i#* files in mariadb-backup
2349 lines
54 KiB
C++
2349 lines
54 KiB
C++
/******************************************************
|
|
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 <my_config.h>
|
|
#include <unireg.h>
|
|
#include <datadict.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
|
|
#include <functional>
|
|
|
|
#ifdef _WIN32
|
|
#include <aclapi.h>
|
|
#endif
|
|
|
|
#ifdef MYSQL_CLIENT
|
|
#define WAS_MYSQL_CLIENT 1
|
|
#undef MYSQL_CLIENT
|
|
#endif
|
|
|
|
#include "table.h"
|
|
|
|
#ifdef WAS_MYSQL_CLIENT
|
|
#define MYSQL_CLIENT 1
|
|
#undef WAS_MYSQL_CLIENT
|
|
#endif
|
|
|
|
#define ROCKSDB_BACKUP_DIR "#rocksdb"
|
|
|
|
/* 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_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;
|
|
};
|
|
|
|
/************************************************************************
|
|
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. */
|
|
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 */
|
|
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);
|
|
}
|
|
|
|
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. */
|
|
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);
|
|
}
|
|
|
|
// TODO: the code can be used to find storage engine of partitions
|
|
/*
|
|
static
|
|
bool is_aria_frm_or_par(const char *path) {
|
|
if (!ends_with(path, ".frm") && !ends_with(path, ".par"))
|
|
return false;
|
|
|
|
const char *frm_path = path;
|
|
if (ends_with(path, ".par")) {
|
|
size_t frm_path_len = strlen(path);
|
|
DBUG_ASSERT(frm_path_len > strlen("frm"));
|
|
frm_path = strdup(path);
|
|
strcpy(const_cast<char *>(frm_path) + frm_path_len - strlen("frm"), "frm");
|
|
}
|
|
|
|
bool result = false;
|
|
File file;
|
|
uchar header[40];
|
|
legacy_db_type dbt;
|
|
|
|
if ((file= mysql_file_open(key_file_frm, frm_path, O_RDONLY | O_SHARE, MYF(0)))
|
|
< 0)
|
|
goto err;
|
|
|
|
if (mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP)))
|
|
goto err;
|
|
|
|
if (!strncmp((char*) header, "TYPE=VIEW\n", 10))
|
|
goto err;
|
|
|
|
if (!is_binary_frm_header(header))
|
|
goto err;
|
|
|
|
dbt = (legacy_db_type)header[3];
|
|
|
|
if (dbt == DB_TYPE_ARIA) {
|
|
result = true;
|
|
}
|
|
else if (dbt == DB_TYPE_PARTITION_DB) {
|
|
MY_STAT state;
|
|
uchar *frm_image= 0;
|
|
// uint n_length;
|
|
|
|
if (mysql_file_fstat(file, &state, MYF(MY_WME)))
|
|
goto err;
|
|
|
|
if (mysql_file_seek(file, 0, SEEK_SET, MYF(MY_WME)))
|
|
goto err;
|
|
|
|
if (read_string(file, &frm_image, (size_t)state.st_size))
|
|
goto err;
|
|
|
|
dbt = (legacy_db_type)frm_image[61];
|
|
if (dbt == DB_TYPE_ARIA) {
|
|
result = true;
|
|
}
|
|
my_free(frm_image);
|
|
}
|
|
|
|
err:
|
|
if (file >= 0)
|
|
mysql_file_close(file, MYF(MY_WME));
|
|
if (frm_path != path)
|
|
free(const_cast<char *>(frm_path));
|
|
return result;
|
|
}
|
|
*/
|
|
|
|
void parse_db_table_from_file_path(
|
|
const char *filepath, char *dbname, char *tablename) {
|
|
dbname[0] = '\0';
|
|
tablename[0] = '\0';
|
|
const char *dbname_start = nullptr;
|
|
const char *tablename_start = filepath;
|
|
const char *const_ptr;
|
|
while ((const_ptr = strchr(tablename_start, FN_LIBCHAR)) != NULL) {
|
|
dbname_start = tablename_start;
|
|
tablename_start = const_ptr + 1;
|
|
}
|
|
if (!dbname_start)
|
|
return;
|
|
size_t dbname_len = tablename_start - dbname_start - 1;
|
|
if (dbname_len >= FN_REFLEN)
|
|
dbname_len = FN_REFLEN-1;
|
|
strmake(dbname, dbname_start, dbname_len);
|
|
strmake(tablename, tablename_start, FN_REFLEN-1);
|
|
char *ptr;
|
|
if ((ptr = strchr(tablename, '.')))
|
|
*ptr = '\0';
|
|
if ((ptr = strstr(tablename, "#P#")))
|
|
*ptr = '\0';
|
|
if ((ptr = strstr(tablename, "#i#")))
|
|
*ptr = '\0';
|
|
}
|
|
|
|
bool is_system_table(const char *dbname, const char *tablename)
|
|
{
|
|
DBUG_ASSERT(dbname);
|
|
DBUG_ASSERT(tablename);
|
|
|
|
Lex_ident_db lex_dbname;
|
|
Lex_ident_table lex_tablename;
|
|
lex_dbname.str = dbname;
|
|
lex_dbname.length = strlen(dbname);
|
|
lex_tablename.str = tablename;
|
|
lex_tablename.length = strlen(tablename);
|
|
|
|
TABLE_CATEGORY tg = get_table_category(lex_dbname, lex_tablename);
|
|
|
|
return (tg == TABLE_CATEGORY_LOG) || (tg == TABLE_CATEGORY_SYSTEM);
|
|
}
|
|
|
|
/************************************************************************
|
|
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", ".TRG", ".TRN", ".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);
|
|
}
|
|
|
|
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,
|
|
bool rewrite)
|
|
{
|
|
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 = convert_dst(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, rewrite);
|
|
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);
|
|
}
|
|
|
|
|
|
bool
|
|
backup_files(ds_ctxt *ds_data, const char *from)
|
|
{
|
|
datadir_iter_t *it;
|
|
datadir_node_t node;
|
|
bool ret = true;
|
|
msg("Starting to backup non-InnoDB tables and files");
|
|
datadir_node_init(&node);
|
|
it = datadir_iter_new(from);
|
|
while (datadir_iter_next(it, &node)) {
|
|
if (!node.is_empty_dir) {
|
|
ret = datafile_copy_backup(ds_data, node.filepath, 1);
|
|
if (!ret) {
|
|
msg("Failed to copy file %s", node.filepath);
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* 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;
|
|
}
|
|
}
|
|
}
|
|
msg("Finished backing up non-InnoDB tables and files");
|
|
out:
|
|
datadir_iter_free(it);
|
|
datadir_node_free(&node);
|
|
return(ret);
|
|
}
|
|
|
|
/** Release resources after backup_files() */
|
|
void backup_release()
|
|
{
|
|
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_files() and backup_release() */
|
|
bool backup_finish(ds_ctxt *ds_data)
|
|
{
|
|
/* Copy buffer pool dump or LRU dump */
|
|
if (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, MB_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[] = {MB_BINLOG_INFO,
|
|
MB_GALERA_INFO,
|
|
XTRABACKUP_DONOR_GALERA_INFO,
|
|
MB_SLAVE_INFO,
|
|
MB_INFO,
|
|
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, std::function<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);
|
|
}
|
|
|
|
/* 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 LOG_FILE_NAME, which we must copy
|
|
if it exists. */
|
|
|
|
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",
|
|
MB_BINLOG_INFO,
|
|
MB_METADATA_FILENAME,
|
|
XTRABACKUP_BINLOG_INFO,
|
|
XTRABACKUP_METADATA_FILENAME,
|
|
".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;
|
|
|
|
/* 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);
|
|
|
|
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.
|
|
*/
|
|
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))
|
|
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;
|
|
}
|
|
|
|
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
|
|
{
|
|
strmake(to_path_full, to_path, sizeof(to_path_full)-1);
|
|
}
|
|
#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];
|
|
|
|
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);
|
|
}
|
|
|
|
void foreach_file_in_db_dirs(
|
|
const char *dir_path, std::function<bool(const char *)> func) {
|
|
DBUG_ASSERT(dir_path);
|
|
|
|
datadir_iter_t *it;
|
|
datadir_node_t node;
|
|
|
|
datadir_node_init(&node);
|
|
it = datadir_iter_new(dir_path);
|
|
|
|
while (datadir_iter_next(it, &node))
|
|
if (!node.is_empty_dir && !func(node.filepath))
|
|
break;
|
|
|
|
datadir_iter_free(it);
|
|
datadir_node_free(&node);
|
|
}
|
|
|
|
void foreach_file_in_datadir(
|
|
const char *dir_path, std::function<bool(const char *)> func)
|
|
{
|
|
DBUG_ASSERT(dir_path);
|
|
os_file_dir_t dir = os_file_opendir(dir_path);
|
|
os_file_stat_t info;
|
|
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, IF_WIN('\\', '/'));
|
|
if (!pname)
|
|
pname = info.name;
|
|
if (!func(pname))
|
|
break;
|
|
}
|
|
os_file_closedir(dir);
|
|
}
|