{*********************************************************} {* FlashFiler: Table verification & repair component *} {*********************************************************} (* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is TurboPower FlashFiler * * The Initial Developer of the Original Code is * TurboPower Software * * Portions created by the Initial Developer are Copyright (C) 1996-2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** *) {$I ffdefine.inc} unit ffrepair; { TODO:: Have to handle multi-file tables. } { Current limitation: Block 0 must have a valid signature, ThisBlock = 0, and NextBlock must be equal to ffc_W32NoValue. } interface uses Classes, FFChain, FFLLBase, FFFileInt, FFRepCnst; const ciErrorLimit = 10000; { The default error limit for verification. Once this many errors has been found, the verification process will stop. The repair process is not subject to this limit. } type TffRepairEngine = class; { forward declaration } { Use this component to verify & repair FlashFiler tables. This class will look for a registered instance of TffFileInterface corresponding to the FF version of the table being verified/repaired. Description of use: 1. Decide what is to be verified and/or repaired. The items that may be verified are declared in the TffRepairItem enum. By default, all items are verified. To change the items to be verified, use the Items property of the TffRepair class. 2. If verifying, decide how many errors may be encountered before the verification process stops. By default, the verification process will stop after 100 errors have been encountered. To change this value, use the ErrorLimit property. To have verification process the entire table regardless of the number of errors, set the ErrorLimit property to the value zero. 3. Decide whether the table is to be verified or verified & repaired. Verification tells you whether the table contains any structural or content errors. Repair performs a verification and attempts to correct the errors. See the Repair Procedures section below to determine how errors are corrected. If the table is to be verified but not repaired, call the Verify method. If the table is to be verified & repaired, call the Repair method. Even though the Verify method was previously called, the Repair method will once again Verify the entire table. 4. TODO:: verify/repair progress 5. TODO:: verify/repair error reporting REPAIR PROCEDURES TODO:: } TffRepairState = (rmIdle, { Not doing anything } rmAcquireInfo, { Acquiring information from repair engine } rmVerify, { Verifying/Checking } rmRepair { Repairing identified problem } ); TffRepairItem = (riNone, riFileHeader, { Check the file header } riBlockScan, { Verify block headers } riCheckDictionary, { Check the dictionary } riBlockChains, { Verify the free & data block chains } riDeletedBLOBChain, { Verify deleted blob segment chain } riReindex, { Rebuilding an index } riPack { Packing the table } ); TffRepairItems = set of TffRepairItem; TffRepairProgressEvent = { Event raised so that parent application may check progress. } procedure(Repairer : TffRepairEngine; State : TffRepairState; Item : TffRepairItem; const ActionStr : string; const Position, Maximum : Integer) of object; TffRepairEngine = class(TObject) { Use this component to verify & repair FlashFiler tables. } protected FAbort : Boolean; FChainMgrData, FChainMgrFree : TffChainMgr; FCompleted : TNotifyEvent; FCurrentItem : TffRepairItem; { The current item being verified or repaired. } FErrorCodes : TList; FErrorLimit : Integer; FErrors : TStringList; FFileInterface: TffFileInterface; FFixCodes : TList; FFixes : TStringList; FHighestAction : TffRepairAction; { Based upon the errors reported by the file interface, the most serious action that must be taken to repair the table. } FInfo : TffGeneralFileInfo; FItems : TffRepairItems; FOnProgress : TffRepairProgressEvent; FOnReportError : TffReportErrorEvent; FOnReportFix : TffReportFixEvent; FOutputVersion : Longint; FState : TffRepairState; FUnknownBlocks : TList; FUsedBlocksValid : Boolean; procedure CheckLastBlock; procedure ClearChainMgrs; procedure ClearErrors; procedure ClearFileInterface; procedure DoReportError(Block : ICommonBlock; const ErrCode : Integer; args : array of const); procedure DoReportFix(Block : ICommonBlock; const ErrCode : Integer; args : array of const); procedure FixUnknownBlock(const BlockNum : TffWord32; FileHeaderBlock : IFileHeaderBlock); function GetDictBlock(const Inx : Longint) : IStreamBlock; function GetDictBlockCount : Longint; function GetErrorCodes(const Inx : Integer) : Integer; function GetErrorCount : Integer; function GetErrors(const Inx : Integer) : string; function GetFixCodes(const Inx : Integer) : Integer; function GetFixCount : Integer; function GetFixes(const Inx : Integer) : string; procedure GetInfo(var Info : TffGeneralFileInfo); procedure HandleRebuildProgress(FileInterface : TffFileInterface; Position, Maximum : Integer); procedure HandleReportError(Block : ICommonBlock; const ErrCode : Integer; const ErrorStr : string); procedure HandleReportFix(Block : ICommonBlock; const ErrCode : Integer; const RepairStr : string); procedure LinkDataCallback(const Block1Num, Block2Num : TffWord32); function MapItemToActionStr(const Item : TffRepairItem; const State : TffRepairState) : string; procedure MoveDataOrphanCallback(const BlockMoved, PrevBlock : TffWord32); procedure PopulateChainMgrs; procedure ReportProgress(const Position, Maximum : Integer); procedure SetErrorLimit(const Value : Integer); procedure SetItems(const Value : TffRepairItems); procedure VerifyRepair; { This method is called by both the Verify & Repair methods. This method centralizes the logic for verifying & repairing the file. } public procedure Close; { Closes the currently open file. } function GetBlock(const BlockNumber : Longint) : ICommonBlock; { Returns the specified block. } function GetFileHeaderBlock : IFileHeaderBlock; { Returns the file header block for the open file. } function GetIndexHeaderBlock : IIndexHeaderBlock; { Returns the index header block for the open file. } function GetFreeChainDetails : TStringList; { Returns a string list containing information about the chain of free blocks. } function GetDataChainDetails : TStringList; { Returns a string list containing information about the chain of data blocks. } procedure Open(const FileName : string); { Open a file. } procedure Repair; { This method will verify &, if one or more errors are encountered, repair the currently open table. } procedure Verify; { This method will verify the structure & content of the currently open table. } { Properties } property Aborted : Boolean read FAbort; { Returns True if the previous verify was aborted. } property DictBlockCount : Integer read GetDictBlockCount; { Returns the number of data dictionary blocks in the file. } property DictBlocks[const Inx : Longint] : IStreamBlock read GetDictBlock; { Returns the specified data dictionary block. } property ErrorCodes[const Inx : Integer] : Integer read GetErrorCodes; { Use this property to access the error code associated with each flaw found in the file. There is a one-to-one correspondence between the elements in this property & the elements in the Errors property. } property ErrorCount : Integer read GetErrorCount; { Use this property to determine the number of errors encountered during a verify or repair process. } property Errors[const Inx : Integer] : string read GetErrors; { Use this property to access the descriptive message associated with each error. There is a one-to-one correspondence between the elements in this property & the elements in the ErrorCodes property. } property FixCodes[const Inx : Integer] : Integer read GetFixCodes; { Use this property to access the error code associated with each fix made to the file. There is a one-to-one correspondence between the elements in this property & the elements in the Fixes property. } property FixCount : Integer read GetFixCount; { Returns the number of errors fixed by a repair operation. } property Fixes[const Inx : Integer] : string read GetFixes; { Use this property to access the descriptive message associated with each fix made to the file. Note there is not a one-to-one correspondence between the Fixes and the Errors. There is a one-to-one correspondence between the elements in this property & the elements in the FixCodes property. } property OutputVersion : Longint read FOutputVersion write FOutputVersion; { The FF version to be assigned to the table when the table is packed. Defaults to the current FF version. } property State : TffRepairState read FState; { Returns the current state of the repair engine. } published constructor Create; destructor Destroy; override; property ErrorLimit : Integer read FErrorLimit write SetErrorLimit default ciErrorLimit; { Use this property to have the verification process stop after a certain number of errors have been reached. To have the verification process analyze the entire table regardless of the number of errors, set this property to the value zero. Note that this value is ignored by the repair process. The default value is 10. } property Items : TffRepairItems read FItems write SetItems; { Use this property to control the items that are analyzed & repaired. By default, all items are analyzed & repaired. } property OnComplete : TNotifyEvent read FCompleted write FCompleted; { This event is raised when a repair run has completed. } property OnProgress : TffRepairProgressEvent read FOnProgress write FOnProgress; { This event is raised as a repair run progresses. } property OnReportError : TffReportErrorEvent read FOnReportError write FOnReportError; { This event is raised when an error is detected in a block. This event will be raised during both verification & repair. } property OnReportFix : TffReportFixEvent read FOnReportFix write FOnReportFix; { This event is raised when an error is fixed. It is raised only during the repair of a file. } end; implementation uses FFSrBase, SysUtils; const csIdle = ' only when the repair engine is idle.'; {===TffRepair========================================================} constructor TffRepairEngine.Create; var Item : TffRepairItem; begin inherited; FErrorLimit := ciErrorLimit; FErrorCodes := TList.Create; FErrors := TStringList.Create; FFixCodes := TList.Create; FFixes := TStringList.Create; FOutputVersion := FFVersionNumber; FUnknownBlocks := TList.Create; for Item := Low(TffRepairItem) to High(TffRepairItem) do Include(FItems, Item); end; {--------} destructor TffRepairEngine.Destroy; begin ClearChainMgrs; FErrorCodes.Free; FErrors.Free; FFixCodes.Free; FFixes.Free; FUnknownBlocks.Free; ClearFileInterface; inherited; end; {--------} procedure TffRepairEngine.ClearChainMgrs; begin FChainMgrData.Free; FChainMgrData := nil; FChainMgrFree.Free; FChainMgrFree := nil; end; {--------} procedure TffRepairEngine.ClearErrors; begin FAbort := False; FErrorCodes.Clear; FErrors.Clear; FFixCodes.Clear; FFixes.Clear; FHighestAction := raDecide; FUnknownBlocks.Clear; end; {--------} procedure TffRepairEngine.ClearFileInterface; begin if FFileInterface <> nil then begin FInfo.Free; FFileInterface.Close; FFileInterface := nil; end; end; {--------} procedure TffRepairEngine.Close; begin if FState = rmIdle then ClearFileInterface else raise Exception.Create('The Close method can be called' + csIdle); end; {--------} procedure TffRepairEngine.DoReportError(Block : ICommonBlock; const ErrCode : Integer; args : array of const); begin HandleReportError(Block, ErrCode, Format(rcErrStr[ErrCode], args)); end; {--------} procedure TffRepairEngine.DoReportFix(Block : ICommonBlock; const ErrCode : Integer; args : array of const); begin HandleReportFix(Block, ErrCode, Format(rcFixStr[ErrCode], args)); end; {--------} procedure TffRepairEngine.FixUnknownBlock(const BlockNum : TffWord32; FileHeaderBlock : IFileHeaderBlock); var PotentialFirstBlock, PotentialLastBlock, RefBlock : TffWord32; Block : ICommonBlock; DataBlock : IDataBlock; begin PotentialFirstBlock := ffc_W32NoValue; PotentialLastBlock := ffc_W32NoValue; { Make sure this block is not referenced in the data chain. We assume that since it is an unknown block then it will not be a member of the data chain. } if FChainMgrData.Referenced(BlockNum, True, RefBlock) then begin DataBlock := FFileinterface.GetBlock(RefBlock) as IDataBlock; DataBlock.BeginUpdate; try if DataBlock.PrevDataBlock = BlockNum then begin PotentialFirstBlock := DataBlock.BlockNum; DataBlock.PrevDataBlock := ffc_W32NoValue end else begin PotentialLastBlock := DataBlock.BlockNum; DataBlock.NextDataBlock := ffc_W32noValue; end; { if..else } finally DataBlock.EndUpdate; DataBlock := nil; end; end; { Is the block referenced in the free block chain? } if not FChainMgrFree.Referenced(BlockNum, False, RefBlock) then begin { It is not referenced. Get the first free block. } RefBlock := FileHeaderBlock.FirstFreeBlock; { Does the first free block already point to this block? } if RefBlock <> BlockNum then begin { No. Have the unknown block point to the block listed as the first free block. } Block := FFileInterface.GetBlock(BlockNum); Block.BeginUpdate; try Block.NextBlock := FileHeaderBlock.FirstFreeBlock; finally Block.EndUpdate; Block := nil; end; { Set the first free block to be the unknown block. } FileHeaderBlock.BeginUpdate; try FileHeaderBlock.FirstFreeBlock := BlockNum; finally FileHeaderBlock.EndUpdate; end; end else begin { Yes, it is already pointed to by the file header. Add the unknown block to the free block chain manager. } Block := FFileInterface.GetBlock(BlockNum); FChainMgrFree.AddBlock(BlockNum, Block.NextBlock, ffc_W32NoValue); Block := nil; end; { if..else } end; { Is the block referenced in the file header? } if FileHeaderBlock.FirstDataBlock = BlockNum then begin { Update the file header with the first data block. } if PotentialFirstBlock <> ffc_W32NoValue then begin FileHeaderBlock.BeginUpdate; try FileHeaderBlock.FirstDataBlock := PotentialFirstBlock; finally FileHeaderBlock.EndUpdate; end; end else begin { This will be handled later when the data chain is reviewed. } end; { if..else } end; if FileHeaderBlock.LastDataBlock = BlockNum then begin { Update the file header with the last data block. } if PotentialLastBlock <> ffc_W32NoValue then begin FileHeaderBlock.BeginUpdate; try FileHeaderBlock.LastDataBlock := PotentialLastBlock; finally FileHeaderBlock.EndUpdate; end; end else begin { This will be handled later when the data chain is reviewed. } end; { if..else } end; end; {--------} function TffRepairEngine.GetDictBlock(const Inx : Longint) : IStreamBlock; begin { TODO:: Verify state of repair engine } Result := FFileInterface.DictBlocks[Inx]; end; {--------} function TffRepairEngine.GetDictBlockCount : Longint; begin { TODO:: Verify state of repair engine } Result := FFileInterface.DictBlockCount; end; {--------} function TffRepairEngine.GetErrorCodes(const Inx : Integer) : Integer; begin { TODO:: Verify state of repair engine } Result := Integer(FErrorCodes[Inx]); end; {--------} function TffRepairEngine.GetErrorCount : Integer; begin { TODO:: Verify state of repair engine } Result := FErrors.Count; end; {--------} function TffRepairEngine.GetErrors(const Inx : Integer) : string; begin { TODO:: Verify state of repair engine } Result := FErrors[Inx]; end; {--------} function TffRepairEngine.GetFixCodes(const Inx : Integer) : Integer; begin { TODO:: Verify state of repair engine } Result := Integer(FFixCodes[Inx]); end; {--------} function TffRepairEngine.GetFixCount : Integer; begin { TODO:: Verify state of repair engine } Result := FFixes.Count; end; {--------} function TffRepairEngine.GetFixes(const Inx : Integer) : string; begin { TODO:: Verify state of repair engine } Result := FFixes[Inx]; end; {--------} function TffRepairEngine.GetBlock(const BlockNumber : Longint) : ICommonBlock; begin { TODO:: Verify state of repair engine } Result := FFileInterface.GetBlock(BlockNumber); if Result <> nil then Result.OnGetInfo := GetInfo; end; {--------} function TffRepairEngine.GetFileHeaderBlock : IFileHeaderBlock; begin { TODO:: Verify state of repair engine } Result := FFileInterface.GetFileHeaderBlock; if Result <> nil then Result.OnGetInfo := GetInfo; end; {--------} function TffRepairEngine.GetFreeChainDetails : TStringList; begin PopulateChainMgrs; Result := FChainMgrFree.Describe; end; {--------} function TffRepairEngine.GetIndexHeaderBlock : IIndexHeaderBlock; begin { TODO:: Verify state of repair engine } Result := FFileInterface.GetIndexHeaderBlock; if Result <> nil then Result.OnGetInfo := GetInfo; end; {--------} procedure TffRepairEngine.GetInfo(var Info : TffGeneralFileInfo); begin Info := FInfo; end; {--------} function TffRepairEngine.GetDataChainDetails : TStringList; begin PopulateChainMgrs; Result := FChainMgrData.Describe; end; {--------} procedure TffRepairEngine.HandleRebuildProgress(FileInterface : TffFileInterface; Position, Maximum : Integer); begin ReportProgress(Position, Maximum); end; {--------} procedure TffRepairEngine.HandleReportError(Block : ICommonBlock; const ErrCode : Integer; const ErrorStr : string); begin if Block = nil then FErrors.Add(Format('Code %d: %s', [ErrCode, ErrorStr])) else FErrors.Add(Format('Block %d, code %d: %s', [Block.BlockNum, ErrCode, ErrorStr])); FErrorCodes.Add(Pointer(Errcode)); { Record the most severe action that must be taken to repair this file. } if rcAction[ErrCode] > FHighestAction then FHighestAction := rcAction[ErrCode]; { Detect errors that must be handled at this level. } if ErrCode = rciInvalidUsedBlocks then { Indicate that the used blocks field in the file header is invalid. } FUsedBlocksValid := False else if ErrCode = rciUnknownBlockType then { The block type is not valid. When repairing, it will be switched to a free block. However, we must make sure it is not in the chain of used data blocks & is not referenced as the first or last data block in the file header. } FUnknownBlocks.Add(Pointer(Block.BlockNum)); if Assigned(FOnReportError) then FOnReportError(Block, ErrCode, ErrorStr); { Have we reached the error limit? } if (State = rmVerify) and (FErrors.Count = FErrorLimit) then FAbort := True; end; {--------} procedure TffRepairEngine.HandleReportFix(Block : ICommonBlock; const ErrCode : Integer; const RepairStr : string); begin if Block = nil then FErrors.Add(Format('Code %d: %s', [ErrCode, RepairStr])) else FFixes.Add(Format('Block %d (%d): %s', [Block.BlockNum, ErrCode, RepairStr])); FFixCodes.Add(Pointer(Errcode)); if ErrCode = rciInvalidUsedBlocks then FUsedBlocksValid := True; if Assigned(FOnReportFix) then FOnReportFix(Block, ErrCode, RepairStr); end; {--------} procedure TffRepairEngine.LinkDataCallback(const Block1Num, Block2Num : TffWord32); var Block1, Block2 : IDataBlock; begin Block1 := FFileInterface.GetBlock(Block1Num) as IDataBlock; Block2 := FFileInterface.GetBlock(Block2Num) as IDataBlock; Block1.BeginUpdate; try Block1.NextDataBlock := Block2Num; finally Block1.EndUpdate; Block1 := nil; end; Block2.BeginUpdate; try Block2.PrevDataBlock := Block1Num; finally Block2.EndUpdate; Block2 := nil; end; end; {--------} function TffRepairEngine.MapItemToActionStr(const Item : TffRepairItem; const State : TffRepairState): string; begin if State = rmVerify then case Item of riFileHeader : Result := 'Verifying file header'; riBlockScan : Result := 'Scanning blocks'; riCheckDictionary : Result := 'Verifying dictionary'; riBlockChains : Result := 'Verifying block chains'; riDeletedBLOBChain : Result := 'Verifying deleted BLOB chain'; end else if State = rmRepair then case Item of riFileHeader : Result := 'Repairing file header'; riBlockScan : Result := 'Repairing blocks'; riCheckDictionary : Result := 'Repairing dictionary'; riBlockChains : Result := 'Repairing block chains'; riDeletedBLOBChain : Result := 'Repairing deleted BLOB chain'; riReindex : Result := 'Reindexing'; riPack : Result := 'Packing'; end end; {--------} procedure TffRepairEngine.MoveDataOrphanCallback(const BlockMoved, PrevBlock : TffWord32); var MovedBlock, PreviousDataBlock : IDataBlock; begin MovedBlock := FFileInterface.GetBlock(BlockMoved) as IDataBlock; PreviousDataBlock := FFileInterface.GetBlock(PrevBlock) as IDataBlock; MovedBlock.BeginUpdate; try MovedBlock.PrevDataBlock := PrevBlock; MovedBlock.NextDataBlock := ffc_W32NoValue; finally MovedBlock.EndUpdate; MovedBlock := nil; end; PreviousDataBlock.BeginUpdate; try PreviousDataBlock.NextDataBlock := BlockMoved; finally PreviousDataBlock.EndUpdate; PreviousDataBlock := nil; end; end; {--------} procedure TffRepairEngine.Open(const FileName :string); begin if FileExists(FileName) then begin ClearFileInterface; FFileInterface := TffFileInterface.FindInterface(FileName); if FFileInterface = nil then raise Exception.Create('Could not find an interface to handle this file.') else begin FFileInterface.Open(FileName); FFileInterface.OnReportError := HandleReportError; FFileInterface.OnReportFix := HandleReportFix; FInfo := FFileInterface.GetFileInfo; ClearChainMgrs; FChainMgrData := TffChainMgr.Create; FChainMgrFree := TffChainMgr.Create; end; end else raise Exception.Create('File ' + FileName + ' does not exist.'); end; {--------} procedure TffRepairEngine.PopulateChainMgrs; var FileHeaderBlock : IFileHeaderBlock; Block : ICommonBlock; DataBlock : IDataBlock; Inx, MaxBlocks : TffWord32; begin { TODO:: File must be open. } { If the chain managers have not been populated then scan through the blocks. } if not FChainMgrData.Populated then begin FileHeaderBlock := GetFileHeaderBlock; if FUsedBlocksValid then MaxBlocks := FileHeaderBlock.UsedBlocks else MaxBlocks := FileHeaderBlock.EstimatedUsedBlocks; for Inx := 1 to Pred(MaxBlocks) do begin Block := FFileInterface.GetBlock(Inx); { If this is a data block or free block then add information to the appropriate chain manager. } if Block.BlockType = btData then begin DataBlock := (Block as IDataBlock); FChainMgrData.AddBlock(Block.BlockNum, DataBlock.NextDataBlock, DataBlock.PrevDataBlock) end else if Block.BlockType = btFree then FChainMgrFree.AddBlock(Block.BlockNum, Block.NextBlock, ffc_W32NoValue); Block := nil; end; { for } FChainMgrFree.Fixup; FChainMgrData.Fixup; FChainMgrFree.Populated := True; FChainMgrData.Populated := True; end; { if } end; {--------} procedure TffRepairEngine.Repair; begin if FState <> rmIdle then raise Exception.Create('The Repair method can be called' + csIdle); FState := rmRepair; VerifyRepair; end; {--------} procedure TffRepairEngine.ReportProgress(const Position, Maximum : Integer); var ActionStr : string; begin if Assigned(FOnProgress) then begin ActionStr := MapItemToActionStr(FCurrentItem, FState); FOnProgress(Self, FState, FCurrentItem, ActionStr, Position, Maximum); end; end; {--------} procedure TffRepairEngine.SetErrorLimit(const Value : Integer); begin if FState = rmIdle then FErrorLimit := Value else raise Exception.Create('ErrorLimit can be set' + csIdle); end; {--------} procedure TffRepairEngine.SetItems(const Value : TffRepairItems); begin if FState = rmIdle then FItems := Value else raise Exception.Create('RepairItems can be set' + csIdle); end; {--------} procedure TffRepairEngine.Verify; begin if FState <> rmIdle then raise Exception.Create('The Verify method can be called' + csIdle); FState := rmVerify; VerifyRepair; end; {--------} procedure TffrepairEngine.CheckLastBlock; var Block : ICommonBlock; DataBlock : IDataBlock; begin { The last block's NextBlock reference should be ffc_W32NoValue. } if (FChainMgrData.LastBlockNumber <> ffc_W32NoValue) and (FChainMgrData.LastBlockNextBlockNumber <> ffc_W32NoValue) then begin { Get the last data block. } Block := FFileInterface.GetBlock(FChainMgrData.LastBlockNumber); try Block.BeginUpdate; try DataBlock := (Block as IDataBlock); DataBlock.NextDataBlock := ffc_W32NoValue; DoReportFix(Block, rciInvalidBlockRefNext, [ffc_W32NoValue]); finally Block.EndUpdate; end; finally Block := nil; end; end; { if } end; {--------} procedure TffRepairEngine.VerifyRepair; var FileHeaderBlock : IFileHeaderBlock; Block : ICommonBlock; DataBlock : IDataBlock; Inx, BlockNum, MaxBlocks : TffWord32; begin FChainMgrData.Clear; FChainMgrFree.Clear; ClearErrors; try { Init vars } FInfo.Free; FInfo := FFileInterface.GetFileInfo; FUsedBlocksValid := True; FileHeaderBlock := GetFileHeaderBlock; { Verify the file header. } if riFileHeader in FItems then begin FCurrentItem := riFileHeader; ReportProgress(25, 100); FileHeaderBlock.OnGetInfo := GetInfo; if FState = rmVerify then FileHeaderBlock.Verify else FileHeaderBlock.Repair; ReportProgress(100, 100); end; if FAbort then Exit; { Scan through the blocks. } if (riBlockScan in FItems) then begin FCurrentItem := riBlockScan; if FUsedBlocksValid then MaxBlocks := FileHeaderBlock.UsedBlocks else MaxBlocks := FileHeaderBlock.EstimatedUsedBlocks; ReportProgress(0, MaxBlocks); for Inx := 1 to Pred(MaxBlocks) do begin Block := FFileInterface.GetBlock(Inx); try { If this is a data block or free block then add information to the appropriate chain manager. } if Block.BlockType = btData then begin DataBlock := Block as IDataBlock; try FChainMgrData.AddBlock(Block.BlockNum, DataBlock.NextDataBlock, DataBlock.PrevDataBlock); finally DataBlock := nil; end; end else if Block.BlockType = btFree then FChainMgrFree.AddBlock(Block.BlockNum, Block.NextBlock, ffc_W32NoValue); Block.OnGetInfo := GetInfo; if FState = rmVerify then Block.Verify else Block.Repair; finally Block := nil; end; ReportProgress(Inx, MaxBlocks + TffWord32(FUnknownBlocks.Count)); if FAbort then Exit; end; { for } { Check for the case where there is only 1 data block or 1 free block in the table. } FChainMgrFree.Fixup; FChainMgrData.Fixup; FChainMgrFree.Populated := True; FChainMgrData.Populated := True; { Are we repairing and, if so, were any unknown blocks encountered? } if FState = rmRepair then begin { Yes. Roll through the blocks. By this point we assume they have been marked as free blocks. } if FUnknownBlocks.Count > 0 then { Note: The previous line was added because Inx is TffWord32 and Pred(FUnknownBlocks.Count) = -1 which translates to the max value of TffWord32 } for Inx := Pred(FUnknownBlocks.Count) downto 0 do begin BlockNum := TffWord32(FUnknownBlocks[Inx]); FixUnknownBlock(BlockNum, FileHeaderBlock); FUnknownBlocks.Delete(Inx); ReportProgress(MaxBlocks + Inx, MaxBlocks + TffWord32(FUnknownBlocks.Count)); end; { for } end; { if } end; if FAbort then Exit; if riCheckDictionary in FItems then begin FCurrentItem := riCheckDictionary; { TODO } end; if FAbort then Exit; if FAbort then Exit; if riBlockChains in FItems then begin FCurrentItem := riBlockChains; { Verify the data block chain first. } { Are the used data blocks split across multiple chains? } if not FChainMgrData.HasValidChain then begin DoReportError(nil, rciSplitUsedDataBlocks, []); if FState = rmRepair then begin FChainMgrData.LinkChains(LinkDataCallback); DoReportFix(nil, rciSplitUsedDataBlocks, []); end; end; { if } ReportProgress(20, 100); { Are there any orphaned blocks? } if FChainMgrData.HasOrphans then begin DoReportError(nil, rciOrphanedUsedDataBlocks, []); if FState = rmRepair then begin { Add each orphan to the end of the data chain. } FChainMgrData.MoveOrphansToTail(MoveDataOrphanCallback); DoReportFix(nil, rciOrphanedUsedDataBlocks, []); end; end; { if } ReportProgress(40, 100); if FState = rmRepair then begin CheckLastBlock; { Note: The code for CheckLastBlock was put into its own procedure in order to force the block to go out of scope & be freed prior to the table being packed. } { Verify the LastDataBlock property of the file header block. Get what should be the last data block from the chain manager. } if (FileHeaderBlock.LastDataBlock <> FChainMgrData.LastBlockNumber) then begin FileHeaderBlock.BeginUpdate; try FileHeaderBlock.LastDataBlock := FChainMgrData.LastBlockNumber; DoReportFix(FileHeaderBlock, rciInvalidBlockRefLastData, [FileHeaderBlock.LastDataBlock]); finally FileHeaderBlock.EndUpdate; end; end; { if } end; { Check the free block chain. } { TODO } { Verify the FirstDataBlock property of the file header block. } { TODO } ReportProgress(100, 100); end; if FAbort then Exit; if riDeletedBLOBChain in FItems then begin FCurrentItem := riDeletedBLOBChain; { TODO } end; if FAbort then Exit; { Any high-level repairs necessary? } if (FState = rmRepair) and (FHighestAction = raPack) then begin { Deref the file header block so that it will be fully freed when the file is closed for a reindex or pack. } FileHeaderBlock := nil; FFileInterface.OnRebuildProgress := HandleRebuildProgress; FFileInterface.OutputVersion := FOutputVersion; FCurrentItem := riPack; FFileInterface.Pack; end; { if } finally FState := rmIdle; if Assigned(FCompleted) then FCompleted(Self); end; end; {====================================================================} end.