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
* 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:
*
*
* 2. XML parser validates XML syntax, conformance with DTD
*
* 3. xml_convert validates proper conversion from string to
*
* 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 <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 xmlDocPtr create_volume_request_XML();
static int get_doc_from_file(
static int handle_commands(char *commands);
/*
* 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 */
/* The disk set to use */
char *arg_diskset = NULL;
/* The volume name to use */
/* Redundancy level */
char *arg_redundancy = NULL;
/* Number of datapaths */
char *arg_datapaths = NULL;
/* Whether to implement fault recovery */
/* Whether to output the config file */
/* Whether to output the command file instead of */
/* List of available devices */
/* List of unavailable devices */
/*
* Functions
*/
/*
* Frees alloc'd memory, to be called prior to exiting.
*/
static void
clean_up()
{
/* Remove temporary command file */
if (commandfile != NULL) {
/* Ignore failure */
}
/* Free allocated argument strings */
/* Free available dlist and strings within */
/* Free unavailable dlist and strings within */
/* Clean up XML data structures */
cleanup_xml();
}
/*
* Signal handler, called to exit gracefully
*/
static void
int sig)
{
char sigstr[SIG2STR_MAX];
sigstr[0] = '\0';
}
/* Allow layout to cleanup on abnormal exit */
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
char *option,
char *value,
char **saveto)
{
int error = 0;
/* Has this string already been set? */
error = -1;
} else
}
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 the XML document */
/* Create the root node */
/* diskset element */
if (arg_diskset != NULL) {
}
/* available elements */
}
}
/* unavailable elements */
if (unavailable != NULL) {
}
}
/* volume element */
/* Volume name - optional */
}
/* Volume size - required */
/* Volume redundancy - optional */
if (arg_redundancy != NULL) {
}
/* Volume fault recovery - optional */
if (faultrecovery == B_TRUE) {
}
/* Volume datapaths - optional */
if (arg_datapaths != NULL) {
}
if (get_max_verbosity() >= OUTPUT_DEBUG) {
/* Get the text dump */
}
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
int c,
{
int error = 0;
/* Level of verbosity to report */
int verbosity;
switch (c) {
}
break;
case COMMON_SHORTOPT_VERSION:
clean_up();
exit(0);
}
break;
case GETOPT_ERR_MISSING_ARG:
error = -1;
break;
case GETOPT_ERR_INVALID_OPT:
error = -1;
break;
case GETOPT_ERR_INVALID_ARG:
error = -1;
break;
default:
}
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
int argc,
char *argv[])
{
int c;
int error = 0;
/*
* Whether a volume request is specified on the command line
* (vs. a inputfile)
*/
/* Examine next arg */
while (!error && (c = getopt_ext(
/* Check for args common to all scopes */
/* Check for args specific to this scope */
switch (c) {
/* Help */
case COMMON_SHORTOPT_HELP:
clean_up();
exit(0);
break;
/* Config file */
action &= ~ACTION_EXECUTE;
break;
/* Command file */
action &= ~ACTION_EXECUTE;
break;
/* Disk set */
case CREATE_SHORTOPT_DISKSET:
break;
/* Name */
case CREATE_SHORTOPT_NAME:
break;
/* Redundancy */
break;
/* Data paths */
break;
/* Fault recovery */
break;
/* Available devices */
break;
/* Unavailable devices */
break;
/* Size */
case CREATE_SHORTOPT_SIZE:
break;
/* Input file */
optarg, &arg_inputfile);
break;
default:
/* Shouldn't be here! */
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) {
gettext("invalid option(s) specified with input file"));
error = -1;
} else
error = -1;
} else
if (arg_diskset == NULL) {
error = -1;
}
} else
/* First or second invocation method -- one required argument */
if (arg_inputfile == NULL) {
error = -1;
}
/*
* The CREATE_SHORTOPT_CONFIGFILE and
* CREATE_SHORTOPT_COMMANDFILE arguments are mutually
* exclusive. Verify that these were not both specified.
*/
if (!error &&
gettext("-%c and -%c are mutually exclusive"),
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
int argc,
char *argv[])
{
int c;
int error = 0;
/* Examine next arg */
while (!error && (c = getopt_ext(
/* Check for args common to all scopes */
/* Check for args specific to this scope */
switch (c) {
/* Help */
case COMMON_SHORTOPT_HELP:
clean_up();
exit(0);
break;
/* Non-option arg */
case GETOPT_NON_OPTION_ARG:
/* See if non-option arg is subcommand */
} else {
/* Argument not recognized */
error = -1;
}
break;
default:
/* Shouldn't be here! */
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 &&
/* Provide a more enlightening error message */
}
}
/* Parsing appears to be successful */
if (!error) {
/* Was a subcommand specified? */
if (subcmd == SUBCMD_NONE) {
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
const char *string,
{
char *stringdup;
char *device;
char *dup;
int error = 0;
/* Don't let strtok alter original argument */
} else {
/* For each device in the string list... */
/* Duplicate the device string */
break;
}
/* Create new dlist_t for this device */
break;
}
/* Append item to list */
/* strtok needs NULL pointer on subsequent calls */
}
}
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
char *arg,
int *verbosity)
{
int level;
/* Scan for int */
/* Argument was an integer */
switch (level) {
case OUTPUT_QUIET:
case OUTPUT_TERSE:
case OUTPUT_VERBOSE:
#ifdef DEBUG
case OUTPUT_DEBUG:
#endif
return (0);
}
}
return (-1);
}
/*
* Print the help message for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
/* BEGIN CSTYLED */
\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 the help message for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
/* BEGIN CSTYLED */
\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 the help postscript for the command.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
progname);
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
switch (subcmd) {
case SUBCMD_CREATE:
break;
case SUBCMD_NONE:
default:
}
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
/* Create a blank the length of progname */
/* BEGIN CSTYLED */
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\
/* END CSTYLED */
}
/*
* Print the program usage to the given file stream.
*
* @param stream
* stdout or stderr, as appropriate.
*/
static void
{
/* BEGIN CSTYLED */
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
{
int error = 0;
struct utsname uname_info;
if (uname(&uname_info) < 0) {
error = -1;
} else {
}
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
char *file,
char **valid_types,
char **root)
{
int error = 0;
/*
* Create XML doc by reading the specified file using the
* default SAX handler (which has been modified in init_xml())
*/
&xmlDefaultSAXHandler, file, 0);
int i;
/* Is this a valid root element? */
for (i = 0; valid_types[i] != NULL; i++) {
(const xmlChar *)valid_types[i]) == 0) {
*root = valid_types[i];
}
}
/* Was a valid root element found? */
xmlFreeDoc(*doc);
}
}
/* Was a valid root element found? */
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
char **root)
{
int error = 0;
if (arg_inputfile == NULL) {
/* Create a volume-request based on quality of service */
*doc = create_volume_request_XML();
error = -1;
} else {
}
} else {
char *valid[] = {
};
}
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
char *commands)
{
int error = 0;
if (action & ACTION_OUTPUT_COMMANDS) {
}
if (action & ACTION_EXECUTE) {
/* Write a temporary file with 744 permissions */
char *command;
/* Create command line to execute */
if (get_max_verbosity() >= OUTPUT_VERBOSE) {
/* Verbose */
} else {
/* Terse */
}
error = -1;
} else {
/* Execute command */
/* system() failed */
case -1:
break;
/* Command succeded */
case 0:
break;
/* Command failed */
default:
/* CSTYLED */
gettext("execution of command script failed with status %d"),
WEXITSTATUS(error));
error = -1;
}
}
}
}
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
{
int error;
/* Get the xml document for the config */
/* Get the text dump */
/* Should we output the config file? */
if (action & ACTION_OUTPUT_CONFIG) {
} else {
}
/* Proceed to command generation? */
if (action & ACTION_OUTPUT_COMMANDS ||
action & ACTION_EXECUTE) {
char *commands;
/* Get command script from the file */
gettext("could not convert XML to commands"));
error = -1;
} else {
}
}
}
}
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
{
int error;
/* Get layout for given request and system defaults */
/* Retrieve resulting volume 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
char *text,
char **file)
{
int error = 0;
/*
* Create temporary file name -- "XXXXXX" is replaced with
* unique char sequence by mkstemp()
*/
error = -1;
} else {
int fildes;
/* Open temp file */
}
"could not open file for writing: %s"), *file);
error = -1;
} else {
if (mode != 0) {
gettext("could not change permissions of file: %s"),
*file);
error = -1;
}
}
/* Remove file on error */
if (error != 0) {
}
}
/* Free *file on error */
if (error != 0) {
}
}
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
#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) textdomain(TEXT_DOMAIN);
/* Set program name, strip directory */
progname++;
} else {
}
/* Set up signal handlers to exit gracefully */
{
}
/* Set default verbosity level */
/* Verify we're running as root */
if (geteuid() != 0) {
error = -1;
} else {
/* Disable error messages from getopt */
opterr = 0;
/* Parse command-line options */
char *root;
/* Initialize XML defaults */
init_xml();
/* Read volume-request/config file */
/* Is this a volume-config? */
/* Was the -d flag specified? */
if (action & ACTION_OUTPUT_CONFIG) {
/* -d cannot be used with -F <configfile> */
"-%c incompatible with -%c <configfile>"),
error = -1;
printusage = 1;
} else {
}
}
} else
/* Is this a volume-request? */
char *valid[] = {
};
/* Read defaults file */
gettext("Using defaults file: %s\n"),
/* Parse defaults XML */
if ((error = xml_to_defaults(
defaults_doc, &defaults)) == 0) {
}
}
}
}
}
} else {
printusage = 1;
}
}
/* Handle any errors that were propogated */
if (error != 0) {
if (printusage) {
}
}
if (printusage) {
}
}
#ifdef DEBUG
/* Print run report to stderr if METASSIST_DEBUG is set */
int i;
#define TIMEFMT "%8s: %.2d:%.2d:%.2d\n"
for (i = 0; i < argc; i++) {
}
}
#endif
clean_up();
return (error != 0);
}