1d91bfe0fSMartin Matuska /*- 2d91bfe0fSMartin Matuska * Copyright (c) 2003-2008 Tim Kientzle 3d91bfe0fSMartin Matuska * All rights reserved. 4d91bfe0fSMartin Matuska * 5d91bfe0fSMartin Matuska * Redistribution and use in source and binary forms, with or without 6d91bfe0fSMartin Matuska * modification, are permitted provided that the following conditions 7d91bfe0fSMartin Matuska * are met: 8d91bfe0fSMartin Matuska * 1. Redistributions of source code must retain the above copyright 9d91bfe0fSMartin Matuska * notice, this list of conditions and the following disclaimer. 10d91bfe0fSMartin Matuska * 2. Redistributions in binary form must reproduce the above copyright 11d91bfe0fSMartin Matuska * notice, this list of conditions and the following disclaimer in the 12d91bfe0fSMartin Matuska * documentation and/or other materials provided with the distribution. 13d91bfe0fSMartin Matuska * 14d91bfe0fSMartin Matuska * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15d91bfe0fSMartin Matuska * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16d91bfe0fSMartin Matuska * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17d91bfe0fSMartin Matuska * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18d91bfe0fSMartin Matuska * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19d91bfe0fSMartin Matuska * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20d91bfe0fSMartin Matuska * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21d91bfe0fSMartin Matuska * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22d91bfe0fSMartin Matuska * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23d91bfe0fSMartin Matuska * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24d91bfe0fSMartin Matuska */ 25d91bfe0fSMartin Matuska 26d91bfe0fSMartin Matuska /* 27d91bfe0fSMartin Matuska * Command line parser for bsdunzip. 28d91bfe0fSMartin Matuska */ 29d91bfe0fSMartin Matuska 30d91bfe0fSMartin Matuska #include "bsdunzip_platform.h" 31d91bfe0fSMartin Matuska #ifdef HAVE_ERRNO_H 32d91bfe0fSMartin Matuska #include <errno.h> 33d91bfe0fSMartin Matuska #endif 34d91bfe0fSMartin Matuska #ifdef HAVE_STDLIB_H 35d91bfe0fSMartin Matuska #include <stdlib.h> 36d91bfe0fSMartin Matuska #endif 37d91bfe0fSMartin Matuska #ifdef HAVE_STRING_H 38d91bfe0fSMartin Matuska #include <string.h> 39d91bfe0fSMartin Matuska #endif 40d91bfe0fSMartin Matuska 41d91bfe0fSMartin Matuska #include "bsdunzip.h" 42d91bfe0fSMartin Matuska #include "err.h" 43d91bfe0fSMartin Matuska 44d91bfe0fSMartin Matuska /* 45d91bfe0fSMartin Matuska * Short options for bsdunzip. Please keep this sorted. 46d91bfe0fSMartin Matuska */ 47d91bfe0fSMartin Matuska static const char *short_options 48d91bfe0fSMartin Matuska = "aCcd:fI:jLlnO:opP:qtuvx:yZ:"; 49d91bfe0fSMartin Matuska 50d91bfe0fSMartin Matuska /* 51d91bfe0fSMartin Matuska * Long options for bsdunzip. Please keep this list sorted. 52d91bfe0fSMartin Matuska * 53d91bfe0fSMartin Matuska * The symbolic names for options that lack a short equivalent are 54d91bfe0fSMartin Matuska * defined in bsdunzip.h. Also note that so far I've found no need 55d91bfe0fSMartin Matuska * to support optional arguments to long options. That would be 56d91bfe0fSMartin Matuska * a small change to the code below. 57d91bfe0fSMartin Matuska */ 58d91bfe0fSMartin Matuska 59d91bfe0fSMartin Matuska static const struct bsdunzip_option { 60d91bfe0fSMartin Matuska const char *name; 61d91bfe0fSMartin Matuska int required; /* 1 if this option requires an argument. */ 62d91bfe0fSMartin Matuska int equivalent; /* Equivalent short option. */ 63d91bfe0fSMartin Matuska } bsdunzip_longopts[] = { 64d91bfe0fSMartin Matuska { "version", 0, OPTION_VERSION }, 65d91bfe0fSMartin Matuska { NULL, 0, 0 } 66d91bfe0fSMartin Matuska }; 67d91bfe0fSMartin Matuska 68d91bfe0fSMartin Matuska /* 69d91bfe0fSMartin Matuska * This getopt implementation has two key features that common 70d91bfe0fSMartin Matuska * getopt_long() implementations lack. Apart from those, it's a 71d91bfe0fSMartin Matuska * straightforward option parser, considerably simplified by not 72d91bfe0fSMartin Matuska * needing to support the wealth of exotic getopt_long() features. It 73d91bfe0fSMartin Matuska * has, of course, been shamelessly tailored for bsdunzip. (If you're 74d91bfe0fSMartin Matuska * looking for a generic getopt_long() implementation for your 75d91bfe0fSMartin Matuska * project, I recommend Gregory Pietsch's public domain getopt_long() 76d91bfe0fSMartin Matuska * implementation.) The two additional features are: 77d91bfe0fSMartin Matuska */ 78d91bfe0fSMartin Matuska 79d91bfe0fSMartin Matuska int 80d91bfe0fSMartin Matuska bsdunzip_getopt(struct bsdunzip *bsdunzip) 81d91bfe0fSMartin Matuska { 82d91bfe0fSMartin Matuska enum { state_start = 0, state_next_word, state_short, state_long }; 83d91bfe0fSMartin Matuska 84*13d826ffSMartin Matuska const struct bsdunzip_option *popt, *match, *match2; 85*13d826ffSMartin Matuska const char *p, *long_prefix; 86d91bfe0fSMartin Matuska size_t optlength; 87*13d826ffSMartin Matuska int opt; 88*13d826ffSMartin Matuska int required; 89d91bfe0fSMartin Matuska 90*13d826ffSMartin Matuska again: 91*13d826ffSMartin Matuska match = NULL; 92*13d826ffSMartin Matuska match2 = NULL; 93*13d826ffSMartin Matuska long_prefix = "--"; 94*13d826ffSMartin Matuska opt = OPTION_NONE; 95*13d826ffSMartin Matuska required = 0; 96d91bfe0fSMartin Matuska bsdunzip->argument = NULL; 97d91bfe0fSMartin Matuska 98d91bfe0fSMartin Matuska /* First time through, initialize everything. */ 99d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_start) { 100d91bfe0fSMartin Matuska /* Skip program name. */ 101d91bfe0fSMartin Matuska ++bsdunzip->argv; 102d91bfe0fSMartin Matuska --bsdunzip->argc; 103d91bfe0fSMartin Matuska if (*bsdunzip->argv == NULL) 104d91bfe0fSMartin Matuska return (-1); 105d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 106d91bfe0fSMartin Matuska } 107d91bfe0fSMartin Matuska 108d91bfe0fSMartin Matuska /* 109d91bfe0fSMartin Matuska * We're ready to look at the next word in argv. 110d91bfe0fSMartin Matuska */ 111d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_next_word) { 112d91bfe0fSMartin Matuska /* No more arguments, so no more options. */ 113d91bfe0fSMartin Matuska if (bsdunzip->argv[0] == NULL) 114d91bfe0fSMartin Matuska return (-1); 115d91bfe0fSMartin Matuska /* Doesn't start with '-', so no more options. */ 116d91bfe0fSMartin Matuska if (bsdunzip->argv[0][0] != '-') 117d91bfe0fSMartin Matuska return (-1); 118d91bfe0fSMartin Matuska /* "--" marks end of options; consume it and return. */ 119d91bfe0fSMartin Matuska if (strcmp(bsdunzip->argv[0], "--") == 0) { 120d91bfe0fSMartin Matuska ++bsdunzip->argv; 121d91bfe0fSMartin Matuska --bsdunzip->argc; 122b9128a37SMartin Matuska bsdunzip_optind++; 123d91bfe0fSMartin Matuska return (-1); 124d91bfe0fSMartin Matuska } 125d91bfe0fSMartin Matuska /* Get next word for parsing. */ 126d91bfe0fSMartin Matuska bsdunzip->getopt_word = *bsdunzip->argv++; 127d91bfe0fSMartin Matuska --bsdunzip->argc; 128d91bfe0fSMartin Matuska bsdunzip_optind++; 129d91bfe0fSMartin Matuska if (bsdunzip->getopt_word[1] == '-') { 130d91bfe0fSMartin Matuska /* Set up long option parser. */ 131d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_long; 132d91bfe0fSMartin Matuska bsdunzip->getopt_word += 2; /* Skip leading '--' */ 133d91bfe0fSMartin Matuska } else { 134d91bfe0fSMartin Matuska /* Set up short option parser. */ 135d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_short; 136d91bfe0fSMartin Matuska ++bsdunzip->getopt_word; /* Skip leading '-' */ 137d91bfe0fSMartin Matuska } 138d91bfe0fSMartin Matuska } 139d91bfe0fSMartin Matuska 140d91bfe0fSMartin Matuska /* 141d91bfe0fSMartin Matuska * We're parsing a group of POSIX-style single-character options. 142d91bfe0fSMartin Matuska */ 143d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_short) { 144d91bfe0fSMartin Matuska /* Peel next option off of a group of short options. */ 145d91bfe0fSMartin Matuska opt = *bsdunzip->getopt_word++; 146d91bfe0fSMartin Matuska if (opt == '\0') { 147d91bfe0fSMartin Matuska /* End of this group; recurse to get next option. */ 148d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 149*13d826ffSMartin Matuska goto again; 150d91bfe0fSMartin Matuska } 151d91bfe0fSMartin Matuska 152d91bfe0fSMartin Matuska /* Does this option take an argument? */ 153d91bfe0fSMartin Matuska p = strchr(short_options, opt); 154d91bfe0fSMartin Matuska if (p == NULL) 155d91bfe0fSMartin Matuska return ('?'); 156d91bfe0fSMartin Matuska if (p[1] == ':') 157d91bfe0fSMartin Matuska required = 1; 158d91bfe0fSMartin Matuska 159d91bfe0fSMartin Matuska /* If it takes an argument, parse that. */ 160d91bfe0fSMartin Matuska if (required) { 161d91bfe0fSMartin Matuska /* If arg is run-in, bsdunzip->getopt_word already points to it. */ 162d91bfe0fSMartin Matuska if (bsdunzip->getopt_word[0] == '\0') { 163d91bfe0fSMartin Matuska /* Otherwise, pick up the next word. */ 164d91bfe0fSMartin Matuska bsdunzip->getopt_word = *bsdunzip->argv; 165d91bfe0fSMartin Matuska if (bsdunzip->getopt_word == NULL) { 166d91bfe0fSMartin Matuska lafe_warnc(0, 167d91bfe0fSMartin Matuska "Option -%c requires an argument", 168d91bfe0fSMartin Matuska opt); 169d91bfe0fSMartin Matuska return ('?'); 170d91bfe0fSMartin Matuska } 171d91bfe0fSMartin Matuska ++bsdunzip->argv; 172d91bfe0fSMartin Matuska --bsdunzip->argc; 173d91bfe0fSMartin Matuska bsdunzip_optind++; 174d91bfe0fSMartin Matuska } 175d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 176d91bfe0fSMartin Matuska bsdunzip->argument = bsdunzip->getopt_word; 177d91bfe0fSMartin Matuska } 178d91bfe0fSMartin Matuska } 179d91bfe0fSMartin Matuska 180d91bfe0fSMartin Matuska /* We're reading a long option */ 181d91bfe0fSMartin Matuska if (bsdunzip->getopt_state == state_long) { 182d91bfe0fSMartin Matuska /* After this long option, we'll be starting a new word. */ 183d91bfe0fSMartin Matuska bsdunzip->getopt_state = state_next_word; 184d91bfe0fSMartin Matuska 185d91bfe0fSMartin Matuska /* Option name ends at '=' if there is one. */ 186d91bfe0fSMartin Matuska p = strchr(bsdunzip->getopt_word, '='); 187d91bfe0fSMartin Matuska if (p != NULL) { 188d91bfe0fSMartin Matuska optlength = (size_t)(p - bsdunzip->getopt_word); 189d91bfe0fSMartin Matuska bsdunzip->argument = (char *)(uintptr_t)(p + 1); 190d91bfe0fSMartin Matuska } else { 191d91bfe0fSMartin Matuska optlength = strlen(bsdunzip->getopt_word); 192d91bfe0fSMartin Matuska } 193d91bfe0fSMartin Matuska 194d91bfe0fSMartin Matuska /* Search the table for an unambiguous match. */ 195d91bfe0fSMartin Matuska for (popt = bsdunzip_longopts; popt->name != NULL; popt++) { 196d91bfe0fSMartin Matuska /* Short-circuit if first chars don't match. */ 197d91bfe0fSMartin Matuska if (popt->name[0] != bsdunzip->getopt_word[0]) 198d91bfe0fSMartin Matuska continue; 199d91bfe0fSMartin Matuska /* If option is a prefix of name in table, record it.*/ 200d91bfe0fSMartin Matuska if (strncmp(bsdunzip->getopt_word, popt->name, optlength) == 0) { 201d91bfe0fSMartin Matuska match2 = match; /* Record up to two matches. */ 202d91bfe0fSMartin Matuska match = popt; 203d91bfe0fSMartin Matuska /* If it's an exact match, we're done. */ 204d91bfe0fSMartin Matuska if (strlen(popt->name) == optlength) { 205d91bfe0fSMartin Matuska match2 = NULL; /* Forget the others. */ 206d91bfe0fSMartin Matuska break; 207d91bfe0fSMartin Matuska } 208d91bfe0fSMartin Matuska } 209d91bfe0fSMartin Matuska } 210d91bfe0fSMartin Matuska 211d91bfe0fSMartin Matuska /* Fail if there wasn't a unique match. */ 212d91bfe0fSMartin Matuska if (match == NULL) { 213d91bfe0fSMartin Matuska lafe_warnc(0, 214d91bfe0fSMartin Matuska "Option %s%s is not supported", 215d91bfe0fSMartin Matuska long_prefix, bsdunzip->getopt_word); 216d91bfe0fSMartin Matuska return ('?'); 217d91bfe0fSMartin Matuska } 218d91bfe0fSMartin Matuska if (match2 != NULL) { 219d91bfe0fSMartin Matuska lafe_warnc(0, 220d91bfe0fSMartin Matuska "Ambiguous option %s%s (matches --%s and --%s)", 221d91bfe0fSMartin Matuska long_prefix, bsdunzip->getopt_word, match->name, match2->name); 222d91bfe0fSMartin Matuska return ('?'); 223d91bfe0fSMartin Matuska } 224d91bfe0fSMartin Matuska 225d91bfe0fSMartin Matuska /* We've found a unique match; does it need an argument? */ 226d91bfe0fSMartin Matuska if (match->required) { 227d91bfe0fSMartin Matuska /* Argument required: get next word if necessary. */ 228d91bfe0fSMartin Matuska if (bsdunzip->argument == NULL) { 229d91bfe0fSMartin Matuska bsdunzip->argument = *bsdunzip->argv; 230d91bfe0fSMartin Matuska if (bsdunzip->argument == NULL) { 231d91bfe0fSMartin Matuska lafe_warnc(0, 232d91bfe0fSMartin Matuska "Option %s%s requires an argument", 233d91bfe0fSMartin Matuska long_prefix, match->name); 234d91bfe0fSMartin Matuska return ('?'); 235d91bfe0fSMartin Matuska } 236d91bfe0fSMartin Matuska ++bsdunzip->argv; 237d91bfe0fSMartin Matuska --bsdunzip->argc; 238d91bfe0fSMartin Matuska bsdunzip_optind++; 239d91bfe0fSMartin Matuska } 240d91bfe0fSMartin Matuska } else { 241d91bfe0fSMartin Matuska /* Argument forbidden: fail if there is one. */ 242d91bfe0fSMartin Matuska if (bsdunzip->argument != NULL) { 243d91bfe0fSMartin Matuska lafe_warnc(0, 244d91bfe0fSMartin Matuska "Option %s%s does not allow an argument", 245d91bfe0fSMartin Matuska long_prefix, match->name); 246d91bfe0fSMartin Matuska return ('?'); 247d91bfe0fSMartin Matuska } 248d91bfe0fSMartin Matuska } 249d91bfe0fSMartin Matuska return (match->equivalent); 250d91bfe0fSMartin Matuska } 251d91bfe0fSMartin Matuska 252d91bfe0fSMartin Matuska return (opt); 253d91bfe0fSMartin Matuska } 254