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