file-dotlock.c revision 021665c0147e5c655efb2c3f0eff2549b31f6b5a
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina/* Copyright (c) 2003-2008 Dovecot authors, see the included COPYING file */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "lib.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "ioloop.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "str.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "hex-binary.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "hostpid.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "randgen.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "write-full.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "safe-mkstemp.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "nfs-workarounds.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include "file-dotlock.h"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <stdio.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <stdlib.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <signal.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <time.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <utime.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#include <sys/stat.h>
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#define DEFAULT_LOCK_SUFFIX ".lock"
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina/* 0.1 .. 0.2msec */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina/* If the dotlock is newer than this, don't verify that the PID it contains
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina is valid (since it most likely is). */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#define STALE_PID_CHECK_SECS 2
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina/* Maximum difference between current time and create file's ctime before
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina logging a warning. Should be less than a second in normal operation. */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina#define MAX_TIME_DIFF 30
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březinastruct dotlock {
968e2bc3bb28458b25c3b19868ea50f0325d8b0aPavel Březina struct dotlock_settings settings;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina dev_t dev;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina ino_t ino;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina time_t mtime;
968e2bc3bb28458b25c3b19868ea50f0325d8b0aPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina char *path;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina char *lock_path;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina int fd;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina time_t lock_time;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina};
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březinastruct file_change_info {
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina dev_t dev;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina ino_t ino;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina off_t size;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina time_t ctime, mtime;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina};
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březinastruct lock_info {
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina const struct dotlock_settings *set;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina const char *path, *lock_path, *temp_path;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina int fd;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina struct file_change_info lock_info;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina struct file_change_info file_info;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina time_t last_pid_check;
e6092759dc4268b105b79478aa9b1398f21ed7a9Pavel Březina time_t last_change;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina unsigned int have_pid:1;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina unsigned int pid_read:1;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina unsigned int use_io_notify:1;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina unsigned int lock_stated:1;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina};
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březinastatic struct dotlock *
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březinafile_dotlock_alloc(const struct dotlock_settings *settings)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina{
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina struct dotlock *dotlock;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina dotlock = i_new(struct dotlock, 1);
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina dotlock->settings = *settings;
968e2bc3bb28458b25c3b19868ea50f0325d8b0aPavel Březina if (dotlock->settings.lock_suffix == NULL)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina dotlock->settings.lock_suffix = DEFAULT_LOCK_SUFFIX;
968e2bc3bb28458b25c3b19868ea50f0325d8b0aPavel Březina dotlock->fd = -1;
968e2bc3bb28458b25c3b19868ea50f0325d8b0aPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina return dotlock;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina}
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březinastatic pid_t read_local_pid(const char *lock_path)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina{
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina char buf[512], *host;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina int fd;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina ssize_t ret;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina pid_t pid;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina fd = open(lock_path, O_RDONLY);
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina if (fd == -1)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina return -1; /* ignore the actual error */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina /* read line */
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina ret = read(fd, buf, sizeof(buf)-1);
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina (void)close(fd);
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina if (ret <= 0)
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina return -1;
f8c829e72968b574e1c9bda96f4d5f206622358fPavel Březina
/* 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;
pid = (pid_t)strtoul(buf, NULL, 0);
if (pid <= 0)
return -1;
return pid;
}
static bool
update_change_info(const struct stat *st, struct file_change_info *change,
time_t *last_change_r, time_t now, bool check_ctime)
{
/* ctime is checked only if we're not doing NFS attribute cache
flushes. it changes them. */
if (change->ino != st->st_ino || !CMP_DEV_T(change->dev, st->st_dev) ||
(change->ctime != st->st_ctime && check_ctime) ||
change->mtime != st->st_mtime || change->size != st->st_size) {
time_t change_time = now;
if (change->ctime == 0) {
/* First check, set last_change to file's change time.
Use mtime instead if it's higher, but only if it's
not higher than current time, because the mtime
can also be used for keeping metadata. */
change_time = st->st_mtime <= now &&
(st->st_mtime > st->st_ctime || !check_ctime) ?
st->st_mtime : st->st_ctime;
}
if (*last_change_r < change_time)
*last_change_r = change_time;
change->ino = st->st_ino;
change->dev = st->st_dev;
change->ctime = st->st_ctime;
change->mtime = st->st_mtime;
change->size = st->st_size;
return TRUE;
}
return FALSE;
}
static int update_lock_info(time_t now, struct lock_info *lock_info,
bool *changed_r)
{
struct stat st;
/* don't waste time flushing attribute cache the first time we're here.
if it's stale we'll get back here soon. */
if (lock_info->set->nfs_flush && lock_info->lock_stated) {
nfs_flush_file_handle_cache(lock_info->lock_path);
nfs_flush_attr_cache_unlocked(lock_info->lock_path);
}
lock_info->lock_stated = TRUE;
if (nfs_safe_lstat(lock_info->lock_path, &st) < 0) {
if (errno != ENOENT) {
i_error("lstat(%s) failed: %m", lock_info->lock_path);
return -1;
}
return 1;
}
*changed_r = update_change_info(&st, &lock_info->lock_info,
&lock_info->last_change, now,
!lock_info->set->nfs_flush);
return 0;
}
static int dotlock_override(struct lock_info *lock_info)
{
if (unlink(lock_info->lock_path) < 0 && errno != ENOENT) {
i_error("unlink(%s) failed: %m",
lock_info->lock_path);
return -1;
}
/* make sure we sleep for a while after overriding the lock file.
otherwise another process might try to override it at the same time
and unlink our newly created dotlock. */
if (lock_info->use_io_notify)
usleep(LOCK_RANDOM_USLEEP_TIME);
return 0;
}
static int check_lock(time_t now, struct lock_info *lock_info)
{
time_t stale_timeout = lock_info->set->stale_timeout;
pid_t pid = -1;
bool changed;
int ret;
if ((ret = update_lock_info(now, lock_info, &changed)) != 0)
return ret;
if (changed || !lock_info->pid_read) {
/* either our first check or someone else got the lock file.
if the dotlock was created only a couple of seconds ago,
don't bother to read its PID. */
if (lock_info->lock_info.mtime >= now - STALE_PID_CHECK_SECS)
lock_info->pid_read = FALSE;
else {
pid = read_local_pid(lock_info->lock_path);
lock_info->pid_read = TRUE;
}
lock_info->have_pid = pid != -1;
} else if (!lock_info->have_pid) {
/* no pid checking */
} 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 - now check again if the dotlock was just
deleted or replaced */
if ((ret = update_lock_info(now, lock_info, &changed)) != 0)
return ret;
if (!changed) {
/* still there, go ahead and override it */
return dotlock_override(lock_info);
}
return 1;
}
if (stale_timeout == 0) {
/* no change checking */
return 0;
}
if (now > lock_info->last_change + stale_timeout) {
struct stat st;
/* possibly stale lock file. check also the timestamp of the
file we're protecting. */
if (lock_info->set->nfs_flush) {
nfs_flush_file_handle_cache(lock_info->path);
nfs_flush_attr_cache_maybe_locked(lock_info->path);
}
if (nfs_safe_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 {
(void)update_change_info(&st, &lock_info->file_info,
&lock_info->last_change, now,
!lock_info->set->nfs_flush);
}
}
if (now > lock_info->last_change + stale_timeout) {
/* no changes for a while, assume stale lock */
return dotlock_override(lock_info);
}
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 try_create_lock_hardlink(struct lock_info *lock_info, bool write_pid,
string_t *tmp_path)
{
const char *temp_prefix = lock_info->set->temp_prefix;
const char *p;
if (lock_info->temp_path == NULL) {
/* we'll need our temp file first. */
i_assert(lock_info->fd == -1);
p = strrchr(lock_info->lock_path, '/');
str_truncate(tmp_path, 0);
if (temp_prefix != NULL) {
if (*temp_prefix != '/' && p != NULL) {
/* add directory */
str_append_n(tmp_path, lock_info->lock_path,
p - lock_info->lock_path);
str_append_c(tmp_path, '/');
}
str_append(tmp_path, temp_prefix);
} else {
if (p != NULL) {
/* add directory */
str_append_n(tmp_path, lock_info->lock_path,
p - lock_info->lock_path);
str_append_c(tmp_path, '/');
}
str_printfa(tmp_path, ".temp.%s.%s.",
my_hostname, my_pid);
}
lock_info->fd = safe_mkstemp(tmp_path, 0666,
(uid_t)-1, (gid_t)-1);
if (lock_info->fd == -1)
return -1;
if (write_pid) {
if (file_write_pid(lock_info->fd,
str_c(tmp_path)) < 0) {
(void)close(lock_info->fd);
lock_info->fd = -1;
return -1;
}
}
lock_info->temp_path = str_c(tmp_path);
}
if (nfs_safe_link(lock_info->temp_path,
lock_info->lock_path, TRUE) < 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) {
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;
if (errno != ENOENT)
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 void dotlock_wait_end(struct ioloop *ioloop)
{
io_loop_stop(ioloop);
}
static void dotlock_wait(struct lock_info *lock_info)
{
struct ioloop *ioloop;
struct io *io;
struct timeout *to;
if (!lock_info->use_io_notify) {
usleep(LOCK_RANDOM_USLEEP_TIME);
return;
}
ioloop = io_loop_create();
switch (io_add_notify(lock_info->lock_path, dotlock_wait_end,
ioloop, &io)) {
case IO_NOTIFY_ADDED:
break;
case IO_NOTIFY_NOTFOUND:
/* the lock file doesn't exist anymore, don't sleep */
io_loop_destroy(&ioloop);
return;
case IO_NOTIFY_DISABLED:
/* listening for files not supported */
io_loop_destroy(&ioloop);
lock_info->use_io_notify = FALSE;
usleep(LOCK_RANDOM_USLEEP_TIME);
return;
}
to = timeout_add(LOCK_RANDOM_USLEEP_TIME/1000,
dotlock_wait_end, ioloop);
io_loop_run(ioloop);
io_remove(&io);
timeout_remove(&to);
io_loop_destroy(&ioloop);
}
static int dotlock_create(const char *path, struct dotlock *dotlock,
enum dotlock_create_flags flags, bool write_pid,
const char **lock_path_r)
{
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;
string_t *tmp_path;
int ret;
bool do_wait;
now = time(NULL);
lock_path = *lock_path_r = 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;
tmp_path = t_str_new(256);
memset(&lock_info, 0, sizeof(lock_info));
lock_info.path = path;
lock_info.set = set;
lock_info.lock_path = lock_path;
lock_info.fd = -1;
lock_info.use_io_notify = set->use_io_notify;
last_notify = 0; do_wait = FALSE;
do {
if (do_wait) {
dotlock_wait(&lock_info);
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,
tmp_path);
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;
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);
}
}
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 (st.st_ctime + MAX_TIME_DIFF < now ||
st.st_ctime - MAX_TIME_DIFF > now) {
i_warning("Created dotlock file's timestamp is "
"different than current time "
"(%s vs %s): %s", dec2str(st.st_ctime),
dec2str(now), path);
}
}
}
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 (lock_info.temp_path != NULL) {
if (unlink(lock_info.temp_path) < 0)
i_error("unlink(%s) failed: %m", lock_info.temp_path);
}
if (ret == 0)
errno = EAGAIN;
return ret;
}
static void file_dotlock_free(struct dotlock **_dotlock)
{
struct dotlock *dotlock = *_dotlock;
int old_errno;
*_dotlock = NULL;
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->lock_path);
i_free(dotlock);
}
static int file_dotlock_create_real(struct dotlock *dotlock, const char *path,
enum dotlock_create_flags flags)
{
const char *lock_path;
struct stat st;
int fd, ret;
ret = dotlock_create(path, dotlock, flags, TRUE, &lock_path);
if (ret <= 0 || (flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0)
return ret;
fd = dotlock->fd;
dotlock->fd = -1;
if (close(fd) < 0) {
i_error("close(%s) failed: %m", lock_path);
return -1;
}
/* With NFS the writes may have been flushed only when closing the
file. Get the mtime again after that to avoid "dotlock was modified"
errors. */
if (lstat(lock_path, &st) < 0) {
if (errno != ENOENT)
i_error("stat(%s) failed: %m", lock_path);
else {
i_error("dotlock %s was immediately deleted under us",
lock_path);
}
return -1;
}
/* extra sanity check won't hurt.. */
if (st.st_dev != dotlock->dev || st.st_ino != dotlock->ino) {
errno = ENOENT;
i_error("dotlock %s was immediately recreated under us",
lock_path);
return -1;
}
dotlock->mtime = st.st_mtime;
return 1;
}
int file_dotlock_create(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);
T_FRAME(
ret = file_dotlock_create_real(dotlock, path, flags);
);
if (ret <= 0 || (flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0)
file_dotlock_free(&dotlock);
*dotlock_r = dotlock;
return ret;
}
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 (nfs_safe_lstat(lock_path, &st) < 0) {
if (errno == ENOENT) {
i_warning("Our dotlock file %s was deleted "
"(kept it %d secs)", lock_path,
(int)(time(NULL) - dotlock->lock_time));
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)(time(NULL) - dotlock->lock_time));
}
if (unlink(lock_path) < 0) {
if (errno == ENOENT) {
i_warning("Our dotlock file %s was deleted "
"(kept it %d secs)", lock_path,
(int)(time(NULL) - dotlock->lock_time));
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);
T_FRAME(
const char *lock_path;
ret = dotlock_create(path, dotlock, flags, FALSE, &lock_path);
);
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;
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 &&
!file_dotlock_is_locked(dotlock)) {
i_warning("Our dotlock file %s was overridden "
"(kept it %d secs)", lock_path,
(int)(time(NULL) - dotlock->lock_time));
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;
}
int file_dotlock_touch(struct dotlock *dotlock)
{
time_t now = time(NULL);
struct utimbuf buf;
int ret = 0;
if (dotlock->mtime == now)
return 0;
dotlock->mtime = now;
buf.actime = buf.modtime = now;
T_FRAME(
const char *lock_path = file_dotlock_get_lock_path(dotlock);
if (utime(lock_path, &buf) < 0) {
i_error("utime(%s) failed: %m", lock_path);
ret = -1;
}
);
return ret;
}
bool file_dotlock_is_locked(struct dotlock *dotlock)
{
struct stat st, st2;
const char *lock_path;
lock_path = file_dotlock_get_lock_path(dotlock);
if (fstat(dotlock->fd, &st) < 0) {
i_error("fstat(%s) failed: %m", lock_path);
return FALSE;
}
if (nfs_safe_lstat(lock_path, &st2) < 0) {
i_error("lstat(%s) failed: %m", lock_path);
return FALSE;
}
return st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev);
}
const char *file_dotlock_get_lock_path(struct dotlock *dotlock)
{
if (dotlock->lock_path == NULL) {
dotlock->lock_path =
i_strconcat(dotlock->path,
dotlock->settings.lock_suffix, NULL);
}
return dotlock->lock_path;
}