diff options
Diffstat (limited to 'src/lib-storage/index/mbox/mbox-lock.c')
-rw-r--r-- | src/lib-storage/index/mbox/mbox-lock.c | 900 |
1 files changed, 900 insertions, 0 deletions
diff --git a/src/lib-storage/index/mbox/mbox-lock.c b/src/lib-storage/index/mbox/mbox-lock.c new file mode 100644 index 0000000..cebff48 --- /dev/null +++ b/src/lib-storage/index/mbox/mbox-lock.c @@ -0,0 +1,900 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "eacces-error.h" +#include "restrict-access.h" +#include "nfs-workarounds.h" +#include "ipwd.h" +#include "mail-index-private.h" +#include "mbox-storage.h" +#include "istream-raw-mbox.h" +#include "mbox-file.h" +#include "mbox-lock.h" + +#include <time.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#ifdef HAVE_FLOCK +# include <sys/file.h> +#endif + +/* 0.1 .. 0.2msec */ +#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)i_rand() % 100000) + +enum mbox_lock_type { + MBOX_LOCK_DOTLOCK, + MBOX_LOCK_DOTLOCK_TRY, + MBOX_LOCK_FCNTL, + MBOX_LOCK_FLOCK, + MBOX_LOCK_LOCKF, + + MBOX_LOCK_COUNT +}; + +enum mbox_dotlock_op { + MBOX_DOTLOCK_OP_LOCK, + MBOX_DOTLOCK_OP_UNLOCK, + MBOX_DOTLOCK_OP_TOUCH +}; + +struct mbox_lock_context { + struct mbox_mailbox *mbox; + bool locked_status[MBOX_LOCK_COUNT]; + bool checked_file; + + int lock_type; + bool dotlock_last_stale; + bool fcntl_locked; + bool using_privileges; +}; + +struct mbox_lock_data { + enum mbox_lock_type type; + const char *name; + int (*func)(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +}; + +static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +static int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +static int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +#ifdef HAVE_FLOCK +static int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +#else +# define mbox_lock_flock NULL +#endif +#ifdef HAVE_LOCKF +static int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time); +#else +# define mbox_lock_lockf NULL +#endif + +static struct mbox_lock_data lock_data[] = { + { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock }, + { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try }, + { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl }, + { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock }, + { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf }, + { 0, NULL, NULL } +}; + +static int ATTR_NOWARN_UNUSED_RESULT +mbox_lock_list(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time, int idx); +static int ATTR_NOWARN_UNUSED_RESULT +mbox_unlock_files(struct mbox_lock_context *ctx); + +static void mbox_read_lock_methods(const char *str, const char *env, + enum mbox_lock_type *locks) +{ + enum mbox_lock_type type; + const char *const *lock; + int i, dest; + + for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) { + for (type = 0; lock_data[type].name != NULL; type++) { + if (strcasecmp(*lock, lock_data[type].name) == 0) { + type = lock_data[type].type; + break; + } + } + if (lock_data[type].name == NULL) + i_fatal("%s: Invalid value %s", env, *lock); + if (lock_data[type].func == NULL) { + i_fatal("%s: Support for lock type %s " + "not compiled into binary", env, *lock); + } + + for (i = 0; i < dest; i++) { + if (locks[i] == type) + i_fatal("%s: Duplicated value %s", env, *lock); + } + + /* @UNSAFE */ + locks[dest++] = type; + } + locks[dest] = (enum mbox_lock_type)-1; +} + +static void mbox_init_lock_settings(struct mbox_storage *storage) +{ + enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1]; + enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1]; + int r, w; + + mbox_read_lock_methods(storage->set->mbox_read_locks, + "mbox_read_locks", read_locks); + mbox_read_lock_methods(storage->set->mbox_write_locks, + "mbox_write_locks", write_locks); + + /* check that read/write list orders match. write_locks must contain + at least read_locks and possibly more. */ + for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) { + if (read_locks[r] == (enum mbox_lock_type)-1) + break; + if (read_locks[r] == write_locks[w]) + r++; + } + if (read_locks[r] != (enum mbox_lock_type)-1) { + i_fatal("mbox read/write lock list settings are invalid. " + "Lock ordering must be the same with both, " + "and write locks must contain all read locks " + "(and possibly more)"); + } + + storage->read_locks = p_new(storage->storage.pool, + enum mbox_lock_type, MBOX_LOCK_COUNT+1); + memcpy(storage->read_locks, read_locks, + sizeof(*storage->read_locks) * (MBOX_LOCK_COUNT+1)); + + storage->write_locks = p_new(storage->storage.pool, + enum mbox_lock_type, MBOX_LOCK_COUNT+1); + memcpy(storage->write_locks, write_locks, + sizeof(*storage->write_locks) * (MBOX_LOCK_COUNT+1)); + + storage->lock_settings_initialized = TRUE; +} + +static int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type) +{ + struct mbox_mailbox *mbox = ctx->mbox; + struct stat st; + + if (ctx->checked_file || lock_type == F_UNLCK) + return 0; + + if (mbox->mbox_fd != -1) { + /* we could flush NFS file handle cache here if we wanted to + be sure that the file is latest, but mbox files get rarely + deleted and the flushing might cause errors (e.g. EBUSY for + trying to flush a /var/mail mountpoint) */ + if (nfs_safe_stat(mailbox_get_path(&mbox->box), &st) < 0) { + if (errno == ENOENT) + mailbox_set_deleted(&mbox->box); + else + mbox_set_syscall_error(mbox, "stat()"); + return -1; + } + + if (st.st_ino != mbox->mbox_ino || + !CMP_DEV_T(st.st_dev, mbox->mbox_dev)) + mbox_file_close(mbox); + } + + if (mbox->mbox_fd == -1) { + if (mbox_file_open(mbox) < 0) + return -1; + } + + ctx->checked_file = TRUE; + return 0; +} + +static bool dotlock_callback(unsigned int secs_left, bool stale, void *context) +{ + struct mbox_lock_context *ctx = context; + enum mbox_lock_type *lock_types; + int i; + + if (ctx->using_privileges) + restrict_access_drop_priv_gid(); + + if (stale && !ctx->dotlock_last_stale) { + /* get next index we wish to try locking. it's the one after + dotlocking. */ + lock_types = ctx->lock_type == F_WRLCK || + (ctx->lock_type == F_UNLCK && + ctx->mbox->mbox_lock_type == F_WRLCK) ? + ctx->mbox->storage->write_locks : + ctx->mbox->storage->read_locks; + + for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) { + if (lock_types[i] == MBOX_LOCK_DOTLOCK) + break; + } + + if (lock_types[i] != (enum mbox_lock_type)-1 && + lock_types[i+1] != (enum mbox_lock_type)-1) { + i++; + if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) { + /* we couldn't get fd lock - + it's really locked */ + ctx->dotlock_last_stale = TRUE; + return FALSE; + } + mbox_lock_list(ctx, F_UNLCK, 0, i); + } + } + ctx->dotlock_last_stale = stale; + + index_storage_lock_notify(&ctx->mbox->box, stale ? + MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE : + MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT, + secs_left); + if (ctx->using_privileges) { + if (restrict_access_use_priv_gid() < 0) { + /* shouldn't get here */ + return FALSE; + } + } + return TRUE; +} + +static int ATTR_NULL(2) ATTR_NOWARN_UNUSED_RESULT +mbox_dotlock_privileged_op(struct mbox_mailbox *mbox, + struct dotlock_settings *set, + enum mbox_dotlock_op op) +{ + const char *box_path, *dir, *fname; + int ret = -1, orig_dir_fd, orig_errno; + + orig_dir_fd = open(".", O_RDONLY); + if (orig_dir_fd == -1) { + mailbox_set_critical(&mbox->box, "open(.) failed: %m"); + return -1; + } + + /* allow dotlocks to be created only for files we can read while we're + unprivileged. to make sure there are no race conditions we first + have to chdir to the mbox file's directory and then use relative + paths. unless this is done, users could: + - create *.lock files to any directory writable by the + privileged group + - DoS other users by dotlocking their mailboxes infinitely + */ + box_path = mailbox_get_path(&mbox->box); + fname = strrchr(box_path, '/'); + if (fname == NULL) { + /* already relative */ + fname = box_path; + } else { + dir = t_strdup_until(box_path, fname); + if (chdir(dir) < 0) { + mailbox_set_critical(&mbox->box, + "chdir(%s) failed: %m", dir); + i_close_fd(&orig_dir_fd); + return -1; + } + fname++; + } + if (op == MBOX_DOTLOCK_OP_LOCK) { + if (access(fname, R_OK) < 0) { + mailbox_set_critical(&mbox->box, + "access(%s) failed: %m", box_path); + i_close_fd(&orig_dir_fd); + return -1; + } + } + + if (restrict_access_use_priv_gid() < 0) { + i_close_fd(&orig_dir_fd); + return -1; + } + + switch (op) { + case MBOX_DOTLOCK_OP_LOCK: + /* we're now privileged - avoid doing as much as possible */ + ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock); + if (ret > 0) + mbox->mbox_used_privileges = TRUE; + else if (ret < 0 && errno == EACCES) { + const char *errmsg = + eacces_error_get_creating("file_dotlock_create", + fname); + mailbox_set_critical(&mbox->box, "%s", errmsg); + } else { + mbox_set_syscall_error(mbox, "file_dotlock_create()"); + } + break; + case MBOX_DOTLOCK_OP_UNLOCK: + /* we're now privileged - avoid doing as much as possible */ + ret = file_dotlock_delete(&mbox->mbox_dotlock); + if (ret < 0) + mbox_set_syscall_error(mbox, "file_dotlock_delete()"); + mbox->mbox_used_privileges = FALSE; + break; + case MBOX_DOTLOCK_OP_TOUCH: + ret = file_dotlock_touch(mbox->mbox_dotlock); + if (ret < 0) + mbox_set_syscall_error(mbox, "file_dotlock_touch()"); + break; + } + + orig_errno = errno; + restrict_access_drop_priv_gid(); + + if (fchdir(orig_dir_fd) < 0) { + mailbox_set_critical(&mbox->box, "fchdir() failed: %m"); + } + i_close_fd(&orig_dir_fd); + errno = orig_errno; + return ret; +} + +static void +mbox_dotlock_log_eacces_error(struct mbox_mailbox *mbox, const char *path) +{ + const char *dir, *errmsg, *name; + struct stat st; + struct group group; + int orig_errno = errno; + + errmsg = eacces_error_get_creating("file_dotlock_create", path); + dir = strrchr(path, '/'); + dir = dir == NULL ? "." : t_strdup_until(path, dir); + /* allow privileged locking for + a) user's own INBOX, + b) another user's shared INBOX, and + c) anything called INBOX (in inbox=no namespace) */ + if (!mbox->box.inbox_any && strcmp(mbox->box.name, "INBOX") != 0) { + mailbox_set_critical(&mbox->box, + "%s (not INBOX -> no privileged locking)", errmsg); + } else if (!mbox->mbox_privileged_locking) { + dir = mailbox_list_get_root_forced(mbox->box.list, + MAILBOX_LIST_PATH_TYPE_DIR); + mailbox_set_critical(&mbox->box, + "%s (under root dir %s -> no privileged locking)", + errmsg, dir); + } else if (stat(dir, &st) == 0 && + (st.st_mode & 02) == 0 && /* not world-writable */ + (st.st_mode & 020) != 0) { /* group-writable */ + if (i_getgrgid(st.st_gid, &group) <= 0) + name = dec2str(st.st_gid); + else + name = group.gr_name; + mailbox_set_critical(&mbox->box, + "%s (set mail_privileged_group=%s)", errmsg, name); + } else { + mailbox_set_critical(&mbox->box, + "%s (nonstandard permissions in %s)", errmsg, dir); + } + errno = orig_errno; +} + +static int +mbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try) +{ + struct mbox_mailbox *mbox = ctx->mbox; + struct dotlock_settings set; + int ret; + + if (lock_type == F_UNLCK) { + if (!mbox->mbox_dotlocked) + return 1; + + if (!mbox->mbox_used_privileges) { + if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) { + mbox_set_syscall_error(mbox, + "file_dotlock_delete()"); + } + } else { + ctx->using_privileges = TRUE; + mbox_dotlock_privileged_op(mbox, NULL, + MBOX_DOTLOCK_OP_UNLOCK); + ctx->using_privileges = FALSE; + } + mbox->mbox_dotlocked = FALSE; + return 1; + } + + if (mbox->mbox_dotlocked) + return 1; + + ctx->dotlock_last_stale = TRUE; + + i_zero(&set); + set.use_excl_lock = mbox->storage->storage.set->dotlock_use_excl; + set.nfs_flush = mbox->storage->storage.set->mail_nfs_storage; + set.timeout = mail_storage_get_lock_timeout(&mbox->storage->storage, + mbox->storage->set->mbox_lock_timeout); + set.stale_timeout = mbox->storage->set->mbox_dotlock_change_timeout; + set.callback = dotlock_callback; + set.context = ctx; + + ret = file_dotlock_create(&set, mailbox_get_path(&mbox->box), 0, + &mbox->mbox_dotlock); + if (ret >= 0) { + /* success / timeout */ + } else if (errno == EACCES && restrict_access_have_priv_gid() && + mbox->mbox_privileged_locking) { + /* try again, this time with extra privileges */ + ret = mbox_dotlock_privileged_op(mbox, &set, + MBOX_DOTLOCK_OP_LOCK); + } else if (errno == EACCES) + mbox_dotlock_log_eacces_error(mbox, mailbox_get_path(&mbox->box)); + else + mbox_set_syscall_error(mbox, "file_dotlock_create()"); + + if (ret < 0) { + if ((ENOSPACE(errno) || errno == EACCES) && try) + return 1; + return -1; + } + if (ret == 0) { + mail_storage_set_error(&mbox->storage->storage, + MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT); + return 0; + } + mbox->mbox_dotlocked = TRUE; + + if (mbox_file_open_latest(ctx, lock_type) < 0) + return -1; + return 1; +} + +static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time ATTR_UNUSED) +{ + return mbox_lock_dotlock_int(ctx, lock_type, FALSE); +} + +static int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time ATTR_UNUSED) +{ + return mbox_lock_dotlock_int(ctx, lock_type, TRUE); +} + +#ifdef HAVE_FLOCK +static int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time) +{ + time_t now; + unsigned int next_alarm; + + if (mbox_file_open_latest(ctx, lock_type) < 0) + return -1; + + if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1) + return 1; + + if (lock_type == F_WRLCK) + lock_type = LOCK_EX; + else if (lock_type == F_RDLCK) + lock_type = LOCK_SH; + else + lock_type = LOCK_UN; + + if (max_wait_time == 0) { + /* usually we're waiting here, but if we came from + mbox_lock_dotlock(), we just want to try locking */ + lock_type |= LOCK_NB; + } else { + now = time(NULL); + if (now >= max_wait_time) + alarm(1); + else + alarm(I_MIN(max_wait_time - now, 5)); + } + + while (flock(ctx->mbox->mbox_fd, lock_type) < 0) { + if (errno != EINTR) { + if (errno == EWOULDBLOCK && max_wait_time == 0) { + /* non-blocking lock trying failed */ + return 0; + } + alarm(0); + mbox_set_syscall_error(ctx->mbox, "flock()"); + return -1; + } + + now = time(NULL); + if (now >= max_wait_time) { + alarm(0); + return 0; + } + + /* notify locks once every 5 seconds. + try to use rounded values. */ + next_alarm = (max_wait_time - now) % 5; + if (next_alarm == 0) + next_alarm = 5; + alarm(next_alarm); + + index_storage_lock_notify(&ctx->mbox->box, + MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT, + max_wait_time - now); + } + + alarm(0); + return 1; +} +#endif + +#ifdef HAVE_LOCKF +static int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time) +{ + time_t now; + unsigned int next_alarm; + + if (mbox_file_open_latest(ctx, lock_type) < 0) + return -1; + + if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1) + return 1; + + if (lock_type == F_UNLCK) + lock_type = F_ULOCK; + else if (max_wait_time == 0) { + /* usually we're waiting here, but if we came from + mbox_lock_dotlock(), we just want to try locking */ + lock_type = F_TLOCK; + } else { + now = time(NULL); + if (now >= max_wait_time) + alarm(1); + else + alarm(I_MIN(max_wait_time - now, 5)); + lock_type = F_LOCK; + } + + while (lockf(ctx->mbox->mbox_fd, lock_type, 0) < 0) { + if (errno != EINTR) { + if ((errno == EACCES || errno == EAGAIN) && + max_wait_time == 0) { + /* non-blocking lock trying failed */ + return 0; + } + alarm(0); + mbox_set_syscall_error(ctx->mbox, "lockf()"); + return -1; + } + + now = time(NULL); + if (now >= max_wait_time) { + alarm(0); + return 0; + } + + /* notify locks once every 5 seconds. + try to use rounded values. */ + next_alarm = (max_wait_time - now) % 5; + if (next_alarm == 0) + next_alarm = 5; + alarm(next_alarm); + + index_storage_lock_notify(&ctx->mbox->box, + MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT, + max_wait_time - now); + } + + alarm(0); + return 1; +} +#endif + +static int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time) +{ + struct flock fl; + time_t now; + unsigned int next_alarm; + int wait_type; + + if (mbox_file_open_latest(ctx, lock_type) < 0) + return -1; + + if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1) + return 1; + + i_zero(&fl); + fl.l_type = lock_type; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + if (max_wait_time == 0) { + /* usually we're waiting here, but if we came from + mbox_lock_dotlock(), we just want to try locking */ + wait_type = F_SETLK; + } else { + wait_type = F_SETLKW; + now = time(NULL); + if (now >= max_wait_time) + alarm(1); + else + alarm(I_MIN(max_wait_time - now, 5)); + } + + while (fcntl(ctx->mbox->mbox_fd, wait_type, &fl) < 0) { + if (errno != EINTR) { + if ((errno == EACCES || errno == EAGAIN) && + wait_type == F_SETLK) { + /* non-blocking lock trying failed */ + return 0; + } + alarm(0); + if (errno != EACCES) { + mbox_set_syscall_error(ctx->mbox, "fcntl()"); + return -1; + } + mailbox_set_critical(&ctx->mbox->box, + "fcntl() failed with mbox file %s: " + "File is locked by another process (EACCES)", + mailbox_get_path(&ctx->mbox->box)); + return -1; + } + + now = time(NULL); + if (now >= max_wait_time) { + alarm(0); + return 0; + } + + /* notify locks once every 5 seconds. + try to use rounded values. */ + next_alarm = (max_wait_time - now) % 5; + if (next_alarm == 0) + next_alarm = 5; + alarm(next_alarm); + + index_storage_lock_notify(&ctx->mbox->box, + MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT, + max_wait_time - now); + } + + alarm(0); + ctx->fcntl_locked = TRUE; + return 1; +} + +static int ATTR_NOWARN_UNUSED_RESULT +mbox_lock_list(struct mbox_lock_context *ctx, int lock_type, + time_t max_wait_time, int idx) +{ + enum mbox_lock_type *lock_types; + enum mbox_lock_type type; + int i, ret = 0; + bool locked_status; + + ctx->lock_type = lock_type; + + lock_types = lock_type == F_WRLCK || + (lock_type == F_UNLCK && ctx->mbox->mbox_lock_type == F_WRLCK) ? + ctx->mbox->storage->write_locks : + ctx->mbox->storage->read_locks; + for (i = idx; lock_types[i] != (enum mbox_lock_type)-1; i++) { + type = lock_types[i]; + locked_status = lock_type != F_UNLCK; + + if (ctx->locked_status[type] == locked_status) + continue; + ctx->locked_status[type] = locked_status; + + ret = lock_data[type].func(ctx, lock_type, max_wait_time); + if (ret <= 0) + break; + } + return ret; +} + +static int mbox_update_locking(struct mbox_mailbox *mbox, int lock_type, + bool *fcntl_locked_r) +{ + struct mbox_lock_context ctx; + time_t max_wait_time; + int ret, i; + bool drop_locks; + + *fcntl_locked_r = FALSE; + + index_storage_lock_notify_reset(&mbox->box); + + if (!mbox->storage->lock_settings_initialized) + mbox_init_lock_settings(mbox->storage); + + if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) { + /* read-only mbox stream. no need to lock. */ + i_assert(mbox_is_backend_readonly(mbox)); + mbox->mbox_lock_type = lock_type; + return 1; + } + + max_wait_time = time(NULL) + + mail_storage_get_lock_timeout(&mbox->storage->storage, + mbox->storage->set->mbox_lock_timeout); + + i_zero(&ctx); + ctx.mbox = mbox; + + if (mbox->mbox_lock_type == F_WRLCK) { + /* dropping to shared lock. first drop those that we + don't remove completely. */ + const enum mbox_lock_type *read_locks = + mbox->storage->read_locks; + + for (i = 0; i < MBOX_LOCK_COUNT; i++) + ctx.locked_status[i] = TRUE; + for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++) + ctx.locked_status[read_locks[i]] = FALSE; + drop_locks = TRUE; + } else { + drop_locks = FALSE; + } + + mbox->mbox_lock_type = lock_type; + ret = mbox_lock_list(&ctx, lock_type, max_wait_time, 0); + if (ret <= 0) { + if (!drop_locks) + mbox_unlock_files(&ctx); + if (ret == 0) { + mail_storage_set_error(&mbox->storage->storage, + MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT); + } + return ret; + } + + if (drop_locks) { + /* dropping to shared lock: drop the locks that are only + in write list */ + const enum mbox_lock_type *read_locks = + mbox->storage->read_locks; + const enum mbox_lock_type *write_locks = + mbox->storage->write_locks; + + memset(ctx.locked_status, 0, sizeof(ctx.locked_status)); + for (i = 0; write_locks[i] != (enum mbox_lock_type)-1; i++) + ctx.locked_status[write_locks[i]] = TRUE; + for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++) + ctx.locked_status[read_locks[i]] = FALSE; + + mbox->mbox_lock_type = F_WRLCK; + mbox_lock_list(&ctx, F_UNLCK, 0, 0); + mbox->mbox_lock_type = F_RDLCK; + } + + *fcntl_locked_r = ctx.fcntl_locked; + return 1; +} + +int mbox_lock(struct mbox_mailbox *mbox, int lock_type, + unsigned int *lock_id_r) +{ + const char *path = mailbox_get_path(&mbox->box); + int mbox_fd = mbox->mbox_fd; + bool fcntl_locked; + int ret; + + if (lock_type == F_RDLCK && mbox->external_transactions > 0 && + mbox->mbox_lock_type != F_RDLCK) { + /* we have a transaction open that is going to save mails + and apparently also wants to read from the same mailbox + (copy, move, catenate). we need to write lock the mailbox, + since we can't later upgrade a read lock to write lock. */ + lock_type = F_WRLCK; + } + + /* allow only unlock -> shared/exclusive or exclusive -> shared */ + i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK); + i_assert(lock_type == F_RDLCK || mbox->mbox_lock_type != F_RDLCK); + + if (mbox->mbox_lock_type == F_UNLCK) { + ret = mbox_update_locking(mbox, lock_type, &fcntl_locked); + if (ret <= 0) + return ret; + + if (mbox->storage->storage.set->mail_nfs_storage) { + if (fcntl_locked) { + nfs_flush_attr_cache_fd_locked(path, mbox_fd); + nfs_flush_read_cache_locked(path, mbox_fd); + } else { + nfs_flush_attr_cache_unlocked(path); + nfs_flush_read_cache_unlocked(path, mbox_fd); + } + } + + mbox->mbox_lock_id += 2; + } + + if (lock_type == F_RDLCK) { + mbox->mbox_shared_locks++; + *lock_id_r = mbox->mbox_lock_id; + } else { + mbox->mbox_excl_locks++; + *lock_id_r = mbox->mbox_lock_id + 1; + } + if (mbox->mbox_stream != NULL) + istream_raw_mbox_set_locked(mbox->mbox_stream); + return 1; +} + +static int mbox_unlock_files(struct mbox_lock_context *ctx) +{ + int ret = 0; + + if (mbox_lock_list(ctx, F_UNLCK, 0, 0) < 0) + ret = -1; + + ctx->mbox->mbox_lock_id += 2; + ctx->mbox->mbox_lock_type = F_UNLCK; + return ret; +} + +int mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id) +{ + struct mbox_lock_context ctx; + bool fcntl_locked; + int i; + + i_assert(mbox->mbox_lock_id == (lock_id & ~1U)); + + if ((lock_id & 1) != 0) { + /* dropping exclusive lock */ + i_assert(mbox->mbox_excl_locks > 0); + if (--mbox->mbox_excl_locks > 0) + return 0; + if (mbox->mbox_shared_locks > 0) { + /* drop to shared lock */ + if (mbox_update_locking(mbox, F_RDLCK, + &fcntl_locked) < 0) + return -1; + return 0; + } + } else { + /* dropping shared lock */ + i_assert(mbox->mbox_shared_locks > 0); + if (--mbox->mbox_shared_locks > 0) + return 0; + if (mbox->mbox_excl_locks > 0) + return 0; + } + /* all locks gone */ + + /* make sure we don't read the stream while unlocked */ + if (mbox->mbox_stream != NULL) + istream_raw_mbox_set_unlocked(mbox->mbox_stream); + + i_zero(&ctx); + ctx.mbox = mbox; + + for (i = 0; i < MBOX_LOCK_COUNT; i++) + ctx.locked_status[i] = TRUE; + + return mbox_unlock_files(&ctx); +} + +unsigned int mbox_get_cur_lock_id(struct mbox_mailbox *mbox) +{ + return mbox->mbox_lock_id + + (mbox->mbox_excl_locks > 0 ? 1 : 0); +} + +void mbox_dotlock_touch(struct mbox_mailbox *mbox) +{ + if (mbox->mbox_dotlock == NULL) + return; + + if (!mbox->mbox_used_privileges) + (void)file_dotlock_touch(mbox->mbox_dotlock); + else { + mbox_dotlock_privileged_op(mbox, NULL, + MBOX_DOTLOCK_OP_TOUCH); + } +} |