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