1186690Sobrien/*	$NetBSD: getopt_long.c,v 1.21.4.1 2008/01/09 01:34:14 matt Exp $	*/
2186690Sobrien
3186690Sobrien/*-
4186690Sobrien * Copyright (c) 2000 The NetBSD Foundation, Inc.
5186690Sobrien * All rights reserved.
6186690Sobrien *
7186690Sobrien * This code is derived from software contributed to The NetBSD Foundation
8186690Sobrien * by Dieter Baron and Thomas Klausner.
9186690Sobrien *
10186690Sobrien * Redistribution and use in source and binary forms, with or without
11186690Sobrien * modification, are permitted provided that the following conditions
12186690Sobrien * are met:
13186690Sobrien * 1. Redistributions of source code must retain the above copyright
14186690Sobrien *    notice, this list of conditions and the following disclaimer.
15186690Sobrien * 2. Redistributions in binary form must reproduce the above copyright
16186690Sobrien *    notice, this list of conditions and the following disclaimer in the
17186690Sobrien *    documentation and/or other materials provided with the distribution.
18186690Sobrien *
19186690Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20186690Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21186690Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22186690Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23186690Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24186690Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25186690Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26186690Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27186690Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28186690Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29186690Sobrien * POSSIBILITY OF SUCH DAMAGE.
30186690Sobrien */
31186690Sobrien
32191771Sobrien#include "file.h"
33191771Sobrien
34191771Sobrien#ifndef	lint
35192350SdelphijFILE_RCSID("@(#)$File: getopt_long.c,v 1.6 2009/02/13 18:48:05 christos Exp $")
36191771Sobrien#endif	/* lint */
37191771Sobrien
38186690Sobrien#include <assert.h>
39186690Sobrien#ifdef HAVE_ERR_H
40186690Sobrien#include <err.h>
41186690Sobrien#else
42186690Sobrien#define warnx printf
43186690Sobrien#endif
44186690Sobrien#include <errno.h>
45192350Sdelphij#if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION)
46186690Sobrien#include <getopt.h>
47186690Sobrien#else
48186690Sobrien#include "mygetopt.h"
49186690Sobrien#endif
50186690Sobrien#include <stdlib.h>
51186690Sobrien#include <string.h>
52186690Sobrien
53186690Sobrien#define REPLACE_GETOPT
54186690Sobrien
55186690Sobrien#ifndef _DIAGASSERT
56186690Sobrien#define _DIAGASSERT assert
57186690Sobrien#endif
58186690Sobrien
59186690Sobrien#ifdef REPLACE_GETOPT
60186690Sobrien#ifdef __weak_alias
61186690Sobrien__weak_alias(getopt,_getopt)
62186690Sobrien#endif
63186690Sobrienint	opterr = 1;		/* if error message should be printed */
64186690Sobrienint	optind = 1;		/* index into parent argv vector */
65186690Sobrienint	optopt = '?';		/* character checked for validity */
66186690Sobrienint	optreset;		/* reset getopt */
67186690Sobrienchar    *optarg;		/* argument associated with option */
68186690Sobrien#elif HAVE_NBTOOL_CONFIG_H && !HAVE_DECL_OPTRESET
69186690Sobrienstatic int optreset;
70186690Sobrien#endif
71186690Sobrien
72186690Sobrien#ifdef __weak_alias
73186690Sobrien__weak_alias(getopt_long,_getopt_long)
74186690Sobrien#endif
75186690Sobrien
76186690Sobrien#define IGNORE_FIRST	(*options == '-' || *options == '+')
77186690Sobrien#define PRINT_ERROR	((opterr) && ((*options != ':') \
78186690Sobrien				      || (IGNORE_FIRST && options[1] != ':')))
79186690Sobrien#define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL)
80186690Sobrien#define PERMUTE         (!IS_POSIXLY_CORRECT && !IGNORE_FIRST)
81186690Sobrien/* XXX: GNU ignores PC if *options == '-' */
82186690Sobrien#define IN_ORDER        (!IS_POSIXLY_CORRECT && *options == '-')
83186690Sobrien
84186690Sobrien/* return values */
85186690Sobrien#define	BADCH	(int)'?'
86186690Sobrien#define	BADARG		((IGNORE_FIRST && options[1] == ':') \
87186690Sobrien			 || (*options == ':') ? (int)':' : (int)'?')
88186690Sobrien#define INORDER (int)1
89186690Sobrien
90186690Sobrien#define	EMSG	""
91186690Sobrien
92186690Sobrienstatic int getopt_internal(int, char **, const char *);
93186690Sobrienstatic int gcd(int, int);
94186690Sobrienstatic void permute_args(int, int, int, char **);
95186690Sobrien
96186690Sobrienstatic const char *place = EMSG; /* option letter processing */
97186690Sobrien
98186690Sobrien/* XXX: set optreset to 1 rather than these two */
99186690Sobrienstatic int nonopt_start = -1; /* first non option argument (for permute) */
100186690Sobrienstatic int nonopt_end = -1;   /* first option after non options (for permute) */
101186690Sobrien
102186690Sobrien/* Error messages */
103186690Sobrienstatic const char recargchar[] = "option requires an argument -- %c";
104186690Sobrienstatic const char recargstring[] = "option requires an argument -- %s";
105186690Sobrienstatic const char ambig[] = "ambiguous option -- %.*s";
106186690Sobrienstatic const char noarg[] = "option doesn't take an argument -- %.*s";
107186690Sobrienstatic const char illoptchar[] = "unknown option -- %c";
108186690Sobrienstatic const char illoptstring[] = "unknown option -- %s";
109186690Sobrien
110186690Sobrien
111186690Sobrien/*
112186690Sobrien * Compute the greatest common divisor of a and b.
113186690Sobrien */
114186690Sobrienstatic int
115186690Sobriengcd(a, b)
116186690Sobrien	int a;
117186690Sobrien	int b;
118186690Sobrien{
119186690Sobrien	int c;
120186690Sobrien
121186690Sobrien	c = a % b;
122186690Sobrien	while (c != 0) {
123186690Sobrien		a = b;
124186690Sobrien		b = c;
125186690Sobrien		c = a % b;
126186690Sobrien	}
127186690Sobrien
128186690Sobrien	return b;
129186690Sobrien}
130186690Sobrien
131186690Sobrien/*
132186690Sobrien * Exchange the block from nonopt_start to nonopt_end with the block
133186690Sobrien * from nonopt_end to opt_end (keeping the same order of arguments
134186690Sobrien * in each block).
135186690Sobrien */
136186690Sobrienstatic void
137186690Sobrienpermute_args(panonopt_start, panonopt_end, opt_end, nargv)
138186690Sobrien	int panonopt_start;
139186690Sobrien	int panonopt_end;
140186690Sobrien	int opt_end;
141186690Sobrien	char **nargv;
142186690Sobrien{
143186690Sobrien	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
144186690Sobrien	char *swap;
145186690Sobrien
146186690Sobrien	_DIAGASSERT(nargv != NULL);
147186690Sobrien
148186690Sobrien	/*
149186690Sobrien	 * compute lengths of blocks and number and size of cycles
150186690Sobrien	 */
151186690Sobrien	nnonopts = panonopt_end - panonopt_start;
152186690Sobrien	nopts = opt_end - panonopt_end;
153186690Sobrien	ncycle = gcd(nnonopts, nopts);
154186690Sobrien	cyclelen = (opt_end - panonopt_start) / ncycle;
155186690Sobrien
156186690Sobrien	for (i = 0; i < ncycle; i++) {
157186690Sobrien		cstart = panonopt_end+i;
158186690Sobrien		pos = cstart;
159186690Sobrien		for (j = 0; j < cyclelen; j++) {
160186690Sobrien			if (pos >= panonopt_end)
161186690Sobrien				pos -= nnonopts;
162186690Sobrien			else
163186690Sobrien				pos += nopts;
164186690Sobrien			swap = nargv[pos];
165186690Sobrien			nargv[pos] = nargv[cstart];
166186690Sobrien			nargv[cstart] = swap;
167186690Sobrien		}
168186690Sobrien	}
169186690Sobrien}
170186690Sobrien
171186690Sobrien/*
172186690Sobrien * getopt_internal --
173186690Sobrien *	Parse argc/argv argument vector.  Called by user level routines.
174186690Sobrien *  Returns -2 if -- is found (can be long option or end of options marker).
175186690Sobrien */
176186690Sobrienstatic int
177186690Sobriengetopt_internal(nargc, nargv, options)
178186690Sobrien	int nargc;
179186690Sobrien	char **nargv;
180186690Sobrien	const char *options;
181186690Sobrien{
182186690Sobrien	char *oli;				/* option letter list index */
183186690Sobrien	int optchar;
184186690Sobrien
185186690Sobrien	_DIAGASSERT(nargv != NULL);
186186690Sobrien	_DIAGASSERT(options != NULL);
187186690Sobrien
188186690Sobrien	optarg = NULL;
189186690Sobrien
190186690Sobrien	/*
191186690Sobrien	 * XXX Some programs (like rsyncd) expect to be able to
192186690Sobrien	 * XXX re-initialize optind to 0 and have getopt_long(3)
193186690Sobrien	 * XXX properly function again.  Work around this braindamage.
194186690Sobrien	 */
195186690Sobrien	if (optind == 0)
196186690Sobrien		optind = 1;
197186690Sobrien
198186690Sobrien	if (optreset)
199186690Sobrien		nonopt_start = nonopt_end = -1;
200186690Sobrienstart:
201186690Sobrien	if (optreset || !*place) {		/* update scanning pointer */
202186690Sobrien		optreset = 0;
203186690Sobrien		if (optind >= nargc) {          /* end of argument vector */
204186690Sobrien			place = EMSG;
205186690Sobrien			if (nonopt_end != -1) {
206186690Sobrien				/* do permutation, if we have to */
207186690Sobrien				permute_args(nonopt_start, nonopt_end,
208186690Sobrien				    optind, nargv);
209186690Sobrien				optind -= nonopt_end - nonopt_start;
210186690Sobrien			}
211186690Sobrien			else if (nonopt_start != -1) {
212186690Sobrien				/*
213186690Sobrien				 * If we skipped non-options, set optind
214186690Sobrien				 * to the first of them.
215186690Sobrien				 */
216186690Sobrien				optind = nonopt_start;
217186690Sobrien			}
218186690Sobrien			nonopt_start = nonopt_end = -1;
219186690Sobrien			return -1;
220186690Sobrien		}
221186690Sobrien		if ((*(place = nargv[optind]) != '-')
222186690Sobrien		    || (place[1] == '\0')) {    /* found non-option */
223186690Sobrien			place = EMSG;
224186690Sobrien			if (IN_ORDER) {
225186690Sobrien				/*
226186690Sobrien				 * GNU extension:
227186690Sobrien				 * return non-option as argument to option 1
228186690Sobrien				 */
229186690Sobrien				optarg = nargv[optind++];
230186690Sobrien				return INORDER;
231186690Sobrien			}
232186690Sobrien			if (!PERMUTE) {
233186690Sobrien				/*
234186690Sobrien				 * if no permutation wanted, stop parsing
235186690Sobrien				 * at first non-option
236186690Sobrien				 */
237186690Sobrien				return -1;
238186690Sobrien			}
239186690Sobrien			/* do permutation */
240186690Sobrien			if (nonopt_start == -1)
241186690Sobrien				nonopt_start = optind;
242186690Sobrien			else if (nonopt_end != -1) {
243186690Sobrien				permute_args(nonopt_start, nonopt_end,
244186690Sobrien				    optind, nargv);
245186690Sobrien				nonopt_start = optind -
246186690Sobrien				    (nonopt_end - nonopt_start);
247186690Sobrien				nonopt_end = -1;
248186690Sobrien			}
249186690Sobrien			optind++;
250186690Sobrien			/* process next argument */
251186690Sobrien			goto start;
252186690Sobrien		}
253186690Sobrien		if (nonopt_start != -1 && nonopt_end == -1)
254186690Sobrien			nonopt_end = optind;
255186690Sobrien		if (place[1] && *++place == '-') {	/* found "--" */
256186690Sobrien			place++;
257186690Sobrien			return -2;
258186690Sobrien		}
259186690Sobrien	}
260186690Sobrien	if ((optchar = (int)*place++) == (int)':' ||
261186690Sobrien	    (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
262186690Sobrien		/* option letter unknown or ':' */
263186690Sobrien		if (!*place)
264186690Sobrien			++optind;
265186690Sobrien		if (PRINT_ERROR)
266186690Sobrien			warnx(illoptchar, optchar);
267186690Sobrien		optopt = optchar;
268186690Sobrien		return BADCH;
269186690Sobrien	}
270186690Sobrien	if (optchar == 'W' && oli[1] == ';') {		/* -W long-option */
271186690Sobrien		/* XXX: what if no long options provided (called by getopt)? */
272186690Sobrien		if (*place)
273186690Sobrien			return -2;
274186690Sobrien
275186690Sobrien		if (++optind >= nargc) {	/* no arg */
276186690Sobrien			place = EMSG;
277186690Sobrien			if (PRINT_ERROR)
278186690Sobrien				warnx(recargchar, optchar);
279186690Sobrien			optopt = optchar;
280186690Sobrien			return BADARG;
281186690Sobrien		} else				/* white space */
282186690Sobrien			place = nargv[optind];
283186690Sobrien		/*
284186690Sobrien		 * Handle -W arg the same as --arg (which causes getopt to
285186690Sobrien		 * stop parsing).
286186690Sobrien		 */
287186690Sobrien		return -2;
288186690Sobrien	}
289186690Sobrien	if (*++oli != ':') {			/* doesn't take argument */
290186690Sobrien		if (!*place)
291186690Sobrien			++optind;
292186690Sobrien	} else {				/* takes (optional) argument */
293186690Sobrien		optarg = NULL;
294186690Sobrien		if (*place)			/* no white space */
295186690Sobrien			optarg = (char *)place;
296186690Sobrien		/* XXX: disable test for :: if PC? (GNU doesn't) */
297186690Sobrien		else if (oli[1] != ':') {	/* arg not optional */
298186690Sobrien			if (++optind >= nargc) {	/* no arg */
299186690Sobrien				place = EMSG;
300186690Sobrien				if (PRINT_ERROR)
301186690Sobrien					warnx(recargchar, optchar);
302186690Sobrien				optopt = optchar;
303186690Sobrien				return BADARG;
304186690Sobrien			} else
305186690Sobrien				optarg = nargv[optind];
306186690Sobrien		}
307186690Sobrien		place = EMSG;
308186690Sobrien		++optind;
309186690Sobrien	}
310186690Sobrien	/* dump back option letter */
311186690Sobrien	return optchar;
312186690Sobrien}
313186690Sobrien
314186690Sobrien#ifdef REPLACE_GETOPT
315186690Sobrien/*
316186690Sobrien * getopt --
317186690Sobrien *	Parse argc/argv argument vector.
318186690Sobrien *
319186690Sobrien * [eventually this will replace the real getopt]
320186690Sobrien */
321186690Sobrienint
322186690Sobriengetopt(nargc, nargv, options)
323186690Sobrien	int nargc;
324186690Sobrien	char * const *nargv;
325186690Sobrien	const char *options;
326186690Sobrien{
327186690Sobrien	int retval;
328186690Sobrien
329186690Sobrien	_DIAGASSERT(nargv != NULL);
330186690Sobrien	_DIAGASSERT(options != NULL);
331186690Sobrien
332186690Sobrien	retval = getopt_internal(nargc, (char **)nargv, options);
333186690Sobrien	if (retval == -2) {
334186690Sobrien		++optind;
335186690Sobrien		/*
336186690Sobrien		 * We found an option (--), so if we skipped non-options,
337186690Sobrien		 * we have to permute.
338186690Sobrien		 */
339186690Sobrien		if (nonopt_end != -1) {
340186690Sobrien			permute_args(nonopt_start, nonopt_end, optind,
341186690Sobrien				     (char **)nargv);
342186690Sobrien			optind -= nonopt_end - nonopt_start;
343186690Sobrien		}
344186690Sobrien		nonopt_start = nonopt_end = -1;
345186690Sobrien		retval = -1;
346186690Sobrien	}
347186690Sobrien	return retval;
348186690Sobrien}
349186690Sobrien#endif
350186690Sobrien
351186690Sobrien/*
352186690Sobrien * getopt_long --
353186690Sobrien *	Parse argc/argv argument vector.
354186690Sobrien */
355186690Sobrienint
356186690Sobriengetopt_long(nargc, nargv, options, long_options, idx)
357186690Sobrien	int nargc;
358186690Sobrien	char * const *nargv;
359186690Sobrien	const char *options;
360186690Sobrien	const struct option *long_options;
361186690Sobrien	int *idx;
362186690Sobrien{
363186690Sobrien	int retval;
364186690Sobrien
365186690Sobrien#define IDENTICAL_INTERPRETATION(_x, _y)				\
366186690Sobrien	(long_options[(_x)].has_arg == long_options[(_y)].has_arg &&	\
367186690Sobrien	 long_options[(_x)].flag == long_options[(_y)].flag &&		\
368186690Sobrien	 long_options[(_x)].val == long_options[(_y)].val)
369186690Sobrien
370186690Sobrien	_DIAGASSERT(nargv != NULL);
371186690Sobrien	_DIAGASSERT(options != NULL);
372186690Sobrien	_DIAGASSERT(long_options != NULL);
373186690Sobrien	/* idx may be NULL */
374186690Sobrien
375186690Sobrien	retval = getopt_internal(nargc, (char **)nargv, options);
376186690Sobrien	if (retval == -2) {
377186690Sobrien		char *current_argv, *has_equal;
378186690Sobrien		size_t current_argv_len;
379186690Sobrien		int i, ambiguous, match;
380186690Sobrien
381186690Sobrien		current_argv = (char *)place;
382186690Sobrien		match = -1;
383186690Sobrien		ambiguous = 0;
384186690Sobrien
385186690Sobrien		optind++;
386186690Sobrien		place = EMSG;
387186690Sobrien
388186690Sobrien		if (*current_argv == '\0') {		/* found "--" */
389186690Sobrien			/*
390186690Sobrien			 * We found an option (--), so if we skipped
391186690Sobrien			 * non-options, we have to permute.
392186690Sobrien			 */
393186690Sobrien			if (nonopt_end != -1) {
394186690Sobrien				permute_args(nonopt_start, nonopt_end,
395186690Sobrien					     optind, (char **)nargv);
396186690Sobrien				optind -= nonopt_end - nonopt_start;
397186690Sobrien			}
398186690Sobrien			nonopt_start = nonopt_end = -1;
399186690Sobrien			return -1;
400186690Sobrien		}
401186690Sobrien		if ((has_equal = strchr(current_argv, '=')) != NULL) {
402186690Sobrien			/* argument found (--option=arg) */
403186690Sobrien			current_argv_len = has_equal - current_argv;
404186690Sobrien			has_equal++;
405186690Sobrien		} else
406186690Sobrien			current_argv_len = strlen(current_argv);
407186690Sobrien
408186690Sobrien		for (i = 0; long_options[i].name; i++) {
409186690Sobrien			/* find matching long option */
410186690Sobrien			if (strncmp(current_argv, long_options[i].name,
411186690Sobrien			    current_argv_len))
412186690Sobrien				continue;
413186690Sobrien
414186690Sobrien			if (strlen(long_options[i].name) ==
415186690Sobrien			    (unsigned)current_argv_len) {
416186690Sobrien				/* exact match */
417186690Sobrien				match = i;
418186690Sobrien				ambiguous = 0;
419186690Sobrien				break;
420186690Sobrien			}
421186690Sobrien			if (match == -1)		/* partial match */
422186690Sobrien				match = i;
423186690Sobrien			else if (!IDENTICAL_INTERPRETATION(i, match))
424186690Sobrien				ambiguous = 1;
425186690Sobrien		}
426186690Sobrien		if (ambiguous) {
427186690Sobrien			/* ambiguous abbreviation */
428186690Sobrien			if (PRINT_ERROR)
429186690Sobrien				warnx(ambig, (int)current_argv_len,
430186690Sobrien				     current_argv);
431186690Sobrien			optopt = 0;
432186690Sobrien			return BADCH;
433186690Sobrien		}
434186690Sobrien		if (match != -1) {			/* option found */
435186690Sobrien		        if (long_options[match].has_arg == no_argument
436186690Sobrien			    && has_equal) {
437186690Sobrien				if (PRINT_ERROR)
438186690Sobrien					warnx(noarg, (int)current_argv_len,
439186690Sobrien					     current_argv);
440186690Sobrien				/*
441186690Sobrien				 * XXX: GNU sets optopt to val regardless of
442186690Sobrien				 * flag
443186690Sobrien				 */
444186690Sobrien				if (long_options[match].flag == NULL)
445186690Sobrien					optopt = long_options[match].val;
446186690Sobrien				else
447186690Sobrien					optopt = 0;
448186690Sobrien				return BADARG;
449186690Sobrien			}
450186690Sobrien			if (long_options[match].has_arg == required_argument ||
451186690Sobrien			    long_options[match].has_arg == optional_argument) {
452186690Sobrien				if (has_equal)
453186690Sobrien					optarg = has_equal;
454186690Sobrien				else if (long_options[match].has_arg ==
455186690Sobrien				    required_argument) {
456186690Sobrien					/*
457186690Sobrien					 * optional argument doesn't use
458186690Sobrien					 * next nargv
459186690Sobrien					 */
460186690Sobrien					optarg = nargv[optind++];
461186690Sobrien				}
462186690Sobrien			}
463186690Sobrien			if ((long_options[match].has_arg == required_argument)
464186690Sobrien			    && (optarg == NULL)) {
465186690Sobrien				/*
466186690Sobrien				 * Missing argument; leading ':'
467186690Sobrien				 * indicates no error should be generated
468186690Sobrien				 */
469186690Sobrien				if (PRINT_ERROR)
470186690Sobrien					warnx(recargstring, current_argv);
471186690Sobrien				/*
472186690Sobrien				 * XXX: GNU sets optopt to val regardless
473186690Sobrien				 * of flag
474186690Sobrien				 */
475186690Sobrien				if (long_options[match].flag == NULL)
476186690Sobrien					optopt = long_options[match].val;
477186690Sobrien				else
478186690Sobrien					optopt = 0;
479186690Sobrien				--optind;
480186690Sobrien				return BADARG;
481186690Sobrien			}
482186690Sobrien		} else {			/* unknown option */
483186690Sobrien			if (PRINT_ERROR)
484186690Sobrien				warnx(illoptstring, current_argv);
485186690Sobrien			optopt = 0;
486186690Sobrien			return BADCH;
487186690Sobrien		}
488186690Sobrien		if (long_options[match].flag) {
489186690Sobrien			*long_options[match].flag = long_options[match].val;
490186690Sobrien			retval = 0;
491186690Sobrien		} else
492186690Sobrien			retval = long_options[match].val;
493186690Sobrien		if (idx)
494186690Sobrien			*idx = match;
495186690Sobrien	}
496186690Sobrien	return retval;
497186690Sobrien#undef IDENTICAL_INTERPRETATION
498186690Sobrien}
499