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