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