read-cache: leave lock in right state in write_locked_index()
If the original version of `write_locked_index()` returned with an error, it didn't roll back the lockfile unless the error occured at the very end, during closing/committing. See commit03b866477
(read-cache: new API write_locked_index instead of write_index/write_cache, 2014-06-13). In commit9f41c7a6b
(read-cache: close index.lock in do_write_index, 2017-04-26), we learned to close the lock slightly earlier in the callstack. That was mostly a side-effect of lockfiles being implemented using temporary files, but didn't cause any real harm. Recently, commit076aa2cbd
(tempfile: auto-allocate tempfiles on heap, 2017-09-05) introduced a subtle bug. If the temporary file is deleted (i.e., the lockfile is rolled back), the tempfile-pointer in the `struct lock_file` will be left dangling. Thus, an attempt to reuse the lockfile, or even just to roll it back, will induce undefined behavior -- most likely a crash. Besides not crashing, we clearly want to make things consistent. The guarantees which the lockfile-machinery itself provides is A) if we ask to commit and it fails, roll back, and B) if we ask to close and it fails, do _not_ roll back. Let's do the same for consistency. Do not delete the temporary file in `do_write_index()`. One of its callers, `write_locked_index()` will thereby avoid rolling back the lock. The other caller, `write_shared_index()`, will delete its temporary file anyway. Both of these callers will avoid undefined behavior (crashing). Teach `write_locked_index(..., COMMIT_LOCK)` to roll back the lock before returning. If we have already succeeded and committed, it will be a noop. Simplify the existing callers where we now have a superfluous call to `rollback_lockfile()`. That should keep future readers from wondering why the callers are inconsistent. Signed-off-by: Martin Ågren <martin.agren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
812d6b0075
commit
df60cf5789
@ -616,7 +616,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
|
|||||||
if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
|
if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
|
||||||
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
|
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
|
||||||
ret = error("could not write %s", buf.buf);
|
ret = error("could not write %s", buf.buf);
|
||||||
rollback_lock_file(&lock);
|
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
changed_files(&wt_modified, buf.buf, workdir);
|
changed_files(&wt_modified, buf.buf, workdir);
|
||||||
|
4
cache.h
4
cache.h
@ -616,6 +616,10 @@ extern int read_index_unmerged(struct index_state *);
|
|||||||
* split index to the lockfile. If the temporary file for the shared
|
* split index to the lockfile. If the temporary file for the shared
|
||||||
* index cannot be created, fall back to the behavior described in
|
* index cannot be created, fall back to the behavior described in
|
||||||
* the previous paragraph.
|
* the previous paragraph.
|
||||||
|
*
|
||||||
|
* With `COMMIT_LOCK`, the lock is always committed or rolled back.
|
||||||
|
* Without it, the lock is closed, but neither committed nor rolled
|
||||||
|
* back.
|
||||||
*/
|
*/
|
||||||
extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
|
extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
|
||||||
|
|
||||||
|
4
merge.c
4
merge.c
@ -91,9 +91,7 @@ int checkout_fast_forward(const struct object_id *head,
|
|||||||
}
|
}
|
||||||
if (unpack_trees(nr_trees, t, &opts))
|
if (unpack_trees(nr_trees, t, &opts))
|
||||||
return -1;
|
return -1;
|
||||||
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) {
|
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
|
||||||
rollback_lock_file(&lock_file);
|
|
||||||
return error(_("unable to write new index file"));
|
return error(_("unable to write new index file"));
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
14
read-cache.c
14
read-cache.c
@ -2182,9 +2182,8 @@ static int has_racy_timestamp(struct index_state *istate)
|
|||||||
void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
|
void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
|
||||||
{
|
{
|
||||||
if ((istate->cache_changed || has_racy_timestamp(istate)) &&
|
if ((istate->cache_changed || has_racy_timestamp(istate)) &&
|
||||||
verify_index(istate) &&
|
verify_index(istate))
|
||||||
write_locked_index(istate, lockfile, COMMIT_LOCK))
|
write_locked_index(istate, lockfile, COMMIT_LOCK);
|
||||||
rollback_lock_file(lockfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2321,7 +2320,6 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
|
|||||||
return -1;
|
return -1;
|
||||||
if (close_tempfile_gently(tempfile)) {
|
if (close_tempfile_gently(tempfile)) {
|
||||||
error(_("could not close '%s'"), tempfile->filename.buf);
|
error(_("could not close '%s'"), tempfile->filename.buf);
|
||||||
delete_tempfile(&tempfile);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (stat(tempfile->filename.buf, &st))
|
if (stat(tempfile->filename.buf, &st))
|
||||||
@ -2501,7 +2499,8 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
|
|||||||
(istate->cache_changed & ~EXTMASK)) {
|
(istate->cache_changed & ~EXTMASK)) {
|
||||||
if (si)
|
if (si)
|
||||||
hashclr(si->base_sha1);
|
hashclr(si->base_sha1);
|
||||||
return do_write_locked_index(istate, lock, flags);
|
ret = do_write_locked_index(istate, lock, flags);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getenv("GIT_TEST_SPLIT_INDEX")) {
|
if (getenv("GIT_TEST_SPLIT_INDEX")) {
|
||||||
@ -2517,7 +2516,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
|
|||||||
if (new_shared_index) {
|
if (new_shared_index) {
|
||||||
ret = write_shared_index(istate, lock, flags);
|
ret = write_shared_index(istate, lock, flags);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = write_split_index(istate, lock, flags);
|
ret = write_split_index(istate, lock, flags);
|
||||||
@ -2526,6 +2525,9 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
|
|||||||
if (!ret && !new_shared_index)
|
if (!ret && !new_shared_index)
|
||||||
freshen_shared_index(sha1_to_hex(si->base_sha1), 1);
|
freshen_shared_index(sha1_to_hex(si->base_sha1), 1);
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (flags & COMMIT_LOCK)
|
||||||
|
rollback_lock_file(lock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1183,7 +1183,6 @@ static int read_and_refresh_cache(struct replay_opts *opts)
|
|||||||
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
|
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
|
||||||
if (the_index.cache_changed && index_fd >= 0) {
|
if (the_index.cache_changed && index_fd >= 0) {
|
||||||
if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
|
if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
|
||||||
rollback_lock_file(&index_lock);
|
|
||||||
return error(_("git %s: failed to refresh the index"),
|
return error(_("git %s: failed to refresh the index"),
|
||||||
_(action_name(opts)));
|
_(action_name(opts)));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user