diff --git a/src/borg/repository.py b/src/borg/repository.py index dab40817c..f1b85e375 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -864,6 +864,12 @@ class Repository: if keep: kept.append((offset, obj_id, size)) + if complete: # the listed objects must reach the pack's end, no trailing object unaccounted for + pack_size = self.store.info(pack_key).size + assert ( + covered == pack_size + ), f"pack {bin_to_hex(pack_id)}: {pack_size - covered} trailing bytes unaccounted for" + for drop_id in drop_ids: # remove dropped objects from the index del self.chunks[drop_id] diff --git a/src/borg/testsuite/repository_test.py b/src/borg/testsuite/repository_test.py index fcc5ae0c9..196157011 100644 --- a/src/borg/testsuite/repository_test.py +++ b/src/borg/testsuite/repository_test.py @@ -267,6 +267,22 @@ def test_compact_pack_keep_all_is_noop(repo_fixtures, request): assert new_pack_id == old_pack_id assert pdchunk(repository.get(H(0))) == b"DATA0" assert pdchunk(repository.get(H(1))) == b"DATA1" + + +def test_compact_pack_complete_detects_short_coverage(repo_fixtures, request): + # complete=True must catch a pack whose listed objects do not reach its end: shrink the last + # object's recorded obj_size so the summed coverage falls short of the actual pack file size. + chunk0 = fchunk(b"DATA0", chunk_id=H(0)) + chunk1 = fchunk(b"DATA1", chunk_id=H(1)) + repository = get_repository_from_fixture(repo_fixtures, request) + build_one_pack(repository, [(H(0), chunk0), (H(1), chunk1)]) + with repository: + old_pack_id = repository.chunks[H(0)].pack_id + entry = repository.chunks[H(1)] + repository.chunks[H(1)] = entry._replace(obj_size=entry.obj_size - 1) # leave 1 trailing byte unaccounted + + with pytest.raises(AssertionError): + repository.compact_pack(old_pack_id, keep_ids={H(0), H(1)}, drop_ids=set()) assert bin_to_hex(old_pack_id) in [info.name for info in repository.store_list("packs")]