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