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