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