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
clean_up()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
interrupthandler(int sig)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
copy_arg(char * option,char * value,char ** saveto)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
create_volume_request_XML()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
handle_common_opts(int c,boolean_t * handled)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
parse_create_opts(int argc,char * argv[])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
parse_opts(int argc,char * argv[])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
parse_tokenized_list(const char * string,dlist_t ** list)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
parse_verbose_arg(char * arg,int * verbosity)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
print_help_create(FILE * stream)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
print_help_main(FILE * stream)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
print_manual_reference(FILE * stream)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
print_usage(FILE * stream)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
print_usage_create(FILE * stream)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
print_usage_main(FILE * stream)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
print_version(FILE * stream)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
get_doc_from_file(char * file,char ** valid_types,xmlDocPtr * doc,char ** root)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
get_volume_request_or_config(xmlDocPtr * doc,char ** root)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
handle_commands(char * commands)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
handle_config(devconfig_t * config)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
handle_request(request_t * request,defaults_t * defaults)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
write_temp_file(char * text,mode_t mode,char ** file)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
main(int argc,char * argv[])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