/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "mail-cache.h"
#include "mail-index-modseq.h"
#include "mailbox-list-private.h"
#include "mailbox-recent-flags.h"
#include "index-storage.h"
#include "index-rebuild.h"

static void
index_index_copy_vsize(struct index_rebuild_context *ctx,
		       struct mail_index_view *view,
		       uint32_t old_seq, uint32_t new_seq)
{
	const void *data;
	bool expunged;

	mail_index_lookup_ext(view, old_seq, ctx->box->mail_vsize_ext_id,
			      &data, &expunged);
	if (data != NULL && !expunged) {
		mail_index_update_ext(ctx->trans, new_seq,
				      ctx->box->mail_vsize_ext_id, data, NULL);
	}
}

static void
index_index_copy_cache(struct index_rebuild_context *ctx,
		       struct mail_index_view *view,
		       uint32_t old_seq, uint32_t new_seq)
{
	struct mail_index_map *map;
	const void *data;
	uint32_t reset_id = 0;
	bool expunged;

	if (ctx->cache_ext_id == (uint32_t)-1)
		return;

	mail_index_lookup_ext_full(view, old_seq, ctx->cache_ext_id,
				   &map, &data, &expunged);
	if (expunged)
		return;

	if (!mail_index_ext_get_reset_id(view, map, ctx->cache_ext_id,
					 &reset_id) || reset_id == 0)
		return;

	if (!ctx->cache_used) {
		/* set reset id */
		ctx->cache_used = TRUE;
		ctx->cache_reset_id = reset_id;
		mail_index_ext_reset(ctx->trans, ctx->cache_ext_id,
				     ctx->cache_reset_id, TRUE);
	}
	if (ctx->cache_reset_id == reset_id) {
		mail_index_update_ext(ctx->trans, new_seq,
				      ctx->cache_ext_id, data, NULL);
	}
}

static void
index_index_copy_from_old(struct index_rebuild_context *ctx,
			  struct mail_index_view *view,
			  uint32_t old_seq, uint32_t new_seq)
{
	struct mail_index *index = mail_index_view_get_index(view);
	const struct mail_index_record *rec;
	ARRAY_TYPE(keyword_indexes) old_keywords;
	struct mail_keywords *kw;
	uint64_t modseq;

	/* copy flags */
	rec = mail_index_lookup(view, old_seq);
	mail_index_update_flags(ctx->trans, new_seq,
				MODIFY_REPLACE, rec->flags);

	/* copy keywords */
	t_array_init(&old_keywords, 32);
	mail_index_lookup_keywords(view, old_seq, &old_keywords);
	kw = mail_index_keywords_create_from_indexes(index, &old_keywords);
	mail_index_update_keywords(ctx->trans, new_seq, MODIFY_REPLACE, kw);
	mail_index_keywords_unref(&kw);

	/* copy modseq */
	modseq = mail_index_modseq_lookup(view, old_seq);
	mail_index_update_modseq(ctx->trans, new_seq, modseq);

	index_index_copy_vsize(ctx, view, old_seq, new_seq);
	index_index_copy_cache(ctx, view, old_seq, new_seq);
}

void index_rebuild_index_metadata(struct index_rebuild_context *ctx,
				  uint32_t new_seq, uint32_t uid)
{
	uint32_t old_seq;

	if (mail_index_lookup_seq(ctx->view, uid, &old_seq)) {
		/* the message exists in the old index.
		   copy the metadata from it. */
		index_index_copy_from_old(ctx, ctx->view, old_seq, new_seq);
	} else if (ctx->backup_view != NULL &&
		   mail_index_lookup_seq(ctx->backup_view, uid, &old_seq)) {
		/* copy the metadata from backup index. */
		index_index_copy_from_old(ctx, ctx->backup_view,
					  old_seq, new_seq);
	}
}

static void
index_rebuild_header(struct index_rebuild_context *ctx,
		     index_rebuild_generate_uidvalidity_t *gen_uidvalidity)
{
	const struct mail_index_header *hdr, *backup_hdr, *trans_hdr;
	struct mail_index *index = mail_index_view_get_index(ctx->view);
	struct mail_index_modseq_header modseq_hdr;
	struct mail_index_view *trans_view;
	uint32_t uid_validity, next_uid, first_recent_uid;
	uint64_t modseq;

	hdr = mail_index_get_header(ctx->view);
	backup_hdr = ctx->backup_view == NULL ? NULL :
		mail_index_get_header(ctx->backup_view);
	trans_view = mail_index_transaction_open_updated_view(ctx->trans);
	trans_hdr = mail_index_get_header(trans_view);

	/* set uidvalidity */
	if (hdr->uid_validity != 0)
		uid_validity = hdr->uid_validity;
	else if (backup_hdr != NULL && backup_hdr->uid_validity != 0)
		uid_validity = backup_hdr->uid_validity;
	else
		uid_validity = gen_uidvalidity(ctx->box->list);
	mail_index_update_header(ctx->trans,
		offsetof(struct mail_index_header, uid_validity),
		&uid_validity, sizeof(uid_validity), TRUE);

	/* set next-uid */
	if (hdr->next_uid != 0)
		next_uid = hdr->next_uid;
	else if (backup_hdr != NULL && backup_hdr->next_uid != 0)
		next_uid = backup_hdr->next_uid;
	else
		next_uid = 1;
	if (next_uid > trans_hdr->next_uid) {
		mail_index_update_header(ctx->trans,
			offsetof(struct mail_index_header, next_uid),
			&next_uid, sizeof(next_uid), FALSE);
	}

	/* set first_recent_uid */
	first_recent_uid = hdr->first_recent_uid;
	if (backup_hdr != NULL &&
	    backup_hdr->first_recent_uid > first_recent_uid &&
	    backup_hdr->first_recent_uid <= next_uid)
		first_recent_uid = backup_hdr->first_recent_uid;
	first_recent_uid = I_MIN(first_recent_uid, next_uid);
	mail_index_update_header(ctx->trans,
		offsetof(struct mail_index_header, first_recent_uid),
		&first_recent_uid, sizeof(first_recent_uid), FALSE);

	/* set highest-modseq */
	i_zero(&modseq_hdr);
	modseq_hdr.highest_modseq = mail_index_modseq_get_highest(ctx->view);
	if (ctx->backup_view != NULL) {
		modseq = mail_index_modseq_get_highest(ctx->backup_view);
		if (modseq_hdr.highest_modseq < modseq)
			modseq_hdr.highest_modseq = modseq;
	}
	mail_index_update_header_ext(ctx->trans, index->modseq_ext_id,
				     0, &modseq_hdr, sizeof(modseq_hdr));
	mail_index_view_close(&trans_view);
}

static void
index_rebuild_box_preserve_header(struct index_rebuild_context *ctx,
				  uint32_t ext_id)
{
	const void *hdr;
	size_t hdr_size;

	mail_index_get_header_ext(ctx->view, ext_id, &hdr, &hdr_size);
	if (hdr_size == 0 && ctx->backup_view != NULL) {
		mail_index_get_header_ext(ctx->backup_view, ext_id,
					  &hdr, &hdr_size);
	}
	if (hdr_size == 0)
		return;
	mail_index_update_header_ext(ctx->trans, ext_id, 0, hdr, hdr_size);
}

struct index_rebuild_context *
index_index_rebuild_init(struct mailbox *box, struct mail_index_view *view,
			 struct mail_index_transaction *trans)
{
	struct index_rebuild_context *ctx;
	const char *index_dir, *backup_path;
	enum mail_index_open_flags open_flags = MAIL_INDEX_OPEN_FLAG_READONLY;

	/* Rebuilding really should be done locked so multiple processes won't
	   try to rebuild concurrently. Also at the end of rebuild cache
	   purging requires this lock. */
	i_assert(mail_index_is_locked(view->index));

	ctx = i_new(struct index_rebuild_context, 1);
	ctx->box = box;
	ctx->view = view;
	ctx->trans = trans;
	mail_index_reset(ctx->trans);
	mailbox_recent_flags_reset(box);
	(void)mail_index_ext_lookup(box->index, "cache", &ctx->cache_ext_id);

	/* open cache and read the caching decisions. */
	(void)mail_cache_open_and_verify(ctx->box->cache);

	/* if backup index file exists, try to use it */
	index_dir = mailbox_get_index_path(box);
	backup_path = t_strconcat(box->index_prefix, ".backup", NULL);
	ctx->backup_index = mail_index_alloc(box->event,
					     index_dir, backup_path);

#ifndef MMAP_CONFLICTS_WRITE
	if (box->storage->set->mmap_disable)
#endif
		open_flags |= MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE;
	mail_index_set_lock_method(ctx->backup_index,
				   box->storage->set->parsed_lock_method,
				   UINT_MAX);
	if (mail_index_open(ctx->backup_index, open_flags) <= 0)
		mail_index_free(&ctx->backup_index);
	else
		ctx->backup_view = mail_index_view_open(ctx->backup_index);
	return ctx;
}

void index_index_rebuild_deinit(struct index_rebuild_context **_ctx,
				index_rebuild_generate_uidvalidity_t *cb)
{
	struct index_rebuild_context *ctx = *_ctx;

	*_ctx = NULL;

	/* initialize cache file with the old field decisions */
	(void)mail_cache_purge_with_trans(ctx->box->cache, ctx->trans,
					  (uint32_t)-1, "rebuilding index");
	index_rebuild_header(ctx, cb);
	index_rebuild_box_preserve_header(ctx, ctx->box->box_name_hdr_ext_id);
	index_rebuild_box_preserve_header(ctx, ctx->box->box_last_rename_stamp_ext_id);
	index_rebuild_box_preserve_header(ctx, ctx->box->pop3_uidl_hdr_ext_id);
	if (ctx->backup_index != NULL) {
		mail_index_view_close(&ctx->backup_view);
		mail_index_close(ctx->backup_index);
		mail_index_free(&ctx->backup_index);
	}
	i_free(ctx);
}
