-
-
Notifications
You must be signed in to change notification settings - Fork 19
Description
The following is an initial comparison made by Claude of the current "Firebird Internals" documentation (v1.5) against the actual Firebird source code (latest master, v6.0).
Document reviewed: "Firebird Internals: Inside a Firebird Database" v1.5 (9 February 2026) by Norman Dunbar and Mark Rotteveel.
Reference: Firebird source code, specifically src/jrd/ods.h and src/jrd/btr.h from the current Firebird 6.x codebase (which also contains the ODS 12/13 definitions used by Firebird 3.x/4.x/5.x).
Note: The document explicitly states it describes ODS 11.1 (Firebird 2.1) with a 4,096-byte page size and includes a caution that newer ODS versions may differ. However, many structural descriptions are presented as general truths without ODS-version qualification. The errors below are cases where the document's descriptions are factually incorrect even for ODS 11.1, or where the document makes forward-looking claims that are now verifiably wrong.
Error 1: Base Page Header — pag_checksum Description Is Stale
Section: 3. Standard Database Page Header
Document says:
pag_checksum— Two bytes, unsigned. Bytes 0x02 - 0x03. Checksum for the whole page. No longer used, always 12345, 0x3039."From Firebird 3.0, it is possible that this field in the page header will probably have a new name and function."
What actually happened (source: ods.h, line 249):
In ODS 12.0 (Firebird 3.0), pag_checksum was removed entirely. The field at offset 2 is now pag_reserved (USHORT) — it exists solely for alignment padding and carries no semantic meaning.
Additionally, the old pag_reserved at offset 12 was renamed to pag_pageno and repurposed as a page number self-check field for validation.
The speculative language ("it is possible that" / "will probably") should be updated to reflect the actual change that was made over 10 years ago.
Source reference: src/jrd/ods.h lines 246–256:
struct pag
{
UCHAR pag_type;
UCHAR pag_flags;
USHORT pag_reserved; // not used but anyway present because of alignment rules
ULONG pag_generation;
ULONG pag_scn;
ULONG pag_pageno; // for validation
};Error 2: Base Page Header — Missing crypted_page Flag
Section: 3. Standard Database Page Header
Document says: The pag_flags byte "holds various flags for the page" (no specifics given for the base header level).
What's missing: Since ODS 12.0, bit 7 (0x80) of pag_flags is used as the crypted_page flag on all page types. When set, it indicates the page's data (everything after the 16-byte pag header) is encrypted on disk.
Source reference: src/jrd/ods.h line 242:
inline constexpr UCHAR crypted_page = 0x80;Error 3: Page Type 0x0a — Described as "Write Ahead Log Page"
Section: 3. Standard Database Page Header (page type table) & Section 13
Document says:
0x0a — Page 2 of any database is a Write Ahead Log page. These pages are no longer used.
"From Firebird 3.0 it is likely there will not be a WAL page in any new databases."
Actual: In ODS 12.0 (Firebird 3.0), page type 10 (0x0a) was repurposed as pag_scns — the SCN Inventory Page. Page 2 (FIRST_SCN_PAGE) is now always a SCN page, not a WAL page. WAL pages no longer exist at all.
Source reference: src/jrd/ods.h lines 210, 216:
inline constexpr SCHAR pag_scns = 10; // SCN's inventory page
inline constexpr ULONG FIRST_SCN_PAGE = 2;Error 4: Header Page — Struct Layout Does Not Match ODS 12.0+
Section: 4. Database Header Page — Type 0x01
Document shows the header page struct as:
struct header_page
{
pag hdr_header;
USHORT hdr_page_size;
USHORT hdr_ods_version;
SLONG hdr_PAGES;
ULONG hdr_next_page;
SLONG hdr_oldest_transaction;
SLONG hdr_oldest_active;
SLONG hdr_next_transaction;
USHORT hdr_sequence;
USHORT hdr_flags;
SLONG hdr_creation_date[2];
SLONG hdr_attachment_id;
...
UCHAR hdr_data[1];
};Actual (ODS 12.0+, source: ods.h lines 512–543):
The struct was completely reorganized. Key differences:
| Issue | Document | Actual (ODS 12.0+) |
|---|---|---|
hdr_ods_minor position |
Offset 0x3e (near end) | Offset 20 (right after hdr_ods_version) |
| Transaction fields type | SLONG (4 bytes) | FB_UINT64 (8 bytes) |
hdr_attachment_id type |
SLONG (4 bytes) | FB_UINT64 (8 bytes) |
hdr_sequence |
Present | Removed |
hdr_next_page |
Present | Removed |
hdr_implementation |
SSHORT | Replaced by hdr_db_impl struct (4 bytes: cpu, os, cc, compat) |
hdr_ods_minor_original |
Present | Removed |
hdr_bumped_transaction |
Present | Removed |
hdr_backup_pages |
Present | Removed |
hdr_misc[3] |
Present (12 bytes) | Removed |
hdr_backup_mode |
In hdr_flags bits |
Separate UCHAR field at offset 24 |
hdr_shutdown_mode |
In hdr_flags bits |
Separate UCHAR field at offset 25 |
hdr_replica_mode |
N/A | New UCHAR field at offset 26 |
hdr_guid |
N/A | New UCHAR[16] at offset 84 |
hdr_crypt_page |
N/A | New ULONG at offset 112 |
hdr_crypt_plugin |
N/A | New TEXT[32] at offset 116 |
| Total fixed size | ~96 bytes | 152 bytes |
hdr_data offset |
0x60 | 0x94 (148) |
While the document does say it describes ODS 11.1, it would benefit from at least noting that the header page layout changed fundamentally in ODS 12.0.
Error 5: Header Page Flags — Incomplete and Partially Wrong for Modern ODS
Section: 4. Database Header Page — hdr_flags
Document shows hdr_shutdown_mask as 0x1080 (bits 7 and 12) packed into hdr_flags.
Actual (ODS 12.0+): Shutdown mode and backup mode are no longer packed into hdr_flags. They have their own dedicated byte fields (hdr_shutdown_mode and hdr_backup_mode).
The current hdr_flags are (ods.h lines 592–598):
| Flag | Value | Description |
|---|---|---|
hdr_active_shadow |
0x01 | Active shadow file |
hdr_force_write |
0x02 | Forced writes |
hdr_crypt_process |
0x04 | Encryption status changing (was unused bit 2) |
hdr_no_reserve |
0x08 | Don't reserve space for versions (was 0x20) |
hdr_SQL_dialect_3 |
0x10 | SQL dialect 3 (was 0x100) |
hdr_read_only |
0x20 | Read-only (was 0x200) |
hdr_encrypted |
0x40 | Database is encrypted (new) |
Note: The flag values have been reassigned. In ODS 11.x, hdr_no_reserve was 0x20 and hdr_SQL_dialect_3 was 0x100. In ODS 12.0+, they are 0x08 and 0x10 respectively.
Error 6: Header Page Clumplets — Values Are Renumbered in ODS 12.0+
Section: 4. Database Header Page — clumplet types
Document shows HDR_sweep_interval = 0x06 and HDR_difference_file = 0x0c.
Actual (ODS 12.0+, ods.h lines 575–588):
| Value | Document (ODS 11.x) | Actual (ODS 12.0+) |
|---|---|---|
| 0 | HDR_end |
HDR_end ✅ |
| 1 | HDR_root_file_name |
HDR_root_file_name ✅ |
| 2 | HDR_journal_server |
(commented out as HDR_file) |
| 3 | HDR_file |
(commented out as HDR_last_page) |
| 4 | HDR_last_page |
HDR_sweep_interval |
| 5 | HDR_unlicensed |
HDR_crypt_checksum |
| 6 | HDR_sweep_interval |
HDR_difference_file |
| 7 | HDR_log_name |
HDR_backup_guid |
| 8 | HDR_journal_file |
HDR_crypt_key |
| 9 | HDR_password_file_key |
HDR_crypt_hash |
| 10 | HDR_backup_info |
(commented out: HDR_db_guid) |
| 11 | HDR_cache_file |
HDR_repl_seq |
| 12 | HDR_difference_file |
(no longer exists) |
| 13 | HDR_backup_guid |
(no longer exists) |
The clumplet numbering was completely reshuffled. Several old types were removed and new encryption/replication types were added.
Error 7: PIP Page — Missing New Fields
Section: 5. Page Inventory Page — Type 0x02
Document shows the PIP struct as:
struct page_inv_page
{
pag pip_header;
SLONG pip_min;
UCHAR pip_bits[1];
};Actual (ODS 12.0+, ods.h lines 619–625):
struct page_inv_page
{
pag pip_header;
ULONG pip_min; // was SLONG
ULONG pip_extent; // NEW - lowest free extent
ULONG pip_used; // NEW - number of pages allocated from this PIP
UCHAR pip_bits[1];
};The total fixed size grew from 20 bytes to 32 bytes. The pip_bits array now starts at offset 28 instead of 20. pip_min changed from SLONG to ULONG.
Error 8: Pointer Page — ppg_max_space Field
Section: 7. Pointer Page — Type 0x04
Document says: ppg_max_space exists at offset 0x1e, "intended to indicate the last entry in the ppg_page array holding a page number which has free space in the page, but it has never been used."
Actual (ODS 12.0+, ods.h lines 690–699): The ppg_max_space field has been removed entirely. The pointer page struct no longer contains this field. ppg_page[] now starts at offset 32 (was 0x20).
Also, the per-data-page bitmaps at the end of the pointer page now use 8 bits (one full byte) per data page instead of the 2-bit encoding described in the document. The flags are (ods.h lines 713–717):
| Flag | Value | Description |
|---|---|---|
ppg_dp_full |
0x01 | Data page is FULL |
ppg_dp_large |
0x02 | Large object on data page |
ppg_dp_swept |
0x04 | Sweep has nothing to do (new) |
ppg_dp_secondary |
0x08 | Primary record versions not stored (new) |
ppg_dp_empty |
0x10 | Data page is empty (new) |
Error 9: Data Page — Offsets Are Wrong for ODS 12.0+
Section: 8. Data Page — Type 0x05
Document says:
dpg_sequence— Offset 0x10 on the page
dpg_relation— Offset 0x14 on the page
dpg_count— Offset 0x16 on the page
dpg_rpt— begins at offset 0x18 on the page
Actual (ODS 12.0+, ods.h lines 341–352):
The offsets are still correct (base pag header is still 16 bytes, so data page fields start at offset 16), but:
dpg_sequenceis now ULONG (was SLONG) — offset 0x10 ✅dpg_relation— offset 0x14 ✅dpg_count— offset 0x16 ✅dpg_rpt— offset 0x18 ✅
The offsets happen to still be correct. However, the document says dpg_rpt is at offset 0x18, while in version 1.2 of the document there was an off-by-2 error that was already fixed.
The new flags dpg_swept (0x08) and dpg_secondary (0x10) are not mentioned.
Error 10: Record Header — Missing rhde Struct and New Flags
Section: 8.1. Record Header
Document shows only rhd (unfragmented) and rhdf (fragmented) record headers.
What's missing (ODS 12.0+, ods.h lines 794–804):
There is a third record header variant: rhde — the extended record header used when rhd_long_tranum (0x400) is set. This struct has a rhde_tra_high field (USHORT) that stores the upper 16 bits of a 48-bit transaction number.
Similarly, rhdf now also includes rhdf_tra_high between rhdf_format and rhdf_f_page.
New record flags not documented:
| Flag | Value | Description |
|---|---|---|
rhd_uk_modified |
0x200 | Unique key field values changed |
rhd_long_tranum |
0x400 | Transaction number is 64-bit (use rhde/rhdf) |
rhd_not_packed |
0x800 | Record stored as-is (no RLE compression) |
Source reference: src/jrd/ods.h lines 886–897.
Error 11: Index Root Page — Struct Layout Significantly Changed
Section: 9. Index Root Page — Type 0x06
Document shows irt_repeat as 12 bytes (0x0b in the text — which is actually 11, likely a typo):
struct irt_repeat {
SLONG irt_root;
union {
float irt_selectivity;
SLONG irt_transaction;
} irt_stuff;
USHORT irt_desc;
UCHAR irt_keys;
UCHAR irt_flags;
};Actual (ODS 12.0+, ods.h lines 393–421): irt_repeat is now 24 bytes:
| Offset | Field | Type | Size |
|---|---|---|---|
| 0 | irt_transaction |
FB_UINT64 | 8 |
| 8 | irt_page_num |
ULONG | 4 |
| 12 | irt_page_space_id |
ULONG | 4 |
| 16 | irt_desc |
USHORT | 2 |
| 18 | irt_flags |
USHORT | 2 (was UCHAR) |
| 20 | irt_state |
UCHAR | 1 (new) |
| 21 | irt_keys |
UCHAR | 1 |
| 22 | irt_dummy |
USHORT | 2 (alignment) |
Key changes:
irt_rootreplaced byirt_page_num+irt_page_space_idirt_selectivityremoved from the repeat struct (lives only in key descriptorsirtd)irt_transactionis now FB_UINT64 (was SLONG in union)irt_flagswidened from UCHAR to USHORT- New
irt_statefield replaces the old in-progress behavior - New flag:
irt_condition(0x20) for conditional indices
The document also says descriptors are "0x0b bytes long" — this should be 12 bytes for ODS 11.x (4+4+2+1+1 = 12), and is 24 bytes for ODS 12.0+.
Error 12: Index Root Page — Index Flag Values
Section: 9. Index Root Page — irt_flags
Document shows:
Bit 3: Index is a foreign key index
Bit 4: Index is a primary key index
Actual (ODS 12.0+, ods.h lines 442–447):
inline constexpr USHORT irt_unique = 1; // bit 0
inline constexpr USHORT irt_descending = 2; // bit 1
inline constexpr USHORT irt_foreign = 4; // bit 2 ← was bit 3 in document
inline constexpr USHORT irt_primary = 8; // bit 3 ← was bit 4 in document
inline constexpr USHORT irt_expression = 16; // bit 4 ← was bit 5 in document
inline constexpr USHORT irt_condition = 32; // bit 5 ← NEWThe document has the foreign key flag at bit 3 (value 8) and primary key at bit 4 (value 16). The actual source code has them at bit 2 (value 4) and bit 3 (value 8) respectively. The document's bit numbering appears to have been wrong even for ODS 11.x — these flag values have not changed between ODS versions.
Cross-checking with btr.h (lines 112–117) confirms:
inline constexpr int idx_unique = 1;
inline constexpr int idx_descending = 2;
inline constexpr int idx_foreign = 4;
inline constexpr int idx_primary = 8;
inline constexpr int idx_expression = 16;
inline constexpr int idx_condition = 32;However, looking at the old fbdump source code, the old fbdump also used these same values (bit 2 = in_progress, bit 3 = foreign, bit 4 = primary, bit 5 = expression), and its output correctly shows "Foreign Key" for flag value 8 and "Primary Key" for flag value 17. So the document's description of the bit positions is wrong, but the old fbdump code was actually correct (it tests flags & 4 for "Creating", flags & 8 for "Foreign Key", flags & 16 for "Primary Key").
Wait — re-reading the document more carefully, it says:
- Bit 2: "Index [creation?] is in progress"
- Bit 3: "Index is a foreign key index"
- Bit 4: "Index is a primary key index"
And the source says:
- Bit 2 (
irt_foreign = 4): Foreign key - Bit 3 (
irt_primary = 8): Primary key - Bit 4 (
irt_expression = 16): Expression
So the document has irt_in_progress at bit 2 where the source has irt_foreign. In the current source, the "in progress" concept is handled by irt_state, not as a flag bit. In older ODS, bit 2 was irt_in_progress (value 4). The bit assignments for foreign (bit 3) and primary (bit 4) in the document match the old ODS 11.x code.
Corrected assessment: For ODS 11.x, the document's flag assignments may have been correct. But in ODS 12.0+, irt_in_progress was removed as a flag and the remaining flags were renumbered:
irt_foreignmoved from bit 3 → bit 2irt_primarymoved from bit 4 → bit 3irt_expressionmoved from bit 5 → bit 4irt_conditionadded at bit 5
Error 13: B-tree Page — Jump Info Structure Differs
Section: 10.2. Index Jump Info
Document says the jump info is a separate struct following the btree header at byte 0x22:
struct IndexJumpInfo
{
USHORT firstNodeOffset;
USHORT jumpAreaSize;
UCHAR jumpers;
};And says jumpers is at "Offset 0x05 in the structure" (this is wrong — it should be offset 0x04 within the 5-byte struct).
Actual (ODS 12.0+, ods.h lines 293–312):
The jump info fields are now integrated directly into the btree_page struct:
struct btree_page
{
pag btr_header; // offset 0, 16 bytes
ULONG btr_sibling; // offset 16
ULONG btr_left_sibling; // offset 20
SLONG btr_prefix_total; // offset 24
USHORT btr_relation; // offset 28
USHORT btr_length; // offset 30
UCHAR btr_id; // offset 32
UCHAR btr_level; // offset 33
USHORT btr_jump_interval; // offset 34 ← replaces firstNodeOffset
USHORT btr_jump_size; // offset 36 ← replaces jumpAreaSize
UCHAR btr_jump_count; // offset 38 ← replaces jumpers
UCHAR btr_nodes[1]; // offset 39 ← node data starts here
};There is no separate IndexJumpInfo struct. The three jump fields are regular members of btree_page. Also:
- The field formerly called
firstNodeOffsetis nowbtr_jump_interval(jump interval between nodes, different semantic) jumpAreaSize→btr_jump_sizejumpers→btr_jump_count- Node data starts at offset 39 (was 0x27 = 39 if you add the 5-byte jump info to 0x22, so the byte offset is coincidentally the same but the structural approach is different)
Error 14: B-tree Page — pag_flags Bit Numbering
Section: 10.1. B-Tree Header — pag_flags
Document says:
Bit 3: set means that this page/bucket is part of a descending index
Bit 4: set means that non-leaf nodes will contain record number information
Bit 5: set means that large keys are permitted/used
Bit 6: set means that the page contains index jump nodes
Actual (ODS 12.0+, ods.h lines 331–337):
The old B-tree page flags are all commented out in the current source:
//const UCHAR btr_dont_gc = 1; // Don't garbage-collect this page
//const UCHAR btr_descending = 2; // Page/bucket is part of a descending index
//const UCHAR btr_jump_info = 16; // AB: 2003-index-structure enhancement
inline constexpr UCHAR btr_released = 32; // Page was released from b-treeThe only remaining active flag is btr_released = 32 (bit 5). The descending flag was at bit 1 (value 2), not bit 3 as the document states. The document's bit 3 = "descending" is wrong — btr_descending = 2 = bit 1.
Note: the old fbdump source code at fbdump.c line ~822 also has the descending flag at bit 2 (value 4), which also doesn't match the Firebird source's value of 2.
Error 15: Generator Page — gpg_waste Fields Changed
Section: 12. Generator Page — Type 0x09
Document shows:
struct generator_page
{
pag gpg_header;
SLONG gpg_sequence;
SLONG gpg_waste1;
USHORT gpg_waste2;
USHORT gpg_waste3;
USHORT gpg_waste4;
USHORT gpg_waste5;
SINT64 gpg_values[1];
};Actual (ODS 12.0+, ods.h lines 752–758):
struct generator_page
{
pag gpg_header;
ULONG gpg_sequence; // was SLONG
ULONG gpg_dummy1; // single dummy for alignment
SINT64 gpg_values[1]; // starts at offset 24
};The five waste fields (gpg_waste1 through gpg_waste5, totaling 12 bytes) have been replaced by a single gpg_dummy1 (ULONG, 4 bytes). This means gpg_values[] now starts at offset 24 instead of offset 32. The capacity per page is therefore higher in ODS 12.0+: (pageSize - 24) / 8 generators per page (vs. the old (pageSize - 32) / 8).
For an 8192-byte page: 1021 generators per page (ODS 12.0+) vs. 1020 (ODS 11.x with 4-byte-aligned start at offset 32 — though the document says the formula is (page_size - 32) / 8 which gives 508 for 4K pages).
Error 16: Index Jump Node — Struct Contains Pointers (Not On-Disk)
Section: 10.3. Index Jump Nodes
Document shows:
struct IndexJumpNode
{
UCHAR* nodePointer;
USHORT prefix;
USHORT length;
USHORT offset;
UCHAR* data;
};Issue: This struct contains pointers (UCHAR* nodePointer and UCHAR* data). These are in-memory runtime structures, not on-disk formats. Pointers are 4 or 8 bytes depending on platform and are meaningless on disk. The actual on-disk representation of jump nodes is a packed sequence of: prefix (variable-length encoded), length (variable-length encoded), offset (variable-length encoded), and data bytes — not a fixed struct with pointers.
The document presents this as if it were the on-disk layout, which is misleading.
Summary
| # | Section | Severity | Category |
|---|---|---|---|
| 1 | Base pag header — checksum | Medium | Stale/speculative text |
| 2 | Base pag header — crypted_page flag | Low | Missing info |
| 3 | Page type 0x0a — WAL vs SCN | High | Factually wrong for ODS 12+ |
| 4 | Header page — struct layout | High | Completely different in ODS 12+ |
| 5 | Header page — flag values | High | Values reassigned in ODS 12+ |
| 6 | Header page — clumplet values | High | Renumbered in ODS 12+ |
| 7 | PIP page — missing fields | Medium | Missing pip_extent, pip_used |
| 8 | Pointer page — ppg_max_space | Medium | Removed; per-page bits changed from 2→8 |
| 9 | Data page — new flags | Low | Missing dpg_swept, dpg_secondary |
| 10 | Record header — missing rhde |
Medium | Missing struct and new flags |
| 11 | Index root — irt_repeat size | High | 24 bytes, not 12 |
| 12 | Index root — flag bit positions | Medium | Renumbered in ODS 12+ |
| 13 | B-tree page — jump info | Medium | Integrated into struct |
| 14 | B-tree page — pag_flags bits | Medium | Wrong bit for descending |
| 15 | Generator page — waste fields | Medium | Reduced; gpg_values offset changed |
| 16 | Jump node — pointer fields | Low | Runtime struct, not on-disk format |