From e197c21807dacadc8305250baa0b9228819189d4 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:05 +0200 Subject: unable_to_lock_die(): rename function from unable_to_lock_index_die() This function is used for other things besides the index, so rename it accordingly. Suggested-by: Jeff King Signed-off-by: Michael Haggerty Reviewed-by: Ronnie Sahlberg Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 2a800cef33..f1ce1544d2 100644 --- a/lockfile.c +++ b/lockfile.c @@ -185,7 +185,7 @@ int unable_to_lock_error(const char *path, int err) 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; @@ -198,7 +198,7 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags) { 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; } @@ -209,7 +209,7 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) 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; } -- cgit v1.2.3 From 419f0c0f681b76d720699977abe03d29e22db554 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:07 +0200 Subject: close_lock_file(): exit (successfully) if file is already closed Suggested-by: Jonathan Nieder Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index f1ce1544d2..d02c3bf901 100644 --- a/lockfile.c +++ b/lockfile.c @@ -233,6 +233,10 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) int close_lock_file(struct lock_file *lk) { int fd = lk->fd; + + if (fd < 0) + return 0; + lk->fd = -1; return close(fd); } @@ -251,7 +255,7 @@ 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 (close_lock_file(lk)) return -1; strcpy(result_file, lk->filename); i = strlen(result_file) - 5; /* .lock */ -- cgit v1.2.3 From 5527d5349b42aeb2ea36edfd2d55016f22fefc08 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:08 +0200 Subject: rollback_lock_file(): do not clear filename redundantly It is only necessary to clear the lock_file's filename field if it was not already clear. Signed-off-by: Michael Haggerty Reviewed-by: Ronnie Sahlberg Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index d02c3bf901..5330d6ae52 100644 --- a/lockfile.c +++ b/lockfile.c @@ -280,6 +280,6 @@ void rollback_lock_file(struct lock_file *lk) if (lk->fd >= 0) close(lk->fd); unlink_or_warn(lk->filename); + lk->filename[0] = 0; } - lk->filename[0] = 0; } -- cgit v1.2.3 From 9085f8e279146a31ea8bbd102b6c97f5cb22dcdc Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:09 +0200 Subject: rollback_lock_file(): exit early if lock is not active Eliminate a layer of nesting. Signed-off-by: Michael Haggerty Reviewed-by: Ronnie Sahlberg Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 5330d6ae52..e55149a73a 100644 --- a/lockfile.c +++ b/lockfile.c @@ -276,10 +276,11 @@ 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->fd >= 0) - close(lk->fd); - unlink_or_warn(lk->filename); - lk->filename[0] = 0; - } + if (!lk->filename[0]) + return; + + if (lk->fd >= 0) + close(lk->fd); + unlink_or_warn(lk->filename); + lk->filename[0] = 0; } -- cgit v1.2.3 From 26f5d3b65fa1b40be570a67f1aaca0e2f085d568 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:10 +0200 Subject: rollback_lock_file(): set fd to -1 When rolling back the lockfile, call close_lock_file() so that the lock_file's fd field gets set back to -1. This keeps the lock_file object in a valid state, which is important because these objects are allowed to be reused. It also makes it unnecessary to check whether the file has already been closed, because close_lock_file() takes care of that. Signed-off-by: Michael Haggerty Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index e55149a73a..3df1e8306a 100644 --- a/lockfile.c +++ b/lockfile.c @@ -279,8 +279,7 @@ void rollback_lock_file(struct lock_file *lk) if (!lk->filename[0]) return; - if (lk->fd >= 0) - close(lk->fd); + close_lock_file(lk); unlink_or_warn(lk->filename); lk->filename[0] = 0; } -- cgit v1.2.3 From 41dd4ffaf99532d8344c90a5b1a060ac1f73b232 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:11 +0200 Subject: lockfile: unlock file if lockfile permissions cannot be adjusted If the call to adjust_shared_perm() fails, lock_file returns -1, which to the caller looks like any other failure to lock the file. So in this case, roll back the lockfile before returning so that the lock file is deleted immediately and the lockfile object is left in a predictable state (namely, unlocked). Previously, the lockfile was retained until process cleanup in this situation. Signed-off-by: Michael Haggerty Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 1 + 1 file changed, 1 insertion(+) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 3df1e8306a..d74de8d329 100644 --- a/lockfile.c +++ b/lockfile.c @@ -153,6 +153,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) int save_errno = errno; error("cannot fix permission bits on %s", lk->filename); + rollback_lock_file(lk); errno = save_errno; return -1; } -- cgit v1.2.3 From ebb8e380e98e83f32c1cc04200d3749ab4c0b90a Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:12 +0200 Subject: hold_lock_file_for_append(): release lock on errors If there is an error copying the old contents to the lockfile, roll back the lockfile before exiting so that the lockfile is not held until process cleanup. Signed-off-by: Michael Haggerty Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index d74de8d329..f4ce79bc27 100644 --- a/lockfile.c +++ b/lockfile.c @@ -219,13 +219,13 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) 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; -- cgit v1.2.3 From 04e57d4d32541bc5dba553a31f09aa2ee456bdad Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:13 +0200 Subject: lock_file(): always initialize and register lock_file object The purpose of this change is to make the state diagram for lock_file objects simpler and deterministic. If locking fails, lock_file() sometimes leaves the lock_file object partly initialized, but sometimes not. It sometimes registers the object in lock_file_list, but sometimes not. This makes the state diagram for lock_file objects effectively indeterministic and hard to reason about. A future patch will also change the filename field into a strbuf, which needs more involved initialization, so it will become even more important that the state of a lock_file object is well-defined after a failed attempt to lock. The ambiguity doesn't currently have any ill effects, because lock_file objects cannot be removed from the lock_file_list anyway. But to make it easier to document and reason about the code, make this behavior consistent: *always* initialize the lock_file object and *always* register it in lock_file_list the first time it is used, regardless of whether an error occurs. While we're at it, make sure that all of the lock_file fields are initialized to values appropriate for an unlocked object; the caller is only responsible for making sure that on_list is set to zero before the first time it is used. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index f4ce79bc27..81143e55ad 100644 --- a/lockfile.c +++ b/lockfile.c @@ -129,6 +129,22 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) */ static const size_t max_path_len = sizeof(lk->filename) - 5; + if (!lock_file_list) { + /* One-time initialization */ + sigchain_push_common(remove_lock_file_on_signal); + atexit(remove_lock_file); + } + + if (!lk->on_list) { + /* Initialize *lk and add it to lock_file_list: */ + lk->fd = -1; + 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; return -1; @@ -139,16 +155,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) strcat(lk->filename, ".lock"); 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", -- cgit v1.2.3 From 0a06f148373285822baf5f3e83696732a556a2d1 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:14 +0200 Subject: lockfile.c: document the various states of lock_file objects Document the valid states of lock_file objects, how they get into each state, and how the state is encoded in the object's fields. Signed-off-by: Michael Haggerty Reviewed-by: Ronnie Sahlberg Signed-off-by: Junio C Hamano --- lockfile.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 81143e55ad..2680dc949d 100644 --- a/lockfile.c +++ b/lockfile.c @@ -4,6 +4,48 @@ #include "cache.h" #include "sigchain.h" +/* + * 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. + * + * A lock_file object can be in several states: + * + * - 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, 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. + * + * - Locked, lockfile closed (after 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(), or a + * failed attempt to lock). 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. + */ + static struct lock_file *lock_file_list; static void remove_lock_file(void) -- cgit v1.2.3 From 7108ad232fc7a4c889e82b40c52125adc9796ff5 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:15 +0200 Subject: cache.h: define constants LOCK_SUFFIX and LOCK_SUFFIX_LEN There are a few places that use these values, so define constants for them. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 2680dc949d..23847fc9f2 100644 --- a/lockfile.c +++ b/lockfile.c @@ -166,10 +166,11 @@ static char *resolve_symlink(char *p, size_t s) 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 */ @@ -194,7 +195,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) 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) { lk->owner = getpid(); @@ -308,7 +309,7 @@ int commit_lock_file(struct lock_file *lk) if (close_lock_file(lk)) return -1; strcpy(result_file, lk->filename); - i = strlen(result_file) - 5; /* .lock */ + i = strlen(result_file) - LOCK_SUFFIX_LEN; /* .lock */ result_file[i] = 0; if (rename(lk->filename, result_file)) return -1; -- cgit v1.2.3 From e31e949b9f9b5e6dcff42e2242cf1a6fb3686b91 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:18 +0200 Subject: lock_file(): exit early if lockfile cannot be opened This is a bit easier to read than the old version, which nested part of the non-error code in an "if" block. Signed-off-by: Michael Haggerty Reviewed-by: Ronnie Sahlberg Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 23847fc9f2..a8f32e5495 100644 --- a/lockfile.c +++ b/lockfile.c @@ -197,19 +197,18 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) resolve_symlink(lk->filename, max_path_len); strcat(lk->filename, LOCK_SUFFIX); lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); - if (0 <= lk->fd) { - lk->owner = getpid(); - 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; - } - } - else + if (lk->fd < 0) { lk->filename[0] = 0; + return -1; + } + lk->owner = getpid(); + 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; } -- cgit v1.2.3 From a1754bcce98fa57b9374440c2717ec1159ed8ffb Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:19 +0200 Subject: remove_lock_file(): call rollback_lock_file() It does just what we need. Signed-off-by: Michael Haggerty Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- lockfile.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index a8f32e5495..f8205f6b03 100644 --- a/lockfile.c +++ b/lockfile.c @@ -53,12 +53,8 @@ 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; } } -- cgit v1.2.3 From 4f4713df94e2f3d3adbd39b8fce571e2bd69185e Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:20 +0200 Subject: commit_lock_file(): inline temporary variable Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index f8205f6b03..e148227fb1 100644 --- a/lockfile.c +++ b/lockfile.c @@ -300,12 +300,14 @@ int reopen_lock_file(struct lock_file *lk) int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; - size_t i; + if (close_lock_file(lk)) return -1; + strcpy(result_file, lk->filename); - i = strlen(result_file) - LOCK_SUFFIX_LEN; /* .lock */ - result_file[i] = 0; + /* remove ".lock": */ + result_file[strlen(result_file) - LOCK_SUFFIX_LEN] = 0; + if (rename(lk->filename, result_file)) return -1; lk->filename[0] = 0; -- cgit v1.2.3 From 8a1c7533e2ee468a505656abab364780b15004fc Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:21 +0200 Subject: commit_lock_file(): die() if called for unlocked lockfile object It was previously a bug to call commit_lock_file() with a lock_file object that was not active (an illegal access would happen within the function). It was presumably never done, but this would be an easy programming error to overlook. So before continuing, do a consistency check that the lock_file object really is locked. Helped-by: Johannes Sixt Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index e148227fb1..c897dd8a93 100644 --- a/lockfile.c +++ b/lockfile.c @@ -301,6 +301,9 @@ int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; + if (!lk->filename[0]) + die("BUG: attempt to commit unlocked object"); + if (close_lock_file(lk)) return -1; -- cgit v1.2.3 From 8e86c155d2962f5dff83c9d0d88b836bf040c1fa Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:22 +0200 Subject: close_lock_file(): if close fails, roll back If closing an open lockfile fails, then we cannot be sure of the contents of the lockfile, so there is nothing sensible to do but delete it. This change also insures that the lock_file object is left in a defined state in this error path (namely, unlocked). The only caller that is ultimately affected by this change is try_merge_strategy() -> write_locked_index(), which can call close_lock_file() via various execution paths. This caller uses a static lock_file object which previously could have been reused after a failed close_lock_file() even though it was still in locked state. This change causes the lock_file object to be unlocked on failure, thus fixing this error-handling path. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index c897dd8a93..1d18c67ea8 100644 --- a/lockfile.c +++ b/lockfile.c @@ -37,13 +37,14 @@ * lockfile, and owner holds the PID of the process that locked the * file. * - * - Locked, lockfile closed (after close_lock_file()). Same as the - * previous state, except that the lockfile is closed and fd is -1. + * - 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(), or a - * failed attempt to lock). 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. + * - 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. */ static struct lock_file *lock_file_list; @@ -284,7 +285,13 @@ int close_lock_file(struct lock_file *lk) 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) @@ -330,7 +337,8 @@ void rollback_lock_file(struct lock_file *lk) if (!lk->filename[0]) return; - close_lock_file(lk); - unlink_or_warn(lk->filename); - lk->filename[0] = 0; + if (!close_lock_file(lk)) { + unlink_or_warn(lk->filename); + lk->filename[0] = 0; + } } -- cgit v1.2.3 From 1b1648f46bcb638f558e9cbd2d0e89066c89c44e Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:23 +0200 Subject: commit_lock_file(): rollback lock file on failure to rename If rename() fails, call rollback_lock_file() to delete the lock file (in case it is still present) and reset the filename field to the empty string so that the lockfile object is left in a valid state. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 1d18c67ea8..728ce49765 100644 --- a/lockfile.c +++ b/lockfile.c @@ -318,8 +318,13 @@ int commit_lock_file(struct lock_file *lk) /* remove ".lock": */ result_file[strlen(result_file) - LOCK_SUFFIX_LEN] = 0; - if (rename(lk->filename, result_file)) + if (rename(lk->filename, result_file)) { + int save_errno = errno; + rollback_lock_file(lk); + errno = save_errno; return -1; + } + lk->filename[0] = 0; return 0; } -- cgit v1.2.3 From 707103fdfd0c03511fa547d9b80638d8160f1a88 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:27 +0200 Subject: lockfile: avoid transitory invalid states 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 Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 728ce49765..d35ac448f0 100644 --- a/lockfile.c +++ b/lockfile.c @@ -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 @@ -32,19 +32,27 @@ * * - 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; } } -- cgit v1.2.3 From 2091c5062c6fd928e1ad3e7c059243e597cb8bbf Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:28 +0200 Subject: struct lock_file: declare some fields volatile The function remove_lock_file_on_signal() is used as a signal handler. It is not realistic to make the signal handler conform strictly to the C standard, which is very restrictive about what a signal handler is allowed to do. But let's increase the likelihood that it will work: The lock_file_list global variable and several fields from struct lock_file are used by the signal handler. Declare those values "volatile" to (1) force the main process to write the values to RAM promptly, and (2) prevent updates to these fields from being reordered in a way that leaves an opportunity for a jump to the signal handler while the object is in an inconsistent state. We don't mark the filename field volatile because that would prevent the use of strcpy(), and it is anyway unlikely that a compiler re-orders a strcpy() call across other expressions. So in practice it should be possible to get away without "volatile" in the "filename" case. Suggested-by: Johannes Sixt Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index d35ac448f0..89043f5acc 100644 --- a/lockfile.c +++ b/lockfile.c @@ -55,7 +55,7 @@ * on_list is set. */ -static struct lock_file *lock_file_list; +static struct lock_file *volatile lock_file_list; static void remove_lock_file(void) { -- cgit v1.2.3 From 3e88e8fc085bbfad142d51a07ef918b9b5ca1d72 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:31 +0200 Subject: commit_lock_file(): use a strbuf to manage temporary space Avoid relying on the filename length restrictions that are currently checked by lock_file(). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 89043f5acc..1dd118f556 100644 --- a/lockfile.c +++ b/lockfile.c @@ -319,7 +319,8 @@ int reopen_lock_file(struct lock_file *lk) int commit_lock_file(struct lock_file *lk) { - char result_file[PATH_MAX]; + static struct strbuf result_file = STRBUF_INIT; + int err; if (!lk->active) die("BUG: attempt to commit unlocked object"); @@ -327,11 +328,12 @@ int commit_lock_file(struct lock_file *lk) if (close_lock_file(lk)) return -1; - strcpy(result_file, lk->filename); /* remove ".lock": */ - result_file[strlen(result_file) - LOCK_SUFFIX_LEN] = 0; - - if (rename(lk->filename, result_file)) { + strbuf_add(&result_file, lk->filename, + strlen(lk->filename) - LOCK_SUFFIX_LEN); + err = rename(lk->filename, result_file.buf); + strbuf_reset(&result_file); + if (err) { int save_errno = errno; rollback_lock_file(lk); errno = save_errno; -- cgit v1.2.3 From cf6950d3bfe1447ac04867b1f5654a2fc9c5db96 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:32 +0200 Subject: lockfile: change lock_file::filename into a strbuf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now, we still make sure to allocate at least PATH_MAX characters for the strbuf because resolve_symlink() doesn't know how to expand the space for its return value. (That will be fixed in a moment.) Another alternative would be to just use a strbuf as scratch space in lock_file() but then store a pointer to the naked string in struct lock_file. But lock_file objects are often reused. By reusing the same strbuf, we can avoid having to reallocate the string most times when a lock_file object is reused. Helped-by: Torsten Bögershausen Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 1dd118f556..85c8648c51 100644 --- a/lockfile.c +++ b/lockfile.c @@ -47,9 +47,9 @@ * 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! + * - filename is empty (usually, though there are transitory + * states in which this condition doesn't hold). Client code should + * *not* rely on the filename being empty in this state. * - fd is -1 * - the object is left registered in the lock_file_list, and * on_list is set. @@ -170,13 +170,6 @@ static char *resolve_symlink(char *p, size_t s) /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - /* - * 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) - - LOCK_SUFFIX_LEN; - if (!lock_file_list) { /* One-time initialization */ sigchain_push_common(remove_lock_file_on_signal); @@ -191,30 +184,32 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) lk->fd = -1; lk->active = 0; lk->owner = 0; - lk->filename[0] = 0; + strbuf_init(&lk->filename, PATH_MAX); lk->next = lock_file_list; lock_file_list = lk; lk->on_list = 1; + } else if (lk->filename.len) { + /* This shouldn't happen, but better safe than sorry. */ + die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object", + path); } - if (strlen(path) >= max_path_len) { - errno = ENAMETOOLONG; - return -1; + strbuf_addstr(&lk->filename, path); + if (!(flags & LOCK_NODEREF)) { + resolve_symlink(lk->filename.buf, lk->filename.alloc); + strbuf_setlen(&lk->filename, strlen(lk->filename.buf)); } - strcpy(lk->filename, path); - if (!(flags & LOCK_NODEREF)) - resolve_symlink(lk->filename, max_path_len); - strcat(lk->filename, LOCK_SUFFIX); - lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); + strbuf_addstr(&lk->filename, LOCK_SUFFIX); + lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); if (lk->fd < 0) { - lk->filename[0] = 0; + strbuf_reset(&lk->filename); return -1; } lk->owner = getpid(); lk->active = 1; - if (adjust_shared_perm(lk->filename)) { + if (adjust_shared_perm(lk->filename.buf)) { int save_errno = errno; - error("cannot fix permission bits on %s", lk->filename); + error("cannot fix permission bits on %s", lk->filename.buf); rollback_lock_file(lk); errno = save_errno; return -1; @@ -313,7 +308,7 @@ int reopen_lock_file(struct lock_file *lk) 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); + lk->fd = open(lk->filename.buf, O_WRONLY); return lk->fd; } @@ -329,9 +324,9 @@ int commit_lock_file(struct lock_file *lk) return -1; /* remove ".lock": */ - strbuf_add(&result_file, lk->filename, - strlen(lk->filename) - LOCK_SUFFIX_LEN); - err = rename(lk->filename, result_file.buf); + strbuf_add(&result_file, lk->filename.buf, + lk->filename.len - LOCK_SUFFIX_LEN); + err = rename(lk->filename.buf, result_file.buf); strbuf_reset(&result_file); if (err) { int save_errno = errno; @@ -341,7 +336,7 @@ int commit_lock_file(struct lock_file *lk) } lk->active = 0; - lk->filename[0] = 0; + strbuf_reset(&lk->filename); return 0; } @@ -359,8 +354,8 @@ void rollback_lock_file(struct lock_file *lk) return; if (!close_lock_file(lk)) { - unlink_or_warn(lk->filename); + unlink_or_warn(lk->filename.buf); lk->active = 0; - lk->filename[0] = 0; + strbuf_reset(&lk->filename); } } -- cgit v1.2.3 From 5025d8450a4c8bf9d22a202433072bee780b9b72 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:33 +0200 Subject: resolve_symlink(): use a strbuf for internal scratch space Aside from shortening and simplifying the code, this removes another place where the path name length is arbitrarily limited. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 85c8648c51..cc9b9cbaf8 100644 --- a/lockfile.c +++ b/lockfile.c @@ -126,44 +126,35 @@ static char *last_path_elm(char *p) static char *resolve_symlink(char *p, size_t s) { int depth = MAXDEPTH; + static struct strbuf link = STRBUF_INIT; while (depth--) { - char link[PATH_MAX]; - int link_len = readlink(p, link, sizeof(link)); - if (link_len < 0) { - /* not a symlink anymore */ - return p; - } - else if (link_len < sizeof(link)) - /* readlink() never null-terminates */ - link[link_len] = '\0'; - else { - warning("%s: symlink too long", p); - return p; - } + if (strbuf_readlink(&link, p, strlen(p)) < 0) + break; - if (is_absolute_path(link)) { + if (is_absolute_path(link.buf)) { /* absolute path simply replaces p */ - if (link_len < s) - strcpy(p, link); + if (link.len < s) + strcpy(p, link.buf); else { warning("%s: symlink too long", p); - return p; + break; } } else { /* - * link is a relative path, so I must replace the + * link is a relative path, so replace the * last element of p with it. */ char *r = (char *)last_path_elm(p); - if (r - p + link_len < s) - strcpy(r, link); + if (r - p + link.len < s) + strcpy(r, link.buf); else { warning("%s: symlink too long", p); - return p; + break; } } } + strbuf_reset(&link); return p; } -- cgit v1.2.3 From 6cad805332e3c3a60993f062afd8d896920689ab Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:34 +0200 Subject: resolve_symlink(): take a strbuf parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change resolve_symlink() to take a strbuf rather than a string as parameter. This simplifies the code and removes an arbitrary pathname length restriction. It also means that lock_file's filename field no longer needs to be initialized to a large size. Helped-by: Torsten Bögershausen Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 57 ++++++++++++++++++++++----------------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index cc9b9cbaf8..5f5bcfffbe 100644 --- a/lockfile.c +++ b/lockfile.c @@ -109,58 +109,47 @@ static char *last_path_elm(char *p) #define MAXDEPTH 5 /* - * p = path that may be a symlink - * s = full size of p + * path contains a path that might be a symlink. * - * If p is a symlink, attempt to overwrite p with a path to the real - * file or directory (which may or may not exist), following a chain of - * symlinks if necessary. Otherwise, leave p unmodified. + * If path is a symlink, attempt to overwrite it with a path to the + * real file or directory (which may or may not exist), following a + * chain of symlinks if necessary. Otherwise, leave path unmodified. * - * This is a best-effort routine. If an error occurs, p will either be - * left unmodified or will name a different symlink in a symlink chain - * that started with p's initial contents. - * - * Always returns p. + * This is a best-effort routine. If an error occurs, path will + * either be left unmodified or will name a different symlink in a + * symlink chain that started with the original path. */ - -static char *resolve_symlink(char *p, size_t s) +static void resolve_symlink(struct strbuf *path) { int depth = MAXDEPTH; static struct strbuf link = STRBUF_INIT; while (depth--) { - if (strbuf_readlink(&link, p, strlen(p)) < 0) + if (strbuf_readlink(&link, path->buf, path->len) < 0) break; - if (is_absolute_path(link.buf)) { + if (is_absolute_path(link.buf)) /* absolute path simply replaces p */ - if (link.len < s) - strcpy(p, link.buf); - else { - warning("%s: symlink too long", p); - break; - } - } else { + strbuf_reset(path); + else { /* * link is a relative path, so replace the * last element of p with it. */ - char *r = (char *)last_path_elm(p); - if (r - p + link.len < s) - strcpy(r, link.buf); - else { - warning("%s: symlink too long", p); - break; - } + char *r = last_path_elm(path->buf); + strbuf_setlen(path, r - path->buf); } + + strbuf_addbuf(path, &link); } strbuf_reset(&link); - return p; } /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { + size_t pathlen = strlen(path); + if (!lock_file_list) { /* One-time initialization */ sigchain_push_common(remove_lock_file_on_signal); @@ -175,7 +164,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) lk->fd = -1; lk->active = 0; lk->owner = 0; - strbuf_init(&lk->filename, PATH_MAX); + strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN); lk->next = lock_file_list; lock_file_list = lk; lk->on_list = 1; @@ -185,11 +174,9 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) path); } - strbuf_addstr(&lk->filename, path); - if (!(flags & LOCK_NODEREF)) { - resolve_symlink(lk->filename.buf, lk->filename.alloc); - strbuf_setlen(&lk->filename, strlen(lk->filename.buf)); - } + strbuf_add(&lk->filename, path, pathlen); + if (!(flags & LOCK_NODEREF)) + resolve_symlink(&lk->filename); strbuf_addstr(&lk->filename, LOCK_SUFFIX); lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); if (lk->fd < 0) { -- cgit v1.2.3 From 0c0d6e8601a1cfb8ebbdadb6a25a9f6fadc91359 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:35 +0200 Subject: trim_last_path_component(): replace last_path_elm() Rewrite last_path_elm() to take a strbuf parameter and to trim off the last path name element in place rather than returning a pointer to the beginning of the last path name element. This simplifies the function a bit and makes it integrate better with its caller, which is now also strbuf-based. Rename the function accordingly and a bit less tersely. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 5f5bcfffbe..56ad7e8a6e 100644 --- a/lockfile.c +++ b/lockfile.c @@ -76,32 +76,28 @@ static void remove_lock_file_on_signal(int signo) } /* - * p = absolute or relative path name + * path = absolute or relative path name * - * Return a pointer into p showing the beginning of the last path name - * element. If p is empty or the root directory ("/"), just return p. + * Remove the last path name element from path (leaving the preceding + * "/", if any). If path is empty or the root directory ("/"), set + * path to the empty string. */ -static char *last_path_elm(char *p) +static void trim_last_path_component(struct strbuf *path) { - /* r starts pointing to null at the end of the string */ - char *r = strchr(p, '\0'); - - if (r == p) - return p; /* just return empty string */ - - r--; /* back up to last non-null character */ + int i = path->len; /* back up past trailing slashes, if any */ - while (r > p && *r == '/') - r--; + while (i && path->buf[i - 1] == '/') + i--; /* - * then go backwards until I hit a slash, or the beginning of - * the string + * then go backwards until a slash, or the beginning of the + * string */ - while (r > p && *(r-1) != '/') - r--; - return r; + while (i && path->buf[i - 1] != '/') + i--; + + strbuf_setlen(path, i); } @@ -131,14 +127,12 @@ static void resolve_symlink(struct strbuf *path) if (is_absolute_path(link.buf)) /* absolute path simply replaces p */ strbuf_reset(path); - else { + else /* * link is a relative path, so replace the * last element of p with it. */ - char *r = last_path_elm(path->buf); - strbuf_setlen(path, r - path->buf); - } + trim_last_path_component(path); strbuf_addbuf(path, &link); } -- cgit v1.2.3 From 751bacedaa507b7b6d10b2c1f48e019a01a8fa6e Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:36 +0200 Subject: commit_lock_file_to(): refactor a helper out of commit_lock_file() commit_locked_index(), when writing to an alternate index file, duplicates (poorly) the code in commit_lock_file(). And anyway, it shouldn't have to know so much about the internal workings of lockfile objects. So extract a new function commit_lock_file_to() that does the work common to the two functions, and call it from both commit_lock_file() and commit_locked_index(). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 56ad7e8a6e..cf7f4d0470 100644 --- a/lockfile.c +++ b/lockfile.c @@ -43,9 +43,9 @@ * 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: + * - Unlocked (after commit_lock_file(), commit_lock_file_to(), + * rollback_lock_file(), a failed attempt to lock, or a failed + * close_lock_file()). In this state: * - active is unset * - filename is empty (usually, though there are transitory * states in which this condition doesn't hold). Client code should @@ -284,23 +284,15 @@ int reopen_lock_file(struct lock_file *lk) return lk->fd; } -int commit_lock_file(struct lock_file *lk) +int commit_lock_file_to(struct lock_file *lk, const char *path) { - static struct strbuf result_file = STRBUF_INIT; - int err; - if (!lk->active) - die("BUG: attempt to commit unlocked object"); + die("BUG: attempt to commit unlocked object to \"%s\"", path); if (close_lock_file(lk)) return -1; - /* remove ".lock": */ - strbuf_add(&result_file, lk->filename.buf, - lk->filename.len - LOCK_SUFFIX_LEN); - err = rename(lk->filename.buf, result_file.buf); - strbuf_reset(&result_file); - if (err) { + if (rename(lk->filename.buf, path)) { int save_errno = errno; rollback_lock_file(lk); errno = save_errno; @@ -312,6 +304,26 @@ int commit_lock_file(struct lock_file *lk) return 0; } +int commit_lock_file(struct lock_file *lk) +{ + static struct strbuf result_file = STRBUF_INIT; + int err; + + if (!lk->active) + die("BUG: attempt to commit unlocked object"); + + if (lk->filename.len <= LOCK_SUFFIX_LEN || + strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) + die("BUG: lockfile filename corrupt"); + + /* remove ".lock": */ + strbuf_add(&result_file, lk->filename.buf, + lk->filename.len - LOCK_SUFFIX_LEN); + err = commit_lock_file_to(lk, result_file.buf); + strbuf_reset(&result_file); + return err; +} + int hold_locked_index(struct lock_file *lk, int die_on_error) { return hold_lock_file_for_update(lk, get_index_file(), -- cgit v1.2.3 From 47ba4662bfd755829edd24428cf2e4bc492d70a6 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:37 +0200 Subject: lockfile: rename LOCK_NODEREF to LOCK_NO_DEREF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it harder to misread the name as LOCK_NODE_REF. Suggested-by: Torsten Bögershausen Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index cf7f4d0470..a1cc08a5ff 100644 --- a/lockfile.c +++ b/lockfile.c @@ -169,7 +169,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) } strbuf_add(&lk->filename, path, pathlen); - if (!(flags & LOCK_NODEREF)) + if (!(flags & LOCK_NO_DEREF)) resolve_symlink(&lk->filename); strbuf_addstr(&lk->filename, LOCK_SUFFIX); lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); -- cgit v1.2.3 From 316683bd37608e31cc3f5e932c4e5c7dde1b39f0 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:38 +0200 Subject: lockfile.c: rename static functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove_lock_file() -> remove_lock_files() * remove_lock_file_on_signal() -> remove_lock_files_on_signal() Suggested-by: Torsten Bögershausen Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index a1cc08a5ff..0a8c3c881e 100644 --- a/lockfile.c +++ b/lockfile.c @@ -57,7 +57,7 @@ static struct lock_file *volatile lock_file_list; -static void remove_lock_file(void) +static void remove_lock_files(void) { pid_t me = getpid(); @@ -68,9 +68,9 @@ static void remove_lock_file(void) } } -static void remove_lock_file_on_signal(int signo) +static void remove_lock_files_on_signal(int signo) { - remove_lock_file(); + remove_lock_files(); sigchain_pop(signo); raise(signo); } @@ -146,8 +146,8 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) if (!lock_file_list) { /* One-time initialization */ - sigchain_push_common(remove_lock_file_on_signal); - atexit(remove_lock_file); + sigchain_push_common(remove_lock_files_on_signal); + atexit(remove_lock_files); } if (lk->active) -- cgit v1.2.3 From ec38b4e482e96e62762452cab5714e55abdb48c3 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:39 +0200 Subject: get_locked_file_path(): new function Add a function to return the path of the file that is locked by a lock_file object. This reduces the knowledge that callers have to have about the lock_file layout. Suggested-by: Ronnie Sahlberg Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 0a8c3c881e..c51c6ec69d 100644 --- a/lockfile.c +++ b/lockfile.c @@ -257,6 +257,15 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) return fd; } +char *get_locked_file_path(struct lock_file *lk) +{ + if (!lk->active) + die("BUG: get_locked_file_path() called for unlocked object"); + if (lk->filename.len <= LOCK_SUFFIX_LEN) + die("BUG: get_locked_file_path() called for malformed lock object"); + return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN); +} + int close_lock_file(struct lock_file *lk) { int fd = lk->fd; -- cgit v1.2.3 From 4d423a3e62c7ab0b04c4bd84995c32daff3b24c3 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:40 +0200 Subject: hold_lock_file_for_append(): restore errno before returning Callers who don't pass LOCK_DIE_ON_ERROR might want to examine errno to see what went wrong, so restore errno before returning. In fact this function only has one caller, add_to_alternates_file(), and it *does* use LOCK_DIE_ON_ERROR, but, you know, think of future generations. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index c51c6ec69d..b2f5d36f7e 100644 --- a/lockfile.c +++ b/lockfile.c @@ -243,15 +243,22 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) orig_fd = open(path, O_RDONLY); if (orig_fd < 0) { if (errno != ENOENT) { + int save_errno = errno; + if (flags & LOCK_DIE_ON_ERROR) die("cannot open '%s' for copying", path); rollback_lock_file(lk); - return error("cannot open '%s' for copying", path); + error("cannot open '%s' for copying", path); + errno = save_errno; + return -1; } } else if (copy_fd(orig_fd, fd)) { + int save_errno = errno; + if (flags & LOCK_DIE_ON_ERROR) exit(128); rollback_lock_file(lk); + errno = save_errno; return -1; } return fd; -- cgit v1.2.3 From 216aab1e3d8eef088dc9785febce24a110e9f835 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:41 +0200 Subject: hold_locked_index(): move from lockfile.c to read-cache.c lockfile.c contains the general API for locking any file. Code specifically about the index file doesn't belong here. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index b2f5d36f7e..63f4e94bce 100644 --- a/lockfile.c +++ b/lockfile.c @@ -340,14 +340,6 @@ int commit_lock_file(struct lock_file *lk) return err; } -int hold_locked_index(struct lock_file *lk, int die_on_error) -{ - return hold_lock_file_for_update(lk, get_index_file(), - die_on_error - ? LOCK_DIE_ON_ERROR - : 0); -} - void rollback_lock_file(struct lock_file *lk) { if (!lk->active) -- cgit v1.2.3 From 697cc8efd944a32ca472337cd6640004c474b788 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 1 Oct 2014 12:28:42 +0200 Subject: lockfile.h: extract new header file for the functions in lockfile.c Move the interface declaration for the functions in lockfile.c from cache.h to a new file, lockfile.h. Add #includes where necessary (and remove some redundant includes of cache.h by files that already include builtin.h). Move the documentation of the lock_file state diagram from lockfile.c to the new header file. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- lockfile.c | 52 +--------------------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) (limited to 'lockfile.c') diff --git a/lockfile.c b/lockfile.c index 63f4e94bce..d27e61cafc 100644 --- a/lockfile.c +++ b/lockfile.c @@ -2,59 +2,9 @@ * Copyright (c) 2005, Junio C Hamano */ #include "cache.h" +#include "lockfile.h" #include "sigchain.h" -/* - * 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(), commit_lock_file_to(), - * rollback_lock_file(), a failed attempt to lock, or a failed - * close_lock_file()). In this state: - * - active is unset - * - filename is empty (usually, though there are transitory - * states in which this condition doesn't hold). Client code should - * *not* rely on the filename being empty in this state. - * - 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_files(void) -- cgit v1.2.3