]> granicus.if.org Git - git/commitdiff
lockfile: avoid transitory invalid states
authorMichael Haggerty <mhagger@alum.mit.edu>
Wed, 1 Oct 2014 10:28:27 +0000 (12:28 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 1 Oct 2014 20:48:59 +0000 (13:48 -0700)
Because remove_lock_file() can be called any time by the signal
handler, it is important that any lock_file objects that are in the
lock_file_list are always in a valid state.  And since lock_file
objects are often reused (but are never removed from lock_file_list),
that means we have to be careful whenever mutating a lock_file object
to always keep it in a well-defined state.

This was formerly not the case, because part of the state was encoded
by setting lk->filename to the empty string vs. a valid filename.  It
is wrong to assume that this string can be updated atomically; for
example, even

    strcpy(lk->filename, value)

is unsafe.  But the old code was even more reckless; for example,

    strcpy(lk->filename, path);
    if (!(flags & LOCK_NODEREF))
            resolve_symlink(lk->filename, max_path_len);
    strcat(lk->filename, ".lock");

During the call to resolve_symlink(), lk->filename contained the name
of the file that was being locked, not the name of the lockfile.  If a
signal were raised during that interval, then the signal handler would
have deleted the valuable file!

We could probably continue to use the filename field to encode the
state by being careful to write characters 1..N-1 of the filename
first, and then overwrite the NUL at filename[0] with the first
character of the filename, but that would be awkward and error-prone.

So, instead of using the filename field to determine whether the
lock_file object is active, add a new field "lock_file::active" for
this purpose.  Be careful to set this field only when filename really
contains the name of a file that should be deleted on cleanup.

Helped-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
cache.h
lockfile.c
read-cache.c

diff --git a/cache.h b/cache.h
index 24891a81d6be0b41dd0a9526fe786d2b2a80c7d1..8e25fce3d8286f2a8452e368fb8aaff463ae5dc7 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -576,6 +576,7 @@ extern int refresh_index(struct index_state *, unsigned int flags, const struct
 
 struct lock_file {
        struct lock_file *next;
+       volatile sig_atomic_t active;
        int fd;
        pid_t owner;
        char on_list;
index 728ce497654127ce7245f34c894b3ef8db92fa74..d35ac448f0e6259492e4d5c49554e161016bdfee 100644 (file)
@@ -23,7 +23,7 @@
  * used to prevent a forked process from closing a lockfile created by
  * its parent.
  *
- * A lock_file object can be in several states:
+ * The possible states of a lock_file object are as follows:
  *
  * - Uninitialized.  In this state the object's on_list field must be
  *   zero but the rest of its contents need not be initialized.  As
  *
  * - Locked, lockfile open (after hold_lock_file_for_update(),
  *   hold_lock_file_for_append(), or reopen_lock_file()). In this
- *   state, the lockfile exists, filename holds the filename of the
- *   lockfile, fd holds a file descriptor open for writing to the
- *   lockfile, and owner holds the PID of the process that locked the
- *   file.
+ *   state:
+ *   - the lockfile exists
+ *   - active is set
+ *   - filename holds the filename of the lockfile
+ *   - fd holds a file descriptor open for writing to the lockfile
+ *   - owner holds the PID of the process that locked the file
  *
  * - Locked, lockfile closed (after successful close_lock_file()).
  *   Same as the previous state, except that the lockfile is closed
  *   and fd is -1.
  *
  * - Unlocked (after commit_lock_file(), rollback_lock_file(), a
- *   failed attempt to lock, or a failed close_lock_file()). In this
- *   state, filename[0] == '\0' and fd is -1. The object is left
- *   registered in the lock_file_list, and on_list is set.
+ *   failed attempt to lock, or a failed close_lock_file()).  In this
+ *   state:
+ *   - active is unset
+ *   - filename[0] == '\0' (usually, though there are transitory states
+ *     in which this condition doesn't hold). Client code should *not*
+ *     rely on this fact!
+ *   - fd is -1
+ *   - the object is left registered in the lock_file_list, and
+ *     on_list is set.
  */
 
 static struct lock_file *lock_file_list;
@@ -175,9 +183,13 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
                atexit(remove_lock_file);
        }
 
+       if (lk->active)
+               die("BUG: cannot lock_file(\"%s\") using active struct lock_file",
+                   path);
        if (!lk->on_list) {
                /* Initialize *lk and add it to lock_file_list: */
                lk->fd = -1;
+               lk->active = 0;
                lk->owner = 0;
                lk->filename[0] = 0;
                lk->next = lock_file_list;
@@ -199,6 +211,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
                return -1;
        }
        lk->owner = getpid();
+       lk->active = 1;
        if (adjust_shared_perm(lk->filename)) {
                int save_errno = errno;
                error("cannot fix permission bits on %s", lk->filename);
@@ -298,7 +311,7 @@ int reopen_lock_file(struct lock_file *lk)
 {
        if (0 <= lk->fd)
                die(_("BUG: reopen a lockfile that is still open"));
-       if (!lk->filename[0])
+       if (!lk->active)
                die(_("BUG: reopen a lockfile that has been committed"));
        lk->fd = open(lk->filename, O_WRONLY);
        return lk->fd;
@@ -308,7 +321,7 @@ int commit_lock_file(struct lock_file *lk)
 {
        char result_file[PATH_MAX];
 
-       if (!lk->filename[0])
+       if (!lk->active)
                die("BUG: attempt to commit unlocked object");
 
        if (close_lock_file(lk))
@@ -325,6 +338,7 @@ int commit_lock_file(struct lock_file *lk)
                return -1;
        }
 
+       lk->active = 0;
        lk->filename[0] = 0;
        return 0;
 }
@@ -339,11 +353,12 @@ int hold_locked_index(struct lock_file *lk, int die_on_error)
 
 void rollback_lock_file(struct lock_file *lk)
 {
-       if (!lk->filename[0])
+       if (!lk->active)
                return;
 
        if (!close_lock_file(lk)) {
                unlink_or_warn(lk->filename);
+               lk->active = 0;
                lk->filename[0] = 0;
        }
 }
index 5ffb1d7bb6d1bbacf0ea54678eb047c578f21206..af69f344a232ea8b1930d2ab273ff1782232e7bc 100644 (file)
@@ -2046,6 +2046,7 @@ static int commit_locked_index(struct lock_file *lk)
                        return -1;
                if (rename(lk->filename, alternate_index_output))
                        return -1;
+               lk->active = 0;
                lk->filename[0] = 0;
                return 0;
        } else {