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