rename_tmp_log(): handle a possible mkdir/rmdir race
authorMichael Haggerty <mhagger@alum.mit.edu>
Sat, 18 Jan 2014 22:48:59 +0000 (23:48 +0100)
committerJunio C Hamano <gitster@pobox.com>
Tue, 21 Jan 2014 21:47:13 +0000 (13:47 -0800)
If a directory vanishes while renaming the temporary reflog file,
retry (up to 3 times). This could happen if another process deletes
the directory created by safe_create_leading_directories() just before
we rename the file into the directory.

As far as I can tell, this race could not occur internal to git. The
only time that a directory under $GIT_DIR/logs is deleted is if room
has to be made for a log file for a reference with the same name;
for example, in the following sequence:

git branch foo/bar # Creates file .git/logs/refs/heads/foo/bar
git branch -d foo/bar # Deletes file but leaves .git/logs/refs/heads/foo/
git branch foo # Deletes .git/logs/refs/heads/foo/

But the only reason the last command deletes the directory is because
it wants to create a file with the same name. So if another process
(e.g.,

git branch foo/baz

) wants to create that directory, one of the two is doomed to failure
anyway because of a D/F conflict.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
refs.c
diff --git a/refs.c b/refs.c
index 827e543b0fb0476466e7ea2b448fd6e8c957945a..bbcdc88e418efd8b681fd59d0fe0ad9fefd8a34e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2530,12 +2530,14 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
 
 static int rename_tmp_log(const char *newrefname)
 {
+       int attempts_remaining = 3;
+
+ retry:
        if (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
                error("unable to create directory for %s", newrefname);
                return -1;
        }
 
- retry:
        if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
                if (errno==EISDIR || errno==ENOTDIR) {
                        /*
@@ -2548,6 +2550,13 @@ static int rename_tmp_log(const char *newrefname)
                                return -1;
                        }
                        goto retry;
+               } else if (errno == ENOENT && --attempts_remaining > 0) {
+                       /*
+                        * Maybe another process just deleted one of
+                        * the directories in the path to newrefname.
+                        * Try again from the beginning.
+                        */
+                       goto retry;
                } else {
                        error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
                                newrefname, strerror(errno));