borg2 check: implement repository repair (index and pack rebuild)
borg2 check: implement repository repair (index and pack rebuild)
Refs #9696, #8466, #8476, #9901, #9825. Tracking: #9998.
Problem
The read-only repository check is done: it hashes index/ and packs/ objects via Store.hash,
checks the index first and the packs only if the index is intact, and never rebuilds the index on
that path. But when it finds damage, there is no repair. Repository.check(repair=True) just logs
and returns:
# repository.py, check()
elif repair:
logger.error("Finished ... repository check, errors found (repository repair not implemented).")and the corrupt-index branch has the placeholder for the missing part:
# TODO: --repair will rebuild the index from the packs here instead of stopping (refs #8572).
logger.error("Repository index is corrupted and must be repaired; skipping the pack check.")A repo with a corrupt index/ fragment or a corrupt pack therefore cannot be repaired at the
repository level. This ticket splits that work out of #9696 ("... to be continued").
Scope
Repository-level repair only: rebuilding the chunks index and salvaging packs. Archives repair
(dropping archives with corrupt/lost metadata, recreating lost entries) already exists, and missing
file-content chunks heal on the next borg create. Both are out of scope.
Work items, in order
Command surface. Decide before writing repair code: keep it under
borg check --repair, or split off a read-onlyborg checkand a separateborg repairwith explicit targets (--index,--packs PACKID...) (#9696). The items below assume the existingcheckpaths; only the CLI shape depends on this.Index repair from headers. Rebuild the chunks index by range-reading pack object headers and skipping the payloads, recording
chunk id -> (pack id, offset, size). Reading full packs is too slow to be the routine repair path (#8476, #8572). Replaces the TODO in the corrupt-index branch.Do not trust the headers blindly (#9901). Headers are unauthenticated, so a tampered header (wrong id or sizes) would poison the rebuilt index. Authenticate on first real read (AEAD binds the id to the content) and/or verify ids for mismatching entries. Part of item 2, not a follow-up.
Pack repair. For a pack that fails
Store.hash, keep every object that still AEAD-authenticates in a new pack, drop the corrupt pack, and update the index. Objects that no longer decrypt are already gone; drop them from the index so the affected archives report missing chunks and heal on the next create (#8476, #8572). Needs item 2.Consume the persisted corrupt-pack list. A read-only check records corrupt pack ids in
cache/checked-packs(#9897; PR #9925 makes them survive a completed check). Repair reads that list and touches only the flagged packs, then clears their records once fixed (#9696). Needs PR #9925 plus items 2 and 4.Keep the rebuilt index (#8466).
ArchiveChecker.finish()deletes the cached chunks index after repair, so the next command pays for a slow rebuild. Write the index rebuilt in item 2 back instead. Needs item 2.Repair progress (#9443, #8009). The read-only check shows per-loop percentages; the repair paths (header scan, pack salvage, verify-data delete pass) need the same. Needs items 2 and 4.
Out of scope (tracked elsewhere)
- Vanished-pack detection, cheap cloud check mode (#9898, PR #9925).
- Reporting/UX: findings+repairs summary (#2397, PR #9891), grouped missing-chunk reports (#9218, PR #9965), Ctrl-C at safe boundaries (#7893, PR #9966).
- Post-corruption docs and acknowledging known-lost chunks so check/compact stop returning exit 2 (#9825).
- Repair from a redundant related repo (#6584, milestone 2.1).
Data-safety constraints
- Never delete an object after a single failed read; a second read must also fail first. Transient network or RAM errors must not destroy data.
- A rebuilt index must never claim objects that do not exist. A lying index breaks
borg creatededup (#8476). - An interrupted repair must leave the repository consistent (#7893).
Source: borgbackup/borg