mbox-lock.c revision a0a763f0361201d1ef354f7a2bfa2fa0e53af402
1057N/A/* Copyright (C) 2002 Timo Sirainen */
1057N/A
1057N/A#include "lib.h"
1057N/A#include "mbox-index.h"
1703N/A#include "mbox-lock.h"
1057N/A#include "mail-index-util.h"
660N/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
1057N/A/* 0.1 .. 0.2msec */
1057N/A#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
1057N/A
660N/A/* assume stale dotlock if mbox file hasn't changed for 5 seconds */
2437N/A#define MAX_UNCHANGED_LOCK_WAIT 5
1109N/A
2506N/A/* abort trying to get lock after 30 seconds */
2557N/A#define MAX_LOCK_WAIT 30
2557N/A
2557N/A/* remove lock after 10 mins */
2437N/A#define STALE_LOCK_TIMEOUT (60*10)
2437N/A
2437N/A#ifdef HAVE_FLOCK
2437N/Astatic int mbox_lock_flock(MailIndex *index, MailLockType lock_type)
2506N/A{
2506N/A if (lock_type == MAIL_LOCK_EXCLUSIVE)
2557N/A lock_type = LOCK_EX;
2557N/A else if (lock_type == MAIL_LOCK_SHARED)
2557N/A lock_type = LOCK_SH;
2557N/A else
2557N/A lock_type = LOCK_UN;
2557N/A
2557N/A if (flock(index->mbox_fd, lock_type) < 0)
2557N/A return index_file_set_syscall_error(index, index->mbox_path,
2557N/A "flock()");
2557N/A
2557N/A return TRUE;
2557N/A}
2557N/A#endif
2557N/A
2437N/Astatic int mbox_lock_fcntl(MailIndex *index, MailLockType lock_type)
2557N/A{
2557N/A struct flock fl;
2557N/A
2557N/A fl.l_type = MAIL_LOCK_TO_FLOCK(lock_type);
2557N/A fl.l_whence = SEEK_SET;
2557N/A fl.l_start = 0;
2557N/A fl.l_len = 0;
2557N/A
2557N/A while (fcntl(index->mbox_fd, F_SETLKW, &fl) == -1) {
2557N/A if (errno != EINTR) {
2557N/A index_file_set_syscall_error(index, index->mbox_path,
2557N/A "fcntl()");
2437N/A return FALSE;
2437N/A }
2437N/A }
2506N/A
2506N/A return TRUE;
2557N/A}
2095N/A
2095N/Astatic int mbox_lock_dotlock(MailIndex *index, const char *path, int set)
2095N/A{
2557N/A struct stat st;
2437N/A time_t now, max_wait_time, last_change, last_mtime;
2437N/A off_t last_size;
2437N/A int fd;
2557N/A
2437N/A path = t_strconcat(path, ".lock", NULL);
2437N/A if (!set) {
2437N/A if (unlink(path) == 0 || errno == ENOENT)
2437N/A return TRUE;
2506N/A
2506N/A return index_file_set_syscall_error(index, path, "unlink()");
2557N/A }
2095N/A
2095N/A /* don't bother with the temp files as we'd just leave them lying
2095N/A around. besides, postfix also relies on O_EXCL working so we
2557N/A might as well. */
2095N/A max_wait_time = time(NULL) + MAX_LOCK_WAIT;
2095N/A last_change = time(NULL); last_size = 0; last_mtime = 0;
2095N/A do {
2557N/A now = time(NULL);
2095N/A
2095N/A if (stat(path, &st) == 0) {
2095N/A /* lock exists, see if it's too old */
2557N/A if (now > st.st_ctime + STALE_LOCK_TIMEOUT) {
2557N/A if (unlink(path) < 0 && errno != ENOENT) {
2557N/A index_file_set_syscall_error(
2557N/A index, path, "unlink()");
2557N/A break;
2557N/A }
2557N/A continue;
2557N/A }
2557N/A
2095N/A /* see if there's been any changes in mbox */
2095N/A if (stat(index->mbox_path, &st) < 0) {
2095N/A mbox_set_syscall_error(index, "stat()");
2095N/A break;
2095N/A }
2437N/A
2506N/A if (last_size != st.st_size ||
2506N/A last_mtime != st.st_mtime) {
2506N/A last_change = now;
2557N/A last_size = st.st_size;
2557N/A last_mtime = st.st_mtime;
2557N/A }
2557N/A
2557N/A if (now > last_change + MAX_UNCHANGED_LOCK_WAIT) {
2557N/A /* no changes for a while, assume stale lock */
2557N/A if (unlink(path) < 0 && errno != ENOENT) {
2557N/A index_file_set_syscall_error(
2557N/A index, path, "unlink()");
2557N/A break;
2557N/A }
2557N/A continue;
2557N/A }
2557N/A
2557N/A usleep(LOCK_RANDOM_USLEEP_TIME);
2557N/A continue;
2557N/A }
2557N/A
2557N/A fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0);
2506N/A if (fd != -1) {
2506N/A /* got it */
2506N/A if (close(fd) < 0) {
2557N/A index_file_set_syscall_error(index, path,
2557N/A "close()");
2557N/A }
2557N/A return TRUE;
2557N/A }
2506N/A
2506N/A if (errno != EEXIST) {
2557N/A index_file_set_syscall_error(index, path, "open()");
2557N/A return FALSE;
2557N/A }
2557N/A } while (now < max_wait_time);
2557N/A
2557N/A index_set_error(index, "Timeout while waiting for release of mbox "
2557N/A "dotlock %s", path);
2557N/A return FALSE;
2557N/A}
2557N/A
2557N/Aint mbox_lock(MailIndex *index, MailLockType lock_type)
2095N/A{
2095N/A struct stat st;
2095N/A
2557N/A i_assert(lock_type == MAIL_LOCK_SHARED ||
2437N/A lock_type == MAIL_LOCK_EXCLUSIVE);
2437N/A i_assert(lock_type != MAIL_LOCK_EXCLUSIVE ||
2437N/A index->mbox_lock_type != MAIL_LOCK_SHARED);
2557N/A
2506N/A if (index->mbox_lock_type == lock_type)
2506N/A return TRUE;
2506N/A
2557N/A /* make .lock file first to protect overwriting the file */
2506N/A if (index->mbox_lock_type == MAIL_LOCK_UNLOCK) {
2506N/A if (!mbox_lock_dotlock(index, index->mbox_path, TRUE))
2506N/A return FALSE;
2557N/A }
2095N/A
2095N/A /* now we need to have the file itself locked. open it if needed. */
2095N/A do {
2557N/A if (stat(index->mbox_path, &st) < 0)
2095N/A return mbox_set_syscall_error(index, "stat()");
2095N/A
2095N/A if (st.st_dev != index->mbox_dev ||
2557N/A st.st_ino != index->mbox_ino)
2095N/A mbox_file_close_fd(index);
2095N/A
2095N/A if (index->mbox_fd == -1) {
2557N/A if (!mbox_file_open(index))
2095N/A break;
2095N/A }
2095N/A
2095N/A if (!mbox_lock_fcntl(index, index->mbox_lock_type))
2095N/A break;
2095N/A#ifdef HAVE_FLOCK
2095N/A if (!mbox_lock_flock(index, index->mbox_lock_type))
2095N/A break;
2095N/A#endif
2095N/A index->mbox_lock_type = lock_type;
2506N/A return TRUE;
2095N/A } while (0);
2095N/A
2095N/A if (index->mbox_lock_type == MAIL_LOCK_UNLOCK)
2095N/A (void)mbox_lock_dotlock(index, index->mbox_path, FALSE);
2095N/A
2095N/A return FALSE;
2095N/A}
2095N/A
2506N/Aint mbox_unlock(MailIndex *index)
2506N/A{
2557N/A int failed;
2095N/A
2095N/A index->mbox_lock_counter++;
2095N/A index->mbox_lock_next_sync = MAIL_LOCK_UNLOCK;
2557N/A
2543N/A if (index->mbox_lock_type == MAIL_LOCK_UNLOCK)
2543N/A return TRUE;
2543N/A
2557N/A failed = FALSE;
2437N/A if (index->mbox_fd != -1) {
2437N/A#ifdef HAVE_FLOCK
2437N/A if (!mbox_lock_flock(index, F_UNLCK))
2557N/A failed = TRUE;
2437N/A#endif
2437N/A if (!mbox_lock_fcntl(index, F_UNLCK))
2437N/A failed = TRUE;
2557N/A }
2543N/A
2543N/A if (!mbox_lock_dotlock(index, index->mbox_path, FALSE))
2543N/A failed = TRUE;
2437N/A
2437N/A /* make sure we don't keep mmap() between locks - there could have
2437N/A been changes to file size which would break things. or actually
2543N/A it'd break only if file was shrinked+grown back to exact size,
2543N/A but still possible :) */
2557N/A mbox_file_close_inbuf(index);
2437N/A
2437N/A index->mbox_lock_type = MAIL_LOCK_UNLOCK;
2437N/A return !failed;
2557N/A}
2543N/A