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