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