receive-pack: fix stale packfile locks when dying

When accepting a packfile in git-receive-pack(1), we feed that packfile into git-index-pack(1) to generate the packfile index. As the packfile would often only contain unreachable objects until the references have been updated, concurrently running garbage collection might be tempted to delete the packfile right away and thus cause corruption. To fix this, we ask git-index-pack(1) to create a `.keep` file before moving the packfile into place, which is getting deleted again once all of the reference updates have been processed. Now in production systems we have observed that those `.keep` files are sometimes not getting deleted as expected, where the result is that repositories tend to grow packfiles that are never deleted over time. This seems to be caused by a race when git-receive-pack(1) is killed after we have migrated the kept packfile from the quarantine directory into the main object database. While this race window is typically small it can be extended for example by installing a `proc-receive` hook. Fix this race by registering the lockfile as a tempfile so that it will automatically be removed at exit or when receiving a signal. Helped-by: Jeff King <peff@peff.net> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 10, 2023 at 07:52 UTC c55c30669ced6e08b41b3c921f0da200247c9811
1 file changed +10 -4
builtin/receive-pack.c
+10 -4
@@ -2171,7 +2171,7 @@ static const char *parse_pack_header(struct pack_header *hdr)
2171 }
2172 }
2173
2174 -static const char *pack_lockfile;
2174 +static struct tempfile *pack_lockfile;
2175
2176 static void push_header_arg(struct strvec *args, struct pack_header *hdr)
2177 {
@@ -2238,6 +2238,7 @@ static const char *unpack(int err_fd, struct shallow_info *si)
2238 return "unpack-objects abnormal exit";
2239 } else {
2240 char hostname[HOST_NAME_MAX + 1];
2241 + char *lockfile;
2242
2243 strvec_pushl(&child.args, "index-pack", "--stdin", NULL);
2244 push_header_arg(&child.args, &hdr);
@@ -2267,8 +2268,14 @@ static const char *unpack(int err_fd, struct shallow_info *si)
2268 status = start_command(&child);
2269 if (status)
2270 return "index-pack fork failed";
2270 - pack_lockfile = index_pack_lockfile(child.out, NULL);
2271 +
2272 + lockfile = index_pack_lockfile(child.out, NULL);
2273 + if (lockfile) {
2274 + pack_lockfile = register_tempfile(lockfile);
2275 + free(lockfile);
2276 + }
2277 close(child.out);
2278 +
2279 status = finish_command(&child);
2280 if (status)
2281 return "index-pack abnormal exit";
@@ -2555,8 +2562,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
2562 use_keepalive = KEEPALIVE_ALWAYS;
2563 execute_commands(commands, unpack_status, &si,
2564 &push_options);
2558 - if (pack_lockfile)
2559 - unlink_or_warn(pack_lockfile);
2565 + delete_tempfile(&pack_lockfile);
2566 sigchain_push(SIGPIPE, SIG_IGN);
2567 if (report_status_v2)
2568 report_v2(commands, unpack_status);