1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include "xml_convert.h"
30
31#include <errno.h>
32#include <string.h>
33#include <libintl.h>
34#include <libxslt/xslt.h>
35#include <libxslt/xsltInternals.h>
36#include <libxslt/transform.h>
37#include <libxslt/xsltutils.h>
38#include <locale.h>
39#include <unistd.h>
40#include "volume_error.h"
41#include "volume_output.h"
42#include "volume_string.h"
43
44/*
45 * IDs for localized messages in the generated command script
46 */
47
48#define	CMD_MSG_ENVIRONMENT		"Environment"
49#define	CMD_MSG_AMEND_PATH		"Amend PATH"
50#define	CMD_MSG_DISK_SET_NAME		"Disk set name"
51#define	CMD_MSG_FUNCTIONS		"Functions"
52/* CSTYLED */
53#define	CMD_MSG_ECHO_AND_EXEC		"Echo (verbose) and exec given command, exit on error"
54#define	CMD_MSG_GET_FULL_PATH		"Get full /dev/rdsk path of given slice"
55/* CSTYLED */
56#define	CMD_MSG_FMTHARD_SPECIAL		"Run fmthard, ignore partboot error, error if output"
57#define	CMD_MSG_MAIN			"Main"
58#define	CMD_MSG_VERIFY_ROOT		"Verify root"
59#define	CMD_MSG_RUN_AS_ROOT		"This script must be run as root."
60#define	CMD_MSG_CHECK_FOR_VERBOSE	"Check for verbose option"
61#define	CMD_MSG_DOES_DISK_SET_EXIST	"Does the disk set exist?"
62#define	CMD_MSG_TAKE_DISK_SET		"Take control of disk set"
63#define	CMD_MSG_CREATE_THE_DISK_SET	"Create the disk set"
64#define	CMD_MSG_ADD_DISKS_TO_SET	"Add disks to set"
65#define	CMD_MSG_FORMAT_SLICES		"Format slices"
66#define	CMD_MSG_CREATE			"Create {1} {2}"
67#define	CMD_MSG_DOES_EXIST		"Does {1} exist?"
68#define	CMD_MSG_ADD_SLICES_TO		"Add slices to {1}"
69/* CSTYLED */
70#define	CMD_MSG_ASSOCIATE_WITH_HSP	"Associate {1} {2} with hot spare pool {3}"
71
72/*
73 * ******************************************************************
74 *
75 * Data types
76 *
77 * ******************************************************************
78 */
79
80/*
81 * Encapsulates the parsing of an XML attribute
82 */
83typedef struct {
84
85	/* The name of the attribute */
86	char *name;
87
88	/*
89	 * A function to validate and set the XML attribute value in
90	 * the given devconfig_t structure.
91	 *
92	 * @param	    name
93	 *		    the name of the XML attribute
94	 *
95	 * @param	    value
96	 *		    the value of the XML attribute
97	 *
98	 * @return	    0 if the given value was valid and set
99	 *		    successfully, non-zero otherwise.
100	 */
101	int (*validate_set)(devconfig_t *device, char *name, char *value);
102
103	/*
104	 * A function to get the XML attribute value in the given
105	 * devconfig_t structure.
106	 *
107	 * @param	    name
108	 *		    the name of the XML attribute
109	 *
110	 * @param	    value
111	 *		    the value of the XML attribute
112	 *
113	 * @return	    0 if the given value was retrieved
114	 *		    successfully, non-zero otherwise.
115	 */
116	int (*get_as_string)(devconfig_t *device, char *name, char **value);
117} attr_t;
118
119/*
120 * Encapsulates the parsing of an XML element
121 */
122typedef struct {
123	/* The name of the element */
124	char *name;
125
126	/* The type of element to set in the devconfig_t */
127	component_type_t type;
128
129	/*
130	 * When converting from XML to a devconfig_t hierarchy,
131	 * indicates whether to create a new devconfig_t structure in
132	 * the hierarchy when this XML element is encountered.
133	 */
134	boolean_t is_hierarchical;
135
136	/*
137	 * If is_hierarchical is B_TRUE, whether to use an existing
138	 * devconfig_t structure of this type when this element is
139	 * encountered
140	 */
141	boolean_t singleton;
142
143	/* The valid XML attributes for this element */
144	attr_t *attributes;
145} element_t;
146
147typedef struct {
148	char *msgid;
149	char *message;
150} l10nmessage_t;
151
152/*
153 * ******************************************************************
154 *
155 * Function prototypes
156 *
157 * ******************************************************************
158 */
159
160static int validate_doc(xmlDocPtr doc, const char *name, const char *systemID);
161static int devconfig_to_xml(
162    xmlNodePtr parent, element_t elements[], devconfig_t *device);
163static int xml_to_devconfig(
164    xmlNodePtr cur, element_t elements[], devconfig_t *device);
165static int compare_is_a_diskset(void *obj1, void *obj2);
166static xmlNodePtr xml_find_node(
167    xmlNodePtr node, xmlChar *element, xmlChar *name);
168static xmlDocPtr create_localized_message_doc();
169static int create_localized_message_file(char **tmpfile);
170static int strtobool(char *str, boolean_t *value);
171static int ofprintf_terse(void *unused, char *fmt, ...);
172static int ofprintf_verbose(void *unused, char *fmt, ...);
173
174static int validate_set_size(
175    devconfig_t *volume, char *attr, char *value);
176static int validate_set_size_in_blocks(
177    devconfig_t *slice, char *attr, char *value);
178static int validate_set_diskset_name(
179    devconfig_t *diskset, char *attr, char *name);
180static int validate_add_available_name(
181    devconfig_t *device, char *attr, char *name);
182static int validate_add_unavailable_name(
183    devconfig_t *device, char *attr, char *name);
184static int validate_set_hsp_name(
185    devconfig_t *hsp, char *attr, char *name);
186static int validate_set_disk_name(
187    devconfig_t *disk, char *attr, char *name);
188static int validate_set_slice_name(
189    devconfig_t *slice, char *attr, char *name);
190static int validate_set_slice_start_block(
191    devconfig_t *slice, char *attr, char *value);
192static int validate_set_volume_name(
193    devconfig_t *volume, char *attr, char *name);
194static int validate_set_stripe_interlace(
195    devconfig_t *stripe, char *attr, char *value);
196static int validate_set_stripe_mincomp(
197    devconfig_t *stripe, char *attr, char *value);
198static int validate_set_stripe_maxcomp(
199    devconfig_t *stripe, char *attr, char *value);
200static int validate_set_volume_usehsp(
201    devconfig_t *volume, char *attr, char *value);
202static int validate_set_mirror_nsubmirrors(
203    devconfig_t *mirror, char *attr, char *value);
204static int validate_set_mirror_read(
205    devconfig_t *mirror, char *attr, char *value);
206static int validate_set_mirror_write(
207    devconfig_t *mirror, char *attr, char *value);
208static int validate_set_mirror_passnum(
209    devconfig_t *mirror, char *attr, char *value);
210static int validate_set_volume_redundancy(
211    devconfig_t *volume, char *attr, char *value);
212static int validate_set_volume_datapaths(
213    devconfig_t *volume, char *attr, char *value);
214
215static int get_as_string_name(
216    devconfig_t *device, char *attr, char **value);
217static int get_as_string_mirror_passnum(
218    devconfig_t *mirror, char *attr, char **value);
219static int get_as_string_mirror_read(
220    devconfig_t *mirror, char *attr, char **value);
221static int get_as_string_mirror_write(
222    devconfig_t *mirror, char *attr, char **value);
223static int get_as_string_size_in_blocks(
224    devconfig_t *device, char *attr, char **value);
225static int get_as_string_slice_start_block(
226    devconfig_t *slice, char *attr, char **value);
227static int get_as_string_stripe_interlace(
228    devconfig_t *stripe, char *attr, char **value);
229
230/*
231 * ******************************************************************
232 *
233 * Data
234 *
235 * ******************************************************************
236 */
237
238/* Valid units for the size attribute */
239units_t size_units[] = {
240	{UNIT_KILOBYTES, BYTES_PER_KILOBYTE},
241	{UNIT_MEGABYTES, BYTES_PER_MEGABYTE},
242	{UNIT_GIGABYTES, BYTES_PER_GIGABYTE},
243	{UNIT_TERABYTES, BYTES_PER_TERABYTE},
244	{NULL, 0}
245};
246
247/* Valid units for the interlace attribute */
248units_t interlace_units[] = {
249	{UNIT_BLOCKS, BYTES_PER_BLOCK},
250	{UNIT_KILOBYTES, BYTES_PER_KILOBYTE},
251	{UNIT_MEGABYTES, BYTES_PER_MEGABYTE},
252	{NULL, 0}
253};
254
255/* <diskset> attributes */
256static attr_t diskset_attrs[] = {
257	{ ATTR_NAME, validate_set_diskset_name, get_as_string_name },
258	{ NULL, NULL, NULL }
259};
260
261/* <available> attributes */
262static attr_t available_attrs[] = {
263	{ ATTR_NAME, validate_add_available_name, NULL },
264	{ NULL, NULL, NULL }
265};
266
267/* <unavailable> attributes */
268static attr_t unavailable_attrs[] = {
269	{ ATTR_NAME, validate_add_unavailable_name, NULL },
270	{ NULL, NULL, NULL }
271};
272
273/* <hsp> attributes */
274static attr_t hsp_attrs[] = {
275	{ ATTR_NAME, validate_set_hsp_name, get_as_string_name },
276	{ NULL, NULL, NULL }
277};
278
279/* <disk> attributes */
280static attr_t disk_attrs[] = {
281	{ ATTR_NAME, validate_set_disk_name, get_as_string_name },
282	{ NULL, NULL, NULL }
283};
284
285/* <slice> attributes */
286static attr_t slice_attrs[] = {
287	{ ATTR_NAME, validate_set_slice_name, get_as_string_name },
288	{ ATTR_SIZEINBLOCKS, validate_set_size_in_blocks,
289	    get_as_string_size_in_blocks },
290	{ ATTR_SLICE_STARTSECTOR, validate_set_slice_start_block,
291	    get_as_string_slice_start_block },
292	{ NULL, NULL, NULL }
293};
294
295/* <stripe> attributes */
296static attr_t stripe_attrs[] = {
297	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
298	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
299	{ ATTR_STRIPE_MINCOMP, validate_set_stripe_mincomp, NULL },
300	{ ATTR_STRIPE_MAXCOMP, validate_set_stripe_maxcomp, NULL },
301	{ ATTR_STRIPE_INTERLACE, validate_set_stripe_interlace,
302	    get_as_string_stripe_interlace },
303	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
304	{ NULL, NULL, NULL }
305};
306
307/* <concat> attributes */
308static attr_t concat_attrs[] = {
309	{ ATTR_NAME,   validate_set_volume_name, get_as_string_name },
310	{ ATTR_SIZEINBYTES,   validate_set_size, NULL },
311	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
312	{ NULL, NULL, NULL }
313};
314
315/* <mirror> attributes */
316static attr_t mirror_attrs[] = {
317	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
318	{ ATTR_MIRROR_NSUBMIRRORS, validate_set_mirror_nsubmirrors, NULL },
319	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
320	{ ATTR_MIRROR_READ, validate_set_mirror_read,
321	    get_as_string_mirror_read },
322	{ ATTR_MIRROR_WRITE, validate_set_mirror_write,
323	    get_as_string_mirror_write },
324	{ ATTR_MIRROR_PASSNUM, validate_set_mirror_passnum,
325	    get_as_string_mirror_passnum },
326	{ ATTR_VOLUME_USEHSP, validate_set_volume_usehsp, NULL },
327	{ NULL, NULL, NULL }
328};
329
330/* <volume> attributes */
331static attr_t volume_attrs[] = {
332	{ ATTR_NAME, validate_set_volume_name, get_as_string_name },
333	{ ATTR_SIZEINBYTES, validate_set_size, NULL },
334	{ ATTR_VOLUME_REDUNDANCY, validate_set_volume_redundancy, NULL },
335	{ ATTR_VOLUME_FAULTRECOVERY, validate_set_volume_usehsp, NULL },
336	{ ATTR_VOLUME_DATAPATHS, validate_set_volume_datapaths, NULL },
337	{ NULL, NULL, NULL }
338};
339
340/* volume-request elements */
341static element_t request_elements[] = {
342	{ ELEMENT_DISKSET, TYPE_DISKSET, B_FALSE, B_FALSE, diskset_attrs },
343	{ ELEMENT_AVAILABLE, TYPE_UNKNOWN, B_FALSE, B_FALSE, available_attrs },
344	{ ELEMENT_UNAVAILABLE, TYPE_UNKNOWN, B_FALSE, B_FALSE,
345	    unavailable_attrs },
346	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_FALSE, hsp_attrs },
347	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_FALSE, slice_attrs },
348	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_FALSE, stripe_attrs },
349	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_FALSE, concat_attrs },
350	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_FALSE, mirror_attrs },
351	{ ELEMENT_VOLUME, TYPE_VOLUME, B_TRUE, B_FALSE, volume_attrs },
352	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
353};
354
355/* volume-defaults elements */
356static element_t default_elements[] = {
357	{ ELEMENT_DISKSET, TYPE_DISKSET, B_TRUE, B_FALSE, diskset_attrs },
358	{ ELEMENT_AVAILABLE, TYPE_UNKNOWN, B_FALSE, B_TRUE, available_attrs },
359	{ ELEMENT_UNAVAILABLE, TYPE_UNKNOWN, B_FALSE, B_TRUE,
360	    unavailable_attrs },
361	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_TRUE, hsp_attrs },
362	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_TRUE, slice_attrs },
363	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_TRUE, stripe_attrs },
364	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_TRUE, concat_attrs },
365	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_TRUE, mirror_attrs },
366	{ ELEMENT_VOLUME, TYPE_VOLUME, B_TRUE, B_TRUE, volume_attrs },
367	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
368};
369
370/* volume-config elements */
371static element_t config_elements[] = {
372	{ ELEMENT_DISKSET, TYPE_DISKSET, B_FALSE, B_FALSE, diskset_attrs },
373	{ ELEMENT_DISK, TYPE_DRIVE, B_TRUE, B_FALSE, disk_attrs },
374	{ ELEMENT_SLICE, TYPE_SLICE, B_TRUE, B_FALSE, slice_attrs },
375	{ ELEMENT_HSP, TYPE_HSP, B_TRUE, B_FALSE, hsp_attrs },
376	{ ELEMENT_STRIPE, TYPE_STRIPE, B_TRUE, B_FALSE, stripe_attrs },
377	{ ELEMENT_CONCAT, TYPE_CONCAT, B_TRUE, B_FALSE, concat_attrs },
378	{ ELEMENT_MIRROR, TYPE_MIRROR, B_TRUE, B_FALSE, mirror_attrs },
379	{ NULL, NULL, B_FALSE, B_FALSE, NULL }
380};
381
382/*
383 * ******************************************************************
384 *
385 * External functions
386 *
387 * ******************************************************************
388 */
389
390/*
391 * Initialize the XML parser, setting defaults across all XML
392 * routines.
393 */
394void
395init_xml()
396{
397	/* COMPAT: Do not generate nodes for formatting spaces */
398	LIBXML_TEST_VERSION
399	xmlKeepBlanksDefault(0);
400
401	/* Turn on line numbers for debugging */
402	xmlLineNumbersDefault(1);
403
404	/* Substitute entities as files are parsed */
405	xmlSubstituteEntitiesDefault(1);
406
407	/* Don't load external entity subsets */
408	xmlLoadExtDtdDefaultValue = 0;
409
410	/* Don't validate against DTD by default */
411	xmlDoValidityCheckingDefaultValue = 0;
412
413	/* Set up output handlers for XML parsing */
414	xmlDefaultSAXHandler.warning = (warningSAXFunc)ofprintf_verbose;
415	xmlDefaultSAXHandler.error  = (errorSAXFunc)ofprintf_terse;
416	xmlDefaultSAXHandler.fatalError = (fatalErrorSAXFunc)ofprintf_terse;
417}
418
419/*
420 * Clean up any remaining structures before exiting.
421 */
422void
423cleanup_xml()
424{
425	xsltCleanupGlobals();
426	xmlCleanupParser();
427}
428
429/*
430 * Converts a volume-request XML document into a request_t.
431 *
432 * @param       doc
433 *		an existing volume-request XML document
434 *
435 * @param       request
436 *		RETURN: a new request_t which must be freed via
437 *		free_request
438 *
439 * @return      0 on success, non-zero otherwise.
440 */
441int
442xml_to_request(
443	xmlDocPtr doc,
444	request_t **request)
445{
446	int error = 0;
447
448	*request = NULL;
449
450	/* Validate doc against known DTD */
451	if ((error = validate_doc(
452	    doc, ELEMENT_VOLUMEREQUEST, VOLUME_REQUEST_DTD_LOC)) == 0) {
453
454	    /* Create a request */
455	    if ((error = new_request(request)) == 0) {
456
457		/* Convert the XML doc into a request_t */
458		error = xml_to_devconfig(xmlDocGetRootElement(doc),
459		    request_elements, request_get_diskset_req(*request));
460	    }
461	}
462
463	return (error);
464}
465
466/*
467 * Converts a volume-defaults XML document into a defaults_t.
468 *
469 * @param       doc
470 *		an existing volume-defaults XML document
471 *
472 * @param       defaults
473 *		RETURN: a new defaults_t which must be freed via
474 *		free_defaults
475 *
476 * @return      0 on success, non-zero otherwise.
477 */
478int
479xml_to_defaults(
480	xmlDocPtr doc,
481	defaults_t **defaults)
482{
483	int error = 0;
484
485	*defaults = NULL;
486
487	/* Validate doc against known DTD */
488	if ((error = validate_doc(doc, ELEMENT_VOLUMEDEFAULTS,
489	    VOLUME_DEFAULTS_DTD_LOC)) == 0) {
490
491	    /* Create request defaults */
492	    if ((error = new_defaults(defaults)) == 0) {
493
494		devconfig_t *global;
495
496		/* Get defaults for all disk sets */
497		if ((error = defaults_get_diskset_by_name(
498		    *defaults, NULL, &global)) == 0) {
499
500		    /* Populate the global devconfig_t from the XML doc */
501		    if ((error = xml_to_devconfig(xmlDocGetRootElement(doc),
502			default_elements, global)) == 0) {
503
504			/* Get the components of the global devconfig_t */
505			dlist_t *list = devconfig_get_components(global);
506
507			/*
508			 * Move all named disk set settings out from
509			 * under global settings
510			 */
511			/* CONSTANTCONDITION */
512			while (1) {
513			    dlist_t *removed = NULL;
514			    devconfig_t *component;
515
516			    /* Remove named disk set from under global */
517			    list = dlist_remove_equivalent_item(
518				list, NULL, compare_is_a_diskset, &removed);
519
520			    if (removed == NULL) {
521				/* No named disk set found */
522				break;
523			    }
524
525			    component = removed->obj;
526
527			    /* Append named disk set to disk set list */
528			    defaults_set_disksets(*defaults,
529				dlist_append(dlist_new_item(component),
530				defaults_get_disksets(*defaults), AT_TAIL));
531			}
532		    }
533		}
534	    }
535	}
536
537	return (error);
538}
539
540/*
541 * Converts a volume-config XML document into a devconfig_t.
542 *
543 * @param       doc
544 *		an existing volume-config XML document
545 *
546 * @param       config
547 *		RETURN: a new devconfig_t which must be freed via
548 *		free_devconfig
549 *
550 * @return      0 on success, non-zero otherwise.
551 */
552int
553xml_to_config(
554	xmlDocPtr doc,
555	devconfig_t **config)
556{
557	int error = 0;
558
559	*config = NULL;
560
561	/* Validate doc against known DTD */
562	if ((error = validate_doc(
563	    doc, ELEMENT_VOLUMECONFIG, VOLUME_CONFIG_DTD_LOC)) == 0) {
564
565	    /* Create a devconfig_t */
566	    if ((error = new_devconfig(config, TYPE_DISKSET)) == 0) {
567
568		/* Populate the devconfig_t from the XML doc */
569		error = xml_to_devconfig(
570		    xmlDocGetRootElement(doc), config_elements, *config);
571	    }
572	}
573
574	return (error);
575}
576
577/*
578 * Converts a devconfig_t into a volume-config XML document.
579 *
580 * @param       config
581 *		an existing devconfig_t representing a volume
582 *		configuration.
583 *
584 * @param       doc
585 *		RETURN: a new volume-config XML document which must be
586 *		freed via xmlFreeDoc
587 *
588 * @return      0 on success, non-zero otherwise.
589 */
590int
591config_to_xml(
592	devconfig_t *config,
593	xmlDocPtr *doc)
594{
595	xmlNodePtr root;
596	int error = 0;
597
598	/* Create the XML document */
599	*doc = xmlNewDoc((xmlChar *)"1.0");
600
601	/* Create the root node */
602	root = xmlNewDocNode(
603	    *doc, NULL, (xmlChar *)ELEMENT_VOLUMECONFIG, NULL);
604	xmlAddChild((xmlNodePtr)*doc, (xmlNodePtr)root);
605
606	/* Create sub-nodes from the config devconfig_t */
607	if ((error = devconfig_to_xml(root, config_elements, config)) == 0) {
608
609	    /* Add DTD node and validate */
610	    error = validate_doc(
611		*doc, ELEMENT_VOLUMECONFIG, VOLUME_CONFIG_DTD_LOC);
612	}
613
614	if (error) {
615	    xmlFreeDoc(*doc);
616	}
617
618	return (error);
619}
620
621/*
622 * Converts a volume-config XML document into a Bourne shell script.
623 *
624 * @param       doc
625 *		an existing volume-config XML document
626 *
627 * @param       commands
628 *		RETURN: a new char* which must be freed
629 *
630 * @return      0 on success, non-zero otherwise.
631 */
632int
633xml_to_commands(
634	xmlDocPtr doc,
635	char **commands)
636{
637	char *tmpfile = NULL;
638	int error = 0;
639	xsltStylesheetPtr style = NULL;
640
641	/* Read in XSL stylesheet as a normal XML document */
642	xmlDocPtr xsl_doc = xmlSAXParseFile((xmlSAXHandlerPtr)
643	    &xmlDefaultSAXHandler, VOLUME_COMMAND_XSL_LOC, 0);
644
645	if (xsl_doc != NULL && xsl_doc->xmlChildrenNode != NULL) {
646
647		/*
648		 * Find the "msgfile" variable node.  This is where
649		 * we'll set the location of the file we'll create
650		 * containing the localized messages.
651		 */
652	    xmlNodePtr msgfile_node = xml_find_node(
653		xmlDocGetRootElement(xsl_doc), (xmlChar *)ELEMENT_VARIABLE,
654		(xmlChar *)NAME_L10N_MESSAGE_FILE);
655
656		/*
657		 * Find the "lang" node.  This is where we'll set the
658		 * current locale.
659		 */
660	    xmlNodePtr lang_node = xml_find_node(xmlDocGetRootElement(xsl_doc),
661		(xmlChar *)ELEMENT_PARAM, (xmlChar *)NAME_LANG);
662
663		/*
664		 * Ignore if the nodes are not found -- the script
665		 * will default to the C locale.
666		 */
667	    if (msgfile_node != NULL && lang_node != NULL) {
668		/* Get/set current locale in the "lang" node */
669		char *locale = setlocale(LC_MESSAGES, NULL);
670		xmlNodeSetContent(lang_node, (xmlChar *)locale);
671
672		/* Write localized messages to a temporary file */
673		if ((error = create_localized_message_file(&tmpfile)) == 0) {
674
675		    char *newsel;
676
677		    /* Clear current value of select attribute, if any */
678		    xmlChar *cursel = xmlGetProp(
679			msgfile_node, (xmlChar *)ATTR_SELECT);
680		    if (cursel != NULL) {
681			xmlFree(cursel);
682		    }
683
684			/*
685			 * The select attribute calls the XSLT function
686			 * document() to load an external XML file
687			 */
688		    newsel = stralloccat(3, "document('", tmpfile, "')");
689
690		    if (newsel == NULL) {
691			volume_set_error(gettext("out of memory"));
692			error = -1;
693		    } else {
694
695			/* Set the new value of the select attribute */
696			xmlSetProp(msgfile_node,
697			    (xmlChar *)ATTR_SELECT, (xmlChar *)newsel);
698
699			free(newsel);
700		    }
701		}
702	    }
703
704	    if (error == 0) {
705		style = xsltParseStylesheetDoc(xsl_doc);
706	    }
707	}
708
709	if (style == NULL) {
710	    volume_set_error(
711		gettext("could not load stylesheet from %s"),
712		VOLUME_COMMAND_XSL_LOC);
713	    error = -1;
714	} else {
715
716	    xmlDocPtr result = xsltApplyStylesheet(style, doc, NULL);
717
718	    if (result == NULL) {
719		volume_set_error(
720		    gettext("could not apply stylesheet to volume-config"));
721		error = -1;
722	    } else {
723		int length;
724
725		if (xsltSaveResultToString((xmlChar **)commands,
726		    &length, result, style) == -1) {
727		    error = ENOMEM;
728		}
729	    }
730
731	    xsltFreeStylesheet(style);
732	}
733
734	if (tmpfile != NULL) {
735	    /* Ignore failure */
736	    unlink(tmpfile);
737
738	    free(tmpfile);
739	}
740
741	return (error);
742}
743
744/*
745 * ******************************************************************
746 *
747 * Static functions
748 *
749 * ******************************************************************
750 */
751
752/*
753 * Sets the external DTD node in the given XML document and then
754 * validates it.
755 *
756 * @param       doc
757 *		an existing XML document
758 *
759 * @param       name
760 *		the expected root element name of the XML document
761 *
762 * @param       systemID
763 *		the location of the DTD
764 *
765 * @return      0 on success, non-zero otherwise.
766 */
767static int
768validate_doc(
769	xmlDocPtr doc,
770	const char *name,
771	const char *systemID)
772{
773	xmlValidCtxt context;
774	xmlDtdPtr dtd;
775
776	if (doc == NULL) {
777	    volume_set_error(gettext("NULL %s document"), name);
778	    return (-1);
779	}
780
781	/*
782	 * Assume that we can't trust any DTD but our own.
783	 */
784
785	/* Was a DTD (external or internal) included in the document? */
786	if ((dtd = xmlGetIntSubset(doc)) != NULL) {
787	    /* Remove the DTD node */
788	    oprintf(OUTPUT_DEBUG, gettext("Removing DTD from %s\n"), name);
789	    xmlUnlinkNode((xmlNodePtr)dtd);
790	    xmlFreeDtd(dtd);
791	}
792
793	/* Create the (external) DTD node */
794	oprintf(OUTPUT_DEBUG,
795	    gettext("Creating new external DTD for %s\n"), name);
796	dtd = xmlCreateIntSubset(
797	    doc, (xmlChar *)name, NULL, (xmlChar *)systemID);
798	if (dtd == NULL) {
799	    volume_set_error(
800		gettext("could not create DTD node from %s"), systemID);
801	    return (-1);
802	}
803
804	/* Validate against DTD */
805	oprintf(OUTPUT_DEBUG, gettext("Validating %s against DTD\n"), name);
806	context.userData = NULL;
807	context.error = (xmlValidityErrorFunc)ofprintf_terse;
808	context.warning = (xmlValidityWarningFunc)ofprintf_terse;
809	if (!xmlValidateDocument(&context, doc)) {
810	    volume_set_error(gettext("invalid %s"), name);
811	    return (-1);
812	}
813
814	return (0);
815}
816
817/*
818 * Converts a devconfig_t into an XML node subject to the rules in
819 * the given element_t array.
820 *
821 * @param       parent
822 *		the XML node to which to add new XML nodes resulting
823 *		from conversion of the given devconfig_t
824 *
825 * @param       elements
826 *		the element_ts that describe the structure of the XML
827 *		document and govern the conversion of the given
828 *		devconfig_t
829 *
830 * @param       device
831 *		the devconfig_t to convert
832 *
833 * @return      0 on success, non-zero otherwise.
834 */
835static int
836devconfig_to_xml(
837	xmlNodePtr parent,
838	element_t elements[],
839	devconfig_t *device)
840{
841	int i;
842	int error = 0;
843	xmlNodePtr node = NULL;
844
845	/* Get device type */
846	component_type_t type;
847	if ((error = devconfig_get_type(device, &type)) != 0) {
848	    return (error);
849	}
850
851	/* Search for this element definition */
852	for (i = 0; elements[i].name != NULL; i++) {
853	    element_t *element = &(elements[i]);
854
855	    if (element->type == type) {
856		int j;
857		char **array;
858		dlist_t *components;
859
860		oprintf(OUTPUT_DEBUG, gettext("Element: %s\n"),
861		    devconfig_type_to_str(type));
862
863		/* Create the XML node */
864		node = xmlNewChild(
865		    parent, NULL, (xmlChar *)element->name, NULL);
866
867		/* For each attribute defined for this element... */
868		for (j = 0; element->attributes[j].name != NULL; j++) {
869		    attr_t *attribute = &(element->attributes[j]);
870		    char *value;
871
872		    /* Is there a valid accessor for this attribute? */
873		    if (attribute->get_as_string != NULL) {
874
875			/* Get the attribute value from the device */
876			switch (error = attribute->get_as_string(
877			    device, attribute->name, &value)) {
878
879			    /* Attribute is set in this device */
880			    case 0:
881				oprintf(OUTPUT_DEBUG, "    %s: %s\n",
882				    attribute->name, value);
883
884				/* Set the value in the XML node */
885				xmlSetProp(node, (uchar_t *)attribute->name,
886				    (uchar_t *)value);
887				free(value);
888
889			    /* FALLTHROUGH */
890
891			    /* Attribute is not set in this device */
892			    case ERR_ATTR_UNSET:
893
894				error = 0;
895				break;
896
897			    /* Error */
898			    default:
899				return (error);
900			}
901		    }
902		}
903
904		/* Is this node hierarchical? */
905		if (element->is_hierarchical == B_FALSE) {
906		    node = parent;
907		}
908
909		/* Create <available> nodes */
910		array = devconfig_get_available(device);
911		if (array != NULL) {
912		    for (j = 0; array[j] != NULL; j++) {
913			xmlNodePtr child = xmlNewChild(
914			    node, NULL, (xmlChar *)ELEMENT_AVAILABLE, NULL);
915			xmlSetProp(child,
916			    (xmlChar *)ATTR_NAME, (xmlChar *)array[j]);
917		    }
918		}
919
920		/* Create <unavailable> nodes */
921		array = devconfig_get_unavailable(device);
922		if (array != NULL) {
923		    for (j = 0; array[j] != NULL; j++) {
924			xmlNodePtr child = xmlNewChild(
925			    node, NULL, (xmlChar *)ELEMENT_UNAVAILABLE, NULL);
926			xmlSetProp(child,
927			    (xmlChar *)ATTR_NAME, (xmlChar *)array[j]);
928		    }
929		}
930
931		/*
932		 * Recursively convert subcomponents of this device to
933		 * XML, taking care to encode them in the order
934		 * specified in the element_t list (which should
935		 * mirror what's expected by the DTD).
936		 */
937
938		/* For each element type... */
939		for (j = 0; elements[j].name != NULL; j++) {
940
941		    /* For each component of this device... */
942		    for (components = devconfig_get_components(device);
943			components != NULL && error == 0;
944			components = components->next) {
945
946			devconfig_t *component = (devconfig_t *)components->obj;
947			component_type_t t;
948
949			/* Are the types the same? */
950			if ((error = devconfig_get_type(component, &t)) != 0) {
951			    return (error);
952			} else {
953			    if (elements[j].type == t) {
954				/* Encode child */
955				error = devconfig_to_xml(
956				    node, elements, component);
957			    }
958			}
959		    }
960		}
961
962		/* Element found */
963		break;
964	    }
965	}
966
967	/* Was this device successfully converted? */
968	if (node == NULL) {
969	    volume_set_error(
970		gettext("can't convert device of type \"%s\" to XML element"),
971		devconfig_type_to_str(type));
972	    error = -1;
973	}
974
975	return (error);
976}
977
978/*
979 * Converts an XML node into a devconfig_t subject to the rules in
980 * the given element_t array.
981 *
982 * @param       cure
983 *		the existing XML node to convert
984 *
985 * @param       elements
986 *		the element_ts that describe the structure of the XML
987 *		document and govern the conversion of the given XML
988 *		node
989 *
990 * @param       device
991 *		the devconfig_t node to which to add new devconfig_ts
992 *		resulting from conversion of the given XML node
993 *
994 * @return      0 on success, non-zero otherwise.
995 */
996static int
997xml_to_devconfig(
998	xmlNodePtr cur,
999	element_t elements[],
1000	devconfig_t *device)
1001{
1002	int error = 0;
1003
1004	/* For each child node... */
1005	for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
1006	    int i;
1007	    boolean_t parsed_elem = B_FALSE;
1008
1009	    /* Search for this element definition */
1010	    for (i = 0; elements[i].name != NULL; i++) {
1011		element_t *element = &(elements[i]);
1012
1013		if (xmlStrcmp(cur->name, (xmlChar *)element->name) == 0) {
1014		    int j;
1015		    devconfig_t *component = NULL;
1016
1017		    /* Flag that this element has been parsed */
1018		    parsed_elem = B_TRUE;
1019
1020		    oprintf(OUTPUT_DEBUG, gettext("line %d: Element <%s>\n"),
1021			    XML_GET_LINE(cur), cur->name);
1022
1023		    /* Should a new device be created for this element? */
1024		    if (element->is_hierarchical == B_TRUE) {
1025
1026			/* Should we use an existing device of this type? */
1027			if (element->singleton) {
1028			    devconfig_get_component(
1029				device, element->type, &component, B_FALSE);
1030			}
1031
1032			if (component == NULL) {
1033			    oprintf(OUTPUT_DEBUG,
1034				gettext("Creating new device\n"));
1035
1036			    /* Create device of this type */
1037			    if ((error = new_devconfig(
1038				    &component, element->type)) != 0) {
1039				return (error);
1040			    }
1041
1042			    /* Add component to the toplevel device */
1043			    devconfig_set_components(
1044				device, dlist_append(dlist_new_item(component),
1045				devconfig_get_components(device), AT_TAIL));
1046			}
1047		    } else {
1048			component = device;
1049		    }
1050
1051		    /* For each attribute defined for this element... */
1052		    for (j = 0; element->attributes[j].name != NULL; j++) {
1053			attr_t *attribute = &(element->attributes[j]);
1054
1055			/* Get the value of this attribute */
1056			char *value = (char *)
1057			    xmlGetProp(cur, (xmlChar *)attribute->name);
1058
1059			/* Was this attribute specified? */
1060			if (value != NULL) {
1061			    oprintf(OUTPUT_DEBUG,
1062				gettext("line %d:\tAttribute %s=%s\n"),
1063				XML_GET_LINE(cur), attribute->name, value);
1064
1065			    /* Set this value in the device */
1066			    if ((error = attribute->validate_set(
1067				component, attribute->name, value)) != 0) {
1068				return (error);
1069			    }
1070			}
1071		    }
1072
1073		    /* Get recursive sub-elements */
1074		    if ((error = xml_to_devconfig(
1075			cur, elements, component)) != 0) {
1076			return (error);
1077		    }
1078
1079		    /* Element found */
1080		    break;
1081		}
1082	    }
1083
1084
1085	    /* Make sure all non-text/comment elements were parsed */
1086	    if (parsed_elem == B_FALSE &&
1087		xmlStrcmp(cur->name, (xmlChar *)ELEMENT_TEXT) != 0 &&
1088		xmlStrcmp(cur->name, (xmlChar *)ELEMENT_COMMENT) != 0) {
1089
1090		oprintf(OUTPUT_DEBUG, gettext("Element <%s> NOT PARSED!!!\n"),
1091		    cur->name);
1092	    }
1093	}
1094
1095	return (0);
1096}
1097
1098/*
1099 * Returns 0 if obj2 (devconfig_t *) is a disk set, 1 otherwise.
1100 */
1101static int
1102compare_is_a_diskset(
1103	void *obj1,
1104	void *obj2)
1105{
1106	return (devconfig_isA(
1107	    (devconfig_t *)obj2, TYPE_DISKSET) == B_TRUE ? 0 : 1);
1108}
1109
1110/*
1111 * Recursively searches the given xmlNodePtr for an element of the
1112 * specified type and name.
1113 *
1114 * @param       node
1115 *              the root node to search
1116 *
1117 * @param       element
1118 *              the name of the element type
1119 *
1120 * @param       name
1121 *              the value of the name attribute
1122 *
1123 * @return      a valid xmlNodePtr if an element of the specified
1124 *              type and name was found, NULL otherwise.
1125 */
1126static xmlNodePtr
1127xml_find_node(
1128	xmlNodePtr node,
1129	xmlChar *element,
1130	xmlChar *name)
1131{
1132	xmlNodePtr child;
1133
1134	/* Is the element the right type? */
1135	if (xmlStrcmp(element, node->name) == 0 &&
1136
1137	    /* Does this element's name attribute match? */
1138	    xmlStrcmp(name, xmlGetProp(node, (xmlChar *)ATTR_NAME)) == 0) {
1139
1140	    return (node);
1141	}
1142
1143	/* Check child nodes */
1144	for (child = node->xmlChildrenNode; child != NULL;
1145	    child = child->next) {
1146	    xmlNodePtr found = xml_find_node(child, element, name);
1147
1148	    if (found != NULL) {
1149		return (found);
1150	    }
1151	}
1152
1153	return (NULL);
1154}
1155
1156/*
1157 * Creates an XML document containing all of the localized message
1158 * strings for the generated command script.
1159 *
1160 * @return      a xmlDocPtr which must be freed via xmlFreeDoc
1161 */
1162static xmlDocPtr
1163create_localized_message_doc()
1164{
1165	int i;
1166	char *locale;
1167	xmlDocPtr doc;
1168	xmlNodePtr root;
1169	l10nmessage_t _cmd_messages[21];
1170
1171	/* Create the XML document */
1172	doc = xmlNewDoc((xmlChar *)"1.0");
1173
1174	/* Create the root node */
1175	root = xmlNewDocNode(
1176	    doc, NULL, (xmlChar *)ELEMENT_L10N, NULL);
1177	xmlAddChild((xmlNodePtr) doc, (xmlNodePtr)root);
1178
1179	_cmd_messages[0].msgid = CMD_MSG_ENVIRONMENT;
1180	_cmd_messages[0].message = gettext(CMD_MSG_ENVIRONMENT);
1181	_cmd_messages[1].msgid = CMD_MSG_AMEND_PATH;
1182	_cmd_messages[1].message = gettext(CMD_MSG_AMEND_PATH);
1183	_cmd_messages[2].msgid = CMD_MSG_DISK_SET_NAME;
1184	_cmd_messages[2].message = gettext(CMD_MSG_DISK_SET_NAME);
1185	_cmd_messages[3].msgid = CMD_MSG_FUNCTIONS;
1186	_cmd_messages[3].message = gettext(CMD_MSG_FUNCTIONS);
1187	_cmd_messages[4].msgid = CMD_MSG_ECHO_AND_EXEC;
1188	_cmd_messages[4].message = gettext(CMD_MSG_ECHO_AND_EXEC);
1189	_cmd_messages[5].msgid = CMD_MSG_FMTHARD_SPECIAL;
1190	_cmd_messages[5].message = gettext(CMD_MSG_FMTHARD_SPECIAL);
1191	_cmd_messages[6].msgid = CMD_MSG_GET_FULL_PATH;
1192	_cmd_messages[6].message = gettext(CMD_MSG_GET_FULL_PATH);
1193	_cmd_messages[7].msgid = CMD_MSG_MAIN;
1194	_cmd_messages[7].message = gettext(CMD_MSG_MAIN);
1195	_cmd_messages[8].msgid = CMD_MSG_VERIFY_ROOT;
1196	_cmd_messages[8].message = gettext(CMD_MSG_VERIFY_ROOT);
1197	_cmd_messages[9].msgid = CMD_MSG_RUN_AS_ROOT;
1198	_cmd_messages[9].message = gettext(CMD_MSG_RUN_AS_ROOT);
1199	_cmd_messages[10].msgid = CMD_MSG_CHECK_FOR_VERBOSE;
1200	_cmd_messages[10].message = gettext(CMD_MSG_CHECK_FOR_VERBOSE);
1201	_cmd_messages[11].msgid = (CMD_MSG_DOES_DISK_SET_EXIST);
1202	_cmd_messages[11].message = gettext(CMD_MSG_DOES_DISK_SET_EXIST);
1203	_cmd_messages[12].msgid = (CMD_MSG_TAKE_DISK_SET);
1204	_cmd_messages[12].message = gettext(CMD_MSG_TAKE_DISK_SET);
1205	_cmd_messages[13].msgid = (CMD_MSG_CREATE_THE_DISK_SET);
1206	_cmd_messages[13].message = gettext(CMD_MSG_CREATE_THE_DISK_SET);
1207	_cmd_messages[14].msgid = (CMD_MSG_ADD_DISKS_TO_SET);
1208	_cmd_messages[14].message = gettext(CMD_MSG_ADD_DISKS_TO_SET);
1209	_cmd_messages[15].msgid = (CMD_MSG_FORMAT_SLICES);
1210	_cmd_messages[15].message = gettext(CMD_MSG_FORMAT_SLICES);
1211	_cmd_messages[16].msgid = (CMD_MSG_CREATE);
1212	_cmd_messages[16].message = gettext(CMD_MSG_CREATE);
1213	_cmd_messages[17].msgid = (CMD_MSG_DOES_EXIST);
1214	_cmd_messages[17].message = gettext(CMD_MSG_DOES_EXIST);
1215	_cmd_messages[18].msgid = (CMD_MSG_ADD_SLICES_TO);
1216	_cmd_messages[18].message = gettext(CMD_MSG_ADD_SLICES_TO);
1217	_cmd_messages[19].msgid = (CMD_MSG_ASSOCIATE_WITH_HSP);
1218	_cmd_messages[19].message = gettext(CMD_MSG_ASSOCIATE_WITH_HSP);
1219	_cmd_messages[20].msgid = NULL;
1220
1221	/* Get/set current locale in the "lang" node */
1222	locale = setlocale(LC_MESSAGES, NULL);
1223
1224	/* Add localized <message> elements to stylesheet */
1225	for (i = 0; _cmd_messages[i].msgid != NULL; i++) {
1226	    xmlNsPtr ns = xmlNewNs(NULL, NULL, NULL);
1227
1228	    xmlNodePtr node = xmlNewTextChild(
1229		root, ns, (xmlChar *)ELEMENT_MESSAGE,
1230		(xmlChar *)_cmd_messages[i].message);
1231	    /* Lang attribute */
1232	    xmlSetProp(node,
1233		(xmlChar *)ATTR_LANG, (xmlChar *)locale);
1234
1235	    /* Message ID attribute */
1236	    xmlSetProp(node, (xmlChar *)ATTR_MESSAGEID,
1237		(xmlChar *)_cmd_messages[i].msgid);
1238	}
1239
1240	if (get_max_verbosity() >= OUTPUT_DEBUG) {
1241	    xmlChar *text;
1242	    /* Get the text dump */
1243	    xmlDocDumpFormatMemory(doc, &text, NULL, 1);
1244	    oprintf(OUTPUT_DEBUG,
1245		gettext("Generated message file:\n%s"), text);
1246	    xmlFree(text);
1247	}
1248
1249	return (doc);
1250}
1251
1252/*
1253 * Creates a temporary XML file containing all of the localized
1254 * message strings for the generated command script.
1255 *
1256 * @param       tmpfile
1257 *		RETURN: the name of the temporary XML file
1258 *
1259 * @return      0 on success, non-zero otherwise.
1260 */
1261static int
1262create_localized_message_file(
1263	char **tmpfile)
1264{
1265	int error = 0;
1266
1267	/*
1268	 * Create temporary file name -- "XXXXXX" is replaced with
1269	 * unique char sequence by mkstemp()
1270	 */
1271	*tmpfile = stralloccat(3, "/tmp/", ELEMENT_L10N, "XXXXXX");
1272
1273	if (*tmpfile == NULL) {
1274	    volume_set_error(gettext("out of memory"));
1275	    error = -1;
1276	} else {
1277	    int fildes;
1278	    FILE *msgfile = NULL;
1279
1280	    /* Open temp file */
1281	    if ((fildes = mkstemp(*tmpfile)) != -1) {
1282		msgfile = fdopen(fildes, "w");
1283	    }
1284
1285	    if (msgfile == NULL) {
1286		volume_set_error(gettext(
1287		    "could not open file for writing: %s"), *tmpfile);
1288		error = -1;
1289	    } else {
1290
1291		xmlChar *text;
1292		xmlDocPtr message_doc = create_localized_message_doc();
1293		xmlDocDumpFormatMemory(message_doc, &text, NULL, 1);
1294
1295		if (fprintf(msgfile, "%s", text) < 0) {
1296		    volume_set_error(gettext(
1297			"could not create localized message file: %s"),
1298			*tmpfile);
1299		    error = -1;
1300		}
1301
1302		xmlFree(text);
1303		xmlFreeDoc(message_doc);
1304	    }
1305
1306	    fclose(msgfile);
1307	}
1308
1309	return (error);
1310}
1311
1312/*
1313 * Converts the given string into a boolean.  The string must be
1314 * either VALID_ATTR_TRUE or VALID_ATTR_FALSE.
1315 *
1316 * @param       str
1317 *              the string to convert
1318 *
1319 * @param       bool
1320 *              the addr of the boolean_t
1321 *
1322 * @return      0 if the given string could be converted to a boolean
1323 *              non-zero otherwise.
1324 */
1325static int
1326strtobool(
1327	char *str,
1328	boolean_t *value)
1329{
1330	int error = 0;
1331
1332	if (strcmp(str, VALID_ATTR_TRUE) == 0) {
1333	    *value = B_TRUE;
1334	} else
1335
1336	if (strcmp(str, VALID_ATTR_FALSE) == 0) {
1337	    *value = B_FALSE;
1338	} else
1339
1340	    error = -1;
1341
1342	return (error);
1343}
1344
1345/*
1346 * Wrapper for oprintf with a OUTPUT_TERSE level of verbosity.
1347 * Provides an fprintf-like syntax to enable use as substitute output
1348 * handler for man of the XML commands.
1349 *
1350 * @param       unused
1351 *		unused, in favor of the FILE* passed to
1352 *		set_max_verbosity().
1353 *
1354 * @param       fmt
1355 *		a printf-style format string
1356 *
1357 * @return      the number of characters output
1358 */
1359static int
1360ofprintf_terse(
1361	void *unused,
1362	char *fmt,
1363	...)
1364{
1365	int ret;
1366	va_list ap;
1367
1368	va_start(ap, fmt);
1369	ret = oprintf_va(OUTPUT_TERSE, fmt, ap);
1370	va_end(ap);
1371
1372	return (ret);
1373}
1374
1375/*
1376 * Wrapper for oprintf with a OUTPUT_VERBOSE level of verbosity.
1377 * Provides an fprintf-like syntax to enable use as substitute output
1378 * handler for man of the XML commands.
1379 *
1380 * @param       unused
1381 *		unused, in favor of the FILE* passed to
1382 *		set_max_verbosity().
1383 *
1384 * @param       fmt
1385 *		a printf-style format string
1386 *
1387 * @return      the number of characters output
1388 */
1389static int
1390ofprintf_verbose(
1391	void *unused,
1392	char *fmt,
1393	...)
1394{
1395	int ret;
1396	va_list ap;
1397
1398	va_start(ap, fmt);
1399	ret = oprintf_va(OUTPUT_VERBOSE, fmt, ap);
1400	va_end(ap);
1401
1402	return (ret);
1403}
1404
1405/*
1406 * ******************************************************************
1407 *
1408 * XML attribute validators/mutators
1409 *
1410 * These functions convert the given XML attribute string to the
1411 * appropriate data type, and then pass it on to the appropriate
1412 * devconfig_t mutator.  A non-zero status is returned if the given
1413 * string could not be converted or was invalid.
1414 *
1415 * ******************************************************************
1416 */
1417
1418/*
1419 * Validate and set the size attribute in the given volume
1420 * devconfig_t.
1421 *
1422 * @param       volume
1423 *		the devconfig_t in which to set the size
1424 *
1425 * @param       attr
1426 *		the name of the XML attribute
1427 *
1428 * @param       value
1429 *		the value of the XML attribute
1430 *
1431 * @return      0 on success, non-zero otherwise.
1432 */
1433static int
1434validate_set_size(
1435	devconfig_t *volume,
1436	char *attr,
1437	char *value)
1438{
1439	int error;
1440	uint64_t size = 0;
1441
1442	/* Convert size string to bytes */
1443	if ((error = sizestr_to_bytes(value, &size, size_units)) != 0) {
1444	    return (error);
1445	}
1446
1447	/* Set size in volume */
1448	return (devconfig_set_size(volume, size));
1449}
1450
1451/*
1452 * Validate and set the size_in_blocks attribute in the given slice
1453 * devconfig_t.
1454 *
1455 * @param       volume
1456 *		the devconfig_t in which to set the size_in_blocks
1457 *
1458 * @param       attr
1459 *		the name of the XML attribute
1460 *
1461 * @param       value
1462 *		the value of the XML attribute
1463 *
1464 * @return      0 on success, non-zero otherwise.
1465 */
1466static int
1467validate_set_size_in_blocks(
1468	devconfig_t *slice,
1469	char *attr,
1470	char *value)
1471{
1472	long long size;
1473
1474	/* Convert string to long long */
1475	if (sscanf(value, "%lld", &size) != 1) {
1476	    volume_set_error(gettext("%s: invalid size in blocks"), value);
1477	    return (-1);
1478	}
1479
1480	/* Set the number of submirrors in the slice */
1481	return (devconfig_set_size_in_blocks(slice, (uint64_t)size));
1482}
1483
1484/*
1485 * Validate and set the name attribute in the given diskset
1486 * devconfig_t.
1487 *
1488 * @param       volume
1489 *		the devconfig_t in which to set the name
1490 *
1491 * @param       attr
1492 *		the name of the XML attribute
1493 *
1494 * @param       name
1495 *		the value of the XML attribute
1496 *
1497 * @return      0 on success, non-zero otherwise.
1498 */
1499static int
1500validate_set_diskset_name(
1501	devconfig_t *diskset,
1502	char *attr,
1503	char *name)
1504{
1505	return (devconfig_set_diskset_name(diskset, name));
1506}
1507
1508/*
1509 * Validate and add the given name to the list of available devices in
1510 * the given volume devconfig_t.
1511 *
1512 * @param       device
1513 *		the devconfig_t whose available device list to modify
1514 *
1515 * @param       attr
1516 *		the name of the XML attribute
1517 *
1518 * @param       name
1519 *		the value of the XML attribute
1520 *
1521 * @return      0 on success, non-zero otherwise.
1522 */
1523static int
1524validate_add_available_name(
1525	devconfig_t *device,
1526	char *attr,
1527	char *name)
1528{
1529	char **available;
1530
1531	/* Get available devices for this device */
1532	available = devconfig_get_available(device);
1533
1534	/* Try to add name to array via realloc */
1535	if ((available = append_to_string_array(available, name)) == NULL) {
1536	    return (ENOMEM);
1537	}
1538
1539	/* Set available devices in the device */
1540	devconfig_set_available(device, available);
1541
1542	return (0);
1543}
1544
1545/*
1546 * Validate and add the given name to the list of unavailable devices
1547 * in the given volume devconfig_t.
1548 *
1549 * @param       device
1550 *		the devconfig_t whose unavailable device list to modify
1551 *
1552 * @param       attr
1553 *		the name of the XML attribute
1554 *
1555 * @param       name
1556 *		the value of the XML attribute
1557 *
1558 * @return      0 on success, non-zero otherwise.
1559 */
1560static int
1561validate_add_unavailable_name(
1562	devconfig_t *device,
1563	char *attr,
1564	char *name)
1565{
1566	char **unavailable;
1567
1568	/* Get unavailable devices for this device */
1569	unavailable = devconfig_get_unavailable(device);
1570
1571	/* Try to add name to array via realloc */
1572	if ((unavailable = append_to_string_array(unavailable, name)) == NULL) {
1573	    return (ENOMEM);
1574	}
1575
1576	/* Set unavailable devices in the device */
1577	devconfig_set_unavailable(device, unavailable);
1578
1579	return (0);
1580}
1581
1582/*
1583 * Validate and set the name attribute in the given hsp devconfig_t.
1584 *
1585 * @param       volume
1586 *		the devconfig_t in which to set the name
1587 *
1588 * @param       attr
1589 *		the name of the XML attribute
1590 *
1591 * @param       name
1592 *		the value of the XML attribute
1593 *
1594 * @return      0 on success, non-zero otherwise.
1595 */
1596static int
1597validate_set_hsp_name(
1598	devconfig_t *hsp,
1599	char *attr,
1600	char *name)
1601{
1602	return (devconfig_set_hsp_name(hsp, name));
1603}
1604
1605/*
1606 * Validate and set the name attribute in the given disk devconfig_t.
1607 *
1608 * @param       volume
1609 *		the devconfig_t in which to set the name
1610 *
1611 * @param       attr
1612 *		the name of the XML attribute
1613 *
1614 * @param       name
1615 *		the value of the XML attribute
1616 *
1617 * @return      0 on success, non-zero otherwise.
1618 */
1619static int
1620validate_set_disk_name(
1621	devconfig_t *disk,
1622	char *attr,
1623	char *name)
1624{
1625	return (devconfig_set_name(disk, name));
1626}
1627
1628/*
1629 * Validate and set the name attribute in the given slice devconfig_t.
1630 *
1631 * @param       volume
1632 *		the devconfig_t in which to set the name
1633 *
1634 * @param       attr
1635 *		the name of the XML attribute
1636 *
1637 * @param       name
1638 *		the value of the XML attribute
1639 *
1640 * @return      0 on success, non-zero otherwise.
1641 */
1642static int
1643validate_set_slice_name(
1644	devconfig_t *slice,
1645	char *attr,
1646	char *name)
1647{
1648	return (devconfig_set_name(slice, name));
1649}
1650
1651/*
1652 * Validate and set the start_block attribute in the given slice
1653 * devconfig_t.
1654 *
1655 * @param       volume
1656 *		the devconfig_t in which to set the start_block
1657 *
1658 * @param       attr
1659 *		the name of the XML attribute
1660 *
1661 * @param       value
1662 *		the value of the XML attribute
1663 *
1664 * @return      0 on success, non-zero otherwise.
1665 */
1666static int
1667validate_set_slice_start_block(
1668	devconfig_t *slice,
1669	char *attr,
1670	char *value)
1671{
1672	long long startsector;
1673
1674	/* Convert string to long long */
1675	if (sscanf(value, "%lld", &startsector) != 1) {
1676	    volume_set_error(gettext("%s: invalid start sector"), value);
1677	    return (-1);
1678	}
1679
1680	/* Set the number of submirrors in the slice */
1681	return (devconfig_set_slice_start_block(slice, (uint64_t)startsector));
1682}
1683
1684/*
1685 * Validate and set the name attribute in the given volume
1686 * devconfig_t.
1687 *
1688 * @param       volume
1689 *		the devconfig_t in which to set the name
1690 *
1691 * @param       attr
1692 *		the name of the XML attribute
1693 *
1694 * @param       name
1695 *		the value of the XML attribute
1696 *
1697 * @return      0 on success, non-zero otherwise.
1698 */
1699static int
1700validate_set_volume_name(
1701	devconfig_t *volume,
1702	char *attr,
1703	char *name)
1704{
1705	return (devconfig_set_volume_name(volume, name));
1706}
1707
1708/*
1709 * Validate and set the interlace attribute in the given stripe
1710 * devconfig_t.
1711 *
1712 * @param       volume
1713 *		the devconfig_t in which to set the interlace
1714 *
1715 * @param       attr
1716 *		the name of the XML attribute
1717 *
1718 * @param       value
1719 *		the value of the XML attribute
1720 *
1721 * @return      0 on success, non-zero otherwise.
1722 */
1723static int
1724validate_set_stripe_interlace(
1725	devconfig_t *stripe,
1726	char *attr,
1727	char *value)
1728{
1729	int error;
1730	uint64_t interlace = 0;
1731
1732	/* Convert interlace string to bytes */
1733	if ((error = sizestr_to_bytes(
1734		value, &interlace, interlace_units)) != 0) {
1735	    return (error);
1736	}
1737
1738	/* Set interlace in stripe */
1739	return (devconfig_set_stripe_interlace(stripe, interlace));
1740}
1741
1742/*
1743 * Validate and set the mincomp attribute in the given stripe
1744 * devconfig_t.
1745 *
1746 * @param       volume
1747 *		the devconfig_t in which to set the mincomp
1748 *
1749 * @param       attr
1750 *		the name of the XML attribute
1751 *
1752 * @param       value
1753 *		the value of the XML attribute
1754 *
1755 * @return      0 on success, non-zero otherwise.
1756 */
1757static int
1758validate_set_stripe_mincomp(
1759	devconfig_t *stripe,
1760	char *attr,
1761	char *value)
1762{
1763	uint16_t mincomp;
1764
1765	/* Convert string to a uint16_t */
1766	if (str_to_uint16(value, &mincomp) != 0) {
1767	    volume_set_error(
1768		gettext("invalid minimum stripe components (%s): %s"),
1769		attr, value);
1770	    return (-1);
1771	}
1772
1773	/* Set in stripe */
1774	return (devconfig_set_stripe_mincomp(stripe, mincomp));
1775}
1776
1777/*
1778 * Validate and set the maxcomp attribute in the given stripe
1779 * devconfig_t.
1780 *
1781 * @param       volume
1782 *		the devconfig_t in which to set the maxcomp
1783 *
1784 * @param       attr
1785 *		the name of the XML attribute
1786 *
1787 * @param       value
1788 *		the value of the XML attribute
1789 *
1790 * @return      0 on success, non-zero otherwise.
1791 */
1792static int
1793validate_set_stripe_maxcomp(
1794	devconfig_t *stripe,
1795	char *attr,
1796	char *value)
1797{
1798	uint16_t maxcomp;
1799
1800	/* Convert string to a uint16_t */
1801	if (str_to_uint16(value, &maxcomp) != 0) {
1802	    volume_set_error(
1803		gettext("invalid maximum stripe components (%s): %s"),
1804		attr, value);
1805	    return (-1);
1806	}
1807
1808	/* Set in stripe */
1809	return (devconfig_set_stripe_maxcomp(stripe, maxcomp));
1810}
1811
1812/*
1813 * Validate and set the usehsp attribute in the given volume
1814 * devconfig_t.
1815 *
1816 * @param       volume
1817 *		the devconfig_t in which to set the usehsp
1818 *
1819 * @param       attr
1820 *		the name of the XML attribute
1821 *
1822 * @param       value
1823 *		the value of the XML attribute
1824 *
1825 * @return      0 on success, non-zero otherwise.
1826 */
1827static int
1828validate_set_volume_usehsp(
1829	devconfig_t *volume,
1830	char *attr,
1831	char *value)
1832{
1833	boolean_t usehsp;
1834
1835	/* Get boolean value */
1836	if (strtobool(value, &usehsp) != 0) {
1837	    volume_set_error(
1838		gettext("%s: invalid boolean value for \"%s\" attribute"),
1839		value, attr);
1840	    return (-1);
1841	}
1842
1843	/* Set in volume */
1844	return (devconfig_set_volume_usehsp(volume, usehsp));
1845}
1846
1847/*
1848 * Validate and set the nsubmirrors attribute in the given mirror
1849 * devconfig_t.
1850 *
1851 * @param       volume
1852 *		the devconfig_t in which to set the nsubmirrors
1853 *
1854 * @param       attr
1855 *		the name of the XML attribute
1856 *
1857 * @param       value
1858 *		the value of the XML attribute
1859 *
1860 * @return      0 on success, non-zero otherwise.
1861 */
1862static int
1863validate_set_mirror_nsubmirrors(
1864	devconfig_t *mirror,
1865	char *attr,
1866	char *value)
1867{
1868	uint16_t nsubmirrors;
1869
1870	/* Convert string to a uint16_t */
1871	if (str_to_uint16(value, &nsubmirrors) != 0) {
1872	    volume_set_error(
1873		gettext("invalid number of submirrors (%s): %s"),
1874		attr, value);
1875	    return (-1);
1876	}
1877
1878	/* Set in stripe */
1879	return (devconfig_set_mirror_nsubs(mirror, nsubmirrors));
1880}
1881
1882/*
1883 * Validate and set the read attribute in the given mirror
1884 * devconfig_t.
1885 *
1886 * @param       volume
1887 *		the devconfig_t in which to set the read
1888 *
1889 * @param       attr
1890 *		the name of the XML attribute
1891 *
1892 * @param       value
1893 *		the value of the XML attribute
1894 *
1895 * @return      0 on success, non-zero otherwise.
1896 */
1897static int
1898validate_set_mirror_read(
1899	devconfig_t *mirror,
1900	char *attr,
1901	char *value)
1902{
1903	mirror_read_strategy_t strategy;
1904
1905	if (strcmp(value, VALID_MIRROR_READ_ROUNDROBIN) == 0) {
1906	    strategy = MIRROR_READ_ROUNDROBIN;
1907	} else
1908
1909	if (strcmp(value, VALID_MIRROR_READ_GEOMETRIC) == 0) {
1910	    strategy = MIRROR_READ_GEOMETRIC;
1911	} else
1912
1913	if (strcmp(value, VALID_MIRROR_READ_FIRST) == 0) {
1914	    strategy = MIRROR_READ_FIRST;
1915	} else
1916
1917	{
1918	    volume_set_error(gettext("%s: invalid mirror read value"), value);
1919	    return (-1);
1920	}
1921
1922	return (devconfig_set_mirror_read(mirror, strategy));
1923}
1924
1925/*
1926 * Validate and set the write attribute in the given mirror
1927 * devconfig_t.
1928 *
1929 * @param       volume
1930 *		the devconfig_t in which to set the write
1931 *
1932 * @param       attr
1933 *		the name of the XML attribute
1934 *
1935 * @param       value
1936 *		the value of the XML attribute
1937 *
1938 * @return      0 on success, non-zero otherwise.
1939 */
1940static int
1941validate_set_mirror_write(
1942	devconfig_t *mirror,
1943	char *attr,
1944	char *value)
1945{
1946	mirror_write_strategy_t strategy;
1947
1948	if (strcmp(value, VALID_MIRROR_WRITE_PARALLEL) == 0) {
1949	    strategy = MIRROR_WRITE_PARALLEL;
1950	} else
1951
1952	if (strcmp(value, VALID_MIRROR_WRITE_SERIAL) == 0) {
1953	    strategy = MIRROR_WRITE_SERIAL;
1954	} else
1955
1956	{
1957	    volume_set_error(gettext("%s: invalid mirror write value"), value);
1958	    return (-1);
1959	}
1960
1961	return (devconfig_set_mirror_write(mirror, strategy));
1962}
1963
1964/*
1965 * Validate and set the passnum attribute in the given mirror
1966 * devconfig_t.
1967 *
1968 * @param       volume
1969 *		the devconfig_t in which to set the passnum
1970 *
1971 * @param       attr
1972 *		the name of the XML attribute
1973 *
1974 * @param       value
1975 *		the value of the XML attribute
1976 *
1977 * @return      0 on success, non-zero otherwise.
1978 */
1979static int
1980validate_set_mirror_passnum(
1981	devconfig_t *mirror,
1982	char *attr,
1983	char *value)
1984{
1985	uint16_t passnum;
1986
1987	/* Convert string to a uint16_t */
1988	if (str_to_uint16(value, &passnum) != 0) {
1989	    volume_set_error(
1990		gettext("invalid mirror pass number (%s): %s"),
1991		attr, value);
1992	    return (-1);
1993	}
1994
1995	/* Set in stripe */
1996	return (devconfig_set_mirror_pass(mirror, passnum));
1997}
1998
1999/*
2000 * Validate and set the redundancy attribute in the given volume
2001 * devconfig_t.
2002 *
2003 * @param       volume
2004 *		the devconfig_t in which to set the redundancy
2005 *
2006 * @param       attr
2007 *		the name of the XML attribute
2008 *
2009 * @param       value
2010 *		the value of the XML attribute
2011 *
2012 * @return      0 on success, non-zero otherwise.
2013 */
2014static int
2015validate_set_volume_redundancy(
2016	devconfig_t *volume,
2017	char *attr,
2018	char *value)
2019{
2020	uint16_t redundancy;
2021
2022	/* Convert string to a uint16_t */
2023	if (str_to_uint16(value, &redundancy) != 0) {
2024	    volume_set_error(
2025		gettext("invalid redundancy level (%s): %s"),
2026		attr, value);
2027	    return (-1);
2028	}
2029
2030	/* Set in stripe */
2031	return (devconfig_set_volume_redundancy_level(volume, redundancy));
2032}
2033
2034/*
2035 * Validate and set the datapaths attribute in the given volume
2036 * devconfig_t.
2037 *
2038 * @param       volume
2039 *		the devconfig_t in which to set the datapaths
2040 *
2041 * @param       attr
2042 *		the name of the XML attribute
2043 *
2044 * @param       value
2045 *		the value of the XML attribute
2046 *
2047 * @return      0 on success, non-zero otherwise.
2048 */
2049static int
2050validate_set_volume_datapaths(
2051	devconfig_t *volume,
2052	char *attr,
2053	char *value)
2054{
2055	uint16_t redundancy;
2056
2057	/* Convert string to a uint16_t */
2058	if (str_to_uint16(value, &redundancy) != 0) {
2059	    volume_set_error(
2060		gettext("invalid number of data paths (%s): %s"),
2061		attr, value);
2062	    return (-1);
2063	}
2064
2065	/* Set in stripe */
2066	return (devconfig_set_volume_npaths(volume, redundancy));
2067}
2068
2069/*
2070 * ******************************************************************
2071 *
2072 * XML attribute accessors/converters
2073 *
2074 * These functions get a value from the appropriate devconfig_t
2075 * accessor, and then convert it to a string.
2076 *
2077 * ******************************************************************
2078 */
2079
2080/*
2081 * Get, as a string, the value of the name attribute of the given
2082 * devconfig_t.  This data must be freed.
2083 *
2084 * @param       device
2085 *		the devconfig_t from which to retrieve the name
2086 *
2087 * @param       attr
2088 *		the name of the XML attribute
2089 *
2090 * @param       value
2091 *		RETURN: the value of the XML attribute
2092 *
2093 * @return      0 on success, non-zero otherwise.
2094 */
2095static int
2096get_as_string_name(
2097	devconfig_t *device,
2098	char *attr,
2099	char **value)
2100{
2101	int error;
2102	char *name;
2103
2104	/* Get name */
2105	if ((error = devconfig_get_name(device, &name)) == 0) {
2106	    if ((*value = strdup(name)) == NULL) {
2107		error = ENOMEM;
2108	    }
2109	}
2110
2111	return (error);
2112}
2113
2114/*
2115 * Get, as a string, the value of the passnum attribute of the given
2116 * mirror devconfig_t.  This data must be freed.
2117 *
2118 * @param       device
2119 *		the devconfig_t from which to retrieve the passnum
2120 *
2121 * @param       attr
2122 *		the name of the XML attribute
2123 *
2124 * @param       value
2125 *		RETURN: the value of the XML attribute
2126 *
2127 * @return      0 on success, non-zero otherwise.
2128 */
2129static int
2130get_as_string_mirror_passnum(
2131	devconfig_t *mirror,
2132	char *attr,
2133	char **value)
2134{
2135	int error;
2136	uint16_t passnum;
2137
2138	/* Get mirror pass number */
2139	if ((error = devconfig_get_mirror_pass(mirror, &passnum)) == 0) {
2140	    error = ll_to_str(passnum, value);
2141	}
2142
2143	return (error);
2144}
2145
2146/*
2147 * Get, as a string, the value of the read attribute of the given
2148 * mirror devconfig_t.  This data must be freed.
2149 *
2150 * @param       device
2151 *		the devconfig_t from which to retrieve the read
2152 *
2153 * @param       attr
2154 *		the name of the XML attribute
2155 *
2156 * @param       value
2157 *		RETURN: the value of the XML attribute
2158 *
2159 * @return      0 on success, non-zero otherwise.
2160 */
2161static int
2162get_as_string_mirror_read(
2163	devconfig_t *mirror,
2164	char *attr,
2165	char **value)
2166{
2167	int error;
2168	mirror_read_strategy_t read;
2169
2170	/* Get mirror read strategy */
2171	if ((error = devconfig_get_mirror_read(mirror, &read)) == 0) {
2172	    if ((*value = strdup(
2173		devconfig_read_strategy_to_str(read))) == NULL) {
2174		error = ENOMEM;
2175	    }
2176	}
2177
2178	return (error);
2179}
2180
2181/*
2182 * Get, as a string, the value of the write attribute of the given
2183 * mirror devconfig_t.  This data must be freed.
2184 *
2185 * @param       device
2186 *		the devconfig_t from which to retrieve the write
2187 *
2188 * @param       attr
2189 *		the name of the XML attribute
2190 *
2191 * @param       value
2192 *		RETURN: the value of the XML attribute
2193 *
2194 * @return      0 on success, non-zero otherwise.
2195 */
2196static int
2197get_as_string_mirror_write(
2198	devconfig_t *mirror,
2199	char *attr,
2200	char **value)
2201{
2202	int error;
2203	mirror_write_strategy_t write;
2204
2205	/* Get mirror write strategy */
2206	if ((error = devconfig_get_mirror_write(mirror, &write)) == 0) {
2207	    if ((*value = strdup(
2208		devconfig_write_strategy_to_str(write))) == NULL) {
2209		error = ENOMEM;
2210	    }
2211	}
2212
2213	return (error);
2214}
2215
2216/*
2217 * Get, as a string, the value of the in_blocks attribute of the given
2218 * device devconfig_t.  This data must be freed.
2219 *
2220 * @param       device
2221 *		the devconfig_t from which to retrieve the in_blocks
2222 *
2223 * @param       attr
2224 *		the name of the XML attribute
2225 *
2226 * @param       value
2227 *		RETURN: the value of the XML attribute
2228 *
2229 * @return      0 on success, non-zero otherwise.
2230 */
2231static int
2232get_as_string_size_in_blocks(
2233	devconfig_t *device,
2234	char *attr,
2235	char **value)
2236{
2237	int error;
2238	uint64_t size;
2239
2240	/* Get size in blocks */
2241	if ((error = devconfig_get_size_in_blocks(device, &size)) == 0) {
2242	    error = ll_to_str(size, value);
2243	}
2244
2245	return (error);
2246}
2247
2248/*
2249 * Get, as a string, the value of the start_block attribute of the
2250 * given slice devconfig_t.  This data must be freed.
2251 *
2252 * @param       device
2253 *		the devconfig_t from which to retrieve the start_block
2254 *
2255 * @param       attr
2256 *		the name of the XML attribute
2257 *
2258 * @param       value
2259 *		RETURN: the value of the XML attribute
2260 *
2261 * @return      0 on success, non-zero otherwise.
2262 */
2263static int
2264get_as_string_slice_start_block(
2265	devconfig_t *slice,
2266	char *attr,
2267	char **value)
2268{
2269	int error;
2270	uint64_t start;
2271
2272	/* Get slice start block */
2273	if ((error = devconfig_get_slice_start_block(slice, &start)) == 0) {
2274	    error = ll_to_str(start, value);
2275	}
2276
2277	return (error);
2278}
2279
2280/*
2281 * Get, as a string, the value of the interlace attribute of the given
2282 * stripe devconfig_t.  This data must be freed.
2283 *
2284 * @param       device
2285 *		the devconfig_t from which to retrieve the interlace
2286 *
2287 * @param       attr
2288 *		the name of the XML attribute
2289 *
2290 * @param       value
2291 *		RETURN: the value of the XML attribute
2292 *
2293 * @return      0 on success, non-zero otherwise.
2294 */
2295static int
2296get_as_string_stripe_interlace(
2297	devconfig_t *stripe,
2298	char *attr,
2299	char **value)
2300{
2301	int error;
2302	uint64_t interlace;
2303
2304	/* Get interlace */
2305	if ((error = devconfig_get_stripe_interlace(
2306		stripe, &interlace)) == 0) {
2307	    error = bytes_to_sizestr(interlace, value, interlace_units, B_TRUE);
2308	}
2309
2310	return (error);
2311}
2312