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