#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"
#define GENERAL_USAGE 1
#define DETAIL_USAGE 2
#define MAXOPTIONS (uint_t)('~' - '!' + 1)
#define MAXOPTIONSTRING MAXOPTIONS * 2
struct option standardCmdOptions[] = {
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
struct option standardSubCmdOptions[] = {
{"help", no_argument, NULL, '?'},
{NULL, 0, NULL, 0}
};
static int getSubcommandProps(char *, subCommandProps_t **);
static char *getExecBasename(char *);
static void usage(uint_t);
static void subUsage(uint_t, subCommandProps_t *);
static const char *getLongOption(int);
static char *getOptionArgDesc(int);
static struct option *_longOptions;
static subCommandProps_t *_subCommandProps;
static optionTbl_t *_clientOptionTbl;
static char *commandName;
static int
getSubcommandProps(char *subCommand, subCommandProps_t **subCommandProps)
{
subCommandProps_t *sp;
int len;
for (sp = _subCommandProps; sp->name; sp++) {
len = strlen(subCommand);
if (len == strlen(sp->name) &&
strncasecmp(subCommand, sp->name, len) == 0) {
*subCommandProps = sp;
return (0);
}
}
return (1);
}
static const char *
getLongOption(int shortOption)
{
struct option *op;
for (op = _longOptions; op->name; op++) {
if (shortOption == op->val) {
return (op->name);
}
}
return (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);
}
static void
subUsage(uint_t usageType, subCommandProps_t *subcommand)
{
int i;
char *optionArgDesc;
const char *longOpt;
if (usageType == GENERAL_USAGE) {
(void) printf("%s:\t%s %s [", gettext("Usage"), commandName,
subcommand->name);
for (i = 0; standardSubCmdOptions[i].name; i++) {
(void) printf("-%c", standardSubCmdOptions[i].val);
if (standardSubCmdOptions[i+1].name)
(void) printf(",");
}
(void) fprintf(stdout, "]\n");
return;
}
(void) printf("\n%s:\t%s %s ", gettext("Usage"), commandName,
subcommand->name);
if (subcommand->optionString != NULL) {
if (subcommand->required) {
(void) printf("%s", gettext("<"));
} else {
(void) printf("%s", gettext("["));
}
(void) printf("%s", gettext("OPTIONS"));
if (subcommand->required) {
(void) printf("%s ", gettext(">"));
} else {
(void) printf("%s ", gettext("]"));
}
}
if (!(subcommand->operand & OPERAND_NONE) &&
!(subcommand->operand & OPERAND_MANDATORY)) {
(void) printf(gettext("["));
}
if (subcommand->operand & OPERAND_MANDATORY) {
(void) printf(gettext("<"));
}
if (!(subcommand->operand & OPERAND_NONE)) {
assert(subcommand->operandDefinition);
(void) printf("%s", subcommand->operandDefinition);
}
if (subcommand->operand & OPERAND_MULTIPLE) {
(void) printf(gettext(" ..."));
}
if (subcommand->operand & OPERAND_MANDATORY) {
(void) printf(gettext(">"));
}
if (!(subcommand->operand & OPERAND_NONE) &&
!(subcommand->operand & OPERAND_MANDATORY)) {
(void) printf(gettext("]"));
}
if (subcommand->optionString != NULL) {
(void) printf("\n\t%s:", gettext("OPTIONS"));
for (i = 0; i < strlen(subcommand->optionString); i++) {
if ((longOpt = getLongOption(
subcommand->optionString[i]))
== NULL) {
assert(0);
}
(void) printf("\n\t\t-%c, --%s ",
subcommand->optionString[i], longOpt);
optionArgDesc =
getOptionArgDesc(subcommand->optionString[i]);
if (optionArgDesc != NULL) {
(void) printf("<%s>", optionArgDesc);
}
if (subcommand->exclusive &&
strchr(subcommand->exclusive,
subcommand->optionString[i])) {
(void) printf(" (%s)", gettext("exclusive"));
}
}
}
(void) fprintf(stdout, "\n");
}
static void
usage(uint_t usageType)
{
int i;
subCommandProps_t *sp;
(void) printf("%s:\t%s ", gettext("Usage"), commandName);
for (i = 0; standardCmdOptions[i].name; i++) {
(void) printf("-%c", standardCmdOptions[i].val);
if (standardCmdOptions[i+1].name)
(void) printf(",");
}
if (usageType == GENERAL_USAGE) {
for (i = 0; standardSubCmdOptions[i].name; i++) {
(void) printf(",--%s", standardSubCmdOptions[i].name);
if (standardSubCmdOptions[i+1].name)
(void) printf(",");
}
}
(void) fprintf(stdout, "\n");
for (sp = _subCommandProps; sp->name; sp++) {
subUsage(usageType, sp);
}
}
static char *
getExecBasename(char *execFullname)
{
char *lastSlash, *execBasename;
for (;;) {
lastSlash = strrchr(execFullname, '/');
if (lastSlash == NULL) {
execBasename = execFullname;
break;
} else {
execBasename = lastSlash + 1;
if (*execBasename == '\0') {
*lastSlash = '\0';
continue;
}
break;
}
}
return (execBasename);
}
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 *availOptions;
char *versionString;
char optionStringAll[MAXOPTIONSTRING + 1];
subCommandProps_t *subcommand;
cmdOptions_t cmdOptions[MAXOPTIONS + 1];
optionTbl_t *optionTbl;
struct option *lp;
struct option intLongOpt[MAXOPTIONS + 1];
assert(synTable.versionString);
assert(synTable.subCommandPropsTbl);
assert(funcRet);
versionString = synTable.versionString;
commandName = getExecBasename(argv[0]);
setbuf(stdout, NULL);
_subCommandProps = synTable.subCommandPropsTbl;
_clientOptionTbl = synTable.longOptionTbl;
if (argc < 2) {
usage(GENERAL_USAGE);
return (1);
}
(void) memset(&intLongOpt[0], 0, sizeof (intLongOpt));
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;
}
for (optionTbl = synTable.longOptionTbl;
optionTbl && optionTbl->name; optionTbl++, i++) {
if (i > MAXOPTIONS - 1) {
assert(0);
}
intLongOpt[i].name = optionTbl->name;
intLongOpt[i].has_arg = optionTbl->has_arg;
intLongOpt[i].flag = NULL;
intLongOpt[i].val = optionTbl->val;
}
_longOptions = &intLongOpt[0];
while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions,
NULL)) != EOF) {
switch (opt) {
case '?':
if (optopt == '?') {
usage(DETAIL_USAGE);
return (1);
} else {
usage(GENERAL_USAGE);
return (1);
}
case 'V':
(void) fprintf(stdout, "%s: %s %s\n",
commandName, gettext("Version"),
versionString);
return (1);
default:
break;
}
}
if (getSubcommandProps(argv[1], &subcommand) != 0) {
(void) printf("%s: %s\n", commandName,
gettext("invalid subcommand"));
usage(GENERAL_USAGE);
return (1);
}
getoptargv = argv;
getoptargv++;
getoptargc = argc;
getoptargc -= 1;
(void) memset(optionStringAll, 0, sizeof (optionStringAll));
(void) memset(&cmdOptions[0], 0, sizeof (cmdOptions));
j = 0;
for (lp = _longOptions; lp->name; lp++, j++) {
if (j + 1 >= sizeof (optionStringAll)) {
assert(0);
}
optionStringAll[j] = lp->val;
if (lp->has_arg == required_argument) {
optionStringAll[++j] = ':';
}
}
i = 0;
while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll,
_longOptions, NULL)) != EOF) {
switch (opt) {
case '?':
subUsage(DETAIL_USAGE, subcommand);
return (1);
default:
cmdOptions[i].optval = opt;
if (optarg) {
len = strlen(optarg);
if (len > sizeof (cmdOptions[i].optarg)
- 1) {
(void) printf("%s: %s\n",
commandName,
gettext("option too long"));
errno = EINVAL;
return (-1);
}
(void) strncpy(cmdOptions[i].optarg,
optarg, len);
}
i++;
break;
}
}
operInd = optind + 1;
availOptions = subcommand->optionString;
if (cmdOptions[0].optval != 0) {
if (availOptions == NULL) {
(void) printf("%s: %s\n", commandName,
gettext("no options permitted"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
for (i = 0; cmdOptions[i].optval; i++) {
if (!(strchr(availOptions, cmdOptions[i].optval))) {
(void) printf("%s: '-%c': %s\n", commandName,
cmdOptions[i].optval,
gettext("invalid option"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
} else if (cmdOptions[1].optval != 0 &&
subcommand->exclusive &&
strchr(subcommand->exclusive,
cmdOptions[i].optval)) {
(void) printf("%s: '-%c': %s\n",
commandName, cmdOptions[i].optval,
gettext("is an exclusive option"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
}
} else {
if (availOptions != NULL && subcommand->required) {
(void) printf("%s: %s\n", commandName,
gettext("at least one option required"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
}
if ((operInd == argc) &&
(subcommand->operand & OPERAND_MANDATORY)) {
(void) printf("%s: %s %s\n", commandName, subcommand->name,
gettext("requires an operand"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
if ((argc > operInd) &&
(subcommand->operand & OPERAND_NONE)) {
(void) fprintf(stderr, "%s: %s %s\n",
commandName, subcommand->name,
gettext("takes no operands"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
if ((argc > operInd) && ((argc - operInd) != 1) &&
(subcommand->operand & OPERAND_SINGLE)) {
(void) printf("%s: %s %s\n", commandName,
subcommand->name, gettext("accepts only a single operand"));
subUsage(DETAIL_USAGE, subcommand);
return (1);
}
*funcRet = subcommand->handler(argc - operInd, &argv[operInd],
&cmdOptions[0], callArgs);
return (0);
}