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
This commit is contained in:
92
refs.c
92
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);
|
||||
|
Reference in New Issue
Block a user