Mercurial > dovecot > core-2.2
view src/lib/file-dotlock.c @ 3863:55df57c028d4 HEAD
Added "bool" type and changed all ints that were used as booleans to bool.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 13 Jan 2006 22:25:57 +0200 |
parents | 9680bd9e6346 |
children | 0d64f8888dcd |
line wrap: on
line source
/* Copyright (C) 2003 Timo Sirainen */ #include "lib.h" #include "str.h" #include "hex-binary.h" #include "hostpid.h" #include "randgen.h" #include "write-full.h" #include "file-dotlock.h" #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <sys/stat.h> #define DEFAULT_LOCK_SUFFIX ".lock" /* 0.1 .. 0.2msec */ #define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000) struct dotlock { struct dotlock_settings settings; dev_t dev; ino_t ino; time_t mtime; char *path; int fd; time_t lock_time; }; struct lock_info { const struct dotlock_settings *set; const char *path, *lock_path, *temp_path; int fd; dev_t dev; ino_t ino; off_t size; time_t ctime, mtime; off_t last_size; time_t last_ctime, last_mtime; time_t last_change; bool have_pid; time_t last_pid_check; }; static struct dotlock * file_dotlock_alloc(const struct dotlock_settings *settings) { struct dotlock *dotlock; dotlock = i_new(struct dotlock, 1); dotlock->settings = *settings; if (dotlock->settings.lock_suffix == NULL) dotlock->settings.lock_suffix = DEFAULT_LOCK_SUFFIX; dotlock->fd = -1; return dotlock; } static pid_t read_local_pid(const char *lock_path) { char buf[512], *host; int fd; ssize_t ret; fd = open(lock_path, O_RDONLY); if (fd == -1) return -1; /* ignore the actual error */ /* read line */ ret = read(fd, buf, sizeof(buf)-1); (void)close(fd); if (ret <= 0) return -1; /* fix the string */ if (buf[ret-1] == '\n') ret--; buf[ret] = '\0'; /* it should contain pid:host */ host = strchr(buf, ':'); if (host == NULL) return -1; *host++ = '\0'; /* host must be ours */ if (strcmp(host, my_hostname) != 0) return -1; if (!is_numeric(buf, '\0')) return -1; return (pid_t)strtoul(buf, NULL, 0); } static int check_lock(time_t now, struct lock_info *lock_info) { time_t immediate_stale_timeout = lock_info->set->immediate_stale_timeout; time_t stale_timeout = lock_info->set->stale_timeout; struct stat st; pid_t pid; if (lstat(lock_info->lock_path, &st) < 0) { if (errno != ENOENT) { i_error("lstat(%s) failed: %m", lock_info->lock_path); return -1; } return 1; } if (lock_info->set->immediate_stale_timeout != 0 && now > st.st_mtime + immediate_stale_timeout && now > st.st_ctime + immediate_stale_timeout) { /* old lock file */ if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) { i_error("unlink(%s) failed: %m", lock_info->lock_path); return -1; } return 1; } if (lock_info->ino != st.st_ino || !CMP_DEV_T(lock_info->dev, st.st_dev) || lock_info->ctime != st.st_ctime || lock_info->mtime != st.st_mtime || lock_info->size != st.st_size) { /* either our first check or someone else got the lock file. */ lock_info->dev = st.st_dev; lock_info->ino = st.st_ino; lock_info->ctime = st.st_ctime; lock_info->mtime = st.st_mtime; lock_info->size = st.st_size; pid = read_local_pid(lock_info->lock_path); lock_info->have_pid = pid != -1; lock_info->last_change = now; } else if (!lock_info->have_pid) { /* no pid checking */ pid = -1; } else { if (lock_info->last_pid_check == now) { /* we just checked the pid */ return 0; } /* re-read the pid. even if all times and inodes are the same, the PID in the file might have changed if lock files were rapidly being recreated. */ pid = read_local_pid(lock_info->lock_path); lock_info->have_pid = pid != -1; } if (lock_info->have_pid) { /* we've local PID. Check if it exists. */ if (kill(pid, 0) == 0 || errno != ESRCH) { if (pid != getpid()) return 0; /* it's us. either we're locking it again, or it's a stale lock file with same pid than us. either way, recreate it.. */ } /* doesn't exist - go ahead and delete */ if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) { i_error("unlink(%s) failed: %m", lock_info->lock_path); return -1; } return 1; } if (stale_timeout == 0) { /* no change checking */ return 0; } if (lock_info->last_change != now) { if (stat(lock_info->path, &st) < 0) { if (errno == ENOENT) { /* file doesn't exist. treat it as if it hasn't changed */ } else { i_error("stat(%s) failed: %m", lock_info->path); return -1; } } else if (lock_info->last_size != st.st_size || lock_info->last_ctime != st.st_ctime || lock_info->last_mtime != st.st_mtime) { lock_info->last_change = now; lock_info->last_size = st.st_size; lock_info->last_ctime = st.st_ctime; lock_info->last_mtime = st.st_mtime; } } if (now > lock_info->last_change + stale_timeout) { /* no changes for a while, assume stale lock */ if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) { i_error("unlink(%s) failed: %m", lock_info->lock_path); return -1; } return 1; } return 0; } static int file_write_pid(int fd, const char *path) { const char *str; /* write our pid and host, if possible */ str = t_strdup_printf("%s:%s", my_pid, my_hostname); if (write_full(fd, str, strlen(str)) < 0) { /* failed, leave it empty then */ if (ftruncate(fd, 0) < 0) { i_error("ftruncate(%s) failed: %m", path); return -1; } } return 0; } static int create_temp_file(const char *prefix, const char **path_r, bool write_pid) { string_t *path; size_t len; struct stat st; unsigned char randbuf[8]; int fd; path = t_str_new(256); str_append(path, prefix); len = str_len(path); for (;;) { do { random_fill_weak(randbuf, sizeof(randbuf)); str_truncate(path, len); str_append(path, binary_to_hex(randbuf, sizeof(randbuf))); *path_r = str_c(path); } while (stat(*path_r, &st) == 0); if (errno != ENOENT) { i_error("stat(%s) failed: %m", *path_r); return -1; } fd = open(*path_r, O_RDWR | O_EXCL | O_CREAT, 0666); if (fd != -1) break; if (errno != EEXIST) { i_error("open(%s) failed: %m", *path_r); return -1; } } if (write_pid) { if (file_write_pid(fd, *path_r) < 0) { (void)close(fd); return -1; } } return fd; } static int try_create_lock_hardlink(struct lock_info *lock_info, bool write_pid) { const char *temp_prefix = lock_info->set->temp_prefix; const char *str, *p; if (lock_info->temp_path == NULL) { /* we'll need our temp file first. */ i_assert(lock_info->fd == -1); if (temp_prefix == NULL) { temp_prefix = t_strconcat(".temp.", my_hostname, ".", my_pid, ".", NULL); } p = *temp_prefix == '/' ? NULL : strrchr(lock_info->lock_path, '/'); if (p != NULL) { str = t_strdup_until(lock_info->lock_path, p+1); temp_prefix = t_strconcat(str, temp_prefix, NULL); } lock_info->fd = create_temp_file(temp_prefix, &str, write_pid); if (lock_info->fd == -1) return -1; lock_info->temp_path = str; } if (link(lock_info->temp_path, lock_info->lock_path) < 0) { if (errno == EEXIST) return 0; i_error("link(%s, %s) failed: %m", lock_info->temp_path, lock_info->lock_path); return -1; } if (unlink(lock_info->temp_path) < 0 && errno != ENOENT) { i_error("unlink(%s) failed: %m", lock_info->temp_path); /* non-fatal, continue */ } lock_info->temp_path = NULL; return 1; } static int try_create_lock_excl(struct lock_info *lock_info, bool write_pid) { int fd; fd = open(lock_info->lock_path, O_RDWR | O_EXCL | O_CREAT, 0666); if (fd == -1) { if (errno == EEXIST) return 0; i_error("open(%s) failed: %m", lock_info->lock_path); return -1; } if (write_pid) { if (file_write_pid(fd, lock_info->lock_path) < 0) { (void)close(fd); return -1; } } lock_info->fd = fd; return 1; } static int dotlock_create(const char *path, struct dotlock *dotlock, enum dotlock_create_flags flags, bool write_pid) { const struct dotlock_settings *set = &dotlock->settings; const char *lock_path; struct lock_info lock_info; struct stat st; unsigned int stale_notify_threshold; unsigned int change_secs, wait_left; time_t now, max_wait_time, last_notify; int ret; bool do_wait; now = time(NULL); lock_path = t_strconcat(path, set->lock_suffix, NULL); stale_notify_threshold = set->stale_timeout / 2; max_wait_time = (flags & DOTLOCK_CREATE_FLAG_NONBLOCK) != 0 ? 0 : now + set->timeout; memset(&lock_info, 0, sizeof(lock_info)); lock_info.path = path; lock_info.set = set; lock_info.lock_path = lock_path; lock_info.last_change = now; lock_info.fd = -1; last_notify = 0; do_wait = FALSE; do { if (do_wait) { usleep(LOCK_RANDOM_USLEEP_TIME); do_wait = FALSE; } ret = check_lock(now, &lock_info); if (ret < 0) break; if (ret == 1) { if ((flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0) break; ret = set->use_excl_lock ? try_create_lock_excl(&lock_info, write_pid) : try_create_lock_hardlink(&lock_info, write_pid); if (ret != 0) break; } do_wait = TRUE; if (last_notify != now && set->callback != NULL) { last_notify = now; change_secs = now - lock_info.last_change; wait_left = max_wait_time - now; t_push(); if (change_secs >= stale_notify_threshold && change_secs <= wait_left) { unsigned int secs_left = set->stale_timeout < change_secs ? 0 : set->stale_timeout - change_secs; if (!set->callback(secs_left, TRUE, set->context)) { /* we don't want to override */ lock_info.last_change = now; } } else { (void)set->callback(wait_left, FALSE, set->context); } t_pop(); } now = time(NULL); } while (now < max_wait_time); if (ret > 0) { if (fstat(lock_info.fd, &st) < 0) { i_error("fstat(%s) failed: %m", lock_path); ret = -1; } else { /* successful dotlock creation */ dotlock->dev = st.st_dev; dotlock->ino = st.st_ino; dotlock->path = i_strdup(path); dotlock->fd = lock_info.fd; dotlock->lock_time = now; lock_info.fd = -1; } } if (lock_info.fd != -1) { int old_errno = errno; if (close(lock_info.fd) < 0) i_error("close(%s) failed: %m", lock_path); errno = old_errno; } if (ret == 0) errno = EAGAIN; return ret; } static void file_dotlock_free(struct dotlock *dotlock) { int old_errno; if (dotlock->fd != -1) { old_errno = errno; if (close(dotlock->fd) < 0) i_error("close(%s) failed: %m", dotlock->path); dotlock->fd = -1; errno = old_errno; } i_free(dotlock->path); i_free(dotlock); } int file_dotlock_create(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r) { struct dotlock *dotlock; const char *lock_path; struct stat st; int fd, ret; *dotlock_r = NULL; dotlock = file_dotlock_alloc(set); lock_path = t_strconcat(path, dotlock->settings.lock_suffix, NULL); ret = dotlock_create(path, dotlock, flags, TRUE); if (ret <= 0 || (flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0) { i_free(dotlock); return ret; } fd = dotlock->fd; dotlock->fd = -1; if (close(fd) < 0) { i_error("close(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } /* some NFS implementations may have used cached mtime in previous fstat() call. Check again to avoid "dotlock was modified" errors. */ if (stat(lock_path, &st) < 0) { i_error("stat(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } /* extra sanity check won't hurt.. */ if (st.st_dev != dotlock->dev || st.st_ino != dotlock->ino) { i_error("dotlock %s was immediately recreated under us", lock_path); file_dotlock_free(dotlock); return -1; } dotlock->mtime = st.st_mtime; *dotlock_r = dotlock; return 1; } int file_dotlock_delete(struct dotlock **dotlock_p) { struct dotlock *dotlock; const char *lock_path; struct stat st; dotlock = *dotlock_p; *dotlock_p = NULL; lock_path = file_dotlock_get_lock_path(dotlock); if (lstat(lock_path, &st) < 0) { if (errno == ENOENT) { i_warning("Our dotlock file %s was deleted " "(kept it %d secs)", lock_path, (int)(dotlock->lock_time - time(NULL))); file_dotlock_free(dotlock); return 0; } i_error("lstat(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } if (dotlock->ino != st.st_ino || !CMP_DEV_T(dotlock->dev, st.st_dev)) { i_warning("Our dotlock file %s was overridden " "(kept it %d secs)", lock_path, (int)(dotlock->lock_time - time(NULL))); errno = EEXIST; file_dotlock_free(dotlock); return 0; } if (dotlock->mtime != st.st_mtime && dotlock->fd == -1) { i_warning("Our dotlock file %s was modified (%s vs %s), " "assuming it wasn't overridden (kept it %d secs)", lock_path, dec2str(dotlock->mtime), dec2str(st.st_mtime), (int)(dotlock->lock_time - time(NULL))); } if (unlink(lock_path) < 0) { if (errno == ENOENT) { i_warning("Our dotlock file %s was deleted " "(kept it %d secs)", lock_path, (int)(dotlock->lock_time - time(NULL))); file_dotlock_free(dotlock); return 0; } i_error("unlink(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } file_dotlock_free(dotlock); return 1; } int file_dotlock_open(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r) { struct dotlock *dotlock; int ret; dotlock = file_dotlock_alloc(set); ret = dotlock_create(path, dotlock, flags, FALSE); if (ret <= 0) { file_dotlock_free(dotlock); *dotlock_r = NULL; return -1; } *dotlock_r = dotlock; return dotlock->fd; } int file_dotlock_replace(struct dotlock **dotlock_p, enum dotlock_replace_flags flags) { struct dotlock *dotlock; struct stat st, st2; const char *lock_path; int fd; dotlock = *dotlock_p; *dotlock_p = NULL; fd = dotlock->fd; if ((flags & DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) != 0) dotlock->fd = -1; lock_path = file_dotlock_get_lock_path(dotlock); if ((flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) != 0) { if (fstat(fd, &st) < 0) { i_error("fstat(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } if (lstat(lock_path, &st2) < 0) { i_error("lstat(%s) failed: %m", lock_path); file_dotlock_free(dotlock); return -1; } if (st.st_ino != st2.st_ino || !CMP_DEV_T(st.st_dev, st2.st_dev)) { i_warning("Our dotlock file %s was overridden " "(kept it %d secs)", lock_path, (int)(dotlock->lock_time - time(NULL))); errno = EEXIST; file_dotlock_free(dotlock); return 0; } } if (rename(lock_path, dotlock->path) < 0) { i_error("rename(%s, %s) failed: %m", lock_path, dotlock->path); file_dotlock_free(dotlock); return -1; } file_dotlock_free(dotlock); return 1; } const char *file_dotlock_get_lock_path(struct dotlock *dotlock) { return t_strconcat(dotlock->path, dotlock->settings.lock_suffix, NULL); }