]>
Commit | Line | Data |
---|---|---|
0c1f3509 MG |
1 | #include <assert.h> |
2 | #include <stdio.h> | |
3 | #include <stdlib.h> | |
4 | #include <string.h> | |
5 | ||
6 | #include "getopt.h" | |
7 | ||
8 | /* | |
9 | * Standard getopt global variables. optreset starts as non-zero in order to | |
10 | * trigger initialization behaviour. | |
11 | */ | |
12 | const char * optarg = NULL; | |
13 | int optind = 1; | |
14 | int opterr = 1; | |
15 | int optreset = 1; | |
16 | ||
17 | /* | |
18 | * Quasi-internal global variables -- these are used via GETOPT macros. | |
19 | */ | |
20 | const char * getopt_dummy = "(dummy)"; | |
21 | int getopt_initialized = 0; | |
22 | ||
23 | /* | |
24 | * Internal variables. | |
25 | */ | |
26 | static const char * cmdname = NULL; | |
27 | static struct opt { | |
28 | const char * os; | |
29 | size_t olen; | |
30 | int hasarg; | |
31 | } * opts = NULL; | |
32 | static size_t nopts; | |
33 | static size_t opt_missing; | |
34 | static size_t opt_default; | |
35 | static size_t opt_found; | |
36 | static const char * packedopts; | |
37 | static char popt[3]; | |
38 | static int atexit_registered = 0; | |
39 | ||
40 | /* Print a message. */ | |
41 | #define PRINTMSG(...) do { \ | |
42 | if (cmdname != NULL) \ | |
43 | fprintf(stderr, "%s: ", cmdname); \ | |
44 | fprintf(stderr, __VA_ARGS__); \ | |
45 | fprintf(stderr, "\n"); \ | |
46 | } while (0) | |
47 | ||
48 | /* Print an error message and die. */ | |
49 | #define DIE(...) do { \ | |
50 | PRINTMSG(__VA_ARGS__); \ | |
51 | abort(); \ | |
52 | } while (0) | |
53 | ||
54 | /* Print a warning, if warnings are enabled. */ | |
55 | #define WARN(...) do { \ | |
56 | if (opterr == 0) \ | |
57 | break; \ | |
58 | if (opt_missing != opt_default) \ | |
59 | break; \ | |
60 | PRINTMSG(__VA_ARGS__); \ | |
61 | } while (0) | |
62 | ||
63 | /* Free allocated options array. */ | |
64 | static void | |
65 | atexit_handler(void) | |
66 | { | |
67 | ||
68 | free(opts); | |
69 | opts = NULL; | |
70 | } | |
71 | ||
72 | /* Reset internal state. */ | |
73 | static void | |
74 | reset(int argc, char * const argv[]) | |
75 | { | |
76 | const char * p; | |
77 | ||
78 | /* If we have arguments, stash argv[0] for error messages. */ | |
79 | if (argc > 0) { | |
80 | /* Find the basename, without leading directories. */ | |
81 | for (p = cmdname = argv[0]; *p != '\0'; p++) { | |
82 | if (*p == '/') | |
83 | cmdname = p + 1; | |
84 | } | |
85 | } | |
86 | ||
87 | /* Discard any registered command-line options. */ | |
88 | free(opts); | |
89 | opts = NULL; | |
90 | ||
91 | /* Register atexit handler if we haven't done so already. */ | |
92 | if (!atexit_registered) { | |
93 | atexit(atexit_handler); | |
94 | atexit_registered = 1; | |
95 | } | |
96 | ||
97 | /* We will start scanning from the first option. */ | |
98 | optind = 1; | |
99 | ||
100 | /* We're not in the middle of any packed options. */ | |
101 | packedopts = NULL; | |
102 | ||
103 | /* We haven't found any option yet. */ | |
104 | opt_found = (size_t)(-1); | |
105 | ||
106 | /* We're not initialized yet. */ | |
107 | getopt_initialized = 0; | |
108 | ||
109 | /* Finished resetting state. */ | |
110 | optreset = 0; | |
111 | } | |
112 | ||
113 | /* Search for an option string. */ | |
114 | static size_t | |
115 | searchopt(const char * os) | |
116 | { | |
117 | size_t i; | |
118 | ||
119 | /* Scan the array of options. */ | |
120 | for (i = 0; i < nopts; i++) { | |
121 | /* Is there an option in this slot? */ | |
122 | if (opts[i].os == NULL) | |
123 | continue; | |
124 | ||
125 | /* Does this match up to the length of the option string? */ | |
126 | if (strncmp(opts[i].os, os, opts[i].olen)) | |
127 | continue; | |
128 | ||
129 | /* Do we have <option>\0 or <option>= ? */ | |
130 | if ((os[opts[i].olen] == '\0') || (os[opts[i].olen] == '=')) | |
131 | return (i); | |
132 | } | |
133 | ||
134 | /* Not found. */ | |
135 | return (opt_default); | |
136 | } | |
137 | ||
138 | const char * | |
139 | getopt(int argc, char * const argv[]) | |
140 | { | |
141 | const char * os = NULL; | |
142 | const char * canonical_os = NULL; | |
143 | ||
144 | /* No argument yet. */ | |
145 | optarg = NULL; | |
146 | ||
147 | /* Reset the getopt state if needed. */ | |
148 | if (optreset) | |
149 | reset(argc, argv); | |
150 | ||
151 | /* If not initialized, return dummy option. */ | |
152 | if (!getopt_initialized) | |
153 | return (GETOPT_DUMMY); | |
154 | ||
155 | /* If we've run out of arguments, we're done. */ | |
156 | if (optind >= argc) | |
157 | return (NULL); | |
158 | ||
159 | /* | |
160 | * If we're not already in the middle of a packed single-character | |
161 | * options, see if we should start. | |
162 | */ | |
163 | if ((packedopts == NULL) && (argv[optind][0] == '-') && | |
164 | (argv[optind][1] != '-') && (argv[optind][1] != '\0')) { | |
165 | /* We have one or more single-character options. */ | |
166 | packedopts = &argv[optind][1]; | |
167 | } | |
168 | ||
169 | /* If we're processing single-character options, fish one out. */ | |
170 | if (packedopts != NULL) { | |
171 | /* Construct the option string. */ | |
172 | popt[0] = '-'; | |
173 | popt[1] = *packedopts; | |
174 | popt[2] = '\0'; | |
175 | os = popt; | |
176 | ||
177 | /* We've done this character. */ | |
178 | packedopts++; | |
179 | ||
180 | /* Are we done with this string? */ | |
181 | if (*packedopts == '\0') { | |
182 | packedopts = NULL; | |
183 | optind++; | |
184 | } | |
185 | } | |
186 | ||
187 | /* If we don't have an option yet, do we have dash-dash? */ | |
188 | if ((os == NULL) && (argv[optind][0] == '-') && | |
189 | (argv[optind][1] == '-')) { | |
190 | /* If this is not "--\0", it's an option. */ | |
191 | if (argv[optind][2] != '\0') | |
192 | os = argv[optind]; | |
193 | ||
194 | /* Either way, we want to eat the string. */ | |
195 | optind++; | |
196 | } | |
197 | ||
198 | /* If we have found nothing which looks like an option, we're done. */ | |
199 | if (os == NULL) | |
200 | return (NULL); | |
201 | ||
202 | /* Search for the potential option. */ | |
203 | opt_found = searchopt(os); | |
204 | ||
205 | /* If the option is not registered, give up now. */ | |
206 | if (opt_found == opt_default) { | |
207 | WARN("unknown option: %s", os); | |
208 | return (os); | |
209 | } | |
210 | ||
211 | /* The canonical option string is the one registered. */ | |
212 | canonical_os = opts[opt_found].os; | |
213 | ||
214 | /* Does the option take an argument? */ | |
215 | if (opts[opt_found].hasarg) { | |
216 | /* | |
217 | * If we're processing packed single-character options, the | |
218 | * rest of the string is the argument to this option. | |
219 | */ | |
220 | if (packedopts != NULL) { | |
221 | optarg = packedopts; | |
222 | packedopts = NULL; | |
223 | optind++; | |
224 | } | |
225 | ||
226 | /* | |
227 | * If the option string is <option>=<value>, extract that | |
228 | * value as the option argument. | |
229 | */ | |
230 | if (os[opts[opt_found].olen] == '=') | |
231 | optarg = &os[opts[opt_found].olen + 1]; | |
232 | ||
233 | /* | |
234 | * If we don't have an argument yet, take one from the | |
235 | * remaining command line. | |
236 | */ | |
237 | if ((optarg == NULL) && (optind < argc)) | |
238 | optarg = argv[optind++]; | |
239 | ||
240 | /* If we still have no option, declare it MIA. */ | |
241 | if (optarg == NULL) { | |
242 | WARN("option requires an argument: %s", | |
243 | opts[opt_found].os); | |
244 | opt_found = opt_missing; | |
245 | } | |
246 | } else { | |
247 | /* If we have --foo=bar, something went wrong. */ | |
248 | if (os[opts[opt_found].olen] == '=') { | |
249 | WARN("option doesn't take an argument: %s", | |
250 | opts[opt_found].os); | |
251 | opt_found = opt_default; | |
252 | } | |
253 | } | |
254 | ||
255 | /* Return the canonical option string. */ | |
256 | return (canonical_os); | |
257 | } | |
258 | ||
259 | size_t | |
260 | getopt_lookup(const char * os) | |
261 | { | |
262 | ||
263 | /* Can't reset here. */ | |
264 | if (optreset) | |
265 | DIE("Can't reset in the middle of getopt loop"); | |
266 | ||
267 | /* We should only be called after initialization is complete. */ | |
268 | assert(getopt_initialized); | |
269 | ||
270 | /* GETOPT_DUMMY should never get passed back to us. */ | |
271 | assert(os != GETOPT_DUMMY); | |
272 | ||
273 | /* | |
274 | * Make sure the option passed back to us corresponds to the one we | |
275 | * found earlier. | |
276 | */ | |
277 | assert((opt_found == opt_missing) || (opt_found == opt_default) || | |
278 | ((opt_found < nopts) && (strcmp(os, opts[opt_found].os) == 0))); | |
279 | ||
280 | /* Return the option number we identified earlier. */ | |
281 | return (opt_found); | |
282 | } | |
283 | ||
284 | void | |
285 | getopt_register_opt(const char * os, size_t ln, int hasarg) | |
286 | { | |
287 | ||
288 | /* Can't reset here. */ | |
289 | if (optreset) | |
290 | DIE("Can't reset in the middle of getopt loop"); | |
291 | ||
292 | /* We should only be called during initialization. */ | |
293 | assert(!getopt_initialized); | |
294 | ||
295 | /* We should have space allocated for registering options. */ | |
296 | assert(opts != NULL); | |
297 | ||
298 | /* We should not have registered an option here yet. */ | |
299 | assert(opts[ln].os == NULL); | |
300 | ||
301 | /* Options should be "-X" or "--foo". */ | |
302 | if ((os[0] != '-') || (os[1] == '\0') || | |
303 | ((os[1] == '-') && (os[2] == '\0')) || | |
304 | ((os[1] != '-') && (os[2] != '\0'))) | |
305 | DIE("Not a valid command-line option: %s", os); | |
306 | ||
307 | /* Make sure we haven't already registered this option. */ | |
308 | if (searchopt(os) != opt_default) | |
309 | DIE("Command-line option registered twice: %s", os); | |
310 | ||
311 | /* Record option. */ | |
312 | opts[ln].os = os; | |
313 | opts[ln].olen = strlen(os); | |
314 | opts[ln].hasarg = hasarg; | |
315 | } | |
316 | ||
317 | void | |
318 | getopt_register_missing(size_t ln) | |
319 | { | |
320 | ||
321 | /* Can't reset here. */ | |
322 | if (optreset) | |
323 | DIE("Can't reset in the middle of getopt loop"); | |
324 | ||
325 | /* We should only be called during initialization. */ | |
326 | assert(!getopt_initialized); | |
327 | ||
328 | /* Record missing-argument value. */ | |
329 | opt_missing = ln; | |
330 | } | |
331 | ||
332 | void | |
333 | getopt_setrange(size_t ln) | |
334 | { | |
335 | size_t i; | |
336 | ||
337 | /* Can't reset here. */ | |
338 | if (optreset) | |
339 | DIE("Can't reset in the middle of getopt loop"); | |
340 | ||
341 | /* We should only be called during initialization. */ | |
342 | assert(!getopt_initialized); | |
343 | ||
344 | /* Allocate space for options. */ | |
345 | opts = malloc(ln * sizeof(struct opt)); | |
346 | if ((ln > 0) && (opts == NULL)) | |
347 | DIE("Failed to allocate memory in getopt"); | |
348 | ||
349 | /* Initialize options. */ | |
350 | for (i = 0; i < ln; i++) | |
351 | opts[i].os = NULL; | |
352 | ||
353 | /* Record the number of (potential) options. */ | |
354 | nopts = ln; | |
355 | ||
356 | /* Record default missing-argument and no-such-option values. */ | |
357 | opt_missing = opt_default = ln + 1; | |
358 | } |