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