xref: /illumos-gate/usr/src/common/cmdparse/cmdparse.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
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 #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 }
254 
255 /*
256  * input:
257  *  type of usage statement to print
258  *
259  * Returns:
260  *  return value of subUsage
261  */
262 static void
263 usage(uint_t usageType)
264 {
265 	int i;
266 	subCommandProps_t *sp;
267 
268 	/* print general command usage */
269 	(void) printf("%s:\t%s ", gettext("Usage"), commandName);
270 
271 	for (i = 0; standardCmdOptions[i].name; i++) {
272 		(void) printf("-%c", standardCmdOptions[i].val);
273 		if (standardCmdOptions[i+1].name)
274 			(void) printf(",");
275 	}
276 
277 	if (usageType == GENERAL_USAGE) {
278 		for (i = 0; standardSubCmdOptions[i].name; i++) {
279 			(void) printf(",--%s", standardSubCmdOptions[i].name);
280 			if (standardSubCmdOptions[i+1].name)
281 				(void) printf(",");
282 		}
283 	}
284 
285 	(void) fprintf(stdout, "\n");
286 
287 
288 	/* print all subcommand usage */
289 	for (sp = _subCommandProps; sp->name; sp++) {
290 		subUsage(usageType, sp);
291 	}
292 }
293 
294 /*
295  * input:
296  *  execFullName - exec name of program (argv[0])
297  *
298  * Returns:
299  *  command name portion of execFullName
300  */
301 static char *
302 getExecBasename(char *execFullname)
303 {
304 	char *lastSlash, *execBasename;
305 
306 	/* guard against '/' at end of command invocation */
307 	for (;;) {
308 		lastSlash = strrchr(execFullname, '/');
309 		if (lastSlash == NULL) {
310 			execBasename = execFullname;
311 			break;
312 		} else {
313 			execBasename = lastSlash + 1;
314 			if (*execBasename == '\0') {
315 				*lastSlash = '\0';
316 				continue;
317 			}
318 			break;
319 		}
320 	}
321 	return (execBasename);
322 }
323 
324 /*
325  * cmdParse is a parser that checks syntax of the input command against
326  * various rules tables.
327  *
328  * It provides usage feedback based upon the passed rules tables by calling
329  * two usage functions, usage, subUsage
330  *
331  * When syntax is successfully validated, the associated function is called
332  * using the subcommands table functions.
333  *
334  * Syntax is as follows:
335  *	command subcommand [<options>] [<operand>]
336  *
337  * There are two standard short and long options assumed:
338  *	-?, --help	Provides usage on a command or subcommand
339  *			and stops further processing of the arguments
340  *
341  *	-V, --version	Provides version information on the command
342  *			and stops further processing of the arguments
343  *
344  *	These options are loaded by this function.
345  *
346  * input:
347  *  argc, argv from main
348  *  syntax rules tables (synTables_t structure)
349  *  callArgs - void * passed by caller to be passed to subcommand function
350  *
351  * output:
352  *  funcRet - pointer to int that holds subcommand function return value
353  *
354  * Returns:
355  *
356  *     zero on successful syntax parse and function call
357  *
358  *     1 on unsuccessful syntax parse (no function has been called)
359  *		This could be due to a version or help call or simply a
360  *		general usage call.
361  *
362  *     -1 check errno, call failed
363  *
364  *  This module is not MT-safe.
365  *
366  */
367 int
368 cmdParse(int argc, char *argv[], synTables_t synTable, void *callArgs,
369     int *funcRet)
370 {
371 	int	getoptargc;
372 	char	**getoptargv;
373 	int	opt;
374 	int	operInd;
375 	int	i, j;
376 	int	len;
377 	int	requiredOptionCnt = 0, requiredOptionEntered = 0;
378 	char	*availOptions;
379 	char	*versionString;
380 	char	optionStringAll[MAXOPTIONSTRING + 1];
381 	subCommandProps_t *subcommand;
382 	cmdOptions_t cmdOptions[MAXOPTIONS + 1];
383 	optionTbl_t *optionTbl;
384 	struct option *lp;
385 	struct option intLongOpt[MAXOPTIONS + 1];
386 
387 	/*
388 	 * Check for NULLs on mandatory input arguments
389 	 *
390 	 * Note: longOptionTbl can be NULL in the case
391 	 * where there is no caller defined options
392 	 *
393 	 */
394 	assert(synTable.versionString);
395 	assert(synTable.subCommandPropsTbl);
396 	assert(funcRet);
397 
398 	versionString = synTable.versionString;
399 
400 	/* set global command name */
401 	commandName = getExecBasename(argv[0]);
402 
403 	/* Set unbuffered output */
404 	setbuf(stdout, NULL);
405 
406 	/* load globals */
407 	_subCommandProps = synTable.subCommandPropsTbl;
408 	_clientOptionTbl = synTable.longOptionTbl;
409 
410 	/* There must be at least two arguments */
411 	if (argc < 2) {
412 		usage(GENERAL_USAGE);
413 		return (1);
414 	}
415 
416 	(void) memset(&intLongOpt[0], 0, sizeof (intLongOpt));
417 
418 	/*
419 	 * load standard subcommand options to internal long options table
420 	 * Two separate getopt_long(3C) tables are used.
421 	 */
422 	for (i = 0; standardSubCmdOptions[i].name; i++) {
423 		intLongOpt[i].name = standardSubCmdOptions[i].name;
424 		intLongOpt[i].has_arg = standardSubCmdOptions[i].has_arg;
425 		intLongOpt[i].flag = standardSubCmdOptions[i].flag;
426 		intLongOpt[i].val = standardSubCmdOptions[i].val;
427 	}
428 
429 	/*
430 	 * copy caller's long options into internal long options table
431 	 * We do this for two reasons:
432 	 *  1) We need to use the getopt_long option structure internally
433 	 *  2) We need to prepend the table with the standard option
434 	 *	for all subcommands (currently -?)
435 	 */
436 	for (optionTbl = synTable.longOptionTbl;
437 	    optionTbl && optionTbl->name; optionTbl++, i++) {
438 		if (i > MAXOPTIONS - 1) {
439 			/* option table too long */
440 			assert(0);
441 		}
442 		intLongOpt[i].name = optionTbl->name;
443 		intLongOpt[i].has_arg = optionTbl->has_arg;
444 		intLongOpt[i].flag = NULL;
445 		intLongOpt[i].val = optionTbl->val;
446 	}
447 
448 	/* set option table global */
449 	_longOptions = &intLongOpt[0];
450 
451 
452 	/*
453 	 * Check for help/version request immediately following command
454 	 * '+' in option string ensures POSIX compliance in getopt_long()
455 	 * which means that processing will stop at first non-option
456 	 * argument.
457 	 */
458 	while ((opt = getopt_long(argc, argv, "+?V", standardCmdOptions,
459 	    NULL)) != EOF) {
460 		switch (opt) {
461 			case '?':
462 				/*
463 				 * getopt can return a '?' when no
464 				 * option letters match string. Check for
465 				 * the 'real' '?' in optopt.
466 				 */
467 				if (optopt == '?') {
468 					usage(DETAIL_USAGE);
469 					exit(0);
470 				} else {
471 					usage(GENERAL_USAGE);
472 					return (1);
473 				}
474 				break;
475 			case 'V':
476 				(void) fprintf(stdout, "%s: %s %s\n",
477 				    commandName, gettext("Version"),
478 				    versionString);
479 				exit(0);
480 				break;
481 			default:
482 				break;
483 		}
484 	}
485 
486 	/*
487 	 * subcommand is always in the second argument. If there is no
488 	 * recognized subcommand in the second argument, print error,
489 	 * general usage and then return.
490 	 */
491 	if (getSubcommandProps(argv[1], &subcommand) != 0) {
492 		(void) printf("%s: %s\n", commandName,
493 		    gettext("invalid subcommand"));
494 		usage(GENERAL_USAGE);
495 		return (1);
496 	}
497 
498 	getoptargv = argv;
499 	getoptargv++;
500 	getoptargc = argc;
501 	getoptargc -= 1;
502 
503 	(void) memset(optionStringAll, 0, sizeof (optionStringAll));
504 	(void) memset(&cmdOptions[0], 0, sizeof (cmdOptions));
505 
506 	j = 0;
507 	/*
508 	 * Build optionStringAll from long options table
509 	 */
510 	for (lp = _longOptions;  lp->name; lp++, j++) {
511 		/* sanity check on string length */
512 		if (j + 1 >= sizeof (optionStringAll)) {
513 			/* option table too long */
514 			assert(0);
515 		}
516 		optionStringAll[j] = lp->val;
517 		if (lp->has_arg == required_argument) {
518 			optionStringAll[++j] = ':';
519 		}
520 	}
521 
522 	i = 0;
523 	/*
524 	 * Run getopt for all arguments against all possible options
525 	 * Store all options/option arguments in an array for retrieval
526 	 * later.
527 	 *
528 	 * Once all options are retrieved, a validity check against
529 	 * subcommand table is performed.
530 	 */
531 	while ((opt = getopt_long(getoptargc, getoptargv, optionStringAll,
532 	    _longOptions, NULL)) != EOF) {
533 		switch (opt) {
534 			case '?':
535 				subUsage(DETAIL_USAGE, subcommand);
536 				exit(0);
537 			default:
538 				cmdOptions[i].optval = opt;
539 				if (optarg) {
540 					len = strlen(optarg);
541 					if (len > sizeof (cmdOptions[i].optarg)
542 					    - 1) {
543 						(void) printf("%s: %s\n",
544 						    commandName,
545 						    gettext("option too long"));
546 						errno = EINVAL;
547 						return (-1);
548 					}
549 					(void) strncpy(cmdOptions[i].optarg,
550 					    optarg, len);
551 				}
552 				i++;
553 				break;
554 		}
555 	}
556 
557 	/*
558 	 * increment past last option
559 	 */
560 	operInd = optind + 1;
561 
562 	/*
563 	 * Check validity of given options, if any were given
564 	 */
565 
566 	/* get option string for this subcommand */
567 	availOptions = subcommand->optionString;
568 
569 	/* Get count of required options */
570 	if (subcommand->required) {
571 		requiredOptionCnt = strlen(subcommand->required);
572 	}
573 
574 	if (cmdOptions[0].optval != 0) { /* options were input */
575 		if (availOptions == NULL) { /* no options permitted */
576 			(void) printf("%s: %s\n", commandName,
577 			    gettext("no options permitted"));
578 			subUsage(DETAIL_USAGE, subcommand);
579 			return (1);
580 		}
581 		for (i = 0; cmdOptions[i].optval; i++) {
582 			/* is the option in the available option string? */
583 			if (!(strchr(availOptions, cmdOptions[i].optval))) {
584 				(void) printf("%s: '-%c': %s\n", commandName,
585 				    cmdOptions[i].optval,
586 				    gettext("invalid option"));
587 				subUsage(DETAIL_USAGE, subcommand);
588 				return (1);
589 			/* increment required options entered */
590 			} else if (subcommand->required &&
591 			    (strchr(subcommand->required,
592 			    cmdOptions[i].optval))) {
593 				requiredOptionEntered++;
594 			/* Check for exclusive options */
595 			} else if (cmdOptions[1].optval != 0 &&
596 			    subcommand->exclusive &&
597 			    strchr(subcommand->exclusive,
598 			    cmdOptions[i].optval)) {
599 					(void) printf("%s: '-%c': %s\n",
600 					    commandName, cmdOptions[i].optval,
601 					    gettext("is an exclusive option"));
602 				subUsage(DETAIL_USAGE, subcommand);
603 					return (1);
604 			}
605 		}
606 	} else { /* no options were input */
607 		if (availOptions != NULL && subcommand->required) {
608 			(void) printf("%s: %s\n", commandName,
609 			    gettext("at least one option required"));
610 			subUsage(DETAIL_USAGE, subcommand);
611 			return (1);
612 		}
613 	}
614 
615 	/* Were all required options entered? */
616 	if (requiredOptionEntered != requiredOptionCnt) {
617 		(void) printf("%s: %s: %s\n", commandName,
618 		    gettext("Following option(s) required"),
619 		    subcommand->required);
620 		subUsage(DETAIL_USAGE, subcommand);
621 		return (1);
622 	}
623 
624 
625 	/*
626 	 * If there are no operands,
627 	 * check to see if this is okay
628 	 */
629 	if ((operInd == argc) &&
630 	    (subcommand->operand & OPERAND_MANDATORY)) {
631 		(void) printf("%s: %s %s\n", commandName, subcommand->name,
632 		    gettext("requires an operand"));
633 		subUsage(DETAIL_USAGE, subcommand);
634 		return (1);
635 	}
636 
637 	/*
638 	 * If there are more operands,
639 	 * check to see if this is okay
640 	 */
641 	if ((argc > operInd) &&
642 	    (subcommand->operand & OPERAND_NONE)) {
643 		(void) fprintf(stderr, "%s: %s %s\n", commandName,
644 		    subcommand->name, gettext("takes no operands"));
645 		subUsage(DETAIL_USAGE, subcommand);
646 		return (1);
647 	}
648 
649 	/*
650 	 * If there is more than one more operand,
651 	 * check to see if this is okay
652 	 */
653 	if ((argc > operInd) && ((argc - operInd) != 1) &&
654 	    (subcommand->operand & OPERAND_SINGLE)) {
655 		(void) printf("%s: %s %s\n", commandName,
656 		    subcommand->name, gettext("accepts only a single operand"));
657 		subUsage(DETAIL_USAGE, subcommand);
658 		return (1);
659 	}
660 
661 	/* Finished syntax checks */
662 
663 
664 	/* Call appropriate function */
665 	*funcRet = subcommand->handler(argc - operInd, &argv[operInd],
666 	    &cmdOptions[0], callArgs);
667 
668 	return (0);
669 }
670