diff options
Diffstat (limited to 'lockfile.c')
-rw-r--r-- | lockfile.c | 336 |
1 files changed, 150 insertions, 186 deletions
diff --git a/lockfile.c b/lockfile.c index 021c3375c1..aa69210d8b 100644 --- a/lockfile.c +++ b/lockfile.c @@ -1,61 +1,33 @@ /* * Copyright (c) 2005, Junio C Hamano */ -#include "cache.h" -#include "sigchain.h" - -static struct lock_file *lock_file_list; -static const char *alternate_index_output; - -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(lock_file_list->filename); - } - lock_file_list = lock_file_list->next; - } -} -static void remove_lock_file_on_signal(int signo) -{ - remove_lock_file(); - sigchain_pop(signo); - raise(signo); -} +#include "cache.h" +#include "lockfile.h" /* - * 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); } @@ -63,189 +35,181 @@ static char *last_path_elm(char *p) #define MAXDEPTH 5 /* - * p = path that may be a symlink - * s = full size of p - * - * 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. + * path contains a path that might be a symlink. * - * 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. + * 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. * - * 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--) { - 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, path->buf, path->len) < 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); - else { - warning("%s: symlink too long", p); - return p; - } - } else { + strbuf_reset(path); + 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); - else { - warning("%s: symlink too long", p); - return p; - } - } + trim_last_path_component(path); + + strbuf_addbuf(path, &link); } - return p; + strbuf_reset(&link); } - +/* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - if (strlen(path) >= sizeof(lk->filename)) - return -1; - strcpy(lk->filename, path); - /* - * subtract 5 from size to make sure there's room for adding - * ".lock" for the lock file name - */ - if (!(flags & LOCK_NODEREF)) - resolve_symlink(lk->filename, sizeof(lk->filename)-5); - 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)) - return error("cannot fix permission bits on %s", - lk->filename); - } - else - lk->filename[0] = 0; - return lk->fd; -} + int fd; + struct strbuf filename = STRBUF_INIT; -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)) - die("unable to create '%s.lock': %s", path, strerror(errno)); + strbuf_addstr(&filename, path); + if (!(flags & LOCK_NO_DEREF)) + resolve_symlink(&filename); + + strbuf_addstr(&filename, LOCK_SUFFIX); + fd = create_tempfile(&lk->tempfile, filename.buf); + strbuf_release(&filename); return fd; } -int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) +/* + * Constants defining the gaps between attempts to lock a file. The + * first backoff period is approximately INITIAL_BACKOFF_MS + * milliseconds. The longest backoff period is approximately + * (BACKOFF_MAX_MULTIPLIER * INITIAL_BACKOFF_MS) milliseconds. + */ +#define INITIAL_BACKOFF_MS 1L +#define BACKOFF_MAX_MULTIPLIER 1000 + +/* + * Try locking path, retrying with quadratic backoff for at least + * timeout_ms milliseconds. If timeout_ms is 0, try locking the file + * exactly once. If timeout_ms is -1, try indefinitely. + */ +static int lock_file_timeout(struct lock_file *lk, const char *path, + int flags, long timeout_ms) { - int fd, orig_fd; + int n = 1; + int multiplier = 1; + long remaining_ms = 0; + static int random_initialized = 0; - fd = lock_file(lk, path, flags); - if (fd < 0) { - if (flags & LOCK_DIE_ON_ERROR) - die("unable to create '%s.lock': %s", path, strerror(errno)); - return fd; + if (timeout_ms == 0) + return lock_file(lk, path, flags); + + if (!random_initialized) { + srand((unsigned int)getpid()); + random_initialized = 1; } - orig_fd = open(path, O_RDONLY); - if (orig_fd < 0) { - if (errno != ENOENT) { - if (flags & LOCK_DIE_ON_ERROR) - die("cannot open '%s' for copying", path); - close(fd); - 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); - return -1; + if (timeout_ms > 0) + remaining_ms = timeout_ms; + + while (1) { + long backoff_ms, wait_ms; + int fd; + + fd = lock_file(lk, path, flags); + + if (fd >= 0) + return fd; /* success */ + else if (errno != EEXIST) + return -1; /* failure other than lock held */ + else if (timeout_ms > 0 && remaining_ms <= 0) + return -1; /* failure due to timeout */ + + backoff_ms = multiplier * INITIAL_BACKOFF_MS; + /* back off for between 0.75*backoff_ms and 1.25*backoff_ms */ + wait_ms = (750 + rand() % 500) * backoff_ms / 1000; + sleep_millisec(wait_ms); + remaining_ms -= wait_ms; + + /* Recursion: (n+1)^2 = n^2 + 2n + 1 */ + multiplier += 2*n + 1; + if (multiplier > BACKOFF_MAX_MULTIPLIER) + multiplier = BACKOFF_MAX_MULTIPLIER; + else + n++; } - return fd; } -int close_lock_file(struct lock_file *lk) +void unable_to_lock_message(const char *path, int err, struct strbuf *buf) { - int fd = lk->fd; - lk->fd = -1; - return close(fd); + if (err == EEXIST) { + strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n" + "Another git process seems to be running in this repository, e.g.\n" + "an editor opened by 'git commit'. Please make sure all processes\n" + "are terminated then try again. If it still fails, a git process\n" + "may have crashed in this repository earlier:\n" + "remove the file manually to continue."), + absolute_path(path), strerror(err)); + } else + strbuf_addf(buf, _("Unable to create '%s.lock': %s"), + absolute_path(path), strerror(err)); } -int commit_lock_file(struct lock_file *lk) +NORETURN void unable_to_lock_die(const char *path, int err) { - char result_file[PATH_MAX]; - size_t i; - if (lk->fd >= 0 && close_lock_file(lk)) - return -1; - strcpy(result_file, lk->filename); - i = strlen(result_file) - 5; /* .lock */ - result_file[i] = 0; - if (rename(lk->filename, result_file)) - return -1; - lk->filename[0] = 0; - return 0; -} + struct strbuf buf = STRBUF_INIT; -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); + unable_to_lock_message(path, err, &buf); + die("%s", buf.buf); } -void set_alternate_index_output(const char *name) +/* This should return a meaningful errno on failure */ +int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path, + int flags, long timeout_ms) { - alternate_index_output = name; + int fd = lock_file_timeout(lk, path, flags, timeout_ms); + if (fd < 0) { + if (flags & LOCK_DIE_ON_ERROR) + unable_to_lock_die(path, errno); + if (flags & LOCK_REPORT_ON_ERROR) { + struct strbuf buf = STRBUF_INIT; + unable_to_lock_message(path, errno, &buf); + error("%s", buf.buf); + strbuf_release(&buf); + } + } + return fd; } -int commit_locked_index(struct lock_file *lk) +char *get_locked_file_path(struct lock_file *lk) { - if (alternate_index_output) { - if (lk->fd >= 0 && close_lock_file(lk)) - return -1; - if (rename(lk->filename, alternate_index_output)) - return -1; - lk->filename[0] = 0; - return 0; - } - else - return commit_lock_file(lk); + struct strbuf ret = STRBUF_INIT; + + strbuf_addstr(&ret, get_tempfile_path(&lk->tempfile)); + if (ret.len <= LOCK_SUFFIX_LEN || + strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) + die("BUG: get_locked_file_path() called for malformed lock object"); + /* remove ".lock": */ + strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN); + return strbuf_detach(&ret, NULL); } -void rollback_lock_file(struct lock_file *lk) +int commit_lock_file(struct lock_file *lk) { - if (lk->filename[0]) { - if (lk->fd >= 0) - close(lk->fd); - unlink(lk->filename); + char *result_path = get_locked_file_path(lk); + + if (commit_lock_file_to(lk, result_path)) { + int save_errno = errno; + free(result_path); + errno = save_errno; + return -1; } - lk->filename[0] = 0; + free(result_path); + return 0; } |