Open-source Firebird recovery tool: read-only data recovery for Firebird 2.5–5.0 databases.
fbrescue reads .fdb / .gdb files directly, without the Firebird engine, and gets data out of databases that gbak cannot back up. It never modifies the source file.
Status: v0.5.
infoandscananalyze a database;tablesandexportget the schema and the data out without the engine — from intact databases, and from damaged ones as far as the pages allow (recovery rate);pageshows a single page;reportmakes a diagnostic report without data. Not exported yet: arrays and records with 48-bit transaction numbers — reported, never silently dropped. On a damaged database, work on a copy and trygfixandgbakfirst: they are the engine's own tools.
The database does not open, a query or a backup stops with one of these, and Firebird's own gfix and gbak do not get past it:
internal Firebird consistency check (decompression overran buffer (179)),(wrong record length (183)),(cannot find tip page (165))database file appears corrupt,wrong page type,page ... is of wrong type (expected 5, found 0)I/O error during "read" operation for file,Error while trying to read from filegbak: ERROR...gbak:Exiting before completion due to errors
Or the database is fine, but there is no Firebird to open it: fbrescue exports intact databases too — see open a .fdb file without Firebird, export to CSV or Excel, move the data to PostgreSQL.
What each error means and the order of steps that saves the most data: formeo.github.io/fbrescue (по-русски).
Download the archive for your system from Releases: a single binary for Windows, Linux or macOS, with nothing else to install. Each release lists SHA-256 checksums in SHA256SUMS, and every archive carries a build provenance attestation, so you can check that it was built from this repository by its release workflow:
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify fbrescue_v0.5.0_windows_amd64.zip -R formeo/fbrescue
With Go 1.24 or later: go install github.com/formeo/fbrescue/cmd/fbrescue@latest.
$ fbrescue info shop.fdb
File shop.fdb · 2.4 MB · 307 pages × 8 KB
Engine ODS 13.1 (Firebird 5.0) · dialect 3
Created 2026-10-14 08:48:59 (UTC)
Tx OIT 21 · OAT 22 · OST 22 · next 22
Attributes force write
$ fbrescue scan shop.fdb
Pages 307 total · 287 in use · 20 free
data 141 · index 61 · index root 40 · pointer 40 · generator 1 · header 1 · page inventory 1 · transaction inventory 1 · type 10 1
Transactions 1-22: committed 22 · active 0 · dead 0 · limbo 0
Table Pointer pages Data pages Records Deleted Versions
T 1 40 2700 0 386
T2 1 16 1000 250 250
(38 system tables, 3332 records)
No problems found.
$ fbrescue export shop.fdb --out rescued --format sql
T 2700 rows ✓
T2 750 rows ✓
Wrote rescued: schema.sql and 2 sql files
| Command | What it does |
|---|---|
info <db> |
header, ODS version, flags, warnings (nbackup lock, damaged header, truncated file) |
scan <db> |
page map, damage, per-table summary. Exit status 2 when it finds problems: pages holding another page's contents, pages in use with an invalid type, broken pointer page chains, data pages no table references, missing transaction inventory pages, truncated files |
tables <db> |
CREATE TABLE statements for the user tables, from the stored record formats |
export <db> --out <dir> |
the rows of every user table, plus schema.sql. --format csv (default), sql (INSERTs for isql) or jsonl; --excel for CSV that Excel opens by double-click where the decimal separator is a comma (UTF-8 BOM, ;, decimal comma); --table NAME to pick tables; --charset WIN1251 to decode text stored in CHARACTER SET NONE; --include-deleted to also write deleted rows whose old version is still on disk to <table>.deleted.<format>; --versions all to write every version of every row still on disk, with its transaction and that transaction's state, to <table>.versions.<format>; --ddl schema.sql for tables whose definition is lost (see below). Exit status 2 when something was left out: the reasons are printed, and rows that could not be read are listed in _errors.jsonl |
page <db> <N> |
one page decoded: type and header fields; for data pages every record with its flags, transaction state, version chain and values; pointer pages list their data pages |
report <db> |
a JSON report to attach to a bug report: header, page map, problems found, what exporting each table ran into. No values; table and column names are replaced by numbers |
All commands take --json. A single static binary for Windows, Linux and macOS: no fbclient, no cgo. Multi-file databases are supported: secondary files are looked up next to the main file first. A damaged header page is not fatal for info and scan: the page size is detected from the other pages.
What export handles: every column type of Firebird 2.5–5.0 except arrays — BLOBs of every level, segmented and stream; INT128, DECFLOAT, TIME/TIMESTAMP WITH TIME ZONE with region zones; text in UTF8, UNICODE_FSS and the single-byte character sets (WIN125x, KOI8R/U, ISO8859_x, DOSxxx; not CYRL and NEXT); tables altered many times (each row is read with the format it was written with, columns added later get the default the engine would show); records larger than a page; the record encodings of Firebird 2.5 to 5.0. CSV keeps NULL (empty field) apart from an empty string (""). Each row is exported as SELECT would see it after all transactions finished: the newest committed version, read through the chain of older versions, including versions stored as differences. In tests a 140 MB database exports to CSV in under a second.
BLOBs in SQL exports: values up to 1 MB are written inline as joined literals; larger ones go to <table>.blobs/<row>.<column>.txt|bin, with NULL and a comment naming the file in the INSERT. CSV and JSON Lines always hold the whole value. Firebird 2.5's isql reads statements of at most 64 KB, so SQL files with large inline values load only with isql 3.0 or later.
What export does when the file is damaged, instead of stopping:
- Pointer pages or RDB$PAGES damaged: every data page records its table and its place in the table, so the pages the pointer pages no longer reach are found by reading the whole file. Only pages the page inventory marks as in use are taken, so freed pages with old rows do not come back.
- Pages in the wrong place (Firebird 3+ pages carry their own number): read from where they are, and never taken for the page whose place they occupy.
- Transaction inventory damaged: versions of transactions older than the oldest interesting one are committed by definition. For newer ones whose state is lost, the newest version is taken as committed, and the export says for how many rows.
- Header page damaged: the page size comes from the other pages, the ODS version from the system tables (the one under which they read their own names).
- Table or column definitions damaged: tables missing from RDB$RELATIONS come out as
TABLE_<id>, columns missing from RDB$RELATION_FIELDS asFIELD_<id>. When the record formats themselves are lost,--ddltakes CREATE TABLE statements — the database's creation script,isql -xoutput, or theschema.sqlof an earlier fbrescue export — and computes the record layout the engine would give those tables. A definition is matched to its table by name, or, when the name is lost too, by the length of the table's records. It works for tables whose columns were never dropped or changed.
Anything left out or uncertain is in the summary and in _errors.jsonl, and the exit status is 2.
TestRecovery damages copies of a small shop database (three tables, 21 230 rows, indexes, text blobs) in each way below, ten copies per kind on each of Firebird 2.5, 3.0, 4.0 and 5.0, exports them, and compares every row with the export of the intact database by primary key. Changed, reported: a value could not be read, was written as NULL and is listed in _errors.jsonl. Changed silently and extra rows: wrong values with no warning — bytes changed inside a record, which Firebird pages carry no checksum to detect. With --ddl: the same, given the script that created the database.
| Damage | Copies | Rows recovered | With --ddl | Changed, reported | Changed silently | Extra rows | Export failed |
|---|---|---|---|---|---|---|---|
| zeroed data page | 40 | 99.8% | 99.8% | 2 | 0 | 0 | 0 |
| zeroed index page | 40 | 100.0% | 100.0% | 0 | 0 | 0 | 0 |
| zeroed blob page | 40 | 99.9% | 99.9% | 40 | 0 | 0 | 0 |
| zeroed pointer page | 40 | 100.0% | 100.0% | 0 | 0 | 0 | 0 |
| zeroed system table page | 40 | 95.0% | 100.0% | 0 | 0 | 0 | 0 |
| zeroed transaction inventory page | 40 | 100.0% | 100.0% | 0 | 0 | 0 | 0 |
| zeroed page inventory page | 40 | 100.0% | 100.0% | 0 | 0 | 0 | 0 |
| zeroed header page | 40 | 100.0% | 100.0% | 0 | 0 | 0 | 0 |
| page of random bytes | 40 | 99.8% | 99.8% | 58 | 0 | 0 | 0 |
| 16 flipped bits | 40 | 99.9% | 99.9% | 7 | 103 | 31 | 0 |
| zeroed 64 KB block | 40 | 90.3% | 97.8% | 379 | 0 | 0 | 0 |
| two pages swapped | 40 | 99.9% | 99.9% | 1 | 0 | 0 | 0 |
| truncated file | 40 | 68.3% | 68.3% | 160 | 0 | 0 | 0 |
Rows on a page that is zeroed or cut off are gone; the rest is recovered. The test fails if these numbers get worse.
| Version | Scope |
|---|---|
| v0.1 | info and scan: correct analysis of Firebird 2.5–5.0, damaged files open |
| v0.2 | export of intact databases: core types, CSV and SQL, tables |
| v0.3 | BLOBs, record versions, deleted rows, Firebird 4 types, page |
| v0.4 | damaged databases: page structure rebuilt from the pages, published recovery rate, --ddl, report, --versions all |
| v0.5 | export --excel; site in English, Russian and Portuguese |
| v1.0 | load into a new database, stable JSON reports, package managers |
Every assumption about the on-disk format is tested against databases created by real Firebird engines; docs/format.md lists each one and how it was verified. tools/mkcorpus/mkcorpus.sh downloads Firebird 2.5, 3.0, 4.0 and 5.0 and creates 79 test databases in embedded mode: plain data, deleted rows, each header flag fbrescue names, multi-file, three TIP pages, more than one page inventory range, every exported column type with edge values, tables altered seven times, records larger than a page, rolled-back changes and record versions left on disk, BLOBs of every level, Firebird 4 types with edge values, a shop database for the damage tests. Each engine's own gstat -h and gstat -d -r -s output is saved as the reference.
The main export test is a round trip through the engine: the SQL export is loaded into a new database by the same Firebird version, and SELECT on it must print exactly what SELECT on the original prints — every value of every row, in the engine's own formatting. CI runs all tests against the corpus, including damaged copies (zeroed, misplaced and truncated pages, broken chains) and a check that no command ever changes the database file.
# Ubuntu: sudo apt-get install libtommath1 libncurses6
tools/mkcorpus/mkcorpus.sh corpus
FBRESCUE_CORPUS=$PWD/corpus go test ./...fbrescue replaces FirebirdRepairBase, whose problems are listed in FirebirdRepairBase#10.
MIT