cmdline.c revision 315432
1228753Smm/*- 2228753Smm * Copyright (c) 2003-2008 Tim Kientzle 3228753Smm * All rights reserved. 4228753Smm * 5228753Smm * Redistribution and use in source and binary forms, with or without 6228753Smm * modification, are permitted provided that the following conditions 7228753Smm * are met: 8228753Smm * 1. Redistributions of source code must retain the above copyright 9228753Smm * notice, this list of conditions and the following disclaimer. 10228753Smm * 2. Redistributions in binary form must reproduce the above copyright 11228753Smm * notice, this list of conditions and the following disclaimer in the 12228753Smm * documentation and/or other materials provided with the distribution. 13228753Smm * 14228753Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15228753Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16228753Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17228753Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18228753Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19228753Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20228753Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21228753Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22228753Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23228753Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24228753Smm */ 25228753Smm 26228753Smm/* 27228753Smm * Command line parser for tar. 28228753Smm */ 29228753Smm 30228753Smm#include "bsdtar_platform.h" 31228753Smm__FBSDID("$FreeBSD: stable/11/contrib/libarchive/tar/cmdline.c 315432 2017-03-16 23:07:35Z mm $"); 32228753Smm 33228753Smm#ifdef HAVE_ERRNO_H 34228753Smm#include <errno.h> 35228753Smm#endif 36228753Smm#ifdef HAVE_STDLIB_H 37228753Smm#include <stdlib.h> 38228753Smm#endif 39228753Smm#ifdef HAVE_STRING_H 40228753Smm#include <string.h> 41228753Smm#endif 42228753Smm 43228753Smm#include "bsdtar.h" 44228753Smm#include "err.h" 45228753Smm 46228753Smm/* 47228753Smm * Short options for tar. Please keep this sorted. 48228753Smm */ 49228753Smmstatic const char *short_options 50248616Smm = "aBb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz"; 51228753Smm 52228753Smm/* 53228753Smm * Long options for tar. Please keep this list sorted. 54228753Smm * 55228753Smm * The symbolic names for options that lack a short equivalent are 56228753Smm * defined in bsdtar.h. Also note that so far I've found no need 57228753Smm * to support optional arguments to long options. That would be 58228753Smm * a small change to the code below. 59228753Smm */ 60228753Smm 61232153Smmstatic const struct bsdtar_option { 62228753Smm const char *name; 63228753Smm int required; /* 1 if this option requires an argument. */ 64228753Smm int equivalent; /* Equivalent short option. */ 65228753Smm} tar_longopts[] = { 66228753Smm { "absolute-paths", 0, 'P' }, 67228753Smm { "append", 0, 'r' }, 68315432Smm { "acls", 0, OPTION_ACLS }, 69248616Smm { "auto-compress", 0, 'a' }, 70248616Smm { "b64encode", 0, OPTION_B64ENCODE }, 71228753Smm { "block-size", 1, 'b' }, 72305188Smm { "blocking-factor", 1, 'b' }, 73228753Smm { "bunzip2", 0, 'j' }, 74228753Smm { "bzip", 0, 'j' }, 75228753Smm { "bzip2", 0, 'j' }, 76228753Smm { "cd", 1, 'C' }, 77228753Smm { "check-links", 0, OPTION_CHECK_LINKS }, 78228753Smm { "chroot", 0, OPTION_CHROOT }, 79299529Smm { "clear-nochange-fflags", 0, OPTION_CLEAR_NOCHANGE_FFLAGS }, 80228753Smm { "compress", 0, 'Z' }, 81228753Smm { "confirmation", 0, 'w' }, 82228753Smm { "create", 0, 'c' }, 83228753Smm { "dereference", 0, 'L' }, 84228753Smm { "directory", 1, 'C' }, 85315432Smm { "disable-copyfile", 0, OPTION_NO_MAC_METADATA }, 86228753Smm { "exclude", 1, OPTION_EXCLUDE }, 87228753Smm { "exclude-from", 1, 'X' }, 88228753Smm { "extract", 0, 'x' }, 89228753Smm { "fast-read", 0, 'q' }, 90315432Smm { "fflags", 0, OPTION_FFLAGS }, 91228753Smm { "file", 1, 'f' }, 92228753Smm { "files-from", 1, 'T' }, 93228753Smm { "format", 1, OPTION_FORMAT }, 94228753Smm { "gid", 1, OPTION_GID }, 95228753Smm { "gname", 1, OPTION_GNAME }, 96248616Smm { "grzip", 0, OPTION_GRZIP }, 97228753Smm { "gunzip", 0, 'z' }, 98228753Smm { "gzip", 0, 'z' }, 99228753Smm { "help", 0, OPTION_HELP }, 100248616Smm { "hfsCompression", 0, OPTION_HFS_COMPRESSION }, 101299529Smm { "ignore-zeros", 0, OPTION_IGNORE_ZEROS }, 102228753Smm { "include", 1, OPTION_INCLUDE }, 103232153Smm { "insecure", 0, 'P' }, 104228753Smm { "interactive", 0, 'w' }, 105228753Smm { "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES }, 106228753Smm { "keep-old-files", 0, 'k' }, 107228753Smm { "list", 0, 't' }, 108248616Smm { "lrzip", 0, OPTION_LRZIP }, 109299529Smm { "lz4", 0, OPTION_LZ4 }, 110232153Smm { "lzip", 0, OPTION_LZIP }, 111228753Smm { "lzma", 0, OPTION_LZMA }, 112248616Smm { "lzop", 0, OPTION_LZOP }, 113315432Smm { "mac-metadata", 0, OPTION_MAC_METADATA }, 114228753Smm { "modification-time", 0, 'm' }, 115228753Smm { "newer", 1, OPTION_NEWER_CTIME }, 116228753Smm { "newer-ctime", 1, OPTION_NEWER_CTIME }, 117228753Smm { "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN }, 118228753Smm { "newer-mtime", 1, OPTION_NEWER_MTIME }, 119228753Smm { "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN }, 120228753Smm { "newer-than", 1, OPTION_NEWER_CTIME_THAN }, 121315432Smm { "no-acls", 0, OPTION_NO_ACLS }, 122315432Smm { "no-fflags", 0, OPTION_NO_FFLAGS }, 123315432Smm { "no-mac-metadata", 0, OPTION_NO_MAC_METADATA }, 124228753Smm { "no-recursion", 0, 'n' }, 125228753Smm { "no-same-owner", 0, OPTION_NO_SAME_OWNER }, 126228753Smm { "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS }, 127315432Smm { "no-xattr", 0, OPTION_NO_XATTRS }, 128315432Smm { "no-xattrs", 0, OPTION_NO_XATTRS }, 129232153Smm { "nodump", 0, OPTION_NODUMP }, 130248616Smm { "nopreserveHFSCompression",0, OPTION_NOPRESERVE_HFS_COMPRESSION }, 131232153Smm { "norecurse", 0, 'n' }, 132228753Smm { "null", 0, OPTION_NULL }, 133228753Smm { "numeric-owner", 0, OPTION_NUMERIC_OWNER }, 134248616Smm { "older", 1, OPTION_OLDER_CTIME }, 135248616Smm { "older-ctime", 1, OPTION_OLDER_CTIME }, 136248616Smm { "older-ctime-than", 1, OPTION_OLDER_CTIME_THAN }, 137248616Smm { "older-mtime", 1, OPTION_OLDER_MTIME }, 138248616Smm { "older-mtime-than", 1, OPTION_OLDER_MTIME_THAN }, 139248616Smm { "older-than", 1, OPTION_OLDER_CTIME_THAN }, 140228753Smm { "one-file-system", 0, OPTION_ONE_FILE_SYSTEM }, 141228753Smm { "options", 1, OPTION_OPTIONS }, 142299529Smm { "passphrase", 1, OPTION_PASSPHRASE }, 143228753Smm { "posix", 0, OPTION_POSIX }, 144228753Smm { "preserve-permissions", 0, 'p' }, 145228753Smm { "read-full-blocks", 0, 'B' }, 146228753Smm { "same-owner", 0, OPTION_SAME_OWNER }, 147228753Smm { "same-permissions", 0, 'p' }, 148228753Smm { "strip-components", 1, OPTION_STRIP_COMPONENTS }, 149228753Smm { "to-stdout", 0, 'O' }, 150228753Smm { "totals", 0, OPTION_TOTALS }, 151228753Smm { "uid", 1, OPTION_UID }, 152228753Smm { "uname", 1, OPTION_UNAME }, 153228753Smm { "uncompress", 0, 'Z' }, 154228753Smm { "unlink", 0, 'U' }, 155228753Smm { "unlink-first", 0, 'U' }, 156228753Smm { "update", 0, 'u' }, 157228753Smm { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM }, 158248616Smm { "uuencode", 0, OPTION_UUENCODE }, 159228753Smm { "verbose", 0, 'v' }, 160228753Smm { "version", 0, OPTION_VERSION }, 161315432Smm { "xattrs", 0, OPTION_XATTRS }, 162228753Smm { "xz", 0, 'J' }, 163228753Smm { NULL, 0, 0 } 164228753Smm}; 165228753Smm 166228753Smm/* 167228753Smm * This getopt implementation has two key features that common 168228753Smm * getopt_long() implementations lack. Apart from those, it's a 169228753Smm * straightforward option parser, considerably simplified by not 170228753Smm * needing to support the wealth of exotic getopt_long() features. It 171228753Smm * has, of course, been shamelessly tailored for bsdtar. (If you're 172228753Smm * looking for a generic getopt_long() implementation for your 173228753Smm * project, I recommend Gregory Pietsch's public domain getopt_long() 174228753Smm * implementation.) The two additional features are: 175228753Smm * 176228753Smm * Old-style tar arguments: The original tar implementation treated 177228753Smm * the first argument word as a list of single-character option 178228753Smm * letters. All arguments follow as separate words. For example, 179228753Smm * tar xbf 32 /dev/tape 180228753Smm * Here, the "xbf" is three option letters, "32" is the argument for 181228753Smm * "b" and "/dev/tape" is the argument for "f". We support this usage 182228753Smm * if the first command-line argument does not begin with '-'. We 183228753Smm * also allow regular short and long options to follow, e.g., 184228753Smm * tar xbf 32 /dev/tape -P --format=pax 185228753Smm * 186228753Smm * -W long options: There's an obscure GNU convention (only rarely 187228753Smm * supported even there) that allows "-W option=argument" as an 188228753Smm * alternative way to support long options. This was supported in 189228753Smm * early bsdtar as a way to access long options on platforms that did 190228753Smm * not support getopt_long() and is preserved here for backwards 191228753Smm * compatibility. (Of course, if I'd started with a custom 192228753Smm * command-line parser from the beginning, I would have had normal 193228753Smm * long option support on every platform so that hack wouldn't have 194228753Smm * been necessary. Oh, well. Some mistakes you just have to live 195228753Smm * with.) 196228753Smm * 197228753Smm * TODO: We should be able to use this to pull files and intermingled 198228753Smm * options (such as -C) from the command line in write mode. That 199228753Smm * will require a little rethinking of the argument handling in 200228753Smm * bsdtar.c. 201228753Smm * 202228753Smm * TODO: If we want to support arbitrary command-line options from -T 203228753Smm * input (as GNU tar does), we may need to extend this to handle option 204232153Smm * words from sources other than argv/argc. I'm not really sure if I 205228753Smm * like that feature of GNU tar, so it's certainly not a priority. 206228753Smm */ 207228753Smm 208228753Smmint 209228753Smmbsdtar_getopt(struct bsdtar *bsdtar) 210228753Smm{ 211228753Smm enum { state_start = 0, state_old_tar, state_next_word, 212228753Smm state_short, state_long }; 213228753Smm 214232153Smm const struct bsdtar_option *popt, *match = NULL, *match2 = NULL; 215228753Smm const char *p, *long_prefix = "--"; 216228753Smm size_t optlength; 217228753Smm int opt = '?'; 218228753Smm int required = 0; 219228753Smm 220232153Smm bsdtar->argument = NULL; 221228753Smm 222228753Smm /* First time through, initialize everything. */ 223232153Smm if (bsdtar->getopt_state == state_start) { 224228753Smm /* Skip program name. */ 225228753Smm ++bsdtar->argv; 226228753Smm --bsdtar->argc; 227228753Smm if (*bsdtar->argv == NULL) 228228753Smm return (-1); 229228753Smm /* Decide between "new style" and "old style" arguments. */ 230228753Smm if (bsdtar->argv[0][0] == '-') { 231232153Smm bsdtar->getopt_state = state_next_word; 232228753Smm } else { 233232153Smm bsdtar->getopt_state = state_old_tar; 234232153Smm bsdtar->getopt_word = *bsdtar->argv++; 235228753Smm --bsdtar->argc; 236228753Smm } 237228753Smm } 238228753Smm 239228753Smm /* 240228753Smm * We're parsing old-style tar arguments 241228753Smm */ 242232153Smm if (bsdtar->getopt_state == state_old_tar) { 243228753Smm /* Get the next option character. */ 244232153Smm opt = *bsdtar->getopt_word++; 245228753Smm if (opt == '\0') { 246228753Smm /* New-style args can follow old-style. */ 247232153Smm bsdtar->getopt_state = state_next_word; 248228753Smm } else { 249228753Smm /* See if it takes an argument. */ 250228753Smm p = strchr(short_options, opt); 251228753Smm if (p == NULL) 252228753Smm return ('?'); 253228753Smm if (p[1] == ':') { 254232153Smm bsdtar->argument = *bsdtar->argv; 255232153Smm if (bsdtar->argument == NULL) { 256228753Smm lafe_warnc(0, 257228753Smm "Option %c requires an argument", 258228753Smm opt); 259228753Smm return ('?'); 260228753Smm } 261228753Smm ++bsdtar->argv; 262228753Smm --bsdtar->argc; 263228753Smm } 264228753Smm } 265228753Smm } 266228753Smm 267228753Smm /* 268228753Smm * We're ready to look at the next word in argv. 269228753Smm */ 270232153Smm if (bsdtar->getopt_state == state_next_word) { 271228753Smm /* No more arguments, so no more options. */ 272228753Smm if (bsdtar->argv[0] == NULL) 273228753Smm return (-1); 274228753Smm /* Doesn't start with '-', so no more options. */ 275228753Smm if (bsdtar->argv[0][0] != '-') 276228753Smm return (-1); 277228753Smm /* "--" marks end of options; consume it and return. */ 278228753Smm if (strcmp(bsdtar->argv[0], "--") == 0) { 279228753Smm ++bsdtar->argv; 280228753Smm --bsdtar->argc; 281228753Smm return (-1); 282228753Smm } 283228753Smm /* Get next word for parsing. */ 284232153Smm bsdtar->getopt_word = *bsdtar->argv++; 285228753Smm --bsdtar->argc; 286232153Smm if (bsdtar->getopt_word[1] == '-') { 287228753Smm /* Set up long option parser. */ 288232153Smm bsdtar->getopt_state = state_long; 289232153Smm bsdtar->getopt_word += 2; /* Skip leading '--' */ 290228753Smm } else { 291228753Smm /* Set up short option parser. */ 292232153Smm bsdtar->getopt_state = state_short; 293232153Smm ++bsdtar->getopt_word; /* Skip leading '-' */ 294228753Smm } 295228753Smm } 296228753Smm 297228753Smm /* 298228753Smm * We're parsing a group of POSIX-style single-character options. 299228753Smm */ 300232153Smm if (bsdtar->getopt_state == state_short) { 301228753Smm /* Peel next option off of a group of short options. */ 302232153Smm opt = *bsdtar->getopt_word++; 303228753Smm if (opt == '\0') { 304228753Smm /* End of this group; recurse to get next option. */ 305232153Smm bsdtar->getopt_state = state_next_word; 306228753Smm return bsdtar_getopt(bsdtar); 307228753Smm } 308228753Smm 309228753Smm /* Does this option take an argument? */ 310228753Smm p = strchr(short_options, opt); 311228753Smm if (p == NULL) 312228753Smm return ('?'); 313228753Smm if (p[1] == ':') 314228753Smm required = 1; 315228753Smm 316228753Smm /* If it takes an argument, parse that. */ 317228753Smm if (required) { 318232153Smm /* If arg is run-in, bsdtar->getopt_word already points to it. */ 319232153Smm if (bsdtar->getopt_word[0] == '\0') { 320228753Smm /* Otherwise, pick up the next word. */ 321232153Smm bsdtar->getopt_word = *bsdtar->argv; 322232153Smm if (bsdtar->getopt_word == NULL) { 323228753Smm lafe_warnc(0, 324228753Smm "Option -%c requires an argument", 325228753Smm opt); 326228753Smm return ('?'); 327228753Smm } 328228753Smm ++bsdtar->argv; 329228753Smm --bsdtar->argc; 330228753Smm } 331228753Smm if (opt == 'W') { 332232153Smm bsdtar->getopt_state = state_long; 333228753Smm long_prefix = "-W "; /* For clearer errors. */ 334228753Smm } else { 335232153Smm bsdtar->getopt_state = state_next_word; 336232153Smm bsdtar->argument = bsdtar->getopt_word; 337228753Smm } 338228753Smm } 339228753Smm } 340228753Smm 341228753Smm /* We're reading a long option, including -W long=arg convention. */ 342232153Smm if (bsdtar->getopt_state == state_long) { 343228753Smm /* After this long option, we'll be starting a new word. */ 344232153Smm bsdtar->getopt_state = state_next_word; 345228753Smm 346228753Smm /* Option name ends at '=' if there is one. */ 347232153Smm p = strchr(bsdtar->getopt_word, '='); 348228753Smm if (p != NULL) { 349232153Smm optlength = (size_t)(p - bsdtar->getopt_word); 350232153Smm bsdtar->argument = (char *)(uintptr_t)(p + 1); 351228753Smm } else { 352232153Smm optlength = strlen(bsdtar->getopt_word); 353228753Smm } 354228753Smm 355228753Smm /* Search the table for an unambiguous match. */ 356228753Smm for (popt = tar_longopts; popt->name != NULL; popt++) { 357228753Smm /* Short-circuit if first chars don't match. */ 358232153Smm if (popt->name[0] != bsdtar->getopt_word[0]) 359228753Smm continue; 360228753Smm /* If option is a prefix of name in table, record it.*/ 361232153Smm if (strncmp(bsdtar->getopt_word, popt->name, optlength) == 0) { 362228753Smm match2 = match; /* Record up to two matches. */ 363228753Smm match = popt; 364228753Smm /* If it's an exact match, we're done. */ 365228753Smm if (strlen(popt->name) == optlength) { 366228753Smm match2 = NULL; /* Forget the others. */ 367228753Smm break; 368228753Smm } 369228753Smm } 370228753Smm } 371228753Smm 372228753Smm /* Fail if there wasn't a unique match. */ 373228753Smm if (match == NULL) { 374228753Smm lafe_warnc(0, 375228753Smm "Option %s%s is not supported", 376232153Smm long_prefix, bsdtar->getopt_word); 377228753Smm return ('?'); 378228753Smm } 379228753Smm if (match2 != NULL) { 380228753Smm lafe_warnc(0, 381228753Smm "Ambiguous option %s%s (matches --%s and --%s)", 382232153Smm long_prefix, bsdtar->getopt_word, match->name, match2->name); 383228753Smm return ('?'); 384228753Smm } 385228753Smm 386228753Smm /* We've found a unique match; does it need an argument? */ 387228753Smm if (match->required) { 388228753Smm /* Argument required: get next word if necessary. */ 389232153Smm if (bsdtar->argument == NULL) { 390232153Smm bsdtar->argument = *bsdtar->argv; 391232153Smm if (bsdtar->argument == NULL) { 392228753Smm lafe_warnc(0, 393228753Smm "Option %s%s requires an argument", 394228753Smm long_prefix, match->name); 395228753Smm return ('?'); 396228753Smm } 397228753Smm ++bsdtar->argv; 398228753Smm --bsdtar->argc; 399228753Smm } 400228753Smm } else { 401228753Smm /* Argument forbidden: fail if there is one. */ 402232153Smm if (bsdtar->argument != NULL) { 403228753Smm lafe_warnc(0, 404228753Smm "Option %s%s does not allow an argument", 405228753Smm long_prefix, match->name); 406228753Smm return ('?'); 407228753Smm } 408228753Smm } 409228753Smm return (match->equivalent); 410228753Smm } 411228753Smm 412228753Smm return (opt); 413228753Smm} 414