xref: /titanic_41/usr/src/cmd/lvm/metassist/controller/metassist.c (revision 2f172c55ef76964744bc62b4500ece87f3089b4d)
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 /*
30  * Front end CLI to metassist.  Parses command line, reads in data
31  * files, provides main() entry point into metassist.  Here's the
32  * complete data validation stack for the project:
33  *
34  * 1. Controller validates command line syntax/order of arguments.
35  *
36  * 2. XML parser validates XML syntax, conformance with DTD
37  *
38  * 3. xml_convert validates proper conversion from string to
39  *    size/integer/float/boolean/etc.
40  *
41  * 4. devconfig_t mutators validate limits/boundaries/min/max/names of
42  *    data.  References md_mdiox.h and possibly libmeta.
43  *
44  * 5. layout validates on remaining issues, including existence of
45  *    given devices, feasibility of request, suitability of specified
46  *    components, and subtle misuse of data structure (like both size
47  *    and components specified).
48  */
49 
50 #include "metassist.h"
51 
52 #include <errno.h>
53 #include <libintl.h>
54 
55 #include <math.h>
56 #include <signal.h>
57 #include <string.h>
58 #include <sys/stat.h>
59 #include <sys/utsname.h>
60 #include <sys/wait.h>
61 #include <unistd.h>
62 #include "getopt_ext.h"
63 #include "locale.h"
64 #include "volume_error.h"
65 #include "volume_output.h"
66 #include "volume_request.h"
67 #include "volume_defaults.h"
68 #include "volume_string.h"
69 #include "xml_convert.h"
70 #include "layout.h"
71 
72 /*
73  * Function prototypes
74  */
75 
76 static void clean_up();
77 static void interrupthandler(int x);
78 static int copy_arg(char *option, char *value, char **saveto);
79 static xmlDocPtr create_volume_request_XML();
80 static int handle_common_opts(int c, boolean_t *handled);
81 static int parse_create_opts(int argc, char *argv[]);
82 static int parse_opts(int argc, char *argv[]);
83 static int parse_tokenized_list(const char *string, dlist_t **list);
84 static int parse_verbose_arg(char *arg, int *verbosity);
85 static void print_help_create(FILE *stream);
86 static void print_help_main(FILE *stream);
87 static void print_manual_reference(FILE *stream);
88 static void print_usage(FILE *stream);
89 static void print_usage_create(FILE *stream);
90 static void print_usage_main(FILE *stream);
91 static int print_version(FILE *stream);
92 static int get_doc_from_file(
93     char *file, char **valid_types, xmlDocPtr *doc, char **root);
94 static int get_volume_request_or_config(xmlDocPtr *doc, char **root);
95 static int handle_commands(char *commands);
96 static int handle_config(devconfig_t *config);
97 static int handle_request(request_t *request, defaults_t *defaults);
98 static int write_temp_file(char *text, mode_t mode, char **file);
99 
100 /*
101  * Data
102  */
103 
104 /* Holds argv[0] */
105 char *progname;
106 
107 /* The action to take */
108 int action = ACTION_EXECUTE;
109 
110 /* Holds the name of the temporary command file */
111 char *commandfile = NULL;
112 
113 /* The metassist subcommand */
114 int subcmd = SUBCMD_NONE;
115 
116 /* The volume-request XML file to read */
117 char *arg_inputfile = NULL;
118 
119 /* The size of the requested volume */
120 char *arg_size = NULL;
121 
122 /* The disk set to use */
123 char *arg_diskset = NULL;
124 
125 /* The volume name to use */
126 char *arg_name = NULL;
127 
128 /* Redundancy level */
129 char *arg_redundancy = NULL;
130 
131 /* Number of datapaths */
132 char *arg_datapaths = NULL;
133 
134 /* Whether to implement fault recovery */
135 boolean_t faultrecovery = B_FALSE;
136 
137 /* Whether to output the config file */
138 boolean_t output_configfile = B_FALSE;
139 
140 /* Whether to output the command file instead of */
141 boolean_t output_commandfile = B_FALSE;
142 
143 /* List of available devices */
144 dlist_t *available = NULL;
145 
146 /* List of unavailable devices */
147 dlist_t *unavailable = NULL;
148 
149 /*
150  * Functions
151  */
152 
153 /*
154  * Frees alloc'd memory, to be called prior to exiting.
155  */
156 static void
157 clean_up()
158 {
159 	/* Remove temporary command file */
160 	if (commandfile != NULL) {
161 	    /* Ignore failure */
162 	    unlink(commandfile);
163 	}
164 
165 	/* Free allocated argument strings */
166 	if (commandfile != NULL) free(commandfile);
167 	if (arg_diskset != NULL) free(arg_diskset);
168 	if (arg_name != NULL) free(arg_name);
169 	if (arg_inputfile != NULL) free(arg_inputfile);
170 
171 	/* Free available dlist and strings within */
172 	dlist_free_items(available, free);
173 
174 	/* Free unavailable dlist and strings within */
175 	dlist_free_items(unavailable, free);
176 
177 	/* Clean up XML data structures */
178 	cleanup_xml();
179 }
180 
181 /*
182  * Signal handler, called to exit gracefully
183  */
184 static void
185 interrupthandler(
186 	int sig)
187 {
188 	char sigstr[SIG2STR_MAX];
189 
190 	if (sig2str(sig, sigstr) != 0) {
191 	    sigstr[0] = '\0';
192 	}
193 
194 	fprintf(stderr,
195 	    gettext("Signal %d (%s) caught -- exiting...\n"), sig, sigstr);
196 
197 	/* Allow layout to cleanup on abnormal exit */
198 	layout_clean_up();
199 
200 	clean_up();
201 	exit(1);
202 }
203 
204 /*
205  * Copies and saves the given argument, verifying that the argument
206  * has not already been saved.
207  *
208  * @param       option
209  *              The flag preceding or type of the argument.  Used only
210  *              in the error message when an option has already been
211  *              saved to *saveto.
212  *
213  * @param       value
214  *              The argument to be copied.
215  *
216  * @param       saveto
217  *              Changed to point to the copied data.  This must point
218  *              to NULL data initially, or it will be assumed that
219  *              this argument has already been set.  This memory must
220  *              be free()d by the caller.
221  *
222  * @return      0 on success, non-zero otherwise.
223  */
224 static int
225 copy_arg(
226 	char *option,
227 	char *value,
228 	char **saveto)
229 {
230 	int error = 0;
231 
232 	/* Has this string already been set? */
233 	if (*saveto != NULL) {
234 	    volume_set_error(
235 		gettext("%s: option specified multiple times"), option);
236 	    error = -1;
237 	} else
238 
239 	if ((*saveto = strdup(value)) == NULL) {
240 	    error = ENOMEM;
241 	}
242 
243 	return (error);
244 }
245 
246 /*
247  * Generates the XML volume request corresponding to the command-line
248  * parameters.  No DTD node is included in this request.
249  *
250  * @return      The XML request, or NULL if an error ocurred in
251  *              generating the text.  This memory must be freed with
252  *              XMLFree().
253  */
254 static xmlDocPtr
255 create_volume_request_XML()
256 {
257 	xmlDocPtr doc;
258 	xmlNodePtr request, volume;
259 
260 	/* Create the XML document */
261 	doc = xmlNewDoc((xmlChar *)"1.0");
262 
263 	/* Create the root node */
264 	request = xmlNewDocNode(
265 	    doc, NULL, (xmlChar *)ELEMENT_VOLUMEREQUEST, NULL);
266 	xmlAddChild((xmlNodePtr) doc, (xmlNodePtr)request);
267 
268 	/* diskset element */
269 	if (arg_diskset != NULL) {
270 	    xmlNodePtr node = xmlNewChild(
271 		request, NULL, (xmlChar *)ELEMENT_DISKSET, NULL);
272 	    xmlSetProp(node,
273 		(xmlChar *)ATTR_NAME, (xmlChar *)arg_diskset);
274 	}
275 
276 	/* available elements */
277 	if (available != NULL) {
278 	    dlist_t *item;
279 	    for (item = available; item != NULL; item = item->next) {
280 		xmlNodePtr node = xmlNewChild(
281 		    request, NULL, (xmlChar *)ELEMENT_AVAILABLE, NULL);
282 		xmlSetProp(node,
283 		    (xmlChar *)ATTR_NAME, (xmlChar *)item->obj);
284 	    }
285 	}
286 
287 	/* unavailable elements */
288 	if (unavailable != NULL) {
289 	    dlist_t *item;
290 	    for (item = unavailable; item != NULL; item = item->next) {
291 		xmlNodePtr node = xmlNewChild(
292 		    request, NULL, (xmlChar *)ELEMENT_UNAVAILABLE, NULL);
293 		xmlSetProp(node,
294 		    (xmlChar *)ATTR_NAME, (xmlChar *)item->obj);
295 	    }
296 	}
297 
298 	/* volume element */
299 	volume = xmlNewChild(request, NULL, (xmlChar *)ELEMENT_VOLUME, NULL);
300 
301 	/* Volume name - optional */
302 	if (arg_name != NULL) {
303 	    xmlSetProp(volume,
304 		(xmlChar *)ATTR_NAME, (xmlChar *)arg_name);
305 	}
306 
307 	/* Volume size - required */
308 	xmlSetProp(volume, (xmlChar *)ATTR_SIZEINBYTES, (xmlChar *)arg_size);
309 
310 	/* Volume redundancy - optional */
311 	if (arg_redundancy != NULL) {
312 	    xmlSetProp(volume,
313 		(xmlChar *)ATTR_VOLUME_REDUNDANCY, (xmlChar *)arg_redundancy);
314 	}
315 
316 	/* Volume fault recovery - optional */
317 	if (faultrecovery == B_TRUE) {
318 	    xmlSetProp(volume,
319 		(xmlChar *)ATTR_VOLUME_FAULTRECOVERY, (xmlChar *)"TRUE");
320 	}
321 
322 	/* Volume datapaths - optional */
323 	if (arg_datapaths != NULL) {
324 	    xmlSetProp(volume,
325 		(xmlChar *)ATTR_VOLUME_DATAPATHS, (xmlChar *)arg_datapaths);
326 	}
327 
328 	if (get_max_verbosity() >= OUTPUT_DEBUG) {
329 	    xmlChar *text;
330 	    /* Get the text dump */
331 	    xmlDocDumpFormatMemory(doc, &text, NULL, 1);
332 	    oprintf(OUTPUT_DEBUG,
333 		gettext("Generated volume-request:\n%s"), text);
334 	    xmlFree(text);
335 	}
336 
337 	return (doc);
338 }
339 
340 /*
341  * Checks the given flag for options common to all subcommands.
342  *
343  * @param       c
344  *              The option letter.
345  *
346  * @param       handled
347  *              RETURN: whether the given option flag was handled.
348  *
349  * @return      Non-zero if an error occurred or the given option was
350  *              invalid or incomplete, 0 otherwise.
351  */
352 static int
353 handle_common_opts(
354 	int c,
355 	boolean_t *handled)
356 {
357 	int error = 0;
358 
359 	/* Level of verbosity to report */
360 	int verbosity;
361 
362 	*handled = B_TRUE;
363 
364 	switch (c) {
365 	    case COMMON_SHORTOPT_VERBOSITY:
366 		if ((error = parse_verbose_arg(optarg, &verbosity)) == 0) {
367 		    set_max_verbosity(verbosity, stderr);
368 		}
369 	    break;
370 
371 	    case COMMON_SHORTOPT_VERSION:
372 		if ((error = print_version(stdout)) == 0) {
373 		    clean_up();
374 		    exit(0);
375 		}
376 	    break;
377 
378 	    case GETOPT_ERR_MISSING_ARG:
379 		volume_set_error(
380 		    gettext("option missing a required argument: -%c"), optopt);
381 		error = -1;
382 	    break;
383 
384 	    case GETOPT_ERR_INVALID_OPT:
385 		volume_set_error(gettext("invalid option: -%c"), optopt);
386 		error = -1;
387 	    break;
388 
389 	    case GETOPT_ERR_INVALID_ARG:
390 		volume_set_error(gettext("invalid argument: %s"), optarg);
391 		error = -1;
392 	    break;
393 
394 	    default:
395 		*handled = B_FALSE;
396 	}
397 
398 	return (error);
399 }
400 
401 /*
402  * Parse the command line options for the create subcommand.
403  *
404  * @param       argc
405  *              The number of arguments in the array
406  *
407  * @param       argv
408  *              The argument array
409  */
410 static int
411 parse_create_opts(
412 	int argc,
413 	char *argv[])
414 {
415 	int c;
416 	int error = 0;
417 
418 	/*
419 	 * Whether a volume request is specified on the command line
420 	 * (vs. a inputfile)
421 	 */
422 	boolean_t request_on_command_line = B_FALSE;
423 
424 	/* Examine next arg */
425 	while (!error && (c = getopt_ext(
426 		argc, argv, CREATE_SHORTOPTS)) != GETOPT_DONE_PARSING) {
427 
428 	    boolean_t handled;
429 
430 	    /* Check for args common to all scopes */
431 	    error = handle_common_opts(c, &handled);
432 	    if (error == 0 && handled == B_FALSE) {
433 
434 		/* Check for args specific to this scope */
435 		switch (c) {
436 
437 		    /* Help */
438 		    case COMMON_SHORTOPT_HELP:
439 			print_help_create(stdout);
440 			clean_up();
441 			exit(0);
442 		    break;
443 
444 		    /* Config file */
445 		    case CREATE_SHORTOPT_CONFIGFILE:
446 			action &= ~ACTION_EXECUTE;
447 			action |= ACTION_OUTPUT_CONFIG;
448 		    break;
449 
450 		    /* Command file */
451 		    case CREATE_SHORTOPT_COMMANDFILE:
452 			action &= ~ACTION_EXECUTE;
453 			action |= ACTION_OUTPUT_COMMANDS;
454 		    break;
455 
456 		    /* Disk set */
457 		    case CREATE_SHORTOPT_DISKSET:
458 			error = copy_arg(
459 			    argv[optind - 2], optarg, &arg_diskset);
460 			request_on_command_line = B_TRUE;
461 		    break;
462 
463 		    /* Name */
464 		    case CREATE_SHORTOPT_NAME:
465 			error = copy_arg(
466 			    argv[optind - 2], optarg, &arg_name);
467 			request_on_command_line = B_TRUE;
468 		    break;
469 
470 		    /* Redundancy */
471 		    case CREATE_SHORTOPT_REDUNDANCY:
472 			error = copy_arg(
473 			    argv[optind - 2], optarg, &arg_redundancy);
474 			request_on_command_line = B_TRUE;
475 		    break;
476 
477 		    /* Data paths */
478 		    case CREATE_SHORTOPT_DATAPATHS:
479 			error = copy_arg(
480 			    argv[optind - 2], optarg, &arg_datapaths);
481 			request_on_command_line = B_TRUE;
482 		    break;
483 
484 		    /* Fault recovery */
485 		    case CREATE_SHORTOPT_FAULTRECOVERY:
486 			faultrecovery = B_TRUE;
487 			request_on_command_line = B_TRUE;
488 		    break;
489 
490 		    /* Available devices */
491 		    case CREATE_SHORTOPT_AVAILABLE:
492 			error = parse_tokenized_list(optarg, &available);
493 			request_on_command_line = B_TRUE;
494 		    break;
495 
496 		    /* Unavailable devices */
497 		    case CREATE_SHORTOPT_UNAVAILABLE:
498 			error = parse_tokenized_list(optarg, &unavailable);
499 			request_on_command_line = B_TRUE;
500 		    break;
501 
502 		    /* Size */
503 		    case CREATE_SHORTOPT_SIZE:
504 			request_on_command_line = B_TRUE;
505 			error = copy_arg(
506 			    argv[optind - 1], optarg, &arg_size);
507 		    break;
508 
509 		    /* Input file */
510 		    case CREATE_SHORTOPT_INPUTFILE:
511 			error = copy_arg(gettext("request/configuration file"),
512 			    optarg, &arg_inputfile);
513 		    break;
514 
515 		    default:
516 			/* Shouldn't be here! */
517 			volume_set_error(
518 			    gettext("unexpected option: %c (%d)"), c, c);
519 			error = -1;
520 		}
521 	    }
522 	}
523 
524 	/*
525 	 * Now that the arguments have been parsed, verify that
526 	 * required options were specified.
527 	 */
528 	if (!error) {
529 	    /* Third invocation method -- two required arguments */
530 	    if (request_on_command_line == B_TRUE) {
531 		if (arg_inputfile != NULL) {
532 		    volume_set_error(
533 			gettext("invalid option(s) specified with input file"));
534 		    error = -1;
535 		} else
536 
537 		if (arg_size == NULL) {
538 		    volume_set_error(gettext("no size specified"));
539 		    error = -1;
540 		} else
541 
542 		if (arg_diskset == NULL) {
543 		    volume_set_error(gettext("no disk set specified"));
544 		    error = -1;
545 		}
546 	    } else
547 
548 	    /* First or second invocation method -- one required argument */
549 	    if (arg_inputfile == NULL) {
550 		volume_set_error(gettext("missing required arguments"));
551 		error = -1;
552 	    }
553 
554 		/*
555 		 * The CREATE_SHORTOPT_CONFIGFILE and
556 		 * CREATE_SHORTOPT_COMMANDFILE arguments are mutually
557 		 * exclusive.  Verify that these were not both specified.
558 		 */
559 	    if (!error &&
560 		action & ACTION_OUTPUT_CONFIG &&
561 		action & ACTION_OUTPUT_COMMANDS) {
562 		volume_set_error(
563 		    gettext("-%c and -%c are mutually exclusive"),
564 		    CREATE_SHORTOPT_CONFIGFILE,
565 		    CREATE_SHORTOPT_COMMANDFILE);
566 		error = -1;
567 	    }
568 	}
569 
570 	return (error);
571 }
572 
573 /*
574  * Parse the main command line options.
575  *
576  * @param       argc
577  *              The number of arguments in the array
578  *
579  * @param       argv
580  *              The argument array
581  *
582  * @return      0 on success, non-zero otherwise.
583  */
584 static int
585 parse_opts(
586 	int argc,
587 	char *argv[])
588 {
589 	int c;
590 	int error = 0;
591 
592 	/* Examine next arg */
593 	while (!error && (c = getopt_ext(
594 		argc, argv, MAIN_SHORTOPTS)) != GETOPT_DONE_PARSING) {
595 
596 	    boolean_t handled;
597 
598 	    /* Check for args common to all scopes */
599 	    error = handle_common_opts(c, &handled);
600 
601 	    if (error == 0 && handled == B_FALSE) {
602 
603 		/* Check for args specific to this scope */
604 		switch (c) {
605 
606 		    /* Help */
607 		    case COMMON_SHORTOPT_HELP:
608 			print_help_main(stdout);
609 			clean_up();
610 			exit(0);
611 		    break;
612 
613 		    /* Non-option arg */
614 		    case GETOPT_NON_OPTION_ARG:
615 
616 			/* See if non-option arg is subcommand */
617 			if (strcmp(optarg, MAIN_SUBCMD_CREATE) == 0) {
618 			    subcmd = SUBCMD_CREATE;
619 			    error = parse_create_opts(argc, argv);
620 			} else {
621 			    /* Argument not recognized */
622 			    volume_set_error(
623 				gettext("%s: invalid argument"), optarg);
624 			    error = -1;
625 			}
626 		    break;
627 
628 		    default:
629 			/* Shouldn't be here! */
630 			volume_set_error(
631 			    gettext("unexpected option: %c (%d)"), c, c);
632 			error = -1;
633 		}
634 	    } else
635 
636 		/*
637 		 * Check invalid arguments to see if they are valid
638 		 * options out of place.
639 		 *
640 		 * NOTE: IN THE FUTURE, A CODE BLOCK SIMILAR TO THIS
641 		 * ONE SHOULD BE ADDED FOR EACH NEW SUBCOMMAND.
642 		 */
643 	    if (c == GETOPT_ERR_INVALID_OPT &&
644 		strchr(CREATE_SHORTOPTS, optopt) != NULL) {
645 		/* Provide a more enlightening error message */
646 		volume_set_error(
647 		    gettext("-%c specified before create subcommand"), optopt);
648 	    }
649 	}
650 
651 	/* Parsing appears to be successful */
652 	if (!error) {
653 
654 	    /* Was a subcommand specified? */
655 	    if (subcmd == SUBCMD_NONE) {
656 		volume_set_error(gettext("no subcommand specified"));
657 		error = -1;
658 	    }
659 	}
660 
661 	return (error);
662 }
663 
664 /*
665  * Convert a string containing a comma/space-separated list into a
666  * dlist.
667  *
668  * @param       string
669  *              a comma/space-separated list
670  *
671  * @param       list
672  *              An exisiting dlist to append to, or NULL to create a
673  *              new list.
674  *
675  * @return      The head node of the dlist_t, whether it was newly
676  *              created or passed in.  On memory allocation error,
677  *              errno will be set and processing will stop.
678  */
679 static int
680 parse_tokenized_list(
681 	const char *string,
682 	dlist_t **list)
683 {
684 	char *stringdup;
685 	char *device;
686 	char *dup;
687 	dlist_t *item;
688 	int error = 0;
689 
690 	/* Don't let strtok alter original argument */
691 	if ((stringdup = strdup(string)) == NULL) {
692 	    error = ENOMEM;
693 	} else {
694 
695 	    /* For each device in the string list... */
696 	    while ((device = strtok(stringdup, DEVICELISTDELIM)) != NULL) {
697 
698 		/* Duplicate the device string */
699 		if ((dup = strdup(device)) == NULL) {
700 		    error = ENOMEM;
701 		    break;
702 		}
703 
704 		/* Create new dlist_t for this device */
705 		if ((item = dlist_new_item((void *)dup)) == NULL) {
706 		    error = ENOMEM;
707 		    free(dup);
708 		    break;
709 		}
710 
711 		/* Append item to list */
712 		*list = dlist_append(item, *list, B_TRUE);
713 
714 		/* strtok needs NULL pointer on subsequent calls */
715 		stringdup = NULL;
716 	    }
717 
718 	    free(stringdup);
719 	}
720 
721 	return (error);
722 }
723 
724 /*
725  * Parses the given verbosity level argument string.
726  *
727  * @param       arg
728  *              A string representation of a verbosity level
729  *
730  * @param       verbosity
731  *              RETURN: the verbosity level
732  *
733  * @return      0 if the given verbosity level string cannot
734  *              be interpreted, non-zero otherwise
735  */
736 static int
737 parse_verbose_arg(
738 	char *arg,
739 	int *verbosity)
740 {
741 	int level;
742 
743 	/* Scan for int */
744 	if (sscanf(arg, "%d", &level) == 1) {
745 
746 	    /* Argument was an integer */
747 	    switch (level) {
748 		case OUTPUT_QUIET:
749 		case OUTPUT_TERSE:
750 		case OUTPUT_VERBOSE:
751 #ifdef	DEBUG
752 		case OUTPUT_DEBUG:
753 #endif
754 
755 		*verbosity = level;
756 		return (0);
757 	    }
758 	}
759 
760 	volume_set_error(gettext("%s: invalid verbosity level"), arg);
761 	return (-1);
762 }
763 
764 /*
765  * Print the help message for the command.
766  *
767  * @param       stream
768  *              stdout or stderr, as appropriate.
769  */
770 static void
771 print_help_create(
772 	FILE *stream)
773 {
774 	print_usage_create(stream);
775 
776 	/* BEGIN CSTYLED */
777 	fprintf(stream, gettext("\
778 \n\
779 Create Solaris Volume Manager volumes.\n\
780 \n\
781 -F <inputfile>\n\
782     Specify the volume request or volume configuration file to\n\
783     process.\n\
784 \n\
785 -s <set>\n\
786     Specify the disk set to use when creating volumes.\n\
787 \n\
788 -S <size>\n\
789     Specify the size of the volume to be created.\n\
790 \n\
791 -a <device1,device2,...>\n\
792     Explicitly specify the devices that can be used in the\n\
793     creation of this volume.\n\
794 \n\
795 -c  Output the command script that would implement the specified or\n\
796     generated volume configuration.\n\
797 \n\
798 -d  Output the volume configuration that satisfies the specified or\n\
799     generated volume request.\n\
800 \n\
801 -f  Specify whether the volume should support automatic component\n\
802     replacement after a fault.\n\
803 \n\
804 -n <name>\n\
805     Specify the name of the new volume.\n\
806 \n\
807 -p <n>\n\
808     Specify the number of required paths to the storage volume.\n\
809 \n\
810 -r <n>\n\
811     Specify the redundancy level (0-4) of the data.\n\
812 \n\
813 -u <device1,device2,...>\n\
814     Explicitly specify devices to exclude in the creation of this\n\
815     volume.\n\
816 \n\
817 -v <value>\n\
818     Specify the level of verbosity.\n\
819 \n\
820 -V  Display program version information.\n\
821 \n\
822 -?  Display help information.\n"));
823 
824 	/* END CSTYLED */
825 
826 	print_manual_reference(stream);
827 }
828 
829 /*
830  * Print the help message for the command.
831  *
832  * @param       stream
833  *              stdout or stderr, as appropriate.
834  */
835 static void
836 print_help_main(
837 	FILE *stream)
838 {
839 	print_usage_main(stream);
840 
841 	/* BEGIN CSTYLED */
842 	fprintf(stream, gettext("\
843 \n\
844 Provide assistance, through automation, with common Solaris Volume\n\
845 Manager tasks.\n\
846 \n\
847 -V  Display program version information.\n\
848 \n\
849 -?  Display help information.  This option can follow <subcommand>\n\
850     for subcommand-specific help.\n\
851 \n\
852 The accepted values for <subcommand> are:\n\
853 \n\
854 create          Create Solaris Volume Manager volumes.\n"));
855 	/* END CSTYLED */
856 
857 	print_manual_reference(stream);
858 }
859 
860 /*
861  * Print the help postscript for the command.
862  *
863  * @param       stream
864  *              stdout or stderr, as appropriate.
865  */
866 static void
867 print_manual_reference(
868 	FILE *stream)
869 {
870 	fprintf(stream, gettext("\nFor more information, see %s(1M).\n"),
871 	    progname);
872 }
873 
874 /*
875  * Print the program usage to the given file stream.
876  *
877  * @param       stream
878  *              stdout or stderr, as appropriate.
879  */
880 static void
881 print_usage(
882 	FILE *stream)
883 {
884 	switch (subcmd) {
885 	    case SUBCMD_CREATE:
886 		print_usage_create(stream);
887 	    break;
888 
889 	    case SUBCMD_NONE:
890 	    default:
891 		print_usage_main(stream);
892 	}
893 }
894 
895 /*
896  * Print the program usage to the given file stream.
897  *
898  * @param       stream
899  *              stdout or stderr, as appropriate.
900  */
901 static void
902 print_usage_create(
903 	FILE *stream)
904 {
905 	/* Create a blank the length of progname */
906 	char *blank = strdup(progname);
907 	memset(blank, ' ', strlen(blank) * sizeof (char));
908 
909 	/* BEGIN CSTYLED */
910 	fprintf(stream, gettext("\
911 Usage: %1$s create [-v <n>] [-c] -F <configfile>\n\
912        %1$s create [-v <n>] [-c|-d] -F <requestfile>\n\
913        %1$s create [-v <n>] [-c|-d]\n\
914        %2$s [-f] [-n <name>] [-p <datapaths>] [-r <redundancy>]\n\
915        %2$s [-a <available>[,<available>,...]]\n\
916        %2$s [-u <unavailable>[,<unavailable>,...]]\n\
917        %2$s -s <setname> -S <size>\n\
918        %1$s create -V\n\
919        %1$s create -?\n"), progname, blank);
920 	/* END CSTYLED */
921 
922 	free(blank);
923 }
924 
925 /*
926  * Print the program usage to the given file stream.
927  *
928  * @param       stream
929  *              stdout or stderr, as appropriate.
930  */
931 static void
932 print_usage_main(
933 	FILE *stream)
934 {
935 	/* BEGIN CSTYLED */
936 	fprintf(stream, gettext("\
937 Usage: %1$s <subcommand> [-?] [options]\n\
938        %1$s -V\n\
939        %1$s -?\n"), progname);
940 	/* END CSTYLED */
941 }
942 
943 /*
944  * Print the program version to the given file stream.
945  *
946  * @param       stream
947  *              stdout or stderr, as appropriate.
948  */
949 static int
950 print_version(
951 	FILE *stream)
952 {
953 	int error = 0;
954 	struct utsname uname_info;
955 
956 	if (uname(&uname_info) < 0) {
957 	    error = -1;
958 	    volume_set_error(gettext("could not determine version"));
959 	} else {
960 	    fprintf(stream, gettext("%s %s"), progname, uname_info.version);
961 	}
962 
963 	fprintf(stream, "\n");
964 
965 	return (error);
966 }
967 
968 /*
969  * Get an xmlDocPtr by parsing the given file.
970  *
971  * @param       file
972  *              The file to read
973  *
974  * @param       valid_types
975  *              An array of the allowable root elements.  If the root
976  *              element of the parsed XML file is not in this list, an
977  *              error is returned.
978  *
979  * @param       doc
980  *              RETURN: the XML document
981  *
982  * @param       root
983  *              RETURN: the root element of the document
984  *
985  * @return      0 if the given XML file was successfully parsed,
986  *              non-zero otherwise
987  */
988 static int
989 get_doc_from_file(
990 	char *file,
991 	char **valid_types,
992 	xmlDocPtr *doc,
993 	char **root)
994 {
995 	int error = 0;
996 
997 	*root = NULL;
998 
999 	/*
1000 	 * Create XML doc by reading the specified file using the
1001 	 * default SAX handler (which has been modified in init_xml())
1002 	 */
1003 	*doc = xmlSAXParseFile((xmlSAXHandlerPtr)
1004 	    &xmlDefaultSAXHandler, file, 0);
1005 
1006 	if (*doc != NULL) {
1007 	    int i;
1008 	    xmlNodePtr root_elem = xmlDocGetRootElement(*doc);
1009 
1010 	    /* Is this a valid root element? */
1011 	    for (i = 0; valid_types[i] != NULL; i++) {
1012 		if (xmlStrcmp(root_elem->name,
1013 		    (const xmlChar *)valid_types[i]) == 0) {
1014 		    *root = valid_types[i];
1015 		}
1016 	    }
1017 
1018 	    /* Was a valid root element found? */
1019 	    if (*root == NULL) {
1020 		xmlFreeDoc(*doc);
1021 	    }
1022 	}
1023 
1024 	/* Was a valid root element found? */
1025 	if (*root == NULL) {
1026 	    volume_set_error(
1027 		gettext("%s: invalid or malformed XML file"), file);
1028 	    error = -1;
1029 	}
1030 
1031 	return (error);
1032 }
1033 
1034 /*
1035  * Creates a volume-request or volume-config XML document, based on the
1036  * arguments passed into the command.
1037  *
1038  * @param       doc
1039  *              RETURN: the XML document, or NULL if no valid document
1040  *              could be created.
1041  *
1042  * @param       root
1043  *              RETURN: the root element of the document
1044  *
1045  * @return      0 if a volume-request or volume-config XML document
1046  *              could be read or created, non-zero otherwise
1047  */
1048 static int
1049 get_volume_request_or_config(
1050 	xmlDocPtr *doc,
1051 	char **root)
1052 {
1053 	int error = 0;
1054 
1055 	if (arg_inputfile == NULL) {
1056 	    /* Create a volume-request based on quality of service */
1057 	    *doc = create_volume_request_XML();
1058 
1059 	    if (*doc == NULL) {
1060 		volume_set_error(gettext("error creating volume request"));
1061 		error = -1;
1062 		*root = NULL;
1063 	    } else {
1064 		*root = ELEMENT_VOLUMEREQUEST;
1065 	    }
1066 	} else {
1067 	    char *valid[] = {
1068 		ELEMENT_VOLUMEREQUEST,
1069 		ELEMENT_VOLUMECONFIG,
1070 		NULL
1071 	    };
1072 
1073 	    error = get_doc_from_file(arg_inputfile, valid, doc, root);
1074 	}
1075 
1076 	return (error);
1077 }
1078 
1079 /*
1080  * Handle processing of the given meta* commands.  Commands are
1081  * written to a file, the file is optionally executed, and optionally
1082  * deleted.
1083  *
1084  * @param       commands
1085  *              The commands to write to the command script file.
1086  *
1087  * @return      0 on success, non-zero otherwise.
1088  */
1089 static int
1090 handle_commands(
1091 	char *commands)
1092 {
1093 	int error = 0;
1094 
1095 	if (action & ACTION_OUTPUT_COMMANDS) {
1096 	    printf("%s", commands);
1097 	}
1098 
1099 	if (action & ACTION_EXECUTE) {
1100 
1101 	    /* Write a temporary file with 744 permissions */
1102 	    if ((error = write_temp_file(commands,
1103 		S_IRWXU | S_IRGRP | S_IROTH, &commandfile)) == 0) {
1104 
1105 		char *command;
1106 
1107 		/* Create command line to execute */
1108 		if (get_max_verbosity() >= OUTPUT_VERBOSE) {
1109 		    /* Verbose */
1110 		    command = stralloccat(3,
1111 			commandfile, " ", COMMAND_VERBOSE_FLAG);
1112 		} else {
1113 		    /* Terse */
1114 		    command = strdup(commandfile);
1115 		}
1116 
1117 		if (command == NULL) {
1118 		    volume_set_error(gettext("could not allocate memory"));
1119 		    error = -1;
1120 		} else {
1121 
1122 		    oprintf(OUTPUT_VERBOSE,
1123 			gettext("Executing command script: %s\n"), command);
1124 
1125 		    /* Execute command */
1126 		    switch (error = system(command)) {
1127 			/* system() failed */
1128 			case -1:
1129 			    error = errno;
1130 			break;
1131 
1132 			/* Command succeded */
1133 			case 0:
1134 			break;
1135 
1136 			/* Command failed */
1137 			default:
1138 			    volume_set_error(
1139 				/* CSTYLED */
1140 				gettext("execution of command script failed with status %d"),
1141 				WEXITSTATUS(error));
1142 			    error = -1;
1143 		    }
1144 		    free(command);
1145 		}
1146 	    }
1147 	}
1148 
1149 	return (error);
1150 }
1151 
1152 /*
1153  * Handle processing of the given volume-config devconfig_t.  The
1154  * devconfig_t is first converted to XML.  Then, depending
1155  * on user input to the command, the XML is either written to a file
1156  * or converted to a command script and passed on to
1157  * handle_commands().
1158  *
1159  * @param       config
1160  *              A devconfig_t representing a valid volume-config.
1161  *
1162  * @return      0 on success, non-zero otherwise.
1163  */
1164 static int
1165 handle_config(
1166 	devconfig_t *config)
1167 {
1168 	int error;
1169 	xmlDocPtr doc;
1170 
1171 	/* Get the xml document for the config */
1172 	if ((error = config_to_xml(config, &doc)) == 0) {
1173 
1174 	    /* Get the text dump */
1175 	    xmlChar *text;
1176 	    xmlDocDumpFormatMemory(doc, &text, NULL, 1);
1177 
1178 	    /* Should we output the config file? */
1179 	    if (action & ACTION_OUTPUT_CONFIG) {
1180 		printf("%s", text);
1181 	    } else {
1182 		oprintf(OUTPUT_DEBUG,
1183 		    gettext("Generated volume-config:\n%s"), text);
1184 	    }
1185 
1186 	    xmlFree(text);
1187 
1188 	    /* Proceed to command generation? */
1189 	    if (action & ACTION_OUTPUT_COMMANDS ||
1190 		action & ACTION_EXECUTE) {
1191 		char *commands;
1192 
1193 		/* Get command script from the file */
1194 		if ((error = xml_to_commands(doc, &commands)) == 0) {
1195 		    if (commands == NULL) {
1196 			volume_set_error(
1197 			    gettext("could not convert XML to commands"));
1198 			error = -1;
1199 		    } else {
1200 			error = handle_commands(commands);
1201 			free(commands);
1202 		    }
1203 		}
1204 	    }
1205 
1206 	    xmlFreeDoc(doc);
1207 	}
1208 
1209 	return (error);
1210 }
1211 
1212 /*
1213  * Handle processing of the given volume-request request_t and
1214  * volume-defaults defaults_t.  A layout is generated from these
1215  * structures and the resulting volume-config devconfig_t is passed on
1216  * to handle_config().
1217  *
1218  * @param       request
1219  *              A request_t representing a valid volume-request.
1220  *
1221  * @param       defaults
1222  *              A defaults_t representing a valid volume-defaults.
1223  *
1224  * @return      0 on success, non-zero otherwise.
1225  */
1226 static int
1227 handle_request(
1228 	request_t *request,
1229 	defaults_t *defaults)
1230 {
1231 	int error;
1232 
1233 	/* Get layout for given request and system defaults */
1234 	if ((error = get_layout(request, defaults)) == 0) {
1235 
1236 	    /* Retrieve resulting volume config */
1237 	    devconfig_t *config = request_get_diskset_config(request);
1238 
1239 	    if (config != NULL) {
1240 		error = handle_config(config);
1241 	    }
1242 	}
1243 
1244 	return (error);
1245 }
1246 
1247 /*
1248  * Write the given text to a temporary file with the given
1249  * permissions.  If the file already exists, return an error.
1250  *
1251  * @param       text
1252  *              The text to write to the file.
1253  *
1254  * @param       mode
1255  *              The permissions to give the file, passed to chmod(2).
1256  *
1257  * @param       file
1258  *              RETURN: The name of the file written.  Must be
1259  *              free()d.
1260  *
1261  * @return      0 on success, non-zero otherwise.
1262  */
1263 static int
1264 write_temp_file(
1265 	char *text,
1266 	mode_t mode,
1267 	char **file)
1268 {
1269 	int error = 0;
1270 
1271 	/*
1272 	 * Create temporary file name -- "XXXXXX" is replaced with
1273 	 * unique char sequence by mkstemp()
1274 	 */
1275 	*file = stralloccat(3, "/tmp/", progname, "XXXXXX");
1276 
1277 	if (*file == NULL) {
1278 	    volume_set_error(gettext("out of memory"));
1279 	    error = -1;
1280 	} else {
1281 	    int fildes;
1282 	    FILE *out = NULL;
1283 
1284 	    /* Open temp file */
1285 	    if ((fildes = mkstemp(*file)) != -1) {
1286 		out = fdopen(fildes, "w");
1287 	    }
1288 
1289 	    if (out == NULL) {
1290 		volume_set_error(gettext(
1291 		    "could not open file for writing: %s"), *file);
1292 		error = -1;
1293 	    } else {
1294 
1295 		fprintf(out, "%s", text);
1296 		fclose(out);
1297 
1298 		if (mode != 0) {
1299 		    if (chmod(*file, mode)) {
1300 			volume_set_error(
1301 			    gettext("could not change permissions of file: %s"),
1302 			    *file);
1303 			error = -1;
1304 		    }
1305 		}
1306 
1307 		/* Remove file on error */
1308 		if (error != 0) {
1309 		    unlink(*file);
1310 		}
1311 	    }
1312 
1313 	    /* Free *file on error */
1314 	    if (error != 0) {
1315 		free(*file);
1316 		*file = NULL;
1317 	    }
1318 	}
1319 
1320 	return (error);
1321 }
1322 
1323 /*
1324  * Main entry to metassist.  See the print_usage_* functions* for
1325  * usage.
1326  *
1327  * @return      0 on successful exit, non-zero otherwise
1328  */
1329 int
1330 main(
1331 	int argc,
1332 	char *argv[])
1333 {
1334 	int error = 0;
1335 	int printusage = 0;
1336 
1337 #ifdef DEBUG
1338 	time_t start = time(NULL);
1339 #endif
1340 
1341 	/*
1342 	 * Get the locale set up before calling any other routines
1343 	 * with messages to ouput.  Just in case we're not in a build
1344 	 * environment, make sure that TEXT_DOMAIN gets set to
1345 	 * something.
1346 	 */
1347 #if !defined(TEXT_DOMAIN)
1348 #define	TEXT_DOMAIN "SYS_TEST"
1349 #endif
1350 	(void) setlocale(LC_ALL, "");
1351 	(void) textdomain(TEXT_DOMAIN);
1352 
1353 	/* Set program name, strip directory */
1354 	if ((progname = strrchr(argv[0], '/')) != NULL) {
1355 	    progname++;
1356 	} else {
1357 	    progname = argv[0];
1358 	}
1359 
1360 	/* Set up signal handlers to exit gracefully */
1361 	{
1362 	    struct sigaction act;
1363 	    act.sa_handler = interrupthandler;
1364 	    sigemptyset(&act.sa_mask);
1365 	    act.sa_flags = 0;
1366 	    sigaction(SIGHUP, &act, (struct sigaction *)0);
1367 	    sigaction(SIGINT, &act, (struct sigaction *)0);
1368 	    sigaction(SIGQUIT, &act, (struct sigaction *)0);
1369 	    sigaction(SIGTERM, &act, (struct sigaction *)0);
1370 	}
1371 
1372 	/* Set default verbosity level */
1373 	set_max_verbosity(OUTPUT_TERSE, stderr);
1374 
1375 	/* Verify we're running as root */
1376 	if (geteuid() != 0) {
1377 	    volume_set_error(gettext("must be run as root"));
1378 	    error = -1;
1379 	} else {
1380 
1381 	    /* Disable error messages from getopt */
1382 	    opterr = 0;
1383 
1384 	    /* Parse command-line options */
1385 	    if ((error = parse_opts(argc, argv)) == 0) {
1386 		xmlDocPtr doc;
1387 		char *root;
1388 
1389 		/* Initialize XML defaults */
1390 		init_xml();
1391 
1392 		/* Read volume-request/config file */
1393 		if ((error = get_volume_request_or_config(&doc, &root)) == 0) {
1394 
1395 		    /* Is this a volume-config? */
1396 		    if (strcmp(root, ELEMENT_VOLUMECONFIG) == 0) {
1397 
1398 			/* Was the -d flag specified? */
1399 			if (action & ACTION_OUTPUT_CONFIG) {
1400 			    /* -d cannot be used with -F <configfile> */
1401 			    volume_set_error(gettext(
1402 				"-%c incompatible with -%c <configfile>"),
1403 				CREATE_SHORTOPT_CONFIGFILE,
1404 				CREATE_SHORTOPT_INPUTFILE);
1405 			    error = -1;
1406 			    printusage = 1;
1407 			} else {
1408 			    devconfig_t *config;
1409 			    if ((error = xml_to_config(doc, &config)) == 0) {
1410 				error = handle_config(config);
1411 				free_devconfig(config);
1412 			    }
1413 			}
1414 		    } else
1415 
1416 		    /* Is this a volume-request? */
1417 		    if (strcmp(root, ELEMENT_VOLUMEREQUEST) == 0) {
1418 			request_t *request;
1419 
1420 			if ((error = xml_to_request(doc, &request)) == 0) {
1421 
1422 			    xmlDocPtr defaults_doc;
1423 			    char *valid[] = {
1424 				ELEMENT_VOLUMEDEFAULTS,
1425 				NULL
1426 			    };
1427 
1428 			    /* Read defaults file */
1429 			    if ((error = get_doc_from_file(VOLUME_DEFAULTS_LOC,
1430 				valid, &defaults_doc, &root)) == 0) {
1431 
1432 				defaults_t *defaults;
1433 
1434 				oprintf(OUTPUT_DEBUG,
1435 				    gettext("Using defaults file: %s\n"),
1436 				    VOLUME_DEFAULTS_LOC);
1437 
1438 				/* Parse defaults XML */
1439 				if ((error = xml_to_defaults(
1440 				    defaults_doc, &defaults)) == 0) {
1441 				    error = handle_request(request, defaults);
1442 				    free_defaults(defaults);
1443 				}
1444 
1445 				xmlFreeDoc(defaults_doc);
1446 			    }
1447 
1448 			    free_request(request);
1449 			}
1450 		    }
1451 
1452 		    xmlFreeDoc(doc);
1453 		}
1454 	    } else {
1455 		printusage = 1;
1456 	    }
1457 	}
1458 
1459 	/* Handle any errors that were propogated */
1460 	if (error != 0) {
1461 	    char *message = get_error_string(error);
1462 
1463 	    if (message != NULL && strlen(message)) {
1464 		fprintf(stderr, "%s: %s\n", progname, message);
1465 
1466 		if (printusage) {
1467 		    fprintf(stderr, "\n");
1468 		}
1469 	    }
1470 
1471 	    if (printusage) {
1472 		print_usage(stderr);
1473 	    }
1474 	}
1475 
1476 #ifdef DEBUG
1477 	/* Print run report to stderr if METASSIST_DEBUG is set */
1478 	if (getenv(METASSIST_DEBUG_ENV) != NULL) {
1479 	    time_t end = time(NULL);
1480 	    struct tm *time;
1481 	    int i;
1482 #define	TIMEFMT	"%8s: %.2d:%.2d:%.2d\n"
1483 
1484 	    fprintf(stderr, " Command:");
1485 	    for (i = 0; i < argc; i++) {
1486 		fprintf(stderr, " %s", argv[i]);
1487 	    }
1488 	    fprintf(stderr, "\n");
1489 
1490 	    fprintf(stderr, " Version: ");
1491 	    print_version(stderr);
1492 
1493 	    time = localtime(&start);
1494 	    fprintf(stderr, TIMEFMT, "Start",
1495 		time->tm_hour, time->tm_min, time->tm_sec);
1496 
1497 	    time = localtime(&end);
1498 	    fprintf(stderr, TIMEFMT, "End",
1499 		time->tm_hour, time->tm_min, time->tm_sec);
1500 
1501 	    end -= start;
1502 	    time = gmtime(&end);
1503 	    fprintf(stderr, TIMEFMT, "Duration",
1504 		time->tm_hour, time->tm_min, time->tm_sec);
1505 	}
1506 #endif
1507 
1508 	clean_up();
1509 
1510 	return (error != 0);
1511 }
1512