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:
commit
d0956cfa8e
@ -515,13 +515,14 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
|
||||
saved = shared_repository;
|
||||
shared_repository = 0;
|
||||
switch (safe_create_leading_directories_const(argv[0])) {
|
||||
case -3:
|
||||
case SCLD_OK:
|
||||
case SCLD_PERMS:
|
||||
break;
|
||||
case SCLD_EXISTS:
|
||||
errno = EEXIST;
|
||||
/* fallthru */
|
||||
case -1:
|
||||
die_errno(_("cannot mkdir %s"), argv[0]);
|
||||
break;
|
||||
default:
|
||||
die_errno(_("cannot mkdir %s"), argv[0]);
|
||||
break;
|
||||
}
|
||||
shared_repository = saved;
|
||||
|
25
cache.h
25
cache.h
@ -737,8 +737,29 @@ enum sharedrepo {
|
||||
};
|
||||
int git_config_perm(const char *var, const char *value);
|
||||
int adjust_shared_perm(const char *path);
|
||||
int safe_create_leading_directories(char *path);
|
||||
int safe_create_leading_directories_const(const char *path);
|
||||
|
||||
/*
|
||||
* Create the directory containing the named path, using care to be
|
||||
* somewhat safe against races. Return one of the scld_error values
|
||||
* to indicate success/failure.
|
||||
*
|
||||
* SCLD_VANISHED indicates that one of the ancestor directories of the
|
||||
* path existed at one point during the function call and then
|
||||
* suddenly vanished, probably because another process pruned the
|
||||
* directory while we were working. To be robust against this kind of
|
||||
* race, callers might want to try invoking the function again when it
|
||||
* returns SCLD_VANISHED.
|
||||
*/
|
||||
enum scld_error {
|
||||
SCLD_OK = 0,
|
||||
SCLD_FAILED = -1,
|
||||
SCLD_PERMS = -2,
|
||||
SCLD_EXISTS = -3,
|
||||
SCLD_VANISHED = -4
|
||||
};
|
||||
enum scld_error safe_create_leading_directories(char *path);
|
||||
enum scld_error safe_create_leading_directories_const(const char *path);
|
||||
|
||||
int mkdir_in_gitdir(const char *path);
|
||||
extern void home_config_paths(char **global, char **xdg, char *file);
|
||||
extern char *expand_user_path(const char *path);
|
||||
|
27
dir.c
27
dir.c
@ -1511,8 +1511,13 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
|
||||
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
|
||||
dir = opendir(path->buf);
|
||||
if (!dir) {
|
||||
/* an empty dir could be removed even if it is unreadble */
|
||||
if (!keep_toplevel)
|
||||
if (errno == ENOENT)
|
||||
return keep_toplevel ? -1 : 0;
|
||||
else if (errno == EACCES && !keep_toplevel)
|
||||
/*
|
||||
* An empty dir could be removable even if it
|
||||
* is unreadable:
|
||||
*/
|
||||
return rmdir(path->buf);
|
||||
else
|
||||
return -1;
|
||||
@ -1528,13 +1533,21 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
|
||||
|
||||
strbuf_setlen(path, len);
|
||||
strbuf_addstr(path, e->d_name);
|
||||
if (lstat(path->buf, &st))
|
||||
; /* fall thru */
|
||||
else if (S_ISDIR(st.st_mode)) {
|
||||
if (lstat(path->buf, &st)) {
|
||||
if (errno == ENOENT)
|
||||
/*
|
||||
* file disappeared, which is what we
|
||||
* wanted anyway
|
||||
*/
|
||||
continue;
|
||||
/* fall thru */
|
||||
} else if (S_ISDIR(st.st_mode)) {
|
||||
if (!remove_dir_recurse(path, flag, &kept_down))
|
||||
continue; /* happy */
|
||||
} else if (!only_empty && !unlink(path->buf))
|
||||
} else if (!only_empty &&
|
||||
(!unlink(path->buf) || errno == ENOENT)) {
|
||||
continue; /* happy, too */
|
||||
}
|
||||
|
||||
/* path too long, stat fails, or non-directory still exists */
|
||||
ret = -1;
|
||||
@ -1544,7 +1557,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
|
||||
|
||||
strbuf_setlen(path, original_len);
|
||||
if (!ret && !keep_toplevel && !kept_down)
|
||||
ret = rmdir(path->buf);
|
||||
ret = (!rmdir(path->buf) || errno == ENOENT) ? 0 : -1;
|
||||
else if (kept_up)
|
||||
/*
|
||||
* report the uplevel that it is not an error that we
|
||||
|
@ -693,7 +693,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
|
||||
/* Make sure leading directories are created */
|
||||
status = safe_create_leading_directories_const(path);
|
||||
if (status) {
|
||||
if (status == -3) {
|
||||
if (status == SCLD_EXISTS) {
|
||||
/* something else exists */
|
||||
error(msg, path, _(": perhaps a D/F conflict?"));
|
||||
return -1;
|
||||
|
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);
|
||||
|
67
sha1_file.c
67
sha1_file.c
@ -105,50 +105,59 @@ int mkdir_in_gitdir(const char *path)
|
||||
return adjust_shared_perm(path);
|
||||
}
|
||||
|
||||
int safe_create_leading_directories(char *path)
|
||||
enum scld_error safe_create_leading_directories(char *path)
|
||||
{
|
||||
char *pos = path + offset_1st_component(path);
|
||||
struct stat st;
|
||||
char *next_component = path + offset_1st_component(path);
|
||||
enum scld_error ret = SCLD_OK;
|
||||
|
||||
while (pos) {
|
||||
pos = strchr(pos, '/');
|
||||
if (!pos)
|
||||
while (ret == SCLD_OK && next_component) {
|
||||
struct stat st;
|
||||
char *slash = strchr(next_component, '/');
|
||||
|
||||
if (!slash)
|
||||
break;
|
||||
while (*++pos == '/')
|
||||
;
|
||||
if (!*pos)
|
||||
|
||||
next_component = slash + 1;
|
||||
while (*next_component == '/')
|
||||
next_component++;
|
||||
if (!*next_component)
|
||||
break;
|
||||
*--pos = '\0';
|
||||
|
||||
*slash = '\0';
|
||||
if (!stat(path, &st)) {
|
||||
/* path exists */
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
*pos = '/';
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
else if (mkdir(path, 0777)) {
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
ret = SCLD_EXISTS;
|
||||
} else if (mkdir(path, 0777)) {
|
||||
if (errno == EEXIST &&
|
||||
!stat(path, &st) && S_ISDIR(st.st_mode)) {
|
||||
!stat(path, &st) && S_ISDIR(st.st_mode))
|
||||
; /* somebody created it since we checked */
|
||||
} else {
|
||||
*pos = '/';
|
||||
return -1;
|
||||
}
|
||||
else if (errno == ENOENT)
|
||||
/*
|
||||
* Either mkdir() failed because
|
||||
* somebody just pruned the containing
|
||||
* directory, or stat() failed because
|
||||
* the file that was in our way was
|
||||
* just removed. Either way, inform
|
||||
* the caller that it might be worth
|
||||
* trying again:
|
||||
*/
|
||||
ret = SCLD_VANISHED;
|
||||
else
|
||||
ret = SCLD_FAILED;
|
||||
} else if (adjust_shared_perm(path)) {
|
||||
ret = SCLD_PERMS;
|
||||
}
|
||||
else if (adjust_shared_perm(path)) {
|
||||
*pos = '/';
|
||||
return -2;
|
||||
}
|
||||
*pos++ = '/';
|
||||
*slash = '/';
|
||||
}
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int safe_create_leading_directories_const(const char *path)
|
||||
enum scld_error safe_create_leading_directories_const(const char *path)
|
||||
{
|
||||
/* path points to cache entries, so xstrdup before messing with it */
|
||||
char *buf = xstrdup(path);
|
||||
int result = safe_create_leading_directories(buf);
|
||||
enum scld_error result = safe_create_leading_directories(buf);
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user