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