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 bsdcat. 28 */ 29 30 #include "bsdcat_platform.h" 31 32 #ifdef HAVE_ERRNO_H 33 #include <errno.h> 34 #endif 35 #ifdef HAVE_STDLIB_H 36 #include <stdlib.h> 37 #endif 38 #ifdef HAVE_STRING_H 39 #include <string.h> 40 #endif 41 42 #include "bsdcat.h" 43 #include "err.h" 44 45 /* 46 * Short options for bsdcat. Please keep this sorted. 47 */ 48 static const char *short_options = "h"; 49 50 /* 51 * Long options for bsdcat. Please keep this list sorted. 52 * 53 * The symbolic names for options that lack a short equivalent are 54 * defined in bsdcat.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 bsdcat_option { 60 const char *name; 61 int required; /* 1 if this option requires an argument. */ 62 int equivalent; /* Equivalent short option. */ 63 } bsdcat_longopts[] = { 64 { "help", 0, 'h' }, 65 { "version", 0, OPTION_VERSION }, 66 { NULL, 0, 0 } 67 }; 68 69 /* 70 * This getopt implementation has two key features that common 71 * getopt_long() implementations lack. Apart from those, it's a 72 * straightforward option parser, considerably simplified by not 73 * needing to support the wealth of exotic getopt_long() features. It 74 * has, of course, been shamelessly tailored for bsdcat. (If you're 75 * looking for a generic getopt_long() implementation for your 76 * project, I recommend Gregory Pietsch's public domain getopt_long() 77 * implementation.) The two additional features are: 78 * 79 * Old-style tar arguments: The original tar implementation treated 80 * the first argument word as a list of single-character option 81 * letters. All arguments follow as separate words. For example, 82 * tar xbf 32 /dev/tape 83 * Here, the "xbf" is three option letters, "32" is the argument for 84 * "b" and "/dev/tape" is the argument for "f". We support this usage 85 * if the first command-line argument does not begin with '-'. We 86 * also allow regular short and long options to follow, e.g., 87 * tar xbf 32 /dev/tape -P --format=pax 88 * 89 * -W long options: There's an obscure GNU convention (only rarely 90 * supported even there) that allows "-W option=argument" as an 91 * alternative way to support long options. This was supported in 92 * early bsdtar as a way to access long options on platforms that did 93 * not support getopt_long() and is preserved here for backwards 94 * compatibility. (Of course, if I'd started with a custom 95 * command-line parser from the beginning, I would have had normal 96 * long option support on every platform so that hack wouldn't have 97 * been necessary. Oh, well. Some mistakes you just have to live 98 * with.) 99 * 100 * TODO: We should be able to use this to pull files and intermingled 101 * options (such as -C) from the command line in write mode. That 102 * will require a little rethinking of the argument handling in 103 * bsdcat.c. 104 * 105 * TODO: If we want to support arbitrary command-line options from -T 106 * input (as GNU tar does), we may need to extend this to handle option 107 * words from sources other than argv/argc. I'm not really sure if I 108 * like that feature of GNU tar, so it's certainly not a priority. 109 */ 110 111 int 112 bsdcat_getopt(struct bsdcat *bsdcat) 113 { 114 enum { state_start = 0, state_old_tar, state_next_word, 115 state_short, state_long }; 116 117 const struct bsdcat_option *popt, *match, *match2; 118 const char *p, *long_prefix; 119 size_t optlength; 120 int opt; 121 int required; 122 123 again: 124 match = NULL; 125 match2 = NULL; 126 long_prefix = "--"; 127 opt = '?'; 128 required = 0; 129 bsdcat->argument = NULL; 130 131 /* First time through, initialize everything. */ 132 if (bsdcat->getopt_state == state_start) { 133 /* Skip program name. */ 134 ++bsdcat->argv; 135 --bsdcat->argc; 136 if (*bsdcat->argv == NULL) 137 return (-1); 138 /* Decide between "new style" and "old style" arguments. */ 139 bsdcat->getopt_state = state_next_word; 140 } 141 142 /* 143 * We're ready to look at the next word in argv. 144 */ 145 if (bsdcat->getopt_state == state_next_word) { 146 /* No more arguments, so no more options. */ 147 if (bsdcat->argv[0] == NULL) 148 return (-1); 149 /* Doesn't start with '-', so no more options. */ 150 if (bsdcat->argv[0][0] != '-') 151 return (-1); 152 /* "--" marks end of options; consume it and return. */ 153 if (strcmp(bsdcat->argv[0], "--") == 0) { 154 ++bsdcat->argv; 155 --bsdcat->argc; 156 return (-1); 157 } 158 /* Get next word for parsing. */ 159 bsdcat->getopt_word = *bsdcat->argv++; 160 --bsdcat->argc; 161 if (bsdcat->getopt_word[1] == '-') { 162 /* Set up long option parser. */ 163 bsdcat->getopt_state = state_long; 164 bsdcat->getopt_word += 2; /* Skip leading '--' */ 165 } else { 166 /* Set up short option parser. */ 167 bsdcat->getopt_state = state_short; 168 ++bsdcat->getopt_word; /* Skip leading '-' */ 169 } 170 } 171 172 /* 173 * We're parsing a group of POSIX-style single-character options. 174 */ 175 if (bsdcat->getopt_state == state_short) { 176 /* Peel next option off of a group of short options. */ 177 opt = *bsdcat->getopt_word++; 178 if (opt == '\0') { 179 /* End of this group; recurse to get next option. */ 180 bsdcat->getopt_state = state_next_word; 181 goto again; 182 } 183 184 /* Does this option take an argument? */ 185 p = strchr(short_options, opt); 186 if (p == NULL) 187 return ('?'); 188 if (p[1] == ':') 189 required = 1; 190 191 /* If it takes an argument, parse that. */ 192 if (required) { 193 /* If arg is run-in, bsdcat->getopt_word already points to it. */ 194 if (bsdcat->getopt_word[0] == '\0') { 195 /* Otherwise, pick up the next word. */ 196 bsdcat->getopt_word = *bsdcat->argv; 197 if (bsdcat->getopt_word == NULL) { 198 lafe_warnc(0, 199 "Option -%c requires an argument", 200 opt); 201 return ('?'); 202 } 203 ++bsdcat->argv; 204 --bsdcat->argc; 205 } 206 if (opt == 'W') { 207 bsdcat->getopt_state = state_long; 208 long_prefix = "-W "; /* For clearer errors. */ 209 } else { 210 bsdcat->getopt_state = state_next_word; 211 bsdcat->argument = bsdcat->getopt_word; 212 } 213 } 214 } 215 216 /* We're reading a long option, including -W long=arg convention. */ 217 if (bsdcat->getopt_state == state_long) { 218 /* After this long option, we'll be starting a new word. */ 219 bsdcat->getopt_state = state_next_word; 220 221 /* Option name ends at '=' if there is one. */ 222 p = strchr(bsdcat->getopt_word, '='); 223 if (p != NULL) { 224 optlength = (size_t)(p - bsdcat->getopt_word); 225 bsdcat->argument = (char *)(uintptr_t)(p + 1); 226 } else { 227 optlength = strlen(bsdcat->getopt_word); 228 } 229 230 /* Search the table for an unambiguous match. */ 231 for (popt = bsdcat_longopts; popt->name != NULL; popt++) { 232 /* Short-circuit if first chars don't match. */ 233 if (popt->name[0] != bsdcat->getopt_word[0]) 234 continue; 235 /* If option is a prefix of name in table, record it.*/ 236 if (strncmp(bsdcat->getopt_word, popt->name, optlength) == 0) { 237 match2 = match; /* Record up to two matches. */ 238 match = popt; 239 /* If it's an exact match, we're done. */ 240 if (strlen(popt->name) == optlength) { 241 match2 = NULL; /* Forget the others. */ 242 break; 243 } 244 } 245 } 246 247 /* Fail if there wasn't a unique match. */ 248 if (match == NULL) { 249 lafe_warnc(0, 250 "Option %s%s is not supported", 251 long_prefix, bsdcat->getopt_word); 252 return ('?'); 253 } 254 if (match2 != NULL) { 255 lafe_warnc(0, 256 "Ambiguous option %s%s (matches --%s and --%s)", 257 long_prefix, bsdcat->getopt_word, match->name, match2->name); 258 return ('?'); 259 } 260 261 /* We've found a unique match; does it need an argument? */ 262 if (match->required) { 263 /* Argument required: get next word if necessary. */ 264 if (bsdcat->argument == NULL) { 265 bsdcat->argument = *bsdcat->argv; 266 if (bsdcat->argument == NULL) { 267 lafe_warnc(0, 268 "Option %s%s requires an argument", 269 long_prefix, match->name); 270 return ('?'); 271 } 272 ++bsdcat->argv; 273 --bsdcat->argc; 274 } 275 } else { 276 /* Argument forbidden: fail if there is one. */ 277 if (bsdcat->argument != NULL) { 278 lafe_warnc(0, 279 "Option %s%s does not allow an argument", 280 long_prefix, match->name); 281 return ('?'); 282 } 283 } 284 return (match->equivalent); 285 } 286 287 return (opt); 288 } 289