/* -*- 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/drivers/evms/dos_part.c
 *
 * EVMS DOS partition manager
 *
 * Partial code extracted from
 *
 *  linux/fs/partitions/msdos.c
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/config.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/string.h>
#include <linux/blk.h>
#include <linux/init.h>
#include <linux/iobuf.h>	/* for kiobuf stuffs */

#ifdef CONFIG_BLK_DEV_IDE
#include <linux/ide.h>		/* IDE xlate */
#endif				/* CONFIG_BLK_DEV_IDE */

#include <linux/evms/evms.h>
#include <linux/evms/evms_os2.h>

#include <asm/system.h>
#include <asm/uaccess.h>

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

/* #include "msdos.h" */
#define MSDOS_LABEL_MAGIC               0xAA55
#define GPT_ENTIRE_DISK_INDICATOR       0xEE
#define GPT_ESP_INDICATOR               0xEF

/**
 * struct mbr_ebr -  Skeletal MBR/EBR structure useful for our purposes
 * @unused1: skip IPL record code
 * @partitions: partition table
 * @signature: DOS magic
 *
 * skeletal access to parition table in MBR/EBR
 **/
struct mbr_ebr {
	u8 unused1[0x1be];
	struct partition partitions[4];
	u16 signature;
};

/**
 * struct dos_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 dos_private {
	struct evms_logical_node *source_disk;
	u64 start_sect;
	u64 nr_sects;
	unsigned char type;
};

/**
 * struct extended_part - Structure used to track progress traversing an EBR chain
 * @extended: partition table in the extended boot record
 * @start_sect: address of the extended boot record in 512 byte units
 * @next_ebr_start: address of next ebr in the chain
 * @done: progress flag
 *
 * struct used to track extended boot record chain traversals.
 **/
struct extended_part {
	struct partition *extended;
	u64 start_sect;
	u64 next_ebr_start;
	int done;
};

/* Global variables */
static int cur_comp_part_num;	/* used to track non-primary
				 * partition numbers
				 */
static int exported_nodes;	/* total # of exported segments
				 * produced during this discovery.
				 */

/* External references */
#if CONFIG_BLK_DEV_MD && CONFIG_AUTODETECT_RAID
extern void md_autodetect_dev(kdev_t dev);
#endif

/* Prototypes */
static int mbr_ebr_partition_discover(struct evms_logical_node **);
static int mbr_ebr_partition_delete(struct evms_logical_node *);
static void mbr_ebr_partition_read(struct evms_logical_node *,
				   struct buffer_head *);
static void mbr_ebr_partition_write(struct evms_logical_node *,
				    struct buffer_head *);
static int mbr_ebr_partition_ioctl(struct evms_logical_node *, struct inode *,
				   struct file *, unsigned int, unsigned long);
static int mbr_ebr_partition_init_io(struct evms_logical_node *,
				     int, u64, u64, void *);

static struct evms_plugin_fops fops = {
	.discover	= mbr_ebr_partition_discover,
	.delete		= mbr_ebr_partition_delete,
	.read		= mbr_ebr_partition_read,
	.write		= mbr_ebr_partition_write,
	.init_io	= mbr_ebr_partition_init_io,
	.ioctl		= mbr_ebr_partition_ioctl
};

#define EVMS_MSDOS_PARTITION_MANAGER_ID 1

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

/*
 * Many architectures don't like unaligned accesses, which is
 * frequently the case with the nr_sects and start_sect partition
 * table entries.
 */
#include <asm/unaligned.h>

#define SYS_IND(p)      (get_unaligned(&p->sys_ind))
#define NR_SECTS(p)     (u64)({ __typeof__(p->nr_sects) __a =        \
                                get_unaligned(&p->nr_sects);    \
                                le32_to_cpu(__a); \
                        })

#define START_SECT(p)   (u64)({ __typeof__(p->start_sect) __a =      \
                                get_unaligned(&p->start_sect);  \
                                le32_to_cpu(__a); \
                        })

/******************************************/
/* List Support -  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 */

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 dos_private *dos_prv;

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

		lsln = &(*ldln)->segment_list;
		while (*lsln) {
			dos_prv = (*lsln)->segment->private;
			if (dos_prv->start_sect == start_sect)
				if (dos_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);
}

static inline int
is_extended_partition(struct partition *p)
{
	return (SYS_IND(p) == DOS_EXTENDED_PARTITION ||
		SYS_IND(p) == WIN98_EXTENDED_PARTITION ||
		SYS_IND(p) == LINUX_EXTENDED_PARTITION);
}

static inline u64
part_start(struct partition *part, u64 ext_start, u64 ebr_start)
{
	u64 pstart = START_SECT(part);
	pstart += (is_extended_partition(part)) ? ext_start : ebr_start;
	return (pstart);
}

static int
validate_mbr_ebr(struct evms_logical_node *node,
		 struct mbr_ebr *mbr_ebr, u64 ext_start,
		 u64 ebr_start)
{
	int valid_mbr_ebr, i, j, mbr_flag;
	struct partition *pi, *pj;
	u64 pi_start, pi_end, pj_start, pj_end;

	/* assume an MBR */
	mbr_flag = TRUE;

	/* assume its valid */
	valid_mbr_ebr = TRUE;

	/* check for valid signature */
	if (mbr_ebr->signature != cpu_to_le16(MSDOS_LABEL_MAGIC)) {
		LOG_DEBUG("%s: invalid signature on '%s'!\n",
			  __FUNCTION__, node->name);
		valid_mbr_ebr = FALSE;
	}

	/* check for an AIX IPL signature */
#define IPLRECID 0xc9c2d4c1	/* Value is EBCIDIC 'IBMA'           */
	if (*(unsigned int *) mbr_ebr == IPLRECID) {
		LOG_DEBUG("%s: found an AIX IPL signature on '%s'\n",
			  __FUNCTION__, node->name);
		valid_mbr_ebr = FALSE;
	}

	/* check for boot sector fields */

#if 0				//Remove checking of the first byte

	/* attempt to make some initial assumptions about
	 * what type of data structure this could be. we
	 * start by checking the 1st byte. we can tell a
	 * few things based on what is or isn't there.
	 */
	if (valid_mbr_ebr == TRUE)
		switch (*(u_char *) mbr_ebr) {
			/* check for JMP as 1st instruction
			 * if found, assume (for now), that
			 * this is a boot sector.
			 */
			/* Removed the JMP opcode check because it's not enough to determine
			 * that this sector does not have a valid MBR.
			 * Note:  To avoid going thru validation process of partition table,
			 * it's necessary to have a better boot sector check
			 * (eg. JMP opcode && other conditions) */
			/*
			   case 0xEB:
			   LOG_DEBUG("%s: boot sector detected!\n", __FUNCTION__);
			   valid_mbr_ebr = FALSE;
			 */
			/* let this fall thru to pick up the
			 * mbr_flag == FALSE.
			 */

			/* the MBR should contain boot strap
			 * code, so we don't expect the 1st
			 * byte to be a 0x0. If the 1st byte
			 * IS 0x0, its assumed (for now) to
			 * be an EBR.
			 */
		case 0:
			mbr_flag = FALSE;
			break;
		}
#endif				//Remove checking of the first byte

	if (valid_mbr_ebr == TRUE) {
		/* dump the partition table entries in debug mode */
		LOG_DEBUG
		    ("%s: disk relative starts: ext_part("PFU64"), ebr("PFU64").\n",
		     __FUNCTION__, ext_start, ebr_start);
		for (i = 0; i < 4; i++) {
			pi = &mbr_ebr->partitions[i];
			LOG_DEBUG
			    ("%s: Partition: index(%d), start("PFU64"), size("PFU64"), sys(0x%x).\n",
			     __FUNCTION__, i, START_SECT(pi), NR_SECTS(pi),
			     SYS_IND(pi));
		}

		/* check for PMBR (Protected Master Boot Record)
		 * and skip this node if found
		 */
		for (i = 0; i < 4; i++) {
			pi = &mbr_ebr->partitions[i];

			if (SYS_IND(pi) == 0xEE) {
				valid_mbr_ebr = FALSE;
				LOG_DETAILS
				    ("%s: detected PMBR on '%s', skipping.\n",
				     __FUNCTION__, node->name);
				break;
			}
		}

		/* check of this segment is marked as non-dividable
		 * and skip if found
		 */
		if (node->iflags & EVMS_TOP_SEGMENT) {
			valid_mbr_ebr = FALSE;
		}
	}

	if (valid_mbr_ebr == TRUE) {
		/* check for mbr/ebr partition table validity */
		for (i = 0; i < 4; i++) {
			pi = &mbr_ebr->partitions[i];
			if (NR_SECTS(pi)) {
				/* check for partition extending past end of node */
				pi_start = part_start(pi, ext_start, ebr_start);
				pi_end = pi_start + NR_SECTS(pi) - 1;
				if (pi_end >= node->total_vsectors) {
					LOG_DEBUG
					    ("%s: partition(%d) ends("PFU64") beyond the end of the disk(%s,"PFU64")!\n",
					     __FUNCTION__, i, pi_end,
					     node->name, node->total_vsectors);
					valid_mbr_ebr = FALSE;
				}
				if (valid_mbr_ebr == FALSE)
					break;

				/* check for partition overlap */
				for (j = i + 1; j < 4; j++) {
					pj = &mbr_ebr->partitions[j];
					if (NR_SECTS(pj)) {
						pj_start =
						    part_start(pj, ext_start,
							       ebr_start);
						pj_end =
						    pj_start + NR_SECTS(pj) - 1;
						if (pi_start == pj_start) {
							valid_mbr_ebr = FALSE;
						} else if (pi_start < pj_start) {
							if (pi_end >= pj_start)
								valid_mbr_ebr =
								    FALSE;
						} else if (pi_start <= pj_end)
							valid_mbr_ebr = FALSE;

						if (valid_mbr_ebr == FALSE) {
							LOG_DEBUG
							    ("%s: overlapping partitions(%d,%d) detected on '%s'!\n",
							     __FUNCTION__, i, j,
							     node->name);
							break;
						}
					}
				}
				if (valid_mbr_ebr == FALSE)
					break;
			}
		}
	}
	if (valid_mbr_ebr == TRUE) {
		LOG_DEBUG("%s: valid %cBR detected on '%s'!\n", __FUNCTION__,
			  (mbr_flag == TRUE) ? 'M' : 'E', node->name);
	} else {
		LOG_DEBUG("%s: no valid MBR/EBR detected on '%s'!\n",
			  __FUNCTION__, node->name);
	}
	return (valid_mbr_ebr);
}

/*
 * Function:  add_segment
 */
static int
mbr_ebr_process_segment(struct evms_logical_node **discover_list,
			struct evms_logical_node *node,
			u64 start_sect,
			u64 nr_sects,
			unsigned char type, int part_num, char *partition_name)
{
	struct dos_private *dos_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 {
		dos_prv = kmalloc(sizeof (*dos_prv), GFP_KERNEL);
		if (dos_prv) {
			memset(dos_prv, 0, sizeof (*dos_prv));
			dos_prv->source_disk = node;
			dos_prv->start_sect = start_sect;
			dos_prv->nr_sects = nr_sects;
			dos_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 = dos_prv;
			segment->flags = node->flags;
			if (partition_name)
				strcpy(segment->name, partition_name);
			else {
				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);
			}
			/* watch for super floppy format gpt system partition
			 * and dont let it be sub divided
			 */
			if (segment->system_id == GPT_ESP_INDICATOR) {
				node->iflags |= EVMS_TOP_SEGMENT;
			}
			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 (dos_prv)
				kfree(dos_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;
}

static inline void
print_partition_info(char *leading_comment, struct partition *p)
{
	LOG_EXTRA("%s: boot_ind(0x%02x), sys_ind(0x%02x), startCHS(%u,%u,%u), endCHS(%u,%u,%u), startLBA("PFU64"), sizeLBA("PFU64")\n",
		  leading_comment, p->boot_ind, p->sys_ind, p->cyl, p->head,
		  p->sector, p->end_cyl, p->end_head, p->end_sector,
		  START_SECT(p), NR_SECTS(p));
}

#ifdef CONFIG_BSD_DISKLABEL
#define BSD_DISKLABEL_PART_TABLE_SECTOR_OFFSET 1
static inline void
print_bsd_partition_info(char *leading_comment, struct bsd_partition *p)
{
	LOG_EXTRA("%s: p_size(%u), p_offset(%u), p_fsize(%u), p_fstype(0x%02X), p_frag(0x%02X), p_cpg(%u)\n",
		  leading_comment, p->p_size, p->p_offset, p->p_fsize, p->p_fstype, p->p_frag, p->p_cpg);
}

/*
 * bsd_disklabel_partition
 *
 * Return:
 *  - 0 for 0 partition
 *  - (positive) number for number of BSD partitions found
 *  - (negative) error code
 */
static int
bsd_disklabel_partition(struct evms_logical_node **discover_list,
			struct evms_logical_node *node, struct partition *bsd)
{
	struct bsd_disklabel *l;
	struct bsd_partition *p;
	int max_partitions;
	char *data;
	int rc = 0;
	int count = 0;

	data = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (data)
		rc = INIT_IO(node,
			     0,
			     START_SECT(bsd) +
			     BSD_DISKLABEL_PART_TABLE_SECTOR_OFFSET, 1, data);
	else
		rc = -ENOMEM;
	if (!rc) {

		l = (struct bsd_disklabel *) data;
		if (l->d_magic == BSD_DISKMAGIC) {

			max_partitions =
			    ((SYS_IND(bsd) ==
			      OPENBSD_PARTITION) ? OPENBSD_MAXPARTITIONS :
			     BSD_MAXPARTITIONS);
			if (l->d_npartitions < max_partitions)
				max_partitions = l->d_npartitions;
			for (p = l->d_partitions;
			     p - l->d_partitions < max_partitions; p++) {
				if (p->p_fstype != BSD_FS_UNUSED) {
					print_bsd_partition_info(__FUNCTION__, p);
					rc = mbr_ebr_process_segment
					    (discover_list, node,
					     (u64) p->p_offset,
					     (u64) p->p_size, p->p_fstype,
					     cur_comp_part_num++, NULL);
					if (rc)
						break;
					count++;
				}
			}
		}
	}
	if (data)
		kfree(data);
	if (!rc)
		rc = count;
	LOG_DETAILS("%s: exported (%d) partitions\n", __FUNCTION__, rc);
	return rc;
}
#endif

#ifdef CONFIG_UNIXWARE_DISKLABEL
#define UNIXWARE_PART_TABLE_SECTOR_OFFSET 29

/*
 * unixware_partition
 *
 * Return:
 *  - 0 for 0 partition
 *  - (positive) number for number of UNIXWARE partitions found
 *  - (negative) error code
 */
static int
unixware_partition(struct evms_logical_node **discover_list,
		   struct evms_logical_node *node,
		   struct partition *unixware_part)
{
	struct unixware_disklabel *l;
	struct unixware_slice *p;
	char *data = NULL;
	int rc = 0;
	int count = 0;

	data = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (data)
		rc = INIT_IO(node,
			     0,
			     START_SECT(unixware_part) +
			     UNIXWARE_PART_TABLE_SECTOR_OFFSET, 1, data);
	else
		rc = -ENOMEM;
	if (!rc) {
		l = (struct unixware_disklabel *) data;
		if (le32_to_cpu(l->d_magic) == UNIXWARE_DISKMAGIC &&
		    le32_to_cpu(l->vtoc.v_magic) == UNIXWARE_DISKMAGIC2) {
			p = &l->vtoc.v_slice[1];	/* The 0th slice is the same as whole disk. */
			while (p - &l->vtoc.v_slice[0] < UNIXWARE_NUMSLICE) {
				if (p->s_label != UNIXWARE_FS_UNUSED) {
					rc = mbr_ebr_process_segment
					    (discover_list, node, START_SECT(p),
					     NR_SECTS(p), UNIXWARE_PARTITION,
					     cur_comp_part_num++, NULL);
					if (rc)
						break;
					count++;
				}
				p++;
			}
		}
	}
	if (data)
		kfree(data);
	if (!rc)
		rc = count;
	LOG_DETAILS("%s: exported (%d) partitions\n", __FUNCTION__, rc);
	return rc;
}
#endif

#ifdef CONFIG_SOLARIS_X86_PARTITION
#define SOLARIS_X86_PART_TABLE_SECTOR_OFFSET 1
/*
 * solaris_x86_partition
 *
 * Return:
 *  - 0 for 0 partition
 *  - (positive) number for number of solaris partitions found
 *  - (negative) error code
 */
static int
solaris_x86_partition(struct evms_logical_node **discover_list,
		      struct evms_logical_node *node,
		      struct partition *solaris_x86, int probe_only)
{				/* if TRUE, do not add segments */
	long offset = START_SECT(solaris_x86);
	struct solaris_x86_vtoc *v;
	struct solaris_x86_slice *s;
	int i;
	char *data = NULL;
	int rc = 0;
	int count = 0;

	data = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (data)
		rc = INIT_IO(node,
			     0,
			     START_SECT(solaris_x86) +
			     SOLARIS_X86_PART_TABLE_SECTOR_OFFSET, 1, data);
	else
		rc = -ENOMEM;
	if (!rc) {

		v = (struct solaris_x86_vtoc *) data;

		if (v->v_sanity == SOLARIS_X86_VTOC_SANE) {
			if (v->v_version != 1) {
				LOG_WARNING
				    ("%s: cannot handle version %d vtoc>\n",
				     __FUNCTION__, v->v_version);
			} else {
				for (i = 0; i < v->v_nparts; i++) {
					s = &v->v_slice[i];
					LOG_EXTRA
					    ("s[%d] s_tag(%u), s_flag(%u), s_start(%u), s_size(%u), last_sector(%u)\n",
					     i, s->s_tag, s->s_flag, s->s_start,
					     s->s_size,
					     s->s_start + s->s_size - 1);

					if ((s->s_size == 0)
					    || (s->s_tag == 0x05))
						continue;
					if (!probe_only) {
						rc = mbr_ebr_process_segment
						    (discover_list, node,
						     (u64) (s->s_start +
								  offset),
						     (u64) s->s_size,
						     SOLARIS_X86_PARTITION,
						     cur_comp_part_num++, NULL);
						if (rc)
							break;
					}
					count++;
				}
			}
		}
	}
	if (data)
		kfree(data);
	if (!rc)
		rc = count;
	LOG_DETAILS("%s: %s (%d) partitions\n",
		    __FUNCTION__, probe_only ? " " : "exported", rc);
	return rc;
}
#endif

/*
 * os2lvm_partition() looks for DLAT at last sector of the track containing MBR/EBR
 *
 * Returns:     1 - os2 DLAT was found
 *              0 otherwise
 *
 */
static int
os2lvm_partition(u64 MBR_EBR_sect,
		 struct evms_logical_node *node, struct dla_table_sector *dlat)
{
	struct hd_geometry geometry;
	int rc;
	u32 crc_hold;

	rc = evms_cs_kernel_ioctl(node, HDIO_GETGEO, (unsigned long) &geometry);
	if (rc) {
		LOG_SERIOUS("%s: ioctl failed(%u) on '%s'\n",
			    __FUNCTION__, rc, node->name);
	} else
	    if (!INIT_IO(node, 0, MBR_EBR_sect + geometry.sectors - 1, 1, dlat))
	{
		if ((dlat->DLA_Signature1 == cpu_to_le32(DLA_TABLE_SIGNATURE1))
		    && (dlat->DLA_Signature2 ==
			cpu_to_le32(DLA_TABLE_SIGNATURE2))) {
			crc_hold = le32_to_cpu(dlat->DLA_CRC);
			dlat->DLA_CRC = 0;
			if (evms_cs_calculate_crc
			    (EVMS_INITIAL_CRC, (void *) dlat,
			     node->hardsector_size) == crc_hold)
				return 1;
		}
	}
	return 0;
}

static int
mbr_ebr_process_logical_drive(struct evms_logical_node **discover_list,
			      struct evms_logical_node *node,
			      struct extended_part *ext_info,
			      int i,
			      struct partition *p,
			      int os2lvm, struct dla_table_sector *dlat)
{
	int rc = 0;
	char tmp_buf[EVMS_VOLUME_NAME_SIZE], *partition_name;

	LOG_EXTRA("%s: PartitionTableIndex(%i), Start("PFU64"), Size("PFU64")\n",
		  __FUNCTION__, i, START_SECT(p), NR_SECTS(p));

	if (NR_SECTS(p)) {
		if (is_extended_partition(p)) {
			ext_info->next_ebr_start =
			    (u64) (START_SECT(p) +
					 START_SECT(ext_info->extended));
			ext_info->done = FALSE;	/* not done yet */
		} else {
			partition_name = NULL;
			if (os2lvm && p->sys_ind != LVM_PARTITION_INDICATOR &&
			    le32_to_cpu(dlat->DLA_Array[i].Partition_Start) ==
			    (ext_info->start_sect + START_SECT(p))
			    && le32_to_cpu(dlat->DLA_Array[i].Partition_Size) ==
			    NR_SECTS(p)
			    && dlat->DLA_Array[i].Drive_Letter != '\0') {
				sprintf(tmp_buf, "os2/%c",
					dlat->DLA_Array[i].Drive_Letter);
				partition_name = tmp_buf;
			}
			print_partition_info(__FUNCTION__, p);

			rc = mbr_ebr_process_segment(discover_list,
						     node,
						     ext_info->start_sect +
						     START_SECT(p), NR_SECTS(p),
						     p->sys_ind,
						     cur_comp_part_num++,
						     partition_name);
		}
	}
	return (rc);
}

static int
mbr_ebr_process_ebr(struct evms_logical_node **discover_list,
		    struct evms_logical_node *node,
		    struct extended_part *ext_info, struct mbr_ebr *ebr)
{
	int rc = 0, i, os2lvm;
	struct partition *p;
	struct dla_table_sector *dlat = NULL;

	/* allocate space for the OS2 DLAT info */
	dlat = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (dlat) {
		/* read the dlat for this mbr */
		os2lvm = os2lvm_partition(ext_info->start_sect, node, dlat);

		/* walk thru the partition table in the mbr
		 * processing each partition record.
		 */
		for (i = 0; i < 4; i++) {
			p = &ebr->partitions[i];
			rc = mbr_ebr_process_logical_drive(discover_list,
							   node,
							   ext_info,
							   i, p, os2lvm, dlat);
		}
	} else {
		rc = -ENOMEM;
	}

	/* free the space used for OS2 DLAT info */
	if (dlat)
		kfree(dlat);

	return (rc);
}

static int
mbr_ebr_probe_for_ebr(struct evms_logical_node **discover_list,
		      struct evms_logical_node *node,
		      struct extended_part *ext_info)
{
	int rc = 0;
	u_char *sector_buffer = NULL;
	struct mbr_ebr *ebr = NULL;

	/* allocate a sector size buffer */
	sector_buffer = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (sector_buffer)
		/* read the location of the mbr sector */
		rc = INIT_IO(node, 0, ext_info->start_sect, 1, sector_buffer);
	else
		rc = -ENOMEM;

	if (!rc) {
		ebr = (struct mbr_ebr *) sector_buffer;
		if (validate_mbr_ebr(node, ebr,
				     START_SECT(ext_info->extended),
				     ext_info->start_sect) == TRUE)
			rc = mbr_ebr_process_ebr(discover_list,
						 node, ext_info, ebr);
	}

	if (sector_buffer)
		kfree(sector_buffer);

	return (rc);
}

static int
mbr_ebr_process_extended_partition(struct evms_logical_node **discover_list,
				   struct evms_logical_node *node,
				   struct partition *p)
{
	int rc = 0;
	struct extended_part ext_info;

	memset(&ext_info, 0, sizeof (ext_info));
	ext_info.done = FALSE;
	ext_info.extended = p;
	ext_info.next_ebr_start = START_SECT(p);
	while (ext_info.done == FALSE) {
		ext_info.done = TRUE;	/* assume done, unless we find another EBR */
		ext_info.start_sect = ext_info.next_ebr_start;
		rc = mbr_ebr_probe_for_ebr(discover_list, node, &ext_info);
	}
	return rc;
}

/*
 * is_non_dos_extended
 *
 * This function returns TRUE if the partition entry represents a non-DOS
 * extended partition such as UnixWare, Solaris x86 and BSD
 */
static int
is_non_dos_extended(struct evms_logical_node **discover_list,
		    struct evms_logical_node *node, struct partition *p)
{
	if (NR_SECTS(p)) {
#ifdef CONFIG_BSD_DISKLABEL
		if (SYS_IND(p) == BSD_PARTITION ||
		    SYS_IND(p) == NETBSD_PARTITION ||
		    SYS_IND(p) == OPENBSD_PARTITION)
			return TRUE;
#endif

#ifdef CONFIG_UNIXWARE_DISKLABEL
		if (SYS_IND(p) == UNIXWARE_PARTITION)
			return TRUE;
#endif

#ifdef CONFIG_SOLARIS_X86_PARTITION
		if ((SYS_IND(p) == SOLARIS_X86_PARTITION) &&
		    (solaris_x86_partition(discover_list, node, p, TRUE) > 0))
			return TRUE;
#endif
	}
	return (FALSE);
}

/*
 * mbr_ebr_process_other_primary_partition
 * This function processes other (non-DOS) primary partitions such as
 * UnixWare, Solaris x86 and BSD
 */
static int
mbr_ebr_process_other_primary_partition(struct evms_logical_node
					**discover_list,
					struct evms_logical_node *node,
					struct partition *p)
{
	if (NR_SECTS(p)) {
#ifdef CONFIG_BSD_DISKLABEL
		if (SYS_IND(p) == BSD_PARTITION ||
		    SYS_IND(p) == NETBSD_PARTITION ||
		    SYS_IND(p) == OPENBSD_PARTITION)
			return bsd_disklabel_partition(discover_list, node, p);
#endif

#ifdef CONFIG_UNIXWARE_DISKLABEL
		if (SYS_IND(p) == UNIXWARE_PARTITION)
			return unixware_partition(discover_list, node, p);
#endif

#ifdef CONFIG_SOLARIS_X86_PARTITION
		if (SYS_IND(p) == SOLARIS_X86_PARTITION)
			return solaris_x86_partition(discover_list, node, p,
						     FALSE);
#endif
	}
	return (0);
}

static int
mbr_ebr_process_dos_primary_partition(struct evms_logical_node **discover_list,
				      struct evms_logical_node *node,
				      int i,
				      struct partition *p,
				      int os2lvm, struct dla_table_sector *dlat)
{
	int rc = 0;
	char tmp_buf[EVMS_VOLUME_NAME_SIZE], *partition_name;

	LOG_EVERYTHING("%s: PartitionTableIndex(%i), Start("PFU64"), Size("PFU64")\n",
		       __FUNCTION__, i, START_SECT(p), NR_SECTS(p));

	if (NR_SECTS(p)) {

		if (is_extended_partition(p))
			rc = mbr_ebr_process_extended_partition(discover_list,
								node, p);

		else {
			partition_name = NULL;
			if (os2lvm && p->sys_ind != LVM_PARTITION_INDICATOR &&
			    le32_to_cpu(dlat->DLA_Array[i].Partition_Start) ==
			    START_SECT(p)
			    && le32_to_cpu(dlat->DLA_Array[i].Partition_Size) ==
			    NR_SECTS(p)
			    && dlat->DLA_Array[i].Drive_Letter != '\0') {
				sprintf(tmp_buf, "os2/%c",
					dlat->DLA_Array[i].Drive_Letter);
				partition_name = tmp_buf;
			}
			print_partition_info(__FUNCTION__, p);

			rc = mbr_ebr_process_segment(discover_list,
						     node,
						     START_SECT(p),
						     NR_SECTS(p),
						     p->sys_ind,
						     i + 1, partition_name);
		}
	}
	return (rc);
}

static int
mbr_ebr_process_mbr(struct evms_logical_node **discover_list,
		    struct evms_logical_node *node, struct mbr_ebr *mbr)
{
	int rc = 0, i, os2lvm;
	struct partition *p;
	struct dla_table_sector *dlat = NULL;

	cur_comp_part_num = 5;	/* set this value for each disk */

	/* allocate space for the OS2 DLAT info */
	dlat = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (dlat) {
		/* read the dlat for this mbr */
		os2lvm = os2lvm_partition(0, node, dlat);

		/* Pass 1: walk thru the partition table in the mbr
		 * processing each partition record.
		 */
		for (i = 0; i < 4; i++) {
			p = &mbr->partitions[i];
			if (is_non_dos_extended(discover_list, node, p)) {
				LOG_DETAILS
				    (" Found and skip a non-dos extended partition.\n");
				continue;
			}

			mbr_ebr_process_dos_primary_partition(discover_list,
							      node,
							      i,
							      p, os2lvm, dlat);
		}

		/* Pass 2: walk thru the partition table in the mbr
		 * processing each partition record for non-DOS extended partitions
		 */
		for (i = 0; i < 4; i++) {
			p = &mbr->partitions[i];
			mbr_ebr_process_other_primary_partition(discover_list,
								node, p);
		}

	} else {
		rc = -ENOMEM;
	}

	/* free the space used for OS2 DLAT info */
	if (dlat)
		kfree(dlat);

	return (rc);
}

static int
mbr_ebr_probe_for_mbr(struct evms_logical_node **discover_list,
		      struct evms_logical_node *node)
{
	int rc = 0;
	u_char *sector_buffer = NULL;
	struct mbr_ebr *mbr = NULL;

	LOG_DEBUG("%s: probing (%s).\n", __FUNCTION__, node->name);

	/* allocate a sector size buffer */
	sector_buffer = kmalloc(node->hardsector_size, GFP_KERNEL);
	if (sector_buffer)
		/* read the location of the mbr sector */
		rc = INIT_IO(node, 0, 0, 1, sector_buffer);
	else
		rc = -ENOMEM;
	if (rc) {
		LOG_ERROR("%s: read error(%d) on '%s'.\n",
			  __FUNCTION__, rc, node->name);
	} else {
		mbr = (struct mbr_ebr *) sector_buffer;
		if (validate_mbr_ebr(node, mbr, 0, 0) == TRUE) {
			/* since it looks like this disk has a
			 * valid MBR, remove the disk node from
			 * the discover list. it may already be
			 * on the global list, or it will be
			 * added to it. in the case of an mbr
			 * with no partitions, it is simply
			 * removed and forgotten. when one or
			 * more partitions are created, the
			 * disk will be examined and handled
			 * properly during the following
			 * rediscover operation.
			 */
			evms_cs_remove_logical_node_from_list(discover_list,
							      node);

			rc = mbr_ebr_process_mbr(discover_list, node, mbr);
		}
	}

	if (sector_buffer)
		kfree(sector_buffer);

	return (rc);
}

/*
 * Function: mbr_ebr_partition_discover
 *
 */
static int
mbr_ebr_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;
		if (node->plugin->id == plugin_header.id)
			/* don't recurse into our own objects
			 */
			continue;
		mbr_ebr_probe_for_mbr(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: mbr_ebr_partition_delete
 *
 */
static int
mbr_ebr_partition_delete(struct evms_logical_node *segment)
{
	int rc = 0;
	struct dos_private *dos_prv;
	struct evms_logical_node *empty_disk = NULL;

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

	if (!segment) {
		rc = -ENODEV;
	} else {
		dos_prv = segment->private;
		if (dos_prv) {
			/* remove the segment from the
			 * disk's segment list
			 */
			rc = remove_segment_from_disk(dos_prv->source_disk,
						      segment, &empty_disk);
			/* free the local instance data */
			kfree(dos_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: mbr_ebr_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
mbr_ebr_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("PFU64").\n",
	     (io_flag) ? "WRITE" : "READ", node->total_vsectors - 1, node->name,
	     (u64) bh->b_rsector);

	bh->b_end_io(bh, 0);
}

/*
 * Function: mbr_ebr_partition_read
 *
 */
static void
mbr_ebr_partition_read(struct evms_logical_node *partition,
		       struct buffer_head *bh)
{
	struct dos_private *dos_prv = partition->private;

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

/*
 * Function: mbr_ebr_partition_write
 *
 */
static void
mbr_ebr_partition_write(struct evms_logical_node *partition,
			struct buffer_head *bh)
{
	struct dos_private *dos_prv = partition->private;

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

/*
 * Function: mbr_ebr_partition_init_io
 *
 */
static int
mbr_ebr_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 dos_private *dos_prv = partition->private;

	if ((sect_nr + num_sects) <= partition->total_vsectors) {
		rc = INIT_IO(dos_prv->source_disk, io_flag,
			     sect_nr + dos_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,
		     (dos_prv->nr_sects - 1), sect_nr, num_sects);
		rc = -EINVAL;
	}

	return (rc);
}

/*
 * Function: mbr_ebr_partition_ioctl
 *
 */
static int
mbr_ebr_partition_ioctl(struct evms_logical_node *partition,
			struct inode *inode,
			struct file *file, unsigned int cmd, unsigned long arg)
{
	struct dos_private *dos_prv;
	struct hd_geometry hd_geo;
	int rc;

	rc = 0;
	dos_prv = partition->private;
	if (!inode)
		return -EINVAL;
	switch (cmd) {
	case HDIO_GETGEO:
		{
			rc = IOCTL(dos_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 = dos_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 += dos_prv->start_sect;
			/* intentionally fall thru to
			 * default ioctl down to device
			 * manager.
			 */
		}
	default:
		rc = IOCTL(dos_prv->source_disk, inode, file, cmd, arg);
	}
	return rc;
}

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

static void __exit
dos_part_exit(void)
{
	evms_cs_unregister_plugin(&plugin_header);
}

module_init(dos_part_init);
module_exit(dos_part_exit);
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
