//------------------------------------------------------------------------- // Desc: Rebuild corrupted database. // 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: flblddb.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" typedef struct Container_Info { FLMUINT uiHighetstDrnFound; FLMUINT uiHighestNextDrnFound; FLMUINT uiNumNextDrnsFound; FLMBOOL bCountEstimated; } CONTAINER_INFO, * CONTAINER_INFO_p; typedef struct RECOV_DICT_REC { FlmRecord * pRec; FLMUINT uiBlkAddress; FLMUINT uiElmOffset; FLMBOOL bAdded; FLMBOOL bGotFromDataRec; RECOV_DICT_REC * pNext; } RECOV_DICT_REC; typedef struct { RECOV_DICT_REC * pRecovRecs; POOL pool; } RECOV_DICT_INFO; FSTATIC RCODE bldAdjustNextDrn( FDB * pDb, LFILE * pLFile, CONTAINER_INFO * pContInfo); FSTATIC RCODE bldRecovData( FDB * pDb, REBUILD_STATE * pRebuildState, CONTAINER_INFO * pContainerInfo, FLMBOOL bRecovDictRecs, FLMBOOL * pbStartedTransRV); FSTATIC RCODE bldCheckBlock( STATE_INFO * pStateInfo, HDR_INFO * pHdrInfo, FLMUINT uiBlkAddress, FLMUINT uiPrevBlkAddress, eCorruptionType * peCorruptionCode); FSTATIC RCODE bldExtractRecs( FDB * pDb, REBUILD_STATE * pRebuildState, LFILE * pLFile, CONTAINER_INFO * pContInfo, FLMUINT uiBlkAddress, LF_HDR * pLogicalFile, FLMBOOL bRecovDictRecs, RECOV_DICT_INFO ** ppRecovDictInfoRV); FSTATIC RCODE bldGetNextElm( REBUILD_STATE * pRebuildState, STATE_INFO * pStateInfo, FLMBOOL * pbGotNextElmRV, FLMBOOL * pbGotNewBlockRV); FSTATIC RCODE bldGetOneRec( FDB * pDb, REBUILD_STATE * pRebuildState, STATE_INFO * pStateInfo, FLMBOOL bRecovDictRecs, FLMBOOL * pbGotNewBlockRV, FLMBOOL * pbGotRecord); FSTATIC RCODE bldSaveRecovDictRec( FDB * pDb, RECOV_DICT_INFO ** ppRecovDictInfoRV, FlmRecord * pRecord, FLMUINT uiDrn, FLMBOOL bGotFromDataRec, FLMUINT uiBlkAddress, FLMUINT uiElmOffset); FSTATIC void bldFreeRecovDictInfo( RECOV_DICT_INFO * pRecovDictInfo); FSTATIC RCODE bldDoDict( FDB * pDb, REBUILD_STATE * pRebuildState, RECOV_DICT_INFO * pDictToDo, FLMBOOL * pbStartedTransRV); FSTATIC RCODE bldDetermineBlkSize( F_SuperFileHdl * pSFileHdl, FLMUINT uiMaxFileSize, FLMUINT * puiBlkSizeRV, STATUS_HOOK fnStatusFunc, REBUILD_INFO * pCallbackData, void * AppArg); /*************************************************************************** Desc: This routine adds all of the recovered dictionary records to their appropriate dictionaries. ****************************************************************************/ FINLINE RCODE bldAddRecovDictRecs( FDB * pDb, REBUILD_STATE * pRebuildState, RECOV_DICT_INFO ** ppDictListRV, FLMBOOL * pbStartedTransRV) { RECOV_DICT_INFO * pDict; if( (pDict = *ppDictListRV) != NULL) { *ppDictListRV = NULL; return bldDoDict( pDb, pRebuildState, pDict, pbStartedTransRV); } return FERR_OK; } /*************************************************************************** Desc: Setup corrupt info structure prior to calling status callback ****************************************************************************/ FINLINE RCODE bldReportReason( REBUILD_STATE * pRebuildState, eCorruptionType eCorruption, FLMUINT uiErrBlkAddress, FLMUINT uiErrElmOffset, FLMUINT uiErrDrn, FLMUINT uiErrElmRecOffset, FLMUINT uiErrFieldNum) { RCODE rc = FERR_OK; if( pRebuildState->fnStatusFunc) { pRebuildState->CorruptInfo.eCorruption = eCorruption; pRebuildState->CorruptInfo.uiErrBlkAddress = uiErrBlkAddress; pRebuildState->CorruptInfo.uiErrElmOffset = uiErrElmOffset; pRebuildState->CorruptInfo.uiErrDrn = uiErrDrn; pRebuildState->CorruptInfo.uiErrElmRecOffset = uiErrElmRecOffset; pRebuildState->CorruptInfo.uiErrFieldNum = uiErrFieldNum; rc = (*pRebuildState->fnStatusFunc)( FLM_PROBLEM_STATUS, (void *)&pRebuildState->CorruptInfo, (void *)0, pRebuildState->AppArg); pRebuildState->CorruptInfo.eCorruption = FLM_NO_CORRUPTION; } return( rc); } /*************************************************************************** Desc: This routine determines whether or not a container should be done. ****************************************************************************/ FINLINE FLMBOOL bldDoContainer( FLMUINT uiContainerNum, FLMBOOL bDoDictContainers) { if (!bDoDictContainers) { switch (uiContainerNum) { case FLM_DICT_CONTAINER: return( FALSE); case FLM_DATA_CONTAINER: return( TRUE); default: if (uiContainerNum < FLM_RESERVED_TAG_NUMS) { return( TRUE); } else { return( FALSE); } } } return( TRUE); } /*************************************************************************** Desc: Reads through a database, extracts data records from all containers and puts them into the database specified by hDb. It is assumed that the new database has the same containers as the old database. ****************************************************************************/ RCODE flmDbRebuildFile( REBUILD_STATE * pRebuildState, // Rebuild state information. FLMBOOL bBadHeader // Was file's header or log header // information bad? ) { RCODE rc = FERR_OK; FLMUINT uiCurrLf; FLMBOOL bFdbInitialized = FALSE; FLMBOOL bStartedTrans = FALSE; LFILE * pLFile; FLMUINT uiTemp; FLMUINT uiContainerNum; CONTAINER_INFO * pContainerInfo = NULL; CONTAINER_INFO * pContInfo; FDB * pDb = (FDB *)pRebuildState->hDb; pRebuildState->CorruptInfo.eErrLocale = LOCALE_B_TREE; pRebuildState->CorruptInfo.uiErrLfType = LF_CONTAINER; bFdbInitialized = TRUE; if (RC_BAD( fdbInit( pDb, FLM_UPDATE_TRANS, FDB_DONT_RESET_DIAG, FLM_AUTO_TRANS | FLM_NO_TIMEOUT, &bStartedTrans))) { goto Exit; } if( RC_BAD( rc = f_alloc( pRebuildState->pHdrInfo->FileHdr.uiBlockSize, &pRebuildState->pBlk))) { goto Exit; } // Do a first pass to recover any dictionary items that may not have // been added from the dictionary file that was passed into the rebuild // function. if( RC_BAD( rc = bldRecovData( pDb, pRebuildState, pContainerInfo, TRUE, &bStartedTrans))) { goto Exit; } // Reset records recovered to zero after dictionary pass. pRebuildState->CallbackData.uiRecsRecov = 0; // Allocate an array of structures to keep information on each // container. if( RC_BAD( rc = f_calloc( (FLMUINT)( sizeof( CONTAINER_INFO) * pDb->pDict->uiLFileCnt), &pContainerInfo))) { goto Exit; } if( RC_BAD( rc = bldRecovData( pDb, pRebuildState, pContainerInfo, FALSE, &bStartedTrans))) { goto Exit; } // Adjust the next DRN for all containers so that they are at least as high // as the next DRN in the containers we were rebuilding from. for( uiCurrLf = 0, pContInfo = pContainerInfo, pLFile = (LFILE *)pDb->pDict->pLFileTbl; uiCurrLf < pDb->pDict->uiLFileCnt; uiCurrLf++, pLFile++, pContInfo++) { if (pLFile->uiLfType == LF_CONTAINER) { uiContainerNum = pLFile->uiLfNum; if (bldDoContainer( uiContainerNum, FALSE)) { if (RC_BAD( rc = bldAdjustNextDrn( pDb, pLFile, pContInfo))) { goto Exit; } } } } // Preserve other things in the log header that we ought // to try and preserve. if( !bBadHeader) { FLMBYTE * pucLogHdr = &pDb->pFile->ucUncommittedLogHdr [0]; // Set the commit count one less than the old database's // This is because it will be incremented if the transaction // successfully commits - which will make it exactly right. uiTemp = (FLMUINT)FB2UD( &pRebuildState->pLogHdr [LOG_COMMIT_COUNT]) - 1; if ((FLMUINT)FB2UD( &pucLogHdr [LOG_COMMIT_COUNT]) < uiTemp) { UD2FBA( (FLMUINT32)uiTemp, &pucLogHdr [LOG_COMMIT_COUNT]); } } // Signal the we are finished the rebuild if (pRebuildState->fnStatusFunc) { pRebuildState->CallbackData.iDoingFlag = REBUILD_FINISHED; pRebuildState->CallbackData.bStartFlag = TRUE; if (RC_BAD( rc = (*pRebuildState->fnStatusFunc)( FLM_REBUILD_STATUS, (void *)&pRebuildState->CallbackData, (void *)0, pRebuildState->AppArg))) { goto Exit; } pRebuildState->CallbackData.bStartFlag = FALSE; } Exit: if (pContainerInfo) { f_free( &pContainerInfo); } if (pRebuildState->pBlk) { f_free( &pRebuildState->pBlk); } if (bStartedTrans) { if (rc == FERR_OK) { rc = flmCommitDbTrans( pDb, 0, TRUE); } else { (void)flmAbortDbTrans( pDb); } } if (bFdbInitialized) { fdbExit( pDb); } return( rc); } /*************************************************************************** Desc: This routine adjusts the next DRN for a container so that it is at least as high as the DRN in the file we are rebuilding from. ****************************************************************************/ FSTATIC RCODE bldAdjustNextDrn( FDB * pDb, LFILE * pLFile, CONTAINER_INFO * pContInfo) { RCODE rc; FLMUINT uiNextDrn; BTSK StackBuf [BH_MAX_LEVELS]; FLMBOOL bUsedStack = FALSE; // First see what the next DRN is currently set to uiNextDrn = 0; if( RC_BAD( rc = FSGetNextDrn( pDb, pLFile, FALSE, &uiNextDrn))) { goto Exit; } // Adjust the next DRN to be at least as high as the highest DRN // in the old databaseor the highest next DRN found int the // old database. if( uiNextDrn < pContInfo->uiHighetstDrnFound) { uiNextDrn = pContInfo->uiHighetstDrnFound + 1; } if( uiNextDrn < pContInfo->uiHighestNextDrnFound) { uiNextDrn = pContInfo->uiHighestNextDrnFound; } // Add either 100 or 1000 to next record number - just in case // things weren't as accurate as they should have been in the // old database. We want to make sure the next record // number is high enough to avoid accidentally reusing any // records which may have been used in the old database. uiNextDrn += ((pContInfo->uiNumNextDrnsFound != 1) ? 1000 : 100); // If there is no root block, the next DRN is stored inside the // logical file header. Otherwise, it is stored in the rightmost // element of the B-Tree - the one with a DRN of DRN_LAST_MARKER. if( pLFile->uiRootBlk == BT_END) { // LFILE is up to date from previously calling FSGetNxtDrn pLFile->uiNextDrn = uiNextDrn; if (RC_BAD( rc = flmLFileWrite( pDb, pLFile))) { goto Exit; } } else { BTSK * pStack = StackBuf; FLMBYTE KeyBuf [DIN_KEY_SIZ + 4]; FLMBYTE DrnMarker [DIN_KEY_SIZ]; FLMBYTE * pNextDrnBuf; // Set up the stack FSInitStackCache( &StackBuf [0], BH_MAX_LEVELS); bUsedStack = TRUE; pStack->pKeyBuf = KeyBuf; // Find the element whose DRN is DRN_LAST_MARKER flmUINT32ToBigEndian( (FLMUINT32)DRN_LAST_MARKER, DrnMarker); if( RC_BAD( rc = FSBtSearch( pDb, pLFile, &pStack, DrnMarker, DIN_KEY_SIZ, 0))) { goto Exit; } // Log the block before modifying it if( RC_BAD( rc = FSLogPhysBlk( pDb, pStack))) { goto Exit; } pNextDrnBuf = CURRENT_ELM( pStack); pNextDrnBuf += BBE_GETR_KL( pNextDrnBuf ) + BBE_KEY; // Update with the next DRN value and dirty the block UD2FBA( (FLMUINT32)uiNextDrn, pNextDrnBuf ); } Exit: if( bUsedStack) { FSReleaseStackCache( StackBuf, BH_MAX_LEVELS, FALSE); } return( rc); } /*************************************************************************** Desc: This routine recovers all records in the database for all containers. *****************************************************************************/ FSTATIC RCODE bldRecovData( FDB * pDb, REBUILD_STATE * pRebuildState, // Pointer to rebuild state information. // This structure has the container // number being recovered. CONTAINER_INFO * pContainerInfo, // Information being remembered for // ALL containers. FLMBOOL bRecovDictRecs, // Flag indicating whether we are to // doing a pass to recover dictionary // data or regular data. FLMBOOL * pbStartedTransRV) { RCODE rc = FERR_OK; FLMUINT uiBlkAddress; FLMUINT uiBytesRead; F_SuperFileHdl * pSFileHdl = pRebuildState->pSFileHdl; HDR_INFO * pHdrInfo = pRebuildState->pHdrInfo; LF_HDR LogicalFile; LF_STATS LfStats; FLMBYTE * pucBlk = pRebuildState->pBlk; STATUS_HOOK fnStatusFunc = pRebuildState->fnStatusFunc; REBUILD_INFO * pCallbackData = &pRebuildState->CallbackData; void * AppArg = pRebuildState->AppArg; FLMUINT uiBlockSize = pHdrInfo->FileHdr.uiBlockSize; FLMUINT uiCurrContainerNum = 0; LFILE * pCurrLFile = NULL; CONTAINER_INFO * pCurrContInfo = NULL; FLMUINT uiBlkContainerNum; LFILE * pBlkLFile; RECOV_DICT_INFO * pRecovDictInfo = NULL; FLMUINT uiFileNumber = 0; FLMUINT uiOffset = 0; F_FileHdlImp * pFileHdl = NULL; FLMUINT uiMaxFileSize = pRebuildState->uiMaxFileSize; FLMUINT uiDbVersion = pRebuildState->pHdrInfo->FileHdr.uiVersionNum; // Read through all blocks in the file -- looking for leaf blocks // of containers. Read until we get an error or run out of file. LogicalFile.pLfStats = &LfStats; fnStatusFunc = pRebuildState->fnStatusFunc; pCallbackData->iDoingFlag = (FLMINT)((bRecovDictRecs) ? (FLMINT)REBUILD_RECOVER_DICT : (FLMINT)REBUILD_RECOVER_DATA); pCallbackData->bStartFlag = TRUE; pCallbackData->ui64BytesExamined = 0; for (;;) { if (uiOffset >= uiMaxFileSize || !uiFileNumber) { uiOffset = 0; uiFileNumber++; if (uiFileNumber > MAX_DATA_BLOCK_FILE_NUMBER( uiDbVersion)) { break; } if (RC_BAD( rc = pSFileHdl->GetFileHdl( uiFileNumber, FALSE, &pFileHdl))) { if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH) { rc = FERR_OK; break; } goto Exit; } } // Read the block into memory. if (RC_BAD( rc = pFileHdl->SectorRead( uiOffset, uiBlockSize, pucBlk, &uiBytesRead))) { if (rc == FERR_IO_END_OF_FILE) { if (!uiBytesRead) { // Set uiOffset so we will go to the next file. uiOffset = uiMaxFileSize; continue; } else { rc = FERR_OK; } } else { goto Exit; } } if (fnStatusFunc) { pCallbackData->ui64BytesExamined += (FLMUINT64)uiBlockSize; if (RC_BAD( rc = (*fnStatusFunc)( FLM_REBUILD_STATUS, (void *)pCallbackData, (void *)0, AppArg))) { goto Exit; } pCallbackData->bStartFlag = FALSE; } f_yieldCPU(); uiBlkAddress = (FLMUINT)GET_BH_ADDR( pucBlk); if ((FSGetFileOffset( uiBlkAddress) == uiOffset) && (BH_GET_TYPE( pucBlk) == BHT_LEAF) && (pucBlk [BH_LEVEL] == 0) && ((uiBlkContainerNum = (FLMUINT)FB2UW( &pucBlk [BH_LOG_FILE_NUM])) != 0) && (bldDoContainer( uiBlkContainerNum, bRecovDictRecs))) { if (uiBlkContainerNum != uiCurrContainerNum) { if (RC_BAD( rc = fdictGetContainer( pDb->pDict, uiBlkContainerNum, &pBlkLFile))) { if (rc != FERR_BAD_CONTAINER) goto Exit; rc = FERR_OK; goto Do_Next_Block; } else { uiCurrContainerNum = uiBlkContainerNum; f_memset( &LogicalFile, 0, sizeof( LF_HDR)); f_memset( &LfStats, 0, sizeof( LF_STATS)); LogicalFile.pLfStats = &LfStats; LogicalFile.pLFile = pCurrLFile = pBlkLFile; if (bRecovDictRecs) { pCurrContInfo = NULL; } else { pCurrContInfo = &pContainerInfo [pCurrLFile - ((LFILE *)pDb->pDict->pLFileTbl)]; } pRebuildState->CorruptInfo.uiErrLfNumber = uiBlkContainerNum; } } // Estimate the number of records in the block if we did't have // a count of records in the container. The loop ignores the // possibility that the block may be corrupted -- we are trying // to estimate what might have been in the block. It loops through // the elements in the block, looking for those which are marked // as FIRST elements. When it encounters one of these, it will // increment the counter. if (pCurrContInfo && !pCurrContInfo->bCountEstimated) { FLMUINT uiBlkOffset; FLMUINT uiEndOfBlock; FLMBYTE * pucElm; FLMUINT uiElmLen; FLMUINT uiElmKeyLen; FLMUINT uiElmPKCLen; FLMUINT uiNxtBlkAddr; FLMBOOL bIncremented; uiEndOfBlock = (FLMUINT)FB2UW( &pucBlk [BH_ELM_END]); uiNxtBlkAddr = (FLMUINT)FB2UD( &pucBlk [BH_NEXT_BLK]); // If uiEndOfBlock is too big, adjust it down so we can // estimate. if (uiEndOfBlock > uiBlockSize) { uiEndOfBlock = uiBlockSize; } uiBlkOffset = BH_OVHD; bIncremented = FALSE; while (uiBlkOffset < uiEndOfBlock) { pucElm = &pucBlk [uiBlkOffset]; uiElmLen = (FLMUINT)(BBE_LEN( pucElm)); uiElmKeyLen = (FLMUINT)(BBE_GET_KL( pucElm)); uiElmPKCLen = (FLMUINT)(BBE_GET_PKC( pucElm)); // If it is a FIRST element, and it is NOT the LEM // element, increment the count. if ((BBE_IS_FIRST( pucElm)) && ((uiElmLen != BBE_LEM_LEN) || (uiElmKeyLen > 0) || (uiElmPKCLen > 0) || (uiBlkOffset + uiElmLen != uiEndOfBlock) || (uiNxtBlkAddr != BT_END))) { pCallbackData->uiTotRecs++; bIncremented = TRUE; } uiBlkOffset += uiElmLen; } // Decrement the estimated count by one if it is a last // block - one of the elements in a last block should // always be a DRN_LAST_MARKER element. if ((bIncremented) && (uiNxtBlkAddr == BT_END)) { pCallbackData->uiTotRecs--; } } // See if we can now extract any records from the block uiBlkAddress = FSBlkAddress( uiFileNumber, uiOffset); if( RC_BAD( rc = bldExtractRecs( pDb, pRebuildState, pCurrLFile, pCurrContInfo, uiBlkAddress, &LogicalFile, bRecovDictRecs, &pRecovDictInfo))) { goto Exit; } } Do_Next_Block: uiOffset += uiBlockSize; } // If we are recovering dictionary records, we need to now add them // into the appropriate dictionaries. if( bRecovDictRecs) { if( RC_BAD( rc = bldAddRecovDictRecs( pDb, pRebuildState, &pRecovDictInfo, pbStartedTransRV))) { goto Exit; } } Exit: bldFreeRecovDictInfo( pRecovDictInfo); return( rc); } /*************************************************************************** Desc: This routine checks a few things in the block header and then attempts to decrypt the block so we can read through its elements. Ret: 0 if block is OK, error code otherwise *****************************************************************************/ FSTATIC RCODE bldCheckBlock( STATE_INFO * pStateInfo, HDR_INFO * pHdrInfo, FLMUINT uiBlkAddress, FLMUINT uiPrevBlkAddress, eCorruptionType * peCorruptionCode) { RCODE rc = FERR_OK; // Determine where the end of block is -- make sure it is a legal value pStateInfo->uiBlkAddress = uiBlkAddress; // Must force the block address to be correct so this check will not // fail. We already have previously verified that the offset matches, and // we know that we got it from the right block file. However, the low // byte of the block header will not be correct because until we do a // block checksum calculation, it will hold the low checksum byte. // We don't do a block checksum calculation during rebuild, so at this // point, it still holds the low checksum byte. SET_BH_ADDR( pStateInfo->pBlk, uiBlkAddress); if ((*peCorruptionCode = flmVerifyBlockHeader( pStateInfo, NULL, pHdrInfo->FileHdr.uiBlockSize, 0, uiPrevBlkAddress, FALSE, TRUE)) != FLM_NO_CORRUPTION) { goto Exit; } pStateInfo->uiElmOffset = BH_OVHD; // Decrypt the block if necessary to check the elements. // If encryption is not enabled for the database, or the block // is already decrypted, do nothing. Exit: return( rc); } /*************************************************************************** Desc: This routine traverses all elements within a block, extracting whatever records it can from the block. *****************************************************************************/ FSTATIC RCODE bldExtractRecs( FDB * pDb, REBUILD_STATE * pRebuildState, LFILE * pLFile, CONTAINER_INFO * pContInfo, FLMUINT uiBlkAddress, LF_HDR * pLogicalFile, FLMBOOL bRecovDictRecs, RECOV_DICT_INFO ** ppRecovDictInfoRV) { RCODE rc = FERR_OK; eCorruptionType eCorruptionCode; FLMBOOL bGotNewBlock; FLMUINT uiSaveElmOffset; STATE_INFO * pStateInfo = pRebuildState->pStateInfo; FLMBOOL bStateInitialized = TRUE; STATUS_HOOK fnStatusFunc = pRebuildState->fnStatusFunc; void * AppArg = pRebuildState->AppArg; FLMBOOL bGotRecord; // Setup the STATE variable for processing through the block flmInitReadState( pStateInfo, &bStateInitialized, pRebuildState->pHdrInfo->FileHdr.uiVersionNum, pDb, pLogicalFile, 0xFF, BHT_LEAF, pRebuildState->pKeyBuffer); pStateInfo->pBlk = pRebuildState->pBlk; if( (RC_BAD( rc = bldCheckBlock( pStateInfo, pRebuildState->pHdrInfo, uiBlkAddress, 0, &eCorruptionCode))) || (eCorruptionCode != FLM_NO_CORRUPTION)) { if( eCorruptionCode != FLM_NO_CORRUPTION) { (void)bldReportReason( pRebuildState, eCorruptionCode, uiBlkAddress, 0, 0, 0xFFFF, 0); } goto Exit; } // Go through each element in the block, extracting whatever data // we can. The loop quits if it finds an inconsistency in the block. bGotNewBlock = FALSE; while ((pStateInfo->uiElmOffset < pStateInfo->uiEndOfBlock) && (!bGotNewBlock)) { bGotRecord = FALSE; if (pRebuildState->pRecord) { pRebuildState->pRecord->clear(); } uiSaveElmOffset = pStateInfo->uiElmOffset; // Get the element and check it if( (eCorruptionCode = flmVerifyElement( pStateInfo, FLM_CHK_FIELDS)) != FLM_NO_CORRUPTION) { if( RC_BAD( rc = bldReportReason( pRebuildState, eCorruptionCode, uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0))) { goto Exit; } } // Skip continuation elements -- at this point, it should only // be continuation elements which are at the first of the block. // This is because the bldGetOneRec routine will traverse through // continuation elements for a record. else if ((BBE_IS_FIRST( pStateInfo->pElm)) && (pStateInfo->uiCurKeyLen)) { if (pStateInfo->uiElmDrn == DRN_LAST_MARKER) { if (pStateInfo->uiElmRecLen == 4) { FLMUINT uiNxtDrn = (FLMUINT)FB2UD( pStateInfo->pElmRec); if (pContInfo) { pContInfo->uiNumNextDrnsFound++; if (uiNxtDrn > pContInfo->uiHighestNextDrnFound) { pContInfo->uiHighestNextDrnFound = uiNxtDrn; } } } } // If the element is the FIRST element in a record, // see if we can extract a record from it. else if( RC_BAD( rc = bldGetOneRec( pDb, pRebuildState, pStateInfo, bRecovDictRecs, &bGotNewBlock, &bGotRecord))) { // If we didn't have enough memory to retrieve the record, just // skip it. if( rc == FERR_MEM) { bGotRecord = FALSE; if (pRebuildState->pRecord) { pRebuildState->pRecord->clear(); } rc = FERR_OK; } else { goto Exit; } } } // If we didn't get a data record, there was some inconsistency // we encountered, so we continue to the next element in the block. if( bGotRecord) { f_yieldCPU(); if( pContInfo) { if( pStateInfo->uiElmDrn > pContInfo->uiHighetstDrnFound) { pContInfo->uiHighetstDrnFound = pStateInfo->uiElmDrn; } } // Add the record to the database if( bRecovDictRecs) { FlmRecord * pDictRec; FLMUINT uiDictDrn = 0; FLMBOOL bGotFromDataRec; CHK_RECORD ChkRec; f_memset( &ChkRec, 0, sizeof( ChkRec)); // If this is not a dictionary container record, need to do the // callback to see if there is dictionary information in this // record. if( pLFile->uiLfNum == FLM_DICT_CONTAINER) { pDictRec = pRebuildState->pRecord; uiDictDrn = pStateInfo->uiElmDrn; bGotFromDataRec = FALSE; } else { if( !fnStatusFunc) { pDictRec = NULL; } else { ChkRec.pRecord = pRebuildState->pRecord; ChkRec.uiContainer = pLFile->uiLfNum; ChkRec.uiDrn = pStateInfo->uiElmDrn; if( RC_BAD( rc = (*fnStatusFunc)( FLM_CHECK_RECORD_STATUS, (void *)&ChkRec, (void *)0, AppArg))) { if( ChkRec.pDictRecSet) { ChkRec.pDictRecSet->Release(); ChkRec.pDictRecSet = NULL; } goto Exit; } if( ChkRec.pDictRecSet) { if( (pDictRec = ChkRec.pDictRecSet->first()) != NULL) { uiDictDrn = pDictRec->getID(); } } else { pDictRec = NULL; } bGotFromDataRec = TRUE; } } if( !pDictRec) { rc = FERR_OK; } else { for (;;) { rc = bldSaveRecovDictRec( pDb, ppRecovDictInfoRV, pDictRec, uiDictDrn, bGotFromDataRec, uiBlkAddress, uiSaveElmOffset); if( RC_BAD( rc) || !bGotFromDataRec) { break; } // If bGotFromDataRec is TRUE, there may be more than // one that was returned. if( (pDictRec = ChkRec.pDictRecSet->next()) == NULL) { break; } uiDictDrn = pDictRec->getID(); } } if( ChkRec.pDictRecSet) { ChkRec.pDictRecSet->Release(); ChkRec.pDictRecSet = NULL; } } else if( pLFile->uiLfNum == FLM_TRACKER_CONTAINER) { rc = FSRecUpdate( pDb, pLFile, pRebuildState->pRecord, pStateInfo->uiElmDrn, REC_UPD_ADD); } else { rc = flmAddRecord( pDb, pLFile, &pStateInfo->uiElmDrn, pRebuildState->pRecord, TRUE, FALSE, FALSE, FALSE, NULL); if( RC_OK(rc) && fnStatusFunc ) { CHK_RECORD ChkRec; f_memset( &ChkRec, 0, sizeof( ChkRec)); ChkRec.pRecord = pRebuildState->pRecord; ChkRec.uiContainer = pLFile->uiLfNum; ChkRec.uiDrn = pStateInfo->uiElmDrn; rc = (*fnStatusFunc)( FLM_EXAMINE_RECORD_STATUS, (void *)&ChkRec, (void *)0, AppArg); if (ChkRec.pDictRecSet) { ChkRec.pDictRecSet->Release(); ChkRec.pDictRecSet = NULL; } } if( pRebuildState->pRecord->isReadOnly()) { pRebuildState->pRecord->Release(); pRebuildState->pRecord = NULL; } } if( RC_BAD( rc)) { if( (rc == FERR_EXISTS) || (rc == FERR_NOT_UNIQUE)) { eCorruptionCode = (rc == FERR_EXISTS) ? FLM_REBUILD_REC_EXISTS : FLM_REBUILD_KEY_NOT_UNIQUE; if (RC_BAD( rc = bldReportReason( pRebuildState, eCorruptionCode, uiBlkAddress, uiSaveElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0))) { goto Exit; } } else { goto Exit; } } else { // Make sure the tempory memory is freed. // Eats up the memory during a rebuild. GedPoolReset( &pDb->TempPool, NULL); if (!bRecovDictRecs) { pRebuildState->CallbackData.uiRecsRecov++; } } } pStateInfo->uiElmOffset += pStateInfo->uiElmLen; } Exit: return( rc); } /*************************************************************************** Desc: This routine gets the next element for a record. If necessary, it will try to go to the next block. Ret: TRUE if we got the next element, FALSE otherwise. *****************************************************************************/ FSTATIC RCODE bldGetNextElm( REBUILD_STATE * pRebuildState, STATE_INFO * pStateInfo, FLMBOOL * pbGotNextElmRV, FLMBOOL * pbGotNewBlockRV) { RCODE rc = FERR_OK; FLMUINT uiSaveDrn; FLMUINT uiBytesRead; FLMUINT uiBlkAddress; FLMBYTE * pBlk = pStateInfo->pBlk; HDR_INFO * pHdrInfo = pRebuildState->pHdrInfo; eCorruptionType eCorruptionCode; FLMUINT uiSaveBlkAddress = pStateInfo->uiBlkAddress; *pbGotNextElmRV = FALSE; uiSaveDrn = pStateInfo->uiElmDrn; // See if we need to go to the next block to get the element pStateInfo->uiElmOffset += pStateInfo->uiElmLen; if (pStateInfo->uiElmOffset >= pStateInfo->uiEndOfBlock) { // Get the next block *pbGotNewBlockRV = TRUE; uiBlkAddress = (FLMUINT)FB2UD( &pBlk [BH_NEXT_BLK]); rc = pRebuildState->pSFileHdl->ReadBlock( uiBlkAddress, pHdrInfo->FileHdr.uiBlockSize, pBlk, &uiBytesRead); if( uiBytesRead < pHdrInfo->FileHdr.uiBlockSize) { rc = RC_SET( FERR_IO_END_OF_FILE); } if( RC_BAD( rc)) { RCODE TempRc; if( rc == FERR_IO_END_OF_FILE || rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH) { rc = FERR_OK; } if( RC_BAD( TempRc = bldReportReason( pRebuildState, FLM_BAD_BLK_HDR_NEXT, uiSaveBlkAddress, 0, 0, 0xFFFF, 0))) { if( RC_OK( rc)) { rc = TempRc; } } goto Exit; } // Make sure it is the right type of block else if( (RC_BAD( rc = bldCheckBlock( pStateInfo, pRebuildState->pHdrInfo, uiBlkAddress, uiSaveBlkAddress, &eCorruptionCode))) || (eCorruptionCode != FLM_NO_CORRUPTION)) { if (eCorruptionCode != FLM_NO_CORRUPTION) { (void)bldReportReason( pRebuildState, eCorruptionCode, uiBlkAddress, 0, 0, 0xFFFF, 0); } goto Exit; } } // Verify other things about the element if( (eCorruptionCode = flmVerifyElement( pStateInfo, FLM_CHK_FIELDS)) != FLM_NO_CORRUPTION) { rc = bldReportReason( pRebuildState, eCorruptionCode, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0); goto Exit; } // This had better not be a LEM element if( pStateInfo->uiCurKeyLen == 0) { rc = bldReportReason( pRebuildState, FLM_BAD_LEM, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0); goto Exit; } // This element had better not be the first element if( BBE_IS_FIRST( pStateInfo->pElm)) { rc = bldReportReason( pRebuildState, FLM_BAD_FIRST_ELM_FLAG, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0); goto Exit; } // Must stay on the same DRN while processing the record if (pStateInfo->uiElmDrn != uiSaveDrn) { rc = bldReportReason( pRebuildState, FLM_BAD_CONT_ELM_KEY, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, 0xFFFF, 0); goto Exit; } *pbGotNextElmRV = TRUE; Exit: return( rc); } /*************************************************************************** Desc: This routine retrieves one record from a block -- at the current element. It will follow continuation elements if necessary. The record is returned in pRebuildState->pRecord. A NULL is returned if some inconsistency was encountered. *****************************************************************************/ FSTATIC RCODE bldGetOneRec( FDB * pDb, REBUILD_STATE * pRebuildState, STATE_INFO * pStateInfo, FLMBOOL bRecovDictRecs, FLMBOOL * pbGotNewBlockRV, FLMBOOL * pbGotRecord) { RCODE rc = FERR_OK; FLMBYTE * pValue; FLMBYTE * pData; FLMBYTE * pTempValue; eCorruptionType eCorruptionCode; FlmRecord * pRecord = NULL; FLMBOOL bAllocatedRecord = FALSE; FLMBOOL bFieldDone; FLMUINT uiSaveElmRecOffset; FLMBOOL bGotNextElm; FLMBOOL bSkipField = FALSE; FLMBOOL bSkippedField = FALSE; FLMUINT uiSkipToLevel = 0; // Setup things to get the record if( pRebuildState->pRecord) { pRebuildState->pRecord->clear(); pRecord = pRebuildState->pRecord; } pValue = NULL; pTempValue = NULL; *pbGotRecord = FALSE; // Follow elements until we have traversed all continuation elements // or until we discover some inconsistency. for (;;) { uiSaveElmRecOffset = pStateInfo->uiElmRecOffset; if ((eCorruptionCode = flmVerifyElmFOP( pStateInfo)) != FLM_NO_CORRUPTION) { if ((bRecovDictRecs) && (eCorruptionCode == FLM_BAD_ELM_FLD_NUM)) { bSkipField = TRUE; bSkippedField = TRUE; uiSkipToLevel = pStateInfo->uiFieldLevel; bFieldDone = (pStateInfo->uiFieldProcessedLen == pStateInfo->uiFieldLen) ? TRUE : FALSE; } else { rc = bldReportReason( pRebuildState, eCorruptionCode, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, uiSaveElmRecOffset, pStateInfo->uiFieldNum); goto Exit; } } // See if we are starting a new field if( (pStateInfo->uiFOPType == FLM_FOP_STANDARD) || (pStateInfo->uiFOPType == FLM_FOP_OPEN) || (pStateInfo->uiFOPType == FLM_FOP_TAGGED) || (pStateInfo->uiFOPType == FLM_FOP_NO_VALUE) || (pStateInfo->uiFOPType == FLM_FOP_ENCRYPTED)) { // If we skipped a previous field, see if this field is a child // or grandchild, etc. of the field that was skipped. If so, we skip // this field as well. We stop skipping when we come to a field // that is a sibling or aunt/uncle to the field that was skipped. if( (bSkipField) && (pStateInfo->uiFieldLevel <= uiSkipToLevel)) { bSkipField = FALSE; } // See if field should be skipped. if( !bSkipField) { FLMUINT uiState; FLMUINT uiDictFieldType; if( RC_BAD( fdictGetField( pDb->pDict, pStateInfo->uiFieldNum, &uiDictFieldType, NULL, &uiState))) { bSkipField = TRUE; bSkippedField = TRUE; uiSkipToLevel = pStateInfo->uiFieldLevel; } } // If we aren't skipping the field, allocate space for it if( !bSkipField) { void * pvField; if( !pRecord) { if( (pRecord = f_new FlmRecord) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } bAllocatedRecord = TRUE; } // Create a new field in the record. if( RC_BAD( rc = pRecord->insertLast( pStateInfo->uiFieldLevel, pStateInfo->uiFieldNum, pStateInfo->uiFieldType, &pvField))) { goto Exit; } pStateInfo->pvField = pvField; // Allocate space for the field's value and set the field's type. if( !pStateInfo->uiFieldLen) { pValue = NULL; } else { if (!pStateInfo->uiEncId) { if (RC_BAD( rc = pRecord->allocStorageSpace( pvField, pStateInfo->uiFieldType, pStateInfo->uiFieldLen, 0, 0, 0, &pValue, NULL))) { goto Exit; } } else { 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; } bFieldDone = ((!pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiFieldLen) || (pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiEncFieldLen)) ? TRUE : FALSE; } else if( (pStateInfo->uiFOPType == FLM_FOP_JUMP_LEVEL) || (pStateInfo->uiFOPType == FLM_FOP_NEXT_DRN)) { bFieldDone = FALSE; } else { bFieldDone = ((!pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiFieldLen) || (pStateInfo->uiEncId && pStateInfo->uiFieldProcessedLen == pStateInfo->uiEncFieldLen)) ? TRUE : FALSE; } // See if we got some data with this FOP if( pValue && pStateInfo->uiFOPDataLen && !bSkipField && pStateInfo->uiFOPType != FLM_FOP_REC_INFO) { f_memcpy( pTempValue, pStateInfo->pFOPData, pStateInfo->uiFOPDataLen); pTempValue += pStateInfo->uiFOPDataLen; } // See if we are done with this field if( bFieldDone) { // Verify the value if( pStateInfo->uiFieldLen) { if( bSkipField || pStateInfo->uiFieldType == 0xFF) { eCorruptionCode = FLM_NO_CORRUPTION; } else { if (!pStateInfo->uiEncId) { eCorruptionCode = flmVerifyField( pValue, pStateInfo->uiFieldLen, pStateInfo->uiFieldType); } // Encrypted fields are never supposed to be found in the dictionary, so // if we do not have an ITT table, we will not be able to decrypt this field. // Therefore, we should be in our first pass. This field / record will // not be used to recover the dictionary. else if (pDb->pFile->pDictList->pIttTbl) { if (RC_BAD( rc = flmDecryptField( pDb->pDict, pRecord, pStateInfo->pvField, pStateInfo->uiEncId, &pDb->TempPool))) { goto Exit; } eCorruptionCode = flmVerifyField( pData, pStateInfo->uiFieldLen, pStateInfo->uiFieldType); } } if( eCorruptionCode != FLM_NO_CORRUPTION) { rc = bldReportReason( pRebuildState, eCorruptionCode, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, uiSaveElmRecOffset, pStateInfo->uiFieldNum); goto Exit; } } // Set pValue to NULL for the next field pValue = pTempValue = NULL; } // See if we are at the end of this element if( pStateInfo->uiElmRecOffset == pStateInfo->uiElmRecLen) { // If the last element flag is set, we are done with this // record and can return - unless we have a half processed field. if( BBE_IS_LAST( pStateInfo->pElm)) { if (!bFieldDone) { rc = bldReportReason( pRebuildState, FLM_BAD_LAST_ELM_FLAG, pStateInfo->uiBlkAddress, pStateInfo->uiElmOffset, pStateInfo->uiElmDrn, uiSaveElmRecOffset, pStateInfo->uiFieldNum); } else { pRebuildState->pRecord = pRecord; *pbGotRecord = TRUE; pRecord = NULL; bAllocatedRecord = FALSE; } goto Exit; } else { // Attempt to get the next element if( (RC_BAD( rc = bldGetNextElm( pRebuildState, pStateInfo, &bGotNextElm, pbGotNewBlockRV))) || !bGotNextElm) { // Need to set bSkippedField to TRUE so that cleanup will // occur at the bottom of this routine. bSkippedField = TRUE; goto Exit; } } } } Exit: if( bSkippedField || RC_BAD( rc)) { if (pRebuildState->pRecord) { pRebuildState->pRecord->Release(); pRebuildState->pRecord = NULL; } *pbGotRecord = FALSE; } if( pRecord && bAllocatedRecord) { pRecord->Release(); } return( rc); } /*************************************************************************** Desc: This routine recovers any dictionary records from the corrupted file that it can recover, attempting to find any that may not have been in the dictionary file that was passed into the rebuild routines. *****************************************************************************/ FSTATIC RCODE bldSaveRecovDictRec( FDB * pDb, // FDB for newly created database RECOV_DICT_INFO ** ppRecovDictInfoRV, // Recover info FlmRecord * pRecord, // Dictionary Record FLMUINT uiDrn, // Dict. record DRN FLMBOOL bGotFromDataRec, // Was this dictionary record // extracted from a data record? FLMUINT uiBlkAddress, // Block the record was // recovered from FLMUINT uiElmOffset // Offset in block the // record was recovered // from ) { RCODE rc = FERR_OK; RECOV_DICT_INFO * pRecovDictInfo; RECOV_DICT_REC * pRecovDictRec; RECOV_DICT_REC * pNewDictRec = NULL; RECOV_DICT_REC * pPrevDictRec; FLMUINT uiRecType = pRecord->getFieldID( pRecord->root()); FlmRecord * pDummyRec; LFILE * pDictLFile; // Ignore any record that is not a dictionary record or // an unregistered (comment) record. if( uiRecType < FLM_RESERVED_TAG_NUMS) { goto Exit; } // Determine if the record already exists in the dictionary. If it // does, simply ignore this record - the record that was loaded from // the dictionary file takes precedence. if( RC_OK( rc = fdictGetContainer( pDb->pDict, FLM_DICT_CONTAINER, &pDictLFile))) { pDummyRec = NULL; rc = FSReadRecord( pDb, pDictLFile, uiDrn, &pDummyRec, NULL, NULL); if( pDummyRec) { pDummyRec->Release(); } } if( rc != FERR_NOT_FOUND) { goto Exit; } rc = FERR_OK; // If the dictionary was not found in the list, create an entry in the // list. if( (pRecovDictInfo = *ppRecovDictInfoRV) == NULL) { if( RC_BAD( rc = f_calloc( (FLMUINT)sizeof( RECOV_DICT_INFO), &pRecovDictInfo))) { goto Exit; } GedPoolInit( &pRecovDictInfo->pool, 512); *ppRecovDictInfoRV = pRecovDictInfo; } // Determine if the record is already in our list of records. If so, // simply ignore it, or remove the old one. The old one is removed if // it is "less desirable to recover". Desirability of record types is // as follows: // // 1. Field Definitions && Template definitions take top priority // 2. Container Definitions // 3. Area Definitions // 4. Reserve Definitions // 5. Index Definitions // 6. Other pPrevDictRec = NULL; pRecovDictRec = pRecovDictInfo->pRecovRecs; while( pRecovDictRec) { if( pRecovDictRec->pRec->getID() != uiDrn) { pPrevDictRec = pRecovDictRec; pRecovDictRec = pRecovDictRec->pNext; } else if( bGotFromDataRec && !pRecovDictRec->bGotFromDataRec) { // Throw away this record and keep the one that was previously // recovered from the dictionary container. Records recovered // from the dictionary container are preferred to ones that // were extracted from a data record. goto Exit; } else if( !bGotFromDataRec && pRecovDictRec->bGotFromDataRec) { // Throw away prior dictionary record that was recovered, because // we got it from a data record. This newer one was actually found // in the dictionary, so we will keep it in preference to the // earlier one that we found in a data record. goto Remove_Rec; } else { switch (pRecovDictRec->pRec->getFieldID( pRecovDictRec->pRec->root())) { case FLM_FIELD_TAG: goto Exit; case FLM_CONTAINER_TAG: if( uiRecType == FLM_FIELD_TAG) { Remove_Rec: if (pPrevDictRec) { pPrevDictRec->pNext = pRecovDictRec->pNext; } else { pRecovDictInfo->pRecovRecs = pRecovDictRec->pNext; } // Might as well use the old structure for the new record, // so we don't have to allocate it below. pNewDictRec = pRecovDictRec; if( pRecovDictRec->pRec) { pRecovDictRec->pRec->Release(); pRecovDictRec->pRec = NULL; } f_memset( pNewDictRec, 0, sizeof( RECOV_DICT_REC)); } else { goto Exit; } break; case FLM_AREA_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG) { goto Remove_Rec; } else { goto Exit; } case FLM_RESERVED_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG) { goto Remove_Rec; } else { goto Exit; } case FLM_INDEX_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG || uiRecType == FLM_RESERVED_TAG) { goto Remove_Rec; } else { goto Exit; } default: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG || uiRecType == FLM_RESERVED_TAG || uiRecType == FLM_INDEX_TAG) { goto Remove_Rec; } else { goto Exit; } } break; } } // Link the record into the list of records that need to be // recovered. We don't add it right away, because we want to be sure // and do them in a certain order, as follows: // // 1. Field definitions // 2. Container definitions // 3. Area definitions // 4. Template definitions // 5. Index definitions // 6. Reserve definitions // 7. Other if( !pNewDictRec) { // All elements of pNewDictRec are initialized below. if( (pNewDictRec = (RECOV_DICT_REC *)GedPoolAlloc( &pRecovDictInfo->pool, sizeof( RECOV_DICT_REC))) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } pNewDictRec->pNext = NULL; pNewDictRec->bAdded = FALSE; } if( (pNewDictRec->pRec = pRecord->copy()) == NULL) { rc = RC_SET( FERR_MEM); } pNewDictRec->bGotFromDataRec = bGotFromDataRec; pNewDictRec->pRec->setID( uiDrn); pNewDictRec->uiBlkAddress = uiBlkAddress; pNewDictRec->uiElmOffset = uiElmOffset; pPrevDictRec = NULL; pRecovDictRec = pRecovDictInfo->pRecovRecs; while( pRecovDictRec) { switch( pRecovDictRec->pRec->getFieldID( pRecovDictRec->pRec->root())) { case FLM_FIELD_TAG: break; case FLM_CONTAINER_TAG: if( uiRecType == FLM_FIELD_TAG) { goto Insert_Rec; } break; case FLM_AREA_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG) { goto Insert_Rec; } break; case FLM_INDEX_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG) { goto Insert_Rec; } break; case FLM_RESERVED_TAG: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG || uiRecType == FLM_INDEX_TAG) { goto Insert_Rec; } break; default: if( uiRecType == FLM_FIELD_TAG || uiRecType == FLM_CONTAINER_TAG || uiRecType == FLM_AREA_TAG || uiRecType == FLM_INDEX_TAG || uiRecType == FLM_RESERVED_TAG) { goto Insert_Rec; } break; } pPrevDictRec = pRecovDictRec; pRecovDictRec = pRecovDictRec->pNext; } Insert_Rec: pNewDictRec->pNext = pRecovDictRec; if( pPrevDictRec) { pPrevDictRec->pNext = pNewDictRec; } else { pRecovDictInfo->pRecovRecs = pNewDictRec; } Exit: return( rc); } /*************************************************************************** Desc: This routine frees all of the recovery dictionary information. *****************************************************************************/ FSTATIC void bldFreeRecovDictInfo( RECOV_DICT_INFO * pRecovDictInfo) { if( pRecovDictInfo) { RECOV_DICT_REC * pDictRec; pDictRec = pRecovDictInfo->pRecovRecs; while (pDictRec) { if (pDictRec->pRec) { pDictRec->pRec->Release(); pDictRec->pRec = NULL; } pDictRec = pDictRec->pNext; } GedPoolFree( &pRecovDictInfo->pool); f_free( &pRecovDictInfo); } } /*************************************************************************** Desc: This routine adds all of the recovered dictionary records for a specific dictionary. It makes sure to do parent dictionaries first. *****************************************************************************/ FSTATIC RCODE bldDoDict( FDB * pDb, REBUILD_STATE * pRebuildState, RECOV_DICT_INFO * pDictToDo, FLMBOOL * pbStartedTransRV) { RCODE rc = FERR_OK; RECOV_DICT_REC * pDictRec; RECOV_DICT_REC * pFirstDictRecInTrans = NULL; LFILE * pDictLFile; STATUS_HOOK fnStatusFunc = pRebuildState->fnStatusFunc; REBUILD_INFO * pCallbackData = &pRebuildState->CallbackData; FLMBOOL bHaveLFile; FLMBOOL bAddedAtLeastOne; FLMBOOL bFailedAtLeastOne; FlmRecord * pSaveRec; FLMUINT uiRecordsPerTrans; FLMUINT uiRecordInTrans; FLMUINT uiDictRecId; // Commit the current update transaction if( *pbStartedTransRV) { *pbStartedTransRV = FALSE; if( RC_BAD( rc = flmCommitDbTrans( pDb, 0, FALSE))) { goto Exit; } } // Add all of the records for pDictToDo - one per transaction. bHaveLFile = FALSE; uiRecordsPerTrans = 100; Do_Dict_Recs: pDictRec = pDictToDo->pRecovRecs; bAddedAtLeastOne = FALSE; bFailedAtLeastOne = FALSE; uiRecordInTrans = 0; while (pDictRec) { if (!pDictRec->bAdded) { if( !uiRecordInTrans) { // Start an update transaction if( RC_BAD( rc = flmBeginDbTrans( pDb, FLM_UPDATE_TRANS, FLM_NO_TIMEOUT))) { goto Exit; } *pbStartedTransRV = TRUE; pFirstDictRecInTrans = pDictRec; } uiRecordInTrans++; // Don't optimize and keep pDictLFile as a local variable! if( RC_BAD( rc = fdictGetContainer( pDb->pDict, FLM_DICT_CONTAINER, &pDictLFile))) { rc = FERR_OK; goto Exit; } bHaveLFile = TRUE; // Call the status callback to give the application a chance // to change the definition record if( fnStatusFunc) { if( RC_BAD( rc = (*fnStatusFunc)( FLM_REBUILD_ADD_DICT_REC_STATUS, (void *)pDictRec->pRec, (void *)0, pRebuildState->AppArg))) { goto Exit; } } // Add the dictionary record. // NOTE: We are deliberately ignoring return codes here - so we // can attempt to process as many records as possible. uiDictRecId = pDictRec->pRec->getID(); if (RC_BAD( flmLFileDictUpdate( pDb, pDictLFile, &uiDictRecId, pDictRec->pRec, NULL, FALSE, FALSE, NULL, TRUE))) { *pbStartedTransRV = FALSE; (void)flmAbortDbTrans( pDb); bFailedAtLeastOne = TRUE; uiRecordInTrans = 0; } else if( (uiRecordInTrans >= uiRecordsPerTrans) || !pDictRec->pNext) { *pbStartedTransRV = FALSE; if (RC_BAD( flmCommitDbTrans( pDb, 0, FALSE))) { bFailedAtLeastOne = TRUE; } else { // Set bAdded to TRUE on all dict recs in transaction. while( pFirstDictRecInTrans != pDictRec) { pFirstDictRecInTrans->bAdded = TRUE; pFirstDictRecInTrans = pFirstDictRecInTrans->pNext; } pDictRec->bAdded = bAddedAtLeastOne = TRUE; pRebuildState->CallbackData.uiRecsRecov += uiRecordInTrans; if( fnStatusFunc) { if (RC_BAD( rc = (*fnStatusFunc)( FLM_REBUILD_STATUS, (void *)pCallbackData, (void *)0, pRebuildState->AppArg))) { goto Exit; } } f_yieldCPU(); } uiRecordInTrans = 0; } } pDictRec = pDictRec->pNext; } // Retry the add loop until either they all FAIL or they all succeed. // This is done to handle record template dependencies - or other // dependencies for that matter - that may not have been in the proper // order in the list. if( (bAddedAtLeastOne || uiRecordsPerTrans > 1) && bFailedAtLeastOne) { // We MUST do single record transactions from this point on // so the commit above will be called if the last pDictRec // was added on any prior pass. uiRecordsPerTrans = 1; goto Do_Dict_Recs; } // Log the records that failed pDictRec = pDictToDo->pRecovRecs; pSaveRec = pRebuildState->pRecord; while (pDictRec) { if (!pDictRec->bAdded) { RCODE TempRc; pRebuildState->pRecord = pDictRec->pRec; if (RC_BAD( TempRc = bldReportReason( pRebuildState, FLM_DICT_REC_ADD_ERR, pDictRec->uiBlkAddress, pDictRec->uiElmOffset, pDictRec->pRec->getID(), 0xFFFF, 0))) { rc = TempRc; } } pDictRec = pDictRec->pNext; } pRebuildState->pRecord = pSaveRec; Exit: bldFreeRecovDictInfo( pDictToDo); if ((RC_OK( rc)) && (!(*pbStartedTransRV))) { if (RC_OK( rc = flmBeginDbTrans( pDb, FLM_UPDATE_TRANS, FLM_NO_TIMEOUT))) { *pbStartedTransRV = TRUE; } } return( rc); } /**************************************************************************** Desc: Extract create options from file header and log header pieces. ****************************************************************************/ void flmGetCreateOpts( FILE_HDR * pFileHdr, FLMBYTE * pucLogHdr, CREATE_OPTS * pCreateOpts) { f_memset( pCreateOpts, 0, sizeof( CREATE_OPTS)); if (pFileHdr) { pCreateOpts->uiBlockSize = pFileHdr->uiBlockSize; pCreateOpts->uiVersionNum = pFileHdr->uiVersionNum; pCreateOpts->uiDefaultLanguage = pFileHdr->uiDefaultLanguage; pCreateOpts->uiAppMajorVer = pFileHdr->uiAppMajorVer; pCreateOpts->uiAppMinorVer = pFileHdr->uiAppMinorVer; } else { pCreateOpts->uiBlockSize = DEFAULT_BLKSIZ; pCreateOpts->uiVersionNum = FLM_CUR_FILE_FORMAT_VER_NUM; pCreateOpts->uiDefaultLanguage = DEFAULT_LANG; // uiAppMajorVer and uiAppMinorVer are already zero. } if (pucLogHdr) { pCreateOpts->uiMinRflFileSize = (FLMUINT)FB2UD( &pucLogHdr [LOG_RFL_MIN_FILE_SIZE]); pCreateOpts->uiMaxRflFileSize = (FLMUINT)FB2UD( &pucLogHdr [LOG_RFL_MAX_FILE_SIZE]); pCreateOpts->bKeepRflFiles = (FLMBOOL)((pucLogHdr [LOG_KEEP_RFL_FILES]) ? TRUE : FALSE); pCreateOpts->bLogAbortedTransToRfl = (FLMBOOL)((pucLogHdr [LOG_KEEP_ABORTED_TRANS_IN_RFL]) ? TRUE : FALSE); } else { pCreateOpts->uiMinRflFileSize = DEFAULT_MIN_RFL_FILE_SIZE; pCreateOpts->uiMaxRflFileSize = DEFAULT_MAX_RFL_FILE_SIZE; pCreateOpts->bKeepRflFiles = DEFAULT_KEEP_RFL_FILES_FLAG; pCreateOpts->bLogAbortedTransToRfl = DEFAULT_LOG_ABORTED_TRANS_FLAG; } } /**************************************************************************** Desc: Rebuilds a damaged database. Notes: This routine performs the following actions: 1) A temporary database is created; 2) A copy of the source database is saved; 3) The source database is scanned. Data records from all containers are extracted and stored in the temporary database. 4) When the rebuild is complete, the temporary database file is copied over the source database file. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmDbRebuild( const char * pszSourceDbPath, const char * pszSourceDataDir, const char * pszDestDbPath, const char * pszDestDataDir, const char * pszDestRflDir, const char * pszDictPath, CREATE_OPTS * pCreateOpts, FLMUINT * puiTotRecsRV, FLMUINT * puiRecsRecovRV, STATUS_HOOK fnStatusFunc, void * pvStatusData) { RCODE rc = FERR_OK; FDB * pDb = NULL; FFILE * pFile; F_SuperFileHdl * pSFileHdl = NULL; FLMBOOL bFileLocked = FALSE; FLMBOOL bWriteLocked = FALSE; REBUILD_STATE * pRebuildState = NULL; HDR_INFO * pHdrInfo; CREATE_OPTS * pDefaultCreateOpts = NULL; FLMUINT uiTransID; ServerLockObject * pWriteLockObj = NULL; ServerLockObject * pFileLockObj = NULL; FLMBOOL bMutexLocked = FALSE; F_FileHdlImp * pLockFileHdl = NULL; FLOCK_INFO LockInfo; FLMUINT uiFileNumber; FLMUINT uiDbVersion = 0; FLMBOOL bUsedFFile = FALSE; FLMBOOL bBadHeader = FALSE; FlmECache * pECacheMgr = NULL; f_mutexLock( gv_FlmSysData.hShareMutex); bMutexLocked = TRUE; // See if there is an FFILE structure for this file // May unlock and re-lock the global mutex. if( RC_BAD( rc = flmFindFile( pszSourceDbPath, pszSourceDataDir, &pFile))) { goto Exit; } // If we didn't find an FFILE structure, get an // exclusive lock on the file. if( !pFile) { f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; // Attempt to get an exclusive lock on the file. if( RC_BAD( rc = flmCreateLckFile( pszSourceDbPath, &pLockFileHdl))) { goto Exit; } } else { if( RC_BAD( rc = flmCheckFFileState( pFile))) { goto Exit; } // The call to flmVerifyFileUse will wait if the file is in // the process of being opened by another thread. if (RC_BAD( rc = flmVerifyFileUse( gv_FlmSysData.hShareMutex, &pFile))) { goto Exit; } // Increment the use count on the FFILE so it will not // disappear while we are copying the file. if (++pFile->uiUseCount == 1) { flmUnlinkFileFromNUList( pFile); } bUsedFFile = TRUE; f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; // See if the thread already has a file lock. If so, there // is no need to obtain another. However, we also want to // make sure there is no write lock. If there is, // we cannot do the rebuild right now. pFile->pFileLockObj->GetLockInfo( (FLMINT)0, &LockInfo); if (LockInfo.eCurrLockType == FLM_LOCK_EXCLUSIVE && LockInfo.uiThreadId == f_threadId()) { // See if there is already a transaction going. pFile->pWriteLockObj->GetLockInfo( (FLMINT)0, &LockInfo); if ((LockInfo.eCurrLockType == FLM_LOCK_EXCLUSIVE) && (LockInfo.uiThreadId == f_threadId())) { rc = RC_SET( FERR_TRANS_ACTIVE); goto Exit; } } else { pFileLockObj = pFile->pFileLockObj; pFileLockObj->AddRef(); if (RC_BAD( rc = pFileLockObj->Lock( TRUE, NULL, FALSE, TRUE, FLM_NO_TIMEOUT, 0))) { goto Exit; } bFileLocked = TRUE; } // Lock the write object to eliminate contention with // the checkpoint thread. pWriteLockObj = pFile->pWriteLockObj; pWriteLockObj->AddRef(); // Only contention here is with the checkpoint thread. // Wait forever for the checkpoint thread to give // up the lock. if (RC_BAD( rc = dbWriteLock( pFile))) { goto Exit; } bWriteLocked = TRUE; } if( RC_BAD( rc = f_calloc( sizeof( REBUILD_STATE), &pRebuildState))) { goto Exit; } if( RC_BAD( rc = f_calloc( sizeof( HDR_INFO), &pRebuildState->pHdrInfo))) { goto Exit; } if( RC_BAD( rc = f_calloc( sizeof( STATE_INFO), &pRebuildState->pStateInfo))) { goto Exit; } if( RC_BAD( rc = f_calloc( sizeof( CREATE_OPTS), &pDefaultCreateOpts))) { goto Exit; } if( RC_BAD( rc = f_calloc( LOG_HEADER_SIZE, &pRebuildState->pLogHdr))) { goto Exit; } if( RC_BAD( rc = f_calloc( MAX_KEY_SIZ, &pRebuildState->pKeyBuffer))) { goto Exit; } pRebuildState->AppArg = pvStatusData; pRebuildState->fnStatusFunc = fnStatusFunc; pHdrInfo = pRebuildState->pHdrInfo; /* Open the corrupted database. */ if ((pSFileHdl = f_new F_SuperFileHdl) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } if( RC_BAD( rc = pSFileHdl->Setup( NULL, pszSourceDbPath, pszSourceDataDir))) { goto Exit; } pRebuildState->pSFileHdl = pSFileHdl; /* Check the header information to see if we were in the middle of a previous copy. */ if (RC_OK( rc = flmGetHdrInfo( pSFileHdl, &pHdrInfo->FileHdr, &pHdrInfo->LogHdr, pRebuildState->pLogHdr))) { if (!pCreateOpts) { flmGetCreateOpts( &pHdrInfo->FileHdr, pRebuildState->pLogHdr, pDefaultCreateOpts); pCreateOpts = pDefaultCreateOpts; } rc = FERR_OK; uiDbVersion = pHdrInfo->FileHdr.uiVersionNum; pRebuildState->uiMaxFileSize = flmGetMaxFileSize( uiDbVersion, pRebuildState->pLogHdr); } else if ((rc == FERR_BLOCK_CHECKSUM) || (rc == FERR_INCOMPLETE_LOG) || (rc == FERR_DATA_ERROR) || ((rc == FERR_UNSUPPORTED_VERSION) && (pHdrInfo->FileHdr.uiVersionNum == 0))) { if ((rc == FERR_BLOCK_CHECKSUM) || (rc == FERR_DATA_ERROR)) { bBadHeader = TRUE; } rc = FERR_OK; if (!pCreateOpts) { flmGetCreateOpts( &pHdrInfo->FileHdr, pRebuildState->pLogHdr, pDefaultCreateOpts); pCreateOpts = pDefaultCreateOpts; } uiDbVersion = pHdrInfo->FileHdr.uiVersionNum; pRebuildState->uiMaxFileSize = flmGetMaxFileSize( uiDbVersion, pRebuildState->pLogHdr); } else if (rc == FERR_UNSUPPORTED_VERSION || rc == FERR_NEWER_FLAIM) { goto Exit; } else if ((rc == FERR_NOT_FLAIM) || (!VALID_BLOCK_SIZE( pHdrInfo->FileHdr.uiBlockSize))) { FLMUINT uiSaveBlockSize; FLMUINT uiCalcBlockSize = 0; FLMBYTE ucFileHdrBuf[ FLM_FILE_HEADER_SIZE]; uiDbVersion = (FLMUINT)((rc != FERR_NOT_FLAIM) ? pHdrInfo->FileHdr.uiVersionNum : FLM_CUR_FILE_FORMAT_VER_NUM); pSFileHdl->SetDbVersion( uiDbVersion); pRebuildState->uiMaxFileSize = flmGetMaxFileSize( uiDbVersion, pRebuildState->pLogHdr); if (!pCreateOpts) { if (rc != FERR_NOT_FLAIM) { flmGetCreateOpts( &pHdrInfo->FileHdr, pRebuildState->pLogHdr, pDefaultCreateOpts); } else { flmGetCreateOpts( NULL, NULL, pDefaultCreateOpts); } // Set block size to zero, so we will always take the calculated // block size below. pDefaultCreateOpts->uiBlockSize = 0; pCreateOpts = pDefaultCreateOpts; } /* Try to determine the correct block size. */ if (RC_BAD( rc = bldDetermineBlkSize( pSFileHdl, pRebuildState->uiMaxFileSize, &uiCalcBlockSize, fnStatusFunc, &pRebuildState->CallbackData, pRebuildState->AppArg))) { goto Exit; } uiSaveBlockSize = pCreateOpts->uiBlockSize; pCreateOpts->uiBlockSize = uiCalcBlockSize; // Initialize pHdrInfo->FileHdr to useable values. flmInitFileHdrInfo( pCreateOpts, &pHdrInfo->FileHdr, ucFileHdrBuf); /* Only use the passed-in block size (uiSaveBlockSize) if it was non-zero. */ if (uiSaveBlockSize) pCreateOpts->uiBlockSize = uiSaveBlockSize; } else { goto Exit; } // Calculate the file size. pSFileHdl->SetDbVersion( uiDbVersion); pRebuildState->CallbackData.ui64DatabaseSize = 0; for (uiFileNumber = 1;;uiFileNumber++) { FLMUINT uiTmpSize; if (RC_BAD( pSFileHdl->GetFileSize( uiFileNumber, &uiTmpSize))) { break; } pRebuildState->CallbackData.ui64DatabaseSize += (FLMUINT64)uiTmpSize; } // Delete the destination database in case it already exists. if (RC_BAD( rc = FlmDbRemove( pszDestDbPath, pszDestDataDir, pszDestRflDir, TRUE))) { if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH) { rc = FERR_OK; } else { goto Exit; } } /* If no block size has been specified or determined yet, use what we read from the file header. */ if (!pCreateOpts->uiBlockSize) pCreateOpts->uiBlockSize = pHdrInfo->FileHdr.uiBlockSize; pSFileHdl->SetDbVersion( pHdrInfo->FileHdr.uiVersionNum); pSFileHdl->SetBlockSize( pHdrInfo->FileHdr.uiBlockSize); /* Set the ECache manger into the super file handle */ if( pFile && pFile->pECacheMgr) { pSFileHdl->setECacheMgr( pFile->pECacheMgr); } else if( gv_FlmSysData.bOkToUseESM) { if( (pECacheMgr = f_new FlmECache) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } if( !pECacheMgr->setupECache( pHdrInfo->FileHdr.uiBlockSize, pRebuildState->uiMaxFileSize)) { pECacheMgr->Release(); pECacheMgr = NULL; } else { pSFileHdl->setECacheMgr( pECacheMgr); } } /* When creating the new file, set the transaction ID to one greater than it is in the corrupt file. However, don't let it get greater than about 2 billion - want to leave room for 2 billion transactions in case they were corrupted somehow in our old file. */ uiTransID = ((FLMUINT)FB2UD( &pRebuildState->pLogHdr [LOG_CURR_TRANS_ID]) + 1) & 0x7FFFFFFF; if (RC_BAD( rc = flmCreateNewFile( pszDestDbPath, pszDestDataDir, pszDestRflDir, pszDictPath, NULL, pCreateOpts, uiTransID, (FDB * *)&pRebuildState->hDb, pRebuildState))) { goto Exit; } pDb = (FDB *)pRebuildState->hDb; /* Rebuild the database */ if (RC_BAD( rc = flmDbRebuildFile( pRebuildState, bBadHeader))) { goto Exit; } Exit: /* Close the temporary database, if it is still open. */ if (pDb) { FFILE * pTmpFile; FFILE * pTmpFile1; // Get the FFILE pointer for the temporary file before closing it. pTmpFile = pDb->pFile; (void)FlmDbClose( (HFDB *)&pDb); // Force temporary FFILE structure to be cleaned up, if it // isn't already gone. The following code searches for the // temporary file in the not-used list. If it finds it, // it will unlink it. if (!bMutexLocked) { f_mutexLock( gv_FlmSysData.hShareMutex); bMutexLocked = TRUE; } pTmpFile1 = gv_FlmSysData.pLrnuFile; while (pTmpFile1) { if (pTmpFile1 == pTmpFile) { flmFreeFile( pTmpFile); break; } pTmpFile1 = pTmpFile1->pNextNUFile; } } if (bUsedFFile) { if (!bMutexLocked) { f_mutexLock( gv_FlmSysData.hShareMutex); bMutexLocked = TRUE; } if (!(--pFile->uiUseCount)) { flmLinkFileToNUList( pFile); } } if (bMutexLocked) { f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; } /* Unlock the file, if it is locked. */ if (bWriteLocked) { dbWriteUnlock( pFile); bWriteLocked = FALSE; } if (bFileLocked) { RCODE rc3; if (RC_BAD( rc3 = pFileLockObj->Unlock( TRUE, NULL))) { if (RC_OK( rc)) { rc = rc3; } } bFileLocked = FALSE; } if( pSFileHdl) { pSFileHdl->Release(); } if( pECacheMgr) { pECacheMgr->Release(); pECacheMgr = NULL; } if (pWriteLockObj) { pWriteLockObj->Release(); pWriteLockObj = NULL; } if (pFileLockObj) { pFileLockObj->Release(); pFileLockObj = NULL; } if (pLockFileHdl) { (void)pLockFileHdl->Close(); pLockFileHdl->Release(); pLockFileHdl = NULL; } if( pDefaultCreateOpts) { f_free( &pDefaultCreateOpts); } if (pRebuildState) { if( puiTotRecsRV) { *puiTotRecsRV = pRebuildState->CallbackData.uiTotRecs; } if( puiRecsRecovRV) { *puiRecsRecovRV = pRebuildState->CallbackData.uiRecsRecov; } if ((pRebuildState->pStateInfo) && (pRebuildState->pStateInfo->pRecord)) { pRebuildState->pStateInfo->pRecord->Release(); } if (pRebuildState->pRecord) { pRebuildState->pRecord->Release(); pRebuildState->pRecord = NULL; } if( pRebuildState->pHdrInfo) { f_free( &pRebuildState->pHdrInfo); } if( pRebuildState->pStateInfo) { f_free( &pRebuildState->pStateInfo); } if( pRebuildState->pLogHdr) { f_free( &pRebuildState->pLogHdr); } if( pRebuildState->pKeyBuffer) { f_free( &pRebuildState->pKeyBuffer); } f_free( &pRebuildState); } else { if( puiTotRecsRV) { *puiTotRecsRV = 0; } if( puiRecsRecovRV) { *puiRecsRecovRV = 0; } } return( rc); } /*************************************************************************** Desc: This routine reads through a database and makes a best guess as to the true block size of the database. *****************************************************************************/ FSTATIC RCODE bldDetermineBlkSize( F_SuperFileHdl * pSFileHdl, // Super file handle for database. FLMUINT uiMaxFileSize, FLMUINT * puiBlkSizeRV, // Calculated block size is returned here. STATUS_HOOK fnStatusFunc, // Callback function. REBUILD_INFO * pCallbackData, // Callback structure. void * AppArg) // User data for callback. { RCODE rc = FERR_OK; FLMBYTE ucBlkHeader [BH_OVHD]; FLMUINT uiBytesRead; FLMUINT uiBlkAddress; FLMUINT uiFileNumber = 0; FLMUINT uiOffset = 0; FLMUINT uiCount4K = 0; FLMUINT uiCount8K = 0; FLMUINT64 ui64BytesDone = 0; F_FileHdlImp * pFileHdl = NULL; /* Start from byte offset 0 in the first file. */ pCallbackData->iDoingFlag = REBUILD_GET_BLK_SIZ; pCallbackData->bStartFlag = TRUE; for (;;) { if (uiOffset >= uiMaxFileSize || !uiFileNumber) { uiOffset = 0; uiFileNumber++; if (RC_BAD( rc = pSFileHdl->GetFileHdl( uiFileNumber, FALSE, &pFileHdl))) { if (rc == FERR_IO_PATH_NOT_FOUND) { rc = RC_SET( FERR_IO_END_OF_FILE); break; } goto Exit; } } if ((RC_OK(rc = pFileHdl->Read( uiOffset, BH_OVHD, ucBlkHeader, &uiBytesRead))) || (rc == FERR_IO_END_OF_FILE)) { if (RC_OK( rc)) { ui64BytesDone += (FLMUINT64)MIN_BLOCK_SIZE; } else { ui64BytesDone += (FLMUINT64)uiBytesRead; } uiBlkAddress = GET_BH_ADDR( ucBlkHeader); if ((uiBytesRead == BH_OVHD) && (FSGetFileOffset( uiBlkAddress) == uiOffset)) { if (uiOffset % 4096 == 0) uiCount4K++; if (uiOffset % 8192 == 0) uiCount8K++; } if (rc != FERR_OK || uiBytesRead < BH_OVHD) { // Even if the file is not full size, set offset to // the maximum file offset so we will attempt to go // to the next file at the top of this loop. If that // fails, we will assume we truly are at EOF. uiOffset = uiMaxFileSize; } else { uiOffset += MIN_BLOCK_SIZE; } /* Call the callback function to report copy progress. */ if (fnStatusFunc != NULL) { pCallbackData->ui64BytesExamined = ui64BytesDone; if (RC_BAD( rc = (*fnStatusFunc)( FLM_REBUILD_STATUS, (void *)pCallbackData, (void *)0, AppArg))) { goto Exit; } pCallbackData->bStartFlag = FALSE; } f_yieldCPU(); } else { goto Exit; } } if (rc == FERR_IO_END_OF_FILE) rc = FERR_OK; // If our count of 4K blocks is greater than 66% of the number // of 4K blocks that would fit in the database, we will use // a 4K block size. Otherwise, we will use an 8K block size. if (uiCount4K > (FLMUINT)(((ui64BytesDone / (FLMUINT64)4096) * (FLMUINT64)66) / (FLMUINT64)100)) { *puiBlkSizeRV = 4096; } else { *puiBlkSizeRV = 8192; } Exit: return( rc); }