/*
 * Copyright 2016-2018, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * check_btt_info.c -- check BTT Info
 */

#include <stdlib.h>
#include <stdint.h>
#include <endian.h>

#include "out.h"
#include "btt.h"
#include "libpmempool.h"
#include "pmempool.h"
#include "pool.h"
#include "check_util.h"

enum question {
	Q_RESTORE_FROM_BACKUP,
	Q_REGENERATE,
	Q_REGENERATE_CHECKSUM,
	Q_RESTORE_FROM_HEADER
};

/*
 * location_release -- (internal) release check_btt_info_loc allocations
 */
static void
location_release(location *loc)
{
	free(loc->arenap);
	loc->arenap = NULL;
}

/*
 * btt_info_checksum -- (internal) check BTT Info checksum
 */
static int
btt_info_checksum(PMEMpoolcheck *ppc, location *loc)
{
	LOG(3, NULL);

	loc->arenap = calloc(1, sizeof(struct arena));
	if (!loc->arenap) {
		ERR("!calloc");
		ppc->result = CHECK_RESULT_INTERNAL_ERROR;
		CHECK_ERR(ppc, "cannot allocate memory for arena");
		goto error_cleanup;
	}

	/* read the BTT Info header at well known offset */
	if (pool_read(ppc->pool, &loc->arenap->btt_info,
			sizeof(loc->arenap->btt_info), loc->offset)) {
		CHECK_ERR(ppc, "arena %u: cannot read BTT Info header",
			loc->arenap->id);
		ppc->result = CHECK_RESULT_ERROR;
		goto error_cleanup;
	}

	loc->arenap->id = ppc->pool->narenas;

	/* BLK is consistent even without BTT Layout */
	if (ppc->pool->params.type == POOL_TYPE_BLK) {
		int is_zeroed = util_is_zeroed((const void *)
			&loc->arenap->btt_info, sizeof(loc->arenap->btt_info));
		if (is_zeroed) {
			CHECK_INFO(ppc, "BTT Layout not written");
			loc->step = CHECK_STEP_COMPLETE;
			ppc->pool->blk_no_layout = 1;
			location_release(loc);
			check_end(ppc->data);
			return 0;
		}
	}

	/* check consistency of BTT Info */
	if (pool_btt_info_valid(&loc->arenap->btt_info)) {
		CHECK_INFO(ppc, "arena %u: BTT Info header checksum correct",
			loc->arenap->id);
		loc->valid.btti_header = 1;
	} else if (CHECK_IS_NOT(ppc, REPAIR)) {
		CHECK_ERR(ppc, "arena %u: BTT Info header checksum incorrect",
			loc->arenap->id);
		ppc->result = CHECK_RESULT_NOT_CONSISTENT;
		check_end(ppc->data);
		goto error_cleanup;
	}

	return 0;

error_cleanup:
	location_release(loc);
	return -1;
}

/*
 * btt_info_backup -- (internal) check BTT Info backup
 */
static int
btt_info_backup(PMEMpoolcheck *ppc, location *loc)
{
	LOG(3, NULL);

	/* check BTT Info backup consistency */
	const size_t btt_info_size = sizeof(ppc->pool->bttc.btt_info);
	uint64_t btt_info_off = pool_next_arena_offset(ppc->pool, loc->offset) -
		btt_info_size;

	if (pool_read(ppc->pool, &ppc->pool->bttc.btt_info, btt_info_size,
			btt_info_off)) {
		CHECK_ERR(ppc, "arena %u: cannot read BTT Info backup",
			loc->arenap->id);
		goto error;
	}

	/* check whether this BTT Info backup is valid */
	if (pool_btt_info_valid(&ppc->pool->bttc.btt_info)) {
		loc->valid.btti_backup = 1;

		/* restore BTT Info from backup */
		if (!loc->valid.btti_header && CHECK_IS(ppc, REPAIR))
			CHECK_ASK(ppc, Q_RESTORE_FROM_BACKUP, "arena %u: BTT "
				"Info header checksum incorrect.|Restore BTT "
				"Info from backup?", loc->arenap->id);
	}

	/*
	 * if BTT Info backup require repairs it will be fixed in further steps
	 */

	return check_questions_sequence_validate(ppc);

error:
	ppc->result = CHECK_RESULT_ERROR;
	location_release(loc);
	return -1;
}

/*
 * btt_info_from_backup_fix -- (internal) fix BTT Info using its backup
 */
static int
btt_info_from_backup_fix(PMEMpoolcheck *ppc, location *loc, uint32_t question,
	void *ctx)
{
	LOG(3, NULL);

	ASSERTeq(ctx, NULL);
	ASSERTne(loc, NULL);

	switch (question) {
	case Q_RESTORE_FROM_BACKUP:
		CHECK_INFO(ppc,
			"arena %u: restoring BTT Info header from backup",
			loc->arenap->id);

		memcpy(&loc->arenap->btt_info, &ppc->pool->bttc.btt_info,
			sizeof(loc->arenap->btt_info));
		loc->valid.btti_header = 1;
		break;
	default:
		ERR("not implemented question id: %u", question);
	}

	return 0;
}

/*
 * btt_info_gen -- (internal) ask whether try to regenerate BTT Info
 */
static int
btt_info_gen(PMEMpoolcheck *ppc, location *loc)
{
	LOG(3, NULL);

	if (loc->valid.btti_header)
		return 0;

	ASSERT(CHECK_IS(ppc, REPAIR));

	if (!loc->pool_valid.btti_offset) {
		ppc->result = CHECK_RESULT_NOT_CONSISTENT;
		check_end(ppc->data);
		return CHECK_ERR(ppc, "can not find any valid BTT Info");
	}

	CHECK_ASK(ppc, Q_REGENERATE,
		"arena %u: BTT Info header checksum incorrect.|Do you want to "
		"regenerate BTT Info?", loc->arenap->id);

	return check_questions_sequence_validate(ppc);
}

/*
 * btt_info_gen_fix -- (internal) fix by regenerating BTT Info
 */
static int
btt_info_gen_fix(PMEMpoolcheck *ppc, location *loc, uint32_t question,
	void *ctx)
{
	LOG(3, NULL);

	ASSERTeq(ctx, NULL);
	ASSERTne(loc, NULL);

	switch (question) {
	case Q_REGENERATE:
		CHECK_INFO(ppc, "arena %u: regenerating BTT Info header",
			loc->arenap->id);

		/*
		 * We do not have valid BTT Info backup so we get first valid
		 * BTT Info and try to calculate BTT Info for current arena
		 */
		uint64_t arena_size = ppc->pool->set_file->size - loc->offset;
		if (arena_size > BTT_MAX_ARENA)
			arena_size = BTT_MAX_ARENA;

		uint64_t space_left = ppc->pool->set_file->size - loc->offset -
			arena_size;

		struct btt_info *bttd = &loc->arenap->btt_info;
		struct btt_info *btts = &loc->pool_valid.btti;

		btt_info_convert2h(bttd);

		/*
		 * all valid BTT Info structures have the same signature, UUID,
		 * parent UUID, flags, major, minor, external LBA size, internal
		 * LBA size, nfree, info size and data offset
		 */
		memcpy(bttd->sig, btts->sig, BTTINFO_SIG_LEN);
		memcpy(bttd->uuid, btts->uuid, BTTINFO_UUID_LEN);
		memcpy(bttd->parent_uuid, btts->parent_uuid, BTTINFO_UUID_LEN);
		memset(bttd->unused, 0, BTTINFO_UNUSED_LEN);
		bttd->flags = btts->flags;
		bttd->major = btts->major;
		bttd->minor = btts->minor;

		/* other parameters can be calculated */
		if (btt_info_set(bttd, btts->external_lbasize, btts->nfree,
				arena_size, space_left)) {
			CHECK_ERR(ppc, "can not restore BTT Info");
			return -1;
		}

		ASSERTeq(bttd->external_lbasize, btts->external_lbasize);
		ASSERTeq(bttd->internal_lbasize, btts->internal_lbasize);
		ASSERTeq(bttd->nfree, btts->nfree);
		ASSERTeq(bttd->infosize, btts->infosize);
		ASSERTeq(bttd->dataoff, btts->dataoff);
		return 0;

	default:
		ERR("not implemented question id: %u", question);
		return -1;
	}
}

/*
 * btt_info_checksum_retry -- (internal) check BTT Info checksum
 */
static int
btt_info_checksum_retry(PMEMpoolcheck *ppc, location *loc)
{
	LOG(3, NULL);

	if (loc->valid.btti_header)
		return 0;

	btt_info_convert2le(&loc->arenap->btt_info);

	/* check consistency of BTT Info */
	if (pool_btt_info_valid(&loc->arenap->btt_info)) {
		CHECK_INFO(ppc, "arena %u: BTT Info header checksum correct",
			loc->arenap->id);
		loc->valid.btti_header = 1;
		return 0;
	}

	if (CHECK_IS_NOT(ppc, ADVANCED)) {
		ppc->result = CHECK_RESULT_CANNOT_REPAIR;
		CHECK_INFO(ppc, REQUIRE_ADVANCED);
		CHECK_ERR(ppc, "arena %u: BTT Info header checksum incorrect",
			loc->arenap->id);
		check_end(ppc->data);
		goto error_cleanup;
	}

	CHECK_ASK(ppc, Q_REGENERATE_CHECKSUM,
		"arena %u: BTT Info header checksum incorrect.|Do you want to "
		"regenerate BTT Info checksum?", loc->arenap->id);

	return check_questions_sequence_validate(ppc);

error_cleanup:
	location_release(loc);
	return -1;
}

/*
 * btt_info_checksum_fix -- (internal) fix by regenerating BTT Info checksum
 */
static int
btt_info_checksum_fix(PMEMpoolcheck *ppc, location *loc, uint32_t question,
	void *ctx)
{
	LOG(3, NULL);

	ASSERTeq(ctx, NULL);
	ASSERTne(loc, NULL);

	switch (question) {
	case Q_REGENERATE_CHECKSUM:
		util_checksum(&loc->arenap->btt_info, sizeof(struct btt_info),
			&loc->arenap->btt_info.checksum, 1, 0);
		loc->valid.btti_header = 1;
		break;

	default:
		ERR("not implemented question id: %u", question);
	}

	return 0;
}

/*
 * btt_info_backup_checksum -- (internal) check BTT Info backup checksum
 */
static int
btt_info_backup_checksum(PMEMpoolcheck *ppc, location *loc)
{
	LOG(3, NULL);

	ASSERT(loc->valid.btti_header);

	if (loc->valid.btti_backup)
		return 0;

	/* BTT Info backup is not valid so it must be fixed */
	if (CHECK_IS_NOT(ppc, REPAIR)) {
		CHECK_ERR(ppc,
			"arena %u: BTT Info backup checksum incorrect",
			loc->arenap->id);
		ppc->result = CHECK_RESULT_NOT_CONSISTENT;
		check_end(ppc->data);
		goto error_cleanup;
	}

	CHECK_ASK(ppc, Q_RESTORE_FROM_HEADER,
		"arena %u: BTT Info backup checksum incorrect.|Do you want to "
		"restore it from BTT Info header?", loc->arenap->id);

	return check_questions_sequence_validate(ppc);

error_cleanup:
	location_release(loc);
	return -1;
}

/*
 * btt_info_backup_fix -- (internal) prepare restore BTT Info backup from header
 */
static int
btt_info_backup_fix(PMEMpoolcheck *ppc, location *loc, uint32_t question,
	void *ctx)
{
	LOG(3, NULL);

	ASSERTeq(ctx, NULL);
	ASSERTne(loc, NULL);

	switch (question) {
	case Q_RESTORE_FROM_HEADER:
		/* BTT Info backup would be restored in check_write step */
		CHECK_INFO(ppc,
			"arena %u: restoring BTT Info backup from header",
			loc->arenap->id);
		break;

	default:
		ERR("not implemented question id: %u", question);
	}

	return 0;
}

struct step {
	int (*check)(PMEMpoolcheck *, location *);
	int (*fix)(PMEMpoolcheck *, location *, uint32_t, void *);
};

static const struct step steps[] = {
	{
		.check		= btt_info_checksum,
	},
	{
		.check		= btt_info_backup,
	},
	{
		.fix		= btt_info_from_backup_fix,
	},
	{
		.check		= btt_info_gen,
	},
	{
		.fix		= btt_info_gen_fix,
	},
	{
		.check		= btt_info_checksum_retry,
	},
	{
		.fix		= btt_info_checksum_fix,
	},
	{
		.check		= btt_info_backup_checksum,
	},
	{
		.fix		= btt_info_backup_fix,
	},
	{
		.check		= NULL,
		.fix		= NULL,
	},
};

/*
 * step_exe -- (internal) perform single step according to its parameters
 */
static inline int
step_exe(PMEMpoolcheck *ppc, location *loc)
{
	ASSERT(loc->step < ARRAY_SIZE(steps));

	const struct step *step = &steps[loc->step++];

	if (!step->fix)
		return step->check(ppc, loc);

	if (!check_answer_loop(ppc, loc, NULL, 1, step->fix))
		return 0;

	if (check_has_error(ppc->data))
		location_release(loc);

	return -1;
}

/*
 * check_btt_info -- entry point for btt info check
 */
void
check_btt_info(PMEMpoolcheck *ppc)
{
	LOG(3, NULL);

	location *loc = check_get_step_data(ppc->data);
	uint64_t nextoff = 0;

	/* initialize check */
	if (!loc->offset) {
		CHECK_INFO(ppc, "checking BTT Info headers");
		loc->offset = BTT_ALIGNMENT;
		if (ppc->pool->params.type == POOL_TYPE_BLK)
			loc->offset += BTT_ALIGNMENT;

		loc->pool_valid.btti_offset = pool_get_first_valid_btt(
			ppc->pool, &loc->pool_valid.btti, loc->offset, NULL);

		/* Without valid BTT Info we can not proceed */
		if (!loc->pool_valid.btti_offset) {
			if (ppc->pool->params.type == POOL_TYPE_BTT) {
				CHECK_ERR(ppc,
					"can not find any valid BTT Info");
				ppc->result = CHECK_RESULT_NOT_CONSISTENT;
				check_end(ppc->data);
				return;
			}
		} else
			btt_info_convert2h(&loc->pool_valid.btti);
	}

	do {
		/* jump to next offset */
		if (ppc->result != CHECK_RESULT_PROCESS_ANSWERS) {
			loc->offset += nextoff;
			loc->step = 0;
			loc->valid.btti_header = 0;
			loc->valid.btti_backup = 0;
		}

		/* do all checks */
		while (CHECK_NOT_COMPLETE(loc, steps)) {
			if (step_exe(ppc, loc) || ppc->pool->blk_no_layout == 1)
				return;
		}

		/* save offset and insert BTT to cache for next steps */
		loc->arenap->offset = loc->offset;
		loc->arenap->valid = true;
		check_insert_arena(ppc, loc->arenap);
		nextoff = le64toh(loc->arenap->btt_info.nextoff);

	} while (nextoff > 0);
}
