view src/lib-index/mail-cache.c @ 2378:00a4d69a1d1c HEAD

mail_cache_lock() crashed if index wasn't locked while it was called.
author Timo Sirainen <tss@iki.fi>
date Wed, 28 Jul 2004 19:31:54 +0300
parents f2acbe281ac1
children fed6d07bd8ee
line wrap: on
line source

/* Copyright (C) 2003-2004 Timo Sirainen */

#include "lib.h"
#include "buffer.h"
#include "hash.h"
#include "file-lock.h"
#include "mmap-util.h"
#include "write-full.h"
#include "mail-cache-private.h"

#include <unistd.h>

uint32_t mail_cache_uint32_to_offset(uint32_t offset)
{
	unsigned char buf[4];

	i_assert(offset < 0x40000000);
	i_assert((offset & 3) == 0);

	offset >>= 2;
	buf[0] = 0x80 | ((offset & 0x0fe00000) >> 21);
	buf[1] = 0x80 | ((offset & 0x001fc000) >> 14);
	buf[2] = 0x80 | ((offset & 0x00003f80) >> 7);
	buf[3] = 0x80 |  (offset & 0x0000007f);
	return *((uint32_t *) buf);
}

uint32_t mail_cache_offset_to_uint32(uint32_t offset)
{
	const unsigned char *buf = (const unsigned char *) &offset;

	if ((offset & 0x80808080) != 0x80808080)
		return 0;

	return (((uint32_t)buf[3] & 0x7f) << 2) |
		(((uint32_t)buf[2] & 0x7f) << 9) |
		(((uint32_t)buf[1] & 0x7f) << 16) |
		(((uint32_t)buf[0] & 0x7f) << 23);
}

void mail_cache_set_syscall_error(struct mail_cache *cache,
				  const char *function)
{
	i_assert(function != NULL);

	if (ENOSPACE(errno)) {
		cache->index->nodiskspace = TRUE;
		return;
	}

	mail_index_set_error(cache->index,
			     "%s failed with index cache file %s: %m",
			     function, cache->filepath);
}

void mail_cache_set_corrupted(struct mail_cache *cache, const char *fmt, ...)
{
	va_list va;

	(void)unlink(cache->filepath);
	mail_cache_file_close(cache);

	va_start(va, fmt);
	t_push();
	mail_index_set_error(cache->index, "Corrupted index cache file %s: %s",
			     cache->filepath, t_strdup_vprintf(fmt, va));
	t_pop();
	va_end(va);
}

void mail_cache_file_close(struct mail_cache *cache)
{
	if (cache->mmap_base != NULL) {
		if (munmap(cache->mmap_base, cache->mmap_length) < 0)
			mail_cache_set_syscall_error(cache, "munmap()");
	}

	cache->mmap_base = NULL;
	cache->hdr = NULL;
	cache->mmap_length = 0;

	if (cache->fd != -1) {
		if (close(cache->fd) < 0)
			mail_cache_set_syscall_error(cache, "close()");
		cache->fd = -1;
	}
}

int mail_cache_reopen(struct mail_cache *cache)
{
	if (MAIL_CACHE_IS_UNUSABLE(cache) && cache->need_compress) {
		/* unusable, we're just waiting for compression */
		return 0;
	}

	mail_cache_file_close(cache);

	cache->fd = open(cache->filepath, O_RDWR);
	if (cache->fd == -1) {
		if (errno == ENOENT)
			cache->need_compress = TRUE;
		else
			mail_cache_set_syscall_error(cache, "open()");
		return -1;
	}

	if (mail_cache_map(cache, 0, 0) < 0)
		return -1;

	if (mail_cache_header_fields_read(cache) < 0)
		return -1;

	if (cache->hdr->file_seq != cache->index->hdr->cache_file_seq) {
		/* still different - maybe a race condition or maybe the
		   file_seq really is corrupted. either way, this shouldn't
		   happen often so we'll just mark cache to be compressed
		   later which fixes this. */
		cache->need_compress = TRUE;
		return 0;
	}

	return 1;
}

static int mmap_verify_header(struct mail_cache *cache)
{
	const struct mail_cache_header *hdr;

	/* check that the header is still ok */
	if (cache->mmap_length < sizeof(struct mail_cache_header)) {
		mail_cache_set_corrupted(cache, "File too small");
		return FALSE;
	}
	cache->hdr = hdr = cache->mmap_base;

	if (cache->hdr->version != MAIL_CACHE_VERSION) {
		/* version changed - upgrade silently */
		return FALSE;
	}

	if (cache->hdr->indexid != cache->index->indexid) {
		/* index id changed */
		mail_cache_set_corrupted(cache, "indexid changed");
		return FALSE;
	}

	/* only check the header if we're locked */
	if (cache->locked)
		return TRUE;

	if (hdr->used_file_size < sizeof(struct mail_cache_header)) {
		mail_cache_set_corrupted(cache, "used_file_size too small");
		return FALSE;
	}
	if ((hdr->used_file_size % sizeof(uint32_t)) != 0) {
		mail_cache_set_corrupted(cache, "used_file_size not aligned");
		return FALSE;
	}

	if (hdr->used_file_size > cache->mmap_length) {
		mail_cache_set_corrupted(cache, "used_file_size too large");
		return FALSE;
	}
	return TRUE;
}

int mail_cache_map(struct mail_cache *cache, size_t offset, size_t size)
{
	if (size == 0)
		size = sizeof(struct mail_cache_header);

	if (offset < cache->mmap_length &&
	    size <= cache->mmap_length - offset) {
		/* already mapped */
		return 0;
	}

	if (cache->mmap_base != NULL) {
		if (munmap(cache->mmap_base, cache->mmap_length) < 0)
			mail_cache_set_syscall_error(cache, "munmap()");
	} else {
		if (cache->fd == -1) {
			/* unusable, waiting for compression */
			i_assert(cache->need_compress);
			return -1;
		}
	}

	/* map the whole file */
	cache->hdr = NULL;
	cache->mmap_length = 0;

	cache->mmap_base = mmap_ro_file(cache->fd, &cache->mmap_length);
	if (cache->mmap_base == MAP_FAILED) {
		cache->mmap_base = NULL;
		mail_cache_set_syscall_error(cache, "mmap()");
		return -1;
	}

	if (!mmap_verify_header(cache)) {
		cache->need_compress = TRUE;
		return -1;
	}

	return 0;
}

static int mail_cache_open_and_verify(struct mail_cache *cache)
{
	cache->filepath = i_strconcat(cache->index->filepath,
				      MAIL_CACHE_FILE_PREFIX, NULL);

	cache->fd = open(cache->filepath, O_RDWR);
	if (cache->fd == -1) {
		if (errno == ENOENT) {
			cache->need_compress = TRUE;
			return 0;
		}

		mail_cache_set_syscall_error(cache, "open()");
		return -1;
	}

	if (mail_cache_map(cache, 0, sizeof(struct mail_cache_header)) < 0)
		return -1;

	return mail_cache_header_fields_read(cache);
}

struct mail_cache *mail_cache_open_or_create(struct mail_index *index)
{
	struct mail_cache *cache;

	cache = i_new(struct mail_cache, 1);
	cache->index = index;
	cache->fd = -1;
        cache->field_pool = pool_alloconly_create("Cache fields", 1024);
	cache->field_name_hash =
		hash_create(default_pool, cache->field_pool, 0,
			    strcase_hash, (hash_cmp_callback_t *)strcasecmp);

	if (!index->mmap_disable && !index->mmap_no_write) {
		if (mail_cache_open_and_verify(cache) < 0) {
			/* failed for some reason - doesn't really matter,
			   it's disabled for now. */
			mail_cache_file_close(cache);
		}
	}

	return cache;
}

void mail_cache_free(struct mail_cache *cache)
{
	mail_cache_file_close(cache);

	hash_destroy(cache->field_name_hash);
	pool_unref(cache->field_pool);
	i_free(cache->field_file_map);
	i_free(cache->file_field_map);
	i_free(cache->fields);
	i_free(cache->filepath);
	i_free(cache);
}

int mail_cache_lock(struct mail_cache *cache)
{
	unsigned int lock_id;
	int i, ret;

	i_assert(!cache->locked);

	if (MAIL_CACHE_IS_UNUSABLE(cache))
		return 0;

	if (mail_index_lock_shared(cache->index, TRUE, &lock_id) < 0)
		return -1;

	if (cache->hdr->file_seq != cache->index->hdr->cache_file_seq) {
		/* we want the latest cache file */
		if ((ret = mail_cache_reopen(cache)) <= 0) {
			mail_index_unlock(cache->index, lock_id);
			return ret;
		}
	}

	for (i = 0; i < 3; i++) {
		if ((ret = file_wait_lock(cache->fd, F_WRLCK)) <= 0) {
			mail_cache_set_syscall_error(cache, "file_wait_lock()");
			break;
		}
		cache->locked = TRUE;

		if (cache->hdr->file_seq == cache->index->hdr->cache_file_seq) {
			/* got it */
			break;
		}

		/* okay, so it was just compressed. try again. */
		mail_cache_unlock(cache);
		if ((ret = mail_cache_reopen(cache)) <= 0)
			break;
		ret = 0;
	}

	if (ret > 0)
		cache->hdr_copy = *cache->hdr;

	mail_index_unlock(cache->index, lock_id);
	return ret;
}

static void mail_cache_update_need_compress(struct mail_cache *cache)
{
	const struct mail_cache_header *hdr = cache->hdr;
	unsigned int cont_percentage;
	uoff_t max_del_space;

        cont_percentage = hdr->continued_record_count * 100 /
		(cache->index->map->records_count == 0 ? 1 :
		 cache->index->map->records_count);
	if (cont_percentage >= COMPRESS_CONTINUED_PERCENTAGE &&
	    hdr->used_file_size >= COMPRESS_MIN_SIZE) {
		/* too many continued rows, compress */
		cache->need_compress = TRUE;
	}

	/* see if we've reached the max. deleted space in file */
	max_del_space = hdr->used_file_size / 100 * COMPRESS_PERCENTAGE;
	if (hdr->deleted_space >= max_del_space &&
	    hdr->used_file_size >= COMPRESS_MIN_SIZE)
		cache->need_compress = TRUE;
}

void mail_cache_unlock(struct mail_cache *cache)
{
	i_assert(cache->locked);

	if (cache->field_header_write_pending)
                (void)mail_cache_header_fields_update(cache);

	cache->locked = FALSE;

	if (cache->hdr_modified) {
		cache->hdr_modified = FALSE;
		if (pwrite_full(cache->fd, &cache->hdr_copy,
				sizeof(cache->hdr_copy), 0) < 0)
			mail_cache_set_syscall_error(cache, "pwrite_full()");
                mail_cache_update_need_compress(cache);
	}

	if (file_wait_lock(cache->fd, F_UNLCK) <= 0)
		mail_cache_set_syscall_error(cache, "file_wait_lock(F_UNLCK)");
}

struct mail_cache_view *
mail_cache_view_open(struct mail_cache *cache, struct mail_index_view *iview)
{
	struct mail_cache_view *view;

	view = i_new(struct mail_cache_view, 1);
	view->cache = cache;
	view->view = iview;
	view->cached_exists_buf =
		buffer_create_dynamic(default_pool,
				      cache->file_fields_count + 10,
				      (size_t)-1);
	return view;
}

void mail_cache_view_close(struct mail_cache_view *view)
{
	if (view->cache->field_header_write_pending)
                (void)mail_cache_header_fields_update(view->cache);

	buffer_free(view->cached_exists_buf);
	i_free(view);
}