1 #include <stddef.h>
2 #include <string.h>
3 
4 #include "getopt.h"
5 
6 #if !defined(_MSC_VER)
7 const int no_argument = 0;
8 const int required_argument = 1;
9 const int optional_argument = 2;
10 #endif
11 
12 char* optarg;
13 int optopt;
14 int optind = 1; // The variable optind [...] shall be initialized to 1 by the system
15 int opterr;
16 
17 static char* optcursor = NULL;
18 
19 /* Implemented based on [1] and [2] for optional arguments.
20  * optopt is handled FreeBSD-style, per [3].
21  * Other GNU and FreeBSD extensions are purely accidental.
22  *
23  * [1] http://pubs.opengroup.org/onlinepubs/000095399/functions/getopt.html
24  * [2] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html
25  * [3] http://www.freebsd.org/cgi/man.cgi?query=getopt&sektion=3&manpath=FreeBSD+9.0-RELEASE
26  */
getopt(int argc,char * const argv[],const char * optstring)27 int getopt(int argc, char* const argv[], const char* optstring) {
28     int optchar = -1;
29     const char* optdecl = NULL;
30 
31     optarg = NULL;
32     opterr = 0;
33     optopt = 0;
34 
35     /* Unspecified, but we need it to avoid overrunning the argv bounds. */
36     if (optind >= argc) { goto no_more_optchars; }
37 
38     /* If, when getopt() is called argv[optind] is a null pointer,
39      * getopt() shall return -1 without changing optind.
40      */
41     if (argv[optind] == NULL) { goto no_more_optchars; }
42 
43     /* If, when getopt() is called *argv[optind]  is not the character '-',
44      * getopt() shall return -1 without changing optind.
45      */
46     if (*argv[optind] != '-') { goto no_more_optchars; }
47 
48     /* If, when getopt() is called argv[optind] points to the string "-",
49      * getopt() shall return -1 without changing optind.
50      */
51     if (strcmp(argv[optind], "-") == 0) { goto no_more_optchars; }
52 
53     /* If, when getopt() is called argv[optind] points to the string "--",
54      * getopt() shall return -1 after incrementing optind.
55      */
56     if (strcmp(argv[optind], "--") == 0) {
57         ++optind;
58         goto no_more_optchars;
59     }
60 
61     if (optcursor == NULL || *optcursor == '\0') { optcursor = argv[optind] + 1; }
62 
63     optchar = *optcursor;
64 
65     /* FreeBSD: The variable optopt saves the last known option character returned by getopt(). */
66     optopt = optchar;
67 
68     /* The getopt() function shall return the next option character (if one is found)
69      * from argv that matches a character in optstring, if there is one that matches.
70      */
71     optdecl = strchr(optstring, optchar);
72 
73     if (optdecl) {
74         /* [I]f a character is followed by a colon, the option takes an argument. */
75         if (optdecl[1] == ':') {
76             optarg = ++optcursor;
77 
78             if (*optarg == '\0') {
79                 /* GNU extension: Two colons mean an option takes an optional arg;
80                  * if there is text in the current argv-element (i.e., in the same word
81                  * as the option name itself, for example, "-oarg"), then it is returned
82                  * in optarg, otherwise optarg is set to zero.
83                  */
84                 if (optdecl[2] != ':') {
85                     /* If the option was the last character in the string pointed to by
86                      * an element of argv, then optarg shall contain the next element
87                      * of argv, and optind shall be incremented by 2. If the resulting
88                      * value of optind is greater than argc, this indicates a missing
89                      * option-argument, and getopt() shall return an error indication.
90                      * Otherwise, optarg shall point to the string following the
91                      * option character in that element of argv, and optind shall be
92                      * incremented by 1.
93                      */
94                     if (++optind < argc) {
95                         optarg = argv[optind];
96                     } else {
97                         /* If it detects a missing option-argument, it shall return the
98                          * colon character ( ':' ) if the first character of optstring
99                          * was a colon, or a question-mark character ( '?' ) otherwise.
100                          */
101                         optarg = NULL;
102                         optchar = (optstring[0] == ':') ? ':' : '?';
103                     }
104                 } else {
105                     optarg = NULL;
106                 }
107             }
108 
109             optcursor = NULL;
110         }
111     } else {
112         /* If getopt() encounters an option character that is not contained in
113          * optstring, it shall return the question-mark ( '?' ) character.
114          */
115         optchar = '?';
116     }
117 
118     if (optcursor == NULL || *++optcursor == '\0') { ++optind; }
119 
120     return(optchar);
121 
122 no_more_optchars:
123     optcursor = NULL;
124     return(-1);
125 }
126 
127 /* Implementation based on http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html */
getopt_long(int argc,char * const argv[],const char * optstring,const struct option * longopts,int * longindex)128 int getopt_long(int argc,
129                 char* const argv[],
130                 const char* optstring,
131                 const struct option* longopts,
132                 int* longindex) {
133     const struct option* o = longopts;
134     const struct option* match = NULL;
135     int num_matches = 0;
136     size_t argument_name_length = 0;
137     const char* current_argument = NULL;
138     int retval = -1;
139 
140     optarg = NULL;
141     optopt = 0;
142 
143     if (optind >= argc) { return(-1); }
144 
145     if (strlen(argv[optind]) < 3 || strncmp(argv[optind], "--", 2) != 0) {
146         return(getopt(argc, argv, optstring));
147     }
148 
149     // it's an option; starts with -- and is longer than two chars
150     current_argument = argv[optind] + 2;
151     argument_name_length = strcspn(current_argument, "=");
152 
153     for ( ; o->name; ++o)
154         if (strncmp(o->name, current_argument, argument_name_length) == 0) {
155             match = o;
156             ++num_matches;
157         }
158 
159 
160     if (num_matches == 1) {
161         /* If longindex is not NULL, it points to a variable which is set to the
162          * index of the long option relative to longopts.
163          */
164         if (longindex) { *longindex = (match - longopts); }
165 
166         /* If flag is NULL, then getopt_long() shall return val.
167          * Otherwise, getopt_long() returns 0, and flag shall point to a variable
168          * which shall be set to val if the option is found, but left unchanged if
169          * the option is not found.
170          */
171         if (match->flag) { *(match->flag) = match->val; }
172 
173         retval = match->flag ? 0 : match->val;
174 
175         if (match->has_arg != no_argument) {
176             optarg = strchr(argv[optind], '=');
177 
178             if (optarg != NULL) { ++optarg; }
179 
180             if (match->has_arg == required_argument) {
181                 /* Only scan the next argv for required arguments. Behavior is not
182                    specified, but has been observed with Ubuntu and macOS. */
183                 if (optarg == NULL && ++optind < argc) { optarg = argv[optind]; }
184 
185                 if (optarg == NULL) { retval = ':'; }
186             }
187         } else if (strchr(argv[optind], '=')) {
188             /* An argument was provided to a non-argument option.
189              * I haven't seen this specified explicitly, but both GNU and BSD-based
190              * implementations show this behavior.
191              */
192             retval = '?';
193         }
194     } else {
195         // unknown option or ambiguous match
196         retval = '?';
197     }
198 
199     ++optind;
200     return(retval);
201 }
202