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