Skip to content

Commit 6c003d6

Browse files
peffgitster
authored andcommitted
reopen_tempfile(): truncate opened file
We provide a reopen_tempfile() function, which is in turn used by reopen_lockfile(). The idea is that a caller may want to rewrite the tempfile without letting go of the lock. And that's what our one caller does: after running add--interactive, "commit -p" will update the cache-tree extension of the index and write out the result, all while holding the lock. However, because we open the file with only the O_WRONLY flag, the existing index content is left in place, and we overwrite it starting at position 0. If the new index after updating the cache-tree is smaller than the original, those final bytes are not overwritten and remain in the file. This results in a corrupt index, since those cruft bytes are interpreted as part of the trailing hash (or even as an extension, if there are enough bytes). This bug actually pre-dates reopen_tempfile(); the original code from 9c4d6c0 (cache-tree: Write updated cache-tree after commit, 2014-07-13) has the same bug, and those lines were eventually refactored into the tempfile module. Nobody noticed until now for two reasons: - the bug can only be triggered in interactive mode ("commit -p" or "commit -i") - the size of the index must shrink after updating the cache-tree, which implies a non-trivial deletion. Notice that the included test actually has to create a 2-deep hierarchy. A single level is not enough to actually cause shrinkage. The fix is to truncate the file before writing out the second index. We can do that at the caller by using ftruncate(). But we shouldn't have to do that. There is no other place in Git where we want to open a file and overwrite bytes, making reopen_tempfile() a confusing and error-prone interface. Let's pass O_TRUNC there, which gives callers the same state they had after initially opening the file or lock. It's possible that we could later add a caller that wants something else (e.g., to open with O_APPEND). But this is the only caller we've had in the history of the codebase. Let's punt on doing anything more clever until another one comes along. Reported-by: Luc Van Oostenryck <[email protected]> Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 53f9a3e commit 6c003d6

File tree

4 files changed

+23
-5
lines changed

4 files changed

+23
-5
lines changed

lockfile.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ static inline int close_lock_file_gently(struct lock_file *lk)
263263
* nobody else) to inspect the contents you wrote, while still
264264
* holding the lock yourself.
265265
*
266-
* * `reopen_lock_file()` to reopen the lockfile. Make further updates
267-
* to the contents.
266+
* * `reopen_lock_file()` to reopen the lockfile, truncating the existing
267+
* contents. Write out the new contents.
268268
*
269269
* * `commit_lock_file()` to make the final version permanent.
270270
*/

t/t0090-cache-tree.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ test_expect_success PERL 'commit --interactive gives cache-tree on partial commi
161161
test_cache_tree
162162
'
163163

164+
test_expect_success PERL 'commit -p with shrinking cache-tree' '
165+
mkdir -p deep/subdir &&
166+
echo content >deep/subdir/file &&
167+
git add deep &&
168+
git commit -m add &&
169+
git rm -r deep &&
170+
171+
before=$(wc -c <.git/index) &&
172+
git commit -m delete -p &&
173+
after=$(wc -c <.git/index) &&
174+
175+
# double check that the index shrank
176+
test $before -gt $after &&
177+
178+
# and that our index was not corrupted
179+
git fsck
180+
'
181+
164182
test_expect_success 'commit in child dir has cache-tree' '
165183
mkdir dir &&
166184
>dir/child.t &&

tempfile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ int reopen_tempfile(struct tempfile *tempfile)
279279
BUG("reopen_tempfile called for an inactive object");
280280
if (0 <= tempfile->fd)
281281
BUG("reopen_tempfile called for an open object");
282-
tempfile->fd = open(tempfile->filename.buf, O_WRONLY);
282+
tempfile->fd = open(tempfile->filename.buf, O_WRONLY|O_TRUNC);
283283
return tempfile->fd;
284284
}
285285

tempfile.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ extern int close_tempfile_gently(struct tempfile *tempfile);
235235
* it (and nobody else) to inspect or even modify the file's
236236
* contents.
237237
*
238-
* * `reopen_tempfile()` to reopen the temporary file. Make further
239-
* updates to the contents.
238+
* * `reopen_tempfile()` to reopen the temporary file, truncating the existing
239+
* contents. Write out the new contents.
240240
*
241241
* * `rename_tempfile()` to move the file to its permanent location.
242242
*/

0 commit comments

Comments
 (0)