xref: /illumos-gate/usr/src/common/cmdparse/cmdparse.c (revision f9c7ecc9013918bf789e154e2ed5f2e8fb6ce24c)
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