mbox-lock.c revision 6797b2bb35596ec47d7f140eba79d4c686f370bc
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen/* 0.1 .. 0.2msec */
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* lock methods to use in wanted order */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#define DEFAULT_WRITE_LOCK_METHODS "dotlock fcntl"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* lock timeout */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* assume stale dotlock if mbox file hasn't changed for n seconds */
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen int (*func)(struct mbox_lock_context *ctx, int lock_type,
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
20a802016205bbcafc90f164f769ea801f88d014Timo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
20a802016205bbcafc90f164f769ea801f88d014Timo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainen { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock },
287ba82a8da3eaa473b5735d4eeac2fb4c5d8117Timo Sirainen { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try },
024815ea2ffdda9ea79919f18e865663977f73eaTimo Sirainen { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl },
287ba82a8da3eaa473b5735d4eeac2fb4c5d8117Timo Sirainen { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock },
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf },
024815ea2ffdda9ea79919f18e865663977f73eaTimo Sirainenstatic enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
1175f27441385a7011629f295f42708f9a3a4ffcTimo Sirainenstatic enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainenstatic int lock_timeout, dotlock_change_timeout;
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainenstatic int mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainenstatic int mbox_unlock_files(struct mbox_lock_context *ctx);
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainenstatic void mbox_read_lock_methods(const char *str, const char *env,
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen const char *const *lock;
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) {
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen for (type = 0; lock_data[type].name != NULL; type++) {
de12ff295bb3d0873b4dced5840612cbacd635efTimo Sirainen if (strcasecmp(*lock, lock_data[type].name) == 0) {
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen for (i = 0; i < dest; i++) {
de12ff295bb3d0873b4dced5840612cbacd635efTimo Sirainen i_fatal("%s: Duplicated value %s", env, *lock);
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen /* @UNSAFE */
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainen const char *str;
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainen if (str == NULL) str = DEFAULT_READ_LOCK_METHODS;
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen mbox_read_lock_methods(str, "MBOX_READ_LOCKS", read_locks);
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen if (str == NULL) str = DEFAULT_WRITE_LOCK_METHODS;
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen mbox_read_lock_methods(str, "MBOX_WRITE_LOCKS", write_locks);
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen /* check that read/write list orders match. write_locks must contain
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainen at least read_locks and possibly more. */
c27f03fa8fd2ef4acd1db814fae7d90e0eb9d3aeTimo Sirainen for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) {
f23298fea47eecbeded985ee2537a34c4c4ef56bTimo Sirainen if (read_locks[r] != (enum mbox_lock_type)-1) {
f23298fea47eecbeded985ee2537a34c4c4ef56bTimo Sirainen i_fatal("mbox read/write lock list settings are invalid. "
f23298fea47eecbeded985ee2537a34c4c4ef56bTimo Sirainen "Lock ordering must be the same with both, "
287ba82a8da3eaa473b5735d4eeac2fb4c5d8117Timo Sirainen "and write locks must contain all read locks "
f23298fea47eecbeded985ee2537a34c4c4ef56bTimo Sirainen "(and possibly more)");
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainen lock_timeout = str == NULL ? MBOX_DEFAULT_LOCK_TIMEOUT : atoi(str);
519e0a461271843833a2b42626ad93f6e7ddc497Timo Sirainenstatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen if (ctx->checked_file || lock_type == F_UNLCK)
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen /* we could flush NFS file handle cache here if we wanted to
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen be sure that the file is latest, but mbox files get rarely
367c05967091a2cbfce59b7f274f55b1a0f9e8c9Timo Sirainen deleted and the flushing might cause errors (e.g. EBUSY for
367c05967091a2cbfce59b7f274f55b1a0f9e8c9Timo Sirainen trying to flush a /var/mail mountpoint) */
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen /* get next index we wish to try locking. it's the one after
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen dotlocking. */
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
287ba82a8da3eaa473b5735d4eeac2fb4c5d8117Timo Sirainen if (lock_types[i] != (enum mbox_lock_type)-1 &&
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) {
287ba82a8da3eaa473b5735d4eeac2fb4c5d8117Timo Sirainen /* we couldn't get fd lock -
1175f27441385a7011629f295f42708f9a3a4ffcTimo Sirainen it's really locked */
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen index_storage_lock_notify(&ctx->mbox->ibox, stale ?
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainen /* shouldn't get here */
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainenstatic int mbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen /* allow dotlocks to be created only for files we can read while we're
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen unprivileged. to make sure there are no race conditions we first
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen have to chdir to the mbox file's directory and then use relative
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen paths. unless this is done, users could:
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen - create *.lock files to any directory writable by the
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen privileged group
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen - DoS other users by dotlocking their mailboxes infinitely
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen /* already relative */
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainen /* we're now privileged - avoid doing as much as possible */
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen /* we're now privileged - avoid doing as much as possible */
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen ret = file_dotlock_delete(&mbox->mbox_dotlock);
b79ec51bdeef6ef950eb5e890e65cc0491cf5fe9Timo Sirainen if (!file_dotlock_is_locked(mbox->mbox_dotlock)) {
64541374b58e4c702b1926e87df421d180ffa006Timo Sirainenmbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen ret = file_dotlock_delete(&mbox->mbox_dotlock);
8e7da21696c9f8a6d5e601243fb6172ec85d47b2Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_delete()");
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen set.use_excl_lock = (mbox->storage->storage.flags &
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen set.nfs_flush = (mbox->storage->storage.flags &
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen ret = file_dotlock_create(&set, mbox->path, 0, &mbox->mbox_dotlock);
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (ret < 0 && errno == EACCES && restrict_access_have_priv_gid() &&
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen /* try again, this time with extra privileges */
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if ((ENOSPACE(errno) || errno == EACCES) && try)
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen mbox_set_syscall_error(mbox, "file_lock_dotlock()");
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen mail_storage_set_error(&mbox->storage->storage,
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainen return mbox_lock_dotlock_int(ctx, lock_type, FALSE);
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen return mbox_lock_dotlock_int(ctx, lock_type, TRUE);
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen while (flock(ctx->mbox->mbox_fd, lock_type | LOCK_NB) < 0) {
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
6a04c5112961c5f4fb2d2f25192b3dc424d62ad0Timo Sirainen while (lockf(ctx->mbox->mbox_fd, lock_type, 0) < 0) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* usually we're waiting here, but if we came from
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mbox_lock_dotlock(), we just want to try locking */
8e7da21696c9f8a6d5e601243fb6172ec85d47b2Timo Sirainen while (fcntl(ctx->mbox->mbox_fd, wait_type, &fl) < 0) {
8e7da21696c9f8a6d5e601243fb6172ec85d47b2Timo Sirainen /* non-blocking lock trying failed */
d482b35af87f5fd872bad007da0475813a401a49Timo Sirainen mail_storage_set_critical(&ctx->mbox->storage->storage,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen "fcntl() failed with mbox file %s: "
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen "File is locked by another process (EACCES)",
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* notify locks once every 5 seconds.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen try to use rounded values. */
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainenstatic int mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen (lock_type == F_UNLCK && ctx->mbox->mbox_lock_type == F_WRLCK) ?
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen for (i = idx; lock_types[i] != (enum mbox_lock_type)-1; i++) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ret = lock_data[type].func(ctx, lock_type, max_wait_time);
0ce3bbb0f03fb0ee99258b41b5a1d689c1158a75Timo Sirainenstatic int mbox_update_locking(struct mbox_mailbox *mbox, int lock_type,
3c24d47ad5ff02ea00684233bef314ef2eefda4aTimo Sirainen if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) {
3c24d47ad5ff02ea00684233bef314ef2eefda4aTimo Sirainen /* read-only mbox stream. no need to lock. */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* dropping to shared lock. first drop those that we
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen don't remove completely. */
91b5eae18db48ebb70eee5407a7ab52bf798ee12Timo Sirainen for (i = 0; i < MBOX_LOCK_COUNT; i++)
91b5eae18db48ebb70eee5407a7ab52bf798ee12Timo Sirainen for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen ret = mbox_lock_list(&ctx, lock_type, max_wait_time, 0);
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen mail_storage_set_error(&mbox->storage->storage,
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen /* dropping to shared lock: drop the locks that are only
cd65dfcedc603fabbf644be793efb09096f6c1b5Timo Sirainen in write list */
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen memset(ctx.lock_status, 0, sizeof(ctx.lock_status));
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen for (i = 0; write_locks[i] != (enum mbox_lock_type)-1; i++)
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainenint mbox_lock(struct mbox_mailbox *mbox, int lock_type,
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen unsigned int *lock_id_r)
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen /* allow only unlock -> shared/exclusive or exclusive -> shared */
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen i_assert(lock_type == F_RDLCK || mbox->mbox_lock_type != F_RDLCK);
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen /* mbox must be locked before index */
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen i_assert(mbox->ibox.index->lock_type != F_WRLCK);
e86d0d34fe365da4c7ca4312d575bfcbf3a01c0eTimo Sirainen ret = mbox_update_locking(mbox, lock_type, &fcntl_locked);
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainenstatic int mbox_unlock_files(struct mbox_lock_context *ctx)
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainenint mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id)
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen i_assert(mbox->mbox_lock_id == (lock_id & ~1));
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen /* dropping exclusive lock */
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen /* drop to shared lock */
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen /* dropping shared lock */
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen /* all locks gone */
b2105c78f0fd58281317e6d777ded860f33153a3Timo Sirainen for (i = 0; i < MBOX_LOCK_COUNT; i++)