view src/lib-index/mail-tree.c @ 1000:0fbafade2d85 HEAD

If auth/login process died unexpectedly, the exit status or killing signal wasn't logged.
author Timo Sirainen <tss@iki.fi>
date Tue, 21 Jan 2003 09:58:49 +0200
parents d458f318ab02
children 2512ad6fd9e9
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "mmap-util.h"
#include "file-set-size.h"
#include "write-full.h"
#include "mail-index.h"
#include "mail-index-util.h"
#include "mail-tree.h"

#include <unistd.h>
#include <fcntl.h>

#define MAIL_TREE_MIN_SIZE \
	(sizeof(struct mail_tree_header) + \
	 INDEX_MIN_RECORDS_COUNT * sizeof(struct mail_tree_node))

static int tree_set_syscall_error(struct mail_tree *tree, const char *function)
{
	i_assert(function != NULL);

	index_set_error(tree->index, "%s failed with binary tree file %s: %m",
			function, tree->filepath);
	return FALSE;
}

int _mail_tree_set_corrupted(struct mail_tree *tree, const char *fmt, ...)
{
	va_list va;

	va_start(va, fmt);
	t_push();

	index_set_error(tree->index, "Corrupted binary tree file %s: %s",
			tree->filepath, t_strdup_vprintf(fmt, va));

	t_pop();
	va_end(va);

	/* make sure we don't get back here */
	tree->index->inconsistent = TRUE;
	(void)unlink(tree->filepath);

	return FALSE;
}

static int mmap_update(struct mail_tree *tree)
{
	i_assert(!tree->anon_mmap);

	if (tree->mmap_base != NULL) {
		/* make sure we're synced before munmap() */
		if (tree->modified &&
		    msync(tree->mmap_base, tree->mmap_highwater, MS_SYNC) < 0)
			return tree_set_syscall_error(tree, "msync()");
		tree->modified = FALSE;

		if (munmap(tree->mmap_base, tree->mmap_full_length) < 0)
			tree_set_syscall_error(tree, "munmap()");
	}

	tree->mmap_used_length = 0;
	tree->header = NULL;
	tree->node_base = NULL;

	tree->mmap_base = mmap_rw_file(tree->fd, &tree->mmap_full_length);
	if (tree->mmap_base == MAP_FAILED) {
		tree->mmap_base = NULL;
		return tree_set_syscall_error(tree, "mmap()");
	}

	debug_mprotect(tree->mmap_base, tree->mmap_full_length, tree->index);
	return TRUE;
}

static int mmap_verify(struct mail_tree *tree)
{
	struct mail_tree_header *hdr;
	unsigned int extra;

	if (tree->mmap_full_length <
	    sizeof(struct mail_tree_header) + sizeof(struct mail_tree_node)) {
		index_set_error(tree->index, "Too small binary tree file %s",
				tree->filepath);
		(void)unlink(tree->filepath);
		return FALSE;
	}

	extra = (tree->mmap_full_length - sizeof(struct mail_tree_header)) %
		sizeof(struct mail_tree_node);

	if (extra != 0) {
		/* partial write or corrupted -
		   truncate the file to valid length */
		tree->mmap_full_length -= extra;
		if (ftruncate(tree->fd, (off_t)tree->mmap_full_length) < 0)
			tree_set_syscall_error(tree, "ftruncate()");
	}

	hdr = tree->mmap_base;
	if (hdr->used_file_size > tree->mmap_full_length) {
		_mail_tree_set_corrupted(tree,
			"used_file_size larger than real file size "
			"(%"PRIuUOFF_T" vs %"PRIuSIZE_T")",
			hdr->used_file_size, tree->mmap_full_length);
		return FALSE;
	}

	if (hdr->used_file_size < sizeof(struct mail_tree_header) ||
	    (hdr->used_file_size - sizeof(struct mail_tree_header)) %
	    sizeof(struct mail_tree_node) != 0) {
		_mail_tree_set_corrupted(tree,
			"Invalid used_file_size in header (%"PRIuUOFF_T")",
			hdr->used_file_size);
		return FALSE;
	}

	tree->header = tree->mmap_base;
	tree->node_base = (struct mail_tree_node *)
		((char *) tree->mmap_base + sizeof(struct mail_tree_header));
	tree->sync_id = hdr->sync_id;
	tree->mmap_used_length = hdr->used_file_size;
	tree->mmap_highwater = tree->mmap_used_length;
	return TRUE;
}

int _mail_tree_mmap_update(struct mail_tree *tree, int forced)
{
	debug_mprotect(tree->mmap_base, tree->mmap_full_length,
		       tree->index);

	if (!forced && tree->header != NULL &&
	    tree->sync_id == tree->header->sync_id) {
		/* make sure file size hasn't changed */
		tree->mmap_used_length = tree->header->used_file_size;
		if (tree->mmap_used_length > tree->mmap_full_length) {
			i_panic("Tree file size was grown without "
				"updating sync_id");
		}

		return TRUE;
	}

	return mmap_update(tree) && mmap_verify(tree);
}

static struct mail_tree *mail_tree_open(struct mail_index *index)
{
	struct mail_tree *tree;
	const char *path;
	int fd;

	path = t_strconcat(index->filepath, ".tree", NULL);
	fd = open(path, O_RDWR | O_CREAT, 0660);
	if (fd == -1) {
		if (errno == ENOSPC)
			index->nodiskspace = TRUE;

		index_file_set_syscall_error(index, path, "open()");
		return NULL;
	}

	tree = i_new(struct mail_tree, 1);
	tree->fd = fd;
	tree->index = index;
	tree->filepath = i_strdup(path);

	index->tree = tree;
	return tree;
}

static struct mail_tree *mail_tree_create_anon(struct mail_index *index)
{
	struct mail_tree *tree;

	tree = i_new(struct mail_tree, 1);
	tree->anon_mmap = TRUE;
	tree->fd = -1;
	tree->index = index;
	tree->filepath = i_strdup("(in-memory tree)");

	index->tree = tree;
	return tree;
}

int mail_tree_create(struct mail_index *index)
{
	struct mail_tree *tree;

	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);

	tree = !index->nodiskspace ? mail_tree_open(index) :
		mail_tree_create_anon(index);
	if (tree == NULL)
		return FALSE;

	if (!mail_tree_rebuild(tree)) {
		mail_tree_free(tree);
		return FALSE;
	}

	return TRUE;
}

static int mail_tree_open_init(struct mail_tree *tree)
{
	if (!mmap_update(tree))
		return FALSE;

	if (tree->mmap_full_length == 0) {
		/* just created it */
		return FALSE;
	}

	if (!mmap_verify(tree)) {
		/* broken header */
		return FALSE;
	}

	if (tree->header->indexid != tree->index->indexid) {
		index_set_error(tree->index,
				"IndexID mismatch for binary tree file %s",
				tree->filepath);

		return FALSE;
	} 

	return TRUE;
}

int mail_tree_open_or_create(struct mail_index *index)
{
	struct mail_tree *tree;

	tree = mail_tree_open(index);
	if (tree == NULL)
		return FALSE;

	if (!mail_tree_open_init(tree)) {
		/* lock and check again, just to avoid rebuilding it twice
		   if two processes notice the error at the same time */
		if (!tree->index->set_lock(tree->index, MAIL_LOCK_EXCLUSIVE)) {
			mail_tree_free(tree);
			return FALSE;
		}

		if (!mail_tree_open_init(tree)) {
			if (!mail_tree_rebuild(tree)) {
				mail_tree_free(tree);
				return FALSE;
			}
		}
	}

	return TRUE;
}

static void mail_tree_close(struct mail_tree *tree)
{
	if (tree->anon_mmap) {
		if (munmap_anon(tree->mmap_base, tree->mmap_full_length) < 0)
			tree_set_syscall_error(tree, "munmap_anon()");
	} else if (tree->mmap_base != NULL) {
		if (munmap(tree->mmap_base, tree->mmap_full_length) < 0)
			tree_set_syscall_error(tree, "munmap()");
	}
	tree->mmap_base = NULL;
	tree->mmap_full_length = 0;
	tree->mmap_used_length = 0;
	tree->header = NULL;

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

	i_free(tree->filepath);
}

void mail_tree_free(struct mail_tree *tree)
{
	tree->index->tree = NULL;

	mail_tree_close(tree);
	i_free(tree);
}

static int mail_tree_init(struct mail_tree *tree)
{
        struct mail_tree_header hdr;

	/* first node is always used, and is the RBNULL node */
	memset(&hdr, 0, sizeof(struct mail_tree_header));
	hdr.indexid = tree->index->indexid;
	hdr.used_file_size = sizeof(struct mail_tree_header) +
		sizeof(struct mail_tree_node);

	if (tree->anon_mmap) {
		tree->mmap_full_length = MAIL_TREE_MIN_SIZE;
		tree->mmap_base = mmap_anon(tree->mmap_full_length);
		memcpy(tree->mmap_base, &hdr, sizeof(struct mail_tree_header));
		return mmap_verify(tree);
	}

	if (lseek(tree->fd, 0, SEEK_SET) < 0)
		return tree_set_syscall_error(tree, "lseek()");

	if (write_full(tree->fd, &hdr, sizeof(hdr)) < 0) {
		if (errno == ENOSPC)
			tree->index->nodiskspace = TRUE;

		return tree_set_syscall_error(tree, "write_full()");
	}

	if (file_set_size(tree->fd, MAIL_TREE_MIN_SIZE) < 0) {
		if (errno == ENOSPC)
			tree->index->nodiskspace = TRUE;

		return tree_set_syscall_error(tree, "file_set_size()");
	}

	return TRUE;
}

int mail_tree_rebuild(struct mail_tree *tree)
{
	struct mail_index_record *rec;

	if (!tree->index->set_lock(tree->index, MAIL_LOCK_EXCLUSIVE))
		return FALSE;

	if (!mail_tree_init(tree) ||
	    (!tree->anon_mmap && !_mail_tree_mmap_update(tree, TRUE))) {
		tree->index->header->flags |= MAIL_INDEX_FLAG_REBUILD_TREE;
		return FALSE;
	}

	rec = tree->index->lookup(tree->index, 1);
	while (rec != NULL) {
		if (!mail_tree_insert(tree, rec->uid,
				      INDEX_RECORD_INDEX(tree->index, rec))) {
			tree->index->header->flags |=
				MAIL_INDEX_FLAG_REBUILD_TREE;
			return FALSE;
		}

		rec = tree->index->next(tree->index, rec);
	}

	return TRUE;
}

int mail_tree_sync_file(struct mail_tree *tree, int *fsync_fd)
{
	*fsync_fd = -1;

	if (!tree->modified || tree->anon_mmap)
		return TRUE;

	i_assert(tree->mmap_base != NULL);

	if (msync(tree->mmap_base, tree->mmap_highwater, MS_SYNC) < 0)
		return tree_set_syscall_error(tree, "msync()");

	tree->mmap_highwater = tree->mmap_used_length;
	tree->modified = FALSE;

	*fsync_fd = tree->fd;
	return TRUE;
}

int _mail_tree_grow(struct mail_tree *tree)
{
	uoff_t new_fsize;
	unsigned int grow_count;
	void *base;

	grow_count = tree->index->header->messages_count *
		INDEX_GROW_PERCENTAGE / 100;
	if (grow_count < 16)
		grow_count = 16;

	new_fsize = (uoff_t)tree->mmap_full_length +
		(grow_count * sizeof(struct mail_tree_node));
	i_assert(new_fsize < OFF_T_MAX);

	if (tree->anon_mmap) {
		i_assert(new_fsize < SSIZE_T_MAX);

		base = mremap_anon(tree->mmap_base, tree->mmap_full_length,
				   (size_t)new_fsize, MREMAP_MAYMOVE);
		if (base == MAP_FAILED)
			return tree_set_syscall_error(tree, "mremap_anon()");

		tree->mmap_base = base;
		tree->mmap_full_length = (size_t)new_fsize;
		return mmap_verify(tree);
	}

	if (file_set_size(tree->fd, (off_t)new_fsize) < 0) {
		if (errno == ENOSPC)
			tree->index->nodiskspace = TRUE;
		return tree_set_syscall_error(tree, "file_set_size()");
	}

	/* file size changed, let others know about it too by changing
	   sync_id in header. */
	tree->header->sync_id++;
	tree->modified = TRUE;

	if (!_mail_tree_mmap_update(tree, TRUE))
		return FALSE;

	return TRUE;
}

void _mail_tree_truncate(struct mail_tree *tree)
{
	/* pretty much copy&pasted from mail_index_compress() */
	uoff_t empty_space, truncate_threshold;

	i_assert(tree->index->lock_type == MAIL_LOCK_EXCLUSIVE);

	if (tree->mmap_full_length <= MAIL_TREE_MIN_SIZE || tree->anon_mmap)
		return;

	empty_space = tree->mmap_full_length - tree->mmap_used_length;

	truncate_threshold =
		tree->mmap_full_length / 100 * INDEX_TRUNCATE_PERCENTAGE;

	if (empty_space > truncate_threshold) {
		tree->mmap_full_length = tree->mmap_used_length +
			(empty_space * INDEX_TRUNCATE_KEEP_PERCENTAGE / 100);

		/* keep the size record-aligned */
		tree->mmap_full_length -= (tree->mmap_full_length -
					   sizeof(struct mail_tree_header)) %
			sizeof(struct mail_tree_node);

		if (tree->mmap_full_length < MAIL_TREE_MIN_SIZE)
			tree->mmap_full_length = MAIL_TREE_MIN_SIZE;

		if (ftruncate(tree->fd, (off_t)tree->mmap_full_length) < 0)
			tree_set_syscall_error(tree, "ftruncate()");

		tree->header->sync_id++;
	}
}