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