view src/lib-index/mbox/mbox-lock.c @ 373:5d2298649157 HEAD

dotlock fixes: don't delete dotlock when we didn't create it (when removing a shared lock). if mbox file hasn't been changed for 5 seconds, assume stale lock file.
author Timo Sirainen <tss@iki.fi>
date Sun, 06 Oct 2002 14:35:13 +0300
parents 9b36fc3c1385
children 83da62e0675a
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "mbox-index.h"
#include "mbox-lock.h"
#include "mail-index-util.h"

#include <time.h>
#include <stdlib.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)rand() % 100000)

/* assume stale dotlock if mbox file hasn't changed for 5 seconds */
#define MAX_UNCHANGED_LOCK_WAIT 5

/* abort trying to get lock after 30 seconds */
#define MAX_LOCK_WAIT 30

/* remove lock after 10 mins */
#define STALE_LOCK_TIMEOUT (60*10)

#ifdef HAVE_FLOCK
static int mbox_lock_flock(MailIndex *index, int lock_type)
{
	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 (flock(index->mbox_fd, lock_type) < 0)
		return index_file_set_syscall_error(index, index->mbox_path,
						    "flock()");

	return TRUE;
}
#endif

static int mbox_lock_fcntl(MailIndex *index, int lock_type)
{
	struct flock fl;

	fl.l_type = lock_type;
	fl.l_whence = SEEK_SET;
	fl.l_start = 0;
	fl.l_len = 0;

	while (fcntl(index->mbox_fd, F_SETLKW, &fl) == -1) {
		if (errno != EINTR) {
			index_file_set_syscall_error(index, index->mbox_path,
						     "fcntl()");
			return FALSE;
		}
	}

	return TRUE;
}

static int mbox_lock_dotlock(MailIndex *index, const char *path, int set)
{
	struct stat st;
	time_t now, max_wait_time, last_change, last_mtime;
	off_t last_size;
	int fd;

	path = t_strconcat(path, ".lock", NULL);
	if (!set) {
		if (unlink(path) == 0 || errno == ENOENT)
			return TRUE;

		return index_file_set_syscall_error(index, path, "unlink()");
	}

	/* don't bother with the temp files as we'd just leave them lying
	   around. besides, postfix also relies on O_EXCL working so we
	   might as well. */
	max_wait_time = time(NULL) + MAX_LOCK_WAIT;
	last_change = time(NULL); last_size = 0; last_mtime = 0;
	do {
		now = time(NULL);

		if (stat(path, &st) == 0) {
			/* lock exists, see if it's too old */
			if (now > st.st_ctime + STALE_LOCK_TIMEOUT) {
				if (unlink(path) < 0 && errno != ENOENT) {
					index_file_set_syscall_error(
						index, path, "unlink()");
					break;
				}
				continue;
			}

			/* see if there's been any changes in mbox */
			if (stat(index->mbox_path, &st) < 0) {
				mbox_set_syscall_error(index, "stat()");
				break;
			}

			if (last_size != st.st_size ||
			    last_mtime != st.st_mtime) {
				last_change = now;
				last_size = st.st_size;
				last_mtime = st.st_mtime;
			}

			if (now > last_change + MAX_UNCHANGED_LOCK_WAIT) {
				/* no changes for a while, assume stale lock */
				if (unlink(path) < 0 && errno != ENOENT) {
					index_file_set_syscall_error(
						index, path, "unlink()");
					break;
				}
				continue;
			}

			usleep(LOCK_RANDOM_USLEEP_TIME);
			continue;
		}

		fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0);
		if (fd != -1) {
			/* got it */
			if (close(fd) < 0) {
				index_file_set_syscall_error(index, path,
							     "close()");
			}
			return TRUE;
		}

		if (errno != EEXIST) {
			index_file_set_syscall_error(index, path, "open()");
			return FALSE;
		}
	} while (now < max_wait_time);

	index_set_error(index, "Timeout while waiting for release of mbox "
			"dotlock %s", path);
	return FALSE;
}

static int mbox_lock(MailIndex *index, int exclusive)
{
	i_assert(index->mbox_fd != -1);

	if (++index->mbox_locks > 1)
		return TRUE;

        index->mbox_lock_type = exclusive ? F_WRLCK : F_RDLCK;
	if (!mbox_lock_fcntl(index, index->mbox_lock_type)) {
		(void)mbox_lock_dotlock(index, index->mbox_path, FALSE);
		return FALSE;
	}
#ifdef HAVE_FLOCK
	if (!mbox_lock_flock(index, index->mbox_lock_type)) {
		(void)mbox_lock_dotlock(index, index->mbox_path, FALSE);
		return FALSE;
	}
#endif

	if (exclusive) {
		if (!mbox_lock_dotlock(index, index->mbox_path, TRUE))
			return FALSE;
	}


	return TRUE;
}

int mbox_lock_read(MailIndex *index)
{
	return mbox_lock(index, FALSE);
}

int mbox_lock_write(MailIndex *index)
{
	i_assert(index->mbox_locks == 0 || index->mbox_lock_type != F_RDLCK);
	return mbox_lock(index, TRUE);
}

int mbox_unlock(MailIndex *index)
{
	int failed;

	i_assert(index->mbox_fd != -1);
	i_assert(index->mbox_locks > 0);

	if (--index->mbox_locks > 0)
		return TRUE;

	failed = FALSE;
#ifdef HAVE_FLOCK
	if (!mbox_lock_flock(index, F_UNLCK))
		failed = TRUE;
#endif
	if (!mbox_lock_fcntl(index, F_UNLCK))
		failed = TRUE;

	if (index->mbox_lock_type == F_WRLCK) {
		if (!mbox_lock_dotlock(index, index->mbox_path, FALSE))
			failed = TRUE;
	}

	return !failed;
}