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