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