/* -*- linux-c -*- */
/*
 *
 *
 *   Copyright (c) International Business Machines  Corp., 2000
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *
 */

/* linux/driver/evms/gpt_part.c
 *
 * EVMS - EFI GPT segment manager plugin
 *
 * This plugin provides support for the GUID Partition Table format specified
 * by the Extensible Firmware Interface documentation ... version 1.02
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/config.h>
#include <linux/string.h>
#include <linux/blk.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/evms/evms.h>

/* prefix used in logging messages */
#define LOG_PREFIX "gpt_part: "

/**
 * struct gpt_private -  Private data structure for this plugin
 * @source_object: object this IO will get remapped to
 * @start_sect: source object relative starting address in 512 byte units
 * @nr_sect: partition size in 512 bytes units
 * @type: partition type or filesystem format indicator
 * 
 * private copy of the just the fields we require to remap IO requests
 * to the underlying object.
 **/
struct gpt_private {
	struct evms_logical_node *source_disk;
	u64 start_sect;
	u64 nr_sects;
	unsigned char type;
};

#define GPT_DISKMAGIC               0x5452415020494645	// "EFI PART"
#define GPT_PNAME_SIZE              36	// max unicode partition name size

/**
 * struct guid - GUID structure
 * @time_low: timestamp - low order 32 bits
 * @time_mid: timestamp - mid 16 bits
 * @time_high: timestamp - high 16 bits
 * @clock_seq_high: clock - high order 8 bits
 * @clock_seq_low: clock - low order 8 bits
 * @node: spatial reference - unique id (ie. mac address of nic)
 *
 * GUID structure
 **/
struct guid {
	u32 time_low;
	u16 time_mid;
	u16 time_high;
	u8 clock_seq_high;
	u8 clock_seq_low;
	u8 node[6];
};

/**
 * struct gpt_partition - GPT partition record definition
 * @type: partition type
 * @part_id: partition record id
 * @start: address of 1st block of partition
 * @end: address of last block of partition
 * @attributes: bit field reserved by EFI spec
 * @name: unicode name of partition
 *
 * GPT partition record definition
 **/
struct gpt_partition {
	struct guid type;
	struct guid part_id;
	u64 start;
	u64 end;
	u64 attributes;
	u16 name[GPT_PNAME_SIZE];
};

/**
 * struct gpt_header - GPT header
 * @signature: EFI compatible header signature
 * @version: spec revision number
 * @size: size (bytes) of gpt header
 * @crc: crc of gpt header
 * @reserve: reserved by spec ... must be zero
 * @my_lba: lba of gpt header
 * @alternate_lba: lba of 2nd copy of gpt header
 * @start_useable: lba of 1st block of useable area on disk
 * @end_useable: lba of last block of useable area on disk
 * @disk_id: GUID - identifies this disk
 * @ptable_lba: lba of partition table
 * @ptable_count: number of entries in the partition table
 * @ptable_entry_size: size of partition table entry
 * @ptable_crc: crc of partition table
 *
 * GPT header
 **/
struct gpt_header {
	u64 signature;
	u32 version;
	u32 size;
	u32 crc;
	u32 reserve;
	u64 my_lba;
	u64 alternate_lba;
	u64 start_useable;
	u64 end_useable;
	struct guid disk_id;
	u64 ptable_lba;
	u32 ptable_count;
	u32 ptable_entry_size;
	u32 ptable_crc;
};

struct guid EFI_SYSTEM_PARTITION = {
	0xC12A7328,
	0xF81F,
	0x11D2,
	0xBA,
	0x4B,
	{0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B}
};

struct guid BASIC_DATA_PARTITION = {
	0xEBD0A0A2,
	0xB9E5,
	0x4433,
	0x87,
	0xC0,
	{0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7}
};

struct guid LEGACY_MBR_PARTITION = {
	0x024DEE41,
	0x33E7,
	0x11D3,
	0x9D,
	0x69,
	{0x00, 0x08, 0xC7, 0x81, 0xF3, 0x9F}
};

struct guid GPT_SWAP_PARTITION = {
	0x0657FD6D,
	0xA4AB,
	0x43C4,
	0x84,
	0xE5,
	{0x09, 0x33, 0xC8, 0x4B, 0x4F, 0x4F}
};

struct guid UNUSED_GPT_PARTITION = {
	0, 0, 0, 0, 0,
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};

static int exported_nodes;	/* total # of exported segments
				 * produced during this discovery.
				 */

/* Prototypes */
static int partition_discover(struct evms_logical_node **);
static int partition_delete(struct evms_logical_node *);
static void partition_read(struct evms_logical_node *, struct buffer_head *);
static void partition_write(struct evms_logical_node *, struct buffer_head *);
static int partition_ioctl(struct evms_logical_node *,
			   struct inode *,
			   struct file *, unsigned int, unsigned long);
static int partition_init_io(struct evms_logical_node *,
			     int, u64, u64, void *);

static struct evms_plugin_fops fops = {
	.discover	= partition_discover,
	.delete		= partition_delete,
	.read		= partition_read,
	.write		= partition_write,
	.init_io	= partition_init_io,
	.ioctl		= partition_ioctl
};

#define EVMS_GPT_PARTITION_MANAGER_ID 3

static struct evms_plugin_header plugin_header = {
	.id = SetPluginID(IBM_OEM_ID,
			  EVMS_SEGMENT_MANAGER,
			  EVMS_GPT_PARTITION_MANAGER_ID),
	.version = {
		.major		= 1,
		.minor		= 1,
		.patchlevel	= 2
	},
	.required_services_version = {
		.major		= 0,
		.minor		= 5,
		.patchlevel	= 0
	},
	.fops = &fops
};

/***************************************************/
/* List Support - Typedefs, Variables, & Functions */
/***************************************************/

/* Typedefs */

struct segment_list_node {
	struct evms_logical_node *segment;
	struct segment_list_node *next;
};

struct disk_list_node {
	struct evms_logical_node *disk;
	struct segment_list_node *segment_list;
	struct disk_list_node *next;
};

/* Variables */

static struct disk_list_node *my_disk_list;

/* Functions */

/*
 *  Function: Convert a GPT header from disk format to the arch specific
 *  format.
 */
static void
disk_gpt_header_to_cpu(struct gpt_header *gh)
{
	gh->signature = le64_to_cpu(gh->signature);
	gh->version = le32_to_cpu(gh->version);
	gh->size = le32_to_cpu(gh->size);
	gh->crc = le32_to_cpu(gh->crc);
	gh->reserve = le32_to_cpu(gh->reserve);
	gh->my_lba = le64_to_cpu(gh->my_lba);
	gh->alternate_lba = le64_to_cpu(gh->alternate_lba);
	gh->start_useable = le64_to_cpu(gh->start_useable);
	gh->end_useable = le64_to_cpu(gh->end_useable);
	gh->disk_id.time_low = le32_to_cpu(gh->disk_id.time_low);
	gh->disk_id.time_mid = le16_to_cpu(gh->disk_id.time_mid);
	gh->disk_id.time_high = le16_to_cpu(gh->disk_id.time_high);
	gh->ptable_lba = le64_to_cpu(gh->ptable_lba);
	gh->ptable_count = le32_to_cpu(gh->ptable_count);
	gh->ptable_entry_size = le32_to_cpu(gh->ptable_entry_size);
	gh->ptable_crc = le32_to_cpu(gh->ptable_crc);
}

static int
matching_guids(struct guid *g1, struct guid *g2)
{
	if ((le32_to_cpu(g1->time_low) == g2->time_low) &&
	    (le16_to_cpu(g1->time_mid) == g2->time_mid) &&
	    (le16_to_cpu(g1->time_high) == g2->time_high) &&
	    (g1->clock_seq_high == g2->clock_seq_high) &&
	    (g1->clock_seq_low == g2->clock_seq_low)) {
		return 1;
	}
	return 0;
}
static inline int
isa_basic_data_gpt_partition_record(struct gpt_partition *p)
{
	return (matching_guids(&p->type, &BASIC_DATA_PARTITION));
}
static inline int
isa_legacy_mbr_gpt_partition_record(struct gpt_partition *p)
{
	return (matching_guids(&p->type, &LEGACY_MBR_PARTITION));
}
static inline int
isa_esp_gpt_partition_record(struct gpt_partition *p)
{
	return (matching_guids(&p->type, &EFI_SYSTEM_PARTITION));
}
static inline int
isa_gpt_swap_partition_record(struct gpt_partition *p)
{
	return (matching_guids(&p->type, &GPT_SWAP_PARTITION));
}
static inline int
isa_unused_gpt_partition_record(struct gpt_partition *p)
{
	return (matching_guids(&p->type, &UNUSED_GPT_PARTITION));
}

static struct disk_list_node **
lookup_disk(struct evms_logical_node *disk)
{
	struct disk_list_node **ldln;

	ldln = &my_disk_list;
	while (*ldln) {
		if ((*ldln)->disk == disk)
			break;
		ldln = &(*ldln)->next;
	}
	return (ldln);
}

static struct segment_list_node **
lookup_segment(struct disk_list_node *disk, struct evms_logical_node *segment)
{
	struct segment_list_node **lsln;

	lsln = &disk->segment_list;
	while (*lsln) {
		if ((*lsln)->segment == segment)
			break;
		lsln = &(*lsln)->next;
	}
	return (lsln);
}

static struct evms_logical_node *
find_segment_on_disk(struct evms_logical_node *disk,
		     u64 start_sect, u64 nr_sects)
{
	struct evms_logical_node *rc = NULL;
	struct disk_list_node **ldln;
	struct segment_list_node **lsln;
	struct gpt_private *gpt_prv;

	ldln = lookup_disk(disk);
	if (*ldln) {
		/* disk found in list */
		/* attempt to find segment */

		lsln = &(*ldln)->segment_list;
		while (*lsln) {
			gpt_prv = (*lsln)->segment->private;
			if (gpt_prv->start_sect == start_sect)
				if (gpt_prv->nr_sects == nr_sects)
					break;
			lsln = &(*lsln)->next;
		}
		if (*lsln)
			rc = (*lsln)->segment;
	}
	return (rc);
}

/* function description: add_segment_to_disk
 *
 * this function attempts to add a segment to the segment
 * list of a disk. if the specified disk is not found, it
 * will be added to the global disk list. this function will
 * return a pointer to the matching segment in the disk's
 * segment list. the caller must compare the returned pointer
 * to the specified segment to see if the
 * specified segment was already present in the disk's segment
 * list. if the return pointer matches the specified segment,
 * then the specified segment was added to the list. if the
 * return segment pointer to does not match the specified
 * segment pointer, then the specified segment pointer was
 * a duplicate and can be thrown away.
 */
static int
add_segment_to_disk(struct evms_logical_node *disk,
		    struct evms_logical_node *segment)
{
	int rc = 0;
	struct disk_list_node **ldln, *new_disk;
	struct segment_list_node **lsln, *new_segment;

	ldln = lookup_disk(disk);
	if (*ldln == NULL) {
		/* disk not in list, add disk */
		new_disk = kmalloc(sizeof (*new_disk), GFP_KERNEL);
		if (new_disk) {
			memset(new_disk, 0, sizeof (*new_disk));
			new_disk->disk = disk;
			*ldln = new_disk;
		} else {
			rc = -ENOMEM;
		}
	}
	if (!rc) {
		/* attempt to add segment */
		lsln = lookup_segment(*ldln, segment);
		if (*lsln == NULL) {
			/* segment not in list, add segment */
			new_segment =
			    kmalloc(sizeof (*new_segment), GFP_KERNEL);
			if (new_segment) {
				memset(new_segment, 0, sizeof (*new_segment));
				new_segment->segment = segment;
				*lsln = new_segment;
			} else {
				rc = -ENOMEM;
			}
		} else
			rc = -1;
	}
	return (rc);
}

static int
remove_segment_from_disk(struct evms_logical_node *disk,
			 struct evms_logical_node *segment,
			 struct evms_logical_node **empty_disk)
{
	int rc = 0;
	struct disk_list_node **ldln, *tmp_disk_node;
	struct segment_list_node **lsln, *tmp_segment_node;

	*empty_disk = NULL;
	ldln = lookup_disk(disk);
	if (*ldln == NULL) {
		rc = -1;
	} else {
		/* disk found in list */
		/* attempt to add segment */
		lsln = lookup_segment(*ldln, segment);
		if (*lsln == NULL) {
			rc = -2;
		} else {
			tmp_segment_node = *lsln;
			/* remove segment from list */
			*lsln = (*lsln)->next;
			/* free the segment list node */
			kfree(tmp_segment_node);

			if ((*ldln)->segment_list == NULL) {
				tmp_disk_node = *ldln;
				*empty_disk = tmp_disk_node->disk;
				/* remove disk from list */
				*ldln = (*ldln)->next;
				/* free the disk list node */
				kfree(tmp_disk_node);
			}
		}
	}
	return (rc);
}

/*
 * Function:  add_segment
 */
static int
process_segment(struct evms_logical_node **discover_list,
		struct evms_logical_node *node,
		u64 start_sect,
		u64 nr_sects,
		int type, int part_num, int evms_top_segment)
{
	struct gpt_private *gpt_prv = NULL;
	struct evms_logical_node *segment;
	int rc = 0;

	segment = find_segment_on_disk(node, start_sect, nr_sects);
	if (segment) {
		LOG_DETAILS("exporting segment '%s'.\n", segment->name);
	} else {
		gpt_prv = kmalloc(sizeof (*gpt_prv), GFP_KERNEL);
		if (gpt_prv) {
			gpt_prv->source_disk = node;
			gpt_prv->start_sect = start_sect;
			gpt_prv->nr_sects = nr_sects;
			gpt_prv->type = type;
			rc = evms_cs_allocate_logical_node(&segment);
		} else {
			rc = -ENOMEM;
		}
		if (!rc) {
			segment->plugin = &plugin_header;
			segment->system_id = (unsigned int) type;
			segment->total_vsectors = nr_sects;
			segment->block_size = node->block_size;
			segment->hardsector_size = node->hardsector_size;
			segment->private = gpt_prv;
			segment->flags = node->flags;
			if (evms_top_segment)
				segment->iflags |= EVMS_TOP_SEGMENT;
			strcpy(segment->name, node->name);
			if (GetPluginType(node->plugin->id) ==
			    EVMS_SEGMENT_MANAGER) {
				strcat(segment->name, ".");
			}
			sprintf(segment->name + strlen(segment->name), "%d",
				part_num);
			LOG_DETAILS("creating segment '%s'.\n", segment->name);
			rc = add_segment_to_disk(node, segment);
			if (rc) {
				LOG_ERROR
				    ("%s: error(%d) adding segment '%s'!\n",
				     __FUNCTION__, rc, segment->name);
				rc = 0;
			} else {
				MOD_INC_USE_COUNT;
			}
		}
		if (rc) {
			if (gpt_prv)
				kfree(gpt_prv);
			if (segment)
				evms_cs_deallocate_logical_node(segment);
		}
	}
	if (!rc) {
		evms_cs_add_logical_node_to_list(discover_list, segment);
		exported_nodes++;
	}
	return rc;
}

void
print_mem(void *buffer, int length)
{
	int i, done;
	unsigned char *bufptr;

	bufptr = (unsigned char *) buffer;
	i = done = 0;
	while (!done) {
		if ((i % 16) == 0)
			printk(KERN_INFO "\n0x%p->", buffer + i);
		printk(KERN_INFO "%02x ", bufptr[i]);
		if (++i >= length)
			done++;
	}
	printk(KERN_INFO "\n");
}

/*
 *  Function: get GPT Partition Table - reads partition table
 *            into memory and performs crc check.
 *
 */
static struct gpt_partition *
get_gpt_partition_table(struct evms_logical_node *node, struct gpt_header *gh)
{
	int rc;
	struct gpt_partition *pt;
	u32 sector_count, calculated_crc;

	sector_count =
	    evms_cs_size_in_vsectors(gh->ptable_count * gh->ptable_entry_size);

	pt = kmalloc(sector_count * EVMS_VSECTOR_SIZE, GFP_KERNEL);
	if (pt) {

		rc = INIT_IO(node, 0, gh->ptable_lba, sector_count, pt);
		if (!rc) {

			calculated_crc = evms_cs_calculate_crc(EVMS_INITIAL_CRC,
							       pt,
							       gh->
							       ptable_count *
							       gh->
							       ptable_entry_size);

			if (~calculated_crc != gh->ptable_crc) {
				rc = -ENODATA;
			}

		}
	} else {
		rc = -ENOMEM;
	}

	if (rc) {
		if (pt)
			kfree(pt);
		pt = NULL;
	}

	return (pt);
}

/*
 *  Function: Validate GPT Header - runs basic checks to
 *            sanity check a gpt header.
 *
 */
static int
isa_valid_gpt_header(struct evms_logical_node *node, u64 lsn,
		     struct gpt_header *gh)
{
	u32 crc;
	u32 calculated_crc;
	u64 sector_count;

	/* signature */
	if (le64_to_cpu(gh->signature) != GPT_DISKMAGIC)
		return 0;

	/* crc */
	crc = le32_to_cpu(gh->crc);
	gh->crc = 0;
	calculated_crc =
	    ~(evms_cs_calculate_crc(EVMS_INITIAL_CRC, gh, le32_to_cpu(gh->size)));
	gh->crc = cpu_to_le32(crc);

	if (calculated_crc != crc)
		return 0;

	/* spec says lba reported by header must match actual location on disk */
	if (lsn != le64_to_cpu(gh->my_lba))
		return 0;

	/* sanity check partition table info found in header */
	if (gh->ptable_count == 0 || gh->ptable_entry_size == 0)
		return 0;

	sector_count =
	    evms_cs_size_in_vsectors(le64_to_cpu(gh->ptable_count) *
				     le64_to_cpu(gh->ptable_entry_size));

	if ((le64_to_cpu(gh->ptable_lba) + sector_count - 1) >=
	    node->total_vsectors - 1)
		return 0;

	return 1;
}

/*
 *  Function: get GPT Partition Table Header
 *
 */
static struct gpt_header *
get_gpt_header(struct evms_logical_node *node, u64 lsn)
{
	int rc;
	struct gpt_header *gh = NULL;

	gh = kmalloc(EVMS_VSECTOR_SIZE, GFP_KERNEL);
	if (gh) {
		rc = INIT_IO(node, 0, lsn, 1, gh);
		if (!rc) {
			if (isa_valid_gpt_header(node, lsn, gh)) {
				disk_gpt_header_to_cpu(gh);
			} else {
				rc = -ENODATA;
			}

		}
		if (rc) {
			kfree(gh);
			gh = NULL;
		}
	}

	return (gh);
}

/*
 *  Function: Get GPT Information
 *
 */
static int
get_gpt_info(struct evms_logical_node *node,
	     struct gpt_header **gh, struct gpt_partition **ptable)
{
	struct gpt_header *gh1 = NULL, *gh2 = NULL;

	*gh = NULL;
	*ptable = NULL;

	gh1 = get_gpt_header(node, 1);	// offset past protective mbr

	if (gh1) {
		*gh = gh1;
		gh2 = get_gpt_header(node, gh1->alternate_lba);
		if (gh2)
			kfree(gh2);
		else
			LOG_WARNING
			    ("alternate guid partition table header is invalid, using primary copy.\n");
	} else {
		gh2 = get_gpt_header(node, node->total_vsectors - 1);
		if (gh2) {
			*gh = gh2;
			LOG_WARNING
			    ("primary guid partition table header is invalid, using alternate copy\n");
		} else {
			LOG_DETAILS("no gpt header discovered on node %s\n",
				    node->name);
			return 0;
		}
	}

	*ptable = get_gpt_partition_table(node, *gh);
	if (!*ptable) {
		kfree(*gh);
		*gh = NULL;
		return 0;
	}

	return 1;
}

/*
 *  Function: Probe for GPT segments on logical node
 *
 */
static int
probe_for_segments(struct evms_logical_node **discover_list,
		   struct evms_logical_node *node)
{
	int rc;
	int evms_top_segment;
	u32 i;
	u64 pstart,pend;
	struct gpt_header *gh = NULL;
	struct gpt_partition *ptable = NULL;
	struct gpt_partition *part = NULL;

	/* no need to inspect our own nodes */
	if (node->plugin->id == plugin_header.id)
		return 0;

	/* nor nodes marked as EVMS_TOP_SEGMENT */
	if (node->iflags & EVMS_TOP_SEGMENT)
		return 0;

	/* look for guid partition table & header */
	if (!get_gpt_info(node, &gh, &ptable)) {
		if (gh)
			kfree(gh);
		if (ptable)
			kfree(ptable);
		return 0;
	}

	/* walk the guid partition table, producing segment storage objects */
	for (i = 0, part = ptable; i < gh->ptable_count; i++, part++) {

		if (!isa_unused_gpt_partition_record(part)) {

			pstart = le64_to_cpu(part->start);
			pend   = le64_to_cpu(part->end);

			LOG_DETAILS
			    ("gpt partition start="PFU64"  end="PFU64"\n",
			     pstart, (pend - pstart + 1));

			/* stop other seg mgrs from recursive discovery on a gpt system partition */
			if (isa_esp_gpt_partition_record(part))
				evms_top_segment = 1;
			else
				evms_top_segment = 0;

			rc = process_segment(discover_list,
					     node,
					     pstart,
					     (pend - pstart + 1),
					     0, i+1, evms_top_segment);

		}

	}

	/* remove node we just consumed */
	evms_cs_remove_logical_node_from_list(discover_list, node);

	kfree(ptable);
	kfree(gh);
	return 1;
}

/*
 * Function: partition_discover
 *
 */
static int
partition_discover(struct evms_logical_node **discover_list)
{
	int rc = 0;
	struct evms_logical_node *node, *next_node;

	MOD_INC_USE_COUNT;
	LOG_ENTRY_EXIT("%s: ENTRY\n", __FUNCTION__);

	/* initialize global variable */
	exported_nodes = 0;

	/* examine each node on the discover list */
	next_node = *discover_list;
	while (next_node) {
		node = next_node;
		next_node = node->next;
		probe_for_segments(discover_list, node);
	}

	LOG_ENTRY_EXIT("%s: EXIT(exported nodes:%d, error code:%d)\n",
		       __FUNCTION__, exported_nodes, rc);
	if (exported_nodes)
		rc = exported_nodes;
	MOD_DEC_USE_COUNT;
	return (rc);
}

/*
 * Function: partition_delete
 *
 */
static int
partition_delete(struct evms_logical_node *segment)
{
	int rc = 0;
	struct gpt_private *gpt_prv;
	struct evms_logical_node *empty_disk = NULL;

	LOG_DETAILS("deleting segment '%s'.\n", segment->name);

	if (!segment) {
		rc = -ENODEV;
	} else {
		gpt_prv = segment->private;
		if (gpt_prv) {
			/* remove the segment from the
			 * disk's segment list
			 */
			rc = remove_segment_from_disk(gpt_prv->source_disk,
						      segment, &empty_disk);
			/* free the local instance data */
			kfree(gpt_prv);
		}
		/* free the segment node */
		evms_cs_deallocate_logical_node(segment);
		MOD_DEC_USE_COUNT;
		/* if the last segment on the disk was
		 * deleted, delete the disk node too
		 */
		if (empty_disk)
			DELETE(empty_disk);
	}
	return (rc);
}

/*
 * function: partition_io_error
 *
 * this function was primarily created because the function
 * buffer_IO_error is inline and kgdb doesn't allow breakpoints
 * to be set on inline functions. Since this was an error path
 * and not mainline, I decided to add a trace statement to help
 * report on the failing condition.
 *
 */
static void
partition_io_error(struct evms_logical_node *node, int io_flag,
		   struct buffer_head *bh)
{
	LOG_SERIOUS
	    ("attempt to %s beyond partition boundary("PFU64") on (%s), rsector(%ld).\n",
	     (io_flag) ? "WRITE" : "READ", node->total_vsectors - 1, node->name,
	     bh->b_rsector);

	bh->b_end_io(bh, 0);
}

/*
 * Function: partition_read
 *
 */
static void
partition_read(struct evms_logical_node *partition, struct buffer_head *bh)
{
	struct gpt_private *gpt_prv = partition->private;

	if ((bh->b_rsector + (bh->b_size >> EVMS_VSECTOR_SIZE_SHIFT)) <=
	    partition->total_vsectors) {
		bh->b_rsector += gpt_prv->start_sect;
		R_IO(gpt_prv->source_disk, bh);
	} else
		partition_io_error(partition, READ, bh);
}

/*
 * Function: partition_write
 *
 */
static void
partition_write(struct evms_logical_node *partition, struct buffer_head *bh)
{
	struct gpt_private *gpt_prv = partition->private;

	if ((bh->b_rsector + (bh->b_size >> EVMS_VSECTOR_SIZE_SHIFT)) <=
	    partition->total_vsectors) {
		bh->b_rsector += gpt_prv->start_sect;
		W_IO(gpt_prv->source_disk, bh);
	} else
		partition_io_error(partition, WRITE, bh);
}

/*
 * Function: partition_init_io
 *
 */
static int
partition_init_io(struct evms_logical_node *partition, int io_flag,	/* 0=read, 1=write */
		  u64 sect_nr,	/* disk LBA */
		  u64 num_sects,	/* # of sectors */
		  void *buf_addr)
{				/* buffer address */
	int rc;
	struct gpt_private *gpt_prv = partition->private;

	if ((sect_nr + num_sects) <= partition->total_vsectors) {
		rc = INIT_IO(gpt_prv->source_disk, io_flag,
			     sect_nr + gpt_prv->start_sect, num_sects,
			     buf_addr);
	} else {
		LOG_SERIOUS
		    ("init_io: attempt to %s beyond partition(%s) boundary("PFU64") at sector("PFU64") for count("PFU64").\n",
		     (io_flag) ? "WRITE" : "READ", partition->name,
		     (gpt_prv->nr_sects - 1), sect_nr, num_sects);
		rc = -EINVAL;
	}

	return (rc);
}

/*
 * Function: partition_ioctl
 *
 */
static int
partition_ioctl(struct evms_logical_node *partition,
		struct inode *inode,
		struct file *file, unsigned int cmd, unsigned long arg)
{
	struct gpt_private *gpt_prv;
	struct hd_geometry hd_geo;
	int rc;

	rc = 0;
	gpt_prv = partition->private;
	if (!inode)
		return -EINVAL;
	switch (cmd) {
	case HDIO_GETGEO:
		{
			rc = IOCTL(gpt_prv->source_disk, inode, file, cmd, arg);
			if (rc)
				break;
			if (copy_from_user
			    (&hd_geo, (void *) arg,
			     sizeof (struct hd_geometry)))
				rc = -EFAULT;
			if (rc)
				break;
			hd_geo.start = gpt_prv->start_sect;
			if (copy_to_user
			    ((void *) arg, &hd_geo,
			     sizeof (struct hd_geometry)))
				rc = -EFAULT;
		}
		break;
	case EVMS_GET_BMAP:
		{
			struct evms_get_bmap_pkt *bmap =
			    (struct evms_get_bmap_pkt *) arg;
			bmap->rsector += gpt_prv->start_sect;
			/* intentionally fall thru to
			 * default ioctl down to device
			 * manager.
			 */
		}
	default:
		rc = IOCTL(gpt_prv->source_disk, inode, file, cmd, arg);
	}
	return rc;
}

/*
 * Function: gpt_module_init
 *
 */
static int __init
gpt_module_init(void)
{
	return evms_cs_register_plugin(&plugin_header);	/* register with EVMS */
}

/*
 * Function: gpt module exit
 */
static void __exit
gpt_module_exit(void)
{
	evms_cs_unregister_plugin(&plugin_header);
}

module_init(gpt_module_init);
module_exit(gpt_module_exit);
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
