1d91bfe0fSMartin Matuska /*- 2*bd66c1b4SMartin Matuska * SPDX-License-Identifier: BSD-2-Clause 3*bd66c1b4SMartin Matuska * 4d91bfe0fSMartin Matuska * Copyright (c) 2003-2008 Tim Kientzle 5d91bfe0fSMartin Matuska * All rights reserved. 6d91bfe0fSMartin Matuska */ 7d91bfe0fSMartin Matuska 8d91bfe0fSMartin Matuska /* 9d91bfe0fSMartin Matuska * Command line parser for bsdunzip. 10d91bfe0fSMartin Matuska */ 11d91bfe0fSMartin Matuska 12d91bfe0fSMartin Matuska #include "bsdunzip_platform.h" 13d91bfe0fSMartin Matuska #ifdef HAVE_ERRNO_H 14d91bfe0fSMartin Matuska #include <errno.h> 15d91bfe0fSMartin Matuska #endif 16d91bfe0fSMartin Matuska #ifdef HAVE_STDLIB_H 17d91bfe0fSMartin Matuska #include <stdlib.h> 18d91bfe0fSMartin Matuska #endif 19d91bfe0fSMartin Matuska #ifdef HAVE_STRING_H 20d91bfe0fSMartin Matuska #include <string.h> 21d91bfe0fSMartin Matuska #endif 22d91bfe0fSMartin Matuska 23d91bfe0fSMartin Matuska #include "bsdunzip.h" 24d91bfe0fSMartin Matuska #include "err.h" 25d91bfe0fSMartin Matuska 26d91bfe0fSMartin Matuska /* 27d91bfe0fSMartin Matuska * Short options for bsdunzip. Please keep this sorted. 28d91bfe0fSMartin Matuska */ 29d91bfe0fSMartin Matuska static const char *short_options 30d91bfe0fSMartin Matuska = "aCcd:fI:jLlnO:opP:qtuvx:yZ:"; 31d91bfe0fSMartin Matuska 32d91bfe0fSMartin Matuska /* 33d91bfe0fSMartin Matuska * Long options for bsdunzip. Please keep this list sorted. 34d91bfe0fSMartin Matuska * 35d91bfe0fSMartin Matuska * The symbolic names for options that lack a short equivalent are 36d91bfe0fSMartin Matuska * defined in bsdunzip.h. Also note that so far I've found no need 37d91bfe0fSMartin Matuska * to support optional arguments to long options. That would be 38d91bfe0fSMartin Matuska * a small change to the code below. 39d91bfe0fSMartin Matuska */ 40d91bfe0fSMartin Matuska 41d91bfe0fSMartin Matuska static const struct bsdunzip_option { 42d91bfe0fSMartin Matuska const char *name; 43d91bfe0fSMartin Matuska int required; /* 1 if this option requires an argument. */ 44d91bfe0fSMartin Matuska int equivalent; /* Equivalent short option. */ 45d91bfe0fSMartin Matuska } bsdunzip_longopts[] = { 46d91bfe0fSMartin Matuska { "version", 0, OPTION_VERSION }, 47d91bfe0fSMartin Matuska { NULL, 0, 0 } 48d91bfe0fSMartin Matuska }; 49d91bfe0fSMartin Matuska 50d91bfe0fSMartin Matuska /* 51d91bfe0fSMartin Matuska * This getopt implementation has two key features that common 52d91bfe0fSMartin Matuska * getopt_long() implementations lack. Apart from those, it's a 53d91bfe0fSMartin Matuska * straightforward option parser, considerably simplified by not 54d91bfe0fSMartin Matuska * needing to support the wealth of exotic getopt_long() features. It 55d91bfe0fSMartin Matuska * has, of course, been shamelessly tailored for bsdunzip. (If you're 56d91bfe0fSMartin Matuska * looking for a generic getopt_long() implementation for your 57d91bfe0fSMartin Matuska * project, I recommend Gregory Pietsch's public domain getopt_long() 58d91bfe0fSMartin Matuska * implementation.) The two additional features are: 59d91bfe0fSMartin Matuska */ 60d91bfe0fSMartin Matuska 61d91bfe0fSMartin Matuska int 62d91bfe0fSMartin Matuska bsdunzip_getopt(struct bsdunzip *bsdunzip) 63d91bfe0fSMartin Matuska { 64d91bfe0fSMartin Matuska enum { state_start = 0, state_next_word, state_short, state_long }; 65d91bfe0fSMartin Matuska 6613d826ffSMartin Matuska const struct bsdunzip_option *popt, *match, *match2; 6713d826ffSMartin Matuska const char *p, *long_prefix; 68d91bfe0fSMartin Matuska size_t optlength; 6913d826ffSMartin Matuska int opt; 7013d826ffSMartin Matuska int required; 71d91bfe0fSMartin Matuska 7213d826ffSMartin Matuska again: 7313d826ffSMartin Matuska match = NULL; 7413d826ffSMartin Matuska match2 = NULL; 7513d826ffSMartin Matuska long_prefix = "--"; 7613d826ffSMartin Matuska opt = OPTION_NONE; 7713d826ffSMartin Matuska required = 0; 78d91bfe0fSMartin Matuska bsdunzip->argument = NULL; 79d91bfe0fSMartin Matuska 80d91bfe0fSMartin Matuska /* First time through, initialize everything. */ 81d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_start) { 82d91bfe0fSMartin Matuska /* Skip program name. */ 83d91bfe0fSMartin Matuska ++bsdunzip->argv; 84d91bfe0fSMartin Matuska --bsdunzip->argc; 85d91bfe0fSMartin Matuska if (*bsdunzip->argv == NULL) 86d91bfe0fSMartin Matuska return (-1); 87d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 88d91bfe0fSMartin Matuska } 89d91bfe0fSMartin Matuska 90d91bfe0fSMartin Matuska /* 91d91bfe0fSMartin Matuska * We're ready to look at the next word in argv. 92d91bfe0fSMartin Matuska */ 93d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_next_word) { 94d91bfe0fSMartin Matuska /* No more arguments, so no more options. */ 95d91bfe0fSMartin Matuska if (bsdunzip->argv[0] == NULL) 96d91bfe0fSMartin Matuska return (-1); 97d91bfe0fSMartin Matuska /* Doesn't start with '-', so no more options. */ 98d91bfe0fSMartin Matuska if (bsdunzip->argv[0][0] != '-') 99d91bfe0fSMartin Matuska return (-1); 100d91bfe0fSMartin Matuska /* "--" marks end of options; consume it and return. */ 101d91bfe0fSMartin Matuska if (strcmp(bsdunzip->argv[0], "--") == 0) { 102d91bfe0fSMartin Matuska ++bsdunzip->argv; 103d91bfe0fSMartin Matuska --bsdunzip->argc; 104b9128a37SMartin Matuska bsdunzip_optind++; 105d91bfe0fSMartin Matuska return (-1); 106d91bfe0fSMartin Matuska } 107d91bfe0fSMartin Matuska /* Get next word for parsing. */ 108d91bfe0fSMartin Matuska bsdunzip->getopt_word = *bsdunzip->argv++; 109d91bfe0fSMartin Matuska --bsdunzip->argc; 110d91bfe0fSMartin Matuska bsdunzip_optind++; 111d91bfe0fSMartin Matuska if (bsdunzip->getopt_word[1] == '-') { 112d91bfe0fSMartin Matuska /* Set up long option parser. */ 113d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_long; 114d91bfe0fSMartin Matuska bsdunzip->getopt_word += 2; /* Skip leading '--' */ 115d91bfe0fSMartin Matuska } else { 116d91bfe0fSMartin Matuska /* Set up short option parser. */ 117d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_short; 118d91bfe0fSMartin Matuska ++bsdunzip->getopt_word; /* Skip leading '-' */ 119d91bfe0fSMartin Matuska } 120d91bfe0fSMartin Matuska } 121d91bfe0fSMartin Matuska 122d91bfe0fSMartin Matuska /* 123d91bfe0fSMartin Matuska * We're parsing a group of POSIX-style single-character options. 124d91bfe0fSMartin Matuska */ 125d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_short) { 126d91bfe0fSMartin Matuska /* Peel next option off of a group of short options. */ 127d91bfe0fSMartin Matuska opt = *bsdunzip->getopt_word++; 128d91bfe0fSMartin Matuska if (opt == '\0') { 129d91bfe0fSMartin Matuska /* End of this group; recurse to get next option. */ 130d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 13113d826ffSMartin Matuska goto again; 132d91bfe0fSMartin Matuska } 133d91bfe0fSMartin Matuska 134d91bfe0fSMartin Matuska /* Does this option take an argument? */ 135d91bfe0fSMartin Matuska p = strchr(short_options, opt); 136d91bfe0fSMartin Matuska if (p == NULL) 137d91bfe0fSMartin Matuska return ('?'); 138d91bfe0fSMartin Matuska if (p[1] == ':') 139d91bfe0fSMartin Matuska required = 1; 140d91bfe0fSMartin Matuska 141d91bfe0fSMartin Matuska /* If it takes an argument, parse that. */ 142d91bfe0fSMartin Matuska if (required) { 143d91bfe0fSMartin Matuska /* If arg is run-in, bsdunzip->getopt_word already points to it. */ 144d91bfe0fSMartin Matuska if (bsdunzip->getopt_word[0] == '\0') { 145d91bfe0fSMartin Matuska /* Otherwise, pick up the next word. */ 146d91bfe0fSMartin Matuska bsdunzip->getopt_word = *bsdunzip->argv; 147d91bfe0fSMartin Matuska if (bsdunzip->getopt_word == NULL) { 148d91bfe0fSMartin Matuska lafe_warnc(0, 149d91bfe0fSMartin Matuska "Option -%c requires an argument", 150d91bfe0fSMartin Matuska opt); 151d91bfe0fSMartin Matuska return ('?'); 152d91bfe0fSMartin Matuska } 153d91bfe0fSMartin Matuska ++bsdunzip->argv; 154d91bfe0fSMartin Matuska --bsdunzip->argc; 155d91bfe0fSMartin Matuska bsdunzip_optind++; 156d91bfe0fSMartin Matuska } 157d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 158d91bfe0fSMartin Matuska bsdunzip->argument = bsdunzip->getopt_word; 159d91bfe0fSMartin Matuska } 160d91bfe0fSMartin Matuska } 161d91bfe0fSMartin Matuska 162d91bfe0fSMartin Matuska /* We're reading a long option */ 163d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_long) { 164d91bfe0fSMartin Matuska /* After this long option, we'll be starting a new word. */ 165d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 166d91bfe0fSMartin Matuska 167d91bfe0fSMartin Matuska /* Option name ends at '=' if there is one. */ 168d91bfe0fSMartin Matuska p = strchr(bsdunzip->getopt_word, '='); 169d91bfe0fSMartin Matuska if (p != NULL) { 170d91bfe0fSMartin Matuska optlength = (size_t)(p - bsdunzip->getopt_word); 171d91bfe0fSMartin Matuska bsdunzip->argument = (char *)(uintptr_t)(p + 1); 172d91bfe0fSMartin Matuska } else { 173d91bfe0fSMartin Matuska optlength = strlen(bsdunzip->getopt_word); 174d91bfe0fSMartin Matuska } 175d91bfe0fSMartin Matuska 176d91bfe0fSMartin Matuska /* Search the table for an unambiguous match. */ 177d91bfe0fSMartin Matuska for (popt = bsdunzip_longopts; popt->name != NULL; popt++) { 178d91bfe0fSMartin Matuska /* Short-circuit if first chars don't match. */ 179d91bfe0fSMartin Matuska if (popt->name[0] != bsdunzip->getopt_word[0]) 180d91bfe0fSMartin Matuska continue; 181d91bfe0fSMartin Matuska /* If option is a prefix of name in table, record it.*/ 182d91bfe0fSMartin Matuska if (strncmp(bsdunzip->getopt_word, popt->name, optlength) == 0) { 183d91bfe0fSMartin Matuska match2 = match; /* Record up to two matches. */ 184d91bfe0fSMartin Matuska match = popt; 185d91bfe0fSMartin Matuska /* If it's an exact match, we're done. */ 186d91bfe0fSMartin Matuska if (strlen(popt->name) == optlength) { 187d91bfe0fSMartin Matuska match2 = NULL; /* Forget the others. */ 188d91bfe0fSMartin Matuska break; 189d91bfe0fSMartin Matuska } 190d91bfe0fSMartin Matuska } 191d91bfe0fSMartin Matuska } 192d91bfe0fSMartin Matuska 193d91bfe0fSMartin Matuska /* Fail if there wasn't a unique match. */ 194d91bfe0fSMartin Matuska if (match == NULL) { 195d91bfe0fSMartin Matuska lafe_warnc(0, 196d91bfe0fSMartin Matuska "Option %s%s is not supported", 197d91bfe0fSMartin Matuska long_prefix, bsdunzip->getopt_word); 198d91bfe0fSMartin Matuska return ('?'); 199d91bfe0fSMartin Matuska } 200d91bfe0fSMartin Matuska if (match2 != NULL) { 201d91bfe0fSMartin Matuska lafe_warnc(0, 202d91bfe0fSMartin Matuska "Ambiguous option %s%s (matches --%s and --%s)", 203d91bfe0fSMartin Matuska long_prefix, bsdunzip->getopt_word, match->name, match2->name); 204d91bfe0fSMartin Matuska return ('?'); 205d91bfe0fSMartin Matuska } 206d91bfe0fSMartin Matuska 207d91bfe0fSMartin Matuska /* We've found a unique match; does it need an argument? */ 208d91bfe0fSMartin Matuska if (match->required) { 209d91bfe0fSMartin Matuska /* Argument required: get next word if necessary. */ 210d91bfe0fSMartin Matuska if (bsdunzip->argument == NULL) { 211d91bfe0fSMartin Matuska bsdunzip->argument = *bsdunzip->argv; 212d91bfe0fSMartin Matuska if (bsdunzip->argument == NULL) { 213d91bfe0fSMartin Matuska lafe_warnc(0, 214d91bfe0fSMartin Matuska "Option %s%s requires an argument", 215d91bfe0fSMartin Matuska long_prefix, match->name); 216d91bfe0fSMartin Matuska return ('?'); 217d91bfe0fSMartin Matuska } 218d91bfe0fSMartin Matuska ++bsdunzip->argv; 219d91bfe0fSMartin Matuska --bsdunzip->argc; 220d91bfe0fSMartin Matuska bsdunzip_optind++; 221d91bfe0fSMartin Matuska } 222d91bfe0fSMartin Matuska } else { 223d91bfe0fSMartin Matuska /* Argument forbidden: fail if there is one. */ 224d91bfe0fSMartin Matuska if (bsdunzip->argument != NULL) { 225d91bfe0fSMartin Matuska lafe_warnc(0, 226d91bfe0fSMartin Matuska "Option %s%s does not allow an argument", 227d91bfe0fSMartin Matuska long_prefix, match->name); 228d91bfe0fSMartin Matuska return ('?'); 229d91bfe0fSMartin Matuska } 230d91bfe0fSMartin Matuska } 231d91bfe0fSMartin Matuska return (match->equivalent); 232d91bfe0fSMartin Matuska } 233d91bfe0fSMartin Matuska 234d91bfe0fSMartin Matuska return (opt); 235d91bfe0fSMartin Matuska } 236