/*
 * Copyright 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.
 */

/*
 * obj_verify.c -- tool for creating and verifying a pmemobj pool
 */

#include <stddef.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "libpmemobj.h"
#include "set.h"
#include "os.h"

#define SIGNATURE_LEN 10
#define NUMBER_LEN 10
#define FILL_SIZE 245 /* so that size of one record is 1024 bytes */
#define SKIP_OFFSET offsetof(struct data_s, checksum)

static const char *Signature = "OBJ_VERIFY";

POBJ_LAYOUT_BEGIN(obj_verify);
POBJ_LAYOUT_ROOT(obj_verify, struct root_s);
POBJ_LAYOUT_ROOT(obj_verify, struct data_s);
POBJ_LAYOUT_END(obj_verify);

struct data_s {
	char signature[SIGNATURE_LEN];
	char number_str[NUMBER_LEN];
	uint64_t number;
	uint32_t fill[FILL_SIZE];
	uint64_t checksum;
};

struct root_s {
	uint64_t count;
};

static void
fill_data_s(struct data_s *rec, uint64_t number)
{
	memcpy(rec->signature, Signature, sizeof(rec->signature));
	snprintf(rec->number_str, NUMBER_LEN, "%09lu", number);
	rec->number = number;

	for (int i = 0; i < FILL_SIZE; i++)
		rec->fill[i] = (uint32_t)rand();

	util_checksum(rec, sizeof(*rec), &rec->checksum,
			1 /* insert */, SKIP_OFFSET);
}

static int
alloc_objs(PMEMobjpool *pop, TOID(struct root_s) root, unsigned cnt,
		unsigned class_id)
{
	int aborted = 0;

	TX_BEGIN(pop) {
		TX_ADD_FIELD(root, count);

		for (unsigned i = 0; i < cnt; ++i) {
			PMEMoid oid = pmemobj_tx_xalloc(sizeof(struct data_s),
					0,
					POBJ_CLASS_ID(class_id));
			fill_data_s(pmemobj_direct(oid),
					D_RW(root)->count++);
		}
	} TX_ONABORT {
		aborted = 1;
	} TX_END

	return aborted;
}

/*
 * do_create -- (internal) create a pool to be verified
 */
static void
do_create(const char *path, const char *layout)
{
	struct pobj_alloc_class_desc class;
	PMEMobjpool *pop;
	uint64_t count;

	srand((unsigned int)time(NULL));

	if ((pop = pmemobj_create(path, layout, 0,
						S_IWUSR | S_IRUSR)) == NULL) {
		if (errno != EEXIST) {
			out("!%s: pmemobj_create: %s",
				path, pmemobj_errormsg());
			exit(-1);
		}

		if ((pop = pmemobj_open(path, layout)) == NULL) {
			out("!%s: pmemobj_open: %s",
				path, pmemobj_errormsg());
			exit(-1);
		}
	}

	TOID(struct root_s) root = POBJ_ROOT(pop, struct root_s);

	class.header_type = POBJ_HEADER_NONE;
	class.unit_size = sizeof(struct data_s);
	class.alignment = 0;
	class.units_per_block = 1000;

	if (pmemobj_ctl_set(pop, "heap.alloc_class.new.desc", &class) != 0) {
		pmemobj_close(pop);
		out("!pmemobj_ctl_set: %s", path);
		exit(-1);
	}

	out("create(%s): allocating records in the pool ...", path);

	count = D_RO(root)->count;

	while (alloc_objs(pop, root, class.units_per_block,
			class.class_id) == 0)
		;

	count = D_RO(root)->count - count;
	if (count) {
		out("create(%s): allocated %lu records (of size %zu)",
			path, count, sizeof(struct data_s));
	} else {
		out("create(%s): pool is full", path);
	}

	pmemobj_close(pop);
}


/*
 * do_verify -- (internal) verify a poolset
 */
static void
do_verify(const char *path, const char *layout)
{
	PMEMobjpool *pop;
	PMEMoid oid;
	uint64_t count = 0;
	int error = 0;

	if ((pop = pmemobj_open(path, layout)) == NULL) {
		out("!%s: pmemobj_open: %s",
			path, pmemobj_errormsg());
		exit(-1);
	}

	TOID(struct root_s) root = POBJ_ROOT(pop, struct root_s);
	TOID(struct data_s) rec;

	POBJ_FOREACH(pop, oid) {
		TOID_ASSIGN(rec, oid);
		if (!util_checksum(D_RW(rec), sizeof(*D_RW(rec)),
					&D_RW(rec)->checksum,
					0 /* verify */, SKIP_OFFSET)) {
			out("verify(%s): incorrect record: %s (#%lu)",
				path, D_RW(rec)->signature, count);
			error = 1;
			break;
		}

		count++;
	}

	if (D_RO(root)->count != count) {
		out(
			"verify(%s): incorrect number of records (is: %lu, should be: %lu)",
			path, count, D_RO(root)->count);
		error = 1;
	}

	pmemobj_close(pop);

	if (error) {
		out("verify(%s): pool file contains error", path);
		exit(-1);
	}

	out(
		"verify(%s): pool file successfully verified (%lu records of size %zu)",
		path, count, sizeof(struct data_s));
}

int
main(int argc, char *argv[])
{
	util_init();
	out_init("OBJ_VERIFY", "OBJ_VERIFY", "", 1, 0);

	if (argc < 4) {
		out("Usage: %s <obj_pool> <layout> <op:c|v>\n"
		    "Options:\n"
		    "   c - create\n"
		    "   v - verify\n",
		    argv[0]);
		exit(-1);
	}

	const char *path = argv[1];
	const char *layout = argv[2];
	const char *op;

	/*
	 * This application can be very time-consuming
	 * when it is run on an non-pmem filesystem,
	 * so let's set PMEM_IS_PMEM_FORCE to 1 for this case.
	 */
	os_setenv("PMEM_IS_PMEM_FORCE", "1", 1 /* overwrite */);

	/* go through all arguments one by one */
	for (int arg = 3; arg < argc; arg++) {
		op = argv[arg];

		if (op[1] != '\0') {
			out("op must be c or v (c=create, v=verify)");
			exit(-1);
		}

		switch (op[0]) {
		case 'c': /* create and verify (no debug) */
			do_create(path, layout);
			break;

		case 'v': /* verify (no debug) */
			do_verify(path, layout);
			break;

		default:
			out("op must be c or v (c=create, v=verify)");
			exit(-1);
			break;
		}
	}

	out_fini();

	return 0;
}
