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