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 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 /* 26 * Copyright 2020 Joyent Inc. 27 */ 28 29 #include <stdlib.h> 30 #include <stdio.h> 31 #include <sys/types.h> 32 #include <unistd.h> 33 #include <libintl.h> 34 #include <errno.h> 35 #include <string.h> 36 #include <assert.h> 37 #include <getopt.h> 38 #include "cmdparse.h" 39 40 /* Usage types */ 41 #define GENERAL_USAGE 1 42 #define DETAIL_USAGE 2 43 44 /* printable ascii character set len */ 45 #define MAXOPTIONS (uint_t)('~' - '!' + 1) 46 47 /* 48 * MAXOPTIONSTRING is the max length of the options string used in getopt and 49 * will be the printable character set + ':' for each character, 50 * providing for options with arguments. e.g. "t:Cs:hglr:" 51 */ 52 #define MAXOPTIONSTRING MAXOPTIONS * 2 53 54 /* standard command options table to support -?, -V */ 55 struct option standardCmdOptions[] = { 56 {"help", no_argument, NULL, '?'}, 57 {"version", no_argument, NULL, 'V'}, 58 {NULL, 0, NULL, 0} 59 }; 60 61 /* standard subcommand options table to support -? */ 62 struct option standardSubCmdOptions[] = { 63 {"help", no_argument, NULL, '?'}, 64 {NULL, 0, NULL, 0} 65 }; 66 67 /* forward declarations */ 68 static int getSubcommandProps(char *, subCommandProps_t **); 69 static char *getExecBasename(char *); 70 static void usage(uint_t); 71 static void subUsage(uint_t, subCommandProps_t *); 72 static const char *getLongOption(int); 73 static char *getOptionArgDesc(int); 74 75 /* global data */ 76 static struct option *_longOptions; 77 static subCommandProps_t *_subCommandProps; 78 static optionTbl_t *_clientOptionTbl; 79 static char *commandName; 80 81 82 /* 83 * input: 84 * subCommand - subcommand value 85 * output: 86 * subCommandProps - pointer to subCommandProps_t structure allocated by caller 87 * 88 * On successful return, subCommandProps contains the properties for the value 89 * in subCommand. On failure, the contents of subCommandProps is unspecified. 90 * 91 * Returns: 92 * zero on success 93 * non-zero on failure 94 * 95 */ 96 static int 97 getSubcommandProps(char *subCommand, subCommandProps_t **subCommandProps) 98 { 99 subCommandProps_t *sp; 100 int len; 101 102 for (sp = _subCommandProps; sp->name; sp++) { 103 len = strlen(subCommand); 104 if (len == strlen(sp->name) && 105 strncasecmp(subCommand, sp->name, len) == 0) { 106 *subCommandProps = sp; 107 return (0); 108 } 109 } 110 return (1); 111 } 112 113 /* 114 * input: 115 * shortOption - short option character for which to return the 116 * associated long option string 117 * 118 * Returns: 119 * on success, long option name 120 * on failure, NULL 121 */ 122 static const char * 123 getLongOption(int shortOption) 124 { 125 struct option *op; 126 for (op = _longOptions; op->name; op++) { 127 if (shortOption == op->val) { 128 return (op->name); 129 } 130 } 131 return (NULL); 132 } 133 134 /* 135 * input 136 * shortOption - short option character for which to return the 137 * option argument 138 * Returns: 139 * on success, argument string 140 * on failure, NULL 141 */ 142 static char * 143 getOptionArgDesc(int shortOption) 144 { 145 optionTbl_t *op; 146 for (op = _clientOptionTbl; op->name; op++) { 147 if (op->val == shortOption && 148 op->has_arg == required_argument) { 149 return (op->argDesc); 150 } 151 } 152 return (NULL); 153 } 154 155 156 /* 157 * Print usage for a subcommand. 158 * 159 * input: 160 * usage type - GENERAL_USAGE, DETAIL_USAGE 161 * subcommand - pointer to subCommandProps_t structure 162 * 163 * Returns: 164 * none 165 * 166 */ 167 static void 168 subUsage(uint_t usageType, subCommandProps_t *subcommand) 169 { 170 int i; 171 char *optionArgDesc; 172 const char *longOpt; 173 174 if (usageType == GENERAL_USAGE) { 175 (void) printf("%s:\t%s %s [", gettext("Usage"), commandName, 176 subcommand->name); 177 178 for (i = 0; standardSubCmdOptions[i].name; i++) { 179 (void) printf("-%c", standardSubCmdOptions[i].val); 180 if (standardSubCmdOptions[i+1].name) 181 (void) printf(","); 182 } 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 if ((longOpt = getLongOption( 240 subcommand->optionString[i])) 241 == NULL) { 242 /* no long option exists for short option */ 243 assert(0); 244 } 245 (void) printf("\n\t\t-%c, --%s ", 246 subcommand->optionString[i], longOpt); 247 optionArgDesc = 248 getOptionArgDesc(subcommand->optionString[i]); 249 if (optionArgDesc != NULL) { 250 (void) printf("<%s>", optionArgDesc); 251 } 252 if (subcommand->exclusive && 253 strchr(subcommand->exclusive, 254 subcommand->optionString[i])) { 255 (void) printf(" (%s)", gettext("exclusive")); 256 } 257 } 258 } 259 (void) fprintf(stdout, "\n"); 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 char *availOptions; 385 char *versionString; 386 char optionStringAll[MAXOPTIONSTRING + 1]; 387 subCommandProps_t *subcommand; 388 cmdOptions_t cmdOptions[MAXOPTIONS + 1]; 389 optionTbl_t *optionTbl; 390 struct option *lp; 391 struct option intLongOpt[MAXOPTIONS + 1]; 392 393 /* 394 * Check for NULLs on mandatory input arguments 395 * 396 * Note: longOptionTbl can be NULL in the case 397 * where there is no caller defined options 398 * 399 */ 400 assert(synTable.versionString); 401 assert(synTable.subCommandPropsTbl); 402 assert(funcRet); 403 404 versionString = synTable.versionString; 405 406 /* set global command name */ 407 commandName = getExecBasename(argv[0]); 408 409 /* Set unbuffered output */ 410 setbuf(stdout, NULL); 411 412 /* load globals */ 413 _subCommandProps = synTable.subCommandPropsTbl; 414 _clientOptionTbl = synTable.longOptionTbl; 415 416 /* There must be at least two arguments */ 417 if (argc < 2) { 418 usage(GENERAL_USAGE); 419 return (1); 420 } 421 422 (void) memset(&intLongOpt[0], 0, sizeof (intLongOpt)); 423 424 /* 425 * load standard subcommand options to internal long options table 426 * Two separate getopt_long(3C) tables are used. 427 */ 428 for (i = 0; standardSubCmdOptions[i].name; i++) { 429 intLongOpt[i].name = standardSubCmdOptions[i].name; 430 intLongOpt[i].has_arg = standardSubCmdOptions[i].has_arg; 431 intLongOpt[i].flag = standardSubCmdOptions[i].flag; 432 intLongOpt[i].val = standardSubCmdOptions[i].val; 433 } 434 435 /* 436 * copy caller's long options into internal long options table 437 * We do this for two reasons: 438 * 1) We need to use the getopt_long option structure internally 439 * 2) We need to prepend the table with the standard option 440 * for all subcommands (currently -?) 441 */ 442 for (optionTbl = synTable.longOptionTbl; 443 optionTbl && optionTbl->name; optionTbl++, i++) { 444 if (i > MAXOPTIONS - 1) { 445 /* option table too long */ 446 assert(0); 447 } 448 intLongOpt[i].name = optionTbl->name; 449 intLongOpt[i].has_arg = optionTbl->has_arg; 450 intLongOpt[i].flag = NULL; 451 intLongOpt[i].val = optionTbl->val; 452 } 453 454 /* set option table global */ 455 _longOptions = &intLongOpt[0]; 456 457 458 /* 459 * Check for help/version request immediately following command 460 * '+' in option string ensures POSIX compliance in getopt_long() 461 * which means that processing will stop at first non-option 462 * argument. 463 */ 464 while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions, 465 NULL)) != EOF) { 466 switch (opt) { 467 case '?': 468 /* 469 * getopt can return a '?' when no 470 * option letters match string. Check for 471 * the 'real' '?' in optopt. 472 */ 473 if (optopt == '?') { 474 usage(DETAIL_USAGE); 475 return (1); 476 } else { 477 usage(GENERAL_USAGE); 478 return (1); 479 } 480 case 'V': 481 (void) fprintf(stdout, "%s: %s %s\n", 482 commandName, gettext("Version"), 483 versionString); 484 return (1); 485 default: 486 break; 487 } 488 } 489 490 /* 491 * subcommand is always in the second argument. If there is no 492 * recognized subcommand in the second argument, print error, 493 * general usage and then return. 494 */ 495 if (getSubcommandProps(argv[1], &subcommand) != 0) { 496 (void) printf("%s: %s\n", commandName, 497 gettext("invalid subcommand")); 498 usage(GENERAL_USAGE); 499 return (1); 500 } 501 502 getoptargv = argv; 503 getoptargv++; 504 getoptargc = argc; 505 getoptargc -= 1; 506 507 (void) memset(optionStringAll, 0, sizeof (optionStringAll)); 508 (void) memset(&cmdOptions[0], 0, sizeof (cmdOptions)); 509 510 j = 0; 511 /* 512 * Build optionStringAll from long options table 513 */ 514 for (lp = _longOptions; lp->name; lp++, j++) { 515 /* sanity check on string length */ 516 if (j + 1 >= sizeof (optionStringAll)) { 517 /* option table too long */ 518 assert(0); 519 } 520 optionStringAll[j] = lp->val; 521 if (lp->has_arg == required_argument) { 522 optionStringAll[++j] = ':'; 523 } 524 } 525 526 i = 0; 527 /* 528 * Run getopt for all arguments against all possible options 529 * Store all options/option arguments in an array for retrieval 530 * later. 531 * 532 * Once all options are retrieved, a validity check against 533 * subcommand table is performed. 534 */ 535 while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll, 536 _longOptions, NULL)) != EOF) { 537 switch (opt) { 538 case '?': 539 subUsage(DETAIL_USAGE, subcommand); 540 return (1); 541 default: 542 cmdOptions[i].optval = opt; 543 if (optarg) { 544 len = strlen(optarg); 545 if (len > sizeof (cmdOptions[i].optarg) 546 - 1) { 547 (void) printf("%s: %s\n", 548 commandName, 549 gettext("option too long")); 550 errno = EINVAL; 551 return (-1); 552 } 553 (void) strncpy(cmdOptions[i].optarg, 554 optarg, len); 555 } 556 i++; 557 break; 558 } 559 } 560 561 /* 562 * increment past last option 563 */ 564 operInd = optind + 1; 565 566 /* 567 * Check validity of given options, if any were given 568 */ 569 570 /* get option string for this subcommand */ 571 availOptions = subcommand->optionString; 572 573 if (cmdOptions[0].optval != 0) { /* options were input */ 574 if (availOptions == NULL) { /* no options permitted */ 575 (void) printf("%s: %s\n", commandName, 576 gettext("no options permitted")); 577 subUsage(DETAIL_USAGE, subcommand); 578 return (1); 579 } 580 for (i = 0; cmdOptions[i].optval; i++) { 581 /* is the option in the available option string? */ 582 if (!(strchr(availOptions, cmdOptions[i].optval))) { 583 (void) printf("%s: '-%c': %s\n", commandName, 584 cmdOptions[i].optval, 585 gettext("invalid option")); 586 subUsage(DETAIL_USAGE, subcommand); 587 return (1); 588 589 /* Check for exclusive options */ 590 } else if (cmdOptions[1].optval != 0 && 591 subcommand->exclusive && 592 strchr(subcommand->exclusive, 593 cmdOptions[i].optval)) { 594 (void) printf("%s: '-%c': %s\n", 595 commandName, cmdOptions[i].optval, 596 gettext("is an exclusive option")); 597 subUsage(DETAIL_USAGE, subcommand); 598 return (1); 599 } 600 } 601 } else { /* no options were input */ 602 if (availOptions != NULL && subcommand->required) { 603 (void) printf("%s: %s\n", commandName, 604 gettext("at least one option required")); 605 subUsage(DETAIL_USAGE, subcommand); 606 return (1); 607 } 608 } 609 610 /* 611 * If there are no operands, 612 * check to see if this is okay 613 */ 614 if ((operInd == argc) && 615 (subcommand->operand & OPERAND_MANDATORY)) { 616 (void) printf("%s: %s %s\n", commandName, subcommand->name, 617 gettext("requires an operand")); 618 subUsage(DETAIL_USAGE, subcommand); 619 return (1); 620 } 621 622 /* 623 * If there are more operands, 624 * check to see if this is okay 625 */ 626 if ((argc > operInd) && 627 (subcommand->operand & OPERAND_NONE)) { 628 (void) fprintf(stderr, "%s: %s %s\n", 629 commandName, subcommand->name, 630 gettext("takes no operands")); 631 subUsage(DETAIL_USAGE, subcommand); 632 return (1); 633 } 634 635 /* 636 * If there is more than one more operand, 637 * check to see if this is okay 638 */ 639 if ((argc > operInd) && ((argc - operInd) != 1) && 640 (subcommand->operand & OPERAND_SINGLE)) { 641 (void) printf("%s: %s %s\n", commandName, 642 subcommand->name, gettext("accepts only a single operand")); 643 subUsage(DETAIL_USAGE, subcommand); 644 return (1); 645 } 646 647 /* Finished syntax checks */ 648 649 650 /* Call appropriate function */ 651 *funcRet = subcommand->handler(argc - operInd, &argv[operInd], 652 &cmdOptions[0], callArgs); 653 654 return (0); 655 } 656