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 2020 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 const 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 const 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 const 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 } 686