From 5a2c22fd19f815853282e88238184fcdfec8fb76 Mon Sep 17 00:00:00 2001 From: formeo Date: Sun, 21 Dec 2025 13:33:12 +0300 Subject: [PATCH] new files --- uBtreePage.pas | 163 ++++++++++++++++++++++++++++++++ uDatabaseStats.pas | 226 +++++++++++++++++++++++++++++++++++++++++++++ uFlagManager.pas | 169 +++++++++++++++++++++++++++++++++ uPageAnalyzer.pas | 170 ++++++++++++++++++++++++++++++++++ uPointer.pas | 36 ++++++++ uStructs.pas | 202 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 966 insertions(+) create mode 100644 uBtreePage.pas create mode 100644 uDatabaseStats.pas create mode 100644 uFlagManager.pas create mode 100644 uPageAnalyzer.pas create mode 100644 uPointer.pas create mode 100644 uStructs.pas diff --git a/uBtreePage.pas b/uBtreePage.pas new file mode 100644 index 0000000..88f192f --- /dev/null +++ b/uBtreePage.pas @@ -0,0 +1,163 @@ +unit uBtreePage; + +interface + +uses + Windows, SysUtils, uStructs; + +const + pag_left = $01; // Это самая левая страница в уровне + pag_root = $02; // Это корневая страница + pag_leaf = $04; // Это листовая страница + + key_delete = $01; // Ключ помечен как удалённый + key_right = $02; // Ключ указывает на правый узел (внутренний узел) + key_bias = $04; // Ключ является смещенным (bias key) + key_dupeq = $08; // Ключ помечен как дубликат (для уникальных индексов) + +type + // Фиксированная часть заголовка B-Tree страницы + TBtreePageHeader = packed record + pag_header: TPag; // 0x00-0x0F - Стандартный заголовок + pag_next: SLong; // 0x10-0x13 - Указатель на следующую страницу (лист) + pag_sibling: SLong;// 0x14-0x17 - Указатель на соседнюю страницу + pag_parent: SLong; // 0x18-0x1B - Указатель на родительскую страницу + pag_count: UShort; // 0x1C-0x1D - Количество ключей на странице + pag_flags: UChar; // 0x1E - Флаги страницы + pag_fill: UChar; // 0x1F - Процент заполнения (не используется) + // pag_keys начинается с байта 0x20 + end; + + // Тип для представления одного ключа + TBtreeKey = record + Length: UShort; + Flags: SChar; + Data: TBytes; // Данные ключа + PageOrRecordNumber: SLong; // Для внутренних узлов - номер страницы, для листьев - номер записи + end; + + // Тип для массива ключей + TBtreeKeyArray = array of TBtreeKey; + + // Класс для анализа B-Tree страницы + TBtreePageAnalyzer = class + private + FPageSize: Integer; + FPageNumber: ULong; // Номер самой B-Tree страницы (передаётся для контекста) + FHeader: TBtreePageHeader; + FPageData: TBytes; // Данные всей страницы (для извлечения ключей) + + // Внутренняя функция для получения ключа по смещению + function GetKeyAtOffset(Offset: Integer): TBtreeKey; + public + // Конструктор - принимает байты страницы, размер страницы и номер страницы + constructor Create(const PageBuffer: TBytes; PageSize: Integer; PageNum: ULong); + + // Получить массив всех ключей на странице + function GetKeys: TBtreeKeyArray; + + // Получить флаги страницы + property Flags: UChar read FHeader.pag_flags; + // Получить количество ключей + property Count: UShort read FHeader.pag_count; + // Получить номер следующей страницы + property NextPage: SLong read FHeader.pag_next; + // Получить номер соседней страницы + property SiblingPage: SLong read FHeader.pag_sibling; + // Получить номер родительской страницы + property ParentPage: SLong read FHeader.pag_parent; + // Это листовая страница? + property IsLeaf: Boolean read (FHeader.pag_flags and pag_leaf) <> 0; + // Это корневая страница? + property IsRoot: Boolean read (FHeader.pag_flags and pag_root) <> 0; + // Это самая левая страница? + property IsLeftmost: Boolean read (FHeader.pag_flags and pag_left) <> 0; + + end; + +implementation + +constructor TBtreePageAnalyzer.Create(const PageBuffer: TBytes; PageSize: Integer; PageNum: ULong); +var + ExpectedMinSize: Integer; +begin + if (PageSize < MIN_PAGE_SIZE) or (PageSize > MAX_PAGE_SIZE) or + (Length(PageBuffer) <> PageSize) then + raise Exception.Create('Invalid page size or buffer size for B-Tree page.'); + + ExpectedMinSize := SizeOf(TBtreePageHeader); + if Length(PageBuffer) < ExpectedMinSize then + raise Exception.Create('Page buffer too small for B-Tree header.'); + + // Копируем фиксированную часть заголовка + Move(PageBuffer[0], FHeader, SizeOf(TBtreePageHeader)); + + // Проверяем тип страницы + if FHeader.pag_header.pag_type <> $07 then + raise Exception.Create('Not a B-Tree Index Page.'); + + FPageSize := PageSize; + FPageNumber := PageNum; + FPageData := Copy(PageBuffer); // Копируем все данные страницы +end; + +function TBtreePageAnalyzer.GetKeyAtOffset(Offset: Integer): TBtreeKey; +var + KeyLength: UShort; + KeyFlags: SChar; + KeyDataOffset, KeyPageOffset: Integer; +begin + // Проверяем границы + if (Offset + 2 {key_length} + 1 {key_flags} > Length(FPageData)) then + raise Exception.Create('Invalid offset for key in B-Tree page.'); + + // Читаем key_length (2 байта) + KeyLength := PWord(@FPageData[Offset])^; + Inc(Offset, 2); + + // Читаем key_flags (1 байт) + KeyFlags := PShortInt(@FPageData[Offset])^; + Inc(Offset, 1); + + // Проверяем, не выходит ли длина данных за пределы + if (Offset + KeyLength + 4 {key_page} > Length(FPageData)) then + raise Exception.Create('Key length exceeds page boundary.'); + + // Копируем key_data + SetLength(Result.Data, KeyLength); + if KeyLength > 0 then + Move(FPageData[Offset], Result.Data[0], KeyLength); + Inc(Offset, KeyLength); + + // Читаем key_page (4 байта) + Result.PageOrRecordNumber := PLongInt(@FPageData[Offset])^; + + // Заполняем поля результата + Result.Length := KeyLength; + Result.Flags := KeyFlags; +end; + +function TBtreePageAnalyzer.GetKeys: TBtreeKeyArray; +var + i: Integer; + CurrentOffset: Integer; + Keys: TBtreeKeyArray; +begin + SetLength(Keys, FHeader.pag_count); + + // Смещение начала массива ключей + CurrentOffset := SizeOf(TBtreePageHeader); + + for i := 0 to FHeader.pag_count - 1 do + begin + Keys[i] := GetKeyAtOffset(CurrentOffset); + + // Вычисляем смещение следующего ключа + // key_length(2) + key_flags(1) + key_data(key_length) + key_page(4) + CurrentOffset := CurrentOffset + 2 + 1 + Keys[i].Length + 4; + end; + + Result := Keys; +end; + +end. diff --git a/uDatabaseStats.pas b/uDatabaseStats.pas new file mode 100644 index 0000000..3b20eee --- /dev/null +++ b/uDatabaseStats.pas @@ -0,0 +1,226 @@ +unit uDatabaseStats; + +interface + +uses + Classes, uPageAnalyzer, uDataPage, uTipPage, uBtreePage; + +type + SChar = Shortint; + SShort = Smallint; + UShort = Word; + SLong = Longint; + ULong = LongWord; + UChar = type Byte; + + TPageTypeStats = record + HeaderPages: ULong; + PipPages: ULong; + TipPages: ULong; + PointerPages: ULong; + DataPages: ULong; + IndexRootPages: ULong; + IndexBtreePages: ULong; + BlobPages: ULong; + GeneratorPages: ULong; + WalPages: ULong; + UnknownPages: ULong; + end; + + + TTransactionStats = record + Active: ULong; + Limbo: ULong; + Dead: ULong; + Committed: ULong; + Total: ULong; + end; + + + TRelationStats = record + RelationID: ULong; + PageCount: ULong; // Количество Data Pages, принадлежащих отношению + RecordCount: ULong; // Пример: количество записей (требует парсинга фрагментов) + end; + TRelationStatsArray = array of TRelationStats; + + TDatabaseStats = class + private + FPageAnalyzer: TPageAnalyzer; + FPageStats: TPageTypeStats; + FTransactionStats: TTransactionStats; + FRelationStats: TRelationStatsArray; + FStatsCalculated: Boolean; + + // Внутренняя функция для подсчета типов страниц + procedure CalculatePageStats; + // Внутренняя функция для подсчета статистики по TIP + procedure CalculateTransactionStats; + // Внутренняя функция для подсчета статистики по отношениям (упрощенная) + procedure CalculateRelationStats; + public + // Конструктор - принимает TPageAnalyzer + constructor Create(APageAnalyzer: TPageAnalyzer); + // Деструктор + destructor Destroy; override; + + // Вычислить всю статистику + procedure CalculateStats; + + // Получить статистику по типам страниц + property PageStats: TPageTypeStats read FPageStats; + // Получить статистику по транзакциям + property TransactionStats: TTransactionStats read FTransactionStats; + // Получить статистику по отношениям + property RelationStats: TRelationStatsArray read FRelationStats; + // Статистика уже вычислена? + property StatsCalculated: Boolean read FStatsCalculated; + end; + +implementation + +uses + SysUtils; + +constructor TDatabaseStats.Create(APageAnalyzer: TPageAnalyzer); +begin + inherited Create; + FPageAnalyzer := APageAnalyzer; + FStatsCalculated := False; +end; + +destructor TDatabaseStats.Destroy; +begin + inherited; +end; + +procedure TDatabaseStats.CalculatePageStats; +var + PageNum: ULong; + PageInfo: TPageInfo; +begin + + FillChar(FPageStats, SizeOf(FPageStats), 0); + + for PageNum := 0 to FPageAnalyzer.GetLastPageNumber do + begin + PageInfo := FPageAnalyzer.GetPageInfo(PageNum); + case PageInfo.Header.pag_type of + pag_header: Inc(FPageStats.HeaderPages); + pag_pip: Inc(FPageStats.PipPages); + pag_tip: Inc(FPageStats.TipPages); + pag_pointer: Inc(FPageStats.PointerPages); + pag_data: Inc(FPageStats.DataPages); + pag_index_root: Inc(FPageStats.IndexRootPages); + pag_index_bt: Inc(FPageStats.IndexBtreePages); + pag_blob: Inc(FPageStats.BlobPages); + pag_generator: Inc(FPageStats.GeneratorPages); + pag_wal: Inc(FPageStats.WalPages); + else + Inc(FPageStats.UnknownPages); + end; + end; +end; + +procedure TDatabaseStats.CalculateTransactionStats; +var + PageNum: ULong; + PageInfo: TPageInfo; + TipAnalyzer: TTIPAnalyzer; + TxInfoArray: TTransactionInfoArray; + i: Integer; +begin + // Инициализируем счетчики + FillChar(FTransactionStats, SizeOf(FTransactionStats), 0); + + for PageNum := 0 to FPageAnalyzer.GetLastPageNumber do + begin + PageInfo := FPageAnalyzer.GetPageInfo(PageNum); + if PageInfo.Header.pag_type = pag_tip then + begin + TipAnalyzer := TTIPAnalyzer.Create(PageInfo.Buffer, PageInfo.Size, PageInfo.Number); + try + TxInfoArray := TipAnalyzer.GetTransactionInfo(); // Получить все транзакции на странице + for i := 0 to High(TxInfoArray) do + begin + case TxInfoArray[i].State of + tsActive: Inc(FTransactionStats.Active); + tsLimbo: Inc(FTransactionStats.Limbo); + tsDead: Inc(FTransactionStats.Dead); + tsCommitted: Inc(FTransactionStats.Committed); + end; + Inc(FTransactionStats.Total); + end; + finally + TipAnalyzer.Free; + end; + end; + end; +end; + +procedure TDatabaseStats.CalculateRelationStats; +var + PageNum: ULong; + PageInfo: TPageInfo; + DataPageHeader: TData_Page_Fixed_Part; + Fragments: TRecordFragmentsArray; + FoundRelation: Boolean; + i, j: Integer; +begin + SetLength(FRelationStats, 0); + + for PageNum := 0 to FPageAnalyzer.GetLastPageNumber do + begin + PageInfo := FPageAnalyzer.GetPageInfo(PageNum); + if PageInfo.Header.pag_type = pag_data then + begin + if Length(PageInfo.Buffer) >= SizeOf(TData_Page_Fixed_Part) then + begin + DataPageHeader := TData_Page_Fixed_Part(PageInfo.Buffer[0]); + var RelationID := DataPageHeader.dpg_relation; + + FoundRelation := False; + for j := 0 to High(FRelationStats) do + begin + if FRelationStats[j].RelationID = RelationID then + begin + Inc(FRelationStats[j].PageCount); + FoundRelation := True; + Break; + end; + end; + + + if not FoundRelation then + begin + SetLength(FRelationStats, Length(FRelationStats) + 1); + FRelationStats[High(FRelationStats)].RelationID := RelationID; + FRelationStats[High(FRelationStats)].PageCount := 1; + FRelationStats[High(FRelationStats)].RecordCount := 0; + end; + + + Fragments := ExtractDataFragments(PageInfo.Buffer, PageInfo.Size); + + for j := 0 to High(FRelationStats) do + begin + if FRelationStats[j].RelationID = RelationID then + begin + Inc(FRelationStats[j].RecordCount, Length(Fragments)); + Break; + end; + end; + end; + end; + end; +end; + +procedure TDatabaseStats.CalculateStats; +begin + CalculatePageStats; + CalculateTransactionStats; + CalculateRelationStats; + FStatsCalculated := True; +end; + +end. diff --git a/uFlagManager.pas b/uFlagManager.pas new file mode 100644 index 0000000..9ff0475 --- /dev/null +++ b/uFlagManager.pas @@ -0,0 +1,169 @@ +unit uFlagManager; + +interface + +uses + Classes, SysUtils, uStructs; + +type + TDatabaseFlags = record + ActiveShadow: Boolean; // hdr_active_shadow 0x01 (bit 0) + ForceWrite: Boolean; // hdr_force_write 0x02 (bit 1) + NoChecksums: Boolean; // hdr_no_checksums 0x10 (bit 4) + NoReserve: Boolean; // hdr_no_reserve 0x20 (bit 5) + SqlDialect3: Boolean; // hdr_sql_dialect_3 0x100 (bit 8) + ReadOnly: Boolean; // hdr_read_only 0x200 (bit 9) + end; + + + TFlagManager = class + private + FFileName: string; + FHeaderPageBuffer: TBytes; + FHeaderPageSize: Integer; + + procedure LoadHeaderPage; + procedure SaveHeaderPage; + function FlagsToUShort(const Flags: TDatabaseFlags): UShort; + function UShortToFlags(FlagsValue: UShort): TDatabaseFlags; + public + constructor Create(const AFileName: string); + destructor Destroy; override; + function GetFlags: TDatabaseFlags; + procedure SetFlags(const NewFlags: TDatabaseFlags); + procedure SetForceWrite(Value: Boolean); + procedure SetReadOnly(Value: Boolean); + end; + +implementation + +uses + uPageAnalyzer; + +constructor TFlagManager.Create(const AFileName: string); +begin + inherited Create; + FFileName := AFileName; + LoadHeaderPage; +end; + +destructor TFlagManager.Destroy; +begin + inherited; +end; + +procedure TFlagManager.LoadHeaderPage; +var + FileStream: TFileStream; + HeaderPageStruct: THdrPage; + PageSize: Integer; +begin + if not FileExists(FFileName) then + raise Exception.Create('File does not exist: ' + FFileName); + + FileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite); + try + SetLength(FHeaderPageBuffer, MIN_PAGE_SIZE); // Читаем минимум для получения hdr_page_size + if FileStream.Read(FHeaderPageBuffer[0], MIN_PAGE_SIZE) <> MIN_PAGE_SIZE then + raise Exception.Create('Cannot read header page.'); + + + if FHeaderPageBuffer[0] <> pag_header then + raise Exception.Create('File does not start with a Header Page (type 0x01).'); + + + Move(FHeaderPageBuffer[0], HeaderPageStruct, SizeOf(THdr)); + PageSize := HeaderPageStruct.fix_data.hdr_page_size; + + + if (PageSize < MIN_PAGE_SIZE) or (PageSize > MAX_PAGE_SIZE) then + raise Exception.Create('Invalid page size in header: ' + IntToStr(PageSize)); + + + SetLength(FHeaderPageBuffer, PageSize); + FileStream.Seek(0, soFromBeginning); + if FileStream.Read(FHeaderPageBuffer[0], PageSize) <> PageSize then + raise Exception.Create('Cannot read full header page.'); + + FHeaderPageSize := PageSize; + finally + FileStream.Free; + end; +end; + +procedure TFlagManager.SaveHeaderPage; +var + FileStream: TFileStream; +begin + FileStream := TFileStream.Create(FFileName, fmOpenReadWrite or fmShareExclusive); + try + FileStream.Seek(0, soFromBeginning); + if FileStream.Write(FHeaderPageBuffer[0], FHeaderPageSize) <> FHeaderPageSize then + raise Exception.Create('Cannot write header page.'); + finally + FileStream.Free; + end; +end; + +function TFlagManager.FlagsToUShort(const Flags: TDatabaseFlags): UShort; +begin + Result := 0; + if Flags.ActiveShadow then Result := Result or $01; + if Flags.ForceWrite then Result := Result or $02; + if Flags.NoChecksums then Result := Result or $10; + if Flags.NoReserve then Result := Result or $20; + if Flags.SqlDialect3 then Result := Result or $100; + if Flags.ReadOnly then Result := Result or $200; +end; + +function TFlagManager.UShortToFlags(FlagsValue: UShort): TDatabaseFlags; +begin + Result.ActiveShadow := (FlagsValue and $01) <> 0; + Result.ForceWrite := (FlagsValue and $02) <> 0; + Result.NoChecksums := (FlagsValue and $10) <> 0; + Result.NoReserve := (FlagsValue and $20) <> 0; + Result.SqlDialect3 := (FlagsValue and $100) <> 0; + Result.ReadOnly := (FlagsValue and $200) <> 0; +end; + +function TFlagManager.GetFlags: TDatabaseFlags; +var + HeaderStruct: THdrPage; + FlagsValue: UShort; +begin + Move(FHeaderPageBuffer[0], HeaderStruct, SizeOf(THdr)); + FlagsValue := HeaderStruct.fix_data.hdr_flags; + Result := UShortToFlags(FlagsValue); +end; + +procedure TFlagManager.SetFlags(const NewFlags: TDatabaseFlags); +var + HeaderStruct: THdrPage; + NewFlagsValue: UShort; +begin + Move(FHeaderPageBuffer[0], HeaderStruct, SizeOf(THdr)); + NewFlagsValue := FlagsToUShort(NewFlags); + HeaderStruct.fix_data.hdr_flags := NewFlagsValue; + Move(HeaderStruct, FHeaderPageBuffer[0], SizeOf(THdr)); + SaveHeaderPage; +end; + +procedure TFlagManager.SetForceWrite(Value: Boolean); +var + CurrentFlags: TDatabaseFlags; +begin + CurrentFlags := GetFlags(); + CurrentFlags.ForceWrite := Value; + SetFlags(CurrentFlags); +end; + +procedure TFlagManager.SetReadOnly(Value: Boolean); +var + CurrentFlags: TDatabaseFlags; +begin + CurrentFlags := GetFlags(); + CurrentFlags.ReadOnly := Value; + SetFlags(CurrentFlags); +end; + +end. diff --git a/uPageAnalyzer.pas b/uPageAnalyzer.pas new file mode 100644 index 0000000..1936961 --- /dev/null +++ b/uPageAnalyzer.pas @@ -0,0 +1,170 @@ +unit uPageAnalyzer; + +interface + +uses + Windows, SysUtils, Classes, uStructs; // uStructs содержит исправленные TPag и типы + +const + // Типы страниц Firebird (ODS 11.1 / Firebird 2.1+) + // Эти константы можно определить здесь для удобства + pag_undefined = $00; + pag_header = $01; + pag_pip = $02; // Page Inventory Page + pag_tip = $03; // Transaction Inventory Page + pag_pointer = $04; // Pointer Page + pag_data = $05; // Data Page + pag_index_root = $06; // Index Root Page + pag_index_bt = $07; // Index B-Tree Page + pag_blob = $08; // Blob Page + pag_generator = $09; // Generator Page + pag_wal = $0a; // Write Ahead Log page (устаревшее) + +type + // Используем TPag из uStructs + // TPag = packed record + // pag_type: SChar; + // pag_flags: UChar; + // pag_checksum: UShort; + // pag_generation: ULong; + // pag_scn: ULong; + // reserved: ULong; + // end; + + // Информация о прочитанной странице + TPageInfo = record + Number: ULong; // Номер страницы + Buffer: TBytes; // Байтовый буфер страницы + Header: TPag; // Стандартный заголовок страницы + Size: Integer; // Размер страницы + TypeStr: string; // Строковое описание типа + end; + + // Класс для общего анализа страниц базы данных + TPageAnalyzer = class + private + FFileName: string; + FPageSize: Integer; + FFileSize: Int64; + FFileStream: TFileStream; + + + function ReadPageToBuffer(PageNumber: ULong): TBytes; + function GetPageHeader(const PageBuffer: TBytes): TPag; + function GetPageTypeString(PageType: SChar): string; + public + constructor Create(const AFileName: string); + destructor Destroy; override; + function GetPageInfo(PageNumber: ULong): TPageInfo; + function GetLastPageNumber: ULong; + + property FileName: string read FFileName; + property PageSize: Integer read FPageSize; + property FileSize: Int64 read FFileSize; + end; + +implementation + +constructor TPageAnalyzer.Create(const AFileName: string); +var + HeaderPageBuffer: TBytes; + HeaderPageStruct: THdrPage; +begin + if not FileExists(AFileName) then + raise Exception.Create('File does not exist: ' + AFileName); + + FFileName := AFileName; + FFileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyWrite); + FFileSize := FFileStream.Size; + + SetLength(HeaderPageBuffer, MIN_PAGE_SIZE); + FFileStream.Seek(0, soFromBeginning); + if FFileStream.Read(HeaderPageBuffer[0], MIN_PAGE_SIZE) <> MIN_PAGE_SIZE then + raise Exception.Create('Cannot read header page.'); + + + if HeaderPageBuffer[0] <> pag_header then + raise Exception.Create('File does not start with a Header Page (type 0x01).'); + + Move(HeaderPageBuffer[0], HeaderPageStruct, SizeOf(THdr)); + FPageSize := HeaderPageStruct.fix_data.hdr_page_size; + + + if (FPageSize < MIN_PAGE_SIZE) or (FPageSize > MAX_PAGE_SIZE) or + ((FPageSize and (FPageSize - 1)) <> 0) then + raise Exception.Create('Invalid page size found in header: ' + IntToStr(FPageSize)); + + + SetLength(HeaderPageBuffer, FPageSize); + FFileStream.Seek(0, soFromBeginning); + if FFileStream.Read(HeaderPageBuffer[0], FPageSize) <> FPageSize then + raise Exception.Create('Cannot read full header page.'); + + Move(HeaderPageBuffer[0], HeaderPageStruct, SizeOf(THdr)); + FPageSize := HeaderPageStruct.fix_data.hdr_page_size; +end; + +destructor TPageAnalyzer.Destroy; +begin + FFileStream.Free; + inherited; +end; + +function TPageAnalyzer.ReadPageToBuffer(PageNumber: ULong): TBytes; +var + Offset: Int64; +begin + Offset := Int64(PageNumber) * FPageSize; + if Offset + FPageSize > FFileSize then + raise Exception.Create('Page number out of file bounds: ' + IntToStr(PageNumber)); + + SetLength(Result, FPageSize); + FFileStream.Seek(Offset, soFromBeginning); + if FFileStream.Read(Result[0], FPageSize) <> FPageSize then + raise Exception.Create('Error reading page: ' + IntToStr(PageNumber)); +end; + +function TPageAnalyzer.GetPageHeader(const PageBuffer: TBytes): TPag; +begin + if Length(PageBuffer) < SizeOf(TPag) then + raise Exception.Create('Page buffer too small for standard header.'); + + Move(PageBuffer[0], Result, SizeOf(TPag)); +end; + +function TPageAnalyzer.GetPageTypeString(PageType: SChar): string; +begin + case PageType of + pag_undefined: Result := 'Undefined page'; + pag_header: Result := 'Header page'; + pag_pip: Result := 'Page Inventory Page (PIP)'; + pag_tip: Result := 'Transaction Inventory Page (TIP)'; + pag_pointer: Result := 'Pointer Page'; + pag_data: Result := 'Data Page'; + pag_index_root: Result := 'Index Root Page'; + pag_index_bt: Result := 'Index B-Tree Page'; + pag_blob: Result := 'Blob Page'; + pag_generator: Result := 'Generator Page'; + pag_wal: Result := 'Write Ahead Log page'; + else + Result := 'Unknown page type: $' + IntToHex(Byte(PageType), 2); + end; +end; + +function TPageAnalyzer.GetPageInfo(PageNumber: ULong): TPageInfo; +begin + Result.Number := PageNumber; + Result.Size := FPageSize; + Result.Buffer := ReadPageToBuffer(PageNumber); + Result.Header := GetPageHeader(Result.Buffer); + Result.TypeStr := GetPageTypeString(Result.Header.pag_type); +end; + +function TPageAnalyzer.GetLastPageNumber: ULong; +begin + Result := (FFileSize div FPageSize) - 1; + if FFileSize mod FPageSize <> 0 then + raise Exception.Create('File size is not a multiple of page size. File might be corrupted.'); +end; + +end. diff --git a/uPointer.pas b/uPointer.pas new file mode 100644 index 0000000..e393479 --- /dev/null +++ b/uPointer.pas @@ -0,0 +1,36 @@ +unit uPointer; + +interface + +uses uStructs, Windows; + +const + MAX_PAGE_SIZE = 32768; + MIN_PAGE_SIZE = 1024; + +type + SChar = Shortint; + SShort = Smallint; + UShort = Word; + SLong = Longint; + ULong = LongWord; + + Tpnr_page = record + pp_header: Tpag; + ppg_sequence: SLong; + ppg_next: SLong; + ppg_count: UShort; + ppg_relation: UShort; + ppg_min_space: UShort; + ppg_max_space: UShort; + + end; + + TPointer_page = record + fix_data: Tpnr_page; + ppg_page: array [0 .. (MAX_PAGE_SIZE - sizeof(Tpnr_page))] of SLong; + end; + +implementation + +end. diff --git a/uStructs.pas b/uStructs.pas new file mode 100644 index 0000000..d493169 --- /dev/null +++ b/uStructs.pas @@ -0,0 +1,202 @@ +unit uStructs; + +interface + +uses + SysUtils, Classes; + +const + MAX_PAGE_SIZE = 32768; + MIN_PAGE_SIZE = 1024; + +type + SChar = Shortint; + SShort = Smallint; + UShort = Word; + SLong = Longint; + ULong = LongWord; + UChar = type Byte; + + TPag = packed record + pag_type: SChar; // 0x00 - Тип страницы + pag_flags: UChar; // 0x01 - Флаги страницы (unsigned!) + pag_checksum: UShort; // 0x02-0x03 - Контрольная сумма (не используется, 12345) + pag_generation: ULong; // 0x04-0x07 - Номер поколения страницы + pag_scn: ULong; // 0x08-0x0b - SCN (Sequence Number), использовался WAL + reserved: ULong; // 0x0c-0x0f - Зарезервировано + end; + + THdr = packed record + hdr_header: TPag; // 0x00-0x0F - Стандартный заголовок + hdr_page_size: UShort; // 0x10-0x11 - Размер страницы + hdr_ods_version: UShort; // 0x12-0x13 - Major ODS version + hdr_pages: SLong; // 0x14-0x17 - Номер первой Pointer Page для RDB$PAGES + hdr_next_page: ULong; // 0x18-0x1B - Номер следующей страницы (многофайловая БД) + hdr_oldest_transaction: SLong; // 0x1C-0x1F - Самая старая активная транзакция + hdr_oldest_active: SLong; // 0x20-0x23 - Самая старая активная (на момент старта) + hdr_next_transaction: SLong; // 0x24-0x27 - Номер следующей транзакции + hdr_sequence: UShort; // 0x28-0x29 - Номер файла в многофайловой БД + hdr_flags: UShort; // 0x2A-0x2B - Флаги базы данных + hdr_creation_date: array [0 .. 1] of SLong; // 0x2C-0x33 - Дата создания + hdr_attachment_id: SLong; // 0x34-0x37 - ID следующего подключения + hdr_shadow_count: SLong; // 0x38-0x3B - Счетчик синхронизации теней + hdr_implementation: SShort; // 0x3C-0x3D - Платформа создания + hdr_ods_minor: UShort; // 0x3E-0x3F - Minor ODS version + hdr_ods_minor_original: UShort;// 0x40-0x41 - Original minor ODS version + hdr_end: UShort; // 0x42-0x43 - Конец данных hdr_data + hdr_page_buffers: ULong; // 0x44-0x47 - Размер кэша страниц + hdr_bumped_transaction: SLong; // 0x48-0x4B - Зарезервировано/устаревшее + hdr_oldest_snapshot: SLong; // 0x4C-0x4F - Самый старый снэпшот + hdr_backup_pages: SLong; // 0x50-0x53 - Количество заблокированных страниц для бэкапа + hdr_misc: array [0 .. 2] of SLong; // 0x54-0x5F - Исправлено: было [0..3], теперь [0..2] (3 элемента) + end; + + THdrPage = packed record + fix_data: THdr; + var_data: array [0 .. MAX_PAGE_SIZE - 1 - SizeOf(THdr)] of Byte; + end; + + // Not IB related + EGDBError = class(Exception); + + PGDBFile = ^TGDBFileInfo; + + TGDBFileInfo = record + Header: THdr; + Filename: ShortString; + ContinuationFile: ShortString; + FirstLogicalPage: LongWord; + LastLogicalPage: LongWord; + TotalPages: LongWord; + end; + + TGDBInfo = class + private + FList: TList; + FFilename: string; + procedure GetDBFiles; + function GetItem(I: Integer): TGDBFileInfo; + public + constructor Create(const AFilename: string); + destructor Destroy; override; + function Count: Integer; + property Items[I: Integer]: TGDBFileInfo read GetItem; default; + end; + +implementation + +{ TGDBInfo } + +function TGDBInfo.Count: Integer; +begin + Result := FList.Count; +end; + +constructor TGDBInfo.Create(const AFilename: string); +begin + inherited Create; + FList := TList.Create; + FFilename := AFilename; + GetDBFiles; +end; + +destructor TGDBInfo.Destroy; +var + I: Integer; +begin + for I := Count - 1 downto 0 do + begin + FreeMem(FList[I]); + FList.Delete(I); + end; + inherited; +end; + +procedure TGDBInfo.GetDBFiles; +var + FS: TFileStream; + HeaderPage: THdrPage; + NewFile: PGDBFile; + CurrentFilename: ShortString; + FilenameSize: Byte; + StartPage: LongWord; + SourceDir: string; + DataOffset: Integer; +begin + if not FileExists(FFilename) then + raise EGDBError.Create('File does not exist - ' + FFilename); + + SourceDir := ExtractFilePath(FFilename); + if SourceDir = '' then + SourceDir := IncludeTrailingBackSlash(GetCurrentDir); + StartPage := 0; + CurrentFilename := SourceDir + ExtractFilename(FFilename); + repeat + FS := TFileStream.Create(CurrentFilename, fmOpenRead or fmShareDenyNone); + try + GetMem(NewFile, SizeOf(TGDBFileInfo)); + FS.Read(HeaderPage, SizeOf(HeaderPage)); + Move(HeaderPage, NewFile.Header, SizeOf(THdr)); + DataOffset := 0; + + while HeaderPage.var_data[DataOffset] <> 3 do // HDR_file + begin + if HeaderPage.var_data[DataOffset + 1] = 0 then + Break; + Inc(DataOffset, HeaderPage.var_data[DataOffset + 1] + 2); + if DataOffset > HeaderPage.fix_data.hdr_page_size - + SizeOf(HeaderPage.fix_data) then + raise EGDBError.Create('Continuation'); + end; + FilenameSize := HeaderPage.var_data[DataOffset + 1]; + NewFile.Filename := CurrentFilename; + SetLength(NewFile.ContinuationFile, FilenameSize); + if FilenameSize > 0 then + Move(HeaderPage.var_data[DataOffset + 2], NewFile.ContinuationFile[1], + FilenameSize); + + + Inc(DataOffset, 2 + FilenameSize); + while (DataOffset < HeaderPage.fix_data.hdr_page_size - SizeOf(HeaderPage.fix_data)) and + (HeaderPage.var_data[DataOffset] <> 4) do + begin + if HeaderPage.var_data[DataOffset + 1] = 0 then + Break; + Inc(DataOffset, HeaderPage.var_data[DataOffset + 1] + 2); + end; + + if (DataOffset < HeaderPage.fix_data.hdr_page_size - SizeOf(HeaderPage.fix_data)) and + (HeaderPage.var_data[DataOffset] = 4) then + begin + Move(HeaderPage.var_data[DataOffset + 2], NewFile.LastLogicalPage, SizeOf(ULong)); + end + else + begin + NewFile.LastLogicalPage := 0; + end; + + NewFile.FirstLogicalPage := StartPage; + if NewFile.LastLogicalPage <> 0 then + NewFile.TotalPages := NewFile.LastLogicalPage - NewFile.FirstLogicalPage + 1 + else + NewFile.TotalPages := 0; + + Inc(StartPage, NewFile.TotalPages); + FList.Add(NewFile); + CurrentFilename := NewFile.ContinuationFile; + if CurrentFilename = '' then + begin + Break; + end; + finally + FS.Free; + end; + until False; +end; + +function TGDBInfo.GetItem(I: Integer): TGDBFileInfo; +begin + Result := PGDBFile(FList[I])^; +end; + +end.