//------------------------------------------------------------------------- // Desc: Check database for corruptions. // Tabs: 3 // // Copyright (c) 1991-2006 Novell, Inc. All Rights Reserved. // // This program is free software; you can redistribute it and/or // modify it under the terms of version 2 of the GNU General Public // License as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, contact Novell, Inc. // // To contact Novell about this file by physical or electronic mail, // you may find current contact information at www.novell.com // // $Id: flchkdb.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" FSTATIC RCODE chkGetDictInfo( DB_INFO * pDbInfo); FSTATIC RCODE chkVerifyBlkChain( DB_INFO * pDbInfo, BLOCK_INFO * pBlkInfo, eCorruptionLocale eLocale, FLMUINT uiFirstBlkAddr, FLMUINT uiBlkType, FLMUINT * puiBlkCount, FLMBOOL * pbStartOverRV); FSTATIC RCODE chkVerifyLFHBlocks( DB_INFO * pDbInfo, FLMBOOL * pbStartOverRV); FSTATIC RCODE chkVerifyAvailList( DB_INFO * pDbInfo, FLMBOOL * pbStartOverRV); FSTATIC RCODE chkVerifyTrackerCounts( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndexNum, FLMUINT uiKeyCount, FLMUINT uiRefCount); FSTATIC RCODE chkIsCountIndex( STATE_INFO * pStateInfo, FLMUINT uiIndexNum, FLMBOOL * pbIsCountIndex); FSTATIC RCODE chkResolveRSetMissingKey( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIxRefDrn); FSTATIC RCODE chkVerifyDelNonUniqueRec( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiRecDrn, FLMUINT * puiRecContainerRV, FLMBOOL * pbDelRecRV); FSTATIC RCODE chkVerifyKeyExists( FDB * pDb, LFILE * pLFile, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiRefDrn, FLMBOOL * pbFoundRV); FSTATIC RCODE chkAddDelKeyRef( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndexNum, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, FLMUINT uiFlags); FSTATIC RCODE chkGetKeySource( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, FLMUINT * puiRecordContainerRV, FLMBOOL * pbKeyInRecRV, FLMBOOL * pbKeyInIndexRV); FSTATIC RCODE chkReportIxError( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, eCorruptionType eCorruption, FLMUINT uiErrIx, FLMUINT uiErrDrn, FLMBYTE * pucErrKey, FLMUINT uiErrKeyLen, FLMBOOL * pbFixErrRV); FSTATIC RCODE chkGetNextRSKey( IX_CHK_INFO * pIxChkInfo); FSTATIC RCODE chkResolveIXMissingKey( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo); FSTATIC RCODE chkRSInit( const char * pszIoPath, IF_ResultSet ** ppRSet); FSTATIC RCODE chkRSFinalize( IX_CHK_INFO * pIxChkInfo, FLMUINT64 * pui64TotalEntries); FSTATIC FLMINT chkCompareKeySet( FLMUINT uiIxNum1, FLMBYTE * pData1, FLMUINT uiLength1, FLMUINT uiDrn1, FLMUINT uiIxNum2, FLMBYTE * pData2, FLMUINT uiLength2, FLMUINT uiDrn2); FSTATIC RCODE chkBlkRead( DB_INFO * pDbInfo, FLMUINT uiBlkAddress, LFILE * pLFile, FLMBYTE ** ppBlk, SCACHE ** ppSCache, eCorruptionType * peCorruption); FSTATIC RCODE chkVerifyBTrees( DB_INFO * pDbInfo, F_Pool * pPool, FLMBOOL * pbStartOverRV); FSTATIC RCODE chkReportError( DB_INFO * pDbInfo, eCorruptionType eCorruption, eCorruptionLocale eErrLocale, FLMUINT uiErrLfNumber, FLMUINT uiErrLfType, FLMUINT uiErrBTreeLevel, FLMUINT uiErrBlkAddress, FLMUINT uiErrParentBlkAddress, FLMUINT uiErrElmOffset, FLMUINT uiErrDrn, FLMUINT uiErrElmRecOffset, FLMUINT uiErrFieldNum, FLMBYTE * pBlk); FSTATIC RCODE chkGetNextRSKey( IX_CHK_INFO * pIxChkInfo); FSTATIC RCODE chkVerifyKeyNotUnique( STATE_INFO * pStateInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT * puiRefCountRV); FSTATIC RCODE chkStartUpdate( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo); FSTATIC RCODE chkEndUpdate( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo); FSTATIC RCODE chkReadBlkFromDisk( FILE_HDR * pFileHdr, F_SuperFileHdl * pSFileHdl, FLMUINT uiFilePos, FLMUINT uiBlkAddress, LFILE * pLFile, FFILE * pFile, FLMBYTE * pBlk); FSTATIC RCODE chkVerifyElmFields( STATE_INFO * pStateInfo, DB_INFO * pDbInfo, IX_CHK_INFO * pIxChkInfo, F_Pool * pTmpPool, FLMUINT * puiErrElmRecOffsetRV, eCorruptionType * peElmErrCorruptCode); FSTATIC RCODE chkVerifySubTree( DB_INFO * pDbInfo, IX_CHK_INFO * pIxChkInfo, STATE_INFO * ParentState, STATE_INFO * pStateInfo, FLMUINT uiBlkAddress, F_Pool * pTmpPool, FLMBYTE * pucResetKey, FLMUINT uiResetKeyLen, FLMUINT uiResetDrn); FSTATIC RCODE chkGetLfInfo( DB_INFO * pDbInfo, F_Pool * pPool, LF_STATS * pLfStats, LFILE * pLFile, LF_STATS * pCurrLfStats, FLMBOOL * pbCurrLfLevelChangedRV); FSTATIC RCODE chkSetupLfTable( DB_INFO * pDbInfo, F_Pool * pPool); FSTATIC RCODE chkSetupIxInfo( DB_INFO * pDbInfo, IX_CHK_INFO * pIxInfoRV); FSTATIC RCODE chkOutputIndexKeys( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, IXD * pIxd, REC_KEY * pKeyList); /**************************************************************************** Desc: ****************************************************************************/ class F_ChkResultSetCompare : public IF_ResultSetCompare { public: RCODE FLMAPI compare( const void * pvData1, FLMUINT uiLength1, const void * pvData2, FLMUINT uiLength2, FLMINT * piCompare) { FLMBYTE * pucData1 = (FLMBYTE *) pvData1; FLMBYTE * pucData2 = (FLMBYTE *) pvData2; FLMUINT uiIxNum1; FLMUINT uiIxNum2; FLMUINT uiDrn1; FLMUINT uiDrn2; uiIxNum1 = (FLMUINT) FB2UW( &(pucData1[RS_IX_OFFSET])); uiIxNum2 = (FLMUINT) FB2UW( &(pucData2[RS_IX_OFFSET])); uiDrn1 = (FLMUINT) FB2UD( &(pucData1[RS_REF_OFFSET])); uiDrn2 = (FLMUINT) FB2UD( &(pucData2[RS_REF_OFFSET])); *piCompare = chkCompareKeySet( uiIxNum1, &(pucData1[RS_KEY_OFFSET]), uiLength1 - RS_KEY_OVERHEAD, uiDrn1, uiIxNum2, &(pucData2[RS_KEY_OFFSET]), uiLength2 - RS_KEY_OVERHEAD, uiDrn2); return( FERR_OK); } }; /**************************************************************************** Desc: ****************************************************************************/ FINLINE RCODE chkCallProgFunc( DB_INFO * pDbInfo) { if( (pDbInfo->fnStatusFunc) && (RC_OK( pDbInfo->LastStatusRc))) { pDbInfo->LastStatusRc = (*pDbInfo->fnStatusFunc)( FLM_CHECK_STATUS, (void *)pDbInfo->pProgress, (void *)0, pDbInfo->pProgress->AppArg); } return( pDbInfo->LastStatusRc); } /**************************************************************************** Desc: ****************************************************************************/ class F_ChkSortStatus : public IF_ResultSetSortStatus { public: F_ChkSortStatus( IX_CHK_INFO * pIxChkInfo) { m_pIxChkInfo = pIxChkInfo; } RCODE FLMAPI reportSortStatus( FLMUINT64 ui64EstTotalUnits, FLMUINT64 ui64UnitsDone) { FLMINT iRetVal = 0; DB_CHECK_PROGRESS * pProgress = m_pIxChkInfo->pDbInfo->pProgress; // Set the status values. pProgress->ui64NumRSUnits = ui64EstTotalUnits; pProgress->ui64NumRSUnitsDone = ui64UnitsDone; // Call the progress callback. if (RC_BAD( chkCallProgFunc( m_pIxChkInfo->pDbInfo))) { iRetVal = -1; goto Exit; } Exit: pProgress->bStartFlag = FALSE; return( iRetVal); } private: IX_CHK_INFO * m_pIxChkInfo; }; /**************************************************************************** Desc: This routine counts the number of fields in an object table. ****************************************************************************/ FINLINE void chkCountFields( FDICT * pDict, FLMUINT * puiNumFieldsRV) { FLMUINT uiTblSize = pDict->uiIttCnt; ITT * pItt = pDict->pIttTbl; FLMUINT uiCount = 0; FLMUINT uiCurrObj; for (uiCurrObj = 0; uiCurrObj < uiTblSize; uiCurrObj++, pItt++) { if (ITT_IS_FIELD( pItt)) { uiCount++; } } (*puiNumFieldsRV) += uiCount; } /**************************************************************************** Desc: Frees memory allocated to an IX_CHK_INFO structure ****************************************************************************/ FINLINE RCODE chkFreeIxInfo( IX_CHK_INFO * pIxInfoRV) { pIxInfoRV->pool.poolFree(); pIxInfoRV->pRSet->Release(); pIxInfoRV->pRSet = NULL; f_free( &(pIxInfoRV->puiIxArray)); f_memset( pIxInfoRV, 0, sizeof(IX_CHK_INFO)); return (FERR_OK); } /**************************************************************************** Desc: Checks for physical corruption in a FLAIM database. Note: The routine verifies the database by first reading through the database to count certain block types which are in linked lists. It then verifies the linked lists. It also verifies the B-TREEs in the database. The reason for the first pass is so that when we verify the linked lists, we can keep ourselves from getting into an infinite loop if there is a loop in the lists. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmDbCheck( HFDB hDb, const char * pDbFileName, const char * pszDataDir, const char * pRflDir, FLMUINT uiCheckFlags, F_Pool * pPool, DB_CHECK_PROGRESS * pCheckProgress, STATUS_HOOK fnStatusFunc, void * AppArg) { RCODE rc = FERR_OK; F_SuperFileHdl * pSFileHdl = NULL; FLMBYTE * pBlk = NULL; FLMUINT uiFileEnd; FLMUINT uiBlockSize; DB_CHECK_PROGRESS Progress; FLMBOOL bOpenedDb = FALSE; FDB * pDb = (FDB *) hDb; FLMBOOL bIgnore; FLMUINT uiLoop; FLMUINT64 ui64TmpSize; FLMBOOL bStartOver; FLMBOOL bOkToCloseTrans = FALSE; DB_INFO * pDbInfo; F_Pool localPool; void * pvDbInfoMark; if (hDb != HFDB_NULL && IsInCSMode( hDb)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto ExitCS; } localPool.poolInit( 512); if (!pPool) { pPool = &localPool; } if( RC_BAD( rc = pPool->poolCalloc( sizeof( DB_INFO), (void **)&pDbInfo))) { goto Exit; } pvDbInfoMark = pPool->poolMark(); if (hDb == HFDB_NULL) { if (RC_BAD( rc = FlmDbOpen( pDbFileName, pszDataDir, pRflDir, 0, NULL, &hDb))) { goto Exit; } bOpenedDb = TRUE; pDb = (FDB *) hDb; } pDbInfo->fnStatusFunc = fnStatusFunc; pDbInfo->LastStatusRc = FERR_OK; pDbInfo->pDb = pDb; if (pCheckProgress) { pDbInfo->pProgress = pCheckProgress; } else { pDbInfo->pProgress = &Progress; } f_memset( pDbInfo->pProgress, 0, sizeof(DB_CHECK_PROGRESS)); pDbInfo->bDbInitialized = TRUE; if (RC_BAD( rc = fdbInit( pDb, FLM_NO_TRANS, 0, 0, &bIgnore))) { goto Exit; } // Initialize the information block and Progress structure. Because // FlmDbCheck will start and stop read transactions during its // processing we can't allow any existing read transactions to exist. // However, it is OK for an update transaction to be in progress. An // update transaction will NOT be stopped and restarted. The only // reason a read transaction may be stopped and restarted is if we get // an old view error - something that cannot normally happen during an // update transaction. if (flmGetDbTransType( pDb) == FLM_READ_TRANS) { // If it is an invisible transaction, it may be aborted. if (pDb->uiFlags & FDB_INVISIBLE_TRANS) { if (RC_BAD( rc = flmAbortDbTrans( pDb))) { goto Exit; } } else { rc = RC_SET( FERR_TRANS_ACTIVE); goto Exit; } } // Since we know that the check will start read transactions during // its processing, set the flag to indicate that the KRef table should // be cleaned up on exit if we are still in a read transaction. bOkToCloseTrans = TRUE; uiBlockSize = pDb->pFile->FileHdr.uiBlockSize; // Check does its own reads using the handle to the file. pSFileHdl = pDb->pSFileHdl; pDbInfo->pSFileHdl = pSFileHdl; // Allocate memory to use for reading through the data blocks. if (RC_BAD( rc = f_alloc( uiBlockSize, &pBlk))) { goto Exit; } Begin_Check: // Initialize all statistics in the DB_INFO structure. rc = FERR_OK; bStartOver = FALSE; pPool->poolReset( pvDbInfoMark); pDbInfo->pProgress->bPhysicalCorrupt = FALSE; pDbInfo->pProgress->bLogicalIndexCorrupt = FALSE; pDbInfo->pProgress->ui64DatabaseSize = 0; pDbInfo->pProgress->uiNumFields = 0; pDbInfo->pProgress->uiNumIndexes = 0; pDbInfo->pProgress->uiNumContainers = 0; pDbInfo->pProgress->uiNumLogicalFiles = 0; pDbInfo->pLogicalFiles = NULL; pDbInfo->pProgress->pLfStats = NULL; pDbInfo->uiFlags = uiCheckFlags; pDbInfo->bStartedUpdateTrans = FALSE; f_memset( &pDbInfo->pProgress->AvailBlocks, 0, sizeof(BLOCK_INFO)); f_memset( &pDbInfo->pProgress->LFHBlocks, 0, sizeof(BLOCK_INFO)); f_memset( &pDbInfo->FileHdr, 0, sizeof(FILE_HDR)); f_memset( &Progress, 0, sizeof(DB_CHECK_PROGRESS)); Progress.AppArg = AppArg; // Get the dictionary information for the file if (RC_BAD( rc = chkGetDictInfo( pDbInfo))) { goto Exit; } Progress.ui64BytesExamined = 0; for (uiLoop = 1; uiLoop <= MAX_DATA_BLOCK_FILE_NUMBER( pDb->pFile->FileHdr.uiVersionNum); uiLoop++) { if (RC_BAD( pSFileHdl->getFileSize( uiLoop, &ui64TmpSize))) { break; } Progress.ui64DatabaseSize += ui64TmpSize; } // See if we have a valid end of file uiFileEnd = pDbInfo->pDb->LogHdr.uiLogicalEOF; if (FSGetFileOffset( uiFileEnd) % uiBlockSize != 0) { if (RC_BAD( rc = chkReportError( pDbInfo, FLM_BAD_FILE_SIZE, LOCALE_NONE, 0, 0, 0xFF, uiFileEnd, 0, 0, 0, 0xFFFF, 0, NULL))) { goto Exit; } } else if (Progress.ui64DatabaseSize < FSGetSizeInBytes( pDbInfo->pDb->pFile->uiMaxFileSize, uiFileEnd)) { Progress.ui64DatabaseSize = FSGetSizeInBytes( pDbInfo->pDb->pFile->uiMaxFileSize, uiFileEnd); } // Verify and count the LFH and PCODE blocks, B-Trees, and the AVAIL // list. if (RC_BAD( rc = chkVerifyLFHBlocks( pDbInfo, &bStartOver))) { goto Exit; } if (bStartOver) { goto Begin_Check; } // Check the b-trees. if (RC_BAD( rc = chkVerifyBTrees( pDbInfo, pPool, &bStartOver))) { goto Exit; } if (bStartOver) { goto Begin_Check; } // Check the avail list. if (RC_BAD( rc = chkVerifyAvailList( pDbInfo, &bStartOver))) { goto Exit; } if (bStartOver) { goto Begin_Check; } // Signal that we are finished. pDbInfo->pProgress->iCheckPhase = CHECK_FINISHED; pDbInfo->pProgress->bStartFlag = TRUE; if (RC_BAD( rc = chkCallProgFunc( pDbInfo))) { goto Exit; } pDbInfo->pProgress->bStartFlag = FALSE; Exit: // Pass out any error code returned by the callback. if ((RC_OK( rc)) && (RC_BAD( pDbInfo->LastStatusRc))) { rc = pDbInfo->LastStatusRc; } if (pDb && pDbInfo->bDbInitialized) { // Close down the transaction, if one is going if (bOkToCloseTrans && flmGetDbTransType( pDb) == FLM_READ_TRANS) { KrefCntrlFree( pDb); (void) flmAbortDbTrans( pDb); } fdbExit( pDb); } // Free memory, if allocated if (pBlk) { f_free( &pBlk); } // Close the database we opened. if (bOpenedDb) { (void) FlmDbClose( &hDb); } ExitCS: return (rc); } /**************************************************************************** Desc: This routine opens a file and reads its dictionary into memory. ****************************************************************************/ FSTATIC RCODE chkGetDictInfo( DB_INFO * pDbInfo) { RCODE rc = FERR_OK; FDB * pDb = pDbInfo->pDb; // Close down the transaction, if one is going. if (flmGetDbTransType( pDb) != FLM_UPDATE_TRANS) { if (pDb->uiTransType == FLM_READ_TRANS) { (void) flmAbortDbTrans( pDb); } // Start a read transaction on the file to ensure we are connected // to the file's dictionary structures. if (RC_BAD( rc = flmBeginDbTrans( pDb, FLM_READ_TRANS, 0, FLM_DONT_POISON_CACHE))) { goto Exit; } f_memcpy( &pDbInfo->FileHdr, &pDb->pFile->FileHdr, sizeof(FILE_HDR)); pDbInfo->pProgress->uiVersionNum = pDbInfo->FileHdr.uiVersionNum; pDbInfo->pProgress->uiBlockSize = pDbInfo->FileHdr.uiBlockSize; pDbInfo->pProgress->uiDefaultLanguage = pDbInfo->FileHdr.uiDefaultLanguage; } Exit: return (rc); } /**************************************************************************** Desc: This routine follows all of the blocks in a chain, verifying that they are properly linked. It also verifies each block's header. ****************************************************************************/ FSTATIC RCODE chkVerifyBlkChain( DB_INFO * pDbInfo, BLOCK_INFO * pBlkInfo, eCorruptionLocale eLocale, FLMUINT uiFirstBlkAddr, FLMUINT uiBlkType, FLMUINT * puiBlkCount, FLMBOOL * pbStartOverRV) { RCODE rc = FERR_OK; eCorruptionType eCorruption = FLM_NO_CORRUPTION; SCACHE * pSCache = NULL; FLMBYTE * pBlk = NULL; FLMUINT uiPrevBlkAddress; FLMUINT uiBlkCount = 0; STATE_INFO StateInfo; FLMBOOL bStateInitialized = FALSE; FLMUINT64 ui64SaveBytesExamined; FDB * pDb = pDbInfo->pDb; FILE_HDR * pFileHdr = &pDb->pFile->FileHdr; FLMUINT uiVersionNum = pFileHdr->uiVersionNum; FLMUINT uiBlockSize = pFileHdr->uiBlockSize; FLMUINT uiMaxBlocks; uiMaxBlocks = (FLMUINT)(FSGetSizeInBytes( pDb->pFile->uiMaxFileSize, pDb->LogHdr.uiLogicalEOF) / (FLMUINT64) uiBlockSize); uiPrevBlkAddress = BT_END; // There must be at least ONE block if it is the LFH chain. if ((uiBlkType == BHT_LFH_BLK) && (uiFirstBlkAddr == BT_END)) { eCorruption = FLM_BAD_LFH_LIST_PTR; (void) chkReportError( pDbInfo, eCorruption, eLocale, 0, 0, 0xFF, 0, 0, 0, 0, 0xFFFF, 0, NULL); goto Exit; } // Read through all of the blocks, verifying them as we go. Restart_Chain: uiBlkCount = 0; flmInitReadState( &StateInfo, &bStateInitialized, uiVersionNum, pDb, NULL, (FLMUINT) ((uiBlkType == BHT_FREE) ? (FLMUINT) 0xFF : (FLMUINT) 0), uiBlkType, NULL); ui64SaveBytesExamined = pDbInfo->pProgress->ui64BytesExamined; StateInfo.uiBlkAddress = uiFirstBlkAddr; while ((StateInfo.uiBlkAddress != BT_END) && (uiBlkCount < uiMaxBlocks)) { StateInfo.pBlk = NULL; if (RC_BAD( rc = chkBlkRead( pDbInfo, StateInfo.uiBlkAddress, StateInfo.pLogicalFile ? StateInfo.pLogicalFile->pLFile : NULL, &pBlk, &pSCache, &eCorruption))) { if (rc == FERR_OLD_VIEW) { FLMUINT uiSaveDictSeq = pDb->pDict->uiDictSeq; if (RC_BAD( rc = chkGetDictInfo( pDbInfo))) { goto Exit; } // If the dictionary ID changed, start over. if (pDb->pDict->uiDictSeq != uiSaveDictSeq) { *pbStartOverRV = TRUE; goto Exit; } pDbInfo->pProgress->ui64BytesExamined = ui64SaveBytesExamined; goto Restart_Chain; } pBlkInfo->eCorruption = eCorruption; pBlkInfo->uiNumErrors++; rc = chkReportError( pDbInfo, eCorruption, eLocale, 0, 0, 0xFF, StateInfo.uiBlkAddress, 0, 0, 0, 0xFFFF, 0, pBlk); } StateInfo.pBlk = pBlk; uiBlkCount++; pDbInfo->pProgress->ui64BytesExamined += (FLMUINT64) uiBlockSize; if (RC_BAD( rc = chkCallProgFunc( pDbInfo))) { goto Exit; } f_yieldCPU(); if (( eCorruption = flmVerifyBlockHeader( &StateInfo, pBlkInfo, uiBlockSize, 0, (uiBlkType == BHT_FREE) ? 0L : uiPrevBlkAddress, TRUE, TRUE)) != FLM_NO_CORRUPTION) { pBlkInfo->eCorruption = eCorruption; pBlkInfo->uiNumErrors++; chkReportError( pDbInfo, eCorruption, eLocale, 0, 0, 0xFF, StateInfo.uiBlkAddress, 0, 0, 0, 0xFFFF, 0, pBlk); goto Exit; } uiPrevBlkAddress = StateInfo.uiBlkAddress; StateInfo.uiBlkAddress = (FLMUINT) FB2UD( &pBlk[BH_NEXT_BLK]); } if ((StateInfo.uiBlkAddress != BT_END) && (RC_OK( pDbInfo->LastStatusRc))) { switch (uiBlkType) { case BHT_LFH_BLK: eCorruption = FLM_BAD_LFH_LIST_END; break; case BHT_PCODE_BLK: eCorruption = FLM_BAD_PCODE_LIST_END; break; case BHT_FREE: eCorruption = FLM_BAD_AVAIL_LIST_END; break; } pBlkInfo->eCorruption = eCorruption; pBlkInfo->uiNumErrors++; chkReportError( pDbInfo, eCorruption, eLocale, 0, 0, 0xFF, uiPrevBlkAddress, 0, 0, 0, 0xFFFF, 0, pBlk); goto Exit; } Exit: if (puiBlkCount) { *puiBlkCount = uiBlkCount; } if (bStateInitialized && StateInfo.pRecord) { StateInfo.pRecord->Release(); } if (pSCache) { ScaReleaseCache( pSCache, FALSE); } else if (pBlk) { f_free( &pBlk); } if (RC_OK( rc) && (eCorruption != FLM_NO_CORRUPTION)) { rc = (uiBlkType == BHT_FREE) ? RC_SET( FERR_DATA_ERROR) : RC_SET( FERR_DD_ERROR); } return (rc); } /**************************************************************************** Desc: This routine verifies the LFH blocks. ****************************************************************************/ FSTATIC RCODE chkVerifyLFHBlocks( DB_INFO * pDbInfo, FLMBOOL * pbStartOverRV) { RCODE rc = FERR_OK; pDbInfo->pProgress->uiLfNumber = 0; pDbInfo->pProgress->uiLfType = 0; pDbInfo->pProgress->iCheckPhase = CHECK_LFH_BLOCKS; pDbInfo->pProgress->bStartFlag = TRUE; if (RC_BAD( rc = chkCallProgFunc( pDbInfo))) { goto Exit; } pDbInfo->pProgress->bStartFlag = FALSE; // Go through the LFH blocks. if (RC_BAD( rc = chkVerifyBlkChain( pDbInfo, &pDbInfo->pProgress->LFHBlocks, LOCALE_LFH_LIST, pDbInfo->pDb->pFile->FileHdr.uiFirstLFHBlkAddr, BHT_LFH_BLK, NULL, pbStartOverRV)) || *pbStartOverRV) { goto Exit; } Exit: return (rc); } /**************************************************************************** Desc: This routine reads through the blocks in the AVAIL list and verifies that we don't have a loop or some other corruption in the list. ****************************************************************************/ FSTATIC RCODE chkVerifyAvailList( DB_INFO * pDbInfo, FLMBOOL * pbStartOverRV) { RCODE rc = FERR_OK; FLMUINT uiBlkCount; pDbInfo->pProgress->uiLfNumber = 0; pDbInfo->pProgress->uiLfType = 0; pDbInfo->pProgress->iCheckPhase = CHECK_AVAIL_BLOCKS; pDbInfo->pProgress->bStartFlag = TRUE; if (RC_BAD( rc = chkCallProgFunc( pDbInfo))) { goto Exit; } pDbInfo->pProgress->bStartFlag = FALSE; f_yieldCPU(); if (RC_BAD( rc = chkVerifyBlkChain( pDbInfo, &pDbInfo->pProgress->AvailBlocks, LOCALE_AVAIL_LIST, pDbInfo->pDb->LogHdr.uiFirstAvailBlkAddr, BHT_FREE, &uiBlkCount, pbStartOverRV)) || *pbStartOverRV) { goto Exit; } // See if the block count matches the block count stored in the log // header. if (uiBlkCount != pDbInfo->pDb->LogHdr.uiAvailBlkCount) { (void) chkReportError( pDbInfo, FLM_BAD_AVAIL_BLOCK_COUNT, LOCALE_AVAIL_LIST, 0, 0, 0xFF, 0, 0, 0, 0, 0xFFFF, 0, NULL); rc = RC_SET( FERR_DATA_ERROR); goto Exit; } Exit: return (rc); } /**************************************************************************** Desc: Verifies the key and reference counts against the counts that are stored in the tracker record. ****************************************************************************/ FSTATIC RCODE chkVerifyTrackerCounts( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndexNum, FLMUINT uiKeyCount, FLMUINT uiRefCount) { RCODE rc = FERR_OK; FDB * pDb = pStateInfo->pDb; FlmRecord * pRecord = NULL; eCorruptionType eCorruption; FLMUINT uiTrackerKeyCount = 0; FLMUINT uiTrackerRefCount = 0; void * pvField; // Retrieve the tracker record from record cache. if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, FLM_TRACKER_CONTAINER, uiIndexNum, TRUE, NULL, NULL, &pRecord))) { if (rc != FERR_NOT_FOUND) { goto Exit; } rc = FERR_OK; } else { if ((pvField = pRecord->find( pRecord->root(), FLM_KEY_TAG)) != NULL) { if (RC_BAD( rc = pRecord->getUINT( pvField, &uiTrackerKeyCount))) { goto Exit; } } if ((pvField = pRecord->find( pRecord->root(), FLM_REFS_TAG)) != NULL) { if (RC_BAD( rc = pRecord->getUINT( pvField, &uiTrackerRefCount))) { goto Exit; } } } // See if the counts match what we got from the tracker record. if (uiKeyCount != uiTrackerKeyCount || uiRefCount != uiTrackerRefCount) { // Log an error. eCorruption = (eCorruptionType) ((uiKeyCount != uiTrackerKeyCount) ? FLM_KEY_COUNT_MISMATCH : FLM_REF_COUNT_MISMATCH); if (RC_BAD( rc = chkReportError( pIxChkInfo->pDbInfo, eCorruption, LOCALE_INDEX, uiIndexNum, LF_INDEX, 0xFF, 0, 0, 0, 0, 0xFFFF, 0, NULL))) { goto Exit; } } Exit: if (pRecord) { pRecord->Release(); } return (rc); } /**************************************************************************** Desc: Determine if an index is an index that keeps key and reference counts. ****************************************************************************/ FSTATIC RCODE chkIsCountIndex( STATE_INFO * pStateInfo, FLMUINT uiIndexNum, FLMBOOL * pbIsCountIndex) { RCODE rc = FERR_OK; FDB * pDb = pStateInfo->pDb; IXD * pIxd; if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, uiIndexNum, NULL, &pIxd, TRUE))) { goto Exit; } *pbIsCountIndex = (FLMBOOL) ((pIxd->uiFlags & IXD_COUNT) ? (FLMBOOL) TRUE : (FLMBOOL) FALSE); Exit: return (rc); } /**************************************************************************** Desc: Verifies the current index key against the result set. ****************************************************************************/ RCODE chkVerifyIXRSet( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIxRefDrn) { RCODE rc = FERR_OK; FLMINT iCmpVal = 0; FLMUINT uiIteration = 0; FLMBOOL bRSetEmpty = FALSE; RS_IX_KEY * pCurrRSKey; RS_IX_KEY * pPrevRSKey; if (!pIxChkInfo->pCurrRSKey) { pIxChkInfo->pCurrRSKey = &pIxChkInfo->IxKey1; pIxChkInfo->pPrevRSKey = &pIxChkInfo->IxKey2; } // Compare index and result set keys while (!bRSetEmpty) { if (pIxChkInfo->bGetNextRSKey) { // Get the next key from the result set. If the result set is // empty, then pIxChkInfo->uiRSKeyLen will be set to zero, // forcing the problem to be resolved below. if (RC_BAD( rc = chkGetNextRSKey( pIxChkInfo))) { if (rc == FERR_EOF_HIT || rc == FERR_NOT_FOUND) { // Set bRSetEmpty to TRUE so that the loop will exit after // the current key is resolved. Otherwise, conflict // resolution on the current key will be repeated forever // (infinite loop). bRSetEmpty = TRUE; rc = FERR_OK; } else { goto Exit; } } else { // Updated statistics pIxChkInfo->pDbInfo->pProgress->ui64NumKeysExamined++; } } pCurrRSKey = pIxChkInfo->pCurrRSKey; pPrevRSKey = pIxChkInfo->pPrevRSKey; if (pCurrRSKey->uiRSKeyLen == 0 || bRSetEmpty) { // We don't have a key because we got an EOF when reading the // result set. Need to resolve the fact that the result set does // not have a key that is found in the index. Set iCmpVal to 1 to // force this resolution. iCmpVal = 1; } else { // Compare the index key and result set key. iCmpVal = chkCompareKeySet( pCurrRSKey->uiRSIxNum, &(pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET]), pCurrRSKey->uiRSKeyLen - RS_KEY_OVERHEAD, pCurrRSKey->uiRSRefDrn, pStateInfo->pLogicalFile->pLFile->uiLfNum, pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, uiIxRefDrn); } if (iCmpVal < 0) { // If a comparison is done where the keys from the result set // don't match what we got from the index, we will forego // verifying the tracker counts. Verifying of tracker counts can // only occur if we have an otherwise clean check of the index // keys. pIxChkInfo->bCheckCounts = FALSE; // The result set key is less than the index key. This could mean // that the result set key needs to be added to the index. if ((RC_BAD( rc = chkResolveIXMissingKey( pStateInfo, pIxChkInfo))) || (pIxChkInfo->pDbInfo->bReposition)) { // If the key was added to the index (bReposition == TRUE) or // we got some other error, we don't want to get the next // result set key. pIxChkInfo->bGetNextRSKey = FALSE; goto Exit; } else { // False alarm. The index is missing the key because of a // concurrent update. We want to get the next RS key. pIxChkInfo->bGetNextRSKey = TRUE; } } else if (iCmpVal > 0) { // If a comparison is done where the keys from the result set // don't match what we got from the index, we will forego // verifying the tracker counts. Verifying of tracker counts can // only occur if we have an otherwise clean check of the index // keys. pIxChkInfo->bCheckCounts = FALSE; // The result set key is greater than the index key. This could // mean that the index key needs to be deleted from the index. // Whether we delete the index key or not, we don't need to get // the next result set key, but we do want to reposition and get // the next index key. pIxChkInfo->bGetNextRSKey = FALSE; if ((RC_BAD( rc = chkResolveRSetMissingKey( pStateInfo, pIxChkInfo, uiIxRefDrn))) || pIxChkInfo->pDbInfo->bReposition) { goto Exit; } break; } else { // The index and result set keys are equal. We want to get the // next result set and index keys. pIxChkInfo->bGetNextRSKey = TRUE; // Determine if we have switched indexes. If so, verify the key // and reference counts against the counts in the tracker record. if (pCurrRSKey->uiRSIxNum != pPrevRSKey->uiRSIxNum) { if (pIxChkInfo->bCheckCounts) { // Verify the key and reference counts against tracker // record. if (RC_BAD( rc = chkVerifyTrackerCounts( pStateInfo, pIxChkInfo, pPrevRSKey->uiRSIxNum, pIxChkInfo->uiRSIxKeyCount, pIxChkInfo->uiRSIxRefCount))) { goto Exit; } } // Determine if the new index is one that supports counts. if (RC_BAD( rc = chkIsCountIndex( pStateInfo, pCurrRSKey->uiRSIxNum, &pIxChkInfo->bCheckCounts))) { goto Exit; } if (pIxChkInfo->bCheckCounts) { // Set the counts to one. pIxChkInfo->uiRSIxKeyCount = 1; pIxChkInfo->uiRSIxRefCount = 1; } } else { if (pIxChkInfo->bCheckCounts) { // Always increment the reference count. pIxChkInfo->uiRSIxRefCount++; // See if the key changed. if (pCurrRSKey->uiRSKeyLen != pPrevRSKey->uiRSKeyLen || (pCurrRSKey->uiRSKeyLen > RS_KEY_OFFSET && f_memcmp( &pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET], &pPrevRSKey->pucRSKeyBuf[RS_KEY_OFFSET], pCurrRSKey->uiRSKeyLen - RS_KEY_OFFSET) != 0)) { pIxChkInfo->uiRSIxKeyCount++; } else { // If the keys are the same, at least the DRNs better // be different. flmAssert( pCurrRSKey->uiRSRefDrn != pPrevRSKey->uiRSRefDrn); } } } break; } // Call the yield function periodically uiIteration++; if (!(uiIteration & 0x1F)) { f_yieldCPU(); } } Exit: return (rc); } /**************************************************************************** Desc: Retrieves the next key from the sorted result set ****************************************************************************/ RCODE chkGetNextRSKey( IX_CHK_INFO * pIxChkInfo) { RCODE rc = FERR_OK; RS_IX_KEY * pCurrRSKey; // Swap current and last key pointers - this allows us to always keep // the last key without having to memcpy the keys. pCurrRSKey = pIxChkInfo->pCurrRSKey; pIxChkInfo->pCurrRSKey = pIxChkInfo->pPrevRSKey; pIxChkInfo->pPrevRSKey = pCurrRSKey; pCurrRSKey = pIxChkInfo->pCurrRSKey; // Get the next key if (RC_BAD( rc = pIxChkInfo->pRSet->getNext( pCurrRSKey->pucRSKeyBuf, MAX_KEY_SIZ + RS_KEY_OVERHEAD, &pCurrRSKey->uiRSKeyLen))) { goto Exit; } // Verify that the key meets the minimum length requirements flmAssert( pCurrRSKey->uiRSKeyLen >= RS_KEY_OVERHEAD); // Extract the index number and reference DRN pCurrRSKey->uiRSIxNum = (FLMUINT) FB2UW( &(pCurrRSKey->pucRSKeyBuf[RS_IX_OFFSET])); pCurrRSKey->uiRSRefDrn = (FLMUINT) FB2UD( &(pCurrRSKey->pucRSKeyBuf[RS_REF_OFFSET])); Exit: return (rc); } /**************************************************************************** Desc: Resolves the case of a key found in the result set but not in the current index. ****************************************************************************/ RCODE chkResolveIXMissingKey( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo) { RCODE rc = FERR_OK; FLMBOOL bKeyInRec; FLMBOOL bKeyInIndex; FLMBOOL bFixCorruption = FALSE; RS_IX_KEY * pCurrRSKey = pIxChkInfo->pCurrRSKey; // Determine if the record generates the key and if the key is found in // the index. if (RC_BAD( rc = chkGetKeySource( pStateInfo, pIxChkInfo, pCurrRSKey->uiRSIxNum, &(pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET]), (FLMUINT) (pCurrRSKey->uiRSKeyLen - RS_KEY_OVERHEAD), pCurrRSKey->uiRSRefDrn, NULL, &bKeyInRec, &bKeyInIndex))) { if (rc == FERR_INDEX_OFFLINE) { rc = FERR_OK; } goto Exit; } // If the record does not generate the key or the key+ref is in the // index, the index is not corrupt. if (!bKeyInRec || bKeyInIndex) { pIxChkInfo->pDbInfo->pProgress->ui64NumConflicts++; goto Exit; } // Otherwise, the index is corrupt; Update statistics pIxChkInfo->pDbInfo->pProgress->ui64NumRecKeysNotFound++; pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexCorruptions++; // Report the error if (RC_BAD( rc = chkReportIxError( pStateInfo, pIxChkInfo, FLM_DRN_NOT_IN_KEY_REFSET, pCurrRSKey->uiRSIxNum, pCurrRSKey->uiRSRefDrn, &(pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET]), (FLMUINT) (pCurrRSKey->uiRSKeyLen - RS_KEY_OVERHEAD), &bFixCorruption))) { goto Exit; } // Exit if the application does not want to repair the corruption. if (!bFixCorruption) { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; goto Exit; } // Fix the corruption; Update statistics pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexRepairs++; // Add the key if (RC_OK( rc = chkAddDelKeyRef( pStateInfo, pIxChkInfo, pCurrRSKey->uiRSIxNum, &(pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET]), (FLMUINT) (pCurrRSKey->uiRSKeyLen - RS_KEY_OVERHEAD), pCurrRSKey->uiRSRefDrn, 0))) { pIxChkInfo->pDbInfo->bReposition = TRUE; goto Exit; } else { if (rc == FERR_NOT_UNIQUE) { // A subsequent record probably also generates this key, but the // index is a unique index so we were not allowed to add the // missing key + ref to the index. This record should probably be // deleted. if (RC_OK( rc = chkResolveNonUniqueKey( pStateInfo, pIxChkInfo, pCurrRSKey->uiRSIxNum, &(pCurrRSKey->pucRSKeyBuf[RS_KEY_OFFSET]), (FLMUINT) (pCurrRSKey->uiRSKeyLen - RS_KEY_OVERHEAD), pCurrRSKey->uiRSRefDrn))) { pIxChkInfo->pDbInfo->bReposition = TRUE; goto Exit; } } else { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; } } Exit: return (rc); } /**************************************************************************** Desc: Resolves the case of a key found in the current index but not in the result set. ****************************************************************************/ FSTATIC RCODE chkResolveRSetMissingKey( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIxRefDrn) { RCODE rc = FERR_OK; FLMBOOL bKeyInRec; FLMBOOL bKeyInIndex; FLMBOOL bFixCorruption = FALSE; // See if the key is found in the index and/or generated by the record. if (RC_BAD( rc = chkGetKeySource( pStateInfo, pIxChkInfo, pStateInfo->pLogicalFile->pLFile->uiLfNum, pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, uiIxRefDrn, NULL, &bKeyInRec, &bKeyInIndex))) { if (rc == FERR_INDEX_OFFLINE) { rc = FERR_OK; } goto Exit; } // If the key is generated by the record or the key is not found in the // index, the index is not corrupt. if (bKeyInRec || !bKeyInIndex) { pIxChkInfo->pDbInfo->pProgress->ui64NumConflicts++; goto Exit; } // Otherwise, the index is corrupt; Update statistics pIxChkInfo->pDbInfo->pProgress->ui64NumKeysNotFound++; pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexCorruptions++; // Report the error if (RC_BAD( rc = chkReportIxError( pStateInfo, pIxChkInfo, FLM_IX_KEY_NOT_FOUND_IN_REC, pStateInfo->pLogicalFile->pLFile->uiLfNum, uiIxRefDrn, pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, &bFixCorruption))) { goto Exit; } // Exit if the application does not want to repair the corruption. if (!bFixCorruption) { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; goto Exit; } // Fix the corruption; Update statistics pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexRepairs++; // Delete the reference from the index if (RC_OK( rc = chkAddDelKeyRef( pStateInfo, pIxChkInfo, pStateInfo->pLogicalFile->pLFile->uiLfNum, pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, uiIxRefDrn, KREF_DELETE_FLAG))) { pIxChkInfo->pDbInfo->bReposition = TRUE; } else { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; } Exit: return (rc); } /**************************************************************************** Desc: Resolves the case of multiple references associated with a key in a unique index. ****************************************************************************/ RCODE chkResolveNonUniqueKey( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn) { RCODE rc = FERR_OK; FDB * pDb = pStateInfo->pDb; LFILE * pRecLFile = NULL; FLMBOOL bDeleteRec = FALSE; FLMUINT uiRecContainer; RCODE rc2 = FERR_OK; FLMBOOL bFixCorruption = FALSE; FlmRecord * pOldRecord = NULL; // Verify that the record violates the constraints of the unique index // and should be deleted. if (RC_BAD( rc = chkVerifyDelNonUniqueRec( pStateInfo, pIxChkInfo, uiIndex, pucKey, uiKeyLen, uiDrn, &uiRecContainer, &bDeleteRec))) { goto Exit; } if (bDeleteRec) { // Update statistics pIxChkInfo->pDbInfo->pProgress->ui64NumNonUniqueKeys++; pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexCorruptions++; // Report the error if (RC_BAD( rc = chkReportIxError( pStateInfo, pIxChkInfo, FLM_NON_UNIQUE_ELM_KEY_REF, uiIndex, uiDrn, pucKey, uiKeyLen, &bFixCorruption))) { goto Exit; } if (!bFixCorruption) { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; goto Exit; } // Delete the record that generated the non-unique reference. ; // Update statistics pIxChkInfo->pDbInfo->pProgress->uiLogicalIndexRepairs++; // Start an update transaction, if necessary. if (RC_BAD( rc = chkStartUpdate( pStateInfo, pIxChkInfo))) { goto Exit; } // Re-verify that the record should be deleted. if (RC_BAD( rc = chkVerifyDelNonUniqueRec( pStateInfo, pIxChkInfo, uiIndex, pucKey, uiKeyLen, uiDrn, &uiRecContainer, &bDeleteRec))) { goto Exit; } if (bDeleteRec == TRUE) { void * pvMark; // Mark the temporary pool. pvMark = pDb->TempPool.poolMark(); // Call the internal delete function, passing boolean flags // indicating that missing keys should not prevent the record // deletion and that the record validator callback should not be // called. if (RC_BAD( rc = fdictGetContainer( pDb->pDict, uiRecContainer, &pRecLFile))) { goto Exit; } rc = flmDeleteRecord( pDb, pRecLFile, uiDrn, &pOldRecord, TRUE); if (gv_FlmSysData.UpdateEvents.pEventCBList) { flmUpdEventCallback( pDb, F_EVENT_DELETE_RECORD, (HFDB) pDb, rc, uiDrn, uiRecContainer, NULL, pOldRecord); } // Reset the temporary pool. The flmDeleteRecord call // allocates space for the record that is being deleted. pDb->TempPool.poolReset( pvMark); if (RC_BAD( rc)) { // If the record had already been deleted, continue the check // without reporting the error. if (rc == FERR_NOT_FOUND) { rc = FERR_OK; // Update statistics pIxChkInfo->pDbInfo->pProgress->uiNumProblemsFixed++; } else { // Set the logical corruption flag pIxChkInfo->pDbInfo->pProgress->bLogicalIndexCorrupt = TRUE; } goto Exit; } // Update statistics pIxChkInfo->pDbInfo->pProgress->uiNumProblemsFixed++; } } else { // Increment the conflict counter pIxChkInfo->pDbInfo->pProgress->ui64NumConflicts++; } Exit: // End the update. chkEndUpdate will be a no-op if an update // transaction was not started. rc2 = chkEndUpdate( pStateInfo, pIxChkInfo); if (pOldRecord) { pOldRecord->Release(); } return ((RCODE) ((rc != FERR_OK) ? (RCODE) rc : (RCODE) rc2)); } /**************************************************************************** Desc: Verifies that the specified record should be deleted because it generates key(s) which violate the constraints of a unique index. ****************************************************************************/ FSTATIC RCODE chkVerifyDelNonUniqueRec( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiRecDrn, FLMUINT * puiRecContainerRV, FLMBOOL * pbDelRecRV) { RCODE rc = FERR_OK; FLMBOOL bKeyInRec; FLMBOOL bRecRefdByKey; FLMUINT uiRefCount; FLMUINT uiRecContainer; *pbDelRecRV = FALSE; *puiRecContainerRV = 0; // See if the key is found in the index and/or generated by the record. if (RC_BAD( rc = chkGetKeySource( pStateInfo, pIxChkInfo, uiIndex, pucKey, uiKeyLen, uiRecDrn, &uiRecContainer, &bKeyInRec, &bRecRefdByKey))) { if (rc == FERR_INDEX_OFFLINE) { rc = FERR_OK; } goto Exit; } *puiRecContainerRV = uiRecContainer; if (bKeyInRec == TRUE) { // Verify that the key is not unique if (RC_BAD( rc = chkVerifyKeyNotUnique( pStateInfo, uiIndex, pucKey, uiKeyLen, &uiRefCount))) { goto Exit; } if (uiRefCount > 1) { // The unique index has multiple references for the specified // key. Since the current record generates a non-unique key, it // should be deleted even if it is not one of the records // referenced by the key. Of course, if it is already referenced // by the key, deleting the record will reduce the number of // references associated with the key by one. *pbDelRecRV = TRUE; } else if (uiRefCount == 1 && bRecRefdByKey == FALSE) { // The unique index already has a key corresponding to the key // being generated by the current record. However, the record is // not referenced from the unique index. The record should still // be deleted since it generates a non-unique key. *pbDelRecRV = TRUE; } } Exit: return (rc); } /**************************************************************************** Desc: Determines if a key is generated by the current record and/or if the key is found in the current index ****************************************************************************/ FSTATIC RCODE chkGetKeySource( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, FLMUINT * puiRecordContainerRV, FLMBOOL * pbKeyInRecRV, FLMBOOL * pbKeyInIndexRV) { RCODE rc = FERR_OK; FlmRecord * pRecord = NULL; FDB * pDb = pStateInfo->pDb; LFILE * pLFile; LFILE * pIxLFile; REC_KEY * pKeys = NULL; REC_KEY * pTempKey = NULL; IXD * pIxd; FLMBYTE ucRecKeyBuf[MAX_KEY_SIZ]; FLMUINT uiRecKeyLen; FLMUINT uiKeyCount; FLMBOOL bResetKRef = FALSE; void * pIxPoolMark; void * pDbPoolMark; FLMUINT uiContainerNum; // Initialize return values. *pbKeyInRecRV = FALSE; *pbKeyInIndexRV = FALSE; if (puiRecordContainerRV) { *puiRecordContainerRV = 0; } // Initialize variables pIxPoolMark = pIxChkInfo->pool.poolMark(); // Need to mark the DB's temporary pool. The index code allocates // memory for new CDL entries from the DB pool. If the pool is not // reset, it grows during the check and becomes VERY large. pDbPoolMark = pDb->TempPool.poolMark(); // Set up the KRef so that flmGetRecKeys will work if (RC_BAD( rc = KrefCntrlCheck( pDb))) { goto Exit; } bResetKRef = TRUE; // Get the LFile and IXD of the index if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, uiIndex, &pIxLFile, &pIxd))) { // Return FERR_INDEX_OFFLINE error. goto Exit; } if ((uiContainerNum = pIxd->uiContainerNum) == 0) { // Container number is always the last two bytes of the key. flmAssert( uiKeyLen > getIxContainerPartLen( pIxd)); uiContainerNum = getContainerFromKey( pucKey, uiKeyLen); } // Get the LFile of the record that caused the error if (RC_BAD( rc = fdictGetContainer( pDb->pDict, uiContainerNum, &pLFile))) { goto Exit; } // Set the record container return value if (puiRecordContainerRV) { *puiRecordContainerRV = uiContainerNum; } // See if the key is in the index. if (RC_BAD( rc = chkVerifyKeyExists( pDb, pIxLFile, pucKey, uiKeyLen, uiDrn, pbKeyInIndexRV))) { goto Exit; } // Read the record if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, pLFile->uiLfNum, uiDrn, FALSE, NULL, NULL, &pRecord))) { if (rc != FERR_NOT_FOUND) { goto Exit; } // NOTE: Deliberately not bringing in to cache if not found there. if (RC_BAD( rc = FSReadRecord( pDb, pLFile, uiDrn, &pRecord, NULL, NULL))) { if (rc == FERR_NOT_FOUND) { *pbKeyInRecRV = FALSE; rc = FERR_OK; } else { goto Exit; } } } if (pRecord) { // Generate record keys if (RC_BAD( rc = flmGetRecKeys( pDb, pIxd, pRecord, pRecord->getContainerID(), TRUE, &(pIxChkInfo->pool), &pKeys))) { goto Exit; } uiKeyCount = 0; pTempKey = pKeys; while (pTempKey != NULL) { // Build the collated keys for each key tree. if (RC_BAD( rc = KYTreeToKey( pDb, pIxd, pTempKey->pKey, pTempKey->pKey->getContainerID(), ucRecKeyBuf, &uiRecKeyLen, 0))) { goto Exit; } if (KYKeyCompare( pucKey, uiKeyLen, ucRecKeyBuf, uiRecKeyLen) == BT_EQ_KEY) { *pbKeyInRecRV = TRUE; break; } pTempKey = pTempKey->pNextKey; uiKeyCount++; // Release the CPU periodically to prevent CPU hog problems. f_yieldCPU(); } } Exit: if (pKeys) { pTempKey = pKeys; while (pTempKey) { pTempKey->pKey->Release(); pTempKey = pTempKey->pNextKey; } } if (pRecord) { pRecord->Release(); } // Remove any keys added to the KRef if (bResetKRef) { KYAbortCurrentRecord( pDb); } // Reset the DB's temporary pool pDb->TempPool.poolReset( pDbPoolMark); // Reset the index check pool pIxChkInfo->pool.poolReset( pIxPoolMark); return (rc); } /**************************************************************************** Desc: Verify that a key is (or is not) found in an index. ****************************************************************************/ FSTATIC RCODE chkVerifyKeyExists( FDB * pDb, LFILE * pLFile, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiRefDrn, FLMBOOL * pbFoundRV) { RCODE rc = FERR_OK; BTSK stackBuf[BH_MAX_LEVELS]; BTSK * stack = stackBuf; FLMUINT uiDinDomain = DIN_DOMAIN( uiRefDrn) + 1; FLMBYTE ucBtKeyBuf[MAX_KEY_SIZ]; DIN_STATE dinState; FLMUINT uiTmpDrn; *pbFoundRV = FALSE; f_memset( &dinState, 0, sizeof(DIN_STATE)); // Initialize stack cache. FSInitStackCache( &(stackBuf[0]), BH_MAX_LEVELS); stack = stackBuf; stack->pKeyBuf = ucBtKeyBuf; // Search for the key. if (RC_BAD( rc = FSBtSearch( pDb, pLFile, &stack, pucKey, uiKeyLen, uiDinDomain))) { goto Exit; } if (stack->uiCmpStatus == BT_EQ_KEY) { uiTmpDrn = uiRefDrn; // Reading the current element, position to or after uiTmpDrn rc = FSRefSearch( stack, &dinState, &uiTmpDrn); // If the entry was not found, returns FERR_FAILURE if (rc == FERR_OK) { *pbFoundRV = TRUE; } else if (rc != FERR_FAILURE) { goto Exit; } else { rc = FERR_OK; } } Exit: // Free the stack cache FSReleaseStackCache( stackBuf, BH_MAX_LEVELS, FALSE); return (rc); } /**************************************************************************** Desc: Compares a composite key (index, ref, key) for equality. Note: Since index references are sorted in decending order, a composite key with a lower ref DRN will sort after a key with a higher ref DRN. ****************************************************************************/ FLMINT chkCompareKeySet( FLMUINT uiIxNum1, FLMBYTE * pData1, FLMUINT uiLength1, FLMUINT uiDrn1, FLMUINT uiIxNum2, FLMBYTE * pData2, FLMUINT uiLength2, FLMUINT uiDrn2) { FLMINT iCmpVal = 0; FLMUINT uiMinLen; // Compare index numbers if (uiIxNum1 > uiIxNum2) { iCmpVal = 1; goto Exit; } else if (uiIxNum1 < uiIxNum2) { iCmpVal = -1; goto Exit; } // Compare keys uiMinLen = (FLMUINT) (uiLength1 < uiLength2) ? uiLength1 : uiLength2; iCmpVal = f_memcmp( pData1, pData2, uiMinLen); if (iCmpVal == 0) { // Compare references if (uiLength1 == uiLength2) { // A key with a lower ref DRN will sort after a key with a higher // ref DRN. if (uiDrn1 > uiDrn2) { iCmpVal = -1; } else if (uiDrn1 < uiDrn2) { iCmpVal = 1; } else { iCmpVal = 0; goto Exit; } } else if (uiLength1 > uiLength2) { iCmpVal = 1; } else { iCmpVal = -1; } } else { iCmpVal = (FLMINT) ((iCmpVal > 0) ? (FLMINT) 1 : (FLMINT) -1); } Exit: return (iCmpVal); } /**************************************************************************** Desc: This routine adds or deletes an index key and/or reference. ****************************************************************************/ FSTATIC RCODE chkAddDelKeyRef( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, FLMUINT uiIndexNum, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, FLMUINT uiFlags) { RCODE rc = FERR_OK; RCODE rc2 = FERR_OK; FLMBYTE ucKeyBuf[sizeof(KREF_ENTRY) + MAX_KEY_SIZ]; KREF_ENTRY * pKrefEntry = (KREF_ENTRY *) (&ucKeyBuf[0]); IXD * pIxd; LFILE * pLFile; FLMBOOL bStartedUpdate = FALSE; FLMBOOL bKeyInRec; FLMBOOL bKeyInIndex; // Start an update transaction, if necessary if (RC_BAD( rc = chkStartUpdate( pStateInfo, pIxChkInfo))) { goto Exit; } bStartedUpdate = TRUE; // Look up the LFILE and IXD for the index. if (RC_BAD( rc = fdictGetIndex( pStateInfo->pDb->pDict, pStateInfo->pDb->pFile->bInLimitedMode, uiIndexNum, &pLFile, &pIxd))) { // Shouldn't get FERR_INDEX_OFFLINE in here. goto Exit; } // Verify that the state has not changed if (RC_BAD( rc = chkGetKeySource( pStateInfo, pIxChkInfo, uiIndexNum, pucKey, uiKeyLen, uiDrn, NULL, &bKeyInRec, &bKeyInIndex))) { goto Exit; } if ((bKeyInIndex == TRUE && ((uiFlags & KREF_DELETE_FLAG) != 0)) || (bKeyInIndex == FALSE && uiFlags == 0)) { // Setup the KrefEntry structure flmAssert( uiIndexNum > 0 && uiIndexNum < FLM_UNREGISTERED_TAGS); f_memcpy( &(ucKeyBuf[sizeof(KREF_ENTRY)]), pucKey, uiKeyLen); pKrefEntry->ui16KeyLen = (FLMUINT16) uiKeyLen; pKrefEntry->ui16IxNum = (FLMUINT16) uiIndexNum; pKrefEntry->uiDrn = uiDrn; pKrefEntry->uiTrnsSeq = 1; pKrefEntry->uiFlags = uiFlags; if ((pIxd->uiFlags & IXD_UNIQUE) != 0) { // Do not allow duplicate keys to be added to a unique index. pKrefEntry->uiFlags |= KREF_UNIQUE_KEY; } // Add or delete the key/reference. if (RC_BAD( rc = FSRefUpdate( pStateInfo->pDb, pLFile, pKrefEntry))) { goto Exit; } // Update statistics pIxChkInfo->pDbInfo->pProgress->uiNumProblemsFixed++; } Exit: // End the update. if (bStartedUpdate == TRUE) { if (RC_BAD( rc2 = chkEndUpdate( pStateInfo, pIxChkInfo))) { goto Exit; } } rc = (RCODE) ((rc != FERR_OK) ? (RCODE) rc : (RCODE) rc2); return (rc); } /**************************************************************************** Desc: Populates the CORRUPT_INFO structure and calls the user's callback routine. ****************************************************************************/ FSTATIC RCODE chkReportIxError( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, eCorruptionType eCorruption, FLMUINT uiErrIx, FLMUINT uiErrDrn, FLMBYTE * pucErrKey, FLMUINT uiErrKeyLen, FLMBOOL * pbFixErrRV) { RCODE rc = FERR_OK; FDB * pDb = pStateInfo->pDb; F_Pool * pTmpPool; IXD * pIxd; LFILE * pLFile; void * pIxPoolMark; void * pDbPoolMark = NULL; FLMBOOL bResetKRef = FALSE; CORRUPT_INFO CorruptInfo; FLMUINT uiContainerNum; f_memset( &CorruptInfo, 0, sizeof(CORRUPT_INFO)); // Mark the index check pool pIxPoolMark = pIxChkInfo->pool.poolMark(); pTmpPool = &(pIxChkInfo->pool); // Need to mark the DB's temporary pool. The index code allocates // memory for new CDL entries from the DB pool. If the pool is not // reset, it grows during the check and becomes VERY large. pDbPoolMark = pDb->TempPool.poolMark(); // Set up the KRef so that flmGetRecKeys will work if (RC_BAD( rc = KrefCntrlCheck( pDb))) { goto Exit; } bResetKRef = TRUE; // Report the error CorruptInfo.eErrLocale = LOCALE_INDEX; CorruptInfo.eCorruption = eCorruption; CorruptInfo.uiErrLfNumber = uiErrIx; CorruptInfo.uiErrDrn = uiErrDrn; CorruptInfo.uiErrElmOffset = pStateInfo->uiElmOffset; if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, uiErrIx, NULL, &pIxd, TRUE))) { goto Exit; } // Generate the key tree using the key that caused the error if (RC_BAD( rc = flmIxKeyOutput( pIxd, pucErrKey, uiErrKeyLen, &(CorruptInfo.pErrIxKey), FALSE))) { goto Exit; } // Get the LFile if ((uiContainerNum = pIxd->uiContainerNum) == 0) { // Container number is always the last two bytes of the key. flmAssert( uiErrKeyLen > getIxContainerPartLen( pIxd)); uiContainerNum = getContainerFromKey( pucErrKey, uiErrKeyLen); } // Get the LFile if (RC_BAD( rc = fdictGetContainer( pDb->pDict, uiContainerNum, &pLFile))) { goto Exit; } // Read the record if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, pLFile->uiLfNum, uiErrDrn, FALSE, NULL, NULL, &(CorruptInfo.pErrRecord)))) { if (rc != FERR_NOT_FOUND) { goto Check_Error; } // NOTE: Deliberately not bringing in to cache if not found there. if (RC_BAD( rc = FSReadRecord( pDb, pLFile, uiErrDrn, &(CorruptInfo.pErrRecord), NULL, NULL))) { Check_Error: // Record may have been deleted or cannot be returned because of // an old view error. if (rc == FERR_NOT_FOUND) { rc = FERR_OK; } else if (FlmErrorIsFileCorrupt( rc)) { pIxChkInfo->pDbInfo->pProgress->bPhysicalCorrupt = TRUE; rc = FERR_OK; goto Exit; } else { goto Exit; } } } // Generate index keys for the current index and record if (CorruptInfo.pErrRecord != NULL) { if (RC_BAD( rc = flmGetRecKeys( pDb, pIxd, CorruptInfo.pErrRecord, CorruptInfo.pErrRecord->getContainerID(), TRUE, pTmpPool, &(CorruptInfo.pErrRecordKeyList)))) { goto Exit; } } *pbFixErrRV = FALSE; if ((pIxChkInfo->pDbInfo->fnStatusFunc) && (RC_OK( pIxChkInfo->pDbInfo->LastStatusRc))) { pIxChkInfo->pDbInfo->LastStatusRc = (*pIxChkInfo->pDbInfo->fnStatusFunc) ( FLM_PROBLEM_STATUS, (void *) &CorruptInfo, (void *) pbFixErrRV, pIxChkInfo->pDbInfo->pProgress->AppArg); } Exit: if (CorruptInfo.pErrRecord) { CorruptInfo.pErrRecord->Release(); } if (CorruptInfo.pErrIxKey) { CorruptInfo.pErrIxKey->Release(); } if (CorruptInfo.pErrRecordKeyList) { REC_KEY * pTempKey = CorruptInfo.pErrRecordKeyList; while (pTempKey) { pTempKey->pKey->Release(); pTempKey = pTempKey->pNextKey; } } // Remove any keys added to the KRef if (bResetKRef) { KYAbortCurrentRecord( pDb); } // Reset the DB's temporary pool pDb->TempPool.poolReset( pDbPoolMark); // Reset the index check pool pIxChkInfo->pool.poolReset( pIxPoolMark); return (rc); } /**************************************************************************** Desc: This routine verifies that a key is not unique ****************************************************************************/ FSTATIC RCODE chkVerifyKeyNotUnique( STATE_INFO * pStateInfo, FLMUINT uiIndex, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT * puiRefCountRV) { RCODE rc = FERR_OK; FDB * pDb = pStateInfo->pDb; FlmRecord * pKeyTree = NULL; IXD * pIxd; FLMUINT uiRefDrn; *puiRefCountRV = 0; // Get the IXD if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, uiIndex, NULL, &pIxd))) { goto Exit; } // This routine should not be called unless the index is a unique // index. flmAssert( ((pIxd->uiFlags & IXD_UNIQUE) != 0)); // Generate the key tree from the collation key. if (RC_BAD( rc = flmIxKeyOutput( pIxd, pucKey, uiKeyLen, &pKeyTree, FALSE))) { goto Exit; } // Count up to the first two references for the key. if (RC_BAD( rc = FlmKeyRetrieve( (HFDB) pDb, uiIndex, pKeyTree->getContainerID(), pKeyTree, 0, FO_EXACT, NULL, &uiRefDrn))) { // If the key is NOT found, the problem no longer exists. if ((rc == FERR_NOT_FOUND) || (rc == FERR_BOF_HIT) || (rc == FERR_EOF_HIT)) { rc = FERR_OK; } goto Exit; } // Found at least one reference. *puiRefCountRV = 1; // Go exclusive of the last key/reference found to see if there are // more references for the key. if (RC_BAD( rc = FlmKeyRetrieve( (HFDB) pDb, uiIndex, pKeyTree->getContainerID(), pKeyTree, uiRefDrn, FO_KEY_EXACT | FO_EXCL, NULL, &uiRefDrn))) { if ((rc == FERR_NOT_FOUND) || (rc == FERR_BOF_HIT) || (rc == FERR_EOF_HIT)) { rc = FERR_OK; } goto Exit; } // May be more references, but it is sufficient to know that there are // at least two. *puiRefCountRV = 2; Exit: return (rc); } /**************************************************************************** Desc: ****************************************************************************/ FSTATIC RCODE chkStartUpdate( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo) { RCODE rc = FERR_OK; RCODE rc2 = FERR_OK; FDB * pDb = pStateInfo->pDb; FLMBOOL bAbortedReadTrans = FALSE; if (flmGetDbTransType( pDb) == FLM_READ_TRANS) { // Free the KrefCntrl KrefCntrlFree( pDb); // Abort the read transaction if (RC_BAD( rc = flmAbortDbTrans( pDb))) { goto Exit; } bAbortedReadTrans = TRUE; // Try to start an update transaction if (RC_BAD( rc = flmBeginDbTrans( pDb, FLM_UPDATE_TRANS, FLM_NO_TIMEOUT, FLM_DONT_POISON_CACHE))) { goto Exit; } pIxChkInfo->pDbInfo->bStartedUpdateTrans = TRUE; } if (RC_BAD( pIxChkInfo->pDbInfo->LastStatusRc)) { rc = pIxChkInfo->pDbInfo->LastStatusRc; goto Exit; } Exit: // If something went wrong after the update transaction was started, // abort the transaction. if (RC_BAD( rc)) { if (pIxChkInfo->pDbInfo->bStartedUpdateTrans == TRUE) { (void) flmAbortDbTrans( pDb); pIxChkInfo->pDbInfo->bStartedUpdateTrans = FALSE; } } // Re-start the read transaction. if (bAbortedReadTrans == TRUE && pIxChkInfo->pDbInfo->bStartedUpdateTrans == FALSE) { rc2 = flmBeginDbTrans( pDb, FLM_READ_TRANS, 0, FLM_DONT_POISON_CACHE); } rc = (RCODE) ((rc != FERR_OK) ? (RCODE) rc : (RCODE) rc2); return (rc); } /**************************************************************************** Desc: ****************************************************************************/ FSTATIC RCODE chkEndUpdate( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo) { RCODE rc = FERR_OK; RCODE rc2 = FERR_OK; if (pIxChkInfo->pDbInfo->bStartedUpdateTrans == TRUE) { // Commit the update transaction that was started. If the // transaction started by the application, do not commit it. if (RC_BAD( rc = flmCommitDbTrans( pStateInfo->pDb, 0, FALSE))) { goto Exit; } pIxChkInfo->pDbInfo->bStartedUpdateTrans = FALSE; } Exit: // Re-start read transaction if (flmGetDbTransType( pStateInfo->pDb) == FLM_NO_TRANS) { rc2 = flmBeginDbTrans( pStateInfo->pDb, FLM_READ_TRANS, 0, FLM_DONT_POISON_CACHE); } rc = (RCODE) ((rc != FERR_OK) ? (RCODE) rc : (RCODE) rc2); return (rc); } /**************************************************************************** Desc: Initializes a result set for use by the logical check code ****************************************************************************/ RCODE chkRSInit( const char * pIoPath, IF_ResultSet ** ppRSet) { RCODE rc = FERR_OK; IF_ResultSet * pRSet = NULL; F_ChkResultSetCompare * pCompare = NULL; if( RC_BAD( rc = FlmAllocResultSet( &pRSet))) { goto Exit; } if( (pCompare = f_new F_ChkResultSetCompare) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } if( RC_BAD( rc = pRSet->setupResultSet( pIoPath, pCompare, 0))) { goto Exit; } *ppRSet = pRSet; pRSet = NULL; Exit: if( pRSet) { pRSet->Release(); } if( pCompare) { pCompare->Release(); } return (rc); } /**************************************************************************** Desc: Sorts a result set. ****************************************************************************/ RCODE chkRSFinalize( IX_CHK_INFO * pIxChkInfo, FLMUINT64 * pui64TotalEntries) { RCODE rc = FERR_OK; DB_CHECK_PROGRESS * pProgress = pIxChkInfo->pDbInfo->pProgress; DB_CHECK_PROGRESS saveInfo; F_ChkSortStatus chkSortStatus( pIxChkInfo); // Save the current check phase information. f_memcpy( &saveInfo, pProgress, sizeof(DB_CHECK_PROGRESS)); // Set information for the result set sort phase. pProgress->iCheckPhase = CHECK_RS_SORT; pProgress->bStartFlag = TRUE; pProgress->ui64NumRSUnits = 0; pProgress->ui64NumRSUnitsDone = 0; if (RC_BAD( rc = pIxChkInfo->pRSet->finalizeResultSet( &chkSortStatus, pui64TotalEntries))) { goto Exit; } Exit: f_memcpy( pProgress, &saveInfo, sizeof( DB_CHECK_PROGRESS)); pProgress->bStartFlag = TRUE; return (rc); } /**************************************************************************** Desc: ****************************************************************************/ RCODE chkBlkRead( DB_INFO * pDbInfo, FLMUINT uiBlkAddress, LFILE * pLFile, FLMBYTE ** ppBlk, SCACHE ** ppSCache, eCorruptionType * peCorruption) { RCODE rc = FERR_OK; FDB * pDb = pDbInfo->pDb; FILE_HDR * pFileHdr = &pDb->pFile->FileHdr; if (*ppSCache) { ScaReleaseCache( *ppSCache, FALSE); *ppSCache = NULL; *ppBlk = NULL; } else if (*ppBlk) { f_free( ppBlk); *ppBlk = NULL; } if (pDb->uiKilledTime) { rc = RC_SET( FERR_OLD_VIEW); goto Exit; } // Get the block from cache. if (RC_OK( rc = ScaGetBlock( pDb, pLFile, 0, uiBlkAddress, NULL, ppSCache))) { *ppBlk = (*ppSCache)->pucBlk; } else { // Try to read the block directly from disk. FLMUINT uiBlkLen = pFileHdr->uiBlockSize; FLMUINT uiTransID; FLMBYTE * pucBlk; FLMUINT uiLastReadTransID; FLMUINT uiPrevBlkAddr; FLMUINT uiFilePos; // If we didn't get a corruption error, jump to exit. if (!FlmErrorIsFileCorrupt( rc)) { goto Exit; } // Allocate memory for the block. if (RC_BAD( rc = f_calloc( uiBlkLen, ppBlk))) { goto Exit; } pucBlk = *ppBlk; uiFilePos = uiBlkAddress; uiTransID = pDb->LogHdr.uiCurrTransID; uiLastReadTransID = 0xFFFFFFFF; // Follow version chain until we find version we need. for (;;) { if (RC_BAD( rc = chkReadBlkFromDisk( pFileHdr, pDbInfo->pSFileHdl, uiFilePos, uiBlkAddress, pLFile, pDb->pFile, pucBlk))) { goto Exit; } // See if we can use the current version of the block, or if we // must go get a previous version. if ((FLMUINT) FB2UD( &pucBlk[BH_TRANS_ID]) <= uiTransID) { break; } // If the transaction ID is greater than or equal to the last // one we read, we have a corruption. This test will keep us from // looping around forever. if ((FLMUINT) FB2UD( &pucBlk[BH_TRANS_ID]) >= uiLastReadTransID) { rc = RC_SET( FERR_DATA_ERROR); goto Exit; } uiLastReadTransID = (FLMUINT) FB2UD( &pucBlk[BH_TRANS_ID]); // Block is too new, go for next older version. If previous // block address is same as current file position or zero, we // have a problem. uiPrevBlkAddr = (FLMUINT) FB2UD( &pucBlk[BH_PREV_BLK_ADDR]); if ((uiPrevBlkAddr == uiFilePos) || (!uiPrevBlkAddr)) { rc = (pDb->uiKilledTime) ? RC_SET( FERR_OLD_VIEW) : RC_SET( FERR_DATA_ERROR); goto Exit; } uiFilePos = uiPrevBlkAddr; } // See if we even got the block we thought we wanted. if (GET_BH_ADDR( pucBlk) != uiBlkAddress) { rc = RC_SET( FERR_DATA_ERROR); goto Exit; } } Exit: *peCorruption = FLM_NO_CORRUPTION; if (RC_BAD( rc)) { switch (rc) { case FERR_DATA_ERROR: *peCorruption = FLM_COULD_NOT_SYNC_BLK; break; case FERR_BLOCK_CHECKSUM: *peCorruption = FLM_BAD_BLK_CHECKSUM; break; default: break; } } return (rc); } /**************************************************************************** Desc: ****************************************************************************/ FSTATIC RCODE chkReadBlkFromDisk( FILE_HDR * pFileHdr, F_SuperFileHdl * pSFileHdl, FLMUINT uiFilePos, FLMUINT uiBlkAddress, LFILE * pLFile, FFILE * pFile, FLMBYTE * pBlk) { RCODE rc = FERR_OK; FLMUINT uiBytesRead; FLMUINT uiBlkLen = pFileHdr->uiBlockSize; if (RC_BAD( rc = pSFileHdl->readBlock( uiFilePos, uiBlkLen, pBlk, &uiBytesRead))) { if (rc == FERR_IO_END_OF_FILE) { rc = RC_SET( FERR_DATA_ERROR); } goto Exit; } if (uiBytesRead < uiBlkLen) { rc = RC_SET( FERR_DATA_ERROR); goto Exit; } // Verify the block checksum BEFORE decrypting or using any data. if (RC_BAD( rc = BlkCheckSum( pBlk, CHECKSUM_CHECK, uiBlkAddress, uiBlkLen))) { goto Exit; } // If this is an index block it may be encrypted, we need to decrypt // it before we can use it. The function ScaDecryptBlock will check if // the index is encrypted first. If not, it will return. if (pLFile && pLFile->uiLfType == LF_INDEX) { if (RC_BAD( rc = ScaDecryptBlock( pFile, pBlk))) { goto Exit; } } Exit: return (rc); } /**************************************************************************** Desc: ****************************************************************************/ FSTATIC RCODE chkVerifyElmFields( STATE_INFO * pStateInfo, DB_INFO * pDbInfo, IX_CHK_INFO * pIxChkInfo, F_Pool * pTmpPool, FLMUINT * puiErrElmRecOffsetRV, eCorruptionType * peElmCorruptCode) { FLMBYTE * pValue = pStateInfo->pValue; FLMBYTE * pData = pStateInfo->pData; FLMBYTE * pTempValue; FlmRecord * pRecord = pStateInfo->pRecord; FLMBOOL bKRefAbortRec = FALSE; FLMUINT uiSaveElmRecOffset = 0; void * pDbPoolMark = NULL; void * pKeyMark = NULL; FLMBOOL bResetDbPool = FALSE; RCODE rc = FERR_OK; void * pvField = pStateInfo->pvField; REC_KEY * pKeyList = NULL; REC_KEY * pTmpKey; *peElmCorruptCode = FLM_NO_CORRUPTION; pTempValue = pValue ? (FLMBYTE *) &pValue[pStateInfo->uiFieldProcessedLen] : NULL; while( (*peElmCorruptCode == FLM_NO_CORRUPTION) && (pStateInfo->uiElmRecOffset < pStateInfo->uiElmRecLen)) { uiSaveElmRecOffset = pStateInfo->uiElmRecOffset; if ((*peElmCorruptCode = flmVerifyElmFOP( pStateInfo)) != FLM_NO_CORRUPTION) { break; } if (!pStateInfo->bElmRecOK) { pValue = pTempValue = NULL; if (pRecord) { pRecord->clear(); } continue; } switch (pStateInfo->uiFOPType) { case FLM_FOP_CONT_DATA: { if ((pTempValue != NULL) && (pStateInfo->uiFOPDataLen)) { f_memcpy( pTempValue, pStateInfo->pFOPData, pStateInfo->uiFOPDataLen); pTempValue += pStateInfo->uiFOPDataLen; } break; } case FLM_FOP_STANDARD: case FLM_FOP_OPEN: case FLM_FOP_TAGGED: case FLM_FOP_NO_VALUE: case FLM_FOP_LARGE: { if (pValue) { pValue = pTempValue = NULL; } if (pvField) { pvField = NULL; } if (!pRecord) { if ((pRecord = f_new FlmRecord) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } } if (RC_BAD( rc = pRecord->insertLast( pStateInfo->uiFieldLevel, pStateInfo->uiFieldNum, pStateInfo->uiFieldType, &pvField))) { goto Exit; } if (pStateInfo->uiFieldLen) { if (RC_BAD( rc = pRecord->allocStorageSpace( pvField, pStateInfo->uiFieldType, pStateInfo->uiFieldLen, 0, 0, 0, &pValue, NULL))) { goto Exit; } pTempValue = pValue; } if ((pTempValue) && (pStateInfo->uiFOPDataLen)) { f_memcpy( pTempValue, pStateInfo->pFOPData, pStateInfo->uiFOPDataLen); pTempValue += pStateInfo->uiFOPDataLen; } break; } case FLM_FOP_ENCRYPTED: { if (pValue) { pValue = pTempValue = NULL; } if (pData) { pData = NULL; } if (pvField) { pvField = NULL; } if (!pRecord) { if ((pRecord = f_new FlmRecord) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } } if (RC_BAD( rc = pRecord->insertLast( pStateInfo->uiFieldLevel, pStateInfo->uiFieldNum, pStateInfo->uiFieldType, &pvField ))) { goto Exit; } if (pStateInfo->uiFieldLen) { if (RC_BAD( rc = pRecord->allocStorageSpace( pvField, pStateInfo->uiFieldType, pStateInfo->uiFieldLen, pStateInfo->uiEncFieldLen, pStateInfo->uiEncId, FLD_HAVE_ENCRYPTED_DATA, &pData, &pValue))) { goto Exit; } pTempValue = pValue; } if ((pTempValue) && (pStateInfo->uiFOPDataLen)) { f_memcpy( pTempValue, pStateInfo->pFOPData, pStateInfo->uiFOPDataLen); pTempValue += pStateInfo->uiFOPDataLen; } break; } case FLM_FOP_JUMP_LEVEL: { break; } default: { *peElmCorruptCode = FLM_BAD_FOP; } } if ((!pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiFieldLen) || (pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiEncFieldLen)) { // The whole field has been retrieved. Verify the field and graft // it into the record being built. if (pValue && (pDbInfo->uiFlags & FLM_CHK_FIELDS)) { if (pStateInfo->uiFieldType == 0xFF) { // Hit Rec Info object - don't care what's in it - must // not assert, because this would kill our ability to check // older versions of the database which have REC_INFO data // in them. *peElmCorruptCode = FLM_NO_CORRUPTION; } else { if (!pStateInfo->uiEncId) { *peElmCorruptCode = flmVerifyField( pStateInfo, pValue, pStateInfo->uiFieldLen, pStateInfo->uiFieldType); } else { // Decrypt the field and store the decrypted data. if (!pStateInfo->pDb->pFile->bInLimitedMode) { if (RC_BAD( rc = flmDecryptField( pStateInfo->pDb->pDict, pRecord, pvField, pStateInfo->uiEncId, pTmpPool))) { goto Exit; } *peElmCorruptCode = flmVerifyField( pStateInfo, pData, pStateInfo->uiFieldLen, pStateInfo->uiFieldType); } else { // If we can't decrypt the field, then just pass it // for now. *peElmCorruptCode = FLM_NO_CORRUPTION; } } } } else { *peElmCorruptCode = FLM_NO_CORRUPTION; } pValue = pTempValue = NULL; } // If this is the last element of the record, verify the record's // keys. if (BBE_IS_LAST( pStateInfo->pElm) && (pStateInfo->uiElmRecOffset == pStateInfo->uiElmRecLen)) { pValue = pTempValue = NULL; if (!pDbInfo->pProgress->bPhysicalCorrupt && (pDbInfo->uiFlags & FLM_CHK_INDEX_REFERENCING)) { FLMUINT uiLoop; IXD * pIxd; if (pStateInfo->pLogicalFile->pLFile->uiLfType == LF_CONTAINER) { // Need to mark the DB's temporary pool. The index code // allocates memory for new CDL entries from the DB pool. // If the pool is not reset, it grows during the check and // becomes VERY large. pDbPoolMark = pDbInfo->pDb->TempPool.poolMark(); bResetDbPool = TRUE; // Set up the KRef table so that flmGetRecKeys will work // correctly. if (RC_BAD( rc = KrefCntrlCheck( pStateInfo->pDb))) { goto Exit; } bKRefAbortRec = TRUE; for (uiLoop = 0; uiLoop < pIxChkInfo->uiIxCount; uiLoop++) { if (RC_BAD( rc = fdictGetIndex( pStateInfo->pDb->pDict, pStateInfo->pDb->pFile->bInLimitedMode, pIxChkInfo->puiIxArray[uiLoop], NULL, &pIxd, TRUE))) { goto Exit; } if (pIxd->uiFlags & IXD_OFFLINE) { continue; } if (pIxd->uiContainerNum == pStateInfo->pLogicalFile->pLFile->uiLfNum || !pIxd->uiContainerNum) { // Mark the field pool so that it can be reset after // the record keys have been generated and output. pKeyMark = pTmpPool->poolMark(); // Build the record keys for the current index. Do // not remove duplicate keys. The result set will // remove any duplicates. if (RC_BAD( rc = flmGetRecKeys( pStateInfo->pDb, pIxd, pRecord, pStateInfo->pLogicalFile->pLFile->uiLfNum, FALSE, pTmpPool, &pKeyList))) { goto Exit; } // If the record generated keys for the current // index, output the keys to the result set. if (pKeyList) { if (RC_BAD( rc = chkOutputIndexKeys( pStateInfo, pIxChkInfo, pIxd, pKeyList))) { goto Exit; } pTmpKey = pKeyList; while (pTmpKey) { pTmpKey->pKey->Release(); pTmpKey->pKey = NULL; pTmpKey = pTmpKey->pNextKey; } pKeyList = NULL; } // Reset the field pool pTmpPool->poolReset( pKeyMark); } } // Clean up any keys that may have been added to the KRef // table. KYAbortCurrentRecord( pStateInfo->pDb); bKRefAbortRec = FALSE; // Reset the DB's temporary pool pDbInfo->pDb->TempPool.poolReset( pDbPoolMark); bResetDbPool = FALSE; } } if (pRecord) { pRecord->clear(); } pValue = pTempValue = NULL; pTmpPool->poolReset( NULL); } if (*peElmCorruptCode != FLM_NO_CORRUPTION) { pStateInfo->bElmRecOK = FALSE; } } Exit: // Clean up any keys that may have been added to the KRef table. This // is a fail-safe case to clean up the KRef in case KYKeysCommit didn't // get called above. if (bKRefAbortRec) { KYAbortCurrentRecord( pStateInfo->pDb); } // Free any keys in the key list if (pKeyList) { pTmpKey = pKeyList; while (pTmpKey) { pTmpKey->pKey->Release(); pTmpKey->pKey = NULL; pTmpKey = pTmpKey->pNextKey; } } // Reset the DB's temporary pool if (bResetDbPool) { pDbInfo->pDb->TempPool.poolReset( pDbPoolMark); } if (*peElmCorruptCode != FLM_NO_CORRUPTION || RC_BAD( rc)) { pValue = pTempValue = NULL; pTmpPool->poolReset( NULL); if (pRecord) { pRecord->clear(); } } pStateInfo->pValue = pValue; pStateInfo->pData = pData; pStateInfo->pvField = pvField; pStateInfo->pRecord = pRecord; if (*peElmCorruptCode != FLM_NO_CORRUPTION) { *puiErrElmRecOffsetRV = uiSaveElmRecOffset; } return (rc); } /**************************************************************************** Desc: This routine checks all of the blocks/links in a sub-tree of a B-TREE. It calls itself recursively whenever it descends a level in the tree. ****************************************************************************/ FSTATIC RCODE chkVerifySubTree( DB_INFO * pDbInfo, IX_CHK_INFO * pIxChkInfo, STATE_INFO * pParentState, STATE_INFO * pStateInfo, FLMUINT uiBlkAddress, F_Pool * pTmpPool, FLMBYTE * pucResetKey, FLMUINT uiResetKeyLen, FLMUINT uiResetDrn) { RCODE rc = FERR_OK; SCACHE * pSCache = NULL; FLMBYTE * pBlk = NULL; FLMUINT uiLevel = pStateInfo->uiLevel; FLMUINT uiBlkType = pStateInfo->uiBlkType; FLMUINT uiLfType = pStateInfo->pLogicalFile->pLFile->uiLfType; FLMUINT uiBlockSize = pDbInfo->pDb->pFile->FileHdr.uiBlockSize; FLMUINT uiParentBlkAddress; FLMBYTE * pChildBlkAddr; FLMUINT uiChildBlkAddress; FLMUINT uiPrevNextBlkAddress; eCorruptionType eElmCorruptCode; eCorruptionType eBlkCorruptionCode = FLM_NO_CORRUPTION; eCorruptionType eLastCorruptCode = FLM_NO_CORRUPTION; FLMUINT uiNumErrors = 0; FLMUINT uiErrElmRecOffset = 0; FLMUINT64 ui64SaveKeyCount = 0; FLMUINT64 ui64SaveKeyRefs = 0; BLOCK_INFO SaveBlkInfo; BLOCK_INFO * pBlkInfo; FLMBOOL bProcessElm; FLMBOOL bCountElm; FLMBOOL bDescendToChildBlocks; FLMINT iCompareStatus; eCorruptionType eHdrCorruptCode; // Setup the state information. pStateInfo->pBlk = NULL; pStateInfo->uiBlkAddress = uiBlkAddress; uiPrevNextBlkAddress = pStateInfo->uiNextBlkAddr; uiParentBlkAddress = (pParentState) ? pParentState->uiBlkAddress : BT_END; f_yieldCPU(); // Read the sub-tree root block into memory. bDescendToChildBlocks = TRUE; if (RC_BAD( rc = chkBlkRead( pDbInfo, uiBlkAddress, pStateInfo->pLogicalFile ? pStateInfo->pLogicalFile->pLFile : NULL, &pBlk, &pSCache, &eBlkCorruptionCode))) { if (eBlkCorruptionCode != FLM_NO_CORRUPTION) { uiNumErrors++; eLastCorruptCode = eBlkCorruptionCode; chkReportError( pDbInfo, eBlkCorruptionCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, 0, 0, 0xFFFF, 0, pBlk); if (eBlkCorruptionCode == FLM_BAD_BLK_CHECKSUM) { bDescendToChildBlocks = FALSE; // Allow to continue the check, but if this is a non-leaf // block a non-zero eBlkCorruptionCode will prevent us from // descending to child blocks. Set rc to SUCCESS so we won't // goto Exit below. rc = FERR_OK; } else if (eBlkCorruptionCode == FLM_COULD_NOT_SYNC_BLK) { eLastCorruptCode = eBlkCorruptionCode; // Need the goto here, because rc is changed to SUCCESS, and // the goto below would get skipped. rc = FERR_OK; goto fix_state; } } else if (rc == FERR_OLD_VIEW) { pDbInfo->bReposition = TRUE; } // If rc was not changed to SUCCESS above, goto Exit. if (RC_BAD( rc)) { goto Exit; } } pStateInfo->pBlk = pBlk; // Verify the block header; Don't re-count the block if we are resetting. if (!uiResetKeyLen) { pDbInfo->pProgress->ui64BytesExamined += (FLMUINT64) uiBlockSize; pBlkInfo = &pStateInfo->BlkInfo; } else { pBlkInfo = NULL; } chkCallProgFunc( pDbInfo); // Check the block header. if (( eHdrCorruptCode = flmVerifyBlockHeader( pStateInfo, pBlkInfo, uiBlockSize, (pParentState == NULL) ? BT_END : 0, (pParentState == NULL) ? BT_END : pParentState->uiLastChildAddr, TRUE, TRUE)) == FLM_NO_CORRUPTION) { // Verify the previous block's next block address -- it should equal // the current block's address. if ((uiPrevNextBlkAddress) && (uiPrevNextBlkAddress != uiBlkAddress)) { eHdrCorruptCode = FLM_BAD_PREV_BLK_NEXT; } } if (eHdrCorruptCode != FLM_NO_CORRUPTION) { eLastCorruptCode = eHdrCorruptCode; uiNumErrors++; chkReportError( pDbInfo, eHdrCorruptCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, 0, 0, 0xFFFF, 0, pBlk); } // Go through the elements in the block. pStateInfo->uiElmOffset = BH_OVHD; while ((pStateInfo->uiElmOffset < pStateInfo->uiEndOfBlock) && (RC_OK( pDbInfo->LastStatusRc))) { // If we are resetting, save any statistical information so we can // back it out if we need to. if (uiResetKeyLen) { ui64SaveKeyCount = pStateInfo->ui64KeyCount; ui64SaveKeyRefs = pStateInfo->ui64KeyRefs; f_memcpy( &SaveBlkInfo, &pStateInfo->BlkInfo, sizeof(BLOCK_INFO)); bCountElm = FALSE; bProcessElm = FALSE; } else { bCountElm = TRUE; bProcessElm = TRUE; } pStateInfo->BlkInfo.ui64ElementCount++; if ((eElmCorruptCode = flmVerifyElement( pStateInfo, pDbInfo->uiFlags)) != FLM_NO_CORRUPTION) { // Report any errors in the element. eLastCorruptCode = eElmCorruptCode; uiNumErrors++; if (RC_BAD( rc = chkReportError( pDbInfo, eElmCorruptCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0, pBlk))) { break; } } // Keep track of the number of continuation elements. if ((uiBlkType == BHT_LEAF) && (!BBE_IS_FIRST( pStateInfo->pElm)) && (pStateInfo->uiElmLen != BBE_LEM_LEN)) { pStateInfo->BlkInfo.ui64ContElementCount++; pStateInfo->BlkInfo.ui64ContElmBytes += pStateInfo->uiElmLen; } // Do some further checking. if (eElmCorruptCode != FLM_NO_CORRUPTION) { pStateInfo->bElmRecOK = FALSE; } else { // See if we are resetting iCompareStatus = 0; if (uiResetKeyLen && pStateInfo->bValidKey && ((!pStateInfo->uiCurKeyLen) || ((iCompareStatus = flmCompareKeys( pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, pucResetKey, uiResetKeyLen)) >= 0))) { if (iCompareStatus > 0) { if (uiBlkType == BHT_LEAF) { uiResetKeyLen = 0; pucResetKey = NULL; uiResetDrn = 0; bCountElm = TRUE; } bProcessElm = TRUE; } else if (uiLfType == LF_INDEX) { FLMBYTE * pTmpElm = pStateInfo->pElm; FLMUINT uiLowestDrn = FSGetDomain( &pTmpElm, pStateInfo->uiElmOvhd); if (uiResetDrn >= uiLowestDrn) { bProcessElm = TRUE; bCountElm = TRUE; } } else { // Processing a container bProcessElm = TRUE; } } if (uiBlkType == BHT_LEAF) { // No need to parse LEM element. if ((pStateInfo->uiCurKeyLen != 0) && (pStateInfo->bValidKey)) { if (uiLfType == LF_CONTAINER) { if (pStateInfo->uiElmDrn != DRN_LAST_MARKER) { if (RC_BAD( rc = chkVerifyElmFields( pStateInfo, pDbInfo, pIxChkInfo, pTmpPool, &uiErrElmRecOffset, &eElmCorruptCode))) { goto Exit; } } } else if (bProcessElm) { uiErrElmRecOffset = 0xFFFF; if (!pDbInfo->pProgress->bPhysicalCorrupt && (pDbInfo->uiFlags & FLM_CHK_INDEX_REFERENCING)) { if ((RC_BAD( rc = flmVerifyIXRefs( pStateInfo, pIxChkInfo, uiResetDrn, &eElmCorruptCode))) || pDbInfo->bReposition) { goto Exit; } } else { if ((RC_BAD( rc = flmVerifyIXRefs( pStateInfo, NULL, uiResetDrn, &eElmCorruptCode))) || pDbInfo->bReposition) { goto Exit; } } } } if (bProcessElm) { uiResetKeyLen = 0; pucResetKey = NULL; uiResetDrn = 0; if (eElmCorruptCode != FLM_NO_CORRUPTION) { // Report any errors in the element. eLastCorruptCode = eElmCorruptCode; uiNumErrors++; chkReportError( pDbInfo, eElmCorruptCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, uiErrElmRecOffset, pStateInfo->uiFieldNum, pBlk); if (RC_BAD( pDbInfo->LastStatusRc)) { break; } } } } else { if (uiBlkType == BHT_NON_LEAF_DATA) { pChildBlkAddr = &pStateInfo->pElm[BNE_DATA_CHILD_BLOCK]; } else { pChildBlkAddr = &pStateInfo->pElm[BNE_CHILD_BLOCK]; } uiChildBlkAddress = (FLMUINT) FB2UD( pChildBlkAddr); // Check the child sub-tree -- NOTE, this is a recursive call. // First see if we have a pucResetKey that we want to position // to. If so, make sure we are positioned to it before // descending to the child block. if (bProcessElm) { if (!bDescendToChildBlocks) { rc = FERR_OK; } else { rc = chkVerifySubTree( pDbInfo, pIxChkInfo, pStateInfo, (pStateInfo - 1), uiChildBlkAddress, pTmpPool, pucResetKey, uiResetKeyLen, uiResetDrn); } if ((RC_BAD( rc)) || (RC_BAD( pDbInfo->LastStatusRc)) || (pDbInfo->bReposition)) { goto Exit; } // Once we reach the key, set it to an empty to key so that // we will always descend to the child block after this // point. uiResetKeyLen = 0; pucResetKey = NULL; uiResetDrn = 0; } // Save the child block address in the level information. pStateInfo->uiLastChildAddr = uiChildBlkAddress; } } // If we were resetting on this element, restore the statistics to // what they were before. if (!bCountElm) { pStateInfo->ui64KeyCount = ui64SaveKeyCount; pStateInfo->ui64KeyRefs = ui64SaveKeyRefs; f_memcpy( &pStateInfo->BlkInfo, &SaveBlkInfo, sizeof(BLOCK_INFO)); } // Go to the next element. pStateInfo->uiElmOffset += pStateInfo->uiElmLen; } // Verify that we ended exactly on the end of the block. if ((eLastCorruptCode == FLM_NO_CORRUPTION) && (pStateInfo->uiEndOfBlock >= BH_OVHD) && (pStateInfo->uiEndOfBlock <= uiBlockSize) && (pStateInfo->uiElmOffset > pStateInfo->uiEndOfBlock)) { eLastCorruptCode = FLM_BAD_ELM_END; uiNumErrors++; chkReportError( pDbInfo, eLastCorruptCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, pStateInfo->uiElmOffset, 0, 0xFFFF, 0, pBlk); } // Verify that the last key in the block matches the parent's key. if ((eLastCorruptCode == FLM_NO_CORRUPTION) && (pParentState) && (RC_OK( pDbInfo->LastStatusRc))) { if (pStateInfo->bValidKey && pParentState->bValidKey && (flmCompareKeys( pStateInfo->pCurKey, pStateInfo->uiCurKeyLen, pParentState->pCurKey, pParentState->uiCurKeyLen) != 0)) { eLastCorruptCode = FLM_BAD_PARENT_KEY; uiNumErrors++; chkReportError( pDbInfo, eLastCorruptCode, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiLevel, uiBlkAddress, uiParentBlkAddress, 0, 0, 0xFFFF, 0, pBlk); } } fix_state: // If the block could not be verified, set the level's next block // address and last child address to zero to indicate that we really // aren't sure we're at the right place in this level in the B-TREE. if (eLastCorruptCode != FLM_NO_CORRUPTION) { pStateInfo->BlkInfo.eCorruption = eLastCorruptCode; pStateInfo->BlkInfo.uiNumErrors += uiNumErrors; // Reset all child block states. for (;;) { pStateInfo->uiNextBlkAddr = 0; pStateInfo->uiLastChildAddr = 0; pStateInfo->bValidKey = FALSE; pStateInfo->uiElmLastFlag = 0xFF; // Quit when the leaf level has been reached. if (pStateInfo->uiLevel == 0) { break; } pStateInfo--; } } Exit: if (pSCache) { ScaReleaseCache( pSCache, FALSE); } else if (pBlk) { f_free( &pBlk); } pStateInfo->pBlk = NULL; return (rc); } /**************************************************************************** Desc: This routine reads the LFH areas from disk to make sure they are up to date in memory. ****************************************************************************/ FSTATIC RCODE chkGetLfInfo( DB_INFO * pDbInfo, F_Pool * pPool, LF_STATS * pLfStats, LFILE * pLFile, LF_STATS * pCurrLfStats, FLMBOOL * pbCurrLfLevelChangedRV) { RCODE rc = FERR_OK; SCACHE * pSCache = NULL; FLMBYTE * pBlk = NULL; FLMUINT uiSaveLevel; eCorruptionType eBlkCorruptionCode; // Read in the block containing the logical file header. if (RC_BAD( rc = chkBlkRead( pDbInfo, pLFile->uiBlkAddress, pLFile, &pBlk, &pSCache, &eBlkCorruptionCode))) { // Log the error. if (eBlkCorruptionCode != FLM_NO_CORRUPTION) { chkReportError( pDbInfo, eBlkCorruptionCode, LOCALE_LFH_LIST, 0, 0, 0xFF, pLFile->uiBlkAddress, 0, 0, 0, 0xFFFF, 0, pBlk); } goto Exit; } // Copy the LFH from the block to the LFILE. uiSaveLevel = pLfStats->uiNumLevels; if (RC_BAD( rc = flmBufferToLFile( &pBlk[pLFile->uiOffsetInBlk], pLFile, pLFile->uiBlkAddress, pLFile->uiOffsetInBlk))) { goto Exit; } // Read root block to get the number of levels in the B-TREE if (pLFile->uiRootBlk == BT_END) { pLfStats->uiNumLevels = 0; } else { if (RC_BAD( rc = chkBlkRead( pDbInfo, pLFile->uiRootBlk, pLFile, &pBlk, &pSCache, &eBlkCorruptionCode))) { if (eBlkCorruptionCode != FLM_NO_CORRUPTION) { chkReportError( pDbInfo, eBlkCorruptionCode, LOCALE_B_TREE, pLFile->uiLfNum, pLFile->uiLfType, 0xFF, pLFile->uiRootBlk, 0, 0, 0, 0xFFFF, 0, pBlk); } goto Exit; } pLfStats->uiNumLevels = (FLMUINT) (pBlk[BH_LEVEL]) + 1; // Make sure that the level extracted from the block is valid. if (pBlk[BH_LEVEL] >= BH_MAX_LEVELS) { chkReportError( pDbInfo, FLM_BAD_BLK_HDR_LEVEL, LOCALE_B_TREE, pLFile->uiLfNum, pLFile->uiLfType, (FLMUINT) (pBlk[BH_LEVEL]), pLFile->uiRootBlk, 0, 0, 0, 0xFFFF, 0, pBlk); // Force pLfStats->uiNumLevels to 1 so that we don't crash pLfStats->uiNumLevels = 1; } } // If the number of levels changed, reset the level information on this // logical file. if (uiSaveLevel != pLfStats->uiNumLevels && pLfStats->uiNumLevels) { if (pLfStats->uiNumLevels > uiSaveLevel) { if( RC_BAD( rc = pPool->poolCalloc( (FLMUINT) (sizeof(LEVEL_INFO) * pLfStats->uiNumLevels), (void **)&pLfStats->pLevelInfo))) { rc = RC_SET( FERR_MEM); goto Exit; } } if (pCurrLfStats == pLfStats) { *pbCurrLfLevelChangedRV = TRUE; } } Exit: if (pSCache) { ScaReleaseCache( pSCache, FALSE); } else if (pBlk) { f_free( &pBlk); } return (rc); } /**************************************************************************** Desc: This routine allocates and initializes the LF table (array of LF_HDR structures). ****************************************************************************/ FSTATIC RCODE chkSetupLfTable( DB_INFO * pDbInfo, F_Pool * pPool) { RCODE rc = FERR_OK; FLMUINT uiCnt; FLMUINT uiNumIndexes = 0; FLMUINT uiIxStart; FLMUINT uiNumDataCont = 0; FLMUINT uiNumDictCont = 0; FLMUINT uiIxOffset; FLMUINT uiDataOffset; FLMUINT uiDictOffset; FDB * pDb = pDbInfo->pDb; LFILE * pLFile; LFILE * pTmpLFile; LF_HDR * pLogicalFile; LF_STATS * pLfStats; // Set up the table such that the dictionary is checked first, followed // by data containers, and then indexes. This is necessary for the // logical (index) check to work. The data records must be extracted // before the indexes are checked so that the temporary result set, // used during the logical check, can be built. pDbInfo->pProgress->uiNumFields = 0; pDbInfo->pProgress->uiNumIndexes = 0; pDbInfo->pProgress->uiNumContainers = 0; pDbInfo->pProgress->uiNumLogicalFiles = (FLMUINT) ((pDb->pDict) ? (FLMUINT) pDb->pDict->uiLFileCnt : (FLMUINT) 0); // Determine the number of fields. if (pDb->pDict) { chkCountFields( pDb->pDict, &pDbInfo->pProgress->uiNumFields); for (uiCnt = 0, pLFile = (LFILE *) pDb->pDict->pLFileTbl; uiCnt < pDb->pDict->uiLFileCnt; uiCnt++, pLFile++) { if (pLFile->uiLfType == LF_INDEX) { pDbInfo->pProgress->uiNumIndexes++; uiNumIndexes++; } else { pDbInfo->pProgress->uiNumContainers++; if (pLFile->uiLfNum == FLM_DICT_CONTAINER) { uiNumDictCont++; } else { uiNumDataCont++; } } } } // Allocate memory for each LFILE, then set up each LFILE. if (!pDbInfo->pProgress->uiNumLogicalFiles) { pDbInfo->pLogicalFiles = NULL; pDbInfo->pProgress->pLfStats = NULL; } else { if( RC_BAD( rc = pPool->poolCalloc( (FLMUINT)(sizeof(LF_HDR) * pDbInfo->pProgress->uiNumLogicalFiles), (void **)&pDbInfo->pLogicalFiles))) { rc = RC_SET( FERR_MEM); goto Exit; } if( RC_BAD( rc = pPool->poolCalloc( (FLMUINT)(sizeof(LF_STATS) * pDbInfo->pProgress->uiNumLogicalFiles), (void **)&pDbInfo->pProgress->pLfStats))) { rc = RC_SET( FERR_MEM); goto Exit; } uiDictOffset = 0; uiDataOffset = uiNumDictCont; uiIxOffset = uiDataOffset + uiNumDataCont; uiIxStart = uiIxOffset; for (uiCnt = 0, pTmpLFile = (LFILE *) pDb->pDict->pLFileTbl; uiCnt < pDbInfo->pProgress->uiNumLogicalFiles; uiCnt++, pTmpLFile++) { if (pTmpLFile->uiLfType == LF_INDEX) { FLMUINT uiTmpIxOffset = uiIxOffset; // Indexes need to be in order from lowest to highest because // the result set is sorted that way. while (uiTmpIxOffset > uiIxStart) { if (pDbInfo->pLogicalFiles[uiTmpIxOffset - 1].pLFile->uiLfNum < pTmpLFile->uiLfNum) { break; } f_memcpy( &pDbInfo->pLogicalFiles[uiTmpIxOffset], &pDbInfo->pLogicalFiles[uiTmpIxOffset - 1], sizeof(LF_HDR)); f_memcpy( &pDbInfo->pProgress->pLfStats[uiTmpIxOffset], &pDbInfo->pProgress->pLfStats[uiTmpIxOffset - 1], sizeof(LF_STATS)); pDbInfo->pLogicalFiles [uiTmpIxOffset].pLfStats = &pDbInfo->pProgress->pLfStats [uiTmpIxOffset]; uiTmpIxOffset--; } pLogicalFile = &(pDbInfo->pLogicalFiles[uiTmpIxOffset]); pLfStats = &(pDbInfo->pProgress->pLfStats[uiTmpIxOffset]); uiIxOffset++; } else { if (pTmpLFile->uiLfNum == FLM_DICT_CONTAINER) { pLogicalFile = &(pDbInfo->pLogicalFiles[uiDictOffset]); pLfStats = &(pDbInfo->pProgress->pLfStats[uiDictOffset]); uiDictOffset++; } else { pLogicalFile = &(pDbInfo->pLogicalFiles[uiDataOffset]); pLfStats = &(pDbInfo->pProgress->pLfStats[uiDataOffset]); uiDataOffset++; } } pLogicalFile->pLfStats = pLfStats; // Copy the LFILE information - so we can return the information // even after the database has been closed. if( RC_BAD( rc = pPool->poolAlloc( sizeof( LFILE), (void **)&pLFile))) { goto Exit; } pLogicalFile->pLFile = pLFile; // Copy the LFILE structure so we can get enough information to // read them from disk, then read them from disk so we have a // read-consistent view of them. f_memcpy( pLFile, pTmpLFile, sizeof(LFILE)); if (RC_BAD( rc = flmLFileRead( pDb, pLFile))) { goto Exit; } pLfStats->uiLfType = pLFile->uiLfType; if (pLFile->uiLfType == LF_INDEX) { pLfStats->uiIndexNum = pLFile->uiLfNum; pLfStats->uiContainerNum = 0; } else { pLfStats->uiIndexNum = 0; pLfStats->uiContainerNum = pLFile->uiLfNum; } // If the logical file is an index, get pointers to the index // definition and its field definitions. if (pLFile->uiLfType == LF_INDEX) { IXD * pTmpIxd; IFD * pTmpIfd; if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, pLFile->uiLfNum, NULL, &pTmpIxd, TRUE))) { if (rc == FERR_BAD_IX) { chkReportError( pDbInfo, FLM_BAD_PCODE_IXD_TBL, LOCALE_IXD_TBL, pLFile->uiLfNum, pLFile->uiLfType, 0xFF, 0, 0, 0, 0, 0xFFFF, 0, NULL); rc = RC_SET( FERR_PCODE_ERROR); } goto Exit; } pTmpIfd = pTmpIxd->pFirstIfd; // Copy the IXD and IFD information - so we can return the // information even after the database has been closed. if( RC_BAD( rc = pPool->poolAlloc( (FLMUINT)(sizeof(IXD) + sizeof(IFD) * pTmpIxd->uiNumFlds), (void **)&pLogicalFile->pIxd))) { goto Exit; } pLogicalFile->pIfd = (IFD *)((void *)(&pLogicalFile->pIxd[1])); f_memcpy( pLogicalFile->pIxd, pTmpIxd, sizeof(IXD)); f_memcpy( pLogicalFile->pIfd, pTmpIfd, sizeof(IFD) * pTmpIxd->uiNumFlds); pLfStats->uiContainerNum = pLogicalFile->pIxd->uiContainerNum; } // Get the current number of levels in the logical file and // allocate an array of LEVEL_INFO structures for the levels. pLfStats->uiNumLevels = 0; if (RC_BAD( rc = chkGetLfInfo( pDbInfo, pPool, pLfStats, pLFile, NULL, NULL))) { goto Exit; } } } Exit: return (rc); } /**************************************************************************** Desc: This routine checks all of the B-TREES in the database -- all indexes and containers. ****************************************************************************/ RCODE chkVerifyBTrees( DB_INFO * pDbInfo, F_Pool * pPool, FLMBOOL * pbStartOverRV) { RCODE rc = FERR_OK; FDB * pDb = pDbInfo->pDb; FLMUINT uiCurrLf; FLMUINT uiCurrLevel; FLMBYTE * pKeyBuffer = NULL; FLMUINT uiKeysAllocated = 0; STATE_INFO State[BH_MAX_LEVELS]; FLMBOOL bStateInitialized[BH_MAX_LEVELS]; FLMBYTE ucResetKeyBuff[MAX_KEY_SIZ]; FLMBYTE * pucResetKey = NULL; FLMUINT uiResetKeyLen = 0; FLMUINT uiResetDrn = 0; LF_HDR * pLogicalFile; LF_STATS * pLfStats; LFILE * pLFile; FLMUINT uiSaveDictSeq; FLMUINT uiTmpLf; LF_STATS * pTmpLfStats; F_Pool tmpPool; FLMBOOL bRSFinalized = FALSE; DB_CHECK_PROGRESS * pProgress = pDbInfo->pProgress; IX_CHK_INFO IxChkInfo; IX_CHK_INFO * pIxChkInfo = NULL; void * pvPoolMark; FILE_HDR * pFileHdr = &pDb->pFile->FileHdr; for (uiCurrLevel = 0; uiCurrLevel < BH_MAX_LEVELS; uiCurrLevel++) { bStateInitialized[uiCurrLevel] = FALSE; } if (*pbStartOverRV) { goto Exit; } pvPoolMark = pPool->poolMark(); uiSaveDictSeq = pDb->pDict->uiDictSeq; if (RC_BAD( rc = chkSetupLfTable( pDbInfo, pPool))) { goto Exit; } if (pDbInfo->uiFlags & FLM_CHK_INDEX_REFERENCING) { if (RC_BAD( rc = chkSetupIxInfo( pDbInfo, &IxChkInfo))) { goto Exit; } pIxChkInfo = &IxChkInfo; } // Loop through all of the logical files in the database and perform a // structural and logical check. uiCurrLf = 0; while (uiCurrLf < pDbInfo->pProgress->uiNumLogicalFiles) { pProgress->uiCurrLF = uiCurrLf + 1; pLogicalFile = &pDbInfo->pLogicalFiles[uiCurrLf]; pLfStats = &pDbInfo->pProgress->pLfStats[uiCurrLf]; pLFile = pLogicalFile->pLFile; if (pLFile->uiRootBlk == BT_END) { rc = FERR_OK; uiCurrLf++; uiResetKeyLen = 0; pucResetKey = NULL; uiResetDrn = 0; continue; } // Allocate space to hold the keys, if not already allocated. if (uiKeysAllocated < pLfStats->uiNumLevels) { // If there is already a key allocated, deallocate it if (pKeyBuffer) { f_free( &pKeyBuffer); uiKeysAllocated = 0; } if (RC_BAD( rc = f_alloc( pLfStats->uiNumLevels * MAX_KEY_SIZ, &pKeyBuffer))) { goto Exit; } uiKeysAllocated = pLfStats->uiNumLevels; } // Setup PROGRESS_CHECK_INFO structure pProgress->iCheckPhase = CHECK_B_TREE; pProgress->bStartFlag = TRUE; pProgress->uiLfNumber = pLFile->uiLfNum; pProgress->uiLfType = pLFile->uiLfType; if (pLFile->uiLfType == LF_INDEX) { pProgress->bUniqueIndex = (pLogicalFile->pIxd->uiFlags & IXD_UNIQUE) ? TRUE : FALSE; } if (RC_BAD( rc = chkCallProgFunc( pDbInfo))) { break; } pProgress->bStartFlag = FALSE; // Initialize the state information for each level of the B-TREE. for (uiCurrLevel = 0; uiCurrLevel < pLfStats->uiNumLevels; uiCurrLevel++) { // If we are resetting to a particular key, save the statistics // which were gathered so far. if (uiResetKeyLen) { // Save the statistics which were gathered. pLfStats->pLevelInfo[uiCurrLevel].ui64KeyCount = State[uiCurrLevel].ui64KeyCount; f_memcpy( &pLfStats->pLevelInfo[uiCurrLevel].BlockInfo, &State[uiCurrLevel].BlkInfo, sizeof(BLOCK_INFO)); } flmInitReadState( &State[uiCurrLevel], &bStateInitialized[uiCurrLevel], pFileHdr->uiVersionNum, pDb, pLogicalFile, uiCurrLevel, (FLMUINT) ((!uiCurrLevel) ? (FLMUINT) BHT_LEAF : (FLMUINT) BHT_NON_LEAF), &pKeyBuffer[uiCurrLevel * MAX_KEY_SIZ]); if (!uiResetKeyLen) { State[uiCurrLevel].uiLastChildAddr = BT_END; State[uiCurrLevel].uiElmLastFlag = TRUE; } else { // Restore the statistics which were gathered so far. State[uiCurrLevel].ui64KeyCount = pLfStats->pLevelInfo[uiCurrLevel].ui64KeyCount; f_memcpy( &State[uiCurrLevel].BlkInfo, &pLfStats->pLevelInfo[uiCurrLevel].BlockInfo, sizeof(BLOCK_INFO)); } } // Need to finalize the result set used by the logical check. If the // current logical file is an index and the result set has not been // finalized, call chkRSFinalize. if (!pDbInfo->pProgress->bPhysicalCorrupt && (pDbInfo->uiFlags & FLM_CHK_INDEX_REFERENCING) && bRSFinalized == FALSE && pLFile->uiLfType == LF_INDEX) { FLMUINT64 ui64NumRSKeys = 0; // Finalize the result set. if (RC_BAD( rc = chkRSFinalize( pIxChkInfo, &ui64NumRSKeys))) { goto Exit; } // Reset uiNumKeys to reflect the number of keys in the result // set now that all duplicates have been eliminated. if (pDbInfo->pProgress->ui64NumKeys > ui64NumRSKeys) { pDbInfo->pProgress->ui64NumDuplicateKeys = pDbInfo->pProgress->ui64NumKeys - ui64NumRSKeys; } pDbInfo->pProgress->ui64NumKeys = ui64NumRSKeys; // Set bRSFinalized to TRUE so that subsequent passes will not // attempt to finalize the result set again. bRSFinalized = TRUE; } // Call chkVerifySubTree to check the B-TREE starting at the root // block. tmpPool.poolInit( 512); pDbInfo->bReposition = FALSE; rc = chkVerifySubTree( pDbInfo, pIxChkInfo, NULL, &State[pLfStats->uiNumLevels - 1], pLFile->uiRootBlk, &tmpPool, pucResetKey, uiResetKeyLen, uiResetDrn); tmpPool.poolFree(); if (rc == FERR_OLD_VIEW) { // If it is a read transaction, reset. if (flmGetDbTransType( pDb) == FLM_READ_TRANS) { // Free the KrefCntrl KrefCntrlFree( pDb); // Abort the read transaction if (RC_BAD( rc = flmAbortDbTrans( pDb))) { goto Exit; } // Try to start a new read transaction if (RC_BAD( rc = flmBeginDbTrans( pDb, FLM_READ_TRANS, 0, FLM_DONT_POISON_CACHE))) { goto Exit; } } rc = FERR_OK; pDbInfo->bReposition = TRUE; } if (RC_BAD( rc)) { goto Exit; } // We may get told to reposition if we had to repair an index or we // got an old view error. if (pDbInfo->bReposition) { // If the dictionary has changed we must start all over. if (pDb->pDict->uiDictSeq != uiSaveDictSeq) { *pbStartOverRV = TRUE; goto Exit; } // Save the current key at the bottom level of the B-Tree. This // is the point we want to try to reset to. Don't change the // reset key if the current key length is zero - this may have // occurred because of some error - we want to keep moving // forward in the file if at all possible. if (State[0].uiCurKeyLen) { uiResetKeyLen = State[0].uiCurKeyLen; pucResetKey = &ucResetKeyBuff[0]; uiResetDrn = State[0].uiCurrIxRefDrn; f_memcpy( pucResetKey, State[0].pCurKey, uiResetKeyLen); } // Re-read each logical file's LFH information. pProgress->ui64DatabaseSize = FSGetSizeInBytes( pDb->pFile->uiMaxFileSize, pDb->LogHdr.uiLogicalEOF); // Reread each of the LFH blocks and update the root block // address and other pertinent information for each logical file. for (uiTmpLf = 0, pTmpLfStats = pDbInfo->pProgress->pLfStats; uiTmpLf < pDbInfo->pProgress->uiNumLogicalFiles; uiTmpLf++, pTmpLfStats++) { FLMBOOL bCurrLfLevelChanged = FALSE; if (RC_BAD( rc = chkGetLfInfo( pDbInfo, pPool, pTmpLfStats, pDbInfo->pLogicalFiles[uiTmpLf].pLFile, pLfStats, &bCurrLfLevelChanged))) { goto Exit; } // If the number of levels for the current logical file // changed, reset things so we will recheck the entire logical // file. if (bCurrLfLevelChanged) { pucResetKey = NULL; uiResetKeyLen = 0; } } continue; } // Verify that all of the levels' next block address's are BT_END. if (RC_OK( pDbInfo->LastStatusRc)) { for (uiCurrLevel = 0; uiCurrLevel < pLfStats->uiNumLevels; uiCurrLevel++) { // Save the statistics which were gathered. pLfStats->pLevelInfo[uiCurrLevel].ui64KeyCount = State[uiCurrLevel].ui64KeyCount; f_memcpy( &pLfStats->pLevelInfo[uiCurrLevel].BlockInfo, &State[uiCurrLevel].BlkInfo, sizeof(BLOCK_INFO)); // Make sure the last block had a NEXT block address of // BT_END. if ((State[uiCurrLevel].uiNextBlkAddr) && (State[uiCurrLevel].uiNextBlkAddr != BT_END)) { chkReportError( pDbInfo, FLM_BAD_LAST_BLK_NEXT, LOCALE_B_TREE, pDbInfo->pProgress->uiLfNumber, pDbInfo->pProgress->uiLfType, uiCurrLevel, 0, 0, 0, 0, 0xFFFF, 0, NULL); } } } if (RC_BAD( pDbInfo->LastStatusRc)) { break; } uiCurrLf++; pucResetKey = NULL; uiResetKeyLen = 0; uiResetDrn = 0; } // If index check was requested, no structural corruptions were // detected, and this is the last logical file, need to make sure that // the result set is empty. if (RC_OK( rc) && !pDbInfo->pProgress->bPhysicalCorrupt && (pDbInfo->uiFlags & FLM_CHK_INDEX_REFERENCING) && bRSFinalized == TRUE && uiCurrLf == pDbInfo->pProgress->uiNumLogicalFiles) { for (;;) { if (RC_BAD( rc = chkGetNextRSKey( pIxChkInfo))) { if (rc == FERR_EOF_HIT || rc == FERR_NOT_FOUND) { rc = FERR_OK; break; } goto Exit; } else { // Updated statistics pIxChkInfo->pDbInfo->pProgress->ui64NumKeysExamined++; if (RC_BAD( rc = chkResolveIXMissingKey( &(State[0]), pIxChkInfo))) { goto Exit; } } } } // Clear the unique index flag pProgress->bUniqueIndex = FALSE; Exit: // Clear the pRecord for each level in the state array. for (uiCurrLevel = 0; uiCurrLevel < BH_MAX_LEVELS; uiCurrLevel++) { if (bStateInitialized[uiCurrLevel] && State[uiCurrLevel].pRecord) { State[uiCurrLevel].pRecord->Release(); State[uiCurrLevel].pRecord = NULL; } } // Cleanup any temporary index check files if (pIxChkInfo != NULL) { chkFreeIxInfo( pIxChkInfo); } if (pKeyBuffer) { f_free( &pKeyBuffer); } if (RC_OK( rc) && RC_BAD( pDbInfo->LastStatusRc)) { rc = pDbInfo->LastStatusRc; } return (rc); } /**************************************************************************** Desc: ****************************************************************************/ RCODE chkReportError( DB_INFO * pDbInfo, eCorruptionType eCorruption, eCorruptionLocale eErrLocale, FLMUINT uiErrLfNumber, FLMUINT uiErrLfType, FLMUINT uiErrBTreeLevel, FLMUINT uiErrBlkAddress, FLMUINT uiErrParentBlkAddress, FLMUINT uiErrElmOffset, FLMUINT uiErrDrn, FLMUINT uiErrElmRecOffset, FLMUINT uiErrFieldNum, FLMBYTE * pBlk) { CORRUPT_INFO CorruptInfo; FLMBOOL bFixErr; CorruptInfo.eCorruption = eCorruption; CorruptInfo.eErrLocale = eErrLocale; CorruptInfo.uiErrLfNumber = uiErrLfNumber; CorruptInfo.uiErrLfType = uiErrLfType; CorruptInfo.uiErrBTreeLevel = uiErrBTreeLevel; CorruptInfo.uiErrBlkAddress = uiErrBlkAddress; CorruptInfo.uiErrParentBlkAddress = uiErrParentBlkAddress; CorruptInfo.uiErrElmOffset = uiErrElmOffset; CorruptInfo.uiErrDrn = uiErrDrn; CorruptInfo.uiErrElmRecOffset = uiErrElmRecOffset; CorruptInfo.uiErrFieldNum = uiErrFieldNum; CorruptInfo.pBlk = pBlk; CorruptInfo.pErrIxKey = NULL; CorruptInfo.pErrRecord = NULL; CorruptInfo.pErrRecordKeyList = NULL; if ((pDbInfo->fnStatusFunc) && (RC_OK( pDbInfo->LastStatusRc))) { bFixErr = FALSE; pDbInfo->LastStatusRc = (*pDbInfo->fnStatusFunc) (FLM_PROBLEM_STATUS, (void *) &CorruptInfo, (void *) &bFixErr, pDbInfo->pProgress->AppArg); } if (eCorruption != FLM_OLD_VIEW) { pDbInfo->pProgress->bPhysicalCorrupt = TRUE; pDbInfo->uiFlags &= ~FLM_CHK_INDEX_REFERENCING; } return (pDbInfo->LastStatusRc); } /**************************************************************************** Desc: Initializes an IX_CHK_INFO structure ****************************************************************************/ FSTATIC RCODE chkSetupIxInfo( DB_INFO * pDbInfo, IX_CHK_INFO * pIxInfoRV) { RCODE rc = FERR_OK; FLMUINT uiIxCount = 0; FLMUINT uiIxNum = 0; IXD * pIxd; LFILE * pLFile; char szTmpIoPath[ F_PATH_MAX_SIZE]; char szBaseName[ F_FILENAME_SIZE]; FDB * pDb = pDbInfo->pDb; f_memset( pIxInfoRV, 0, sizeof(IX_CHK_INFO)); pIxInfoRV->pool.poolInit( 512); pIxInfoRV->pDbInfo = pDbInfo; // Set up the result set path if (RC_BAD( rc = flmGetTmpDir( szTmpIoPath))) { if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH) { if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathReduce( pDb->pFile->pszDbPath, szTmpIoPath, szBaseName))) { goto Exit; } } else { goto Exit; } } // Initialize the result set. The result set will be used to build an // ordered list of keys for comparision to the database's indexes. if (RC_BAD( rc = chkRSInit( szTmpIoPath, &pIxInfoRV->pRSet))) { goto Exit; } // Build list of all indexes uiIxCount = 0; if (pDb->pDict) { uiIxCount += pDb->pDict->uiIxdCnt; } // Allocate memory to save each index number and its associated // container number. if (RC_BAD( rc = f_alloc( (FLMUINT) ((sizeof(FLMUINT) * uiIxCount) + (sizeof(FLMBOOL) * uiIxCount)), &(pIxInfoRV->puiIxArray)))) { goto Exit; } // Save the index numbers into the array. uiIxCount = 0; if (pDb->pDict) { for (uiIxNum = 0, pIxd = (IXD *) pDb->pDict->pIxdTbl; uiIxNum < pDb->pDict->uiIxdCnt; uiIxNum++, pIxd++) { pIxInfoRV->puiIxArray[uiIxCount] = pIxd->uiIndexNum; uiIxCount++; } } if (RC_OK( fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, FLM_DICT_INDEX, &pLFile, NULL))) { pIxInfoRV->puiIxArray[uiIxCount] = FLM_DICT_INDEX; uiIxCount++; } pIxInfoRV->uiIxCount = uiIxCount; pIxInfoRV->bGetNextRSKey = TRUE; Exit: // Clean up any memory on error exit. if (RC_BAD( rc)) { pIxInfoRV->pool.poolFree(); if (pIxInfoRV->puiIxArray) { f_free( &(pIxInfoRV->puiIxArray)); } } return (rc); } /**************************************************************************** Desc: Outputs keys to the temporary result set ****************************************************************************/ FSTATIC RCODE chkOutputIndexKeys( STATE_INFO * pStateInfo, IX_CHK_INFO * pIxChkInfo, IXD * pIxd, REC_KEY * pKeyList) { RCODE rc = FERR_OK; FLMUINT uiKeyLen; REC_KEY * pKey; FLMBYTE ucBuf[MAX_KEY_SIZ + RS_KEY_OVERHEAD]; pKey = pKeyList; while (pKey) { // Set the index and reference UW2FBA( (FLMUINT16) pIxd->uiIndexNum, &(ucBuf[RS_IX_OFFSET])); UD2FBA( (FLMUINT32) pStateInfo->uiElmDrn, &(ucBuf[RS_REF_OFFSET])); // Convert the key tree to a collation key if (RC_BAD( rc = KYTreeToKey( pIxChkInfo->pDbInfo->pDb, pIxd, pKey->pKey, pKey->pKey->getContainerID(), &(ucBuf[RS_KEY_OVERHEAD]), &uiKeyLen, 0))) { goto Exit; } // Add the composite key (index, ref, key) to the result set if (RC_BAD( rc = pIxChkInfo->pRSet->addEntry( ucBuf, uiKeyLen + RS_KEY_OVERHEAD))) { goto Exit; } // Update statistics. Note that uiNumKeys will reflect the total // number of keys generated by records, including any duplicate // keys. This value is updated to reflect the correct number of keys // once the result set has been finalized. pIxChkInfo->pDbInfo->pProgress->ui64NumKeys++; pKey = pKey->pNextKey; } Exit: return (rc); }