cmdline.c revision 315433
1251881Speter/*- 2251881Speter * Copyright (c) 2003-2008 Tim Kientzle 3251881Speter * All rights reserved. 4251881Speter * 5251881Speter * Redistribution and use in source and binary forms, with or without 6251881Speter * modification, are permitted provided that the following conditions 7251881Speter * are met: 8251881Speter * 1. Redistributions of source code must retain the above copyright 9251881Speter * notice, this list of conditions and the following disclaimer. 10251881Speter * 2. Redistributions in binary form must reproduce the above copyright 11251881Speter * notice, this list of conditions and the following disclaimer in the 12251881Speter * documentation and/or other materials provided with the distribution. 13251881Speter * 14251881Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15251881Speter * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16251881Speter * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17251881Speter * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18251881Speter * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19251881Speter * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20251881Speter * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21251881Speter * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22251881Speter * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23299742Sdim * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24251881Speter */ 25251881Speter 26251881Speter/* 27299742Sdim * Command line parser for tar. 28299742Sdim */ 29299742Sdim 30251881Speter#include "bsdtar_platform.h" 31251881Speter__FBSDID("$FreeBSD: stable/10/contrib/libarchive/tar/cmdline.c 315433 2017-03-16 23:08:18Z mm $"); 32299742Sdim 33299742Sdim#ifdef HAVE_ERRNO_H 34251881Speter#include <errno.h> 35251881Speter#endif 36251881Speter#ifdef HAVE_STDLIB_H 37299742Sdim#include <stdlib.h> 38251881Speter#endif 39299742Sdim#ifdef HAVE_STRING_H 40251881Speter#include <string.h> 41299742Sdim#endif 42299742Sdim 43299742Sdim#include "bsdtar.h" 44299742Sdim#include "err.h" 45251881Speter 46299742Sdim/* 47299742Sdim * Short options for tar. Please keep this sorted. 48251881Speter */ 49251881Speterstatic const char *short_options 50251881Speter = "aBb:C:cf:HhI:JjkLlmnOoPpqrSs:T:tUuvW:wX:xyZz"; 51251881Speter 52251881Speter/* 53251881Speter * Long options for tar. Please keep this list sorted. 54251881Speter * 55251881Speter * The symbolic names for options that lack a short equivalent are 56251881Speter * defined in bsdtar.h. Also note that so far I've found no need 57251881Speter * to support optional arguments to long options. That would be 58251881Speter * a small change to the code below. 59251881Speter */ 60251881Speter 61251881Speterstatic const struct bsdtar_option { 62251881Speter const char *name; 63251881Speter int required; /* 1 if this option requires an argument. */ 64251881Speter int equivalent; /* Equivalent short option. */ 65251881Speter} tar_longopts[] = { 66251881Speter { "absolute-paths", 0, 'P' }, 67251881Speter { "append", 0, 'r' }, 68251881Speter { "acls", 0, OPTION_ACLS }, 69251881Speter { "auto-compress", 0, 'a' }, 70251881Speter { "b64encode", 0, OPTION_B64ENCODE }, 71251881Speter { "block-size", 1, 'b' }, 72251881Speter { "blocking-factor", 1, 'b' }, 73251881Speter { "bunzip2", 0, 'j' }, 74251881Speter { "bzip", 0, 'j' }, 75251881Speter { "bzip2", 0, 'j' }, 76251881Speter { "cd", 1, 'C' }, 77251881Speter { "check-links", 0, OPTION_CHECK_LINKS }, 78251881Speter { "chroot", 0, OPTION_CHROOT }, 79251881Speter { "clear-nochange-fflags", 0, OPTION_CLEAR_NOCHANGE_FFLAGS }, 80251881Speter { "compress", 0, 'Z' }, 81251881Speter { "confirmation", 0, 'w' }, 82251881Speter { "create", 0, 'c' }, 83251881Speter { "dereference", 0, 'L' }, 84251881Speter { "directory", 1, 'C' }, 85251881Speter { "disable-copyfile", 0, OPTION_NO_MAC_METADATA }, 86251881Speter { "exclude", 1, OPTION_EXCLUDE }, 87251881Speter { "exclude-from", 1, 'X' }, 88251881Speter { "extract", 0, 'x' }, 89251881Speter { "fast-read", 0, 'q' }, 90299742Sdim { "fflags", 0, OPTION_FFLAGS }, 91251881Speter { "file", 1, 'f' }, 92251881Speter { "files-from", 1, 'T' }, 93251881Speter { "format", 1, OPTION_FORMAT }, 94251881Speter { "gid", 1, OPTION_GID }, 95251881Speter { "gname", 1, OPTION_GNAME }, 96251881Speter { "grzip", 0, OPTION_GRZIP }, 97251881Speter { "gunzip", 0, 'z' }, 98251881Speter { "gzip", 0, 'z' }, 99251881Speter { "help", 0, OPTION_HELP }, 100251881Speter { "hfsCompression", 0, OPTION_HFS_COMPRESSION }, 101251881Speter { "ignore-zeros", 0, OPTION_IGNORE_ZEROS }, 102251881Speter { "include", 1, OPTION_INCLUDE }, 103251881Speter { "insecure", 0, 'P' }, 104251881Speter { "interactive", 0, 'w' }, 105251881Speter { "keep-newer-files", 0, OPTION_KEEP_NEWER_FILES }, 106251881Speter { "keep-old-files", 0, 'k' }, 107251881Speter { "list", 0, 't' }, 108251881Speter { "lrzip", 0, OPTION_LRZIP }, 109251881Speter { "lz4", 0, OPTION_LZ4 }, 110251881Speter { "lzip", 0, OPTION_LZIP }, 111251881Speter { "lzma", 0, OPTION_LZMA }, 112251881Speter { "lzop", 0, OPTION_LZOP }, 113299742Sdim { "mac-metadata", 0, OPTION_MAC_METADATA }, 114299742Sdim { "modification-time", 0, 'm' }, 115299742Sdim { "newer", 1, OPTION_NEWER_CTIME }, 116299742Sdim { "newer-ctime", 1, OPTION_NEWER_CTIME }, 117299742Sdim { "newer-ctime-than", 1, OPTION_NEWER_CTIME_THAN }, 118251881Speter { "newer-mtime", 1, OPTION_NEWER_MTIME }, 119299742Sdim { "newer-mtime-than", 1, OPTION_NEWER_MTIME_THAN }, 120251881Speter { "newer-than", 1, OPTION_NEWER_CTIME_THAN }, 121251881Speter { "no-acls", 0, OPTION_NO_ACLS }, 122299742Sdim { "no-fflags", 0, OPTION_NO_FFLAGS }, 123299742Sdim { "no-mac-metadata", 0, OPTION_NO_MAC_METADATA }, 124299742Sdim { "no-recursion", 0, 'n' }, 125299742Sdim { "no-same-owner", 0, OPTION_NO_SAME_OWNER }, 126299742Sdim { "no-same-permissions", 0, OPTION_NO_SAME_PERMISSIONS }, 127251881Speter { "no-xattr", 0, OPTION_NO_XATTRS }, 128299742Sdim { "no-xattrs", 0, OPTION_NO_XATTRS }, 129299742Sdim { "nodump", 0, OPTION_NODUMP }, 130299742Sdim { "nopreserveHFSCompression",0, OPTION_NOPRESERVE_HFS_COMPRESSION }, 131251881Speter { "norecurse", 0, 'n' }, 132251881Speter { "null", 0, OPTION_NULL }, 133299742Sdim { "numeric-owner", 0, OPTION_NUMERIC_OWNER }, 134299742Sdim { "older", 1, OPTION_OLDER_CTIME }, 135251881Speter { "older-ctime", 1, OPTION_OLDER_CTIME }, 136299742Sdim { "older-ctime-than", 1, OPTION_OLDER_CTIME_THAN }, 137299742Sdim { "older-mtime", 1, OPTION_OLDER_MTIME }, 138299742Sdim { "older-mtime-than", 1, OPTION_OLDER_MTIME_THAN }, 139299742Sdim { "older-than", 1, OPTION_OLDER_CTIME_THAN }, 140299742Sdim { "one-file-system", 0, OPTION_ONE_FILE_SYSTEM }, 141299742Sdim { "options", 1, OPTION_OPTIONS }, 142299742Sdim { "passphrase", 1, OPTION_PASSPHRASE }, 143299742Sdim { "posix", 0, OPTION_POSIX }, 144299742Sdim { "preserve-permissions", 0, 'p' }, 145299742Sdim { "read-full-blocks", 0, 'B' }, 146299742Sdim { "same-owner", 0, OPTION_SAME_OWNER }, 147299742Sdim { "same-permissions", 0, 'p' }, 148251881Speter { "strip-components", 1, OPTION_STRIP_COMPONENTS }, 149299742Sdim { "to-stdout", 0, 'O' }, 150299742Sdim { "totals", 0, OPTION_TOTALS }, 151251881Speter { "uid", 1, OPTION_UID }, 152299742Sdim { "uname", 1, OPTION_UNAME }, 153299742Sdim { "uncompress", 0, 'Z' }, 154299742Sdim { "unlink", 0, 'U' }, 155251881Speter { "unlink-first", 0, 'U' }, 156299742Sdim { "update", 0, 'u' }, 157299742Sdim { "use-compress-program", 1, OPTION_USE_COMPRESS_PROGRAM }, 158251881Speter { "uuencode", 0, OPTION_UUENCODE }, 159299742Sdim { "verbose", 0, 'v' }, 160299742Sdim { "version", 0, OPTION_VERSION }, 161251881Speter { "xattrs", 0, OPTION_XATTRS }, 162299742Sdim { "xz", 0, 'J' }, 163299742Sdim { NULL, 0, 0 } 164299742Sdim}; 165299742Sdim 166251881Speter/* 167299742Sdim * This getopt implementation has two key features that common 168299742Sdim * getopt_long() implementations lack. Apart from those, it's a 169299742Sdim * straightforward option parser, considerably simplified by not 170251881Speter * needing to support the wealth of exotic getopt_long() features. It 171299742Sdim * has, of course, been shamelessly tailored for bsdtar. (If you're 172299742Sdim * looking for a generic getopt_long() implementation for your 173299742Sdim * project, I recommend Gregory Pietsch's public domain getopt_long() 174299742Sdim * implementation.) The two additional features are: 175299742Sdim * 176251881Speter * Old-style tar arguments: The original tar implementation treated 177299742Sdim * the first argument word as a list of single-character option 178299742Sdim * letters. All arguments follow as separate words. For example, 179251881Speter * tar xbf 32 /dev/tape 180299742Sdim * Here, the "xbf" is three option letters, "32" is the argument for 181299742Sdim * "b" and "/dev/tape" is the argument for "f". We support this usage 182299742Sdim * if the first command-line argument does not begin with '-'. We 183299742Sdim * also allow regular short and long options to follow, e.g., 184251881Speter * tar xbf 32 /dev/tape -P --format=pax 185299742Sdim * 186299742Sdim * -W long options: There's an obscure GNU convention (only rarely 187299742Sdim * supported even there) that allows "-W option=argument" as an 188299742Sdim * alternative way to support long options. This was supported in 189299742Sdim * early bsdtar as a way to access long options on platforms that did 190299742Sdim * not support getopt_long() and is preserved here for backwards 191251881Speter * compatibility. (Of course, if I'd started with a custom 192299742Sdim * command-line parser from the beginning, I would have had normal 193299742Sdim * long option support on every platform so that hack wouldn't have 194251881Speter * been necessary. Oh, well. Some mistakes you just have to live 195299742Sdim * with.) 196251881Speter * 197299742Sdim * TODO: We should be able to use this to pull files and intermingled 198299742Sdim * options (such as -C) from the command line in write mode. That 199251881Speter * will require a little rethinking of the argument handling in 200299742Sdim * bsdtar.c. 201299742Sdim * 202299742Sdim * TODO: If we want to support arbitrary command-line options from -T 203299742Sdim * input (as GNU tar does), we may need to extend this to handle option 204299742Sdim * words from sources other than argv/argc. I'm not really sure if I 205299742Sdim * like that feature of GNU tar, so it's certainly not a priority. 206299742Sdim */ 207299742Sdim 208299742Sdimint 209251881Speterbsdtar_getopt(struct bsdtar *bsdtar) 210299742Sdim{ 211299742Sdim enum { state_start = 0, state_old_tar, state_next_word, 212299742Sdim state_short, state_long }; 213299742Sdim 214299742Sdim const struct bsdtar_option *popt, *match = NULL, *match2 = NULL; 215299742Sdim const char *p, *long_prefix = "--"; 216299742Sdim size_t optlength; 217299742Sdim int opt = '?'; 218299742Sdim int required = 0; 219251881Speter 220299742Sdim bsdtar->argument = NULL; 221299742Sdim 222251881Speter /* First time through, initialize everything. */ 223251881Speter if (bsdtar->getopt_state == state_start) { 224299742Sdim /* Skip program name. */ 225299742Sdim ++bsdtar->argv; 226251881Speter --bsdtar->argc; 227299742Sdim if (*bsdtar->argv == NULL) 228251881Speter return (-1); 229251881Speter /* Decide between "new style" and "old style" arguments. */ 230299742Sdim if (bsdtar->argv[0][0] == '-') { 231251881Speter bsdtar->getopt_state = state_next_word; 232299742Sdim } else { 233299742Sdim bsdtar->getopt_state = state_old_tar; 234299742Sdim bsdtar->getopt_word = *bsdtar->argv++; 235299742Sdim --bsdtar->argc; 236299742Sdim } 237251881Speter } 238299742Sdim 239299742Sdim /* 240251881Speter * We're parsing old-style tar arguments 241299742Sdim */ 242251881Speter if (bsdtar->getopt_state == state_old_tar) { 243251881Speter /* Get the next option character. */ 244299742Sdim opt = *bsdtar->getopt_word++; 245299742Sdim if (opt == '\0') { 246251881Speter /* New-style args can follow old-style. */ 247299742Sdim bsdtar->getopt_state = state_next_word; 248299742Sdim } else { 249299742Sdim /* See if it takes an argument. */ 250299742Sdim p = strchr(short_options, opt); 251251881Speter if (p == NULL) 252299742Sdim return ('?'); 253299742Sdim if (p[1] == ':') { 254299742Sdim bsdtar->argument = *bsdtar->argv; 255299742Sdim if (bsdtar->argument == NULL) { 256299742Sdim lafe_warnc(0, 257299742Sdim "Option %c requires an argument", 258251881Speter opt); 259299742Sdim return ('?'); 260251881Speter } 261251881Speter ++bsdtar->argv; 262299742Sdim --bsdtar->argc; 263299742Sdim } 264299742Sdim } 265299742Sdim } 266299742Sdim 267299742Sdim /* 268251881Speter * We're ready to look at the next word in argv. 269251881Speter */ 270299742Sdim if (bsdtar->getopt_state == state_next_word) { 271299742Sdim /* No more arguments, so no more options. */ 272299742Sdim if (bsdtar->argv[0] == NULL) 273299742Sdim return (-1); 274299742Sdim /* Doesn't start with '-', so no more options. */ 275299742Sdim if (bsdtar->argv[0][0] != '-') 276251881Speter return (-1); 277299742Sdim /* "--" marks end of options; consume it and return. */ 278299742Sdim if (strcmp(bsdtar->argv[0], "--") == 0) { 279299742Sdim ++bsdtar->argv; 280299742Sdim --bsdtar->argc; 281299742Sdim return (-1); 282299742Sdim } 283251881Speter /* Get next word for parsing. */ 284251881Speter bsdtar->getopt_word = *bsdtar->argv++; 285251881Speter --bsdtar->argc; 286299742Sdim if (bsdtar->getopt_word[1] == '-') { 287299742Sdim /* Set up long option parser. */ 288299742Sdim bsdtar->getopt_state = state_long; 289299742Sdim bsdtar->getopt_word += 2; /* Skip leading '--' */ 290299742Sdim } else { 291299742Sdim /* Set up short option parser. */ 292299742Sdim bsdtar->getopt_state = state_short; 293299742Sdim ++bsdtar->getopt_word; /* Skip leading '-' */ 294251881Speter } 295299742Sdim } 296251881Speter 297251881Speter /* 298299742Sdim * We're parsing a group of POSIX-style single-character options. 299299742Sdim */ 300299742Sdim if (bsdtar->getopt_state == state_short) { 301299742Sdim /* Peel next option off of a group of short options. */ 302251881Speter opt = *bsdtar->getopt_word++; 303299742Sdim if (opt == '\0') { 304299742Sdim /* End of this group; recurse to get next option. */ 305299742Sdim bsdtar->getopt_state = state_next_word; 306299742Sdim return bsdtar_getopt(bsdtar); 307251881Speter } 308299742Sdim 309299742Sdim /* Does this option take an argument? */ 310251881Speter p = strchr(short_options, opt); 311299742Sdim if (p == NULL) 312299742Sdim return ('?'); 313299742Sdim if (p[1] == ':') 314251881Speter required = 1; 315299742Sdim 316299742Sdim /* If it takes an argument, parse that. */ 317299742Sdim if (required) { 318251881Speter /* If arg is run-in, bsdtar->getopt_word already points to it. */ 319299742Sdim if (bsdtar->getopt_word[0] == '\0') { 320251881Speter /* Otherwise, pick up the next word. */ 321251881Speter bsdtar->getopt_word = *bsdtar->argv; 322299742Sdim if (bsdtar->getopt_word == NULL) { 323299742Sdim lafe_warnc(0, 324299742Sdim "Option -%c requires an argument", 325299742Sdim opt); 326299742Sdim return ('?'); 327299742Sdim } 328299742Sdim ++bsdtar->argv; 329299742Sdim --bsdtar->argc; 330251881Speter } 331299742Sdim if (opt == 'W') { 332299742Sdim bsdtar->getopt_state = state_long; 333299742Sdim long_prefix = "-W "; /* For clearer errors. */ 334251881Speter } else { 335299742Sdim bsdtar->getopt_state = state_next_word; 336299742Sdim bsdtar->argument = bsdtar->getopt_word; 337251881Speter } 338299742Sdim } 339299742Sdim } 340299742Sdim 341299742Sdim /* We're reading a long option, including -W long=arg convention. */ 342251881Speter if (bsdtar->getopt_state == state_long) { 343299742Sdim /* After this long option, we'll be starting a new word. */ 344299742Sdim bsdtar->getopt_state = state_next_word; 345251881Speter 346299742Sdim /* Option name ends at '=' if there is one. */ 347299742Sdim p = strchr(bsdtar->getopt_word, '='); 348299742Sdim if (p != NULL) { 349299742Sdim optlength = (size_t)(p - bsdtar->getopt_word); 350251881Speter bsdtar->argument = (char *)(uintptr_t)(p + 1); 351299742Sdim } else { 352299742Sdim optlength = strlen(bsdtar->getopt_word); 353299742Sdim } 354251881Speter 355299742Sdim /* Search the table for an unambiguous match. */ 356251881Speter for (popt = tar_longopts; popt->name != NULL; popt++) { 357251881Speter /* Short-circuit if first chars don't match. */ 358251881Speter if (popt->name[0] != bsdtar->getopt_word[0]) 359251881Speter continue; 360251881Speter /* If option is a prefix of name in table, record it.*/ 361251881Speter if (strncmp(bsdtar->getopt_word, popt->name, optlength) == 0) { 362251881Speter match2 = match; /* Record up to two matches. */ 363251881Speter match = popt; 364251881Speter /* If it's an exact match, we're done. */ 365299742Sdim if (strlen(popt->name) == optlength) { 366299742Sdim match2 = NULL; /* Forget the others. */ 367299742Sdim break; 368251881Speter } 369251881Speter } 370299742Sdim } 371299742Sdim 372299742Sdim /* Fail if there wasn't a unique match. */ 373299742Sdim if (match == NULL) { 374299742Sdim lafe_warnc(0, 375299742Sdim "Option %s%s is not supported", 376251881Speter long_prefix, bsdtar->getopt_word); 377299742Sdim return ('?'); 378299742Sdim } 379299742Sdim if (match2 != NULL) { 380251881Speter lafe_warnc(0, 381251881Speter "Ambiguous option %s%s (matches --%s and --%s)", 382299742Sdim long_prefix, bsdtar->getopt_word, match->name, match2->name); 383299742Sdim return ('?'); 384299742Sdim } 385299742Sdim 386299742Sdim /* We've found a unique match; does it need an argument? */ 387299742Sdim if (match->required) { 388251881Speter /* Argument required: get next word if necessary. */ 389299742Sdim if (bsdtar->argument == NULL) { 390299742Sdim bsdtar->argument = *bsdtar->argv; 391299742Sdim if (bsdtar->argument == NULL) { 392251881Speter lafe_warnc(0, 393251881Speter "Option %s%s requires an argument", 394299742Sdim long_prefix, match->name); 395299742Sdim return ('?'); 396299742Sdim } 397299742Sdim ++bsdtar->argv; 398299742Sdim --bsdtar->argc; 399299742Sdim } 400251881Speter } else { 401299742Sdim /* Argument forbidden: fail if there is one. */ 402251881Speter if (bsdtar->argument != NULL) { 403299742Sdim lafe_warnc(0, 404299742Sdim "Option %s%s does not allow an argument", 405299742Sdim long_prefix, match->name); 406299742Sdim return ('?'); 407299742Sdim } 408251881Speter } 409299742Sdim return (match->equivalent); 410299742Sdim } 411251881Speter 412299742Sdim return (opt); 413299742Sdim} 414251881Speter