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