/*
 * 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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include "xml_convert.h"

#include <errno.h>
#include <string.h>
#include <libintl.h>
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <locale.h>
#include <unistd.h>
#include "volume_error.h"
#include "volume_output.h"
#include "volume_string.h"

/*
 * IDs for localized messages in the generated command script
 */

#define	CMD_MSG_ENVIRONMENT		"Environment"
#define	CMD_MSG_AMEND_PATH		"Amend PATH"
#define	CMD_MSG_DISK_SET_NAME		"Disk set name"
#define	CMD_MSG_FUNCTIONS		"Functions"
/* CSTYLED */
#define	CMD_MSG_ECHO_AND_EXEC		"Echo (verbose) and exec given command, exit on error"
#define	CMD_MSG_GET_FULL_PATH		"Get full /dev/rdsk path of given slice"
/* CSTYLED */
#define	CMD_MSG_FMTHARD_SPECIAL		"Run fmthard, ignore partboot error, error if output"
#define	CMD_MSG_MAIN			"Main"
#define	CMD_MSG_VERIFY_ROOT		"Verify root"
#define	CMD_MSG_RUN_AS_ROOT		"This script must be run as root."
#define	CMD_MSG_CHECK_FOR_VERBOSE	"Check for verbose option"
#define	CMD_MSG_DOES_DISK_SET_EXIST	"Does the disk set exist?"
#define	CMD_MSG_TAKE_DISK_SET		"Take control of disk set"
#define	CMD_MSG_CREATE_THE_DISK_SET	"Create the disk set"
#define	CMD_MSG_ADD_DISKS_TO_SET	"Add disks to set"
#define	CMD_MSG_FORMAT_SLICES		"Format slices"
#define	CMD_MSG_CREATE			"Create {1} {2}"
#define	CMD_MSG_DOES_EXIST		"Does {1} exist?"
#define	CMD_MSG_ADD_SLICES_TO		"Add slices to {1}"
/* CSTYLED */
#define	CMD_MSG_ASSOCIATE_WITH_HSP	"Associate {1} {2} with hot spare pool {3}"

/*
 * ******************************************************************
 *
 * Data types
 *
 * ******************************************************************
 */

/*
 * Encapsulates the parsing of an XML attribute
 */
typedef struct {

	/* The name of the attribute */
	char *name;

	/*
	 * A function to validate and set the XML attribute value in
	 * the given devconfig_t structure.
	 *
	 * @param	    name
	 *		    the name of the XML attribute
	 *
	 * @param	    value
	 *		    the value of the XML attribute
	 *
	 * @return	    0 if the given value was valid and set
	 *		    successfully, non-zero otherwise.
	 */
	int (*validate_set)(devconfig_t *device, char *name, char *value);

	/*
	 * A function to get the XML attribute value in the given
	 * devconfig_t structure.
	 *
	 * @param	    name
	 *		    the name of the XML attribute
	 *
	 * @param	    value
	 *		    the value of the XML attribute
	 *
	 * @return	    0 if the given value was retrieved
	 *		    successfully, non-zero otherwise.
	 */
	int (*get_as_string)(devconfig_t *device, char *name, char **value);
} attr_t;

/*
 * Encapsulates the parsing of an XML element
 */
typedef struct {
	/* The name of the element */
	char *name;

	/* The type of element to set in the devconfig_t */
	component_type_t type;

	/*
	 * When converting from XML to a devconfig_t hierarchy,
	 * indicates whether to create a new devconfig_t structure in
	 * the hierarchy when this XML element is encountered.
	 */
	boolean_t is_hierarchical;

	/*
	 * If is_hierarchical is B_TRUE, whether to use an existing
	 * devconfig_t structure of this type when this element is
	 * encountered
	 */
	boolean_t singleton;

	/* The valid XML attributes for this element */
	attr_t *attributes;
} element_t;

typedef struct {
	char *msgid;
	char *message;
} l10nmessage_t;

/*
 * ******************************************************************
 *
 * Function prototypes
 *
 * ******************************************************************
 */

static int validate_doc(xmlDocPtr doc, const char *name, const char *systemID);
static int devconfig_to_xml(
    xmlNodePtr parent, element_t elements[], devconfig_t *device);
static int xml_to_devconfig(
    xmlNodePtr cur, element_t elements[], devconfig_t *device);
static int compare_is_a_diskset(void *obj1, void *obj2);
static xmlNodePtr xml_find_node(
    xmlNodePtr node, xmlChar *element, xmlChar *name);
static xmlDocPtr create_localized_message_doc();
static int create_localized_message_file(char **tmpfile);
static int strtobool(char *str, boolean_t *value);
static int ofprintf_terse(void *unused, char *fmt, ...);
static int ofprintf_verbose(void *unused, char *fmt, ...);

static int validate_set_size(
    devconfig_t *volume, char *attr, char *value);
static int validate_set_size_in_blocks(
    devconfig_t *slice, char *attr, char *value);
static int validate_set_diskset_name(
    devconfig_t *diskset, char *attr, char *name);
static int validate_add_available_name(
    devconfig_t *device, char *attr, char *name);
static int validate_add_unavailable_name(
    devconfig_t *device, char *attr, char *name);
static int validate_set_hsp_name(
    devconfig_t *hsp, char *attr, char *name);
static int validate_set_disk_name(
    devconfig_t *disk, char *attr, char *name);
static int validate_set_slice_name(
    devconfig_t *slice, char *attr, char *name);
static int validate_set_slice_start_block(
    devconfig_t *slice, char *attr, char *value);
static int validate_set_volume_name(
    devconfig_t *volume, char *attr, char *name);
static int validate_set_stripe_interlace(
    devconfig_t *stripe, char *attr, char *value);
static int validate_set_stripe_mincomp(
    devconfig_t *stripe, char *attr, char *value);
static int validate_set_stripe_maxcomp(
    devconfig_t *stripe, char *attr, char *value);
static int validate_set_volume_usehsp(
    devconfig_t *volume, char *attr, char *value);
static int validate_set_mirror_nsubmirrors(
    devconfig_t *mirror, char *attr, char *value);
static int validate_set_mirror_read(
    devconfig_t *mirror, char *attr, char *value);
static int validate_set_mirror_write(
    devconfig_t *mirror, char *attr, char *value);
static int validate_set_mirror_passnum(
    devconfig_t *mirror, char *attr, char *value);
static int validate_set_volume_redundancy(
    devconfig_t *volume, char *attr, char *value);
static int validate_set_volume_datapaths(
    devconfig_t *volume, char *attr, char *value);

static int get_as_string_name(
    devconfig_t *device, char *attr, char **value);
static int get_as_string_mirror_passnum(
    devconfig_t *mirror, char *attr, char **value);
static int get_as_string_mirror_read(
    devconfig_t *mirror, char *attr, char **value);
static int get_as_string_mirror_write(
    devconfig_t *mirror, char *attr, char **value);
static int get_as_string_size_in_blocks(
    devconfig_t *device, char *attr, char **value);
static int get_as_string_slice_start_block(
    devconfig_t *slice, char *attr, char **value);
static int get_as_string_stripe_interlace(
    devconfig_t *stripe, char *attr, char **value);

/*
 * ******************************************************************
 *
 * Data
 *
 * ******************************************************************
 */

/* Valid units for the size attribute */
units_t size_units[] = {
	{UNIT_KILOBYTES, BYTES_PER_KILOBYTE},
	{UNIT_MEGABYTES, BYTES_PER_MEGABYTE},
	{UNIT_GIGABYTES, BYTES_PER_GIGABYTE},
	{UNIT_TERABYTES, BYTES_PER_TERABYTE},
	{NULL, 0}
};

/* Valid units for the interlace attribute */
units_t interlace_units[] = {
	{UNIT_BLOCKS, BYTES_PER_BLOCK},
	{UNIT_KILOBYTES, BYTES_PER_KILOBYTE},
	{UNIT_MEGABYTES, BYTES_PER_MEGABYTE},
	{NULL, 0}
};

/* <diskset> attributes */
static attr_t diskset_attrs[] = {
	{ ATTR_NAME, validate_set_diskset_name, get_as_string_name },
	{ NULL, NULL, NULL }
};

/* <available> attributes */
static attr_t available_attrs[] = {
	{ ATTR_NAME, validate_add_available_name, NULL },
	{ NULL, NULL, NULL }
};

/* <unavailable> attributes */
static attr_t unavailable_attrs[] = {
	{ ATTR_NAME, validate_add_unavailable_name, NULL },
	{ NULL, NULL, NULL }
};

/* <hsp> attributes */
static attr_t hsp_attrs[] = {
	{ ATTR_NAME, validate_set_hsp_name, get_as_string_name },
	{ NULL, NULL, NULL }
};

/* <disk> attributes */
static attr_t disk_attrs[] = {
	{ ATTR_NAME, validate_set_disk_name, get_as_string_name },
	{ NULL, NULL, NULL }
};

/* <slice> attributes */
static attr_t slice_attrs[] = {
	{ ATTR_NAME, validate_set_slice_name, get_as_string_name },
	{ ATTR_SIZEINBLOCKS, validate_set_size_in_blocks,
	    get_as_string_size_in_blocks },
	{ ATTR_SLICE_STARTSECTOR, validate_set_slice_start_block,
	    get_as_string_slice_start_block },
	{ NULL, NULL, NULL }
};

/* <stripe> attributes */
static attr_t stripe_attrs[] = {
	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
	{ ATTR_STRIPE_MINCOMP, validate_set_stripe_mincomp, NULL },
	{ ATTR_STRIPE_MAXCOMP, validate_set_stripe_maxcomp, NULL },
	{ ATTR_STRIPE_INTERLACE, validate_set_stripe_interlace,
	    get_as_string_stripe_interlace },
	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
	{ NULL, NULL, NULL }
};

/* <concat> attributes */
static attr_t concat_attrs[] = {
	{ ATTR_NAME,   validate_set_volume_name, get_as_string_name },
	{ ATTR_SIZEINBYTES,   validate_set_size, NULL },
	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
	{ NULL, NULL, NULL }
};

/* <mirror> attributes */
static attr_t mirror_attrs[] = {
	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
	{ ATTR_MIRROR_NSUBMIRRORS, validate_set_mirror_nsubmirrors, NULL },
	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
	{ ATTR_MIRROR_READ, validate_set_mirror_read,
	    get_as_string_mirror_read },
	{ ATTR_MIRROR_WRITE, validate_set_mirror_write,
	    get_as_string_mirror_write },
	{ ATTR_MIRROR_PASSNUM, validate_set_mirror_passnum,
	    get_as_string_mirror_passnum },
	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
	{ NULL, NULL, NULL }
};

/* <volume> attributes */
static attr_t volume_attrs[] = {
	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
	{ ATTR_VOLUME_REDUNDANCY, validate_set_volume_redundancy, NULL },
	{ ATTR_VOLUME_FAULTRECOVERY, validate_set_volume_usehsp, NULL },
	{ ATTR_VOLUME_DATAPATHS, validate_set_volume_datapaths, NULL },
	{ NULL, NULL, NULL }
};

/* volume-request elements */
static element_t request_elements[] = {
	{ ELEMENT_DISKSET, TYPE_DISKSET, B_FALSE, B_FALSE, diskset_attrs },
	{ ELEMENT_AVAILABLE, TYPE_UNKNOWN, B_FALSE, B_FALSE, available_attrs },
	{ ELEMENT_UNAVAILABLE, TYPE_UNKNOWN, B_FALSE, B_FALSE,
	    unavailable_attrs },
	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_FALSE, hsp_attrs },
	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_FALSE, slice_attrs },
	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_FALSE, stripe_attrs },
	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_FALSE, concat_attrs },
	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_FALSE, mirror_attrs },
	{ ELEMENT_VOLUME, TYPE_VOLUME, B_TRUE, B_FALSE, volume_attrs },
	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
};

/* volume-defaults elements */
static element_t default_elements[] = {
	{ ELEMENT_DISKSET, TYPE_DISKSET, B_TRUE, B_FALSE, diskset_attrs },
	{ ELEMENT_AVAILABLE, TYPE_UNKNOWN, B_FALSE, B_TRUE, available_attrs },
	{ ELEMENT_UNAVAILABLE, TYPE_UNKNOWN, B_FALSE, B_TRUE,
	    unavailable_attrs },
	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_TRUE, hsp_attrs },
	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_TRUE, slice_attrs },
	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_TRUE, stripe_attrs },
	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_TRUE, concat_attrs },
	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_TRUE, mirror_attrs },
	{ ELEMENT_VOLUME, TYPE_VOLUME, B_TRUE, B_TRUE, volume_attrs },
	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
};

/* volume-config elements */
static element_t config_elements[] = {
	{ ELEMENT_DISKSET, TYPE_DISKSET, B_FALSE, B_FALSE, diskset_attrs },
	{ ELEMENT_DISK, TYPE_DRIVE, B_TRUE, B_FALSE, disk_attrs },
	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_FALSE, slice_attrs },
	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_FALSE, hsp_attrs },
	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_FALSE, stripe_attrs },
	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_FALSE, concat_attrs },
	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_FALSE, mirror_attrs },
	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
};

/*
 * ******************************************************************
 *
 * External functions
 *
 * ******************************************************************
 */

/*
 * Initialize the XML parser, setting defaults across all XML
 * routines.
 */
void
init_xml()
{
	/* COMPAT: Do not generate nodes for formatting spaces */
	LIBXML_TEST_VERSION
	xmlKeepBlanksDefault(0);

	/* Turn on line numbers for debugging */
	xmlLineNumbersDefault(1);

	/* Substitute entities as files are parsed */
	xmlSubstituteEntitiesDefault(1);

	/* Don't load external entity subsets */
	xmlLoadExtDtdDefaultValue = 0;

	/* Don't validate against DTD by default */
	xmlDoValidityCheckingDefaultValue = 0;

	/* Set up output handlers for XML parsing */
	xmlDefaultSAXHandler.warning = (warningSAXFunc)ofprintf_verbose;
	xmlDefaultSAXHandler.error  = (errorSAXFunc)ofprintf_terse;
	xmlDefaultSAXHandler.fatalError = (fatalErrorSAXFunc)ofprintf_terse;
}

/*
 * Clean up any remaining structures before exiting.
 */
void
cleanup_xml()
{
	xsltCleanupGlobals();
	xmlCleanupParser();
}

/*
 * Converts a volume-request XML document into a request_t.
 *
 * @param       doc
 *		an existing volume-request XML document
 *
 * @param       request
 *		RETURN: a new request_t which must be freed via
 *		free_request
 *
 * @return      0 on success, non-zero otherwise.
 */
int
xml_to_request(
	xmlDocPtr doc,
	request_t **request)
{
	int error = 0;

	*request = NULL;

	/* Validate doc against known DTD */
	if ((error = validate_doc(
	    doc, ELEMENT_VOLUMEREQUEST, VOLUME_REQUEST_DTD_LOC)) == 0) {

	    /* Create a request */
	    if ((error = new_request(request)) == 0) {

		/* Convert the XML doc into a request_t */
		error = xml_to_devconfig(xmlDocGetRootElement(doc),
		    request_elements, request_get_diskset_req(*request));
	    }
	}

	return (error);
}

/*
 * Converts a volume-defaults XML document into a defaults_t.
 *
 * @param       doc
 *		an existing volume-defaults XML document
 *
 * @param       defaults
 *		RETURN: a new defaults_t which must be freed via
 *		free_defaults
 *
 * @return      0 on success, non-zero otherwise.
 */
int
xml_to_defaults(
	xmlDocPtr doc,
	defaults_t **defaults)
{
	int error = 0;

	*defaults = NULL;

	/* Validate doc against known DTD */
	if ((error = validate_doc(doc, ELEMENT_VOLUMEDEFAULTS,
	    VOLUME_DEFAULTS_DTD_LOC)) == 0) {

	    /* Create request defaults */
	    if ((error = new_defaults(defaults)) == 0) {

		devconfig_t *global;

		/* Get defaults for all disk sets */
		if ((error = defaults_get_diskset_by_name(
		    *defaults, NULL, &global)) == 0) {

		    /* Populate the global devconfig_t from the XML doc */
		    if ((error = xml_to_devconfig(xmlDocGetRootElement(doc),
			default_elements, global)) == 0) {

			/* Get the components of the global devconfig_t */
			dlist_t *list = devconfig_get_components(global);

			/*
			 * Move all named disk set settings out from
			 * under global settings
			 */
			/* CONSTANTCONDITION */
			while (1) {
			    dlist_t *removed = NULL;
			    devconfig_t *component;

			    /* Remove named disk set from under global */
			    list = dlist_remove_equivalent_item(
				list, NULL, compare_is_a_diskset, &removed);

			    if (removed == NULL) {
				/* No named disk set found */
				break;
			    }

			    component = removed->obj;

			    /* Append named disk set to disk set list */
			    defaults_set_disksets(*defaults,
				dlist_append(dlist_new_item(component),
				defaults_get_disksets(*defaults), AT_TAIL));
			}
		    }
		}
	    }
	}

	return (error);
}

/*
 * Converts a volume-config XML document into a devconfig_t.
 *
 * @param       doc
 *		an existing volume-config XML document
 *
 * @param       config
 *		RETURN: a new devconfig_t which must be freed via
 *		free_devconfig
 *
 * @return      0 on success, non-zero otherwise.
 */
int
xml_to_config(
	xmlDocPtr doc,
	devconfig_t **config)
{
	int error = 0;

	*config = NULL;

	/* Validate doc against known DTD */
	if ((error = validate_doc(
	    doc, ELEMENT_VOLUMECONFIG, VOLUME_CONFIG_DTD_LOC)) == 0) {

	    /* Create a devconfig_t */
	    if ((error = new_devconfig(config, TYPE_DISKSET)) == 0) {

		/* Populate the devconfig_t from the XML doc */
		error = xml_to_devconfig(
		    xmlDocGetRootElement(doc), config_elements, *config);
	    }
	}

	return (error);
}

/*
 * Converts a devconfig_t into a volume-config XML document.
 *
 * @param       config
 *		an existing devconfig_t representing a volume
 *		configuration.
 *
 * @param       doc
 *		RETURN: a new volume-config XML document which must be
 *		freed via xmlFreeDoc
 *
 * @return      0 on success, non-zero otherwise.
 */
int
config_to_xml(
	devconfig_t *config,
	xmlDocPtr *doc)
{
	xmlNodePtr root;
	int error = 0;

	/* Create the XML document */
	*doc = xmlNewDoc((xmlChar *)"1.0");

	/* Create the root node */
	root = xmlNewDocNode(
	    *doc, NULL, (xmlChar *)ELEMENT_VOLUMECONFIG, NULL);
	xmlAddChild((xmlNodePtr)*doc, (xmlNodePtr)root);

	/* Create sub-nodes from the config devconfig_t */
	if ((error = devconfig_to_xml(root, config_elements, config)) == 0) {

	    /* Add DTD node and validate */
	    error = validate_doc(
		*doc, ELEMENT_VOLUMECONFIG, VOLUME_CONFIG_DTD_LOC);
	}

	if (error) {
	    xmlFreeDoc(*doc);
	}

	return (error);
}

/*
 * Converts a volume-config XML document into a Bourne shell script.
 *
 * @param       doc
 *		an existing volume-config XML document
 *
 * @param       commands
 *		RETURN: a new char* which must be freed
 *
 * @return      0 on success, non-zero otherwise.
 */
int
xml_to_commands(
	xmlDocPtr doc,
	char **commands)
{
	char *tmpfile = NULL;
	int error = 0;
	xsltStylesheetPtr style = NULL;

	/* Read in XSL stylesheet as a normal XML document */
	xmlDocPtr xsl_doc = xmlSAXParseFile((xmlSAXHandlerPtr)
	    &xmlDefaultSAXHandler, VOLUME_COMMAND_XSL_LOC, 0);

	if (xsl_doc != NULL && xsl_doc->xmlChildrenNode != NULL) {

		/*
		 * Find the "msgfile" variable node.  This is where
		 * we'll set the location of the file we'll create
		 * containing the localized messages.
		 */
	    xmlNodePtr msgfile_node = xml_find_node(
		xmlDocGetRootElement(xsl_doc), (xmlChar *)ELEMENT_VARIABLE,
		(xmlChar *)NAME_L10N_MESSAGE_FILE);

		/*
		 * Find the "lang" node.  This is where we'll set the
		 * current locale.
		 */
	    xmlNodePtr lang_node = xml_find_node(xmlDocGetRootElement(xsl_doc),
		(xmlChar *)ELEMENT_PARAM, (xmlChar *)NAME_LANG);

		/*
		 * Ignore if the nodes are not found -- the script
		 * will default to the C locale.
		 */
	    if (msgfile_node != NULL && lang_node != NULL) {
		/* Get/set current locale in the "lang" node */
		char *locale = setlocale(LC_MESSAGES, NULL);
		xmlNodeSetContent(lang_node, (xmlChar *)locale);

		/* Write localized messages to a temporary file */
		if ((error = create_localized_message_file(&tmpfile)) == 0) {

		    char *newsel;

		    /* Clear current value of select attribute, if any */
		    xmlChar *cursel = xmlGetProp(
			msgfile_node, (xmlChar *)ATTR_SELECT);
		    if (cursel != NULL) {
			xmlFree(cursel);
		    }

			/*
			 * The select attribute calls the XSLT function
			 * document() to load an external XML file
			 */
		    newsel = stralloccat(3, "document('", tmpfile, "')");

		    if (newsel == NULL) {
			volume_set_error(gettext("out of memory"));
			error = -1;
		    } else {

			/* Set the new value of the select attribute */
			xmlSetProp(msgfile_node,
			    (xmlChar *)ATTR_SELECT, (xmlChar *)newsel);

			free(newsel);
		    }
		}
	    }

	    if (error == 0) {
		style = xsltParseStylesheetDoc(xsl_doc);
	    }
	}

	if (style == NULL) {
	    volume_set_error(
		gettext("could not load stylesheet from %s"),
		VOLUME_COMMAND_XSL_LOC);
	    error = -1;
	} else {

	    xmlDocPtr result = xsltApplyStylesheet(style, doc, NULL);

	    if (result == NULL) {
		volume_set_error(
		    gettext("could not apply stylesheet to volume-config"));
		error = -1;
	    } else {
		int length;

		if (xsltSaveResultToString((xmlChar **)commands,
		    &length, result, style) == -1) {
		    error = ENOMEM;
		}
	    }

	    xsltFreeStylesheet(style);
	}

	if (tmpfile != NULL) {
	    /* Ignore failure */
	    unlink(tmpfile);

	    free(tmpfile);
	}

	return (error);
}

/*
 * ******************************************************************
 *
 * Static functions
 *
 * ******************************************************************
 */

/*
 * Sets the external DTD node in the given XML document and then
 * validates it.
 *
 * @param       doc
 *		an existing XML document
 *
 * @param       name
 *		the expected root element name of the XML document
 *
 * @param       systemID
 *		the location of the DTD
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_doc(
	xmlDocPtr doc,
	const char *name,
	const char *systemID)
{
	xmlValidCtxt context;
	xmlDtdPtr dtd;

	if (doc == NULL) {
	    volume_set_error(gettext("NULL %s document"), name);
	    return (-1);
	}

	/*
	 * Assume that we can't trust any DTD but our own.
	 */

	/* Was a DTD (external or internal) included in the document? */
	if ((dtd = xmlGetIntSubset(doc)) != NULL) {
	    /* Remove the DTD node */
	    oprintf(OUTPUT_DEBUG, gettext("Removing DTD from %s\n"), name);
	    xmlUnlinkNode((xmlNodePtr)dtd);
	    xmlFreeDtd(dtd);
	}

	/* Create the (external) DTD node */
	oprintf(OUTPUT_DEBUG,
	    gettext("Creating new external DTD for %s\n"), name);
	dtd = xmlCreateIntSubset(
	    doc, (xmlChar *)name, NULL, (xmlChar *)systemID);
	if (dtd == NULL) {
	    volume_set_error(
		gettext("could not create DTD node from %s"), systemID);
	    return (-1);
	}

	/* Validate against DTD */
	oprintf(OUTPUT_DEBUG, gettext("Validating %s against DTD\n"), name);
	context.userData = NULL;
	context.error = (xmlValidityErrorFunc)ofprintf_terse;
	context.warning = (xmlValidityWarningFunc)ofprintf_terse;
	if (!xmlValidateDocument(&context, doc)) {
	    volume_set_error(gettext("invalid %s"), name);
	    return (-1);
	}

	return (0);
}

/*
 * Converts a devconfig_t into an XML node subject to the rules in
 * the given element_t array.
 *
 * @param       parent
 *		the XML node to which to add new XML nodes resulting
 *		from conversion of the given devconfig_t
 *
 * @param       elements
 *		the element_ts that describe the structure of the XML
 *		document and govern the conversion of the given
 *		devconfig_t
 *
 * @param       device
 *		the devconfig_t to convert
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
devconfig_to_xml(
	xmlNodePtr parent,
	element_t elements[],
	devconfig_t *device)
{
	int i;
	int error = 0;
	xmlNodePtr node = NULL;

	/* Get device type */
	component_type_t type;
	if ((error = devconfig_get_type(device, &type)) != 0) {
	    return (error);
	}

	/* Search for this element definition */
	for (i = 0; elements[i].name != NULL; i++) {
	    element_t *element = &(elements[i]);

	    if (element->type == type) {
		int j;
		char **array;
		dlist_t *components;

		oprintf(OUTPUT_DEBUG, gettext("Element: %s\n"),
		    devconfig_type_to_str(type));

		/* Create the XML node */
		node = xmlNewChild(
		    parent, NULL, (xmlChar *)element->name, NULL);

		/* For each attribute defined for this element... */
		for (j = 0; element->attributes[j].name != NULL; j++) {
		    attr_t *attribute = &(element->attributes[j]);
		    char *value;

		    /* Is there a valid accessor for this attribute? */
		    if (attribute->get_as_string != NULL) {

			/* Get the attribute value from the device */
			switch (error = attribute->get_as_string(
			    device, attribute->name, &value)) {

			    /* Attribute is set in this device */
			    case 0:
				oprintf(OUTPUT_DEBUG, "    %s: %s\n",
				    attribute->name, value);

				/* Set the value in the XML node */
				xmlSetProp(node, (uchar_t *)attribute->name,
				    (uchar_t *)value);
				free(value);

			    /* FALLTHROUGH */

			    /* Attribute is not set in this device */
			    case ERR_ATTR_UNSET:

				error = 0;
				break;

			    /* Error */
			    default:
				return (error);
			}
		    }
		}

		/* Is this node hierarchical? */
		if (element->is_hierarchical == B_FALSE) {
		    node = parent;
		}

		/* Create <available> nodes */
		array = devconfig_get_available(device);
		if (array != NULL) {
		    for (j = 0; array[j] != NULL; j++) {
			xmlNodePtr child = xmlNewChild(
			    node, NULL, (xmlChar *)ELEMENT_AVAILABLE, NULL);
			xmlSetProp(child,
			    (xmlChar *)ATTR_NAME, (xmlChar *)array[j]);
		    }
		}

		/* Create <unavailable> nodes */
		array = devconfig_get_unavailable(device);
		if (array != NULL) {
		    for (j = 0; array[j] != NULL; j++) {
			xmlNodePtr child = xmlNewChild(
			    node, NULL, (xmlChar *)ELEMENT_UNAVAILABLE, NULL);
			xmlSetProp(child,
			    (xmlChar *)ATTR_NAME, (xmlChar *)array[j]);
		    }
		}

		/*
		 * Recursively convert subcomponents of this device to
		 * XML, taking care to encode them in the order
		 * specified in the element_t list (which should
		 * mirror what's expected by the DTD).
		 */

		/* For each element type... */
		for (j = 0; elements[j].name != NULL; j++) {

		    /* For each component of this device... */
		    for (components = devconfig_get_components(device);
			components != NULL && error == 0;
			components = components->next) {

			devconfig_t *component = (devconfig_t *)components->obj;
			component_type_t t;

			/* Are the types the same? */
			if ((error = devconfig_get_type(component, &t)) != 0) {
			    return (error);
			} else {
			    if (elements[j].type == t) {
				/* Encode child */
				error = devconfig_to_xml(
				    node, elements, component);
			    }
			}
		    }
		}

		/* Element found */
		break;
	    }
	}

	/* Was this device successfully converted? */
	if (node == NULL) {
	    volume_set_error(
		gettext("can't convert device of type \"%s\" to XML element"),
		devconfig_type_to_str(type));
	    error = -1;
	}

	return (error);
}

/*
 * Converts an XML node into a devconfig_t subject to the rules in
 * the given element_t array.
 *
 * @param       cure
 *		the existing XML node to convert
 *
 * @param       elements
 *		the element_ts that describe the structure of the XML
 *		document and govern the conversion of the given XML
 *		node
 *
 * @param       device
 *		the devconfig_t node to which to add new devconfig_ts
 *		resulting from conversion of the given XML node
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
xml_to_devconfig(
	xmlNodePtr cur,
	element_t elements[],
	devconfig_t *device)
{
	int error = 0;

	/* For each child node... */
	for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
	    int i;
	    boolean_t parsed_elem = B_FALSE;

	    /* Search for this element definition */
	    for (i = 0; elements[i].name != NULL; i++) {
		element_t *element = &(elements[i]);

		if (xmlStrcmp(cur->name, (xmlChar *)element->name) == 0) {
		    int j;
		    devconfig_t *component = NULL;

		    /* Flag that this element has been parsed */
		    parsed_elem = B_TRUE;

		    oprintf(OUTPUT_DEBUG, gettext("line %d: Element <%s>\n"),
			    XML_GET_LINE(cur), cur->name);

		    /* Should a new device be created for this element? */
		    if (element->is_hierarchical == B_TRUE) {

			/* Should we use an existing device of this type? */
			if (element->singleton) {
			    devconfig_get_component(
				device, element->type, &component, B_FALSE);
			}

			if (component == NULL) {
			    oprintf(OUTPUT_DEBUG,
				gettext("Creating new device\n"));

			    /* Create device of this type */
			    if ((error = new_devconfig(
				    &component, element->type)) != 0) {
				return (error);
			    }

			    /* Add component to the toplevel device */
			    devconfig_set_components(
				device, dlist_append(dlist_new_item(component),
				devconfig_get_components(device), AT_TAIL));
			}
		    } else {
			component = device;
		    }

		    /* For each attribute defined for this element... */
		    for (j = 0; element->attributes[j].name != NULL; j++) {
			attr_t *attribute = &(element->attributes[j]);

			/* Get the value of this attribute */
			char *value = (char *)
			    xmlGetProp(cur, (xmlChar *)attribute->name);

			/* Was this attribute specified? */
			if (value != NULL) {
			    oprintf(OUTPUT_DEBUG,
				gettext("line %d:\tAttribute %s=%s\n"),
				XML_GET_LINE(cur), attribute->name, value);

			    /* Set this value in the device */
			    if ((error = attribute->validate_set(
				component, attribute->name, value)) != 0) {
				return (error);
			    }
			}
		    }

		    /* Get recursive sub-elements */
		    if ((error = xml_to_devconfig(
			cur, elements, component)) != 0) {
			return (error);
		    }

		    /* Element found */
		    break;
		}
	    }


	    /* Make sure all non-text/comment elements were parsed */
	    if (parsed_elem == B_FALSE &&
		xmlStrcmp(cur->name, (xmlChar *)ELEMENT_TEXT) != 0 &&
		xmlStrcmp(cur->name, (xmlChar *)ELEMENT_COMMENT) != 0) {

		oprintf(OUTPUT_DEBUG, gettext("Element <%s> NOT PARSED!!!\n"),
		    cur->name);
	    }
	}

	return (0);
}

/*
 * Returns 0 if obj2 (devconfig_t *) is a disk set, 1 otherwise.
 */
static int
compare_is_a_diskset(
	void *obj1,
	void *obj2)
{
	return (devconfig_isA(
	    (devconfig_t *)obj2, TYPE_DISKSET) == B_TRUE ? 0 : 1);
}

/*
 * Recursively searches the given xmlNodePtr for an element of the
 * specified type and name.
 *
 * @param       node
 *              the root node to search
 *
 * @param       element
 *              the name of the element type
 *
 * @param       name
 *              the value of the name attribute
 *
 * @return      a valid xmlNodePtr if an element of the specified
 *              type and name was found, NULL otherwise.
 */
static xmlNodePtr
xml_find_node(
	xmlNodePtr node,
	xmlChar *element,
	xmlChar *name)
{
	xmlNodePtr child;

	/* Is the element the right type? */
	if (xmlStrcmp(element, node->name) == 0 &&

	    /* Does this element's name attribute match? */
	    xmlStrcmp(name, xmlGetProp(node, (xmlChar *)ATTR_NAME)) == 0) {

	    return (node);
	}

	/* Check child nodes */
	for (child = node->xmlChildrenNode; child != NULL;
	    child = child->next) {
	    xmlNodePtr found = xml_find_node(child, element, name);

	    if (found != NULL) {
		return (found);
	    }
	}

	return (NULL);
}

/*
 * Creates an XML document containing all of the localized message
 * strings for the generated command script.
 *
 * @return      a xmlDocPtr which must be freed via xmlFreeDoc
 */
static xmlDocPtr
create_localized_message_doc()
{
	int i;
	char *locale;
	xmlDocPtr doc;
	xmlNodePtr root;
	l10nmessage_t _cmd_messages[21];

	/* Create the XML document */
	doc = xmlNewDoc((xmlChar *)"1.0");

	/* Create the root node */
	root = xmlNewDocNode(
	    doc, NULL, (xmlChar *)ELEMENT_L10N, NULL);
	xmlAddChild((xmlNodePtr) doc, (xmlNodePtr)root);

	_cmd_messages[0].msgid = CMD_MSG_ENVIRONMENT;
	_cmd_messages[0].message = gettext(CMD_MSG_ENVIRONMENT);
	_cmd_messages[1].msgid = CMD_MSG_AMEND_PATH;
	_cmd_messages[1].message = gettext(CMD_MSG_AMEND_PATH);
	_cmd_messages[2].msgid = CMD_MSG_DISK_SET_NAME;
	_cmd_messages[2].message = gettext(CMD_MSG_DISK_SET_NAME);
	_cmd_messages[3].msgid = CMD_MSG_FUNCTIONS;
	_cmd_messages[3].message = gettext(CMD_MSG_FUNCTIONS);
	_cmd_messages[4].msgid = CMD_MSG_ECHO_AND_EXEC;
	_cmd_messages[4].message = gettext(CMD_MSG_ECHO_AND_EXEC);
	_cmd_messages[5].msgid = CMD_MSG_FMTHARD_SPECIAL;
	_cmd_messages[5].message = gettext(CMD_MSG_FMTHARD_SPECIAL);
	_cmd_messages[6].msgid = CMD_MSG_GET_FULL_PATH;
	_cmd_messages[6].message = gettext(CMD_MSG_GET_FULL_PATH);
	_cmd_messages[7].msgid = CMD_MSG_MAIN;
	_cmd_messages[7].message = gettext(CMD_MSG_MAIN);
	_cmd_messages[8].msgid = CMD_MSG_VERIFY_ROOT;
	_cmd_messages[8].message = gettext(CMD_MSG_VERIFY_ROOT);
	_cmd_messages[9].msgid = CMD_MSG_RUN_AS_ROOT;
	_cmd_messages[9].message = gettext(CMD_MSG_RUN_AS_ROOT);
	_cmd_messages[10].msgid = CMD_MSG_CHECK_FOR_VERBOSE;
	_cmd_messages[10].message = gettext(CMD_MSG_CHECK_FOR_VERBOSE);
	_cmd_messages[11].msgid = (CMD_MSG_DOES_DISK_SET_EXIST);
	_cmd_messages[11].message = gettext(CMD_MSG_DOES_DISK_SET_EXIST);
	_cmd_messages[12].msgid = (CMD_MSG_TAKE_DISK_SET);
	_cmd_messages[12].message = gettext(CMD_MSG_TAKE_DISK_SET);
	_cmd_messages[13].msgid = (CMD_MSG_CREATE_THE_DISK_SET);
	_cmd_messages[13].message = gettext(CMD_MSG_CREATE_THE_DISK_SET);
	_cmd_messages[14].msgid = (CMD_MSG_ADD_DISKS_TO_SET);
	_cmd_messages[14].message = gettext(CMD_MSG_ADD_DISKS_TO_SET);
	_cmd_messages[15].msgid = (CMD_MSG_FORMAT_SLICES);
	_cmd_messages[15].message = gettext(CMD_MSG_FORMAT_SLICES);
	_cmd_messages[16].msgid = (CMD_MSG_CREATE);
	_cmd_messages[16].message = gettext(CMD_MSG_CREATE);
	_cmd_messages[17].msgid = (CMD_MSG_DOES_EXIST);
	_cmd_messages[17].message = gettext(CMD_MSG_DOES_EXIST);
	_cmd_messages[18].msgid = (CMD_MSG_ADD_SLICES_TO);
	_cmd_messages[18].message = gettext(CMD_MSG_ADD_SLICES_TO);
	_cmd_messages[19].msgid = (CMD_MSG_ASSOCIATE_WITH_HSP);
	_cmd_messages[19].message = gettext(CMD_MSG_ASSOCIATE_WITH_HSP);
	_cmd_messages[20].msgid = NULL;

	/* Get/set current locale in the "lang" node */
	locale = setlocale(LC_MESSAGES, NULL);

	/* Add localized <message> elements to stylesheet */
	for (i = 0; _cmd_messages[i].msgid != NULL; i++) {
	    xmlNsPtr ns = xmlNewNs(NULL, NULL, NULL);

	    xmlNodePtr node = xmlNewTextChild(
		root, ns, (xmlChar *)ELEMENT_MESSAGE,
		(xmlChar *)_cmd_messages[i].message);
	    /* Lang attribute */
	    xmlSetProp(node,
		(xmlChar *)ATTR_LANG, (xmlChar *)locale);

	    /* Message ID attribute */
	    xmlSetProp(node, (xmlChar *)ATTR_MESSAGEID,
		(xmlChar *)_cmd_messages[i].msgid);
	}

	if (get_max_verbosity() >= OUTPUT_DEBUG) {
	    xmlChar *text;
	    /* Get the text dump */
	    xmlDocDumpFormatMemory(doc, &text, NULL, 1);
	    oprintf(OUTPUT_DEBUG,
		gettext("Generated message file:\n%s"), text);
	    xmlFree(text);
	}

	return (doc);
}

/*
 * Creates a temporary XML file containing all of the localized
 * message strings for the generated command script.
 *
 * @param       tmpfile
 *		RETURN: the name of the temporary XML file
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
create_localized_message_file(
	char **tmpfile)
{
	int error = 0;

	/*
	 * Create temporary file name -- "XXXXXX" is replaced with
	 * unique char sequence by mkstemp()
	 */
	*tmpfile = stralloccat(3, "/tmp/", ELEMENT_L10N, "XXXXXX");

	if (*tmpfile == NULL) {
	    volume_set_error(gettext("out of memory"));
	    error = -1;
	} else {
	    int fildes;
	    FILE *msgfile = NULL;

	    /* Open temp file */
	    if ((fildes = mkstemp(*tmpfile)) != -1) {
		msgfile = fdopen(fildes, "w");
	    }

	    if (msgfile == NULL) {
		volume_set_error(gettext(
		    "could not open file for writing: %s"), *tmpfile);
		error = -1;
	    } else {

		xmlChar *text;
		xmlDocPtr message_doc = create_localized_message_doc();
		xmlDocDumpFormatMemory(message_doc, &text, NULL, 1);

		if (fprintf(msgfile, "%s", text) < 0) {
		    volume_set_error(gettext(
			"could not create localized message file: %s"),
			*tmpfile);
		    error = -1;
		}

		xmlFree(text);
		xmlFreeDoc(message_doc);
	    }

	    fclose(msgfile);
	}

	return (error);
}

/*
 * Converts the given string into a boolean.  The string must be
 * either VALID_ATTR_TRUE or VALID_ATTR_FALSE.
 *
 * @param       str
 *              the string to convert
 *
 * @param       bool
 *              the addr of the boolean_t
 *
 * @return      0 if the given string could be converted to a boolean
 *              non-zero otherwise.
 */
static int
strtobool(
	char *str,
	boolean_t *value)
{
	int error = 0;

	if (strcmp(str, VALID_ATTR_TRUE) == 0) {
	    *value = B_TRUE;
	} else

	if (strcmp(str, VALID_ATTR_FALSE) == 0) {
	    *value = B_FALSE;
	} else

	    error = -1;

	return (error);
}

/*
 * Wrapper for oprintf with a OUTPUT_TERSE level of verbosity.
 * Provides an fprintf-like syntax to enable use as substitute output
 * handler for man of the XML commands.
 *
 * @param       unused
 *		unused, in favor of the FILE* passed to
 *		set_max_verbosity().
 *
 * @param       fmt
 *		a printf-style format string
 *
 * @return      the number of characters output
 */
static int
ofprintf_terse(
	void *unused,
	char *fmt,
	...)
{
	int ret;
	va_list ap;

	va_start(ap, fmt);
	ret = oprintf_va(OUTPUT_TERSE, fmt, ap);
	va_end(ap);

	return (ret);
}

/*
 * Wrapper for oprintf with a OUTPUT_VERBOSE level of verbosity.
 * Provides an fprintf-like syntax to enable use as substitute output
 * handler for man of the XML commands.
 *
 * @param       unused
 *		unused, in favor of the FILE* passed to
 *		set_max_verbosity().
 *
 * @param       fmt
 *		a printf-style format string
 *
 * @return      the number of characters output
 */
static int
ofprintf_verbose(
	void *unused,
	char *fmt,
	...)
{
	int ret;
	va_list ap;

	va_start(ap, fmt);
	ret = oprintf_va(OUTPUT_VERBOSE, fmt, ap);
	va_end(ap);

	return (ret);
}

/*
 * ******************************************************************
 *
 * XML attribute validators/mutators
 *
 * These functions convert the given XML attribute string to the
 * appropriate data type, and then pass it on to the appropriate
 * devconfig_t mutator.  A non-zero status is returned if the given
 * string could not be converted or was invalid.
 *
 * ******************************************************************
 */

/*
 * Validate and set the size attribute in the given volume
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the size
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_size(
	devconfig_t *volume,
	char *attr,
	char *value)
{
	int error;
	uint64_t size = 0;

	/* Convert size string to bytes */
	if ((error = sizestr_to_bytes(value, &size, size_units)) != 0) {
	    return (error);
	}

	/* Set size in volume */
	return (devconfig_set_size(volume, size));
}

/*
 * Validate and set the size_in_blocks attribute in the given slice
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the size_in_blocks
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_size_in_blocks(
	devconfig_t *slice,
	char *attr,
	char *value)
{
	long long size;

	/* Convert string to long long */
	if (sscanf(value, "%lld", &size) != 1) {
	    volume_set_error(gettext("%s: invalid size in blocks"), value);
	    return (-1);
	}

	/* Set the number of submirrors in the slice */
	return (devconfig_set_size_in_blocks(slice, (uint64_t)size));
}

/*
 * Validate and set the name attribute in the given diskset
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_diskset_name(
	devconfig_t *diskset,
	char *attr,
	char *name)
{
	return (devconfig_set_diskset_name(diskset, name));
}

/*
 * Validate and add the given name to the list of available devices in
 * the given volume devconfig_t.
 *
 * @param       device
 *		the devconfig_t whose available device list to modify
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_add_available_name(
	devconfig_t *device,
	char *attr,
	char *name)
{
	char **available;

	/* Get available devices for this device */
	available = devconfig_get_available(device);

	/* Try to add name to array via realloc */
	if ((available = append_to_string_array(available, name)) == NULL) {
	    return (ENOMEM);
	}

	/* Set available devices in the device */
	devconfig_set_available(device, available);

	return (0);
}

/*
 * Validate and add the given name to the list of unavailable devices
 * in the given volume devconfig_t.
 *
 * @param       device
 *		the devconfig_t whose unavailable device list to modify
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_add_unavailable_name(
	devconfig_t *device,
	char *attr,
	char *name)
{
	char **unavailable;

	/* Get unavailable devices for this device */
	unavailable = devconfig_get_unavailable(device);

	/* Try to add name to array via realloc */
	if ((unavailable = append_to_string_array(unavailable, name)) == NULL) {
	    return (ENOMEM);
	}

	/* Set unavailable devices in the device */
	devconfig_set_unavailable(device, unavailable);

	return (0);
}

/*
 * Validate and set the name attribute in the given hsp devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_hsp_name(
	devconfig_t *hsp,
	char *attr,
	char *name)
{
	return (devconfig_set_hsp_name(hsp, name));
}

/*
 * Validate and set the name attribute in the given disk devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_disk_name(
	devconfig_t *disk,
	char *attr,
	char *name)
{
	return (devconfig_set_name(disk, name));
}

/*
 * Validate and set the name attribute in the given slice devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_slice_name(
	devconfig_t *slice,
	char *attr,
	char *name)
{
	return (devconfig_set_name(slice, name));
}

/*
 * Validate and set the start_block attribute in the given slice
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the start_block
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_slice_start_block(
	devconfig_t *slice,
	char *attr,
	char *value)
{
	long long startsector;

	/* Convert string to long long */
	if (sscanf(value, "%lld", &startsector) != 1) {
	    volume_set_error(gettext("%s: invalid start sector"), value);
	    return (-1);
	}

	/* Set the number of submirrors in the slice */
	return (devconfig_set_slice_start_block(slice, (uint64_t)startsector));
}

/*
 * Validate and set the name attribute in the given volume
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       name
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_volume_name(
	devconfig_t *volume,
	char *attr,
	char *name)
{
	return (devconfig_set_volume_name(volume, name));
}

/*
 * Validate and set the interlace attribute in the given stripe
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the interlace
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_stripe_interlace(
	devconfig_t *stripe,
	char *attr,
	char *value)
{
	int error;
	uint64_t interlace = 0;

	/* Convert interlace string to bytes */
	if ((error = sizestr_to_bytes(
		value, &interlace, interlace_units)) != 0) {
	    return (error);
	}

	/* Set interlace in stripe */
	return (devconfig_set_stripe_interlace(stripe, interlace));
}

/*
 * Validate and set the mincomp attribute in the given stripe
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the mincomp
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_stripe_mincomp(
	devconfig_t *stripe,
	char *attr,
	char *value)
{
	uint16_t mincomp;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &mincomp) != 0) {
	    volume_set_error(
		gettext("invalid minimum stripe components (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_stripe_mincomp(stripe, mincomp));
}

/*
 * Validate and set the maxcomp attribute in the given stripe
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the maxcomp
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_stripe_maxcomp(
	devconfig_t *stripe,
	char *attr,
	char *value)
{
	uint16_t maxcomp;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &maxcomp) != 0) {
	    volume_set_error(
		gettext("invalid maximum stripe components (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_stripe_maxcomp(stripe, maxcomp));
}

/*
 * Validate and set the usehsp attribute in the given volume
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the usehsp
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_volume_usehsp(
	devconfig_t *volume,
	char *attr,
	char *value)
{
	boolean_t usehsp;

	/* Get boolean value */
	if (strtobool(value, &usehsp) != 0) {
	    volume_set_error(
		gettext("%s: invalid boolean value for \"%s\" attribute"),
		value, attr);
	    return (-1);
	}

	/* Set in volume */
	return (devconfig_set_volume_usehsp(volume, usehsp));
}

/*
 * Validate and set the nsubmirrors attribute in the given mirror
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the nsubmirrors
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_mirror_nsubmirrors(
	devconfig_t *mirror,
	char *attr,
	char *value)
{
	uint16_t nsubmirrors;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &nsubmirrors) != 0) {
	    volume_set_error(
		gettext("invalid number of submirrors (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_mirror_nsubs(mirror, nsubmirrors));
}

/*
 * Validate and set the read attribute in the given mirror
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the read
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_mirror_read(
	devconfig_t *mirror,
	char *attr,
	char *value)
{
	mirror_read_strategy_t strategy;

	if (strcmp(value, VALID_MIRROR_READ_ROUNDROBIN) == 0) {
	    strategy = MIRROR_READ_ROUNDROBIN;
	} else

	if (strcmp(value, VALID_MIRROR_READ_GEOMETRIC) == 0) {
	    strategy = MIRROR_READ_GEOMETRIC;
	} else

	if (strcmp(value, VALID_MIRROR_READ_FIRST) == 0) {
	    strategy = MIRROR_READ_FIRST;
	} else

	{
	    volume_set_error(gettext("%s: invalid mirror read value"), value);
	    return (-1);
	}

	return (devconfig_set_mirror_read(mirror, strategy));
}

/*
 * Validate and set the write attribute in the given mirror
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the write
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_mirror_write(
	devconfig_t *mirror,
	char *attr,
	char *value)
{
	mirror_write_strategy_t strategy;

	if (strcmp(value, VALID_MIRROR_WRITE_PARALLEL) == 0) {
	    strategy = MIRROR_WRITE_PARALLEL;
	} else

	if (strcmp(value, VALID_MIRROR_WRITE_SERIAL) == 0) {
	    strategy = MIRROR_WRITE_SERIAL;
	} else

	{
	    volume_set_error(gettext("%s: invalid mirror write value"), value);
	    return (-1);
	}

	return (devconfig_set_mirror_write(mirror, strategy));
}

/*
 * Validate and set the passnum attribute in the given mirror
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the passnum
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_mirror_passnum(
	devconfig_t *mirror,
	char *attr,
	char *value)
{
	uint16_t passnum;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &passnum) != 0) {
	    volume_set_error(
		gettext("invalid mirror pass number (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_mirror_pass(mirror, passnum));
}

/*
 * Validate and set the redundancy attribute in the given volume
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the redundancy
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_volume_redundancy(
	devconfig_t *volume,
	char *attr,
	char *value)
{
	uint16_t redundancy;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &redundancy) != 0) {
	    volume_set_error(
		gettext("invalid redundancy level (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_volume_redundancy_level(volume, redundancy));
}

/*
 * Validate and set the datapaths attribute in the given volume
 * devconfig_t.
 *
 * @param       volume
 *		the devconfig_t in which to set the datapaths
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
validate_set_volume_datapaths(
	devconfig_t *volume,
	char *attr,
	char *value)
{
	uint16_t redundancy;

	/* Convert string to a uint16_t */
	if (str_to_uint16(value, &redundancy) != 0) {
	    volume_set_error(
		gettext("invalid number of data paths (%s): %s"),
		attr, value);
	    return (-1);
	}

	/* Set in stripe */
	return (devconfig_set_volume_npaths(volume, redundancy));
}

/*
 * ******************************************************************
 *
 * XML attribute accessors/converters
 *
 * These functions get a value from the appropriate devconfig_t
 * accessor, and then convert it to a string.
 *
 * ******************************************************************
 */

/*
 * Get, as a string, the value of the name attribute of the given
 * devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the name
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_name(
	devconfig_t *device,
	char *attr,
	char **value)
{
	int error;
	char *name;

	/* Get name */
	if ((error = devconfig_get_name(device, &name)) == 0) {
	    if ((*value = strdup(name)) == NULL) {
		error = ENOMEM;
	    }
	}

	return (error);
}

/*
 * Get, as a string, the value of the passnum attribute of the given
 * mirror devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the passnum
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_mirror_passnum(
	devconfig_t *mirror,
	char *attr,
	char **value)
{
	int error;
	uint16_t passnum;

	/* Get mirror pass number */
	if ((error = devconfig_get_mirror_pass(mirror, &passnum)) == 0) {
	    error = ll_to_str(passnum, value);
	}

	return (error);
}

/*
 * Get, as a string, the value of the read attribute of the given
 * mirror devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the read
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_mirror_read(
	devconfig_t *mirror,
	char *attr,
	char **value)
{
	int error;
	mirror_read_strategy_t read;

	/* Get mirror read strategy */
	if ((error = devconfig_get_mirror_read(mirror, &read)) == 0) {
	    if ((*value = strdup(
		devconfig_read_strategy_to_str(read))) == NULL) {
		error = ENOMEM;
	    }
	}

	return (error);
}

/*
 * Get, as a string, the value of the write attribute of the given
 * mirror devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the write
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_mirror_write(
	devconfig_t *mirror,
	char *attr,
	char **value)
{
	int error;
	mirror_write_strategy_t write;

	/* Get mirror write strategy */
	if ((error = devconfig_get_mirror_write(mirror, &write)) == 0) {
	    if ((*value = strdup(
		devconfig_write_strategy_to_str(write))) == NULL) {
		error = ENOMEM;
	    }
	}

	return (error);
}

/*
 * Get, as a string, the value of the in_blocks attribute of the given
 * device devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the in_blocks
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_size_in_blocks(
	devconfig_t *device,
	char *attr,
	char **value)
{
	int error;
	uint64_t size;

	/* Get size in blocks */
	if ((error = devconfig_get_size_in_blocks(device, &size)) == 0) {
	    error = ll_to_str(size, value);
	}

	return (error);
}

/*
 * Get, as a string, the value of the start_block attribute of the
 * given slice devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the start_block
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_slice_start_block(
	devconfig_t *slice,
	char *attr,
	char **value)
{
	int error;
	uint64_t start;

	/* Get slice start block */
	if ((error = devconfig_get_slice_start_block(slice, &start)) == 0) {
	    error = ll_to_str(start, value);
	}

	return (error);
}

/*
 * Get, as a string, the value of the interlace attribute of the given
 * stripe devconfig_t.  This data must be freed.
 *
 * @param       device
 *		the devconfig_t from which to retrieve the interlace
 *
 * @param       attr
 *		the name of the XML attribute
 *
 * @param       value
 *		RETURN: the value of the XML attribute
 *
 * @return      0 on success, non-zero otherwise.
 */
static int
get_as_string_stripe_interlace(
	devconfig_t *stripe,
	char *attr,
	char **value)
{
	int error;
	uint64_t interlace;

	/* Get interlace */
	if ((error = devconfig_get_stripe_interlace(
		stripe, &interlace)) == 0) {
	    error = bytes_to_sizestr(interlace, value, interlace_units, B_TRUE);
	}

	return (error);
}