cmdparse.c revision fcf3ce441efd61da9bb2884968af01cb7c1452cc
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <libintl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include "cmdparse.h"
/* Usage types */
#define GENERAL_USAGE 1
#define HELP_USAGE 2
#define DETAIL_USAGE 3
/* printable ascii character set len */
#define MAXOPTIONS (uint_t)('~' - '!' + 1)
/*
* MAXOPTIONSTRING is the max length of the options string used in getopt and
* will be the printable character set + ':' for each character,
* providing for options with arguments. e.g. "t:Cs:hglr:"
*/
#define MAXOPTIONSTRING MAXOPTIONS * 2
/* standard command options table to support -?, -V */
struct option standardCmdOptions[] = {
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
/* standard subcommand options table to support -? */
struct option standardSubCmdOptions[] = {
{"help", no_argument, NULL, '?'},
{NULL, 0, NULL, 0}
};
/* forward declarations */
static int getSubcommand(char *, subcommand_t **);
static char *getExecBasename(char *);
static void usage(uint_t);
static void subUsage(uint_t, subcommand_t *);
static void subUsageObject(uint_t, subcommand_t *, object_t *);
static int getObject(char *, object_t **);
static int getObjectRules(uint_t, objectRules_t **);
static char *getLongOption(int);
static optionProp_t *getOptions(uint_t, uint_t);
static char *getOptionArgDesc(int);
extern void seeMan(void);
/* global data */
static struct option *_longOptions;
static subcommand_t *_subcommands;
static object_t *_objects;
static objectRules_t *_objectRules;
static optionRules_t *_optionRules;
static optionTbl_t *_clientOptionTbl;
static char *commandName;
/*
* input:
* object - object value
* output:
* opCmd - pointer to opCmd_t structure allocated by caller
*
* On successful return, opCmd contains the rules for the value in
* object. On failure, the contents of opCmd is unspecified.
*
* Returns:
* zero on success
* non-zero on failure
*
*/
static int
getObjectRules(uint_t object, objectRules_t **objectRules)
{
objectRules_t *sp;
for (sp = _objectRules; sp->value; sp++) {
if (sp->value == object) {
*objectRules = sp;
return (0);
}
}
return (1);
}
/*
* input:
* arg - pointer to array of char containing object string
*
* output:
* object - pointer to object_t structure pointer
* on success, contains the matching object structure based on
* input object name
*
* Returns:
* zero on success
* non-zero otherwise
*
*/
static int
getObject(char *arg, object_t **object)
{
object_t *op;
int len;
for (op = _objects; op->name; op++) {
len = strlen(arg);
if (len == strlen(op->name) &&
strncasecmp(arg, op->name, len) == 0) {
*object = op;
return (0);
}
}
return (1);
}
/*
* input:
* arg - pointer to array of char containing subcommand string
* output:
* subcommand - pointer to subcommand_t pointer
* on success, contains the matching subcommand structure based on
* input subcommand name
*
* Returns:
* zero on success
* non-zero on failure
*/
static int
getSubcommand(char *arg, subcommand_t **subcommand)
{
subcommand_t *sp;
int len;
for (sp = _subcommands; sp->name; sp++) {
len = strlen(arg);
if (len == strlen(sp->name) &&
strncasecmp(arg, sp->name, len) == 0) {
*subcommand = sp;
return (0);
}
}
return (1);
}
/*
* input:
* object - object for which to get options
* subcommand - subcommand for which to get options
*
* Returns:
* on success, optionsProp_t pointer to structure matching input object
* value
* on failure, NULL is returned
*/
static optionProp_t *
getOptions(uint_t object, uint_t subcommand)
{
uint_t currObject;
optionRules_t *op = _optionRules;
while (op && ((currObject = op->objectValue) != 0)) {
if ((currObject == object) &&
(op->subcommandValue == subcommand)) {
return (&(op->optionProp));
}
op++;
}
return (NULL);
}
/*
* input:
* shortOption - short option character for which to return the
* associated long option string
*
* Returns:
* on success, long option name
* on failure, NULL
*/
static char *
getLongOption(int shortOption)
{
struct option *op;
for (op = _longOptions; op->name; op++) {
if (shortOption == op->val) {
return (op->name);
}
}
return (NULL);
}
/*
* input
* shortOption - short option character for which to return the
* option argument
* Returns:
* on success, argument string
* on failure, NULL
*/
static char *
getOptionArgDesc(int shortOption)
{
optionTbl_t *op;
for (op = _clientOptionTbl; op->name; op++) {
if (op->val == shortOption &&
op->has_arg == required_argument) {
return (op->argDesc);
}
}
return (NULL);
}
/*
* Print usage for a subcommand.
*
* input:
* usage type - GENERAL_USAGE, HELP_USAGE, DETAIL_USAGE
* subcommand - pointer to subcommand_t structure
*
* Returns:
* none
*
*/
static void
subUsage(uint_t usageType, subcommand_t *subcommand)
{
int i;
object_t *objp;
(void) fprintf(stdout, "%s:\t%s %s [",
gettext("Usage"), commandName, subcommand->name);
for (i = 0; standardSubCmdOptions[i].name; i++) {
(void) fprintf(stdout, "-%c",
standardSubCmdOptions[i].val);
if (standardSubCmdOptions[i+1].name)
(void) fprintf(stdout, ",");
}
(void) fprintf(stdout, "] %s [", "<OBJECT>");
for (i = 0; standardSubCmdOptions[i].name; i++) {
(void) fprintf(stdout, "-%c",
standardSubCmdOptions[i].val);
if (standardSubCmdOptions[i+1].name)
(void) fprintf(stdout, ",");
}
(void) fprintf(stdout, "] %s", "[<OPERAND>]");
(void) fprintf(stdout, "\n");
if (usageType == GENERAL_USAGE) {
return;
}
(void) fprintf(stdout, "%s:\n", gettext("Usage by OBJECT"));
/*
* iterate through object table
* For each object, print appropriate usage
* based on rules tables
*/
for (objp = _objects; objp->value; objp++) {
subUsageObject(usageType, subcommand, objp);
}
(void) atexit(seeMan);
}
/*
* Print usage for a subcommand and object.
*
* input:
* usage type - GENERAL_USAGE, HELP_USAGE, DETAIL_USAGE
* subcommand - pointer to subcommand_t structure
* objp - pointer to a object_t structure
*
* Returns:
* none
*
*/
static void
subUsageObject(uint_t usageType, subcommand_t *subcommand, object_t *objp)
{
int i;
objectRules_t *objRules = NULL;
opCmd_t *opCmd = NULL;
optionProp_t *options;
char *optionArgDesc;
char *longOpt;
if (getObjectRules(objp->value, &objRules) != 0) {
/*
* internal subcommand rules table error
* no object entry in object
*/
assert(0);
}
opCmd = &(objRules->opCmd);
if (opCmd->invOpCmd & subcommand->value) {
return;
}
options = getOptions(objp->value, subcommand->value);
/* print generic subcommand usage */
(void) fprintf(stdout, "\t%s %s ", commandName, subcommand->name);
/* print object */
(void) fprintf(stdout, "%s ", objp->name);
/* print options if applicable */
if (options != NULL) {
if (options->required) {
(void) fprintf(stdout, "%s", gettext("<"));
} else {
(void) fprintf(stdout, "%s", gettext("["));
}
(void) fprintf(stdout, "%s", gettext("OPTIONS"));
if (options->required) {
(void) fprintf(stdout, "%s ", gettext(">"));
} else {
(void) fprintf(stdout, "%s ", gettext("]"));
}
}
/* print operand requirements */
if (opCmd->optOpCmd & subcommand->value) {
(void) fprintf(stdout, gettext("["));
}
if (!(opCmd->noOpCmd & subcommand->value)) {
(void) fprintf(stdout, gettext("<"));
if (objRules->operandDefinition) {
(void) fprintf(stdout, "%s",
objRules->operandDefinition);
} else {
/*
* Missing operand description
* from table
*/
assert(0);
}
}
if (opCmd->multOpCmd & subcommand->value) {
(void) fprintf(stdout, gettext(" ..."));
}
if (!(opCmd->noOpCmd & subcommand->value)) {
(void) fprintf(stdout, gettext(">"));
}
if (opCmd->optOpCmd & subcommand->value) {
(void) fprintf(stdout, gettext("]"));
}
if (usageType == HELP_USAGE) {
(void) fprintf(stdout, "\n");
return;
}
/* print options for subcommand, object */
if (options != NULL && options->optionString != NULL) {
(void) fprintf(stdout, "\n\t%s:", gettext("OPTIONS"));
for (i = 0; i < strlen(options->optionString); i++) {
if ((longOpt = getLongOption(
options->optionString[i]))
== NULL) {
/* no long option exists for short option */
assert(0);
}
(void) fprintf(stdout, "\n\t\t-%c, --%s ",
options->optionString[i], longOpt);
optionArgDesc =
getOptionArgDesc(options->optionString[i]);
if (optionArgDesc != NULL) {
(void) fprintf(stdout, "<%s>", optionArgDesc);
}
if (options->exclusive &&
strchr(options->exclusive,
options->optionString[i])) {
(void) fprintf(stdout, " (%s)",
gettext("exclusive"));
}
}
}
(void) fprintf(stdout, "\n");
(void) atexit(seeMan);
}
/*
* input:
* type of usage statement to print
*
* Returns:
* return value of subUsage
*/
static void
usage(uint_t usageType)
{
int i;
subcommand_t subcommand;
subcommand_t *sp;
/* print general command usage */
(void) fprintf(stdout, "%s:\t%s ",
gettext("Usage"), commandName);
for (i = 0; standardCmdOptions[i].name; i++) {
(void) fprintf(stdout, "-%c",
standardCmdOptions[i].val);
if (standardCmdOptions[i+1].name)
(void) fprintf(stdout, ",");
}
if (usageType == HELP_USAGE || usageType == GENERAL_USAGE) {
for (i = 0; standardCmdOptions[i].name; i++) {
}
}
(void) fprintf(stdout, "\n");
/* print all subcommand usage */
for (sp = _subcommands; sp->name; sp++) {
subcommand.name = sp->name;
subcommand.value = sp->value;
if (usageType == HELP_USAGE) {
(void) fprintf(stdout, "\n");
}
subUsage(usageType, &subcommand);
}
(void) atexit(seeMan);
}
/*
* input:
* execFullName - exec name of program (argv[0])
*
* Returns:
* command name portion of execFullName
*/
static char *
getExecBasename(char *execFullname)
{
char *lastSlash, *execBasename;
/* guard against '/' at end of command invocation */
for (;;) {
lastSlash = strrchr(execFullname, '/');
if (lastSlash == NULL) {
execBasename = execFullname;
break;
} else {
execBasename = lastSlash + 1;
if (*execBasename == '\0') {
*lastSlash = '\0';
continue;
}
break;
}
}
return (execBasename);
}
/*
* cmdParse is a parser that checks syntax of the input command against
* various rules tables.
*
* It provides usage feedback based upon the passed rules tables by calling
* two usage functions, usage, subUsage, and subUsageObject handling command,
* subcommand and object usage respectively.
*
* When syntax is successfully validated, the associated function is called
* using the subcommands table functions.
*
* Syntax is as follows:
* command subcommand object [<options>] [<operand>]
*
* There are two standard short and long options assumed:
* -?, --help Provides usage on a command or subcommand
* and stops further processing of the arguments
*
* -V, --version Provides version information on the command
* and stops further processing of the arguments
*
* These options are loaded by this function.
*
* input:
* argc, argv from main
* syntax rules tables (synTables_t structure)
* callArgs - void * passed by caller to be passed to subcommand function
*
* output:
* funcRet - pointer to int that holds subcommand function return value
*
* Returns:
*
* zero on successful syntax parse and function call
*
* 1 on unsuccessful syntax parse (no function has been called)
* This could be due to a version or help call or simply a
* general usage call.
*
* -1 check errno, call failed
*
* This module is not MT-safe.
*
*/
int
cmdParse(int argc, char *argv[], synTables_t synTable, void *callArgs,
int *funcRet)
{
int getoptargc;
char **getoptargv;
int opt;
int operInd;
int i, j;
int len;
char *versionString;
char optionStringAll[MAXOPTIONSTRING + 1];
optionProp_t *availOptions;
objectRules_t *objRules = NULL;
opCmd_t *opCmd = NULL;
subcommand_t *subcommand;
object_t *object;
cmdOptions_t cmdOptions[MAXOPTIONS + 1];
struct option *lp;
optionTbl_t *optionTbl;
struct option intLongOpt[MAXOPTIONS + 1];
/*
* Check for NULLs on mandatory input arguments
*
* Note: longOptionTbl and optionRulesTbl can be NULL in the case
* where there is no caller defined options
*
*/
if (synTable.versionString == NULL ||
synTable.subcommandTbl == NULL ||
synTable.objectRulesTbl == NULL ||
synTable.objectTbl == NULL ||
funcRet == NULL) {
assert(0);
}
versionString = synTable.versionString;
/* set global command name */
commandName = getExecBasename(argv[0]);
/* Set unbuffered output */
setbuf(stdout, NULL);
/* load globals */
_subcommands = synTable.subcommandTbl;
_objectRules = synTable.objectRulesTbl;
_optionRules = synTable.optionRulesTbl;
_objects = synTable.objectTbl;
_clientOptionTbl = synTable.longOptionTbl;
/* There must be at least two arguments */
if (argc < 2) {
usage(GENERAL_USAGE);
return (1);
}
(void) memset(&intLongOpt[0], 0, sizeof (intLongOpt));
/*
* load standard subcommand options to internal long options table
* Two separate getopt_long(3C) tables are used.
*/
for (i = 0; standardSubCmdOptions[i].name; i++) {
intLongOpt[i].name = standardSubCmdOptions[i].name;
intLongOpt[i].has_arg = standardSubCmdOptions[i].has_arg;
intLongOpt[i].flag = standardSubCmdOptions[i].flag;
intLongOpt[i].val = standardSubCmdOptions[i].val;
}
/*
* copy caller's long options into internal long options table
* We do this for two reasons:
* 1) We need to use the getopt_long option structure internally
* 2) We need to prepend the table with the standard option
* for all subcommands (currently -?)
*/
for (optionTbl = synTable.longOptionTbl;
optionTbl && optionTbl->name; optionTbl++, i++) {
if (i > MAXOPTIONS - 1) {
/* option table too long */
assert(0);
}
intLongOpt[i].name = optionTbl->name;
intLongOpt[i].has_arg = optionTbl->has_arg;
intLongOpt[i].flag = NULL;
intLongOpt[i].val = optionTbl->val;
}
/* set option table global */
_longOptions = &intLongOpt[0];
/*
* Check for help/version request immediately following command
* '+' in option string ensures POSIX compliance in getopt_long()
* which means that processing will stop at first non-option
* argument.
*/
while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions,
NULL)) != EOF) {
switch (opt) {
case '?':
/*
* getopt can return a '?' when no
* option letters match string. Check for
* the 'real' '?' in optopt.
*/
if (optopt == '?') {
usage(HELP_USAGE);
return (0);
} else {
usage(GENERAL_USAGE);
return (0);
}
case 'V':
(void) fprintf(stdout, "%s: %s %s\n",
commandName, gettext("Version"),
versionString);
(void) atexit(seeMan);
return (0);
default:
break;
}
}
/*
* subcommand is always in the second argument. If there is no
* recognized subcommand in the second argument, print error,
* general usage and then return.
*/
if (getSubcommand(argv[1], &subcommand) != 0) {
(void) fprintf(stderr, "%s: %s\n",
commandName, gettext("invalid subcommand"));
usage(GENERAL_USAGE);
return (1);
}
if (argc == 2) {
(void) fprintf(stderr, "%s: %s\n",
commandName, gettext("missing object"));
subUsage(GENERAL_USAGE, subcommand);
(void) atexit(seeMan);
return (1);
}
getoptargv = argv;
getoptargv++;
getoptargc = argc;
getoptargc -= 1;
while ((opt = getopt_long(getoptargc, getoptargv, "+?",
standardSubCmdOptions, NULL)) != EOF) {
switch (opt) {
case '?':
/*
* getopt can return a '?' when no
* option letters match string. Check for
* the 'real' '?' in optopt.
*/
if (optopt == '?') {
subUsage(HELP_USAGE, subcommand);
return (0);
} else {
subUsage(GENERAL_USAGE, subcommand);
return (0);
}
default:
break;
}
}
/*
* object is always in the third argument. If there is no
* recognized object in the third argument, print error,
* help usage for the subcommand and then return.
*/
if (getObject(argv[2], &object) != 0) {
(void) fprintf(stderr, "%s: %s\n",
commandName, gettext("invalid object"));
subUsage(HELP_USAGE, subcommand);
return (1);
}
if (getObjectRules(object->value, &objRules) != 0) {
/*
* internal subcommand rules table error
* no object entry in object table
*/
assert(0);
}
opCmd = &(objRules->opCmd);
/*
* Is command valid for this object?
*/
if (opCmd->invOpCmd & subcommand->value) {
(void) fprintf(stderr, "%s: %s %s\n", commandName,
gettext("invalid subcommand for"), object->name);
subUsage(HELP_USAGE, subcommand);
return (1);
}
/*
* offset getopt arg begin since
* getopt(3C) assumes options
* follow first argument
*/
getoptargv = argv;
getoptargv++;
getoptargv++;
getoptargc = argc;
getoptargc -= 2;
(void) memset(optionStringAll, 0, sizeof (optionStringAll));
(void) memset(&cmdOptions[0], 0, sizeof (cmdOptions));
j = 0;
/*
* Build optionStringAll from long options table
*/
for (lp = _longOptions; lp->name; lp++, j++) {
/* sanity check on string length */
if (j + 1 >= sizeof (optionStringAll)) {
/* option table too long */
assert(0);
}
optionStringAll[j] = lp->val;
if (lp->has_arg == required_argument) {
optionStringAll[++j] = ':';
}
}
i = 0;
/*
* Run getopt for all arguments against all possible options
* Store all options/option arguments in an array for retrieval
* later.
* Once all options are retrieved, check against object
* and subcommand (option rules table) for validity.
* This is done later.
*/
while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll,
_longOptions, NULL)) != EOF) {
switch (opt) {
case '?':
if (optopt == '?') {
subUsageObject(DETAIL_USAGE,
subcommand, object);
return (0);
} else {
subUsage(GENERAL_USAGE, subcommand);
return (0);
}
default:
cmdOptions[i].optval = opt;
if (optarg) {
len = strlen(optarg);
if (len > sizeof (cmdOptions[i].optarg)
- 1) {
(void) fprintf(stderr,
"%s: %s\n",
commandName,
gettext("option too long"));
errno = EINVAL;
return (-1);
}
(void) strncpy(cmdOptions[i].optarg,
optarg, len);
}
i++;
break;
}
}
/*
* increment past last option
*/
operInd = optind + 2;
/*
* Check validity of given options, if any were given
*/
/* get option string for this object and subcommand */
availOptions = getOptions(object->value, subcommand->value);
if (cmdOptions[0].optval != 0) { /* options were input */
if (availOptions == NULL) { /* no options permitted */
(void) fprintf(stderr, "%s: %s\n",
commandName, gettext("no options permitted"));
subUsageObject(HELP_USAGE, subcommand, object);
return (1);
}
for (i = 0; cmdOptions[i].optval; i++) {
/* Check for invalid options */
if (availOptions->optionString == NULL) {
/*
* internal option table error
* There must be an option string if
* there is an entry in the table
*/
assert(0);
}
/* is the option in the available option string? */
if (!(strchr(availOptions->optionString,
cmdOptions[i].optval))) {
(void) fprintf(stderr,
"%s: '-%c': %s\n",
commandName, cmdOptions[i].optval,
gettext("invalid option"));
subUsageObject(DETAIL_USAGE, subcommand,
object);
return (1);
/* Check for exclusive options */
} else if (cmdOptions[1].optval != 0 &&
availOptions->exclusive &&
strchr(availOptions->exclusive,
cmdOptions[i].optval)) {
(void) fprintf(stderr,
"%s: '-%c': %s\n",
commandName, cmdOptions[i].optval,
gettext("is an exclusive option"));
subUsageObject(DETAIL_USAGE, subcommand,
object);
return (1);
}
}
} else { /* no options were input */
if (availOptions != NULL &&
(availOptions->required)) {
(void) fprintf(stderr, "%s: %s\n",
commandName,
gettext("at least one option required"));
subUsageObject(DETAIL_USAGE, subcommand,
object);
return (1);
}
}
/*
* If there are no more arguments (operands),
* check to see if this is okay
*/
if ((operInd == argc) &&
(opCmd->reqOpCmd & subcommand->value)) {
(void) fprintf(stderr, "%s: %s %s %s\n",
commandName, subcommand->name,
object->name, gettext("requires an operand"));
subUsageObject(HELP_USAGE, subcommand, object);
(void) atexit(seeMan);
return (1);
}
/*
* If there are more operands,
* check to see if this is okay
*/
if ((argc > operInd) &&
(opCmd->noOpCmd & subcommand->value)) {
(void) fprintf(stderr, "%s: %s %s %s\n",
commandName, subcommand->name,
object->name, gettext("takes no operands"));
subUsageObject(HELP_USAGE, subcommand, object);
return (1);
}
/*
* If there is more than one more operand,
* check to see if this is okay
*/
if ((argc > operInd) && ((argc - operInd) != 1) &&
!(opCmd->multOpCmd & subcommand->value)) {
(void) fprintf(stderr, "%s: %s %s %s\n",
commandName, subcommand->name, object->name,
gettext("accepts only a single operand"));
subUsageObject(HELP_USAGE, subcommand, object);
return (1);
}
/* Finished syntax checks */
/* Call appropriate function */
return (subcommand->handler(argc - operInd, &argv[operInd],
object->value, &cmdOptions[0], callArgs, funcRet));
}