xref: /illumos-gate/usr/src/lib/libc/port/gen/getopt.c (revision 7c478bd9)
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*	Copyright (c) 1988 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
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 "synonyms.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 		/* LINTED: variable format specifier */
234 		ERR(_libc_gettext("%s: illegal option -- %s\n"),
235 		    c, (longopt ? optind : 0));
236 		/*
237 		 * Note: When the long option is unrecognized, optopt
238 		 * will be '-' here, which matches the specification.
239 		 */
240 		if (argv[optind][++_sp] == '\0' || longopt) {
241 			optind++;
242 			_sp = 1;
243 		}
244 		return ('?');
245 	}
246 	optopt = c = *cp;
247 
248 	/*
249 	 * A valid option has been identified.  If it should have an
250 	 * option-argument, process that now.  SUS defines the setting
251 	 * of optarg as follows:
252 	 *
253 	 *   1.	If the option was the last character in the string pointed to
254 	 *	by an element of argv, then optarg contains the next element
255 	 *	of argv, and optind is incremented by 2. If the resulting
256 	 *	value of optind is not less than argc, this indicates a
257 	 *	missing option-argument, and getopt() returns an error
258 	 *	indication.
259 	 *
260 	 *   2.	Otherwise, optarg points to the string following the option
261 	 *	character in that element of argv, and optind is incremented
262 	 *	by 1.
263 	 *
264 	 * The second clause allows -abcd (where b requires an option-argument)
265 	 * to be interpreted as "-a -b cd".
266 	 *
267 	 * Note that the option-argument can legally be an empty string,
268 	 * such as:
269 	 * 	command --option= operand
270 	 * which explicitly sets the value of --option to nil
271 	 */
272 	if (*(cp + 1) == ':') {
273 		/* The option takes an argument */
274 		if (!longopt && argv[optind][_sp+1] != '\0') {
275 			optarg = &argv[optind++][_sp+1];
276 		} else if (longopt && longoptarg) {
277 			/*
278 			 * The option argument was explicitly set to
279 			 * the empty string on the command line (--option=)
280 			 */
281 			optind++;
282 			optarg = longoptarg;
283 		} else if (++optind >= argc) {
284 			/* LINTED: variable format specifier */
285 			ERR(_libc_gettext("%s: option requires an argument" \
286 			    " -- %s\n"), c, (longopt ? optind - 1 : 0));
287 			_sp = 1;
288 			optarg = NULL;
289 			return (optstring[0] == ':' ? ':' : '?');
290 		} else
291 			optarg = argv[optind++];
292 		_sp = 1;
293 	} else {
294 		/* The option does NOT take an argument */
295 		if (longopt && (longoptarg != NULL)) {
296 		    /* User supplied an arg to an option that takes none */
297 		    /* LINTED: variable format specifier */
298 		    ERR(_libc_gettext(
299 			"%s: option doesn't take an argument -- %s\n"),
300 			0, (longopt ? optind : 0));
301 		    optarg = longoptarg = NULL;
302 		    c = '?';
303 		}
304 
305 		if (longopt || argv[optind][++_sp] == '\0') {
306 			_sp = 1;
307 			optind++;
308 		}
309 		optarg = NULL;
310 	}
311 	return (c);
312 } /* getopt() */
313