Skip to content

Commit

Permalink
MDEV-19781 Add page id matching check in innochecksum tool
Browse files Browse the repository at this point in the history
Added the condition in innochecksum tool to check page id mismatch.
This could catch the write corruption caused by InnoDB.

Added the debug insert inside fil_io() to check whether it writes
the page to wrong offset.
  • Loading branch information
Thirunarayanan committed Jun 28, 2019
1 parent f7a4a87 commit e4a0dbf
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 15 deletions.
101 changes: 88 additions & 13 deletions extra/innochecksum.cc
Expand Up @@ -100,6 +100,8 @@ ulong srv_page_size;
page_size_t univ_page_size(0, 0, false);
/* Current page number (0 based). */
unsigned long long cur_page_num;
/* Current space. */
unsigned long long cur_space;
/* Skip the checksum verification. */
static bool no_check;
/* Enabled for strict checksum verification. */
Expand Down Expand Up @@ -451,6 +453,27 @@ ulong read_file(
return bytes;
}

/** Check whether the page contains all zeroes.
@param[in] buf page
@param[in] size physical size of the page
@return true if the page is all zeroes; else false */
static bool is_page_all_zeroes(
byte* buf,
ulint size)
{
/* On pages that are not all zero, the page number
must match. */
const ulint* p = reinterpret_cast<const ulint*>(buf);
const ulint* const end = reinterpret_cast<const ulint*>(buf + size);
do {
if (*p++) {
return false;
}
} while (p != end);

return true;
}

/** Check if page is corrupted or not.
@param[in] buf page frame
@param[in] page_size page size
Expand All @@ -462,10 +485,10 @@ ulong read_file(
static
bool
is_page_corrupted(
byte* buf,
byte* buf,
const page_size_t& page_size,
bool is_encrypted,
bool is_compressed)
bool is_encrypted,
bool is_compressed)
{

/* enable if page is corrupted. */
Expand All @@ -478,6 +501,24 @@ is_page_corrupted(
ulint space_id = mach_read_from_4(
buf + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID);

if (mach_read_from_4(buf + FIL_PAGE_OFFSET) != cur_page_num
|| space_id != cur_space) {
/* On pages that are not all zero, the page number
must match. */
if (is_page_all_zeroes(buf, page_size.physical())) {
return false;
}

if (is_log_enabled) {
fprintf(log_file,
"page id mismatch space::" ULINTPF
" page::%llu \n",
space_id, cur_page_num);
}

return true;
}

/* We can't trust only a page type, thus we take account
also fsp_flags or crypt_data on page 0 */
if ((page_type == FIL_PAGE_PAGE_COMPRESSED && is_compressed) ||
Expand Down Expand Up @@ -1576,9 +1617,6 @@ int main(
FILE* fil_page_type = NULL;
fpos_t pos;

/* Use to check the space id of given file. If space_id is zero,
then check whether page is doublewrite buffer.*/
ulint space_id = 0UL;
/* enable when space_id of given file is zero. */
bool is_system_tablespace = false;

Expand Down Expand Up @@ -1700,9 +1738,8 @@ int main(
/* enable variable is_system_tablespace when space_id of given
file is zero. Use to skip the checksum verification and rewrite
for doublewrite pages. */
is_system_tablespace = (!memcmp(&space_id, buf +
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, 4))
? true : false;
cur_space = mach_read_from_4(buf + FIL_PAGE_SPACE_ID);
cur_page_num = mach_read_from_4(buf + FIL_PAGE_OFFSET);

/* Determine page size, zip_size and page compression
from fsp_flags and encryption metadata from page 0 */
Expand All @@ -1715,7 +1752,9 @@ int main(
srv_page_size = page_size.logical();
bool is_compressed = FSP_FLAGS_HAS_PAGE_COMPRESSION(flags);

if (page_size.physical() > UNIV_ZIP_SIZE_MIN) {
if (physical_page_size == UNIV_ZIP_SIZE_MIN) {
partial_page_read = false;
} else {
/* Read rest of the page 0 to determine crypt_data */
bytes = ulong(read_file(buf, partial_page_read, page_size.physical(), fil_in));

Expand All @@ -1731,6 +1770,7 @@ int main(
partial_page_read = false;
}


/* Now that we have full page 0 in buffer, check encryption */
bool is_encrypted = check_encryption(filename, page_size, buf);

Expand All @@ -1741,7 +1781,9 @@ int main(
unsigned long long tmp_allow_mismatches = allow_mismatches;
allow_mismatches = 0;

exit_status = verify_checksum(buf, page_size, is_encrypted, is_compressed, &mismatch_count);
exit_status = verify_checksum(
buf, page_size, is_encrypted,
is_compressed, &mismatch_count);

if (exit_status) {
fprintf(stderr, "Error: Page 0 checksum mismatch, can't continue. \n");
Expand Down Expand Up @@ -1805,6 +1847,36 @@ int main(
}
}

off_t cur_offset = 0;
/* Find the first non all-zero page and fetch the
space id from there. */
while (is_page_all_zeroes(buf, physical_page_size)) {
bytes = ulong(read_file(
buf, false, physical_page_size,
fil_in));

if (feof(fil_in)) {
fprintf(stderr, "All are "
"zero-filled pages.");
goto my_exit;
}

cur_offset++;
}

cur_space = mach_read_from_4(buf + FIL_PAGE_SPACE_ID);
is_system_tablespace = (cur_space == 0);

if (cur_offset > 0) {
/* Re-read the non-zero page to check the
checksum. So move the file pointer to
previous position and reset the page number too. */
cur_page_num = mach_read_from_4(buf + FIL_PAGE_OFFSET);
if (!start_page) {
goto first_non_zero;
}
}

/* seek to the necessary position */
if (start_page) {
if (!read_from_stdin) {
Expand Down Expand Up @@ -1902,6 +1974,7 @@ int main(
goto my_exit;
}

first_non_zero:
if (is_system_tablespace) {
/* enable when page is double write buffer.*/
skip_page = is_page_doublewritebuffer(buf);
Expand All @@ -1922,8 +1995,10 @@ int main(
checksum verification.*/
if (!no_check
&& !skip_page
&& (exit_status = verify_checksum(buf, page_size,
is_encrypted, is_compressed, &mismatch_count))) {
&& (exit_status = verify_checksum(
buf, page_size,
is_encrypted, is_compressed,
&mismatch_count))) {
goto my_exit;
}

Expand Down
2 changes: 1 addition & 1 deletion mysql-test/suite/encryption/r/innochecksum.result
Expand Up @@ -33,7 +33,7 @@ CREATE TABLE t6 (a INT AUTO_INCREMENT PRIMARY KEY, b TEXT) ENGINE=InnoDB;
# Run innochecksum on t2
# Run innochecksum on t3
# Run innochecksum on t6
# no encryption corrupting the field should not have effect
# Space ID mismatch
# Restore the original tables
# Corrupt FIL_DATA+10 (data)
# Run innochecksum on t2
Expand Down
3 changes: 2 additions & 1 deletion mysql-test/suite/encryption/t/innochecksum.test
Expand Up @@ -206,7 +206,8 @@ EOF
--exec $INNOCHECKSUM $t3_IBD

--echo # Run innochecksum on t6
--echo # no encryption corrupting the field should not have effect
--echo # Space ID mismatch
--error 1
--exec $INNOCHECKSUM $t6_IBD

--enable_result_log
Expand Down
6 changes: 6 additions & 0 deletions mysql-test/suite/innodb/r/page_id_innochecksum.result
@@ -0,0 +1,6 @@
# Set the environmental variables
create table t1(f1 int not null)engine=innodb;
insert into t1 values(1), (2), (3);
# Change the page offset
FOUND 1 /page id mismatch/ in result.log
drop table t1;
52 changes: 52 additions & 0 deletions mysql-test/suite/innodb/t/page_id_innochecksum.test
@@ -0,0 +1,52 @@
--source include/have_innodb.inc
--echo # Set the environmental variables
let MYSQLD_BASEDIR= `SELECT @@basedir`;
let MYSQLD_DATADIR= `SELECT @@datadir`;
let INNODB_PAGE_SIZE=`select @@innodb_page_size`;

create table t1(f1 int not null)engine=innodb;
insert into t1 values(1), (2), (3);
let $resultlog=$MYSQLTEST_VARDIR/tmp/result.log;

--source include/shutdown_mysqld.inc
--echo # Change the page offset
perl;
use strict;
use warnings;
use Fcntl qw(:DEFAULT :seek);
do "$ENV{MTR_SUITE_DIR}/../innodb/include/crc32.pl";

my $page_size = $ENV{INNODB_PAGE_SIZE};

sysopen IBD_FILE, "$ENV{MYSQLD_DATADIR}/test/t1.ibd", O_RDWR
|| die "Cannot open t1.ibd\n";
sysread(IBD_FILE, $_, 38) || die "Cannot read t1.ibd\n";
my $space = unpack("x[34]N", $_);
sysseek(IBD_FILE, $page_size * 3, SEEK_SET) || die "Cannot seek t1.ibd\n";

my $head = pack("Nx[18]", 4); # better to have a valid page number
my $body = chr(0) x ($page_size - 38 - 8);

# Calculate innodb_checksum_algorithm=crc32 for the unencrypted page.
# The following bytes are excluded:
# bytes 0..3 (the checksum is stored there)
# bytes 26..37 (encryption key version, post-encryption checksum, tablespace id)
# bytes $page_size-8..$page_size-1 (checksum, LSB of FIL_PAGE_LSN)
my $polynomial = 0x82f63b78; # CRC-32C
my $ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial);

my $page= pack("N",$ck).$head.pack("NNN",1,$ck,$space).$body.pack("Nx[4]",$ck);
die unless syswrite(IBD_FILE, $page, $page_size) == $page_size;
close IBD_FILE;
EOF

--error 1
exec $INNOCHECKSUM -C crc32 -l $resultlog $MYSQLD_DATADIR/test/t1.ibd;

let SEARCH_FILE = $MYSQLTEST_VARDIR/tmp/result.log;
let SEARCH_PATTERN=page id mismatch;
--source include/search_pattern_in_file.inc

--remove_file $resultlog
--source include/start_mysqld.inc
drop table t1;
14 changes: 14 additions & 0 deletions storage/innobase/fil/fil0fil.cc
Expand Up @@ -5056,6 +5056,20 @@ fil_io(

req_type.set_fil_node(node);

#ifdef UNIV_DEBUG
if (req_type.is_write()
&& page_id.space() != SRV_LOG_SPACE_FIRST_ID
&& (page_id.space() != TRX_SYS_SPACE
|| buf_dblwr == NULL
|| !(page_id.page_no() >=
(buf_dblwr->block1 + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE)
|| page_id.page_no() >=
(buf_dblwr->block2 + TRX_SYS_DOUBLEWRITE_BLOCK_SIZE)))) {

ut_ad(offset == page_id.page_no() * page_size.physical());
}
#endif /* UNIV_DEBUG */

/* Queue the aio request */
dberr_t err = os_aio(
req_type,
Expand Down

0 comments on commit e4a0dbf

Please sign in to comment.