#include "cache.h"
#include "sigchain.h"
-static struct lock_file *lock_file_list;
+/*
+ * File write-locks as used by Git.
+ *
+ * For an overview of how to use the lockfile API, please see
+ *
+ * Documentation/technical/api-lockfile.txt
+ *
+ * This module keeps track of all locked files in lock_file_list for
+ * use at cleanup. This list and the lock_file objects that comprise
+ * it must be kept in self-consistent states at all time, because the
+ * program can be interrupted any time by a signal, in which case the
+ * signal handler will walk through the list attempting to clean up
+ * any open lock files.
+ *
+ * A lockfile is owned by the process that created it. The lock_file
+ * object has an "owner" field that records its owner. This field is
+ * used to prevent a forked process from closing a lockfile created by
+ * its parent.
+ *
+ * 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
+ * soon as the object is used in any way, it is irrevocably
+ * registered in the lock_file_list, and on_list is set.
+ *
+ * - Locked, lockfile open (after hold_lock_file_for_update(),
+ * hold_lock_file_for_append(), or reopen_lock_file()). In this
+ * 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:
+ * - 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 *volatile lock_file_list;
static void remove_lock_file(void)
{
pid_t me = getpid();
while (lock_file_list) {
- if (lock_file_list->owner == me &&
- lock_file_list->filename[0]) {
- if (lock_file_list->fd >= 0)
- close(lock_file_list->fd);
- unlink_or_warn(lock_file_list->filename);
- }
+ if (lock_file_list->owner == me)
+ rollback_lock_file(lock_file_list);
lock_file_list = lock_file_list->next;
}
}
static int lock_file(struct lock_file *lk, const char *path, int flags)
{
/*
- * subtract 5 from size to make sure there's room for adding
- * ".lock" for the lock file name
+ * subtract LOCK_SUFFIX_LEN from size to make sure there's
+ * room for adding ".lock" for the lock file name:
*/
- static const size_t max_path_len = sizeof(lk->filename) - 5;
+ static const size_t max_path_len = sizeof(lk->filename) -
+ LOCK_SUFFIX_LEN;
+
+ if (!lock_file_list) {
+ /* One-time initialization */
+ sigchain_push_common(remove_lock_file_on_signal);
+ 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;
+ lock_file_list = lk;
+ lk->on_list = 1;
+ }
if (strlen(path) >= max_path_len) {
errno = ENAMETOOLONG;
strcpy(lk->filename, path);
if (!(flags & LOCK_NODEREF))
resolve_symlink(lk->filename, max_path_len);
- strcat(lk->filename, ".lock");
+ strcat(lk->filename, LOCK_SUFFIX);
lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
- if (0 <= lk->fd) {
- if (!lock_file_list) {
- sigchain_push_common(remove_lock_file_on_signal);
- atexit(remove_lock_file);
- }
- lk->owner = getpid();
- if (!lk->on_list) {
- lk->next = lock_file_list;
- lock_file_list = lk;
- lk->on_list = 1;
- }
- if (adjust_shared_perm(lk->filename)) {
- int save_errno = errno;
- error("cannot fix permission bits on %s",
- lk->filename);
- errno = save_errno;
- return -1;
- }
- }
- else
+ if (lk->fd < 0) {
lk->filename[0] = 0;
+ 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);
+ rollback_lock_file(lk);
+ errno = save_errno;
+ return -1;
+ }
return lk->fd;
}
return -1;
}
-NORETURN void unable_to_lock_index_die(const char *path, int err)
+NORETURN void unable_to_lock_die(const char *path, int err)
{
struct strbuf buf = STRBUF_INIT;
{
int fd = lock_file(lk, path, flags);
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
- unable_to_lock_index_die(path, errno);
+ unable_to_lock_die(path, errno);
return fd;
}
fd = lock_file(lk, path, flags);
if (fd < 0) {
if (flags & LOCK_DIE_ON_ERROR)
- unable_to_lock_index_die(path, errno);
+ unable_to_lock_die(path, errno);
return fd;
}
if (errno != ENOENT) {
if (flags & LOCK_DIE_ON_ERROR)
die("cannot open '%s' for copying", path);
- close(fd);
+ rollback_lock_file(lk);
return error("cannot open '%s' for copying", path);
}
} else if (copy_fd(orig_fd, fd)) {
if (flags & LOCK_DIE_ON_ERROR)
exit(128);
- close(fd);
+ rollback_lock_file(lk);
return -1;
}
return fd;
int close_lock_file(struct lock_file *lk)
{
int fd = lk->fd;
+
+ if (fd < 0)
+ return 0;
+
lk->fd = -1;
- return close(fd);
+ if (close(fd)) {
+ int save_errno = errno;
+ rollback_lock_file(lk);
+ errno = save_errno;
+ return -1;
+ }
+ return 0;
+}
+
+int reopen_lock_file(struct lock_file *lk)
+{
+ if (0 <= lk->fd)
+ die(_("BUG: reopen a lockfile that is still open"));
+ if (!lk->active)
+ die(_("BUG: reopen a lockfile that has been committed"));
+ lk->fd = open(lk->filename, O_WRONLY);
+ return lk->fd;
}
int commit_lock_file(struct lock_file *lk)
{
char result_file[PATH_MAX];
- size_t i;
- if (lk->fd >= 0 && close_lock_file(lk))
+
+ if (!lk->active)
+ die("BUG: attempt to commit unlocked object");
+
+ if (close_lock_file(lk))
return -1;
+
strcpy(result_file, lk->filename);
- i = strlen(result_file) - 5; /* .lock */
- result_file[i] = 0;
- if (rename(lk->filename, result_file))
+ /* remove ".lock": */
+ result_file[strlen(result_file) - LOCK_SUFFIX_LEN] = 0;
+
+ if (rename(lk->filename, result_file)) {
+ int save_errno = errno;
+ rollback_lock_file(lk);
+ errno = save_errno;
return -1;
+ }
+
+ lk->active = 0;
lk->filename[0] = 0;
return 0;
}
void rollback_lock_file(struct lock_file *lk)
{
- if (lk->filename[0]) {
- if (lk->fd >= 0)
- close(lk->fd);
+ if (!lk->active)
+ return;
+
+ if (!close_lock_file(lk)) {
unlink_or_warn(lk->filename);
+ lk->active = 0;
+ lk->filename[0] = 0;
}
- lk->filename[0] = 0;
}