Skip to content

Commit cfaaf93

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. This is a cherry-pick of commit 9f8716a
1 parent 7301fba commit cfaaf93

File tree

11 files changed

+400
-81
lines changed

11 files changed

+400
-81
lines changed

extra/innochecksum.cc

Lines changed: 141 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ The parts not included are excluded by #ifndef UNIV_INNOCHECKSUM. */
4848
#include "buf0buf.h" /* buf_page_is_corrupted */
4949
#include "page0zip.h" /* page_zip_*() */
5050
#include "trx0undo.h" /* TRX_* */
51-
#include "ut0crc32.h" /* ut_crc32_init() */
51+
#include "ut0crc32.h" /* ut_crc32_init() */
5252
#include "fil0crypt.h" /* fil_space_verify_crypt_checksum */
53-
5453
#include <string.h>
5554

5655
#ifdef UNIV_NONINL
@@ -79,6 +78,8 @@ static ulint extent_size;
7978
static ulint xdes_size;
8079
ulong srv_page_size;
8180
ulong srv_page_size_shift;
81+
static uint32_t dblwr_1;
82+
static uint32_t dblwr_2;
8283
/* Current page number (0 based). */
8384
uint32_t cur_page_num;
8485
/* Current space. */
@@ -102,8 +103,10 @@ FILE* log_file = NULL;
102103
/* Enabled for log write option. */
103104
static bool is_log_enabled = false;
104105
static bool skip_freed_pages;
106+
static uint32_t tablespace_flags= 0;
105107
static byte field_ref_zero_buf[UNIV_PAGE_SIZE_MAX];
106108
const byte *field_ref_zero = field_ref_zero_buf;
109+
constexpr uint32_t USE_FSP_FLAGS{UINT32_MAX};
107110

108111
#ifndef _WIN32
109112
/* advisory lock for non-window system. */
@@ -258,12 +261,9 @@ void print_leaf_stats(
258261
}
259262

260263
/** Init the page size for the tablespace.
261-
@param[in] buf buffer used to read the page */
262-
static void init_page_size(const byte* buf)
264+
@param[in] flags InnoDB tablespace flags */
265+
static void init_page_size_from_flags(const uint32_t flags)
263266
{
264-
const unsigned flags = mach_read_from_4(buf + FIL_PAGE_DATA
265-
+ FSP_SPACE_FLAGS);
266-
267267
if (fil_space_t::full_crc32(flags)) {
268268
const ulong ssize = FSP_FLAGS_FCRC32_GET_PAGE_SSIZE(flags);
269269
srv_page_size_shift = UNIV_ZIP_SIZE_SHIFT_MIN - 1 + ssize;
@@ -550,24 +550,15 @@ is_page_corrupted(
550550
return(is_corrupted);
551551
}
552552

553-
/********************************************//*
554-
Check if page is doublewrite buffer or not.
555-
@param [in] page buffer page
556-
557-
@retval true if page is doublewrite buffer otherwise false.
558-
*/
559-
static
560-
bool
561-
is_page_doublewritebuffer(
562-
const byte* page)
553+
/** Check if page is doublewrite buffer or not.
554+
@retval true if page is doublewrite buffer otherwise false. */
555+
static bool is_page_doublewritebuffer()
563556
{
564-
if ((cur_page_num >= extent_size)
565-
&& (cur_page_num < extent_size * 3)) {
566-
/* page is doublewrite buffer. */
567-
return (true);
568-
}
569-
570-
return (false);
557+
if (cur_space != 0) return false;
558+
const uint32_t extent{static_cast<uint32_t>(
559+
cur_page_num & ~(extent_size - 1))};
560+
return cur_page_num > FSP_DICT_HDR_PAGE_NO &&
561+
extent && (extent == dblwr_1 || extent == dblwr_2);
571562
}
572563

573564
/*******************************************************//*
@@ -774,7 +765,7 @@ Parse the page and collect/dump the information about page type
774765
@param [in] file file for diagnosis.
775766
@param [in] is_encrypted tablespace is encrypted
776767
*/
777-
void
768+
static void
778769
parse_page(
779770
const byte* page,
780771
byte* xdes,
@@ -794,6 +785,12 @@ parse_page(
794785
str = skip_page ? "Double_write_buffer" : "-";
795786
page_no = mach_read_from_4(page + FIL_PAGE_OFFSET);
796787
if (skip_freed_pages) {
788+
789+
/** Skip doublewrite pages when -r is enabled */
790+
if (is_page_doublewritebuffer()) {
791+
return;
792+
}
793+
797794
const byte *des= xdes + XDES_ARR_OFFSET +
798795
xdes_size * ((page_no & (physical_page_size - 1))
799796
/ extent_size);
@@ -988,6 +985,18 @@ parse_page(
988985
fprintf(file, "#::" UINT32PF "\t\t|\t\tTransaction system "
989986
"page\t\t|\t%s\n", cur_page_num, str);
990987
}
988+
989+
if (cur_space == 0 &&
990+
(mach_read_from_4(page + TRX_SYS_DOUBLEWRITE +
991+
TRX_SYS_DOUBLEWRITE_MAGIC) ==
992+
TRX_SYS_DOUBLEWRITE_MAGIC_N)) {
993+
dblwr_1 = mach_read_from_4(
994+
page + TRX_SYS_DOUBLEWRITE +
995+
TRX_SYS_DOUBLEWRITE_BLOCK1);
996+
dblwr_2 = mach_read_from_4(
997+
page + TRX_SYS_DOUBLEWRITE +
998+
TRX_SYS_DOUBLEWRITE_BLOCK2);
999+
}
9911000
break;
9921001

9931002
case FIL_PAGE_TYPE_FSP_HDR:
@@ -1230,6 +1239,9 @@ static struct my_option innochecksum_options[] = {
12301239
{"skip-freed-pages", 'r', "skip freed pages for the tablespace",
12311240
&skip_freed_pages, &skip_freed_pages, 0, GET_BOOL, NO_ARG,
12321241
0, 0, 0, 0, 0, 0},
1242+
{"tablespace-flags", 0, "InnoDB tablespace flags (default: 4294967295 "
1243+
"= read from page 0)", &tablespace_flags, &tablespace_flags, 0,
1244+
GET_UINT, REQUIRED_ARG, USE_FSP_FLAGS, 0, USE_FSP_FLAGS, 0, 0, 0},
12331245

12341246
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
12351247
};
@@ -1304,6 +1316,14 @@ innochecksum_get_one_option(
13041316
my_end(0);
13051317
exit(EXIT_SUCCESS);
13061318
break;
1319+
default:
1320+
if (tablespace_flags != USE_FSP_FLAGS &&
1321+
!fil_space_t::is_valid_flags(tablespace_flags, false) &&
1322+
!fil_space_t::is_valid_flags(tablespace_flags, true)) {
1323+
fprintf(stderr, "Error: Provided --tablespace-flags "
1324+
"is not valid.");
1325+
return true;
1326+
}
13071327
}
13081328

13091329
return(false);
@@ -1434,6 +1454,87 @@ rewrite_checksum(
14341454
&& !write_file(filename, fil_in, buf, flags, pos);
14351455
}
14361456

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

1572-
/* Read the minimum page size. */
1573-
bytes = fread(buf, 1, UNIV_ZIP_SIZE_MIN, fil_in);
1574-
partial_page_read = true;
1575-
1576-
if (bytes != UNIV_ZIP_SIZE_MIN) {
1577-
fprintf(stderr, "Error: Was not able to read the "
1578-
"minimum page size ");
1579-
fprintf(stderr, "of %d bytes. Bytes read was " ULINTPF "\n",
1580-
UNIV_ZIP_SIZE_MIN, bytes);
1581-
1673+
/* Read and validate page 0 */
1674+
if (!read_and_validate_page0(fil_in, buf)) {
15821675
exit_status = 1;
15831676
goto my_exit;
15841677
}
15851678

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

16191682
/* Verify page 0 contents. Note that we can't allow
@@ -1624,7 +1687,8 @@ int main(
16241687
allow_mismatches = 0;
16251688

16261689
exit_status = verify_checksum(buf, is_encrypted,
1627-
&mismatch_count, flags);
1690+
&mismatch_count,
1691+
tablespace_flags);
16281692

16291693
if (exit_status) {
16301694
fprintf(stderr, "Error: Page 0 checksum mismatch, can't continue. \n");
@@ -1635,7 +1699,8 @@ int main(
16351699

16361700
if ((exit_status = rewrite_checksum(
16371701
filename, fil_in, buf,
1638-
&pos, is_encrypted, flags))) {
1702+
&pos, is_encrypted,
1703+
tablespace_flags))) {
16391704
goto my_exit;
16401705
}
16411706

@@ -1831,7 +1896,7 @@ int main(
18311896
first_non_zero:
18321897
if (is_system_tablespace) {
18331898
/* enable when page is double write buffer.*/
1834-
skip_page = is_page_doublewritebuffer(buf);
1899+
skip_page = is_page_doublewritebuffer();
18351900
} else {
18361901
skip_page = false;
18371902
}
@@ -1852,13 +1917,16 @@ int main(
18521917
&& !is_page_free(xdes, physical_page_size, cur_page_num)
18531918
&& (exit_status = verify_checksum(
18541919
buf, is_encrypted,
1855-
&mismatch_count, flags))) {
1920+
&mismatch_count,
1921+
tablespace_flags))) {
18561922
goto my_exit;
18571923
}
18581924

1859-
if ((exit_status = rewrite_checksum(
1860-
filename, fil_in, buf,
1861-
&pos, is_encrypted, flags))) {
1925+
if (!is_page_doublewritebuffer() &&
1926+
(exit_status = rewrite_checksum(
1927+
filename, fil_in, buf,
1928+
&pos, is_encrypted,
1929+
tablespace_flags))) {
18621930
goto my_exit;
18631931
}
18641932

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)