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 "bsdtar_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 "bsdtar.h" 44 45 /* 46 * Short options for tar. Please keep this sorted. 47 */ 48 static const char *short_options 49 = "Bb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz"; 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 bsdtar.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 struct 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 { "absolute-paths", 0, 'P' }, 66 { "append", 0, 'r' }, 67 { "block-size", 1, 'b' }, 68 { "bunzip2", 0, 'j' }, 69 { "bzip", 0, 'j' }, 70 { "bzip2", 0, 'j' }, 71 { "cd", 1, 'C' }, 72 { "check-links", 0, OPTION_CHECK_LINKS }, 73 { "chroot", 0, OPTION_CHROOT }, 74 { "compress", 0, 'Z' }, 75 { "confirmation", 0, 'w' }, 76 { "create", 0, 'c' }, 77 { "dereference", 0, 'L' }, 78 { "directory", 1, 'C' }, 79 { "exclude", 1, OPTION_EXCLUDE }, 80 { "exclude-from", 1, 'X' }, 81 { "extract", 0, 'x' }, 82 { "fast-read", 0, 'q' }, 83 { "file", 1, 'f' }, 84 { "files-from", 1, 'T' }, 85 { "format", 1, OPTION_FORMAT }, 86 { "options", 1, OPTION_OPTIONS }, 87 { "gunzip", 0, 'z' }, 88 { "gzip", 0, 'z' }, 89 { "help", 0, OPTION_HELP }, 90 { "include", 1, OPTION_INCLUDE }, 91 { "interactive", 0, 'w' }, 92 { "insecure", 0, 'P' }, 93 { "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES }, 94 { "keep-old-files", 0, 'k' }, 95 { "list", 0, 't' }, 96 { "lzma", 0, OPTION_LZMA }, 97 { "modification-time", 0, 'm' }, 98 { "newer", 1, OPTION_NEWER_CTIME }, 99 { "newer-ctime", 1, OPTION_NEWER_CTIME }, 100 { "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN }, 101 { "newer-mtime", 1, OPTION_NEWER_MTIME }, 102 { "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN }, 103 { "newer-than", 1, OPTION_NEWER_CTIME_THAN }, 104 { "nodump", 0, OPTION_NODUMP }, 105 { "norecurse", 0, 'n' }, 106 { "no-recursion", 0, 'n' }, 107 { "no-same-owner", 0, OPTION_NO_SAME_OWNER }, 108 { "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS }, 109 { "null", 0, OPTION_NULL }, 110 { "numeric-owner", 0, OPTION_NUMERIC_OWNER }, 111 { "one-file-system", 0, OPTION_ONE_FILE_SYSTEM }, 112 { "posix", 0, OPTION_POSIX }, 113 { "preserve-permissions", 0, 'p' }, 114 { "read-full-blocks", 0, 'B' }, 115 { "same-owner", 0, OPTION_SAME_OWNER }, 116 { "same-permissions", 0, 'p' }, 117 { "strip-components", 1, OPTION_STRIP_COMPONENTS }, 118 { "to-stdout", 0, 'O' }, 119 { "totals", 0, OPTION_TOTALS }, 120 { "uncompress", 0, 'Z' }, 121 { "unlink", 0, 'U' }, 122 { "unlink-first", 0, 'U' }, 123 { "update", 0, 'u' }, 124 { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM }, 125 { "verbose", 0, 'v' }, 126 { "version", 0, OPTION_VERSION }, 127 { "xz", 0, 'J' }, 128 { NULL, 0, 0 } 129 }; 130 131 /* 132 * This getopt implementation has two key features that common 133 * getopt_long() implementations lack. Apart from those, it's a 134 * straightforward option parser, considerably simplified by not 135 * needing to support the wealth of exotic getopt_long() features. It 136 * has, of course, been shamelessly tailored for bsdtar. (If you're 137 * looking for a generic getopt_long() implementation for your 138 * project, I recommend Gregory Pietsch's public domain getopt_long() 139 * implementation.) The two additional features are: 140 * 141 * Old-style tar arguments: The original tar implementation treated 142 * the first argument word as a list of single-character option 143 * letters. All arguments follow as separate words. For example, 144 * tar xbf 32 /dev/tape 145 * Here, the "xbf" is three option letters, "32" is the argument for 146 * "b" and "/dev/tape" is the argument for "f". We support this usage 147 * if the first command-line argument does not begin with '-'. We 148 * also allow regular short and long options to follow, e.g., 149 * tar xbf 32 /dev/tape -P --format=pax 150 * 151 * -W long options: There's an obscure GNU convention (only rarely 152 * supported even there) that allows "-W option=argument" as an 153 * alternative way to support long options. This was supported in 154 * early bsdtar as a way to access long options on platforms that did 155 * not support getopt_long() and is preserved here for backwards 156 * compatibility. (Of course, if I'd started with a custom 157 * command-line parser from the beginning, I would have had normal 158 * long option support on every platform so that hack wouldn't have 159 * been necessary. Oh, well. Some mistakes you just have to live 160 * with.) 161 * 162 * TODO: We should be able to use this to pull files and intermingled 163 * options (such as -C) from the command line in write mode. That 164 * will require a little rethinking of the argument handling in 165 * bsdtar.c. 166 * 167 * TODO: If we want to support arbitrary command-line options from -T 168 * input (as GNU tar does), we may need to extend this to handle option 169 * words from sources other than argv/arc. I'm not really sure if I 170 * like that feature of GNU tar, so it's certainly not a priority. 171 */ 172 173 int 174 bsdtar_getopt(struct bsdtar *bsdtar) 175 { 176 enum { state_start = 0, state_old_tar, state_next_word, 177 state_short, state_long }; 178 static int state = state_start; 179 static char *opt_word; 180 181 const struct option *popt, *match = NULL, *match2 = NULL; 182 const char *p, *long_prefix = "--"; 183 size_t optlength; 184 int opt = '?'; 185 int required = 0; 186 187 bsdtar->optarg = NULL; 188 189 /* First time through, initialize everything. */ 190 if (state == state_start) { 191 /* Skip program name. */ 192 ++bsdtar->argv; 193 --bsdtar->argc; 194 if (*bsdtar->argv == NULL) 195 return (-1); 196 /* Decide between "new style" and "old style" arguments. */ 197 if (bsdtar->argv[0][0] == '-') { 198 state = state_next_word; 199 } else { 200 state = state_old_tar; 201 opt_word = *bsdtar->argv++; 202 --bsdtar->argc; 203 } 204 } 205 206 /* 207 * We're parsing old-style tar arguments 208 */ 209 if (state == state_old_tar) { 210 /* Get the next option character. */ 211 opt = *opt_word++; 212 if (opt == '\0') { 213 /* New-style args can follow old-style. */ 214 state = state_next_word; 215 } else { 216 /* See if it takes an argument. */ 217 p = strchr(short_options, opt); 218 if (p == NULL) 219 return ('?'); 220 if (p[1] == ':') { 221 bsdtar->optarg = *bsdtar->argv; 222 if (bsdtar->optarg == NULL) { 223 bsdtar_warnc(bsdtar, 0, 224 "Option %c requires an argument", 225 opt); 226 return ('?'); 227 } 228 ++bsdtar->argv; 229 --bsdtar->argc; 230 } 231 } 232 } 233 234 /* 235 * We're ready to look at the next word in argv. 236 */ 237 if (state == state_next_word) { 238 /* No more arguments, so no more options. */ 239 if (bsdtar->argv[0] == NULL) 240 return (-1); 241 /* Doesn't start with '-', so no more options. */ 242 if (bsdtar->argv[0][0] != '-') 243 return (-1); 244 /* "--" marks end of options; consume it and return. */ 245 if (strcmp(bsdtar->argv[0], "--") == 0) { 246 ++bsdtar->argv; 247 --bsdtar->argc; 248 return (-1); 249 } 250 /* Get next word for parsing. */ 251 opt_word = *bsdtar->argv++; 252 --bsdtar->argc; 253 if (opt_word[1] == '-') { 254 /* Set up long option parser. */ 255 state = state_long; 256 opt_word += 2; /* Skip leading '--' */ 257 } else { 258 /* Set up short option parser. */ 259 state = state_short; 260 ++opt_word; /* Skip leading '-' */ 261 } 262 } 263 264 /* 265 * We're parsing a group of POSIX-style single-character options. 266 */ 267 if (state == state_short) { 268 /* Peel next option off of a group of short options. */ 269 opt = *opt_word++; 270 if (opt == '\0') { 271 /* End of this group; recurse to get next option. */ 272 state = state_next_word; 273 return bsdtar_getopt(bsdtar); 274 } 275 276 /* Does this option take an argument? */ 277 p = strchr(short_options, opt); 278 if (p == NULL) 279 return ('?'); 280 if (p[1] == ':') 281 required = 1; 282 283 /* If it takes an argument, parse that. */ 284 if (required) { 285 /* If arg is run-in, opt_word already points to it. */ 286 if (opt_word[0] == '\0') { 287 /* Otherwise, pick up the next word. */ 288 opt_word = *bsdtar->argv; 289 if (opt_word == NULL) { 290 bsdtar_warnc(bsdtar, 0, 291 "Option -%c requires an argument", 292 opt); 293 return ('?'); 294 } 295 ++bsdtar->argv; 296 --bsdtar->argc; 297 } 298 if (opt == 'W') { 299 state = state_long; 300 long_prefix = "-W "; /* For clearer errors. */ 301 } else { 302 state = state_next_word; 303 bsdtar->optarg = opt_word; 304 } 305 } 306 } 307 308 /* We're reading a long option, including -W long=arg convention. */ 309 if (state == state_long) { 310 /* After this long option, we'll be starting a new word. */ 311 state = state_next_word; 312 313 /* Option name ends at '=' if there is one. */ 314 p = strchr(opt_word, '='); 315 if (p != NULL) { 316 optlength = (size_t)(p - opt_word); 317 bsdtar->optarg = (char *)(uintptr_t)(p + 1); 318 } else { 319 optlength = strlen(opt_word); 320 } 321 322 /* Search the table for an unambiguous match. */ 323 for (popt = tar_longopts; popt->name != NULL; popt++) { 324 /* Short-circuit if first chars don't match. */ 325 if (popt->name[0] != opt_word[0]) 326 continue; 327 /* If option is a prefix of name in table, record it.*/ 328 if (strncmp(opt_word, popt->name, optlength) == 0) { 329 match2 = match; /* Record up to two matches. */ 330 match = popt; 331 /* If it's an exact match, we're done. */ 332 if (strlen(popt->name) == optlength) { 333 match2 = NULL; /* Forget the others. */ 334 break; 335 } 336 } 337 } 338 339 /* Fail if there wasn't a unique match. */ 340 if (match == NULL) { 341 bsdtar_warnc(bsdtar, 0, 342 "Option %s%s is not supported", 343 long_prefix, opt_word); 344 return ('?'); 345 } 346 if (match2 != NULL) { 347 bsdtar_warnc(bsdtar, 0, 348 "Ambiguous option %s%s (matches --%s and --%s)", 349 long_prefix, opt_word, match->name, match2->name); 350 return ('?'); 351 } 352 353 /* We've found a unique match; does it need an argument? */ 354 if (match->required) { 355 /* Argument required: get next word if necessary. */ 356 if (bsdtar->optarg == NULL) { 357 bsdtar->optarg = *bsdtar->argv; 358 if (bsdtar->optarg == NULL) { 359 bsdtar_warnc(bsdtar, 0, 360 "Option %s%s requires an argument", 361 long_prefix, match->name); 362 return ('?'); 363 } 364 ++bsdtar->argv; 365 --bsdtar->argc; 366 } 367 } else { 368 /* Argument forbidden: fail if there is one. */ 369 if (bsdtar->optarg != NULL) { 370 bsdtar_warnc(bsdtar, 0, 371 "Option %s%s does not allow an argument", 372 long_prefix, match->name); 373 return ('?'); 374 } 375 } 376 return (match->equivalent); 377 } 378 379 return (opt); 380 } 381