/*
 * The main reconfiguration routine.
 *
 *               (C) 2000 by Jakob Oestergaard
 *
 * This source is covered by the GNU GPL, the same as all Linux kernel
 * sources.
 *
 */

#include "raidreconf.h"
#include "rrc_common.h"
#include <stdio.h>

mdu_version_t ver;
int test = 0;
int algorithm_check = 0;

#ifndef MAX
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

#define MAX_RECONF_BLKS_PER_CHUNK 10
#define MIN_MEM_KB (1024*10)
#define MAX_MEM_KB (1024*1024*2)
#define MAX_WISHES 10000

level_driver_t *source_driver;
level_driver_t *sink_driver;
md_cfg_entry_t *old_md_cfg;
md_cfg_entry_t *new_md_cfg;
rrc_disk_t *old_rrc_cfg;
rrc_disk_t *new_rrc_cfg;

unsigned long source_blocks = 0, sink_blocks = 0;

/* Find gcd(a,b) using Euclid's Algorithm */
static unsigned
euclid (unsigned a, unsigned b)
{
	unsigned small = MIN (a, b);
	unsigned large = MAX (a, b);

	while (small != large) {
		unsigned nnew = large - small;

		small = MIN (small, nnew);
		large = MAX (small, nnew);
	}
	return small;
}

/* Find out how many of the black ones is in the system */
static const char *
getphysmem (unsigned *mkb)
{
	FILE *mf = fopen ("/proc/meminfo", "r");

	if (!mf)
		return "Couldn't open /proc/meminfo for reading!";
	assert (mkb);

	while (!feof (mf)) {
		char lbuf[1024];
		int rc;

		if (!fgets (lbuf, 1024, mf)) {
			fclose (mf);
			return "Cannot read from /proc/meminfo";
		}
		if (!strncmp (lbuf, "MemTotal:", 9)) {
			rc = sscanf (lbuf, "MemTotal:\t %u", mkb);
			fclose (mf);
			if (rc != 1)
				return
				    "Parse error reading memory from /proc/meminfo";
			return 0;
		}
	}
	fclose (mf);
	return "Couldn't parse /proc/meminfo!";
}

/* Allocate the source_disk free map */
static const char *
alloc_source_disk_map (void)
{
	unsigned int sd;

	assert (reconf_block_size);
	source_disk_free_map =

	    (char **) malloc (sizeof (char *) *
			      old_md_cfg->array.param.nr_disks);
	if (!source_disk_free_map)
		return "Cannot allocate source_disk_free_map";

	for (sd = 0; sd != old_md_cfg->array.param.nr_disks; sd++) {
		/* initialize .reconf_blocks in rrc structure */
		old_rrc_cfg[sd].reconf_blocks = old_rrc_cfg[sd].blocks;
#if 0
		old_rrc_cfg[sd].reconf_blocks = old_rrc_cfg[sd].chunks
		    * (old_md_cfg->array.param.chunk_size / MD_BLK_SIZ)
		    / reconf_block_size;
#endif

		/* We wish to allocate one bit for each reconf. block */
		source_disk_free_map[sd] =
		    (char *) malloc ((old_rrc_cfg[sd].reconf_blocks + 7) /
				     8);
		if (!source_disk_free_map[sd])
			return
			    "Cannot allocate disk free map for one disk";

		/* Initialize */
		memset (source_disk_free_map[sd], 0,
			(old_rrc_cfg[sd].reconf_blocks + 7) / 8);
	}

	/* Initialize the sink disk .reconf_blocks as well */
	for (sd = 0; sd != new_md_cfg->array.param.nr_disks; sd++) {
		new_rrc_cfg[sd].reconf_blocks = new_rrc_cfg[sd].blocks;
#if 0
		new_rrc_cfg[sd].reconf_blocks = new_rrc_cfg[sd].chunks
		    * (new_md_cfg->array.param.chunk_size / MD_BLK_SIZ)
		    / reconf_block_size;
#endif
	}

	return 0;
}


/* The main thing
 */
const char *
run_reconfiguration (void)
{
	const char *ret;
	unsigned physmem;
	int source_complete = 0;
	time_t last_p = 0;
	char msgbuf[2048];

	assert (source_driver);
	assert (sink_driver);
	assert (old_md_cfg);
	assert (new_md_cfg);
	assert (old_rrc_cfg);
	assert (new_rrc_cfg);

	/* Find block size */
	reconf_block_size =
	    euclid (old_md_cfg->array.param.chunk_size / MD_BLK_SIZ,
		    new_md_cfg->array.param.chunk_size / MD_BLK_SIZ);
	if (!reconf_block_size
	    || (old_md_cfg->array.param.chunk_size / MD_BLK_SIZ /
		reconf_block_size > MAX_RECONF_BLKS_PER_CHUNK)
	    || (new_md_cfg->array.param.chunk_size / MD_BLK_SIZ /
		reconf_block_size > MAX_RECONF_BLKS_PER_CHUNK)) {
		fprintf (stderr,
			 "Old array chunk size is %u KB, new array chunk size is %u KB, gcd is %lu KB, this is not good.\n",
			 old_md_cfg->array.param.chunk_size / MD_BLK_SIZ,
			 new_md_cfg->array.param.chunk_size / MD_BLK_SIZ,
			 reconf_block_size);
		return "Wild array configuration or internal error.";
	}
	fprintf (stderr,
		 "Using %lu Kbyte blocks to move from %i Kbyte chunks to %i Kbyte chunks.\n",
		 reconf_block_size,
		 old_md_cfg->array.param.chunk_size / MD_BLK_SIZ,
		 new_md_cfg->array.param.chunk_size / MD_BLK_SIZ);

	/* Allocate source disk map and initialize sink disks .reconf_blocks as well */
	if ((ret = alloc_source_disk_map ()))
		return ret;

	/* Dimension wish list according to phys. memory in the system */
	if ((ret = getphysmem (&physmem)))
		return ret;
	if (physmem < MIN_MEM_KB || physmem > MAX_MEM_KB)
		return "Physical memory in system seems unreasonable.";

	fprintf (stderr, "Detected %u KB of physical memory in system\n",
		 physmem);

	/* Let's be just slightly nice to the system, it should have it's hands full soon anyway */
	max_wishes =
	    physmem / 3 / (reconf_block_size + sizeof (wish_t) +
			   sizeof (fulfilled_t));
	/* We limit the maximum number of wishes, because we have some inefficient searches going
	   on in the request merging code... */
	if (max_wishes > MAX_WISHES)
		max_wishes = MAX_WISHES;

	fprintf (stderr,
		 "A maximum of %u outstanding requests is allowed\n",
		 max_wishes);

	/* Initialize drivers */

	/* set up source first, so that sink driver can have it's # of blocks */
	if (
	    (ret =
	     source_driver->initialize (source_driver->priv, old_md_cfg,
					old_rrc_cfg,
					&source_blocks))) return ret;

	if ((ret = sink_driver->initialize (sink_driver->priv, new_md_cfg,
					    new_rrc_cfg, &sink_blocks)))
		return ret;

	if (source_blocks > sink_blocks) {
		sprintf (msgbuf,
			 "I will SHRINK your old device %s of %lu blocks\n"
			 "to a new device %s of %lu blocks\n"
			 "using a block-size of %lu KB",
			 old_md_cfg->md_name, source_blocks,
			 new_md_cfg->md_name, sink_blocks,
			 reconf_block_size);
	}
	else if (source_blocks < sink_blocks) {
		sprintf (msgbuf,
			 "I will grow your old device %s of %lu blocks\n"
			 "to a new device %s of %lu blocks\n"
			 "using a block-size of %lu KB",
			 old_md_cfg->md_name, source_blocks,
			 new_md_cfg->md_name, sink_blocks,
			 reconf_block_size);
	}
	else {
		sprintf (msgbuf,
			 "I will convert your old device %s of %lu blocks\n"
			 "to a new device %s of same size\n"
			 "using a block-size of %lu KB",
			 old_md_cfg->md_name, source_blocks,
			 new_md_cfg->md_name, reconf_block_size);
	}
	confirm (msgbuf);

	fprintf (stderr,
		 "Converting %lu block device to %lu block device\n",
		 source_blocks, sink_blocks);

	if (
	    (ret =
	     setup_free_blocks (old_md_cfg->array.param.nr_disks,
				old_rrc_cfg))) return ret;

	if ((ret = initialize_unique_disks ()))
		return ret;

	/*
	 * First we mark all blocks the sink won't need as free in the source
	 */
	source_driver->free_blocks_above_gblock (source_driver->priv,
						 sink_blocks);

	/*
	 * Then we unfree all blocks in the sink, to make sure they are actually
	 * requested by the sink requester
	 */
	sink_driver->unfree_all_blocks (sink_driver->priv);

	/* Now start the show ! */
	while (!source_complete) {
		driver_status_t drc;
		time_t now = time (0);

		if (now - last_p) {
			last_p = now;
			/* progress is the number of free blocks / the number of blocks in the source */
			hash_progress (nr_free_blocks (), source_blocks);
		}

		drc = sink_driver->request_blocks (sink_driver->priv);
		if (drc == LDR_FAILED)
			return
			    "Source driver failed while reading blocks!";
		else if (drc == LDR_DONE)
			source_complete = 1;

		fulfill_wishes (!source_complete);

		if ((ret = generic_write_blocks (1))) {
			fprintf (stderr,
				 "Generic writer returned failure.\n");
			return ret;
		}
	}

	assert (!nr_wishes_left ());

	/*
	 * Now we mark all blocks the sink won't need as free in the source,
	 * this is needed for the following consistency check to succeed
	 */
	source_driver->free_blocks_above_gblock (source_driver->priv,
						 sink_blocks);

#if 0
	if (nr_free_blocks () != source_blocks) {
		fprintf (stderr,
			 "\nA mismatch between the number of freed blocks (%lu) and the number of source \n"
			 "blocks (%lu) was detected.  Printing debug info...\n",
			 nr_free_blocks (), source_blocks);

		debug_print_nonfree_blocks ();

		return "Internal error";
	}
#endif

	hash_progress (nr_free_blocks (), source_blocks);

	fprintf (stderr, "\nSource drained, flushing sink.\n");
	generic_write_blocks (0);

	assert (!nr_gifts_left ());

	if (!test) {
		fprintf (stderr,
			 "Reconfiguration succeeded, will update superblocks...\n");

		if ((ret = sink_driver->update_super (new_md_cfg)))
			return ret;
	}
	else {
		fprintf (stderr,
			 "Because of --test switch we will not update the superblocks\n");
	}

	print_common_stats ();

	fprintf (stderr,
		 "Congratulations, your array has been reconfigured,\n"
		 "and no errors seem to have occured.\n");

	return 0;
}

void
confirm (const char *s)
{
	char buf[10];

	printf ("---------------------------------------------------\n"
		"%s\nIs this what you want? (yes/no): ", s);
	fflush (stdout);
	if (test) {
		printf (" (assuming yes, because of --test switch)\n");
	}
	else {
		fscanf (stdin, "%4s", buf);
		if (strcmp (buf, "yes")) {
			exit (1);
		}
	}
}

void
hash_progress (unsigned long now, unsigned long total)
{
#define PBLEN 44
	char buf[PBLEN + 1];
	unsigned long li;
	unsigned long i;
	static int lstate = 0;
	char lead[] = { '/', '-', '\\', '|' };

	if (now > total)
		now = total;
	i = li = now * PBLEN / total;
	lstate++;
	lstate &= 3;
	memset (buf, ' ', PBLEN);
	buf[PBLEN] = 0;
	while (i)
		buf[--i] = '#';
	printf ("\rWorking (%c) [%08lu/%08lu] [%s] ", lead[lstate], now,
		total, buf);
	fflush (stdout);
#undef PBLEN
}

