1/*	$NetBSD: getopt_long.c,v 1.1.1.1.10.1 2012/03/07 23:18:29 riz 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
38FILE_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.1.10.1 2012/03/07 23:18:29 riz 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
69int	opterr = 1;		/* if error message should be printed */
70int	optind = 1;		/* index into parent argv vector */
71int	optopt = '?';		/* character checked for validity */
72int	optreset;		/* reset getopt */
73char    *optarg;		/* argument associated with option */
74#elif HAVE_NBTOOL_CONFIG_H && !HAVE_DECL_OPTRESET
75static 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
98static int getopt_internal(int, char **, const char *);
99static int gcd(int, int);
100static void permute_args(int, int, int, char **);
101
102static const char *place = EMSG; /* option letter processing */
103
104/* XXX: set optreset to 1 rather than these two */
105static int nonopt_start = -1; /* first non option argument (for permute) */
106static int nonopt_end = -1;   /* first option after non options (for permute) */
107
108/* Error messages */
109static const char recargchar[] = "option requires an argument -- %c";
110static const char recargstring[] = "option requires an argument -- %s";
111static const char ambig[] = "ambiguous option -- %.*s";
112static const char noarg[] = "option doesn't take an argument -- %.*s";
113static const char illoptchar[] = "unknown option -- %c";
114static const char illoptstring[] = "unknown option -- %s";
115
116
117/*
118 * Compute the greatest common divisor of a and b.
119 */
120static int
121gcd(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 */
142static void
143permute_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 */
182static int
183getopt_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;
206start:
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 */
327int
328getopt(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 */
361int
362getopt_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