mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
516 lines
14 KiB
C++
516 lines
14 KiB
C++
/******************************************************
|
|
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);
|
|
MSAN_STAT_WORKAROUND(&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);
|
|
}
|
|
|
|
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);
|
|
|
|
return(XB_FIL_CUR_SUCCESS);
|
|
}
|
|
|
|
/* Stack usage 131224 with clang */
|
|
PRAGMA_DISABLE_CHECK_STACK_FRAME
|
|
|
|
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(false, 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(false, 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(false, tmp_page,
|
|
space->flags));
|
|
}
|
|
|
|
return buf_page_is_corrupted(false, page, space->flags);
|
|
}
|
|
PRAGMA_REENABLE_CHECK_STACK_FRAME
|
|
|
|
/** 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 */
|
|
{
|
|
aligned_free(cursor->buf);
|
|
cursor->buf = NULL;
|
|
|
|
if (cursor->node != NULL) {
|
|
xb_fil_node_close_file(cursor->node);
|
|
cursor->file = OS_FILE_CLOSED;
|
|
}
|
|
}
|