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