git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 2/8] midx.c: make changing the preferred pack safe

From
Taylor Blau <me@ttaylorr.com>
Date
Dec 14, 2021, 01:55 UTC
Message-ID
<4ea52e66dd6cbc5ca32c36c60266e9757b784a04.1639446906.git.me@ttaylorr.com>
In-Reply-To
<cover.1639446906.git.me@ttaylorr.com>

The previous patch demonstrates a bug where a MIDX's auxiliary object order can become out of sync with a MIDX bitmap.

This is because of two confounding factors:
  - First, the object order is stored in a file which is named according
    to the multi-pack index's checksum, and the MIDX does not store the
    object order. This means that the object order can change without
    altering the checksum.
  - But the .rev file is moved into place with finalize_object_file(),
    which link(2)'s the file into place instead of renaming it. For us,
    that means that a modified .rev file will not be moved into place if
    MIDX's checksum was unchanged.

The fix here is two-fold. First, we need to stop linking the file into place and instead rename it. It's likely we were using finalize_object_file() instead of a pure rename() because the former also adjusts shared permissions. But that is unnecessary, because we already do so in write_rev_file_order(), so rename alone is safe.

But we also need to make the MIDX's checksum change in some way when the preferred pack changes without altering the set of packs stored in a MIDX to prevent a race where the new .rev file is moved into place before the MIDX is updated. Here, you'd get the opposite effect: reading old bitmaps with the new object order.

But this race bites us even here: suppose that we didn't change the MIDX checksum, but only renamed the auxiliary object order into place instead of hardlinking it. Then when we go to generate the new bitmap, we'll load the old MIDX bitmap, along with the MIDX that it references. That's fine, since the new MIDX isn't moved into place until after the new bitmap is generated. But the new object order *has* been moved into place. So we'll read the old bitmaps in the new order when generating the new bitmap file, meaning that without this secondary change, bitmap generation itself would become a victim of the race described here.

This can all be prevented by forcing the MIDX's checksum to change when the object order changes. We could include the entire object order in the MIDX, but doing so is somewhat awkward. (For example, the code that writes a .rev file expects to know the checksum of the associated pack or MIDX, but writing that data into the MIDX itself makes that a circular dependency).

Instead, make the object order used during bitmap generation part of the MIDX itself. That means that the new test in t5326 will cause the MIDX's checksum to update, preventing the stale read problem.

In theory, it is possible to store a "fingerprint" of the full object order here, so long as that fingerprint changes at least as often as the full object order does. Some possibilities here include storing the identity of the preferred pack, along with the mtimes of the non-preferred packs in a consistent order. But storing a limited part of the information makes it difficult to reason about whether or not there are gaps between the two that would cause us to get bitten by this bug again.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
---
 Documentation/technical/multi-pack-index.txt |  1 +
 Documentation/technical/pack-format.txt      | 13 +++++-----
 midx.c                                       | 25 ++++++++++++++++----
 t/t5326-multi-pack-bitmaps.sh                |  2 +-
 4 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt
index 86f40f2490..6e1fb092c6 100644
--- a/Documentation/technical/multi-pack-index.txt
+++ b/Documentation/technical/multi-pack-index.txt
@@ -24,6 +24,7 @@ and their offsets into multiple packfiles. It contains:
   - An offset within the jth packfile for the object.
 - If large offsets are required, we use another list of large
   offsets similar to version 2 pack-indexes.
+- An optional list of objects in pseudo-pack order (used with MIDX bitmaps).
 
 Thus, we can provide O(log N) lookup time for any number
 of packfiles.
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
index 8d2f42f29e..6d3efb7d16 100644
--- a/Documentation/technical/pack-format.txt
+++ b/Documentation/technical/pack-format.txt
@@ -376,6 +376,11 @@ CHUNK DATA:
 	[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
 	    8-byte offsets into large packfiles.
 
+	[Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'})
+	    A list of MIDX positions (one per object in the MIDX, num_objects in
+	    total, each a 4-byte unsigned integer in network byte order), sorted
+	    according to their relative bitmap/pseudo-pack positions.
+
 TRAILER:
 
 	Index checksum of the above contents.
@@ -456,9 +461,5 @@ In short, a MIDX's pseudo-pack is the de-duplicated concatenation of
 objects in packs stored by the MIDX, laid out in pack order, and the
 packs arranged in MIDX order (with the preferred pack coming first).
 
-Finally, note that the MIDX's reverse index is not stored as a chunk in
-the multi-pack-index itself. This is done because the reverse index
-includes the checksum of the pack or MIDX to which it belongs, which
-makes it impossible to write in the MIDX. To avoid races when rewriting
-the MIDX, a MIDX reverse index includes the MIDX's checksum in its
-filename (e.g., `multi-pack-index-xyz.rev`).
+The MIDX's reverse index is stored in the optional 'RIDX' chunk within
+the MIDX itself.
diff --git a/midx.c b/midx.c
index 837b46b2af..d3179e9c02 100644
--- a/midx.c
+++ b/midx.c
@@ -33,6 +33,7 @@
 #define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
 #define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
 #define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */
+#define MIDX_CHUNKID_REVINDEX 0x52494458 /* "RIDX" */
 #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
 #define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t))
 #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
@@ -833,6 +834,18 @@ static int write_midx_large_offsets(struct hashfile *f,
 	return 0;
 }
 
+static int write_midx_revindex(struct hashfile *f,
+			       void *data)
+{
+	struct write_midx_context *ctx = data;
+	uint32_t i;
+
+	for (i = 0; i < ctx->entries_nr; i++)
+		hashwrite_be32(f, ctx->pack_order[i]);
+
+	return 0;
+}
+
 struct midx_pack_order_data {
 	uint32_t nr;
 	uint32_t pack;
@@ -891,7 +904,7 @@ static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash,
 	tmp_file = write_rev_file_order(NULL, ctx->pack_order, ctx->entries_nr,
 					midx_hash, WRITE_REV);
 
-	if (finalize_object_file(tmp_file, buf.buf))
+	if (rename(tmp_file, buf.buf))
 		die(_("cannot store reverse index file"));
 
 	strbuf_release(&buf);
@@ -1403,15 +1416,19 @@ static int write_midx_internal(const char *object_dir,
 			(size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH,
 			write_midx_large_offsets);
 
+	if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) {
+		ctx.pack_order = midx_pack_order(&ctx);
+		add_chunk(cf, MIDX_CHUNKID_REVINDEX,
+			  ctx.entries_nr * sizeof(uint32_t),
+			  write_midx_revindex);
+	}
+
 	write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
 	write_chunkfile(cf, &ctx);
 
 	finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
 	free_chunkfile(cf);
 
-	if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
-		ctx.pack_order = midx_pack_order(&ctx);
-
 	if (flags & MIDX_WRITE_REV_INDEX)
 		write_midx_reverse_index(midx_name.buf, midx_hash, &ctx);
 	if (flags & MIDX_WRITE_BITMAP) {
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index 0ca2868b0b..353282310d 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -395,7 +395,7 @@ test_expect_success 'hash-cache values are propagated from pack bitmaps' '
 	)
 '
 
-test_expect_failure 'changing the preferred pack does not corrupt bitmaps' '
+test_expect_success 'changing the preferred pack does not corrupt bitmaps' '
 	rm -fr repo &&
 	git init repo &&
 	test_when_finished "rm -fr repo" &&
-- 
2.34.1.25.gb3157a20e6
Previous: Taylor BlauNext: Taylor Blau
Message 13 of 87 in “midx: prevent bitmap corruption when permuting pack order”
  1. 0/2 midx: prevent bitmap corruption when permuting pack orderTaylor Blau, Dec 8, 2021
  2. 1/2 t5326: demonstrate bitmap corruption after permutationTaylor Blau, Dec 8, 2021
  3. 2/2 midx.c: make changing the preferred pack safeTaylor Blau, Dec 8, 2021
  4. Derrick StoleeDec 8, 2021
  5. Jeff KingDec 8, 2021
  6. Taylor BlauDec 10, 2021
  7. Taylor BlauDec 10, 2021
  8. Taylor BlauDec 11, 2021
  9. Derrick StoleeDec 13, 2021
  10. Taylor BlauDec 13, 2021
  11. 0/8 midx: prevent bitmap corruption when permuting pack orderTaylor Blau, Dec 14, 2021
  12. 1/8 t5326: demonstrate bitmap corruption after permutationTaylor Blau, Dec 14, 2021
  13. 2/8 midx.c: make changing the preferred pack safeTaylor Blau, Dec 14, 2021
  14. 3/8 pack-revindex.c: instrument loading on-disk reverse indexTaylor Blau, Dec 14, 2021
  15. 4/8 t5326: drop unnecessary setupTaylor Blau, Dec 14, 2021
  16. 5/8 t5326: extract `test_rev_exists`Taylor Blau, Dec 14, 2021
  17. Derrick StoleeDec 20, 2021
  18. Taylor BlauJan 4, 2022
  19. 6/8 t5326: move tests to t/lib-bitmap.shTaylor Blau, Dec 14, 2021
  20. 7/8 t/lib-bitmap.sh: parameterize tests over reverse index sourceTaylor Blau, Dec 14, 2021
  21. 8/8 midx: read `RIDX` chunk when presentTaylor Blau, Dec 14, 2021
  22. Derrick StoleeDec 20, 2021
  23. Taylor BlauJan 4, 2022
  24. Junio C HamanoDec 15, 2021
  25. Taylor BlauDec 15, 2021
  26. Junio C HamanoDec 15, 2021
  27. Junio C HamanoDec 15, 2021
  28. Derrick StoleeDec 20, 2021
  29. Taylor BlauDec 20, 2021
  30. Derrick StoleeDec 20, 2021
  31. Junio C HamanoDec 15, 2021
  32. Taylor BlauDec 15, 2021
  33. 0/9 midx: prevent bitmap corruption when permuting pack orderTaylor Blau, Jan 4, 2022
  34. 1/9 t5326: demonstrate bitmap corruption after permutationTaylor Blau, Jan 4, 2022
  35. Jonathan TanJan 20, 2022
  36. Taylor BlauJan 20, 2022
  37. Junio C HamanoJan 20, 2022
  38. Taylor BlauJan 20, 2022
  39. Jonathan TanJan 24, 2022
  40. 2/9 midx.c: make changing the preferred pack safeTaylor Blau, Jan 4, 2022
  41. Junio C HamanoJan 14, 2022
  42. Junio C HamanoJan 14, 2022
  43. Taylor BlauJan 15, 2022
  44. Junio C HamanoJan 15, 2022
  45. Jonathan TanJan 20, 2022
  46. Taylor BlauJan 20, 2022
  47. 3/9 pack-revindex.c: instrument loading on-disk reverse indexTaylor Blau, Jan 4, 2022
  48. Jonathan TanJan 20, 2022
  49. Taylor BlauJan 20, 2022
  50. Jonathan TanJan 24, 2022
  51. 4/9 t5326: drop unnecessary setupTaylor Blau, Jan 4, 2022
  52. 5/9 t5326: extract `test_rev_exists`Taylor Blau, Jan 4, 2022
  53. 6/9 t5326: move tests to t/lib-bitmap.shTaylor Blau, Jan 4, 2022
  54. 7/9 t/lib-bitmap.sh: parameterize tests over reverse index sourceTaylor Blau, Jan 4, 2022
  55. Jonathan TanJan 24, 2022
  56. Taylor BlauJan 25, 2022
  57. Jonathan TanJan 26, 2022
  58. 8/9 midx: read `RIDX` chunk when presentTaylor Blau, Jan 4, 2022
  59. Jonathan TanJan 24, 2022
  60. Taylor BlauJan 25, 2022
  61. Jonathan TanJan 26, 2022
  62. 9/9 pack-bitmap.c: gracefully fallback after opening pack/MIDXTaylor Blau, Jan 4, 2022
  63. Jonathan TanJan 24, 2022
  64. Taylor BlauJan 25, 2022
  65. 0/9 midx: prevent bitmap corruption when permuting pack orderTaylor Blau, Jan 25, 2022
  66. 1/9 t5326: demonstrate bitmap corruption after permutationTaylor Blau, Jan 25, 2022
  67. Ævar Arnfjörð BjarmasonJan 26, 2022
  68. Taylor BlauJan 26, 2022
  69. 2/9 midx.c: make changing the preferred pack safeTaylor Blau, Jan 25, 2022
  70. 3/9 pack-revindex.c: instrument loading on-disk reverse indexTaylor Blau, Jan 25, 2022
  71. Ævar Arnfjörð BjarmasonJan 26, 2022
  72. 4/9 t5326: drop unnecessary setupTaylor Blau, Jan 25, 2022
  73. 5/9 t5326: extract `test_rev_exists`Taylor Blau, Jan 25, 2022
  74. Ævar Arnfjörð BjarmasonJan 26, 2022
  75. Taylor BlauJan 26, 2022
  76. 6/9 t5326: move tests to t/lib-bitmap.shTaylor Blau, Jan 25, 2022
  77. 7/9 t/lib-bitmap.sh: parameterize tests over reverse index sourceTaylor Blau, Jan 25, 2022
  78. 8/9 midx: read `RIDX` chunk when presentTaylor Blau, Jan 25, 2022
  79. Ævar Arnfjörð BjarmasonJan 26, 2022
  80. Taylor BlauJan 26, 2022
  81. 9/9 pack-bitmap.c: gracefully fallback after opening pack/MIDXTaylor Blau, Jan 25, 2022
  82. Ævar Arnfjörð BjarmasonJan 26, 2022
  83. Ævar Arnfjörð BjarmasonJan 26, 2022
  84. Taylor BlauJan 26, 2022
  85. Jonathan TanJan 27, 2022
  86. Taylor BlauFeb 24, 2022
  87. Derrick StoleeJan 27, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.