regex: use regexec_buf()
[gitweb.git] / lockfile.c
index 920247249823b8f69524b845d6bdb0cb0b1b802b..9268cdf325f3881104d6d12ef31639536b15dcb2 100644 (file)
 /*
  * Copyright (c) 2005, Junio C Hamano
  */
-#include "cache.h"
 
-static struct lock_file *lock_file_list;
-static const char *alternate_index_output;
+#include "cache.h"
+#include "lockfile.h"
 
-static void remove_lock_file(void)
+/*
+ * path = absolute or relative path name
+ *
+ * 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 void trim_last_path_component(struct strbuf *path)
 {
-       pid_t me = getpid();
+       int i = path->len;
 
-       while (lock_file_list) {
-               if (lock_file_list->owner == me &&
-                   lock_file_list->filename[0])
-                       unlink(lock_file_list->filename);
-               lock_file_list = lock_file_list->next;
-       }
+       /* back up past trailing slashes, if any */
+       while (i && path->buf[i - 1] == '/')
+               i--;
+
+       /*
+        * then go backwards until a slash, or the beginning of the
+        * string
+        */
+       while (i && path->buf[i - 1] != '/')
+               i--;
+
+       strbuf_setlen(path, i);
 }
 
-static void remove_lock_file_on_signal(int signo)
+
+/* We allow "recursive" symbolic links. Only within reason, though */
+#define MAXDEPTH 5
+
+/*
+ * path contains a path that might be a symlink.
+ *
+ * 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.
+ *
+ * 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 void resolve_symlink(struct strbuf *path)
 {
-       remove_lock_file();
-       signal(SIGINT, SIG_DFL);
-       raise(signo);
+       int depth = MAXDEPTH;
+       static struct strbuf link = STRBUF_INIT;
+
+       while (depth--) {
+               if (strbuf_readlink(&link, path->buf, path->len) < 0)
+                       break;
+
+               if (is_absolute_path(link.buf))
+                       /* absolute path simply replaces p */
+                       strbuf_reset(path);
+               else
+                       /*
+                        * link is a relative path, so replace the
+                        * last element of p with it.
+                        */
+                       trim_last_path_component(path);
+
+               strbuf_addbuf(path, &link);
+       }
+       strbuf_reset(&link);
 }
 
-static int lock_file(struct lock_file *lk, const char *path)
+/* Make sure errno contains a meaningful value on error */
+static int lock_file(struct lock_file *lk, const char *path, int flags)
 {
        int fd;
-       struct stat st;
-
-       if ((!lstat(path, &st)) && S_ISLNK(st.st_mode)) {
-               ssize_t sz;
-               static char target[PATH_MAX];
-               sz = readlink(path, target, sizeof(target));
-               if (sz < 0)
-                       warning("Cannot readlink %s", path);
-               else if (target[0] != '/')
-                       warning("Cannot lock target of relative symlink %s", path);
-               else
-                       path = target;
-       }
-       sprintf(lk->filename, "%s.lock", path);
-       fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
-       if (0 <= fd) {
-               if (!lock_file_list) {
-                       signal(SIGINT, 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;
+       struct strbuf filename = STRBUF_INIT;
+
+       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_update(struct lock_file *lk, const char *path, int die_on_error)
+/*
+ * 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 = lock_file(lk, path);
-       if (fd < 0 && die_on_error)
-               die("unable to create '%s.lock': %s", path, strerror(errno));
-       return fd;
+       int n = 1;
+       int multiplier = 1;
+       long remaining_ms = 0;
+       static int random_initialized = 0;
+
+       if (timeout_ms == 0)
+               return lock_file(lk, path, flags);
+
+       if (!random_initialized) {
+               srand((unsigned int)getpid());
+               random_initialized = 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++;
+       }
 }
 
-int commit_lock_file(struct lock_file *lk)
+void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
 {
-       char result_file[PATH_MAX];
-       int i;
-       strcpy(result_file, lk->filename);
-       i = strlen(result_file) - 5; /* .lock */
-       result_file[i] = 0;
-       i = rename(lk->filename, result_file);
-       lk->filename[0] = 0;
-       return i;
+       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 hold_locked_index(struct lock_file *lk, int die_on_error)
+NORETURN void unable_to_lock_die(const char *path, int err)
 {
-       return hold_lock_file_for_update(lk, get_index_file(), die_on_error);
+       struct strbuf buf = STRBUF_INIT;
+
+       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 && (flags & LOCK_DIE_ON_ERROR))
+               unable_to_lock_die(path, errno);
+       return fd;
 }
 
-int commit_locked_index(struct lock_file *lk)
+char *get_locked_file_path(struct lock_file *lk)
 {
-       if (alternate_index_output) {
-               int result = rename(lk->filename, alternate_index_output);
-               lk->filename[0] = 0;
-               return result;
-       }
-       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])
-               unlink(lk->filename);
-       lk->filename[0] = 0;
+       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;
+       }
+       free(result_path);
+       return 0;
 }