Merge from vendor branch TNFTP:
[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.14 2005/11/20 12:37:48 swildner 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') && long_support == 0)
197                         return -1;
198                 if ((*place != '-') ||
199                     ((*place == '-') && (place[1] == '\0') && long_support != 0)) {
200                         /* found non-option */
201                         place = EMSG;
202                         if (IN_ORDER) {
203                                 /*
204                                  * GNU extension: 
205                                  * return non-option as argument to option 1
206                                  */
207                                 optarg = nargv[optind++];
208                                 return INORDER;
209                         }
210                         if (!PERMUTE) {
211                                 /*
212                                  * if no permutation wanted, stop parsing
213                                  * at first non-option
214                                  */
215                                 return -1;
216                         }
217                         /* do permutation */
218                         if (nonopt_start == -1)
219                                 nonopt_start = optind;
220                         else if (nonopt_end != -1) {
221                                 permute_args(nonopt_start, nonopt_end,
222                                     optind, nargv);
223                                 nonopt_start = optind -
224                                     (nonopt_end - nonopt_start);
225                                 nonopt_end = -1;
226                         }
227                         optind++;
228                         /* process next argument */
229                         goto start;
230                 }
231                 if (nonopt_start != -1 && nonopt_end == -1)
232                         nonopt_end = optind;
233                 if (place[1] && *++place == '-') {      /* found "--" */
234                         if (place[1] == '\0') {
235                                 ++optind;
236                                 /*
237                                  * We found an option (--), so if we skipped
238                                  * non-options, we have to permute.
239                                  */
240                                 if (nonopt_end != -1) {
241                                         permute_args(nonopt_start, nonopt_end,
242                                                      optind, nargv);
243                                         optind -= nonopt_end - nonopt_start;
244                                 }
245                                 nonopt_start = nonopt_end = -1;
246                                 return -1;
247                         } else if (long_support) {
248                                 place++;
249                                 return -2;
250                         }
251                 }
252         }
253         if (long_support == 2 && (place[1] || strchr(options, *place) == NULL))
254                 return -3;
255         return getopt_internal_short(nargc, nargv, options, long_support);
256 }
257
258 static int
259 getopt_internal_short(int nargc, char * const *nargv, const char *options,
260                       int long_support)
261 {
262         const char *oli;                        /* option letter list index */
263         int optchar;
264
265         if ((optchar = (int)*place++) == (int)':' ||
266             (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
267                 /* option letter unknown or ':' */
268                 if (PRINT_ERROR) {
269                         if (long_support == 2)
270                                 warnx(illoptstring, --place);
271                         else
272                                 warnx(illoptchar, optchar);
273                 }
274                 if (long_support == 2)
275                         place = EMSG;
276                 if (*place == 0)
277                         ++optind;
278                 optopt = optchar;
279                 return BADCH;
280         }
281         if (long_support && optchar == 'W' && oli[1] == ';') {
282                 /* -W long-option */
283                 if (*place) 
284                         return -2;
285
286                 if (++optind >= nargc) {        /* no arg */
287                         place = EMSG;
288                         if (PRINT_ERROR)
289                                 warnx(recargchar, optchar);
290                         optopt = optchar;
291                         return BADARG;
292                 } else                          /* white space */
293                         place = nargv[optind];
294                 /*
295                  * Handle -W arg the same as --arg (which causes getopt to
296                  * stop parsing).
297                  */
298                 return -2;
299         }
300         if (*++oli != ':') {                    /* doesn't take argument */
301                 if (!*place)
302                         ++optind;
303         } else {                                /* takes (optional) argument */
304                 optarg = NULL;
305                 if (*place)                     /* no white space */
306                         optarg = place;
307                 /* XXX: disable test for :: if PC? (GNU doesn't) */
308                 else if (oli[1] != ':') {       /* arg not optional */
309                         if (++optind >= nargc) {        /* no arg */
310                                 place = EMSG;
311                                 if (PRINT_ERROR)
312                                         warnx(recargchar, optchar);
313                                 optopt = optchar;
314                                 return BADARG;
315                         } else
316                                 optarg = nargv[optind];
317                 }
318                 place = EMSG;
319                 ++optind;
320         }
321         /* dump back option letter */
322         return optchar;
323 }
324
325 static int
326 getopt_long_internal(int nargc, char * const *nargv, const char *options,
327                      const struct option *long_options, int *idx, int long_only)
328 {
329         int retval;
330
331         /* idx may be NULL */
332
333         retval = getopt_internal(nargc, nargv, options, long_only ? 2 : 1);
334 recheck:
335         if (retval == -2 || retval == -3) {
336                 char *current_argv, *has_equal;
337                 size_t current_argv_len;
338                 int i, match;
339
340                 current_argv = place;
341                 match = -1;
342
343                 optind++;
344                 place = EMSG;
345
346                 if ((has_equal = strchr(current_argv, '=')) != NULL) {
347                         /* argument found (--option=arg) */
348                         current_argv_len = has_equal - current_argv;
349                         has_equal++;
350                 } else
351                         current_argv_len = strlen(current_argv);
352             
353                 for (i = 0; long_options[i].name; i++) {
354                         /* find matching long option */
355                         if (strncmp(current_argv, long_options[i].name,
356                             current_argv_len))
357                                 continue;
358
359                         if (strlen(long_options[i].name) ==
360                             (unsigned)current_argv_len) {
361                                 /* exact match */
362                                 match = i;
363                                 break;
364                         }
365                         if (match == -1)                /* partial match */
366                                 match = i;
367                         else {
368                                 /* ambiguous abbreviation */
369                                 if (PRINT_ERROR)
370                                         warnx(ambig, (int)current_argv_len,
371                                              current_argv);
372                                 optopt = 0;
373                                 return BADCH;
374                         }
375                 }
376                 if (match != -1) {                      /* option found */
377                         if (long_options[match].has_arg == no_argument
378                             && has_equal) {
379                                 if (PRINT_ERROR)
380                                         warnx(noarg, (int)current_argv_len,
381                                              current_argv);
382                                 /*
383                                  * XXX: GNU sets optopt to val regardless of
384                                  * flag
385                                  */
386                                 if (long_options[match].flag == NULL)
387                                         optopt = long_options[match].val;
388                                 else
389                                         optopt = 0;
390                                 return BADARG;
391                         }
392                         if (long_options[match].has_arg == required_argument ||
393                             long_options[match].has_arg == optional_argument) {
394                                 if (has_equal)
395                                         optarg = has_equal;
396                                 else if (long_options[match].has_arg ==
397                                     required_argument) {
398                                         /*
399                                          * optional argument doesn't use
400                                          * next nargv
401                                          */
402                                         optarg = nargv[optind++];
403                                 }
404                         }
405                         if ((long_options[match].has_arg == required_argument)
406                             && (optarg == NULL)) {
407                                 /*
408                                  * Missing argument; leading ':'
409                                  * indicates no error should be generated
410                                  */
411                                 if (PRINT_ERROR)
412                                         warnx(recargstring, current_argv);
413                                 /*
414                                  * XXX: GNU sets optopt to val regardless
415                                  * of flag
416                                  */
417                                 if (long_options[match].flag == NULL)
418                                         optopt = long_options[match].val;
419                                 else
420                                         optopt = 0;
421                                 --optind;
422                                 return BADARG;
423                         }
424                 } else if (retval == -3) {
425                         --optind;
426                         place = current_argv;
427                         retval = getopt_internal_short(nargc, nargv,
428                             options, long_only ? 2 : 1);
429                         goto recheck;
430                 } else {                        /* unknown option */
431                         if (PRINT_ERROR)
432                                 warnx(illoptstring, current_argv);
433                         optopt = 0;
434                         return BADCH;
435                 }
436                 if (long_options[match].flag) {
437                         *long_options[match].flag = long_options[match].val;
438                         retval = 0;
439                 } else 
440                         retval = long_options[match].val;
441                 if (idx)
442                         *idx = match;
443         }
444         return retval;
445 }
446
447 #ifdef REPLACE_GETOPT
448 /*
449  * getopt --
450  *      Parse argc/argv argument vector.
451  *
452  * [eventually this will replace the real getopt]
453  */
454 int
455 getopt(int nargc, char * const *nargv, const char *options)
456 {
457         return getopt_internal(nargc, nargv, options, 0);
458 }
459 #endif
460
461 /*
462  * getopt_long --
463  *      Parse argc/argv argument vector.
464  */
465
466 int
467 getopt_long(int nargc, char * const *nargv, const char *options,
468             const struct option *long_options, int *idx)
469 {
470         return getopt_long_internal(nargc, nargv, options, long_options,
471                                     idx, 0);
472 }
473
474 /*
475  * getopt_long_only --
476  *      Parse argc/argv argument vector.
477  *      Prefers long options over short options for single dash arguments.
478  */
479
480 int
481 getopt_long_only(int nargc, char * const *nargv, const char *options,
482                  const struct option *long_options, int *idx)
483 {
484         return getopt_long_internal(nargc, nargv, options, long_options,
485                                     idx, 1);
486 }