diff --git a/src/common/classes/ClumpletReader.cpp b/src/common/classes/ClumpletReader.cpp index 2114d65f91..09b1d39013 100644 --- a/src/common/classes/ClumpletReader.cpp +++ b/src/common/classes/ClumpletReader.cpp @@ -355,6 +355,19 @@ ClumpletReader::ClumpletType ClumpletReader::getClumpletType(UCHAR tag) const return IntSpb; } break; + case isc_action_svc_validate: + switch(tag) + { + case isc_spb_val_tab_incl: + case isc_spb_val_tab_excl: + case isc_spb_val_idx_incl: + case isc_spb_val_idx_excl: + case isc_spb_dbname: + return StringSpb; + case isc_spb_val_lock_timeout: + return IntSpb; + } + break; } invalid_structure("wrong spb state"); break; diff --git a/src/common/utils.cpp b/src/common/utils.cpp index e224437fbe..a7027b75b5 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -1028,4 +1028,65 @@ void logAndDie(const char* text) #endif } + +const char switch_char = '-'; + +bool switchMatch(const Firebird::string& sw, const char* target) +{ +/************************************** + * + * s w i t c h M a t c h + * + ************************************** + * + * Functional description + * Returns true if switch matches target + * + **************************************/ + size_t n = strlen(target); + if (n < sw.length()) + { + return false; + } + n = sw.length(); + return memcmp(sw.c_str(), target, n) == 0; +} + + +in_sw_tab_t* findSwitch(in_sw_tab_t* table, Firebird::string sw) +{ +/************************************** + * + * f i n d S w i t c h + * + ************************************** + * + * Functional description + * Returns pointer to in_sw_tab entry for current switch + * If not a switch, returns 0. + * + **************************************/ + if (sw.isEmpty()) + { + return 0; + } + if (sw[0] != switch_char) + { + return 0; + } + sw.erase(0, 1); + sw.upper(); + + for (in_sw_tab_t* in_sw_tab = table; in_sw_tab->in_sw_name; in_sw_tab++) + { + if ((sw.length() >= in_sw_tab->in_sw_min_length) && + switchMatch(sw, in_sw_tab->in_sw_name)) + { + return in_sw_tab; + } + } + + return 0; +} + } // namespace fb_utils diff --git a/src/common/utils_proto.h b/src/common/utils_proto.h index d2710156c9..bd6b010335 100644 --- a/src/common/utils_proto.h +++ b/src/common/utils_proto.h @@ -139,6 +139,9 @@ namespace fb_utils void logAndDie(const char* text); + // command-line helpers + bool switchMatch(const Firebird::string& sw, const char* target); + in_sw_tab_t* findSwitch(in_sw_tab_t* table, Firebird::string sw); } // namespace fb_utils #endif // INCLUDE_UTILS_PROTO_H diff --git a/src/include/consts_pub.h b/src/include/consts_pub.h index 3b726b68f4..36b4bb570b 100644 --- a/src/include/consts_pub.h +++ b/src/include/consts_pub.h @@ -293,7 +293,8 @@ #define isc_action_svc_set_mapping 27 // Set auto admins mapping in security database #define isc_action_svc_drop_mapping 28 // Drop auto admins mapping in security database #define isc_action_svc_display_user_adm 29 // Displays user(s) from security database with admin info -#define isc_action_svc_last 30 // keep it last ! +#define isc_action_svc_validate 30 // Starts database online validation +#define isc_action_svc_last 31 // keep it last ! /***************************** * Service information items * @@ -463,6 +464,16 @@ #define isc_spb_res_create 0x2000 #define isc_spb_res_use_all_space 0x4000 +/***************************************** + * Parameters for isc_action_svc_validate * + *****************************************/ + +#define isc_spb_val_tab_incl 1 // include filter based on regular expression +#define isc_spb_val_tab_excl 2 // exclude filter based on regular expression +#define isc_spb_val_idx_incl 3 // regexp of indices to validate +#define isc_spb_val_idx_excl 4 // regexp of indices to NOT validate +#define isc_spb_val_lock_timeout 5 // how long to wait for table lock + /****************************************** * Parameters for isc_spb_res_access_mode * ******************************************/ diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp index d0958ab246..fc4692228d 100644 --- a/src/jrd/Relation.cpp +++ b/src/jrd/Relation.cpp @@ -26,6 +26,7 @@ #include "../jrd/btr_proto.h" #include "../jrd/dpm_proto.h" #include "../jrd/idx_proto.h" +#include "../jrd/lck_proto.h" #include "../jrd/met_proto.h" #include "../jrd/pag_proto.h" #include "../jrd/vio_debug.h" @@ -382,6 +383,229 @@ bool jrd_rel::hasTriggers() const return false; } +Lock* jrd_rel::createLock(thread_db* tdbb, MemoryPool* pool, jrd_rel* relation, lck_t lckType, bool noAst) +{ + Database *dbb = tdbb->getDatabase(); + if (!pool) + pool = dbb->dbb_permanent; + + const SSHORT relLockLen = relation->getRelLockKeyLength(); + + Lock* lock = FB_NEW_RPT(*pool, relLockLen) Lock(); + lock->lck_dbb = dbb; + + lock->lck_length = relLockLen; + relation->getRelLockKey(tdbb, &lock->lck_key.lck_string[0]); + + lock->lck_type = lckType; + switch (lckType) + { + case LCK_relation: + break; + + case LCK_rel_gc: + lock->lck_ast = noAst ? NULL : blocking_ast_gcLock; + break; + + default: + fb_assert(false); + } + lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type); + lock->lck_parent = dbb->dbb_lock; + lock->lck_object = relation; + + return lock; +} + +bool jrd_rel::acquireGCLock(thread_db* tdbb, int wait) +{ + fb_assert(rel_flags & REL_gc_lockneed); + if (!(rel_flags & REL_gc_lockneed)) + { + fb_assert(rel_gc_lock->lck_id); + fb_assert(rel_gc_lock->lck_physical == (rel_flags & REL_gc_disabled ? LCK_SR : LCK_SW)); + return true; + } + + if (!rel_gc_lock) + rel_gc_lock = createLock(tdbb, NULL, this, LCK_rel_gc, false); + + fb_assert(!rel_gc_lock->lck_id); + fb_assert(!(rel_flags & REL_gc_blocking)); + + ThreadStatusGuard temp_status(tdbb); + + const USHORT level = (rel_flags & REL_gc_disabled) ? LCK_SR : LCK_SW; + bool ret = LCK_lock(tdbb, rel_gc_lock, level, wait); + if (!ret && (level == LCK_SW)) + { + rel_flags |= REL_gc_disabled; + ret = LCK_lock(tdbb, rel_gc_lock, LCK_SR, wait); + if (!ret) + rel_flags &= ~REL_gc_disabled; + } + + if (ret) + rel_flags &= ~REL_gc_lockneed; + + return ret; +} + +void jrd_rel::downgradeGCLock(thread_db* tdbb) +{ + if (!rel_sweep_count && (rel_flags & REL_gc_blocking)) + { + fb_assert(!(rel_flags & REL_gc_lockneed)); + fb_assert(rel_gc_lock->lck_id); + fb_assert(rel_gc_lock->lck_physical == LCK_SW); + + rel_flags &= ~REL_gc_blocking; + rel_flags |= REL_gc_disabled; + + LCK_downgrade(tdbb, rel_gc_lock); + + if (rel_gc_lock->lck_physical != LCK_SR) + { + rel_flags &= ~REL_gc_disabled; + if (rel_gc_lock->lck_physical < LCK_SR) + rel_flags |= REL_gc_lockneed; + } + } +} + +int jrd_rel::blocking_ast_gcLock(void* ast_object) +{ +/**** + SR - gc forbidden, awaiting moment to re-establish SW lock + SW - gc allowed, usual state + PW - gc allowed to the one connection only +****/ + jrd_rel* relation = static_cast(ast_object); + + try + { + Lock* lock = relation->rel_gc_lock; + Database* dbb = lock->lck_dbb; + + AstContextHolder tdbb(dbb, lock->lck_attachment); + + fb_assert(!(relation->rel_flags & REL_gc_lockneed)); + if (relation->rel_flags & REL_gc_lockneed) // work already done syncronously ? + return 0; + + relation->rel_flags |= REL_gc_blocking; + if (relation->rel_sweep_count) + return 0; + + if (relation->rel_flags & REL_gc_disabled) + { + // someone is acquired EX lock + + fb_assert(lock->lck_id); + fb_assert(lock->lck_physical == LCK_SR); + + LCK_release(tdbb, lock); + relation->rel_flags &= ~(REL_gc_disabled | REL_gc_blocking); + relation->rel_flags |= REL_gc_lockneed; + } + else + { + // someone is acquired PW lock + + fb_assert(lock->lck_id); + fb_assert(lock->lck_physical == LCK_SW); + + relation->rel_flags |= REL_gc_disabled; + relation->downgradeGCLock(tdbb); + } + } + catch (const Firebird::Exception&) + {} // no-op + + return 0; +} + + +/// jrd_rel::GCExclusive + +jrd_rel::GCExclusive::GCExclusive(thread_db* tdbb, jrd_rel* relation) : + m_tdbb(tdbb), + m_relation(relation), + m_lock(NULL) +{ +} + +jrd_rel::GCExclusive::~GCExclusive() +{ + release(); + delete m_lock; +} + +bool jrd_rel::GCExclusive::acquire(int wait) +{ + // if validation is already running - go out + if (m_relation->rel_flags & REL_gc_disabled) + return false; + + ThreadStatusGuard temp_status(m_tdbb); + + m_relation->rel_flags |= REL_gc_disabled; + + int sleeps = -wait * 10; + while (m_relation->rel_sweep_count) + { + Database::Checkout cout(m_tdbb->getDatabase()); + THD_sleep(100); + + if (wait < 0 && --sleeps == 0) + break; + } + + if (m_relation->rel_sweep_count) + { + m_relation->rel_flags &= ~REL_gc_disabled; + return false; + } + + if (!(m_relation->rel_flags & REL_gc_lockneed)) + { + m_relation->rel_flags |= REL_gc_lockneed; + LCK_release(m_tdbb, m_relation->rel_gc_lock); + } + + // we need no AST here + if (!m_lock) + m_lock = jrd_rel::createLock(m_tdbb, NULL, m_relation, LCK_rel_gc, true); + + const bool ret = LCK_lock(m_tdbb, m_lock, LCK_PW, wait); + if (!ret) + m_relation->rel_flags &= ~REL_gc_disabled; + + return ret; +} + +void jrd_rel::GCExclusive::release() +{ + if (!m_lock || !m_lock->lck_id) + return; + + fb_assert(m_relation->rel_flags & REL_gc_disabled); + + if (!(m_relation->rel_flags & REL_gc_lockneed)) + { + m_relation->rel_flags |= REL_gc_lockneed; + LCK_release(m_tdbb, m_relation->rel_gc_lock); + } + + LCK_convert(m_tdbb, m_lock, LCK_EX, LCK_WAIT); + m_relation->rel_flags &= ~REL_gc_disabled; + + LCK_release(m_tdbb, m_lock); +} + + +/// RelationPages + void RelationPages::free(RelationPages*& nextFree) { rel_next_free = nextFree; diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index 7a1e215469..3d066c8a35 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -23,6 +23,7 @@ #define JRD_RELATION_H #include "../jrd/jrd.h" +#include "../jrd/lck.h" #include "../jrd/pag.h" #include "../jrd/val.h" @@ -202,6 +203,7 @@ public: Lock* rel_existence_lock; // existence lock, if any Lock* rel_partners_lock; // partners lock + Lock* rel_gc_lock; // garbage collection lock IndexLock* rel_index_locks; // index existence locks IndexBlock* rel_index_blocks; // index blocks for caching index info trig_vec* rel_pre_erase; // Pre-operation erase trigger @@ -273,11 +275,50 @@ private: RelationPages* getPagesInternal(thread_db* tdbb, SLONG tran, bool allocPages); public: - explicit jrd_rel(MemoryPool& p) - : rel_name(p), rel_owner_name(p), rel_view_contexts(p), rel_security_name(p) - { } + explicit jrd_rel(MemoryPool& p); bool hasTriggers() const; + + static Lock* createLock(thread_db* tdbb, MemoryPool* pool, jrd_rel* relation, lck_t, bool); + static int blocking_ast_gcLock(void*); + void downgradeGCLock(thread_db* tdbb); + bool acquireGCLock(thread_db* tdbb, int wait); + + // This guard is used by regular code to prevent online validation while + // dead- or back- versions is removed from disk. + class GCShared + { + public: + GCShared(thread_db* tdbb, jrd_rel* relation); + ~GCShared(); + + bool gcEnabled() const + { + return m_gcEnabled; + } + + private: + thread_db* m_tdbb; + jrd_rel* m_relation; + bool m_gcEnabled; + }; + + // This guard is used by online validation to prevent any modifications of + // table data while it is checked. + class GCExclusive + { + public: + GCExclusive(thread_db* tdbb, jrd_rel* relation); + ~GCExclusive(); + + bool acquire(int wait); + void release(); + + private: + thread_db* m_tdbb; + jrd_rel* m_relation; + Lock* m_lock; + }; }; // rel_flags @@ -299,8 +340,19 @@ const ULONG REL_temp_tran = 0x2000; // relation is a GTT delete rows const ULONG REL_temp_conn = 0x4000; // relation is a GTT preserve rows const ULONG REL_virtual = 0x8000; // relation is virtual const ULONG REL_jrd_view = 0x10000; // relation is VIEW +const ULONG REL_gc_blocking = 0x20000; // request to downgrade\release gc lock +const ULONG REL_gc_disabled = 0x40000; // gc is disabled temporary +const ULONG REL_gc_lockneed = 0x80000; // gc lock should be acquired +/// class jrd_rel + +inline jrd_rel::jrd_rel(MemoryPool& p) + : rel_name(p), rel_owner_name(p), rel_view_contexts(p), rel_security_name(p), + rel_flags(REL_gc_lockneed) +{ +} + inline bool jrd_rel::isSystem() const { return rel_flags & REL_system; @@ -329,6 +381,39 @@ inline RelationPages* jrd_rel::getPages(thread_db* tdbb, SLONG tran, bool allocP return getPagesInternal(tdbb, tran, allocPages); } +/// class jrd_rel::GCShared + +inline jrd_rel::GCShared::GCShared(thread_db* tdbb, jrd_rel* relation) : + m_tdbb(tdbb), + m_relation(relation), + m_gcEnabled(false) +{ + if (m_relation->rel_flags & (REL_gc_blocking | REL_gc_disabled)) + return; + + if (m_relation->rel_flags & REL_gc_lockneed) + m_relation->acquireGCLock(tdbb, LCK_NO_WAIT); + + if (!(m_relation->rel_flags & (REL_gc_blocking | REL_gc_disabled | REL_gc_lockneed))) + { + ++m_relation->rel_sweep_count; + m_gcEnabled = true; + } + + if ((m_relation->rel_flags & REL_gc_blocking) && !m_relation->rel_sweep_count) + m_relation->downgradeGCLock(m_tdbb); +} + +inline jrd_rel::GCShared::~GCShared() +{ + if (m_gcEnabled) + --m_relation->rel_sweep_count; + + if ((m_relation->rel_flags & REL_gc_blocking) && !m_relation->rel_sweep_count) + m_relation->downgradeGCLock(m_tdbb); +} + + // Field block, one for each field in a scanned relation class jrd_fld : public pool_alloc diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 717169ee17..9b22b1424d 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -2558,6 +2558,11 @@ void CMP_shutdown_database(thread_db* tdbb) LCK_release(tdbb, relation->rel_partners_lock); relation->rel_flags |= REL_check_partners; } + if (relation->rel_gc_lock) + { + LCK_release(tdbb, relation->rel_gc_lock); + relation->rel_flags |= REL_gc_lockneed; + } for (IndexLock* index = relation->rel_index_locks; index; index = index->idl_next) { if (index->idl_lock) { diff --git a/src/jrd/lck.cpp b/src/jrd/lck.cpp index d5e073db49..a2f9b2ba88 100644 --- a/src/jrd/lck.cpp +++ b/src/jrd/lck.cpp @@ -231,21 +231,25 @@ public: m_save_lock = att->att_wait_lock; m_cancel_disabled = (m_tdbb->tdbb_flags & TDBB_wait_cancel_disable); - m_tdbb->tdbb_flags |= TDBB_wait_cancel_disable; - - if (!wait) - return; - - switch (lock->lck_type) + if (wait == LCK_WAIT) + { + switch (lock->lck_type) + { + case LCK_tra: + m_tdbb->tdbb_flags &= ~TDBB_wait_cancel_disable; + if (att) + att->att_wait_lock = lock; + break; + + default: + m_tdbb->tdbb_flags |= TDBB_wait_cancel_disable; + } + } + else { - case LCK_tra: m_tdbb->tdbb_flags &= ~TDBB_wait_cancel_disable; if (att) att->att_wait_lock = lock; - break; - - default: - ; } } @@ -498,6 +502,7 @@ SLONG LCK_get_owner_handle(thread_db* tdbb, enum lck_t lock_type) case LCK_tt_exist: case LCK_shared_counter: case LCK_sweep: + case LCK_rel_gc: handle = *LCK_OWNER_HANDLE_DBB(tdbb); break; case LCK_attachment: diff --git a/src/jrd/lck.h b/src/jrd/lck.h index fd44ac031c..f0412c1c60 100644 --- a/src/jrd/lck.h +++ b/src/jrd/lck.h @@ -56,7 +56,8 @@ enum lck_t { LCK_cancel, // Cancellation lock LCK_btr_dont_gc, // Prevent removal of b-tree page from index LCK_shared_counter, // Database-wide shared counter - LCK_tra_pc // Precommitted transaction lock + LCK_tra_pc, // Precommitted transaction lock + LCK_rel_gc // Allow garbage collection for relation }; // Lock owner types diff --git a/src/jrd/lck_proto.h b/src/jrd/lck_proto.h index 92a7341085..a814cddc21 100644 --- a/src/jrd/lck_proto.h +++ b/src/jrd/lck_proto.h @@ -48,4 +48,53 @@ void LCK_release(Jrd::thread_db*, Jrd::Lock*); void LCK_re_post(Jrd::thread_db*, Jrd::Lock*); void LCK_write_data(Jrd::thread_db*, Jrd::Lock*, SLONG); + +class AutoLock +{ +public: + AutoLock(Jrd::thread_db* tdbb, Jrd::Lock* lck = NULL) : + m_tdbb(tdbb), + m_lock(lck) + { + } + + ~AutoLock() + { + release(); + } + + void release() + { + if (m_lock) + { + if (m_lock->lck_id) + LCK_release(m_tdbb, m_lock); + delete m_lock; + m_lock = NULL; + } + } + + Jrd::Lock* operator-> () + { + return m_lock; + } + + operator Jrd::Lock* () + { + return m_lock; + } + + Jrd::Lock* operator= (Jrd::Lock* lck) + { + release(); + m_lock = lck; + return m_lock; + } + +private: + Jrd::thread_db* m_tdbb; + Jrd::Lock* m_lock; +}; + + #endif // JRD_LCK_PROTO_H diff --git a/src/jrd/rlck.cpp b/src/jrd/rlck.cpp index 178b77570e..f7e8a51d68 100644 --- a/src/jrd/rlck.cpp +++ b/src/jrd/rlck.cpp @@ -104,8 +104,15 @@ Lock* RLCK_reserve_relation(thread_db* tdbb, result = LCK_convert(tdbb, lock, level, transaction->getLockWait()); else result = LCK_lock(tdbb, lock, level, transaction->getLockWait()); + if (!result) + { + string err; + err.printf("Acquire lock for relation (%s) failed", relation->rel_name.c_str()); + + ERR_append_status(tdbb->tdbb_status_vector, Arg::Gds(isc_random) << Arg::Str(err)); ERR_punt(); + } return lock; } @@ -139,17 +146,8 @@ Lock* RLCK_transaction_relation_lock(thread_db* tdbb, vec::newVector(*transaction->tra_pool, transaction->tra_relation_locks, relation->rel_id + 1); - const SSHORT relLockLen = relation->getRelLockKeyLength(); - lock = FB_NEW_RPT(*transaction->tra_pool, relLockLen) Lock(); - lock->lck_dbb = tdbb->getDatabase(); - lock->lck_length = relLockLen; - relation->getRelLockKey(tdbb, &lock->lck_key.lck_string[0]); - lock->lck_type = LCK_relation; - lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type); - lock->lck_parent = tdbb->getDatabase()->dbb_lock; - // the lck_object is used here to find the relation - // block from the lock block - lock->lck_object = relation; + lock = jrd_rel::createLock(tdbb, transaction->tra_pool, relation, LCK_relation, true); + // enter all relation locks into the intra-process lock manager and treat // them as compatible within the attachment according to IPLM rules lock->lck_compatible = tdbb->getAttachment(); diff --git a/src/jrd/svc.cpp b/src/jrd/svc.cpp index 5461121a99..7a5e70da72 100644 --- a/src/jrd/svc.cpp +++ b/src/jrd/svc.cpp @@ -71,6 +71,7 @@ #include "../jrd/trace/TraceManager.h" #include "../jrd/trace/TraceObjects.h" #include "../jrd/trace/TraceService.h" +#include "../jrd/val_proto.h" #ifdef HAVE_SYS_TYPES_H #include @@ -657,12 +658,14 @@ THREAD_ENTRY_DECLARE main_gstat(THREAD_ENTRY_PARAM arg); #define MAIN_GSTAT main_gstat #define MAIN_NBAK NBACKUP_main #define MAIN_TRACE TRACE_main +#define MAIN_VALIDATE VAL_service #else #define MAIN_GBAK NULL #define MAIN_GFIX NULL #define MAIN_GSTAT NULL #define MAIN_NBAK NULL #define MAIN_TRACE NULL +#define MAIN_VALIDATE NULL #endif #if !defined(EMBEDDED) && !defined(BOOT_BUILD) @@ -726,6 +729,7 @@ static const serv_entry services[] = { isc_action_svc_set_mapping, "Set Domain Admins Mapping to RDB$ADMIN", NULL, MAIN_GSEC }, { isc_action_svc_drop_mapping, "Drop Domain Admins Mapping to RDB$ADMIN", NULL, MAIN_GSEC }, { isc_action_svc_display_user_adm, "Display User with Admin Info", NULL, MAIN_GSEC }, + { isc_action_svc_validate, "Validate Database", NULL, MAIN_VALIDATE }, /* actions with no names are undocumented */ { isc_action_svc_set_config, NULL, NULL, TEST_THREAD }, { isc_action_svc_default_config, NULL, NULL, TEST_THREAD }, @@ -2166,7 +2170,8 @@ void Service::start(USHORT spb_length, const UCHAR* spb_data) svc_id == isc_action_svc_trace_resume || svc_id == isc_action_svc_trace_list || svc_id == isc_action_svc_set_mapping || - svc_id == isc_action_svc_drop_mapping) + svc_id == isc_action_svc_drop_mapping || + svc_id == isc_action_svc_validate) { /* add the username and password to the end of svc_switches if needed */ if (svc_switches.hasData()) @@ -2709,6 +2714,8 @@ bool Service::process_switches(ClumpletReader& spb, string& switches) string nbk_database, nbk_file; int nbk_level = -1; + bool val_database = false; + bool found = false; do @@ -3083,6 +3090,31 @@ bool Service::process_switches(ClumpletReader& spb, string& switches) } break; + case isc_action_svc_validate: + if (!get_action_svc_parameter(spb.getClumpTag(), val_option_in_sw_table, switches)) { + return false; + } + + switch (spb.getClumpTag()) + { + case isc_spb_dbname: + if (val_database) { + (Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_dbname")).raise(); + } + val_database = true; + // fall thru + case isc_spb_val_tab_incl: + case isc_spb_val_tab_excl: + case isc_spb_val_idx_incl: + case isc_spb_val_idx_excl: + get_action_svc_string(spb, switches); + break; + case isc_spb_val_lock_timeout: + get_action_svc_data(spb, switches); + break; + } + break; + default: return false; } @@ -3133,6 +3165,13 @@ bool Service::process_switches(ClumpletReader& spb, string& switches) switches += nbk_database; switches += nbk_file; break; + + case isc_action_svc_validate: + if (!val_database) + { + (Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_dbname")).raise(); + } + break; } switches.rtrim(); @@ -3179,7 +3218,7 @@ void Service::get_action_svc_data(const ClumpletReader& spb, string& switches) { string s; - s.printf("%"ULONGFORMAT" ", spb.getInt()); + s.printf("%"SLONGFORMAT" ", spb.getInt()); switches += s; } diff --git a/src/jrd/val_proto.h b/src/jrd/val_proto.h index 563e46b232..95e754ca53 100644 --- a/src/jrd/val_proto.h +++ b/src/jrd/val_proto.h @@ -25,6 +25,29 @@ #define JRD_VAL_PROTO_H bool VAL_validate(Jrd::thread_db*, USHORT); +THREAD_ENTRY_DECLARE VAL_service(THREAD_ENTRY_PARAM); + +const int IN_SW_VAL_TAB_INCL = 1; +const int IN_SW_VAL_TAB_EXCL = 2; +const int IN_SW_VAL_IDX_INCL = 3; +const int IN_SW_VAL_IDX_EXCL = 4; +const int IN_SW_VAL_LOCK_TIMEOUT = 5; +const int IN_SW_VAL_DATABASE = 6; +const int IN_SW_VAL_TRUSTED_USER = 7; + +static struct in_sw_tab_t val_option_in_sw_table [] = +{ + {IN_SW_VAL_TAB_INCL, isc_spb_val_tab_incl, "TAB_INCLUDE", 0, 0, 0, false, 0, 5, NULL}, + {IN_SW_VAL_TAB_EXCL, isc_spb_val_tab_excl, "TAB_EXCLUDE", 0, 0, 0, false, 0, 5, NULL}, + {IN_SW_VAL_IDX_INCL, isc_spb_val_idx_incl, "IDX_INCLUDE", 0, 0, 0, false, 0, 5, NULL}, + {IN_SW_VAL_IDX_EXCL, isc_spb_val_idx_excl, "IDX_EXCLUDE", 0, 0, 0, false, 0, 5, NULL}, + {IN_SW_VAL_LOCK_TIMEOUT, isc_spb_val_lock_timeout, "WAIT", 0, 0, 0, false, 0, 1, NULL}, + + {IN_SW_VAL_DATABASE, isc_spb_dbname, "DATABASE", 0, 0, 0, false, 0, 1, NULL}, + {IN_SW_VAL_TRUSTED_USER, 0, TRUSTED_USER_SWITCH, 0, 0, 0, false, 0, TRUSTED_USER_SWITCH_LEN, NULL}, + + {0, 0, NULL, 0, 0, 0, false, 0, 0, NULL} // End of List +}; #endif // JRD_VAL_PROTO_H diff --git a/src/jrd/validation.cpp b/src/jrd/validation.cpp index cf1b13914c..8a782b4832 100644 --- a/src/jrd/validation.cpp +++ b/src/jrd/validation.cpp @@ -556,6 +556,7 @@ VI. ADDITIONAL NOTES #include "../jrd/rse.h" #include "../jrd/sbm.h" #include "../jrd/tra.h" +#include "../jrd/svc.h" #include "../jrd/btr_proto.h" #include "../jrd/cch_proto.h" #include "../jrd/dbg_proto.h" @@ -568,6 +569,12 @@ VI. ADDITIONAL NOTES #include "../jrd/val_proto.h" #include "../jrd/thread_proto.h" +#include "../common/utils_proto.h" +#include "../common/classes/ClumpletWriter.h" +#include "../common/classes/PublicHandle.h" +#include "../jrd/intl_proto.h" +#include "../jrd/lck_proto.h" + #ifdef DEBUG_VAL_VERBOSE #include "../jrd/dmp_proto.h" /* Control variable for verbose output during debug of @@ -580,28 +587,9 @@ static USHORT VAL_debug_level = 0; using namespace Jrd; using namespace Ods; +using namespace Firebird; // Validation/garbage collection/repair control block - -struct vdr -{ - PageBitmap* vdr_page_bitmap; - SLONG vdr_max_page; - USHORT vdr_flags; - USHORT vdr_errors; - SLONG vdr_max_transaction; - ULONG vdr_rel_backversion_counter; // Counts slots w/rhd_chain - ULONG vdr_rel_chain_counter; // Counts chains w/rdr_chain - RecordBitmap* vdr_rel_records; // 1 bit per valid record - RecordBitmap* vdr_idx_records; // 1 bit per index item -}; - -// vdr_flags - -const USHORT vdr_update = 2; // fix simple things -const USHORT vdr_repair = 4; // fix non-simple things (-mend) -const USHORT vdr_records = 8; // Walk all records - enum FETCH_CODE { fetch_ok, //fetch_checksum, @@ -616,9 +604,97 @@ enum RTN { }; +class Validation +{ +public: + Validation(thread_db*, UtilSvc* uSvc = NULL); + ~Validation(); + + void run(thread_db*, USHORT); +private: + + RTN corrupt(thread_db*, USHORT, const jrd_rel*, ...); + FETCH_CODE fetch_page(thread_db*, SLONG, USHORT, WIN *, void *, bool = true); + void garbage_collect(thread_db*); +#ifdef DEBUG_VAL_VERBOSE + void print_rhd(USHORT, const rhd*); +#endif + + void parse_args(thread_db*); + void output(const char*, ...); + + RTN walk_blob(thread_db*, jrd_rel*, blh*, USHORT, RecordNumber); + RTN walk_chain(thread_db*, jrd_rel*, rhd*, RecordNumber); + void walk_database(thread_db*); + RTN walk_data_page(thread_db*, jrd_rel*, SLONG, SLONG); + void walk_generators(thread_db*); + void walk_header(thread_db*, SLONG); + RTN walk_index(thread_db*, jrd_rel*, index_root_page&, USHORT); + void walk_log(thread_db*); + void walk_pip(thread_db*); + RTN walk_pointer_page(thread_db*, jrd_rel*, int); + RTN walk_record(thread_db*, jrd_rel*, rhd*, USHORT, RecordNumber, bool); + RTN walk_relation(thread_db*, jrd_rel*); + RTN walk_root(thread_db*, jrd_rel*); + RTN walk_tip(thread_db*, SLONG); + + PageBitmap* vdr_page_bitmap; + SLONG vdr_max_page; + USHORT vdr_flags; + USHORT vdr_errors; + SLONG vdr_max_transaction; + ULONG vdr_rel_backversion_counter; // Counts slots w/rhd_chain + ULONG vdr_rel_chain_counter; // Counts chains w/rdr_chain + RecordBitmap* vdr_rel_records; // 1 bit per valid record + RecordBitmap* vdr_idx_records; // 1 bit per index item + + UtilSvc* vdr_service; + PatternMatcher* vdr_tab_incl; + PatternMatcher* vdr_tab_excl; + PatternMatcher* vdr_idx_incl; + PatternMatcher* vdr_idx_excl; + int vdr_lock_tout; +}; + +// vdr_flags + +const USHORT VDR_online = 0x01; // online validation (no exclusive attachment) +const USHORT VDR_update = 0x02; // fix simple things +const USHORT VDR_repair = 0x04; // fix non-simple things (-mend) +const USHORT VDR_records = 0x08; // Walk all records +const USHORT VDR_partial = 0x10; // Walk only (some) relations + + +static PatternMatcher* createPatternMatcher(thread_db* tdbb, const char* pattern) +{ + PatternMatcher* matcher = NULL; + try + { + if (pattern) + { + const int len = strlen(pattern); + + Collation* obj = INTL_texttype_lookup(tdbb, CS_UTF8); + matcher = obj->createSimilarToMatcher(*tdbb->getDefaultPool(), + (const UCHAR*) pattern, len, (UCHAR*)"\\", 1); + } + } + catch(const Exception& ex) + { + ISC_STATUS_ARRAY temp; + ex.stuff_exception(temp); + + Arg::StatusVector status(temp); + status << Arg::Gds(isc_random) << Arg::Str(pattern); + status.raise(); + } + return matcher; +} + + #pragma FB_COMPILER_MESSAGE("This table goes to gds__log and it's not localized") -static const TEXT msg_table[VAL_MAX_ERROR][68] = +static const TEXT msg_table[VAL_MAX_ERROR][80] = { "Page %ld wrong type (expected %d encountered %d)", // 0 "Checksum error on page %ld", @@ -631,7 +707,7 @@ static const TEXT msg_table[VAL_MAX_ERROR][68] = "Chain for record %"QUADFORMAT"d is broken", "Data page %ld (sequence %ld) is confused", "Data page %ld (sequence %ld), line %ld is bad", // 10 - "Index %d is corrupt on page %ld level %d. File: %s, line: %d\n\t", + "Index %d is corrupt on page %ld level %d at offset %d. File: %s, line: %d\n\t", "Pointer page (sequence %ld) lost", "Pointer page (sequence %ld) inconsistent", "Record %"QUADFORMAT"d is marked as damaged", @@ -643,7 +719,7 @@ static const TEXT msg_table[VAL_MAX_ERROR][68] = "Transaction inventory page lost, sequence %ld", // 20 "Transaction inventory pages confused, sequence %ld", "Relation has %ld orphan backversions (%ld in use)", - "Index %d is corrupt (missing entries)", + "Index %d is corrupt (record %"QUADFORMAT"d have missing entries)", "Index %d has orphan child page at page %ld", "Index %d has a circular reference at page %ld", // 25 "Index %d has inconsistent left sibling pointer, page %ld level %d", @@ -651,29 +727,6 @@ static const TEXT msg_table[VAL_MAX_ERROR][68] = }; -static RTN corrupt(thread_db*, vdr*, USHORT, const jrd_rel*, ...); -static FETCH_CODE fetch_page(thread_db*, vdr*, SLONG, USHORT, WIN *, void *); -static void garbage_collect(thread_db*, vdr*); -#ifdef DEBUG_VAL_VERBOSE -static void print_rhd(USHORT, const rhd*); -#endif -static RTN walk_blob(thread_db*, vdr*, jrd_rel*, blh*, USHORT, RecordNumber); -static RTN walk_chain(thread_db*, vdr*, jrd_rel*, rhd*, RecordNumber); -static void walk_database(thread_db*, vdr*); -static RTN walk_data_page(thread_db*, vdr*, jrd_rel*, SLONG, SLONG); -static void walk_generators(thread_db*, vdr*); -static void walk_header(thread_db*, vdr*, SLONG); -static RTN walk_index(thread_db*, vdr*, jrd_rel*, index_root_page&, USHORT); -static void walk_log(thread_db*, vdr*); -static void walk_pip(thread_db*, vdr*); -static RTN walk_pointer_page(thread_db*, vdr*, jrd_rel*, int); -static RTN walk_record(thread_db*, vdr*, jrd_rel*, rhd*, USHORT, RecordNumber, bool); -static RTN walk_relation(thread_db*, vdr*, jrd_rel*); -static RTN walk_root(thread_db*, vdr*, jrd_rel*); -static RTN walk_tip(thread_db*, vdr*, SLONG); - - - bool VAL_validate(thread_db* tdbb, USHORT switches) { /************************************** @@ -697,41 +750,23 @@ bool VAL_validate(thread_db* tdbb, USHORT switches) val_pool = dbb->createPool(); Jrd::ContextPoolHolder context(tdbb, val_pool); - vdr control; - control.vdr_page_bitmap = NULL; - control.vdr_flags = 0; - control.vdr_errors = 0; - + USHORT flags = 0; if (switches & isc_dpb_records) - control.vdr_flags |= vdr_records; + flags |= VDR_records; if (switches & isc_dpb_repair) - control.vdr_flags |= vdr_repair; + flags |= VDR_repair; if (!(switches & isc_dpb_no_update)) - control.vdr_flags |= vdr_update; - - control.vdr_max_page = 0; - control.vdr_rel_records = NULL; - control.vdr_idx_records = NULL; + flags |= VDR_update; // initialize validate errors - if (!att->att_val_errors) { - att->att_val_errors = vcl::newVector(*att->att_pool, VAL_MAX_ERROR); - } - else - { - for (USHORT i = 0; i < VAL_MAX_ERROR; i++) - (*att->att_val_errors)[i] = 0; - } - tdbb->tdbb_flags |= TDBB_sweeper; - walk_database(tdbb, &control); - if (control.vdr_errors) - control.vdr_flags &= ~vdr_update; + + Validation control(tdbb); + control.run(tdbb, flags); - garbage_collect(tdbb, &control); CCH_flush(tdbb, FLUSH_FINI, 0); tdbb->tdbb_flags &= ~TDBB_sweeper; @@ -748,7 +783,299 @@ bool VAL_validate(thread_db* tdbb, USHORT switches) return true; } -static RTN corrupt(thread_db* tdbb, vdr* control, USHORT err_code, const jrd_rel* relation, ...) +void Validation::run(thread_db* tdbb, USHORT flags) +{ + Attachment* att = tdbb->getAttachment(); + + if (!att->att_val_errors) { + att->att_val_errors = vcl::newVector(*att->att_pool, VAL_MAX_ERROR); + } + else + { + for (USHORT i = 0; i < VAL_MAX_ERROR; i++) + (*att->att_val_errors)[i] = 0; + } + + vdr_flags = flags; + + walk_database(tdbb); + + if (vdr_errors) + vdr_flags &= ~VDR_update; + + if (!(vdr_flags & VDR_online) && !(vdr_flags & VDR_partial)) + garbage_collect(tdbb); +} + + +Validation::Validation(thread_db* tdbb, UtilSvc* uSvc) +{ + vdr_page_bitmap = NULL; + vdr_max_page = 0; + vdr_flags = 0; + vdr_errors = 0; + vdr_max_transaction = 0; + vdr_rel_backversion_counter = 0; + vdr_rel_chain_counter = 0; + vdr_rel_records = NULL; // 1 bit per valid record + vdr_idx_records = NULL; + + vdr_service = uSvc; + vdr_tab_incl = vdr_tab_excl = NULL; + vdr_idx_incl = vdr_idx_excl = NULL; + vdr_lock_tout = -10; + + if (uSvc) { + parse_args(tdbb); + } + output("Validation started\n\n"); +} + +Validation::~Validation() +{ + delete vdr_tab_incl; + delete vdr_tab_excl; + delete vdr_idx_incl; + delete vdr_idx_excl; + + output("Validation finished\n"); +} + + +void Validation::parse_args(thread_db* tdbb) +{ + in_sw_tab_t local_sw_table[sizeof(val_option_in_sw_table) / sizeof(val_option_in_sw_table[0])]; + memcpy(local_sw_table, val_option_in_sw_table, sizeof(val_option_in_sw_table)); + + const in_sw_tab_t* action_sw = NULL; + const char** argv = vdr_service->argv.begin(); + const char* const* end = vdr_service->argv.end(); + for (++argv; argv < end; argv++) + { + if (!*argv) + continue; + + in_sw_tab_t* sw = fb_utils::findSwitch(&local_sw_table[0], *argv); + if (!sw) + continue; + + if (sw->in_sw_state) + { + string s; + s.printf("Switch %s specified more then once", sw->in_sw_name); + + (Arg::Gds(isc_random) << Arg::Str(s)).raise(); + } + + sw->in_sw_state = true; + + switch (sw->in_sw) + { + case IN_SW_VAL_TAB_INCL: + case IN_SW_VAL_TAB_EXCL: + case IN_SW_VAL_IDX_INCL: + case IN_SW_VAL_IDX_EXCL: + case IN_SW_VAL_LOCK_TIMEOUT: + *argv++ = NULL; + if (argv >= end || !(*argv)) + { + string s; + s.printf("Switch %s requires value", sw->in_sw_name); + + (Arg::Gds(isc_random) << Arg::Str(s)).raise(); + } + break; + + default: + break; + } + + switch (sw->in_sw) + { + case IN_SW_VAL_TAB_INCL: + vdr_tab_incl = createPatternMatcher(tdbb, *argv); + break; + + case IN_SW_VAL_TAB_EXCL: + vdr_tab_excl = createPatternMatcher(tdbb, *argv); + break; + + case IN_SW_VAL_IDX_INCL: + vdr_idx_incl = createPatternMatcher(tdbb, *argv); + break; + + case IN_SW_VAL_IDX_EXCL: + vdr_idx_excl = createPatternMatcher(tdbb, *argv); + break; + + case IN_SW_VAL_LOCK_TIMEOUT: + { + char* end = (char*) *argv; + vdr_lock_tout = -strtol(*argv, &end, 10); + + if (end && *end) + { + string s; + s.printf("Value (%s) is not a valid number", *argv); + + (Arg::Gds(isc_random) << Arg::Str(s)).raise(); + } + } + break; + + default: + break; + } + } +} + + +void Validation::output(const char* format, ...) +{ + if (!vdr_service) + return; + + va_list params; + va_start(params, format); + + string s; + tm now; + int ms; + TimeStamp::getCurrentTimeStamp().decode(&now, &ms); + +// s.printf("%04d-%02d-%02d %02d:%02d:%02d.%04d ", + s.printf("%02d:%02d:%02d.%02d ", +// now.tm_year + 1900, now.tm_mon + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec, ms/100); + vdr_service->outputVerbose(s.c_str()); + + s.vprintf(format, params); + va_end(params); + + vdr_service->outputVerbose(s.c_str()); +} + +static int validate(Service* svc) +{ + PathName dbName; + string userName; + + const in_sw_tab_t* action_sw = NULL; + const char** argv = svc->argv.begin(); + const char* const* end = svc->argv.end(); + for (++argv; argv < end; argv++) + { + if (!*argv) + continue; + + in_sw_tab_t* sw = fb_utils::findSwitch(&val_option_in_sw_table[0], *argv); + if (!sw) + continue; + + switch (sw->in_sw) + { + case IN_SW_VAL_DATABASE: + *argv = NULL; + argv++; + if (argv < end && *argv) + dbName = *argv; + else + ;// error + break; + + case IN_SW_VAL_TRUSTED_USER: + *argv = NULL; + argv++; + if (argv < end && *argv) + userName = *argv; + else; + // error + break; + + default: + break; + } + } + + ClumpletWriter dpb(ClumpletReader::Tagged, MAX_DPB_SIZE, isc_dpb_version1); + if (!userName.isEmpty()) + { + dpb.insertString(isc_dpb_trusted_auth, userName); + } + + ISC_STATUS_ARRAY status = {0}; + Attachment *att = NULL; + + if (jrd8_attach_database(status, dbName.c_str(), &att, dpb.getBufferLength(), dpb.getBuffer())) + { + svc->setServiceStatus(status); + return FB_FAILURE; + } + Database* dbb = att->att_database; + + svc->started(); + + MemoryPool* val_pool = NULL; + int ret_code = FB_SUCCESS; + try + { + ThreadContextHolder tdbb(status); + + // should be AttachmentHolder but it is declared in jrd.cpp + PublicHandleHolder attHolder(att, "validate"); + + tdbb->setDatabase(dbb); + tdbb->setAttachment(att); + tdbb->tdbb_flags |= TDBB_sweeper; + + // should be DatabaseContextHolder but it is declared in jrd.cpp + Database::SyncGuard dsGuard(dbb); + + val_pool = dbb->createPool(); + Jrd::ContextPoolHolder context(tdbb, val_pool); + + Validation control(tdbb, svc); + control.run(tdbb, VDR_records | VDR_online | VDR_partial); + } + catch(const Exception& ex) + { + ex.stuff_exception(status); + svc->setServiceStatus(status); + ret_code = FB_FAILURE; + } + + dbb->deletePool(val_pool); + jrd8_detach_database(status, &att); + return ret_code; +} + + +THREAD_ENTRY_DECLARE VAL_service(THREAD_ENTRY_PARAM arg) +{ + Service* svc = (Service*) arg; + svc->initStatus(); + + int exit_code = FB_SUCCESS; + + try + { + exit_code = validate(svc); + } + catch (const Exception& e) + { + ISC_STATUS_ARRAY status; + e.stuff_exception(status); + svc->setServiceStatus(status); + exit_code = FB_FAILURE; + } + + svc->started(); + svc->finish(); + + return (THREAD_ENTRY_RETURN)(IPTR) exit_code; +} + +RTN Validation::corrupt(thread_db* tdbb, USHORT err_code, const jrd_rel* relation, ...) { /************************************** * @@ -768,12 +1095,12 @@ static RTN corrupt(thread_db* tdbb, vdr* control, USHORT err_code, const jrd_rel const TEXT* err_string = err_code < VAL_MAX_ERROR ? msg_table[err_code]: "Unknown error code"; - TEXT s[256] = ""; + string s; va_list ptr; const char* fn = tdbb->getAttachment()->att_filename.c_str(); va_start(ptr, relation); - VSNPRINTF(s, sizeof(s), err_string, ptr); + s.vprintf(err_string, ptr); va_end(ptr); #ifdef DEBUG_VAL_VERBOSE @@ -782,31 +1109,31 @@ static RTN corrupt(thread_db* tdbb, vdr* control, USHORT err_code, const jrd_rel if (relation) { fprintf(stdout, "LOG:\tDatabase: %s\n\t%s in table %s (%d)\n", - fn, s, relation->rel_name.c_str(), relation->rel_id); + fn, s.c_str(), relation->rel_name.c_str(), relation->rel_id); } else - fprintf(stdout, "LOG:\tDatabase: %s\n\t%s\n", fn, s); + fprintf(stdout, "LOG:\tDatabase: %s\n\t%s\n", fn, s.c_str()); } #endif if (relation) { gds__log("Database: %s\n\t%s in table %s (%d)", - fn, s, relation->rel_name.c_str(), relation->rel_id); + fn, s.c_str(), relation->rel_name.c_str(), relation->rel_id); } else - gds__log("Database: %s\n\t%s", fn, s); + gds__log("Database: %s\n\t%s", fn, s.c_str()); - if (control) - ++control->vdr_errors; + ++vdr_errors; + + s.append("\n"); + output(s.c_str()); return rtn_corrupt; } -static FETCH_CODE fetch_page(thread_db* tdbb, - vdr* control, - SLONG page_number, - USHORT type, WIN* window, void *page_pointer) +FETCH_CODE Validation::fetch_page(thread_db* tdbb, SLONG page_number, USHORT type, + WIN* window, void *page_pointer, bool mark) { /************************************** * @@ -815,8 +1142,8 @@ static FETCH_CODE fetch_page(thread_db* tdbb, ************************************** * * Functional description - * Fetch page and return type of illness, if any. If a control block - * is present, check for doubly allocated pages and account for page + * Fetch page and return type of illness, if any. If a "mark" + * is true, check for doubly allocated pages and account for page * use. * **************************************/ @@ -825,32 +1152,43 @@ static FETCH_CODE fetch_page(thread_db* tdbb, CHECK_DBB(dbb); if (--tdbb->tdbb_quantum < 0) - JRD_reschedule(tdbb, 0, true); + { + JRD_reschedule(tdbb, 0, true); + + if (vdr_service && vdr_service->finished()) + { + CCH_unwind(tdbb, false); + Arg::Gds(isc_att_shutdown).raise(); + } + } window->win_page = page_number; window->win_flags = 0; - *(PAG*) page_pointer = CCH_FETCH_NO_SHADOW(tdbb, window, LCK_write, 0); + *(PAG*) page_pointer = CCH_FETCH_NO_SHADOW(tdbb, window, + (vdr_flags & VDR_online ? LCK_read : LCK_write), + 0); + if ((*(PAG*) page_pointer)->pag_type != type) { - corrupt(tdbb, control, VAL_PAG_WRONG_TYPE, 0, page_number, type, + corrupt(tdbb, VAL_PAG_WRONG_TYPE, 0, page_number, type, (*(PAG*) page_pointer)->pag_type); return fetch_type; } - if (!control) + if (!mark) return fetch_ok; // If "damaged" flag was set, checksum may be incorrect. Check. if ((dbb->dbb_flags & DBB_damaged) && !CCH_validate(window)) { - corrupt(tdbb, control, VAL_PAG_CHECKSUM_ERR, 0, page_number); - if (control->vdr_flags & vdr_repair) + corrupt(tdbb, VAL_PAG_CHECKSUM_ERR, 0, page_number); + if (vdr_flags & VDR_repair) CCH_MARK(tdbb, window); } - control->vdr_max_page = MAX(control->vdr_max_page, page_number); + vdr_max_page = MAX(vdr_max_page, page_number); // For walking back versions & record fragments on data pages we // sometimes will fetch the same page more than once. In that @@ -858,19 +1196,19 @@ static FETCH_CODE fetch_page(thread_db* tdbb, // double allocated (to more than one relation) we'll find it // when the on-page relation id doesn't match - if ((type != pag_data) && PageBitmap::test(control->vdr_page_bitmap, page_number)) + if ((type != pag_data) && PageBitmap::test(vdr_page_bitmap, page_number)) { - corrupt(tdbb, control, VAL_PAG_DOUBLE_ALLOC, 0, page_number); + corrupt(tdbb, VAL_PAG_DOUBLE_ALLOC, 0, page_number); return fetch_duplicate; } - PBM_SET(tdbb->getDefaultPool(), &control->vdr_page_bitmap, page_number); + PBM_SET(tdbb->getDefaultPool(), &vdr_page_bitmap, page_number); return fetch_ok; } -static void garbage_collect(thread_db* tdbb, vdr* control) +void Validation::garbage_collect(thread_db* tdbb) { /************************************** * @@ -893,24 +1231,24 @@ static void garbage_collect(thread_db* tdbb, vdr* control) WIN window(DB_PAGE_SPACE, -1); - for (SLONG sequence = 0, number = 0; number < control->vdr_max_page; sequence++) + for (SLONG sequence = 0, number = 0; number < vdr_max_page; sequence++) { const SLONG page_number = sequence ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->ppFirst; page_inv_page* page = 0; - fetch_page(tdbb, 0, page_number, pag_pages, &window, &page); + fetch_page(tdbb, page_number, pag_pages, &window, &page, false); UCHAR* p = page->pip_bits; const UCHAR* const end = p + pageSpaceMgr.bytesBitPIP; - while (p < end && number < control->vdr_max_page) + while (p < end && number < vdr_max_page) { UCHAR byte = *p++; for (int i = 8; i; --i, byte >>= 1, number++) { - if (PageBitmap::test(control->vdr_page_bitmap, number)) + if (PageBitmap::test(vdr_page_bitmap, number)) { if (byte & 1) { - corrupt(tdbb, control, VAL_PAG_IN_USE, 0, number); - if (control->vdr_flags & vdr_update) + corrupt(tdbb, VAL_PAG_IN_USE, 0, number); + if (vdr_flags & VDR_update) { CCH_MARK(tdbb, &window); p[-1] &= ~(1 << (number & 7)); @@ -918,13 +1256,13 @@ static void garbage_collect(thread_db* tdbb, vdr* control) DEBUG; } } - else if (!(byte & 1) && (control->vdr_flags & vdr_records)) + else if (!(byte & 1) && (vdr_flags & VDR_records)) { // Page is potentially an orphan - but don't declare it as such // unless we think we walked all pages - corrupt(tdbb, control, VAL_PAG_ORPHAN, 0, number); - if (control->vdr_flags & vdr_update) + corrupt(tdbb, VAL_PAG_ORPHAN, 0, number); + if (vdr_flags & VDR_update) { CCH_MARK(tdbb, &window); p[-1] |= 1 << (number & 7); @@ -944,19 +1282,19 @@ static void garbage_collect(thread_db* tdbb, vdr* control) if (VAL_debug_level >= 2) { // We are assuming RSE_get_forward - if (control->vdr_page_bitmap->getFirst()) + if (vdr_page_bitmap->getFirst()) { do { - SLONG dmp_page_number = control->vdr_page_bitmap->current(); + SLONG dmp_page_number = vdr_page_bitmap->current(); DMP_page(dmp_page_number, dbb->dbb_page_size); - } while (control->vdr_page_bitmap->getNext()); + } while (vdr_page_bitmap->getNext()); } } #endif } #ifdef DEBUG_VAL_VERBOSE -static void print_rhd(USHORT length, const rhd* header) +void Validation::print_rhd(USHORT length, const rhd* header) { /************************************** * @@ -993,9 +1331,7 @@ static void print_rhd(USHORT length, const rhd* header) } #endif -static RTN walk_blob(thread_db* tdbb, - vdr* control, - jrd_rel* relation, blh* header, USHORT length, RecordNumber number) +RTN Validation::walk_blob(thread_db* tdbb, jrd_rel* relation, blh* header, USHORT length, RecordNumber number) { /************************************** * @@ -1028,6 +1364,7 @@ static RTN walk_blob(thread_db* tdbb, // Level 1 blobs are a little more complicated WIN window1(DB_PAGE_SPACE, -1), window2(DB_PAGE_SPACE, -1); + window1.win_flags = window2.win_flags = WIN_garbage_collector; const SLONG* pages1 = header->blh_page; const SLONG* const end1 = pages1 + ((USHORT) (length - BLH_SIZE) >> SHIFTLONG); @@ -1036,14 +1373,13 @@ static RTN walk_blob(thread_db* tdbb, for (sequence = 0; pages1 < end1; pages1++) { blob_page* page1 = 0; - fetch_page(tdbb, control, *pages1, pag_blob, &window1, &page1); - if (page1->blp_lead_page != header->blh_lead_page) { - corrupt(tdbb, control, VAL_BLOB_INCONSISTENT, relation, number.getValue()); - } + fetch_page(tdbb, *pages1, pag_blob, &window1, &page1); + if (page1->blp_lead_page != header->blh_lead_page) + corrupt(tdbb, VAL_BLOB_INCONSISTENT, relation, number.getValue()); if ((header->blh_level == 1 && page1->blp_sequence != sequence)) { - corrupt(tdbb, control, VAL_BLOB_CORRUPT, relation, number.getValue()); - CCH_RELEASE(tdbb, &window1); + corrupt(tdbb, VAL_BLOB_CORRUPT, relation, number.getValue()); + CCH_RELEASE_TAIL(tdbb, &window1); return rtn_corrupt; } if (header->blh_level == 1) @@ -1055,29 +1391,27 @@ static RTN walk_blob(thread_db* tdbb, for (; pages2 < end2; pages2++, sequence++) { blob_page* page2 = 0; - fetch_page(tdbb, control, *pages2, pag_blob, &window2, &page2); + fetch_page(tdbb, *pages2, pag_blob, &window2, &page2); if (page2->blp_lead_page != header->blh_lead_page || page2->blp_sequence != sequence) { - corrupt(tdbb, control, VAL_BLOB_CORRUPT, relation, number.getValue()); - CCH_RELEASE(tdbb, &window1); - CCH_RELEASE(tdbb, &window2); + corrupt(tdbb, VAL_BLOB_CORRUPT, relation, number.getValue()); + CCH_RELEASE_TAIL(tdbb, &window1); + CCH_RELEASE_TAIL(tdbb, &window2); return rtn_corrupt; } - CCH_RELEASE(tdbb, &window2); + CCH_RELEASE_TAIL(tdbb, &window2); } } - CCH_RELEASE(tdbb, &window1); + CCH_RELEASE_TAIL(tdbb, &window1); } if (sequence - 1 != header->blh_max_sequence) - return corrupt(tdbb, control, VAL_BLOB_TRUNCATED, relation, number.getValue()); + return corrupt(tdbb, VAL_BLOB_TRUNCATED, relation, number.getValue()); return rtn_ok; } -static RTN walk_chain(thread_db* tdbb, - vdr* control, - jrd_rel* relation, rhd* header, RecordNumber head_number) +RTN Validation::walk_chain(thread_db* tdbb, jrd_rel* relation, rhd* header, RecordNumber head_number) { /************************************** * @@ -1098,6 +1432,7 @@ static RTN walk_chain(thread_db* tdbb, SLONG page_number = header->rhd_b_page; USHORT line_number = header->rhd_b_line; WIN window(DB_PAGE_SPACE, -1); + window.win_flags = WIN_garbage_collector; while (page_number) { @@ -1106,28 +1441,28 @@ static RTN walk_chain(thread_db* tdbb, if (VAL_debug_level) fprintf(stdout, " BV %02d: ", ++counter); #endif - control->vdr_rel_chain_counter++; + vdr_rel_chain_counter++; data_page* page = 0; - fetch_page(tdbb, control, page_number, pag_data, &window, &page); + fetch_page(tdbb, page_number, pag_data, &window, &page); const data_page::dpg_repeat* line = &page->dpg_rpt[line_number]; header = (rhd*) ((UCHAR *) page + line->dpg_offset); if (page->dpg_count <= line_number || !line->dpg_length || (header->rhd_flags & (rhd_blob | rhd_fragment)) || - walk_record(tdbb, control, relation, header, line->dpg_length, + walk_record(tdbb, relation, header, line->dpg_length, head_number, delta_flag) != rtn_ok) { - CCH_RELEASE(tdbb, &window); - return corrupt(tdbb, control, VAL_REC_CHAIN_BROKEN, relation, head_number.getValue()); + CCH_RELEASE_TAIL(tdbb, &window); + return corrupt(tdbb, VAL_REC_CHAIN_BROKEN, relation, head_number.getValue()); } page_number = header->rhd_b_page; line_number = header->rhd_b_line; - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); } return rtn_ok; } -static void walk_database(thread_db* tdbb, vdr* control) +void Validation::walk_database(thread_db* tdbb) { /************************************** * @@ -1155,14 +1490,21 @@ static void walk_database(thread_db* tdbb, vdr* control) DPM_scan_pages(tdbb); WIN window(DB_PAGE_SPACE, -1); header_page* page = 0; - fetch_page(tdbb, control, (SLONG) HEADER_PAGE, pag_header, &window, &page); - control->vdr_max_transaction = page->hdr_next_transaction; + fetch_page(tdbb, (SLONG) HEADER_PAGE, pag_header, &window, &page); + vdr_max_transaction = page->hdr_next_transaction; - walk_header(tdbb, control, page->hdr_next_page); - walk_log(tdbb, control); - walk_pip(tdbb, control); - walk_tip(tdbb, control, page->hdr_next_transaction); - walk_generators(tdbb, control); + if (vdr_flags & VDR_online) { + CCH_RELEASE(tdbb, &window); + } + + if (!(vdr_flags & VDR_partial)) + { + walk_header(tdbb, page->hdr_next_page); + walk_log(tdbb); + walk_pip(tdbb); + walk_tip(tdbb, page->hdr_next_transaction); + walk_generators(tdbb); + } vec* vector; for (USHORT i = 0; (vector = dbb->dbb_relations) && i < vector->count(); i++) @@ -1173,15 +1515,59 @@ static void walk_database(thread_db* tdbb, vdr* control) #endif jrd_rel* relation = (*vector)[i]; if (relation) - walk_relation(tdbb, control, relation); + { + + // Seems we need rel_flags only and can replace relatively heavy and potentially + // dangerous (if DB is hard corrupted) MET_scan_relation by more lightweight + // MET_lookup_relation_id + + relation = MET_lookup_relation_id(tdbb, relation->rel_id, false); + if (!relation) { + continue; + } + + // Can't validate system relations online as they could be modified + // by system transaction which not acquires relation locks + if ((vdr_flags & VDR_online) && relation->isSystem()) + continue; + + if (vdr_tab_incl) + { + vdr_tab_incl->reset(); + if (!vdr_tab_incl->process((UCHAR*)relation->rel_name.c_str(), relation->rel_name.length()) || + !vdr_tab_incl->result()) + continue; + } + + if (vdr_tab_excl) + { + vdr_tab_excl->reset(); + if (!vdr_tab_excl->process((UCHAR*)relation->rel_name.c_str(), relation->rel_name.length()) || + vdr_tab_excl->result()) + continue; + } + + string relName; + relName.printf("Relation %d (%s)", relation->rel_id, relation->rel_name.c_str()); + output("%s\n", relName.c_str()); + + int errs = vdr_errors; + walk_relation(tdbb, relation); + errs = vdr_errors - errs; + + if (!errs) + output("%s is ok\n\n", relName.c_str()); + else + output("%s : %d ERRORS found\n\n", relName.c_str(), errs); + } } - CCH_RELEASE(tdbb, &window); + if (!(vdr_flags & VDR_online)) { + CCH_RELEASE(tdbb, &window); + } } -static RTN walk_data_page(thread_db* tdbb, - vdr* control, - jrd_rel* relation, SLONG page_number, SLONG sequence) +RTN Validation::walk_data_page(thread_db* tdbb, jrd_rel* relation, SLONG page_number, SLONG sequence) { /************************************** * @@ -1197,8 +1583,10 @@ static RTN walk_data_page(thread_db* tdbb, Database* dbb = tdbb->getDatabase(); WIN window(DB_PAGE_SPACE, -1); + window.win_flags = WIN_garbage_collector; + data_page* page = 0; - fetch_page(tdbb, control, page_number, pag_data, &window, &page); + fetch_page(tdbb, page_number, pag_data, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -1212,9 +1600,9 @@ static RTN walk_data_page(thread_db* tdbb, if (page->dpg_relation != relation->rel_id || page->dpg_sequence != sequence) { - ++control->vdr_errors; - CCH_RELEASE(tdbb, &window); - return corrupt(tdbb, control, VAL_DATA_PAGE_CONFUSED, relation, page_number, sequence); + ++vdr_errors; + CCH_RELEASE_TAIL(tdbb, &window); + return corrupt(tdbb, VAL_DATA_PAGE_CONFUSED, relation, page_number, sequence); } // Walk records @@ -1237,16 +1625,16 @@ static RTN walk_data_page(thread_db* tdbb, rhd* header = (rhd*) ((UCHAR *) page + line->dpg_offset); if ((UCHAR *) header < (UCHAR *) end || (UCHAR *) header + line->dpg_length > end_page) { - CCH_RELEASE(tdbb, &window); - return corrupt(tdbb, control, VAL_DATA_PAGE_LINE_ERR, relation, page_number, + CCH_RELEASE_TAIL(tdbb, &window); + return corrupt(tdbb, VAL_DATA_PAGE_LINE_ERR, relation, page_number, sequence, (SLONG) (line - page->dpg_rpt)); } if (header->rhd_flags & rhd_chain) - control->vdr_rel_backversion_counter++; + vdr_rel_backversion_counter++; // Record the existance of a primary version of a record - if ((control->vdr_flags & vdr_records) && + if ((vdr_flags & VDR_records) && !(header->rhd_flags & (rhd_chain | rhd_fragment | rhd_blob))) { // Only set committed (or limbo) records in the bitmap. If there @@ -1255,7 +1643,7 @@ static RTN walk_data_page(thread_db* tdbb, // state of the lone primary record version. if (header->rhd_b_page) - RBM_SET(tdbb->getDefaultPool(), &control->vdr_rel_records, number.getValue()); + RBM_SET(tdbb->getDefaultPool(), &vdr_rel_records, number.getValue()); else { int state; @@ -1264,7 +1652,7 @@ static RTN walk_data_page(thread_db* tdbb, else state = TRA_fetch_state(tdbb, header->rhd_transaction); if (state == tra_committed || state == tra_limbo) - RBM_SET(tdbb->getDefaultPool(), &control->vdr_rel_records, number.getValue()); + RBM_SET(tdbb->getDefaultPool(), &vdr_rel_records, number.getValue()); } } @@ -1280,12 +1668,12 @@ static RTN walk_data_page(thread_db* tdbb, } #endif if (!(header->rhd_flags & rhd_chain) && - ((header->rhd_flags & rhd_large) || (control->vdr_flags & vdr_records))) + ((header->rhd_flags & rhd_large) || (vdr_flags & VDR_records))) { const RTN result = (header->rhd_flags & rhd_blob) ? - walk_blob(tdbb, control, relation, (blh*) header, line->dpg_length, number) : - walk_record(tdbb, control, relation, header, line->dpg_length, number, false); - if ((result == rtn_corrupt) && (control->vdr_flags & vdr_repair)) + walk_blob(tdbb, relation, (blh*) header, line->dpg_length, number) : + walk_record(tdbb, relation, header, line->dpg_length, number, false); + if ((result == rtn_corrupt) && (vdr_flags & VDR_repair)) { CCH_MARK(tdbb, &window); header->rhd_flags |= rhd_damaged; @@ -1298,7 +1686,7 @@ static RTN walk_data_page(thread_db* tdbb, #endif } - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -1308,7 +1696,7 @@ static RTN walk_data_page(thread_db* tdbb, return rtn_ok; } -static void walk_generators(thread_db* tdbb, vdr* control) +void Validation::walk_generators(thread_db* tdbb) { /************************************** * @@ -1338,14 +1726,14 @@ static void walk_generators(thread_db* tdbb, vdr* control) fprintf(stdout, "walk_generator: page %d\n", *ptr); #endif pointer_page* page = 0; - fetch_page(tdbb, control, *ptr, pag_ids, &window, &page); + fetch_page(tdbb, *ptr, pag_ids, &window, &page); CCH_RELEASE(tdbb, &window); } } } } -static void walk_header(thread_db* tdbb, vdr* control, SLONG page_num) +void Validation::walk_header(thread_db* tdbb, SLONG page_num) { /************************************** * @@ -1367,14 +1755,13 @@ static void walk_header(thread_db* tdbb, vdr* control, SLONG page_num) #endif WIN window(DB_PAGE_SPACE, -1); header_page* page = 0; - fetch_page(tdbb, control, page_num, pag_header, &window, &page); + fetch_page(tdbb, page_num, pag_header, &window, &page); page_num = page->hdr_next_page; CCH_RELEASE(tdbb, &window); } } -static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, - index_root_page& root_page, USHORT id) +RTN Validation::walk_index(thread_db* tdbb, jrd_rel* relation, index_root_page& root_page, USHORT id) { /************************************** * @@ -1427,9 +1814,7 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, key.key_length = 0; SLONG previous_number = 0; - if (control) { - RecordBitmap::reset(control->vdr_idx_records); - } + RecordBitmap::reset(vdr_idx_records); bool firstNode = true; bool nullKeyNode = false; // current node is a null key of unique index @@ -1443,8 +1828,10 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, while (next) { WIN window(DB_PAGE_SPACE, -1); + window.win_flags = WIN_garbage_collector; + btree_page* page = 0; - fetch_page(tdbb, control, next, pag_index, &window, &page); + fetch_page(tdbb, next, pag_index, &window, &page); // remember each page for circular reference detection visited_pages.set(next); @@ -1452,8 +1839,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if ((next != page_number) && (page->btr_header.pag_flags & BTR_FLAG_COPY_MASK) != (flags & BTR_FLAG_COPY_MASK)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, 0, __FILE__, __LINE__); } flags = page->btr_header.pag_flags; const bool leafPage = (page->btr_level == 0); @@ -1465,9 +1852,9 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if (page->btr_relation != relation->rel_id || page->btr_id != (UCHAR) (id % 256)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, id + 1, - next, page->btr_level, __FILE__, __LINE__); - CCH_RELEASE(tdbb, &window); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, id + 1, + next, page->btr_level, 0, __FILE__, __LINE__); + CCH_RELEASE_TAIL(tdbb, &window); return rtn_corrupt; } @@ -1480,8 +1867,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if ((jumpInfo.firstNodeOffset < headerSize) || (jumpInfo.firstNodeOffset > page->btr_length)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, pointer - (UCHAR*)page, __FILE__, __LINE__); } USHORT n = jumpInfo.jumpers; @@ -1496,16 +1883,16 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if ((jumpNode.offset < jumpInfo.firstNodeOffset) || (jumpNode.offset > page->btr_length)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, pointer - (UCHAR*)page, __FILE__, __LINE__); } else { // Check if jump node has same length as data node prefix. BTreeNode::readNode(&checknode, (UCHAR*)page + jumpNode.offset, flags, leafPage); if ((jumpNode.prefix + jumpNode.length) != checknode.prefix) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, jumpNode.offset, __FILE__, __LINE__); } } n--; @@ -1527,6 +1914,14 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, break; } + if (node.prefix > key.key_length) + { + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); + CCH_RELEASE_TAIL(tdbb, &window); + return rtn_corrupt; + } + const UCHAR* p; const UCHAR* q; USHORT l; // temporary variable for length @@ -1542,8 +1937,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if (*p > *q) { duplicateNode = false; - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, q - (UCHAR*)page, __FILE__, __LINE__); } else if (*p < *q) { @@ -1563,8 +1958,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, node.prefix < key.key_length && node.length == 0) { duplicateNode = false; - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } // in descending index short key is greater then long key ('aaa' < 'aa') @@ -1588,8 +1983,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if (!ok) { duplicateNode = false; - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } } @@ -1609,8 +2004,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if ((!unique || (unique && nullKeyNode)) && (node.recordNumber < lastNode.recordNumber)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } } @@ -1638,22 +2033,24 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, } // Record the existance of a primary version of a record - if (leafPage && control && (control->vdr_flags & vdr_records)) { - RBM_SET(tdbb->getDefaultPool(), &control->vdr_idx_records, node.recordNumber.getValue()); + if (leafPage && (vdr_flags & VDR_records)) { + RBM_SET(tdbb->getDefaultPool(), &vdr_idx_records, node.recordNumber.getValue()); } // fetch the next page down (if full validation was specified) - if (!leafPage && control && (control->vdr_flags & vdr_records)) + if (!leafPage && (vdr_flags & VDR_records)) { const SLONG down_number = node.pageNumber; const RecordNumber down_record_number = node.recordNumber; - // Note: control == 0 for the fetch_page() call here + // Note: mark == false for the fetch_page() call here // as we don't want to mark the page as visited yet - we'll // mark it when we visit it for real later on WIN down_window(DB_PAGE_SPACE, -1); + down_window.win_flags = WIN_garbage_collector; + btree_page* down_page = 0; - fetch_page(tdbb, 0, down_number, pag_index, &down_window, &down_page); + fetch_page(tdbb, down_number, pag_index, &down_window, &down_page, false); const bool downLeafPage = (down_page->btr_level == 0); // make sure the initial key is greater than the pointer key @@ -1669,8 +2066,8 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, { if (*p < *q) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } else if (*p > *q) { break; @@ -1690,16 +2087,16 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if ((l == 0) && (key.key_length == downNode.length) && (downNode.recordNumber < down_record_number)) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, - id + 1, next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } } // check the left and right sibling pointers against the parent pointers if (previous_number != down_page->btr_left_sibling) { - corrupt(tdbb, control, VAL_INDEX_BAD_LEFT_SIBLING, relation, - id + 1, next, page->btr_level); + corrupt(tdbb, VAL_INDEX_BAD_LEFT_SIBLING, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } BTreeNode::readNode(&downNode, pointer, flags, leafPage); @@ -1708,23 +2105,23 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, if (!(downNode.isEndBucket || downNode.isEndLevel) && (next_number != down_page->btr_sibling)) { - corrupt(tdbb, control, VAL_INDEX_MISSES_NODE, relation, - id + 1, next, page->btr_level); + corrupt(tdbb, VAL_INDEX_MISSES_NODE, relation, + id + 1, next, page->btr_level, node.nodePointer - (UCHAR*)page, __FILE__, __LINE__); } if (downNode.isEndLevel && down_page->btr_sibling) { - corrupt(tdbb, control, VAL_INDEX_ORPHAN_CHILD, relation, id + 1, next); + corrupt(tdbb, VAL_INDEX_ORPHAN_CHILD, relation, id + 1, next); } previous_number = down_number; - CCH_RELEASE(tdbb, &down_window); + CCH_RELEASE_TAIL(tdbb, &down_window); } } if (pointer != endPointer || page->btr_length > dbb->dbb_page_size) { - corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, id + 1, - next, page->btr_level, __FILE__, __LINE__); + corrupt(tdbb, VAL_INDEX_PAGE_CORRUPT, relation, id + 1, + next, page->btr_level, pointer - (UCHAR*)page, __FILE__, __LINE__); } if (next == down) @@ -1753,23 +2150,23 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, // check for circular referenes if (next && visited_pages.test(next)) { - corrupt(tdbb, control, VAL_INDEX_CYCLE, relation, id + 1, next); + corrupt(tdbb, VAL_INDEX_CYCLE, relation, id + 1, next); next = 0; } - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); } // If the index & relation contain different sets of records we // have a corrupt index - if (control && (control->vdr_flags & vdr_records)) + if (vdr_flags & VDR_records) { Database::Checkout dcoHolder(dbb); - RecordBitmap::Accessor accessor(control->vdr_rel_records); + RecordBitmap::Accessor accessor(vdr_rel_records); if (accessor.getFirst()) do { SINT64 next_number = accessor.current(); - if (!RecordBitmap::test(control->vdr_idx_records, next_number)) { - return corrupt(tdbb, control, VAL_INDEX_MISSING_ROWS, relation, id + 1); + if (!RecordBitmap::test(vdr_idx_records, next_number)) { + return corrupt(tdbb, VAL_INDEX_MISSING_ROWS, relation, id + 1, next_number); } } while (accessor.getNext()); } @@ -1777,7 +2174,7 @@ static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, return rtn_ok; } -static void walk_log(thread_db* tdbb, vdr* control) +void Validation::walk_log(thread_db* tdbb) { /************************************** * @@ -1797,13 +2194,13 @@ static void walk_log(thread_db* tdbb, vdr* control) while (page_num) { WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, page_num, pag_log, &window, &page); + fetch_page(tdbb, page_num, pag_log, &window, &page); page_num = page->log_next_page; CCH_RELEASE(tdbb, &window); } } -static void walk_pip(thread_db* tdbb, vdr* control) +void Validation::walk_pip(thread_db* tdbb) { /************************************** * @@ -1834,7 +2231,7 @@ static void walk_pip(thread_db* tdbb, vdr* control) fprintf(stdout, "walk_pip: page %d\n", page_number); #endif WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, page_number, pag_pages, &window, &page); + fetch_page(tdbb, page_number, pag_pages, &window, &page); const UCHAR byte = page->pip_bits[pageSpaceMgr.bytesBitPIP - 1]; CCH_RELEASE(tdbb, &window); if (byte & 0x80) @@ -1842,7 +2239,7 @@ static void walk_pip(thread_db* tdbb, vdr* control) } } -static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, int sequence) +RTN Validation::walk_pointer_page(thread_db* tdbb, jrd_rel* relation, int sequence) { /************************************** * @@ -1861,12 +2258,14 @@ static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, i const vcl* vector = relation->getBasePages()->rel_pages; if (!vector || sequence >= static_cast(vector->count())) { - return corrupt(tdbb, control, VAL_P_PAGE_LOST, relation, sequence); + return corrupt(tdbb, VAL_P_PAGE_LOST, relation, sequence); } pointer_page* page = 0; WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, (*vector)[sequence], pag_pointer, &window, &page); + window.win_flags = WIN_garbage_collector; + + fetch_page(tdbb, (*vector)[sequence], pag_pointer, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -1880,8 +2279,8 @@ static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, i if (page->ppg_relation != relation->rel_id || page->ppg_sequence != sequence) { - CCH_RELEASE(tdbb, &window); - return corrupt(tdbb, control, VAL_P_PAGE_INCONSISTENT, relation, sequence); + CCH_RELEASE_TAIL(tdbb, &window); + return corrupt(tdbb, VAL_P_PAGE_INCONSISTENT, relation, sequence); } // Walk the data pages (someday we may optionally walk pages with "large objects" @@ -1893,8 +2292,8 @@ static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, i { if (*pages) { - const RTN result = walk_data_page(tdbb, control, relation, *pages, seq); - if (result != rtn_ok && (control->vdr_flags & vdr_repair)) + const RTN result = walk_data_page(tdbb, relation, *pages, seq); + if (result != rtn_ok && (vdr_flags & VDR_repair)) { CCH_MARK(tdbb, &window); *pages = 0; @@ -1906,7 +2305,7 @@ static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, i if (page->ppg_header.pag_flags & ppg_eof) { - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); return rtn_eof; } @@ -1915,20 +2314,44 @@ static RTN walk_pointer_page(thread_db* tdbb, vdr* control, jrd_rel* relation, i if (++sequence >= static_cast(vector->count()) || (page->ppg_next && page->ppg_next != (*vector)[sequence])) { - CCH_RELEASE(tdbb, &window); - return corrupt(tdbb, control, VAL_P_PAGE_INCONSISTENT, relation, sequence); + CCH_RELEASE_TAIL(tdbb, &window); + + if (vdr_flags & VDR_online) + { + // relation could be extended before we acquired its lock in PR mode + // let's re-read pointer pages and check again + + DPM_scan_pages(tdbb); + + vector = relation->getBasePages()->rel_pages; + + --sequence; + if (!vector || sequence >= static_cast(vector->count())) { + return corrupt(tdbb, VAL_P_PAGE_LOST, relation, sequence); + } + + fetch_page(tdbb, (*vector)[sequence], pag_pointer, &window, &page, false); + + ++sequence; + const bool error = sequence >= static_cast(vector->count()) || + (page->ppg_next && page->ppg_next != (*vector)[sequence]); + + CCH_RELEASE_TAIL(tdbb, &window); + + if (!error) + return rtn_ok; + } + + return corrupt(tdbb, VAL_P_PAGE_INCONSISTENT, relation, sequence); } - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); return rtn_ok; } -static RTN walk_record(thread_db* tdbb, - vdr* control, - jrd_rel* relation, - rhd* header, - USHORT length, RecordNumber number, bool delta_flag) +RTN Validation::walk_record(thread_db* tdbb, jrd_rel* relation, rhd* header, USHORT length, + RecordNumber number, bool delta_flag) { /************************************** * @@ -1955,20 +2378,20 @@ static RTN walk_record(thread_db* tdbb, if (header->rhd_flags & rhd_damaged) { - corrupt(tdbb, control, VAL_REC_DAMAGED, relation, number.getValue()); + corrupt(tdbb, VAL_REC_DAMAGED, relation, number.getValue()); return rtn_ok; } - if (control && header->rhd_transaction > control->vdr_max_transaction) + if (header->rhd_transaction > vdr_max_transaction) { - corrupt(tdbb, control, VAL_REC_BAD_TID, relation, number.getValue(), header->rhd_transaction); + corrupt(tdbb, VAL_REC_BAD_TID, relation, number.getValue(), header->rhd_transaction); } // If there's a back pointer, verify that it's good if (header->rhd_b_page && !(header->rhd_flags & rhd_chain)) { - const RTN result = walk_chain(tdbb, control, relation, header, number); + const RTN result = walk_chain(tdbb, relation, header, number); if (result != rtn_ok) return result; } @@ -1977,7 +2400,7 @@ static RTN walk_record(thread_db* tdbb, // chasing records, skip the record if (header->rhd_flags & (rhd_fragment | rhd_deleted) || - !((header->rhd_flags & rhd_large) || (control && (control->vdr_flags & vdr_records)))) + !((header->rhd_flags & rhd_large) || (vdr_flags & VDR_records)) ) { return rtn_ok; } @@ -2026,13 +2449,15 @@ static RTN walk_record(thread_db* tdbb, while (flags & rhd_incomplete) { WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, page_number, pag_data, &window, &page); + window.win_flags = WIN_garbage_collector; + + fetch_page(tdbb, page_number, pag_data, &window, &page); const data_page::dpg_repeat* line = &page->dpg_rpt[line_number]; if (page->dpg_relation != relation->rel_id || line_number >= page->dpg_count || !(length = line->dpg_length)) { - corrupt(tdbb, control, VAL_REC_FRAGMENT_CORRUPT, relation, number.getValue()); - CCH_RELEASE(tdbb, &window); + corrupt(tdbb, VAL_REC_FRAGMENT_CORRUPT, relation, number.getValue()); + CCH_RELEASE_TAIL(tdbb, &window); return rtn_corrupt; } fragment = (rhdf*) ((UCHAR *) page + line->dpg_offset); @@ -2070,7 +2495,7 @@ static RTN walk_record(thread_db* tdbb, page_number = fragment->rhdf_f_page; line_number = fragment->rhdf_f_line; flags = fragment->rhdf_flags; - CCH_RELEASE(tdbb, &window); + CCH_RELEASE_TAIL(tdbb, &window); } // Check out record length and format @@ -2078,13 +2503,13 @@ static RTN walk_record(thread_db* tdbb, const Format* format = MET_format(tdbb, relation, header->rhd_format); if (!delta_flag && record_length != format->fmt_length) - return corrupt(tdbb, control, VAL_REC_WRONG_LENGTH, relation, number.getValue()); + return corrupt(tdbb, VAL_REC_WRONG_LENGTH, relation, number.getValue()); return rtn_ok; } -static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation) +RTN Validation::walk_relation(thread_db* tdbb, jrd_rel* relation) { /************************************** * @@ -2101,13 +2526,6 @@ static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation) try { - // If relation hasn't been scanned, do so now - - if (!(relation->rel_flags & REL_scanned) || (relation->rel_flags & REL_being_scanned)) - { - MET_scan_relation(tdbb, relation); - } - // skip deleted relations if (relation->rel_flags & (REL_deleted | REL_deleting)) { return rtn_ok; @@ -2126,18 +2544,47 @@ static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation) return rtn_ok; } + AutoLock lckRead(tdbb); + jrd_rel::GCExclusive lckGC(tdbb, relation); + if (vdr_flags & VDR_online) + { + lckRead = jrd_rel::createLock(tdbb, NULL, relation, LCK_relation, false); + if (!LCK_lock(tdbb, lckRead, LCK_PR, vdr_lock_tout)) + { + output("Acquire relation lock failed\n"); + vdr_errors++; + return rtn_ok; + } + + if (!lckGC.acquire(vdr_lock_tout)) + { + output("Acquire garbage collection lock failed\n"); + vdr_errors++; + return rtn_ok; + } + + WIN window(DB_PAGE_SPACE, -1); + header_page* page = 0; + fetch_page(tdbb, (SLONG) HEADER_PAGE, pag_header, &window, &page, false); + vdr_max_transaction = page->hdr_next_transaction; + CCH_RELEASE(tdbb, &window); + } + // Walk pointer and selected data pages associated with relation - if (control) - { - control->vdr_rel_backversion_counter = 0; - control->vdr_rel_chain_counter = 0; - RecordBitmap::reset(control->vdr_rel_records); - } + vdr_rel_backversion_counter = 0; + vdr_rel_chain_counter = 0; + RecordBitmap::reset(vdr_rel_records); + for (SLONG sequence = 0; true; sequence++) { - const RTN result = walk_pointer_page(tdbb, control, relation, sequence); + const vcl* vector = relation->getBasePages()->rel_pages; + const int ppCnt = vector ? vector->count() : 0; + + output(" process pointer page %4d of %4d\n", sequence, ppCnt); + + const RTN result = walk_pointer_page(tdbb, relation, sequence); if (result == rtn_eof) { break; } @@ -2147,24 +2594,29 @@ static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation) } // Walk indices for the relation - walk_root(tdbb, control, relation); + walk_root(tdbb, relation); + + lckGC.release(); // See if the counts of backversions match - if (control && (control->vdr_flags & vdr_records) && - (control->vdr_rel_backversion_counter != control->vdr_rel_chain_counter)) + if ((vdr_flags & VDR_records) && + (vdr_rel_backversion_counter != vdr_rel_chain_counter)) { - return corrupt(tdbb, control, VAL_REL_CHAIN_ORPHANS, relation, - control->vdr_rel_backversion_counter - control-> vdr_rel_chain_counter, - control-> vdr_rel_chain_counter); + return corrupt(tdbb, VAL_REL_CHAIN_ORPHANS, relation, + vdr_rel_backversion_counter - vdr_rel_chain_counter, + vdr_rel_chain_counter); } } // try catch (const Firebird::Exception&) { - const char* msg = relation->rel_name.length() > 0 ? - "bugcheck during scan of table %d (%s)" : - "bugcheck during scan of table %d"; - gds__log(msg, relation->rel_id, relation->rel_name.c_str()); + if (!(vdr_flags & VDR_online)) + { + const char* msg = relation->rel_name.length() > 0 ? + "bugcheck during scan of table %d (%s)" : + "bugcheck during scan of table %d"; + gds__log(msg, relation->rel_id, relation->rel_name.c_str()); + } #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) { @@ -2180,7 +2632,7 @@ static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation) } -static RTN walk_root(thread_db* tdbb, vdr* control, jrd_rel* relation) +RTN Validation::walk_root(thread_db* tdbb, jrd_rel* relation) { /************************************** * @@ -2198,15 +2650,37 @@ static RTN walk_root(thread_db* tdbb, vdr* control, jrd_rel* relation) RelationPages* relPages = relation->getBasePages(); if (!relPages->rel_index_root) { - return corrupt(tdbb, control, VAL_INDEX_ROOT_MISSING, relation); + return corrupt(tdbb, VAL_INDEX_ROOT_MISSING, relation); } index_root_page* page = 0; WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, relPages->rel_index_root, pag_root, &window, &page); + fetch_page(tdbb, relPages->rel_index_root, pag_root, &window, &page); - for (USHORT i = 0; i < page->irt_count; i++) { - walk_index(tdbb, control, relation, *page, i); + for (USHORT i = 0; i < page->irt_count; i++) + { + if (page->irt_rpt[i].irt_root == 0) + continue; + + MetaName index; + MET_lookup_index(tdbb, index, relation->rel_name, i+1); + + if (vdr_idx_incl) + { + vdr_idx_incl->reset(); + if (!vdr_idx_incl->process((UCHAR*)index.c_str(), index.length()) || !vdr_idx_incl->result()) + continue; + } + + if (vdr_idx_excl) + { + vdr_idx_excl->reset(); + if (!vdr_idx_excl->process((UCHAR*)index.c_str(), index.length()) || vdr_idx_excl->result()) + continue; + } + + output("Index %d (%s)\n", i + 1, index.c_str()); + walk_index(tdbb, relation, *page, i); } CCH_RELEASE(tdbb, &window); @@ -2214,7 +2688,7 @@ static RTN walk_root(thread_db* tdbb, vdr* control, jrd_rel* relation) return rtn_ok; } -static RTN walk_tip(thread_db* tdbb, vdr* control, SLONG transaction) +RTN Validation::walk_tip(thread_db* tdbb, SLONG transaction) { /************************************** * @@ -2233,7 +2707,7 @@ static RTN walk_tip(thread_db* tdbb, vdr* control, SLONG transaction) const vcl* vector = dbb->dbb_t_pages; if (!vector) { - return corrupt(tdbb, control, VAL_TIP_LOST, 0); + return corrupt(tdbb, VAL_TIP_LOST, 0); } tx_inv_page* page = 0; @@ -2243,15 +2717,15 @@ static RTN walk_tip(thread_db* tdbb, vdr* control, SLONG transaction) { if (!(*vector)[sequence] || sequence >= vector->count()) { - corrupt(tdbb, control, VAL_TIP_LOST_SEQUENCE, 0, sequence); - if (!(control->vdr_flags & vdr_repair)) + corrupt(tdbb, VAL_TIP_LOST_SEQUENCE, 0, sequence); + if (!(vdr_flags & VDR_repair)) continue; TRA_extend_tip(tdbb, sequence); vector = dbb->dbb_t_pages; } WIN window(DB_PAGE_SPACE, -1); - fetch_page(tdbb, control, (*vector)[sequence], pag_transactions, &window, &page); + fetch_page(tdbb, (*vector)[sequence], pag_transactions, &window, &page); #ifdef DEBUG_VAL_VERBOSE if (VAL_debug_level) @@ -2259,7 +2733,7 @@ static RTN walk_tip(thread_db* tdbb, vdr* control, SLONG transaction) #endif if (page->tip_next && page->tip_next != (*vector)[sequence + 1]) { - corrupt(tdbb, control, VAL_TIP_CONFUSED, 0, sequence); + corrupt(tdbb, VAL_TIP_CONFUSED, 0, sequence); } CCH_RELEASE(tdbb, &window); } diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index 9977ed8311..3663bcd0fd 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -131,6 +131,45 @@ static void update_in_place(thread_db*, jrd_tra*, record_param*, record_param*); static void verb_post(thread_db*, jrd_tra*, record_param*, Record*, //record_param*, const bool, const bool); +static bool assert_gc_enabled(const jrd_tra* transaction, const jrd_rel* relation) +{ +/************************************** + * + * a s s e r t _ g c _ e n a b l e d + * + ************************************** + * + * Functional description + * Ensure that calls of purge\expunge\VIO_backout is safe and don't broke + * results of online validation run. + * + * Notes + * System and temporary relations are not validated online. + * Non-zero rel_sweep_count is possible only under GCShared control when + * garbage collection is enabled. + * + * VIO_backout is more complex as it could run without GCShared control. + * Therefore we additionally check if we own relation lock in "write" mode - + * in this case online validation is not run against given relation. + * + **************************************/ + if (relation->rel_sweep_count || relation->isSystem() || relation->isTemporary()) + return true; + + if (relation->rel_flags & REL_gc_disabled) + return false; + + vec* vector = transaction->tra_relation_locks; + if (!vector || vector->count() < relation->rel_id) + return false; + + Lock* lock = (*vector)[relation->rel_id]; + if (!lock) + return false; + + return (lock->lck_physical == LCK_SW) || (lock->lck_physical == LCK_EX); +} + // Pick up relation ids #include "../jrd/ini.h" @@ -250,6 +289,8 @@ void VIO_backout(thread_db* tdbb, record_param* rpb, const jrd_tra* transaction) Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); + fb_assert(assert_gc_enabled(transaction, rpb->rpb_relation)); + #ifdef VIO_DEBUG if (debug_flag > DEBUG_WRITES) { @@ -755,8 +796,10 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, #endif case tra_precommitted: + {// scope + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); - if (attachment->att_flags & ATT_NO_CLEANUP || + if (attachment->att_flags & ATT_NO_CLEANUP || !gcGuard.gcEnabled() || rpb->rpb_flags & (rpb_chained | rpb_gc_active)) { if (rpb->rpb_b_page == 0) { @@ -816,6 +859,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, if (!DPM_get(tdbb, rpb, LCK_read)) { return false; } + } // scope break; // If it's active, prepare to fetch the old version. @@ -982,6 +1026,12 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, #endif { CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); + + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); + + if (!gcGuard.gcEnabled()) + return false; + expunge(tdbb, rpb, transaction, (SLONG) 0); } return false; @@ -1024,7 +1074,14 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb, return true; } #endif - purge(tdbb, rpb); + { // scope + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); + + if (!gcGuard.gcEnabled()) + return true; + + purge(tdbb, rpb); + } // Go back to be primary record version and chase versions all over again. if (!DPM_get(tdbb, rpb, LCK_read)) { @@ -1651,7 +1708,9 @@ bool VIO_garbage_collect(thread_db* tdbb, record_param* rpb, const jrd_tra* tran } #endif - if (transaction->tra_attachment->att_flags & ATT_no_cleanup) { + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); + + if (transaction->tra_attachment->att_flags & ATT_no_cleanup || !gcGuard.gcEnabled()) { return true; } @@ -1956,7 +2015,14 @@ bool VIO_get_current(thread_db* tdbb, if (transaction->tra_attachment->att_flags & ATT_no_cleanup) return !foreign_key; - VIO_backout(tdbb, rpb, transaction); + { + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); + + if (!gcGuard.gcEnabled()) + return !foreign_key; + + VIO_backout(tdbb, rpb, transaction); + } continue; case tra_precommitted: Database::Checkout dcoHolder(dbb); @@ -2050,7 +2116,14 @@ bool VIO_get_current(thread_db* tdbb, return !foreign_key; } - VIO_backout(tdbb, rpb, transaction); + { + jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation); + + if (!gcGuard.gcEnabled()) + return !foreign_key; + + VIO_backout(tdbb, rpb, transaction); + } break; case tra_limbo: @@ -2939,6 +3012,7 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee jrd_rel* relation = 0; // wasn't initialized: memory problem in catch() part. vec* vector = 0; + bool ret = true; try { for (size_t i = 1; (vector = dbb->dbb_relations) && i < vector->count(); i++) @@ -2952,10 +3026,16 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee !relation->isTemporary() && relation->getPages(tdbb)->rel_pages) { + jrd_rel::GCShared gcGuard(tdbb, relation); + if (!gcGuard.gcEnabled()) + { + ret = false; + break; + } + rpb.rpb_relation = relation; rpb.rpb_number.setValue(BOF_NUMBER); rpb.rpb_org_scans = relation->rel_scan_count++; - ++relation->rel_sweep_count; traceSweep->beginSweepRelation(relation); @@ -2984,7 +3064,6 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee traceSweep->endSweepRelation(relation); - --relation->rel_sweep_count; --relation->rel_scan_count; } } @@ -2997,16 +3076,13 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee delete rpb.rpb_record; if (relation) { - if (relation->rel_sweep_count) { - --relation->rel_sweep_count; - } if (relation->rel_scan_count) { --relation->rel_scan_count; } } ERR_punt(); } - return true; + return ret; } @@ -3807,6 +3883,8 @@ static void expunge(thread_db* tdbb, record_param* rpb, const jrd_tra* transacti SET_TDBB(tdbb); Attachment* attachment = transaction->tra_attachment; + fb_assert(assert_gc_enabled(transaction, rpb->rpb_relation)); + #ifdef VIO_DEBUG if (debug_flag > DEBUG_WRITES) { @@ -4134,7 +4212,10 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg) relGarbage->getGarbage(dbb->dbb_oldest_snapshot, &relation->rel_gc_bitmap); } - ++relation->rel_sweep_count; + jrd_rel::GCShared gcGuard(tdbb, relation); + if (!gcGuard.gcEnabled()) + continue; + rpb.rpb_relation = relation; if (relation->rel_gc_bitmap) @@ -4144,7 +4225,6 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg) const ULONG dp_sequence = relation->rel_gc_bitmap->current(); if (!(dbb->dbb_flags & DBB_garbage_collector)) { - --relation->rel_sweep_count; goto gc_exit; } @@ -4185,12 +4265,14 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg) if (!(dbb->dbb_flags & DBB_garbage_collector)) { - --relation->rel_sweep_count; goto gc_exit; } if (relation->rel_flags & REL_deleting) { goto rel_exit; } + if (relation->rel_flags & REL_gc_disabled) { + goto rel_exit; + } if (--tdbb->tdbb_quantum < 0) { JRD_reschedule(tdbb, SWEEP_QUANTUM, true); } @@ -4221,7 +4303,6 @@ rel_exit: } */ } - --relation->rel_sweep_count; } } @@ -4283,9 +4364,6 @@ rel_exit: Firebird::stuff_exception(status_vector, ex); jrd_file* file = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE)->file; gds__log_status(file->fil_string, status_vector); - if (relation && relation->rel_sweep_count) { - --relation->rel_sweep_count; - } } gc_exit: @@ -5003,6 +5081,8 @@ static void purge(thread_db* tdbb, record_param* rpb) Database* dbb = tdbb->getDatabase(); CHECK_DBB(dbb); + fb_assert(assert_gc_enabled(tdbb->getTransaction(), rpb->rpb_relation)); + #ifdef VIO_DEBUG if (debug_flag > DEBUG_TRACE_ALL) { printf("purge (record_param %"QUADFORMAT"d)\n", rpb->rpb_number.getValue()); diff --git a/src/utilities/fbsvcmgr.cpp b/src/utilities/fbsvcmgr.cpp index 86194e1b20..12b31d5873 100644 --- a/src/utilities/fbsvcmgr.cpp +++ b/src/utilities/fbsvcmgr.cpp @@ -239,7 +239,14 @@ bool putNumericArgument(char**& av, ClumpletWriter& spb, unsigned int tag) if (! *av) return false; - int n = atoi(*av++); + char* err = NULL; + SLONG n = strtol(*av++, &err, 10); + + if (err && *err) + { + (Arg::Gds(isc_fbsvcmgr_bad_arg) << av[-2]).raise(); + } + spb.insertInt(tag, n); return true; @@ -476,6 +483,17 @@ const SvcSwitches traceChgStateOptions[] = {0, 0, 0, 0, 0} }; +const SvcSwitches validateOptions[] = +{ + {"dbname", putStringArgument, 0, isc_spb_dbname, 0}, + {"val_tab_incl", putStringArgument, 0, isc_spb_val_tab_incl, 0}, + {"val_tab_excl", putStringArgument, 0, isc_spb_val_tab_excl, 0}, + {"val_idx_incl", putStringArgument, 0, isc_spb_val_idx_incl, 0}, + {"val_idx_excl", putStringArgument, 0, isc_spb_val_idx_excl, 0}, + {"val_lock_timeout", putNumericArgument, 0, isc_spb_val_lock_timeout, 0}, + {0, 0, 0, 0, 0} +}; + const SvcSwitches actionSwitch[] = { {"action_backup", putSingleTag, backupOptions, isc_action_svc_backup, isc_info_svc_to_eof}, @@ -498,6 +516,7 @@ const SvcSwitches actionSwitch[] = {"action_trace_list", putSingleTag, 0, isc_action_svc_trace_list, isc_info_svc_line}, {"action_set_mapping", putSingleTag, mappingOptions, isc_action_svc_set_mapping, 0}, {"action_drop_mapping", putSingleTag, mappingOptions, isc_action_svc_drop_mapping, 0}, + {"action_validate", putSingleTag, validateOptions, isc_action_svc_validate, isc_info_svc_line}, {0, 0, 0, 0, 0} }; @@ -846,6 +865,23 @@ static int shutdownCallback(const int reason, const int, void*) return FB_SUCCESS; } +class ShutdownHolder +{ +public: + bool active; + + ShutdownHolder() + : active(false) + { } + + ~ShutdownHolder() + { + if (active) + { + fb_shutdown(0, fb_shutrsn_exit_called); + } + } +}; // simple main function @@ -866,6 +902,8 @@ int main(int ac, char** av) prevCtrlCHandler = signal(SIGINT, ctrl_c_handler); fb_shutdown_callback(NULL, shutdownCallback, fb_shut_confirmation, NULL); + ShutdownHolder shutdownHolder; + ISC_STATUS_ARRAY status; try { @@ -931,6 +969,8 @@ int main(int ac, char** av) return 1; } + shutdownHolder.active = true; + if (spbStart.getBufferLength() > 0) { if (isc_service_start(status, &svc_handle, 0,