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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*
27  * Copyright 2006-2019 J. Schilling
28  *
29  * @(#)getopt.c	1.22 19/12/14 J. Schilling
30  */
31 #if defined(sun)
32 #pragma ident "@(#)getopt.c 1.22 19/12/14 J. Schilling"
33 #endif
34 
35 #if defined(sun)
36 #pragma ident	"@(#)getopt.c	1.23	05/06/08 SMI"
37 #endif
38 
39 /*	Copyright (c) 1988 AT&T	*/
40 /*	  All Rights Reserved  	*/
41 
42 
43 /*
44  * See getopt(3C) and SUS/XPG getopt() for function definition and
45  * requirements.
46  *
47  * This actual implementation is a bit looser than the specification
48  * as it allows any character other than ':' and '(' to be used as
49  * a short option character - The specification only guarantees the
50  * alnum characters ([a-z][A-Z][0-9]).
51  */
52 
53 /*#pragma weak getopt = _getopt*/
54 
55 /*#include "synonyms.h"*/
56 /*#include "_libc_gettext.h"*/
57 
58 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
59 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
60 #endif
61 #define	_libc_gettext(s)	dgettext(TEXT_DOMAIN, s)
62 
63 #include <schily/nlsdefs.h>
64 
65 #include <schily/unistd.h>
66 #include <schily/string.h>
67 #include <schily/stdio.h>
68 #include <schily/getopt.h>
69 #ifndef	HAVE_SNPRINTF
70 #include <schily/schily.h>
71 #define	snprintf	js_snprintf
72 #endif
73 
74 #ifndef	__CYGWIN__
75 /*
76  *	Cygwin uses a nonstandard:
77  *
78  *		__declspec(dllexport)
79  *	or
80  *		__declspec(dllimport)
81  *
82  *	that is in conflict with our standard definition.
83  */
84 extern int optind, opterr, optopt;
85 extern int optflg;
86 extern char *optarg;
87 #endif
88 
89 static char *parseshort __PR((const char *optstring, const int c));
90 #ifdef	DO_GETOPT_LONGONLY
91 static int  parseshortval __PR((const char *optstring, const char *cp));
92 #endif
93 static char *parselong  __PR((const char *optstring, const char *opt,
94 				char **longoptarg));
95 
96 /*
97  * Generalized error processing macro. The parameter i is a pointer to
98  * the failed option string. If it is NULL, the character in c is converted
99  * to a string and displayed instead. s is the error text.
100  *
101  * This could be / should be a static function if it is used more, but
102  * that would require moving the 'optstring[0]' test outside of the
103  * function.
104  */
105 #define	ERR(s, c, i)	if (opterr && !colon) { \
106 	char errbuf[256]; \
107 	char cbuf[2]; \
108 	cbuf[0] = c; \
109 	cbuf[1] = '\0'; \
110 	(void) snprintf(errbuf, sizeof (errbuf), s, argv[0], \
111 	    (i ? argv[i]+2 : cbuf)); \
112 	(void) write(2, errbuf, strlen(errbuf)); }
113 
114 /*
115  * _sp is required to keep state between successive calls to getopt() while
116  * extracting aggregated short-options (ie: -abcd). Hence, getopt() is not
117  * thread safe or reentrant, but it really doesn't matter.
118  *
119  * So, why isn't this "static" you ask?  Because the historical Bourne
120  * shell has actually latched on to this little piece of private data.
121  */
122 #if defined(HAVE_PRAGMA_WEAK) && defined(HAVE_LINK_WEAK)
123 /*
124  * The name of the variable on SVr4 was _sp, but we enhanced the features
125  * of getopt() to support long options and since recent versions of the
126  * Bourne Shell rely on long option support, the easiest way to signal the
127  * enhanced features of this getopt() implementation is to change the name
128  * of the state variable to opt_sp.
129  */
130 #pragma weak _sp = opt_sp	/* Backwards compatibility for old programs */
131 #endif
132 #define	_sp	opt_sp
133 int _sp = 1;
134 
135 /*
136  * Determine if the specified character (c) is present in the string
137  * (optstring) as a regular, single character option. If the option is found,
138  * return a pointer into optstring pointing at the short-option character,
139  * otherwise return null. The characters ':' and '(' are not allowed.
140  */
141 static char *
parseshort(optstring,c)142 parseshort(optstring, c)
143 	const char *optstring;
144 	const char c;
145 {
146 	char *cp = (char *)optstring;
147 
148 	if (c == ':' || c == '(')
149 		return (NULL);
150 	do {
151 #ifdef	DO_GETOPT_LONGONLY
152 		if (*cp == '?' && cp[1] >= '0' && cp[1] <= '9') {
153 			const char *ocp = cp;
154 
155 			do {
156 				cp++;
157 			} while (*cp >= '0' && *cp <= '9');
158 			if (*cp == '?')
159 				cp++;
160 			else
161 				cp = (char *)ocp;
162 		}
163 #endif
164 		if (*cp == c)
165 			return (cp);
166 		while (*cp == '(')
167 			while (*cp != '\0' && *cp != ')')
168 				cp++;
169 	} while (*cp++ != '\0');
170 	return (NULL);
171 }
172 
173 #ifdef	DO_GETOPT_LONGONLY
174 /*
175  * Parse strings in the form "?ddd?", where ddd represents a
176  * decimal numeric value. We are called with cp pointing to the
177  * rightmost '?'.
178  */
179 static int
parseshortval(optstring,cp)180 parseshortval(optstring, cp)
181 	const char *optstring;
182 	const char *cp;
183 {
184 	const char *p = cp;
185 
186 	while (p > optstring) {
187 		p--;
188 		if (*p < '0' || *p > '9')
189 			break;
190 	}
191 	if ((p + 1) == cp)	/* Did not find a number before '?' */
192 		return ('?');
193 	if (*p == '?') {
194 		int	i = 0;
195 
196 		while (*++p != '?') {
197 			if (i > 2000000000)	/* Avoid integer overflow */
198 				return (-1);
199 			i *= 10;
200 			i += *p - '0';
201 		}
202 		return (i);
203 	}
204 	return ('?');
205 }
206 #endif
207 
208 /*
209  * Determine if the specified string (opt) is present in the string
210  * (optstring) as a long-option contained within parenthesis. If the
211  * long-option specifies option-argument, return a pointer to it in
212  * longoptarg.  Otherwise set longoptarg to null. If the option is found,
213  * return a pointer into optstring pointing at the short-option character
214  * associated with this long-option; otherwise return null.
215  *
216  * optstring 	The entire optstring passed to getopt() by the caller
217  *
218  * opt		The long option read from the command line
219  *
220  * longoptarg	The argument to the option is returned in this parameter,
221  *              if an option exists. Possible return values in longoptarg
222  *              are:
223  *                  NULL		No argument was found
224  *		    empty string ("")	Argument was explicitly left empty
225  *					by the user (e.g., --option= )
226  *		    valid string	Argument found on the command line
227  *
228  * returns	Pointer to equivalent short-option in optstring, null
229  *              if option not found in optstring.
230  *
231  * ASSUMES: No parameters are NULL
232  *
233  */
234 static char *
parselong(optstring,opt,longoptarg)235 parselong(optstring, opt, longoptarg)
236 	const char *optstring;
237 	const char *opt;
238 	char **longoptarg;
239 {
240 	char	*cp;	/* ptr into optstring, beginning of one option spec. */
241 	char	*ip;	/* ptr into optstring, traverses every char */
242 	char	*op;	/* pointer into opt */
243 	int	match;	/* nonzero if opt is matching part of optstring */
244 
245 	cp = ip = (char *)optstring;
246 	do {
247 		if (*ip == '\0')
248 			break;
249 		if (*ip != '(' && *++ip == '\0')
250 			break;
251 		if (*ip == ':' && *++ip == '\0')
252 			break;
253 		while (*ip == '(') {
254 			if (*++ip == '\0')
255 				break;
256 			op = (char *)opt;
257 			match = 1;
258 			while (*ip != ')' && *ip != '\0' && *op != '\0')
259 				match = (*ip++ == *op++ && match);
260 			if (match && *ip == ')' &&
261 			    (*op == '\0' || *op == '=')) {
262 				if ((*op) == '=') {
263 					/* may be an empty string - OK */
264 					(*longoptarg) = op + 1;
265 				} else {
266 					(*longoptarg) = NULL;
267 				}
268 				return (cp);
269 			}
270 			if (*ip == ')' && *++ip == '\0')
271 				break;
272 		}
273 		cp = ip;
274 		/*
275 		 * Handle double-colon in optstring ("a::(longa)")
276 		 * The old getopt() accepts it and treats it as a
277 		 * required argument.
278 		 */
279 		while ((cp > optstring) && ((*cp) == ':')) {
280 			--cp;
281 		}
282 	} while (*cp != '\0');
283 	return (NULL);
284 } /* parselong() */
285 
286 /*
287  * External function entry point.
288  */
289 int
getopt(argc,argv,optstring)290 getopt(argc, argv, optstring)
291 	int 		argc;
292 	char *const	*argv;
293 	const char	*optstring;
294 {
295 	int	c;
296 	char	*cp;
297 	int	longopt;
298 	char	*longoptarg = NULL;
299 	int	l = 2;
300 #ifdef	DO_GETOPT_PLUS
301 	int	isplus;		/* The current option starts with a '+' char */
302 	int	plus = 0;	/* Found a '+' at the beginning of optstring */
303 #endif
304 	int	colon = 0;	/* Found a ':' at the beginning of optstring */
305 
306 	while ((c = *optstring) != '\0') {
307 		switch (c) {
308 
309 #ifdef	DO_GETOPT_PLUS
310 		case '+':
311 			plus = 1;
312 			optstring++;
313 			continue;
314 #endif
315 		case ':':
316 			colon = 1;
317 			optstring++;
318 			continue;
319 		case '(':
320 		default:
321 			break;
322 		}
323 		break;
324 	}
325 	/*
326 	 * Has the end of the options been encountered?  The following
327 	 * implements the SUS requirements:
328 	 *
329 	 * If, when getopt() is called:
330 	 *	argv[optind]	is a null pointer
331 	 *	*argv[optind]	is not the character '-'
332 	 *	argv[optind]	points to the string "-"
333 	 * getopt() returns -1 without changing optind. If
334 	 *	argv[optind]	points to the string "--"
335 	 * getopt() returns -1 after incrementing optind.
336 	 */
337 	if (optind >= argc || argv[optind] == NULL ||
338 #ifdef	DO_GETOPT_PLUS
339 	    ((!plus || argv[optind][0] != '+') && argv[optind][0] != '-') ||
340 #else
341 	    argv[optind][0] != '-' ||
342 #endif
343 	    argv[optind][1] == '\0') {
344 		return (-1);
345 	} else if (argv[optind][0] == '-' && argv[optind][1] == '-' &&
346 	    argv[optind][2] == '\0') {		/* "--" */
347 		optind++;
348 		return (-1);
349 	}
350 
351 	/*
352 	 * Getting this far indicates that an option has been encountered.
353 	 * Note that the syntax of optstring applies special meanings to
354 	 * the characters ':' and '(', so they are not permissible as
355 	 * option letters. A special meaning is also applied to the ')'
356 	 * character, but its meaning can be determined from context.
357 	 * Note that the specification only requires that the alnum
358 	 * characters be accepted.
359 	 *
360 	 * If the second character of the argument is a '-' this must be
361 	 * a long-option, otherwise it must be a short option.  Scan for
362 	 * the option in optstring by the appropriate algorithm. Either
363 	 * scan will return a pointer to the short-option character in
364 	 * optstring if the option is found and NULL otherwise.
365 	 *
366 	 * For an unrecognized long-option, optopt will equal 0, but
367 	 * since long-options can't aggregate the failing option can
368 	 * be identified by argv[optind-1].
369 	 */
370 	optopt = c = (unsigned char)argv[optind][_sp];
371 	optarg = NULL;
372 	longopt = (_sp == 1 && c == '-');
373 #ifdef	DO_GETOPT_PLUS
374 	isplus = plus && argv[optind][0] == '+';	/* actual option: +o */
375 	optflg = isplus ? GETOPT_PLUS_FL : 0;
376 	if (isplus)
377 		longopt = (_sp == 1 && c == '+');	/* check for ++xxx   */
378 #endif
379 #ifdef	DO_GETOPT_SDASH_LONG
380 	/*
381 	 * If optstring starts with "()", traditional UNIX "-long" options are
382 	 * allowed in addition to "--long".
383 	 */
384 	if (optstring[0] == '(') {
385 		if (!longopt && _sp == 1 &&
386 		    c != '\0' && argv[optind][2] != '\0') {
387 			longopt = l = 1;
388 		}
389 	}
390 tryshort:
391 #endif
392 	if (!(longopt ?
393 	    ((cp = parselong(optstring, argv[optind]+l, &longoptarg)) != NULL) :
394 	    ((cp = parseshort(optstring, c)) != NULL))) {
395 #ifdef	DO_GETOPT_SDASH_LONG
396 #ifdef	DO_GETOPT_PLUS
397 		if (longopt && optopt != (isplus ? '+' : '-')) {
398 #else
399 		if (longopt && optopt != '-') {
400 #endif
401 			/*
402 			 * In case of "-long" retry as combined short options.
403 			 */
404 			longopt = 0;
405 			goto tryshort;
406 		}
407 #endif
408 		/* LINTED: variable format specifier */
409 		ERR(_libc_gettext("%s: illegal option -- %s\n"),
410 		    c, (longopt ? optind : 0));
411 		/*
412 		 * Note: When the long option is unrecognized, optopt
413 		 * will be '-' here, which matches the specification.
414 		 */
415 		if (argv[optind][++_sp] == '\0' || longopt) {
416 			optind++;
417 			_sp = 1;
418 		}
419 		return ('?');
420 	}
421 	optopt = c = (unsigned char)*cp;
422 #ifdef	DO_GETOPT_LONGONLY
423 	if (*cp == '?') {
424 		optopt = c = parseshortval(optstring, cp);
425 		if (c < 0) {
426 			optopt = (unsigned char)argv[optind++][_sp];
427 			return ('?');
428 		}
429 	}
430 #endif
431 
432 	/*
433 	 * A valid option has been identified.  If it should have an
434 	 * option-argument, process that now.  SUS defines the setting
435 	 * of optarg as follows:
436 	 *
437 	 *   1.	If the option was the last character in the string pointed to
438 	 *	by an element of argv, then optarg contains the next element
439 	 *	of argv, and optind is incremented by 2. If the resulting
440 	 *	value of optind is not less than argc, this indicates a
441 	 *	missing option-argument, and getopt() returns an error
442 	 *	indication.
443 	 *
444 	 *   2.	Otherwise, optarg points to the string following the option
445 	 *	character in that element of argv, and optind is incremented
446 	 *	by 1.
447 	 *
448 	 * The second clause allows -abcd (where b requires an option-argument)
449 	 * to be interpreted as "-a -b cd".
450 	 *
451 	 * Note that the option-argument can legally be an empty string,
452 	 * such as:
453 	 * 	command --option= operand
454 	 * which explicitly sets the value of --option to nil
455 	 */
456 	if (*(cp + 1) == ':') {
457 		/* The option takes an argument */
458 		if (!longopt && argv[optind][_sp+1] != '\0') {
459 			optarg = &argv[optind++][_sp+1];
460 		} else if (longopt && longoptarg) {
461 			/*
462 			 * The option argument was explicitly set to
463 			 * the empty string on the command line (--option=)
464 			 */
465 			optind++;
466 			optarg = longoptarg;
467 		} else if (++optind >= argc) {
468 			/* LINTED: variable format specifier */
469 			ERR(_libc_gettext(
470 				"%s: option requires an argument -- %s\n"),
471 				c, (longopt ? optind - 1 : 0));
472 			_sp = 1;
473 			optarg = NULL;
474 			return (colon ? ':' : '?');
475 		} else
476 			optarg = argv[optind++];
477 		_sp = 1;
478 	} else {
479 		/* The option does NOT take an argument */
480 		if (longopt && (longoptarg != NULL)) {
481 		    /* User supplied an arg to an option that takes none */
482 		    /* LINTED: variable format specifier */
483 		    ERR(_libc_gettext(
484 			"%s: option doesn't take an argument -- %s\n"),
485 			0, (longopt ? optind : 0));
486 		    optarg = longoptarg = NULL;
487 		    c = '?';
488 		}
489 
490 		if (longopt || argv[optind][++_sp] == '\0') {
491 			_sp = 1;
492 			optind++;
493 		}
494 		optarg = NULL;
495 	}
496 	return (c);
497 } /* getopt() */
498