/* -*- 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/evms.c
 *
 * EVMS Base and Common Services
 *
 */

//#define LOCAL_DEBUG 1

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/blk.h>
#include <linux/blkpg.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include <linux/swap.h>
#include <linux/smp_lock.h>
#include <linux/reboot.h>
#include <linux/compiler.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#include <net/checksum.h>
#include <linux/evms/evms.h>
#include <asm/atomic.h>

//#define VFS_PATCH_PRESENT

/* prefix used in logging messages */
#define LOG_PREFIX

struct evms_registered_plugin {
	struct evms_plugin_header *plugin;
	struct evms_registered_plugin *next;
};
static struct evms_registered_plugin *registered_plugin_head = NULL;

static struct evms_list_node *evms_global_device_list = NULL;
static struct evms_list_node *evms_global_feature_node_list = NULL;
static struct evms_list_node *evms_global_notify_list = NULL;

int evms_info_level = EVMS_INFO_LEVEL;
struct proc_dir_entry *evms_proc_dir = NULL;
EXPORT_SYMBOL(evms_info_level);
static struct evms_logical_volume *evms_logical_volumes;
static int evms_volumes = 0;
/* a few variables to aid in detecting memory leaks.
 * these variables are always in use, regardless of
 * the state of EVMS_MEM_DEBUG.
 */
static atomic_t evms_allocs = ATOMIC_INIT(0);
static atomic_t evms_logical_nodes = ATOMIC_INIT(0);

u8 *evms_primary_string = "primary";
EXPORT_SYMBOL(evms_primary_string);
u8 *evms_secondary_string = "secondary";
EXPORT_SYMBOL(evms_secondary_string);

static struct evms_version evms_svc_version = {
	.major		= EVMS_COMMON_SERVICES_MAJOR,
	.minor		= EVMS_COMMON_SERVICES_MINOR,
	.patchlevel	= EVMS_COMMON_SERVICES_PATCHLEVEL
};

/* Handles for "private" EVMS object pools */
static struct evms_pool_mgmt *evms_io_notify_pool;

/* Handles for "public" EVMS object pools */
struct evms_pool_mgmt *evms_bh_pool;
EXPORT_SYMBOL(evms_bh_pool);

/* Handle for the devfs directory entry */
devfs_handle_t evms_dir_devfs_handle;
devfs_handle_t evms_blk_devfs_handle;

/**********************************************************/
/* SYSCTL - EVMS folder					  */
/**********************************************************/

#ifdef CONFIG_PROC_FS
static struct ctl_table_header *evms_table_header;
static int evms_info_level_min = EVMS_INFO_CRITICAL;
static int evms_info_level_max = EVMS_INFO_EVERYTHING;

static ctl_table evms_table[] = {
	{DEV_EVMS_INFO_LEVEL, "evms_info_level",
	 &evms_info_level, sizeof (int), 0644, NULL,
	 &proc_dointvec_minmax, &sysctl_intvec,
	 NULL, &evms_info_level_min, &evms_info_level_max},
	{0}
};

static ctl_table evms_dir_table[] = {
	{DEV_EVMS, "evms", NULL, 0, 0555, evms_table},
	{0}
};

static ctl_table dev_dir_table[] = {
	{CTL_DEV, "dev", NULL, 0, 0555, evms_dir_table},
	{0}
};
#endif

/**********************************************************/
/* START -- arch ioctl32 support                          */
/**********************************************************/
#if defined(CONFIG_PPC64) || defined(CONFIG_SPARC64) || defined(CONFIG_X86_64)
#include <linux/evms/evms_bbr_k.h>
#include <linux/raid/md.h>

extern asmlinkage long
sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);

extern int
register_ioctl32_conversion(unsigned int cmd, void *handler);

extern int
unregister_ioctl32_conversion(unsigned int cmd);

#define uvirt_to_kernel(__x) ((unsigned long)(__x))
typedef unsigned int __uvirt_addr;

struct evms_sector_io32 {
	u64 disk_handle;
	u64 starting_sector;
	u64 sector_count;
	s32 io_flag;
	s32 status;
	__uvirt_addr  buffer_address;
};

struct evms_rediscover32 {
	s32 status;
	u32 drive_count;
	__uvirt_addr  drive_array;
};

struct evms_compute_csum32 {
	__uvirt_addr  buffer_address;
	s32 buffer_size;
	u32 insum;
	u32 outsum;
	s32 status;
};

struct evms_plugin_ioctl32 {
	u32 feature_id;
	s32 feature_command;
	s32 status;
	u32 data_size;
	__uvirt_addr  feature_ioctl_data;
};

struct evms_notify_bbr32 {
	char object_name[EVMS_VOLUME_NAME_SIZE+1];
	u64 count;
	u64 start_sect;
	u64 nr_sect;
	__uvirt_addr buffer;
	s32 rw;
};

#define EVMS_MD_ID              4
#define EVMS_MD_PERS_IOCTL_CMD  1
#define EVMS_MD_ADD             2
#define EVMS_MD_REMOVE          3
#define EVMS_MD_ACTIVATE        4
#define EVMS_MD_DEACTIVATE      5
#define EVMS_MD_GET_ARRAY_INFO  6
#define EVMS_MD_RAID5_INIT_IO   1

struct evms_md_ioctl {
	int mddev_idx;
	int cmd;
	void *arg;
};

struct evms_md_ioctl32 {
	u32 mddev_idx;
	u32 cmd;
	__uvirt_addr arg;
};

struct evms_md_array_info {
	unsigned long state;
	mdp_super_t *sb;
};

struct evms_md_array_info32 {
	u32 state;
	__uvirt_addr sb;
};

struct raid5_ioctl_init_io {
	int rw;
	u64 lsn;
	u64 nr_sects;
	void *data;
};

struct raid5_ioctl_init_io32 {
	s32 rw;
	u64 lsn;
	u64 nr_sects;
	__uvirt_addr data;
};

#define EVMS_MD_PLUGIN_ID ((IBM_OEM_ID << 16) | \
			   (EVMS_REGION_MANAGER << 12) | EVMS_MD_ID)
#define EVMS_BBR_PLUGIN_ID ((IBM_OEM_ID << 16) | \
			    (EVMS_FEATURE << 12) | EVMS_BBR_FEATURE_ID)

#define EVMS_SECTOR_IO_32 _IOWR(EVMS_MAJOR, \
				EVMS_SECTOR_IO_NUMBER, \
				struct evms_sector_io32)
#define EVMS_REDISCOVER_VOLUMES_32 _IOWR(EVMS_MAJOR, \
					 EVMS_REDISCOVER_VOLUMES_NUMBER, \
					 struct evms_rediscover32)
#define EVMS_COMPUTE_CSUM_32 _IOWR(EVMS_MAJOR, \
				   EVMS_COMPUTE_CSUM_NUMBER, \
				   struct evms_compute_csum32)
#define EVMS_PLUGIN_IOCTL_32 _IOR(EVMS_MAJOR, \
				  EVMS_PLUGIN_IOCTL_NUMBER, \
				  struct evms_plugin_ioctl32)

static int evms_sector_io(unsigned int fd,
			  unsigned int cmd,
			  unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	struct evms_sector_io32 parms32;
	struct evms_sector_io_pkt parms;
	unsigned int kcmd;
	void *karg;
	int rc = 0;

	if (copy_from_user(&parms32, (struct evms_sector_io32 *)arg,
			   sizeof(struct evms_sector_io32)))
		return -EFAULT;

	parms.disk_handle     = parms32.disk_handle;
	parms.io_flag         = parms32.io_flag;
	parms.starting_sector = parms32.starting_sector;
	parms.sector_count    = parms32.sector_count;
	parms.buffer_address  = (u8 *)uvirt_to_kernel(parms32.buffer_address);
	parms.status          = 0;

	kcmd = EVMS_SECTOR_IO;
	karg = &parms;

	set_fs(KERNEL_DS);
	rc = sys_ioctl(fd, kcmd, (unsigned long)karg);
	set_fs(old_fs);

	parms32.status = parms.status;

	if (copy_to_user((struct evms_sector_io32 *)arg, &parms32,
			 sizeof(struct evms_sector_io32)))
		return -EFAULT;

	return rc;
}

static int evms_rediscover(unsigned int fd,
			   unsigned int cmd,
			   unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	struct evms_rediscover32 parms32;
	struct evms_rediscover_pkt parms;
	unsigned int kcmd;
	void *karg;
	int rc = 0;

	if (copy_from_user(&parms32, (struct evms_rediscover32 *)arg,
			   sizeof(struct evms_rediscover32)))
		return -EFAULT;

	parms.drive_count = parms32.drive_count;
	parms.drive_array = (void *)uvirt_to_kernel(parms32.drive_array);
	parms.status      = 0;

	kcmd = EVMS_REDISCOVER_VOLUMES;
	karg = &parms;

	set_fs(KERNEL_DS);
	rc = sys_ioctl(fd, kcmd, (unsigned long)karg);
	set_fs(old_fs);

	parms32.status = parms.status;

	if (copy_to_user((struct evms_rediscover32 *)arg, &parms32,
			 sizeof(struct evms_rediscover32)))
		return -EFAULT;

	return rc;
}

static int evms_compute_csum(unsigned int fd,
			     unsigned int cmd,
			     unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	struct evms_compute_csum32 parms32;
	struct evms_compute_csum_pkt parms;
	unsigned int kcmd;
	void *karg;
	int rc = 0;

	if (copy_from_user(&parms32, (struct evms_compute_csum32 *)arg,
			   sizeof(struct evms_compute_csum32)))
		return -EFAULT;

	parms.insum          = parms32.insum;
	parms.outsum         = parms32.outsum;
	parms.buffer_size    = parms32.buffer_size;
	parms.buffer_address = (void *)uvirt_to_kernel(parms32.buffer_address);
	parms.status         = 0;

	kcmd = EVMS_COMPUTE_CSUM;
	karg = &parms;

	set_fs(KERNEL_DS);
	rc = sys_ioctl(fd, kcmd, (unsigned long)karg);
	set_fs(old_fs);

	parms32.status = parms.status;
	parms32.outsum = parms.outsum;

	if (copy_to_user((struct evms_compute_csum32 *)arg, &parms32,
			 sizeof(struct evms_compute_csum32)))
		return -EFAULT;

	return rc;
}

static int evms_bbr_plugin_ioctl(unsigned int fd,
				 unsigned int cmd,
				 unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	struct evms_notify_bbr32 bbr_parms32;
	struct evms_notify_bbr bbr_parms;
	struct evms_plugin_ioctl_pkt *parms =
		(struct evms_plugin_ioctl_pkt *)arg;
	void *old_ptr = NULL;
	int rc;

	if (copy_from_user(&bbr_parms32,
			   (struct evms_notify_bbr32 *)parms->feature_ioctl_data,
			   sizeof(struct evms_notify_bbr32)))
		return -EFAULT;

	memcpy(&bbr_parms, &bbr_parms32, sizeof(struct evms_notify_bbr32));
	bbr_parms.buffer = (void *)uvirt_to_kernel(bbr_parms32.buffer);
	bbr_parms.rw = bbr_parms32.rw;
	old_ptr = parms->feature_ioctl_data;
	parms->feature_ioctl_data = &bbr_parms;

	set_fs(KERNEL_DS);
	rc = sys_ioctl(fd, cmd, arg);
	set_fs(old_fs);

	parms->feature_ioctl_data = old_ptr;

	if (!rc) {
		bbr_parms32.nr_sect = bbr_parms.nr_sect;
		rc = copy_to_user((struct evms_notify_bbr32 *)parms->feature_ioctl_data,
				  &bbr_parms32,
				  sizeof(struct evms_notify_bbr32));
	}

	return rc;
}

static int evms_md_plugin_ioctl(unsigned int fd,
				unsigned int cmd,
				unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	void *old_ptr = NULL;
	void *old_md_ptr = NULL;
	struct evms_md_ioctl32 md_parms32;
	struct evms_md_ioctl md_parms;
	struct evms_md_array_info32 md_array_parms32;
	struct evms_md_array_info md_array_parms;
	struct raid5_ioctl_init_io32 r5_init_io_parms32;
	struct raid5_ioctl_init_io r5_init_io_parms;
	struct evms_plugin_ioctl_pkt *parms =
		(struct evms_plugin_ioctl_pkt *)arg;
	int rc;

	if (copy_from_user(&md_parms32,
			   (struct evms_md_ioctl*)parms->feature_ioctl_data,
			   sizeof(struct evms_md_ioctl32)))
		return -EFAULT;

	md_parms.mddev_idx = md_parms32.mddev_idx;
	md_parms.cmd = md_parms32.cmd;
	md_parms.arg = (void *)uvirt_to_kernel(md_parms32.arg);
	old_ptr = parms->feature_ioctl_data;
	parms->feature_ioctl_data = &md_parms;

	if (parms->feature_command == EVMS_MD_GET_ARRAY_INFO) {
		if (copy_from_user(&md_array_parms32,
				   (struct evms_md_array_info32*)md_parms.arg,
				   sizeof(struct evms_md_array_info32)))
			return -EFAULT;

		md_array_parms.state = md_array_parms32.state;
		md_array_parms.sb =
			(void *)uvirt_to_kernel(md_array_parms32.sb);
		old_md_ptr = (void *)md_parms.arg;
		md_parms.arg = &md_array_parms;
	} else if (parms->feature_command == EVMS_MD_PERS_IOCTL_CMD) {
		if (md_parms.cmd == EVMS_MD_RAID5_INIT_IO) {
			if (copy_from_user(&r5_init_io_parms32,
					   (struct raid5_ioctl_init_io32*)md_parms.arg,
					   sizeof(struct raid5_ioctl_init_io32)))
				return -EFAULT;

			r5_init_io_parms.rw = r5_init_io_parms32.rw;
			r5_init_io_parms.lsn = r5_init_io_parms32.lsn;
			r5_init_io_parms.nr_sects = r5_init_io_parms32.nr_sects;
			r5_init_io_parms.data =
				(void *)uvirt_to_kernel(r5_init_io_parms32.data);
			old_md_ptr = (void *)md_parms.arg;
			md_parms.arg = &r5_init_io_parms;
		}
	}

	set_fs(KERNEL_DS);
	rc = sys_ioctl(fd, cmd, arg);
	set_fs(old_fs);

	parms->feature_ioctl_data = old_ptr;
	md_parms.arg = old_md_ptr;

	if (!rc) {
		if (parms->feature_command == EVMS_MD_GET_ARRAY_INFO) {
			md_array_parms32.state = md_array_parms.state;
			rc = copy_to_user((struct evms_md_array_info32 *)md_parms.arg,
					  &md_array_parms32,
					  sizeof(struct evms_md_array_info32));
		}
		if (!rc) {
			md_parms32.mddev_idx = md_parms.mddev_idx;
			rc = copy_to_user((struct evms_md_ioctl*)parms->feature_ioctl_data,
					  &md_parms32,
					  sizeof(struct evms_md_ioctl32));
		}
	}

	return rc;
}

static int evms_plugin_ioctl(unsigned int fd,
			     unsigned int cmd,
			     unsigned long arg)
{
	mm_segment_t old_fs = get_fs();
	struct evms_plugin_ioctl32 parms32;
	struct evms_plugin_ioctl_pkt parms;
	unsigned int kcmd;
	void *karg;
	int rc;

	if (copy_from_user(&parms32, (struct evms_plugin_ioctl32 *)arg,
			   sizeof(struct evms_plugin_ioctl32)))
		return -EFAULT;

	parms.feature_id = parms32.feature_id;
	parms.feature_command = parms32.feature_command;
	parms.status = parms32.status;
	parms.feature_ioctl_data =
		(void *)uvirt_to_kernel(parms32.feature_ioctl_data);

	kcmd = EVMS_PLUGIN_IOCTL;
	karg = &parms;

	switch (parms.feature_id) {
	case EVMS_MD_PLUGIN_ID:
		rc = evms_md_plugin_ioctl(fd, kcmd, (unsigned long)karg);
		break;
	case EVMS_BBR_PLUGIN_ID:
		rc = evms_bbr_plugin_ioctl(fd, kcmd, (unsigned long)karg);
		break;
	default:
		set_fs(KERNEL_DS);
		rc = sys_ioctl(fd, kcmd, (unsigned long)karg);
		set_fs(old_fs);
	}

	if (!rc) {
		parms32.status = parms.status;
		rc = copy_to_user((struct evms_plugin_ioctl32 *)arg, &parms32,
				  sizeof(struct evms_plugin_ioctl32));
	}

	return rc;
}
#endif

/**********************************************************/
/* START -- exported functions/Common Services            */
/**********************************************************/

int
evms_cs_check_version(struct evms_version *required,
		      struct evms_version *actual)
{
	if (required->major != actual->major)
		return -EINVAL;
	else if (required->minor > actual->minor)
		return -EINVAL;
	else if (required->minor == actual->minor)
		if (required->patchlevel > actual->patchlevel)
			return -EINVAL;
	return 0;
}

EXPORT_SYMBOL(evms_cs_check_version);

int
evms_cs_allocate_logical_node(struct evms_logical_node **pp)
{
	*pp = kmalloc(sizeof (struct evms_logical_node), GFP_KERNEL);
	if (*pp) {
		memset(*pp, 0, sizeof (struct evms_logical_node));
		atomic_inc(&evms_logical_nodes);
		return 0;
	}
	return -ENOMEM;
}

EXPORT_SYMBOL(evms_cs_allocate_logical_node);

void
evms_cs_deallocate_volume_info(struct evms_logical_node *p)
{
	if (p->iflags & EVMS_FEATURE_BOTTOM) {
		evms_cs_remove_item_from_list(&evms_global_feature_node_list,p);
		kfree(p->volume_info);
		p->volume_info = NULL;
		p->iflags &= ~EVMS_FEATURE_BOTTOM;
	}
}

EXPORT_SYMBOL(evms_cs_deallocate_volume_info);

void
evms_cs_deallocate_logical_node(struct evms_logical_node *p)
{
	if (p->next) {
		LOG_SERIOUS("Deallocating object whose NEXT ptr is not null!!\n");
	}
	evms_cs_deallocate_volume_info(p);
	if (p->feature_header) {
		kfree(p->feature_header);
		p->feature_header = NULL;
	}
	kfree(p);
	atomic_dec(&evms_logical_nodes);
}

EXPORT_SYMBOL(evms_cs_deallocate_logical_node);

/*
 * Function:     evms_cs_register_plugin
 * Description: This function is exported so that all plugins can register with EVMS
 */
int
evms_cs_register_plugin(struct evms_plugin_header *plugin)
{
	int rc = 0;
	struct evms_registered_plugin *reg_record, **pp;
	struct evms_version *ver;

	ver = &plugin->required_services_version;

	LOG_EXTRA("registering plugin (plugin.id=%d.%d.%d, "
		  "plugin.ver=%d.%d.%d, req.svc.ver=%d.%d.%d)\n",
		  GetPluginOEM(plugin->id), GetPluginType(plugin->id),
		  GetPluginID(plugin->id), plugin->version.major,
		  plugin->version.minor, plugin->version.patchlevel,
		  ver->major, ver->minor, ver->patchlevel);

	/* check common services requirements */
	rc = evms_cs_check_version(ver, &evms_svc_version);
	if (rc) {
		LOG_SERIOUS("plugin failed to load: common services "
			    "(vers:%d,%d,%d) incompatibility!\n",
			    EVMS_COMMON_SERVICES_MAJOR,
			    EVMS_COMMON_SERVICES_MINOR,
			    EVMS_COMMON_SERVICES_PATCHLEVEL);
	}
	if (!rc) {
		/* ensure a plugin with this feature id is
		 * not already loaded.
		 */
		for (pp = &registered_plugin_head; *pp; pp = &(*pp)->next) {
			if ((*pp)->plugin->id == plugin->id) {
				rc = -EBUSY;
				LOG_ERROR("error(%d) attempting to load another "
					  "plugin with id(%x).\n", rc, plugin->id);
			}
		}
	}
	if (!rc) {
		/* ensure the plugin has provided functions for
		 * the mandatory entry points.
		 */
		if (!plugin->fops->discover) {
			rc = -EINVAL;
		} else if (!plugin->fops->init_io) {
			rc = -EINVAL;
		} else if (!plugin->fops->ioctl) {
			rc = -EINVAL;
		} else if (!plugin->fops->read) {
			rc = -EINVAL;
		} else if (!plugin->fops->write) {
			rc = -EINVAL;
		} else if (!plugin->fops->delete) {
			rc = -EINVAL;
		}
	}
	if (!rc) {
		/* allocate a new plugin registration record */
		reg_record =
		    kmalloc(sizeof (struct evms_registered_plugin), GFP_KERNEL);
		if (!reg_record) {
			rc = -ENOMEM;
		}
	}
	if (!rc) {
		memset(reg_record, 0, sizeof (struct evms_registered_plugin));
		/* store ptr to plugin header in new registration record */
		reg_record->plugin = plugin;

		/* terminate the record */
		reg_record->next = NULL;

		/* find end of the plugin registration list */
		for (pp = &registered_plugin_head; *pp; pp = &(*pp)->next) ;
		/* add registration record to list */
		*pp = reg_record;

		/* increment the usage count */
		MOD_INC_USE_COUNT;
	}

	return rc;
}

EXPORT_SYMBOL(evms_cs_register_plugin);

/*
 * Function:     evms_cs_unregister_plugin
 * Description: This function is exported so that all plugins can
 * unregister with EVMS
 */
int
evms_cs_unregister_plugin(struct evms_plugin_header *plugin)
{
	int rc = 0, found = FALSE;
	struct evms_registered_plugin **pp;
	struct evms_version *ver;

	ver = &plugin->required_services_version;

	LOG_EXTRA("unregistering plugin (plugin.id=%d.%d.%d, "
		  "plugin.ver=%d.%d.%d, req.svc.ver=%d.%d.%d)\n",
		  GetPluginOEM(plugin->id), GetPluginType(plugin->id),
		  GetPluginID(plugin->id), plugin->version.major,
		  plugin->version.minor, plugin->version.patchlevel,
		  ver->major, ver->minor, ver->patchlevel);
	/* ensure a plugin with this feature id is
	 * currently loaded.
	 */
	for (pp = &registered_plugin_head; *pp; pp = &(*pp)->next) {
		if ((*pp)->plugin->id == plugin->id) {
			found = TRUE;
			break;
		}
	}
	if (!found) {
		rc = -ENOPKG;
		LOG_ERROR("error(%d) attempt to unload a non-loaded plugin "
			  "with id(%x).\n", rc, plugin->id);
	}
	/* actually unload the plugin now */
	if (!rc) {
		struct evms_registered_plugin *tmp = *pp;

		/* remove the plugin record from our
		 * internal plugin list
		 */
		*pp = (*pp)->next;
		/* deallocate the plugin registration record
		 */
		kfree(tmp);

		/* decrement the usage count */
		MOD_DEC_USE_COUNT;
	}
	return rc;
}

EXPORT_SYMBOL(evms_cs_unregister_plugin);

/* function: evms_cs_add_logical_node_to_list
 *
 * This functions adds a new logical node to the end of a
 * node list.
 *
 * NOTE: This function is only expected to be called at
 * discovery time, which is singled threaded by nature,
 * and therefore doesn't need to be made SMP safe.
 */
int
evms_cs_add_logical_node_to_list(struct evms_logical_node **list_head,
				 struct evms_logical_node *node)
{
	int rc = 0;
	struct evms_logical_node **pp = NULL;

	/* check to make sure node is not already on a list */
	if (node->next)
		rc = 1;
	else
		/* check to make sure node being added is not already in the list */
		for (pp = list_head; *pp; pp = &(*pp)->next)
			if (*pp == node) {
				rc = 2;
				break;
			}

	/* add node to the end of the list */
	if (!rc)
		*pp = node;

	return rc;
}

EXPORT_SYMBOL(evms_cs_add_logical_node_to_list);

/* function: evms_cs_remove_logical_node_from_list
 *
 * This functions removes a new logical node from a node list.
 *
 * NOTE: This function is only expected to be called at
 * discovery time, which is singled threaded by nature,
 * and therefore doesn't need to be made SMP safe.
 */
int
evms_cs_remove_logical_node_from_list(struct evms_logical_node **list_head,
				      struct evms_logical_node *node)
{
	/* remove this node from the head of the list */
	int rc = 1;		/* assume failure until target node is found */
	struct evms_logical_node **pp;
	for (pp = list_head; *pp; pp = &(*pp)->next)
		if (*pp == node) {
			*pp = (*pp)->next;
			node->next = NULL;
			rc = 0;
			break;
		}
	return rc;
}

EXPORT_SYMBOL(evms_cs_remove_logical_node_from_list);

int
evms_cs_kernel_ioctl(struct evms_logical_node *node, unsigned int cmd,
		     unsigned long arg)
{
	int rc = 0;
	struct inode tmp_inode;
	mm_segment_t fs;

	lock_kernel();
	fs = get_fs();
	set_fs(get_ds());
	rc = IOCTL(node, &tmp_inode, NULL, cmd, arg);
	set_fs(fs);
	unlock_kernel();

	return rc;

}

EXPORT_SYMBOL(evms_cs_kernel_ioctl);

/*
 * function: evms_cs_size_in_vsectors
 *
 * In EVMS a V(irtual)Sector is 512 bytes in size.
 * This function computes the number of VSECTORs an specified
 * item size would require.
 *
 * NOTE: This function has been coded to work with 64 bit values.
 */
unsigned long
evms_cs_size_in_vsectors(long long item_size)
{
	long long sectors;

	sectors = item_size >> EVMS_VSECTOR_SIZE_SHIFT;
	if (item_size & (EVMS_VSECTOR_SIZE - 1))
		sectors++;

	return sectors;
}

EXPORT_SYMBOL(evms_cs_size_in_vsectors);

/*
 * function: evms_cs_log2
 *
 * this function computes the power of the 2 of specified
 * value. If the value is 0, a -1 is returned. If the value
 * is NOT a power of 2, a -2 is return. Otherwise the power
 * of 2 is returned.
 */
int
evms_cs_log2(long long value)
{
	int result = -1;
	long long tmp;

	if (value) {
		tmp = value;
		result++;
		while (!(tmp & 1)) {
			result++;
			tmp >>= 1;
		}
		if (tmp != 1) {
			result = -2;
		}
	}
	return result;
}

EXPORT_SYMBOL(evms_cs_log2);

/*
 * Functions:
 *
 *              build_crc_table()
 *              calculate_crc()
 *
 *
 * Description: The functions in this module provide a means of calculating
 *              the 32 bit CRC for a block of data.  build_crc_table must
 *              be called to initialize this module.  calculate_crc must
 *              NOT be used until after build_crc_table has been called.
 *              Once build_crc_table has been called, calculate_crc can
 *              be used to calculate the crc of the data residing in a
 *              user specified buffer.
 *
 */

#define CRC_POLYNOMIAL     0xEDB88320L

static u32 crc_table[256];
static u32 crc_table_built = FALSE;

/**
 * Function Name: build_crc_table
 *
 * Description: This module implements the crc function using
 *              a table driven method.  The required table
 *              must be setup before the calculate_crc
 *              function can be used.  This table only needs
 *              to be set up once.  This function sets up the
 *              crc table needed by calculate_crc.
 **/
static void
build_crc_table(void)
{
	u32 i, j, crc;

	for (i = 0; i <= 255; i++) {
		crc = i;
		for (j = 8; j > 0; j--) {
			if (crc & 1)
				crc = (crc >> 1) ^ CRC_POLYNOMIAL;
			else
				crc >>= 1;
		}
		crc_table[i] = crc;
	}
	crc_table_built = TRUE;
}

/**
 * Function Name: calculate_crc
 *
 * Description: This function calculates the crc value for
 *              the data in the buffer specified by Buffer.
 *
 * Input: u32    crc : This is the starting crc.  If you are
 *                     starting a new crc calculation, then
 *                     this should be set to 0xFFFFFFFF.  If
 *                     you are continuing a crc calculation
 *                     (i.e. all of the data did not fit in
 *                     the buffer so you could not calculate
 *                     the crc in a single operation), then
 *                     this is the crc output by the last
 *                     calculate_crc call.
 *
 * Output: The crc for the data in the buffer, based upon the value
 *         of the input parameter crc.
 **/
u32
evms_cs_calculate_crc(u32 crc, void *buffer, u32 buffersize)
{
	unsigned char *current_byte;
	u32 temp1, temp2, i;

	current_byte = (unsigned char *) buffer;
	/* Make sure the crc table is available */
	if (crc_table_built == FALSE)
		build_crc_table();
	/* Process each byte in the buffer. */
	for (i = 0; i < buffersize; i++) {
		temp1 = (crc >> 8) & 0x00FFFFFF;
		temp2 =
		    crc_table[(crc ^ (u32) *
			       current_byte) & (u32) 0xff];
		current_byte++;
		crc = temp1 ^ temp2;
	}
	return crc;
}

EXPORT_SYMBOL(evms_cs_calculate_crc);

#define EVMS_ORIGINAL_CALLBACK_FLAG	1<<0
typedef struct io_notify_s {
	unsigned int flags;
	void *private;
	struct buffer_head *bh;
	u64 rsector;
	kdev_t rdev;
	void *b_private;
	void (*callback_function) (struct evms_logical_node * node,
				   struct buffer_head * bh,
				   int uptodate, int *redrive);
	struct io_notify_s *next;
} io_notify_t;

struct evms_pool_mgmt *
evms_cs_create_pool(int objsize,
		    u8 * pool_name,
		    void (*ctor) (void *, kmem_cache_t *, unsigned long),
		    void (*dtor) (void *, kmem_cache_t *, unsigned long))
{
	struct evms_pool_mgmt *pool;

	/* create the pool management structure */
	pool = kmalloc(sizeof (struct evms_pool_mgmt), GFP_KERNEL);
	if (!pool) {
		LOG_CRITICAL("Cannot create %s pool mgmt structure",
			     pool_name);
		return NULL;
	}
	/* initialize various field in pool mgmt structure */
	memset(pool, 0, sizeof (struct evms_pool_mgmt));
	pool->member_size = objsize;
	pool->name = pool_name;
	atomic_set(&pool->waiters, 0);
	init_waitqueue_head(&pool->wait_queue);
	/* go create the pool */
	pool->cachep = kmem_cache_create(pool->name,
					 pool->member_size,
					 0, SLAB_HWCACHE_ALIGN, ctor, dtor);
	if (!pool->cachep) {
		LOG_CRITICAL("Cannot create %s SLAB cache", pool->name);
		kfree(pool);
		pool = NULL;
	}
	return pool;
}

EXPORT_SYMBOL(evms_cs_create_pool);

void *
evms_cs_allocate_from_pool(struct evms_pool_mgmt *pool, int blockable)
{
	void *objp;

	while (1) {
		objp = kmem_cache_alloc(pool->cachep, SLAB_NOIO);
		if (objp || !blockable) {
			return objp;
		} else {
			/* block and wait for an object to
			 * be returned to the pool
			 */
			atomic_inc(&pool->waiters);
			wait_event(pool->wait_queue,
				   (!atomic_read(&pool->waiters)));
		}
	}
	return objp;
}

EXPORT_SYMBOL(evms_cs_allocate_from_pool);

void
evms_cs_deallocate_to_pool(struct evms_pool_mgmt *pool, void *objp)
{
	kmem_cache_free(pool->cachep, objp);
	atomic_set(&pool->waiters, 0);
	if (waitqueue_active(&pool->wait_queue)) {
		wake_up(&pool->wait_queue);
	}
}

EXPORT_SYMBOL(evms_cs_deallocate_to_pool);

void
evms_cs_destroy_pool(struct evms_pool_mgmt *pool)
{
	kmem_cache_destroy(pool->cachep);
	kfree(pool);
}

EXPORT_SYMBOL(evms_cs_destroy_pool);

/*
 * function: evms_end_io
 *
 * This is a support function for
 * evms_cs_register_for_end_io_notification.
 * This function is called during I/O completion on any buffer
 * head that was registered by a plugin. Control is passed here
 * and this routine will, thru the use of the I/O notify entry
 * stored in the b_private field of the buffer head, restore
 * the b_rsector value the buffer head had at the time of
 * registration and pass control to the registered callback
 * address, with pointers to the buffer head and an optional
 * plugin private data. Upon completion of the callback,
 * control is returned back here. The io notify list entry
 * is deleted. This process repeats until this routine
 * detects that all registered plugins have been called back
 * and the buffer head's original end_io function has been
 * called. At this point the DONE flag is set, and we terminate
 * callback loop and exit.
 *
 * Plugins may desire to break or interrupt the callback
 * sequence or chain. This may be useful to redrive I/O or
 * to wait for other buffer heads to complete before
 * allowing the original buffer head callback to occur.
 * To interrupt the callback "chain", a registered
 * plugin's callback must return with the DONE flag set.
 *
 * NOTE: If a plugin set the DONE flag, and wishes to redrive
 * a buffer head, the plugin MUST reregister the buffer head
 * to receive another callback on this buffer head. Also, the
 * plugin MUST ensure that the original buffer head end_io
 * function get called at some point, either by reregistering
 * this buffer head and receiving another callback, or by
 * means of buffer head aggregation triggered by the callbacks
 * of other buffer heads.
 *
 */
static void
evms_end_io(struct buffer_head *bh, int uptodate)
{
	io_notify_t *entry;
	int done;

	done = FALSE;
	while (!done) {
		/* retrieve the io_notify_entry ptr from
		 * the b_private field in the buffer head.
		 */
		entry = (io_notify_t *) bh->b_private;

		/* restore the b_private value to
		 * the previous b_private value (which
		 * should be a previous io_notify_entry
		 * or the original b_private pointer).
		 */
		bh->b_private = entry->b_private;

		/* check for original callback for this bh */
		if (entry->flags & EVMS_ORIGINAL_CALLBACK_FLAG) {
			/* this is the original for bh */

			/* turn off flag marking this as the original */
			entry->flags &= ~EVMS_ORIGINAL_CALLBACK_FLAG;

			/* decrement volume's requests_in_progress var */
			atomic_dec(&evms_logical_volumes[MINOR(bh->b_rdev)].
				   requests_in_progress);

			/* restore b_end_io to original value */
			bh->b_end_io = (void *) entry->callback_function;
			if (bh->b_end_io) {
				/* invoke original callback function
				 * if it exists.
				 */
				bh->b_end_io(bh, uptodate);
			}
			done = TRUE;
		} else {
			/* this is a plugin callback */

			/* restore the rsector value to the
			 * value at the time of callback
			 * registration.
			 */
			bh->b_rsector = entry->rsector;
			bh->b_rdev = entry->rdev;
			/* invoke plugin callback function */
			entry->callback_function(entry->private, bh, uptodate,
						 &done);
		}
		/* free the io notify entry */
		evms_cs_deallocate_to_pool(evms_io_notify_pool, entry);
	}
}

/*
 * function: evms_cs_register_for_end_io_notification
 *
 * This function is an evms common service.
 * This routine allows a (plugin) function to register to
 * participate in the io completion notification process.
 * This is useful for plugins which alter data after it
 * has been read from the disk (i.e. encryption or
 * compression).
 *
 * This routine also records the rsector value at the time
 * of registration, so that it can be restored to that value
 * prior to the callback to a plugin, thus allowing that
 * plugin to work with the value it had seen during the
 * initiating I/O request.
 *
 * This routine also records a private data pointer at the
 * time of registration, and is returned to the plugin
 * at callback time. This private data pointer was designed
 * to contain context/callback/buffer_head specific data, and
 * frees the plugin from having to store and find associated
 * data at the time of the callback. This field is not used
 * by this function and is optional (NULL if unused). It is
 * recorded and returned as a convenience for the plugins.
 *
 * DANGER!!! - WILL ROBINSON - DANGER!!!
 * This routine uses the b_private field in the
 * buffer_head structure. If any lower level driver uses this
 * field and do NOT restore it, the I/O callback will fail!!
 *
 * Any plugins writers requiring a field for private storage
 * should instead use the private field parameter in this
 * function to store their private data.
 *
 */

int
evms_cs_register_for_end_io_notification(void *private,
					 struct buffer_head *bh,
					 void *callback_function)
{
	int rc = 0, done;
	io_notify_t *new_entry;

	done = FALSE;
	while (!done) {
		/* allocate a notify entry */
		new_entry =
		    evms_cs_allocate_from_pool(evms_io_notify_pool,
					       EVMS_BLOCKABLE);
		if (!new_entry) {
			schedule();
			continue;
		}

		/* initialize notify entry */
		new_entry->private = private;
		new_entry->bh = bh;
		new_entry->rsector = bh->b_rsector;
		new_entry->rdev = bh->b_rdev;
		new_entry->b_private = bh->b_private;
		new_entry->flags = 0;

		/* is this the first callback for this bh? */
		if (bh->b_end_io != evms_end_io) {
			/* yes, first callback */
			new_entry->flags |= EVMS_ORIGINAL_CALLBACK_FLAG;
			new_entry->callback_function = (void *) bh->b_end_io;

			/* increment volume's requests_in_progress var */
			atomic_inc(&evms_logical_volumes[MINOR(bh->b_rdev)].
				   requests_in_progress);

			/* set b_end_io so we get control */
			bh->b_end_io = evms_end_io;
		} else {
			/* no, not first callback */
			new_entry->callback_function = callback_function;
			done = TRUE;
		}
		/* set b_private to aid in quick lookup */
		bh->b_private = new_entry;
	}
	return rc;
}

EXPORT_SYMBOL(evms_cs_register_for_end_io_notification);

/* function description: evms_cs_lookup_item_in_list
 *  	
 * this function searches for the specified item in the
 * specified node list. it returns the address of the
 * evms_list_node containing the specified item.
 */
struct evms_list_node **
evms_cs_lookup_item_in_list(struct evms_list_node **node_list, void *item)
{
	struct evms_list_node **list_node;

	list_node = node_list;
	while (*list_node) {
		if ((*list_node)->item == item)
			break;
		list_node = &(*list_node)->next;
	}
	return list_node;
}

EXPORT_SYMBOL(evms_cs_lookup_item_in_list);

/* function description: evms_add_item_to_list
 *
 * this function adds an item to the list. the
 * node for the new item is added to the end
 * of the list. the list is traversed to find the end.
 * while the traversal occurs, the list is checked
 * for the presence of the specified item. if already
 * present in the list, and error code is returned.
 */
/* function description: evms_cs_add_item_to_list
 *
 * this function adds an item to an item list.
 *
 * RC == 0 is returned for:
 *	a successful add of a new item
 *
 * RC == 1 is returned when:
 *	the item is already on the list
 *
 * RC < 0 is returned for an error attempting to add the item.
 */
int
evms_cs_add_item_to_list(struct evms_list_node **list, void *item)
{
	int rc = 0;
	struct evms_list_node **list_node, *new_node;

	list_node = evms_cs_lookup_item_in_list(list, item);
	if (*list_node == NULL) {
		new_node = kmalloc(sizeof (struct evms_list_node), GFP_NOIO);
		if (new_node) {
			memset(new_node, 0, sizeof (struct evms_list_node));
			new_node->item = item;
			*list_node = new_node;
		} else {
			rc = -ENOMEM;
		}
	} else {
		rc = 1;
		LOG_DEBUG("warning: attempt to add duplicate item(%p) to "
			  "list(%p).\n", item, list);
	}
	return rc;
}

EXPORT_SYMBOL(evms_cs_add_item_to_list);

/* function description: evms_remove_item_from_list
 *
 * this function removes a specified item from the
 * specified list. if the specified item is not
 * found in the list, and error is returned.
 */
int
evms_cs_remove_item_from_list(struct evms_list_node **list, void *item)
{
	int rc = 0;
	struct evms_list_node **list_node;

	/* check to see if item is in the list */
	list_node = evms_cs_lookup_item_in_list(list, item);

	/* was the node found in the list? */
	if (*list_node) {
		/* yes, it was found */
		struct evms_list_node *tmp_node;

		/* save ptr to node being removed */
		tmp_node = *list_node;
		/* remove it from the global list */
		*list_node = tmp_node->next;
		/* delete removed node */
		kfree(tmp_node);
	} else {
		/* no, it was not found */
		rc = -1;
		LOG_ERROR("error(%d): attempt to remove nonexistant node(%p) "
			  "from list(%p).\n", rc, item, list);
	}
	return rc;
}

EXPORT_SYMBOL(evms_cs_remove_item_from_list);

/* function description: evms_cs_register_device
 *
 * this function adds a device to the EVMS global device list.
 *
 * RC == 0 is returned for:
 *	a successful add of a new device
 *
 * RC == 1 is returned when:
 *	the device is already on the list
 *
 * RC < 0 is returned for an error attempting to add the device.
 */
int
evms_cs_register_device(struct evms_logical_node *device)
{
	return evms_cs_add_item_to_list(&evms_global_device_list, device);
}

EXPORT_SYMBOL(evms_cs_register_device);

/* function description: evms_cs_unregister_device
 *
 * this function removes a device from the EVMS global device list.
 *
 * RC == 0 is returned for:
 *	a successful removal of the specified device
 *
 * RC < 0 is returned for an error attempting to add the device.
 * 	-ENODATA is returned if specified device is not found.
 */
int
evms_cs_unregister_device(struct evms_logical_node *device)
{
	return evms_cs_remove_item_from_list(&evms_global_device_list,
					      device);
}

EXPORT_SYMBOL(evms_cs_unregister_device);

static struct evms_list_node *find_first_next_list_node = NULL;
int
evms_cs_find_next_device(struct evms_logical_node *in_device,
			 struct evms_logical_node **out_device)
{
	int rc = 0;
	struct evms_list_node **list_node;

	if (in_device == NULL)
		find_first_next_list_node = evms_global_device_list;
	else {
		list_node =
		    evms_cs_lookup_item_in_list(&evms_global_device_list,
						in_device);
		find_first_next_list_node = *list_node;
		if (find_first_next_list_node == NULL)
			rc = -ENODATA;
		else
			find_first_next_list_node =
			    find_first_next_list_node->next;
	}

	if (find_first_next_list_node == NULL)
		*out_device = NULL;
	else
		*out_device = (struct evms_logical_node *)
		    find_first_next_list_node->item;

	return rc;
}

EXPORT_SYMBOL(evms_cs_find_next_device);

void
evms_cs_signal_event(int eventid)
{
	int rc;
	struct evms_list_node **list_node;

	/* signal PID(s) of specified event */
	list_node = &evms_global_notify_list;
	while (*list_node) {
		struct evms_event *event;

		event = (*list_node)->item;
		if (event->eventid == eventid) {
			struct task_struct *tsk;

			tsk = find_task_by_pid(event->pid);
			if (tsk) {
				struct siginfo siginfo;

				siginfo.si_signo = event->signo;
				siginfo.si_errno = 0;
				siginfo.si_code = 0;
				rc = send_sig_info(event->signo, &siginfo, tsk);
			} else {
				/* TODO:
				 * unregister this stale
				 * notification record
				 */
			}
		}
		list_node = &(*list_node)->next;
	}
}

EXPORT_SYMBOL(evms_cs_signal_event);

static inline void
evms_flush_signals(void)
{
	spin_lock(&current->sigmask_lock);
	flush_signals(current);
	spin_unlock(&current->sigmask_lock);
}

static inline void
evms_init_signals(void)
{
	current->exit_signal = SIGCHLD;
	siginitsetinv(&current->blocked, sigmask(SIGKILL));
}

static int
evms_thread(void *arg)
{
	struct evms_thread *thread = arg;
	lock_kernel();

	/*
	 * Detach thread
	 */

	daemonize();

	sprintf(current->comm, thread->name);
	evms_init_signals();
	evms_flush_signals();
	thread->tsk = current;

	current->policy = SCHED_OTHER;
#ifdef O1_SCHEDULER
	set_user_nice(current, -20);
#else
	current->nice = -20;
#endif
	unlock_kernel();

	complete(thread->event);
	while (thread->run) {
		void (*run) (void *data);
		DECLARE_WAITQUEUE(wait, current);

		add_wait_queue(&thread->wqueue, &wait);
#ifdef O1_SCHEDULER
		set_current_state(TASK_INTERRUPTIBLE);
#else
		set_task_state(current, TASK_INTERRUPTIBLE);
#endif
		if (!test_bit(EVMS_THREAD_WAKEUP, &thread->flags)) {
			schedule();
		}
#ifdef O1_SCHEDULER
		set_current_state(TASK_RUNNING);
#else
		current->state = TASK_RUNNING;
#endif
		remove_wait_queue(&thread->wqueue, &wait);
		clear_bit(EVMS_THREAD_WAKEUP, &thread->flags);

		run = thread->run;
		if (run) {
			run(thread->data);
			run_task_queue(&tq_disk);
		}
		if (signal_pending(current)) {
			evms_flush_signals();
		}
	}
	complete(thread->event);
	return 0;
}

struct evms_thread *
evms_cs_register_thread(void (*run) (void *), void *data, const u8 * name)
{
	struct evms_thread *thread;
	int ret;
	struct completion event;

	thread = kmalloc(sizeof (struct evms_thread), GFP_KERNEL);
	if (!thread) {
		return NULL;
	}
	memset(thread, 0, sizeof (struct evms_thread));
	init_waitqueue_head(&thread->wqueue);

	init_completion(&event);
	thread->event = &event;
	thread->run = run;
	thread->data = data;
	thread->name = name;
	ret = kernel_thread(evms_thread, thread, 0);
	if (ret < 0) {
		kfree(thread);
		return NULL;
	}
	wait_for_completion(&event);
	return thread;
}

EXPORT_SYMBOL(evms_cs_register_thread);

void
evms_cs_unregister_thread(struct evms_thread *thread)
{
	struct completion event;

	init_completion(&event);

	thread->event = &event;
	thread->run = NULL;
	thread->name = NULL;
	evms_cs_interrupt_thread(thread);
	wait_for_completion(&event);
	kfree(thread);
}

EXPORT_SYMBOL(evms_cs_unregister_thread);

void
evms_cs_wakeup_thread(struct evms_thread *thread)
{
	set_bit(EVMS_THREAD_WAKEUP, &thread->flags);
	wake_up(&thread->wqueue);
}

EXPORT_SYMBOL(evms_cs_wakeup_thread);

void
evms_cs_interrupt_thread(struct evms_thread *thread)
{
	if (!thread->tsk) {
		LOG_ERROR("error: attempted to interrupt an invalid thread!\n");
		return;
	}
	send_sig(SIGKILL, thread->tsk, 1);
}

EXPORT_SYMBOL(evms_cs_interrupt_thread);

struct proc_dir_entry *
evms_cs_get_evms_proc_dir(void)
{
#ifdef CONFIG_PROC_FS
	if (!evms_proc_dir) {
		evms_proc_dir = create_proc_entry("evms", S_IFDIR, &proc_root);
	}
#endif
	return evms_proc_dir;
}

EXPORT_SYMBOL(evms_cs_get_evms_proc_dir);

int
evms_cs_volume_request_in_progress(kdev_t dev,
				   int operation, int *current_count)
{
	int rc = 0;
	struct evms_logical_volume *volume;

	volume = &evms_logical_volumes[MINOR(dev)];
	if (volume->node) {
		if (operation > 0) {
			atomic_inc(&volume->requests_in_progress);
		} else if (operation < 0) {
			atomic_dec(&volume->requests_in_progress);
		}
		if (current_count) {
			*current_count =
			    atomic_read(&volume->requests_in_progress);
		}
	} else {
		rc = -ENODEV;
	}
	return rc;
}

EXPORT_SYMBOL(evms_cs_volume_request_in_progress);

void
evms_cs_invalidate_volume(struct evms_logical_node *node)
{
	int i;
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		if (evms_logical_volumes[i].node && node->name) {
			if (!
			    (strcmp
			     (evms_logical_volumes[i].node->name,
			      node->name))) {
				LOG_DETAILS("Invalidating EVMS device %s minor "
					    "%d\n", node->name, i);
				invalidate_device(MKDEV(EVMS_MAJOR, i), 0);
				break;
			}
		}
	}
}

EXPORT_SYMBOL(evms_cs_invalidate_volume);

static int
is_open(int minor)
{
	return atomic_read(&evms_logical_volumes[minor].opens);
}

/**********************************************************/
/* END -- exported functions/Common Services              */
/**********************************************************/

/**********************************************************/
/* START -- Proc FS Support functions                     */
/**********************************************************/

#ifdef CONFIG_PROC_FS
static int
evms_info_read_proc(char *page, char **start, off_t off,
		    int count, int *eof, void *data)
{
	int sz = 0;
	char *info_level_text = NULL;

	PROCPRINT("Enterprise Volume Management System: Info\n");
	switch (evms_info_level) {
	case EVMS_INFO_CRITICAL:
		info_level_text = "critical";
		break;
	case EVMS_INFO_SERIOUS:
		info_level_text = "serious";
		break;
	case EVMS_INFO_ERROR:
		info_level_text = "error";
		break;
	case EVMS_INFO_WARNING:
		info_level_text = "warning";
		break;
	case EVMS_INFO_DEFAULT:
		info_level_text = "default";
		break;
	case EVMS_INFO_DETAILS:
		info_level_text = "details";
		break;
	case EVMS_INFO_DEBUG:
		info_level_text = "debug";
		break;
	case EVMS_INFO_EXTRA:
		info_level_text = "extra";
		break;
	case EVMS_INFO_ENTRY_EXIT:
		info_level_text = "entry exit";
		break;
	case EVMS_INFO_EVERYTHING:
		info_level_text = "everything";
		break;
	default:
		info_level_text = "unknown";
		break;
	}
	PROCPRINT("EVMS info level: %d (%s).\n",
		  evms_info_level, info_level_text);

	PROCPRINT("EVMS kernel version: %d.%d.%d\n",
		  EVMS_MAJOR_VERSION,
		  EVMS_MINOR_VERSION, EVMS_PATCHLEVEL_VERSION);

	PROCPRINT("EVMS IOCTL interface version: %d.%d.%d\n",
		  EVMS_IOCTL_INTERFACE_MAJOR,
		  EVMS_IOCTL_INTERFACE_MINOR, EVMS_IOCTL_INTERFACE_PATCHLEVEL);

	PROCPRINT("EVMS Common Services version: %d.%d.%d\n",
		  EVMS_COMMON_SERVICES_MAJOR,
		  EVMS_COMMON_SERVICES_MINOR, EVMS_COMMON_SERVICES_PATCHLEVEL);

	*eof = 1;

out:
	*start = page + off;
	sz -= off;
	if (sz < 0)
		sz = 0;
	return sz > count ? count : sz;
}

static int
evms_plugins_read_proc(char *page, char **start, off_t off,
		       int count, int *eof, void *data)
{
	int sz = 0;
	struct evms_registered_plugin *rp = NULL;

	PROCPRINT("Enterprise Volume Management System: Plugins\n");
	/*             0    1    1    2    2    3    3    4    4    5    5    6    6    7 */
	/*         1   5    0    5    0    5    0    5    0    5    0    5    0    5    0 */
	PROCPRINT(" ---------Plugin----------      required services\n");
	PROCPRINT(" ----id----        version      version\n\n");
	for (rp = registered_plugin_head; rp; rp = rp->next) {
		PROCPRINT(" %x.%x.%x\t   %d.%d.%d\t%d.%d.%d\n",
			  GetPluginOEM(rp->plugin->id),
			  GetPluginType(rp->plugin->id),
			  GetPluginID(rp->plugin->id),
			  rp->plugin->version.major,
			  rp->plugin->version.minor,
			  rp->plugin->version.patchlevel,
			  rp->plugin->required_services_version.major,
			  rp->plugin->required_services_version.minor,
			  rp->plugin->required_services_version.patchlevel);
	}

out:
	*start = page + off;
	sz -= off;
	if (sz < 0)
		sz = 0;
	return sz > count ? count : sz;
}

static int
evms_volumes_read_proc(char *page,
		       char **start, off_t off, int count, int *eof, void *data)
{
	int sz = 0, j;

	PROCPRINT("Enterprise Volume Management System: Volumes\n");
	PROCPRINT("major   minor          #blocks type   flags name\n\n");
	for (j = 1; j < MAX_EVMS_VOLUMES; j++) {
		struct evms_logical_volume *volume;

		volume = &evms_logical_volumes[j];
		if (volume->node) {
			PROCPRINT("%5d %7d %16Ld %s %s %s %s%s\n",
				  EVMS_MAJOR, j,
				  (long long)volume->node->total_vsectors >> 1,
				  (volume->flags & EVMS_VOLUME_FLAG) ? "evms  " : "compat",
				  (volume->flags & EVMS_VOLUME_READ_ONLY) ? "ro" : "rw",
				  (volume->flags & EVMS_VOLUME_PARTIAL) ? "p " : "  ",
				  EVMS_DEV_NODE_PATH, volume->name);
		}
	}
out:
	*start = page + off;
	sz -= off;
	if (sz < 0)
		sz = 0;
	return sz > count ? count : sz;

}
#endif

/**********************************************************/
/* END -- Proc FS Support functions                       */
/**********************************************************/

/**********************************************************/
/* START -- FOPS functions definitions                    */
/**********************************************************/

/************************************************/
/* START -- IOCTL commands -- EVMS specific     */
/************************************************/

static int
evms_ioctl_cmd_get_ioctl_version(void *arg)
{
	int rc = 0;
	struct evms_version ver;

	ver.major = EVMS_IOCTL_INTERFACE_MAJOR;
	ver.minor = EVMS_IOCTL_INTERFACE_MINOR;
	ver.patchlevel = EVMS_IOCTL_INTERFACE_PATCHLEVEL;

	/* copy info to userspace */
	if (copy_to_user(arg, &ver, sizeof (ver)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_get_version(void *arg)
{
	int rc = 0;
	struct evms_version ver;

	ver.major = EVMS_MAJOR_VERSION;
	ver.minor = EVMS_MINOR_VERSION;
	ver.patchlevel = EVMS_PATCHLEVEL_VERSION;

	/* copy info to userspace */
	if (copy_to_user(arg, &ver, sizeof (ver)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_get_info_level(void *arg)
{
	int rc = 0;

	/* copy info to userspace */
	if (copy_to_user(arg, &evms_info_level, sizeof (evms_info_level)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_set_info_level(void *arg)
{
	int temp, rc = 0;

	/* copy info from userspace */
	if (copy_from_user(&temp, arg, sizeof (temp)))
		rc = -EFAULT;
	else
		evms_info_level = temp;

	return rc;
}

/* function: evms_quiesce_volume
 *
 * this function performs the actual quiesce operation on
 * a volume in kernel memory.
 *
 * when quiescing, all new I/Os to a volume are stopped,
 * causing the calling thread to block. this thread then
 * waits until all I/Os in progress are completed, before
 * return control to the caller.
 *
 * when unquiescing, all new I/Os are allowed to proceed
 * unencumbered, and all threads waiting (blocked) on this
 * volume, are woken up and allowed to proceed.
 *
 */
static int
evms_quiesce_volume(struct evms_logical_volume *volume,
		    struct inode *inode,
		    struct file *file, struct evms_quiesce_vol_pkt *qv)
{
	int rc;

	LOG_DEBUG("%squiescing %s.\n",
		  ((qv->command) ? "" : "un"), volume->name);

#ifdef VFS_PATCH_PRESENT
	if (qv->do_vfs) {
		/* VFS function call to sync and lock the filesystem */
		fsync_dev_lockfs(MKDEV(EVMS_MAJOR, qv->minor));
		volume->vfs_quiesced = TRUE;
	}
#endif
	volume->quiesced = qv->command;

	/* Command specified was "quiesce". */
	if (qv->command) {
		/* After setting the volume to
		 * a quiesced state, there could
		 * be threads (on SMP systems)
		 * that are executing in the
		 * function, evms_handle_request,
		 * between the "wait_event" and the
		 * "atomic_inc" lines. We need to
		 * provide a "delay" sufficient
		 * to allow those threads to
		 * to reach the atomic_inc's
		 * before executing the while loop
		 * below. The "schedule" call should
		 * provide this.
		 */
		schedule();
		/* wait for outstanding requests
		 * to complete
		 */
		while (atomic_read(&volume->requests_in_progress) > 0)
			schedule();
	}
	/* send this command down the stack so lower */
	/* layers can know about this                */
	rc = IOCTL(volume->node, inode, file,
		   EVMS_QUIESCE_VOLUME, (unsigned long) qv);
	if (!rc) {
		/* Command specified was "unquiesce". */
		if (!qv->command) {
			/* "wakeup" any I/O requests waiting on
			 * this volume.
			 */
			if (waitqueue_active(&volume->wait_queue))
				wake_up(&volume->wait_queue);
#ifdef VFS_PATCH_PRESENT
			if (volume->vfs_quiesced) {
				/* VFS function call to unlock the filesystem */
				unlockfs(MKDEV(EVMS_MAJOR, qv->minor));
				volume->vfs_quiesced = FALSE;
			}
#endif
		}
	} else {
		LOG_ERROR("error(%d) %squiescing %s.\n",
			  rc, ((qv->command) ? "" : "un"), volume->name);
	}
	return rc;
}

/* function: evms_delete_volume
 *
 * this function performs the actual delete operation on
 * a volume to purge it from kernel memory. all structures
 * and memory consumed by this volume will be free as well
 * as clearing or unregistering any system services or
 * global data arrays.
 *
 * NOTE: this function will return -EBUSY on attempts to
 * delete mounted volumes.
 *
 */
static int
evms_delete_volume(struct evms_logical_volume *volume,
		   struct evms_delete_vol_pkt *dv)
{
	int rc = 0;

	/* if this is a "permament" delete */
	/* check to make sure volume is not mounted */
	if (dv->command) {
		if (is_open(dv->minor)) {
			rc = -EBUSY;
		} else {
			// invalidate the device since it is not coming back
			// this is required incase we are re-using the minor number
			invalidate_device(MKDEV(EVMS_MAJOR, dv->minor), 1);
		}
	}

	/* invoke the delete ioctl at the top of the feature stack */
	if (!rc) {
		LOG_DETAILS("deleting '%s'.\n", volume->name);
		rc = DELETE(volume->node);
	}

	/* the volume has been deleted, do any clean up work
	 * required.
	 */
	if (!rc) {
		devfs_unregister(volume->devfs_handle);
		if (dv->command) {
			/* if "permanent" delete, free the name
			 * and NULL the name field.
			 */
			kfree(volume->name);
			volume->name = NULL;
			volume->flags = 0;
		} else {
			/* if "soft" delete, leave the name so
			 * we can use it to reassign the same
			 * minor to this volume after a
			 * rediscovery.
			 */
			volume->flags = EVMS_VOLUME_SOFT_DELETED;
		}
		volume->node = NULL;
		set_device_ro(MKDEV(EVMS_MAJOR, dv->minor), 0);
		blk_size[EVMS_MAJOR][dv->minor] = 0;
		blksize_size[EVMS_MAJOR][dv->minor] = 0;
		hardsect_size[EVMS_MAJOR][dv->minor] = 0;
		evms_volumes--;
	} else {
		LOG_ERROR("error(%d) %s deleting %s.\n",
			  rc, ((dv->command) ? "hard" : "soft"), volume->name);
	}
	return rc;
}

/* function: evms_user_delete_volume
 *
 * this function, depending on the parameters, performs
 * a "soft" or a "hard" delete. for a "soft" delete, a
 * quiesce & delete request is queued up, to be executed
 * at the beginning of the next rediscovery. for a
 * "hard" delete, the target volume is quiesced and then
 * deleted. if there is any errors attempting to delete
 * the target, then the target is unquiesced. if an
 * associative volume is specified it is quiesced before
 * the target volume is quiesced, and is unquiesced
 * after the attempt to delete the target volume.
 *
 */
static int
evms_user_delete_volume(struct evms_logical_volume *lvt,
			struct inode *inode,
			struct file *file, struct evms_delete_vol_pkt *dv)
{
	int rc = 0;

	if (!dv->command) {
		/* "soft delete" requested */
		lvt->flags |= (EVMS_REQUESTED_QUIESCE | EVMS_REQUESTED_DELETE);
		if (dv->do_vfs) {
			lvt->flags |= EVMS_REQUESTED_VFS_QUIESCE;
		}
	} else {
		/* "hard delete" requested */
		int qa = FALSE;
		struct evms_quiesce_vol_pkt qv;
		struct evms_logical_volume *lva = NULL;

		if (dv->associative_minor) {
			/* associative volume specified
			 *
			 * quiesce it
			 */
			lva = &evms_logical_volumes[dv->associative_minor];
			/* quiesce associative volume */
			qv.command = EVMS_QUIESCE;
			qv.do_vfs = EVMS_VFS_DO_NOTHING;
			qv.minor = dv->associative_minor;
			rc = evms_quiesce_volume(lva, inode, file, &qv);
			qa = (rc) ? FALSE : TRUE;
		}
		if (!rc) {
			/* quiesce target volume */
			qv.command = EVMS_QUIESCE;
			qv.do_vfs = EVMS_VFS_DO_NOTHING;
			qv.minor = dv->minor;
			rc = evms_quiesce_volume(lvt, inode, file, &qv);
		}
		if (!rc) {
			/* delete the target volume */
			rc = evms_delete_volume(lvt, dv);
			if (rc) {
				/* got an error undeleting...
				 *
				 * unquiesce the target
				 */
				qv.command = EVMS_UNQUIESCE;
				qv.do_vfs = EVMS_VFS_DO_NOTHING;
				qv.minor = dv->minor;
				evms_quiesce_volume(lvt, inode, file, &qv);
			}
		}
		if (dv->associative_minor) {
			/* associative volume specified
			 *
			 * unquiesce it
			 */
			if (qa) {
				/* only unquiesce associative
				 * if we successfully quiesced
				 * it previously.
				 */
				qv.command = EVMS_UNQUIESCE;
				qv.do_vfs = EVMS_VFS_DO_NOTHING;
				qv.minor = dv->associative_minor;
				evms_quiesce_volume(lva, inode, file, &qv);
			}
		}
	}
	return rc;
}

/* function: evms_ioctl_cmd_delete_volume
 *
 * this function copy user data to/from the kernel, and
 * validates user parameters. after validation, control
 * is passed to worker routine evms_user_delete_volume.
 *
 */
static int
evms_ioctl_cmd_delete_volume(struct inode *inode,
			     struct file *file, unsigned long arg)
{
	int rc = 0;
	struct evms_delete_vol_pkt tmp, *user_parms;
	struct evms_logical_volume *volume = NULL;

	user_parms = (struct evms_delete_vol_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	/* check to make sure associative minor is in use */
	if (!rc) {
		if (tmp.associative_minor) {
			volume = &evms_logical_volumes[tmp.associative_minor];
			if (volume->node == NULL)
				rc = -ENXIO;
		}
	}
	/* check to make sure target minor is in use */
	if (!rc) {
		volume = &evms_logical_volumes[tmp.minor];
		if (volume->node == NULL)
			rc = -ENXIO;
		else
			rc = evms_user_delete_volume(volume, inode, file, &tmp);
	}
	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

/* function: evms_full_rediscover_prep
 *
 * this function helps to prevent problems when evms is
 * configured with the base built in statically and some
 * plugins built as modules.
 *
 * in these cases, when the initial discovery is done,
 * only the statically built modules are available for
 * volume construction. as a result, some volumes that
 * require the plugins built as modules (which haven't
 * been loaded), to be fully reconstructed, may come up
 * as compatibility volumes or partial volumes.
 *
 * when parts of evms are built as modules, the
 * evms_rediscover_pkty utility is used, to perform a secondary
 * rediscover, after all the plugins built as modules
 * have been loaded, to construct all the volumes
 * requiring these plugins.
 *
 * however since some of the volumes, requiring the plugins
 * built as modules, may have been already exported as
 * compatibility or partial volumes, we need to purge these
 * volumes from kernel's memory, so that can be rediscovered
 * and claimed by the appropriate plugins, and reconstructed
 * into the correct volumes.
 *
 * this function purges all compatibility volumes that are
 * not in use(mounted) and all partial volumes, prior to
 * doing the secondary rediscover, thus allowing volumes to
 * rediscovered correctly.
 *
 * NOTE: again, this is only required in cases when a
 * combination of plugins are built statically and as
 * modules.
 *
 */
static void
evms_full_rediscover_prep(struct inode *inode, struct file *file)
{
	int rc = 0, i;

	LOG_DETAILS("%s: started.\n", __FUNCTION__);
	/* check for acceptable volumes to be deleted */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		struct evms_logical_volume *volume = NULL;
		struct evms_delete_vol_pkt dv;
		int volume_open, doit;

		volume = &evms_logical_volumes[i];
		if (!volume->node)
			continue;
		volume_open = is_open(i);
		/* only proceed on volumes that are:
		 *   partial volumes
		 *      OR
		 *   unopened compatibility volumes
		 */
		doit = FALSE;
		if (volume->flags & EVMS_VOLUME_PARTIAL) {
			/* do all partial volumes
			 */
			doit = TRUE;
		} else if (!(volume->flags & EVMS_VOLUME_FLAG)) {
			/* check all compatibility volumes
			 */
			if (!volume_open && !is_swap_partition(MKDEV(EVMS_MAJOR, i))) {
				/* only do unopened volumes
				 */
				doit = TRUE;
			}
		}
		if (doit == FALSE) {
			continue;
		}
		/* delete the volume from memory.
		 * do a 'soft' delete if volume
		 * is mounted, and 'hard' delete
		 * if it is not.
		 *
		 * NOTE: the delete operation will
		 * clear the bits in the flags field.
		 */
		dv.command = volume_open ? EVMS_SOFT_DELETE : EVMS_HARD_DELETE;
		dv.minor = i;
		dv.associative_minor = 0;
		dv.status = 0;
		rc = evms_user_delete_volume(volume, inode, file, &dv);
	}
	LOG_DETAILS("%s: completed.\n", __FUNCTION__);
}

static int
evms_ioctl_cmd_rediscover_volumes(struct inode *inode,
				  struct file *file,
				  unsigned int cmd, unsigned long arg)
{
	int rc, i;
	struct evms_rediscover_pkt tmp, *user_parms;
	u64 *array_ptr = NULL;
	ulong array_size = 0;
	struct evms_logical_volume *volume = NULL;

	LOG_DEBUG("Rediscovering volumes...\n");

	rc = tmp.drive_count = 0;
	user_parms = (struct evms_rediscover_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (tmp.drive_count == REDISCOVER_ALL_DEVICES) {
		evms_full_rediscover_prep(inode, file);
	}
	/* quiesce all queued volumes */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		struct evms_quiesce_vol_pkt qv;

		volume = &evms_logical_volumes[i];
		if (!volume->node) {
			continue;
		}
		if (!(volume->flags & EVMS_REQUESTED_QUIESCE)) {
			continue;
		}
		qv.command = EVMS_QUIESCE;
		qv.minor = i;
		qv.do_vfs = (volume->flags & EVMS_REQUESTED_VFS_QUIESCE) ?
		    EVMS_VFS_DO : EVMS_VFS_DO_NOTHING, qv.status = 0;
		rc = evms_quiesce_volume(volume, inode, file, &qv);
	}
	/* "soft" delete all queued volumes */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		struct evms_delete_vol_pkt dv;

		volume = &evms_logical_volumes[i];
		if (!volume->node) {
			continue;
		}
		if (!(volume->flags & EVMS_REQUESTED_DELETE)) {
			continue;
		}
		dv.command = EVMS_SOFT_DELETE;
		dv.minor = i;
		dv.associative_minor = 0;
		dv.status = 0;
		rc = evms_delete_volume(volume, &dv);
	}

	if (tmp.drive_count && (tmp.drive_count != REDISCOVER_ALL_DEVICES)) {
		if (!rc) {
			/* create space for userspace drive array */
			array_size =
			    sizeof (*tmp.drive_array) * tmp.drive_count;
			array_ptr = tmp.drive_array;
			tmp.drive_array = kmalloc(array_size, GFP_KERNEL);
			if (!tmp.drive_array) {
				rc = -ENOMEM;
			}
		}
		if (!rc)
			/* copy rediscover drive array to kernel space */
			if (copy_from_user
			    (tmp.drive_array, array_ptr, array_size))
				rc = -EFAULT;
	}

	if (!rc) {
		static int evms_discover_volumes(struct evms_rediscover_pkt *);
		/* perform the rediscovery operation */
		rc = evms_discover_volumes(&tmp);
	}

	/* clean up after operation */
	if (tmp.drive_count && (tmp.drive_count != REDISCOVER_ALL_DEVICES))
		kfree(tmp.drive_array);

	/* set return code and copy info to userspace */
	tmp.status = rc;
	if (copy_to_user(&user_parms->status, &tmp.status, sizeof (tmp.status)))
		rc = -EFAULT;

	return rc;
}

static struct evms_list_node *user_disk_ptr;
static int
evms_ioctl_cmd_get_logical_disk(void *arg)
{
	int rc = 0;
	struct evms_user_disk_pkt tmp, *user_parms;

	user_parms = (struct evms_user_disk_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user
	    (&tmp.command, &user_parms->command, sizeof (tmp.command)))
		rc = -EFAULT;

	if (!rc) {
		if (tmp.command == EVMS_FIRST_DISK)
			user_disk_ptr = evms_global_device_list;
		else		/* tmp.command == EVMS_NEXT_DISK */
			user_disk_ptr = user_disk_ptr->next;

		if (user_disk_ptr == NULL)
			tmp.status = EVMS_DISK_INVALID;
		else {
			tmp.status = EVMS_DISK_VALID;
			tmp.disk_handle =
			    NODE_TO_DEV_HANDLE(user_disk_ptr->item);
		}
		/* copy info to userspace */
		if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
			rc = -EFAULT;
	}
	return rc;
}

static int
evms_ioctl_cmd_get_logical_disk_info(void *arg)
{
	int rc = 0;
	struct evms_user_disk_info_pkt tmp, *user_parms;
	struct evms_list_node *p;
	struct evms_logical_node *disk_node = NULL;

	user_parms = (struct evms_user_disk_info_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user
	    (&tmp.disk_handle, &user_parms->disk_handle,
	     sizeof (tmp.disk_handle)))
		rc = -EFAULT;

	/* check handle for validity */
	if (!rc) {
		rc = -EINVAL;
		disk_node = DEV_HANDLE_TO_NODE(tmp.disk_handle);
		for (p = evms_global_device_list; p; p = p->next)
			if (p->item == disk_node) {
				rc = 0;
				user_disk_ptr = p;
				break;
			}
	}

	/* populate kernel copy of user's structure with appropriate info */
	if (!rc) {
		struct hd_geometry geo;
		struct evms_logical_node *node =
		    (struct evms_logical_node *) user_disk_ptr->item;
		tmp.flags = node->flags;
		strcpy(tmp.disk_name, EVMS_DEV_NODE_PATH);
		strcat(tmp.disk_name, node->name);
		rc = evms_cs_kernel_ioctl(node, EVMS_UPDATE_DEVICE_INFO,
					  (ulong) NULL);
		if (!rc) {
			tmp.total_sectors = node->total_vsectors;
			tmp.hardsect_size = node->hardsector_size;
			tmp.block_size = node->block_size;
			rc = evms_cs_kernel_ioctl(node, HDIO_GETGEO,
						  (unsigned long) &geo);
		}
		if (!rc) {
			tmp.geo_sectors = geo.sectors;
			tmp.geo_heads = geo.heads;
			tmp.geo_cylinders = geo.cylinders;
		}
	}

	/* set return code and copy info to userspace */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_sector_io(void *arg)
{
	int rc;
#define MAX_IO_SIZE 128
	u64 io_size, max_io_size = MAX_IO_SIZE;
#undef MAX_IO_SIZE
	struct evms_sector_io_pkt tmp, *user_parms;
	struct evms_logical_node *disk_node = NULL;
	struct evms_list_node *list_node;
	unsigned char *io_buffer;

	rc = 0;
	list_node = NULL;
	io_buffer = NULL;

	user_parms = (struct evms_sector_io_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	/* check handle for validity */
	if (!rc) {
		rc = -EINVAL;
		disk_node = DEV_HANDLE_TO_NODE(tmp.disk_handle);
		for (list_node = evms_global_device_list; list_node;
		     list_node = list_node->next)
			if (list_node->item == disk_node) {
				rc = 0;
				break;
			}
	}
	if (!rc) {
		int done;
		/* allocate a io buffer upto 64Kbytes in size */
		if (tmp.sector_count < max_io_size)
			max_io_size = tmp.sector_count;
		do {
			done = TRUE;
			/* allocate buffer large enough to max_io_size sectors */
			io_buffer =
			    kmalloc(max_io_size << EVMS_VSECTOR_SIZE_SHIFT,
				    GFP_KERNEL);
			if (!io_buffer) {
				max_io_size >>= 1;
				if (!max_io_size) {
					rc = -ENOMEM;
				} else {
					done = FALSE;
				}
			}
		} while (!done);
	}
	/* perform io with specified disk */
	if (!rc) {
		u64 io_sector_offset, io_remaining;
		u64 io_bytes;
		u_char *user_buffer_ptr;

		io_remaining = tmp.sector_count;
		io_sector_offset = 0;
		user_buffer_ptr = tmp.buffer_address;
		while (io_remaining) {
			/* compute the io_size for this pass */
			io_size = (io_remaining >= max_io_size) ?
			    max_io_size : io_remaining;

			io_bytes = io_size << EVMS_VSECTOR_SIZE_SHIFT;
			/* for writes, copy a sector from user to kernel */
			if (tmp.io_flag == EVMS_SECTOR_IO_WRITE) {
				/* copy sector from user data buffer */
				if (copy_from_user(io_buffer,
						   user_buffer_ptr, io_bytes))
					rc = -EFAULT;
			}
			if (rc)
				break;

			/* perform IO one sector at a time */
			rc = INIT_IO(disk_node,
				     tmp.io_flag,
				     io_sector_offset + tmp.starting_sector,
				     io_size, io_buffer);

			if (rc)
				break;

			if (tmp.io_flag != EVMS_SECTOR_IO_WRITE) {
				/* copy sector to user data buffer */
				if (copy_to_user(user_buffer_ptr,
						 io_buffer, io_bytes))
					rc = -EFAULT;
			}
			if (rc)
				break;

			user_buffer_ptr += io_bytes;
			tmp.buffer_address += io_bytes;
			io_sector_offset += io_size;
			io_remaining -= io_size;
		}
	}

	/* if the sector_buffer was allocated, free it */
	if (io_buffer)
		kfree(io_buffer);

	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

static int user_minor;
static int
evms_ioctl_cmd_get_minor(void *arg)
{
	int rc = 0;
	struct evms_user_minor_pkt tmp, *user_parms;

	user_parms = (struct evms_user_minor_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user
	    (&tmp.command, &user_parms->command, sizeof (tmp.command)))
		rc = -EFAULT;

	if (!rc) {
		if (tmp.command == EVMS_FIRST_VOLUME)
			user_minor = 1;
		else		/* tmp.command == EVMS_NEXT_VOLUME */
			user_minor++;

		tmp.status = EVMS_VOLUME_INVALID;
		for (; user_minor < MAX_EVMS_VOLUMES; user_minor++) {
			struct evms_logical_volume *lv;

			lv = &evms_logical_volumes[user_minor];
			/* see if any corrupt volumes have been
			 * unmounted. If so, clean up the
			 * evms_logical_volumes array entry, and
			 * don't report the volume to the user.
			 */
			if (lv->flags & EVMS_VOLUME_CORRUPT) {
				if (!is_open(user_minor)) {
					/* clear logical volume structure
					   * for this volume so it may be
					   * reused.
					 */
					LOG_WARNING("ioctl_get_minor: found "
						    "unmounted %s volume"
						    "(%u,%u,%s).\n",
						    (lv->flags & EVMS_VOLUME_SOFT_DELETED) ?
						     "'soft deleted'" : "",
						    EVMS_MAJOR, user_minor,
						    lv->name);
					LOG_WARNING("    releasing minor(%d) "
						    "used by volume(%s)!\n",
						    user_minor, lv->name);
					kfree(lv->name);
					lv->name = NULL;
					lv->flags = 0;
				}
			}
			if (lv->node || (lv->flags & EVMS_VOLUME_CORRUPT)) {
				tmp.status = EVMS_VOLUME_VALID;
				tmp.minor = user_minor;
				break;
			}
		}

		/* copy info to userspace */
		if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
			rc = -EFAULT;
	}
	return rc;
}

static int
evms_ioctl_cmd_get_volume_data(void *arg)
{
	int rc = 0;
	struct evms_volume_data_pkt tmp, *user_parms;
	struct evms_logical_volume *volume = NULL;
	struct evms_logical_node *node = NULL;

	user_parms = (struct evms_volume_data_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (!rc) {
		volume = &evms_logical_volumes[tmp.minor];
		node = volume->node;
		if (node == NULL)
			rc = -ENODEV;
	}
	if (!rc) {
		tmp.flags = volume->flags;
		strcpy(tmp.volume_name, EVMS_DEV_NODE_PATH);
		strcat(tmp.volume_name, volume->name);
	}

	/* copy return code and info to userspace */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;
	return rc;
}

static struct evms_registered_plugin *ioctl_reg_record;
static int
evms_ioctl_cmd_get_plugin(void *arg)
{
	int rc = 0;
	struct evms_kernel_plugin_pkt tmp, *user_parms;

	user_parms = (struct evms_kernel_plugin_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user
	    (&tmp.command, &user_parms->command, sizeof (tmp.command)))
		rc = -EFAULT;

	if (!rc) {
		/* if the command is not 0, then verify
		 * that ioctl_reg_record is pointing to
		 * current and valid plugin header.
		 */
		if (tmp.command) {	/* tmp.command == EVMS_NEXT_PLUGIN */
			struct evms_registered_plugin *tmp_reg_record;
			tmp_reg_record = registered_plugin_head;
			/* search the current plugin list */
			while (tmp_reg_record) {
				if (tmp_reg_record == ioctl_reg_record)
					break;
				tmp_reg_record = tmp_reg_record->next;
			}
			/* if the ioctl_reg_record is not in the
			 * current list, then start at the beginning.
			 */
			if (!tmp_reg_record)
				tmp.command = EVMS_FIRST_PLUGIN;
		}

		if (tmp.command == EVMS_FIRST_PLUGIN)
			/* start at beginning of plugin list */
			ioctl_reg_record = registered_plugin_head;
		else		/* tmp.command == EVMS_NEXT_PLUGIN */
			/* continue from current position in list */
			ioctl_reg_record = ioctl_reg_record->next;

		tmp.status = EVMS_PLUGIN_INVALID;
		tmp.id = 0;
		if (ioctl_reg_record) {
			tmp.id = ioctl_reg_record->plugin->id;
			tmp.version = ioctl_reg_record->plugin->version;
			tmp.status = EVMS_PLUGIN_VALID;
		}

		/* copy info to userspace */
		if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
			rc = -EFAULT;
	}
	return rc;
}

static int
evms_ioctl_cmd_plugin_ioctl(struct inode *inode,
			    struct file *file,
			    unsigned int cmd, unsigned long arg)
{
	int rc = 0, found = FALSE;
	struct evms_plugin_ioctl_pkt tmp, *user_parms;
	struct evms_registered_plugin *p;

	user_parms = (struct evms_plugin_ioctl_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (!rc) {
		/* search for the specified plugin */
		for (p = registered_plugin_head; p; p = p->next)
			/* check for the specified feature id */
			if (p->plugin->id == tmp.feature_id) {
				found = TRUE;
				/* check that entry point is used */
				if (p->plugin->fops->direct_ioctl)
					rc = DIRECT_IOCTL(p, inode, file, cmd,
							  arg);
				else
					rc = -ENOSYS;
				break;
			}
		/* was the specified plugin found? */
		if (found == FALSE)
			rc = -ENOPKG;

		/* copy the status value back to the user */
		tmp.status = rc;
		if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
			rc = -EFAULT;
	}
	return rc;
}

#define MAX_BUFFER_SIZE 65536
static int
evms_ioctl_cmd_kernel_partial_csum(void *arg)
{
	int rc = 0;
	u64 compute_size = MAX_BUFFER_SIZE;
	struct evms_compute_csum_pkt tmp, *user_parms;
	unsigned char *buffer = NULL;

	user_parms = (struct evms_compute_csum_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (!rc) {
		/* allocate a io buffer upto 64Kbytes in size */
		if (tmp.buffer_size < MAX_BUFFER_SIZE)
			compute_size = tmp.buffer_size;

		/* allocate buffer large enough to hold a single sector */
		buffer = kmalloc(compute_size, GFP_KERNEL);
		if (!buffer) {
			rc = -ENOMEM;
		}
	}
	/* perform io with specified disk */
	if (!rc) {
		u64 remaining_bytes;
		u_char *user_buffer_ptr;
		unsigned int insum = tmp.insum;

		remaining_bytes = tmp.buffer_size;
		user_buffer_ptr = tmp.buffer_address;
		while (remaining_bytes) {
			/* compute the compute_size for this pass */
			compute_size = (remaining_bytes >= MAX_BUFFER_SIZE) ?
			    MAX_BUFFER_SIZE : remaining_bytes;

			/* copy into kernel from user data buffer */
			if (copy_from_user(buffer, user_buffer_ptr,
					   compute_size))
				rc = -EFAULT;
			if (rc)
				break;
			/* compute the checksum for this pass */
			tmp.outsum = csum_partial(buffer, tmp.buffer_size,
						  insum);
			/* set up for another possible pass */
			insum = tmp.outsum;
			/* update loop progress variables */
			user_buffer_ptr += compute_size;
			tmp.buffer_address += compute_size;
			remaining_bytes -= compute_size;
		}
	}

	/* if the sector_buffer was allocated, free it */
	if (buffer)
		kfree(buffer);

	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

#undef MAX_BUFFER_SIZE

static int
evms_ioctl_cmd_get_bmap(struct inode *inode,
			struct file *file, unsigned int cmd, unsigned long arg)
{
	int rc = 0;
	struct evms_get_bmap_pkt tmp, *user_parms;

	user_parms = (struct evms_get_bmap_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	/* pass the ioctl down the volume stack */
	if (!rc) {
		struct evms_logical_volume *volume;

		volume = &evms_logical_volumes[MINOR(inode->i_rdev)];
		rc = IOCTL(volume->node, inode, file, cmd,
			   (unsigned long) &tmp);
	}
	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_process_notify_event(unsigned long arg)
{
	int rc = 0, found = FALSE;
	struct evms_notify_pkt tmp, *user_parms;
	struct evms_list_node **list_node = NULL;
	struct evms_event *event = NULL;

	user_parms = (struct evms_notify_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	/* check to see if PID has already been registered
	 * for this event.
	 */
	if (!rc) {
		list_node = &evms_global_notify_list;
		while (*list_node) {
			event = (*list_node)->item;
			if ((event->pid == tmp.eventry.pid) &&
			    (event->eventid == tmp.eventry.eventid)) {
				found = TRUE;
				break;
			}
			list_node = &(*list_node)->next;
		}
	}
	if (tmp.command) {	/* tmp.command == EVMS_REGISTER_EVENT */
		/* registration code */
		if (found) {
			rc = -EBUSY;
			LOG_ERROR("error(%d) pid(%d) already register to "
				  "receive signal(%d) on event(%d).\n", rc,
				  tmp.eventry.pid, tmp.eventry.signo,
				  tmp.eventry.eventid);
		} else {
			/* register this pid/event type */
			event = kmalloc(sizeof (struct evms_event), GFP_KERNEL);
			if (!event) {
				rc = -ENOMEM;
				LOG_ERROR("error(%d) allocating event structure.\n", rc);
			} else {
				memset(event, 0, sizeof (struct evms_event));
				event->pid = tmp.eventry.pid;
				event->eventid = tmp.eventry.eventid;
				event->signo = tmp.eventry.signo;
				rc = evms_cs_add_item_to_list
				    (&evms_global_notify_list, event);
			}
		}
	} else {		/* tmp.command == EVMS_UNREGISTER_EVENT */
		/* unregistration code */
		if (!found) {
			rc = -ENODATA;
			LOG_ERROR("error(%d) attempting to unregister a "
				  "non-registered pid(%d) on event(%d).\n",
				  rc, tmp.eventry.pid, tmp.eventry.eventid);
		} else {
			event = (*list_node)->item;
			rc = evms_cs_remove_item_from_list
			    (&evms_global_notify_list, event);
			if (!rc) {
				kfree(event);
			}
		}
	}
	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_check_mount_status(struct inode *inode, struct file *file,
				  ulong arg)
{
	int rc = 0;
	struct evms_mount_status_pkt tmp, *user_parms;

	user_parms = (struct evms_mount_status_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (!rc) {
		tmp.mounted =
		    (is_mounted(MKDEV(EVMS_MAJOR, tmp.minor))) ? TRUE : FALSE;
	}

	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

static int
evms_ioctl_cmd_check_open_status(struct inode *inode, struct file *file,
				  ulong arg)
{
	int rc = 0;
	struct evms_open_status_pkt tmp, *user_parms;

	user_parms = (struct evms_open_status_pkt *) arg;
	/* copy user's parameters to kernel space */
	if (copy_from_user(&tmp, user_parms, sizeof (tmp)))
		rc = -EFAULT;

	if (!rc) {
		tmp.opens = is_open(tmp.minor);
	}

	/* copy the status value back to the user */
	tmp.status = rc;
	if (copy_to_user(user_parms, &tmp, sizeof (tmp)))
		rc = -EFAULT;

	return rc;
}

/************************************************/
/* END -- IOCTL commands -- EVMS specific       */
/************************************************/

/************************************************/
/* START -- IOCTL commands -- Volume specific   */
/************************************************/

/************************************************/
/* END -- IOCTL commands -- Volume specific     */
/************************************************/

/************************************************/
/* START -- IOCTL main                          */
/************************************************/

/*
 * Function: evms_ioctl
 *
 *  This function is the main ioctl entry point for all of evms.
 */

static int
evms_ioctl(struct inode *inode,
	   struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned long minor = 0;
	int rc = 0;
	struct evms_logical_node *node = NULL;

	/* check user access */
	if (!capable(CAP_SYS_ADMIN))
		rc = -EACCES;

	if (!inode)
		rc = -EINVAL;

	if (!rc) {
		/* get the minor */
		minor = MINOR(inode->i_rdev);
		LOG_EXTRA("ioctl: minor(%lu), dir(%d), size(%d), type(%d), "
			  "nr(%d)\n", minor,
			  (cmd >> _IOC_DIRSHIFT) & _IOC_DIRMASK,
			  (cmd >> _IOC_SIZESHIFT) & _IOC_SIZEMASK,
			  (cmd >> _IOC_TYPESHIFT) & _IOC_TYPEMASK,
			  (cmd >> _IOC_NRSHIFT) & _IOC_NRMASK);

		/* insure this minor points to a valid volume */
		if (minor) {
			node = evms_logical_volumes[minor].node;
			if (node == NULL)
				rc = -ENXIO;
		}
	}

	/* process the IOCTL commands */
	if (!rc) {
		if (!minor) {
			/* process all EVMS specific commands */
			switch (cmd) {
			case EVMS_GET_IOCTL_VERSION:
				rc = evms_ioctl_cmd_get_ioctl_version((void *)
								      arg);
				break;
			case EVMS_GET_VERSION:
				rc = evms_ioctl_cmd_get_version((void *) arg);
				break;
			case EVMS_GET_INFO_LEVEL:
				rc = evms_ioctl_cmd_get_info_level((void *)
								   arg);
				break;
			case EVMS_SET_INFO_LEVEL:
				rc = evms_ioctl_cmd_set_info_level((void *)
								   arg);
				break;
			case EVMS_REDISCOVER_VOLUMES:
				rc = evms_ioctl_cmd_rediscover_volumes(inode,
								       file,
								       cmd,
								       arg);
				break;
			case EVMS_GET_LOGICAL_DISK:
				rc = evms_ioctl_cmd_get_logical_disk((void *)
								     arg);
				break;
			case EVMS_GET_LOGICAL_DISK_INFO:
				rc = evms_ioctl_cmd_get_logical_disk_info((void
									   *)
									  arg);
				break;
			case EVMS_SECTOR_IO:
				rc = evms_ioctl_cmd_sector_io((void *) arg);
				break;
			case EVMS_GET_MINOR:
				rc = evms_ioctl_cmd_get_minor((void *) arg);
				break;
			case EVMS_GET_VOLUME_DATA:
				rc = evms_ioctl_cmd_get_volume_data((void *)
								    arg);
				break;
			case EVMS_DELETE_VOLUME:
				rc = evms_ioctl_cmd_delete_volume(inode, file,
								  arg);
				break;
			case EVMS_GET_PLUGIN:
				rc = evms_ioctl_cmd_get_plugin((void *) arg);
				break;
			case EVMS_PLUGIN_IOCTL:
				rc = evms_ioctl_cmd_plugin_ioctl(inode, file,
								 cmd, arg);
				break;
			case EVMS_COMPUTE_CSUM:
				rc = evms_ioctl_cmd_kernel_partial_csum((void *)
									arg);
				break;
			case EVMS_PROCESS_NOTIFY_EVENT:
				rc = evms_ioctl_cmd_process_notify_event(arg);
				break;
			case EVMS_CHECK_MOUNT_STATUS:
				rc = evms_ioctl_cmd_check_mount_status(inode,
								       file,
								       arg);
				break;
			case EVMS_CHECK_OPEN_STATUS:
				rc = evms_ioctl_cmd_check_open_status(inode,
								       file,
								       arg);
				break;
			default:
				rc = -EINVAL;
				break;
			}
		} else {
			/* process Volume specific commands */
			switch (cmd) {
				/* pick up standard blk ioctls */
			case BLKFLSBUF:
			case BLKROSET:
			case BLKROGET:
			case BLKRASET:
			case BLKRAGET:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)
			case BLKBSZGET:
			case BLKBSZSET:
#endif
			case BLKSSZGET:
				rc = blk_ioctl(inode->i_rdev, cmd, arg);
				break;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)
			case BLKGETSIZE:
				{
					/* casting size down to 32-bits until
					 * kernel allows return of 64-bit size
					 * values.
					 */
					long size = node->total_vsectors;
					if (copy_to_user
					    ((long *) arg, &size,
					     sizeof (long)))
						rc = -EFAULT;
				}
				break;
			case BLKGETSIZE64:
				{
					u64 size_in_bytes =
					    node->
					    total_vsectors <<
					    EVMS_VSECTOR_SIZE_SHIFT;
					if (copy_to_user
					    ((u64 *) arg, &size_in_bytes,
					     sizeof (u64)))
						rc = -EFAULT;
				}
				break;
#endif
			case EVMS_GET_IOCTL_VERSION:
				rc = evms_ioctl_cmd_get_ioctl_version((void *)
								      arg);
				break;
			case EVMS_GET_BMAP:
				rc = evms_ioctl_cmd_get_bmap(inode, file, cmd,
							     arg);
				break;
			case EVMS_GET_VOL_STRIPE_INFO:
				{
					struct evms_vol_stripe_info_pkt info;

					info.size =
					    PAGE_SIZE >>
					    EVMS_VSECTOR_SIZE_SHIFT;
					info.width = 1;
					if (copy_to_user
					    ((struct evms_vol_stripe_info_pkt *)
					     arg, &info, sizeof (info)))
						rc = -EFAULT;
				}
				break;

			default:
				rc = IOCTL(node, inode, file, cmd, arg);
				break;
			}
		}
	}
	return rc;
}

/************************************************/
/* END -- IOCTL main                            */
/************************************************/

/************************************************/
/* START -- CHECK MEDIA CHANGE		        */
/************************************************/

static int
evms_check_media_change(kdev_t dev)
{
	int rc = 0;
	struct evms_logical_volume *volume = NULL;

	/* check user access */
	if (!capable(CAP_SYS_ADMIN))
		rc = -EACCES;
	if (!rc) {
		int minor;
		/* get the minor */
		minor = MINOR(dev);
		/* insure this minor points to a valid volume */
		volume = &evms_logical_volumes[minor];
		if (volume->node == NULL) {
			rc = -ENXIO;
		}
	}
	if (!rc) {
		if (volume->flags & EVMS_DEVICE_REMOVABLE) {
			/* check for media change */
			rc = evms_cs_kernel_ioctl(volume->node,
						  EVMS_CHECK_MEDIA_CHANGE,
						  (unsigned long) NULL);
			if (rc < 0) {
				LOG_ERROR("error(%d) doing EVMS_CHECK_MEDIA_CHANGE "
					  "ioctl on '%s'.\n", rc, volume->name);
			}
		}
	}
	return rc;
}

/************************************************/
/* END -- CHECK MEDIA CHANGE		        */
/************************************************/

static int
evms_check_for_device_changes(struct inode *inode, struct file *file)
{
	int rc = 0, something_changed = 0, i;
	struct evms_rediscover_pkt kernel_rd_pckt = { 0, 0, NULL };
	struct evms_list_node *disk_list = NULL, *lnode, *next_lnode;
	struct evms_logical_node *disk, *new_device_list = NULL;
	struct evms_logical_volume *volume = NULL;

	/* check for new devices
	 *
	 * put all new devices on the disk list so they
	 * will be included in the rediscovery process.
	 */
	static void evms_discover_logical_disks(struct evms_logical_node **);
	evms_discover_logical_disks(&new_device_list);
	if (new_device_list) {
		LOG_DETAILS("%s: new devices detected.\n", __FUNCTION__);
		something_changed++;
		/* put these new nodes on the disk list */
		while (new_device_list) {
			disk = new_device_list;
			rc = evms_cs_remove_logical_node_from_list
			    (&new_device_list, disk);
			if (rc) {
				LOG_ERROR("%s: error(%d) removing device(%s) "
					  "from list.\n", __FUNCTION__, rc,
					  disk->name);
			}
			rc = evms_cs_add_item_to_list(&disk_list, disk);
			if (rc) {
				LOG_ERROR("%s: error(%d) adding device(%s) "
					  "from list.\n", __FUNCTION__, rc,
					  disk->name);
			}
		}
	}

	/* check all devices for changed removable media
	 *
	 * scan the global device list and issue check
	 * media change on each removable media device.
	 * put all removable devices that indicate a
	 * media change on the disk list.
	 *
	 * also scan for devices that have been unplugged
	 * or contain corrupt volumes.
	 */
	for (lnode = evms_global_device_list; lnode; lnode = lnode->next) {
		int add_to_list = FALSE;
		disk = (struct evms_logical_node *) lnode->item;
		/* only really check removable media devices */
		if (disk->flags & EVMS_DEVICE_REMOVABLE) {
			/* check for media change */
			rc = evms_cs_kernel_ioctl(disk,
						  EVMS_CHECK_MEDIA_CHANGE,
						  (unsigned long) NULL);
			if (rc < 0) {
				LOG_ERROR("%s: error(%d) doing EVMS_CHECK_MEDIA_CHANGE "
					  "ioctl on '%s'.\n", __FUNCTION__, rc,
					  disk->name);
			} else if (rc == 1) {
				add_to_list = TRUE;
			}
		}
		/* check for device that where present
		 * before but are gone (unplugged
		 * device or unloaded driver).
		 */
		rc = IOCTL(disk, inode, file,
			   EVMS_CHECK_DEVICE_STATUS, (ulong) NULL);
		if (rc) {
			LOG_ERROR("error(%d) doing EVMS_CHECK_DEVICE_STATUS "
				  "ioctl on '%s'.\n", rc, volume->name);
		}
		if (disk->flags & EVMS_DEVICE_UNAVAILABLE) {
			add_to_list = TRUE;
		}
		if (add_to_list) {
			something_changed++;
			rc = evms_cs_add_item_to_list(&disk_list, disk);
		}
	}
	/* log a statement that we detected changed media.
	 */
	if (disk_list) {
		LOG_DETAILS("%s: media change detected.\n", __FUNCTION__);
	}

	/* check for volumes with removed removable media.
	 * mark the volumes that reside on changed media.
	 */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		volume = &evms_logical_volumes[i];
		if (!volume->node)
			continue;
		if (!(volume->flags & EVMS_DEVICE_REMOVABLE))
			continue;
		if (evms_check_media_change(MKDEV(EVMS_MAJOR, i)) <= 0)
			continue;
		/* remember which volumes have changed media */
		volume->flags |= EVMS_MEDIA_CHANGED;
		something_changed++;
	}

	/* check for removed devices */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		int status;
		volume = &evms_logical_volumes[i];
		if (!volume->node)
			continue;
		/* check for device status */
		status = 0;
		rc = IOCTL(volume->node, inode, file,
			   EVMS_CHECK_DEVICE_STATUS, (ulong) & status);
		if (rc) {
			LOG_ERROR("error(%d) doing EVMS_CHECK_DEVICE_STATUS "
				  "ioctl on '%s'.\n", rc, volume->name);
			continue;
		}
		if (!(status & EVMS_DEVICE_UNAVAILABLE)) {
			continue;
		}
		/* remember which volumes have changed media */
		volume->flags |= EVMS_DEVICE_UNPLUGGED;
		something_changed++;
	}

	/* do we have some work to do? */
	if (something_changed) {
		/* check for volumes to be deleted */
		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			struct evms_quiesce_vol_pkt qv;

			volume = &evms_logical_volumes[i];
			if (!volume->node)
				continue;
			/* only proceed on volumes with:
			 *  changed media,
			 *  hot-unplugged devices,
			 *  & partial volumes
			 */
			if (!(volume->flags &
			      (EVMS_MEDIA_CHANGED |
			       EVMS_VOLUME_PARTIAL | EVMS_DEVICE_UNPLUGGED)))
				continue;
			/* gather the disk's needing to be
			 * rediscovered to rebuild this
			 * volume.
			 *
			 * this will locate other disks that
			 * the volume resides on that don't
			 * indicate media change.
			 */
			rc = evms_cs_kernel_ioctl(volume->node,
						  EVMS_GET_DISK_LIST,
						  (unsigned long) &disk_list);
			if (rc) {
				LOG_ERROR("%s: error(%d) retrieving underlying "
					  "disk list for '%s', skipping ...\n",
					  __FUNCTION__, rc, volume->name);
				continue;
			}
			/* quiesce all the changed volumes
			 * prior to being deleted.
			 */
			qv.command = 1;	// quiesce
			qv.minor = i;	//
			qv.status = 0;	// reset status
			qv.do_vfs = 0;
			rc = evms_quiesce_volume(volume, inode, file, &qv);
			if (rc) {
				LOG_ERROR("%s: error(%d) attempting to quiesce "
					  "'%s%s'.\n", __FUNCTION__, rc,
					  EVMS_DEV_NODE_PATH, volume->name);
			}
		}

		/* we need to revalidate all the changed
		 * media. this is accomplished by issuing
		 * the revalidate disk ioctl to each device
		 * with changed media. the device manager
		 * remembers which devices indicated
		 * media changed (set by check media
		 * changed ioctl issued earlier), and will
		 * only issue the revalidate disk ioctl to
		 * those disks one time.
		 *
		 * NOTE:
		 * this needs to be done BEFORE deleting
		 * the volumes because deleting the
		 * last segment on disk will cause the
		 * associated disk node to freed, and we
		 * will not be able to issue the
		 * revalidate disk ioctl after that.
		 */
		for (lnode = disk_list; lnode; lnode = lnode->next) {
			disk = (struct evms_logical_node *) lnode->item;
			/* only really do removable media devices */
			if (disk->flags & EVMS_MEDIA_CHANGED) {
				/* go revalidate the change media */
				rc = evms_cs_kernel_ioctl(disk,
							  EVMS_REVALIDATE_DISK,
							  (unsigned long) NULL);
				if (rc) {
					LOG_ERROR("%s: error(%d) attempting to "
						  "revalidate '%s%s'.\n",
						  __FUNCTION__, rc,
						  EVMS_DEV_NODE_PATH, volume->name);
				}
			}
		}

		/* delete all the affected volumes */
		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			struct evms_delete_vol_pkt dv;

			volume = &evms_logical_volumes[i];
			if (!volume->node)
				continue;
			/* only proceed on volumes with:
			 *  changed media,
			 *  hot-unplugged devices,
			 *  & partial volumes
			 */
			if (!(volume->flags &
			      (EVMS_MEDIA_CHANGED |
			       EVMS_VOLUME_PARTIAL | EVMS_DEVICE_UNPLUGGED)))
				continue;
			/* only delete quiesced volumes */
			if (!volume->quiesced)
				continue;
			/* delete the volume from memory.
			 * do a 'soft' delete if volume
			 * is mounted, and 'hard' delete
			 * if it is not.
			 *
			 * NOTE: the delete operation will
			 * clear the bits in the flags field.
			 */
			dv.command = (is_open(i)) ? 0 : 1;
			dv.minor = i;
			dv.status = 0;
			rc = evms_delete_volume(volume, &dv);
		}

		/* at this point all devices indicating
		 * media change that had volumes on them
		 * should be gone. however, we could still
		 * have devices indicating media change
		 * that had no volumes on them in the disk
		 * list. we need to delete these devices
		 * from kernel memory and the global device
		 * list.
		 */
		for (lnode = evms_global_device_list; lnode; lnode = next_lnode) {
			next_lnode = lnode->next;

			disk = (struct evms_logical_node *) lnode->item;
			if (disk->flags & EVMS_MEDIA_CHANGED) {
				rc = DELETE(disk);
			}
		}

		/* all the devices that indicated media
		 * change should be gone, both from kernel
		 * memory and global device list. we now
		 * need to remove any references to these
		 * devices from the disk list.
		 *
		 * when removable media is installed, it
		 * will get detected in the device manager's
		 * rediscovery as a new device and added to
		 * the discover list.
		 */
		for (lnode = disk_list; lnode; lnode = next_lnode) {
			struct evms_list_node *glnode;
			int lnode_still_there;

			next_lnode = lnode->next;

			lnode_still_there = FALSE;
			for (glnode = evms_global_device_list;
			     glnode; glnode = glnode->next) {
				if (glnode->item == lnode->item) {
					lnode_still_there = TRUE;
					break;
				}
			}
			if (lnode_still_there == FALSE) {
				rc = evms_cs_remove_item_from_list(&disk_list,
								   lnode->item);
				if (rc) {
					LOG_ERROR("%s: error(%d) attempting to "
						  "remove item(%p) from "
						  "disk_list(%p).\n",
						  __FUNCTION__, rc, lnode->item,
						  &disk_list);
				}
			}
		}

		/* build the in-kernel rediscover packet */

		/* allocate the space for the drive_array in
		 * the struct evms_rediscover_pkt packet. to do this
		 * we need to count the number of disk nodes,
		 * then allocate the necessary space.
		 */
		/* count the disk nodes */
		for (lnode = disk_list; lnode; lnode = lnode->next)
			kernel_rd_pckt.drive_count++;
		/* allocate the space */
		if (kernel_rd_pckt.drive_count) {
			kernel_rd_pckt.drive_array =
			    kmalloc(kernel_rd_pckt.drive_count *
				    sizeof (u64), GFP_KERNEL);
			if (!kernel_rd_pckt.drive_array) {
				rc = -ENOMEM;
				LOG_ERROR("%s: error(%d) allocating rediscover "
					  "drive array.\n", __FUNCTION__, rc);
			}
		}
		/* populate the drive array
		 *
		 * this also frees the disk_list which is useful
		 * if we had an error allocating the drive array.
		 */
		for (i = 0, lnode = disk_list; lnode; lnode = next_lnode, i++) {
			next_lnode = lnode->next;

			/* remove this disk from the disk list */
			disk = (struct evms_logical_node *) lnode->item;
			rc = evms_cs_remove_item_from_list(&disk_list, disk);
			if (!rc) {
				/* add this disk to rediscover
				 * packet
				 */
				kernel_rd_pckt.drive_array[i] =
				    NODE_TO_DEV_HANDLE(disk);
			}
		}
		/* perform the rediscovery operation */
		if (!rc) {
			static int evms_discover_volumes(struct
							 evms_rediscover_pkt *);
			rc = evms_discover_volumes(&kernel_rd_pckt);
			if (kernel_rd_pckt.drive_count) {
				kfree(kernel_rd_pckt.drive_array);
			}
		}
		LOG_DETAILS("%s: rediscover completed.\n", __FUNCTION__);
	}

	return rc;
}

/************************************************/
/* START -- REVALIDATE DISK		        */
/************************************************/

static int
evms_revalidate_disk(kdev_t dev)
{
	int rc = 0;
	struct evms_logical_volume *volume = NULL;

	/* check user access */
	if (!capable(CAP_SYS_ADMIN))
		rc = -EACCES;
	if (!rc) {
		int minor;
		/* get the minor */
		minor = MINOR(dev);
		/* insure this minor points to a valid volume */
		volume = &evms_logical_volumes[minor];
		if (volume->node == NULL) {
			rc = -ENXIO;
		}
	}
	if (!rc) {
		/* go revalidate the change media */
		rc = evms_cs_kernel_ioctl(volume->node,
					  EVMS_REVALIDATE_DISK,
					  (unsigned long) NULL);
	}
	return rc;
}

/************************************************/
/* END -- REVALIDATE DISK		        */
/************************************************/

/************************************************/
/* START -- OPEN			        */
/************************************************/

static int
evms_open(struct inode *inode, struct file *file)
{
	int rc = 0, minor = 0;
	struct evms_logical_volume *volume = NULL;

	rc = evms_check_for_device_changes(inode, file);
	if (!rc) {
		/* get the minor */
		minor = MINOR(inode->i_rdev);
		if (minor) {
			/* insure this minor points to a valid volume */
			volume = &evms_logical_volumes[minor];
			if (volume->node == NULL) {
				rc = -ENXIO;
			}
		}
	}
	/* go "open" the volume */
	if (!rc && minor) {
		atomic_inc(&volume->opens);
		rc = IOCTL(volume->node, inode, file,
			   EVMS_OPEN_VOLUME, (unsigned long) NULL);
		if (rc) {
			LOG_ERROR("error(%d) doing EVMS_OPEN_VOLUME ioctl to "
				  "'%s'.\n", rc, volume->name);
			atomic_dec(&volume->opens);
		}
	}
	return rc;
}

/************************************************/
/* END -- OPEN				        */
/************************************************/

/************************************************/
/* START -- RELEASE			        */
/************************************************/

static int
evms_release(struct inode *inode, struct file *file)
{
	int rc = 0, minor = 0;
	struct evms_logical_volume *volume = NULL;

	/* get the minor */
	minor = MINOR(inode->i_rdev);
	if (minor) {
		/* insure this minor points to a valid volume */
		volume = &evms_logical_volumes[minor];
		if (volume->node) {
			rc = IOCTL(volume->node, inode, file,
				   EVMS_CLOSE_VOLUME, (unsigned long) NULL);
			if (rc) {
				LOG_ERROR("error(%d) doing EVMS_CLOSE_VOLUME "
					  "ioctl to '%s'.\n", rc, volume->name);
			}
			atomic_dec(&volume->opens);
		}
	}
	return 0;
}

/************************************************/
/* END -- RELEASE			        */
/************************************************/

static struct block_device_operations evms_fops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,14)
	owner:THIS_MODULE,
#endif
	open:evms_open,
	release:evms_release,
	ioctl:evms_ioctl,
	check_media_change:evms_check_media_change,
	revalidate:evms_revalidate_disk
};

/**********************************************************/
/* END -- FOPS functions definitions                      */
/**********************************************************/

/**********************************************************/
/* START -- RUNTIME support functions                     */
/**********************************************************/

static request_queue_t *
evms_find_queue(kdev_t dev)
{
	request_queue_t *rq = NULL;
	struct evms_logical_volume *volume;

	volume = &evms_logical_volumes[MINOR(dev)];
	rq = &volume->request_queue;
	return rq;
}

/*
 * Function:    evms_make_request_fn
 *
 */
static int
evms_make_request_fn(request_queue_t * q, int rw, struct buffer_head *bh)
{
	struct evms_logical_volume *volume;

//	volume = &evms_logical_volumes[MINOR(bh->b_rdev)];
	volume = q->queuedata;
	wait_event(volume->wait_queue, (!volume->quiesced));
	if (volume->node) {
		switch (rw) {
		case READ:
		case READA:
			atomic_inc(&volume->requests_in_progress);
			R_IO(volume->node, bh);
			atomic_dec(&volume->requests_in_progress);
			break;
		case WRITE:
			atomic_inc(&volume->requests_in_progress);
			W_IO(volume->node, bh);
			atomic_dec(&volume->requests_in_progress);
			break;
		default:
			bh->b_end_io(bh, 0);
		}
	} else {
		LOG_ERROR("request for unknown logical volume [minor(%d)].\n",
			  MINOR(bh->b_rdev));
		bh->b_end_io(bh, 0);
	}
	return 0;
}

/**********************************************************/
/* END -- RUNTIME support functions                       */
/**********************************************************/

/**********************************************************/
/* START -- INIT/DISCOVERY support functions              */
/**********************************************************/

#ifdef LOCAL_DEBUG
static void
display_discover_list(struct evms_logical_node *discover_list, char *text)
{
	struct evms_logical_node *node;

	LOG_DETAILS("discover list:(%s)\n", text);
	for (node = discover_list; node; node = node->next) {
		LOG_DETAILS("\nnode info:\n");
		LOG_DETAILS("node.....................(0x%p)\n", node);
		LOG_DETAILS("name.....................(%s)\n", node->name);
		LOG_DETAILS("plugin id................(0x%x)\n",
			    node->plugin->id);
		LOG_DETAILS("size.....................("PFU64")\n",
			    node->total_vsectors);
		LOG_DETAILS("flags....................(0x%x)\n", node->flags);
		LOG_DETAILS("iflags...................(0x%x)\n", node->iflags);
		LOG_DETAILS("sector size..............(%d)\n",
			    node->hardsector_size);
		LOG_DETAILS("block size...............(%d)\n",
			    node->block_size);
		LOG_DETAILS("sys id...................(0x%x)\n",
			    node->system_id);

		if (node->feature_header) {
			struct evms_feature_header *fh;

			fh = node->feature_header;
			LOG_DETAILS("\nfeature header:\n");
			LOG_DETAILS("signature................(0x%x)\n",
				    fh->signature);
			LOG_DETAILS("crc......................(0x%x)\n",
				    fh->crc);
			LOG_DETAILS("feature header version...(%d.%d.%d)\n",
				    fh->version.major, fh->version.minor,
				    fh->version.patchlevel);
			LOG_DETAILS("engine version...........(%d.%d.%d)\n",
				    fh->engine_version.major,
				    fh->engine_version.minor,
				    fh->engine_version.patchlevel);
			LOG_DETAILS("flags....................(0x%x)\n",
				    fh->flags);
			LOG_DETAILS("feature id...............(0x%x)\n",
				    fh->feature_id);
			LOG_DETAILS("sequence#................("PFU64")\n",
				    fh->sequence_number);
			LOG_DETAILS("alignment padding........("PFU64")\n",
				    fh->alignment_padding);
			LOG_DETAILS("feature data1 lsn........("PFU64")\n",
				    fh->feature_data1_start_lsn);
			LOG_DETAILS("feature data1 size.......("PFU64")\n",
				    fh->feature_data1_size);
			LOG_DETAILS("feature data2 lsn........("PFU64")\n",
				    fh->feature_data2_start_lsn);
			LOG_DETAILS("feature data2 size.......("PFU64")\n",
				    fh->feature_data2_size);
			LOG_DETAILS("volume sn................("PFU64")\n",
				    fh->volume_serial_number);
			LOG_DETAILS("volume minor#............(%d)\n",
				    fh->volume_system_id);
			LOG_DETAILS("object depth.............(%d)\n",
				    fh->object_depth);
			LOG_DETAILS("object name..............(%s)\n",
				    fh->object_name);
			LOG_DETAILS("volume name..............(%s)\n",
				    fh->volume_name);
		}

		if (node->volume_info) {
			struct evms_volume_info *vi;

			vi = node->volume_info;
			LOG_DETAILS("\nvolume info:\n");
			LOG_DETAILS("volume name..............(%s)\n",
				    vi->volume_name);
			LOG_DETAILS("volume sn................("PFU64")\n",
				    vi->volume_sn);
			LOG_DETAILS("volume minor#............(%d)\n",
				    vi->volume_minor);
		}
	}
	if (discover_list) {
		LOG_DETAILS("\n");
	}
}
#endif

/*
 * Function:     evms_discover_logical_disks
 * Description: Construct the logical disk list by calling all registered device managers.
 */
static void
evms_discover_logical_disks(struct evms_logical_node **disk_list)
{
	struct evms_registered_plugin *p;
	LOG_EXTRA("discovering logical disks...\n");
	for (p = registered_plugin_head; p; p = p->next) {
		if (GetPluginType(p->plugin->id) == EVMS_DEVICE_MANAGER) {
			DISCOVER(p, disk_list);
		}
	}
}

/*
 * Function:     evms_discover_logical_partitions
 * Description: Construct the logical partition list by calling all registered partition managers.
 */
static void
evms_discover_logical_partitions(struct evms_logical_node **discover_list)
{
	int rc, done;

	struct evms_registered_plugin *p;
	LOG_EXTRA("discovering segments...\n");
	do {
		done = TRUE;
		for (p = registered_plugin_head; p; p = p->next) {
			if (GetPluginType(p->plugin->id) ==
			    EVMS_SEGMENT_MANAGER) {
				rc = DISCOVER(p, discover_list);
				/* RC > 0 means the plugin added something to
				 * the discover list. This also means we must
				 * loop thru these plugins another time.
				 * RC == 0 means nothing was added to the
				 * discover list by this plugin. RC < 0 means
				 * the plugin encountered some error and
				 * nothing was added to the list.
				 * NOTE: If a plugin has both added something
				 * new to the discover list and encountered an
				 * error, RC > 0 must be returned.
				 */
				if (rc > 0)
					done = FALSE;
			}
		}
	} while (done == FALSE);

	/* send the end of discovery signal to each
	 * partition manager plugin.
	 */
	for (p = registered_plugin_head; p; p = p->next)
		if (GetPluginType(p->plugin->id) == EVMS_SEGMENT_MANAGER)
			if (p->plugin->fops->end_discover)
				rc = END_DISCOVER(p, discover_list);
}

/*
 * Function:     evms_discover_volume_groups
 * Description: Find volume groups within the logical partitions list
 */
static void
evms_discover_volume_groups(struct evms_logical_node **discover_list)
{
	int rc, done;

	struct evms_registered_plugin *p;
	LOG_EXTRA("discovering regions...\n");
	do {
		done = TRUE;
		for (p = registered_plugin_head; p; p = p->next) {
			if (GetPluginType(p->plugin->id) == EVMS_REGION_MANAGER) {
				rc = DISCOVER(p, discover_list);
				/* RC > 0 means the plugin added something to
				 * the discover list. This also means we must
				 * loop thru these plugins another time.
				 * RC == 0 means nothing was added to the
				 * discover list by this plugin. RC < 0 means
				 * the plugin encountered some error and
				 * nothing was added to the list.
				 * NOTE: If a plugin has both added something
				 * new to the discover list and encountered an
				 * error, RC > 0 must be returned.
				 */
				if (rc > 0)
					done = FALSE;
			}
		}
	} while (done == FALSE);

	/* send the end of discovery signal to each volume
	 * group plugin.
	 */
	for (p = registered_plugin_head; p; p = p->next)
		if (GetPluginType(p->plugin->id) == EVMS_REGION_MANAGER)
			if (p->plugin->fops->end_discover)
				rc = END_DISCOVER(p, discover_list);
}

/*
 *
 * convert all the feature header fields into cpu native format
 * from the on-disk Little Endian format. From this point forward
 * all plugins can deal with feature headers natively.
 */
void
le_feature_header_to_cpu(struct evms_feature_header *fh)
{
	fh->signature = le32_to_cpup(&fh->signature);
	fh->crc = le32_to_cpup(&fh->crc);
	fh->version.major = le32_to_cpup(&fh->version.major);
	fh->version.minor = le32_to_cpup(&fh->version.minor);
	fh->version.patchlevel = le32_to_cpup(&fh->version.patchlevel);
	fh->engine_version.major = le32_to_cpup(&fh->engine_version.major);
	fh->engine_version.minor = le32_to_cpup(&fh->engine_version.minor);
	fh->engine_version.patchlevel = le32_to_cpup(&fh->engine_version.patchlevel);
	fh->flags = le32_to_cpup(&fh->flags);
	fh->feature_id = le32_to_cpup(&fh->feature_id);
	fh->sequence_number = le64_to_cpup(&fh->sequence_number);
	fh->alignment_padding = le64_to_cpup(&fh->alignment_padding);
	fh->feature_data1_start_lsn = le64_to_cpup(&fh->feature_data1_start_lsn);
	fh->feature_data1_size = le64_to_cpup(&fh->feature_data1_size);
	fh->feature_data2_start_lsn = le64_to_cpup(&fh->feature_data2_start_lsn);
	fh->feature_data2_size = le64_to_cpup(&fh->feature_data2_size);
	fh->volume_serial_number = le64_to_cpup(&fh->volume_serial_number);
	fh->volume_system_id = le32_to_cpup(&fh->volume_system_id);
	fh->object_depth = le32_to_cpup(&fh->object_depth);
}

static int
edef_load_feature_header(struct evms_logical_node *node)
{
	int i, rc = 0, rc_array[2] = { 0, 0 };
	unsigned long size_in_bytes;
	u64 size_in_sectors, starting_sector = 0;
	struct evms_feature_header *fh = NULL, *fh1 = NULL, *fh2 = NULL;
	char *location_name = NULL;
	struct evms_version version = {
		EVMS_FEATURE_HEADER_MAJOR,
		EVMS_FEATURE_HEADER_MINOR,
		EVMS_FEATURE_HEADER_PATCHLEVEL
	};

	if (!node->feature_header) {
		size_in_sectors = evms_cs_size_in_vsectors(sizeof (*fh));
		size_in_bytes = size_in_sectors << EVMS_VSECTOR_SIZE_SHIFT;
		fh1 = kmalloc(size_in_bytes, GFP_KERNEL);
		if (fh1) {
			fh2 = kmalloc(size_in_bytes, GFP_KERNEL);
			if (!fh2) {
				kfree(fh1);
				rc = -ENOMEM;
			}
		} else {
			rc = -ENOMEM;
		}

		for (i = 0; i < 2; i++) {
			if (i == 0) {
				starting_sector =
				    node->total_vsectors - size_in_sectors;
				fh = fh1;
				location_name = evms_primary_string;
			} else {
				starting_sector--;
				fh = fh2;
				location_name = evms_secondary_string;
			}
			/* read header into buffer */
			rc = INIT_IO(node,
				     0, starting_sector, size_in_sectors, fh);
			if (rc) {
				LOG_ERROR("error(%d) probing for %s feature "
					  "header(at "PFU64") on '%s'.\n", rc,
					  location_name, starting_sector,
					  node->name);
				rc_array[i] = rc;
				continue;
			}
			/* validate header signature */
			if (cpu_to_le32(fh->signature) !=
			    EVMS_FEATURE_HEADER_SIGNATURE) {
				rc = -ENODATA;
				rc_array[i] = rc;
				continue;
			}
			/* validate header CRC */
			if (fh->crc != EVMS_MAGIC_CRC) {
				u32 org_crc, final_crc;
				org_crc = cpu_to_le32(fh->crc);
				fh->crc = 0;
				final_crc =
				    evms_cs_calculate_crc(EVMS_INITIAL_CRC, fh,
							  sizeof (*fh));
				if (final_crc != org_crc) {
					LOG_ERROR("CRC mismatch error [stored(%x), "
						  "computed(%x)] in %s feature "
						  "header(at "PFU64") on '%s'.\n",
						  org_crc, final_crc, location_name,
						  starting_sector, node->name);
					rc = -EINVAL;
					rc_array[i] = rc;
					continue;
				}
			} else {
				LOG_WARNING("CRC disabled in %s feature header "
					    "(at "PFU64") on '%s'.\n",
					    location_name, starting_sector,
					    node->name);
			}
			/* convert the feature header from the
			 * on-disk format (Little Endian) to
			 * native cpu format.
			 */
			le_feature_header_to_cpu(fh);
			/* verify the system data version */
			rc = evms_cs_check_version(&version, &fh->version);
			if (rc) {
				LOG_ERROR("error: obsolete version(%d,%d,%d) "
					  "in %s feature header on '%s'.\n",
					  fh->version.major, fh->version.minor,
					  fh->version.patchlevel, location_name,
					  node->name);
				rc_array[i] = rc;
			}
		}

		/* getting same return code for both copies? */
		if (rc_array[0] == rc_array[1]) {
			rc = rc_array[0];
			/* if no errors on both copies,
			 * check the sequence numbers.
			 * use the highest sequence number.
			 */
			if (!rc) {
				/* compare sequence numbers */
				if (fh1->sequence_number ==
				    fh2->sequence_number) {
					fh = fh1;
				} else {
					LOG_WARNING("%s feature header sequence "
						    "number("PFU64") mismatches "
						    "%s feature header sequence "
						    "number("PFU64") on '%s'!\n",
						    evms_primary_string,
						    fh1->sequence_number,
						    evms_secondary_string,
						    fh2->sequence_number, node->name);
					if (fh1->sequence_number >
					    fh2->sequence_number) {
						fh = fh1;
						location_name =
						    evms_primary_string;
						/* indicate bad sequence number of secondary */
						rc_array[1] = -1;
					} else {
						fh = fh2;
						location_name =
						    evms_secondary_string;
						/* indicate bad sequence number of primary */
						rc_array[0] = -1;
					}
				}
			}
			/* getting different return codes for each copy */
		} else
			/* either primary or secondary copy is
			 * valid, so use the valid copy.
			 */
		if ((rc_array[0] == 0) || (rc_array[1] == 0)) {
			char *warn_name = NULL;

			/* indicate success */
			rc = 0;
			/* set variables based on which copy is valid */
			if (rc_array[0] == 0) {
				/* use primary (rear) copy if its good */
				fh = fh1;
				location_name = evms_primary_string;
				warn_name = evms_secondary_string;
			} else {
				/* use secondary (front) copy if its good */
				fh = fh2;
				location_name = evms_secondary_string;
				warn_name = evms_primary_string;
			}
			/* warn the user about the invalid copy */
			LOG_WARNING("warning: error(%d) probing/verifying the "
				    "%s feature header on '%s'.\n",
				    rc_array[0] + rc_array[1],
				    warn_name, node->name);
		} else
			/* both copies had a different error,
			 * and one was a fatal error, so
			 * indicate fatal error.
			 */
		if ((rc_array[0] == -EINVAL) || (rc_array[1] == -EINVAL)) {
			rc = -EINVAL;
		}

		/* on error, set fh to NULL */
		if (rc)
			fh = NULL;

		/* deallocate metadata buffers appropriately */
		if (fh != fh1)
			kfree(fh1);
		if (fh != fh2)
			kfree(fh2);

		/* save validated feature header pointer */
		if (!rc) {
			node->feature_header = fh;
			if (rc_array[0] != rc_array[1]) {
				LOG_DETAILS("using %s feature header on '%s'.\n",
					    location_name, node->name);
			}
		}

		/* if no signature found, adjust return code */
		if (rc == -ENODATA) {
			rc = 0;
			LOG_DEBUG("no feature header found on '%s'.\n",
				  node->name);
		}
	}
	return rc;
}

static int
edef_find_first_features(struct evms_logical_node **discover_list)
{
	int rc;
	struct evms_logical_node *node, *tmp_list_head;

	tmp_list_head = *discover_list;
	*discover_list = NULL;

	while (tmp_list_head) {
		struct evms_list_node **evms_node;

		node = tmp_list_head;
		evms_cs_remove_logical_node_from_list(&tmp_list_head, node);

		/* check for duplicate pointers
		 * search for the node in global list
		 */
		evms_node =
		    evms_cs_lookup_item_in_list(&evms_global_feature_node_list,
						node);
		/* already present? */
		if (*evms_node) {
			/* yes, already present */
			rc = -ENODATA;	/* dont process this node further */
			LOG_DETAILS("deleting duplicate reference to '%s'.\n",
				    node->name);
			/* forget this node */
			node = NULL;
		} else {
			/* load the feature header if present */
			rc = edef_load_feature_header(node);
			/* This node have a feature header ?
			 * it won't be if there is no header to load
			 * OR
			 * there was a fatal error attempting to read it.
			 */
			if (node->feature_header) {
				/* check for object flag */
				if (node->feature_header->flags &
				    EVMS_VOLUME_DATA_OBJECT) {
					LOG_DEFAULT("object detected, deleting "
						    "'%s'.\n", node->name);
					rc = -EINVAL;
				} else
					/* check for stop-data flag */
				if (node->feature_header->flags &
					    EVMS_VOLUME_DATA_STOP) {
					LOG_DEFAULT("stop data detected, "
						    "deleting '%s'.\n",
						    node->name);
					rc = -EINVAL;
				} else {
					/* we have a valid feature header.
					 * initialize appropriate node fields
					 * to indicate this.
					 */
					node->flags |= EVMS_VOLUME_FLAG;
					node->iflags |= EVMS_FEATURE_BOTTOM;
					node->volume_info =
					    kmalloc(sizeof
						    (struct evms_volume_info),
						    GFP_KERNEL);
					if (node->volume_info) {
						/* set up volume
						 * info struct
						 */
						memset(node->volume_info, 0,
						       sizeof
						       (struct
							evms_volume_info));
						node->volume_info->volume_sn =
						    node->feature_header->
						    volume_serial_number;
						node->volume_info->
						    volume_minor =
						    node->feature_header->
						    volume_system_id;
						strcpy(node->volume_info->
						       volume_name,
						       node->feature_header->
						       volume_name);
						/* register(add) node to
						 * the global list.
						 */
						rc = evms_cs_add_item_to_list
						    (&evms_global_feature_node_list,
						     node);
					} else {
						rc = -ENOMEM;
					}
				}
			}
		}
		/* if any errors, delete the node */
		if (rc) {
			if (node) {
				DELETE(node);
			}
		} else
			/* on successful processing of this node
			 * place it back on the discover list.
			 */
			evms_cs_add_logical_node_to_list(discover_list, node);
	}
	return 0;
}

/* These define describe the node types that can be isolated. */
#define ISOLATE_ASSOCIATIVE_FEATURES		0
#define ISOLATE_COMPATIBILITY_VOLUMES		1
#define ISOLATE_EVMS_VOLUMES			2
#define ISOLATE_EVMS_VOLUME_SERIAL_NUMBER	3
#define ISOLATE_EVMS_NODES_BY_FEATURE_AND_DEPTH	4
static int
edef_isolate_nodes_by_type(unsigned int type,
			   struct evms_logical_node **src_list,
			   struct evms_logical_node **trg_list,
			   u32 compare32, u64 compare64)
{
	struct evms_logical_node *node, *next_node;
	int rc = 0, found_node;
	struct evms_feature_header *fh = NULL;

	for (node = *src_list; node; node = next_node) {
		next_node = node->next;

		if (node->feature_header)
			fh = node->feature_header;
		found_node = FALSE;
		switch (type) {
		case ISOLATE_ASSOCIATIVE_FEATURES:
			if (fh) {
				if (GetPluginType(fh->feature_id) ==
				    EVMS_ASSOCIATIVE_FEATURE)
					found_node = TRUE;
			}
			break;
		case ISOLATE_COMPATIBILITY_VOLUMES:
			if (!(node->flags & EVMS_VOLUME_FLAG))
				found_node = TRUE;
			break;
		case ISOLATE_EVMS_VOLUMES:
			if (node->flags & EVMS_VOLUME_FLAG)
				found_node = TRUE;
			break;
			/* EVMS volumes with same serial # */
		case ISOLATE_EVMS_VOLUME_SERIAL_NUMBER:
			if (node->volume_info->volume_sn == compare64)
				found_node = TRUE;
			break;
		case ISOLATE_EVMS_NODES_BY_FEATURE_AND_DEPTH:
			if (fh)
				if (fh->object_depth == compare64)
					if (fh->feature_id == compare32)
						found_node = TRUE;
			break;
		}
		if (found_node == TRUE) {
			rc = evms_cs_remove_logical_node_from_list(src_list,
								   node);
			if (rc)
				break;
			rc = evms_cs_add_logical_node_to_list(trg_list, node);
			if (rc)
				break;
		}
	}
	return rc;
}

static int
edef_apply_feature(struct evms_logical_node *node,
		   struct evms_logical_node **volume_node_list)
{
	struct evms_registered_plugin *p;
	int rc = -1;

	for (p = registered_plugin_head; p; p = p->next) {
		if (p->plugin->id == node->feature_header->feature_id) {
			rc = DISCOVER(p, volume_node_list);
			break;
		}
	}
	return rc;
}

static int
edef_get_feature_plugin_header(u32 id, struct evms_plugin_header **header)
{
	int rc = -ENOPKG;
	struct evms_registered_plugin *p;

	for (p = registered_plugin_head; p; p = p->next) {
		if (p->plugin->id == id) {
			*header = p->plugin;
			rc = 0;
			break;
		}
	}
	if (rc) {
		LOG_SERIOUS("no plugin loaded for feature id(0x%x)\n", id);
	}
	return rc;
}

typedef struct evms_volume_build_info_s {
	int node_count;
	int feature_header_count;
	int feature_count;
	int associative_feature_count;
	u64 max_depth;
	struct evms_plugin_header *plugin;
	struct evms_logical_node *feature_node_list;
} evms_volume_build_info_t;

/*
 * edef_evaluate_volume_node_list:
 *   does:
 *	1) put all nodes from feature list back on volume list
 *      2) loads the node's feature headers
 *      3) counts the node list's entries
 *      4) builds the feature node list
 *	5) counts the feature headers for associative features
 *	6) sets feature count to >1 if >1 features to be processed
 */
static int
edef_evaluate_volume_node_list(struct evms_logical_node **volume_node_list,
			       evms_volume_build_info_t * vbi,
			       int volume_complete)
{
	int rc;
	struct evms_logical_node *node;

	vbi->node_count =
	    vbi->feature_count =
	    vbi->associative_feature_count = vbi->max_depth = 0;
	vbi->plugin = NULL;

	/* put all feature nodes back on the volume list */
	rc = edef_isolate_nodes_by_type(ISOLATE_EVMS_VOLUMES,
					&vbi->feature_node_list,
					volume_node_list, 0, 0);
	if (rc)
		return rc;

	/* load all the feature headers */
	if (!volume_complete) {
		for (node = *volume_node_list; node; node = node->next) {
			rc = edef_load_feature_header(node);
			if (rc)
				return rc;
		}
	}

	/* find the 1st max depth object:
	 *   record the depth
	 *   record the plugin
	 */
	for (node = *volume_node_list; node; node = node->next) {
		struct evms_plugin_header *plugin;
		struct evms_feature_header *fh = node->feature_header;

		/* count the nodes */
		vbi->node_count++;

		/* no feature header found, continue to next node */
		if (!fh)
			continue;

		/* check the depth */
		if (fh->object_depth > vbi->max_depth) {
			/* record new max depth */
			vbi->max_depth = fh->object_depth;
			/* find the plugin header for this feature id */
			rc = edef_get_feature_plugin_header(fh->feature_id,
							    &plugin);
			if (rc)
				return rc;
			/* check for >1 plugins */
			if (vbi->plugin != plugin) {
				vbi->feature_count++;
				vbi->plugin = plugin;
			}
		}
		/* check for "associative" feature indicator */
		if (GetPluginType(vbi->plugin->id) == EVMS_ASSOCIATIVE_FEATURE)
			vbi->associative_feature_count++;
	}
	/* build a list of max depth nodes for this feature */
	if (vbi->max_depth) {
		rc = edef_isolate_nodes_by_type
		    (ISOLATE_EVMS_NODES_BY_FEATURE_AND_DEPTH, volume_node_list,
		     &vbi->feature_node_list, vbi->plugin->id, vbi->max_depth);
		if (rc)
			return rc;
		if (!vbi->plugin)
			return -ENODATA;
		if (!vbi->feature_node_list)
			return -ENODATA;
	}

	return rc;
}

/* function: edef_check_feature_conditions
 *
 * This routine verifies the state of volume based on the features
 * headers and nodes in the current discovery list. All detected
 * errors are considered fatal.
 */
static int
edef_check_feature_conditions(evms_volume_build_info_t * vbi)
{
	int rc = 0;

	if (vbi->associative_feature_count) {
		if (vbi->node_count > 1) {
			rc = -EVMS_VOLUME_FATAL_ERROR;
			LOG_ERROR("associative ERROR: > 1 nodes(%d) remaining "
				  "to be processed!\n", vbi->node_count);
		} else if (vbi->max_depth != 1) {
			rc = -EVMS_VOLUME_FATAL_ERROR;
			LOG_ERROR("associative ERROR: associative feature "
				  "found at node depth("PFU64") != 1!\n",
				  vbi->max_depth);
		} else
			rc = -EVMS_ASSOCIATIVE_FEATURE;
	}
	if (!rc) {
		if (!vbi->max_depth) {
			if (vbi->node_count > 1) {
				rc = -EVMS_VOLUME_FATAL_ERROR;
				LOG_ERROR("max depth ERROR: > 1 nodes(%d) "
					  "remaining to be processed!\n",
					  vbi->node_count);
			}
		} else if (vbi->max_depth == 1) {
			if (vbi->feature_count > 1) {
				rc = -EVMS_VOLUME_FATAL_ERROR;
				LOG_ERROR("max depth 1 ERROR: > 1 features "
					  "remaining to be processed!\n");
			}
		}
	}
	return rc;
}

/* function: edef_apply_features
 *
 * This routine applies none, one, or more features to an EVMS
 * volume. The system data structure is first verified and then
 * features are applied and verified recursively until the
 * entire volume has been constructed. Fatal errors result in
 * all nodes in the volume discovery list being deleted.
 */
static int
edef_apply_features(struct evms_logical_node **volume_node_list)
{
	int rc = 1, done, top_feature_applying;
	evms_volume_build_info_t vbi;

	vbi.feature_node_list = NULL;
	rc = edef_evaluate_volume_node_list(volume_node_list, &vbi, FALSE);

	/* ensure we don't go into the next loop
	 * without having a target plugin to
	 * pass control to.
	 */
	if (!rc) {
		if (!vbi.plugin) {
			rc = -ENODATA;
		}
	}

	/* this loop should ONLY get used when
	 * there are features to process.
	 */
	done = (rc) ? TRUE : FALSE;
	while (!done) {
		rc = edef_check_feature_conditions(&vbi);
		if (rc)
			break;
		top_feature_applying = (vbi.max_depth == 1) ? TRUE : FALSE;
		rc = vbi.plugin->fops->discover(&vbi.feature_node_list);
		if (!rc) {
			rc = edef_evaluate_volume_node_list(volume_node_list,
							    &vbi,
							    top_feature_applying);
			if (top_feature_applying == TRUE) {
				if (vbi.node_count > 1) {
					rc = -EVMS_VOLUME_FATAL_ERROR;
					LOG_ERROR("ERROR: detected > 1 node at "
						  "volume completion!\n");
				}
				done = TRUE;
			} else {
				if (!vbi.plugin) {
					rc = -EVMS_VOLUME_FATAL_ERROR;
					LOG_ERROR("ERROR: depth("PFU64"): "
						  "expected another feature!\n",
						  vbi.max_depth);
					done = TRUE;
				}
			}
		} else {	/* rc != 0 */
			rc = -EVMS_VOLUME_FATAL_ERROR;
			done = TRUE;
		}
	}
	if (rc)
		/* put all feature nodes back on the volume list */
		if (edef_isolate_nodes_by_type(ISOLATE_EVMS_VOLUMES,
					       &vbi.feature_node_list,
					       volume_node_list, 0, 0))
			BUG();
	return rc;
}

static int
edef_delete_node(struct evms_logical_node **node_list,
		 struct evms_logical_node *node, int return_code,
		 char *log_text)
{
	int rc;

	rc = evms_cs_remove_logical_node_from_list(node_list, node);
	if (!rc) {
		LOG_ERROR("%s error(%d): deleting volume(%s), node(%s)\n",
			  log_text, return_code,
			  node->volume_info->volume_name, node->name);
		rc = DELETE(node);
		if (rc) {
			LOG_ERROR("error(%d) while deleting node(%s)\n",
				  rc, node->name);
		}
	} else {
		LOG_WARNING("%s error(%d): node gone, assumed deleted by "
			    "plugin.\n", log_text, return_code);
		/* plugin must have cleaned up the node.
		 * So just reset the return code and leave.
		 */
		rc = 0;
	}

	return rc;
}

static int
edef_process_evms_volumes(struct evms_logical_node **discover_list,
			  struct evms_logical_node **associative_feature_list)
{
	int rc = 0;
	struct evms_logical_node *node, *evms_volumes_list, *volume_node_list;
	u64 volume_sn;

	/* put all EVMS volumes on their own list */
	evms_volumes_list = NULL;
	rc = edef_isolate_nodes_by_type(ISOLATE_EVMS_VOLUMES,
					discover_list,
					&evms_volumes_list, 0, 0);

	/* apply features to each EVMS volume */
	/* one volume at a time on each pass  */
	while (evms_volumes_list) {
		node = evms_volumes_list;
		/* put all nodes for one EVMS volume on separate list */
		volume_node_list = NULL;
		volume_sn = node->volume_info->volume_sn;
		rc = edef_isolate_nodes_by_type
		    (ISOLATE_EVMS_VOLUME_SERIAL_NUMBER, &evms_volumes_list,
		     &volume_node_list, 0, volume_sn);
		if (rc)
			break;
		/* go apply all the volume features now */
		rc = edef_apply_features(&volume_node_list);
		switch (rc) {
		case 0:	/* SUCCESS */
			/* remove volume just processed */
			node = volume_node_list;
			rc = evms_cs_remove_logical_node_from_list
			    (&volume_node_list, node);
			if (rc)
				break;
			/* put volume on global list */
			rc = evms_cs_add_logical_node_to_list(discover_list,
							      node);
			break;
		case -EVMS_ASSOCIATIVE_FEATURE:
			/* put all "associative" features on their own list */
			rc = edef_isolate_nodes_by_type
			    (ISOLATE_ASSOCIATIVE_FEATURES, &volume_node_list,
			     associative_feature_list, 0, 0);
			break;
		default:	/* FATAL ERROR */
			/* delete each node remaining in the list */
			if (volume_node_list) {
				LOG_ERROR("encountered fatal error building volume '%s'\n",
					  volume_node_list->volume_info->volume_name);
			}
			while (volume_node_list) {
				node = volume_node_list;
				edef_delete_node(&volume_node_list,
						 node, rc, "EVMS feature");
			}
			rc = 0;
			break;
		}
		if (rc)
			break;
	}
	return rc;
}

static int
edef_process_associative_volumes(struct evms_logical_node
				 **associative_feature_list,
				 struct evms_logical_node **discover_list)
{
	int rc = 0;
	struct evms_logical_node *node;

	while (*associative_feature_list) {
		node = *associative_feature_list;
		/* remove this node from associative feature list */
		rc = evms_cs_remove_logical_node_from_list
		    (associative_feature_list, node);
		if (rc)
			break;
		/* put volume on global list */
		rc = evms_cs_add_logical_node_to_list(discover_list, node);
		if (rc)
			break;
		rc = edef_load_feature_header(node);
		if (rc)
			break;
		rc = edef_apply_feature(node, discover_list);
		if (rc)
			edef_delete_node(discover_list, node, rc,
					 "Associative feature");
	}
	return rc;
}

static int
edef_check_for_incomplete_volumes(struct evms_logical_node **discover_list)
{
	int rc = 0;
	struct evms_logical_node *next_node, *node;

	/* check to see if any incomplete volumes are left around */
	/* if so, delete them.                                    */
	/* complete volumes should not have feature_headers       */
	/* hanging off them, if we find any, we know the volume   */
	/* is incomplete.                                         */

	for (node = *discover_list; node; node = next_node) {
		next_node = node->next;

		if (node->feature_header) {
			edef_delete_node(discover_list, node, rc,
					 "Unexpected feature header");
		}
	}
	return rc;
}

/*
 * Function:     evms_discover_evms_features
 * Description: Find features for nodes on the logical partitions list
 */
static int
evms_discover_evms_features(struct evms_logical_node **discover_list)
{
	struct evms_logical_node *associative_feature_list;
	int rc = 0;

	LOG_EXTRA("discovering evms volume features...\n");

	/* initialize "associative" features list */
	associative_feature_list = NULL;

	/* find the bottom features */
	rc = edef_find_first_features(discover_list);
#ifdef LOCAL_DEBUG
	display_discover_list(*discover_list, "after 1st features hdr");
#endif
	if (!rc)
		/* process EVMS volumes here */
		rc = edef_process_evms_volumes(discover_list,
					       &associative_feature_list);
#ifdef LOCAL_DEBUG
	display_discover_list(*discover_list, "after evms volumes");
#endif
	if (!rc)
		/* process "associative" features here */
		rc = edef_process_associative_volumes(&associative_feature_list,
						      discover_list);
#ifdef LOCAL_DEBUG
	display_discover_list(*discover_list, "after associatives");
#endif
	if (!rc)
		/* check for incomplete volumes */
		rc = edef_check_for_incomplete_volumes(discover_list);

	return rc;
}

/*
 * function: eelv_assign_volume_minor
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine assigns a specific minor number to a volume. It
 * also performs the remaining steps to make this volume visible
 * and usable to the kernel.
 *
 */
static int
eelv_assign_volume_minor(struct evms_logical_node *node, int minor)
{
	struct evms_logical_volume *volume;

	/* initialize the logical_node entry in the volume array */
	volume = &evms_logical_volumes[minor];
	volume->name = kmalloc(strlen(EVMS_GET_NODE_NAME(node)) + 1, GFP_KERNEL);
	if (!volume->name) {
		return -ENOMEM;
	}
	strcpy(volume->name, EVMS_GET_NODE_NAME(node));
	volume->node = node;

	/* copy flags from top level node into volume structure */
	volume->flags = node->flags;

	/* check for read-only volume */
	if (volume->flags & EVMS_VOLUME_READ_ONLY) {
		set_device_ro(MKDEV(EVMS_MAJOR, minor), 1);
	}

	/* adjust volume size based on hardsector size */
	node->total_vsectors &= ~((node->hardsector_size >>
				   EVMS_VSECTOR_SIZE_SHIFT) - 1);

	/* initialize the global device arrays */
	blksize_size[EVMS_MAJOR][minor] = node->block_size;
	hardsect_size[EVMS_MAJOR][minor] = node->hardsector_size;
	blk_size[EVMS_MAJOR][minor] = (int) (node->total_vsectors >> 1);

	/* register this volume with devfs */
	volume->devfs_handle = devfs_register(evms_dir_devfs_handle,
					      volume->name, DEVFS_FL_DEFAULT,
					      EVMS_MAJOR, minor,
					      S_IFBLK|S_IRUGO|S_IWUGO,
					      &evms_fops, NULL);

	evms_volumes++;

	LOG_DEFAULT("Exporting EVMS Volume(%u,%u) from \"%s%s\".\n",
		    EVMS_MAJOR, minor, EVMS_DEV_NODE_PATH, volume->name);
	return 0;
}

/*
 * function: eelv_check_for_duplicity
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine compares the serial number in the top most node
 * in the volume to the list of currently exported volumes. If
 * this volumes serial number is found in the list then we know
 * this volume is a duplicate and it is then delete.
 *
 */
static void
eelv_check_for_duplicity(struct evms_logical_node **discover_list)
{
	struct evms_logical_node *next_node, *node;
	struct evms_logical_volume *lv;
	int i, is_dup;

	for (node = *discover_list; node; node = next_node) {
		next_node = node->next;

		is_dup = FALSE;
		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			lv = &evms_logical_volumes[i];
			/* only check exported volumes */
			if (lv->node) {
				char *type_ptr = NULL;

				/* check for duplicate pointer */
				if (node == lv->node) {
					is_dup = TRUE;
					type_ptr = "pointer";
					/* check for duplicate node */
				} else if (!strcmp(node->name, lv->node->name)) {
					is_dup = TRUE;
					type_ptr = "node";
				}
				if (is_dup == TRUE) {
					evms_cs_remove_logical_node_from_list
					    (discover_list, node);
					LOG_DETAILS("deleting duplicate %s to "
						    "EVMS volume(%u,%u,%s)...\n",
						    type_ptr, EVMS_MAJOR, i,
						    EVMS_GET_NODE_NAME(node));
					/* forget duplicate */
					break;
				}
			}
		}
	}
}

/*
 * function: eelv_reassign_soft_deleted_volume_minors
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine reassigns minor numbers to rediscovered "soft"
 * deleted volumes.
 *
 */
static void
eelv_reassign_soft_deleted_volume_minors(struct evms_logical_node
					 **discover_list)
{
	struct evms_logical_node *next_node, *node;
	struct evms_logical_volume *lv;
	int rc, i, flags;
	char *name;

	for (node = *discover_list; node; node = next_node) {
		next_node = node->next;

		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			lv = &evms_logical_volumes[i];
			/* only check soft deleted volumes:
			 *  they have a non-NULL name.
			 */
			if (lv->flags & EVMS_VOLUME_SOFT_DELETED) {
				if (!strcmp(EVMS_GET_NODE_NAME(node), lv->name)) {
					/* reassign requested minor */
					evms_cs_remove_logical_node_from_list(discover_list,
									      node);
					LOG_DEFAULT("Re");
					/* free the previously used name */
					name = lv->name;
					lv->name = NULL;
					/* clear the EVMS_VOLUME_SOFT_DELETED flag */
					flags = lv->flags;
					lv->flags = 0;
					rc = eelv_assign_volume_minor(node, i);
					if (rc) {
						DELETE(node);
						lv->node = NULL;
						lv->name = name;
						lv->flags = flags;
					} else {
						kfree(name);
					}
					break;
				}
			}
		}
	}
}

/*
 * function: eelv_assign_evms_volume_minors
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine assigns minor numbers to new evms volumes. If
 * the specified minor is already in use, the requested minor
 * is set to 0, and will be assigned next available along with
 * any remaining volumes at the end of evms_export_logical_volumes.
 *
 */
static void
eelv_assign_evms_volume_minors(struct evms_logical_node **discover_list)
{
	struct evms_logical_node *next_node, *node, *lv_node;
	unsigned int requested_minor;
	int rc, lv_flags;

	for (node = *discover_list; node; node = next_node) {
		next_node = node->next;

		/* only process evms volumes */
		if (!(node->flags & EVMS_VOLUME_FLAG)) {
			continue;
		}

		/* is there a requested minor? */
		requested_minor = node->volume_info->volume_minor;
		if (!requested_minor) {
			continue;
		}

		lv_flags = 0;
		lv_node = NULL;

		/* check range of requested minor */
		if (requested_minor >= MAX_EVMS_VOLUMES) {
			lv_node = node;
		} else {
			struct evms_logical_volume *lv;
			lv = &evms_logical_volumes[requested_minor];
			lv_node = lv->node;
			lv_flags = lv->flags;
		}

		if (!lv_node && !(lv_flags & EVMS_VOLUME_SOFT_DELETED)) {
			/* assign requested minor */
			evms_cs_remove_logical_node_from_list(discover_list,
							      node);
			rc = eelv_assign_volume_minor(node, requested_minor);
			if (rc) {
				DELETE(node);
			}
		} else {
			LOG_WARNING("EVMS volume(%s) requesting invalid/in-use "
				    "minor(%d), assigning next available!\n",
				    node->volume_info->volume_name,
				    requested_minor);
			/* requested minor is already
			 * in use, defer assignment
			 * until later.
			 */
			node->volume_info->volume_minor = 0;
		}
	}
}

/*
 * function: eelv_assign_remaining_evms_volume_minors
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine assigns minor numbers to new evms volumes that
 * have no/conflicting minor assignments. This function will
 * search from high(255) minor values down, for the first available
 * minor. Searching high to low minimizes the possibility of
 * conflicting evms volumes causing "compatibility" minor
 * assignments to shift from expected assignments.
 *
 */
static void
eelv_assign_remaining_evms_volume_minors(struct evms_logical_node
					 **discover_list)
{
	struct evms_logical_node *next_node, *node;
	int requested_minor, rc;

	for (node = *discover_list; node; node = next_node) {
		next_node = node->next;

		/* only process evms volumes */
		/* all remaining evms volumes should now
		 * have a minor value of 0, meaning they
		 * had no minor assignment, or their minor
		 * assignment conflicted with an existing
		 * minor assignment.
		 */
		if (node->flags & EVMS_VOLUME_FLAG) {
			evms_cs_remove_logical_node_from_list(discover_list,
							      node);
			/* find next available minor number */
			for (requested_minor = 255;
			     requested_minor > 0 &&
			     (evms_logical_volumes[requested_minor].node ||
			      evms_logical_volumes[requested_minor].name);
			     requested_minor--) ;
			/* check range of assigned minor */
			if (!requested_minor) {
				LOG_CRITICAL("no more minor numbers available "
					     "for evms volumes!!!!\n");
				DELETE(node);
			} else {
				/* assign requested minor */
				rc = eelv_assign_volume_minor(node, requested_minor);
				if (rc) {
					DELETE(node);
				}
			}
		}
	}
}

/*
 * function: eelv_assign_remaining_volume_minors
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine assigns minor numbers to all remaining unassigned
 * volumes. Minor numbers are assigned on an availability
 * basis. The first free minor number is used in the assignment.
 *
 */
static void
eelv_assign_remaining_volume_minors(struct evms_logical_node **discover_list)
{
	struct evms_logical_node *node;
	int minor, rc;

	while (*discover_list) {
		node = *discover_list;
		evms_cs_remove_logical_node_from_list(discover_list, node);

		/* find next available minor number */
		for (minor = 1;
		     minor < MAX_EVMS_VOLUMES &&
		     (evms_logical_volumes[minor].node ||
		      evms_logical_volumes[minor].name);
		     minor++) ;

		if (minor >= MAX_EVMS_VOLUMES) {
			LOG_CRITICAL("no more minor numbers available for "
				     "compatibility volumes!!!!\n");
			DELETE(node);
		} else {
			/* assign minor */
			rc = eelv_assign_volume_minor(node, minor);
			if (rc) {
				DELETE(node);
			}
		}
	}
}

/*
 * function: eelv_check_for_unreassign_soft_deleted_volume
 *
 * This is a support function for evms_export_logical_volumes.
 * This routine reports any "soft deleted" volumes that were not
 * found after a rediscovery.
 */
static void
eelv_check_for_unreassign_soft_deleted_volume(void)
{
	struct evms_logical_volume *lv;
	int i;

	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		lv = &evms_logical_volumes[i];
		/* only check soft deleted volumes:
		 *  they have a NULL node ptr &
		 *  they have a non-NULL name.
		 */
		if (lv->flags & EVMS_VOLUME_SOFT_DELETED) {
			if (is_open(i))
				lv->flags |= EVMS_VOLUME_CORRUPT;
			LOG_ERROR("error: rediscovery failed to find %smounted "
				  "'soft deleted' volume(%u,%u,%s)...\n",
				  ((lv->flags & EVMS_VOLUME_CORRUPT) ? "" : "un"),
				  EVMS_MAJOR, i, lv->name);
			if (lv->flags & EVMS_VOLUME_CORRUPT) {
				LOG_ERROR("    flagging volume(%u,%u,%s) as "
					  "CORRUPT!\n", EVMS_MAJOR, i, lv->name);
			} else {
				LOG_ERROR("    releasing minor(%d) used by "
					  "volume(%s)!\n", i, lv->name);
				/* clear logical volume structure
				 * for this volume so it may be
				 * reused.
				 */
				kfree(lv->name);
				lv->name = NULL;
				lv->flags = 0;
			}
		}
	}
}

static void
eelv_unquiesce_volumes(void)
{
	int i;

	/* check each volume array entry */
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		struct evms_logical_volume *volume;

		volume = &evms_logical_volumes[i];
		/* is this volume "quiesced" ? */
		if (volume->quiesced) {
			int rc = 1;
			if (volume->node) {
				/* "unquiesce" it */
				struct inode inode;
				struct evms_quiesce_vol_pkt qv;

				qv.command = qv.status = 0;
				qv.do_vfs = 0;
				qv.minor = i;
				rc = evms_quiesce_volume(volume, &inode, NULL,
							 &qv);
			}
			/* Wake up any waiters */
			if (rc) {
				/* clear the flag */
				volume->quiesced = 0;
				/* wake up the waiters */
				if (waitqueue_active(&volume->wait_queue))
					wake_up(&volume->wait_queue);
#ifdef VFS_PATCH_PRESENT
				/* unquiesce VFS if quiesced */
				if (volume->vfs_quiesced) {
					/* VFS function call to unlock the filesystem */
					unlockfs(MKDEV(EVMS_MAJOR, i));
					volume->vfs_quiesced = FALSE;
				}
#endif
			}
		}
	}
}

/*
 * Function:     evms_export_logical_volumes
 *
 * This function is called from evms_discover_volumes. It
 * check for duplicate volumes, assigns minor values to evms
 * volumes, and assigns minor values to the remaining volumes.
 * In addition to assigning minor values to each volume this
 * function also completes the final steps necessary to allow
 * the volumes to be using by the operating system.
 */
static void
evms_export_logical_volumes(struct evms_logical_node **discover_list)
{
	LOG_EXTRA("exporting EVMS logical volumes...\n");

	eelv_check_for_duplicity(discover_list);

	eelv_reassign_soft_deleted_volume_minors(discover_list);

	eelv_assign_evms_volume_minors(discover_list);

	eelv_assign_remaining_evms_volume_minors(discover_list);

	eelv_assign_remaining_volume_minors(discover_list);

	eelv_check_for_unreassign_soft_deleted_volume();

	/* "unquiesce" any "quiesced" volumes */
	eelv_unquiesce_volumes();
}

static int
edv_populate_discover_list(struct evms_list_node *src_list,
			   struct evms_logical_node **trg_list,
			   struct evms_rediscover_pkt *discover_parms)
{
	int rc = 0, i, move_node, use_all_disks = FALSE;
	struct evms_list_node *src_node;
	struct evms_logical_node *disk_node = NULL;

	/* if no discover parameters are specified */
	/* copy ALL the disk nodes into the        */
	/* discovery list.                         */
	if ((discover_parms == NULL) ||
	    (discover_parms->drive_count == REDISCOVER_ALL_DEVICES))
		use_all_disks = TRUE;

	if (!use_all_disks) {
		LOG_DEBUG("discovering %d disks:\n", discover_parms->drive_count);
		for (i = 0; i < discover_parms->drive_count; i++) {
			disk_node = DEV_HANDLE_TO_NODE(discover_parms->drive_array[i]);
			LOG_DEBUG("   disk %d: %s\n", i, disk_node->name);
		}
	}

	/* copy the disk nodes specified in the */
	/* discover_parms over to a discover list */
	src_node = src_list;
	while (src_node) {
		move_node = use_all_disks;
		if (move_node == FALSE) {
			/* check the rediscovery array */
			for (i = 0; i < discover_parms->drive_count; i++) {
				disk_node =
				    DEV_HANDLE_TO_NODE(discover_parms->
						       drive_array[i]);
				if (disk_node == src_node->item) {
					move_node = TRUE;
					break;
				}
			}
		}
		/* check to see if we want this node */
		if (move_node == TRUE)
			evms_cs_add_logical_node_to_list(trg_list,
							 (struct
							  evms_logical_node *)
							 src_node->item);
		/* advance to next struct evms_list_node */
		src_node = src_node->next;
	}
	return rc;
}

static int
evms_discover_volumes(struct evms_rediscover_pkt *discover_parms)
{
	int rc = 0;
	struct evms_logical_node *discover_list = NULL;

	evms_discover_logical_disks(&discover_list);
	if (evms_global_device_list) {
		/* move the appropriate disk nodes, based on */
		/* on the discover parameters, onto the      */
		/* discover list for the partition managers  */
		/* to process                                */
		edv_populate_discover_list(evms_global_device_list,
					   &discover_list, discover_parms);
	}
	if (discover_list) {
#ifdef LOCAL_DEBUG
		display_discover_list(discover_list, "after dev mgrs");
#endif
		evms_discover_logical_partitions(&discover_list);
	}
	if (discover_list) {
#ifdef LOCAL_DEBUG
		display_discover_list(discover_list, "after seg mgrs");
#endif
		evms_discover_volume_groups(&discover_list);
	}
	if (discover_list) {
#ifdef LOCAL_DEBUG
		display_discover_list(discover_list, "after reg mgrs");
#endif
		evms_discover_evms_features(&discover_list);
	}
	if (discover_list) {
#ifdef LOCAL_DEBUG
		display_discover_list(discover_list, "after features");
#endif
		evms_export_logical_volumes(&discover_list);
		evms_cs_signal_event(EVMS_EVENT_END_OF_DISCOVERY);
	}
	return rc;
}

/* function: evms_notify_reboot
 *
 * this function gets called at shutdown time and is used
 * to remove any evms controlled volumes from memory, thus
 * allowing any plugins needing to flush internal caches
 * to do so.
 */
int
evms_notify_reboot(struct notifier_block *this, unsigned long code, void *x)
{
	int i;
	struct evms_logical_volume *volume;

	switch (code) {
	case SYS_DOWN:
	case SYS_HALT:
	case SYS_POWER_OFF:
		LOG_DEFAULT("stopping all evms controlled volumes.\n");

		/* quiesce all volumes */
		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			struct evms_quiesce_vol_pkt qv;
			struct inode inode;

			volume = &evms_logical_volumes[i];
			if (!volume->node)
				continue;
			qv.command = 1;	// quiesce
			qv.minor = i;
			qv.status = 0;	// reset status
			qv.do_vfs = 0;
			evms_quiesce_volume(volume, &inode, NULL, &qv);
		}
		/* delete all volumes
		 *
		 * to ensure this work under the
		 * most circumstances, a "soft"
		 * delete will be done. this will
		 * handle the strange case of a
		 * volume still being mounted.
		 */
		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			struct evms_delete_vol_pkt dv;

			volume = &evms_logical_volumes[i];
			if (!volume->node)
				continue;
			/* only delete quiesced volumes */
			if (!volume->quiesced)
				continue;
			/* delete the volume from memory.
			 * do a 'soft' delete if volume
			 * is mounted, and 'hard' delete
			 * if it is not.
			 */
			dv.command = (is_open(i)) ? 0 : 1;
			dv.minor = i;
			dv.status = 0;
			evms_delete_volume(volume, &dv);
		}
	}
	return NOTIFY_DONE;
}

static struct notifier_block evms_notifier = {
	.notifier_call	= evms_notify_reboot,
	.next		= NULL,
	.priority	= INT_MAX,	/* before any real devices */
};

/*
 * Function: find_root_fs_dev
 * If "root=/dev/evms/???" was specified on the kernel command line, and devfs
 * is not enabled, we need to determine the appropriate minor number for the
 * specified volume for the root fs.
 */
static void
find_root_fs_dev(void)
{
#ifndef MODULE
	char root_name[64] = { 0 };
	char *name;
	int i;

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,18)
	strncpy(root_name, root_device_name, 63);
#else
	get_root_device_name(root_name);
#endif

	if (!strncmp(root_name, EVMS_DIR_NAME "/", strlen(EVMS_DIR_NAME) + 1)) {
		name = &root_name[strlen(EVMS_DIR_NAME) + 1];

		for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
			if (evms_logical_volumes[i].name &&
			    !strncmp(name, evms_logical_volumes[i].name,
				     strlen(evms_logical_volumes[i].name))) {
				ROOT_DEV = MKDEV(EVMS_MAJOR, i);
				return;
			}
		}
	}
#endif
}

/*
 * Function: bh_cache_ctor
 * this function initializes the b_wait field in the buffer heads
 * in our private buffer head pool.
 */
static void
io_notify_cache_ctor(void *foo, kmem_cache_t * cachep, unsigned long flags)
{
	if ((flags & (SLAB_CTOR_VERIFY | SLAB_CTOR_CONSTRUCTOR)) ==
	    SLAB_CTOR_CONSTRUCTOR) {
		io_notify_t *io_notify = (io_notify_t *) foo;
		memset(io_notify, 0, sizeof (*io_notify));
	}
}

/*
 * Function: bh_cache_ctor
 * this function initializes the b_wait field in the buffer heads
 * in our private buffer head pool.
 */
static void
bh_cache_ctor(void *foo, kmem_cache_t * cachep, unsigned long flags)
{
	if ((flags & (SLAB_CTOR_VERIFY | SLAB_CTOR_CONSTRUCTOR)) ==
	    SLAB_CTOR_CONSTRUCTOR) {
		struct buffer_head *bh = (struct buffer_head *) foo;
		memset(bh, 0, sizeof (*bh));
		init_waitqueue_head(&bh->b_wait);
	}
}

/*
 * Function:  evms_init_module
 * This function runs once at system initialization.
 */
static int __init
evms_init_module(void)
{
	int rc = -ENOMEM, i;
	int *evms_blocksizes;

	LOG_DEFAULT("EVMS v%d.%d.%d initializing .... info level(%d).\n",
		    EVMS_MAJOR_VERSION, EVMS_MINOR_VERSION,
		    EVMS_PATCHLEVEL_VERSION, evms_info_level);

	/* Initialize memory management counters */
	atomic_set(&evms_allocs, 0);
	atomic_set(&evms_logical_nodes, 0);

	/* Initialize the io_notify_entry pool */
	evms_io_notify_pool = evms_cs_create_pool(sizeof (io_notify_t),
						  "EVMS IO Notify",
						  io_notify_cache_ctor, NULL);
	if (!evms_io_notify_pool) {
		LOG_CRITICAL("can't allocate memory for I/O Notify pool.\n");
		goto error;
	}

	/* Initialize the "public" buffer_head pool */
	evms_bh_pool = evms_cs_create_pool(sizeof (struct buffer_head),
					   "EVMS BH", bh_cache_ctor, NULL);
	if (!evms_bh_pool) {
		LOG_CRITICAL("can't allocate memory for BH pool.\n");
		goto error;
	}

	/* Allocate the EVMS blk_size array */
	evms_blocksizes = kmalloc(MAX_EVMS_VOLUMES * sizeof (int), GFP_KERNEL);
	if (!evms_blocksizes) {
		LOG_CRITICAL("can't allocate memory for blk_size array.\n");
		goto error;
	}
	memset(evms_blocksizes, 0, MAX_EVMS_VOLUMES * sizeof (int));
	blk_size[EVMS_MAJOR] = evms_blocksizes;

	/* Allocate the EVMS blksize_size array */
	evms_blocksizes = kmalloc(MAX_EVMS_VOLUMES * sizeof (int), GFP_KERNEL);
	if (!evms_blocksizes) {
		LOG_CRITICAL("can't allocate memory for blksize_size array.\n");
		goto error;
	}
	memset(evms_blocksizes, 0, MAX_EVMS_VOLUMES * sizeof (int));
	blksize_size[EVMS_MAJOR] = evms_blocksizes;

	/* Allocate the EVMS hardsect_size array */
	evms_blocksizes = kmalloc(MAX_EVMS_VOLUMES * sizeof (int), GFP_KERNEL);
	if (!evms_blocksizes) {
		LOG_CRITICAL("can't allocate memory for hardsect_size array.\n");
		goto error;
	}
	memset(evms_blocksizes, 0, MAX_EVMS_VOLUMES * sizeof (int));
	hardsect_size[EVMS_MAJOR] = evms_blocksizes;


	/* Allocate the logical volume array */
	evms_logical_volumes = kmalloc(sizeof (struct evms_logical_volume) *
				       MAX_EVMS_VOLUMES, GFP_KERNEL);
	if (!evms_logical_volumes) {
		LOG_CRITICAL("can't allocate memory for logical volume array.\n");
		goto error;
	}

	/* initialize the logical volume array entries */
	memset(evms_logical_volumes, 0,
	       sizeof (struct evms_logical_volume) * MAX_EVMS_VOLUMES);
	for (i = 1; i < MAX_EVMS_VOLUMES; i++) {
		struct evms_logical_volume *volume = &evms_logical_volumes[i];
		atomic_set(&volume->opens, 0);
		atomic_set(&volume->requests_in_progress, 0);
		init_waitqueue_head(&volume->wait_queue);
		blk_queue_make_request(&volume->request_queue, evms_make_request_fn);
		volume->request_queue.queuedata = volume;
	}


	/* Register the block device */
	rc = devfs_register_blkdev(EVMS_MAJOR, EVMS_DIR_NAME, &evms_fops);
	if (rc) {
		LOG_CRITICAL("error(%d) calling devfs_register_blkdev().\n", rc);
		goto error;
	}

	/* Register with devfs. A NULL return is not fatal. */
	evms_dir_devfs_handle = devfs_mk_dir(NULL, EVMS_DIR_NAME, NULL);
	if (evms_dir_devfs_handle) {
		evms_blk_devfs_handle = devfs_register(evms_dir_devfs_handle,
						       EVMS_DEV_NAME, DEVFS_FL_DEFAULT,
						       EVMS_MAJOR, 0,
						       S_IFBLK|S_IRUGO|S_IWUGO,
						       &evms_fops, NULL);
		if (!evms_blk_devfs_handle) {
			LOG_DETAILS("NULL return from devfs_register() for \"%s\"\n",
				    EVMS_DEV_NAME);
		}
	}

	read_ahead[EVMS_MAJOR] = 4096;
	blk_dev[EVMS_MAJOR].queue = evms_find_queue;
	blk_queue_make_request(BLK_DEFAULT_QUEUE(EVMS_MAJOR), evms_make_request_fn);

#ifdef CONFIG_PROC_FS
	evms_cs_get_evms_proc_dir();
	if (evms_proc_dir) {
		create_proc_read_entry("info", 0, evms_proc_dir,
				       evms_info_read_proc, NULL);
		create_proc_read_entry("plugins", 0, evms_proc_dir,
				       evms_plugins_read_proc, NULL);
		create_proc_read_entry("volumes", 0, evms_proc_dir,
				       evms_volumes_read_proc, NULL);
	}
	evms_table_header = register_sysctl_table(dev_dir_table, 1);
#endif

	/* Register for reboot notification */
	register_reboot_notifier(&evms_notifier);

#if defined(CONFIG_PPC64) || defined(CONFIG_SPARC64) || defined(CONFIG_X86_64)
	/* Register evms 32bit ioctl handlers. UGLY!!! */
	lock_kernel();
	register_ioctl32_conversion(EVMS_GET_IOCTL_VERSION,NULL);
	register_ioctl32_conversion(EVMS_GET_VERSION,NULL);
	register_ioctl32_conversion(EVMS_GET_INFO_LEVEL,NULL);
	register_ioctl32_conversion(EVMS_SET_INFO_LEVEL,NULL);
	register_ioctl32_conversion(EVMS_REDISCOVER_VOLUMES_32, evms_rediscover);
	register_ioctl32_conversion(EVMS_DELETE_VOLUME,NULL);
	register_ioctl32_conversion(EVMS_PLUGIN_IOCTL_32, evms_plugin_ioctl);
	register_ioctl32_conversion(EVMS_PROCESS_NOTIFY_EVENT,NULL);
	register_ioctl32_conversion(EVMS_GET_LOGICAL_DISK,NULL);
	register_ioctl32_conversion(EVMS_GET_LOGICAL_DISK_INFO,NULL);
	register_ioctl32_conversion(EVMS_SECTOR_IO_32, evms_sector_io);
	register_ioctl32_conversion(EVMS_GET_MINOR,NULL);
	register_ioctl32_conversion(EVMS_GET_VOLUME_DATA,NULL);
	register_ioctl32_conversion(EVMS_GET_PLUGIN,NULL);
	register_ioctl32_conversion(EVMS_COMPUTE_CSUM_32, evms_compute_csum);
	register_ioctl32_conversion(EVMS_GET_BMAP,NULL);
	register_ioctl32_conversion(EVMS_CHECK_MOUNT_STATUS,NULL);
	register_ioctl32_conversion(EVMS_CHECK_OPEN_STATUS,NULL);
	register_ioctl32_conversion(EVMS_GET_VOL_STRIPE_INFO,NULL);
	unlock_kernel();
#endif

done:
	return rc;

error:
	if (evms_logical_volumes) {
		kfree(evms_logical_volumes);
		evms_logical_volumes = NULL;
	}
	if (hardsect_size[EVMS_MAJOR]) {
		kfree(hardsect_size[EVMS_MAJOR]);
		hardsect_size[EVMS_MAJOR] = NULL;
	}
	if (blksize_size[EVMS_MAJOR]) {
		kfree(blksize_size[EVMS_MAJOR]);
		blksize_size[EVMS_MAJOR] = NULL;
	}
	if (blk_size[EVMS_MAJOR]) {
		kfree(blk_size[EVMS_MAJOR]);
		blk_size[EVMS_MAJOR] = NULL;
	}
	if (evms_bh_pool) {
		evms_cs_destroy_pool(evms_bh_pool);
		evms_bh_pool = NULL;
	}
	if (evms_io_notify_pool) {
		evms_cs_destroy_pool(evms_io_notify_pool);
		evms_io_notify_pool = NULL;
	}
	goto done;
}

/*
 * Function:  evms_exit_module
 * This function runs once when the EVMS core module is unloaded.
 */
static void __exit
evms_exit_module(void)
{
	LOG_DEFAULT("EVMS v%d.%d.%d unloading ....\n", EVMS_MAJOR_VERSION,
		    EVMS_MINOR_VERSION, EVMS_PATCHLEVEL_VERSION);

#if defined(CONFIG_PPC64) || defined(CONFIG_SPARC64) || defined(CONFIG_X86_64)
	/* Unregister evms 32bit ioctl handlers */
	lock_kernel();
	unregister_ioctl32_conversion(EVMS_GET_IOCTL_VERSION);
	unregister_ioctl32_conversion(EVMS_GET_VERSION);
	unregister_ioctl32_conversion(EVMS_GET_INFO_LEVEL);
	unregister_ioctl32_conversion(EVMS_SET_INFO_LEVEL);
	unregister_ioctl32_conversion(EVMS_REDISCOVER_VOLUMES_32);
	unregister_ioctl32_conversion(EVMS_DELETE_VOLUME);
	unregister_ioctl32_conversion(EVMS_PLUGIN_IOCTL_32);
	unregister_ioctl32_conversion(EVMS_PROCESS_NOTIFY_EVENT);
	unregister_ioctl32_conversion(EVMS_GET_LOGICAL_DISK);
	unregister_ioctl32_conversion(EVMS_GET_LOGICAL_DISK_INFO);
	unregister_ioctl32_conversion(EVMS_SECTOR_IO_32);
	unregister_ioctl32_conversion(EVMS_GET_MINOR);
	unregister_ioctl32_conversion(EVMS_GET_VOLUME_DATA);
	unregister_ioctl32_conversion(EVMS_GET_PLUGIN);
	unregister_ioctl32_conversion(EVMS_COMPUTE_CSUM_32);
	unregister_ioctl32_conversion(EVMS_GET_BMAP);
	unregister_ioctl32_conversion(EVMS_CHECK_MOUNT_STATUS);
	unregister_ioctl32_conversion(EVMS_CHECK_OPEN_STATUS);
	unregister_ioctl32_conversion(EVMS_GET_VOL_STRIPE_INFO);
	unlock_kernel();
#endif

	unregister_reboot_notifier(&evms_notifier);

#ifdef CONFIG_PROC_FS
	unregister_sysctl_table(evms_table_header);
	if (evms_proc_dir) {
		remove_proc_entry("volumes", evms_proc_dir);
		remove_proc_entry("plugins", evms_proc_dir);
		remove_proc_entry("info", evms_proc_dir);
		remove_proc_entry("evms", NULL);
	}
#endif

	/* Clean up the default queue. */
	blk_dev[EVMS_MAJOR].queue = NULL;
	blk_queue_make_request(BLK_DEFAULT_QUEUE(EVMS_MAJOR), NULL);

	/* Unregister with devfs */
	devfs_unregister(evms_dir_devfs_handle);

	/* Unregister the block device */
	devfs_unregister_blkdev(EVMS_MAJOR, EVMS_DIR_NAME);

	/* Deallocate logical volumes array */
	kfree(evms_logical_volumes);

	/* Deallocate device arrays */
	kfree(blk_size[EVMS_MAJOR]);
	kfree(blksize_size[EVMS_MAJOR]);
	kfree(hardsect_size[EVMS_MAJOR]);
	blk_size[EVMS_MAJOR] = NULL;
	blksize_size[EVMS_MAJOR] = NULL;
	hardsect_size[EVMS_MAJOR] = NULL;
	read_ahead[EVMS_MAJOR] = 0;

	/* Destroy buffer head pool */
	evms_cs_destroy_pool(evms_bh_pool);

	/* Destroy io notify pool */
	evms_cs_destroy_pool(evms_io_notify_pool);
}

/*
 * Function: evms_init_discover
 * If EVMS is statically built into the kernel, this function will be called
 * to perform an initial volume discovery.
 */
int __init
evms_init_discover(void)
{
	/* go find volumes */
	evms_discover_volumes(NULL);

	/* Check if the root fs is on EVMS */
	if (MAJOR(ROOT_DEV) == EVMS_MAJOR) {
		find_root_fs_dev();
	}

	return 0;
}

static int __init
evms_boot_info_level(char *str)
{
	int evms_boot_info_level = (int) simple_strtoul(str, NULL, 10);
	if (evms_boot_info_level) {
		evms_info_level = evms_boot_info_level;
	}
	return 1;
}

__setup("evms_info_level=", evms_boot_info_level);
module_init(evms_init_module);
module_exit(evms_exit_module);
__initcall(evms_init_discover);
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

