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
getSubcommandProps(char * subCommand,subCommandProps_t ** subCommandProps)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 *
getLongOption(int shortOption)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 *
getOptionArgDesc(int shortOption)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
subUsage(uint_t usageType,subCommandProps_t * subcommand)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
usage(uint_t usageType)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 *
getExecBasename(char * execFullname)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
cmdParse(int argc,char * argv[],synTables_t synTable,void * callArgs,int * funcRet)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