Пример #1
0
mdbox_write_index_header(struct mailbox *box,
                         const struct mailbox_update *update,
                         struct mail_index_transaction *trans)
{
    struct mdbox_mailbox *mbox = (struct mdbox_mailbox *)box;
    struct mail_index_transaction *new_trans = NULL;
    struct mail_index_view *view;
    const struct mail_index_header *hdr;
    uint32_t uid_validity, uid_next;

    if (mdbox_map_open_or_create(mbox->storage->map) < 0)
        return -1;

    if (trans == NULL) {
        new_trans = mail_index_transaction_begin(box->view, 0);
        trans = new_trans;
    }

    view = mail_index_view_open(box->index);
    hdr = mail_index_get_header(view);
    uid_validity = hdr->uid_validity;
    if (update != NULL && update->uid_validity != 0)
        uid_validity = update->uid_validity;
    else if (uid_validity == 0) {
        /* set uidvalidity */
        uid_validity = dbox_get_uidvalidity_next(box->list);
    }

    if (hdr->uid_validity != uid_validity) {
        mail_index_update_header(trans,
                                 offsetof(struct mail_index_header, uid_validity),
                                 &uid_validity, sizeof(uid_validity), TRUE);
    }
Пример #2
0
static int
maildir_mail_get_fname(struct maildir_mailbox *mbox, struct mail *mail,
		       const char **fname_r)
{
	enum maildir_uidlist_rec_flag flags;
	struct mail_index_view *view;
	uint32_t seq;
	bool exists;
	int ret;

	ret = maildir_sync_lookup(mbox, mail->uid, &flags, fname_r);
	if (ret != 0)
		return ret;

	/* file exists in index file, but not in dovecot-uidlist anymore. */
	mail_set_expunged(mail);

	/* one reason this could happen is if we delayed opening
	   dovecot-uidlist and we're trying to open a mail that got recently
	   expunged. Let's test this theory first: */
	mail_index_refresh(mbox->box.index);
	view = mail_index_view_open(mbox->box.index);
	exists = mail_index_lookup_seq(view, mail->uid, &seq);
	mail_index_view_close(&view);

	if (exists) {
		/* the message still exists in index. this means there's some
		   kind of a desync, which doesn't get fixed if cur/ mtime is
		   the same as in index. fix this by forcing a resync. */
		(void)maildir_storage_sync_force(mbox, mail->uid);
	}
	return 0;
}
int mailbox_list_index_refresh(struct mailbox_list *list)
{
	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
	struct mail_index_view *view;
	int ret;

	if (ilist->syncing)
		return 0;

	if (mailbox_list_index_index_open(list) < 0)
		return -1;
	if (mail_index_refresh(ilist->index) < 0) {
		mailbox_list_index_set_index_error(list);
		return -1;
	}

	view = mail_index_view_open(ilist->index);
	if (ilist->mailbox_tree == NULL ||
	    mailbox_list_index_need_refresh(ilist, view)) {
		/* refresh list of mailboxes */
		ret = mailbox_list_index_sync(list);
	} else {
		ret = mailbox_list_index_parse(list, view, FALSE);
	}
	mail_index_view_close(&view);
	return ret;
}
void mailbox_list_index_refresh_later(struct mailbox_list *list)
{
	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
	struct mailbox_list_index_header new_hdr;
	struct mail_index_view *view;
	struct mail_index_transaction *trans;

	if (!ilist->has_backing_store)
		return;

	(void)mailbox_list_index_index_open(list);

	view = mail_index_view_open(ilist->index);
	if (!mailbox_list_index_need_refresh(ilist, view)) {
		new_hdr.refresh_flag = 1;

		trans = mail_index_transaction_begin(view,
					MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
		mail_index_update_header_ext(trans, ilist->ext_id,
			offsetof(struct mailbox_list_index_header, refresh_flag),
			&new_hdr.refresh_flag, sizeof(new_hdr.refresh_flag));
		if (mail_index_transaction_commit(&trans) < 0)
			mail_index_mark_corrupted(ilist->index);

	}
Пример #5
0
static int
index_storage_mailbox_update_pvt(struct mailbox *box,
				 const struct mailbox_update *update)
{
	struct mail_index_transaction *trans;
	struct mail_index_view *view;
	int ret;

	if ((ret = mailbox_open_index_pvt(box)) <= 0)
		return ret;

	mail_index_refresh(box->index_pvt);
	view = mail_index_view_open(box->index_pvt);
	trans = mail_index_transaction_begin(view,
					MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
	if (update->min_highest_modseq != 0 &&
	    mail_index_modseq_get_highest(view) < update->min_highest_pvt_modseq) {
		mail_index_modseq_enable(box->index_pvt);
		mail_index_update_highest_modseq(trans,
						 update->min_highest_pvt_modseq);
	}

	if ((ret = mail_index_transaction_commit(&trans)) < 0)
		mailbox_set_index_error(box);
	mail_index_view_close(&view);
	return ret;
}
Пример #6
0
int index_storage_mailbox_update(struct mailbox *box,
				 const struct mailbox_update *update)
{
	const struct mail_index_header *hdr;
	struct mail_index_view *view;
	struct mail_index_transaction *trans;
	int ret;

	if (mailbox_open(box) < 0)
		return -1;

	/* make sure we get the latest index info */
	mail_index_refresh(box->index);
	view = mail_index_view_open(box->index);
	hdr = mail_index_get_header(view);

	trans = mail_index_transaction_begin(view,
					MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
	if (update->uid_validity != 0 &&
	    hdr->uid_validity != update->uid_validity) {
		uint32_t uid_validity = update->uid_validity;

		if (hdr->uid_validity != 0) {
			/* UIDVALIDITY change requires index to be reset */
			mail_index_reset(trans);
		}
		mail_index_update_header(trans,
			offsetof(struct mail_index_header, uid_validity),
			&uid_validity, sizeof(uid_validity), TRUE);
	}
Пример #7
0
static struct mail_index_view *
imapc_mailbox_get_sync_view(struct imapc_mailbox *mbox)
{
	if (mbox->sync_view == NULL)
		mbox->sync_view = mail_index_view_open(mbox->box.index);
	return mbox->sync_view;
}
Пример #8
0
static int
index_list_mailbox_open_unchanged_view(struct mailbox *box,
				       struct mail_index_view **view_r,
				       uint32_t *seq_r)
{
	struct index_mailbox_list *ilist;
	struct mail_index_view *view;
	uint32_t uid, seq;
	int ret;

	ilist = INDEX_LIST_CONTEXT(box->list);

	if (ilist == NULL) {
		/* indexing disabled */
		return 0;
	}

	ret = mailbox_list_index_lookup(ilist->list_sync_view, box->name, &uid);
	if (ret <= 0)
		return ret;

	/* make sure we're synced */
	if (index_list_update_mail_index(ilist, box) < 0)
		return -1;

	/* found from list index. lookup the mail index record for it */
	view = mail_index_view_open(ilist->mail_index);
	if (!mail_index_lookup_seq(view, uid, &seq)) {
		mail_index_view_close(&view);
		return 0;
	}

	T_BEGIN {
		ret = box->v.list_index_has_changed(box, view, seq);
	} T_END;
	if (ret != 0) {
		/* error / mailbox has changed. we'll need to sync it. */
		mail_index_view_close(&view);
		return ret < 0 ? -1 : 0;
	}

	*view_r = view;
	*seq_r = seq;
	return 1;
}
Пример #9
0
static void mbox_save_init_sync(struct mailbox_transaction_context *t)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)t->box;
	struct mbox_save_context *ctx = (struct mbox_save_context *)t->save_ctx;
	const struct mail_index_header *hdr;
	struct mail_index_view *view;

	/* open a new view to get the header. this is required if we just
	   synced the mailbox so we can get updated next_uid. */
	(void)mail_index_refresh(mbox->box.index);
	view = mail_index_view_open(mbox->box.index);
	hdr = mail_index_get_header(view);

	ctx->next_uid = hdr->next_uid;
	ctx->uid_validity = hdr->uid_validity;
	ctx->synced = TRUE;

	mail_index_view_close(&view);
}
Пример #10
0
void index_pop3_uidl_update_exists_finish(struct mailbox_transaction_context *trans)
{
	struct mail_index_view *view;
	struct mailbox_index_pop3_uidl uidl;
	const void *data;
	size_t size;
	bool seen_all_msgs;

	if (trans->highest_pop3_uidl_uid == 0)
		return;

	/* First check that we actually looked at UIDL for all messages.
	   Otherwise we can't say for sure if the newest messages had UIDLs. */
	if (trans->prev_pop3_uidl_tracking_seq !=
	    mail_index_view_get_messages_count(trans->view))
		return;

	/* Just to be sure: Refresh the index and check again. POP3 keeps
	   transactions open for duration of the entire session. Maybe another
	   process already added new mails (and already updated this header).
	   This check is racy, but normally UIDLs aren't added after migration
	   so it's a bit questionable if it's even worth having this check in
	   there. */
	view = mail_index_view_open(trans->box->index);
	seen_all_msgs = mail_index_refresh(trans->box->index) == 0 &&
		trans->prev_pop3_uidl_tracking_seq ==
		mail_index_view_get_messages_count(view);
	mail_index_view_close(&view);
	if (!seen_all_msgs)
		return;

	/* check if we have already the same header */
	mail_index_get_header_ext(trans->view, trans->box->pop3_uidl_hdr_ext_id,
				  &data, &size);
	if (size >= sizeof(uidl)) {
		memcpy(&uidl, data, size);
		if (trans->highest_pop3_uidl_uid == uidl.max_uid_with_pop3_uidl)
			return;
	}
	index_pop3_uidl_set_max_uid(trans->box, trans->itrans,
				    trans->highest_pop3_uidl_uid);
}
Пример #11
0
static uint32_t get_next_file_seq(struct mail_cache *cache)
{
	const struct mail_index_ext *ext;
	struct mail_index_view *view;
	uint32_t file_seq;

	/* make sure we look up the latest reset_id */
	if (mail_index_refresh(cache->index) < 0)
		return -1;

	view = mail_index_view_open(cache->index);
	ext = mail_index_view_get_ext(view, cache->ext_id);
	file_seq = ext != NULL ? ext->reset_id + 1 : (uint32_t)ioloop_time;

	if (cache->hdr != NULL && file_seq <= cache->hdr->file_seq)
		file_seq = cache->hdr->file_seq + 1;
	mail_index_view_close(&view);

	return file_seq != 0 ? file_seq : 1;
}
Пример #12
0
int sdbox_read_header(struct sdbox_mailbox *mbox,
		      struct sdbox_index_header *hdr, bool log_error,
		      bool *need_resize_r)
{
	struct mail_index_view *view;
	const void *data;
	size_t data_size;
	int ret = 0;

	i_assert(mbox->box.opened);

	view = mail_index_view_open(mbox->box.index);
	mail_index_get_header_ext(view, mbox->hdr_ext_id,
				  &data, &data_size);
	if (data_size < SDBOX_INDEX_HEADER_MIN_SIZE &&
	    (!mbox->box.creating || data_size != 0)) {
		if (log_error) {
			mail_storage_set_critical(
				&mbox->storage->storage.storage,
				"sdbox %s: Invalid dbox header size",
				mailbox_get_path(&mbox->box));
		}
		ret = -1;
	} else {
		memset(hdr, 0, sizeof(*hdr));
		memcpy(hdr, data, I_MIN(data_size, sizeof(*hdr)));
		if (guid_128_is_empty(hdr->mailbox_guid))
			ret = -1;
		else {
			/* data is valid. remember it in case mailbox
			   is being reset */
			mail_index_set_ext_init_data(mbox->box.index,
						     mbox->hdr_ext_id,
						     hdr, sizeof(*hdr));
		}
	}
	mail_index_view_close(&view);
	*need_resize_r = data_size < sizeof(*hdr);
	return ret;
}
Пример #13
0
int index_storage_mailbox_open(struct mailbox *box, bool move_to_memory)
{
	struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
	enum mail_index_open_flags index_flags;
	int ret;

	i_assert(!box->opened);

	index_flags = ibox->index_flags;
	if (move_to_memory)
		ibox->index_flags &= ~MAIL_INDEX_OPEN_FLAG_CREATE;

	if (index_storage_mailbox_alloc_index(box) < 0)
		return -1;

	/* make sure mail_index_set_permissions() has been called */
	(void)mailbox_get_permissions(box);

	ret = mail_index_open(box->index, index_flags);
	if (ret <= 0 || move_to_memory) {
		if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
			i_assert(ret <= 0);
			mailbox_set_index_error(box);
			return -1;
		}

		if (mail_index_move_to_memory(box->index) < 0) {
			/* try opening once more. it should be created
			   directly into memory now. */
			if (mail_index_open_or_create(box->index,
						      index_flags) < 0)
				i_panic("in-memory index creation failed");
		}
	}
	if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
		if (mail_index_is_in_memory(box->index)) {
			mail_storage_set_critical(box->storage,
				"Couldn't create index file");
			mail_index_close(box->index);
			return -1;
		}
	}

	if ((box->flags & MAILBOX_FLAG_OPEN_DELETED) == 0) {
		if (mail_index_is_deleted(box->index)) {
			mailbox_set_deleted(box);
			mail_index_close(box->index);
			return -1;
		}
	}

	box->cache = mail_index_get_cache(box->index);
	index_cache_register_defaults(box);
	box->view = mail_index_view_open(box->index);
	ibox->keyword_names = mail_index_get_keywords(box->index);
	ibox->vsize_hdr_ext_id =
		mail_index_ext_register(box->index, "hdr-vsize",
					sizeof(struct index_vsize_header), 0,
					sizeof(uint64_t));

	box->opened = TRUE;

	if ((box->enabled_features & MAILBOX_FEATURE_CONDSTORE) != 0)
		mail_index_modseq_enable(box->index);

	index_thread_mailbox_opened(box);
	hook_mailbox_opened(box);
	return 0;
}
Пример #14
0
static int mdbox_map_open_internal(struct mdbox_map *map, bool create_missing)
{
	enum mail_index_open_flags open_flags;
	struct mailbox_permissions perm;
	int ret = 0;

	if (map->view != NULL) {
		/* already opened */
		return 1;
	}

	mailbox_list_get_root_permissions(map->root_list, &perm);
	mail_index_set_permissions(map->index, perm.file_create_mode,
				   perm.file_create_gid,
				   perm.file_create_gid_origin);

	open_flags = MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY |
		mail_storage_settings_to_index_flags(MAP_STORAGE(map)->set);
	if (create_missing) {
		if ((ret = mdbox_map_mkdir_storage(map)) < 0)
			return -1;
		if (ret > 0) {
			/* storage/ directory already existed.
			   the index should exist also. */
		} else {
			open_flags |= MAIL_INDEX_OPEN_FLAG_CREATE;
		}
	}
	ret = mail_index_open(map->index, open_flags);
	if (ret == 0 && create_missing) {
		/* storage/ already existed, but indexes didn't. we'll need to
		   take extra steps to make sure we won't overwrite any m.*
		   files that may already exist. */
		map->verify_existing_file_ids = TRUE;
		open_flags |= MAIL_INDEX_OPEN_FLAG_CREATE;
		ret = mail_index_open(map->index, open_flags);
	}
	if (ret < 0) {
		mail_storage_set_internal_error(MAP_STORAGE(map));
		mail_index_reset_error(map->index);
		return -1;
	}
	if (ret == 0) {
		/* index not found - for now just return failure */
		i_assert(!create_missing);
		return 0;
	}

	map->view = mail_index_view_open(map->index);
	mdbox_map_cleanup(map);

	if (mail_index_get_header(map->view)->uid_validity == 0) {
		if (mdbox_map_generate_uid_validity(map) < 0 ||
		    mdbox_map_refresh(map) < 0) {
			mail_storage_set_internal_error(MAP_STORAGE(map));
			mail_index_reset_error(map->index);
			mail_index_close(map->index);
			return -1;
		}
	}
	return 1;
}
Пример #15
0
static int mbox_mail_seek(struct index_mail *mail)
{
	struct mbox_transaction_context *t =
		(struct mbox_transaction_context *)mail->mail.mail.transaction;
	struct mail *_mail = &mail->mail.mail;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	enum mbox_sync_flags sync_flags = 0;
	int ret, try;
	bool deleted;

	if (_mail->expunged || mbox->syncing)
		return -1;

	if (_mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) {
		mail_set_aborted(_mail);
		return -1;
	}

	if (mbox->mbox_stream != NULL &&
	    istream_raw_mbox_is_corrupted(mbox->mbox_stream)) {
		/* clear the corruption by forcing a full resync */
		sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC;
	}

	for (try = 0; try < 2; try++) {
		if ((sync_flags & MBOX_SYNC_FORCE_SYNC) != 0) {
			/* dirty offsets are broken. make sure we can sync. */
			mbox_prepare_resync(_mail);
		}
		if (mbox->mbox_lock_type == F_UNLCK) {
			i_assert(t->read_lock_id == 0);
			sync_flags |= MBOX_SYNC_LOCK_READING;
			if (mbox_sync(mbox, sync_flags) < 0)
				return -1;
			t->read_lock_id = mbox_get_cur_lock_id(mbox);
			i_assert(t->read_lock_id != 0);

			/* refresh index file after mbox has been locked to
			   make sure we get only up-to-date mbox offsets. */
			if (mail_index_refresh(mbox->box.index) < 0) {
				mailbox_set_index_error(&mbox->box);
				return -1;
			}

			i_assert(mbox->mbox_lock_type != F_UNLCK);
		} else if (t->read_lock_id == 0) {
			/* file is already locked by another transaction, but
			   we must keep it locked for the entire transaction,
			   so increase the lock counter. */
			if (mbox_lock(mbox, mbox->mbox_lock_type,
				      &t->read_lock_id) < 0)
				i_unreached();
		}

		if (mbox_file_open_stream(mbox) < 0)
			return -1;

		ret = mbox_file_seek(mbox, _mail->transaction->view,
				     _mail->seq, &deleted);
		if (ret > 0) {
			/* success */
			break;
		}
		if (ret < 0) {
			if (deleted)
				mail_set_expunged(_mail);
			return -1;
		}

		/* we'll need to re-sync it completely */
		sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC;
	}
	if (ret == 0) {
		mail_storage_set_critical(&mbox->storage->storage,
			"Losing sync for mail uid=%u in mbox file %s",
			_mail->uid, mailbox_get_path(&mbox->box));
	}
	return 0;
}

static int mbox_mail_get_received_date(struct mail *_mail, time_t *date_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;

	if (index_mail_get_received_date(_mail, date_r) == 0)
		return 0;

	if (mbox_mail_seek(mail) < 0)
		return -1;
	data->received_date =
		istream_raw_mbox_get_received_time(mbox->mbox_stream);
	if (data->received_date == (time_t)-1) {
		/* it's broken and conflicts with our "not found"
		   return value. change it. */
		data->received_date = 0;
	}

	*date_r = data->received_date;
	return 0;
}

static int mbox_mail_get_save_date(struct mail *_mail, time_t *date_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;

	if (index_mail_get_save_date(_mail, date_r) == 0)
		return 0;

	/* no way to know this. save the current time into cache and use
	   that from now on. this works only as long as the index files
	   are permanent */
	data->save_date = ioloop_time;
	*date_r = data->save_date;
	return 0;
}

static int
mbox_mail_get_md5_header(struct index_mail *mail, const char **value_r)
{
	struct mail *_mail = &mail->mail.mail;
	static uint8_t empty_md5[16] =
		{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	const void *ext_data;

	if (mail->data.guid != NULL) {
		*value_r = mail->data.guid;
		return 1;
	}

	mail_index_lookup_ext(_mail->transaction->view, _mail->seq,
			      mbox->md5hdr_ext_idx, &ext_data, NULL);
	if (ext_data != NULL && memcmp(ext_data, empty_md5, 16) != 0) {
		mail->data.guid = p_strdup(mail->mail.data_pool,
					   binary_to_hex(ext_data, 16));
		*value_r = mail->data.guid;
		return 1;
	} else if (mail_index_is_expunged(_mail->transaction->view, _mail->seq)) {
		mail_set_expunged(_mail);
		return -1;
	} else {
		return 0;
	}
}

static int
mbox_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
		      const char **value_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	uoff_t offset;
	bool move_offset;
	int ret;

	switch (field) {
	case MAIL_FETCH_FROM_ENVELOPE:
		if (mbox_mail_seek(mail) < 0)
			return -1;

		*value_r = istream_raw_mbox_get_sender(mbox->mbox_stream);
		return 0;
	case MAIL_FETCH_GUID:
	case MAIL_FETCH_HEADER_MD5:
		if ((ret = mbox_mail_get_md5_header(mail, value_r)) != 0)
			return ret < 0 ? -1 : 0;

		/* i guess in theory the empty_md5 is valid and can happen,
		   but it's almost guaranteed that it means the MD5 sum is
		   missing. recalculate it. */
		if (mbox->mbox_lock_type == F_UNLCK ||
		    mbox->mbox_stream == NULL) {
			offset = 0;
			move_offset = FALSE;
		} else {
			offset = istream_raw_mbox_get_start_offset(mbox->mbox_stream);
			move_offset = TRUE;
		}
		mbox->mbox_save_md5 = TRUE;
		if (mbox_sync(mbox, MBOX_SYNC_FORCE_SYNC |
			      MBOX_SYNC_READONLY) < 0)
			return -1;
		if (move_offset) {
			if (istream_raw_mbox_seek(mbox->mbox_stream,
						  offset) < 0) {
				i_error("mbox %s sync lost during MD5 syncing",
					_mail->box->name);
				return -1;
			}
		}

		if ((ret = mbox_mail_get_md5_header(mail, value_r)) == 0) {
			i_error("mbox %s resyncing didn't save header MD5 values",
				_mail->box->name);
			return -1;
		}
		return ret < 0 ? -1 : 0;
	default:
		break;
	}

	return index_mail_get_special(_mail, field, value_r);
}

static int
mbox_mail_get_next_offset(struct index_mail *mail, uoff_t *next_offset_r)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box;
	struct mail_index_view *view;
	const struct mail_index_header *hdr;
	uint32_t seq;
	int trailer_size;
	int ret = 1;

	*next_offset_r = (uoff_t)-1;

	hdr = mail_index_get_header(mail->mail.mail.transaction->view);
	if (mail->mail.mail.seq > hdr->messages_count) {
		/* we're appending a new message */
		return 0;
	}

	/* We can't really trust trans_view. The next message may already be
	   expunged from it. Also hdr.messages_count may be incorrect there.
	   So refresh the index to get the latest changes and get the next
	   message's offset using a new view. */
	i_assert(mbox->mbox_lock_type != F_UNLCK);
	if (mbox_sync_header_refresh(mbox) < 0)
		return -1;

	view = mail_index_view_open(mail->mail.mail.box->index);
	hdr = mail_index_get_header(view);
	if (!mail_index_lookup_seq(view, mail->mail.mail.uid, &seq))
		i_panic("Message unexpectedly expunged from index");

	if (seq < hdr->messages_count) {
		if (mbox_file_lookup_offset(mbox, view, seq + 1,
					    next_offset_r) <= 0)
			ret = -1;
	} else if (mail->mail.mail.box->input != NULL) {
		/* opened the mailbox as input stream. we can't trust the
		   sync_size, since it's wrong with compressed mailboxes */
		ret = 0;
	} else {
		/* last message, use the synced mbox size */
		trailer_size =
			mbox->storage->storage.set->mail_save_crlf ? 2 : 1;
		*next_offset_r = mbox->mbox_hdr.sync_size - trailer_size;
	}
	mail_index_view_close(&view);
	return ret;
}

static int mbox_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	struct istream *input;
	struct message_size hdr_size;
	uoff_t old_offset, body_offset, body_size, next_offset;

	if (index_mail_get_physical_size(_mail, size_r) == 0)
		return 0;

	/* we want to return the header size as seen by mail_get_stream(). */
	old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
	if (mail_get_stream(_mail, &hdr_size, NULL, &input) < 0)
		return -1;

	/* our header size varies, so don't do any caching */
	if (istream_raw_mbox_get_body_offset(mbox->mbox_stream, &body_offset) < 0) {
		mail_storage_set_critical(_mail->box->storage,
			"mbox %s: Couldn't get body offset for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}

	/* use the next message's offset to avoid reading through the entire
	   message body to find out its size */
	if (mbox_mail_get_next_offset(mail, &next_offset) > 0)
		body_size = next_offset - body_offset;
	else
		body_size = (uoff_t)-1;

	/* verify that the calculated body size is correct */
	if (istream_raw_mbox_get_body_size(mbox->mbox_stream,
					   body_size, &body_size) < 0) {
		mail_storage_set_critical(_mail->box->storage,
			"mbox %s: Couldn't get body size for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}

	data->physical_size = hdr_size.physical_size + body_size;
	*size_r = data->physical_size;

	i_stream_seek(input, old_offset);
	return 0;
}

static int mbox_mail_init_stream(struct index_mail *mail)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box;
	struct istream *raw_stream;
	uoff_t hdr_offset, next_offset;
	int ret;

	if (mbox_mail_seek(mail) < 0)
		return -1;

	ret = mbox_mail_get_next_offset(mail, &next_offset);
	if (ret < 0) {
		if (mbox_mail_seek(mail) < 0)
			return -1;
		ret = mbox_mail_get_next_offset(mail, &next_offset);
		if (ret < 0) {
			i_warning("mbox %s: Can't find next message offset "
				  "for uid=%u", mailbox_get_path(&mbox->box),
				  mail->mail.mail.uid);
		}
	}

	raw_stream = mbox->mbox_stream;
	if (istream_raw_mbox_get_header_offset(raw_stream, &hdr_offset) < 0) {
		mail_storage_set_critical(mbox->box.storage,
			"mbox %s: Couldn't get header offset for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}
	i_stream_seek(raw_stream, hdr_offset);

	if (next_offset != (uoff_t)-1)
		istream_raw_mbox_set_next_offset(raw_stream, next_offset);

	raw_stream = i_stream_create_limit(raw_stream, (uoff_t)-1);
	mail->data.stream =
		i_stream_create_header_filter(raw_stream,
				HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
				mbox_hide_headers, mbox_hide_headers_count,
				*null_header_filter_callback, (void *)NULL);
	i_stream_unref(&raw_stream);
	return 0;
}

static int mbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED,
				struct message_size *hdr_size,
				struct message_size *body_size,
				struct istream **stream_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;

	if (mail->data.stream == NULL) {
		if (mbox_mail_init_stream(mail) < 0)
			return -1;
	}

	return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
}

static void mbox_mail_set_seq(struct mail *_mail, uint32_t seq, bool saving)
{
	struct index_mail *mail = (struct index_mail *)_mail;

	index_mail_set_seq(_mail, seq, saving);
	mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
}

static bool mbox_mail_set_uid(struct mail *_mail, uint32_t uid)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	bool ret;

	ret = index_mail_set_uid(_mail, uid);
	mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
	return ret;
}

struct mail_vfuncs mbox_mail_vfuncs = {
	index_mail_close,
	index_mail_free,
	mbox_mail_set_seq,
	mbox_mail_set_uid,
	index_mail_set_uid_cache_updates,
	index_mail_prefetch,
	index_mail_precache,
	index_mail_add_temp_wanted_fields,

	index_mail_get_flags,
	index_mail_get_keywords,
	index_mail_get_keyword_indexes,
	index_mail_get_modseq,
	index_mail_get_pvt_modseq,
	index_mail_get_parts,
	index_mail_get_date,
	mbox_mail_get_received_date,
	mbox_mail_get_save_date,
	index_mail_get_virtual_size,
	mbox_mail_get_physical_size,
	index_mail_get_first_header,
	index_mail_get_headers,
	index_mail_get_header_stream,
	mbox_mail_get_stream,
	index_mail_get_binary_stream,
	mbox_mail_get_special,
	index_mail_get_real_mail,
	index_mail_update_flags,
	index_mail_update_keywords,
	index_mail_update_modseq,
	index_mail_update_pvt_modseq,
	NULL,
	index_mail_expunge,
	index_mail_set_cache_corrupted,
	index_mail_opened
};