Skip to content

Commit b78a556

Browse files
ttaylorrgitster
authored andcommitted
commit-graph.c: gracefully handle file descriptor exhaustion
When writing a layered commit-graph, the commit-graph machinery uses 'commit_graph_filenames_after' and 'commit_graph_hash_after' to keep track of the layers in the chain that we are in the process of writing. When the number of commit-graph layers shrinks, we initialize all entries in the aforementioned arrays, because we know the structure of the new commit-graph chain immediately (since there are no new layers, there are no unknown hash values). But when the number of commit-graph layers grows (i.e., that 'num_commit_graphs_after > num_commit_graphs_before'), then we leave some entries in the filenames and hashes arrays as uninitialized, because we will fill them in later as those values become available. For instance, we rely on 'write_commit_graph_file's to store the filename and hash of the last layer in the new chain, which is the one that it is responsible for writing. But, it's possible that 'write_commit_graph_file' may fail, e.g., from file descriptor exhaustion. In this case it is possible that 'git_mkstemp_mode' will fail, and that function will return early *before* setting the values for the last commit-graph layer's filename and hash. This causes a number of upleasant side-effects. For instance, trying to 'free()' each entry in 'ctx->commit_graph_filenames_after' (and similarly for the hashes array) causes us to 'free()' uninitialized memory, since the area is allocated with 'malloc()' and is therefore subject to contain garbage (which is left alone when 'write_commit_graph_file' returns early). This can manifest in other issues, like a general protection fault, and/or leaving a stray 'commit-graph-chain.lock' around after the process dies. (The reasoning for this is still a mystery to me, since we'd otherwise usually expect the kernel to run tempfile.c's 'atexit()' handlers in the case of a normal death...) To resolve this, initialize the memory with 'CALLOC_ARRAY' so that uninitialized entries are filled with zeros, and can thus be 'free()'d as a noop instead of causing a fault. Helped-by: Jeff King <[email protected]> Helped-by: Derrick Stolee <[email protected]> Signed-off-by: Taylor Blau <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b30fdb4 commit b78a556

File tree

2 files changed

+15
-2
lines changed

2 files changed

+15
-2
lines changed

commit-graph.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,8 +1602,8 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
16021602
free(old_graph_name);
16031603
}
16041604

1605-
ALLOC_ARRAY(ctx->commit_graph_filenames_after, ctx->num_commit_graphs_after);
1606-
ALLOC_ARRAY(ctx->commit_graph_hash_after, ctx->num_commit_graphs_after);
1605+
CALLOC_ARRAY(ctx->commit_graph_filenames_after, ctx->num_commit_graphs_after);
1606+
CALLOC_ARRAY(ctx->commit_graph_hash_after, ctx->num_commit_graphs_after);
16071607

16081608
for (i = 0; i < ctx->num_commit_graphs_after &&
16091609
i < ctx->num_commit_graphs_before; i++)

t/t5324-split-commit-graph.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,17 @@ test_expect_success '--split=replace replaces the chain' '
374374
graph_read_expect 2
375375
'
376376

377+
test_expect_success ULIMIT_FILE_DESCRIPTORS 'handles file descriptor exhaustion' '
378+
git init ulimit &&
379+
(
380+
cd ulimit &&
381+
for i in $(test_seq 64)
382+
do
383+
test_commit $i &&
384+
test_might_fail run_with_limited_open_files git commit-graph write \
385+
--split=no-merge --reachable || return 1
386+
done
387+
)
388+
'
389+
377390
test_done

0 commit comments

Comments
 (0)