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