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