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