/*
 * Common interface for the level drivers
 *
 * (C) 2000 by Jakob Oestergaard
 *
 * This source is covered by the GNU GPL, the same as all Linux kernel
 * sources.
 */

#include "rrc_common.h"
#include "raidreconf.h"

typedef struct wishlist_diskid_t {
	int source_disk_id;
	wish_t *wish_list;
	struct wishlist_diskid_t *next;
} wishlist_diskid_t;

typedef struct giftlist_diskid_t {
	int sink_disk_id;
	fulfilled_t *gift_list;
	struct giftlist_diskid_t *next;
} giftlist_diskid_t;

unsigned max_wishes = 0;
unsigned long reconf_block_size = 0;

wishlist_diskid_t *wish_lists = 0;
giftlist_diskid_t *gift_lists = 0;

static unsigned wish_list_length = 0;
static unsigned gift_list_length = 0;

static unsigned long nr_wishes_hooked = 0;
static unsigned long nr_gifts_hooked = 0;
static unsigned long nr_max_wishes_hooked = 0;
static unsigned long nr_max_gifts_hooked = 0;

/* Stuff for OOM handling... */
static int is_out_of_memory = 0;

#define MALLOC_TRY_SIZE (sizeof(wish_t) + sizeof(fulfilled_t) + reconf_block_size)

/* Free block management */
char **source_disk_free_map = 0;
unsigned long *source_disk_free_size = 0;
unsigned long *nr_free_disk_blocks = 0;

/* Some disk management as well */
int nr_unique_disks = 0;
rrc_disk_t **global_disk_rrc = 0;

/* Stats */
static unsigned long free_friends_depth = 0;


#define GOOD_READ_LENGTH 128


const char *
initialize_unique_disks (void)
{
	int dsk;

	global_disk_rrc =
	    (rrc_disk_t **) malloc (sizeof (rrc_disk_t *) *
				    (old_md_cfg->array.param.nr_disks +
				     new_md_cfg->array.param.nr_disks));
	if (!global_disk_rrc)
		return "Out of memory allocating global disk rrc array";
	for (dsk = 0; dsk != old_md_cfg->array.param.nr_disks; dsk++) {
		int rdsk;

		for (rdsk = nr_unique_disks; rdsk; rdsk--)
			if (global_disk_rrc[rdsk - 1]->disk_id ==
			    old_rrc_cfg[dsk].disk_id)
				break;
		if (!rdsk)
			global_disk_rrc[nr_unique_disks++] =
			    old_rrc_cfg + dsk;
	}
	for (dsk = 0; dsk != new_md_cfg->array.param.nr_disks; dsk++) {
		int rdsk;

		for (rdsk = nr_unique_disks; rdsk; rdsk--)
			if (global_disk_rrc[rdsk - 1]->disk_id ==
			    new_rrc_cfg[dsk].disk_id)
				break;
		if (!rdsk)
			global_disk_rrc[nr_unique_disks++] =
			    new_rrc_cfg + dsk;
	}
	fprintf (stderr, "%i unique disks detected.\n", nr_unique_disks);
	return 0;
}

void
print_common_stats (void)
{
	fprintf (stderr, "Maximum friend-freeing depth: %9lu\n",
		 free_friends_depth);
	fprintf (stderr, "Total wishes hooked:          %9lu\n",
		 nr_wishes_hooked);
	fprintf (stderr, "Maximum wishes hooked:        %9lu\n",
		 nr_max_wishes_hooked);
	fprintf (stderr, "Total gifts hooked:           %9lu\n",
		 nr_gifts_hooked);
	fprintf (stderr, "Maximum gifts hooked:         %9lu\n",
		 nr_max_gifts_hooked);
}

/* Utility routines for wish_list and gift_list handling */

wish_t *
wish_list_source_diskid (int diskid)
{
	wishlist_diskid_t *wlb;

	for (wlb = wish_lists; wlb; wlb = wlb->next)
		if (wlb->source_disk_id == diskid)
			break;
	if (!wlb)
		return 0;
	return wlb->wish_list;
}

fulfilled_t *
gift_list_sink_diskid (int diskid)
{
	giftlist_diskid_t *flb;

	for (flb = gift_lists; flb; flb = flb->next)
		if (flb->sink_disk_id == diskid)
			break;
	if (!flb)
		return 0;
	return flb->gift_list;
}

void
hook_wish (wish_t * wish)
{
	wishlist_diskid_t *wlb;

	for (wlb = wish_lists; wlb; wlb = wlb->next)
		if (wlb->source_disk_id == wish->source_disk_id)
			break;
	if (!wlb) {
		wishlist_diskid_t *nwl =
		    (wishlist_diskid_t *)

		    malloc (sizeof (wishlist_diskid_t));
		if (!nwl) {
			/* XXX: Implement a safe_malloc() wish emergency pre-allocated pool */
			fprintf (stderr,
				 "Out of memory where we must not fail. Hope you didn't need those data anyway...\n");
			abort ();
		}
		/* hook wish */
		wish->next = 0;
		nwl->wish_list = wish;
		wish_list_length++;
		/* hook wish list */
		nwl->source_disk_id = wish->source_disk_id;
		nwl->next = wish_lists;
		wish_lists = nwl;
		/* Account */
		nr_wishes_hooked++;
		if (wish_list_length > nr_max_wishes_hooked)
			nr_max_wishes_hooked = wish_list_length;
		return;
	}
	/* just hook wish */
	wish->next = wlb->wish_list;
	wlb->wish_list = wish;
	wish_list_length++;
	/* account */
	nr_wishes_hooked++;
	if (wish_list_length > nr_max_wishes_hooked)
		nr_max_wishes_hooked = wish_list_length;
}


void
unhook_wish (wish_t * wish)
{
	wishlist_diskid_t *wlb;
	wish_t *pwish;

	for (wlb = wish_lists; wlb; wlb = wlb->next)
		if (wlb->source_disk_id == wish->source_disk_id)
			break;
	if (!wlb) {
		fprintf (stderr,
			 "\nunhook_wish() was asked to unhook a wish for a disk with no wishs. Continuing anyway.\n");
		return;
	}
	if (wish == wlb->wish_list) {
		assert (wish_list_length);
		/* Unhook first wish */
		wlb->wish_list = wish->next;
		free (wish);
		wish_list_length--;
		return;
	}
	/* Find wish and unhook it */
	for (pwish = wlb->wish_list; pwish; pwish = pwish->next)
		if (pwish->next == wish)
			break;
	if (!pwish) {
		fprintf (stderr,
			 "\nunhook_wish() was asked to unhook a non-existant wish. Continuing anyway.\n");
		return;
	}
	assert (wish_list_length);
	pwish->next = wish->next;
	free (wish);
	wish_list_length--;
}


void
hook_gift (fulfilled_t * gift)
{
	giftlist_diskid_t *flb;

	for (flb = gift_lists; flb; flb = flb->next)
		if (flb->sink_disk_id == gift->sink_disk_id)
			break;
	if (!flb) {
		giftlist_diskid_t *ngl =
		    (giftlist_diskid_t *)

		    malloc (sizeof (giftlist_diskid_t));
		if (!ngl) {
			fprintf (stderr,
				 "\nOut of memory when hooking gift.  Will jump into ocean.\n");
			abort ();
		}
		/* hook gift into new disk giftlist */
		ngl->gift_list = gift;
		gift->next = 0;
		gift_list_length++;
		/* hook disk gift list */
		ngl->sink_disk_id = gift->sink_disk_id;
		ngl->next = gift_lists;
		gift_lists = ngl;
		/* Account */
		nr_gifts_hooked++;
		if (gift_list_length > nr_max_gifts_hooked)
			nr_max_gifts_hooked = gift_list_length;
		return;
	}
	/* just hook gift */
	gift->next = flb->gift_list;
	flb->gift_list = gift;
	gift_list_length++;
	/* Account */
	nr_gifts_hooked++;
	if (gift_list_length > nr_max_gifts_hooked)
		nr_max_gifts_hooked = gift_list_length;
}

void
unhook_gift (fulfilled_t * gift)
{
	giftlist_diskid_t *flb;
	fulfilled_t *pgift;

	for (flb = gift_lists; flb; flb = flb->next)
		if (flb->sink_disk_id == gift->sink_disk_id)
			break;
	if (!flb) {
		fprintf (stderr,
			 "\nunhook_gift() was asked to unhook a gift for a disk with no gifts. Continuing anyway.\n");
		return;
	}
	if (gift == flb->gift_list) {
		assert (gift_list_length);
		/* Unhook first gift */
		flb->gift_list = gift->next;
		free (gift->data);
		free (gift);
		gift_list_length--;
		return;
	}
	/* Find gift and unhook it */
	for (pgift = flb->gift_list; pgift; pgift = pgift->next)
		if (pgift->next == gift)
			break;
	if (!pgift) {
		fprintf (stderr,
			 "\nunhook_gift() was asked to unhook a non-existant gift. Continuing anyway.\n");
		return;
	}
	assert (gift_list_length);
	pgift->next = gift->next;
	free (gift->data);
	free (gift);
	gift_list_length--;
}


const char *
setup_free_blocks (unsigned ndisks, rrc_disk_t * diskcfg)
{
	int d;
	source_disk_free_map = (char **) malloc (sizeof (char *) * ndisks);

	if (!source_disk_free_map)
		return "Cannot allocate free disk map";
	source_disk_free_size =
	    (unsigned long *) malloc (sizeof (unsigned long) * ndisks);

	if (!source_disk_free_size)
		return "Cannot allocate free disk map size array";
	nr_free_disk_blocks =
	    (unsigned long *) malloc (sizeof (unsigned long) * ndisks);

	if (!nr_free_disk_blocks)
		return
		    "Cannot allocate per-disk free block accounting array";

	for (d = 0; d != ndisks; d++) {
		/* allocate one bit for each block */
		source_disk_free_size[d] =
		    (diskcfg[d].reconf_blocks + 7) / 8;
		source_disk_free_map[d] =
		    (char *) malloc (source_disk_free_size[d]);
		if (!source_disk_free_map[d])
			return
			    "Cannot allocate free disk map for specific disk";
		/* Initially, no blocks are free */
		memset (source_disk_free_map[d], 0,
			source_disk_free_size[d]);
		/* Update per-disk stuff */
		nr_free_disk_blocks[d] = 0;
	}

	fprintf (stderr, "Allocated free block map for %i disks\n",
		 ndisks);

	return 0;
}

/* The good stuff */


int
can_wish_again (void)
{
	if (is_out_of_memory)
		return 0;
	return wish_list_length < max_wishes;
}

int
is_gblock_in_source (unsigned long gblock)
{
	return gblock < source_blocks;
}

void
insert_wish (unsigned long gblock)
{
	const char *ret;
	wish_t *nwish;
	int source_disk_id, sink_disk_id;
	unsigned long source_disk_block, sink_disk_block;
	int free_on_source;

	/*
	 *  If block is free already, there's no need to free it.  But we
	 * must check that it's free on both the source and the sink
	 */
	ret =
	    source_driver->map_global_to_local (source_driver->priv,
						gblock, &source_disk_id,
						&source_disk_block);
	if (ret) {
		fprintf (stderr,
			 "\nSource driver returned error: \"%s\". Will dump core.\n",
			 ret);
		abort ();
	}
	free_on_source =
	    is_disk_block_free (source_disk_id, source_disk_block);
	ret =
	    sink_driver->map_global_to_local (sink_driver->priv, gblock,
					      &sink_disk_id,
					      &sink_disk_block);
	if (ret) {
		fprintf (stderr,
			 "\nSink driver returned error: \"%s\". Will dump core.\n",
			 ret);
		abort ();
	}
	if (free_on_source
	    && is_disk_block_free (sink_disk_id, sink_disk_block)) {
/*      fprintf(stderr, "Block %lu is free already\n", gblock); */
		return;
	}

	/* Insert wish */
	nwish = (wish_t *) malloc (sizeof (wish_t));
	if (!nwish) {
		fprintf (stderr,
			 "\nOut of memory when inserting wish. Hoping to live thru it...\n");
		is_out_of_memory = 1;
		return;
	}

	/* If this block is not free on the sink disk, insert it in the wish_list 
	   ** as well. 
	 */
	nwish->sink_disk_id = sink_disk_id;
	nwish->sink_rblock = sink_disk_block;

	/* We also wish for blocks that aren't in the source...  */
	if (is_gblock_in_source (gblock)) {

		/* Have the source driver map the global block to source-disk,block addressing */
		nwish->source_disk_id = source_disk_id;
		nwish->source_rblock = source_disk_block;

	}
	else {
		/* Blocks outside of the source are treated specially. We pretend that their source and
		 * sink disks and disk blocks are identical
		 */
		nwish->source_disk_id = nwish->sink_disk_id;
		nwish->source_rblock = nwish->sink_rblock;
	}

	/*
	 * This may be a request to free a block from the source disks, where the
	 * corrosponding location on the sink disks is not yet free.
	 * The common reader will detect this case and bring two gifts from only
	 * one wish.  So, worst case, we will end up with twice as many gifts
	 * as we had wishes.
	 */

#if 0
	fprintf (stderr,
		 "Hooking wish: src disk %i blk %4lu  ->  sink disk %i blk %4lu\n",
		 nwish->source_disk_id, nwish->source_rblock,
		 nwish->sink_disk_id, nwish->sink_rblock);
#endif

	hook_wish (nwish);
}


unsigned long
nr_free_blocks (void)
{
	unsigned long ret = 0;
	int d;

	for (d = 0; d != old_md_cfg->array.param.nr_disks; d++) {
		ret += nr_free_disk_blocks[d];
/*      fprintf(stderr, "\nFree blocks in disk %i = %lu\n", d, nr_free_disk_blocks[d]); */
	}
	return ret;
}


int
is_diskid_in_source (int diskid)
{
	int d;

	for (d = 0; d != old_md_cfg->array.param.nr_disks; d++)
		if (old_rrc_cfg[d].disk_id == diskid)
			return 1;
	return 0;
}

int
is_disk_block_free (int diskid, unsigned long block)
{
	int disk;
	unsigned long blk, ofs;

	for (disk = 0; disk != old_md_cfg->array.param.nr_disks; disk++)
		if (old_rrc_cfg[disk].disk_id == diskid)
			break;
	if (disk == old_md_cfg->array.param.nr_disks) {
		/* disk is not in source, so block is free per definition */
		return 1;
	}
	/* fast check first */
	if (!nr_free_disk_blocks[disk])
		return 0;
	if (block >= old_rrc_cfg[disk].reconf_blocks) {
		/* Block is out of scope on disk.  This must be an error as we cannot resize devices */
		fprintf (stderr,
			 "\nBlock %lu is out of scope in is_disk_block_free request on diskid %i\n",
			 block, diskid);
		/* added 9-Jan-2001 DSC */
		return 0;
	}
	/* Then look in map */
	blk = block / 8;
	ofs = block % 8;
	if (blk >= source_disk_free_size[disk]) {
		fprintf (stderr,
			 "\nA driver inquired about a free block out of range. Let's see what will happen.\n");
		return 0;
	}
	return !!(source_disk_free_map[disk][blk] & (1 << ofs));
}

static void
common_mark_disk_block_free (int diskid, unsigned long block, int check)
{
	int disk;
	unsigned long blk, ofs;

	for (disk = 0; disk != old_md_cfg->array.param.nr_disks; disk++)
		if (old_rrc_cfg[disk].disk_id == diskid)
			break;
	if (disk == old_md_cfg->array.param.nr_disks) {
		/* disk is not in source, so block is free per definition */
		fprintf (stderr, "\ndisk_id %i not in source\n", diskid);
		return;
	}
	if (block >= old_rrc_cfg[disk].reconf_blocks) {
		/* Block must be free, since it's definitely not in the source disk scope. */
		/* But this must also be an error, as we cannot resize a single device */
		fprintf (stderr,
			 "\nblock %lu is out of range on diskid %i\n",
			 block, diskid);
		return;
	}
	/* Then look in map */
	blk = block / 8;
	ofs = block % 8;
	if (blk >= source_disk_free_size[disk]) {
		/* blocks out of range are also free */
		fprintf (stderr,
			 "\nblock %lu out of range for free map on diskid %i\n",
			 block, diskid);
		abort ();
	}
	if (!(source_disk_free_map[disk][blk] & (1 << ofs))) {
		source_disk_free_map[disk][blk] |= (1 << ofs);
		nr_free_disk_blocks[disk]++;

		if (nr_free_disk_blocks[disk] >
		    old_rrc_cfg[disk].reconf_blocks) {
			abort ();
		}

		if (nr_free_blocks () > source_blocks) {
			abort ();
		}

	}
	else if (check) {
		fprintf (stderr,
			 "\nDouble free of block %lu on diskid %i\n",
			 block, disk);
	}
}

void
mark_disk_block_free (int diskid, unsigned long block)
{
	common_mark_disk_block_free (diskid, block, 1);
}

void
unchecked_mark_disk_block_free (int diskid, unsigned long block)
{
	common_mark_disk_block_free (diskid, block, 0);
}

void
mark_disk_block_unfree (int diskid, unsigned long block)
{
	int disk;
	unsigned long blk, ofs;

	for (disk = 0; disk != old_md_cfg->array.param.nr_disks; disk++)
		if (old_rrc_cfg[disk].disk_id == diskid)
			break;
	if (disk == old_md_cfg->array.param.nr_disks) {
		/* disk is not in source, so block cannot be unfreed
		 * This is an acceptable error, (in order to keep the unfree_all_blocks() routines simple
		 */
		return;
	}
	if (block >= old_rrc_cfg[disk].reconf_blocks) {
		fprintf (stderr,
			 "\nblock %lu is out of range on diskid %i and cannot be unfreed \n",
			 block, diskid);
		abort ();
	}
	/* Then look in map */
	blk = block / 8;
	ofs = block % 8;
	if (blk >= source_disk_free_size[disk]) {
		/* blocks out of range are also free */
		fprintf (stderr,
			 "\nblock %lu out of range for free map on diskid %i\n",
			 block, diskid);
		abort ();
	}
	if (source_disk_free_map[disk][blk] & (1 << ofs)) {

		assert (nr_free_disk_blocks[disk]);

		source_disk_free_map[disk][blk] =
		    source_disk_free_map[disk][blk] & ~(1 << ofs);
		nr_free_disk_blocks[disk]--;

	}
	else {
		/* We don't care about unfreeing unfree blocks */
	}
}

void
debug_print_nonfree_blocks (void)
{
	int disk;

	for (disk = 0; disk != old_md_cfg->array.param.nr_disks; disk++) {
		unsigned long blk;

		fprintf (stderr, "\nUnfree blocks on disk %i\n", disk);
		for (blk = 0; blk != old_rrc_cfg[disk].reconf_blocks;
		     blk++) {
			if (!is_disk_block_free
			    (old_rrc_cfg[disk].disk_id,
			     blk)) fprintf (stderr, "%lu ", blk);
		}
	}
}

void
debug_print_wish_list (void)
{
	wishlist_diskid_t *wll = wish_lists;

	while (wll) {
		wish_t *wl;

		fprintf (stderr, "Wish list for source disk id %i:\n",
			 wll->source_disk_id);
		for (wl = wll->wish_list; wl; wl = wl->next)
			fprintf (stderr,
				 "  source disk id %i block %lu , sink disk id %i block %lu\n",
				 wl->source_disk_id, wl->source_rblock,
				 wl->sink_disk_id, wl->sink_rblock);
		wll = wll->next;
	}
}


const char *
generic_write_blocks (int partial)
{
	/*
	 * Find nice long consecutive sequences for each disk...
	 *
	 * To-do: Optimization: Start with disks that didn't have read requests,
	 * or had read requests near the write-point. Only if we have to, we
	 * should use the disks we're also reading from.
	 */
	int dsk;
	const char *ret = 0;

	/* Traverse sink disks */
	for (dsk = 0; dsk != new_md_cfg->array.param.nr_disks; dsk++) {
		fulfilled_t *gift =

		    gift_list_sink_diskid (new_rrc_cfg[dsk].disk_id);
		if (!gift)
			continue;
		/* Now write gifts to disks, sorted */
		while (gift) {
			fulfilled_t *cgift, *best_gift = gift;
			unsigned long long lrc;

			for (cgift = gift; cgift; cgift = cgift->next)
				if (cgift->sink_rblock <
				    best_gift->sink_rblock) best_gift =
					    cgift;

			/* Seek and write */
			lrc = raidseek (new_rrc_cfg[dsk].fd,
					best_gift->sink_rblock *
					reconf_block_size);
			if (lrc == -1) {
				fprintf (stderr,
					 "\nWriter seek error on disk %i block %lu\n",
					 dsk,
					 best_gift->sink_rblock *
					 reconf_block_size);
				abort ();
			}

			if (!algorithm_check) {
				int rc =
				    write (new_rrc_cfg[dsk].fd,
					   best_gift->data,
					   reconf_block_size * 1024);

				if (rc == -1) {
					fprintf (stderr,
						 "\nWrite error on disk %i\n",
						 dsk);
					ret =
					    "Writer failed to flush data to disk - bad blocks on disk ?";
				}
			}
			/* Unhook the gift */
			unhook_gift (best_gift);

			/* Update gift pointer, as we may just have unhooked the head */
			gift =
			    gift_list_sink_diskid (new_rrc_cfg[dsk].
						   disk_id);
		}
		/* Yes, we could sync() here, but it's better to schedule write to as many drives
		 * as possible before doing so 
		 */
	}

	/* sync() disks for two reasons:
	 * It's nice to know that the data reached the platter.  However
	 * this isn't much good usually, since if the reconfiguration fails
	 * the layout is hosed anyway.
	 * If we will be reading from the same disks, it's important for us
	 * to know that the write has completed, if we will be doing more
	 * sophisticated requesting in the future 
	 */
	for (dsk = 0; dsk != new_md_cfg->array.param.nr_disks; dsk++)
		fsync (new_rrc_cfg[dsk].fd);

	return ret;
}

int
must_fulfill_more (int partial)
{
	/* We tell the source driver whether we require that it
	 * fulfills more wishes.
	 * It will only ask us this if there are no good sequences
	 * left, so we should only tell it "yes" if we really need
	 * those extra gifts
	 */

	if (!wish_list_length)
		return 0;
	if (!partial)
		return !!nr_wishes_left ();

	/* We consider the number of wishes, the number of max. wishes
	 * and the number of gifts.
	 * if the (wishes fulfilled)/(wishes left) fraction is too small, we tell the
	 * driver to keep working.
	 *
	 * This stuff can be tuned...  We could also take into account the current
	 * free_friends_depth, as that is the number of _extra_ un-wished-for
	 * gifts that ``sometimes'' occur when fulfilling one single wish.
	 */
	return (gift_list_length * 10 / wish_list_length) < 7;
}

unsigned
nr_wishes_left (void)
{
	return wish_list_length;
}

unsigned
nr_gifts_left (void)
{
	return gift_list_length;
}

/*
 * This routine should be generalized to simply fulfill wishes on all disk id's
 * in a reasonably sequential manner.  This will save us from re-implementing
 * the read_blocks routine for each raid level, as these routines are actually
 * identical anyways...
 */

void
fulfill_wishes (int partial)
{
	/*
	 * Read requests from the wish_list, fulfill wishes until
	 * either:
	 * A)  There are no more wishes
	 * B)  There aren't any good wishes left and we've fulfilled enough
	 *
	 * This routine has O(np^2) behaviour for p total wishes in n sequences - which is bad.
	 * Improvements:
	 *     sort wishes at wish-time
	 */

	while (nr_wishes_left ()) {
		wish_t *wp;
		unsigned long length, best_length = 0;
		int disk, best_disk = -1;
		unsigned long begin, best_begin = 0;
		unsigned long end, best_end = 0;
		int progress;

		/*
		 * Look for a good fat contigous read 
		 */
		for (disk = 0; disk != nr_unique_disks; disk++) {
			unsigned long low_block = 0;
			int diskid = global_disk_rrc[disk]->disk_id;

			end = 0;
			begin = global_disk_rrc[disk]->reconf_blocks;
			/* Find request with lowest block >= low_block */
			for (wp = wish_list_source_diskid (diskid); wp;
			     wp = wp->next) {
				assert (wp->source_disk_id == diskid);
				if (wp->source_rblock >= low_block
				    && wp->source_rblock < begin)
					begin = wp->source_rblock;
			}
			/* If there's no wishes for blocks on this disk, keep moving */
			if (begin == global_disk_rrc[disk]->reconf_blocks)
				continue;

			length = 1;
			end = begin + 1;
			/* Now look for requests that follow begin. */
			progress = 1;
			while (progress) {
				progress = 0;
				for (wp = wish_list_source_diskid (diskid);
				     wp; wp = wp->next) {
					assert (wp->source_disk_id ==
						diskid);
					if (wp->source_rblock == end) {
						end++;
						length++;
						progress = 1;
					}
				}
			}
			/* Is this one better ? */
			if (length > best_length) {
				best_length = length;
				best_disk = disk;
				best_begin = begin;
				best_end = end;
			}
		}
		/* So we have a best request - maybe */
		if (best_length > GOOD_READ_LENGTH
		    || (must_fulfill_more (partial) && best_length > 0)) {
			fulfill_wish (global_disk_rrc[best_disk]->disk_id,
				      best_begin, best_end);
		}
		else if (must_fulfill_more (partial)) {
			/* there was no best request */
			fprintf (stderr,
				 "\nNo best request in reader... best is %lu [%lu-%lu]. This is an internal error"
				 " - I will dump core..\n", best_length,
				 best_begin, best_end);
			fprintf (stderr, "%u wishes left\n",
				 nr_wishes_left ());
			fprintf (stderr, "%lu free blocks\n",
				 nr_free_blocks ());
			debug_print_wish_list ();
			abort ();
		}
		else {
			/* We don't want to read anymore, and we don't have to */
			assert (partial);
/*        fprintf(stderr, "\nReader skipping early, wishes are crappy.\n"); */
			return;
		}
	}
	/* Good, we're done. */
}


static void
free_block_and_friends (int diskid, unsigned long block)
{
	unsigned long cdepth = 0;

	while (!is_disk_block_free (diskid, block)) {
		unsigned long source_block;
		int dsk;
		fulfilled_t *gift;
		unsigned long gblock;
		const char *ret;

		cdepth++;
		/* Calculate block and disk numbers */
		source_block = block * reconf_block_size;
		for (dsk = 0; dsk != old_md_cfg->array.param.nr_disks;
		     dsk++)
			if (old_rrc_cfg[dsk].disk_id == diskid)
				break;
		if (dsk == old_md_cfg->array.param.nr_disks) {
			fprintf (stderr,
				 "Diskid %i is not in source, when freeing blocks and their friends...\n",
				 diskid);
			abort ();
		}
		/* Seek to and read the block */
		if (raidseek (old_rrc_cfg[dsk].fd, source_block) == -1) {
			fprintf (stderr,
				 "\nSeek error in fulfill_wish() (secondary) for disk %i block %lu. Will dump core.\n",
				 dsk, source_block);
			abort ();
		}
		gift = (fulfilled_t *) malloc (sizeof (fulfilled_t));
		if (!gift) {
			fprintf (stderr,
				 "\nOh cr*p, we failed malloc() when setting up secondary gift. Dump core for now.\n");
			abort ();
		}
		/* Calculate sink disk for block we're reading */
		gblock =
		    source_driver->map_local_to_global (source_driver->
							priv, diskid,
							block);
		/* We must catch the case where we're shrinking, and the global block 
		   ** does exist in the sink, but not in the source, and for RAID[45], we
		   ** should ignore parity blocks (flagged by gblock being ULONG_MAX)
		 */
		if (gblock >= sink_blocks) {
			free (gift);
			return;
		}

		/* Now map to sink diskid and block */
		if (
		    (ret =
		     sink_driver->map_global_to_local (sink_driver->priv,
						       gblock,
						       &gift->sink_disk_id,
						       &gift->
						       sink_rblock))) {
			fprintf (stderr,
				 "Sink driver returned fatal error: \"%s\"\n",
				 ret);
			abort ();
		}

		/* Allocate data area and fill it */
		if (!
		    (gift->data =
		     (char *) malloc (reconf_block_size * 1024))) {
			fprintf (stderr,
				 "\nWe couldn't allocate memory for the secondary gift buffer."
				 " Dying horrible death for now.\n");
			abort ();
		}
		if (!algorithm_check) {
			int rc =
			    read (old_rrc_cfg[dsk].fd, gift->data,
				  reconf_block_size * 1024);

			if (rc == -1) {
				fprintf (stderr,
					 "\nSecondary request: Read error on disk %i in souce (disk_id=%i)."
					 " Bad blocks on disk ?.\n", dsk,
					 diskid);
				abort ();
			}
		}
		else {
			/* Make sure that we also actually commit any over-comitted pages */
			memset (gift->data, 0, reconf_block_size * 1024);
		}

		/* Mark the block as free */
		mark_disk_block_free (diskid, block);

		/* Hook the un-wished-for gift as well */
		hook_gift (gift);

		/* Repeat procedure -  the sink disk and block is our new prospect... */
		diskid = gift->sink_disk_id;
		block = gift->sink_rblock;
	}
	if (cdepth > free_friends_depth)
		free_friends_depth = cdepth;
}


void
fulfill_wish (int source_diskid, unsigned long begin, unsigned long end)
{
	/*
	 * This routine will read a sequence of blocks as requested
	 */
	unsigned long curr_block = begin;

	while (curr_block != end) {
		wish_t *wishbase = wish_list_source_diskid (source_diskid);

		if (!wishbase) {
			fprintf (stderr,
				 "\nNo wishes on disk_id %i given to fulfill_wish() routine!\n",
				 source_diskid);
			return;
		}
		/* Now find the wish for the current block */
		for (; wishbase; wishbase = wishbase->next)
			if (wishbase->source_rblock == curr_block)
				break;
		if (!wishbase) {
			fprintf (stderr,
				 "\nFound hole in the claimed contiguous wish sequence... Will dump core.\n");
			abort ();
		}

		free_block_and_friends (wishbase->source_disk_id,
					wishbase->source_rblock);

		/* Unhook the wish */
		unhook_wish (wishbase);

		/* Next block */
		curr_block++;
	}
}
