metassist.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Front end CLI to metassist. Parses command line, reads in data
* files, provides main() entry point into metassist. Here's the
* complete data validation stack for the project:
*
* 1. Controller validates command line syntax/order of arguments.
*
* 2. XML parser validates XML syntax, conformance with DTD
*
* 3. xml_convert validates proper conversion from string to
* size/integer/float/boolean/etc.
*
* 4. devconfig_t mutators validate limits/boundaries/min/max/names of
* data. References md_mdiox.h and possibly libmeta.
*
* 5. layout validates on remaining issues, including existence of
* given devices, feasibility of request, suitability of specified
* components, and subtle misuse of data structure (like both size
* and components specified).
*/
#include "metassist.h"
#include <errno.h>
#include <libintl.h>
#include <math.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>
#include "getopt_ext.h"
#include "locale.h"
#include "volume_error.h"
#include "volume_output.h"
#include "volume_request.h"
#include "volume_defaults.h"
#include "volume_string.h"
#include "xml_convert.h"
#include "layout.h"
/*
* Function prototypes
*/
static void clean_up();
static void interrupthandler(int x);
static int copy_arg(char *option, char *value, char **saveto);
static xmlDocPtr create_volume_request_XML();
static int handle_common_opts(int c, boolean_t *handled);
static int parse_create_opts(int argc, char *argv[]);
static int parse_opts(int argc, char *argv[]);
static int parse_tokenized_list(const char *string, dlist_t **list);
static int parse_verbose_arg(char *arg, int *verbosity);
static void print_help_create(FILE *stream);
static void print_help_main(FILE *stream);
static void print_manual_reference(FILE *stream);
static void print_usage(FILE *stream);
static void print_usage_create(FILE *stream);
static void print_usage_main(FILE *stream);
static int print_version(FILE *stream);
static int get_doc_from_file(
char *file, char **valid_types, xmlDocPtr *doc, char **root);
static int get_volume_request_or_config(xmlDocPtr *doc, char **root);
static int handle_commands(char *commands);
static int handle_config(devconfig_t *config);
static int handle_request(request_t *request, defaults_t *defaults);
static int write_temp_file(char *text, mode_t mode, char **file);
/*
* Data
*/
/* Holds argv[0] */
char *progname;
/* The action to take */
int action = ACTION_EXECUTE;
/* Holds the name of the temporary command file */
char *commandfile = NULL;
/* The metassist subcommand */
int subcmd = SUBCMD_NONE;
/* The volume-request XML file to read */
char *arg_inputfile = NULL;
/* The size of the requested volume */
char *arg_size = NULL;
/* The disk set to use */
char *arg_diskset = NULL;
/* The volume name to use */
char *arg_name = NULL;
/* Redundancy level */
char *arg_redundancy = NULL;
/* Number of datapaths */
char *arg_datapaths = NULL;
/* Whether to implement fault recovery */
boolean_t faultrecovery = B_FALSE;
/* Whether to output the config file */
boolean_t output_configfile = B_FALSE;
/* Whether to output the command file instead of */
boolean_t output_commandfile = B_FALSE;
/* List of available devices */
dlist_t *available = NULL;
/* List of unavailable devices */
dlist_t *unavailable = NULL;
/*
* Functions
*/
/*
* Frees alloc'd memory, to be called prior to exiting.
*/
static void
clean_up()
{
/* Remove temporary command file */
if (commandfile != NULL) {
/* Ignore failure */
unlink(commandfile);
}
/* Free allocated argument strings */
if (commandfile != NULL) free(commandfile);
if (arg_diskset != NULL) free(arg_diskset);
if (arg_name != NULL) free(arg_name);
if (arg_inputfile != NULL) free(arg_inputfile);
/* Free available dlist and strings within */
dlist_free_items(available, free);
/* Free unavailable dlist and strings within */
dlist_free_items(unavailable, free);
/* Clean up XML data structures */
cleanup_xml();
}
/*
* Signal handler, called to exit gracefully
*/
static void
interrupthandler(
int sig)
{
char sigstr[SIG2STR_MAX];
if (sig2str(sig, sigstr) != 0) {
sigstr[0] = '\0';
}
fprintf(stderr,
gettext("Signal %d (%s) caught -- exiting...\n"), sig, sigstr);
/* Allow layout to cleanup on abnormal exit */
layout_clean_up();
clean_up();
exit(1);
}
/*
* Copies and saves the given argument, verifying that the argument
* has not already been saved.
*
* @param option
* The flag preceding or type of the argument. Used only
* in the error message when an option has already been
* saved to *saveto.
*
* @param value
* The argument to be copied.
*
* @param saveto
* Changed to point to the copied data. This must point
* to NULL data initially, or it will be assumed that
* this argument has already been set. This memory must
* be free()d by the caller.
*
* @return 0 on success, non-zero otherwise.
*/
static int
copy_arg(
char *option,
char *value,
char **saveto)
{
int error = 0;
/* Has this string already been set? */
if (*saveto != NULL) {
volume_set_error(
gettext("%s: option specified multiple times"), option);
error = -1;
} else
if ((*saveto = strdup(value)) == NULL) {
error = ENOMEM;
}
return (error);
}
/*
* Generates the XML volume request corresponding to the command-line
* parameters. No DTD node is included in this request.
*
* @return The XML request, or NULL if an error ocurred in
* generating the text. This memory must be freed with
* XMLFree().
*/
static xmlDocPtr
create_volume_request_XML()
{
xmlDocPtr doc;
xmlNodePtr request, volume;
/* Create the XML document */
doc = xmlNewDoc((xmlChar *)"1.0");
/* Create the root node */
request = xmlNewDocNode(
doc, NULL, (xmlChar *)ELEMENT_VOLUMEREQUEST, NULL);
xmlAddChild((xmlNodePtr) doc, (xmlNodePtr)request);
/* diskset element */
if (arg_diskset != NULL) {
xmlNodePtr node = xmlNewChild(
request, NULL, (xmlChar *)ELEMENT_DISKSET, NULL);
xmlSetProp(node,
(xmlChar *)ATTR_NAME, (xmlChar *)arg_diskset);
}
/* available elements */
if (available != NULL) {
dlist_t *item;
for (item = available; item != NULL; item = item->next) {
xmlNodePtr node = xmlNewChild(
request, NULL, (xmlChar *)ELEMENT_AVAILABLE, NULL);
xmlSetProp(node,
(xmlChar *)ATTR_NAME, (xmlChar *)item->obj);
}
}
/* unavailable elements */
if (unavailable != NULL) {
dlist_t *item;
for (item = unavailable; item != NULL; item = item->next) {
xmlNodePtr node = xmlNewChild(
request, NULL, (xmlChar *)ELEMENT_UNAVAILABLE, NULL);
xmlSetProp(node,
(xmlChar *)ATTR_NAME, (xmlChar *)item->obj);
}
}
/* volume element */
volume = xmlNewChild(request, NULL, (xmlChar *)ELEMENT_VOLUME, NULL);
/* Volume name - optional */
if (arg_name != NULL) {
xmlSetProp(volume,
(xmlChar *)ATTR_NAME, (xmlChar *)arg_name);
}
/* Volume size - required */
xmlSetProp(volume, (xmlChar *)ATTR_SIZEINBYTES, (xmlChar *)arg_size);
/* Volume redundancy - optional */
if (arg_redundancy != NULL) {
xmlSetProp(volume,
(xmlChar *)ATTR_VOLUME_REDUNDANCY, (xmlChar *)arg_redundancy);
}
/* Volume fault recovery - optional */
if (faultrecovery == B_TRUE) {
xmlSetProp(volume,
(xmlChar *)ATTR_VOLUME_FAULTRECOVERY, (xmlChar *)"TRUE");
}
/* Volume datapaths - optional */
if (arg_datapaths != NULL) {
xmlSetProp(volume,
(xmlChar *)ATTR_VOLUME_DATAPATHS, (xmlChar *)arg_datapaths);
}
if (get_max_verbosity() >= OUTPUT_DEBUG) {
xmlChar *text;
/* Get the text dump */
xmlDocDumpFormatMemory(doc, &text, NULL, 1);
oprintf(OUTPUT_DEBUG,
gettext("Generated volume-request:\n%s"), text);
xmlFree(text);
}
return (doc);
}
/*
* Checks the given flag for options common to all subcommands.
*
* @param c
* The option letter.
*
* @param handled
* RETURN: whether the given option flag was handled.
*
* @return Non-zero if an error occurred or the given option was
* invalid or incomplete, 0 otherwise.
*/
static int
handle_common_opts(
int c,
boolean_t *handled)
{
int error = 0;
/* Level of verbosity to report */
int verbosity;
*handled = B_TRUE;
switch (c) {
case COMMON_SHORTOPT_VERBOSITY:
if ((error = parse_verbose_arg(optarg, &verbosity)) == 0) {
set_max_verbosity(verbosity, stderr);
}
break;
case COMMON_SHORTOPT_VERSION:
if ((error = print_version(stdout)) == 0) {
clean_up();
exit(0);
}
break;
case GETOPT_ERR_MISSING_ARG:
volume_set_error(
gettext("option missing a required argument: -%c"), optopt);
error = -1;
break;
case GETOPT_ERR_INVALID_OPT:
volume_set_error(gettext("invalid option: -%c"), optopt);
error = -1;
break;
case GETOPT_ERR_INVALID_ARG:
volume_set_error(gettext("invalid argument: %s"), optarg);
error = -1;
break;
default:
*handled = B_FALSE;
}
return (error);
}
/*
* Parse the command line options for the create subcommand.
*
* @param argc
* The number of arguments in the array
*
* @param argv
* The argument array
*/
static int
parse_create_opts(
int argc,
char *argv[])
{
int c;
int error = 0;
/*
* Whether a volume request is specified on the command line
* (vs. a inputfile)
*/
boolean_t request_on_command_line = B_FALSE;
/* Examine next arg */
while (!error && (c = getopt_ext(
argc, argv, CREATE_SHORTOPTS)) != GETOPT_DONE_PARSING) {
boolean_t handled;
/* Check for args common to all scopes */
error = handle_common_opts(c, &handled);
if (error == 0 && handled == B_FALSE) {
/* Check for args specific to this scope */
switch (c) {
/* Help */
case COMMON_SHORTOPT_HELP:
print_help_create(stdout);
clean_up();
exit(0);
break;
/* Config file */
case CREATE_SHORTOPT_CONFIGFILE:
action &= ~ACTION_EXECUTE;
action |= ACTION_OUTPUT_CONFIG;
break;
/* Command file */
case CREATE_SHORTOPT_COMMANDFILE:
action &= ~ACTION_EXECUTE;
action |= ACTION_OUTPUT_COMMANDS;
break;
/* Disk set */
case CREATE_SHORTOPT_DISKSET:
error = copy_arg(
argv[optind - 2], optarg, &arg_diskset);
request_on_command_line = B_TRUE;
break;
/* Name */
case CREATE_SHORTOPT_NAME:
error = copy_arg(
argv[optind - 2], optarg, &arg_name);
request_on_command_line = B_TRUE;
break;
/* Redundancy */
case CREATE_SHORTOPT_REDUNDANCY:
error = copy_arg(
argv[optind - 2], optarg, &arg_redundancy);
request_on_command_line = B_TRUE;
break;
/* Data paths */
case CREATE_SHORTOPT_DATAPATHS:
error = copy_arg(
argv[optind - 2], optarg, &arg_datapaths);
request_on_command_line = B_TRUE;
break;
/* Fault recovery */
case CREATE_SHORTOPT_FAULTRECOVERY:
faultrecovery = B_TRUE;
request_on_command_line = B_TRUE;
break;
/* Available devices */
case CREATE_SHORTOPT_AVAILABLE:
error = parse_tokenized_list(optarg, &available);
request_on_command_line = B_TRUE;
break;
/* Unavailable devices */
case CREATE_SHORTOPT_UNAVAILABLE:
error = parse_tokenized_list(optarg, &unavailable);
request_on_command_line = B_TRUE;
break;
/* Size */
case CREATE_SHORTOPT_SIZE:
request_on_command_line = B_TRUE;
error = copy_arg(
argv[optind - 1], optarg, &arg_size);
break;
/* Input file */
case CREATE_SHORTOPT_INPUTFILE:
error = copy_arg(gettext("request/configuration file"),
optarg, &arg_inputfile);
break;
default:
/* Shouldn't be here! */
volume_set_error(
gettext("unexpected option: %c (%d)"), c, c);
error = -1;
}
}
}
/*
* Now that the arguments have been parsed, verify that
* required options were specified.
*/
if (!error) {
/* Third invocation method -- two required arguments */
if (request_on_command_line == B_TRUE) {
if (arg_inputfile != NULL) {
volume_set_error(
gettext("invalid option(s) specified with input file"));
error = -1;
} else
if (arg_size == NULL) {
volume_set_error(gettext("no size specified"));
error = -1;
} else
if (arg_diskset == NULL) {
volume_set_error(gettext("no disk set specified"));
error = -1;
}
} else
/* First or second invocation method -- one required argument */
if (arg_inputfile == NULL) {
volume_set_error(gettext("missing required arguments"));
error = -1;
}
/*
* The CREATE_SHORTOPT_CONFIGFILE and
* CREATE_SHORTOPT_COMMANDFILE arguments are mutually
* exclusive. Verify that these were not both specified.
*/
if (!error &&
action & ACTION_OUTPUT_CONFIG &&
action & ACTION_OUTPUT_COMMANDS) {
volume_set_error(
gettext("-%c and -%c are mutually exclusive"),
CREATE_SHORTOPT_CONFIGFILE,
CREATE_SHORTOPT_COMMANDFILE);
error = -1;
}
}
return (error);
}
/*
* Parse the main command line options.
*
* @param argc
* The number of arguments in the array
*
* @param argv
* The argument array
*
* @return 0 on success, non-zero otherwise.
*/
static int
parse_opts(
int argc,
char *argv[])
{
int c;
int error = 0;
/* Examine next arg */
while (!error && (c = getopt_ext(
argc, argv, MAIN_SHORTOPTS)) != GETOPT_DONE_PARSING) {
boolean_t handled;
/* Check for args common to all scopes */
error = handle_common_opts(c, &handled);
if (error == 0 && handled == B_FALSE) {
/* Check for args specific to this scope */
switch (c) {
/* Help */
case COMMON_SHORTOPT_HELP:
print_help_main(stdout);
clean_up();
exit(0);
break;
/* Non-option arg */
case GETOPT_NON_OPTION_ARG:
/* See if non-option arg is subcommand */
if (strcmp(optarg, MAIN_SUBCMD_CREATE) == 0) {
subcmd = SUBCMD_CREATE;
error = parse_create_opts(argc, argv);
} else {
/* Argument not recognized */
volume_set_error(
gettext("%s: invalid argument"), optarg);
error = -1;
}
break;
default:
/* Shouldn't be here! */
volume_set_error(
gettext("unexpected option: %c (%d)"), c, c);
error = -1;
}
} else
/*
* Check invalid arguments to see if they are valid
* options out of place.
*
* NOTE: IN THE FUTURE, A CODE BLOCK SIMILAR TO THIS
* ONE SHOULD BE ADDED FOR EACH NEW SUBCOMMAND.
*/
if (c == GETOPT_ERR_INVALID_OPT &&
strchr(CREATE_SHORTOPTS, optopt) != NULL) {
/* Provide a more enlightening error message */
volume_set_error(
gettext("-%c specified before create subcommand"), optopt);
}
}
/* Parsing appears to be successful */
if (!error) {
/* Was a subcommand specified? */
if (subcmd == SUBCMD_NONE) {
volume_set_error(gettext("no subcommand specified"));
error = -1;
}
}
return (error);
}
/*
* Convert a string containing a comma/space-separated list into a
* dlist.
*
* @param string
* a comma/space-separated list
*
* @param list
* An exisiting dlist to append to, or NULL to create a
* new list.
*
* @return The head node of the dlist_t, whether it was newly
* created or passed in. On memory allocation error,
* errno will be set and processing will stop.
*/
static int
parse_tokenized_list(
const char *string,
dlist_t **list)
{
char *stringdup;
char *device;
char *dup;
dlist_t *item;
int error = 0;
/* Don't let strtok alter original argument */
if ((stringdup = strdup(string)) == NULL) {
error = ENOMEM;
} else {
/* For each device in the string list... */
while ((device = strtok(stringdup, DEVICELISTDELIM)) != NULL) {
/* Duplicate the device string */
if ((dup = strdup(device)) == NULL) {
error = ENOMEM;
break;
}
/* Create new dlist_t for this device */
if ((item = dlist_new_item((void *)dup)) == NULL) {
error = ENOMEM;
free(dup);
break;
}
/* Append item to list */
*list = dlist_append(item, *list, B_TRUE);
/* strtok needs NULL pointer on subsequent calls */
stringdup = NULL;
}
free(stringdup);
}
return (error);
}
/*
* Parses the given verbosity level argument string.
*
* @param arg
* A string representation of a verbosity level
*
* @param verbosity
* RETURN: the verbosity level
*
* @return 0 if the given verbosity level string cannot
* be interpreted, non-zero otherwise
*/
static int
parse_verbose_arg(
char *arg,
int *verbosity)
{
int level;
/* Scan for int */
if (sscanf(arg, "%d", &level) == 1) {
/* Argument was an integer */
switch (level) {
case OUTPUT_QUIET:
case OUTPUT_TERSE:
case OUTPUT_VERBOSE:
#ifdef DEBUG
case OUTPUT_DEBUG:
#endif
*verbosity = level;
return (0);
}
}
volume_set_error(gettext("%s: invalid verbosity level"), arg);
return (-1);
}
/*
* Print the help message for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_help_create(
FILE *stream)
{
print_usage_create(stream);
/* BEGIN CSTYLED */
fprintf(stream, gettext("\
\n\
Create Solaris Volume Manager volumes.\n\
\n\
-F <inputfile>\n\
Specify the volume request or volume configuration file to\n\
process.\n\
\n\
-s <set>\n\
Specify the disk set to use when creating volumes.\n\
\n\
-S <size>\n\
Specify the size of the volume to be created.\n\
\n\
-a <device1,device2,...>\n\
Explicitly specify the devices that can be used in the\n\
creation of this volume.\n\
\n\
-c Output the command script that would implement the specified or\n\
generated volume configuration.\n\
\n\
-d Output the volume configuration that satisfies the specified or\n\
generated volume request.\n\
\n\
-f Specify whether the volume should support automatic component\n\
replacement after a fault.\n\
\n\
-n <name>\n\
Specify the name of the new volume.\n\
\n\
-p <n>\n\
Specify the number of required paths to the storage volume.\n\
\n\
-r <n>\n\
Specify the redundancy level (0-4) of the data.\n\
\n\
-u <device1,device2,...>\n\
Explicitly specify devices to exclude in the creation of this\n\
volume.\n\
\n\
-v <value>\n\
Specify the level of verbosity.\n\
\n\
-V Display program version information.\n\
\n\
-? Display help information.\n"));
/* END CSTYLED */
print_manual_reference(stream);
}
/*
* Print the help message for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_help_main(
FILE *stream)
{
print_usage_main(stream);
/* BEGIN CSTYLED */
fprintf(stream, gettext("\
\n\
Provide assistance, through automation, with common Solaris Volume\n\
Manager tasks.\n\
\n\
-V Display program version information.\n\
\n\
-? Display help information. This option can follow <subcommand>\n\
for subcommand-specific help.\n\
\n\
The accepted values for <subcommand> are:\n\
\n\
create Create Solaris Volume Manager volumes.\n"));
/* END CSTYLED */
print_manual_reference(stream);
}
/*
* Print the help postscript for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_manual_reference(
FILE *stream)
{
fprintf(stream, gettext("\nFor more information, see %s(1M).\n"),
progname);
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_usage(
FILE *stream)
{
switch (subcmd) {
case SUBCMD_CREATE:
print_usage_create(stream);
break;
case SUBCMD_NONE:
default:
print_usage_main(stream);
}
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_usage_create(
FILE *stream)
{
/* Create a blank the length of progname */
char *blank = strdup(progname);
memset(blank, ' ', strlen(blank) * sizeof (char));
/* BEGIN CSTYLED */
fprintf(stream, gettext("\
Usage: %1$s create [-v <n>] [-c] -F <configfile>\n\
%1$s create [-v <n>] [-c|-d] -F <requestfile>\n\
%1$s create [-v <n>] [-c|-d]\n\
%2$s [-f] [-n <name>] [-p <datapaths>] [-r <redundancy>]\n\
%2$s [-a <available>[,<available>,...]]\n\
%2$s [-u <unavailable>[,<unavailable>,...]]\n\
%2$s -s <setname> -S <size>\n\
%1$s create -V\n\
%1$s create -?\n"), progname, blank);
/* END CSTYLED */
free(blank);
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
print_usage_main(
FILE *stream)
{
/* BEGIN CSTYLED */
fprintf(stream, gettext("\
Usage: %1$s <subcommand> [-?] [options]\n\
%1$s -V\n\
%1$s -?\n"), progname);
/* END CSTYLED */
}
/*
* Print the program version to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static int
print_version(
FILE *stream)
{
int error = 0;
struct utsname uname_info;
if (uname(&uname_info) < 0) {
error = -1;
volume_set_error(gettext("could not determine version"));
} else {
fprintf(stream, gettext("%s %s"), progname, uname_info.version);
}
fprintf(stream, "\n");
return (error);
}
/*
* Get an xmlDocPtr by parsing the given file.
*
* @param file
* The file to read
*
* @param valid_types
* An array of the allowable root elements. If the root
* element of the parsed XML file is not in this list, an
* error is returned.
*
* @param doc
* RETURN: the XML document
*
* @param root
* RETURN: the root element of the document
*
* @return 0 if the given XML file was successfully parsed,
* non-zero otherwise
*/
static int
get_doc_from_file(
char *file,
char **valid_types,
xmlDocPtr *doc,
char **root)
{
int error = 0;
*root = NULL;
/*
* Create XML doc by reading the specified file using the
* default SAX handler (which has been modified in init_xml())
*/
*doc = xmlSAXParseFile((xmlSAXHandlerPtr)
&xmlDefaultSAXHandler, file, 0);
if (*doc != NULL) {
int i;
xmlNodePtr root_elem = xmlDocGetRootElement(*doc);
/* Is this a valid root element? */
for (i = 0; valid_types[i] != NULL; i++) {
if (xmlStrcmp(root_elem->name,
(const xmlChar *)valid_types[i]) == 0) {
*root = valid_types[i];
}
}
/* Was a valid root element found? */
if (*root == NULL) {
xmlFreeDoc(*doc);
}
}
/* Was a valid root element found? */
if (*root == NULL) {
volume_set_error(
gettext("%s: invalid or malformed XML file"), file);
error = -1;
}
return (error);
}
/*
* Creates a volume-request or volume-config XML document, based on the
* arguments passed into the command.
*
* @param doc
* RETURN: the XML document, or NULL if no valid document
* could be created.
*
* @param root
* RETURN: the root element of the document
*
* @return 0 if a volume-request or volume-config XML document
* could be read or created, non-zero otherwise
*/
static int
get_volume_request_or_config(
xmlDocPtr *doc,
char **root)
{
int error = 0;
if (arg_inputfile == NULL) {
/* Create a volume-request based on quality of service */
*doc = create_volume_request_XML();
if (*doc == NULL) {
volume_set_error(gettext("error creating volume request"));
error = -1;
*root = NULL;
} else {
*root = ELEMENT_VOLUMEREQUEST;
}
} else {
char *valid[] = {
ELEMENT_VOLUMEREQUEST,
ELEMENT_VOLUMECONFIG,
NULL
};
error = get_doc_from_file(arg_inputfile, valid, doc, root);
}
return (error);
}
/*
* Handle processing of the given meta* commands. Commands are
* written to a file, the file is optionally executed, and optionally
* deleted.
*
* @param commands
* The commands to write to the command script file.
*
* @return 0 on success, non-zero otherwise.
*/
static int
handle_commands(
char *commands)
{
int error = 0;
if (action & ACTION_OUTPUT_COMMANDS) {
printf("%s", commands);
}
if (action & ACTION_EXECUTE) {
/* Write a temporary file with 744 permissions */
if ((error = write_temp_file(commands,
S_IRWXU | S_IRGRP | S_IROTH, &commandfile)) == 0) {
char *command;
/* Create command line to execute */
if (get_max_verbosity() >= OUTPUT_VERBOSE) {
/* Verbose */
command = stralloccat(3,
commandfile, " ", COMMAND_VERBOSE_FLAG);
} else {
/* Terse */
command = strdup(commandfile);
}
if (command == NULL) {
volume_set_error(gettext("could not allocate memory"));
error = -1;
} else {
oprintf(OUTPUT_VERBOSE,
gettext("Executing command script: %s\n"), command);
/* Execute command */
switch (error = system(command)) {
/* system() failed */
case -1:
error = errno;
break;
/* Command succeded */
case 0:
break;
/* Command failed */
default:
volume_set_error(
/* CSTYLED */
gettext("execution of command script failed with status %d"),
WEXITSTATUS(error));
error = -1;
}
free(command);
}
}
}
return (error);
}
/*
* Handle processing of the given volume-config devconfig_t. The
* devconfig_t is first converted to XML. Then, depending
* on user input to the command, the XML is either written to a file
* or converted to a command script and passed on to
* handle_commands().
*
* @param config
* A devconfig_t representing a valid volume-config.
*
* @return 0 on success, non-zero otherwise.
*/
static int
handle_config(
devconfig_t *config)
{
int error;
xmlDocPtr doc;
/* Get the xml document for the config */
if ((error = config_to_xml(config, &doc)) == 0) {
/* Get the text dump */
xmlChar *text;
xmlDocDumpFormatMemory(doc, &text, NULL, 1);
/* Should we output the config file? */
if (action & ACTION_OUTPUT_CONFIG) {
printf("%s", text);
} else {
oprintf(OUTPUT_DEBUG,
gettext("Generated volume-config:\n%s"), text);
}
xmlFree(text);
/* Proceed to command generation? */
if (action & ACTION_OUTPUT_COMMANDS ||
action & ACTION_EXECUTE) {
char *commands;
/* Get command script from the file */
if ((error = xml_to_commands(doc, &commands)) == 0) {
if (commands == NULL) {
volume_set_error(
gettext("could not convert XML to commands"));
error = -1;
} else {
error = handle_commands(commands);
free(commands);
}
}
}
xmlFreeDoc(doc);
}
return (error);
}
/*
* Handle processing of the given volume-request request_t and
* volume-defaults defaults_t. A layout is generated from these
* structures and the resulting volume-config devconfig_t is passed on
* to handle_config().
*
* @param request
* A request_t representing a valid volume-request.
*
* @param defaults
* A defaults_t representing a valid volume-defaults.
*
* @return 0 on success, non-zero otherwise.
*/
static int
handle_request(
request_t *request,
defaults_t *defaults)
{
int error;
/* Get layout for given request and system defaults */
if ((error = get_layout(request, defaults)) == 0) {
/* Retrieve resulting volume config */
devconfig_t *config = request_get_diskset_config(request);
if (config != NULL) {
error = handle_config(config);
}
}
return (error);
}
/*
* Write the given text to a temporary file with the given
* permissions. If the file already exists, return an error.
*
* @param text
* The text to write to the file.
*
* @param mode
* The permissions to give the file, passed to chmod(2).
*
* @param file
* RETURN: The name of the file written. Must be
* free()d.
*
* @return 0 on success, non-zero otherwise.
*/
static int
write_temp_file(
char *text,
mode_t mode,
char **file)
{
int error = 0;
/*
* Create temporary file name -- "XXXXXX" is replaced with
* unique char sequence by mkstemp()
*/
*file = stralloccat(3, "/tmp/", progname, "XXXXXX");
if (*file == NULL) {
volume_set_error(gettext("out of memory"));
error = -1;
} else {
int fildes;
FILE *out = NULL;
/* Open temp file */
if ((fildes = mkstemp(*file)) != -1) {
out = fdopen(fildes, "w");
}
if (out == NULL) {
volume_set_error(gettext(
"could not open file for writing: %s"), *file);
error = -1;
} else {
fprintf(out, "%s", text);
fclose(out);
if (mode != 0) {
if (chmod(*file, mode)) {
volume_set_error(
gettext("could not change permissions of file: %s"),
*file);
error = -1;
}
}
/* Remove file on error */
if (error != 0) {
unlink(*file);
}
}
/* Free *file on error */
if (error != 0) {
free(*file);
*file = NULL;
}
}
return (error);
}
/*
* Main entry to metassist. See the print_usage_* functions* for
* usage.
*
* @return 0 on successful exit, non-zero otherwise
*/
int
main(
int argc,
char *argv[])
{
int error = 0;
int printusage = 0;
#ifdef DEBUG
time_t start = time(NULL);
#endif
/*
* Get the locale set up before calling any other routines
* with messages to ouput. Just in case we're not in a build
* environment, make sure that TEXT_DOMAIN gets set to
* something.
*/
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
/* Set program name, strip directory */
if ((progname = strrchr(argv[0], '/')) != NULL) {
progname++;
} else {
progname = argv[0];
}
/* Set up signal handlers to exit gracefully */
{
struct sigaction act;
act.sa_handler = interrupthandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGHUP, &act, (struct sigaction *)0);
sigaction(SIGINT, &act, (struct sigaction *)0);
sigaction(SIGQUIT, &act, (struct sigaction *)0);
sigaction(SIGTERM, &act, (struct sigaction *)0);
}
/* Set default verbosity level */
set_max_verbosity(OUTPUT_TERSE, stderr);
/* Verify we're running as root */
if (geteuid() != 0) {
volume_set_error(gettext("must be run as root"));
error = -1;
} else {
/* Disable error messages from getopt */
opterr = 0;
/* Parse command-line options */
if ((error = parse_opts(argc, argv)) == 0) {
xmlDocPtr doc;
char *root;
/* Initialize XML defaults */
init_xml();
/* Read volume-request/config file */
if ((error = get_volume_request_or_config(&doc, &root)) == 0) {
/* Is this a volume-config? */
if (strcmp(root, ELEMENT_VOLUMECONFIG) == 0) {
/* Was the -d flag specified? */
if (action & ACTION_OUTPUT_CONFIG) {
/* -d cannot be used with -F <configfile> */
volume_set_error(gettext(
"-%c incompatible with -%c <configfile>"),
CREATE_SHORTOPT_CONFIGFILE,
CREATE_SHORTOPT_INPUTFILE);
error = -1;
printusage = 1;
} else {
devconfig_t *config;
if ((error = xml_to_config(doc, &config)) == 0) {
error = handle_config(config);
free_devconfig(config);
}
}
} else
/* Is this a volume-request? */
if (strcmp(root, ELEMENT_VOLUMEREQUEST) == 0) {
request_t *request;
if ((error = xml_to_request(doc, &request)) == 0) {
xmlDocPtr defaults_doc;
char *valid[] = {
ELEMENT_VOLUMEDEFAULTS,
NULL
};
/* Read defaults file */
if ((error = get_doc_from_file(VOLUME_DEFAULTS_LOC,
valid, &defaults_doc, &root)) == 0) {
defaults_t *defaults;
oprintf(OUTPUT_DEBUG,
gettext("Using defaults file: %s\n"),
VOLUME_DEFAULTS_LOC);
/* Parse defaults XML */
if ((error = xml_to_defaults(
defaults_doc, &defaults)) == 0) {
error = handle_request(request, defaults);
free_defaults(defaults);
}
xmlFreeDoc(defaults_doc);
}
free_request(request);
}
}
xmlFreeDoc(doc);
}
} else {
printusage = 1;
}
}
/* Handle any errors that were propogated */
if (error != 0) {
char *message = get_error_string(error);
if (message != NULL && strlen(message)) {
fprintf(stderr, "%s: %s\n", progname, message);
if (printusage) {
fprintf(stderr, "\n");
}
}
if (printusage) {
print_usage(stderr);
}
}
#ifdef DEBUG
/* Print run report to stderr if METASSIST_DEBUG is set */
if (getenv(METASSIST_DEBUG_ENV) != NULL) {
time_t end = time(NULL);
struct tm *time;
int i;
#define TIMEFMT "%8s: %.2d:%.2d:%.2d\n"
fprintf(stderr, " Command:");
for (i = 0; i < argc; i++) {
fprintf(stderr, " %s", argv[i]);
}
fprintf(stderr, "\n");
fprintf(stderr, " Version: ");
print_version(stderr);
time = localtime(&start);
fprintf(stderr, TIMEFMT, "Start",
time->tm_hour, time->tm_min, time->tm_sec);
time = localtime(&end);
fprintf(stderr, TIMEFMT, "End",
time->tm_hour, time->tm_min, time->tm_sec);
end -= start;
time = gmtime(&end);
fprintf(stderr, TIMEFMT, "Duration",
time->tm_hour, time->tm_min, time->tm_sec);
}
#endif
clean_up();
return (error != 0);
}