| 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 | } |