2018-03-21 22:29:00 +00:00
|
|
|
/******************************************************
|
|
|
|
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
|
2019-05-11 22:19:05 +03:00
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
2018-03-21 22:29:00 +00:00
|
|
|
|
|
|
|
*******************************************************/
|
|
|
|
|
2018-03-28 17:06:27 +02:00
|
|
|
#include <my_global.h>
|
2018-03-21 22:29:00 +00:00
|
|
|
#include <my_base.h>
|
|
|
|
#include <mysys_err.h>
|
|
|
|
#include "common.h"
|
|
|
|
#include "datasink.h"
|
|
|
|
#include "fsp0fsp.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <winioctl.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);
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
datasink_t datasink_local = {
|
|
|
|
&local_init,
|
|
|
|
&local_open,
|
|
|
|
&local_write,
|
|
|
|
&local_close,
|
|
|
|
&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);
|
2018-05-29 23:00:51 +02:00
|
|
|
my_error(EE_CANT_MKDIR, MYF(ME_BELL),
|
2018-03-21 22:29:00 +00:00
|
|
|
root, my_errno,errbuf, my_errno);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctxt = (ds_ctxt_t *)my_malloc(sizeof(ds_ctxt_t), MYF(MY_FAE));
|
|
|
|
|
|
|
|
ctxt->root = my_strdup(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);
|
2018-05-29 23:00:51 +02:00
|
|
|
my_error(EE_CANT_MKDIR, MYF(ME_BELL),
|
2018-03-21 22:29:00 +00:00
|
|
|
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(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;
|
|
|
|
if (my_seek(fd, hole_bytes, MY_SEEK_CUR, MYF(MY_WME | MY_NABP))
|
|
|
|
== MY_FILEPOS_ERROR)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
ulint flags = mach_read_from_4(&buf[FIL_PAGE_DATA + FSP_SPACE_FLAGS]);
|
|
|
|
ulint ssize = FSP_FLAGS_GET_PAGE_SSIZE(flags);
|
|
|
|
local_file->pagesize= ssize == 0 ? UNIV_PAGE_SIZE_ORIG : ((UNIV_ZIP_SIZE_MIN >> 1) << ssize);
|
MDEV-18644: Support full_crc32 for page_compressed
This is a follow-up task to MDEV-12026, which introduced
innodb_checksum_algorithm=full_crc32 and a simpler page format.
MDEV-12026 did not enable full_crc32 for page_compressed tables,
which we will be doing now.
This is joint work with Thirunarayanan Balathandayuthapani.
For innodb_checksum_algorithm=full_crc32 we change the
page_compressed format as follows:
FIL_PAGE_TYPE: The most significant bit will be set to indicate
page_compressed format. The least significant bits will contain
the compressed page size, rounded up to a multiple of 256 bytes.
The checksum will be stored in the last 4 bytes of the page
(whether it is the full page or a page_compressed page whose
size is determined by FIL_PAGE_TYPE), covering all preceding
bytes of the page. If encryption is used, then the page will
be encrypted between compression and computing the checksum.
For page_compressed, FIL_PAGE_LSN will not be repeated at
the end of the page.
FSP_SPACE_FLAGS (already implemented as part of MDEV-12026):
We will store the innodb_compression_algorithm that may be used
to compress pages. Previously, the choice of algorithm was written
to each compressed data page separately, and one would be unable
to know in advance which compression algorithm(s) are used.
fil_space_t::full_crc32_page_compressed_len(): Determine if the
page_compressed algorithm of the tablespace needs to know the
exact length of the compressed data. If yes, we will reserve and
write an extra byte for this right before the checksum.
buf_page_is_compressed(): Determine if a page uses page_compressed
(in any innodb_checksum_algorithm).
fil_page_decompress(): Pass also fil_space_t::flags so that the
format can be determined.
buf_page_is_zeroes(): Check if a page is full of zero bytes.
buf_page_full_crc32_is_corrupted(): Renamed from
buf_encrypted_full_crc32_page_is_corrupted(). For full_crc32,
we always simply validate the checksum to the page contents,
while the physical page size is explicitly specified by an
unencrypted part of the page header.
buf_page_full_crc32_size(): Determine the size of a full_crc32 page.
buf_dblwr_check_page_lsn(): Make this a debug-only function, because
it involves potentially costly lookups of fil_space_t.
create_table_info_t::check_table_options(),
ha_innobase::check_if_supported_inplace_alter(): Do allow the creation
of SPATIAL INDEX with full_crc32 also when page_compressed is used.
commit_cache_norebuild(): Preserve the compression algorithm when
updating the page_compression_level.
dict_tf_to_fsp_flags(): Set the flags for page compression algorithm.
FIXME: Maybe there should be a table option page_compression_algorithm
and a session variable to back it?
2019-03-18 14:08:43 +02:00
|
|
|
local_file->compressed = fil_space_t::full_crc32(flags)
|
|
|
|
? fil_space_t::is_compressed(flags)
|
|
|
|
: bool(FSP_FLAGS_HAS_PAGE_COMPRESSION(flags));
|
2018-03-21 22:29:00 +00:00
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|