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