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