xref: /dflybsd-src/lib/libc/stdlib/getopt_long.c (revision 7d1ab061891a94bc40e4954c2851bb1691ea3d54)
1 /*	$NetBSD: getopt_long.c,v 1.16 2003/10/27 00:12:42 lukem Exp $	*/
2 /*	$DragonFly: src/lib/libc/stdlib/getopt_long.c,v 1.9 2005/02/28 20:41:15 joerg Exp $ */
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 
42 #include <err.h>
43 #include <errno.h>
44 #include <getopt.h>
45 #include <stdlib.h>
46 #include <string.h>
47 
48 /* XXX BOOTSTRAPPING */
49 #ifndef	__DECONST
50 #define	__DECONST(type, var)	((type)(uintptr_t)(const void *)(var))
51 #endif
52 
53 #ifdef REPLACE_GETOPT
54 int	opterr = 1;		/* if error message should be printed */
55 int	optind = 1;		/* index into parent argv vector */
56 int	optopt = '?';		/* character checked for validity */
57 int	optreset;		/* reset getopt */
58 char    *optarg;		/* argument associated with option */
59 #endif
60 
61 #define IGNORE_FIRST	(*options == '-' || *options == '+')
62 #define PRINT_ERROR	((opterr) && ((*options != ':') \
63 				      || (IGNORE_FIRST && options[1] != ':')))
64 #define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL)
65 #define PERMUTE         (!IS_POSIXLY_CORRECT && !IGNORE_FIRST)
66 /* XXX: GNU ignores PC if *options == '-' */
67 #define IN_ORDER        (!IS_POSIXLY_CORRECT && *options == '-')
68 
69 /* return values */
70 #define	BADCH	(int)'?'
71 #define	BADARG		((IGNORE_FIRST && options[1] == ':') \
72 			 || (*options == ':') ? (int)':' : (int)'?')
73 #define INORDER (int)1
74 
75 static int getopt_internal(int, char * const *, const char *, int);
76 static int getopt_internal_short(int, char * const *, const char *, int);
77 static int getopt_long_internal(int, char * const *, const char *,
78 				const struct option *, int *, int);
79 static int gcd(int, int);
80 static void permute_args(int, int, int, char * const *);
81 
82 static char EMSG[] = {0};
83 static char *place = EMSG; /* option letter processing */
84 
85 /* XXX: set optreset to 1 rather than these two */
86 static int nonopt_start = -1; /* first non option argument (for permute) */
87 static int nonopt_end = -1;   /* first option after non options (for permute) */
88 
89 /* Error messages */
90 static const char recargchar[] = "option requires an argument -- %c";
91 static const char recargstring[] = "option requires an argument -- %s";
92 static const char ambig[] = "ambiguous option -- %.*s";
93 static const char noarg[] = "option doesn't take an argument -- %.*s";
94 static const char illoptchar[] = "unknown option -- %c";
95 static const char illoptstring[] = "unknown option -- %s";
96 
97 
98 /*
99  * Compute the greatest common divisor of a and b.
100  */
101 static int
102 gcd(int a, int b)
103 {
104 	int c;
105 
106 	c = a % b;
107 	while (c != 0) {
108 		a = b;
109 		b = c;
110 		c = a % b;
111 	}
112 
113 	return b;
114 }
115 
116 /*
117  * Exchange the block from nonopt_start to nonopt_end with the block
118  * from nonopt_end to opt_end (keeping the same order of arguments
119  * in each block).
120  */
121 static void
122 permute_args(int panonopt_start, int panonopt_end, int opt_end,
123 	     char * const *nargv)
124 {
125 	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
126 	char *swap;
127 
128 	/*
129 	 * compute lengths of blocks and number and size of cycles
130 	 */
131 	nnonopts = panonopt_end - panonopt_start;
132 	nopts = opt_end - panonopt_end;
133 	ncycle = gcd(nnonopts, nopts);
134 	cyclelen = (opt_end - panonopt_start) / ncycle;
135 
136 	for (i = 0; i < ncycle; i++) {
137 		cstart = panonopt_end+i;
138 		pos = cstart;
139 		for (j = 0; j < cyclelen; j++) {
140 			if (pos >= panonopt_end)
141 				pos -= nnonopts;
142 			else
143 				pos += nopts;
144 			swap = nargv[pos];
145 			/* LINTED const cast */
146 			(__DECONST(char **, nargv))[pos] = nargv[cstart];
147 			/* LINTED const cast */
148 			(__DECONST(char **, nargv))[cstart] = swap;
149 		}
150 	}
151 }
152 
153 /*
154  * getopt_internal --
155  *	Parse argc/argv argument vector.  Called by user level routines.
156  *  Returns -2 if -- is found (can be long option or end of options marker).
157  */
158 static int
159 getopt_internal(int nargc, char * const *nargv, const char *options,
160 		int long_support)
161 {
162 	optarg = NULL;
163 
164 	/*
165 	 * XXX Some programs (like rsyncd) expect to be able to
166 	 * XXX re-initialize optind to 0 and have getopt_long(3)
167 	 * XXX properly function again.  Work around this braindamage.
168 	 */
169 	if (optind == 0)
170 		optind = 1;
171 
172 	if (optreset)
173 		nonopt_start = nonopt_end = -1;
174 start:
175 	if (optreset || !*place) {		/* update scanning pointer */
176 		optreset = 0;
177 		if (optind >= nargc) {          /* end of argument vector */
178 			place = EMSG;
179 			if (nonopt_end != -1) {
180 				/* do permutation, if we have to */
181 				permute_args(nonopt_start, nonopt_end,
182 				    optind, nargv);
183 				optind -= nonopt_end - nonopt_start;
184 			}
185 			else if (nonopt_start != -1) {
186 				/*
187 				 * If we skipped non-options, set optind
188 				 * to the first of them.
189 				 */
190 				optind = nonopt_start;
191 			}
192 			nonopt_start = nonopt_end = -1;
193 			return -1;
194 		}
195 		if ((*(place = nargv[optind]) != '-')
196 		    || (place[1] == '\0')) {    /* found non-option */
197 			place = EMSG;
198 			if (IN_ORDER) {
199 				/*
200 				 * GNU extension:
201 				 * return non-option as argument to option 1
202 				 */
203 				optarg = nargv[optind++];
204 				return INORDER;
205 			}
206 			if (!PERMUTE) {
207 				/*
208 				 * if no permutation wanted, stop parsing
209 				 * at first non-option
210 				 */
211 				return -1;
212 			}
213 			/* do permutation */
214 			if (nonopt_start == -1)
215 				nonopt_start = optind;
216 			else if (nonopt_end != -1) {
217 				permute_args(nonopt_start, nonopt_end,
218 				    optind, nargv);
219 				nonopt_start = optind -
220 				    (nonopt_end - nonopt_start);
221 				nonopt_end = -1;
222 			}
223 			optind++;
224 			/* process next argument */
225 			goto start;
226 		}
227 		if (nonopt_start != -1 && nonopt_end == -1)
228 			nonopt_end = optind;
229 		if (place[1] && *++place == '-') {	/* found "--" */
230 			if (place[1] == '\0') {
231 				++optind;
232 				/*
233 				 * We found an option (--), so if we skipped
234 				 * non-options, we have to permute.
235 				 */
236 				if (nonopt_end != -1) {
237 					permute_args(nonopt_start, nonopt_end,
238 						     optind, nargv);
239 					optind -= nonopt_end - nonopt_start;
240 				}
241 				nonopt_start = nonopt_end = -1;
242 				return -1;
243 			} else if (long_support) {
244 				place++;
245 				return -2;
246 			}
247 		}
248 	}
249 	if (long_support == 2 && (place[1] || strchr(options, *place) == NULL))
250 		return -3;
251 	return getopt_internal_short(nargc, nargv, options, long_support);
252 }
253 
254 static int
255 getopt_internal_short(int nargc, char * const *nargv, const char *options,
256 		      int long_support)
257 {
258 	const char *oli;			/* option letter list index */
259 	int optchar;
260 
261 	if ((optchar = (int)*place++) == (int)':' ||
262 	    (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
263 		/* option letter unknown or ':' */
264 		if (PRINT_ERROR) {
265 			if (long_support == 2)
266 				warnx(illoptstring, --place);
267 			else
268 				warnx(illoptchar, optchar);
269 		}
270 		if (long_support == 2)
271 			place = EMSG;
272 		if (*place)
273 			++optind;
274 		optopt = optchar;
275 		return BADCH;
276 	}
277 	if (long_support && optchar == 'W' && oli[1] == ';') {
278 		/* -W long-option */
279 		if (*place)
280 			return -2;
281 
282 		if (++optind >= nargc) {	/* no arg */
283 			place = EMSG;
284 			if (PRINT_ERROR)
285 				warnx(recargchar, optchar);
286 			optopt = optchar;
287 			return BADARG;
288 		} else				/* white space */
289 			place = nargv[optind];
290 		/*
291 		 * Handle -W arg the same as --arg (which causes getopt to
292 		 * stop parsing).
293 		 */
294 		return -2;
295 	}
296 	if (*++oli != ':') {			/* doesn't take argument */
297 		if (!*place)
298 			++optind;
299 	} else {				/* takes (optional) argument */
300 		optarg = NULL;
301 		if (*place)			/* no white space */
302 			optarg = place;
303 		/* XXX: disable test for :: if PC? (GNU doesn't) */
304 		else if (oli[1] != ':') {	/* arg not optional */
305 			if (++optind >= nargc) {	/* no arg */
306 				place = EMSG;
307 				if (PRINT_ERROR)
308 					warnx(recargchar, optchar);
309 				optopt = optchar;
310 				return BADARG;
311 			} else
312 				optarg = nargv[optind];
313 		}
314 		place = EMSG;
315 		++optind;
316 	}
317 	/* dump back option letter */
318 	return optchar;
319 }
320 
321 static int
322 getopt_long_internal(int nargc, char * const *nargv, const char *options,
323 		     const struct option *long_options, int *idx, int long_only)
324 {
325 	int retval;
326 
327 	/* idx may be NULL */
328 
329 	retval = getopt_internal(nargc, nargv, options, long_only ? 2 : 1);
330 recheck:
331 	if (retval == -2 || retval == -3) {
332 		char *current_argv, *has_equal;
333 		size_t current_argv_len;
334 		int i, match;
335 
336 		current_argv = place;
337 		match = -1;
338 
339 		optind++;
340 		place = EMSG;
341 
342 		if ((has_equal = strchr(current_argv, '=')) != NULL) {
343 			/* argument found (--option=arg) */
344 			current_argv_len = has_equal - current_argv;
345 			has_equal++;
346 		} else
347 			current_argv_len = strlen(current_argv);
348 
349 		for (i = 0; long_options[i].name; i++) {
350 			/* find matching long option */
351 			if (strncmp(current_argv, long_options[i].name,
352 			    current_argv_len))
353 				continue;
354 
355 			if (strlen(long_options[i].name) ==
356 			    (unsigned)current_argv_len) {
357 				/* exact match */
358 				match = i;
359 				break;
360 			}
361 			if (match == -1)		/* partial match */
362 				match = i;
363 			else {
364 				/* ambiguous abbreviation */
365 				if (PRINT_ERROR)
366 					warnx(ambig, (int)current_argv_len,
367 					     current_argv);
368 				optopt = 0;
369 				return BADCH;
370 			}
371 		}
372 		if (match != -1) {			/* option found */
373 		        if (long_options[match].has_arg == no_argument
374 			    && has_equal) {
375 				if (PRINT_ERROR)
376 					warnx(noarg, (int)current_argv_len,
377 					     current_argv);
378 				/*
379 				 * XXX: GNU sets optopt to val regardless of
380 				 * flag
381 				 */
382 				if (long_options[match].flag == NULL)
383 					optopt = long_options[match].val;
384 				else
385 					optopt = 0;
386 				return BADARG;
387 			}
388 			if (long_options[match].has_arg == required_argument ||
389 			    long_options[match].has_arg == optional_argument) {
390 				if (has_equal)
391 					optarg = has_equal;
392 				else if (long_options[match].has_arg ==
393 				    required_argument) {
394 					/*
395 					 * optional argument doesn't use
396 					 * next nargv
397 					 */
398 					optarg = nargv[optind++];
399 				}
400 			}
401 			if ((long_options[match].has_arg == required_argument)
402 			    && (optarg == NULL)) {
403 				/*
404 				 * Missing argument; leading ':'
405 				 * indicates no error should be generated
406 				 */
407 				if (PRINT_ERROR)
408 					warnx(recargstring, current_argv);
409 				/*
410 				 * XXX: GNU sets optopt to val regardless
411 				 * of flag
412 				 */
413 				if (long_options[match].flag == NULL)
414 					optopt = long_options[match].val;
415 				else
416 					optopt = 0;
417 				--optind;
418 				return BADARG;
419 			}
420 		} else if (retval == -3) {
421 			--optind;
422 			place = current_argv;
423 			retval = getopt_internal_short(nargc, nargv,
424 			    options, long_only ? 2 : 1);
425 			goto recheck;
426 		} else {			/* unknown option */
427 			if (PRINT_ERROR)
428 				warnx(illoptstring, current_argv);
429 			optopt = 0;
430 			return BADCH;
431 		}
432 		if (long_options[match].flag) {
433 			*long_options[match].flag = long_options[match].val;
434 			retval = 0;
435 		} else
436 			retval = long_options[match].val;
437 		if (idx)
438 			*idx = match;
439 	}
440 	return retval;
441 }
442 
443 #ifdef REPLACE_GETOPT
444 /*
445  * getopt --
446  *	Parse argc/argv argument vector.
447  *
448  * [eventually this will replace the real getopt]
449  */
450 int
451 getopt(int nargc, char * const *nargv, const char *options)
452 {
453 	return getopt_internal(nargc, nargv, options, 0);
454 }
455 #endif
456 
457 /*
458  * getopt_long --
459  *	Parse argc/argv argument vector.
460  */
461 
462 int
463 getopt_long(int nargc, char * const *nargv, const char *options,
464 	    const struct option *long_options, int *idx)
465 {
466 	return getopt_long_internal(nargc, nargv, options, long_options,
467 				    idx, 0);
468 }
469 
470 /*
471  * getopt_long_only --
472  *	Parse argc/argv argument vector.
473  *	Prefers long options over short options for single dash arguments.
474  */
475 
476 int getopt_long_only(int nargc, char * const *nargv, const char *options,
477 	    const struct option *long_options, int *idx)
478 {
479 	return getopt_long_internal(nargc, nargv, options, long_options,
480 				    idx, 1);
481 }
482