xref: /freebsd/lib/libc/stdlib/getopt_long.c (revision e858faa9bb929e040dfa2ee6f9e4806cec4d685d)
1 /*	$NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $	*/
2 /*	$FreeBSD$ */
3 
4 /*-
5  * Copyright (c) 2000 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Dieter Baron and Thomas Klausner.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *        This product includes software developed by the NetBSD
22  *        Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #include <sys/cdefs.h>
41 #if defined(LIBC_SCCS) && !defined(lint)
42 __RCSID("$NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $");
43 #endif /* LIBC_SCCS and not lint */
44 
45 #include <assert.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <getopt.h>
49 #include <stdlib.h>
50 #include <string.h>
51 
52 /* not part of the original file */
53 #ifndef _DIAGASSERT
54 #define _DIAGASSERT(X)
55 #endif
56 
57 #if HAVE_CONFIG_H && !HAVE_GETOPT_LONG && !HAVE_DECL_OPTIND
58 #define REPLACE_GETOPT
59 #endif
60 
61 #ifdef REPLACE_GETOPT
62 #ifdef __weak_alias
63 __weak_alias(getopt,_getopt)
64 #endif
65 int	opterr = 1;		/* if error message should be printed */
66 int	optind = 1;		/* index into parent argv vector */
67 int	optopt = '?';		/* character checked for validity */
68 int	optreset;		/* reset getopt */
69 char    *optarg;		/* argument associated with option */
70 #elif HAVE_CONFIG_H && !HAVE_DECL_OPTRESET
71 static int optreset;
72 #endif
73 
74 #ifdef __weak_alias
75 __weak_alias(getopt_long,_getopt_long)
76 #endif
77 
78 #if !HAVE_GETOPT_LONG
79 #define IGNORE_FIRST	(*options == '-' || *options == '+')
80 #define PRINT_ERROR	((opterr) && ((*options != ':') \
81 				      || (IGNORE_FIRST && options[1] != ':')))
82 #define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL)
83 #define PERMUTE         (!IS_POSIXLY_CORRECT && !IGNORE_FIRST)
84 /* XXX: GNU ignores PC if *options == '-' */
85 #define IN_ORDER        (!IS_POSIXLY_CORRECT && *options == '-')
86 
87 /* return values */
88 #define	BADCH	(int)'?'
89 #define	BADARG		((IGNORE_FIRST && options[1] == ':') \
90 			 || (*options == ':') ? (int)':' : (int)'?')
91 #define INORDER (int)1
92 
93 #define	EMSG	""
94 
95 static int getopt_internal(int, char * const *, const char *);
96 static int gcd(int, int);
97 static void permute_args(int, int, int, char * const *);
98 
99 static char *place = EMSG; /* option letter processing */
100 
101 /* XXX: set optreset to 1 rather than these two */
102 static int nonopt_start = -1; /* first non option argument (for permute) */
103 static int nonopt_end = -1;   /* first option after non options (for permute) */
104 
105 /* Error messages */
106 static const char recargchar[] = "option requires an argument -- %c";
107 static const char recargstring[] = "option requires an argument -- %s";
108 static const char ambig[] = "ambiguous option -- %.*s";
109 static const char noarg[] = "option doesn't take an argument -- %.*s";
110 static const char illoptchar[] = "unknown option -- %c";
111 static const char illoptstring[] = "unknown option -- %s";
112 
113 
114 /*
115  * Compute the greatest common divisor of a and b.
116  */
117 static int
118 gcd(a, b)
119 	int a;
120 	int b;
121 {
122 	int c;
123 
124 	c = a % b;
125 	while (c != 0) {
126 		a = b;
127 		b = c;
128 		c = a % b;
129 	}
130 
131 	return b;
132 }
133 
134 /*
135  * Exchange the block from nonopt_start to nonopt_end with the block
136  * from nonopt_end to opt_end (keeping the same order of arguments
137  * in each block).
138  */
139 static void
140 permute_args(panonopt_start, panonopt_end, opt_end, nargv)
141 	int panonopt_start;
142 	int panonopt_end;
143 	int opt_end;
144 	char * const *nargv;
145 {
146 	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
147 	char *swap;
148 
149 	_DIAGASSERT(nargv != NULL);
150 
151 	/*
152 	 * compute lengths of blocks and number and size of cycles
153 	 */
154 	nnonopts = panonopt_end - panonopt_start;
155 	nopts = opt_end - panonopt_end;
156 	ncycle = gcd(nnonopts, nopts);
157 	cyclelen = (opt_end - panonopt_start) / ncycle;
158 
159 	for (i = 0; i < ncycle; i++) {
160 		cstart = panonopt_end+i;
161 		pos = cstart;
162 		for (j = 0; j < cyclelen; j++) {
163 			if (pos >= panonopt_end)
164 				pos -= nnonopts;
165 			else
166 				pos += nopts;
167 			swap = nargv[pos];
168 			/* LINTED const cast */
169 			((char **) nargv)[pos] = nargv[cstart];
170 			/* LINTED const cast */
171 			((char **)nargv)[cstart] = swap;
172 		}
173 	}
174 }
175 
176 /*
177  * getopt_internal --
178  *	Parse argc/argv argument vector.  Called by user level routines.
179  *  Returns -2 if -- is found (can be long option or end of options marker).
180  */
181 static int
182 getopt_internal(nargc, nargv, options)
183 	int nargc;
184 	char * const *nargv;
185 	const char *options;
186 {
187 	char *oli;				/* option letter list index */
188 	int optchar;
189 
190 	_DIAGASSERT(nargv != NULL);
191 	_DIAGASSERT(options != NULL);
192 
193 	optarg = NULL;
194 
195 	/*
196 	 * XXX Some programs (like rsyncd) expect to be able to
197 	 * XXX re-initialize optind to 0 and have getopt_long(3)
198 	 * XXX properly function again.  Work around this braindamage.
199 	 */
200 	if (optind == 0)
201 		optind = 1;
202 
203 	if (optreset)
204 		nonopt_start = nonopt_end = -1;
205 start:
206 	if (optreset || !*place) {		/* update scanning pointer */
207 		optreset = 0;
208 		if (optind >= nargc) {          /* end of argument vector */
209 			place = EMSG;
210 			if (nonopt_end != -1) {
211 				/* do permutation, if we have to */
212 				permute_args(nonopt_start, nonopt_end,
213 				    optind, nargv);
214 				optind -= nonopt_end - nonopt_start;
215 			}
216 			else if (nonopt_start != -1) {
217 				/*
218 				 * If we skipped non-options, set optind
219 				 * to the first of them.
220 				 */
221 				optind = nonopt_start;
222 			}
223 			nonopt_start = nonopt_end = -1;
224 			return -1;
225 		}
226 		if ((*(place = nargv[optind]) != '-')
227 		    || (place[1] == '\0')) {    /* found non-option */
228 			place = EMSG;
229 			if (IN_ORDER) {
230 				/*
231 				 * GNU extension:
232 				 * return non-option as argument to option 1
233 				 */
234 				optarg = nargv[optind++];
235 				return INORDER;
236 			}
237 			if (!PERMUTE) {
238 				/*
239 				 * if no permutation wanted, stop parsing
240 				 * at first non-option
241 				 */
242 				return -1;
243 			}
244 			/* do permutation */
245 			if (nonopt_start == -1)
246 				nonopt_start = optind;
247 			else if (nonopt_end != -1) {
248 				permute_args(nonopt_start, nonopt_end,
249 				    optind, nargv);
250 				nonopt_start = optind -
251 				    (nonopt_end - nonopt_start);
252 				nonopt_end = -1;
253 			}
254 			optind++;
255 			/* process next argument */
256 			goto start;
257 		}
258 		if (nonopt_start != -1 && nonopt_end == -1)
259 			nonopt_end = optind;
260 		if (place[1] && *++place == '-') {	/* found "--" */
261 			place++;
262 			return -2;
263 		}
264 	}
265 	if ((optchar = (int)*place++) == (int)':' ||
266 	    (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
267 		/* option letter unknown or ':' */
268 		if (!*place)
269 			++optind;
270 		if (PRINT_ERROR)
271 			warnx(illoptchar, optchar);
272 		optopt = optchar;
273 		return BADCH;
274 	}
275 	if (optchar == 'W' && oli[1] == ';') {		/* -W long-option */
276 		/* XXX: what if no long options provided (called by getopt)? */
277 		if (*place)
278 			return -2;
279 
280 		if (++optind >= nargc) {	/* no arg */
281 			place = EMSG;
282 			if (PRINT_ERROR)
283 				warnx(recargchar, optchar);
284 			optopt = optchar;
285 			return BADARG;
286 		} else				/* white space */
287 			place = nargv[optind];
288 		/*
289 		 * Handle -W arg the same as --arg (which causes getopt to
290 		 * stop parsing).
291 		 */
292 		return -2;
293 	}
294 	if (*++oli != ':') {			/* doesn't take argument */
295 		if (!*place)
296 			++optind;
297 	} else {				/* takes (optional) argument */
298 		optarg = NULL;
299 		if (*place)			/* no white space */
300 			optarg = place;
301 		/* XXX: disable test for :: if PC? (GNU doesn't) */
302 		else if (oli[1] != ':') {	/* arg not optional */
303 			if (++optind >= nargc) {	/* no arg */
304 				place = EMSG;
305 				if (PRINT_ERROR)
306 					warnx(recargchar, optchar);
307 				optopt = optchar;
308 				return BADARG;
309 			} else
310 				optarg = nargv[optind];
311 		}
312 		place = EMSG;
313 		++optind;
314 	}
315 	/* dump back option letter */
316 	return optchar;
317 }
318 
319 #ifdef REPLACE_GETOPT
320 /*
321  * getopt --
322  *	Parse argc/argv argument vector.
323  *
324  * [eventually this will replace the real getopt]
325  */
326 int
327 getopt(nargc, nargv, options)
328 	int nargc;
329 	char * const *nargv;
330 	const char *options;
331 {
332 	int retval;
333 
334 	_DIAGASSERT(nargv != NULL);
335 	_DIAGASSERT(options != NULL);
336 
337 	if ((retval = getopt_internal(nargc, nargv, options)) == -2) {
338 		++optind;
339 		/*
340 		 * We found an option (--), so if we skipped non-options,
341 		 * we have to permute.
342 		 */
343 		if (nonopt_end != -1) {
344 			permute_args(nonopt_start, nonopt_end, optind,
345 				       nargv);
346 			optind -= nonopt_end - nonopt_start;
347 		}
348 		nonopt_start = nonopt_end = -1;
349 		retval = -1;
350 	}
351 	return retval;
352 }
353 #endif
354 
355 /*
356  * getopt_long --
357  *	Parse argc/argv argument vector.
358  */
359 int
360 getopt_long(nargc, nargv, options, long_options, idx)
361 	int nargc;
362 	char * const *nargv;
363 	const char *options;
364 	const struct option *long_options;
365 	int *idx;
366 {
367 	int retval;
368 
369 	_DIAGASSERT(nargv != NULL);
370 	_DIAGASSERT(options != NULL);
371 	_DIAGASSERT(long_options != NULL);
372 	/* idx may be NULL */
373 
374 	if ((retval = getopt_internal(nargc, nargv, options)) == -2) {
375 		char *current_argv, *has_equal;
376 		size_t current_argv_len;
377 		int i, match;
378 
379 		current_argv = place;
380 		match = -1;
381 
382 		optind++;
383 		place = EMSG;
384 
385 		if (*current_argv == '\0') {		/* found "--" */
386 			/*
387 			 * We found an option (--), so if we skipped
388 			 * non-options, we have to permute.
389 			 */
390 			if (nonopt_end != -1) {
391 				permute_args(nonopt_start, nonopt_end,
392 				    optind, nargv);
393 				optind -= nonopt_end - nonopt_start;
394 			}
395 			nonopt_start = nonopt_end = -1;
396 			return -1;
397 		}
398 		if ((has_equal = strchr(current_argv, '=')) != NULL) {
399 			/* argument found (--option=arg) */
400 			current_argv_len = has_equal - current_argv;
401 			has_equal++;
402 		} else
403 			current_argv_len = strlen(current_argv);
404 
405 		for (i = 0; long_options[i].name; i++) {
406 			/* find matching long option */
407 			if (strncmp(current_argv, long_options[i].name,
408 			    current_argv_len))
409 				continue;
410 
411 			if (strlen(long_options[i].name) ==
412 			    (unsigned)current_argv_len) {
413 				/* exact match */
414 				match = i;
415 				break;
416 			}
417 			if (match == -1)		/* partial match */
418 				match = i;
419 			else {
420 				/* ambiguous abbreviation */
421 				if (PRINT_ERROR)
422 					warnx(ambig, (int)current_argv_len,
423 					     current_argv);
424 				optopt = 0;
425 				return BADCH;
426 			}
427 		}
428 		if (match != -1) {			/* option found */
429 		        if (long_options[match].has_arg == no_argument
430 			    && has_equal) {
431 				if (PRINT_ERROR)
432 					warnx(noarg, (int)current_argv_len,
433 					     current_argv);
434 				/*
435 				 * XXX: GNU sets optopt to val regardless of
436 				 * flag
437 				 */
438 				if (long_options[match].flag == NULL)
439 					optopt = long_options[match].val;
440 				else
441 					optopt = 0;
442 				return BADARG;
443 			}
444 			if (long_options[match].has_arg == required_argument ||
445 			    long_options[match].has_arg == optional_argument) {
446 				if (has_equal)
447 					optarg = has_equal;
448 				else if (long_options[match].has_arg ==
449 				    required_argument) {
450 					/*
451 					 * optional argument doesn't use
452 					 * next nargv
453 					 */
454 					optarg = nargv[optind++];
455 				}
456 			}
457 			if ((long_options[match].has_arg == required_argument)
458 			    && (optarg == NULL)) {
459 				/*
460 				 * Missing argument; leading ':'
461 				 * indicates no error should be generated
462 				 */
463 				if (PRINT_ERROR)
464 					warnx(recargstring, current_argv);
465 				/*
466 				 * XXX: GNU sets optopt to val regardless
467 				 * of flag
468 				 */
469 				if (long_options[match].flag == NULL)
470 					optopt = long_options[match].val;
471 				else
472 					optopt = 0;
473 				--optind;
474 				return BADARG;
475 			}
476 		} else {			/* unknown option */
477 			if (PRINT_ERROR)
478 				warnx(illoptstring, current_argv);
479 			optopt = 0;
480 			return BADCH;
481 		}
482 		if (long_options[match].flag) {
483 			*long_options[match].flag = long_options[match].val;
484 			retval = 0;
485 		} else
486 			retval = long_options[match].val;
487 		if (idx)
488 			*idx = match;
489 	}
490 	return retval;
491 }
492 #endif /* !GETOPT_LONG */
493