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 358088 2020-02-19 01:50:47Z 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' }, 88348607Smm { "exclude-vcs", 0, OPTION_EXCLUDE_VCS }, 89228753Smm { "extract", 0, 'x' }, 90228753Smm { "fast-read", 0, 'q' }, 91315432Smm { "fflags", 0, OPTION_FFLAGS }, 92228753Smm { "file", 1, 'f' }, 93228753Smm { "files-from", 1, 'T' }, 94228753Smm { "format", 1, OPTION_FORMAT }, 95228753Smm { "gid", 1, OPTION_GID }, 96228753Smm { "gname", 1, OPTION_GNAME }, 97248616Smm { "grzip", 0, OPTION_GRZIP }, 98228753Smm { "gunzip", 0, 'z' }, 99228753Smm { "gzip", 0, 'z' }, 100228753Smm { "help", 0, OPTION_HELP }, 101248616Smm { "hfsCompression", 0, OPTION_HFS_COMPRESSION }, 102299529Smm { "ignore-zeros", 0, OPTION_IGNORE_ZEROS }, 103228753Smm { "include", 1, OPTION_INCLUDE }, 104232153Smm { "insecure", 0, 'P' }, 105228753Smm { "interactive", 0, 'w' }, 106228753Smm { "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES }, 107228753Smm { "keep-old-files", 0, 'k' }, 108228753Smm { "list", 0, 't' }, 109248616Smm { "lrzip", 0, OPTION_LRZIP }, 110299529Smm { "lz4", 0, OPTION_LZ4 }, 111232153Smm { "lzip", 0, OPTION_LZIP }, 112228753Smm { "lzma", 0, OPTION_LZMA }, 113248616Smm { "lzop", 0, OPTION_LZOP }, 114315432Smm { "mac-metadata", 0, OPTION_MAC_METADATA }, 115228753Smm { "modification-time", 0, 'm' }, 116228753Smm { "newer", 1, OPTION_NEWER_CTIME }, 117228753Smm { "newer-ctime", 1, OPTION_NEWER_CTIME }, 118228753Smm { "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN }, 119228753Smm { "newer-mtime", 1, OPTION_NEWER_MTIME }, 120228753Smm { "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN }, 121228753Smm { "newer-than", 1, OPTION_NEWER_CTIME_THAN }, 122315432Smm { "no-acls", 0, OPTION_NO_ACLS }, 123315432Smm { "no-fflags", 0, OPTION_NO_FFLAGS }, 124315432Smm { "no-mac-metadata", 0, OPTION_NO_MAC_METADATA }, 125228753Smm { "no-recursion", 0, 'n' }, 126358088Smm { "no-safe-writes", 0, OPTION_NO_SAFE_WRITES }, 127228753Smm { "no-same-owner", 0, OPTION_NO_SAME_OWNER }, 128228753Smm { "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS }, 129315432Smm { "no-xattr", 0, OPTION_NO_XATTRS }, 130315432Smm { "no-xattrs", 0, OPTION_NO_XATTRS }, 131232153Smm { "nodump", 0, OPTION_NODUMP }, 132248616Smm { "nopreserveHFSCompression",0, OPTION_NOPRESERVE_HFS_COMPRESSION }, 133232153Smm { "norecurse", 0, 'n' }, 134228753Smm { "null", 0, OPTION_NULL }, 135228753Smm { "numeric-owner", 0, OPTION_NUMERIC_OWNER }, 136248616Smm { "older", 1, OPTION_OLDER_CTIME }, 137248616Smm { "older-ctime", 1, OPTION_OLDER_CTIME }, 138248616Smm { "older-ctime-than", 1, OPTION_OLDER_CTIME_THAN }, 139248616Smm { "older-mtime", 1, OPTION_OLDER_MTIME }, 140248616Smm { "older-mtime-than", 1, OPTION_OLDER_MTIME_THAN }, 141248616Smm { "older-than", 1, OPTION_OLDER_CTIME_THAN }, 142228753Smm { "one-file-system", 0, OPTION_ONE_FILE_SYSTEM }, 143228753Smm { "options", 1, OPTION_OPTIONS }, 144299529Smm { "passphrase", 1, OPTION_PASSPHRASE }, 145228753Smm { "posix", 0, OPTION_POSIX }, 146228753Smm { "preserve-permissions", 0, 'p' }, 147228753Smm { "read-full-blocks", 0, 'B' }, 148358088Smm { "safe-writes", 0, OPTION_SAFE_WRITES }, 149228753Smm { "same-owner", 0, OPTION_SAME_OWNER }, 150228753Smm { "same-permissions", 0, 'p' }, 151228753Smm { "strip-components", 1, OPTION_STRIP_COMPONENTS }, 152228753Smm { "to-stdout", 0, 'O' }, 153228753Smm { "totals", 0, OPTION_TOTALS }, 154228753Smm { "uid", 1, OPTION_UID }, 155228753Smm { "uname", 1, OPTION_UNAME }, 156228753Smm { "uncompress", 0, 'Z' }, 157228753Smm { "unlink", 0, 'U' }, 158228753Smm { "unlink-first", 0, 'U' }, 159228753Smm { "update", 0, 'u' }, 160228753Smm { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM }, 161248616Smm { "uuencode", 0, OPTION_UUENCODE }, 162228753Smm { "verbose", 0, 'v' }, 163228753Smm { "version", 0, OPTION_VERSION }, 164315432Smm { "xattrs", 0, OPTION_XATTRS }, 165228753Smm { "xz", 0, 'J' }, 166324417Smm { "zstd", 0, OPTION_ZSTD }, 167228753Smm { NULL, 0, 0 } 168228753Smm}; 169228753Smm 170228753Smm/* 171228753Smm * This getopt implementation has two key features that common 172228753Smm * getopt_long() implementations lack. Apart from those, it's a 173228753Smm * straightforward option parser, considerably simplified by not 174228753Smm * needing to support the wealth of exotic getopt_long() features. It 175228753Smm * has, of course, been shamelessly tailored for bsdtar. (If you're 176228753Smm * looking for a generic getopt_long() implementation for your 177228753Smm * project, I recommend Gregory Pietsch's public domain getopt_long() 178228753Smm * implementation.) The two additional features are: 179228753Smm * 180228753Smm * Old-style tar arguments: The original tar implementation treated 181228753Smm * the first argument word as a list of single-character option 182228753Smm * letters. All arguments follow as separate words. For example, 183228753Smm * tar xbf 32 /dev/tape 184228753Smm * Here, the "xbf" is three option letters, "32" is the argument for 185228753Smm * "b" and "/dev/tape" is the argument for "f". We support this usage 186228753Smm * if the first command-line argument does not begin with '-'. We 187228753Smm * also allow regular short and long options to follow, e.g., 188228753Smm * tar xbf 32 /dev/tape -P --format=pax 189228753Smm * 190228753Smm * -W long options: There's an obscure GNU convention (only rarely 191228753Smm * supported even there) that allows "-W option=argument" as an 192228753Smm * alternative way to support long options. This was supported in 193228753Smm * early bsdtar as a way to access long options on platforms that did 194228753Smm * not support getopt_long() and is preserved here for backwards 195228753Smm * compatibility. (Of course, if I'd started with a custom 196228753Smm * command-line parser from the beginning, I would have had normal 197228753Smm * long option support on every platform so that hack wouldn't have 198228753Smm * been necessary. Oh, well. Some mistakes you just have to live 199228753Smm * with.) 200228753Smm * 201228753Smm * TODO: We should be able to use this to pull files and intermingled 202228753Smm * options (such as -C) from the command line in write mode. That 203228753Smm * will require a little rethinking of the argument handling in 204228753Smm * bsdtar.c. 205228753Smm * 206228753Smm * TODO: If we want to support arbitrary command-line options from -T 207228753Smm * input (as GNU tar does), we may need to extend this to handle option 208232153Smm * words from sources other than argv/argc. I'm not really sure if I 209228753Smm * like that feature of GNU tar, so it's certainly not a priority. 210228753Smm */ 211228753Smm 212228753Smmint 213228753Smmbsdtar_getopt(struct bsdtar *bsdtar) 214228753Smm{ 215228753Smm enum { state_start = 0, state_old_tar, state_next_word, 216228753Smm state_short, state_long }; 217228753Smm 218232153Smm const struct bsdtar_option *popt, *match = NULL, *match2 = NULL; 219228753Smm const char *p, *long_prefix = "--"; 220228753Smm size_t optlength; 221228753Smm int opt = '?'; 222228753Smm int required = 0; 223228753Smm 224232153Smm bsdtar->argument = NULL; 225228753Smm 226228753Smm /* First time through, initialize everything. */ 227232153Smm if (bsdtar->getopt_state == state_start) { 228228753Smm /* Skip program name. */ 229228753Smm ++bsdtar->argv; 230228753Smm --bsdtar->argc; 231228753Smm if (*bsdtar->argv == NULL) 232228753Smm return (-1); 233228753Smm /* Decide between "new style" and "old style" arguments. */ 234228753Smm if (bsdtar->argv[0][0] == '-') { 235232153Smm bsdtar->getopt_state = state_next_word; 236228753Smm } else { 237232153Smm bsdtar->getopt_state = state_old_tar; 238232153Smm bsdtar->getopt_word = *bsdtar->argv++; 239228753Smm --bsdtar->argc; 240228753Smm } 241228753Smm } 242228753Smm 243228753Smm /* 244228753Smm * We're parsing old-style tar arguments 245228753Smm */ 246232153Smm if (bsdtar->getopt_state == state_old_tar) { 247228753Smm /* Get the next option character. */ 248232153Smm opt = *bsdtar->getopt_word++; 249228753Smm if (opt == '\0') { 250228753Smm /* New-style args can follow old-style. */ 251232153Smm bsdtar->getopt_state = state_next_word; 252228753Smm } else { 253228753Smm /* See if it takes an argument. */ 254228753Smm p = strchr(short_options, opt); 255228753Smm if (p == NULL) 256228753Smm return ('?'); 257228753Smm if (p[1] == ':') { 258232153Smm bsdtar->argument = *bsdtar->argv; 259232153Smm if (bsdtar->argument == NULL) { 260228753Smm lafe_warnc(0, 261228753Smm "Option %c requires an argument", 262228753Smm opt); 263228753Smm return ('?'); 264228753Smm } 265228753Smm ++bsdtar->argv; 266228753Smm --bsdtar->argc; 267228753Smm } 268228753Smm } 269228753Smm } 270228753Smm 271228753Smm /* 272228753Smm * We're ready to look at the next word in argv. 273228753Smm */ 274232153Smm if (bsdtar->getopt_state == state_next_word) { 275228753Smm /* No more arguments, so no more options. */ 276228753Smm if (bsdtar->argv[0] == NULL) 277228753Smm return (-1); 278228753Smm /* Doesn't start with '-', so no more options. */ 279228753Smm if (bsdtar->argv[0][0] != '-') 280228753Smm return (-1); 281228753Smm /* "--" marks end of options; consume it and return. */ 282228753Smm if (strcmp(bsdtar->argv[0], "--") == 0) { 283228753Smm ++bsdtar->argv; 284228753Smm --bsdtar->argc; 285228753Smm return (-1); 286228753Smm } 287228753Smm /* Get next word for parsing. */ 288232153Smm bsdtar->getopt_word = *bsdtar->argv++; 289228753Smm --bsdtar->argc; 290232153Smm if (bsdtar->getopt_word[1] == '-') { 291228753Smm /* Set up long option parser. */ 292232153Smm bsdtar->getopt_state = state_long; 293232153Smm bsdtar->getopt_word += 2; /* Skip leading '--' */ 294228753Smm } else { 295228753Smm /* Set up short option parser. */ 296232153Smm bsdtar->getopt_state = state_short; 297232153Smm ++bsdtar->getopt_word; /* Skip leading '-' */ 298228753Smm } 299228753Smm } 300228753Smm 301228753Smm /* 302228753Smm * We're parsing a group of POSIX-style single-character options. 303228753Smm */ 304232153Smm if (bsdtar->getopt_state == state_short) { 305228753Smm /* Peel next option off of a group of short options. */ 306232153Smm opt = *bsdtar->getopt_word++; 307228753Smm if (opt == '\0') { 308228753Smm /* End of this group; recurse to get next option. */ 309232153Smm bsdtar->getopt_state = state_next_word; 310228753Smm return bsdtar_getopt(bsdtar); 311228753Smm } 312228753Smm 313228753Smm /* Does this option take an argument? */ 314228753Smm p = strchr(short_options, opt); 315228753Smm if (p == NULL) 316228753Smm return ('?'); 317228753Smm if (p[1] == ':') 318228753Smm required = 1; 319228753Smm 320228753Smm /* If it takes an argument, parse that. */ 321228753Smm if (required) { 322232153Smm /* If arg is run-in, bsdtar->getopt_word already points to it. */ 323232153Smm if (bsdtar->getopt_word[0] == '\0') { 324228753Smm /* Otherwise, pick up the next word. */ 325232153Smm bsdtar->getopt_word = *bsdtar->argv; 326232153Smm if (bsdtar->getopt_word == NULL) { 327228753Smm lafe_warnc(0, 328228753Smm "Option -%c requires an argument", 329228753Smm opt); 330228753Smm return ('?'); 331228753Smm } 332228753Smm ++bsdtar->argv; 333228753Smm --bsdtar->argc; 334228753Smm } 335228753Smm if (opt == 'W') { 336232153Smm bsdtar->getopt_state = state_long; 337228753Smm long_prefix = "-W "; /* For clearer errors. */ 338228753Smm } else { 339232153Smm bsdtar->getopt_state = state_next_word; 340232153Smm bsdtar->argument = bsdtar->getopt_word; 341228753Smm } 342228753Smm } 343228753Smm } 344228753Smm 345228753Smm /* We're reading a long option, including -W long=arg convention. */ 346232153Smm if (bsdtar->getopt_state == state_long) { 347228753Smm /* After this long option, we'll be starting a new word. */ 348232153Smm bsdtar->getopt_state = state_next_word; 349228753Smm 350228753Smm /* Option name ends at '=' if there is one. */ 351232153Smm p = strchr(bsdtar->getopt_word, '='); 352228753Smm if (p != NULL) { 353232153Smm optlength = (size_t)(p - bsdtar->getopt_word); 354232153Smm bsdtar->argument = (char *)(uintptr_t)(p + 1); 355228753Smm } else { 356232153Smm optlength = strlen(bsdtar->getopt_word); 357228753Smm } 358228753Smm 359228753Smm /* Search the table for an unambiguous match. */ 360228753Smm for (popt = tar_longopts; popt->name != NULL; popt++) { 361228753Smm /* Short-circuit if first chars don't match. */ 362232153Smm if (popt->name[0] != bsdtar->getopt_word[0]) 363228753Smm continue; 364228753Smm /* If option is a prefix of name in table, record it.*/ 365232153Smm if (strncmp(bsdtar->getopt_word, popt->name, optlength) == 0) { 366228753Smm match2 = match; /* Record up to two matches. */ 367228753Smm match = popt; 368228753Smm /* If it's an exact match, we're done. */ 369228753Smm if (strlen(popt->name) == optlength) { 370228753Smm match2 = NULL; /* Forget the others. */ 371228753Smm break; 372228753Smm } 373228753Smm } 374228753Smm } 375228753Smm 376228753Smm /* Fail if there wasn't a unique match. */ 377228753Smm if (match == NULL) { 378228753Smm lafe_warnc(0, 379228753Smm "Option %s%s is not supported", 380232153Smm long_prefix, bsdtar->getopt_word); 381228753Smm return ('?'); 382228753Smm } 383228753Smm if (match2 != NULL) { 384228753Smm lafe_warnc(0, 385228753Smm "Ambiguous option %s%s (matches --%s and --%s)", 386232153Smm long_prefix, bsdtar->getopt_word, match->name, match2->name); 387228753Smm return ('?'); 388228753Smm } 389228753Smm 390228753Smm /* We've found a unique match; does it need an argument? */ 391228753Smm if (match->required) { 392228753Smm /* Argument required: get next word if necessary. */ 393232153Smm if (bsdtar->argument == NULL) { 394232153Smm bsdtar->argument = *bsdtar->argv; 395232153Smm if (bsdtar->argument == NULL) { 396228753Smm lafe_warnc(0, 397228753Smm "Option %s%s requires an argument", 398228753Smm long_prefix, match->name); 399228753Smm return ('?'); 400228753Smm } 401228753Smm ++bsdtar->argv; 402228753Smm --bsdtar->argc; 403228753Smm } 404228753Smm } else { 405228753Smm /* Argument forbidden: fail if there is one. */ 406232153Smm if (bsdtar->argument != NULL) { 407228753Smm lafe_warnc(0, 408228753Smm "Option %s%s does not allow an argument", 409228753Smm long_prefix, match->name); 410228753Smm return ('?'); 411228753Smm } 412228753Smm } 413228753Smm return (match->equivalent); 414228753Smm } 415228753Smm 416228753Smm return (opt); 417228753Smm} 418