mbox-lock.c revision 0d16525a729011f4fced989a3da74d755ea49e6d
1057N/A/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
1057N/A
1057N/A#include "lib.h"
1057N/A#include "restrict-access.h"
1589N/A#include "nfs-workarounds.h"
1057N/A#include "mail-index-private.h"
660N/A#include "mbox-storage.h"
1057N/A#include "mbox-file.h"
1057N/A#include "mbox-lock.h"
1057N/A
1057N/A#include <time.h>
1057N/A#include <stdlib.h>
1057N/A#include <unistd.h>
1057N/A#include <fcntl.h>
1057N/A#include <sys/stat.h>
1057N/A
1057N/A#ifdef HAVE_FLOCK
1057N/A# include <sys/file.h>
1057N/A#endif
1057N/A
660N/A/* 0.1 .. 0.2msec */
2679N/A#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
1109N/A
2506N/A/* lock methods to use in wanted order */
2691N/A#define DEFAULT_READ_LOCK_METHODS "fcntl"
2557N/A#define DEFAULT_WRITE_LOCK_METHODS "dotlock fcntl"
2557N/A/* lock timeout */
2437N/A#define MBOX_DEFAULT_LOCK_TIMEOUT (5*60)
2437N/A/* assume stale dotlock if mbox file hasn't changed for n seconds */
2437N/A#define DEFAULT_DOTLOCK_CHANGE_TIMEOUT (120)
2437N/A
2506N/Aenum mbox_lock_type {
2506N/A MBOX_LOCK_DOTLOCK,
2557N/A MBOX_LOCK_DOTLOCK_TRY,
2557N/A MBOX_LOCK_FCNTL,
2557N/A MBOX_LOCK_FLOCK,
2557N/A MBOX_LOCK_LOCKF,
2557N/A
2557N/A MBOX_LOCK_COUNT
2557N/A};
2557N/A
2557N/Aenum mbox_dotlock_op {
2557N/A MBOX_DOTLOCK_OP_LOCK,
2557N/A MBOX_DOTLOCK_OP_UNLOCK,
2557N/A MBOX_DOTLOCK_OP_TOUCH
2557N/A};
2557N/A
2437N/Astruct mbox_lock_context {
2557N/A struct mbox_mailbox *mbox;
2557N/A int lock_status[MBOX_LOCK_COUNT];
2557N/A bool checked_file;
2557N/A
2557N/A int lock_type;
2557N/A bool dotlock_last_stale;
2557N/A bool fcntl_locked;
2557N/A bool using_privileges;
2557N/A};
2557N/A
2557N/Astruct mbox_lock_data {
2557N/A enum mbox_lock_type type;
2437N/A const char *name;
2437N/A int (*func)(struct mbox_lock_context *ctx, int lock_type,
2437N/A time_t max_wait_time);
2506N/A};
2506N/A
2557N/Astatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
2095N/A time_t max_wait_time);
2095N/Astatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
2095N/A time_t max_wait_time);
2557N/Astatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
2437N/A time_t max_wait_time);
2437N/A#ifdef HAVE_FLOCK
2437N/Astatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
2557N/A time_t max_wait_time);
2437N/A#else
2437N/A# define mbox_lock_flock NULL
2437N/A#endif
2437N/A#ifdef HAVE_LOCKF
2506N/Astatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
2506N/A time_t max_wait_time);
2557N/A#else
2095N/A# define mbox_lock_lockf NULL
2095N/A#endif
2095N/A
2557N/Astatic struct mbox_lock_data lock_data[] = {
2095N/A { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock },
2095N/A { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try },
2095N/A { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl },
2557N/A { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock },
2095N/A { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf },
2095N/A { 0, NULL, NULL }
2095N/A};
2557N/A
2557N/Astatic bool lock_settings_initialized = FALSE;
2557N/Astatic enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
2557N/Astatic enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
2557N/Astatic int lock_timeout, dotlock_change_timeout;
2557N/A
2557N/Astatic int mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
2557N/A time_t max_wait_time, int idx);
2557N/Astatic int mbox_unlock_files(struct mbox_lock_context *ctx);
2095N/A
2095N/Astatic void mbox_read_lock_methods(const char *str, const char *env,
2095N/A enum mbox_lock_type *locks)
2095N/A{
2095N/A enum mbox_lock_type type;
2437N/A const char *const *lock;
2506N/A int i, dest;
2506N/A
2506N/A for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) {
2557N/A for (type = 0; lock_data[type].name != NULL; type++) {
2557N/A if (strcasecmp(*lock, lock_data[type].name) == 0) {
2557N/A type = lock_data[type].type;
2557N/A break;
2557N/A }
2557N/A }
2557N/A if (lock_data[type].name == NULL)
2557N/A i_fatal("%s: Invalid value %s", env, *lock);
2557N/A if (lock_data[type].func == NULL) {
2557N/A i_fatal("%s: Support for lock type %s "
2557N/A "not compiled into binary", env, *lock);
2557N/A }
2557N/A
2557N/A for (i = 0; i < dest; i++) {
2557N/A if (locks[i] == type)
2557N/A i_fatal("%s: Duplicated value %s", env, *lock);
2557N/A }
2557N/A
2557N/A /* @UNSAFE */
2506N/A locks[dest++] = type;
2506N/A }
2506N/A locks[dest] = (enum mbox_lock_type)-1;
2557N/A}
2557N/A
2557N/Astatic void mbox_init_lock_settings(void)
2557N/A{
2557N/A const char *str;
2506N/A int r, w;
2506N/A
2557N/A str = getenv("MBOX_READ_LOCKS");
2557N/A if (str == NULL) str = DEFAULT_READ_LOCK_METHODS;
2557N/A mbox_read_lock_methods(str, "MBOX_READ_LOCKS", read_locks);
2557N/A
2557N/A str = getenv("MBOX_WRITE_LOCKS");
2557N/A if (str == NULL) str = DEFAULT_WRITE_LOCK_METHODS;
2557N/A mbox_read_lock_methods(str, "MBOX_WRITE_LOCKS", write_locks);
2557N/A
2557N/A /* check that read/write list orders match. write_locks must contain
2557N/A at least read_locks and possibly more. */
2557N/A for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) {
2095N/A if (read_locks[r] == (enum mbox_lock_type)-1)
2095N/A break;
2095N/A if (read_locks[r] == write_locks[w])
2557N/A r++;
2437N/A }
2437N/A if (read_locks[r] != (enum mbox_lock_type)-1) {
2437N/A i_fatal("mbox read/write lock list settings are invalid. "
2557N/A "Lock ordering must be the same with both, "
2506N/A "and write locks must contain all read locks "
2506N/A "(and possibly more)");
2506N/A }
2557N/A
2506N/A str = getenv("MBOX_LOCK_TIMEOUT");
2506N/A lock_timeout = str == NULL ? MBOX_DEFAULT_LOCK_TIMEOUT : atoi(str);
2506N/A
2557N/A str = getenv("MBOX_DOTLOCK_CHANGE_TIMEOUT");
2095N/A dotlock_change_timeout = str == NULL ?
2095N/A DEFAULT_DOTLOCK_CHANGE_TIMEOUT : atoi(str);
2095N/A
2557N/A lock_settings_initialized = TRUE;
2095N/A}
2095N/A
2095N/Astatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
2557N/A{
2095N/A struct mbox_mailbox *mbox = ctx->mbox;
2095N/A struct stat st;
2095N/A
2557N/A if (ctx->checked_file || lock_type == F_UNLCK)
2095N/A return 0;
2095N/A
2095N/A if (mbox->mbox_fd != -1) {
2095N/A /* we could flush NFS file handle cache here if we wanted to
2095N/A be sure that the file is latest, but mbox files get rarely
2095N/A deleted and the flushing might cause errors (e.g. EBUSY for
2095N/A trying to flush a /var/mail mountpoint) */
2095N/A if (nfs_safe_stat(mbox->path, &st) < 0) {
2095N/A mbox_set_syscall_error(mbox, "stat()");
2095N/A return -1;
2506N/A }
2095N/A
2095N/A if (st.st_ino != mbox->mbox_ino ||
2095N/A !CMP_DEV_T(st.st_dev, mbox->mbox_dev))
2095N/A mbox_file_close(mbox);
2095N/A }
2095N/A
2095N/A if (mbox->mbox_fd == -1) {
2095N/A if (mbox_file_open(mbox) < 0)
2506N/A return -1;
2506N/A }
2557N/A
2095N/A ctx->checked_file = TRUE;
2095N/A return 0;
2095N/A}
2557N/A
2543N/Astatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
2543N/A{
2543N/A struct mbox_lock_context *ctx = context;
2557N/A enum mbox_lock_type *lock_types;
2437N/A int i;
2437N/A
2437N/A if (ctx->using_privileges)
2557N/A restrict_access_drop_priv_gid();
2437N/A
2437N/A if (stale && !ctx->dotlock_last_stale) {
2437N/A /* get next index we wish to try locking. it's the one after
2557N/A dotlocking. */
2543N/A lock_types = ctx->lock_type == F_WRLCK ||
2543N/A (ctx->lock_type == F_UNLCK &&
2543N/A ctx->mbox->mbox_lock_type == F_WRLCK) ?
2437N/A write_locks : read_locks;
2437N/A
2437N/A for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
2543N/A if (lock_types[i] == MBOX_LOCK_DOTLOCK)
2543N/A break;
2557N/A }
2437N/A
2437N/A if (lock_types[i] != (enum mbox_lock_type)-1 &&
2437N/A lock_types[i+1] != (enum mbox_lock_type)-1) {
2557N/A i++;
2543N/A if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) {
2543N/A /* we couldn't get fd lock -
2543N/A it's really locked */
2557N/A ctx->dotlock_last_stale = TRUE;
2437N/A return FALSE;
2437N/A }
2437N/A (void)mbox_lock_list(ctx, F_UNLCK, 0, i);
2557N/A }
2543N/A }
2543N/A ctx->dotlock_last_stale = stale;
2543N/A
2437N/A index_storage_lock_notify(&ctx->mbox->ibox, stale ?
2543N/A MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
2543N/A MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
2557N/A secs_left);
2437N/A if (ctx->using_privileges) {
2437N/A if (restrict_access_use_priv_gid() < 0) {
2437N/A /* shouldn't get here */
2437N/A return FALSE;
2506N/A }
2095N/A }
2557N/A return TRUE;
2557N/A}
2557N/A
2557N/Astatic int mbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
2557N/A struct dotlock_settings *set,
2557N/A enum mbox_dotlock_op op)
2557N/A{
2557N/A const char *dir, *fname;
1589N/A int ret = -1, orig_dir_fd;
2557N/A
1589N/A orig_dir_fd = open(".", O_RDONLY);
1741N/A if (orig_dir_fd == -1) {
1589N/A mail_storage_set_critical(&mbox->storage->storage,
2557N/A "open(.) failed: %m");
684N/A return -1;
684N/A }
684N/A
2506N/A /* allow dotlocks to be created only for files we can read while we're
2506N/A unprivileged. to make sure there are no race conditions we first
2557N/A have to chdir to the mbox file's directory and then use relative
2095N/A paths. unless this is done, users could:
2095N/A - create *.lock files to any directory writable by the
2095N/A privileged group
2557N/A - DoS other users by dotlocking their mailboxes infinitely
2095N/A */
684N/A fname = strrchr(mbox->path, '/');
2095N/A if (fname == NULL) {
2506N/A /* already relative */
2506N/A fname = mbox->path;
2557N/A } else {
2095N/A dir = t_strdup_until(mbox->path, fname);
684N/A if (chdir(dir) < 0) {
684N/A mail_storage_set_critical(&mbox->storage->storage,
684N/A "chdir(%s) failed: %m", dir);
1057N/A (void)close(orig_dir_fd);
684N/A return -1;
684N/A }
684N/A fname++;
2506N/A }
2506N/A if (op == MBOX_DOTLOCK_OP_LOCK) {
2679N/A if (access(fname, R_OK) < 0) {
684N/A mail_storage_set_critical(&mbox->storage->storage,
684N/A "access(%s) failed: %m", mbox->path);
684N/A return -1;
2557N/A }
2095N/A }
2095N/A
2095N/A if (restrict_access_use_priv_gid() < 0) {
2557N/A (void)close(orig_dir_fd);
2095N/A return -1;
2095N/A }
2095N/A
2095N/A switch (op) {
2557N/A case MBOX_DOTLOCK_OP_LOCK:
2095N/A /* we're now privileged - avoid doing as much as possible */
2095N/A ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
2437N/A if (ret > 0)
2437N/A mbox->mbox_used_privileges = TRUE;
2557N/A break;
2506N/A case MBOX_DOTLOCK_OP_UNLOCK:
2506N/A /* we're now privileged - avoid doing as much as possible */
2506N/A ret = file_dotlock_delete(&mbox->mbox_dotlock);
2095N/A mbox->mbox_used_privileges = FALSE;
2506N/A break;
2506N/A case MBOX_DOTLOCK_OP_TOUCH:
2557N/A if (!file_dotlock_is_locked(mbox->mbox_dotlock)) {
2095N/A file_dotlock_delete(&mbox->mbox_dotlock);
2095N/A mbox->mbox_used_privileges = TRUE;
2095N/A ret = -1;
2506N/A } else {
2506N/A ret = file_dotlock_touch(mbox->mbox_dotlock);
2557N/A }
2095N/A break;
2095N/A }
2437N/A
2437N/A restrict_access_drop_priv_gid();
2557N/A
2095N/A if (fchdir(orig_dir_fd) < 0) {
2095N/A mail_storage_set_critical(&mbox->storage->storage,
2095N/A "fchdir() failed: %m");
2095N/A }
2557N/A (void)close(orig_dir_fd);
2095N/A return ret;
2095N/A}
2095N/A
2557N/Astatic int
1057N/Ambox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
1057N/A{
1057N/A struct mbox_mailbox *mbox = ctx->mbox;
1057N/A struct dotlock_settings set;
2557N/A int ret;
1703N/A
1703N/A if (lock_type == F_UNLCK) {
1703N/A if (!mbox->mbox_dotlocked)
2506N/A return 1;
2506N/A
2679N/A if (!mbox->mbox_used_privileges)
1703N/A ret = file_dotlock_delete(&mbox->mbox_dotlock);
1703N/A else {
1703N/A ctx->using_privileges = TRUE;
1703N/A ret = mbox_dotlock_privileged_op(mbox, NULL,
2506N/A MBOX_DOTLOCK_OP_UNLOCK);
2506N/A ctx->using_privileges = FALSE;
2557N/A }
1589N/A if (ret <= 0) {
1589N/A mbox_set_syscall_error(mbox, "file_dotlock_delete()");
1589N/A ret = -1;
1589N/A }
2506N/A mbox->mbox_dotlocked = FALSE;
2506N/A return 1;
2679N/A }
1589N/A
1589N/A if (mbox->mbox_dotlocked)
1589N/A return 1;
2557N/A
2095N/A ctx->dotlock_last_stale = -1;
2095N/A
2095N/A memset(&set, 0, sizeof(set));
2557N/A set.use_excl_lock = (mbox->storage->storage.flags &
2095N/A MAIL_STORAGE_FLAG_DOTLOCK_USE_EXCL) != 0;
2095N/A set.nfs_flush = (mbox->storage->storage.flags &
2095N/A MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0;
2557N/A set.timeout = lock_timeout;
2506N/A set.stale_timeout = dotlock_change_timeout;
2506N/A set.callback = dotlock_callback;
2506N/A set.context = ctx;
2506N/A
2557N/A ret = file_dotlock_create(&set, mbox->path, 0, &mbox->mbox_dotlock);
2095N/A if (ret < 0 && errno == EACCES && restrict_access_have_priv_gid() &&
2095N/A mbox->mbox_privileged_locking) {
2095N/A /* try again, this time with extra privileges */
2679N/A ret = mbox_dotlock_privileged_op(mbox, &set,
2679N/A MBOX_DOTLOCK_OP_LOCK);
2095N/A }
2095N/A
2095N/A if (ret < 0) {
2557N/A if ((ENOSPACE(errno) || errno == EACCES) && try)
2506N/A return 1;
2506N/A
2506N/A mbox_set_syscall_error(mbox, "file_lock_dotlock()");
2506N/A return -1;
2506N/A }
2557N/A if (ret == 0) {
2506N/A mail_storage_set_error(&mbox->storage->storage,
2506N/A MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
2506N/A return 0;
2506N/A }
2506N/A mbox->mbox_dotlocked = TRUE;
2557N/A
679N/A if (mbox_file_open_latest(ctx, lock_type) < 0)
679N/A return -1;
684N/A return 1;
684N/A}
2506N/A
2506N/Astatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
2679N/A time_t max_wait_time ATTR_UNUSED)
2557N/A{
2557N/A return mbox_lock_dotlock_int(ctx, lock_type, FALSE);
2557N/A}
2557N/A
2557N/Astatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
2557N/A time_t max_wait_time ATTR_UNUSED)
2679N/A{
2557N/A return mbox_lock_dotlock_int(ctx, lock_type, TRUE);
2557N/A}
2557N/A
2557N/A#ifdef HAVE_FLOCK
2557N/Astatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
2557N/A time_t max_wait_time)
2691N/A{
2095N/A time_t now, last_notify;
2095N/A
2095N/A if (mbox_file_open_latest(ctx, lock_type) < 0)
2095N/A return -1;
2691N/A
2557N/A if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
2095N/A return 1;
2095N/A
2095N/A if (lock_type == F_WRLCK)
2679N/A lock_type = LOCK_EX;
2506N/A else if (lock_type == F_RDLCK)
2506N/A lock_type = LOCK_SH;
2506N/A else
2557N/A lock_type = LOCK_UN;
2679N/A
2679N/A last_notify = 0;
2506N/A while (flock(ctx->mbox->mbox_fd, lock_type | LOCK_NB) < 0) {
2557N/A if (errno != EWOULDBLOCK) {
2679N/A mbox_set_syscall_error(ctx->mbox, "flock()");
2679N/A return -1;
2679N/A }
2679N/A
2506N/A now = time(NULL);
2506N/A if (now >= max_wait_time)
2506N/A return 0;
2679N/A
2506N/A if (now != last_notify) {
2506N/A index_storage_lock_notify(&ctx->mbox->ibox,
2506N/A MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
2679N/A max_wait_time - now);
2506N/A }
2506N/A
2506N/A usleep(LOCK_RANDOM_USLEEP_TIME);
2679N/A }
2506N/A
2506N/A return 1;
2506N/A}
2679N/A#endif
2506N/A
2506N/A#ifdef HAVE_LOCKF
2506N/Astatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
2679N/A time_t max_wait_time)
2506N/A{
2506N/A time_t now, last_notify;
2506N/A
2679N/A if (mbox_file_open_latest(ctx, lock_type) < 0)
2506N/A return -1;
2506N/A
2095N/A if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
2437N/A return 1;
2437N/A
2679N/A if (lock_type != F_UNLCK)
2506N/A lock_type = F_TLOCK;
2506N/A else
2506N/A lock_type = F_ULOCK;
2679N/A
2557N/A last_notify = 0;
2557N/A while (lockf(ctx->mbox->mbox_fd, lock_type, 0) < 0) {
2506N/A if (errno != EAGAIN) {
2679N/A mbox_set_syscall_error(ctx->mbox, "lockf()");
2557N/A return -1;
2557N/A }
2557N/A
2679N/A now = time(NULL);
2557N/A if (now >= max_wait_time)
2557N/A return 0;
2557N/A
2679N/A if (now != last_notify) {
2557N/A index_storage_lock_notify(&ctx->mbox->ibox,
2557N/A MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
2557N/A max_wait_time - now);
2679N/A }
2095N/A
2095N/A usleep(LOCK_RANDOM_USLEEP_TIME);
2095N/A }
2679N/A
2437N/A return 1;
2437N/A}
2437N/A#endif
2679N/A
2506N/Astatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
2506N/A time_t max_wait_time)
2506N/A{
2679N/A struct flock fl;
2095N/A time_t now;
2095N/A unsigned int next_alarm;
2095N/A int wait_type;
2679N/A
1703N/A if (mbox_file_open_latest(ctx, lock_type) < 0)
1703N/A return -1;
1703N/A
1703N/A if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
2679N/A return 1;
2679N/A
1703N/A memset(&fl, 0, sizeof(fl));
1703N/A fl.l_type = lock_type;
1703N/A fl.l_whence = SEEK_SET;
1703N/A fl.l_start = 0;
2679N/A fl.l_len = 0;
2437N/A
2437N/A if (max_wait_time == 0) {
2437N/A /* usually we're waiting here, but if we came from
2679N/A mbox_lock_dotlock(), we just want to try locking */
2437N/A wait_type = F_SETLK;
2437N/A } else {
2437N/A wait_type = F_SETLKW;
2679N/A now = time(NULL);
1703N/A if (now >= max_wait_time)
1703N/A alarm(1);
1703N/A else
2506N/A alarm(I_MIN(max_wait_time - now, 5));
2506N/A }
2679N/A
684N/A while (fcntl(ctx->mbox->mbox_fd, wait_type, &fl) < 0) {
684N/A if (errno != EINTR) {
684N/A if ((errno == EACCES || errno == EAGAIN) &&
1589N/A wait_type == F_SETLK) {
2506N/A /* non-blocking lock trying failed */
2506N/A return 0;
2679N/A }
1589N/A alarm(0);
1589N/A if (errno != EACCES) {
1589N/A mbox_set_syscall_error(ctx->mbox, "fcntl()");
1589N/A return -1;
2679N/A }
2437N/A mail_storage_set_critical(&ctx->mbox->storage->storage,
2437N/A "fcntl() failed with mbox file %s: "
2437N/A "File is locked by another process (EACCES)",
2437N/A ctx->mbox->path);
2506N/A return -1;
2506N/A }
2679N/A
1589N/A now = time(NULL);
1589N/A if (now >= max_wait_time) {
2437N/A alarm(0);
2437N/A return 0;
2679N/A }
1589N/A
1589N/A /* notify locks once every 5 seconds.
1589N/A try to use rounded values. */
1589N/A next_alarm = (max_wait_time - now) % 5;
1589N/A if (next_alarm == 0)
2506N/A next_alarm = 5;
2506N/A alarm(next_alarm);
2691N/A
2437N/A index_storage_lock_notify(&ctx->mbox->ibox,
2437N/A MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
2437N/A max_wait_time - now);
2437N/A }
2506N/A
2506N/A alarm(0);
2679N/A ctx->fcntl_locked = TRUE;
1589N/A return 1;
1589N/A}
2095N/A
2095N/Astatic int mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
2679N/A time_t max_wait_time, int idx)
2437N/A{
2437N/A enum mbox_lock_type *lock_types;
2437N/A enum mbox_lock_type type;
2506N/A int i, ret = 0, lock_status;
2506N/A
2506N/A ctx->lock_type = lock_type;
2679N/A
2437N/A lock_types = lock_type == F_WRLCK ||
2437N/A (lock_type == F_UNLCK && ctx->mbox->mbox_lock_type == F_WRLCK) ?
2437N/A write_locks : read_locks;
2437N/A for (i = idx; lock_types[i] != (enum mbox_lock_type)-1; i++) {
2679N/A type = lock_types[i];
1589N/A lock_status = lock_type != F_UNLCK;
1589N/A
2437N/A if (ctx->lock_status[type] == lock_status)
2437N/A continue;
2679N/A ctx->lock_status[type] = lock_status;
2437N/A
2437N/A ret = lock_data[type].func(ctx, lock_type, max_wait_time);
2437N/A if (ret <= 0)
2679N/A break;
1589N/A }
1589N/A return ret;
1589N/A}
1589N/A
2506N/Astatic int mbox_update_locking(struct mbox_mailbox *mbox, int lock_type,
2506N/A bool *fcntl_locked_r)
2679N/A{
684N/A struct mbox_lock_context ctx;
684N/A time_t max_wait_time;
1589N/A int ret, i;
2437N/A bool drop_locks;
2437N/A
2506N/A *fcntl_locked_r = FALSE;
2506N/A
2679N/A index_storage_lock_notify_reset(&mbox->ibox);
1589N/A
1589N/A if (!lock_settings_initialized)
2437N/A mbox_init_lock_settings();
2437N/A
2679N/A if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) {
2437N/A /* read-only mbox stream. no need to lock. */
2437N/A i_assert(mbox->mbox_readonly);
2437N/A mbox->mbox_lock_type = lock_type;
2679N/A return 1;
2437N/A }
2437N/A
2506N/A max_wait_time = time(NULL) + lock_timeout;
2506N/A
2679N/A memset(&ctx, 0, sizeof(ctx));
2437N/A ctx.mbox = mbox;
2437N/A
2437N/A if (mbox->mbox_lock_type == F_WRLCK) {
2679N/A /* dropping to shared lock. first drop those that we
2437N/A don't remove completely. */
2437N/A for (i = 0; i < MBOX_LOCK_COUNT; i++)
2437N/A ctx.lock_status[i] = 1;
2437N/A for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
2679N/A ctx.lock_status[read_locks[i]] = 0;
2437N/A drop_locks = TRUE;
2437N/A } else {
2437N/A drop_locks = FALSE;
2437N/A }
2679N/A
2437N/A mbox->mbox_lock_type = lock_type;
2437N/A ret = mbox_lock_list(&ctx, lock_type, max_wait_time, 0);
2437N/A if (ret <= 0) {
2437N/A if (!drop_locks)
2679N/A (void)mbox_unlock_files(&ctx);
2543N/A if (ret == 0) {
2543N/A mail_storage_set_error(&mbox->storage->storage,
2543N/A MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
2679N/A }
2437N/A return ret;
2437N/A }
2437N/A
2437N/A if (drop_locks) {
2679N/A /* dropping to shared lock: drop the locks that are only
2437N/A in write list */
2437N/A memset(ctx.lock_status, 0, sizeof(ctx.lock_status));
2437N/A for (i = 0; write_locks[i] != (enum mbox_lock_type)-1; i++)
2679N/A ctx.lock_status[write_locks[i]] = 1;
1589N/A for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
1589N/A ctx.lock_status[read_locks[i]] = 0;
2437N/A
2437N/A mbox->mbox_lock_type = F_WRLCK;
2679N/A (void)mbox_lock_list(&ctx, F_UNLCK, 0, 0);
1589N/A mbox->mbox_lock_type = F_RDLCK;
1589N/A }
2437N/A
2437N/A *fcntl_locked_r = ctx.fcntl_locked;
2679N/A return 1;
1589N/A}
1589N/A
2437N/Aint mbox_lock(struct mbox_mailbox *mbox, int lock_type,
2437N/A unsigned int *lock_id_r)
2679N/A{
1703N/A bool fcntl_locked;
1703N/A int ret;
2437N/A
2437N/A /* allow only unlock -> shared/exclusive or exclusive -> shared */
2679N/A i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
1703N/A i_assert(lock_type == F_RDLCK || mbox->mbox_lock_type != F_RDLCK);
1703N/A
2437N/A /* mbox must be locked before index */
2437N/A i_assert(mbox->ibox.index->lock_type != F_WRLCK);
2679N/A
1703N/A if (mbox->mbox_lock_type == F_UNLCK) {
1703N/A ret = mbox_update_locking(mbox, lock_type, &fcntl_locked);
2437N/A if (ret <= 0)
2437N/A return ret;
2679N/A
1589N/A if ((mbox->storage->storage.flags &
1741N/A MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
1589N/A if (fcntl_locked) {
2679N/A nfs_flush_attr_cache_fd_locked(mbox->path,
2557N/A mbox->mbox_fd);
2557N/A nfs_flush_read_cache_locked(mbox->path,
2557N/A mbox->mbox_fd);
2679N/A } else {
2506N/A nfs_flush_attr_cache_unlocked(mbox->path);
2506N/A nfs_flush_read_cache_unlocked(mbox->path,
2506N/A mbox->mbox_fd);
2679N/A }
2506N/A }
2506N/A
2506N/A mbox->mbox_lock_id += 2;
2679N/A }
2691N/A
2506N/A if (lock_type == F_RDLCK) {
2506N/A mbox->mbox_shared_locks++;
2506N/A *lock_id_r = mbox->mbox_lock_id;
2679N/A } else {
2506N/A mbox->mbox_excl_locks++;
2506N/A *lock_id_r = mbox->mbox_lock_id + 1;
2506N/A }
2679N/A return 1;
2506N/A}
2506N/A
2506N/Astatic int mbox_unlock_files(struct mbox_lock_context *ctx)
2679N/A{
2506N/A int ret = 0;
2506N/A
2506N/A if (mbox_lock_list(ctx, F_UNLCK, 0, 0) < 0)
2506N/A ret = -1;
2679N/A
2506N/A ctx->mbox->mbox_lock_id += 2;
2506N/A ctx->mbox->mbox_lock_type = F_UNLCK;
2506N/A return ret;
2679N/A}
2506N/A
2506N/Aint mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id)
2506N/A{
2679N/A struct mbox_lock_context ctx;
2506N/A bool fcntl_locked;
2506N/A int i;
2506N/A
2506N/A i_assert(mbox->mbox_lock_id == (lock_id & ~1));
2679N/A
2506N/A if (lock_id & 1) {
2506N/A /* dropping exclusive lock */
2506N/A i_assert(mbox->mbox_excl_locks > 0);
2679N/A if (--mbox->mbox_excl_locks > 0)
2437N/A return 0;
2437N/A if (mbox->mbox_shared_locks > 0) {
2437N/A /* drop to shared lock */
2437N/A if (mbox_update_locking(mbox, F_RDLCK,
2679N/A &fcntl_locked) < 0)
2506N/A return -1;
2506N/A return 0;
2506N/A }
2679N/A } else {
2557N/A /* dropping shared lock */
2557N/A i_assert(mbox->mbox_shared_locks > 0);
2557N/A if (--mbox->mbox_shared_locks > 0)
2679N/A return 0;
2506N/A if (mbox->mbox_excl_locks > 0)
2506N/A return 0;
2506N/A }
2506N/A /* all locks gone */
2679N/A
2506N/A memset(&ctx, 0, sizeof(ctx));
2506N/A ctx.mbox = mbox;
2506N/A
2506N/A for (i = 0; i < MBOX_LOCK_COUNT; i++)
2691N/A ctx.lock_status[i] = 1;
2679N/A
2506N/A return mbox_unlock_files(&ctx);
2506N/A}
2506N/A
2679N/Avoid mbox_dotlock_touch(struct mbox_mailbox *mbox)
2506N/A{
2506N/A if (mbox->mbox_dotlock == NULL)
2506N/A return;
2679N/A
2506N/A if (!mbox->mbox_used_privileges)
2506N/A (void)file_dotlock_touch(mbox->mbox_dotlock);
2506N/A else {
2679N/A (void)mbox_dotlock_privileged_op(mbox, NULL,
2095N/A MBOX_DOTLOCK_OP_TOUCH);
2095N/A }
2095N/A}
1109N/A