xref: /illumos-gate/usr/src/lib/libc/port/gen/getopt.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 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * See getopt(3C) and SUS/XPG getopt() for function definition and
32  * requirements.
33  *
34  * This actual implementation is a bit looser than the specification
35  * as it allows any character other than ':' and '(' to be used as
36  * a short option character - The specification only guarantees the
37  * alnum characters ([a-z][A-Z][0-9]).
38  */
39 
40 #pragma weak _getopt = getopt
41 
42 #include "lint.h"
43 #include "_libc_gettext.h"
44 
45 #include <unistd.h>
46 #include <string.h>
47 #include <stdio.h>
48 
49 /*
50  * Generalized error processing macro. The parameter i is a pointer to
51  * the failed option string. If it is NULL, the character in c is converted
52  * to a string and displayed instead. s is the error text.
53  *
54  * This could be / should be a static function if it is used more, but
55  * that would require moving the 'optstring[0]' test outside of the
56  * function.
57  */
58 #define	ERR(s, c, i)	if (opterr && optstring[0] != ':') { \
59 	char errbuf[256]; \
60 	char cbuf[2]; \
61 	cbuf[0] = c; \
62 	cbuf[1] = '\0'; \
63 	(void) snprintf(errbuf, sizeof (errbuf), s, argv[0], \
64 	    (i ? argv[i]+2 : cbuf)); \
65 	(void) write(2, errbuf, strlen(errbuf)); }
66 
67 /*
68  * _sp is required to keep state between successive calls to getopt() while
69  * extracting aggregated short-options (ie: -abcd). Hence, getopt() is not
70  * thread safe or reentrant, but it really doesn't matter.
71  *
72  * So, why isn't this "static" you ask?  Because the historical Bourne
73  * shell has actually latched on to this little piece of private data.
74  */
75 int _sp = 1;
76 
77 /*
78  * Determine if the specified character (c) is present in the string
79  * (optstring) as a regular, single character option. If the option is found,
80  * return a pointer into optstring pointing at the short-option character,
81  * otherwise return null. The characters ':' and '(' are not allowed.
82  */
83 static char *
84 parseshort(const char *optstring, const char c)
85 {
86 	char *cp = (char *)optstring;
87 
88 	if (c == ':' || c == '(')
89 		return (NULL);
90 	do {
91 		if (*cp == c)
92 			return (cp);
93 		while (*cp == '(')
94 			while (*cp != '\0' && *cp != ')')
95 				cp++;
96 	} while (*cp++ != '\0');
97 	return (NULL);
98 }
99 
100 /*
101  * Determine if the specified string (opt) is present in the string
102  * (optstring) as a long-option contained within parenthesis. If the
103  * long-option specifies option-argument, return a pointer to it in
104  * longoptarg.  Otherwise set longoptarg to null. If the option is found,
105  * return a pointer into optstring pointing at the short-option character
106  * associated with this long-option; otherwise return null.
107  *
108  * optstring 	The entire optstring passed to getopt() by the caller
109  *
110  * opt		The long option read from the command line
111  *
112  * longoptarg	The argument to the option is returned in this parameter,
113  *              if an option exists. Possible return values in longoptarg
114  *              are:
115  *                  NULL		No argument was found
116  *		    empty string ("")	Argument was explicitly left empty
117  *					by the user (e.g., --option= )
118  *		    valid string	Argument found on the command line
119  *
120  * returns	Pointer to equivalent short-option in optstring, null
121  *              if option not found in optstring.
122  *
123  * ASSUMES: No parameters are NULL
124  *
125  */
126 static char *
127 parselong(const char *optstring, const char *opt, char **longoptarg)
128 {
129 	char	*cp;	/* ptr into optstring, beginning of one option spec. */
130 	char	*ip;	/* ptr into optstring, traverses every char */
131 	char	*op;	/* pointer into opt */
132 	int	match;	/* nonzero if opt is matching part of optstring */
133 
134 	cp = ip = (char *)optstring;
135 	do {
136 		if (*ip != '(' && *++ip == '\0')
137 			break;
138 		if (*ip == ':' && *++ip == '\0')
139 			break;
140 		while (*ip == '(') {
141 			if (*++ip == '\0')
142 				break;
143 			op = (char *)opt;
144 			match = 1;
145 			while (*ip != ')' && *ip != '\0' && *op != '\0')
146 				match = (*ip++ == *op++ && match);
147 			if (match && *ip == ')' &&
148 			    (*op == '\0' || *op == '=')) {
149 				if ((*op) == '=') {
150 					/* may be an empty string - OK */
151 					(*longoptarg) = op + 1;
152 				} else {
153 					(*longoptarg) = NULL;
154 				}
155 				return (cp);
156 			}
157 			if (*ip == ')' && *++ip == '\0')
158 				break;
159 		}
160 		cp = ip;
161 		/*
162 		 * Handle double-colon in optstring ("a::(longa)")
163 		 * The old getopt() accepts it and treats it as a
164 		 * required argument.
165 		 */
166 		while ((cp > optstring) && ((*cp) == ':')) {
167 			--cp;
168 		}
169 	} while (*cp != '\0');
170 	return (NULL);
171 } /* parselong() */
172 
173 /*
174  * External function entry point.
175  */
176 int
177 getopt(int argc, char *const *argv, const char *optstring)
178 {
179 	char	c;
180 	char	*cp;
181 	int	longopt;
182 	char	*longoptarg;
183 
184 	/*
185 	 * Has the end of the options been encountered?  The following
186 	 * implements the SUS requirements:
187 	 *
188 	 * If, when getopt() is called:
189 	 *	argv[optind]	is a null pointer
190 	 *	*argv[optind]	is not the character '-'
191 	 *	argv[optind]	points to the string "-"
192 	 * getopt() returns -1 without changing optind. If
193 	 *	argv[optind]	points to the string "--"
194 	 * getopt() returns -1 after incrementing optind.
195 	 */
196 	if (_sp == 1) {
197 		if (optind >= argc || argv[optind][0] != '-' ||
198 		    argv[optind] == NULL || argv[optind][1] == '\0')
199 			return (EOF);
200 		else if (strcmp(argv[optind], "--") == 0) {
201 			optind++;
202 			return (EOF);
203 		}
204 	}
205 
206 	/*
207 	 * Getting this far indicates that an option has been encountered.
208 	 * Note that the syntax of optstring applies special meanings to
209 	 * the characters ':' and '(', so they are not permissible as
210 	 * option letters. A special meaning is also applied to the ')'
211 	 * character, but its meaning can be determined from context.
212 	 * Note that the specification only requires that the alnum
213 	 * characters be accepted.
214 	 *
215 	 * If the second character of the argument is a '-' this must be
216 	 * a long-option, otherwise it must be a short option.  Scan for
217 	 * the option in optstring by the appropriate algorithm. Either
218 	 * scan will return a pointer to the short-option character in
219 	 * optstring if the option is found and NULL otherwise.
220 	 *
221 	 * For an unrecognized long-option, optopt will equal 0, but
222 	 * since long-options can't aggregate the failing option can
223 	 * be identified by argv[optind-1].
224 	 */
225 	optopt = c = (unsigned char)argv[optind][_sp];
226 	optarg = NULL;
227 	longopt = (_sp == 1 && c == '-');
228 	if (!(longopt ?
229 	    ((cp = parselong(optstring, argv[optind]+2, &longoptarg)) != NULL) :
230 	    ((cp = parseshort(optstring, c)) != NULL))) {
231 		ERR(_libc_gettext("%s: illegal option -- %s\n"),
232 		    c, (longopt ? optind : 0));
233 		/*
234 		 * Note: When the long option is unrecognized, optopt
235 		 * will be '-' here, which matches the specification.
236 		 */
237 		if (argv[optind][++_sp] == '\0' || longopt) {
238 			optind++;
239 			_sp = 1;
240 		}
241 		return ('?');
242 	}
243 	optopt = c = *cp;
244 
245 	/*
246 	 * A valid option has been identified.  If it should have an
247 	 * option-argument, process that now.  SUS defines the setting
248 	 * of optarg as follows:
249 	 *
250 	 *   1.	If the option was the last character in the string pointed to
251 	 *	by an element of argv, then optarg contains the next element
252 	 *	of argv, and optind is incremented by 2. If the resulting
253 	 *	value of optind is not less than argc, this indicates a
254 	 *	missing option-argument, and getopt() returns an error
255 	 *	indication.
256 	 *
257 	 *   2.	Otherwise, optarg points to the string following the option
258 	 *	character in that element of argv, and optind is incremented
259 	 *	by 1.
260 	 *
261 	 * The second clause allows -abcd (where b requires an option-argument)
262 	 * to be interpreted as "-a -b cd".
263 	 *
264 	 * Note that the option-argument can legally be an empty string,
265 	 * such as:
266 	 * 	command --option= operand
267 	 * which explicitly sets the value of --option to nil
268 	 */
269 	if (*(cp + 1) == ':') {
270 		/* The option takes an argument */
271 		if (!longopt && argv[optind][_sp+1] != '\0') {
272 			optarg = &argv[optind++][_sp+1];
273 		} else if (longopt && longoptarg) {
274 			/*
275 			 * The option argument was explicitly set to
276 			 * the empty string on the command line (--option=)
277 			 */
278 			optind++;
279 			optarg = longoptarg;
280 		} else if (++optind >= argc) {
281 			ERR(_libc_gettext("%s: option requires an argument" \
282 			    " -- %s\n"), c, (longopt ? optind - 1 : 0));
283 			_sp = 1;
284 			optarg = NULL;
285 			return (optstring[0] == ':' ? ':' : '?');
286 		} else
287 			optarg = argv[optind++];
288 		_sp = 1;
289 	} else {
290 		/* The option does NOT take an argument */
291 		if (longopt && (longoptarg != NULL)) {
292 			/* User supplied an arg to an option that takes none */
293 			ERR(_libc_gettext(
294 			    "%s: option doesn't take an argument -- %s\n"),
295 			    0, (longopt ? optind : 0));
296 			optarg = longoptarg = NULL;
297 			c = '?';
298 		}
299 
300 		if (longopt || argv[optind][++_sp] == '\0') {
301 			_sp = 1;
302 			optind++;
303 		}
304 		optarg = NULL;
305 	}
306 	return (c);
307 } /* getopt() */
308