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:
Junio C Hamano 2014-01-27 10:45:33 -08:00
commit d0956cfa8e
6 changed files with 155 additions and 67 deletions

View File

@ -515,13 +515,14 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
saved = shared_repository; saved = shared_repository;
shared_repository = 0; shared_repository = 0;
switch (safe_create_leading_directories_const(argv[0])) { switch (safe_create_leading_directories_const(argv[0])) {
case -3: case SCLD_OK:
case SCLD_PERMS:
break;
case SCLD_EXISTS:
errno = EEXIST; errno = EEXIST;
/* fallthru */ /* fallthru */
case -1:
die_errno(_("cannot mkdir %s"), argv[0]);
break;
default: default:
die_errno(_("cannot mkdir %s"), argv[0]);
break; break;
} }
shared_repository = saved; shared_repository = saved;

25
cache.h
View File

@ -737,8 +737,29 @@ enum sharedrepo {
}; };
int git_config_perm(const char *var, const char *value); int git_config_perm(const char *var, const char *value);
int adjust_shared_perm(const char *path); 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); int mkdir_in_gitdir(const char *path);
extern void home_config_paths(char **global, char **xdg, char *file); extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path); extern char *expand_user_path(const char *path);

27
dir.c
View File

@ -1511,8 +1511,13 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL; flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf); dir = opendir(path->buf);
if (!dir) { if (!dir) {
/* an empty dir could be removed even if it is unreadble */ if (errno == ENOENT)
if (!keep_toplevel) 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); return rmdir(path->buf);
else else
return -1; return -1;
@ -1528,13 +1533,21 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
strbuf_setlen(path, len); strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name); strbuf_addstr(path, e->d_name);
if (lstat(path->buf, &st)) if (lstat(path->buf, &st)) {
; /* fall thru */ if (errno == ENOENT)
else if (S_ISDIR(st.st_mode)) { /*
* 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)) if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */ continue; /* happy */
} else if (!only_empty && !unlink(path->buf)) } else if (!only_empty &&
(!unlink(path->buf) || errno == ENOENT)) {
continue; /* happy, too */ continue; /* happy, too */
}
/* path too long, stat fails, or non-directory still exists */ /* path too long, stat fails, or non-directory still exists */
ret = -1; ret = -1;
@ -1544,7 +1557,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
strbuf_setlen(path, original_len); strbuf_setlen(path, original_len);
if (!ret && !keep_toplevel && !kept_down) if (!ret && !keep_toplevel && !kept_down)
ret = rmdir(path->buf); ret = (!rmdir(path->buf) || errno == ENOENT) ? 0 : -1;
else if (kept_up) else if (kept_up)
/* /*
* report the uplevel that it is not an error that we * report the uplevel that it is not an error that we

View File

@ -693,7 +693,7 @@ static int make_room_for_path(struct merge_options *o, const char *path)
/* Make sure leading directories are created */ /* Make sure leading directories are created */
status = safe_create_leading_directories_const(path); status = safe_create_leading_directories_const(path);
if (status) { if (status) {
if (status == -3) { if (status == SCLD_EXISTS) {
/* something else exists */ /* something else exists */
error(msg, path, _(": perhaps a D/F conflict?")); error(msg, path, _(": perhaps a D/F conflict?"));
return -1; return -1;

92
refs.c
View File

@ -2039,6 +2039,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int type, lflags; int type, lflags;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
int missing = 0; int missing = 0;
int attempts_remaining = 3;
lock = xcalloc(1, sizeof(struct ref_lock)); lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1; 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)); lock->lk = xcalloc(1, sizeof(struct lock_file));
lflags = LOCK_DIE_ON_ERROR; lflags = 0;
if (flags & REF_NODEREF) { if (flags & REF_NODEREF) {
refname = orig_refname; refname = orig_refname;
lflags |= LOCK_NODEREF; 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)) if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
lock->force_write = 1; 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; last_errno = errno;
error("unable to create directory for %s", ref_file); error("unable to create directory for %s", ref_file);
goto error_return; goto error_return;
} }
lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags); 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; return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
error_return: 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" #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) int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{ {
unsigned char sha1[20], orig_sha1[20]; 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))) { if (log && rename_tmp_log(newrefname))
error("unable to create directory for %s", newrefname);
goto rollback; 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; logmoved = log;
lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL); lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);

View File

@ -105,50 +105,59 @@ int mkdir_in_gitdir(const char *path)
return adjust_shared_perm(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); char *next_component = path + offset_1st_component(path);
struct stat st; enum scld_error ret = SCLD_OK;
while (pos) { while (ret == SCLD_OK && next_component) {
pos = strchr(pos, '/'); struct stat st;
if (!pos) char *slash = strchr(next_component, '/');
if (!slash)
break; break;
while (*++pos == '/')
; next_component = slash + 1;
if (!*pos) while (*next_component == '/')
next_component++;
if (!*next_component)
break; break;
*--pos = '\0';
*slash = '\0';
if (!stat(path, &st)) { if (!stat(path, &st)) {
/* path exists */ /* path exists */
if (!S_ISDIR(st.st_mode)) { if (!S_ISDIR(st.st_mode))
*pos = '/'; ret = SCLD_EXISTS;
return -3; } else if (mkdir(path, 0777)) {
}
}
else if (mkdir(path, 0777)) {
if (errno == EEXIST && 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 */ ; /* somebody created it since we checked */
} else { else if (errno == ENOENT)
*pos = '/'; /*
return -1; * 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)) { *slash = '/';
*pos = '/';
return -2;
}
*pos++ = '/';
} }
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 */ /* path points to cache entries, so xstrdup before messing with it */
char *buf = xstrdup(path); char *buf = xstrdup(path);
int result = safe_create_leading_directories(buf); enum scld_error result = safe_create_leading_directories(buf);
free(buf); free(buf);
return result; return result;
} }