]> Dogcows Code - chaz/tar/blobdiff - src/tar.c
Carefully crafted invalid headers can cause buffer overrun.
[chaz/tar] / src / tar.c
index 80091fa2053680e55a0bdcc863e883b71f5831c0..b0e039dbc7ff28d6970103fd4d5dabd9801d09db 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
@@ -1,7 +1,7 @@
 /* A tar (tape archiver) program.
 
    Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000,
-   2001, 2003 Free Software Foundation, Inc.
+   2001, 2003, 2004, 2005 Free Software Foundation, Inc.
 
    Written by John Gilmore, starting 1985-08-25.
 
 
    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
-   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
-#include "system.h"
+#include <system.h>
 
 #include <fnmatch.h>
-#include <getopt.h>
+#include <argp.h>
 
 #include <signal.h>
 #if ! defined SIGCHLD && defined SIGCLD
@@ -38,6 +38,7 @@
 
 #include <getdate.h>
 #include <localedir.h>
+#include <rmt.h>
 #include <prepargs.h>
 #include <quotearg.h>
 #include <xstrtol.h>
@@ -56,7 +57,6 @@
 # define DEFAULT_BLOCKING 20
 #endif
 
-void usage (int) __attribute__ ((noreturn));
 \f
 /* Miscellaneous.  */
 
@@ -129,6 +129,7 @@ static struct fmttab {
   { "star",    STAR_FORMAT },
 #endif
   { "gnu",     GNU_FORMAT },
+  { "pax",     POSIX_FORMAT }, /* An alias for posix */
   { NULL,       0 }
 };
 
@@ -142,9 +143,6 @@ set_archive_format (char const *name)
       USAGE_ERROR ((0, 0, _("%s: Invalid archive format"),
                    quotearg_colon (name)));
 
-  if (archive_format != DEFAULT_FORMAT && archive_format != p->fmt)
-    USAGE_ERROR ((0, 0, _("Conflicting archive format options")));
-  
   archive_format = p->fmt;
 }
 
@@ -186,368 +184,416 @@ enum
   ATIME_PRESERVE_OPTION,
   BACKUP_OPTION,
   CHECKPOINT_OPTION,
+  CHECK_LINKS_OPTION,
   DELETE_OPTION,
   EXCLUDE_OPTION,
+  EXCLUDE_CACHES_OPTION,
   FORCE_LOCAL_OPTION,
-  FORMAT_OPTION,
   GROUP_OPTION,
+  HANG_OPTION,
   IGNORE_CASE_OPTION,
+  IGNORE_COMMAND_ERROR_OPTION,
   IGNORE_FAILED_READ_OPTION,
   INDEX_FILE_OPTION,
+  KEEP_NEWER_FILES_OPTION,
+  LICENSE_OPTION,
   MODE_OPTION,
   NEWER_MTIME_OPTION,
   NO_ANCHORED_OPTION,
   NO_IGNORE_CASE_OPTION,
+  NO_IGNORE_COMMAND_ERROR_OPTION,
   NO_OVERWRITE_DIR_OPTION,
+  NO_RECURSION_OPTION,
+  NO_SAME_OWNER_OPTION,
+  NO_SAME_PERMISSIONS_OPTION,
+  NO_UNQUOTE_OPTION,
   NO_WILDCARDS_OPTION,
   NO_WILDCARDS_MATCH_SLASH_OPTION,
   NULL_OPTION,
   NUMERIC_OWNER_OPTION,
   OCCURRENCE_OPTION,
+  OLD_ARCHIVE_OPTION,
+  ONE_FILE_SYSTEM_OPTION,
   OVERWRITE_OPTION,
   OWNER_OPTION,
   PAX_OPTION,
   POSIX_OPTION,
   PRESERVE_OPTION,
   RECORD_SIZE_OPTION,
+  RECURSION_OPTION,
   RECURSIVE_UNLINK_OPTION,
   REMOVE_FILES_OPTION,
+  RMT_COMMAND_OPTION,
   RSH_COMMAND_OPTION,
+  SAME_OWNER_OPTION,
+  SHOW_DEFAULTS_OPTION,
   SHOW_OMITTED_DIRS_OPTION,
-  STRIP_PATH_OPTION,
+  STRIP_COMPONENTS_OPTION,
   SUFFIX_OPTION,
+  TO_COMMAND_OPTION,
   TOTALS_OPTION,
+  UNQUOTE_OPTION,
+  USAGE_OPTION,
   USE_COMPRESS_PROGRAM_OPTION,
+  UTC_OPTION,
+  VERSION_OPTION,
   VOLNO_FILE_OPTION,
   WILDCARDS_OPTION,
   WILDCARDS_MATCH_SLASH_OPTION
 };
 
-/* If nonzero, display usage information and exit.  */
-static int show_help;
-
-/* If nonzero, print the version on standard output and exit.  */
-static int show_version;
-
-static struct option long_options[] =
-{
-  {"absolute-names", no_argument, 0, 'P'},
-  {"after-date", required_argument, 0, 'N'},
-  {"anchored", no_argument, 0, ANCHORED_OPTION},
-  {"append", no_argument, 0, 'r'},
-  {"atime-preserve", no_argument, 0, ATIME_PRESERVE_OPTION},
-  {"backup", optional_argument, 0, BACKUP_OPTION},
-  {"block-number", no_argument, 0, 'R'},
-  {"blocking-factor", required_argument, 0, 'b'},
-  {"bzip2", no_argument, 0, 'j'},
-  {"catenate", no_argument, 0, 'A'},
-  {"checkpoint", no_argument, 0, CHECKPOINT_OPTION},
-  {"check-links", no_argument, &check_links_option, 1},
-  {"compare", no_argument, 0, 'd'},
-  {"compress", no_argument, 0, 'Z'},
-  {"concatenate", no_argument, 0, 'A'},
-  {"confirmation", no_argument, 0, 'w'},
-  /* FIXME: --selective as a synonym for --confirmation?  */
-  {"create", no_argument, 0, 'c'},
-  {"delete", no_argument, 0, DELETE_OPTION},
-  {"dereference", no_argument, 0, 'h'},
-  {"diff", no_argument, 0, 'd'},
-  {"directory", required_argument, 0, 'C'},
-  {"exclude", required_argument, 0, EXCLUDE_OPTION},
-  {"exclude-from", required_argument, 0, 'X'},
-  {"extract", no_argument, 0, 'x'},
-  {"file", required_argument, 0, 'f'},
-  {"files-from", required_argument, 0, 'T'},
-  {"force-local", no_argument, 0, FORCE_LOCAL_OPTION},
-  {"format", required_argument, 0, FORMAT_OPTION},
-  {"get", no_argument, 0, 'x'},
-  {"group", required_argument, 0, GROUP_OPTION},
-  {"gunzip", no_argument, 0, 'z'},
-  {"gzip", no_argument, 0, 'z'},
-  {"help", no_argument, &show_help, 1},
-  {"ignore-case", no_argument, 0, IGNORE_CASE_OPTION},
-  {"ignore-failed-read", no_argument, 0, IGNORE_FAILED_READ_OPTION},
-  {"ignore-zeros", no_argument, 0, 'i'},
-  /* FIXME: --ignore-end as a new name for --ignore-zeros?  */
-  {"incremental", no_argument, 0, 'G'},
-  {"index-file", required_argument, 0, INDEX_FILE_OPTION},
-  {"info-script", required_argument, 0, 'F'},
-  {"interactive", no_argument, 0, 'w'},
-  {"keep-old-files", no_argument, 0, 'k'},
-  {"label", required_argument, 0, 'V'},
-  {"list", no_argument, 0, 't'},
-  {"listed-incremental", required_argument, 0, 'g'},
-  {"mode", required_argument, 0, MODE_OPTION},
-  {"multi-volume", no_argument, 0, 'M'},
-  {"new-volume-script", required_argument, 0, 'F'},
-  {"newer", required_argument, 0, 'N'},
-  {"newer-mtime", required_argument, 0, NEWER_MTIME_OPTION},
-  {"null", no_argument, 0, NULL_OPTION},
-  {"no-anchored", no_argument, 0, NO_ANCHORED_OPTION},
-  {"no-ignore-case", no_argument, 0, NO_IGNORE_CASE_OPTION},
-  {"no-overwrite-dir", no_argument, 0, NO_OVERWRITE_DIR_OPTION},
-  {"no-wildcards", no_argument, 0, NO_WILDCARDS_OPTION},
-  {"no-wildcards-match-slash", no_argument, 0, NO_WILDCARDS_MATCH_SLASH_OPTION},
-  {"no-recursion", no_argument, &recursion_option, 0},
-  {"no-same-owner", no_argument, &same_owner_option, -1},
-  {"no-same-permissions", no_argument, &same_permissions_option, -1},
-  {"numeric-owner", no_argument, 0, NUMERIC_OWNER_OPTION},
-  {"occurrence", optional_argument, 0, OCCURRENCE_OPTION},
-  {"old-archive", no_argument, 0, 'o'},
-  {"one-file-system", no_argument, 0, 'l'},
-  {"overwrite", no_argument, 0, OVERWRITE_OPTION},
-  {"owner", required_argument, 0, OWNER_OPTION},
-  {"pax-option", required_argument, 0, PAX_OPTION},
-  {"portability", no_argument, 0, 'o'},
-  {"posix", no_argument, 0, POSIX_OPTION},
-  {"preserve", no_argument, 0, PRESERVE_OPTION},
-  {"preserve-order", no_argument, 0, 's'},
-  {"preserve-permissions", no_argument, 0, 'p'},
-  {"recursion", no_argument, &recursion_option, FNM_LEADING_DIR},
-  {"recursive-unlink", no_argument, 0, RECURSIVE_UNLINK_OPTION},
-  {"read-full-records", no_argument, 0, 'B'},
-  /* FIXME: --partial-blocks might be a synonym for --read-full-records?  */
-  {"record-size", required_argument, 0, RECORD_SIZE_OPTION},
-  {"remove-files", no_argument, 0, REMOVE_FILES_OPTION},
-  {"rsh-command", required_argument, 0, RSH_COMMAND_OPTION},
-  {"same-order", no_argument, 0, 's'},
-  {"same-owner", no_argument, &same_owner_option, 1},
-  {"same-permissions", no_argument, 0, 'p'},
-  {"show-omitted-dirs", no_argument, 0, SHOW_OMITTED_DIRS_OPTION},
-  {"sparse", no_argument, 0, 'S'},
-  {"starting-file", required_argument, 0, 'K'},
-  {"strip-path", required_argument, 0, STRIP_PATH_OPTION },
-  {"suffix", required_argument, 0, SUFFIX_OPTION},
-  {"tape-length", required_argument, 0, 'L'},
-  {"to-stdout", no_argument, 0, 'O'},
-  {"totals", no_argument, 0, TOTALS_OPTION},
-  {"touch", no_argument, 0, 'm'},
-  {"uncompress", no_argument, 0, 'Z'},
-  {"ungzip", no_argument, 0, 'z'},
-  {"unlink-first", no_argument, 0, 'U'},
-  {"update", no_argument, 0, 'u'},
-  {"use-compress-program", required_argument, 0, USE_COMPRESS_PROGRAM_OPTION},
-  {"verbose", no_argument, 0, 'v'},
-  {"verify", no_argument, 0, 'W'},
-  {"version", no_argument, &show_version, 1},
-  {"volno-file", required_argument, 0, VOLNO_FILE_OPTION},
-  {"wildcards", no_argument, 0, WILDCARDS_OPTION},
-  {"wildcards-match-slash", no_argument, 0, WILDCARDS_MATCH_SLASH_OPTION},
-  
-  {0, 0, 0, 0}
-};
-
-/* Print a usage message and exit with STATUS.  */
-void
-usage (int status)
-{
-  if (status != TAREXIT_SUCCESS)
-    fprintf (stderr, _("Try `%s --help' for more information.\n"),
-            program_name);
-  else
-    {
-      fputs (_("\
-GNU `tar' saves many files together into a single tape or disk archive, and\n\
-can restore individual files from the archive.\n"),
-            stdout);
-      printf (_("\nUsage: %s [OPTION]... [FILE]...\n\
+const char *argp_program_version = "tar (" PACKAGE_NAME ") " VERSION;
+const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
+static char doc[] = N_("GNU `tar' saves many files together into a single tape or disk archive, and can restore individual files from the archive.\n\
 \n\
 Examples:\n\
-  %s -cf archive.tar foo bar  # Create archive.tar from files foo and bar.\n\
-  %s -tvf archive.tar         # List all files in archive.tar verbosely.\n\
-  %s -xf archive.tar          # Extract all files from archive.tar.\n"),
-            program_name, program_name, program_name, program_name);
-      fputs (_("\
-\n\
-If a long option shows an argument as mandatory, then it is mandatory\n\
-for the equivalent short option also.  Similarly for optional arguments.\n"),
-            stdout);
-      fputs(_("\
-\n\
-Main operation mode:\n\
-  -t, --list              list the contents of an archive\n\
-  -x, --extract, --get    extract files from an archive\n\
-  -c, --create            create a new archive\n\
-  -d, --diff, --compare   find differences between archive and file system\n\
-  -r, --append            append files to the end of an archive\n\
-  -u, --update            only append files newer than copy in archive\n\
-  -A, --catenate          append tar files to an archive\n\
-      --concatenate       same as -A\n\
-      --delete            delete from the archive (not on mag tapes!)\n"),
-           stdout);
-      fputs (_("\
-\n\
-Operation modifiers:\n\
-  -W, --verify               attempt to verify the archive after writing it\n\
-      --remove-files         remove files after adding them to the archive\n\
-  -k, --keep-old-files       don't replace existing files when extracting\n\
-      --overwrite            overwrite existing files when extracting\n\
-      --no-overwrite-dir     preserve metadata of existing directories\n\
-  -U, --unlink-first         remove each file prior to extracting over it\n\
-      --recursive-unlink     empty hierarchies prior to extracting directory\n\
-  -S, --sparse               handle sparse files efficiently\n\
-  -O, --to-stdout            extract files to standard output\n\
-  -G, --incremental          handle old GNU-format incremental backup\n\
-  -g, --listed-incremental=FILE\n\
-                             handle new GNU-format incremental backup\n\
-      --ignore-failed-read   do not exit with nonzero on unreadable files\n\
-      --occurrence[=NUM]     process only the NUMth occurrence of each file in\n\
-                             the archive. This option is valid only in\n\
-                             conjunction with one of the subcommands --delete,\n\
-                             --diff, --extract or --list and when a list of\n\
-                             files is given either on the command line or\n\
-                             via -T option.\n\
-                             NUM defaults to 1.\n"),
-            stdout);
-      fputs (_("\
-\n\
-Handling of file attributes:\n\
-      --owner=NAME             force NAME as owner for added files\n\
-      --group=NAME             force NAME as group for added files\n\
-      --mode=CHANGES           force (symbolic) mode CHANGES for added files\n\
-      --atime-preserve         don't change access times on dumped files\n\
-  -m, --modification-time      don't extract file modified time\n\
-      --same-owner             try extracting files with the same ownership\n\
-      --no-same-owner          extract files as yourself\n\
-      --numeric-owner          always use numbers for user/group names\n\
-  -p, --same-permissions       extract permissions information\n\
-      --no-same-permissions    do not extract permissions information\n\
-      --preserve-permissions   same as -p\n\
-  -s, --same-order             sort names to extract to match archive\n\
-      --preserve-order         same as -s\n\
-      --preserve               same as both -p and -s\n"),
-            stdout);
-      fputs (_("\
-\n\
-Device selection and switching:\n\
-  -f, --file=ARCHIVE             use archive file or device ARCHIVE\n\
-      --force-local              archive file is local even if has a colon\n\
-      --rsh-command=COMMAND      use remote COMMAND instead of rsh\n\
-  -[0-7][lmh]                    specify drive and density\n\
-  -M, --multi-volume             create/list/extract multi-volume archive\n\
-  -L, --tape-length=NUM          change tape after writing NUM x 1024 bytes\n\
-  -F, --info-script=FILE         run script at end of each tape (implies -M)\n\
-      --new-volume-script=FILE   same as -F FILE\n\
-      --volno-file=FILE          use/update the volume number in FILE\n"),
-            stdout);
-      fputs (_("\
-\n\
-Device blocking:\n\
-  -b, --blocking-factor=BLOCKS   BLOCKS x 512 bytes per record\n\
-      --record-size=SIZE         SIZE bytes per record, multiple of 512\n\
-  -i, --ignore-zeros             ignore zeroed blocks in archive (means EOF)\n\
-  -B, --read-full-records        reblock as we read (for 4.2BSD pipes)\n"),
-            stdout);
-      fputs (_("\
-\n\
-Archive format selection:\n\
-      --format=FMTNAME               create archive of the given format.\n\
-                                     FMTNAME is one of the following:\n\
-                                     v7        old V7 tar format\n\
-                                     oldgnu    GNU format as per tar <= 1.12\n\
-                                     ustar     POSIX 1003.1-1988 (ustar) format\n\
-                                     posix     POSIX 1003.1-2001 (pax) format\n\
-                                     gnu       GNU format\n\
-      --old-archive, --portability   same as --format=v7\n\
-      --posix                        same as --format=posix\n\
-  -V, --label=NAME                   create archive with volume name NAME\n\
-              PATTERN                at list/extract time, a globbing PATTERN\n\
-  -j, --bzip2                        filter the archive through bzip2\n\
-  -z, --gzip, --ungzip               filter the archive through gzip\n\
-  -Z, --compress, --uncompress       filter the archive through compress\n\
-      --use-compress-program=PROG    filter through PROG (must accept -d)\n"),
-            stdout);
-      fputs (_("\
-\n\
-Local file selection:\n\
-  -C, --directory=DIR          change to directory DIR\n\
-  -T, --files-from=NAME        get names to extract or create from file NAME\n\
-      --null                   -T reads null-terminated names, disable -C\n\
-      --exclude=PATTERN        exclude files, given as a PATTERN\n\
-  -X, --exclude-from=FILE      exclude patterns listed in FILE\n\
-      --anchored               exclude patterns match file name start (default)\n\
-      --no-anchored            exclude patterns match after any /\n\
-      --ignore-case            exclusion ignores case\n\
-      --no-ignore-case         exclusion is case sensitive (default)\n\
-      --wildcards              exclude patterns use wildcards (default)\n\
-      --no-wildcards           exclude patterns are plain strings\n\
-      --wildcards-match-slash  exclude pattern wildcards match '/' (default)\n\
-      --no-wildcards-match-slash exclude pattern wildcards do not match '/'\n\
-  -P, --absolute-names         don't strip leading `/'s from file names\n\
-  -h, --dereference            dump instead the files symlinks point to\n\
-      --no-recursion           avoid descending automatically in directories\n\
-  -l, --one-file-system        stay in local file system when creating archive\n\
-  -K, --starting-file=NAME     begin at file NAME in the archive\n\
-      --strip-path=NUM         strip NUM leading components from file names\n\
-                               before extraction\n"),
-            stdout);
-#if !MSDOS
-      fputs (_("\
-  -N, --newer=DATE-OR-FILE     only store files newer than DATE-OR-FILE\n\
-      --newer-mtime=DATE       compare date and time when data changed only\n\
-      --after-date=DATE        same as -N\n"),
-            stdout);
-#endif
-      fputs (_("\
-      --backup[=CONTROL]       backup before removal, choose version control\n\
-      --suffix=SUFFIX          backup before removal, override usual suffix\n"),
-            stdout);
-      fputs (_("\
-\n\
-Informative output:\n\
-      --help            print this help, then exit\n\
-      --version         print tar program version number, then exit\n\
-  -v, --verbose         verbosely list files processed\n\
-      --checkpoint      print directory names while reading the archive\n\
-      --check-links     print a message if not all links are dumped\n\
-      --totals          print total bytes written while creating archive\n\
-      --index-file=FILE send verbose output to FILE\n\
-  -R, --block-number    show block number within archive with each message\n\
-  -w, --interactive     ask for confirmation for every action\n\
-      --confirmation    same as -w\n"),
-            stdout);
-      fputs (_("\
-\n\
-Compatibility options:\n\
-  -o                                 when creating, same as --old-archive\n\
-                                     when extracting, same as --no-same-owner\n"),
-             stdout);
-      
-      fputs (_("\
-\n\
-The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
-The version control may be set with --backup or VERSION_CONTROL, values are:\n\
-\n\
+  tar -cf archive.tar foo bar  # Create archive.tar from files foo and bar.\n\
+  tar -tvf archive.tar         # List all files in archive.tar verbosely.\n\
+  tar -xf archive.tar          # Extract all files from archive.tar.\n\
+\vThe backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
+The version control may be set with --backup or VERSION_CONTROL, values are:\n\n\
+  none, off       never make backups\n\
   t, numbered     make numbered backups\n\
   nil, existing   numbered if numbered backups exist, simple otherwise\n\
-  never, simple   always make simple backups\n"),
-            stdout);
-      printf (_("\
-\n\
-ARCHIVE may be FILE, HOST:FILE or USER@HOST:FILE; DATE may be a textual date\n\
-or a file name starting with `/' or `.', in which case the file's date is used.\n\
-*This* `tar' defaults to `--format=%s -f%s -b%d'.\n"),
-             archive_format_string (DEFAULT_ARCHIVE_FORMAT),
-             DEFAULT_ARCHIVE, DEFAULT_BLOCKING);
-      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
-    }
-  exit (status);
-}
+  never, simple   always make simple backups\n");
 
-/* Parse the options for tar.  */
 
-/* Available option letters are DEHIJQY and aenqy.  Some are reserved:
+/* NOTE:
+
+   Available option letters are DEIJQY and aeqy. Consider the following
+   assignments:
 
+   [For Solaris tar compatibility]
    e  exit immediately with a nonzero exit status if unexpected errors occur
-   E  use extended headers (draft POSIX headers, that is)
-   I  same as T (for compatibility with Solaris tar)
-   n  the archive is quickly seekable, so don't worry about random seeks
-   q  stop after extracting the first occurrence of the named file
+   E  use extended headers (--format=posix)
+   [q  alias for --occurrence=1 =/= this would better be used for quiet?]
+   [I  same as T =/= will harm star compatibility]
+
    y  per-file gzip compression
    Y  per-block gzip compression */
 
-#define OPTION_STRING \
-  "-01234567ABC:F:GIK:L:MN:OPRST:UV:WX:Zb:cdf:g:hijklmoprstuvwxyz"
+static struct argp_option options[] = {
+  {NULL, 0, NULL, 0,
+   N_("Main operation mode:"), 0},
+
+  {"list", 't', 0, 0,
+   N_("list the contents of an archive"), 10 },
+  {"extract", 'x', 0, 0,
+   N_("extract files from an archive"), 10 },
+  {"get", 0, 0, OPTION_ALIAS, NULL, 0 },
+  {"create", 'c', 0, 0,
+   N_("create a new archive"), 10 },
+  {"diff", 'd', 0, 0,
+   N_("find differences between archive and file system"), 10 },
+  {"compare", 0, 0, OPTION_ALIAS, NULL, 10},
+  {"append", 'r', 0, 0,
+   N_("append files to the end of an archive"), 10 },
+  {"update", 'u', 0, 0,
+   N_("only append files newer than copy in archive"), 10 },
+  {"catenate", 'A', 0, 0,
+   N_("append tar files to an archive"), 10 },
+  {"concatenate", 0, 0, OPTION_ALIAS, NULL, 10},
+  {"delete", DELETE_OPTION, 0, 0,
+   N_("delete from the archive (not on mag tapes!)"), 10 },
+
+  {NULL, 0, NULL, 0,
+   N_("Operation modifiers:"), 20},
+
+  {"sparse", 'S', 0, 0,
+   N_("handle sparse files efficiently"), 21 },
+  {"incremental", 'G', 0, 0,
+   N_("handle old GNU-format incremental backup"), 21 },
+  {"listed-incremental", 'g', N_("FILE"), 0,
+   N_("handle new GNU-format incremental backup"), 21 },
+  {"ignore-failed-read", IGNORE_FAILED_READ_OPTION, 0, 0,
+   N_("do not exit with nonzero on unreadable files"), 21 },
+  {"occurrence", OCCURRENCE_OPTION, N_("NUMBER"), OPTION_ARG_OPTIONAL,
+   N_("process only the NUMBERth occurrence of each file in the archive. This option is valid only in conjunction with one of the subcommands --delete, --diff, --extract or --list and when a list of files is given either on the command line or via -T option. NUMBER defaults to 1."), 21 },
+  {"seek", 'n', NULL, 0,
+   N_("archive is seekable"), 21 },
+
+  {NULL, 0, NULL, 0,
+   N_("Overwrite control:"), 30},
+
+  {"verify", 'W', 0, 0,
+   N_("attempt to verify the archive after writing it"), 31 },
+  {"remove-files", REMOVE_FILES_OPTION, 0, 0,
+   N_("remove files after adding them to the archive"), 31 },
+  {"keep-old-files", 'k', 0, 0,
+   N_("don't replace existing files when extracting"), 31 },
+  {"keep-newer-files", KEEP_NEWER_FILES_OPTION, 0, 0,
+   N_("don't replace existing files that are newer than their archive copies"), 31 },
+  {"overwrite", OVERWRITE_OPTION, 0, 0,
+   N_("overwrite existing files when extracting"), 31 },
+  {"unlink-first", 'U', 0, 0,
+   N_("remove each file prior to extracting over it"), 31 },
+  {"recursive-unlink", RECURSIVE_UNLINK_OPTION, 0, 0,
+   N_("empty hierarchies prior to extracting directory"), 31 },
+  {"no-overwrite-dir", NO_OVERWRITE_DIR_OPTION, 0, 0,
+   N_("preserve metadata of existing directories"), 31 },
+
+  {NULL, 0, NULL, 0,
+   N_("Select output stream:"), 40},
+
+  {"to-stdout", 'O', 0, 0,
+   N_("extract files to standard output"), 41 },
+  {"to-command", TO_COMMAND_OPTION, N_("COMMAND"), 0,
+   N_("pipe extracted files to another program"), 41 },
+  {"ignore-command-error", IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+   N_("ignore exit codes of children"), 41 },
+  {"no-ignore-command-error", NO_IGNORE_COMMAND_ERROR_OPTION, 0, 0,
+   N_("treat non-zero exit codes of children as error"), 41 },
+
+  {NULL, 0, NULL, 0,
+   N_("Handling of file attributes:"), 50 },
+
+  {"owner", OWNER_OPTION, N_("NAME"), 0,
+   N_("force NAME as owner for added files"), 51 },
+  {"group", GROUP_OPTION, N_("NAME"), 0,
+   N_("force NAME as group for added files"), 51 },
+  {"mode", MODE_OPTION, N_("CHANGES"), 0,
+   N_("force (symbolic) mode CHANGES for added files"), 51 },
+  {"atime-preserve", ATIME_PRESERVE_OPTION, 0, 0,
+   N_("don't change access times on dumped files"), 51 },
+  {"touch", 'm', 0, 0,
+   N_("don't extract file modified time"), 51 },
+  {"same-owner", SAME_OWNER_OPTION, 0, 0,
+   N_("try extracting files with the same ownership"), 51 },
+  {"no-same-owner", NO_SAME_OWNER_OPTION, 0, 0,
+   N_("extract files as yourself"), 51 },
+  {"numeric-owner", NUMERIC_OWNER_OPTION, 0, 0,
+   N_("always use numbers for user/group names"), 51 },
+  {"preserve-permissions", 'p', 0, 0,
+   N_("extract information about file permissions (default for superuser)"),
+   51 },
+  {"same-permissions", 0, 0, OPTION_ALIAS, NULL, 51 },
+  {"no-same-permissions", NO_SAME_PERMISSIONS_OPTION, 0, 0,
+   N_("apply the user's umask when extracting permissions from the archive (default for ordinary users)"), 51 },
+  {"preserve-order", 's', 0, 0,
+   N_("sort names to extract to match archive"), 51 },
+  {"same-order", 0, 0, OPTION_ALIAS, NULL, 51 },
+  {"preserve", PRESERVE_OPTION, 0, 0,
+   N_("same as both -p and -s"), 51 },
+
+  {NULL, 0, NULL, 0,
+   N_("Device selection and switching:"), 60 },
+
+  {"file", 'f', N_("ARCHIVE"), 0,
+   N_("use archive file or device ARCHIVE"), 61 },
+  {"force-local", FORCE_LOCAL_OPTION, 0, 0,
+   N_("archive file is local even if it has a colon"), 61 },
+  {"rmt-command", RMT_COMMAND_OPTION, N_("COMMAND"), 0,
+   N_("use given rmt COMMAND instead of rmt"), 61 },
+  {"rsh-command", RSH_COMMAND_OPTION, N_("COMMAND"), 0,
+   N_("use remote COMMAND instead of rsh"), 61 },
+#ifdef DEVICE_PREFIX
+  {"-[0-7][lmh]", 0, NULL, OPTION_DOC, /* It is OK, since `name' will never be
+                                         translated */
+   N_("specify drive and density"), 61 },
+#endif
+  {NULL, '0', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '1', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '2', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '3', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '4', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '5', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '6', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '7', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '8', NULL, OPTION_HIDDEN, NULL, 61 },
+  {NULL, '9', NULL, OPTION_HIDDEN, NULL, 61 },
+
+  {"multi-volume", 'M', 0, 0,
+   N_("create/list/extract multi-volume archive"), 61 },
+  {"tape-length", 'L', N_("NUMBER"), 0,
+   N_("change tape after writing NUMBER x 1024 bytes"), 61 },
+  {"info-script", 'F', N_("NAME"), 0,
+   N_("run script at end of each tape (implies -M)"), 61 },
+  {"new-volume-script", 0, 0, OPTION_ALIAS, NULL, 61 },
+  {"volno-file", VOLNO_FILE_OPTION, N_("FILE"), 0,
+   N_("use/update the volume number in FILE"), 61 },
+
+  {NULL, 0, NULL, 0,
+   N_("Device blocking:"), 70 },
+
+  {"blocking-factor", 'b', N_("BLOCKS"), 0,
+   N_("BLOCKS x 512 bytes per record"), 71 },
+  {"record-size", RECORD_SIZE_OPTION, N_("NUMBER"), 0,
+   N_("NUMBER of bytes per record, multiple of 512"), 71 },
+  {"ignore-zeros", 'i', 0, 0,
+   N_("ignore zeroed blocks in archive (means EOF)"), 71 },
+  {"read-full-records", 'B', 0, 0,
+   N_("reblock as we read (for 4.2BSD pipes)"), 71 },
+
+  {NULL, 0, NULL, 0,
+   N_("Archive format selection:"), 80 },
+
+  {"format", 'H', N_("FORMAT"), 0,
+   N_("create archive of the given format."), 81 },
+
+  {NULL, 0, NULL, 0, N_("FORMAT is one of the following:"), 82 },
+  {"  v7", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("old V7 tar format"), 83},
+  {"  oldgnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("GNU format as per tar <= 1.12"), 83},
+  {"  gnu", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("GNU tar 1.13.x format"), 83},
+  {"  ustar", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("POSIX 1003.1-1988 (ustar) format"), 83 },
+  {"  pax", 0, NULL, OPTION_DOC|OPTION_NO_TRANS,
+   N_("POSIX 1003.1-2001 (pax) format"), 83 },
+  {"  posix", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("same as pax"), 83 },
+
+  {"old-archive", OLD_ARCHIVE_OPTION, 0, 0, /* FIXME */
+   N_("same as --format=v7"), 88 },
+  {"portability", 0, 0, OPTION_ALIAS, NULL, 88 },
+  {"posix", POSIX_OPTION, 0, 0,
+   N_("same as --format=posix"), 88 },
+  {"pax-option", PAX_OPTION, N_("keyword[[:]=value][,keyword[[:]=value], ...]"), 0,
+   N_("control pax keywords"), 88 },
+  {"label", 'V', N_("TEXT"), 0,
+   N_("create archive with volume name TEXT. At list/extract time, use TEXT as a globbing pattern for volume name"), 88 },
+  {"bzip2", 'j', 0, 0,
+   N_("filter the archive through bzip2"), 88 },
+  {"gzip", 'z', 0, 0,
+   N_("filter the archive through gzip"), 88 },
+  {"gunzip", 0, 0, OPTION_ALIAS, NULL, 88 },
+  {"ungzip", 0, 0, OPTION_ALIAS, NULL, 88 },
+  {"compress", 'Z', 0, 0,
+   N_("filter the archive through compress"), 88 },
+  {"uncompress", 0, 0, OPTION_ALIAS, NULL, 88 },
+  {"use-compress-program", USE_COMPRESS_PROGRAM_OPTION, N_("PROG"), 0,
+   N_("filter through PROG (must accept -d)"), 88 },
+
+  {NULL, 0, NULL, 0,
+   N_("Local file selection:"), 90 },
+
+  {"add-file", ARGP_KEY_ARG, N_("FILE"), 0,
+   N_("add given FILE to the archive (useful if its name starts with a dash)"), 91},
+  {"directory", 'C', N_("DIR"), 0,
+   N_("change to directory DIR"), 91 },
+  {"files-from", 'T', N_("FILE"), 0,
+   N_("get names to extract or create from FILE"), 91 },
+  {"null", NULL_OPTION, 0, 0,
+   N_("-T reads null-terminated names, disable -C"), 91 },
+  {"unquote", UNQUOTE_OPTION, 0, 0,
+   N_("unquote filenames read with -T (default)"), 91 },
+  {"no-unquote", NO_UNQUOTE_OPTION, 0, 0,
+   N_("do not unquote filenames read with -T"), 91 },
+  {"exclude", EXCLUDE_OPTION, N_("PATTERN"), 0,
+   N_("exclude files, given as a PATTERN"), 91 },
+  {"exclude-from", 'X', N_("FILE"), 0,
+   N_("exclude patterns listed in FILE"), 91 },
+  {"exclude-caches", EXCLUDE_CACHES_OPTION, 0, 0,
+   N_("exclude directories containing a cache tag"), 91 },
+  {"ignore-case", IGNORE_CASE_OPTION, 0, 0,
+   N_("exclusion ignores case"), 91 },
+  {"anchored", ANCHORED_OPTION, 0, 0,
+   N_("exclude patterns match file name start"), 91 },
+  {"no-anchored", NO_ANCHORED_OPTION, 0, 0,
+   N_("exclude patterns match after any `/' (default)"), 91 },
+  {"no-ignore-case", NO_IGNORE_CASE_OPTION, 0, 0,
+   N_("exclusion is case sensitive (default)"), 91 },
+  {"no-wildcards", NO_WILDCARDS_OPTION, 0, 0,
+   N_("exclude patterns are plain strings"), 91 },
+  {"no-wildcards-match-slash", NO_WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+   N_("exclude pattern wildcards do not match `/'"), 91 },
+  {"no-recursion", NO_RECURSION_OPTION, 0, 0,
+   N_("avoid descending automatically in directories"), 91 },
+  {"one-file-system", ONE_FILE_SYSTEM_OPTION, 0, 0,
+   N_("stay in local file system when creating archive"), 91 },
+  {NULL, 'l', 0, OPTION_HIDDEN, "", 91},
+  {"recursion", RECURSION_OPTION, 0, 0,
+   N_("recurse into directories (default)"), 91 },
+  {"absolute-names", 'P', 0, 0,
+   N_("don't strip leading `/'s from file names"), 91 },
+  {"dereference", 'h', 0, 0,
+   N_("follow symlinks; archive and dump the files they point to"), 91 },
+  {"starting-file", 'K', N_("MEMBER-NAME"), 0,
+   N_("begin at member MEMBER-NAME in the archive"), 91 },
+  {"strip-components", STRIP_COMPONENTS_OPTION, N_("NUMBER"), 0,
+   N_("strip NUMBER leading components from file names"), 91 },
+  {"newer", 'N', N_("DATE-OR-FILE"), 0,
+   N_("only store files newer than DATE-OR-FILE"), 91 },
+  {"newer-mtime", NEWER_MTIME_OPTION, N_("DATE"), 0,
+   N_("compare date and time when data changed only"), 91 },
+  {"after-date", 'N', N_("DATE"), 0,
+   N_("same as -N"), 91 },
+  {"backup", BACKUP_OPTION, N_("CONTROL"), OPTION_ARG_OPTIONAL,
+   N_("backup before removal, choose version CONTROL"), 91 },
+  {"suffix", SUFFIX_OPTION, N_("STRING"), 0,
+   N_("backup before removal, override usual suffix ('~' unless overridden by environment variable SIMPLE_BACKUP_SUFFIX)"), 91 },
+  {"wildcards", WILDCARDS_OPTION, 0, 0,
+   N_("exclude patterns use wildcards (default)"), 91 },
+  {"wildcards-match-slash", WILDCARDS_MATCH_SLASH_OPTION, 0, 0,
+   N_("exclude pattern wildcards match `/' (default)"), 91 },
+
+  {NULL, 0, NULL, 0,
+   N_("Informative output:"), 100 },
+
+  {"verbose", 'v', 0, 0,
+   N_("verbosely list files processed"), 101 },
+  {"checkpoint", CHECKPOINT_OPTION, 0, 0,
+   N_("display progress messages every 10th record"), 101 },
+  {"check-links", CHECK_LINKS_OPTION, 0, 0,
+   N_("print a message if not all links are dumped"), 102 },
+  {"totals", TOTALS_OPTION, 0, 0,
+   N_("print total bytes written while creating archive"), 102 },
+  {"utc", UTC_OPTION, 0, 0,
+   N_("print file modification dates in UTC"), 102 },
+  {"index-file", INDEX_FILE_OPTION, N_("FILE"), 0,
+   N_("send verbose output to FILE"), 102 },
+  {"block-number", 'R', 0, 0,
+   N_("show block number within archive with each message"), 102 },
+  {"interactive", 'w', 0, 0,
+   N_("ask for confirmation for every action"), 102 },
+  {"confirmation", 0, 0, OPTION_ALIAS, NULL, 102 },
+  {"show-defaults", SHOW_DEFAULTS_OPTION, 0, 0,
+   N_("Show tar defaults"), 102 },
+  {"show-omitted-dirs", SHOW_OMITTED_DIRS_OPTION, 0, 0,
+   N_("When listing or extracting, list each directory that does not match search criteria"), 102 },
+
+  {NULL, 0, NULL, 0,
+   N_("Compatibility options:"), 110 },
+
+  {NULL, 'o', 0, 0,
+   N_("when creating, same as --old-archive. When extracting, same as --no-same-owner"), 111 },
+
+  {NULL, 0, NULL, 0,
+   N_("Other options:"), 120 },
+
+  {"help",  '?', 0, 0,  N_("Give this help list"), -1},
+  {"usage", USAGE_OPTION, 0, 0,  N_("Give a short usage message"), -1},
+  {"license", LICENSE_OPTION, 0, 0, N_("Print license and exit"), -1},
+  {"version", VERSION_OPTION, 0, 0,  N_("Print program version"), -1},
+  /* FIXME -V (--label) conflicts with the default short option for
+     --version */
+  {"HANG",       HANG_OPTION,    "SECS", OPTION_ARG_OPTIONAL | OPTION_HIDDEN,
+   N_("Hang for SECS seconds (default 3600)"), 0},
+  {0, 0, 0, 0, 0, 0}
+};
+
+struct tar_args {
+  char const *textual_date_option;
+  int exclude_options;
+  bool o_option;
+  int pax_option;
+  char const *backup_suffix_string;
+  char const *version_control_string;
+  int input_files;
+};
+
+static void
+show_default_settings (FILE *stream)
+{
+  fprintf (stream,
+          "--format=%s -f%s -b%d --rmt-command=%s",
+          archive_format_string (DEFAULT_ARCHIVE_FORMAT),
+          DEFAULT_ARCHIVE, DEFAULT_BLOCKING,
+          DEFAULT_RMT_COMMAND);
+#ifdef REMOTE_SHELL
+  fprintf (stream, " --rsh-command=%s", REMOTE_SHELL);
+#endif
+  fprintf (stream, "\n");
+}
 
 static void
 set_subcommand_option (enum subcommand subcommand)
@@ -563,669 +609,1007 @@ set_subcommand_option (enum subcommand subcommand)
 static void
 set_use_compress_program_option (const char *string)
 {
-  if (use_compress_program_option && strcmp (use_compress_program_option, string) != 0)
+  if (use_compress_program_option
+      && strcmp (use_compress_program_option, string) != 0)
     USAGE_ERROR ((0, 0, _("Conflicting compression options")));
 
   use_compress_program_option = string;
 }
 
+void
+license ()
+{
+  printf ("tar (%s) %s\n%s\n", PACKAGE_NAME, PACKAGE_VERSION,
+         "Copyright (C) 2004 Free Software Foundation, Inc.\n");
+  puts (_("Based on the work of John Gilmore and Jay Fenlason. See AUTHORS\n\
+for complete list of authors.\n"));
+  printf (_("   GNU tar is free software; you can redistribute it and/or modify\n"
+    "   it under the terms of the GNU General Public License as published by\n"
+    "   the Free Software Foundation; either version 2 of the License, or\n"
+    "   (at your option) any later version.\n"
+    "\n"
+    "   GNU tar is distributed in the hope that it will be useful,\n"
+    "   but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+    "   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+    "   GNU General Public License for more details.\n"
+    "\n"
+    "   You should have received a copy of the GNU General Public License\n"
+    "   along with GNU tar; if not, write to the Free Software Foundation,\n"
+    "   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA\n\n"));
+  exit (0);
+}
+
+static volatile int _argp_hang;
+
+enum read_file_list_state  /* Result of reading file name from the list file */
+  {
+    file_list_success,     /* OK, name read successfully */
+    file_list_end,         /* End of list file */
+    file_list_zero         /* Zero separator encountered where it should not */
+  };
+
+/* Read from FP a sequence of characters up to FILENAME_TERMINATOR and put them
+   into STK.
+ */
+static enum read_file_list_state
+read_name_from_file (FILE *fp, struct obstack *stk)
+{
+  int c;
+  size_t counter = 0;
+
+  for (c = getc (fp); c != EOF && c != filename_terminator; c = getc (fp))
+    {
+      if (c == 0)
+       {
+         /* We have read a zero separator. The file possibly is zero-separated */
+         /* FATAL_ERROR((0, 0, N_("file name contains null character"))); */
+         return file_list_zero;
+       }
+      obstack_1grow (stk, c);
+      counter++;
+    }
+
+  obstack_1grow (stk, 0);
+
+  return (counter == 0 && c == EOF) ? file_list_end : file_list_success;
+}
+
+\f
+static bool files_from_option;  /* When set, tar will not refuse to create
+                                  empty archives */
+static struct obstack argv_stk; /* Storage for additional command line options
+                                  read using -T option */
+
+/* Prevent recursive inclusion of the same file */
+struct file_id_list
+{
+  struct file_id_list *next;
+  ino_t ino;
+  dev_t dev;
+};
+
+static struct file_id_list *file_id_list;
+
 static void
-decode_options (int argc, char **argv)
+add_file_id (const char *filename)
 {
-  int optchar;                 /* option letter */
-  int input_files;             /* number of input files */
-  char const *textual_date_option = 0;
-  char const *backup_suffix_string;
-  char const *version_control_string = 0;
-  int exclude_options = EXCLUDE_WILDCARDS;
-  bool o_option = 0;
-  
-  /* Set some default option values.  */
+  struct file_id_list *p;
+  struct stat st;
 
-  subcommand_option = UNKNOWN_SUBCOMMAND;
-  archive_format = DEFAULT_FORMAT;
-  blocking_factor = DEFAULT_BLOCKING;
-  record_size = DEFAULT_BLOCKING * BLOCKSIZE;
-  excluded = new_exclude ();
-  newer_mtime_option = TYPE_MINIMUM (time_t);
-  recursion_option = FNM_LEADING_DIR;
+  if (stat (filename, &st))
+    stat_fatal (filename);
+  for (p = file_id_list; p; p = p->next)
+    if (p->ino == st.st_ino && p->dev == st.st_dev)
+      {
+       FATAL_ERROR ((0, 0, _("%s: file list already read"),
+                     quotearg_colon (filename)));
+      }
+  p = xmalloc (sizeof *p);
+  p->next = file_id_list;
+  p->ino = st.st_ino;
+  p->dev = st.st_dev;
+  file_id_list = p;
+}
 
-  owner_option = -1;
-  group_option = -1;
+static void
+update_argv (const char *filename, struct argp_state *state)
+{
+  FILE *fp;
+  size_t count = 0, i;
+  char *start, *p;
+  char **new_argv;
+  size_t new_argc;
+  bool is_stdin = false;
+  enum read_file_list_state read_state;
+
+  if (!strcmp (filename, "-"))
+    {
+      is_stdin = true;
+      request_stdin ("-T");
+      fp = stdin;
+    }
+  else
+    {
+      add_file_id (filename);
+      if ((fp = fopen (filename, "r")) == NULL)
+       open_fatal (filename);
+    }
 
-  backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
+  while ((read_state = read_name_from_file (fp, &argv_stk)) == file_list_success)
+    count++;
 
-  /* Convert old-style tar call by exploding option element and rearranging
-     options accordingly.  */
+  if (read_state == file_list_zero)
+    {
+      size_t size;
+
+      WARN ((0, 0, N_("%s: file name read contains nul character"),
+            quotearg_colon (filename)));
+
+      /* Prepare new stack contents */
+      size = obstack_object_size (&argv_stk);
+      p = obstack_finish (&argv_stk);
+      for (; size > 0; size--, p++)
+       if (*p)
+         obstack_1grow (&argv_stk, *p);
+        else
+         obstack_1grow (&argv_stk, '\n');
+      obstack_1grow (&argv_stk, 0);
+      count = 1;
+
+      /* Read rest of files using new filename terminator */
+      filename_terminator = 0;
+      while (read_name_from_file (fp, &argv_stk) == file_list_success)
+       count++;
+    }
 
-  if (argc > 1 && argv[1][0] != '-')
+  if (!is_stdin)
+    fclose (fp);
+
+  if (count == 0)
+    return;
+
+  start = obstack_finish (&argv_stk);
+
+  if (filename_terminator == 0)
+    for (p = start; *p; p += strlen (p) + 1)
+      if (p[0] == '-')
+       count++;
+
+  new_argc = state->argc + count;
+  new_argv = xmalloc (sizeof (state->argv[0]) * (new_argc + 1));
+  memcpy (new_argv, state->argv, sizeof (state->argv[0]) * (state->argc + 1));
+  state->argv = new_argv;
+  memmove (&state->argv[state->next + count], &state->argv[state->next],
+          (state->argc - state->next + 1) * sizeof (state->argv[0]));
+
+  state->argc = new_argc;
+
+  for (i = state->next, p = start; *p; p += strlen (p) + 1, i++)
     {
-      int new_argc;            /* argc value for rearranged arguments */
-      char **new_argv;         /* argv value for rearranged arguments */
-      char *const *in;         /* cursor into original argv */
-      char **out;              /* cursor into rearranged argv */
-      const char *letter;      /* cursor into old option letters */
-      char buffer[3];          /* constructed option buffer */
-      const char *cursor;      /* cursor in OPTION_STRING */
+      if (filename_terminator == 0 && p[0] == '-')
+       state->argv[i++] = "--add-file";
+      state->argv[i] = p;
+    }
+}
 
-      /* Initialize a constructed option.  */
+\f
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct tar_args *args = state->input;
 
-      buffer[0] = '-';
-      buffer[2] = '\0';
+  switch (key)
+    {
+      case ARGP_KEY_ARG:
+       /* File name or non-parsed option, because of ARGP_IN_ORDER */
+       name_add (arg);
+       args->input_files++;
+       break;
 
-      /* Allocate a new argument array, and copy program name in it.  */
+    case 'A':
+      set_subcommand_option (CAT_SUBCOMMAND);
+      break;
 
-      new_argc = argc - 1 + strlen (argv[1]);
-      new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
-      in = argv;
-      out = new_argv;
-      *out++ = *in++;
+    case 'b':
+      {
+       uintmax_t u;
+       if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+              && u == (blocking_factor = u)
+              && 0 < blocking_factor
+              && u == (record_size = u * BLOCKSIZE) / BLOCKSIZE))
+         USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                       _("Invalid blocking factor")));
+      }
+      break;
 
-      /* Copy each old letter option as a separate option, and have the
-        corresponding argument moved next to it.  */
+    case 'B':
+      /* Try to reblock input records.  For reading 4.2BSD pipes.  */
 
-      for (letter = *in++; *letter; letter++)
+      /* It would surely make sense to exchange -B and -R, but it seems
+        that -B has been used for a long while in Sun tar and most
+        BSD-derived systems.  This is a consequence of the block/record
+        terminology confusion.  */
+
+      read_full_records_option = true;
+      break;
+
+    case 'c':
+      set_subcommand_option (CREATE_SUBCOMMAND);
+      break;
+
+    case 'C':
+      name_add ("-C");
+      name_add (arg);
+      break;
+
+    case 'd':
+      set_subcommand_option (DIFF_SUBCOMMAND);
+      break;
+
+    case 'f':
+      if (archive_names == allocated_archive_names)
        {
-         buffer[1] = *letter;
-         *out++ = xstrdup (buffer);
-         cursor = strchr (OPTION_STRING, *letter);
-         if (cursor && cursor[1] == ':')
-           {
-             if (in < argv + argc)
-               *out++ = *in++;
-             else
-               USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
-                             *letter));
-           }
+         allocated_archive_names *= 2;
+         archive_name_array =
+           xrealloc (archive_name_array,
+                     sizeof (const char *) * allocated_archive_names);
        }
+      archive_name_array[archive_names++] = arg;
+      break;
 
-      /* Copy all remaining options.  */
+    case 'F':
+      /* Since -F is only useful with -M, make it implied.  Run this
+        script at the end of each tape.  */
 
-      while (in < argv + argc)
-       *out++ = *in++;
-      *out = 0;
+      info_script_option = arg;
+      multi_volume_option = true;
+      break;
 
-      /* Replace the old option list by the new one.  */
+    case 'g':
+      listed_incremental_option = arg;
+      after_date_option = true;
+      /* Fall through.  */
 
-      argc = new_argc;
-      argv = new_argv;
-    }
+    case 'G':
+      /* We are making an incremental dump (FIXME: are we?); save
+        directories at the beginning of the archive, and include in each
+        directory its contents.  */
 
-  /* Parse all options and non-options as they appear.  */
+      incremental_option = true;
+      break;
 
-  input_files = 0;
+    case 'h':
+      /* Follow symbolic links.  */
+      dereference_option = true;
+      break;
 
-  prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
+    case 'i':
+      /* Ignore zero blocks (eofs).  This can't be the default,
+        because Unix tar writes two blocks of zeros, then pads out
+        the record with garbage.  */
 
-  while (optchar = getopt_long (argc, argv, OPTION_STRING, long_options, 0),
-        optchar != -1)
-    switch (optchar)
+      ignore_zeros_option = true;
+      break;
+
+    case 'I':
+      USAGE_ERROR ((0, 0,
+                   _("Warning: the -I option is not supported;"
+                     " perhaps you meant -j or -T?")));
+      break;
+
+    case 'j':
+      set_use_compress_program_option ("bzip2");
+      break;
+
+    case 'k':
+      /* Don't replace existing files.  */
+      old_files_option = KEEP_OLD_FILES;
+      break;
+
+    case 'K':
+      starting_file_option = true;
+      addname (arg, 0);
+      break;
+
+    case 'l':
+      /* Historically equivalent to --one-file-system. This usage is
+        incompatible with UNIX98 and POSIX specs and therefore is
+        deprecated. The semantics of -l option will be changed in
+        future versions. See TODO.
+      */
+      WARN ((0, 0,
+            _("Semantics of -l option will change in the future releases.")));
+      WARN ((0, 0,
+            _("Please use --one-file-system option instead.")));
+      /* FALL THROUGH */
+    case ONE_FILE_SYSTEM_OPTION:
+      /* When dumping directories, don't dump files/subdirectories
+        that are on other filesystems. */
+      one_file_system_option = true;
+      break;
+
+    case 'L':
       {
-      case '?':
-       usage (TAREXIT_FAILURE);
+       uintmax_t u;
+       if (xstrtoumax (arg, 0, 10, &u, "") != LONGINT_OK)
+         USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                       _("Invalid tape length")));
+       tape_length_option = 1024 * (tarlong) u;
+       multi_volume_option = true;
+      }
+      break;
 
-      case 0:
-       break;
+    case 'm':
+      touch_option = true;
+      break;
 
-      case 1:
-       /* File name or non-parsed option, because of RETURN_IN_ORDER
-          ordering triggered by the leading dash in OPTION_STRING.  */
+    case 'M':
+      /* Make multivolume archive: when we can't write any more into
+        the archive, re-open it, and continue writing.  */
 
-       name_add (optarg);
-       input_files++;
-       break;
+      multi_volume_option = true;
+      break;
 
-      case 'A':
-       set_subcommand_option (CAT_SUBCOMMAND);
-       break;
+    case 'n':
+      seekable_archive = true;
+      break;
+
+#if !MSDOS
+    case 'N':
+      after_date_option = true;
+      /* Fall through.  */
 
-      case 'b':
+    case NEWER_MTIME_OPTION:
+      if (NEWER_OPTION_INITIALIZED (newer_mtime_option))
+       USAGE_ERROR ((0, 0, _("More than one threshold date")));
+
+      if (FILE_SYSTEM_PREFIX_LEN (arg) != 0
+         || ISSLASH (*arg)
+         || *arg == '.')
        {
-         uintmax_t u;
-         if (! (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
-                && u == (blocking_factor = u)
-                && 0 < blocking_factor
-                && u == (record_size = u * BLOCKSIZE) / BLOCKSIZE))
-           USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                         _("Invalid blocking factor")));
+         struct stat st;
+         if (deref_stat (dereference_option, arg, &st) != 0)
+           {
+             stat_error (arg);
+             USAGE_ERROR ((0, 0, _("Date sample file not found")));
+           }
+         newer_mtime_option = get_stat_mtime (&st);
+       }
+      else
+       {
+         if (! get_date (&newer_mtime_option, arg, NULL))
+           {
+             WARN ((0, 0, _("Substituting %s for unknown date format %s"),
+                    tartime (newer_mtime_option, false), quote (arg)));
+             newer_mtime_option.tv_nsec = 0;
+           }
+         else
+           args->textual_date_option = arg;
        }
-       break;
 
-      case 'B':
-       /* Try to reblock input records.  For reading 4.2BSD pipes.  */
+      break;
+#endif /* not MSDOS */
 
-       /* It would surely make sense to exchange -B and -R, but it seems
-          that -B has been used for a long while in Sun tar ans most
-          BSD-derived systems.  This is a consequence of the block/record
-          terminology confusion.  */
+    case 'o':
+      args->o_option = true;
+      break;
 
-       read_full_records_option = true;
-       break;
+    case 'O':
+      to_stdout_option = true;
+      break;
 
-      case 'c':
-       set_subcommand_option (CREATE_SUBCOMMAND);
-       break;
+    case 'p':
+      same_permissions_option = true;
+      break;
 
-      case 'C':
-       name_add ("-C");
-       name_add (optarg);
-       break;
+    case 'P':
+      absolute_names_option = true;
+      break;
 
-      case 'd':
-       set_subcommand_option (DIFF_SUBCOMMAND);
-       break;
+    case 'r':
+      set_subcommand_option (APPEND_SUBCOMMAND);
+      break;
 
-      case 'f':
-       if (archive_names == allocated_archive_names)
-         {
-           allocated_archive_names *= 2;
-           archive_name_array =
-             xrealloc (archive_name_array,
-                       sizeof (const char *) * allocated_archive_names);
-         }
-       archive_name_array[archive_names++] = optarg;
-       break;
+    case 'R':
+      /* Print block numbers for debugging bad tar archives.  */
 
-      case 'F':
-       /* Since -F is only useful with -M, make it implied.  Run this
-          script at the end of each tape.  */
+      /* It would surely make sense to exchange -B and -R, but it seems
+        that -B has been used for a long while in Sun tar ans most
+        BSD-derived systems.  This is a consequence of the block/record
+        terminology confusion.  */
 
-       info_script_option = optarg;
-       multi_volume_option = true;
-       break;
+      block_number_option = true;
+      break;
 
-      case 'g':
-       listed_incremental_option = optarg;
-       after_date_option = true;
-       /* Fall through.  */
+    case 's':
+      /* Names to extr are sorted.  */
 
-      case 'G':
-       /* We are making an incremental dump (FIXME: are we?); save
-          directories at the beginning of the archive, and include in each
-          directory its contents.  */
+      same_order_option = true;
+      break;
 
-       incremental_option = true;
-       break;
+    case 'S':
+      sparse_option = true;
+      break;
 
-      case 'h':
-       /* Follow symbolic links.  */
-       dereference_option = true;
-       break;
+    case 't':
+      set_subcommand_option (LIST_SUBCOMMAND);
+      verbose_option++;
+      break;
 
-      case 'i':
-       /* Ignore zero blocks (eofs).  This can't be the default,
-          because Unix tar writes two blocks of zeros, then pads out
-          the record with garbage.  */
+    case 'T':
+      update_argv (arg, state);
+      /* Indicate we've been given -T option. This is for backward
+        compatibility only, so that `tar cfT archive /dev/null will
+        succeed */
+      files_from_option = true;
+      break;
 
-       ignore_zeros_option = true;
-       break;
+    case 'u':
+      set_subcommand_option (UPDATE_SUBCOMMAND);
+      break;
 
-      case 'I':
-       USAGE_ERROR ((0, 0,
-                     _("Warning: the -I option is not supported;"
-                       " perhaps you meant -j or -T?")));
-       break;
+    case 'U':
+      old_files_option = UNLINK_FIRST_OLD_FILES;
+      break;
 
-      case 'j':
-       set_use_compress_program_option ("bzip2");
-       break;
+    case UTC_OPTION:
+      utc_option = true;
+      break;
 
-      case 'k':
-       /* Don't replace existing files.  */
-       old_files_option = KEEP_OLD_FILES;
-       break;
+    case 'v':
+      verbose_option++;
+      break;
 
-      case 'K':
-       starting_file_option = true;
-       addname (optarg, 0);
-       break;
+    case 'V':
+      volume_label_option = arg;
+      break;
 
-      case 'l':
-       /* When dumping directories, don't dump files/subdirectories
-          that are on other filesystems.  */
+    case 'w':
+      interactive_option = true;
+      break;
 
-       one_file_system_option = true;
-       break;
+    case 'W':
+      verify_option = true;
+      break;
+
+    case 'x':
+      set_subcommand_option (EXTRACT_SUBCOMMAND);
+      break;
 
-      case 'L':
+    case 'X':
+      if (add_exclude_file (add_exclude, excluded, arg,
+                           args->exclude_options | recursion_option, '\n')
+         != 0)
        {
-         uintmax_t u;
-         if (xstrtoumax (optarg, 0, 10, &u, "") != LONGINT_OK)
-           USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                         _("Invalid tape length")));
-         tape_length_option = 1024 * (tarlong) u;
-         multi_volume_option = true;
+         int e = errno;
+         FATAL_ERROR ((0, e, "%s", quotearg_colon (arg)));
        }
-       break;
+      break;
 
-      case 'm':
-       touch_option = true;
-       break;
+    case 'y':
+      USAGE_ERROR ((0, 0,
+                   _("Warning: the -y option is not supported;"
+                     " perhaps you meant -j?")));
+      break;
 
-      case 'M':
-       /* Make multivolume archive: when we can't write any more into
-          the archive, re-open it, and continue writing.  */
+    case 'z':
+      set_use_compress_program_option ("gzip");
+      break;
 
-       multi_volume_option = true;
-       break;
+    case 'Z':
+      set_use_compress_program_option ("compress");
+      break;
 
-#if !MSDOS
-      case 'N':
-       after_date_option = true;
-       /* Fall through.  */
+    case ANCHORED_OPTION:
+      args->exclude_options |= EXCLUDE_ANCHORED;
+      break;
 
-      case NEWER_MTIME_OPTION:
-       if (newer_mtime_option != TYPE_MINIMUM (time_t))
-         USAGE_ERROR ((0, 0, _("More than one threshold date")));
+    case ATIME_PRESERVE_OPTION:
+      atime_preserve_option = true;
+      break;
 
-       if (FILESYSTEM_PREFIX_LEN (optarg) != 0
-           || ISSLASH (*optarg)
-           || *optarg == '.')
-         {
-           struct stat st;
-           if (deref_stat (dereference_option, optarg, &st) != 0)
-             {
-               stat_error (optarg);
-               USAGE_ERROR ((0, 0, _("Date file not found")));
-             }
-           newer_mtime_option = st.st_mtime;
-         }
-       else
-         {
-           newer_mtime_option = get_date (optarg, 0);
-           if (newer_mtime_option == (time_t) -1)
-             WARN ((0, 0, _("Substituting %s for unknown date format %s"),
-                    tartime (newer_mtime_option), quote (optarg)));
-           else
-             textual_date_option = optarg;
-         }
+    case CHECKPOINT_OPTION:
+      checkpoint_option = true;
+      break;
 
-       break;
-#endif /* not MSDOS */
+    case BACKUP_OPTION:
+      backup_option = true;
+      if (arg)
+       args->version_control_string = arg;
+      break;
 
-      case 'o':
-       o_option = true;
-       break;
+    case DELETE_OPTION:
+      set_subcommand_option (DELETE_SUBCOMMAND);
+      break;
+
+    case EXCLUDE_OPTION:
+      add_exclude (excluded, arg, args->exclude_options | recursion_option);
+      break;
+
+    case EXCLUDE_CACHES_OPTION:
+      exclude_caches_option = true;
+      break;
+
+    case FORCE_LOCAL_OPTION:
+      force_local_option = true;
+      break;
+
+    case 'H':
+      set_archive_format (arg);
+      break;
+
+    case INDEX_FILE_OPTION:
+      index_file_name = arg;
+      break;
+
+    case IGNORE_CASE_OPTION:
+      args->exclude_options |= FNM_CASEFOLD;
+      break;
+
+    case IGNORE_COMMAND_ERROR_OPTION:
+      ignore_command_error_option = true;
+      break;
+
+    case IGNORE_FAILED_READ_OPTION:
+      ignore_failed_read_option = true;
+      break;
+
+    case KEEP_NEWER_FILES_OPTION:
+      old_files_option = KEEP_NEWER_FILES;
+      break;
+
+    case GROUP_OPTION:
+      if (! (strlen (arg) < GNAME_FIELD_SIZE
+            && gname_to_gid (arg, &group_option)))
+       {
+         uintmax_t g;
+         if (xstrtoumax (arg, 0, 10, &g, "") == LONGINT_OK
+             && g == (gid_t) g)
+           group_option = g;
+         else
+           FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                         _("%s: Invalid group")));
+       }
+      break;
+
+    case MODE_OPTION:
+      mode_option = mode_compile (arg);
+      if (!mode_option)
+       FATAL_ERROR ((0, 0, _("Invalid mode given on option")));
+      initial_umask = umask (0);
+      umask (initial_umask);
+      break;
+
+    case NO_ANCHORED_OPTION:
+      args->exclude_options &= ~ EXCLUDE_ANCHORED;
+      break;
+
+    case NO_IGNORE_CASE_OPTION:
+      args->exclude_options &= ~ FNM_CASEFOLD;
+      break;
+
+    case NO_IGNORE_COMMAND_ERROR_OPTION:
+      ignore_command_error_option = false;
+      break;
+
+    case NO_OVERWRITE_DIR_OPTION:
+      old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
+      break;
+
+    case NO_WILDCARDS_OPTION:
+      args->exclude_options &= ~ EXCLUDE_WILDCARDS;
+      break;
 
-      case 'O':
-       to_stdout_option = true;
-       break;
+    case NO_WILDCARDS_MATCH_SLASH_OPTION:
+      args->exclude_options |= FNM_FILE_NAME;
+      break;
 
-      case 'p':
-       same_permissions_option = true;
-       break;
+    case NULL_OPTION:
+      filename_terminator = '\0';
+      break;
 
-      case 'P':
-       absolute_names_option = true;
-       break;
+    case NUMERIC_OWNER_OPTION:
+      numeric_owner_option = true;
+      break;
 
-      case 'r':
-       set_subcommand_option (APPEND_SUBCOMMAND);
-       break;
+    case OCCURRENCE_OPTION:
+      if (!arg)
+       occurrence_option = 1;
+      else
+       {
+         uintmax_t u;
+         if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK)
+           occurrence_option = u;
+         else
+           FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                         _("Invalid number")));
+       }
+      break;
 
-      case 'R':
-       /* Print block numbers for debugging bad tar archives.  */
+    case OVERWRITE_OPTION:
+      old_files_option = OVERWRITE_OLD_FILES;
+      break;
 
-       /* It would surely make sense to exchange -B and -R, but it seems
-          that -B has been used for a long while in Sun tar ans most
-          BSD-derived systems.  This is a consequence of the block/record
-          terminology confusion.  */
+    case OWNER_OPTION:
+      if (! (strlen (arg) < UNAME_FIELD_SIZE
+            && uname_to_uid (arg, &owner_option)))
+       {
+         uintmax_t u;
+         if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+             && u == (uid_t) u)
+           owner_option = u;
+         else
+           FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                         _("Invalid owner")));
+       }
+      break;
 
-       block_number_option = true;
-       break;
+    case PAX_OPTION:
+      args->pax_option++;
+      xheader_set_option (arg);
+      break;
 
-      case 's':
-       /* Names to extr are sorted.  */
+    case POSIX_OPTION:
+      set_archive_format ("posix");
+      break;
 
-       same_order_option = true;
-       break;
+    case PRESERVE_OPTION:
+      same_permissions_option = true;
+      same_order_option = true;
+      break;
 
-      case 'S':
-       sparse_option = true;
-       break;
+    case RECORD_SIZE_OPTION:
+      {
+       uintmax_t u;
+       if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+              && u == (size_t) u))
+         USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                       _("Invalid record size")));
+       record_size = u;
+       if (record_size % BLOCKSIZE != 0)
+         USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
+                       BLOCKSIZE));
+       blocking_factor = record_size / BLOCKSIZE;
+      }
+      break;
 
-      case 't':
-       set_subcommand_option (LIST_SUBCOMMAND);
-       verbose_option++;
-       break;
+    case RECURSIVE_UNLINK_OPTION:
+      recursive_unlink_option = true;
+      break;
 
-      case 'T':
-       files_from_option = optarg;
-       break;
+    case REMOVE_FILES_OPTION:
+      remove_files_option = true;
+      break;
 
-      case 'u':
-       set_subcommand_option (UPDATE_SUBCOMMAND);
-       break;
+    case RMT_COMMAND_OPTION:
+      rmt_command = arg;
+      break;
 
-      case 'U':
-       old_files_option = UNLINK_FIRST_OLD_FILES;
-       break;
+    case RSH_COMMAND_OPTION:
+      rsh_command_option = arg;
+      break;
 
-      case 'v':
-       verbose_option++;
-       break;
+    case SHOW_DEFAULTS_OPTION:
+      show_default_settings (stdout);
+      exit(0);
 
-      case 'V':
-       volume_label_option = optarg;
-       break;
+    case STRIP_COMPONENTS_OPTION:
+      {
+       uintmax_t u;
+       if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
+              && u == (size_t) u))
+         USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                       _("Invalid number of elements")));
+       strip_name_components = u;
+      }
+      break;
 
-      case 'w':
-       interactive_option = true;
-       break;
+    case SHOW_OMITTED_DIRS_OPTION:
+      show_omitted_dirs_option = true;
+      break;
 
-      case 'W':
-       verify_option = true;
-       break;
+    case SUFFIX_OPTION:
+      backup_option = true;
+      args->backup_suffix_string = arg;
+      break;
 
-      case 'x':
-       set_subcommand_option (EXTRACT_SUBCOMMAND);
-       break;
+    case TO_COMMAND_OPTION:
+      if (to_command_option)
+        USAGE_ERROR ((0, 0, _("Only one --to-command option allowed")));
+      to_command_option = arg;
+      break;
 
-      case 'X':
-       if (add_exclude_file (add_exclude, excluded, optarg,
-                             exclude_options | recursion_option, '\n')
-           != 0)
-         {
-           int e = errno;
-           FATAL_ERROR ((0, e, "%s", quotearg_colon (optarg)));
-         }
-       break;
+    case TOTALS_OPTION:
+      totals_option = true;
+      break;
 
-      case 'y':
-       USAGE_ERROR ((0, 0,
-                     _("Warning: the -y option is not supported;"
-                       " perhaps you meant -j?")));
-       break;
+    case USE_COMPRESS_PROGRAM_OPTION:
+      set_use_compress_program_option (arg);
+      break;
 
-      case 'z':
-       set_use_compress_program_option ("gzip");
-       break;
+    case VOLNO_FILE_OPTION:
+      volno_file_option = arg;
+      break;
 
-      case 'Z':
-       set_use_compress_program_option ("compress");
-       break;
+    case WILDCARDS_OPTION:
+      args->exclude_options |= EXCLUDE_WILDCARDS;
+      break;
 
-      case ANCHORED_OPTION:
-       exclude_options |= EXCLUDE_ANCHORED;
-       break;
+    case WILDCARDS_MATCH_SLASH_OPTION:
+      args->exclude_options &= ~ FNM_FILE_NAME;
+      break;
 
-      case ATIME_PRESERVE_OPTION:
-       atime_preserve_option = true;
-       break;
+    case CHECK_LINKS_OPTION:
+      check_links_option = 1;
+      break;
 
-      case CHECKPOINT_OPTION:
-       checkpoint_option = true;
-       break;
+    case NO_RECURSION_OPTION:
+      recursion_option = 0;
+      break;
 
-      case BACKUP_OPTION:
-       backup_option = true;
-       if (optarg)
-         version_control_string = optarg;
-       break;
+    case NO_SAME_OWNER_OPTION:
+      same_owner_option = -1;
+      break;
 
-      case DELETE_OPTION:
-       set_subcommand_option (DELETE_SUBCOMMAND);
-       break;
+    case NO_SAME_PERMISSIONS_OPTION:
+      same_permissions_option = -1;
+      break;
 
-      case EXCLUDE_OPTION:
-       add_exclude (excluded, optarg, exclude_options | recursion_option);
-       break;
+    case RECURSION_OPTION:
+      recursion_option = FNM_LEADING_DIR;
+      break;
 
-      case FORCE_LOCAL_OPTION:
-       force_local_option = true;
-       break;
+    case SAME_OWNER_OPTION:
+      same_owner_option = 1;
+      break;
 
-      case FORMAT_OPTION:
-       set_archive_format (optarg);
-       break;
-       
-      case INDEX_FILE_OPTION:
-       index_file_name = optarg;
-       break;
+    case UNQUOTE_OPTION:
+      unquote_option = true;
+      break;
 
-      case IGNORE_CASE_OPTION:
-       exclude_options |= FNM_CASEFOLD;
-       break;
+    case NO_UNQUOTE_OPTION:
+      unquote_option = false;
+      break;
 
-      case IGNORE_FAILED_READ_OPTION:
-       ignore_failed_read_option = true;
-       break;
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
 
-      case GROUP_OPTION:
-       if (! (strlen (optarg) < GNAME_FIELD_SIZE
-              && gname_to_gid (optarg, &group_option)))
-         {
-           uintmax_t g;
-           if (xstrtoumax (optarg, 0, 10, &g, "") == LONGINT_OK
-               && g == (gid_t) g)
-             group_option = g;
-           else
-             FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                           _("%s: Invalid group")));
-         }
-       break;
+#ifdef DEVICE_PREFIX
+      {
+       int device = key - '0';
+       int density;
+       static char buf[sizeof DEVICE_PREFIX + 10];
+       char *cursor;
 
-      case MODE_OPTION:
-       mode_option
-         = mode_compile (optarg,
-                         MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
-       if (mode_option == MODE_INVALID)
-         FATAL_ERROR ((0, 0, _("Invalid mode given on option")));
-       if (mode_option == MODE_MEMORY_EXHAUSTED)
-         xalloc_die ();
-       break;
+       if (arg[1])
+         argp_error (state, _("Malformed density argument: %s"), quote (arg));
 
-      case NO_ANCHORED_OPTION:
-       exclude_options &= ~ EXCLUDE_ANCHORED;
-       break;
+       strcpy (buf, DEVICE_PREFIX);
+       cursor = buf + strlen (buf);
 
-      case NO_IGNORE_CASE_OPTION:
-       exclude_options &= ~ FNM_CASEFOLD;
-       break;
+#ifdef DENSITY_LETTER
 
-      case NO_OVERWRITE_DIR_OPTION:
-       old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
-       break;
+       sprintf (cursor, "%d%c", device, arg[0]);
 
-      case NO_WILDCARDS_OPTION:
-       exclude_options &= ~ EXCLUDE_WILDCARDS;
-       break;
+#else /* not DENSITY_LETTER */
 
-      case NO_WILDCARDS_MATCH_SLASH_OPTION:
-       exclude_options |= FNM_FILE_NAME;
-       break;
+       switch (arg[0])
+         {
+         case 'l':
+#ifdef LOW_NUM
+           device += LOW_NUM;
+#endif
+           break;
 
-      case NULL_OPTION:
-       filename_terminator = '\0';
-       break;
+         case 'm':
+#ifdef MID_NUM
+           device += MID_NUM;
+#else
+           device += 8;
+#endif
+           break;
 
-      case NUMERIC_OWNER_OPTION:
-       numeric_owner_option = true;
-       break;
+         case 'h':
+#ifdef HGH_NUM
+           device += HGH_NUM;
+#else
+           device += 16;
+#endif
+           break;
 
-      case OCCURRENCE_OPTION:
-       if (!optarg)
-         occurrence_option = 1;
-       else
-         {
-           uintmax_t u;
-           if (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK)
-             occurrence_option = u;
-           else
-             FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                           _("Invalid number")));
+         default:
+           argp_error (state, _("Unknown density: `%c'"), arg[0]);
          }
-       break;
-       
-      case OVERWRITE_OPTION:
-       old_files_option = OVERWRITE_OLD_FILES;
-       break;
+       sprintf (cursor, "%d", device);
+
+#endif /* not DENSITY_LETTER */
 
-      case OWNER_OPTION:
-       if (! (strlen (optarg) < UNAME_FIELD_SIZE
-              && uname_to_uid (optarg, &owner_option)))
+       if (archive_names == allocated_archive_names)
          {
-           uintmax_t u;
-           if (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
-               && u == (uid_t) u)
-             owner_option = u;
-           else
-             FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                           _("Invalid owner")));
+           allocated_archive_names *= 2;
+           archive_name_array =
+             xrealloc (archive_name_array,
+                       sizeof (const char *) * allocated_archive_names);
          }
-       break;
+       archive_name_array[archive_names++] = xstrdup (buf);
+      }
+      break;
 
-      case PAX_OPTION:
-       xheader_set_option (optarg);
-       break;
-                           
-      case POSIX_OPTION:
-       set_archive_format ("posix");
-       break;
+#else /* not DEVICE_PREFIX */
 
-      case PRESERVE_OPTION:
-       same_permissions_option = true;
-       same_order_option = true;
-       break;
+      argp_error (state,
+                 _("Options `-[0-7][lmh]' not supported by *this* tar"));
 
-      case RECORD_SIZE_OPTION:
-       {
-         uintmax_t u;
-         if (! (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
-                && u == (size_t) u))
-           USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                         _("Invalid record size")));
-         record_size = u;
-         if (record_size % BLOCKSIZE != 0)
-           USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
-                         BLOCKSIZE));
-         blocking_factor = record_size / BLOCKSIZE;
-       }
-       break;
+#endif /* not DEVICE_PREFIX */
 
-      case RECURSIVE_UNLINK_OPTION:
-       recursive_unlink_option = true;
-       break;
+    case '?':
+      state->flags |= ARGP_NO_EXIT;
+      argp_state_help (state, state->out_stream,
+                      ARGP_HELP_STD_HELP & ~ARGP_HELP_BUG_ADDR);
+      fprintf (state->out_stream, _("\n*This* tar defaults to:\n"));
+      show_default_settings (state->out_stream);
+      fprintf (state->out_stream, "\n");
+      fprintf (state->out_stream, _("Report bugs to %s.\n"),
+              argp_program_bug_address);
+      exit (0);
+
+    case USAGE_OPTION:
+      argp_state_help (state, state->out_stream,
+                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
+      break;
 
-      case REMOVE_FILES_OPTION:
-       remove_files_option = true;
-       break;
+    case VERSION_OPTION:
+      fprintf (state->out_stream, "%s\n", argp_program_version);
+      exit (0);
 
-      case RSH_COMMAND_OPTION:
-       rsh_command_option = optarg;
-       break;
+    case LICENSE_OPTION:
+      license ();
+      break;
 
-      case STRIP_PATH_OPTION:
-       {
-         uintmax_t u;
-         if (! (xstrtoumax (optarg, 0, 10, &u, "") == LONGINT_OK
-                && u == (size_t) u))
-           USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (optarg),
-                         _("Invalid number of elements")));
-         strip_path_elements = u;
-       }
-       break;
-       
-      case SUFFIX_OPTION:
-       backup_option = true;
-       backup_suffix_string = optarg;
-       break;
+    case HANG_OPTION:
+      _argp_hang = atoi (arg ? arg : "3600");
+      while (_argp_hang-- > 0)
+       sleep (1);
+      break;
 
-      case TOTALS_OPTION:
-       totals_option = true;
-       break;
-       
-      case USE_COMPRESS_PROGRAM_OPTION:
-       set_use_compress_program_option (optarg);
-       break;
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
 
-      case VOLNO_FILE_OPTION:
-       volno_file_option = optarg;
-       break;
+static struct argp argp = {
+  options,
+  parse_opt,
+  N_("[FILE]..."),
+  doc,
+  NULL,
+  NULL,
+  NULL
+};
 
-      case WILDCARDS_OPTION:
-       exclude_options |= EXCLUDE_WILDCARDS;
-       break;
+void
+usage (int status)
+{
+  argp_help (&argp, stderr, ARGP_HELP_SEE, (char*) program_name);
+  exit (status);
+}
 
-      case WILDCARDS_MATCH_SLASH_OPTION:
-       exclude_options &= ~ FNM_FILE_NAME;
-       break;
+/* Parse the options for tar.  */
 
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-      case '6':
-      case '7':
+static struct argp_option *
+find_argp_option (struct argp_option *options, int letter)
+{
+  for (;
+       !(options->name == NULL
+        && options->key == 0
+        && options->arg == 0
+        && options->flags == 0
+        && options->doc == NULL); options++)
+    if (options->key == letter)
+      return options;
+  return NULL;
+}
 
-#ifdef DEVICE_PREFIX
-       {
-         int device = optchar - '0';
-         int density;
-         static char buf[sizeof DEVICE_PREFIX + 10];
-         char *cursor;
+static void
+decode_options (int argc, char **argv)
+{
+  int index;
+  struct tar_args args;
 
-         density = getopt_long (argc, argv, "lmh", 0, 0);
-         strcpy (buf, DEVICE_PREFIX);
-         cursor = buf + strlen (buf);
+  /* Set some default option values.  */
+  args.textual_date_option = NULL;
+  args.exclude_options = EXCLUDE_WILDCARDS;
+  args.o_option = 0;
+  args.pax_option = 0;
+  args.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
+  args.version_control_string = 0;
+  args.input_files = 0;
 
-#ifdef DENSITY_LETTER
+  subcommand_option = UNKNOWN_SUBCOMMAND;
+  archive_format = DEFAULT_FORMAT;
+  blocking_factor = DEFAULT_BLOCKING;
+  record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+  excluded = new_exclude ();
+  newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+  newer_mtime_option.tv_nsec = -1;
+  recursion_option = FNM_LEADING_DIR;
+  unquote_option = true;
 
-         sprintf (cursor, "%d%c", device, density);
+  owner_option = -1;
+  group_option = -1;
 
-#else /* not DENSITY_LETTER */
+  /* Convert old-style tar call by exploding option element and rearranging
+     options accordingly.  */
 
-         switch (density)
-           {
-           case 'l':
-#ifdef LOW_NUM
-             device += LOW_NUM;
-#endif
-             break;
+  if (argc > 1 && argv[1][0] != '-')
+    {
+      int new_argc;            /* argc value for rearranged arguments */
+      char **new_argv;         /* argv value for rearranged arguments */
+      char *const *in;         /* cursor into original argv */
+      char **out;              /* cursor into rearranged argv */
+      const char *letter;      /* cursor into old option letters */
+      char buffer[3];          /* constructed option buffer */
 
-           case 'm':
-#ifdef MID_NUM
-             device += MID_NUM;
-#else
-             device += 8;
-#endif
-             break;
+      /* Initialize a constructed option.  */
 
-           case 'h':
-#ifdef HGH_NUM
-             device += HGH_NUM;
-#else
-             device += 16;
-#endif
-             break;
+      buffer[0] = '-';
+      buffer[2] = '\0';
 
-           default:
-             usage (TAREXIT_FAILURE);
-           }
-         sprintf (cursor, "%d", device);
+      /* Allocate a new argument array, and copy program name in it.  */
 
-#endif /* not DENSITY_LETTER */
+      new_argc = argc - 1 + strlen (argv[1]);
+      new_argv = xmalloc ((new_argc + 1) * sizeof (char *));
+      in = argv;
+      out = new_argv;
+      *out++ = *in++;
+
+      /* Copy each old letter option as a separate option, and have the
+        corresponding argument moved next to it.  */
+
+      for (letter = *in++; *letter; letter++)
+       {
+         struct argp_option *opt;
 
-         if (archive_names == allocated_archive_names)
+         buffer[1] = *letter;
+         *out++ = xstrdup (buffer);
+         opt = find_argp_option (options, *letter);
+         if (opt && opt->arg)
            {
-             allocated_archive_names *= 2;
-             archive_name_array =
-               xrealloc (archive_name_array,
-                         sizeof (const char *) * allocated_archive_names);
+             if (in < argv + argc)
+               *out++ = *in++;
+             else
+               USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."),
+                             *letter));
            }
-         archive_name_array[archive_names++] = strdup (buf);
        }
-       break;
 
-#else /* not DEVICE_PREFIX */
+      /* Copy all remaining options.  */
 
-       USAGE_ERROR ((0, 0,
-                     _("Options `-[0-7][lmh]' not supported by *this* tar")));
+      while (in < argv + argc)
+       *out++ = *in++;
+      *out = 0;
+
+      /* Replace the old option list by the new one.  */
+
+      argc = new_argc;
+      argv = new_argv;
+    }
+
+  /* Parse all options and non-options as they appear.  */
+
+  prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
+
+  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_HELP,
+                 &index, &args))
+    exit (1);
 
-#endif /* not DEVICE_PREFIX */
-      }
 
   /* Special handling for 'o' option:
 
@@ -1236,7 +1620,7 @@ decode_options (int argc, char **argv)
      The old GNU tar semantics is retained when used with --create
      option, otherwise UNIX98 semantics is assumed */
 
-  if (o_option)
+  if (args.o_option)
     {
       if (subcommand_option == CREATE_SUBCOMMAND)
        {
@@ -1246,57 +1630,45 @@ decode_options (int argc, char **argv)
       else
        {
          /* UNIX98 compatibility */
-         same_owner_option = 1;
+         same_owner_option = -1;
        }
     }
 
   /* Handle operands after any "--" argument.  */
-  for (; optind < argc; optind++)
-    {
-      name_add (argv[optind]);
-      input_files++;
-    }
-
-  /* Process trivial options.  */
-
-  if (show_version)
+  for (; index < argc; index++)
     {
-      printf ("tar (%s) %s\n%s\n", PACKAGE_NAME, PACKAGE_VERSION,
-             "Copyright (C) 2003 Free Software Foundation, Inc.");
-      puts (_("\
-This program comes with NO WARRANTY, to the extent permitted by law.\n\
-You may redistribute it under the terms of the GNU General Public License;\n\
-see the file named COPYING for details."));
-
-      puts (_("Written by John Gilmore and Jay Fenlason."));
-
-      exit (TAREXIT_SUCCESS);
+      name_add (argv[index]);
+      args.input_files++;
     }
 
-  if (show_help)
-    usage (TAREXIT_SUCCESS);
-
   /* Derive option values and check option consistency.  */
 
   if (archive_format == DEFAULT_FORMAT)
-    archive_format = DEFAULT_ARCHIVE_FORMAT;
+    {
+      if (args.pax_option)
+       archive_format = POSIX_FORMAT;
+      else
+       archive_format = DEFAULT_ARCHIVE_FORMAT;
+    }
 
   if (volume_label_option && subcommand_option == CREATE_SUBCOMMAND)
     assert_format (FORMAT_MASK (OLDGNU_FORMAT)
                   | FORMAT_MASK (GNU_FORMAT));
 
-  if (incremental_option
-      || multi_volume_option
-      || sparse_option)
+
+  if (incremental_option || multi_volume_option)
+    assert_format (FORMAT_MASK (OLDGNU_FORMAT) | FORMAT_MASK (GNU_FORMAT));
+
+  if (sparse_option)
     assert_format (FORMAT_MASK (OLDGNU_FORMAT)
                   | FORMAT_MASK (GNU_FORMAT)
                   | FORMAT_MASK (POSIX_FORMAT));
-  
+
   if (occurrence_option)
     {
-      if (!input_files && !files_from_option)
+      if (!args.input_files)
        USAGE_ERROR ((0, 0,
-                     _("--occurrence is meaningless without file list")));
+                     _("--occurrence is meaningless without file list")));
       if (subcommand_option != DELETE_SUBCOMMAND
          && subcommand_option != DIFF_SUBCOMMAND
          && subcommand_option != EXTRACT_SUBCOMMAND
@@ -1305,6 +1677,18 @@ see the file named COPYING for details."));
                          _("--occurrence cannot be used in the requested operation mode")));
     }
 
+  if (seekable_archive && subcommand_option == DELETE_SUBCOMMAND)
+    {
+      /* The current code in delete.c is based on the assumption that
+        skip_member() reads all data from the archive. So, we should
+        make sure it won't use seeks. On the other hand, the same code
+        depends on the ability to backspace a record in the archive,
+        so setting seekable_archive to false is technically incorrect.
+         However, it is tested only in skip_member(), so it's not a
+        problem. */
+      seekable_archive = false;
+    }
+
   if (archive_names == 0)
     {
       /* If no archive file name given, try TAPE from the environment, or
@@ -1323,7 +1707,7 @@ see the file named COPYING for details."));
                  _("Multiple archive files require `-M' option")));
 
   if (listed_incremental_option
-      && newer_mtime_option != TYPE_MINIMUM (time_t))
+      && NEWER_OPTION_INITIALIZED (newer_mtime_option))
     USAGE_ERROR ((0, 0,
                  _("Cannot combine --listed-incremental with --newer")));
 
@@ -1362,18 +1746,32 @@ see the file named COPYING for details."));
       if (subcommand_option == UPDATE_SUBCOMMAND)
        USAGE_ERROR ((0, 0, _("Cannot update compressed archives")));
     }
-  
+
+  /* It is no harm to use --pax-option on non-pax archives in archive
+     reading mode. It may even be useful, since it allows to override
+     file attributes from tar headers. Therefore I allow such usage.
+     --gray */
+  if (args.pax_option
+      && archive_format != POSIX_FORMAT
+      && (subcommand_option != EXTRACT_SUBCOMMAND
+         || subcommand_option != DIFF_SUBCOMMAND
+         || subcommand_option != LIST_SUBCOMMAND))
+    USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives")));
+
   /* If ready to unlink hierarchies, so we are for simpler files.  */
   if (recursive_unlink_option)
     old_files_option = UNLINK_FIRST_OLD_FILES;
 
+  if (utc_option)
+    verbose_option = 2;
+
   /* Forbid using -c with no input files whatsoever.  Check that `-f -',
      explicit or implied, is used correctly.  */
 
   switch (subcommand_option)
     {
     case CREATE_SUBCOMMAND:
-      if (input_files == 0 && !files_from_option)
+      if (args.input_files == 0 && !files_from_option)
        USAGE_ERROR ((0, 0,
                      _("Cowardly refusing to create an empty archive")));
       break;
@@ -1406,18 +1804,24 @@ see the file named COPYING for details."));
 
   /* Prepare for generating backup names.  */
 
-  if (backup_suffix_string)
-    simple_backup_suffix = xstrdup (backup_suffix_string);
+  if (args.backup_suffix_string)
+    simple_backup_suffix = xstrdup (args.backup_suffix_string);
 
   if (backup_option)
-    backup_type = xget_version ("--backup", version_control_string);
+    {
+      backup_type = xget_version ("--backup", args.version_control_string);
+      /* No backup is needed either if explicitely disabled or if
+        the extracted files are not being written to disk. */
+      if (backup_type == no_backups || EXTRACT_OVER_PIPE)
+       backup_option = false;
+    }
 
-  if (verbose_option && textual_date_option)
+  if (verbose_option && args.textual_date_option)
     {
-      char const *treated_as = tartime (newer_mtime_option);
-      if (strcmp (textual_date_option, treated_as) != 0)
+      char const *treated_as = tartime (newer_mtime_option, true);
+      if (strcmp (args.textual_date_option, treated_as) != 0)
        WARN ((0, 0, _("Treating date `%s' as %s"),
-              textual_date_option, treated_as));
+              args.textual_date_option, treated_as));
     }
 }
 
@@ -1428,11 +1832,9 @@ see the file named COPYING for details."));
 int
 main (int argc, char **argv)
 {
-#if HAVE_CLOCK_GETTIME
-  if (clock_gettime (CLOCK_REALTIME, &start_timespec) != 0)
-#endif
-    start_time = time (0);
+  set_start_time ();
   program_name = argv[0];
+
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
@@ -1448,6 +1850,8 @@ main (int argc, char **argv)
     xmalloc (sizeof (const char *) * allocated_archive_names);
   archive_names = 0;
 
+  obstack_init (&argv_stk);
+
 #ifdef SIGCHLD
   /* System V fork+wait does not work if SIGCHLD is ignored.  */
   signal (SIGCHLD, SIG_DFL);
@@ -1458,7 +1862,7 @@ main (int argc, char **argv)
   /* Decode options.  */
 
   decode_options (argc, argv);
-  name_init (argc, argv);
+  name_init ();
 
   /* Main command execution.  */
 
@@ -1483,8 +1887,6 @@ main (int argc, char **argv)
 
     case CREATE_SUBCOMMAND:
       create_archive ();
-      name_close ();
-
       if (totals_option)
        print_total_written ();
       break;
@@ -1510,7 +1912,7 @@ main (int argc, char **argv)
     }
 
   if (check_links_option)
-      check_links ();
+    check_links ();
 
   if (volno_file_option)
     closeout_volume_number ();
@@ -1534,7 +1936,7 @@ tar_stat_init (struct tar_stat_info *st)
 {
   memset (st, 0, sizeof (*st));
 }
-     
+
 void
 tar_stat_destroy (struct tar_stat_info *st)
 {
@@ -1546,4 +1948,3 @@ tar_stat_destroy (struct tar_stat_info *st)
   free (st->sparse_map);
   memset (st, 0, sizeof (*st));
 }
-
This page took 0.077679 seconds and 4 git commands to generate.