From 0f75dde5856d13717f2fe3eda12aea3654e66708 Mon Sep 17 00:00:00 2001 From: sunliqiang Date: Sun, 25 Oct 2026 16:14:52 +0800 Subject: [PATCH 1/5] Validate irt_backslot against the index slot count A slot left in the irt_migrate state by ALTER INDEX ... SET TABLESPACE stores the number of the slot holding the original index in irt_backslot. getIndexSlot() and the irt_migrate branch of modifyIrtRepeat() turned that field into &irt_rpt[backId] with only an fb_assert guarding the range, so a corrupted index root page moved both the reads in getIndexSlot() and the setKill() write in modifyIrtRepeat() up to 65535 * 24 bytes past the page buffer. Add index_root_page::getBackSlot() returning NULL when the stored slot points outside the page and raise BUGCHECK(204) in both call sites instead of using the bad pointer. Checked with a database whose index root page carries a migrated slot with irt_backslot = 0xFFFF: CREATE INDEX used to corrupt engine memory, now it stops with the index inconsistency error while normal DDL and DML are unaffected. Unit tests for the slot range check run as part of libEngine14_test. --- src/jrd/btr.cpp | 12 +++--- src/jrd/ods.h | 14 +++++++ src/jrd/tests/IndexRootPageTest.cpp | 62 +++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 src/jrd/tests/IndexRootPageTest.cpp diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 43a8cc794a0..e1eb7a2f340 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -233,10 +233,11 @@ namespace if (traNumber && rpt->getTransaction() != traNumber) { - const auto backId = rpt->irt_backslot; - fb_assert(backId < root->irt_count); + const auto backSlot = root->getBackSlot(rpt); + if (!backSlot) + BUGCHECK(204); // msg 204 index inconsistent - return &root->irt_rpt[backId]; + return backSlot; } } @@ -3189,8 +3190,9 @@ static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::ir CCH_MARK(tdbb, window); const auto backId = irt_desc->irt_backslot; - fb_assert(backId < root->irt_count); - const auto backIrtDesc = &root->irt_rpt[backId]; + const auto backIrtDesc = root->getBackSlot(irt_desc); + if (!backIrtDesc) + BUGCHECK(204); // msg 204 index inconsistent if (state == tra_committed) { diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 76e0b4f16bf..0bb029226e3 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -449,6 +449,20 @@ struct index_root_page void setEmpty(); } irt_rpt[1]; + // Return the backing slot referenced by a migrating index slot, + // or NULL when the stored slot number points outside this page. + const irt_repeat* getBackSlot(const irt_repeat* rpt) const + { + const auto slot = rpt->irt_backslot; + return slot < irt_count ? &irt_rpt[slot] : nullptr; + } + + irt_repeat* getBackSlot(const irt_repeat* rpt) + { + const auto slot = rpt->irt_backslot; + return slot < irt_count ? &irt_rpt[slot] : nullptr; + } + static_assert(sizeof(struct irt_repeat) == 24, "struct irt_repeat size mismatch"); static_assert(offsetof(struct irt_repeat, irt_transaction) == 0, "irt_transaction offset mismatch"); static_assert(offsetof(struct irt_repeat, irt_page_num) == 8, "irt_root offset mismatch"); diff --git a/src/jrd/tests/IndexRootPageTest.cpp b/src/jrd/tests/IndexRootPageTest.cpp new file mode 100644 index 00000000000..b292f51294f --- /dev/null +++ b/src/jrd/tests/IndexRootPageTest.cpp @@ -0,0 +1,62 @@ +#include "firebird.h" +#include "boost/test/unit_test.hpp" +#include "../jrd/ods.h" + +using namespace Firebird; +using namespace Jrd; +using namespace Ods; + +BOOST_AUTO_TEST_SUITE(EngineSuite) +BOOST_AUTO_TEST_SUITE(IndexRootPageSuite) + + +BOOST_AUTO_TEST_SUITE(BackSlotTests) + +// Index root page image with room for 16 slots +union RootPageImage +{ + index_root_page page; + UCHAR bytes[sizeof(index_root_page) + 15 * sizeof(index_root_page::irt_repeat)]; +}; + +// Zero-filled image, so every test case starts from a clean page +static index_root_page* makeRootPage(unsigned count) +{ + static RootPageImage image; + + memset(&image, 0, sizeof(image)); + image.page.irt_relation = 1; + image.page.irt_count = count; + return &image.page; +} + +BOOST_AUTO_TEST_CASE(BackSlotInRange) +{ + auto root = makeRootPage(4); + + root->irt_rpt[1].irt_backslot = 2; + BOOST_TEST(root->getBackSlot(&root->irt_rpt[1]) == &root->irt_rpt[2]); + + root->irt_rpt[1].irt_backslot = 3; // last valid slot + BOOST_TEST(root->getBackSlot(&root->irt_rpt[1]) == &root->irt_rpt[3]); +} + +BOOST_AUTO_TEST_CASE(BackSlotOutOfRange) +{ + auto root = makeRootPage(4); + + root->irt_rpt[0].irt_backslot = 4; // == irt_count + BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); + + root->irt_rpt[0].irt_backslot = 0xFFFF; + BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); + + root->irt_count = 0; // no slot may resolve anymore + BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); +} + +BOOST_AUTO_TEST_SUITE_END() // BackSlotTests + + +BOOST_AUTO_TEST_SUITE_END() // IndexRootPageSuite +BOOST_AUTO_TEST_SUITE_END() // EngineSuite From 4e348eef18209249a2c14ad2bae6f45a9896b1aa Mon Sep 17 00:00:00 2001 From: sunliqiang Date: Sun, 25 Oct 2026 20:29:40 +0800 Subject: [PATCH 2/5] Require the irt_migrate state in getBackSlot() getBackSlot() encapsulates the backslot logic and may later be used by callers that did not check the slot state first. Assert the slot is in the irt_migrate state and return NULL for other states as well, so such a call still ends in BUGCHECK(204) in release builds. The definitions move below the irt_* state constants (irt_migrate is declared after index_root_page), matching the layout of the other irt_repeat methods. IndexRootPageTest now builds the migrating slot through the regular state transitions and adds a case for a non-migrating slot. --- src/jrd/ods.h | 36 ++++++++++++++++++---------- src/jrd/tests/IndexRootPageTest.cpp | 37 +++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 0bb029226e3..13966f690f0 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -450,18 +450,10 @@ struct index_root_page } irt_rpt[1]; // Return the backing slot referenced by a migrating index slot, - // or NULL when the stored slot number points outside this page. - const irt_repeat* getBackSlot(const irt_repeat* rpt) const - { - const auto slot = rpt->irt_backslot; - return slot < irt_count ? &irt_rpt[slot] : nullptr; - } - - irt_repeat* getBackSlot(const irt_repeat* rpt) - { - const auto slot = rpt->irt_backslot; - return slot < irt_count ? &irt_rpt[slot] : nullptr; - } + // or NULL when the slot is not in the irt_migrate state or the + // stored slot number points outside this page + const irt_repeat* getBackSlot(const irt_repeat* rpt) const; + irt_repeat* getBackSlot(const irt_repeat* rpt); static_assert(sizeof(struct irt_repeat) == 24, "struct irt_repeat size mismatch"); static_assert(offsetof(struct irt_repeat, irt_transaction) == 0, "irt_transaction offset mismatch"); @@ -789,6 +781,26 @@ inline void index_root_page::irt_repeat::setMigrate(const Jrd::PageNumber& rootP setState(irt_migrate); } +inline const index_root_page::irt_repeat* index_root_page::getBackSlot(const irt_repeat* rpt) const +{ + fb_assert(rpt->getState() == irt_migrate); + + if (rpt->getState() != irt_migrate || rpt->irt_backslot >= irt_count) + return nullptr; + + return &irt_rpt[rpt->irt_backslot]; +} + +inline index_root_page::irt_repeat* index_root_page::getBackSlot(const irt_repeat* rpt) +{ + fb_assert(rpt->getState() == irt_migrate); + + if (rpt->getState() != irt_migrate || rpt->irt_backslot >= irt_count) + return nullptr; + + return &irt_rpt[rpt->irt_backslot]; +} + inline constexpr int STUFF_COUNT = 4; inline constexpr ULONG END_LEVEL = ~0; diff --git a/src/jrd/tests/IndexRootPageTest.cpp b/src/jrd/tests/IndexRootPageTest.cpp index b292f51294f..96172203355 100644 --- a/src/jrd/tests/IndexRootPageTest.cpp +++ b/src/jrd/tests/IndexRootPageTest.cpp @@ -30,14 +30,26 @@ static index_root_page* makeRootPage(unsigned count) return &image.page; } +// Bring a slot into the irt_migrate state through the regular state +// transitions, ending with the given backing slot number +static index_root_page::irt_repeat* makeMigratedSlot(index_root_page::irt_repeat* slot, USHORT backSlot) +{ + slot->setInProgress(12); + slot->setInProgress(PageNumber(1, 77), 12); + slot->setKill(); + slot->setRollback(12); + slot->setMigrate(PageNumber(1, 99), 12, backSlot); + return slot; +} + BOOST_AUTO_TEST_CASE(BackSlotInRange) { auto root = makeRootPage(4); - root->irt_rpt[1].irt_backslot = 2; + makeMigratedSlot(&root->irt_rpt[1], 2); BOOST_TEST(root->getBackSlot(&root->irt_rpt[1]) == &root->irt_rpt[2]); - root->irt_rpt[1].irt_backslot = 3; // last valid slot + root->irt_rpt[1].setMigrate(PageNumber(1, 99), 12, 3); // last valid slot BOOST_TEST(root->getBackSlot(&root->irt_rpt[1]) == &root->irt_rpt[3]); } @@ -45,16 +57,33 @@ BOOST_AUTO_TEST_CASE(BackSlotOutOfRange) { auto root = makeRootPage(4); - root->irt_rpt[0].irt_backslot = 4; // == irt_count + makeMigratedSlot(&root->irt_rpt[0], 4); // == irt_count BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); - root->irt_rpt[0].irt_backslot = 0xFFFF; + root->irt_rpt[0].setMigrate(PageNumber(1, 99), 12, 0xFFFF); BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); root->irt_count = 0; // no slot may resolve anymore BOOST_TEST(root->getBackSlot(&root->irt_rpt[0]) == nullptr); } +BOOST_AUTO_TEST_CASE(BackSlotNotMigrating) +{ + auto root = makeRootPage(4); + + // slot in the regular working state: the backing slot number is + // meaningless and must not resolve even when in range + auto slot = &root->irt_rpt[1]; + slot->setInProgress(12); + slot->setInProgress(PageNumber(1, 77), 12); + slot->setKill(); + slot->setRollback(12); + slot->setNormal(); + slot->irt_backslot = 2; + + BOOST_TEST(root->getBackSlot(slot) == nullptr); +} + BOOST_AUTO_TEST_SUITE_END() // BackSlotTests From d356e5d73d9ba8913e4938768f0e3c6fbb3e54e0 Mon Sep 17 00:00:00 2001 From: sunliqiang Date: Sun, 25 Oct 2026 22:28:39 +0800 Subject: [PATCH 3/5] Deduplicate the getBackSlot overloads The non-const overload now delegates to the const one instead of repeating its body. --- src/jrd/ods.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 13966f690f0..41cfc61f77a 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -793,12 +793,8 @@ inline const index_root_page::irt_repeat* index_root_page::getBackSlot(const irt inline index_root_page::irt_repeat* index_root_page::getBackSlot(const irt_repeat* rpt) { - fb_assert(rpt->getState() == irt_migrate); - - if (rpt->getState() != irt_migrate || rpt->irt_backslot >= irt_count) - return nullptr; - - return &irt_rpt[rpt->irt_backslot]; + return const_cast( + static_cast(this)->getBackSlot(rpt)); } inline constexpr int STUFF_COUNT = 4; From 1ca65a7cf6229a09fb82625156c4e0366e605453 Mon Sep 17 00:00:00 2001 From: sunliqiang Date: Mon, 26 Oct 2026 00:33:46 +0800 Subject: [PATCH 4/5] Release the index root page on bugcheck As discussed in the review: getIndexSlot() now takes the window and releases the index root page before raising BUGCHECK, the check in modifyIrtRepeat() runs before CCH_MARK() with the release in place, and BTR_description() and walk_index() pass their windows through so the error path never leaves the page latched. --- src/jrd/btr.cpp | 38 ++++++++++++++++++++------------- src/jrd/btr_proto.h | 4 ++-- src/jrd/idx.cpp | 10 ++++----- src/jrd/replication/Applier.cpp | 2 +- src/jrd/validation.cpp | 8 +++---- src/jrd/validation.h | 2 +- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index e1eb7a2f340..6d26fa27b60 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -221,7 +221,8 @@ namespace return TipCache::traState(tdbb, descTrans, checkPresence, creating); } - const index_root_page::irt_repeat* getIndexSlot(thread_db* tdbb, const index_root_page* root, MetaId indexId) + const index_root_page::irt_repeat* getIndexSlot(thread_db* tdbb, const index_root_page* root, + WIN* window, MetaId indexId) { const auto rpt = &root->irt_rpt[indexId]; @@ -235,7 +236,10 @@ namespace { const auto backSlot = root->getBackSlot(rpt); if (!backSlot) + { + CCH_RELEASE(tdbb, window); BUGCHECK(204); // msg 204 index inconsistent + } return backSlot; } @@ -334,7 +338,7 @@ void dumpIndexRoot(const char* up, const char* from, thread_db* tdbb, WIN* windo for (MetaId i = 0; i < root->irt_count; ++i) { const auto* idp = rel->lookupIndex(tdbb, i, 0); - const auto& rpt = *getIndexSlot(tdbb, root, i); + const auto& rpt = *getIndexSlot(tdbb, root, window, i); const auto page = rpt.getRootPage(); printf("Index %d '%s' root %d:%d tra %" SQUADFORMAT " %s\n", @@ -1137,7 +1141,7 @@ UCHAR* IndexDuplicateScanner::init(thread_db* tdbb, win* window) const auto relPerm = m_relation->getPermanent(); - if (!BTR_description(tdbb, relPerm, root, &m_index, m_indexId)) + if (!BTR_description(tdbb, relPerm, root, window, &m_index, m_indexId)) { CCH_RELEASE(tdbb, window); return nullptr; @@ -1313,7 +1317,7 @@ void BTR_all(thread_db* tdbb, jrd_rel* relation, IndexDescList& idxList, bool sy for (MetaId id = 0; id < root->irt_count; id++) { - const auto irt_desc = getIndexSlot(tdbb, root, id); + const auto irt_desc = getIndexSlot(tdbb, root, &window, id); const TraNumber descTrans = irt_desc->getTransaction(); switch (irt_desc->getState()) @@ -1347,7 +1351,7 @@ void BTR_all(thread_db* tdbb, jrd_rel* relation, IndexDescList& idxList, bool sy } index_desc idx; - if (BTR_description(tdbb, relPerm, root, &idx, id, + if (BTR_description(tdbb, relPerm, root, &window, &idx, id, BTR_DESCRIBE_NO_THROW | (sysRq ? BTR_DESCRIBE_SYSTEM_RQ : 0))) { idxList.add(idx); @@ -1745,8 +1749,8 @@ bool BTR_activate_index(thread_db* tdbb, jrd_rel* relation, MetaId id) } -bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_root_page* root, index_desc* idx, - MetaId id, USHORT flags) +bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_root_page* root, WIN* window, + index_desc* idx, MetaId id, USHORT flags) { /************************************** * @@ -1765,7 +1769,7 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro if (id >= root->irt_count) return false; - const auto irt_desc = getIndexSlot(tdbb, root, id); + const auto irt_desc = getIndexSlot(tdbb, root, window, id); const auto rootPage = irt_desc->getRootPage(); if (!rootPage) @@ -2166,7 +2170,7 @@ btree_page* BTR_find_page(thread_db* tdbb, window->win_page = rootPage.value(); const index_root_page* rpage = BTR_fetch_root(FB_FUNCTION, tdbb, window); - if (!BTR_description(tdbb, retrieval->getPermRelation(), rpage, idx, retrieval->irb_index)) + if (!BTR_description(tdbb, retrieval->getPermRelation(), rpage, window, idx, retrieval->irb_index)) { CCH_RELEASE(tdbb, window); IBERROR(260); // msg 260 index unexpectedly deleted @@ -2587,7 +2591,7 @@ bool BTR_lookup(thread_db* tdbb, jrd_rel* relation, MetaId id, index_desc* buffe WIN window(rootPage.value()); const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - const bool result = (id < root->irt_count && BTR_description(tdbb, relation->getPermanent(), root, buffer, id)); + const bool result = (id < root->irt_count && BTR_description(tdbb, relation->getPermanent(), root, &window, buffer, id)); CCH_RELEASE(tdbb, &window); return result; } @@ -3188,11 +3192,15 @@ static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::ir if (state != tra_committed && state != tra_dead) return ModifyIrtRepeatValue::Skip; - CCH_MARK(tdbb, window); const auto backId = irt_desc->irt_backslot; const auto backIrtDesc = root->getBackSlot(irt_desc); if (!backIrtDesc) + { + CCH_RELEASE(tdbb, window); BUGCHECK(204); // msg 204 index inconsistent + } + + CCH_MARK(tdbb, window); if (state == tra_committed) { @@ -3271,7 +3279,7 @@ bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, in { for (; id < root->irt_count; ++id) { - const auto irt_desc = getIndexSlot(tdbb, root, id); + const auto irt_desc = getIndexSlot(tdbb, root, window, id); if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), window, id)) { @@ -3292,7 +3300,7 @@ bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, in continue; } - if (BTR_description(tdbb, relation->getPermanent(), root, idx, id)) + if (BTR_description(tdbb, relation->getPermanent(), root, window, idx, id)) return true; } @@ -3676,7 +3684,7 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, MetaId id, SelectivityL return; } - const auto indexRpt = getIndexSlot(tdbb, root, id); + const auto indexRpt = getIndexSlot(tdbb, root, &window, id); const auto pageNumber = indexRpt->getRootPage(); if (!pageNumber) @@ -4030,7 +4038,7 @@ void BTR_copy_index(thread_db* tdbb, jrd_rel* relation, MetaId indexId, ULONG pa } index_desc idx; - if (BTR_description(tdbb, relation->getPermanent(), root, &idx, indexId)) + if (BTR_description(tdbb, relation->getPermanent(), root, &rootWindow, &idx, indexId)) { CCH_RELEASE(tdbb, &rootWindow); diff --git a/src/jrd/btr_proto.h b/src/jrd/btr_proto.h index c8deb401162..182e28c46f5 100644 --- a/src/jrd/btr_proto.h +++ b/src/jrd/btr_proto.h @@ -37,8 +37,8 @@ void BTR_complement_key(Jrd::temporary_key*); void BTR_create(Jrd::thread_db*, Jrd::IndexCreation&, Jrd::SelectivityList&); bool BTR_delete_index(Jrd::thread_db*, Jrd::win*, MetaId, bool); void BTR_delete_tree(Jrd::thread_db*, USHORT, USHORT, Jrd::PageNumber); -bool BTR_description(Jrd::thread_db*, Jrd::Cached::Relation*, const Ods::index_root_page*, Jrd::index_desc*, - MetaId, USHORT flags = 0); +bool BTR_description(Jrd::thread_db*, Jrd::Cached::Relation*, const Ods::index_root_page*, Jrd::WIN*, + Jrd::index_desc*, MetaId, USHORT flags = 0); DSC* BTR_eval_expression(Jrd::thread_db*, Jrd::index_desc*, Jrd::Record*); void BTR_evaluate(Jrd::thread_db*, const Jrd::IndexRetrieval*, Jrd::RecordBitmap**, Jrd::RecordBitmap*); UCHAR* BTR_find_leaf(Ods::btree_page*, Jrd::temporary_key*, UCHAR*, USHORT*, bool, int); diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index afe5718a63c..1d411be3edd 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -112,7 +112,7 @@ bool IDX_check_master_types(thread_db* tdbb, index_desc& idx, jrd_rel* partner_r const auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); // get the description of the partner index - const bool ok = BTR_description(tdbb, partner_relation->getPermanent(), root, &partner_idx, idx.idx_primary_index); + const bool ok = BTR_description(tdbb, partner_relation->getPermanent(), root, &window, &partner_idx, idx.idx_primary_index); CCH_RELEASE(tdbb, &window); if (!ok) @@ -1399,7 +1399,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, for (USHORT id = 0; id < root->irt_count; id++) { - if (BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id)) + if (BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id)) { const bool isComplementary = (idx.idx_flags & irt_complementary) == irt_complementary; const IndexCreation* creation = tdbb->tdbb_indexCreation; @@ -1497,7 +1497,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, if (isComplementary && !creatingIndex && !insertion.iib_removed) { root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id); // set 'deleted' node marker in lower bit and insert key into b-tree insertion.iib_number.increment(); @@ -1508,7 +1508,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, insertion.iib_number.setValue(recno); root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id); } } } @@ -2234,7 +2234,7 @@ static idx_e check_partner_index(thread_db* tdbb, // get the description of the partner index index_desc partner_idx; - if (!BTR_description(tdbb, getPermanent(partner_relation), root, &partner_idx, index_id)) + if (!BTR_description(tdbb, getPermanent(partner_relation), root, &window, &partner_idx, index_id)) { CCH_RELEASE(tdbb, &window); BUGCHECK(175); // msg 175 partner index description not found diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp index a9df67fae8c..4124400bcb2 100644 --- a/src/jrd/replication/Applier.cpp +++ b/src/jrd/replication/Applier.cpp @@ -1084,7 +1084,7 @@ bool Applier::lookupKey(thread_db* tdbb, jrd_rel* relation, index_desc& key) for (USHORT i = 0; i < root->irt_count; i++) { - if (BTR_description(tdbb, relation->getPermanent(), root, &idx, i)) + if (BTR_description(tdbb, relation->getPermanent(), root, &window, &idx, i)) { if (idx.idx_flags & idx_primary) { diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index 9c74f59f06a..85c896c59cd 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -2041,7 +2041,7 @@ void Validation::walk_generators() } } -Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_page, USHORT id) +Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_page, WIN* rootWindow, USHORT id) { /************************************** * @@ -2085,7 +2085,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ AutoSetRestoreFlag flags(&root_page->irt_rpt[id].irt_flags, irt_expression | irt_condition, false); - BTR_description(vdr_tdbb, getPermanent(relation), root_page, &idx, id); + BTR_description(vdr_tdbb, getPermanent(relation), root_page, rootWindow, &idx, id); } null_key = &nullKey; @@ -3369,14 +3369,14 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo) AutoSetRestoreFlag flag(&page->irt_rpt[i].irt_flags, irt_expression, false); IdxInfo info; - if (BTR_description(vdr_tdbb, getPermanent(relation), page, &info.m_desc, i)) + if (BTR_description(vdr_tdbb, getPermanent(relation), page, &window, &info.m_desc, i)) vdr_cond_idx.add(info); } continue; } output("Index %d (%s)\n", i + 1, index.toQuotedString().c_str()); - walk_index(relation, page, i); + walk_index(relation, page, &window, i); } release_page(&window); diff --git a/src/jrd/validation.h b/src/jrd/validation.h index 6063209bd8e..2e6d7e63a35 100644 --- a/src/jrd/validation.h +++ b/src/jrd/validation.h @@ -226,7 +226,7 @@ class Validation RTN walk_data_page(jrd_rel*, ULONG, ULONG, UCHAR&); void walk_database(); void walk_generators(); - RTN walk_index(jrd_rel*, Ods::index_root_page*, USHORT); + RTN walk_index(jrd_rel*, Ods::index_root_page*, WIN*, USHORT); void walk_pip(); RTN walk_pointer_page(jrd_rel*, ULONG); RTN walk_record(jrd_rel*, const Ods::rhd*, USHORT, RecordNumber, bool); From 7a570b031185086ebdc3af5d4a321acf6518f46b Mon Sep 17 00:00:00 2001 From: sunliqiang Date: Wed, 28 Oct 2026 23:50:58 +0800 Subject: [PATCH 5/5] Unwind the cache on the corrupted backslot paths Drop the WIN* threading again: getIndexSlot() and BTR_description() keep their signatures, so the fix stays local to the backslot handling. getIndexSlot() callers hold the index root page read-latched and never marked, so CCH_unwind(tdbb, false) releases the latched pages before BUGCHECK(204) reports the inconsistency. modifyIrtRepeat() runs on paths where BTR_create() has already marked the page and CCH_unwind() refuses to release marked buffers, so it releases its own window with CCH_RELEASE() - the window is already a parameter there. The check in modifyIrtRepeat() stays ahead of CCH_MARK() either way. --- src/jrd/btr.cpp | 31 +++++++++++++++---------------- src/jrd/btr_proto.h | 4 ++-- src/jrd/idx.cpp | 10 +++++----- src/jrd/replication/Applier.cpp | 2 +- src/jrd/validation.cpp | 8 ++++---- src/jrd/validation.h | 2 +- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 6d26fa27b60..61fb180a37f 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -221,8 +221,7 @@ namespace return TipCache::traState(tdbb, descTrans, checkPresence, creating); } - const index_root_page::irt_repeat* getIndexSlot(thread_db* tdbb, const index_root_page* root, - WIN* window, MetaId indexId) + const index_root_page::irt_repeat* getIndexSlot(thread_db* tdbb, const index_root_page* root, MetaId indexId) { const auto rpt = &root->irt_rpt[indexId]; @@ -237,7 +236,7 @@ namespace const auto backSlot = root->getBackSlot(rpt); if (!backSlot) { - CCH_RELEASE(tdbb, window); + CCH_unwind(tdbb, false); BUGCHECK(204); // msg 204 index inconsistent } @@ -338,7 +337,7 @@ void dumpIndexRoot(const char* up, const char* from, thread_db* tdbb, WIN* windo for (MetaId i = 0; i < root->irt_count; ++i) { const auto* idp = rel->lookupIndex(tdbb, i, 0); - const auto& rpt = *getIndexSlot(tdbb, root, window, i); + const auto& rpt = *getIndexSlot(tdbb, root, i); const auto page = rpt.getRootPage(); printf("Index %d '%s' root %d:%d tra %" SQUADFORMAT " %s\n", @@ -1141,7 +1140,7 @@ UCHAR* IndexDuplicateScanner::init(thread_db* tdbb, win* window) const auto relPerm = m_relation->getPermanent(); - if (!BTR_description(tdbb, relPerm, root, window, &m_index, m_indexId)) + if (!BTR_description(tdbb, relPerm, root, &m_index, m_indexId)) { CCH_RELEASE(tdbb, window); return nullptr; @@ -1317,7 +1316,7 @@ void BTR_all(thread_db* tdbb, jrd_rel* relation, IndexDescList& idxList, bool sy for (MetaId id = 0; id < root->irt_count; id++) { - const auto irt_desc = getIndexSlot(tdbb, root, &window, id); + const auto irt_desc = getIndexSlot(tdbb, root, id); const TraNumber descTrans = irt_desc->getTransaction(); switch (irt_desc->getState()) @@ -1351,7 +1350,7 @@ void BTR_all(thread_db* tdbb, jrd_rel* relation, IndexDescList& idxList, bool sy } index_desc idx; - if (BTR_description(tdbb, relPerm, root, &window, &idx, id, + if (BTR_description(tdbb, relPerm, root, &idx, id, BTR_DESCRIBE_NO_THROW | (sysRq ? BTR_DESCRIBE_SYSTEM_RQ : 0))) { idxList.add(idx); @@ -1749,8 +1748,8 @@ bool BTR_activate_index(thread_db* tdbb, jrd_rel* relation, MetaId id) } -bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_root_page* root, WIN* window, - index_desc* idx, MetaId id, USHORT flags) +bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_root_page* root, index_desc* idx, + MetaId id, USHORT flags) { /************************************** * @@ -1769,7 +1768,7 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro if (id >= root->irt_count) return false; - const auto irt_desc = getIndexSlot(tdbb, root, window, id); + const auto irt_desc = getIndexSlot(tdbb, root, id); const auto rootPage = irt_desc->getRootPage(); if (!rootPage) @@ -2170,7 +2169,7 @@ btree_page* BTR_find_page(thread_db* tdbb, window->win_page = rootPage.value(); const index_root_page* rpage = BTR_fetch_root(FB_FUNCTION, tdbb, window); - if (!BTR_description(tdbb, retrieval->getPermRelation(), rpage, window, idx, retrieval->irb_index)) + if (!BTR_description(tdbb, retrieval->getPermRelation(), rpage, idx, retrieval->irb_index)) { CCH_RELEASE(tdbb, window); IBERROR(260); // msg 260 index unexpectedly deleted @@ -2591,7 +2590,7 @@ bool BTR_lookup(thread_db* tdbb, jrd_rel* relation, MetaId id, index_desc* buffe WIN window(rootPage.value()); const index_root_page* const root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - const bool result = (id < root->irt_count && BTR_description(tdbb, relation->getPermanent(), root, &window, buffer, id)); + const bool result = (id < root->irt_count && BTR_description(tdbb, relation->getPermanent(), root, buffer, id)); CCH_RELEASE(tdbb, &window); return result; } @@ -3279,7 +3278,7 @@ bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, in { for (; id < root->irt_count; ++id) { - const auto irt_desc = getIndexSlot(tdbb, root, window, id); + const auto irt_desc = getIndexSlot(tdbb, root, id); if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), window, id)) { @@ -3300,7 +3299,7 @@ bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, in continue; } - if (BTR_description(tdbb, relation->getPermanent(), root, window, idx, id)) + if (BTR_description(tdbb, relation->getPermanent(), root, idx, id)) return true; } @@ -3684,7 +3683,7 @@ void BTR_selectivity(thread_db* tdbb, jrd_rel* relation, MetaId id, SelectivityL return; } - const auto indexRpt = getIndexSlot(tdbb, root, &window, id); + const auto indexRpt = getIndexSlot(tdbb, root, id); const auto pageNumber = indexRpt->getRootPage(); if (!pageNumber) @@ -4038,7 +4037,7 @@ void BTR_copy_index(thread_db* tdbb, jrd_rel* relation, MetaId indexId, ULONG pa } index_desc idx; - if (BTR_description(tdbb, relation->getPermanent(), root, &rootWindow, &idx, indexId)) + if (BTR_description(tdbb, relation->getPermanent(), root, &idx, indexId)) { CCH_RELEASE(tdbb, &rootWindow); diff --git a/src/jrd/btr_proto.h b/src/jrd/btr_proto.h index 182e28c46f5..c8deb401162 100644 --- a/src/jrd/btr_proto.h +++ b/src/jrd/btr_proto.h @@ -37,8 +37,8 @@ void BTR_complement_key(Jrd::temporary_key*); void BTR_create(Jrd::thread_db*, Jrd::IndexCreation&, Jrd::SelectivityList&); bool BTR_delete_index(Jrd::thread_db*, Jrd::win*, MetaId, bool); void BTR_delete_tree(Jrd::thread_db*, USHORT, USHORT, Jrd::PageNumber); -bool BTR_description(Jrd::thread_db*, Jrd::Cached::Relation*, const Ods::index_root_page*, Jrd::WIN*, - Jrd::index_desc*, MetaId, USHORT flags = 0); +bool BTR_description(Jrd::thread_db*, Jrd::Cached::Relation*, const Ods::index_root_page*, Jrd::index_desc*, + MetaId, USHORT flags = 0); DSC* BTR_eval_expression(Jrd::thread_db*, Jrd::index_desc*, Jrd::Record*); void BTR_evaluate(Jrd::thread_db*, const Jrd::IndexRetrieval*, Jrd::RecordBitmap**, Jrd::RecordBitmap*); UCHAR* BTR_find_leaf(Ods::btree_page*, Jrd::temporary_key*, UCHAR*, USHORT*, bool, int); diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index 1d411be3edd..afe5718a63c 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -112,7 +112,7 @@ bool IDX_check_master_types(thread_db* tdbb, index_desc& idx, jrd_rel* partner_r const auto root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); // get the description of the partner index - const bool ok = BTR_description(tdbb, partner_relation->getPermanent(), root, &window, &partner_idx, idx.idx_primary_index); + const bool ok = BTR_description(tdbb, partner_relation->getPermanent(), root, &partner_idx, idx.idx_primary_index); CCH_RELEASE(tdbb, &window); if (!ok) @@ -1399,7 +1399,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, for (USHORT id = 0; id < root->irt_count; id++) { - if (BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id)) + if (BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id)) { const bool isComplementary = (idx.idx_flags & irt_complementary) == irt_complementary; const IndexCreation* creation = tdbb->tdbb_indexCreation; @@ -1497,7 +1497,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, if (isComplementary && !creatingIndex && !insertion.iib_removed) { root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); // set 'deleted' node marker in lower bit and insert key into b-tree insertion.iib_number.increment(); @@ -1508,7 +1508,7 @@ void IDX_garbage_collect(thread_db* tdbb, record_param* rpb, RecordStack& going, insertion.iib_number.setValue(recno); root = BTR_fetch_root(FB_FUNCTION, tdbb, &window); - BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &window, &idx, id); + BTR_description(tdbb, getPermanent(rpb->rpb_relation), root, &idx, id); } } } @@ -2234,7 +2234,7 @@ static idx_e check_partner_index(thread_db* tdbb, // get the description of the partner index index_desc partner_idx; - if (!BTR_description(tdbb, getPermanent(partner_relation), root, &window, &partner_idx, index_id)) + if (!BTR_description(tdbb, getPermanent(partner_relation), root, &partner_idx, index_id)) { CCH_RELEASE(tdbb, &window); BUGCHECK(175); // msg 175 partner index description not found diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp index 4124400bcb2..a9df67fae8c 100644 --- a/src/jrd/replication/Applier.cpp +++ b/src/jrd/replication/Applier.cpp @@ -1084,7 +1084,7 @@ bool Applier::lookupKey(thread_db* tdbb, jrd_rel* relation, index_desc& key) for (USHORT i = 0; i < root->irt_count; i++) { - if (BTR_description(tdbb, relation->getPermanent(), root, &window, &idx, i)) + if (BTR_description(tdbb, relation->getPermanent(), root, &idx, i)) { if (idx.idx_flags & idx_primary) { diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index 85c896c59cd..9c74f59f06a 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -2041,7 +2041,7 @@ void Validation::walk_generators() } } -Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_page, WIN* rootWindow, USHORT id) +Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_page, USHORT id) { /************************************** * @@ -2085,7 +2085,7 @@ Validation::RTN Validation::walk_index(jrd_rel* relation, index_root_page* root_ AutoSetRestoreFlag flags(&root_page->irt_rpt[id].irt_flags, irt_expression | irt_condition, false); - BTR_description(vdr_tdbb, getPermanent(relation), root_page, rootWindow, &idx, id); + BTR_description(vdr_tdbb, getPermanent(relation), root_page, &idx, id); } null_key = &nullKey; @@ -3369,14 +3369,14 @@ Validation::RTN Validation::walk_root(jrd_rel* relation, bool getInfo) AutoSetRestoreFlag flag(&page->irt_rpt[i].irt_flags, irt_expression, false); IdxInfo info; - if (BTR_description(vdr_tdbb, getPermanent(relation), page, &window, &info.m_desc, i)) + if (BTR_description(vdr_tdbb, getPermanent(relation), page, &info.m_desc, i)) vdr_cond_idx.add(info); } continue; } output("Index %d (%s)\n", i + 1, index.toQuotedString().c_str()); - walk_index(relation, page, &window, i); + walk_index(relation, page, i); } release_page(&window); diff --git a/src/jrd/validation.h b/src/jrd/validation.h index 2e6d7e63a35..6063209bd8e 100644 --- a/src/jrd/validation.h +++ b/src/jrd/validation.h @@ -226,7 +226,7 @@ class Validation RTN walk_data_page(jrd_rel*, ULONG, ULONG, UCHAR&); void walk_database(); void walk_generators(); - RTN walk_index(jrd_rel*, Ods::index_root_page*, WIN*, USHORT); + RTN walk_index(jrd_rel*, Ods::index_root_page*, USHORT); void walk_pip(); RTN walk_pointer_page(jrd_rel*, ULONG); RTN walk_record(jrd_rel*, const Ods::rhd*, USHORT, RecordNumber, bool);