Skip to content

Commit 4fca3d1

Browse files
committed
huge refactoring
1 parent 5a2c22f commit 4fca3d1

File tree

7 files changed

+168
-87
lines changed

7 files changed

+168
-87
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,5 @@ __recovery/
6464

6565
# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
6666
*.stat
67+
* .idea
68+
*

FirebirdRepairBase.dpr

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ uses
1515
uPageAnalyzer in 'uPageAnalyzer.pas',
1616
uBtreePage in 'uBtreePage.pas',
1717
uDatabaseStats in 'uDatabaseStats.pas',
18-
uFlagManager in 'uFlagManager.pas';
18+
uFlagManager in 'uFlagManager.pas',
19+
uRecordParser in 'uRecordParser.pas';
1920

2021
{$R *.res}
2122

FirebirdRepairBase.dproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<DCCReference Include="uBtreePage.pas"/>
123123
<DCCReference Include="uDatabaseStats.pas"/>
124124
<DCCReference Include="uFlagManager.pas"/>
125+
<DCCReference Include="uRecordParser.pas"/>
125126
<BuildConfiguration Include="Base">
126127
<Key>Base</Key>
127128
</BuildConfiguration>

README.md

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,84 @@
11
# FirebirdRepairBase
2-
Program for analize and repare broken firebird database, it is working on the low level
3-
This is dirty version of program, now program is in develop process.
4-
5-
Now it features:
6-
* check database pages by type
7-
* replace some header page params
8-
* set READ ONLY and FORCE WRITE flags
9-
* replace check sum on pages
10-
* generate new page
11-
12-
# In developing
13-
* Analisys pages, pump data.
14-
* Analisys TIP sequence, generate lost TIP pages
15-
16-
# Firebird Database Pages
17-
* Database Header Page
18-
* Page Inventory Page (PIP)
19-
* Transaction Inventory Page (TIP)
20-
* Pointer Page
21-
* Data Page
22-
* Index Root Page
23-
* Index B-Tree Page
24-
* Blob Data Page
25-
* Generator Page
26-
* Write Ahead Log Page
2+
3+
A low-level tool for analyzing and potentially recovering data from **broken** Firebird database files (`.fdb`, `.gdb`). It bypasses the standard Firebird engine to read raw page structures and data.
4+
5+
**Warning:** This is a **development version**. It directly manipulates database files. Always create a **backup** of your database before using this tool. Use at your own risk.
6+
7+
## Features
8+
9+
* **Database Structure Analysis:**
10+
* Opens and reads Firebird database files.
11+
* Identifies and counts pages by type (Header, PIP, TIP, Pointer, Data, Index Root/BTree, Blob, Generator, WAL).
12+
* Displays basic page information (number, type, checksum, relation ID).
13+
* **Header Manipulation:**
14+
* Reads and interprets database header flags.
15+
* Allows setting/clearing specific flags (e.g., Read Only, Force Write).
16+
* **Page Content Inspection:**
17+
* Reads specific pages by number.
18+
* Extracts and displays raw record fragments from Data Pages.
19+
* Allows changing page type and checksum (Dangerous!).
20+
* **Transaction Analysis:**
21+
* Reads Transaction Inventory Pages (TIP).
22+
* Counts transactions by state (Active, Limbo, Dead, Committed).
23+
* **Basic Data Recovery:**
24+
* Extracts raw data fragments from Data Pages.
25+
* *(WIP)* Basic MVCC (Multi-Version Concurrency Control) awareness for data visibility (see TODO).
26+
* *(New)* Parses basic record headers to identify deleted records and data length.
27+
* **Record Fragment Parsing:**
28+
* Introduces `uRecordParser` module for analyzing individual record fragments.
29+
* Extracts key information like transaction ID, back pointer, flags, and data length from the record header.
30+
31+
## In Development (TODO)
32+
33+
* **Advanced Data Recovery:**
34+
* Implement full MVCC logic to reconstruct the correct, visible version of records based on transaction states and TIP page data.
35+
* Export recovered data to a new, valid Firebird database or other formats (e.g., CSV).
36+
* Analyze and potentially repair B-Tree index structures.
37+
* Analyze and potentially repair Blob page chains.
38+
* **Deeper Integrity Checks:**
39+
* Verify logical links between pages (e.g., Pointer Pages -> Data Pages, Record Back Pointers).
40+
* Identify orphaned pages.
41+
* **Page Generation/Repair:**
42+
* Implement logic to generate missing TIP or PIP pages if necessary.
43+
* Attempt to rebuild damaged page structures.
44+
* **User Interface Improvements:**
45+
* Add search functionality within page data.
46+
* Provide more detailed views for different page types (B-Tree keys, Blob data).
47+
* Add a 'Restore from Backup' feature.
48+
* Improve error handling and user feedback.
49+
50+
## Supported Firebird Pages
51+
52+
* Database Header Page
53+
* Page Inventory Page (PIP)
54+
* Transaction Inventory Page (TIP)
55+
* Pointer Page
56+
* Data Page
57+
* Index Root Page
58+
* Index B-Tree Page
59+
* Blob Data Page
60+
* Generator Page
61+
* Write Ahead Log Page (WAL) (Note: WAL functionality might be limited)
62+
63+
## Technical Details
64+
65+
* **Language:** Pascal (Delphi).
66+
* **Target:** Windows.
67+
* **Approach:** Direct file I/O, parsing raw page structures according to Firebird Internal Format documentation (`fbint-page-1.html`) and Firebird source code conventions.
68+
* **Modules:**
69+
* `uStructs`: Core data structures (TPag, THdr, etc.).
70+
* `uPageAnalyzer`: General page reading and type identification.
71+
* `uDataPage`: Data Page header parsing and record fragment extraction.
72+
* `uTipPage`: TIP page header parsing and transaction state analysis.
73+
* `uBtreePage`: B-Tree page header parsing and key extraction.
74+
* `uRecordParser`: *(New)* Parses headers of individual record fragments.
75+
* `uDatabaseStats`: Aggregates statistics using `uPageAnalyzer`.
76+
* `uFlagManager`: Manages database header flags.
2777

2878
## License
29-
MIT:
79+
80+
MIT
3081

3182
## Author
83+
3284
Gordienko Roman

main.pas

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ interface
55
uses
66
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
77
Dialogs, StdCtrls, FileCtrl, ComCtrls, uStructs, DB, ExtCtrls,
8-
Menus, uCommon, uDatabase, XPMan, uDataPage, Grids, DBGrids, DBClient,
8+
Menus, uCommon, XPMan, uDataPage, Grids, DBGrids, DBClient,
99
DBCtrls, Spin, Vcl.Buttons,
10-
uPageAnalyzer, uDatabaseStats, uFlagManager;
10+
uPageAnalyzer, uDatabaseStats, uFlagManager, StrUtils;
1111

1212
type
1313
TfrmMain = class(TForm)
@@ -200,6 +200,7 @@ procedure TfrmMain.btnReWriteClick(Sender: TObject);
200200
PageInfo: TPageInfo;
201201
NewPageBuffer: TBytes;
202202
FileStream: TFileStream;
203+
ModifiedHeader: TPag; // --- ÄÎÁÀÂËÅÍÎ ---
203204
begin
204205
// ReWriting page - ÂÍÈÌÀÍÈÅ: Ýòî ÎÏÀÑÍÀß îïåðàöèÿ!
205206
if Application.MessageBox('Do You Want to Rewrite Database Page? THIS IS DANGEROUS!',
@@ -214,10 +215,18 @@ procedure TfrmMain.btnReWriteClick(Sender: TObject);
214215
PageInfo := FPageAnalyzer.GetPageInfo(PageNum);
215216
NewPageBuffer := Copy(PageInfo.Buffer); // Êîïèðóåì òåêóùèé áóôåð
216217

217-
// Ìîäèôèöèðóåì çàãîëîâîê â áóôåðå
218-
TPag(NewPageBuffer[0]).pag_checksum := UShort(NewChecksum);
218+
// --- ÈÑÏÐÀÂËÅÍÈÅ: Èñïîëüçóåì Move äëÿ áåçîïàñíîãî êîïèðîâàíèÿ è èçìåíåíèÿ çàãîëîâêà ---
219+
// Ñêîïèðóåì ñòàíäàðòíûé çàãîëîâîê èç áóôåðà â ëîêàëüíóþ ïåðåìåííóþ
220+
Move(NewPageBuffer[0], ModifiedHeader, SizeOf(TPag));
221+
222+
// Èçìåíèì ïîëÿ â ëîêàëüíîé ïåðåìåííîé
223+
ModifiedHeader.pag_checksum := UShort(NewChecksum);
219224
if (NewType >= 0) and (NewType <= $FF) then // Ïðîâåðÿåì äèàïàçîí
220-
TPag(NewPageBuffer[0]).pag_type := SChar(NewType);
225+
ModifiedHeader.pag_type := SChar(NewType);
226+
227+
// Ñêîïèðóåì èçìåí¸ííóþ ëîêàëüíóþ ïåðåìåííóþ îáðàòíî â áóôåð
228+
Move(ModifiedHeader, NewPageBuffer[0], SizeOf(TPag));
229+
// --- ÊÎÍÅÖ ÈÑÏÐÀÂËÅÍÈß ---
221230

222231
// Çàïèñûâàåì èçìåí¸ííûé áóôåð îáðàòíî â ôàéë
223232
FileStream := TFileStream.Create(FPageAnalyzer.FileName, fmOpenReadWrite or fmShareExclusive);
@@ -290,20 +299,19 @@ procedure TfrmMain.btnGetHeaderFlagsClick(Sender: TObject);
290299
try
291300
Flags := FFlagManager.GetFlags;
292301

293-
// Ôîðìèðóåì ñòðîêó äëÿ îòîáðàæåíèÿ (íàïðèìåð, áèíàðíîå ïðåäñòàâëåíèå èëè ïðîñòî ñïèñîê)
294-
// Äëÿ ïðîñòîòû, âûâåäåì ñïèñîê àêòèâíûõ ôëàãîâ
302+
295303
edtFlags.text := 'Active flags: ';
296304
if Flags.ActiveShadow then edtFlags.text := edtFlags.text + 'ActiveShadow, ';
297305
if Flags.ForceWrite then edtFlags.text := edtFlags.text + 'ForceWrite, ';
298306
if Flags.NoChecksums then edtFlags.text := edtFlags.text + 'NoChecksums, ';
299307
if Flags.NoReserve then edtFlags.text := edtFlags.text + 'NoReserve, ';
300308
if Flags.SqlDialect3 then edtFlags.text := edtFlags.text + 'SqlDialect3, ';
301309
if Flags.ReadOnly then edtFlags.text := edtFlags.text + 'ReadOnly, ';
302-
// Óáèðàåì ïîñëåäíþþ çàïÿòóþ
303-
if edtFlags.text.EndsWith(', ') then
304-
edtFlags.text := edtFlags.text.Remove(edtFlags.text.Length - 2);
305310

306-
// Èëè ìîæíî âûâåñòè âñå ôëàãè ñ óêàçàíèåì ñòàòóñà
311+
if EndsStr(', ', edtFlags.text) then // Èñïîëüçóåì EndsStr èç StrUtils
312+
edtFlags.text := Copy(edtFlags.text, 1, Length(edtFlags.text) - 2); // Óäàëÿåì ïîñëåäíèå 2 ñèìâîëà
313+
314+
307315
lstDBFlags.Items.Add(Format('Active Shadow: %s', [BoolToStr(Flags.ActiveShadow, True)]));
308316
lstDBFlags.Items.Add(Format('Force Write: %s', [BoolToStr(Flags.ForceWrite, True)]));
309317
lstDBFlags.Items.Add(Format('No Checksums: %s', [BoolToStr(Flags.NoChecksums, True)]));
@@ -348,28 +356,28 @@ procedure TfrmMain.checkDB;
348356
lstLog.Items.Add('Database page size: ' + IntToStr(FPageAnalyzer.PageSize));
349357
lstLog.Items.Add('Scanning pages...');
350358

351-
// Âû÷èñëÿåì ñòàòèñòèêó ÷åðåç FDatabaseStats
359+
352360
FDatabaseStats.CalculateStats;
353361

354-
// Ïîëó÷àåì ðåçóëüòàòû
362+
355363
PageStats := FDatabaseStats.PageStats;
356364
TxStats := FDatabaseStats.TransactionStats;
357365
RelStats := FDatabaseStats.RelationStats;
358366

359-
// Îáíîâëÿåì ïðîãðåññ áàð (äëÿ íàãëÿäíîñòè, õîòÿ òåïåðü ýòî áûñòðî)
367+
360368
pbDetectedPage.Min := 0;
361-
pbDetectedPage.Max := 100; // Óñëîâíî
369+
pbDetectedPage.Max := 100;
362370
pbDetectedPage.Position := 100;
363371

364-
// Âûâîäèì ðåçóëüòàòû ïîäñ÷åòà
372+
365373
lstLog.Items.Add('--- Page Type Summary ---');
366374
lstLog.Items.Add('Count Header Pages: ' + IntToStr(PageStats.HeaderPages));
367375
lstLog.Items.Add('Count Page Inventory Pages (PIP): ' + IntToStr(PageStats.PipPages));
368376
lstLog.Items.Add('Count Transaction Inventory Pages (TIP): ' + IntToStr(PageStats.TipPages));
369377
lstLog.Items.Add('Count Pointer Pages: ' + IntToStr(PageStats.PointerPages));
370378
lstLog.Items.Add('Count Data Pages: ' + IntToStr(PageStats.DataPages));
371379
lstLog.Items.Add('Count Index Root Pages: ' + IntToStr(PageStats.IndexRootPages));
372-
lstLog.Items.Add('Count Index B-Tree Pages: ' + IntToStr(PageStats.IndexBtreePages)); // Èñïðàâëåíî
380+
lstLog.Items.Add('Count Index B-Tree Pages: ' + IntToStr(PageStats.IndexBtreePages));
373381
lstLog.Items.Add('Count Blob Pages: ' + IntToStr(PageStats.BlobPages));
374382
lstLog.Items.Add('Count Generator Pages: ' + IntToStr(PageStats.GeneratorPages));
375383
lstLog.Items.Add('Count Write Ahead Log Pages: ' + IntToStr(PageStats.WalPages));
@@ -415,12 +423,10 @@ procedure TfrmMain.btnWriteFlagsClick(Sender: TObject);
415423
try
416424
NewFlags := FFlagManager.GetFlags;
417425

418-
// Îáíîâëÿåì ôëàãè â ñîîòâåòñòâèè ñ ÷åêáîêñàìè
426+
419427
NewFlags.ForceWrite := chkSetFW.Checked;
420428
NewFlags.ReadOnly := chkReadOnly.Checked;
421-
// Äîáàâüòå äðóãèå ôëàãè ïî íåîáõîäèìîñòè
422429

423-
// Óñòàíàâëèâàåì ôëàãè ÷åðåç FFlagManager
424430
FFlagManager.SetFlags(NewFlags);
425431

426432
Application.MessageBox('Header flags updated!', 'Information', MB_OK + MB_ICONINFORMATION);
@@ -452,14 +458,14 @@ procedure TfrmMain.UpdateFlagCheckboxesFromManager;
452458
end;
453459
end;
454460

455-
// tsFlagsShow - òåïåðü èñïîëüçóåò UpdateFlagCheckboxesFromManager
461+
456462
procedure TfrmMain.tsFlagsShow(Sender: TObject);
457463
begin
458-
// Îáíîâëÿåì ÷åêáîêñû ïðè ïîêàçå âêëàäêè
464+
459465
UpdateFlagCheckboxesFromManager;
460466
end;
461467

462-
// pgcServicesChange - îáíîâëÿåò MaxValue äëÿ sePosition
468+
463469
procedure TfrmMain.pgcServicesChange(Sender: TObject);
464470
begin
465471
if tsGenerateNewPage.Showing then
@@ -513,15 +519,13 @@ procedure TfrmMain.btnGotoPageClick(Sender: TObject);
513519
end;
514520

515521
btnGotoPage.Enabled := False;
516-
mmoData.Clear; // Î÷èùàåì Memo ïåðåä âûâîäîì
517-
518-
// Èñïîëüçóåì ôóíêöèþ èç uDataPage äëÿ èçâëå÷åíèÿ ôðàãìåíòîâ
522+
mmoData.Clear;
519523
PageFragments := ExtractDataFragments(PageInfo.Buffer, PageInfo.Size);
520524

521525
pbDataProgress.Min := 0;
522-
pbDataProgress.Max := Length(PageFragments) - 1; // Êîëè÷åñòâî ôðàãìåíòîâ
526+
pbDataProgress.Max := Length(PageFragments) - 1;
523527

524-
for i := 0 to High(PageFragments) do // Èñïîëüçóåì High äëÿ ìàññèâà
528+
for i := 0 to High(PageFragments) do
525529
begin
526530
pbDataProgress.Position := i;
527531
mmoData.Lines.Add('Fragment ' + IntToStr(i) + ', Offset: ' + IntToStr(PageFragments[i].Offset));

0 commit comments

Comments
 (0)