/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <limits.h>
#include <libdiskmgt.h>
#include <libintl.h>

#include <meta.h>

#define	_LAYOUT_DISCOVERY_C

#include "volume_dlist.h"
#include "volume_error.h"
#include "volume_nvpair.h"
#include "volume_output.h"

#include "layout_device_cache.h"
#include "layout_device_util.h"
#include "layout_dlist_util.h"
#include "layout_discovery.h"
#include "layout_request.h"
#include "layout_slice.h"
#include "layout_svm_util.h"

/*
 * lists of device dm_descriptor_t handles discovered during
 * the initial system probe.  Lists are populated by
 * discover_known_devices.
 *
 * "bad" slices are those that are known to libdiskmgt but
 * cannot be accessed. An example would be a slice that has
 * disappeared due to disk re-slicing: libdiskmgt may have a
 * cached handle for it, but the slice no longer exists.
 *
 * "bad" disks are thoese that are known to libdiskmgt but
 * cannot be accessed.  An example would be a disk that has
 * failed or has gone offline: libdiskmgt may have a cached
 * handle for it, but the disk does not respond.
 */
static dlist_t	*_bad_slices = NULL;
static dlist_t	*_bad_disks = NULL;

static dlist_t	*_known_slices = NULL;
static dlist_t	*_known_disks = NULL;
static dlist_t	*_known_hbas = NULL;

/*
 * helper functions for building known device lists, used by
 * discover_known_devices.
 */
static int generate_known_slices(dlist_t *disks, dlist_t **known,
	dlist_t **bad);
static int generate_known_disks(dlist_t **known, dlist_t **bad);
static int generate_known_hbas(dlist_t *disks, dlist_t **known);
static int generate_known_hba_name(
	dm_descriptor_t hba,
	dm_descriptor_t	alias,
	dm_descriptor_t disk);

static void print_known_devices();
static void print_device_list(dlist_t *devices);

/*
 * lists of device dm_descriptor_t handles that are usable by layout.
 * These devices must still pass the user specified available/unavailable
 * filter before they're actually considered available.
 *
 * Lists are populated by discover_usable_devices.
 */
static dlist_t	*_usable_slices = NULL;
static dlist_t	*_usable_disks = NULL;
static dlist_t	*_usable_hbas = NULL;

/*
 * private flag that remembers if any HBA is known to support MPXIO
 */
static boolean_t _mpxio_enabled = B_FALSE;

/*
 * The slice_class struct is used to group slices by usage class.
 */
typedef struct {
	char	*usage;		/* usage description */
	dlist_t	*sliceinfo;	/* list with info about each slice with usage */
} slice_class_t;

#define	USE_DISKSET	"diskset"

static int check_slice_usage(
	char		*dsname,
	dm_descriptor_t slice,
	dm_descriptor_t disk,
	boolean_t	*avail,
	dlist_t		**bad,
	dlist_t		**classes);

static int check_svm_slice_usage(
	char		*dsname,
	dm_descriptor_t slice,
	dm_descriptor_t disk,
	boolean_t	*avail,
	dlist_t		**classes);

static int save_slice_classification(
	char		*dsname,
	dm_descriptor_t slice,
	dm_descriptor_t disk,
	char		*usage,
	char		*usage_detail,
	dlist_t		**classes);

static int generate_usable_disks_and_slices_in_local_set(
	dlist_t		**classes,
	dlist_t		**bad_disks,
	dlist_t		**usable_disks,
	dlist_t		**usable_slices);

static int generate_usable_disks_and_slices_in_named_set(
	char		*dsname,
	dlist_t		**classes,
	dlist_t		**bad_slices,
	dlist_t		**usable_disks,
	dlist_t		**usable_slices);

static int create_usable_slices(
	dm_descriptor_t disk,
	dlist_t		*used,
	dlist_t		*unused,
	dlist_t 	**usable);

static int add_new_usable(
	dm_descriptor_t disk,
	uint64_t	stblk,
	uint64_t	nblks,
	dlist_t		**next_unused,
	dlist_t		**usable);

static int update_slice_attributes(
	dm_descriptor_t slice,
	uint64_t	stblk,
	uint64_t	nblks,
	uint64_t	nbytes);

static int generate_usable_hbas(
	dlist_t		*disks,
	dlist_t		**usable);

static void print_usable_devices();

static void print_unusable_devices(
	dlist_t		*badslices,
	dlist_t		*baddisks,
	dlist_t		*usedslices);

static char *get_slice_usage_msg(
	char		*usage);

/*
 * virtual slices...
 */
static int generate_virtual_slices(
	dlist_t 	*avail_disks_local_set,
	dlist_t		**usable);

/*
 * multipathed disks have aliases, as do slices on those disks.
 * these need to be tracked since the user may specify them.
 * A multi-pathed disk is one connected to the system thru
 * more than one physical HBA, each connection gets a distinct
 * name in the device tree and they're all more or less equivalent.
 * No indication as to how many possible physical connections a
 * disk may have, so we pick an arbitrary number of aliases to
 * support. There is nothing significant about this number,
 * it just controls the number of alias slots that get allocated.
 */
#define	MAX_ALIASES	8

/*
 * attribute name for layout private information stored in
 * device nvpair attribute lists.
 */
static char *ATTR_DEVICE_ALIASES = "layout_device_aliases";

static int compare_start_blocks(
	void *desc1, void *desc2);

static int compare_desc_display_names(
	void *desc1, void *desc2);

/*
 * FUNCTION:	is_mpxio_enabled()
 *
 * RETURNS:	boolean_t - B_TRUE - if MPXIO appears enabled for the system
 *			    B_FALSE - otherwise
 *
 * PURPOSE:	returns the value of _mpxio_enabled which is set to B_TRUE
 *		during system configuration discovery if any of the knwon
 *		HBAs advertises itself as a "multiplex" controller.
 */
boolean_t
is_mpxio_enabled()
{
	return (_mpxio_enabled);
}

/*
 * FUNCTION:	discover_known_devices()
 *
 * SIDEEFFECT:	populates the module private lists of known devices
 *		(_known_slices, _known_disks, _known_hbas).
 *
 *		All known devices will also have had their CTD
 *		short names inferred and stored.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Load physical devices discovered thru libdiskmgt.
 */
int
discover_known_devices()
{
	int	error = 0;

	oprintf(OUTPUT_TERSE,
		gettext("\nScanning system physical "
			"device configuration...\n"));

	/* initialize layout_device_cache */
	((error = create_device_caches()) != 0) ||

	(error = generate_known_disks(&_known_disks, &_bad_disks)) ||
	(error = generate_known_slices(_known_disks, &_known_slices,
		&_bad_slices)) ||
	(error = generate_known_hbas(_known_disks, &_known_hbas));

	if (error == 0) {
	    print_known_devices();
	}

	return (error);
}

/*
 * FUNCTION:	release_known_devices()
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Unloads all state currently held for known
 *		physical devices.
 */
int
release_known_devices(
	char	*diskset)
{
	/* these lists are module private */
	dlist_free_items(_bad_slices, NULL);
	dlist_free_items(_bad_disks, NULL);
	dlist_free_items(_known_slices, NULL);
	dlist_free_items(_known_disks, NULL);
	dlist_free_items(_known_hbas, NULL);

	_bad_slices = NULL;
	_bad_disks = NULL;
	_known_slices = NULL;
	_known_disks = NULL;
	_known_hbas = NULL;

	/* clean up state kept in layout_device_cache */
	release_device_caches();

	return (0);
}

/*
 * FUNCTION:	discover_usable_devices(char *diskset)
 *
 * INPUT:	diskset	- a char * diskset name.
 *
 * SIDEEFFECT:	Traverses the lists of known devices and populates the
 *		module private lists of usable devices (_usable_slices,
 *		_usable_disks, _usable_hbas), as well as the module
 *		private list of used slices.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Process the known devices and determine which of them are
 *		usable for generating volumes in the specified diskset.
 *
 *		The specified diskset's name cannot be NULL or 0 length.
 */
int
discover_usable_devices(
	char	*diskset)
{
	int	error = 0;

	dlist_t *used_classes = NULL;
	dlist_t *iter = NULL;

	if (diskset == NULL || diskset[0] == '\0') {
	    volume_set_error(
		    gettext("a diskset name must be specified in "
			    "the request\n"));
	    return (-1);
	}

	oprintf(OUTPUT_TERSE,
		gettext("\nDetermining usable physical devices "
			"for disk set \"%s\"...\n"),
		diskset);

	error = generate_usable_disks_and_slices_in_local_set(
	    &used_classes, &_bad_slices, &_usable_disks, &_usable_slices);
	if (error == 0) {

	    error = generate_usable_disks_and_slices_in_named_set(
		diskset, &used_classes, &_bad_slices, &_usable_disks,
		&_usable_slices);
	    if (error == 0) {

		error = generate_usable_hbas(_usable_disks, &_usable_hbas);
		if (error == 0) {

		    print_usable_devices();
		    print_unusable_devices(
			_bad_slices, _bad_disks, used_classes);
		}
	    }
	}

	/*
	 * free slice classification usage and lists, items are char*
	 * the used_classes structure is only filled in if verbose
	 * output was requested.
	 */
	for (iter = used_classes; iter != NULL; iter = iter->next) {
	    slice_class_t *class = (slice_class_t *)iter->obj;
	    free(class->usage);
	    dlist_free_items(class->sliceinfo, free);
	}

	dlist_free_items(used_classes, free);
	return (error);
}

/*
 * FUNCTION:	release_usable_devices()
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Unloads all state currently held for usable
 *		physical devices.
 */
int
release_usable_devices()
{
	/* list items are shared with _known_XXX lists */

	dlist_free_items(_usable_slices, NULL);
	dlist_free_items(_usable_disks, NULL);
	dlist_free_items(_usable_hbas, NULL);

	_usable_slices = NULL;
	_usable_disks = NULL;
	_usable_hbas = NULL;

	/* clean up state kept in layout_device_util */
	release_virtual_slices();

	return (0);
}

/*
 * FUNCTION:	get_known_slices(dlist_t **list)
 *		get_known_disks(dlist_t **list)
 *		get_known_hbas(dlist_t **list)
 *
 * OUTPUT:	list	- a dlist_t pointer to hold the returned list of
 *			devices.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Public accessors for the module private lists of
 *		available devices.
 */
int
get_known_slices(
	dlist_t **list)
{
	*list = _known_slices;

	return (0);
}

int
get_known_disks(
	dlist_t **list)
{
	*list = _known_disks;

	return (0);
}

int
get_known_hbas(
	dlist_t **list)
{
	*list = _known_hbas;

	return (0);
}

/* make fully qualified DID device name */
static char *
make_fully_qualified_did_device_name(
	char	*device)
{
	static char	buf[MAXPATHLEN];

	if (device != NULL && strrchr(device, '/') == NULL) {
	    (void) snprintf(buf, MAXPATHLEN-1, "%s/%s",
		    "/dev/did/dsk", device);
	    return (buf);
	}

	return (device);
}

/*
 * FUNCTION:	generate_known_disks(dlist_t **known,
 *			dlist_t **bad)
 *
 * INPUT:	NONE
 *
 * OUTPUT:	known	- populated list of known disks
 *		bad	- populated list of known bad disks
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Does the system configuration discovery to determine
 *		what disks are known to be attached to the system.
 *
 *		Determines the CTD name for each disk and saves it.
 */
static int
generate_known_disks(
	dlist_t	**known,
	dlist_t **bad)
{
	int	i;
	int	error = 0;
	dm_descriptor_t	*ddp;

	ddp = dm_get_descriptors(DM_DRIVE, NULL, &error);
	(void) add_descriptors_to_free(ddp);

	*known = NULL;

	if (error != 0) {
	    volume_set_error(
		    gettext("Error discovering system hardware configuration,\n"
		    "unable to communicate with libdiskmgt or diskmgtd.\n"));
	    return (-1);
	}

	if ((ddp == NULL) || (ddp[0] == NULL)) {
	    volume_set_error(gettext("there are no known disks\n"));
	    return (-1);
	}

	/* iterate all returned disks and add them to the known list */
	for (i = 0; (ddp[i] != NULL) && (error == 0); i++) {
	    dm_descriptor_t disk = (dm_descriptor_t)ddp[i];
	    dlist_t *aliases = NULL;
	    uint32_t mtype = DM_MT_UNKNOWN;
	    uint32_t dtype = DM_DT_UNKNOWN;
	    boolean_t bad_disk = B_FALSE;
	    boolean_t online = B_TRUE;

#if defined(i386)
	    /* on X86, disks must have a solaris FDISK partition */
	    boolean_t solpart = B_FALSE;
#endif	/* defined(i386) */

	    if (((error = disk_get_is_online(disk, &online)) == 0 &&
		online == B_FALSE) || error == ENODEV) {
		/* if the disk is offline, report it as bad */
		bad_disk = B_TRUE;
		error = 0;
	    } else

	    if (error == 0 &&
		(((error = disk_get_media_type(disk, &mtype)) != 0) ||
		((error = disk_get_drive_type(disk, &dtype)) != 0)) &&
		error == ENODEV) {
		/*
		 * if any disk attribute access fails with ENODEV
		 * report it as bad
		 */
		bad_disk = B_TRUE;
		error = 0;
	    } else {

		/*
		 * Determine whether disk is fixed by checking its
		 * drive type.  If drive type is unknown, check media
		 * type.
		 */
		int isfixed = (dtype == DM_DT_FIXED ||
		    (dtype == DM_DT_UNKNOWN && mtype == DM_MT_FIXED));

		if (!isfixed) {
		    continue;  /* ignore non-fixed disks */
		}

#if defined(i386)
		if (((error = disk_get_has_solaris_partition(disk,
		    &solpart)) != 0) || (solpart != B_TRUE)) {

		    /* X86 drive has no solaris partition, report as bad */
		    oprintf(OUTPUT_DEBUG,
			    gettext("%s has no solaris FDISK partition.\n"));

		    bad_disk = B_TRUE;
		}
#endif	/* defined(i386) */

	    }

	    if (bad_disk) {
		/* remember bad disks and continue */
		if (dlist_contains(*bad, (void *)(uintptr_t)disk,
		    compare_descriptor_names) != B_TRUE) {
		    dlist_t *item = dlist_new_item((void *)(uintptr_t)disk);
		    if (item == NULL) {
			error = ENOMEM;
		    } else {
			*bad = dlist_append(item, *bad, AT_TAIL);
		    }
		}
		continue;
	    }

	    /* get disk name and multipath aliases */
	    if ((error = disk_get_aliases(disk, &aliases)) == 0) {
		dlist_t *iter;
		boolean_t disk_name_set = B_FALSE;

		for (iter = aliases;
		    (iter != NULL) && (error == 0);
		    iter = iter->next) {

		    dm_descriptor_t	ap = (uintptr_t)iter->obj;
		    char		*alias;

		    if ((error = get_name(ap, &alias)) == 0) {
			/* save first alias as display name */
			if (disk_name_set != B_TRUE) {
			    /* make sure DID disk alias is fully qualified */

			    if (is_did_disk_name(alias) == B_TRUE) {
				char *qual_name =
				    make_fully_qualified_did_device_name(alias);

				set_display_name(disk, qual_name);
				oprintf(OUTPUT_DEBUG,
					gettext("DID disk name: %s\n"),
					qual_name);
			    } else {
				set_display_name(disk, alias);
				oprintf(OUTPUT_DEBUG,
					gettext("disk name: %s\n"),
					alias);
			    }
			    disk_name_set = B_TRUE;

			} else {
			    /* save others as aliases */
			    set_alias(disk, alias);
			    oprintf(OUTPUT_DEBUG,
				    gettext("  alias: %s\n"),
				    alias);
			}
		    }
		}

		dlist_free_items(aliases, NULL);
	    }

	    if (error == 0) {
		dlist_t *item = dlist_new_item((void *)(uintptr_t)disk);
		if (item == NULL) {
		    error = ENOMEM;
		} else {
		    *known =
			dlist_insert_ordered(item, *known,
				ASCENDING, compare_desc_display_names);
		}
	    }
	}

	if (ddp != NULL) {
	    free(ddp);
	}

	return (error);
}

/*
 * FUNCTION:	generate_known_slices(dlist_t *disks,
 *		dlist_t **known, dlist_t **bad)
 *
 * OUTPUT:	disks	- a pointer to a list of known disks
 *		known	- a pointer to a dlist_t list to hold the known slices
 *		bad	- a pointer to a dlist_t to hold the bad slices
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Examines input list of known disks and determines the slices
 *		attached to each.
 *
 *		Some slices returned from libdiskmgt may not really exist,
 *		this is detected when trying to get more information about
 *		the slice -- ENODEV is returned.  Any such slices will be
 *		added to the bad slice list.
 */
static int
generate_known_slices(
	dlist_t		*disks,
	dlist_t		**known,
	dlist_t		**bad)
{
	dlist_t		*iter;
	int		error = 0;

	/* iterate list of disks and add their slices to the known list */
	for (iter = disks; (iter != NULL) && (error == 0); iter = iter->next) {

	    dm_descriptor_t disk = (uintptr_t)iter->obj;
	    dlist_t *slices = NULL;
	    dlist_t *iter1;
	    char *dname = NULL;
	    boolean_t disk_ctd_alias_derived = B_FALSE;

	    if (((error = disk_get_slices(disk, &slices)) != 0) ||
		((error = get_display_name(disk, &dname)) != 0)) {
		continue;
	    }

	    for (iter1 = slices;
		(iter1 != NULL) && (error == 0);
		iter1 = iter1->next) {

		dm_descriptor_t slice = (uintptr_t)iter1->obj;
		uint32_t index = 0;
		nvlist_t *attrs = NULL;
		char *sname = NULL;

		if (((error = get_name(slice, &sname)) != 0) ||
		    ((error = slice_get_index(slice, &index)) != 0) ||
		    ((error = get_cached_attributes(slice, &attrs)) != 0)) {

		    if (error == ENODEV) {
			/* bad slice, remember it and continue */
			dlist_t *item =
			    dlist_new_item((void *)(uintptr_t)slice);
			if (item == NULL) {
			    error = ENOMEM;
			} else {
			    *bad = dlist_insert_ordered(
				    item, *bad,
				    ASCENDING, compare_descriptor_names);
			    error = 0;
			}
		    }
		    continue;
		}

		if ((error == 0) && (is_did_slice_name(sname) == B_TRUE) &&
		    (disk_ctd_alias_derived == B_FALSE)) {
		    /* BEGIN CSTYLED */
		    /*
		     * If the slice name is a DID name, get the local CTD
		     * name for slice, extract the disk name and add it as
		     * an alias for the disk.
		     *
		     * This is the only way to derive the CTD alias for the
		     * disk when DID is enabled.
		     *
		     * The disk_ctd_alias_derived flag ensure the disk's
		     * CTD alias is only set once.
		     *
		     * The slice's CTD aliases are then derived from the
		     * disk's CTD alias in the normal, non-DID name processing
		     * which happens below.
		     */
		    /* END CSTYLED */
		    char *local = NULL;
		    if ((error = nvlist_lookup_string(attrs, DM_LOCALNAME,
				&local)) != 0) {
			if (error == ENOENT) {
			    /* no local name -> no DID */
			    error = 0;
			}
		    } else {
			char *localdisk = NULL;
			char *diskonly = NULL;
			if ((error = extract_diskname(local,
			    &localdisk)) == 0) {
			    if ((diskonly = strrchr(localdisk, '/')) != NULL) {
				++diskonly;
			    } else {
				diskonly = localdisk;
			    }
			    oprintf(OUTPUT_DEBUG,
				    gettext("  set DID disk CTD alias: %s\n"),
				    diskonly);
			    error = set_alias(disk, diskonly);
			    free(localdisk);
			    disk_ctd_alias_derived = B_TRUE;
			}
		    }
		}

		/* derive slice display name from disk's display name */
		if (error == 0) {
		    if ((error = make_slicename_for_diskname_and_index(
			dname, index, &sname)) == 0) {
			error = set_display_name(slice, sname);
		    }
		}

		/* set slice aliases using disk aliases */
		if (error == 0) {
		    dlist_t *aliases = NULL;
		    if ((error = get_aliases(disk, &aliases)) == 0) {

			dlist_t *iter2 = aliases;
			for (; (iter2 != NULL) && (error == 0);
			    iter2 = iter2->next) {

			    char *dalias = (char *)iter2->obj;
			    char *salias = NULL;

			    if ((error = make_slicename_for_diskname_and_index(
				dalias, index, &salias)) == 0) {
				error = set_alias(slice, salias);
				free(salias);
			    }
			}
			dlist_free_items(aliases, free);
		    }
		}

		if (error == 0) {
		    dlist_t *item = dlist_new_item((void *)(uintptr_t)slice);
		    if (item == NULL) {
			error = ENOMEM;
		    } else {
			*known =
			    dlist_insert_ordered(
				    item, *known,
				    ASCENDING, compare_desc_display_names);
		    }
		}
	    }

	    dlist_free_items(slices, NULL);
	}

	return (error);
}

/*
 * FUNCTION:	generate_known_hbas(dlist_t *disks, dlist_t **known)
 *
 * INPUT:	diskset	- a char * diskset name.
 *
 * OUTPUT:	populates the list of known HBAs.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Examines known disk list and derives the list of known HBAs.
 *
 *		Determines the CTD name for an HBA and saves it.
 */
static int
generate_known_hbas(
	dlist_t	*disks,
	dlist_t	**known)
{
	dlist_t	*iter;
	int	error = 0;

	/*
	 * for each known disk follow its HBA connections and
	 * assemble the list of known HBAs.
	 */
	for (iter = disks;
	    (iter != NULL) && (error == 0);
	    iter = iter->next) {

	    dm_descriptor_t	disk = (uintptr_t)iter->obj;
	    dlist_t 		*hbas = NULL;
	    dlist_t 		*iter2 = NULL;
	    dlist_t		*iter3 = NULL;
	    dlist_t		*aliases = NULL;
	    char		*dname = NULL;

	    ((error = get_display_name(disk, &dname)) != 0) ||
	    (error = disk_get_aliases(disk, &aliases)) ||
	    (error = disk_get_hbas(disk, &hbas));

	    if (error == 0) {

		if ((hbas == NULL) || (dlist_length(hbas) == 0)) {

		    oprintf(OUTPUT_DEBUG,
			    gettext("Disk %s has no HBA/Controller?!\n"),
			    dname);
		    error = -1;

		    dlist_free_items(hbas, NULL);
		    dlist_free_items(aliases, NULL);

		    continue;
		}

		for (iter2 = hbas, iter3 = aliases;
		    iter2 != NULL && iter3 != NULL;
		    iter2 = iter2->next, iter3 = iter3->next) {

		    dm_descriptor_t	hba = (uintptr_t)iter2->obj;
		    dm_descriptor_t	alias = (uintptr_t)iter3->obj;
		    dlist_t		*item = NULL;

		    /* scan list of known HBAs and see if known */
		    if (dlist_contains(*known, (void*)(uintptr_t)hba,
			compare_descriptor_names) == B_TRUE) {
			/* known HBA */
			continue;
		    }

		    /* see if HBA supports MPXIO */
		    if ((error == 0) && (_mpxio_enabled != B_TRUE)) {
			hba_is_multiplex(hba, &_mpxio_enabled);
		    }

		    /* generate a CTD name for HBA */
		    error = generate_known_hba_name(hba, alias, disk);
		    if (error == 0) {
			/* add to known HBA list */
			if ((item = dlist_new_item((void *)(uintptr_t)hba)) ==
			    NULL) {
			    error = ENOMEM;
			} else {
			    *known =
				dlist_insert_ordered(item, *known,
				    ASCENDING, compare_desc_display_names);
			}
		    }
		}
	    }

	    dlist_free_items(aliases, NULL);
	    dlist_free_items(hbas, NULL);
	}

	return (error);
}

/*
 * FUNCTION:	generate_known_hba_name(dm_descriptor_t hba,
 *		dm_descriptor_t alias, char *diskname)
 *
 * INPUT:	hba	- a dm_descriptor_t HBA handle.
 *		alias	- a dm_descriptor_t disk alias handle.
 *		diskname - a char * disk name
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Sets the CTD name for the input HBA.
 *
 *		The CTD name for the HBA is generated from the input
 *		disk alias (ex: cXdXtXsX) or from the disk name if
 *		the input alias is a DID name (ex: dX).
 */
static int
generate_known_hba_name(
	dm_descriptor_t	hba,
	dm_descriptor_t	alias,
	dm_descriptor_t disk)
{
	char	*hbaname = NULL;
	char	*aliasname = NULL;
	int	error = 0;

	((error = get_name(alias, &aliasname)) != 0) ||
	(error = extract_hbaname(aliasname, &hbaname));
	if (error != 0) {
	    free(hbaname);
	    return (error);
	}

	/* see if the input alias is a DID name... */
	if (is_did_disk_name(aliasname) == B_TRUE) {

	    /* look for a non-DID name in disk's aliases */
	    dlist_t *aliases = NULL;
	    error = get_aliases(disk, &aliases);

	    for (; (error == 0) && (aliases != NULL);
		aliases = aliases->next) {

		aliasname = (char *)aliases->obj;
		if (is_did_disk_name(aliasname) != B_TRUE) {
		    /* this is the "local" CTD name generated by */
		    /* generate_known_disks() above */
		    error = extract_hbaname(aliasname, &hbaname);
		    if ((error == 0) && (hbaname != NULL)) {
			set_display_name(hba, hbaname);
			break;
		    }
		}
	    }
	    dlist_free_items(aliases, free);

	} else {
	    /* use whatever was derived from the alias name */
	    set_display_name(hba, hbaname);
	}

	return (error);
}

/*
 * FUNCTION:	print_known_devices()
 *
 * PURPOSE:	Print out the known devices.
 *
 *		Iterates the lists of known slices, disks and HBAs
 *		and prints out their CTD and device names.
 */
static void
print_known_devices(
	char	*diskset)
{
	int i = 0;
	struct {
		char *msg;
		dlist_t *list;
	}	devs[3];

	devs[0].msg = gettext("HBA/Controllers");
	devs[0].list = _known_hbas;
	devs[1].msg = gettext("disks");
	devs[1].list = _known_disks;
	devs[2].msg = gettext("slices");
	devs[2].list = _known_slices;

	for (i = 0; i < 3; i++) {

	    oprintf(OUTPUT_VERBOSE,
		    gettext("\n  These %s are known:\n\n"),
		    devs[i].msg);

	    print_device_list(devs[i].list);
	}
}

/*
 * FUNCTION:	get_usable_slices(dlist_t **list)
 *
 * OUTPUT:	list	- a dlist_t pointer to hold the returned list of
 *			devices.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Public accessors the the modules private lists of
 *		available devices.
 *
 *		The functions are keyed by diskset name in the event
 *		objects in different disksets are loaded concurrently.
 */
int
get_usable_slices(
	dlist_t **list)
{
	*list = _usable_slices;

	return (0);
}

int
get_usable_disks(
	dlist_t **list)
{
	*list = _usable_disks;

	return (0);
}

int
get_usable_hbas(
	dlist_t **list)
{
	*list = _usable_hbas;

	return (0);
}

/*
 * FUNCTION:	generate_usable_disks_and_slices_in_local_set(dlist_t **classes,
 *			dlist_t **bad_disks, dlist_t **usable_disks,
 *			dlist_t **usable_slices)
 *
 * OUTPUT:	used_classes - a pointer to a list of slice_class_t structs
 *			updated with known slices that have detected uses
 *			added to the correct class'e list of slices.
 *		bad_disks - a pointer to a list of bad/unusable disks updated
 *			with any bad disks that were detected
 *		useable_disks - a pointer to a list of usable disks
 *		useable_slices - a pointer to a list of usable slices
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Scans the disks in the local set to determine which are
 *		usable during layout processing.
 *
 *		Determines which are usable by layout using usages detected
 *		by libdiskmgt.
 */
static int
generate_usable_disks_and_slices_in_local_set(
	dlist_t **classes,
	dlist_t **bad_slices,
	dlist_t **usable_disks,
	dlist_t **usable_slices)
{
	char	*dsname = MD_LOCAL_NAME;
	dlist_t *disks;
	dlist_t *iter;
	int 	error;

	/* Get disks in local set */
	error = get_disks_in_diskset(dsname, &disks);
	if (error != 0) {
	    return (error);
	}

	/* For each disk in this set... */
	for (iter = disks; iter != NULL && error == 0; iter = iter->next) {
	    dm_descriptor_t disk = (uintptr_t)iter->obj;
	    dlist_t *slices;

	    /* Get slices on this disk */
	    error = disk_get_slices(disk, &slices);
	    if (error == 0) {
		dlist_t *iter2;

		/*
		 * Assume disk is available until a bad or unavailable
		 * slice is found
		 */
		boolean_t avail = B_TRUE;
		boolean_t bad_disk = B_FALSE;

		/* For each slice on this disk... */
		for (iter2 = slices;
		    iter2 != NULL && error == 0 &&
			avail == B_TRUE && bad_disk == B_FALSE;
		    iter2 = iter2->next) {

		    dm_descriptor_t slice = (uintptr_t)iter2->obj;
		    dlist_t *bad_slices_on_this_disk = NULL;

		    /* Is this slice available? */
		    error = check_slice_usage(dsname, slice,
			disk, &avail, &bad_slices_on_this_disk, classes);

		    /* Is the slice bad (inaccessible)? */
		    if (error != 0 && bad_slices_on_this_disk != NULL) {
			bad_disk = B_TRUE;
			*bad_slices = dlist_append_list(
			    *bad_slices, bad_slices_on_this_disk);
		    }
		}

		/* Is the disk available? */
		if (error == 0 && bad_disk == B_FALSE && avail == B_TRUE) {
		    error = dlist_append_object(
			(void *)(uintptr_t)disk, usable_disks, AT_TAIL);
		}

		dlist_free_items(slices, NULL);
	    }
	}

	dlist_free_items(disks, NULL);

	if (error == 0) {
	    /* BEGIN CSTYLED */
	    /*
	     * Now reslice usable disks in the local set to
	     * simulate the slices they'll have when they're added
	     * to the named disk set, and add these resulting
	     * virtual slices to the list of available slices.
	     */
	    /* END CSTYLED */
	    error = generate_virtual_slices(*usable_disks, usable_slices);
	}

	return (error);
}

/*
 * FUNCTION:	generate_virtual_slices(dlist_t *unused, dlist_t **usable)
 *
 * INPUT:	slice_classes - a list of unused slice dm_descriptor_t handles.
 *
 * OUTPUT:	usable - pointer to the list of usable slices, updated
 *			with any created virtual slices.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Helper which creates virtual slices for each disk which
 *		could be added to a diskset if necessary...
 *
 *		Search the input list of slice classes for the entry
 *		containing slices known to be available for use by layout.
 *
 *		Iterate the list of unused slices and determine the set
 *		of unique disks.
 *
 *		For each unique disk, create virtual slice descriptors to
 *		represent those that will exist if/when the disk is added
 *		to the diskset.
 *
 *		Add theese virtual slices to the list of usable slices.
 */
static int
generate_virtual_slices(
	dlist_t 	*avail_disks_local_set,
	dlist_t		**usable)
{
	dlist_t	*iter = NULL;
	int	error = 0;

	/* generate virtual slices */
	error = create_virtual_slices(avail_disks_local_set);
	if (error == 0) {

	    get_virtual_slices(&iter);
	    for (; (iter != NULL) && (error == 0); iter = iter->next) {

		dlist_t *item = dlist_new_item((void *) iter->obj);
		if (item == NULL) {
		    error = ENOMEM;
		} else {
		    *usable =
			dlist_insert_ordered(item, *usable,
				ASCENDING, compare_desc_display_names);
		}
	    }
	}

	return (error);
}

/*
 * FUNCTION:	generate_usable_disks_and_slices_in_named_set(char *dsname,
 *			dlist_t **classes, dlist_t **bad_slices,
 *			dlist_t **usable_slices, dlist_t **usable_disks)
 *
 * INPUT:	dsname	- a char * diskset name.
 *
 * OUTPUT:	classes	- pointer to a list of slice_class_t structs,
 *			updated to include slices in the disk set with
 *			known uses.
 * 		bad_slices - pointer to a list of bad/unusable slices,
 *			updated to include slices in the disk set that
 *			are inaccessible or no longer existent.
 *		usable_slices - pointer to a list of usable slices in the
 *			disk set.
 *		usable_disks - pointer to a list of usable disks in the
 *			disk set.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	1. determine the disks in the named disk set
 *		2. determine the used slices on the disks
 *		3. determine the unused slices on the disks
 *		4. look for unused space on the disks and collect it
 *		   into an existing unused slice, or create a new
 *		   virtual slice.
 */
static int
generate_usable_disks_and_slices_in_named_set(
	char		*dsname,
	dlist_t		**classes,
	dlist_t		**bad_slices,
	dlist_t		**usable_disks,
	dlist_t		**usable_slices)
{
	dlist_t		*disks = NULL;
	dlist_t		*iter = NULL;
	int		error = 0;

	error = get_disks_in_diskset(dsname, &disks);
	if (error != 0) {
	    return (error);
	}

	/* For each disk... */
	for (iter = disks;
	    iter != NULL && error == 0;
	    iter = iter->next) {

	    dm_descriptor_t	disk = (uintptr_t)iter->obj;
	    dlist_t		*iter2;
	    dlist_t		*slices = NULL;
	    dlist_t		*bad_slices_on_this_disk = NULL;
	    dlist_t		*used_slices_on_this_disk = NULL;
	    dlist_t		*unused_slices_on_this_disk = NULL;
	    boolean_t		bad_disk = B_FALSE;

	    error = disk_get_slices(disk, &slices);
	    if (error != 0) {
		break;
	    }

	    /* Determine the used, unused, and bad slices on the disk */

	    /* For each slice... */
	    for (iter2 = slices;
		iter2 != NULL && error == 0 && bad_disk == B_FALSE;
		iter2 = iter2->next) {

		dm_descriptor_t slice = (uintptr_t)iter2->obj;

		boolean_t	rsvd = B_FALSE;
		boolean_t	avail = B_FALSE;

		/* Get slice usage */
		if (((error = is_reserved_slice(slice, &rsvd)) == 0) &&
		    ((error = check_slice_usage(dsname, slice, disk, &avail,
			&bad_slices_on_this_disk, classes)) == 0)) {

		    /* Is the slice bad (inaccessible)? */
		    if (bad_slices_on_this_disk != NULL) {
			*bad_slices = dlist_append_list(
			    *bad_slices, bad_slices_on_this_disk);
			/*
			 * Since one slice on this disk is bad, don't
			 * use any slices on this disk
			 */
			bad_disk = B_TRUE;
		    } else {

			dlist_t *item =
			    dlist_new_item((void *)(uintptr_t)slice);
			if (item == NULL) {
			    error = ENOMEM;
			} else {
			    /* Add slice to used/unused list as appropriate */
			    if (avail == B_TRUE && rsvd == B_FALSE) {
				unused_slices_on_this_disk = dlist_append(
				    item, unused_slices_on_this_disk, AT_TAIL);
			    } else {
				used_slices_on_this_disk =
				    dlist_insert_ordered(item,
					used_slices_on_this_disk,
					ASCENDING, compare_start_blocks);
			    }
			}
		    }
		}
	    }

	    /* Done iterating slices */

	    if (error == 0 && bad_disk == B_FALSE) {
		/* For each unused slice... */
		for (iter2 = unused_slices_on_this_disk;
		    iter2 != NULL && error == 0;
		    iter2 = iter2->next) {

		    dm_descriptor_t slice = (uintptr_t)iter2->obj;
		    error = update_slice_attributes(slice, 0, 0, 0);

		    /* Only do this once */
		    if (error == 0 && iter2 == unused_slices_on_this_disk) {
			error = add_modified_disk(NULL, disk);
		    }
		}

		if (error == 0) {
		    /* Create usable slices from the used/unused slice lists */
		    error = create_usable_slices(disk, used_slices_on_this_disk,
			unused_slices_on_this_disk, usable_slices);
		    if (error == 0) {
			error = dlist_append_object((void *)(uintptr_t)disk,
			    usable_disks, AT_TAIL);
		    }
		}
	    }

	    dlist_free_items(slices, NULL);
	    dlist_free_items(used_slices_on_this_disk, NULL);
	    dlist_free_items(unused_slices_on_this_disk, NULL);
	}

	return (error);
}

/*
 * FUNCTION:	create_usable_slices(dm_descriptor_t disk, dlist_t *used,
 *			dlist_t *unused, dlist_t **usable);
 *
 * INPUT:	disk	- a dm_descriptor_t disk handle
 *		used	- pointer to a list of pvt_t structs
 *			  representing existing used slices
 *			  on the input disk.
 *		unused	- pointer to a list of pvt_t structs
 *			  representing existing unused slices
 *			  on the input disk.
 *
 * OUTPUT:	usable	- pointer to a list of pvts representing slices
 *			which can be used for new volume layouts.
 *
 *			Slices in this list have any available space on the
 *			disk collected into the fewest, lowest indexed slices
 *			possible.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	helper for generate_usable_slices_and_disks_in_diskset() which
 *		turns any detected free space on the input disk into one or
 *		more slices.
 */
static int
create_usable_slices(
	dm_descriptor_t disk,
	dlist_t		*used,
	dlist_t		*unused,
	dlist_t		**usable)
{
	dlist_t		*iter;
	int		error = 0;
	boolean_t	first = B_TRUE;
	dlist_t		*next_unused = unused;

	char		*dname = NULL;
	uint64_t 	disk_firstblk = 0;
	uint64_t 	disk_nblks = 0;
	uint64_t 	disk_endblk = 0;

	oprintf(OUTPUT_DEBUG,
		gettext("\n  create_usable_slices for disk\n"));

	/* get necessary info about disk: */
	error = get_display_name(disk, &dname);
	if (error != 0) {
	    return (error);
	}

	/* disk start block is first usable block */
	error = disk_get_start_block(disk, &disk_firstblk);
	if (error != 0) {
	    return (error);
	}

	/* disk size determines last usable disk block */
	error = disk_get_size_in_blocks(disk, &disk_nblks);
	if (error != 0) {
	    return (error);
	}

	disk_endblk = disk_firstblk + disk_nblks - 1;

	/* search for gaps before, between and after used slices */
	for (iter = used; iter != NULL && error == 0; iter = iter->next) {

	    dm_descriptor_t cur = (uintptr_t)iter->obj;

	    uint64_t cur_stblk = 0;
	    uint64_t cur_nblks = 0;
	    uint64_t cur_endblk = 0;
	    uint32_t cur_index = 0;

	    uint64_t new_stblk = 0;
	    uint64_t new_endblk = 0;

	    char *sname = NULL;
	    (void) get_display_name(cur, &sname);

	    if (((error = slice_get_index(cur, &cur_index)) != 0) ||
		((error = slice_get_start_block(cur, &cur_stblk)) != 0) ||
		((error = slice_get_size_in_blocks(cur, &cur_nblks)) != 0)) {
		continue;
	    }

	    cur_endblk = cur_stblk + cur_nblks - 1;

	    oprintf(OUTPUT_DEBUG,
		    gettext("  used slice %d (%10llu to %10llu)\n"),
		    cur_index, cur_stblk, cur_endblk);

	    if (first == B_TRUE) {
		/* first slice: make sure it starts at disk_firstblk */
		first = B_FALSE;
		if (cur_stblk != disk_firstblk) {
		    /* close gap at beginning of disk */
		    new_stblk = disk_firstblk;
		    new_endblk = cur_stblk - 1;

		    oprintf(OUTPUT_DEBUG,
			    gettext("    unused space before first "
				    "used slice\n"));
		}
	    }

	    if (iter->next != NULL) {
		/* check for gap between slices */
		dm_descriptor_t next = (uintptr_t)iter->next->obj;
		uint64_t next_stblk = 0;
		uint32_t next_index = 0;

		if (((error = slice_get_start_block(next, &next_stblk)) == 0) &&
		    ((error = slice_get_index(next, &next_index)) == 0)) {
		    if (cur_endblk != next_stblk - 1) {
			/* close gap between slices */
			new_stblk = cur_endblk + 1;
			new_endblk = next_stblk - 1;

			oprintf(OUTPUT_DEBUG,
				gettext("    unused space between slices "
					"%d and %d\n"), cur_index, next_index);
		    }
		}

	    } else {
		/* last slice: make sure it includes last block on disk */
		if (cur_endblk != disk_endblk) {
		    /* close gap at end of disk */
		    new_stblk = cur_endblk + 1;
		    new_endblk = disk_endblk;

		    oprintf(OUTPUT_DEBUG,
			    gettext("    unused space after last slice "
				    "cur_endblk: %llu disk_endblk: %llu\n"),
			    cur_endblk, disk_endblk);
		}
	    }

	    if ((error == 0) && (new_endblk != 0)) {
		error = add_new_usable(disk, new_stblk,
			new_endblk - new_stblk + 1, &next_unused, usable);
	    }
	}

	if (error != 0) {
	    dlist_free_items(*usable, free);
	    *usable = NULL;
	}

	return (error);
}

/*
 * FUNCTION:	add_new_usable(dm_descriptor_t disk, uint64_t stblk,
 *			uint64_t nblks, dlist_t **next_unused,
 *			dlist_t **usable);
 *
 * INPUT:	disk	- a dm_descriptor_t disk handle
 *		stblk	- start block of the usable space
 *		nblks	- number of usable blocks
 *		next_unused	- pointer to the next unused slice
 *
 * OUTPUT:	next_unused	- updated pointer to the next unused slice
 *		usable	- possibly updated pointer to a list of slices on
 *			the disk with usable space
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	helper for create_usable_slices() which turns free space
 *		on the input disk into a usable slice.
 *
 *		If possible an existing unused slice will be recycled
 *		into a usable slice. If there are none, a new virtual
 *		slice will be created.
 */
static int
add_new_usable(
	dm_descriptor_t disk,
	uint64_t	stblk,
	uint64_t	nblks,
	dlist_t		**next_unused,
	dlist_t		**usable)
{
	dm_descriptor_t new_usable = 0;
	int		error = 0;

	/* try to use an existing unused slice for the usable slice */
	if (*next_unused != NULL) {
	    new_usable = (uintptr_t)((*next_unused)->obj);
	    *next_unused = (*next_unused)->next;

	    oprintf(OUTPUT_DEBUG,
		    gettext("\trecyling used slice into usable slice "
			    "start: %llu, end: %llu\n"),
		    stblk, stblk + nblks + 1);
	}

	if (new_usable == NULL) {
	    /* no unused slices, try to make a new virtual slice */
	    uint32_t index = UINT32_MAX;
	    error = disk_get_available_slice_index(disk, &index);
	    if ((error == 0) && (index != UINT32_MAX)) {

		char *dname = NULL;
		error = get_display_name(disk, &dname);
		if (error == 0) {

		    char buf[MAXNAMELEN];
		    (void) snprintf(buf, MAXNAMELEN-1, "%ss%d", dname, index);
		    error = add_virtual_slice(buf, index, 0, 0, disk);
		    if (error == 0) {
			/* retrieve the virtual slice */
			error = slice_get_by_name(buf, &new_usable);
		    }
		}
	    }
	}

	if ((error == 0) && (new_usable != (dm_descriptor_t)0)) {
	    /* BEGIN CSTYLED */
	    /*
	     * have an unused slice, update its attributes to reflect
	     * the usable space it represents
	     */
	    /* END CSTYLED */
	    uint64_t disk_blksz = 0;
	    error = disk_get_blocksize(disk, &disk_blksz);
	    if (error == 0) {
		error = update_slice_attributes(new_usable, stblk,
		    nblks, nblks * disk_blksz);
		if (error == 0) {
		    error = dlist_append_object(
			(void *)(uintptr_t)new_usable, usable, AT_TAIL);
		}
	    }
	}

	return (error);
}

/*
 * FUNCTION:	update_slice_attributes(dm_descriptor_t slice, uint64_t stblk,
 *			uint64_t nblks, uint64_t nbytes)
 *
 * INPUT:	slice	- a dm_descriptor_t slice handle
 *		stblk	- start block of the usable space
 *		nblks	- size of slice in blocks
 *		nbytes	- size of slice in bytes
 *
 * SIDEEFFECT:	adds a modification record for the slice.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	utility which updates several slice attributes in one call.
 */
static int
update_slice_attributes(
	dm_descriptor_t slice,
	uint64_t	stblk,
	uint64_t	nblks,
	uint64_t	nbytes)
{
	char		*sname = NULL;
	uint32_t	 index = 0;
	int		error = 0;

	if ((error = get_display_name(slice, &sname)) == 0) {
	    if ((error = slice_get_index(slice, &index)) == 0) {
		if ((error = slice_set_start_block(slice, stblk)) == 0) {
		    if ((error = slice_set_size_in_blocks(slice, nblks)) == 0) {
			if (nblks == 0) {
			    error = add_slice_to_remove(sname, index);
			} else {
			    error = assemble_modified_slice((dm_descriptor_t)0,
				    sname, index, stblk, nblks, nbytes, NULL);
			}
		    }
		}
	    }
	}

	return (error);
}

/*
 * FUNCTION:	generate_usable_hbas(dlist_t *slices,
 *			dlist_t **usable)
 *
 * INPUT:	disks	- a list of usable disks.
 *
 * OUTPUT:	usable	- a populated list of usable HBAs.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Examines usable disk list and derives the list of usable HBAs.
 *
 */
static int
generate_usable_hbas(
	dlist_t *disks,
	dlist_t	**usable)
{
	dlist_t	*iter;
	int	error = 0;

	/*
	 * for each usable disk, follow its HBA connections and
	 * add them to the list of usable HBAs.
	 */
	for (iter = disks; (iter != NULL) && (error == 0); iter = iter->next) {

	    dm_descriptor_t	dp = NULL;
	    dlist_t 		*hbas = NULL;
	    dlist_t		*iter2 = NULL;

	    dp = (uintptr_t)iter->obj;

	    error = disk_get_hbas(dp, &hbas);
	    if (error == 0) {

		for (iter2 = hbas;
		    (iter2 != NULL) && (error == 0);
		    iter2 = iter2->next) {

		    dm_descriptor_t	hba = (uintptr_t)iter2->obj;
		    dlist_t		*item = NULL;

		    /* scan list of usable HBAs and see if known */
		    if (dlist_contains(*usable, (void*)(uintptr_t)hba,
			compare_descriptor_names) == B_TRUE) {
			/* known HBA, continue to next HBA/alias */
			continue;
		    }

		    /* add this HBA to the usable list */
		    if ((item = dlist_new_item((void *)(uintptr_t)hba)) ==
			NULL) {
			error = ENOMEM;
		    } else {
			*usable =
			    dlist_insert_ordered(item, *usable,
				    ASCENDING, compare_desc_display_names);
		    }
		}
	    }

	    dlist_free_items(hbas, NULL);
	}

	return (error);
}

/*
 * FUNCTION:	check_slice_usage(char *dsname, dm_descriptor_t slice,
 *			dm_descriptor_t disk, boolean_t *avail,
 *			dlist_t **bad, dlist_t **classes)
 *
 * INPUT:	dsname	- a char * diskset name.
 *		slice	- a dm_descriptor_t handle for a known slices.
 *		disk	- a dm_descriptor_t handle the slice's disk.
 *
 * OUTPUT:	avail	- a boolean_t to hold the slice's availability.
 *		bad	- pointer to a list of bad/unusable slices,
 *				possibly updated if the input slice
 *				was determined to be inaccessible.
 *		classes	- pointer to a list of slice_class_t structs,
 *				possibly updated to include the input slice
 *				if it has a known use.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Handles the details of
 *		determining usage and/or availability of a single slice.
 *
 *		Queries the device library for the input slice's detectable
 *		usage status.
 *
 *		If the slice has a detected usage, its name is added to
 *		the appropriate slice_class_t list in the input list of
 *		slice classes, this is only done if verbose output was
 * 		requested.
 */
static int
check_slice_usage(
	char		*dsname,
	dm_descriptor_t slice,
	dm_descriptor_t disk,
	boolean_t	*avail,
	dlist_t		**bad,
	dlist_t		**classes)
{
	boolean_t	online = B_FALSE;
	boolean_t	used = B_FALSE;
	nvlist_t	*stats = NULL;
	char		*name = NULL;
	char		*used_by = NULL;
	char		*use_detail = NULL;
	int		error = 0;

	*avail = B_FALSE;

	if (((error = get_display_name(slice, &name)) != 0) ||
	    (error = disk_get_is_online(disk, &online))) {
	    return (error);
	}

	/*
	 * if the disk is known to be offline, skip getting status
	 * for the slice since it will just fail and return ENODEV.
	 */
	if (online != B_TRUE) {
	    error = ENODEV;
	} else {
	    stats = dm_get_stats(slice, DM_SLICE_STAT_USE, &error);
	}

	if (error != 0) {
	    if (error == ENODEV) {
		dlist_t *item = dlist_new_item((void *)(uintptr_t)slice);
		oprintf(OUTPUT_TERSE,
			gettext("Warning: unable to get slice information "
				"for %s, it will not be used.\n"), name);

		if (item == NULL) {
		    error = ENOMEM;
		} else {
		    error = 0;
		    *bad = dlist_insert_ordered(item, *bad, ASCENDING,
			    compare_desc_display_names);
		}
	    } else {
		oprintf(OUTPUT_TERSE,
			gettext("check_slice_usage: dm_get_stats for "
				"%s failed %d\n"),
			name, error);
	    }

	    return (error);
	}

	/*
	 * check if/how the slice is currently being used,
	 * device library provides this info in the nvpair_t list:
	 *
	 *   stat_type is DM_SLICE_STAT_USE
	 *	used_by:	string (mount, svm, lu, vxvm, fs)
	 *	used_name:	string
	 *
	 */
	if (stats != NULL) {
	    error = get_string(stats, DM_USED_BY, &used_by);
	    if (error != 0) {
		if (error == ENOENT) {
		    used_by = NULL;
		    error = 0;
		} else {
		    oprintf(OUTPUT_TERSE,
			    gettext("check_slice_usage: dm_get_stats.%s for "
				    "%s failed %d\n"),
			    DM_USED_BY, name, error);
		}
	    }

	    if (error == 0) {
		error = get_string(stats, DM_USED_NAME, &use_detail);
		if (error != 0) {
		    if (error == ENOENT) {
			use_detail = NULL;
			error = 0;
		    } else {
			oprintf(OUTPUT_TERSE,
				gettext("check_slice_usage: "
					"dm_get_stats.%s for "
					"%s failed %d\n"),
					DM_USED_NAME, name, error);
		    }
		}
	    }
	}

	if ((error == 0) && (used_by != NULL) && (used_by[0] != '\0')) {

	    /* was detected usage SVM? */
	    if (string_case_compare(used_by, DM_USE_SVM) == 0) {

		/* check use_detail, it is in the form diskset:name */
		if (strncmp("diskset:", use_detail, 8) == 0) {

		    /* check disk set name */
		    char *str = strrchr(use_detail, ':');
		    if ((str != NULL) &&
			    (string_case_compare(str+1, dsname) == 0)) {

			/* slice in the right diskset */
			error = check_svm_slice_usage(
				dsname, slice, disk, &used, classes);

		    } else {

			/* slice in other diskset */
			save_slice_classification(
				dsname, slice, disk, used_by, use_detail,
				classes);
			used = B_TRUE;
		    }

		} else {

		    /* slice is volume component */
		    save_slice_classification(
			    dsname, slice, disk, used_by, use_detail,
			    classes);
		    used = B_TRUE;
		}

	    } else {

		/* save usage */
		save_slice_classification(
			dsname, slice, disk, used_by, use_detail,
			classes);
		used = B_TRUE;
	    }
	}

	nvlist_free(stats);

	if (error == 0) {
	    if (used == B_TRUE) {
		*avail = B_FALSE;
	    } else {
		*avail = B_TRUE;
	    }
	}

	return (error);
}

/*
 * FUNCTION:	check_svm_slice_usage(char *dsname, dm_descriptor_t slice,
 *			dm_descriptor_t disk, boolean_t *used,
 *			dlist_t **classes)
 *
 * INPUT:	dsname	- a char * diskset name.
 *		slice	- a dm_descriptor_t handle for a known slices.
 *		disk	- a dm_descriptor_t handle the slice's disk.
 *
 * OUTPUT:	used	- a boolean_t to hold the slice usage status.
 *		classes	- pointer to a list of slice_class_t possibly updated
 *				with the input slice's SVM specific usage
 *				classification.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Handles the finer details of
 *		a single slice is being used in the context of SVM.
 *
 *		Currently, one thing is checked:
 *
 *		1. determine if the slice is reserved for metadb replicas.
 *		   The convention for disks in disksets is that a single slice
 *		   (index 6 or 7) is set aside for metadb replicas.
 *
 *		If this condition does not hold, the slice is considered
 *		available for use by layout and 'used' is set to B_FALSE.
 */
static int
check_svm_slice_usage(
	char		*dsname,
	dm_descriptor_t slice,
	dm_descriptor_t disk,
	boolean_t	*used,
	dlist_t		**classes)
{
	boolean_t is_replica = B_FALSE;
	uint32_t index = 0;
	char	*diskname = NULL;
	int	error = 0;

	((error = slice_get_index(slice, &index)) != 0) ||
	(error = get_display_name(disk, &diskname)) ||
	(error = is_reserved_replica_slice_index(
		dsname, diskname, index, &is_replica));

	if (error == 0) {
	    if (is_replica == B_TRUE) {
		/* is replica slice -> used */
		save_slice_classification(dsname, slice, disk, DM_USE_SVM,
			gettext("reserved for metadb replicas"), classes);
		*used = B_TRUE;
	    } else {
		*used = B_FALSE;
	    }
	}

	return (error);
}

/*
 * FUNCTION:	save_slice_classification(char *dsname, dm_descriptor_t slice,
 *			dm_descriptor_t disk, char *used_by, char *usage_detail,
 *			dlist_t **classes)
 *
 * INPUT:	dsname	- a char * disk set name
 *		slice	- a dm_descriptor_t slice handle.
 *		disk	- a dm_descriptor_t handle for the slice's disk.
 *		used_by - a char * usage classification.
 *		usage_detail - a char * usage description for the slice.
 *
 * OUTPUT:	classes	- a list of slice_class_t updated to hold a usage
 *				entry for the input slicexs.
 *
 * SIDEEFFECT:	adds the input slice to the list of known, used slices.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise.
 *
 * PURPOSE:	Adds an entry to the
 *		appropriate slice_class_t list of slices.  If there is
 *		not an appropriate slice_class_t entry in the input list
 *		of classes, one is added.
 *
 *		As a performance optimization the slice usage classification
 *		information is only saved if verbose output was requested by
 *		the user.
 */
static int
save_slice_classification(
	char		*dsname,
	dm_descriptor_t	slice,
	dm_descriptor_t	disk,
	char		*usage,
	char		*usage_detail,
	dlist_t		**classes)
{
	int		error = 0;

	error = add_used_slice(slice);

	if ((error == 0) && (get_max_verbosity() >= OUTPUT_VERBOSE)) {

	    dlist_t		*iter;
	    dlist_t		*item;
	    slice_class_t 	*class = NULL;

	    /* locate class struct matching 'usage' */
	    for (iter = *classes; iter != NULL; iter = iter->next) {
		class = (slice_class_t *)iter->obj;
		if (string_case_compare(usage, class->usage) == 0) {
		    break;
		}
	    }

	    if (iter == NULL) {
		/* add a new class to the list of classes */
		class = (slice_class_t *)calloc(1, sizeof (slice_class_t));
		if (class == NULL) {
		    error = ENOMEM;
		} else {
		    class->usage = strdup(usage);
		    if (class->usage == NULL) {
			free(class);
			class = NULL;
			error = ENOMEM;
		    } else {
			item = dlist_new_item((void *)class);
			if (item == NULL) {
			    free(class->usage);
			    free(class);
			    class = NULL;
			    error = ENOMEM;
			} else {
			    *classes = dlist_append(item, *classes, AT_TAIL);
			}
		    }
		}
	    }

	    if ((error == 0) && (class != NULL)) {

		char buf[BUFSIZ];
		char *dup = NULL;
		char *slicename = NULL;

		(void) get_display_name(slice, &slicename);
		(void) snprintf(buf, BUFSIZ-1, "  %s: %s",
			slicename, usage_detail);
		if ((dup = strdup(buf)) == NULL) {
		    error = ENOMEM;
		} else {
		    if ((item = dlist_new_item((void *)dup)) == NULL) {
			free(dup);
			error = ENOMEM;
		    } else {
			class->sliceinfo =
			    dlist_insert_ordered(
				    item, class->sliceinfo,
				    ASCENDING, compare_strings);
		    }
		}
	    }
	}

	return (error);
}

/*
 * FUNCTION:	print_usable_devices()
 *
 * PURPOSE:	Print out the devices determined to be available for
 *		use by layout.
 *
 *		Iterates the lists of usable slices, disks and HBAs
 *		and prints out their CTD and device names.
 */
static void
print_usable_devices()
{
	int	i = 0;

	struct {
		char *msg;
		dlist_t *list;
	}	devs[3];

	devs[0].msg = gettext("HBA/Controllers");
	devs[0].list = _usable_hbas;
	devs[1].msg = gettext("disks");
	devs[1].list = _usable_disks;
	devs[2].msg = gettext("slices");
	devs[2].list = _usable_slices;

	for (i = 0; i < 3; i++) {

	    oprintf(OUTPUT_VERBOSE,
		    gettext("\n  These %s are usable:\n\n"),
		    devs[i].msg);

	    print_device_list(devs[i].list);
	}
}

/*
 * FUNCTION:	print_device_list(dlist_t *devices)
 *
 * INPUT:	devices	- a list of device descriptor handles
 *
 * PURPOSE:	A helper for the print_XXX_devices() routines which iterates
 *		the input list and prints out each device name, CTD name and
 *		alias(es).
 */
static void
print_device_list(
	dlist_t *devices)
{
	dlist_t *iter = NULL;

	for (iter = devices; iter != NULL; iter = iter->next) {

	    dm_descriptor_t device = ((uintptr_t)iter->obj);
	    char	*name = NULL;
	    char	*ctd = NULL;
	    dlist_t	*aliases = NULL;

	    (void) get_display_name(device, &ctd);
	    (void) get_name(device, &name);
	    oprintf(OUTPUT_VERBOSE,
		    "    %-25s %s\n", (ctd != NULL ? ctd : ""), name);

	    (void) get_aliases(device, &aliases);
	    for (; aliases != NULL; aliases = aliases->next) {
		oprintf(OUTPUT_VERBOSE,
			gettext("      (alias: %s)\n"),
			(char *)aliases->obj);
	    }

	    dlist_free_items(aliases, free);
	}
}

/*
 * FUNCTION:	print_unusable_devices(
 *			dlist_t *bad_slices, dlist_t *bad_disks,
 *			dlist_t	*used_classes)
 *
 * INPUT:	used_classes - a list of slice_class_t structs
 *
 * PURPOSE:	Print out the devices determined to be unavailable for
 *		use by layout.
 *
 *		Iterates the input list of slice classifications and prints
 *		out a description of the class and the slices so classified.
 *
 *		Also iterates the lists of bad slices and disks (those that
 *		libdiskmgt returned descriptors for but cannot be accessed)
 *		and notes them as unusable.
 */
static void
print_unusable_devices(
	dlist_t	*bad_slices,
	dlist_t	*bad_disks,
	dlist_t	*used_classes)
{
	dlist_t	*iter = NULL;
	dlist_t	*slices = NULL;
	char	*preamble;

	struct {
		char *msg;
		dlist_t *list;
	}	devs[2];

	/* report bad disks and slices */
	devs[0].msg = gettext("disks");
	devs[0].list = bad_disks;
	devs[1].msg = gettext("slices");
	devs[1].list = bad_slices;

	if (bad_disks != NULL) {
	    oprintf(OUTPUT_VERBOSE,
#if defined(sparc)
		    gettext("\n  These disks are not usable, they may "
			    "may be offline or cannot be accessed:\n\n"));
#elif defined(i386)
		    gettext("\n  These disks are not usable, they may "
			    "may be offline,\n  missing a Solaris FDISK "
			    "partition or cannot be accessed:\n\n"));
#endif
	    print_device_list(bad_disks);
	}

	if (bad_slices != NULL) {
	    oprintf(OUTPUT_VERBOSE, gettext(
		"\n  These slices, and subsequently the disks on which they\n"
		"reside, are not usable, they cannot be accessed:\n\n"));
	    print_device_list(bad_slices);
	}

	/* report used slices and usages */
	preamble = gettext("\n  These slices are not usable, %s:\n\n");
	for (iter = used_classes; iter != NULL; iter = iter->next) {
	    slice_class_t *class = (slice_class_t *)iter->obj;

	    if (class->sliceinfo != NULL) {

		oprintf(OUTPUT_VERBOSE, preamble,
			get_slice_usage_msg(class->usage));

		slices = class->sliceinfo;
		for (; slices != NULL; slices = slices->next) {
		    oprintf(OUTPUT_VERBOSE, "  %s\n", (char *)slices->obj);
		}
	    }
	}

}

/*
 * FUNCTION:	char * get_slice_usage_msg(char *usage)
 *
 * INPUT:	usage - char * string representing a slice usage classification
 *
 * OUTPUT:	char * "friendly" usage message
 *
 * PURPOSE:	the input usage string comes from libdiskmgt and is very terse.
 *
 *		Convert it into a friendlier usage description suitable for user
 *		consumption.
 */
static char *
get_slice_usage_msg(
	char *usage)
{
	char *str = NULL;

	if (string_case_compare(usage, DM_USE_MOUNT) == 0) {
	    str = gettext("they have mounted filesystems");
	} else if (string_case_compare(usage, DM_USE_FS) == 0) {
	    str = gettext("they appear to have unmounted filesystems");
	} else if (string_case_compare(usage, DM_USE_SVM) == 0) {
	    str = gettext("they are utilized by SVM");
	} else if (string_case_compare(usage, DM_USE_VXVM) == 0) {
	    str = gettext("they are utilized by VxVm");
	} else if (string_case_compare(usage, DM_USE_LU) == 0) {
	    str = gettext("they are utilized by LiveUpgrade");
	} else if (string_case_compare(usage, DM_USE_DUMP) == 0) {
	    str = gettext("they are reserved as dump devices");
	} else if (string_case_compare(usage, USE_DISKSET) == 0) {
	    str = gettext("they have disk set issues");
	} else {
	    /* libdiskmgt has detected a usage unknown to layout */
	    str = usage;
	}

	return (str);
}

/*
 * FUNCTION:	set_alias(dm_descriptor_t desc, char *alias)
 *
 * INPUT:	desc	- a dm_descriptor_t handle.
 *		alias	- a char * alias for the device represented
 *				by the descriptor.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Adds the specified alias to the known aliases for the
 *		device associated with the input descriptor.
 */
int
set_alias(
	dm_descriptor_t desc,
	char	*alias)
{
	nvlist_t	*attrs = NULL;
	char		**old_aliases = NULL;
	char		**new_aliases = NULL;
	uint_t		nelem = 0;
	int		error = 0;
	int		i = 0;

	if ((error = get_cached_attributes(desc, &attrs)) != 0) {
	    return (error);
	}

	if ((error = get_string_array(
	    attrs, ATTR_DEVICE_ALIASES, &old_aliases, &nelem)) != 0) {
	    if (error != ENOENT) {
		return (error);
	    }
	    /* no aliases yet */
	    error = 0;
	}

	/* add new alias */
	new_aliases = (char **)calloc(MAX_ALIASES, sizeof (char *));
	if (new_aliases != NULL) {

	    for (i = 0; i < nelem && i < MAX_ALIASES; i++) {
		char *dup = strdup(old_aliases[i]);
		if (dup != NULL) {
		    new_aliases[i] = dup;
		} else {
		    error = ENOMEM;
		}
	    }

	    if (error == 0) {
		if (i == MAX_ALIASES) {
		    volume_set_error(
			    gettext("Maximum number of device aliases "
				    "(8) reached\n"),
			    MAX_ALIASES);
		    error = -1;

		} else {
		    new_aliases[i] = alias;
		    error = set_string_array(attrs, ATTR_DEVICE_ALIASES,
			    new_aliases, i + 1);
		}
	    }

	    free(new_aliases);
	}

	if (error == 0) {
	    /* cache descriptor under this alias */
	    error = add_cached_descriptor(alias, desc);
	}

	return (error);
}

/*
 * FUNCTION:	get_aliases(dm_descriptor_t desc, dlist_t **list)
 *
 * INPUT:	desc	- a dm_descriptor_t handle.
 *
 * OUTPUT:	list	- a dlist_t list pointing to the list of
 *				aliases associated with the device
 *				represented by the descriptor.
 *
 * RETURNS:	int	- 0 on success
 *			 !0 otherwise
 *
 * PURPOSE:	Retrieves aliases for the input descriptor and
 *		appends them to the input list.
 *
 *		The list of returned items must be freed by calling
 *		dlist_free_items(list, free)
 */
int
get_aliases(
	dm_descriptor_t desc,
	dlist_t		**list)
{
	nvlist_t	*attrs = NULL;
	char		**aliases = NULL;
	uint_t		nelem = 0;
	int		error = 0;
	int		i;

	if ((error = get_cached_attributes(desc, &attrs)) != 0) {
	    return (error);
	}

	if ((error = get_string_array(
	    attrs, ATTR_DEVICE_ALIASES, &aliases, &nelem)) != 0) {
	    if (error == ENOENT) {
		/* no aliases */
		return (0);
	    }
	}

	for (i = 0; i < nelem; i++) {
	    dlist_t *item;
	    char *dup;

	    if ((dup = strdup(aliases[i])) == NULL) {
		error = ENOMEM;
		break;
	    }

	    if ((item = dlist_new_item(dup)) == NULL) {
		free(dup);
		error = ENOMEM;
		break;
	    }

	    *list = dlist_append(item, *list, AT_TAIL);
	}

	return (error);
}

/*
 * FUNCTION:	compare_start_blocks(
 *			void *obj1, void *obj2)
 *
 * INPUT:	desc1	- opaque pointer to a dm_descriptor_t
 * 		desc2	- opaque pointer to a dm_descriptor_t
 *
 * RETURNS:	int	- <0 - if desc1.stblk < desc2.stblk
 *			   0 - if desc1.stblk == desc2.stblk
 *			  >0 - if desc1.stblk > desc.stblk
 *
 * PURPOSE:	dlist_t helper which compares the start blocks of
 *		the two input dm_descriptor_t slice handles.
 */
static int
compare_start_blocks(
	void	*desc1,
	void	*desc2)
{
	uint64_t stblk1 = 0;
	uint64_t stblk2 = 0;

	assert(desc1 != (dm_descriptor_t)0);
	assert(desc2 != (dm_descriptor_t)0);

	(void) slice_get_start_block((uintptr_t)desc1, &stblk1);
	(void) slice_get_start_block((uintptr_t)desc2, &stblk2);

	return (stblk1 - stblk2);
}

/*
 * FUNCTION:	compare_desc_display_names(
 *			void *desc1, void *desc2)
 *
 * INPUT:	desc1	- opaque pointer to a dm_descriptor_t
 * 		desc2	- opaque pointer to a dm_descriptor_t
 *
 * RETURNS:	int	- <0 - if desc1.name < desc2.name
 *			   0 - if desc1.name == desc2.name
 *			  >0 - if desc1.name > desc.name
 *
 * PURPOSE:	dlist_t helper which compares the CTD names of the
 *		two input dm_descriptor_t objects.
 */
static int
compare_desc_display_names(
	void	*desc1,
	void	*desc2)
{
	char	*name1 = NULL;
	char	*name2 = NULL;

	assert(desc1 != (dm_descriptor_t)0);
	assert(desc2 != (dm_descriptor_t)0);

	(void) get_display_name((uintptr_t)desc1, &name1);
	(void) get_display_name((uintptr_t)desc2, &name2);

	return (string_case_compare(name1, name2));
}