Skip to content

Commit 9f8716a

Browse files
MDEV-37138: Innochecksum fails to handle doublewrite buffer and
multiple file tablespace Problem: ======= - innochecksum was incorrectly interpreting doublewrite buffer pages as index pages, causing confusion about stale tables in the system tablespace. - innochecksum fails to parse the multi-file system tablespace Solution: ======== 1. Rewrite checksum of doublewrite buffer pages are skipped. 2. Introduced the option --tablespace-flags which can be used to initialize page size. This option can handle the ibdata2, ibdata3 etc without parsing ibdata1.
1 parent de3e6e6 commit 9f8716a

File tree

11 files changed

+399
-80
lines changed

11 files changed

+399
-80
lines changed

extra/innochecksum.cc

Lines changed: 140 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ The parts not included are excluded by #ifndef UNIV_INNOCHECKSUM. */
4949
#include "page0zip.h" /* page_zip_*() */
5050
#include "trx0undo.h" /* TRX_* */
5151
#include "fil0crypt.h" /* fil_space_verify_crypt_checksum */
52-
5352
#include <string.h>
5453

5554
#ifdef UNIV_NONINL
@@ -78,6 +77,8 @@ static ulint extent_size;
7877
static ulint xdes_size;
7978
ulong srv_page_size;
8079
uint32_t srv_page_size_shift;
80+
static uint32_t dblwr_1;
81+
static uint32_t dblwr_2;
8182
/* Current page number (0 based). */
8283
uint32_t cur_page_num;
8384
/* Current space. */
@@ -101,8 +102,10 @@ FILE* log_file = NULL;
101102
/* Enabled for log write option. */
102103
static bool is_log_enabled = false;
103104
static bool skip_freed_pages;
105+
static uint32_t tablespace_flags= 0;
104106
static byte field_ref_zero_buf[UNIV_PAGE_SIZE_MAX];
105107
const byte *field_ref_zero = field_ref_zero_buf;
108+
constexpr uint32_t USE_FSP_FLAGS{UINT32_MAX};
106109

107110
#ifndef _WIN32
108111
/* advisory lock for non-window system. */
@@ -257,12 +260,9 @@ void print_leaf_stats(
257260
}
258261

259262
/** Init the page size for the tablespace.
260-
@param[in] buf buffer used to read the page */
261-
static void init_page_size(const byte* buf)
263+
@param[in] flags InnoDB tablespace flags */
264+
static void init_page_size_from_flags(const uint32_t flags)
262265
{
263-
const unsigned flags = mach_read_from_4(buf + FIL_PAGE_DATA
264-
+ FSP_SPACE_FLAGS);
265-
266266
if (fil_space_t::full_crc32(flags)) {
267267
const uint32_t ssize = FSP_FLAGS_FCRC32_GET_PAGE_SSIZE(flags);
268268
srv_page_size_shift = UNIV_ZIP_SIZE_SHIFT_MIN - 1 + ssize;
@@ -544,24 +544,15 @@ static bool is_page_corrupted(byte *buf, bool is_encrypted, uint32_t flags)
544544
return(is_corrupted);
545545
}
546546

547-
/********************************************//*
548-
Check if page is doublewrite buffer or not.
549-
@param [in] page buffer page
550-
551-
@retval true if page is doublewrite buffer otherwise false.
552-
*/
553-
static
554-
bool
555-
is_page_doublewritebuffer(
556-
const byte* page)
547+
/** Check if page is doublewrite buffer or not.
548+
@retval true if page is doublewrite buffer otherwise false. */
549+
static bool is_page_doublewritebuffer()
557550
{
558-
if ((cur_page_num >= extent_size)
559-
&& (cur_page_num < extent_size * 3)) {
560-
/* page is doublewrite buffer. */
561-
return (true);
562-
}
563-
564-
return (false);
551+
if (cur_space != 0) return false;
552+
const uint32_t extent{static_cast<uint32_t>(
553+
cur_page_num & ~(extent_size - 1))};
554+
return cur_page_num > FSP_DICT_HDR_PAGE_NO &&
555+
extent && (extent == dblwr_1 || extent == dblwr_2);
565556
}
566557

567558
/*******************************************************//*
@@ -768,7 +759,7 @@ Parse the page and collect/dump the information about page type
768759
@param [in] file file for diagnosis.
769760
@param [in] is_encrypted tablespace is encrypted
770761
*/
771-
void
762+
static void
772763
parse_page(
773764
const byte* page,
774765
byte* xdes,
@@ -788,6 +779,12 @@ parse_page(
788779
str = skip_page ? "Double_write_buffer" : "-";
789780
page_no = mach_read_from_4(page + FIL_PAGE_OFFSET);
790781
if (skip_freed_pages) {
782+
783+
/** Skip doublewrite pages when -r is enabled */
784+
if (is_page_doublewritebuffer()) {
785+
return;
786+
}
787+
791788
const byte *des= xdes + XDES_ARR_OFFSET +
792789
xdes_size * ((page_no & (physical_page_size - 1))
793790
/ extent_size);
@@ -982,6 +979,18 @@ parse_page(
982979
fprintf(file, "#::" UINT32PF "\t\t|\t\tTransaction system "
983980
"page\t\t|\t%s\n", cur_page_num, str);
984981
}
982+
983+
if (cur_space == 0 &&
984+
(mach_read_from_4(page + TRX_SYS_DOUBLEWRITE +
985+
TRX_SYS_DOUBLEWRITE_MAGIC) ==
986+
TRX_SYS_DOUBLEWRITE_MAGIC_N)) {
987+
dblwr_1 = mach_read_from_4(
988+
page + TRX_SYS_DOUBLEWRITE +
989+
TRX_SYS_DOUBLEWRITE_BLOCK1);
990+
dblwr_2 = mach_read_from_4(
991+
page + TRX_SYS_DOUBLEWRITE +
992+
TRX_SYS_DOUBLEWRITE_BLOCK2);
993+
}
985994
break;
986995

987996
case FIL_PAGE_TYPE_FSP_HDR:
@@ -1224,6 +1233,9 @@ static struct my_option innochecksum_options[] = {
12241233
{"skip-freed-pages", 'r', "skip freed pages for the tablespace",
12251234
&skip_freed_pages, &skip_freed_pages, 0, GET_BOOL, NO_ARG,
12261235
0, 0, 0, 0, 0, 0},
1236+
{"tablespace-flags", 0, "InnoDB tablespace flags (default: 4294967295 "
1237+
"= read from page 0)", &tablespace_flags, &tablespace_flags, 0,
1238+
GET_UINT, REQUIRED_ARG, USE_FSP_FLAGS, 0, USE_FSP_FLAGS, 0, 0, 0},
12271239

12281240
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
12291241
};
@@ -1298,6 +1310,14 @@ innochecksum_get_one_option(
12981310
my_end(0);
12991311
exit(EXIT_SUCCESS);
13001312
break;
1313+
default:
1314+
if (tablespace_flags != USE_FSP_FLAGS &&
1315+
!fil_space_t::is_valid_flags(tablespace_flags, false) &&
1316+
!fil_space_t::is_valid_flags(tablespace_flags, true)) {
1317+
fprintf(stderr, "Error: Provided --tablespace-flags "
1318+
"is not valid.");
1319+
return true;
1320+
}
13011321
}
13021322

13031323
return(false);
@@ -1428,6 +1448,87 @@ rewrite_checksum(
14281448
&& !write_file(filename, fil_in, buf, flags, pos);
14291449
}
14301450

1451+
/** Read and validate page 0, then initialize tablespace flags
1452+
and page size.
1453+
@param fil_in File pointer
1454+
@param buf Buffer to read page into
1455+
@return whether the page was read successfully */
1456+
static bool read_and_validate_page0(FILE *fil_in, byte *buf)
1457+
{
1458+
/* Read the minimum page size first */
1459+
size_t initial_page_size= UNIV_ZIP_SIZE_MIN;
1460+
if (tablespace_flags != USE_FSP_FLAGS)
1461+
{
1462+
init_page_size_from_flags(tablespace_flags);
1463+
initial_page_size= physical_page_size;
1464+
}
1465+
1466+
/* Read just enough to get the tablespace flags */
1467+
size_t bytes= fread(buf, 1, initial_page_size, fil_in);
1468+
1469+
if (bytes != initial_page_size)
1470+
{
1471+
fprintf(stderr, "Error: Was not able to read the "
1472+
"minimum page size of %zu bytes. Bytes read "
1473+
"was %zu\n", initial_page_size, bytes);
1474+
return false;
1475+
}
1476+
1477+
/* Read space_id and page offset */
1478+
cur_space= mach_read_from_4(buf + FIL_PAGE_SPACE_ID);
1479+
cur_page_num= mach_read_from_4(buf + FIL_PAGE_OFFSET);
1480+
1481+
/* Get tablespace flags from the FSP header */
1482+
uint32_t flags= mach_read_from_4(buf + FSP_HEADER_OFFSET +
1483+
FSP_SPACE_FLAGS);
1484+
1485+
if (tablespace_flags != USE_FSP_FLAGS)
1486+
{
1487+
if (cur_page_num == 0 && flags != tablespace_flags)
1488+
fprintf(stderr, "Error: Mismatch between provided tablespace "
1489+
"flags (0x%x) and file flags (0x%x)\n",
1490+
tablespace_flags, flags);
1491+
}
1492+
else
1493+
{
1494+
if (cur_page_num)
1495+
{
1496+
fprintf(stderr, "Error: First page of the tablespace file "
1497+
"should be 0, but encountered page number %" PRIu32 ". "
1498+
"If you are checking multi file system "
1499+
"tablespace files, please specify the correct "
1500+
"tablespace flags using --tablespace-flags option.\n",
1501+
cur_page_num);
1502+
return false;
1503+
}
1504+
/* Initialize page size parameters based on flags */
1505+
init_page_size_from_flags(flags);
1506+
/* Read the rest of the page if it's larger than the minimum size */
1507+
if (physical_page_size > UNIV_ZIP_SIZE_MIN)
1508+
{
1509+
/* Read rest of the page 0 to determine crypt_data */
1510+
ulint bytes= read_file(buf, true, physical_page_size, fil_in);
1511+
if (bytes != physical_page_size)
1512+
{
1513+
fprintf(stderr, "Error: Was not able to read the rest of the "
1514+
"page of " ULINTPF " bytes. Bytes read was " ULINTPF "\n",
1515+
physical_page_size - UNIV_ZIP_SIZE_MIN, bytes);
1516+
return false;
1517+
}
1518+
}
1519+
tablespace_flags= flags;
1520+
}
1521+
1522+
if (physical_page_size < UNIV_ZIP_SIZE_MIN ||
1523+
physical_page_size > UNIV_PAGE_SIZE_MAX)
1524+
{
1525+
fprintf(stderr, "Error: Invalid page size " ULINTPF
1526+
" encountered\n", physical_page_size);
1527+
return false;
1528+
}
1529+
return true;
1530+
}
1531+
14311532
int main(
14321533
int argc,
14331534
char **argv)
@@ -1563,51 +1664,13 @@ int main(
15631664
}
15641665
}
15651666

1566-
/* Read the minimum page size. */
1567-
bytes = fread(buf, 1, UNIV_ZIP_SIZE_MIN, fil_in);
1568-
partial_page_read = true;
1569-
1570-
if (bytes != UNIV_ZIP_SIZE_MIN) {
1571-
fprintf(stderr, "Error: Was not able to read the "
1572-
"minimum page size ");
1573-
fprintf(stderr, "of %d bytes. Bytes read was " ULINTPF "\n",
1574-
UNIV_ZIP_SIZE_MIN, bytes);
1575-
1667+
/* Read and validate page 0 */
1668+
if (!read_and_validate_page0(fil_in, buf)) {
15761669
exit_status = 1;
15771670
goto my_exit;
15781671
}
15791672

1580-
/* enable variable is_system_tablespace when space_id of given
1581-
file is zero. Use to skip the checksum verification and rewrite
1582-
for doublewrite pages. */
1583-
cur_space = mach_read_from_4(buf + FIL_PAGE_SPACE_ID);
1584-
cur_page_num = mach_read_from_4(buf + FIL_PAGE_OFFSET);
1585-
1586-
/* Determine page size, zip_size and page compression
1587-
from fsp_flags and encryption metadata from page 0 */
1588-
init_page_size(buf);
1589-
1590-
uint32_t flags = mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + buf);
1591-
1592-
if (physical_page_size == UNIV_ZIP_SIZE_MIN) {
1593-
partial_page_read = false;
1594-
} else {
1595-
/* Read rest of the page 0 to determine crypt_data */
1596-
bytes = read_file(buf, partial_page_read, physical_page_size, fil_in);
1597-
if (bytes != physical_page_size) {
1598-
fprintf(stderr, "Error: Was not able to read the "
1599-
"rest of the page ");
1600-
fprintf(stderr, "of " ULINTPF " bytes. Bytes read was " ULINTPF "\n",
1601-
physical_page_size - UNIV_ZIP_SIZE_MIN, bytes);
1602-
1603-
exit_status = 1;
1604-
goto my_exit;
1605-
}
1606-
partial_page_read = false;
1607-
}
1608-
1609-
1610-
/* Now that we have full page 0 in buffer, check encryption */
1673+
/* Check if tablespace is encrypted */
16111674
bool is_encrypted = check_encryption(filename, buf);
16121675

16131676
/* Verify page 0 contents. Note that we can't allow
@@ -1618,7 +1681,8 @@ int main(
16181681
allow_mismatches = 0;
16191682

16201683
exit_status = verify_checksum(buf, is_encrypted,
1621-
&mismatch_count, flags);
1684+
&mismatch_count,
1685+
tablespace_flags);
16221686

16231687
if (exit_status) {
16241688
fprintf(stderr, "Error: Page 0 checksum mismatch, can't continue. \n");
@@ -1629,7 +1693,8 @@ int main(
16291693

16301694
if ((exit_status = rewrite_checksum(
16311695
filename, fil_in, buf,
1632-
&pos, is_encrypted, flags))) {
1696+
&pos, is_encrypted,
1697+
tablespace_flags))) {
16331698
goto my_exit;
16341699
}
16351700

@@ -1825,7 +1890,7 @@ int main(
18251890
first_non_zero:
18261891
if (is_system_tablespace) {
18271892
/* enable when page is double write buffer.*/
1828-
skip_page = is_page_doublewritebuffer(buf);
1893+
skip_page = is_page_doublewritebuffer();
18291894
} else {
18301895
skip_page = false;
18311896
}
@@ -1846,13 +1911,16 @@ int main(
18461911
&& !is_page_free(xdes, physical_page_size, cur_page_num)
18471912
&& (exit_status = verify_checksum(
18481913
buf, is_encrypted,
1849-
&mismatch_count, flags))) {
1914+
&mismatch_count,
1915+
tablespace_flags))) {
18501916
goto my_exit;
18511917
}
18521918

1853-
if ((exit_status = rewrite_checksum(
1854-
filename, fil_in, buf,
1855-
&pos, is_encrypted, flags))) {
1919+
if (!is_page_doublewritebuffer() &&
1920+
(exit_status = rewrite_checksum(
1921+
filename, fil_in, buf,
1922+
&pos, is_encrypted,
1923+
tablespace_flags))) {
18561924
goto my_exit;
18571925
}
18581926

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB STATS_PERSISTENT=0;
2+
SET GLOBAL innodb_log_checkpoint_now=ON;
3+
FLUSH TABLE t1 FOR EXPORT;
4+
UNLOCK TABLES;
5+
INSERT INTO t1 SET a=1;
6+
FLUSH TABLE t1 FOR EXPORT;
7+
UNLOCK TABLES;
8+
# Skip InnoDB Doublewrite Buffer
9+
Should not exist when summary excludes dblwr pages
10+
Should exist when summary includes dblwr pages
11+
# restart
12+
CREATE TABLE ibd_1(f1 INT PRIMARY KEY)ENGINE=InnoDB;
13+
INSERT INTO ibd_1 VALUES(1), (2), (3), (4);
14+
# Pass wrong tablespace flag for ibdata2
15+
FOUND 1 /Error: Page 0 checksum mismatch/ in result.log
16+
# Pass wrong tablespace flag for ibdata1
17+
FOUND 1 /Error: Mismatch between provided tablespace flags/ in result.log
18+
# Pass invalid tablespace flag for ibdata1
19+
FOUND 1 /Error: Provided --tablespace-flags is not valid/ in result.log
20+
# innochecksum should be succesfull
21+
NOT FOUND /Fail/ in result.log
22+
# Create a tablespace with page number > 2^31
23+
# Test innochecksum with the modified ibdata3
24+
FOUND 1 /Error: First page of the tablespace file should be 0, but encountered page number 2147483649/ in result.log
25+
# Test innochecksum with the modified ibdata3 with tablespace flags
26+
FOUND 1 /Exceeded the maximum allowed checksum mismatch/ in result.log
27+
# restart
28+
DROP TABLE t1, ibd_1;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--innodb_data_file_path=ibdata1:3M;ibdata2:1M:autoextend
2+
--innodb_sys_tablespaces

0 commit comments

Comments
 (0)