view src/lib-index/mail-index.c @ 931:d458f318ab02 HEAD

Make sure hdr->used_file_size isn't too small.
author Timo Sirainen <tss@iki.fi>
date Thu, 09 Jan 2003 14:35:16 +0200
parents fd8888f6f037
children 411006be3c66
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "file-lock.h"
#include "file-set-size.h"
#include "mmap-util.h"
#include "mail-index.h"
#include "mail-index-data.h"
#include "mail-index-util.h"
#include "mail-tree.h"
#include "mail-modifylog.h"
#include "mail-custom-flags.h"

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

static int mmap_verify(struct mail_index *index)
{
	struct mail_index_header *hdr;
	unsigned int extra;

	index->mmap_used_length = 0;

	if (index->mmap_full_length < sizeof(struct mail_index_header)) {
                index_set_corrupted(index, "File too small");
		return FALSE;
	}

	extra = (index->mmap_full_length - sizeof(struct mail_index_header)) %
		sizeof(struct mail_index_record);

	if (extra != 0) {
		/* partial write or corrupted -
		   truncate the file to valid length */
		i_assert(!index->anon_mmap);

		index->mmap_full_length -= extra;
		(void)ftruncate(index->fd, (off_t)index->mmap_full_length);
	}

	/* keep the header set even if we fail, so we can update the flags */
	hdr = index->mmap_base;
	index->header = hdr;

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

	if (hdr->used_file_size < sizeof(struct mail_index_header) ||
	    (hdr->used_file_size - sizeof(struct mail_index_header)) %
	    sizeof(struct mail_index_record) != 0) {
		index_set_corrupted(index, "Invalid used_file_size in header "
				    "(%"PRIuUOFF_T")",
				    hdr->used_file_size);
		return FALSE;
	}

	if (hdr->messages_count < hdr->seen_messages_count) {
		index_set_corrupted(index, "Invalid seen messages count "
				    "(%u < %u)", hdr->messages_count,
				    hdr->seen_messages_count);
		return FALSE;
	}

	if (hdr->messages_count < hdr->deleted_messages_count) {
		index_set_corrupted(index, "Invalid deleted messages count "
				    "(%u < %u)", hdr->messages_count,
				    hdr->deleted_messages_count);
		return FALSE;
	}

	index->sync_id = hdr->sync_id;
	index->mmap_used_length = hdr->used_file_size;
	return TRUE;
}

int mail_index_mmap_update(struct mail_index *index)
{
	if (index->anon_mmap)
		return mmap_verify(index);

	if (index->mmap_base != NULL) {
		index->header = (struct mail_index_header *) index->mmap_base;

		/* make sure file size hasn't changed */
		if (index->header->sync_id == index->sync_id) {
			index->mmap_used_length = index->header->used_file_size;
			if (index->mmap_used_length > index->mmap_full_length) {
				i_panic("Index file size was grown without "
					"updating sync_id");
			}
			return TRUE;
		}

		if (msync(index->mmap_base,
			  index->mmap_used_length, MS_SYNC) < 0)
			return index_set_syscall_error(index, "msync()");

		if (munmap(index->mmap_base, index->mmap_full_length) < 0)
			return index_set_syscall_error(index, "munmap()");
	}

	index->mmap_base = mmap_rw_file(index->fd, &index->mmap_full_length);
	if (index->mmap_base == MAP_FAILED) {
		index->mmap_base = NULL;
		index->mmap_used_length = 0;
		index_set_syscall_error(index, "mmap()");
		return FALSE;
	}

	return mmap_verify(index);
}

void mail_index_close(struct mail_index *index)
{
	index->set_flags = 0;
	index->set_cache_fields = 0;

	index->opened = FALSE;
	index->inconsistent = FALSE;

	index->lock_type = MAIL_LOCK_UNLOCK;
	index->header = NULL;

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

	if (index->filepath != NULL) {
		i_free(index->filepath);
		index->filepath = NULL;
	}

	if (index->anon_mmap) {
		if (munmap_anon(index->mmap_base, index->mmap_full_length) < 0)
			index_set_syscall_error(index, "munmap_anon()");
		index->anon_mmap = FALSE;
	} else if (index->mmap_base != NULL) {
		if (munmap(index->mmap_base, index->mmap_full_length) < 0)
			index_set_syscall_error(index, "munmap()");
	}
	index->mmap_base = NULL;

	if (index->data != NULL) {
                mail_index_data_free(index->data);
		index->data = NULL;
	}

	if (index->tree != NULL) {
                mail_tree_free(index->tree);
		index->tree = NULL;
	}

	if (index->modifylog != NULL) {
                mail_modifylog_free(index->modifylog);
		index->modifylog = NULL;
	}

	if (index->custom_flags != NULL) {
		mail_custom_flags_free(index->custom_flags);
                index->custom_flags = NULL;
	}

	if (index->error != NULL) {
		i_free(index->error);
		index->error = NULL;
	}
}

static int mail_index_sync_file(struct mail_index *index)
{
	unsigned int i;
	int failed, fsync_fds[3];

	if (index->anon_mmap)
		return TRUE;

	for (i = 0; i < sizeof(fsync_fds)/sizeof(fsync_fds[0]); i++)
		fsync_fds[i] = -1;

	if (!mail_index_data_sync_file(index->data, &fsync_fds[0]))
		return FALSE;

	if (msync(index->mmap_base, index->mmap_used_length, MS_SYNC) < 0)
		return index_set_syscall_error(index, "msync()");

	failed = FALSE;

	if (index->tree != NULL) {
		if (!mail_tree_sync_file(index->tree, &fsync_fds[1]))
			failed = TRUE;
	}

	if (index->modifylog != NULL) {
		if (!mail_modifylog_sync_file(index->modifylog, &fsync_fds[2]))
			failed = TRUE;
	}

	for (i = 0; i < sizeof(fsync_fds)/sizeof(fsync_fds[0]); i++) {
		if (fsync_fds[i] != -1 && fdatasync(fsync_fds[i]) < 0)
			index_set_error(index, "fdatasync(%u) failed: %m", i);
	}

	if (fdatasync(index->fd) < 0)
		return index_set_syscall_error(index, "fdatasync()");

	return !failed;
}

static void mail_index_update_timestamp(struct mail_index *index)
{
	struct utimbuf ut;

	/* keep index's modify stamp same as the sync file's stamp */
	ut.actime = ioloop_time;
	ut.modtime = index->file_sync_stamp;
	if (utime(index->filepath, &ut) < 0)
		index_set_syscall_error(index, "utime()");
}

int mail_index_fmdatasync(struct mail_index *index, size_t size)
{
	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);

	if (!index->anon_mmap) {
		if (msync(index->mmap_base, size, MS_SYNC) < 0)
			return index_set_syscall_error(index, "msync()");
		if (fdatasync(index->fd) < 0)
			return index_set_syscall_error(index, "fdatasync()");
	}

	return TRUE;
}

static void mail_index_update_header_changes(struct mail_index *index)
{
	if (index->set_flags != 0) {
		index->header->flags |= index->set_flags;
		index->set_flags = 0;
	}

	if (index->set_cache_fields != 0) {
		index->header->cache_fields = index->set_cache_fields;
		index->set_cache_fields = 0;
	}
}

static int mail_index_write_header_changes(struct mail_index *index)
{
	int failed;

	/* use our own locking here so we don't mess up with any other
	   index states, like inconsistency. */
	if (!mail_index_wait_lock(index, F_WRLCK))
		return FALSE;

#ifdef DEBUG
	mprotect(index->mmap_base, index->mmap_used_length,
		 PROT_READ|PROT_WRITE);
#endif

	mail_index_update_header_changes(index);

	failed = msync(index->mmap_base,
		       sizeof(struct mail_index_header), MS_SYNC) < 0;
	if (failed)
		index_set_syscall_error(index, "msync()");

#ifdef DEBUG
	mprotect(index->mmap_base, index->mmap_used_length, PROT_NONE);
#endif

	if (!mail_index_wait_lock(index, F_UNLCK))
		return FALSE;

	return !failed;
}

static int mail_index_lock_remove(struct mail_index *index)
{
	enum mail_lock_type old_lock_type;

	if (!mail_index_wait_lock(index, F_UNLCK))
		return FALSE;

	old_lock_type = index->lock_type;
	index->lock_type = MAIL_LOCK_UNLOCK;

	if (old_lock_type == MAIL_LOCK_SHARED) {
		/* releasing shared lock. we may need to update some
		   flags in header. */
		unsigned int old_flags;
		enum mail_data_field old_cache;

		old_flags = index->header->flags;
		old_cache = index->header->cache_fields;

		if ((old_flags | index->set_flags) != old_flags ||
		    (old_cache | index->set_cache_fields) != old_cache)
			return mail_index_write_header_changes(index);
	}

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

static int mail_index_lock_change(struct mail_index *index,
				  enum mail_lock_type lock_type, int try_lock)
{
	int ret, fd_lock_type;

	/* shared -> exclusive isn't allowed without try_lock */
	i_assert(try_lock || lock_type != MAIL_LOCK_EXCLUSIVE ||
		 index->lock_type != MAIL_LOCK_SHARED);

	if (index->inconsistent) {
		/* index is in inconsistent state and nothing else than
		   free() is allowed for it. */
		return FALSE;
	}

	fd_lock_type = MAIL_LOCK_TO_FLOCK(lock_type);
	if (try_lock) {
		ret = file_try_lock(index->fd, fd_lock_type);
		if (ret < 0)
			index_set_syscall_error(index, "file_try_lock()");
		if (ret <= 0)
			return FALSE;
	} else {
		if (!mail_index_wait_lock(index, fd_lock_type))
			return FALSE;
	}

	index->lock_type = lock_type;
	debug_mprotect(index->mmap_base, index->mmap_full_length, index);

	if (!mail_index_mmap_update(index)) {
		(void)index->set_lock(index, MAIL_LOCK_UNLOCK);
		return FALSE;
	}

	if (index->indexid != index->header->indexid) {
		/* index was rebuilt, there's no way we can maintain
		   consistency */
		index_set_error(index, "Warning: Inconsistency - Index "
				"%s was rebuilt while we had it open",
				index->filepath);
		index->inconsistent = TRUE;
		return FALSE;
	}

	if (index->header->flags & MAIL_INDEX_FLAG_FSCK) {
		/* someone just partially updated the index, need to fsck it */
		if (lock_type == MAIL_LOCK_SHARED) {
			/* we need exclusive lock so fsck()'s set_lock() won't
			   get us back here */
			if (!mail_index_lock_remove(index))
				return FALSE;

			if (!mail_index_wait_lock(index, F_WRLCK))
				return FALSE;
			index->lock_type = MAIL_LOCK_EXCLUSIVE;

			debug_mprotect(index->mmap_base,
				       index->mmap_full_length, index);
		}

		/* check again, in case it was already fscked while we had
		   it unlocked for a while */
		if (index->header->flags & MAIL_INDEX_FLAG_FSCK) {
			if (!index->fsck(index))
				return FALSE;
		}

		if (lock_type == MAIL_LOCK_SHARED) {
			/* drop exclusive lock */
			return index->set_lock(index, lock_type);
		}
	}

	if (lock_type == MAIL_LOCK_EXCLUSIVE) {
		/* while holding exclusive lock, keep the FSCK flag on.
		   when the lock is released, the FSCK flag will also be
		   removed. */
		index->header->flags |= MAIL_INDEX_FLAG_FSCK;
		if (!mail_index_fmdatasync(index,
					   sizeof(struct mail_index_header))) {
			(void)index->set_lock(index, MAIL_LOCK_UNLOCK);
			return FALSE;
		}
	}

	return TRUE;
}

static int mail_index_lock_full(struct mail_index *index,
				enum mail_lock_type lock_type, int try_lock)
{
	int keep_fsck;

	if (index->lock_type == lock_type)
		return TRUE;

	if (index->anon_mmap) {
		/* anonymous mmaps are private and don't need any locking */
		mail_index_update_header_changes(index);
		index->lock_type = lock_type;

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

	if (index->lock_type == MAIL_LOCK_EXCLUSIVE) {
		if (index->modifylog != NULL)
			mail_modifylog_notify_lock_drop(index->modifylog);

		/* dropping exclusive lock (either unlock or to shared) */
		keep_fsck = (index->set_flags & MAIL_INDEX_FLAG_FSCK) != 0;
		mail_index_update_header_changes(index);

		/* remove the FSCK flag only after successful fsync() */
		if (mail_index_sync_file(index) && !keep_fsck) {
			index->header->flags &= ~MAIL_INDEX_FLAG_FSCK;
			if (msync(index->mmap_base,
				  sizeof(struct mail_index_header),
				  MS_SYNC) < 0) {
				/* we only failed to remove the fsck flag,
				   so this isn't fatal. */
				index_set_syscall_error(index, "msync()");
			}
		}

		mail_index_update_timestamp(index);
	}

	if (lock_type == MAIL_LOCK_UNLOCK)
		return mail_index_lock_remove(index);
	else
		return mail_index_lock_change(index, lock_type, try_lock);
}

int mail_index_set_lock(struct mail_index *index, enum mail_lock_type lock_type)
{
	return mail_index_lock_full(index, lock_type, FALSE);
}

int mail_index_try_lock(struct mail_index *index, enum mail_lock_type lock_type)
{
	return mail_index_lock_full(index, lock_type, TRUE);
}

void mail_index_set_lock_notify_callback(struct mail_index *index,
					 MailLockNotifyFunc func, void *context)
{
	index->lock_notify_func = func;
	index->lock_notify_context = context;
}

int mail_index_verify_hole_range(struct mail_index *index)
{
	struct mail_index_header *hdr;
	unsigned int max_records;

	hdr = index->header;
	if (hdr->first_hole_records == 0)
		return TRUE;

	max_records = MAIL_INDEX_RECORD_COUNT(index);
	if (hdr->first_hole_index >= max_records) {
		index_set_corrupted(index,
				    "first_hole_index points outside file");
		return FALSE;
	}

	/* check that first_hole_records is in valid range */
	if (max_records - hdr->first_hole_index < hdr->first_hole_records) {
		index_set_corrupted(index,
				    "first_hole_records points outside file");
		return FALSE;
	}

	return TRUE;
}

struct mail_index_header *mail_index_get_header(struct mail_index *index)
{
	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);

	return index->header;
}

struct mail_index_record *mail_index_lookup(struct mail_index *index,
					    unsigned int seq)
{
	struct mail_index_header *hdr;
	struct mail_index_record *rec;
	const char *error;
	unsigned int idx;

	i_assert(seq > 0);
	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);

	hdr = index->header;
	if (seq > hdr->messages_count) {
		/* out of range */
		return NULL;
	}

	if (!mail_index_verify_hole_range(index))
		return NULL;

	idx = seq-1;
	if (hdr->first_hole_records == 0 || hdr->first_hole_index > idx) {
		/* easy, it's just at the expected index */
		error = t_strdup_printf("Invalid first_hole_index in header: "
					"%u", idx);
	} else if (hdr->first_hole_records ==
		   MAIL_INDEX_RECORD_COUNT(index) - hdr->messages_count) {
		/* only one hole in file, skip it and we're at
		   correct position */
		idx += hdr->first_hole_records;
		error = t_strdup_printf("Invalid hole locations in header: %u",
					idx);
	} else {
		/* find from binary tree */
		idx = mail_tree_lookup_sequence(index->tree, seq);
		if (idx == (unsigned int)-1) {
			index_set_corrupted(index,
				"Sequence %u not found from binary tree "
				"(%u msgs says header)",
				seq, hdr->messages_count);
			return NULL;
		}

		error = t_strdup_printf("Invalid offset returned by "
					"binary tree: %u", idx);
	}

	if (idx >= MAIL_INDEX_RECORD_COUNT(index)) {
		index_set_corrupted(index, "%s", error);
		return NULL;
	}

	rec = INDEX_RECORD_AT(index, idx);
	if (rec->uid == 0) {
		index_set_corrupted(index, "%s", error);
		return NULL;
	}

	return rec;
}

struct mail_index_record *mail_index_next(struct mail_index *index,
					  struct mail_index_record *rec)
{
	struct mail_index_record *end_rec;

	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
	i_assert(rec >= (struct mail_index_record *) index->mmap_base);

	if (rec == NULL)
		return NULL;

	/* go to the next non-deleted record */
	end_rec = INDEX_END_RECORD(index);
	while (++rec < end_rec) {
		if (rec->uid != 0)
			return rec;
	}

	return NULL;
}

struct mail_index_record *
mail_index_lookup_uid_range(struct mail_index *index, unsigned int first_uid,
			    unsigned int last_uid, unsigned int *seq_r)
{
	struct mail_index_record *rec;
	unsigned int idx;

	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
	i_assert(first_uid > 0 && last_uid > 0);
	i_assert(first_uid <= last_uid);

	idx = mail_tree_lookup_uid_range(index->tree, seq_r,
					 first_uid, last_uid);
	if (idx == (unsigned int)-1)
		return NULL;

	if (idx >= MAIL_INDEX_RECORD_COUNT(index)) {
		index_set_error(index, "Corrupted binary tree for index %s: "
				"lookup returned index outside range "
				"(%u >= %"PRIuSIZE_T")", index->filepath, idx,
				MAIL_INDEX_RECORD_COUNT(index));
		index->set_flags |= MAIL_INDEX_FLAG_REBUILD_TREE;
		return NULL;
	}

	rec = INDEX_RECORD_AT(index, idx);
	if (rec->uid < first_uid || rec->uid > last_uid) {
		index_set_error(index, "Corrupted binary tree for index %s: "
				"lookup returned offset to wrong UID "
				"(%u vs %u..%u)", index->filepath,
				rec->uid, first_uid, last_uid);
		index->set_flags |= MAIL_INDEX_FLAG_REBUILD_TREE;
		return NULL;
	}

	return rec;
}

const char *mail_index_lookup_field(struct mail_index *index,
				    struct mail_index_record *rec,
				    enum mail_data_field field)
{
	struct mail_index_data_record *datarec;

	datarec = (rec->data_fields & field) == 0 ? NULL :
		mail_index_data_lookup(index->data, rec, field);
	if (datarec == NULL)
		return NULL;

	if (!mail_index_data_record_verify(index->data, datarec)) {
		/* index is corrupted, it will be rebuilt */
		return NULL;
	}

	return datarec->data;
}

const void *mail_index_lookup_field_raw(struct mail_index *index,
					struct mail_index_record *rec,
					enum mail_data_field field,
					size_t *size)
{
	struct mail_index_data_record_header *datahdr;
	struct mail_index_data_record *datarec;

	if ((rec->data_fields & field) == 0) {
		*size = 0;
		return NULL;
	}

	if (field < DATA_FIELD_LAST) {
		/* read data field */
		datarec = mail_index_data_lookup(index->data, rec, field);
		if (datarec == NULL) {
			*size = 0;
			return NULL;
		}

		*size = datarec->full_field_size;
		return datarec->data;
	}

	/* read header field */
	datahdr = mail_index_data_lookup_header(index->data, rec);
	if (datahdr == NULL) {
		*size = 0;
		return NULL;
	}

	switch (field) {
	case DATA_HDR_INTERNAL_DATE:
		*size = sizeof(datahdr->internal_date);
		return &datahdr->internal_date;
	case DATA_HDR_VIRTUAL_SIZE:
		*size = sizeof(datahdr->virtual_size);
		return &datahdr->virtual_size;
	case DATA_HDR_HEADER_SIZE:
		*size = sizeof(datahdr->header_size);
		return &datahdr->header_size;
	case DATA_HDR_BODY_SIZE:
		*size = sizeof(datahdr->body_size);
		return &datahdr->body_size;
	default:
		*size = 0;
		return NULL;
	}
}

void mail_index_cache_fields_later(struct mail_index *index,
				   enum mail_data_field field)
{
	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);

	field &= ~index->never_cache_fields;

	/* first check if the field even could be in the file */
	if ((index->set_cache_fields & field) != field) {
		if ((index->header->cache_fields & field) == 0) {
			/* no, but make sure the future records will have it.
			   we don't immediately mark the index to cache this
			   field for old messages as some clients never ask
			   the info again */
			index->set_cache_fields |= field;
		} else {
			/* this is at least the second time it's being asked,
			   make sure it'll be cached soon. */
			index->set_flags |= MAIL_INDEX_FLAG_CACHE_FIELDS;
		}
	}
}

time_t mail_get_internal_date(struct mail_index *index,
			      struct mail_index_record *rec)
{
	const time_t *date;
	size_t size;

	date = index->lookup_field_raw(index, rec,
				       DATA_HDR_INTERNAL_DATE, &size);
	if (date == NULL)
		return (time_t)-1;
	else {
		i_assert(size == sizeof(*date));
		return *date;
	}
}

void mail_index_mark_flag_changes(struct mail_index *index,
				  struct mail_index_record *rec,
				  enum mail_flags old_flags,
				  enum mail_flags new_flags)
{
	if ((old_flags & MAIL_SEEN) == 0 && (new_flags & MAIL_SEEN)) {
		/* unseen -> seen */
		index->header->seen_messages_count++;
	} else if ((old_flags & MAIL_SEEN) && (new_flags & MAIL_SEEN) == 0) {
		/* seen -> unseen */
		if (index->header->seen_messages_count ==
		    index->header->messages_count) {
			/* this is the first unseen message */
                        index->header->first_unseen_uid_lowwater = rec->uid;
		} else if (rec->uid < index->header->first_unseen_uid_lowwater)
			index->header->first_unseen_uid_lowwater = rec->uid;

		if (index->header->seen_messages_count == 0) {
			index_set_corrupted(index,
				"seen_messages_count in header is invalid");
		} else {
			index->header->seen_messages_count--;
		}
	}

	if ((old_flags & MAIL_DELETED) == 0 && (new_flags & MAIL_DELETED)) {
		/* undeleted -> deleted */
		index->header->deleted_messages_count++;

		if (index->header->deleted_messages_count == 1) {
			/* this is the first deleted message */
			index->header->first_deleted_uid_lowwater = rec->uid;
		} else if (rec->uid < index->header->first_deleted_uid_lowwater)
			index->header->first_deleted_uid_lowwater = rec->uid;
	} else if ((old_flags & MAIL_DELETED) &&
		   (new_flags & MAIL_DELETED) == 0) {
		/* deleted -> undeleted */
		if (index->header->deleted_messages_count == 0) {
			index_set_corrupted(index,
				"deleted_messages_count in header is invalid");
		} else {
			index->header->deleted_messages_count--;
		}
	}
}

static void update_first_hole_records(struct mail_index *index)
{
        struct mail_index_record *rec, *end_rec;

	/* see if first_hole_records can be grown */
	rec = INDEX_RECORD_AT(index, index->header->first_hole_index +
			      index->header->first_hole_records);
	end_rec = INDEX_END_RECORD(index);
	while (rec < end_rec && rec->uid == 0) {
		index->header->first_hole_records++;
		rec++;
	}
}

static int mail_index_truncate_hole(struct mail_index *index)
{
	index->header->used_file_size = sizeof(struct mail_index_header) +
		(uoff_t)index->header->first_hole_index *
		sizeof(struct mail_index_record);
	index->header->first_hole_index = 0;
	index->header->first_hole_records = 0;

	index->mmap_used_length = index->header->used_file_size;
	if (!mail_index_truncate(index))
		return FALSE;

	if (index->header->messages_count == 0) {
		/* all mail was deleted, truncate data file */
		if (!mail_index_data_reset(index->data))
			return FALSE;
	}

	return TRUE;
}

#define INDEX_NEED_COMPRESS(records, hdr) \
	((records) > INDEX_MIN_RECORDS_COUNT && \
	 (records) * (100-INDEX_COMPRESS_PERCENTAGE) / 100 > \
	 	(hdr)->messages_count)

int mail_index_expunge(struct mail_index *index, struct mail_index_record *rec,
		       unsigned int seq, int external_change)
{
	struct mail_index_header *hdr;
	unsigned int records, uid, idx;

	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
	i_assert(seq != 0);
	i_assert(rec->uid != 0);

	if (!mail_index_verify_hole_range(index))
		return FALSE;

	hdr = index->header;

	/* setting UID to 0 is enough for deleting the mail from index */
	uid = rec->uid;
	rec->uid = 0;

	/* update first hole */
	idx = INDEX_RECORD_INDEX(index, rec);
	if (hdr->first_hole_records == 0) {
		/* first deleted message in index */
		hdr->first_hole_index = idx;
		hdr->first_hole_records = 1;
	} else if (idx+1 == hdr->first_hole_index) {
		/* deleted the previous record before hole */
		hdr->first_hole_index--;
		hdr->first_hole_records++;
	} else if (idx == hdr->first_hole_index + hdr->first_hole_records) {
		/* deleted the next record after hole */
		hdr->first_hole_records++;
		update_first_hole_records(index);
	} else {
		/* second hole coming to index file */
		if (idx < hdr->first_hole_index) {
			/* new hole before the old hole */
			hdr->first_hole_index = idx;
			hdr->first_hole_records = 1;
		}
	}

	/* update message counts */
	if (hdr->messages_count == 0) {
		/* corrupted */
		index_set_corrupted(index,
			"Header says there's no mail while expunging");
		return FALSE;
	}

	hdr->messages_count--;
	mail_index_mark_flag_changes(index, rec, rec->msg_flags, 0);

	(void)mail_index_data_delete(index->data, rec);

	records = MAIL_INDEX_RECORD_COUNT(index);
	if (hdr->first_hole_index + hdr->first_hole_records == records) {
		/* the hole reaches end of file, truncate it */
		(void)mail_index_truncate_hole(index);
	} else {
		if (INDEX_NEED_COMPRESS(records, hdr))
			hdr->flags |= MAIL_INDEX_FLAG_COMPRESS;
	}

	/* expunge() may be called while index is being rebuilt and when
	   tree file hasn't been opened yet */
	if (index->tree != NULL)
		mail_tree_delete(index->tree, uid);
	else {
		/* make sure it also gets updated */
		index->header->flags |= MAIL_INDEX_FLAG_REBUILD_TREE;
	}

	if (seq != 0 && index->modifylog != NULL) {
		if (!mail_modifylog_add_expunge(index->modifylog, seq,
						uid, external_change))
			return FALSE;
	}

	return TRUE;
}

int mail_index_update_flags(struct mail_index *index,
			    struct mail_index_record *rec,
			    unsigned int seq, enum mail_flags flags,
			    int external_change)
{
	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
	i_assert(seq != 0);

	if (flags == rec->msg_flags)
		return TRUE; /* no changes */

        mail_index_mark_flag_changes(index, rec, rec->msg_flags, flags);

	rec->msg_flags = flags;
	return index->modifylog == NULL ? TRUE :
		mail_modifylog_add_flags(index->modifylog, seq,
					 rec->uid, external_change);
}

static int mail_index_grow(struct mail_index *index)
{
	uoff_t pos;
	unsigned int grow_count;
	void *base;

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

	pos = index->mmap_full_length +
		(grow_count * sizeof(struct mail_index_record));
	i_assert(pos < OFF_T_MAX);

	if (index->anon_mmap) {
		i_assert(pos < SSIZE_T_MAX);

		base = mremap_anon(index->mmap_base, index->mmap_full_length,
				   (size_t)pos, MREMAP_MAYMOVE);
		if (base == MAP_FAILED)
			return index_set_syscall_error(index, "mremap_anon()");

		index->mmap_base = base;
		index->mmap_full_length = (size_t)pos;
		return mmap_verify(index);
	}

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

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

	if (!mail_index_mmap_update(index))
		return FALSE;

	return TRUE;
}

struct mail_index_record *mail_index_append_begin(struct mail_index *index)
{
	struct mail_index_record *rec;

	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);

	if (index->mmap_used_length == index->mmap_full_length) {
		if (!mail_index_grow(index))
			return NULL;
	}

	i_assert(index->header->used_file_size == index->mmap_used_length);
	i_assert(index->mmap_used_length + sizeof(struct mail_index_record) <=
		 index->mmap_full_length);

	rec = (struct mail_index_record *) ((char *) index->mmap_base +
					    index->mmap_used_length);
	memset(rec, 0, sizeof(struct mail_index_record));

	index->header->used_file_size += sizeof(struct mail_index_record);
	index->mmap_used_length += sizeof(struct mail_index_record);

	return rec;
}

int mail_index_append_end(struct mail_index *index,
			  struct mail_index_record *rec)
{
	i_assert(rec->uid == 0);

	if (index->header->next_uid == MAX_ALLOWED_UID) {
		index->set_flags |= MAIL_INDEX_FLAG_REBUILD;
		index_set_error(index, "Reached maximum UID in mailbox %s, "
				"rebuilding index", index->filepath);
		return FALSE;
	}

	index->header->messages_count++;
	rec->uid = index->header->next_uid++;

	if (index->tree != NULL) {
		mail_tree_insert(index->tree, rec->uid,
				 INDEX_RECORD_INDEX(index, rec));
	}

	return TRUE;
}

enum mail_index_error mail_index_get_last_error(struct mail_index *index)
{
	if (index->inconsistent)
		return MAIL_INDEX_ERROR_INCONSISTENT;
	if (index->nodiskspace)
		return MAIL_INDEX_ERROR_DISKSPACE;
	if (index->index_lock_timeout)
		return MAIL_INDEX_ERROR_INDEX_LOCK_TIMEOUT;
	if (index->mailbox_lock_timeout)
		return MAIL_INDEX_ERROR_MAILBOX_LOCK_TIMEOUT;

	if (index->error != NULL)
		return MAIL_INDEX_ERROR_INTERNAL;

	return MAIL_INDEX_ERROR_NONE;
}

const char *mail_index_get_last_error_text(struct mail_index *index)
{
	return index->error;
}