/*
 * Single-disk driver code for the raidreconf utility
 * (C) 1999,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"

#define GOOD_READ_LENGTH 128

static const char *single_initialize (void *, md_cfg_entry_t *,
				      rrc_disk_t *, unsigned long *);
static driver_status_t single_request_blocks (void *);
static const char *single_update_super (void *);
static const char *single_map_global_to_local (void *, unsigned long,
					       int *, unsigned long *);
static unsigned long single_map_local_to_global (void *, int,

						 unsigned long);
static void single_free_blocks_above_gblock (void *, unsigned long);
static void single_unfree_all_blocks (void *);

typedef struct single_driver_priv {
	int diskid;
	rrc_disk_t *disks;
	unsigned long blocks_per_chunk;
	unsigned long total_blocks;
	unsigned long blocks_done;
} single_driver_priv;


level_driver_t *
new_single_driver (void)
{
	level_driver_t *drv =

	    (level_driver_t *) malloc (sizeof (level_driver_t));
	single_driver_priv *priv =

	    (single_driver_priv *) malloc (sizeof (single_driver_priv));
	if (!drv || !priv)
		return 0;

	drv->initialize = single_initialize;
	drv->request_blocks = single_request_blocks;
	drv->update_super = single_update_super;
	drv->map_global_to_local = single_map_global_to_local;
	drv->map_local_to_global = single_map_local_to_global;
	drv->free_blocks_above_gblock = single_free_blocks_above_gblock;
	drv->unfree_all_blocks = single_unfree_all_blocks;

	drv->priv = priv;
	priv->diskid = -1;
	priv->disks = 0;
	priv->blocks_per_chunk = 0;
	priv->total_blocks = 0;
	priv->blocks_done = 0;

	return drv;
}

static const char *
single_initialize (void *thisp, md_cfg_entry_t * cfg,
		   rrc_disk_t * cfgdisks, unsigned long *blocks)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	assert (cfgdisks);
	assert (cfg);

	this->disks = cfgdisks;

	this->diskid = cfgdisks[0].disk_id;
	this->blocks_per_chunk =
	    (cfg->array.param.chunk_size / MD_BLK_SIZ) / reconf_block_size;
	this->total_blocks = this->disks->chunks * this->blocks_per_chunk;

	*blocks = this->total_blocks;

	fprintf (stderr, "Single disk size: %lu blocks\n",
		 this->total_blocks);

	return 0;
}

static driver_status_t
single_request_blocks (void *thisp)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	/*
	 * This is simple:  read from where we got to, wish for
	 * blocks while we can still wish...
	 */
	while (can_wish_again () && this->blocks_done < this->total_blocks) {
		if (is_gblock_in_source (this->blocks_done)) {
			insert_wish (this->blocks_done);
		}
		this->blocks_done++;
	}

	if (can_wish_again ())
		return LDR_DONE;

	return LDR_INCOMPLETE;
}


static const char *
single_update_super (void *thisp)
{
	fprintf (stderr,
		 "There's no superblock on a plain disk. So we're done  :)\n");
	return 0;
}

static const char *
single_map_global_to_local (void *thisp, unsigned long gblock, int *diskid,
			    unsigned long *lblock)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	if (gblock >= this->total_blocks)
		return "Cannot map out-of-range gblocks to single device";

	/* This one's simple  :)  */
	*diskid = this->diskid;
	*lblock = gblock;

	return 0;
}

static unsigned long
single_map_local_to_global (void *thisp, int diskid, unsigned long block)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	if (diskid != this->diskid) {
		fprintf (stderr,
			 "Diskid %i is not the single disk id %i -> cannot map to global\n",
			 diskid, this->diskid);
		abort ();
	}

	return block;
}

static void
single_free_blocks_above_gblock (void *thisp, unsigned long gblock)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	unsigned long block;

	for (block = gblock; block < this->total_blocks; block++)
		unchecked_mark_disk_block_free (this->diskid, block);
}

static void
single_unfree_all_blocks (void *thisp)
{
	single_driver_priv *this = (single_driver_priv *) thisp;

	unsigned long block;

	for (block = 0; block != this->total_blocks; block++)
		mark_disk_block_unfree (this->diskid, block);
}
