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 (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #include <stdlib.h>
  27 #include <stdio.h>
  28 #include <sys/types.h>
  29 #include <unistd.h>
  30 #include <libintl.h>
  31 #include <errno.h>
  32 #include <string.h>
  33 #include <assert.h>
  34 #include <getopt.h>
  35 #include <cmdparse.h>
  36 
  37 
  38 /* Usage types */
  39 #define GENERAL_USAGE   1
  40 #define DETAIL_USAGE    2
  41 
  42 /* printable ascii character set len */
  43 #define MAXOPTIONS      (uint_t)('~' - '!' + 1)
  44 
  45 /*
  46  * MAXOPTIONSTRING is the max length of the options string used in getopt and
  47  * will be the printable character set + ':' for each character,
  48  * providing for options with arguments. e.g. "t:Cs:hglr:"
  49  */
  50 #define MAXOPTIONSTRING         MAXOPTIONS * 2
  51 
  52 /* standard command options table to support -?, -V */
  53 struct option standardCmdOptions[] = {
  54         {"help", no_argument, NULL, '?'},
  55         {"version", no_argument, NULL, 'V'},
  56         {NULL, 0, NULL, 0}
  57 };
  58 
  59 /* standard subcommand options table to support -? */
  60 struct option standardSubCmdOptions[] = {
  61         {"help", no_argument, NULL, '?'},
  62         {NULL, 0, NULL, 0}
  63 };
  64 
  65 /* forward declarations */
  66 static int getSubcommandProps(char *, subCommandProps_t **);
  67 static char *getExecBasename(char *);
  68 static void usage(uint_t);
  69 static void subUsage(uint_t, subCommandProps_t *);
  70 static char *getLongOption(int);
  71 static char *getOptionArgDesc(int);
  72 
  73 /* global data */
  74 static struct option *_longOptions;
  75 static subCommandProps_t *_subCommandProps;
  76 static optionTbl_t *_clientOptionTbl;
  77 static char *commandName;
  78 
  79 
  80 /*
  81  * input:
  82  *  subCommand - subcommand value
  83  * output:
  84  *  subCommandProps - pointer to subCommandProps_t structure allocated by caller
  85  *
  86  * On successful return, subCommandProps contains the properties for the value
  87  * in subCommand. On failure, the contents of subCommandProps is unspecified.
  88  *
  89  * Returns:
  90  *  zero on success
  91  *  non-zero on failure
  92  *
  93  */
  94 static int
  95 getSubcommandProps(char *subCommand, subCommandProps_t **subCommandProps)
  96 {
  97         subCommandProps_t *sp;
  98         int len;
  99 
 100         for (sp = _subCommandProps; sp->name; sp++) {
 101                 len = strlen(subCommand);
 102                 if (len == strlen(sp->name) &&
 103                     strncasecmp(subCommand, sp->name, len) == 0) {
 104                         *subCommandProps = sp;
 105                         return (0);
 106                 }
 107         }
 108         return (1);
 109 }
 110 
 111 /*
 112  * input:
 113  *  shortOption - short option character for which to return the
 114  *      associated long option string
 115  *
 116  * Returns:
 117  *  on success, long option name
 118  *  on failure, NULL
 119  */
 120 static char *
 121 getLongOption(int shortOption)
 122 {
 123         struct option *op;
 124         for (op = _longOptions; op->name; op++) {
 125                 if (shortOption == op->val) {
 126                         return (op->name);
 127                 }
 128         }
 129         return (NULL);
 130 }
 131 
 132 /*
 133  * input
 134  *  shortOption - short option character for which to return the
 135  *      option argument
 136  * Returns:
 137  *  on success, argument string
 138  *  on failure, NULL
 139  */
 140 static char *
 141 getOptionArgDesc(int shortOption)
 142 {
 143         optionTbl_t *op;
 144         for (op = _clientOptionTbl; op->name; op++) {
 145                 if (op->val == shortOption &&
 146                     op->has_arg == required_argument) {
 147                         return (op->argDesc);
 148                 }
 149         }
 150         return (NULL);
 151 }
 152 
 153 
 154 /*
 155  * Print usage for a subcommand.
 156  *
 157  * input:
 158  *  usage type - GENERAL_USAGE, DETAIL_USAGE
 159  *  subcommand - pointer to subCommandProps_t structure
 160  *
 161  * Returns:
 162  *  none
 163  *
 164  */
 165 static void
 166 subUsage(uint_t usageType, subCommandProps_t *subcommand)
 167 {
 168         int i;
 169         char *optionArgDesc;
 170         char *longOpt;
 171 
 172         if (usageType == GENERAL_USAGE) {
 173                 (void) printf("%s:\t%s %s [", gettext("Usage"), commandName,
 174                     subcommand->name);
 175                 for (i = 0; standardSubCmdOptions[i].name; i++) {
 176                         (void) printf("-%c", standardSubCmdOptions[i].val);
 177                         if (standardSubCmdOptions[i+1].name)
 178                                 (void) printf(",");
 179                 }
 180                 (void) fprintf(stdout, "]\n");
 181                 return;
 182         }
 183 
 184         /* print subcommand usage */
 185         (void) printf("\n%s:\t%s %s ", gettext("Usage"), commandName,
 186             subcommand->name);
 187 
 188         /* print options if applicable */
 189         if (subcommand->optionString != NULL) {
 190                 if (subcommand->required) {
 191                         (void) printf("%s", gettext("<"));
 192                 } else {
 193                         (void) printf("%s", gettext("["));
 194                 }
 195                 (void) printf("%s", gettext("OPTIONS"));
 196                 if (subcommand->required) {
 197                         (void) printf("%s ", gettext(">"));
 198                 } else {
 199                         (void) printf("%s ", gettext("]"));
 200                 }
 201         }
 202 
 203         /* print operand requirements */
 204         if (!(subcommand->operand & OPERAND_NONE) &&
 205             !(subcommand->operand & OPERAND_MANDATORY)) {
 206                 (void) printf(gettext("["));
 207         }
 208 
 209         if (subcommand->operand & OPERAND_MANDATORY) {
 210                 (void) printf(gettext("<"));
 211         }
 212 
 213         if (!(subcommand->operand & OPERAND_NONE)) {
 214                 assert(subcommand->operandDefinition);
 215                 (void) printf("%s", subcommand->operandDefinition);
 216         }
 217 
 218         if (subcommand->operand & OPERAND_MULTIPLE) {
 219                 (void) printf(gettext(" ..."));
 220         }
 221 
 222         if (subcommand->operand & OPERAND_MANDATORY) {
 223                 (void) printf(gettext(">"));
 224         }
 225 
 226         if (!(subcommand->operand & OPERAND_NONE) &&
 227             !(subcommand->operand & OPERAND_MANDATORY)) {
 228                 (void) printf(gettext("]"));
 229         }
 230 
 231         /* print options for subcommand */
 232         if (subcommand->optionString != NULL) {
 233                 (void) printf("\n\t%s:", gettext("OPTIONS"));
 234                 for (i = 0; i < strlen(subcommand->optionString); i++) {
 235                         assert((longOpt = getLongOption(
 236                             subcommand->optionString[i])) != NULL);
 237                         (void) printf("\n\t\t-%c, --%s  ",
 238                             subcommand->optionString[i],
 239                             longOpt);
 240                         optionArgDesc =
 241                             getOptionArgDesc(subcommand->optionString[i]);
 242                         if (optionArgDesc != NULL) {
 243                                 (void) printf("<%s>", optionArgDesc);
 244                         }
 245                         if (subcommand->exclusive &&
 246                             strchr(subcommand->exclusive,
 247                             subcommand->optionString[i])) {
 248                                 (void) printf(" (%s)", gettext("exclusive"));
 249                         }
 250                 }
 251         }
 252         (void) fprintf(stdout, "\n");
 253         if (subcommand->helpText) {
 254                 (void) printf("%s\n", subcommand->helpText);
 255         }
 256 }
 257 
 258 /*
 259  * input:
 260  *  type of usage statement to print
 261  *
 262  * Returns:
 263  *  return value of subUsage
 264  */
 265 static void
 266 usage(uint_t usageType)
 267 {
 268         int i;
 269         subCommandProps_t *sp;
 270 
 271         /* print general command usage */
 272         (void) printf("%s:\t%s ", gettext("Usage"), commandName);
 273 
 274         for (i = 0; standardCmdOptions[i].name; i++) {
 275                 (void) printf("-%c", standardCmdOptions[i].val);
 276                 if (standardCmdOptions[i+1].name)
 277                         (void) printf(",");
 278         }
 279 
 280         if (usageType == GENERAL_USAGE) {
 281                 for (i = 0; standardSubCmdOptions[i].name; i++) {
 282                         (void) printf(",--%s", standardSubCmdOptions[i].name);
 283                         if (standardSubCmdOptions[i+1].name)
 284                                 (void) printf(",");
 285                 }
 286         }
 287 
 288         (void) fprintf(stdout, "\n");
 289 
 290 
 291         /* print all subcommand usage */
 292         for (sp = _subCommandProps; sp->name; sp++) {
 293                 subUsage(usageType, sp);
 294         }
 295 }
 296 
 297 /*
 298  * input:
 299  *  execFullName - exec name of program (argv[0])
 300  *
 301  * Returns:
 302  *  command name portion of execFullName
 303  */
 304 static char *
 305 getExecBasename(char *execFullname)
 306 {
 307         char *lastSlash, *execBasename;
 308 
 309         /* guard against '/' at end of command invocation */
 310         for (;;) {
 311                 lastSlash = strrchr(execFullname, '/');
 312                 if (lastSlash == NULL) {
 313                         execBasename = execFullname;
 314                         break;
 315                 } else {
 316                         execBasename = lastSlash + 1;
 317                         if (*execBasename == '\0') {
 318                                 *lastSlash = '\0';
 319                                 continue;
 320                         }
 321                         break;
 322                 }
 323         }
 324         return (execBasename);
 325 }
 326 
 327 /*
 328  * cmdParse is a parser that checks syntax of the input command against
 329  * various rules tables.
 330  *
 331  * It provides usage feedback based upon the passed rules tables by calling
 332  * two usage functions, usage, subUsage
 333  *
 334  * When syntax is successfully validated, the associated function is called
 335  * using the subcommands table functions.
 336  *
 337  * Syntax is as follows:
 338  *      command subcommand [<options>] [<operand>]
 339  *
 340  * There are two standard short and long options assumed:
 341  *      -?, --help      Provides usage on a command or subcommand
 342  *                      and stops further processing of the arguments
 343  *
 344  *      -V, --version   Provides version information on the command
 345  *                      and stops further processing of the arguments
 346  *
 347  *      These options are loaded by this function.
 348  *
 349  * input:
 350  *  argc, argv from main
 351  *  syntax rules tables (synTables_t structure)
 352  *  callArgs - void * passed by caller to be passed to subcommand function
 353  *
 354  * output:
 355  *  funcRet - pointer to int that holds subcommand function return value
 356  *
 357  * Returns:
 358  *
 359  *     zero on successful syntax parse and function call
 360  *
 361  *     1 on unsuccessful syntax parse (no function has been called)
 362  *              This could be due to a version or help call or simply a
 363  *              general usage call.
 364  *
 365  *     -1 check errno, call failed
 366  *
 367  *  This module is not MT-safe.
 368  *
 369  */
 370 int
 371 cmdParse(int argc, char *argv[], synTables_t synTable, void *callArgs,
 372     int *funcRet)
 373 {
 374         int     getoptargc;
 375         char    **getoptargv;
 376         int     opt;
 377         int     operInd;
 378         int     i, j;
 379         int     len;
 380         int     requiredOptionCnt = 0, requiredOptionEntered = 0;
 381         char    *availOptions;
 382         char    *versionString;
 383         char    optionStringAll[MAXOPTIONSTRING + 1];
 384         subCommandProps_t *subcommand;
 385         cmdOptions_t cmdOptions[MAXOPTIONS + 1];
 386         optionTbl_t *optionTbl;
 387         struct option *lp;
 388         struct option intLongOpt[MAXOPTIONS + 1];
 389 
 390         /*
 391          * Check for NULLs on mandatory input arguments
 392          *
 393          * Note: longOptionTbl can be NULL in the case
 394          * where there is no caller defined options
 395          *
 396          */
 397         assert(synTable.versionString);
 398         assert(synTable.subCommandPropsTbl);
 399         assert(funcRet);
 400 
 401         versionString = synTable.versionString;
 402 
 403         /* set global command name */
 404         commandName = getExecBasename(argv[0]);
 405 
 406         /* Set unbuffered output */
 407         setbuf(stdout, NULL);
 408 
 409         /* load globals */
 410         _subCommandProps = synTable.subCommandPropsTbl;
 411         _clientOptionTbl = synTable.longOptionTbl;
 412 
 413         /* There must be at least two arguments */
 414         if (argc < 2) {
 415                 usage(GENERAL_USAGE);
 416                 return (1);
 417         }
 418 
 419         (void) memset(&intLongOpt[0], 0, sizeof (intLongOpt));
 420 
 421         /*
 422          * load standard subcommand options to internal long options table
 423          * Two separate getopt_long(3C) tables are used.
 424          */
 425         for (i = 0; standardSubCmdOptions[i].name; i++) {
 426                 intLongOpt[i].name = standardSubCmdOptions[i].name;
 427                 intLongOpt[i].has_arg = standardSubCmdOptions[i].has_arg;
 428                 intLongOpt[i].flag = standardSubCmdOptions[i].flag;
 429                 intLongOpt[i].val = standardSubCmdOptions[i].val;
 430         }
 431 
 432         /*
 433          * copy caller's long options into internal long options table
 434          * We do this for two reasons:
 435          *  1) We need to use the getopt_long option structure internally
 436          *  2) We need to prepend the table with the standard option
 437          *      for all subcommands (currently -?)
 438          */
 439         for (optionTbl = synTable.longOptionTbl;
 440             optionTbl && optionTbl->name; optionTbl++, i++) {
 441                 if (i > MAXOPTIONS - 1) {
 442                         /* option table too long */
 443                         assert(0);
 444                 }
 445                 intLongOpt[i].name = optionTbl->name;
 446                 intLongOpt[i].has_arg = optionTbl->has_arg;
 447                 intLongOpt[i].flag = NULL;
 448                 intLongOpt[i].val = optionTbl->val;
 449         }
 450 
 451         /* set option table global */
 452         _longOptions = &intLongOpt[0];
 453 
 454 
 455         /*
 456          * Check for help/version request immediately following command
 457          * '+' in option string ensures POSIX compliance in getopt_long()
 458          * which means that processing will stop at first non-option
 459          * argument.
 460          */
 461         while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions,
 462             NULL)) != EOF) {
 463                 switch (opt) {
 464                         case '?':
 465                                 /*
 466                                  * getopt can return a '?' when no
 467                                  * option letters match string. Check for
 468                                  * the 'real' '?' in optopt.
 469                                  */
 470                                 if (optopt == '?') {
 471                                         usage(DETAIL_USAGE);
 472                                         exit(0);
 473                                 } else {
 474                                         usage(GENERAL_USAGE);
 475                                         return (1);
 476                                 }
 477                                 break;
 478                         case 'V':
 479                                 (void) fprintf(stdout, "%s: %s %s\n",
 480                                     commandName, gettext("Version"),
 481                                     versionString);
 482                                 exit(0);
 483                                 break;
 484                         default:
 485                                 break;
 486                 }
 487         }
 488 
 489         /*
 490          * subcommand is always in the second argument. If there is no
 491          * recognized subcommand in the second argument, print error,
 492          * general usage and then return.
 493          */
 494         if (getSubcommandProps(argv[1], &subcommand) != 0) {
 495                 (void) printf("%s: %s\n", commandName,
 496                     gettext("invalid subcommand"));
 497                 usage(GENERAL_USAGE);
 498                 return (1);
 499         }
 500 
 501         getoptargv = argv;
 502         getoptargv++;
 503         getoptargc = argc;
 504         getoptargc -= 1;
 505 
 506         (void) memset(optionStringAll, 0, sizeof (optionStringAll));
 507         (void) memset(&cmdOptions[0], 0, sizeof (cmdOptions));
 508 
 509         j = 0;
 510         /*
 511          * Build optionStringAll from long options table
 512          */
 513         for (lp = _longOptions;  lp->name; lp++, j++) {
 514                 /* sanity check on string length */
 515                 if (j + 1 >= sizeof (optionStringAll)) {
 516                         /* option table too long */
 517                         assert(0);
 518                 }
 519                 optionStringAll[j] = lp->val;
 520                 if (lp->has_arg == required_argument) {
 521                         optionStringAll[++j] = ':';
 522                 }
 523         }
 524 
 525         i = 0;
 526         /*
 527          * Run getopt for all arguments against all possible options
 528          * Store all options/option arguments in an array for retrieval
 529          * later.
 530          *
 531          * Once all options are retrieved, a validity check against
 532          * subcommand table is performed.
 533          */
 534         while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll,
 535             _longOptions, NULL)) != EOF) {
 536                 switch (opt) {
 537                         case '?':
 538                                 subUsage(DETAIL_USAGE, subcommand);
 539                                 /*
 540                                  * getopt can return a '?' when no
 541                                  * option letters match string. Check for
 542                                  * the 'real' '?' in optopt.
 543                                  */
 544                                 if (optopt == '?') {
 545                                         exit(0);
 546                                 } else {
 547                                         exit(1);
 548                                 }
 549                         default:
 550                                 cmdOptions[i].optval = opt;
 551                                 if (optarg) {
 552                                         len = strlen(optarg);
 553                                         if (len > sizeof (cmdOptions[i].optarg)
 554                                             - 1) {
 555                                                 (void) printf("%s: %s\n",
 556                                                     commandName,
 557                                                     gettext("option too long"));
 558                                                 errno = EINVAL;
 559                                                 return (-1);
 560                                         }
 561                                         (void) strncpy(cmdOptions[i].optarg,
 562                                             optarg, len);
 563                                 }
 564                                 i++;
 565                                 break;
 566                 }
 567         }
 568 
 569         /*
 570          * increment past last option
 571          */
 572         operInd = optind + 1;
 573 
 574         /*
 575          * Check validity of given options, if any were given
 576          */
 577 
 578         /* get option string for this subcommand */
 579         availOptions = subcommand->optionString;
 580 
 581         /* Get count of required options */
 582         if (subcommand->required) {
 583                 requiredOptionCnt = strlen(subcommand->required);
 584         }
 585 
 586         if (cmdOptions[0].optval != 0) { /* options were input */
 587                 if (availOptions == NULL) { /* no options permitted */
 588                         (void) printf("%s: %s\n", commandName,
 589                             gettext("no options permitted"));
 590                         subUsage(DETAIL_USAGE, subcommand);
 591                         return (1);
 592                 }
 593                 for (i = 0; cmdOptions[i].optval; i++) {
 594                         /* is the option in the available option string? */
 595                         if (!(strchr(availOptions, cmdOptions[i].optval))) {
 596                                 (void) printf("%s: '-%c': %s\n", commandName,
 597                                     cmdOptions[i].optval,
 598                                     gettext("invalid option"));
 599                                 subUsage(DETAIL_USAGE, subcommand);
 600                                 return (1);
 601                         /* increment required options entered */
 602                         } else if (subcommand->required &&
 603                             (strchr(subcommand->required,
 604                             cmdOptions[i].optval))) {
 605                                 requiredOptionEntered++;
 606                         /* Check for exclusive options */
 607                         } else if (cmdOptions[1].optval != 0 &&
 608                             subcommand->exclusive &&
 609                             strchr(subcommand->exclusive,
 610                             cmdOptions[i].optval)) {
 611                                         (void) printf("%s: '-%c': %s\n",
 612                                             commandName, cmdOptions[i].optval,
 613                                             gettext("is an exclusive option"));
 614                                 subUsage(DETAIL_USAGE, subcommand);
 615                                         return (1);
 616                         }
 617                 }
 618         } else { /* no options were input */
 619                 if (availOptions != NULL && subcommand->required) {
 620                         (void) printf("%s: %s\n", commandName,
 621                             gettext("at least one option required"));
 622                         subUsage(DETAIL_USAGE, subcommand);
 623                         return (1);
 624                 }
 625         }
 626 
 627         /* Were all required options entered? */
 628         if (requiredOptionEntered != requiredOptionCnt) {
 629                 (void) printf("%s: %s: %s\n", commandName,
 630                     gettext("Following option(s) required"),
 631                     subcommand->required);
 632                 subUsage(DETAIL_USAGE, subcommand);
 633                 return (1);
 634         }
 635 
 636 
 637         /*
 638          * If there are no operands,
 639          * check to see if this is okay
 640          */
 641         if ((operInd == argc) &&
 642             (subcommand->operand & OPERAND_MANDATORY)) {
 643                 (void) printf("%s: %s %s\n", commandName, subcommand->name,
 644                     gettext("requires an operand"));
 645                 subUsage(DETAIL_USAGE, subcommand);
 646                 return (1);
 647         }
 648 
 649         /*
 650          * If there are more operands,
 651          * check to see if this is okay
 652          */
 653         if ((argc > operInd) &&
 654             (subcommand->operand & OPERAND_NONE)) {
 655                 (void) fprintf(stderr, "%s: %s %s\n", commandName,
 656                     subcommand->name, gettext("takes no operands"));
 657                 subUsage(DETAIL_USAGE, subcommand);
 658                 return (1);
 659         }
 660 
 661         /*
 662          * If there is more than one more operand,
 663          * check to see if this is okay
 664          */
 665         if ((argc > operInd) && ((argc - operInd) != 1) &&
 666             (subcommand->operand & OPERAND_SINGLE)) {
 667                 (void) printf("%s: %s %s\n", commandName,
 668                     subcommand->name, gettext("accepts only a single operand"));
 669                 subUsage(DETAIL_USAGE, subcommand);
 670                 return (1);
 671         }
 672 
 673         /* Finished syntax checks */
 674 
 675 
 676         /* Call appropriate function */
 677         *funcRet = subcommand->handler(argc - operInd, &argv[operInd],
 678             &cmdOptions[0], callArgs);
 679 
 680         return (0);
 681 }