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 */
53struct 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 -? */
60struct option standardSubCmdOptions[] = {
61	{"help", no_argument, NULL, '?'},
62	{NULL, 0, NULL, 0}
63};
64
65/* forward declarations */
66static int getSubcommandProps(char *, subCommandProps_t **);
67static char *getExecBasename(char *);
68static void usage(uint_t);
69static void subUsage(uint_t, subCommandProps_t *);
70static char *getLongOption(int);
71static char *getOptionArgDesc(int);
72
73/* global data */
74static struct option *_longOptions;
75static subCommandProps_t *_subCommandProps;
76static optionTbl_t *_clientOptionTbl;
77static 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 */
94static int
95getSubcommandProps(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 */
120static char *
121getLongOption(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 */
140static char *
141getOptionArgDesc(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 */
165static void
166subUsage(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 */
265static void
266usage(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 */
304static char *
305getExecBasename(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 */
370int
371cmdParse(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				/*
540				 * getopt can return a '?' when no
541				 * option letters match string. Check for
542				 * the 'real' '?' in optopt.
543				 */
544				if (optopt == '?') {
545					exit(0);
546				} else {
547					exit(1);
548				}
549			default:
550				cmdOptions[i].optval = opt;
551				if (optarg) {
552					len = strlen(optarg);
553					if (len > sizeof (cmdOptions[i].optarg)
554					    - 1) {
555						(void) printf("%s: %s\n",
556						    commandName,
557						    gettext("option too long"));
558						errno = EINVAL;
559						return (-1);
560					}
561					(void) strncpy(cmdOptions[i].optarg,
562					    optarg, len);
563				}
564				i++;
565				break;
566		}
567	}
568
569	/*
570	 * increment past last option
571	 */
572	operInd = optind + 1;
573
574	/*
575	 * Check validity of given options, if any were given
576	 */
577
578	/* get option string for this subcommand */
579	availOptions = subcommand->optionString;
580
581	/* Get count of required options */
582	if (subcommand->required) {
583		requiredOptionCnt = strlen(subcommand->required);
584	}
585
586	if (cmdOptions[0].optval != 0) { /* options were input */
587		if (availOptions == NULL) { /* no options permitted */
588			(void) printf("%s: %s\n", commandName,
589			    gettext("no options permitted"));
590			subUsage(DETAIL_USAGE, subcommand);
591			return (1);
592		}
593		for (i = 0; cmdOptions[i].optval; i++) {
594			/* is the option in the available option string? */
595			if (!(strchr(availOptions, cmdOptions[i].optval))) {
596				(void) printf("%s: '-%c': %s\n", commandName,
597				    cmdOptions[i].optval,
598				    gettext("invalid option"));
599				subUsage(DETAIL_USAGE, subcommand);
600				return (1);
601			/* increment required options entered */
602			} else if (subcommand->required &&
603			    (strchr(subcommand->required,
604			    cmdOptions[i].optval))) {
605				requiredOptionEntered++;
606			/* Check for exclusive options */
607			} else if (cmdOptions[1].optval != 0 &&
608			    subcommand->exclusive &&
609			    strchr(subcommand->exclusive,
610			    cmdOptions[i].optval)) {
611					(void) printf("%s: '-%c': %s\n",
612					    commandName, cmdOptions[i].optval,
613					    gettext("is an exclusive option"));
614				subUsage(DETAIL_USAGE, subcommand);
615					return (1);
616			}
617		}
618	} else { /* no options were input */
619		if (availOptions != NULL && subcommand->required) {
620			(void) printf("%s: %s\n", commandName,
621			    gettext("at least one option required"));
622			subUsage(DETAIL_USAGE, subcommand);
623			return (1);
624		}
625	}
626
627	/* Were all required options entered? */
628	if (requiredOptionEntered != requiredOptionCnt) {
629		(void) printf("%s: %s: %s\n", commandName,
630		    gettext("Following option(s) required"),
631		    subcommand->required);
632		subUsage(DETAIL_USAGE, subcommand);
633		return (1);
634	}
635
636
637	/*
638	 * If there are no operands,
639	 * check to see if this is okay
640	 */
641	if ((operInd == argc) &&
642	    (subcommand->operand & OPERAND_MANDATORY)) {
643		(void) printf("%s: %s %s\n", commandName, subcommand->name,
644		    gettext("requires an operand"));
645		subUsage(DETAIL_USAGE, subcommand);
646		return (1);
647	}
648
649	/*
650	 * If there are more operands,
651	 * check to see if this is okay
652	 */
653	if ((argc > operInd) &&
654	    (subcommand->operand & OPERAND_NONE)) {
655		(void) fprintf(stderr, "%s: %s %s\n", commandName,
656		    subcommand->name, gettext("takes no operands"));
657		subUsage(DETAIL_USAGE, subcommand);
658		return (1);
659	}
660
661	/*
662	 * If there is more than one more operand,
663	 * check to see if this is okay
664	 */
665	if ((argc > operInd) && ((argc - operInd) != 1) &&
666	    (subcommand->operand & OPERAND_SINGLE)) {
667		(void) printf("%s: %s %s\n", commandName,
668		    subcommand->name, gettext("accepts only a single operand"));
669		subUsage(DETAIL_USAGE, subcommand);
670		return (1);
671	}
672
673	/* Finished syntax checks */
674
675
676	/* Call appropriate function */
677	*funcRet = subcommand->handler(argc - operInd, &argv[operInd],
678	    &cmdOptions[0], callArgs);
679
680	return (0);
681}
682