summaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2014-01-27 18:45:33 (GMT)
committerJunio C Hamano <gitster@pobox.com>2014-01-27 18:45:33 (GMT)
commitd0956cfa8ef8b3668ea6adc94b27f5dcdc93bde0 (patch)
tree188ea26d10feaf884b59423fdf8c8bafeb73e718 /refs.c
parentc380cf85a79c78d9dceb9290c9d4017d30804521 (diff)
parent08f555cb82f92797ca0aa0d6ba32e6872f1331e5 (diff)
downloadgit-d0956cfa8ef8b3668ea6adc94b27f5dcdc93bde0.zip
git-d0956cfa8ef8b3668ea6adc94b27f5dcdc93bde0.tar.gz
git-d0956cfa8ef8b3668ea6adc94b27f5dcdc93bde0.tar.bz2
Merge branch 'mh/safe-create-leading-directories'
Code clean-up and protection against concurrent write access to the ref namespace. * mh/safe-create-leading-directories: rename_tmp_log(): on SCLD_VANISHED, retry rename_tmp_log(): limit the number of remote_empty_directories() attempts rename_tmp_log(): handle a possible mkdir/rmdir race rename_ref(): extract function rename_tmp_log() remove_dir_recurse(): handle disappearing files and directories remove_dir_recurse(): tighten condition for removing unreadable dir lock_ref_sha1_basic(): if locking fails with ENOENT, retry lock_ref_sha1_basic(): on SCLD_VANISHED, retry safe_create_leading_directories(): add new error value SCLD_VANISHED cmd_init_db(): when creating directories, handle errors conservatively safe_create_leading_directories(): introduce enum for return values safe_create_leading_directories(): always restore slash at end of loop safe_create_leading_directories(): split on first of multiple slashes safe_create_leading_directories(): rename local variable safe_create_leading_directories(): add explicit "slash" pointer safe_create_leading_directories(): reduce scope of local variable safe_create_leading_directories(): fix format of "if" chaining
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c92
1 files changed, 68 insertions, 24 deletions
diff --git a/refs.c b/refs.c
index fc33ee8..89228e2 100644
--- a/refs.c
+++ b/refs.c
@@ -2039,6 +2039,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int type, lflags;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
int missing = 0;
+ int attempts_remaining = 3;
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
@@ -2080,7 +2081,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
lock->lk = xcalloc(1, sizeof(struct lock_file));
- lflags = LOCK_DIE_ON_ERROR;
+ lflags = 0;
if (flags & REF_NODEREF) {
refname = orig_refname;
lflags |= LOCK_NODEREF;
@@ -2093,13 +2094,32 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
lock->force_write = 1;
- if (safe_create_leading_directories(ref_file)) {
+ retry:
+ switch (safe_create_leading_directories(ref_file)) {
+ case SCLD_OK:
+ break; /* success */
+ case SCLD_VANISHED:
+ if (--attempts_remaining > 0)
+ goto retry;
+ /* fall through */
+ default:
last_errno = errno;
error("unable to create directory for %s", ref_file);
goto error_return;
}
lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
+ if (lock->lock_fd < 0) {
+ if (errno == ENOENT && --attempts_remaining > 0)
+ /*
+ * Maybe somebody just deleted one of the
+ * directories leading to ref_file. Try
+ * again:
+ */
+ goto retry;
+ else
+ unable_to_lock_index_die(ref_file, errno);
+ }
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
error_return:
@@ -2508,6 +2528,51 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
*/
#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
+static int rename_tmp_log(const char *newrefname)
+{
+ int attempts_remaining = 4;
+
+ retry:
+ switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+ case SCLD_OK:
+ break; /* success */
+ case SCLD_VANISHED:
+ if (--attempts_remaining > 0)
+ goto retry;
+ /* fall through */
+ default:
+ error("unable to create directory for %s", newrefname);
+ return -1;
+ }
+
+ if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
+ if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
+ /*
+ * rename(a, b) when b is an existing
+ * directory ought to result in ISDIR, but
+ * Solaris 5.8 gives ENOTDIR. Sheesh.
+ */
+ if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+ error("Directory not empty: logs/%s", 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));
+ return -1;
+ }
+ }
+ return 0;
+}
+
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
unsigned char sha1[20], orig_sha1[20];
@@ -2555,30 +2620,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
}
}
- if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
- error("unable to create directory for %s", newrefname);
+ if (log && rename_tmp_log(newrefname))
goto rollback;
- }
- retry:
- if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
- if (errno==EISDIR || errno==ENOTDIR) {
- /*
- * rename(a, b) when b is an existing
- * directory ought to result in ISDIR, but
- * Solaris 5.8 gives ENOTDIR. Sheesh.
- */
- if (remove_empty_directories(git_path("logs/%s", newrefname))) {
- error("Directory not empty: logs/%s", newrefname);
- goto rollback;
- }
- goto retry;
- } else {
- error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
- newrefname, strerror(errno));
- goto rollback;
- }
- }
logmoved = log;
lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);