From b00c9e4b7932594efc469837c5c1512f9a0f75aa Mon Sep 17 00:00:00 2001 From: Ilya Eremin Date: Mon, 20 Nov 2023 12:27:58 +0300 Subject: [PATCH 1/2] Fix possible BUGCHECK "cannot find tip page" during validation with -full or -mend option If a primary record version has a transaction number which is greater than NT and seems corrupted, its state will be treated as tra_active to avoid an attempt to fetch a non-existing TIP page. --- src/jrd/validation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index c930dff55f8..624e0858f1c 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -1801,8 +1801,11 @@ Validation::RTN Validation::walk_data_page(jrd_rel* relation, ULONG page_number, { const TraNumber transaction = Ods::getTraNum(header); + // If the transaction number is greater than NT and seems corrupted, treat its + // state as tra_active to avoid an attempt to fetch a non-existing TIP page const int state = (transaction < dbb->dbb_oldest_transaction) ? - tra_committed : TRA_fetch_state(vdr_tdbb, transaction); + tra_committed : (transaction > vdr_max_transaction) ? + tra_active : TRA_fetch_state(vdr_tdbb, transaction); if (!deleted_flag && (state == tra_committed || state == tra_limbo) || deleted_flag && state != tra_committed) From 12bb94b6f4b0e0b667a1c6db567a1a847ff437c3 Mon Sep 17 00:00:00 2001 From: Ilya Eremin Date: Thu, 21 Mar 2024 11:40:34 +0300 Subject: [PATCH 2/2] 1. Detect a corruption where the TIP has a transaction with a non-zero state which number is greater than Next transaction. When -mend option is specified, fix it by advancing Next transaction. 2. Do not call TRA_extend_tip() when it's possible to reconstruct the TIP chain using tip_next from the previous page. Also TRA_extend_tip() call is only allowed when the sequence is not found at the end of the vector. It prevents the potential loss of TIPs because this function unconditionally allocates a new TIP and overwrites tip_next. 3. Check the count of elements in the vector before accessing them to avoid an assertion failure in getElement(). --- src/jrd/validation.cpp | 82 ++++++++++++++++++++++++++++++++++-------- src/jrd/validation.h | 5 +-- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index 624e0858f1c..556ddb6cd33 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -848,7 +848,8 @@ const Validation::MSG_ENTRY Validation::vdr_msg_table[VAL_MAX_ERROR] = {true, isc_info_ppage_errors, "Data page %" ULONGFORMAT" is not in PP (%" ULONGFORMAT"). Slot (%d) is not found"}, {true, isc_info_ppage_errors, "Data page %" ULONGFORMAT" is not in PP (%" ULONGFORMAT"). Slot (%d) has value %" ULONGFORMAT}, {true, isc_info_ppage_errors, "Pointer page is not found for data page %" ULONGFORMAT". dpg_sequence (%" ULONGFORMAT") is invalid"}, - {true, isc_info_dpage_errors, "Data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} marked as secondary but contains primary record versions"} + {true, isc_info_dpage_errors, "Data page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} marked as secondary but contains primary record versions"}, + {true, isc_info_tpage_errors, "Transaction inventory page %" ULONGFORMAT" {sequence %" ULONGFORMAT"} has transaction with non-zero state which number is greater than Next transaction"} }; Validation::Validation(thread_db* tdbb, UtilSvc* uSvc) @@ -1615,7 +1616,7 @@ void Validation::walk_database() WIN window(DB_PAGE_SPACE, -1); header_page* page = 0; fetch_page(true, HEADER_PAGE, pag_header, &window, &page); - TraNumber next = vdr_max_transaction = Ods::getNT(page); + vdr_max_transaction = Ods::getNT(page); if (vdr_flags & VDR_online) { release_page(&window); @@ -1623,10 +1624,11 @@ void Validation::walk_database() if (!(vdr_flags & VDR_partial)) { + fb_assert(!(vdr_flags & VDR_online)); walk_header(page->hdr_next_page); walk_pip(); walk_scns(); - walk_tip(next); + walk_tip(); walk_generators(); } @@ -3271,7 +3273,7 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo) return rtn_ok; } -Validation::RTN Validation::walk_tip(TraNumber transaction) +Validation::RTN Validation::walk_tip() { /************************************** * @@ -3285,40 +3287,90 @@ Validation::RTN Validation::walk_tip(TraNumber transaction) **************************************/ Database* dbb = vdr_tdbb->getDatabase(); - const vcl* vector = dbb->dbb_t_pages; + vcl* vector = dbb->dbb_t_pages; if (!vector) return corrupt(VAL_TIP_LOST, 0); tx_inv_page* page = 0; - const ULONG pages = transaction / dbb->dbb_page_manager.transPerTIP; + const ULONG trans_per_tip = dbb->dbb_page_manager.transPerTIP; + const ULONG last = vdr_max_transaction / trans_per_tip; + ULONG saved_tip_next = 0; + TraNumber advanced_NT = 0; - for (ULONG sequence = 0; sequence <= pages; sequence++) + for (ULONG sequence = 0; sequence <= last; sequence++) { - if (!(*vector)[sequence] || sequence >= vector->count()) + if (sequence >= vector->count() || !(*vector)[sequence]) { corrupt(VAL_TIP_LOST_SEQUENCE, 0, sequence); - if (!(vdr_flags & VDR_repair)) - continue; - TRA_extend_tip(vdr_tdbb, sequence); - vector = dbb->dbb_t_pages; - vdr_fixed++; + if (saved_tip_next) + { + if (sequence >= vector->count()) + { + vector = dbb->dbb_t_pages = + vcl::newVector(*dbb->dbb_permanent, dbb->dbb_t_pages, sequence + 1); + } + + (*vector)[sequence] = saved_tip_next; + } + else + { + if ((vdr_flags & VDR_repair) && sequence >= vector->count()) + { + TRA_extend_tip(vdr_tdbb, sequence); + vector = dbb->dbb_t_pages; + vdr_fixed++; + } + else + continue; + } } + + if (saved_tip_next && saved_tip_next != (*vector)[sequence]) + corrupt(VAL_TIP_CONFUSED, 0, sequence - 1); WIN window(DB_PAGE_SPACE, -1); fetch_page(true, (*vector)[sequence], pag_transactions, &window, &page); + saved_tip_next = page->tip_next; #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) fprintf(stdout, "walk_tip: page %d next %d\n", (*vector)[sequence], page->tip_next); #endif - if (page->tip_next && page->tip_next != (*vector)[sequence + 1]) + if (sequence == last) { - corrupt(VAL_TIP_CONFUSED, 0, sequence); + for (ULONG number = (vdr_max_transaction % trans_per_tip) + 1; number < trans_per_tip; number++) + { + const ULONG byte = TRANS_OFFSET(number); + const USHORT shift = TRANS_SHIFT(number); + const int state = (page->tip_transactions[byte] >> shift) & TRA_MASK; + + if (state != tra_active) + { + if (!advanced_NT) + corrupt(VAL_TIP_NON_ACTIVE_AFTER_NT, 0, (*vector)[sequence], sequence); + + advanced_NT = sequence * trans_per_tip + number; + } + } } + release_page(&window); } + if ((vdr_flags & VDR_repair) && advanced_NT) + { + WIN window(DB_PAGE_SPACE, -1); + header_page* header = 0; + fetch_page(false, HEADER_PAGE, pag_header, &window, &header); + CCH_MARK(vdr_tdbb, &window); + writeNT(header, advanced_NT); + release_page(&window); + + vdr_max_transaction = advanced_NT; + vdr_fixed++; + } + return rtn_ok; } diff --git a/src/jrd/validation.h b/src/jrd/validation.h index b2d56aaf19e..990ecadfbe3 100644 --- a/src/jrd/validation.h +++ b/src/jrd/validation.h @@ -131,8 +131,9 @@ class Validation VAL_DATA_PAGE_SLOT_BAD_VAL = 37, VAL_DATA_PAGE_HASNO_PP = 38, VAL_DATA_PAGE_SEC_PRI = 39, + VAL_TIP_NON_ACTIVE_AFTER_NT = 40, - VAL_MAX_ERROR = 40 + VAL_MAX_ERROR }; struct MSG_ENTRY @@ -221,7 +222,7 @@ class Validation RTN walk_relation(jrd_rel*); RTN walk_root(jrd_rel*, bool); RTN walk_scns(); - RTN walk_tip(TraNumber); + RTN walk_tip(); }; } // namespace Jrd