@@ -4096,7 +4096,8 @@ static void btr_validate_report2(
40964096@return true if ok */
40974097static bool btr_validate_level (
40984098 dict_index_t *index, const trx_t *trx, ulint level,
4099- bool lockout IF_DEBUG (, Index_details &index_details)) {
4099+ bool lockout IF_DEBUG (, Index_details &index_details),
4100+ blob_ref_map *blob_map = nullptr) {
41004101 buf_block_t *block;
41014102 page_t *page;
41024103 buf_block_t *right_block = nullptr ; /* remove warning */
@@ -4169,13 +4170,40 @@ static bool btr_validate_level(
41694170 ret = false ;
41704171 }
41714172
4173+ #ifdef XTRABACKUP
4174+ /* In xtrabackup --check-tables we report corruption instead of crashing.
4175+ InnoDB uses ut_a here because space ID mismatch or wrong page level
4176+ during B-tree descent is unrecoverable for a running server. In
4177+ xtrabackup we only need a damage report. */
4178+ if (index->space != block->page .id .space () ||
4179+ index->space != page_get_space_id (page)) {
4180+ btr_validate_report1 (index, level, block);
4181+ ib::error () << " B-tree corruption: space ID mismatch during descent."
4182+ << " Expected " << index->space << " , got block space "
4183+ << block->page .id .space () << " , page space "
4184+ << page_get_space_id (page) << " in index " << index->name ();
4185+ mtr_commit (&mtr);
4186+ mem_heap_free (heap);
4187+ return false ;
4188+ }
4189+ if (page_is_leaf (page)) {
4190+ btr_validate_report1 (index, level, block);
4191+ ib::error () << " B-tree corruption: unexpected leaf page "
4192+ << page_get_page_no (page) << " during descent to level "
4193+ << level << " in index " << index->name ();
4194+ mtr_commit (&mtr);
4195+ mem_heap_free (heap);
4196+ return false ;
4197+ }
4198+ #else
41724199 ut_a (index->space == block->page .id .space ());
41734200 ut_a (index->space == page_get_space_id (page));
4201+ ut_a (!page_is_leaf (page));
4202+ #endif /* XTRABACKUP */
41744203#ifdef UNIV_ZIP_DEBUG
41754204 page_zip = buf_block_get_page_zip (block);
41764205 ut_a (!page_zip || page_zip_validate (page_zip, page, index));
41774206#endif /* UNIV_ZIP_DEBUG */
4178- ut_a (!page_is_leaf (page));
41794207
41804208 page_cur_set_before_first (block, &cursor);
41814209 page_cur_move_to_next (&cursor);
@@ -4222,6 +4250,7 @@ static bool btr_validate_level(
42224250 }
42234251
42244252 do {
4253+ ulint cur_page_no;
42254254 mem_heap_empty (heap);
42264255 offsets = offsets2 = nullptr ;
42274256 if (!srv_read_only_mode) {
@@ -4237,7 +4266,32 @@ static bool btr_validate_level(
42374266 ut_a (!page_zip || page_zip_validate (page_zip, page, index));
42384267#endif /* UNIV_ZIP_DEBUG */
42394268
4269+ #ifdef XTRABACKUP
4270+ /* In xtrabackup --check-tables we report corruption instead of crashing.
4271+ Space ID mismatch in the page scan means the page does not belong to
4272+ this index's tablespace -- severe corruption. Stop traversing this
4273+ index because sibling links on a corrupted page are untrustworthy. */
4274+ if (block->page .id .space () != index->space ) {
4275+ btr_validate_report1 (index, level, block);
4276+ ib::error () << " B-tree corruption: page space ID "
4277+ << block->page .id .space () << " != index space "
4278+ << index->space << " on page " << block->page .id .page_no ()
4279+ << " in index " << index->name ();
4280+ ret = false ;
4281+ right_page_no = FIL_NULL ;
4282+ goto node_ptr_fails;
4283+ }
4284+ #else
42404285 ut_a (block->page .id .space () == index->space );
4286+ #endif /* XTRABACKUP */
4287+
4288+ DBUG_EXECUTE_IF (" check_table_wrong_index_id" , {
4289+ thread_local bool injected = false ;
4290+ if (!injected && btr_page_get_index_id (page) == index->id ) {
4291+ mach_write_to_8 (page + PAGE_HEADER + PAGE_INDEX_ID , index->id + 1 );
4292+ injected = true ;
4293+ }
4294+ });
42414295
42424296 if (fseg_page_is_free (seg, block->page .id .space (),
42434297 block->page .id .page_no ())) {
@@ -4252,7 +4306,7 @@ static bool btr_validate_level(
42524306
42534307 ret = false ;
42544308
4255- } else if (!page_validate (page, index)) {
4309+ } else if (!page_validate (page, index, true , blob_map )) {
42564310 btr_validate_report1 (index, level, block);
42574311 ret = false ;
42584312
@@ -4263,17 +4317,53 @@ static bool btr_validate_level(
42634317 ret = false ;
42644318 }
42654319
4320+ #ifdef XTRABACKUP
4321+ /* In xtrabackup --check-tables we report corruption instead of crashing.
4322+ Page level mismatch means the page has wrong metadata (e.g. all-zero
4323+ page from unflushed redo). Stop traversal -- sibling links are
4324+ untrustworthy on a corrupted page. */
4325+ if (btr_page_get_level (page) != level) {
4326+ btr_validate_report1 (index, level, block);
4327+ ib::error () << " B-tree corruption: page level "
4328+ << btr_page_get_level (page) << " != expected level " << level
4329+ << " on page " << page_get_page_no (page) << " in index "
4330+ << index->name ();
4331+ ret = false ;
4332+ right_page_no = FIL_NULL ;
4333+ goto node_ptr_fails;
4334+ }
4335+ #else
42664336 ut_a (btr_page_get_level (page) == level);
4337+ #endif /* XTRABACKUP */
42674338 right_page_no = btr_page_get_next (page, &mtr);
42684339 left_page_no = btr_page_get_prev (page, &mtr);
4269- const ulint cur_page_no = page_get_page_no (page);
4340+ cur_page_no = page_get_page_no (page);
42704341
42714342#ifdef UNIV_DEBUG
42724343 index_details.add_page (cur_page_no, level);
42734344#endif /* UNIV_DEBUG */
42744345
4346+ #ifdef XTRABACKUP
4347+ /* In xtrabackup --check-tables we report corruption instead of crashing.
4348+ An empty non-root page indicates corruption (e.g. all-zero page from
4349+ unflushed redo, or truncated tablespace). Stop traversal -- an
4350+ all-zero page has FIL_PAGE_NEXT=0 which would loop through non-B-tree
4351+ pages indefinitely. */
4352+ if (page_is_empty (page) &&
4353+ !(level == 0 && page_get_page_no (page) == dict_index_get_page (index))) {
4354+ btr_validate_report1 (index, level, block);
4355+ ib::error () << " B-tree corruption: page " << page_get_page_no (page)
4356+ << " is empty but is not the root page"
4357+ << " in index " << index->name ()
4358+ << " . Possible all-zero (unflushed) page." ;
4359+ ret = false ;
4360+ right_page_no = FIL_NULL ;
4361+ goto node_ptr_fails;
4362+ }
4363+ #else
42754364 ut_a (!page_is_empty (page) ||
42764365 (level == 0 && page_get_page_no (page) == dict_index_get_page (index)));
4366+ #endif /* XTRABACKUP */
42774367
42784368 if (right_page_no != FIL_NULL ) {
42794369 const rec_t *right_rec;
@@ -4285,6 +4375,14 @@ static bool btr_validate_level(
42854375
42864376 right_page = buf_block_get_frame (right_block);
42874377
4378+ DBUG_EXECUTE_IF (" check_table_break_sibling_link" , {
4379+ thread_local bool injected = false ;
4380+ if (!injected && btr_page_get_prev (right_page, &mtr) != 0 ) {
4381+ mach_write_to_4 (right_page + FIL_PAGE_PREV , 0 );
4382+ injected = true ;
4383+ }
4384+ });
4385+
42884386 const auto fil_page_prev = btr_page_get_prev (right_page, &mtr);
42894387 if (fil_page_prev != cur_page_no) {
42904388 btr_validate_report2 (index, level, block, right_block);
@@ -4295,9 +4393,15 @@ static bool btr_validate_level(
42954393
42964394 ret = false ;
42974395#ifdef UNIV_DEBUG
4298- /* In debug build, it is best to fail with an assert. */
4299- const bool siblings_link_correct = false ;
4300- ut_ad (siblings_link_correct);
4396+ {
4397+ bool skip_assert = false ;
4398+ DBUG_EXECUTE_IF (" check_table_break_sibling_link" ,
4399+ skip_assert = true ;);
4400+ if (!skip_assert) {
4401+ const bool siblings_link_correct = false ;
4402+ ut_ad (siblings_link_correct);
4403+ }
4404+ }
43014405#endif /* UNIV_DEBUG */
43024406 }
43034407
@@ -4342,9 +4446,25 @@ static bool btr_validate_level(
43424446 }
43434447
43444448 if (level > 0 && left_page_no == FIL_NULL ) {
4449+ #ifdef XTRABACKUP
4450+ /* In xtrabackup --check-tables we report corruption instead of
4451+ crashing. Missing min-record flag on the leftmost non-leaf page
4452+ indicates B-tree structural corruption. */
4453+ if (!(REC_INFO_MIN_REC_FLAG &
4454+ rec_get_info_bits (page_rec_get_next (page_get_infimum_rec (page)),
4455+ page_is_comp (page)))) {
4456+ btr_validate_report1 (index, level, block);
4457+ ib::error () << " B-tree corruption: min-record flag missing on"
4458+ << " leftmost page " << page_get_page_no (page)
4459+ << " at level " << level << " in index " << index->name ();
4460+ ret = false ;
4461+ goto node_ptr_fails;
4462+ }
4463+ #else
43454464 ut_a (REC_INFO_MIN_REC_FLAG &
43464465 rec_get_info_bits (page_rec_get_next (page_get_infimum_rec (page)),
43474466 page_is_comp (page)));
4467+ #endif /* XTRABACKUP */
43484468 }
43494469
43504470 /* Similarly skip the father node check for spatial index for now,
@@ -4425,15 +4545,61 @@ static bool btr_validate_level(
44254545 }
44264546
44274547 if (left_page_no == FIL_NULL ) {
4548+ #ifdef XTRABACKUP
4549+ /* In xtrabackup --check-tables we report corruption instead of
4550+ crashing. Wrong father pointer for leftmost child or unexpected
4551+ prev link on father page indicates structural corruption. */
4552+ if (node_ptr != page_rec_get_next (page_get_infimum_rec (father_page))) {
4553+ btr_validate_report1 (index, level, block);
4554+ ib::error () << " B-tree corruption: leftmost child page "
4555+ << page_get_page_no (page)
4556+ << " has wrong father node pointer"
4557+ << " in index " << index->name ();
4558+ ret = false ;
4559+ goto node_ptr_fails;
4560+ }
4561+ if (btr_page_get_prev (father_page, &mtr) != FIL_NULL ) {
4562+ btr_validate_report1 (index, level, block);
4563+ ib::error () << " B-tree corruption: father page of leftmost child"
4564+ << " has unexpected prev link"
4565+ << " in index " << index->name ();
4566+ ret = false ;
4567+ goto node_ptr_fails;
4568+ }
4569+ #else
44284570 ut_a (node_ptr == page_rec_get_next (page_get_infimum_rec (father_page)));
44294571 ut_a (btr_page_get_prev (father_page, &mtr) == FIL_NULL );
4572+ #endif /* XTRABACKUP */
44304573 }
44314574
44324575 if (right_page_no == FIL_NULL ) {
44334576 const rec_t *expected_node_ptr =
44344577 page_rec_get_prev (page_get_supremum_rec (father_page));
4578+ #ifdef XTRABACKUP
4579+ /* In xtrabackup --check-tables we report corruption instead of
4580+ crashing. Wrong father pointer for rightmost child or unexpected
4581+ next link on father page indicates structural corruption. */
4582+ if (node_ptr != expected_node_ptr) {
4583+ btr_validate_report1 (index, level, block);
4584+ ib::error () << " B-tree corruption: rightmost child page "
4585+ << page_get_page_no (page)
4586+ << " has wrong father node pointer"
4587+ << " in index " << index->name ();
4588+ ret = false ;
4589+ goto node_ptr_fails;
4590+ }
4591+ if (btr_page_get_next (father_page, &mtr) != FIL_NULL ) {
4592+ btr_validate_report1 (index, level, block);
4593+ ib::error () << " B-tree corruption: father page of rightmost child"
4594+ << " has unexpected next link"
4595+ << " in index " << index->name ();
4596+ ret = false ;
4597+ goto node_ptr_fails;
4598+ }
4599+ #else
44354600 ut_a (node_ptr == expected_node_ptr);
44364601 ut_a (btr_page_get_next (father_page, &mtr) == FIL_NULL );
4602+ #endif /* XTRABACKUP */
44374603 } else {
44384604 const rec_t *right_node_ptr;
44394605
@@ -4584,9 +4750,10 @@ static bool btr_validate_spatial_index(
45844750/* * Checks the consistency of an index tree.
45854751 @return true if ok */
45864752bool btr_validate_index (
4587- dict_index_t *index, /* !< in: index */
4588- const trx_t *trx, /* !< in: transaction or NULL */
4589- bool lockout) /* !< in: true if X-latch index is intended */
4753+ dict_index_t *index, /* !< in: index */
4754+ const trx_t *trx, /* !< in: transaction or NULL */
4755+ bool lockout, /* !< in: true if X-latch index is intended */
4756+ blob_ref_map *blob_map) /* !< in: optional LOB reference map */
45904757{
45914758#ifdef UNIV_DEBUG
45924759 Index_details index_details;
@@ -4650,7 +4817,7 @@ bool btr_validate_index(
46504817
46514818 for (ulint i = 0 ; i <= n; ++i) {
46524819 if (!btr_validate_level (index, trx, n - i,
4653- lockout IF_DEBUG (, index_details))) {
4820+ lockout IF_DEBUG (, index_details), blob_map )) {
46544821 ok = false ;
46554822 break ;
46564823 }
0 commit comments