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